Android Armor
Chapters

This Chapter will discuss how a code can be manipulated to achieve desired results.

How to change a function result

Imagine a function that returns if player is alive:

class Player {
  int health;
  // other fields...

  bool IsAlive();
  // other functions...
};

bool Player::IsAlive() {
  return this->health > 0;
}
Player::IsAlive():
    ldr     r1, [r0]
    mov     r0, #0
    cmp     r1, #0
    movgt   r0, #1
    bx      lr

Now, suppose our goal is to fake this function result, how would we do that? Normally, we would just make it return true without a check, and this is exactly what we are going to do!
To patch it, simply get the address of Player::IsAlive in memory and patch it to return true. It can be patched in multiple ways, the first is with assembly, by modifying the first few bytes of the function, and the second is with frida:

Player::IsAlive():
    mov     r0, #1
    bx      lr
    cmp     r1 #0
    movgt   r0 #1
    bx      lr
ASM PATCH
Interceptor.replace(FunctionAddress, new NativeCallback(() => true, "bool", []));
FRIDA

The first code, is the asm after modification, which in fact only first 2 instructions will run: mov r0 #1 and bx lr
It basically changes the function to:

bool Player::IsAlive() {
  return true;
  return this->health > 0;
}

And the second code block, the one done with FRIDA Interceptor API, replaces the whole function by hooking it and not calling the original (trampoline) function.

What is FRIDA?

Frida is an essential toolkit for identifying and exploiting vulnerabilities on mobile applications. Read more about it here.

How does hooking work?

You might be wondering, how can we replace a function? How can we attach to it? How can we only modify arguments or return value of all the calls and references for this function? and... more!
All this is done through patching asm in memory, or setting up jumps ahead of time with a jump table.
You simply place a jump instruction that jumps to your implementation, where you do the magic needed to accomplish what you need! In our case above, we only needed to make a function that returns true.

How can we call original function after hooking?

This can be done by setting up a trampoline, but first let's understand how hooking works with FRIDA:
FRIDA sets up the target function and inserts instructions that makes it jump to frida core function, which would use a table to identify the function's hook that it should call, then calls your js (or cpp) implementation. It can also attach to a function, which would just allow you to modify the arguments or result after or before calling the original implementation.
But how can it call the original implementation? How can we do it?
When you hook a function, you modify it's asm, making a hook that is triggered by any caller. Instead of doing that, you should make a trampoline first, which is basically an address that when you call, the original function will be called.

size_t computedSize = ComputeHookSize(FunctionAddress);
    
uint8_t* functionHeader = ReadBytes(FunctionAddress, computedSize);

HookFunction(FunctionAddress);

uint8_t* trampoline = allocate(4096, "rwx");

WriteBytes(trampoline, functionHeader, computedSize);

WriteJumpInstruction(trampoline, functionHeader + computedSize);

delete[] functionHeader;

and when you want to call the function, call the address of trampoline. what this does is basically, before we hook a function, we copy the bytes we want to modify to a new address that we allocate with executable permissions, then put a jump instruction that jumps to the function data that had no modifications (after the hook bytes).
Usually a hook needs 8 to 12 bytes.
Frida does this automatically, and when you call the hooked function, frida core will check if it was called from an internal (frida) function, which would mean that who hooked it is who called it, and call the trampoline instead. What a wonderful tool :D

Chapter Contents

Go to next Chapter: