iOS Lightweight Hooking Library

Said Al Mujaini
4 min readJan 19, 2022

--

I would like to share with you a simple project I’ve been working on recently — iOS Lightweight Hooking Library.

Table of Contents:

How MSHookMemory works ?

What is new in SMHookMemory ?

Dynamic Binary Instrumentation — trampoline

How MSHookMemory works ?

In general MSHookMemory function used to write memory bytes at certain memory address, let’s see how this function is implemented.

void MSHookMemory(void *target, const void *data, size_t size);

The function takes 3 parameters:

1) address of memory to be changed at runtime.

2) address of memory data (bytes) to be written.

3) size of the memory data.

I made a simple app called MyApp to help us demonstrate, let’s see below example.

We can see that this function named “numberOfUsers” returns one value which is stored in w0, hence if we can change the value stored in register w0 we can control the return value of the function.

At address 0x10005be8 a value is being loaded from the stack to the register w0, let hook this instruction and replace it with something else.

mov w0, 0x1- is an arm64 instruction to store the number 1 to the register w0

https://armconverter.com — converts arm instructions to hex

uint64_t slide = (uint64_t)_dyld_get_image_vmaddr_slide(0);
uint64_t real_addr = slide + 0x10005be8;
uint8_t patch[] = {0x20, 0x00, 0x80, 0x52}; //mov w0, 0x1MSHookMemory((void*)address, patch, sizeof(patch));

With that, let’s test the app.

Perfect !! 🎉🎉🎉

What is new in SMHookMemory ?

The main goal of SMHookMemory is only to make hooking and patching memory address clear and easy to use, SMHookMemory uses MSHookMemory to write to memory and apply patches. Then What is new? 😅

bool SMHookMemory(char *imageName, uint64_t addr, const char* inst);

The function takes 3 parameters:

1) binary image name

2) address of memory to be changed — directly from Ghidra/IDA/Hopper…

3) ARM instruction

Let’s see how we can implement SMHookMemory for the same example.

SMHookMemory("MyApp", 0x10005be8, "mov w0, 0x1");

That’s it 😁.

— The function iterates through all loaded dylibs and gets the slide for the passed image name, then add it to the passed address.

— The arm instruction is assembled using keystone assembler and then the generated bytes are written to the specified address at runtime.

Dynamic Binary Instrumentation — trampoline

What is trampoline and how it works?

Trampoline is when you jump to somewhere else and then return back to the same position, Mostly this can be useful to:

  • Trace functions.
  • Detect how many times certain memory address is accessed.
  • Access register values at certain memory address.

I made a simple trampoline function that:

  • Branch to a function which saves all register values.
  • Then branch and link to the handle function specified by the user.
  • After that, branch to a function which restore all register values and execute the original overwritten instruction.
  • Finally, branch back to where it was before.
void SMInstrument(char *imageName, uint64_t addr, void* handle);

The function takes 3 parameters:

1) binary image name

2) address of memory — directly from Ghidra/IDA/Hopper…

3) address of handle function

Let’s implement the function for the same example above 😈

We will be instrumeting at address 0x100005bf0 which is the last instruction in the function.

void handle(void){      NSLog(@"number of Users: %llu\n", ctx.general.regs.x0);      // change the original value      *(int*)&ctx.general.regs.x0 = 41414141;      NSLog(@"new number of Users: %llu\n", ctx.general.regs.x0);}%ctor{       SMInstrument("MyApp", 0x100005bf0 , &handle);}

Let’s test the app now.

Console output

Hopefully you enjoyed reading this post and I really hope you benefited from this. You can find the library on GitHub.

Ideas, suggestions or feedbacks are welcome — contact me on my twitter https://twitter.com/Saeed97271

Thanks for reading 😁

--

--