AndroidNativeEmu支持某个函数符号进行hook,代码实例如下:
他是怎么实现的呢?我们从其源代码来分析。
考虑如下问题:
如何实现的函数符号的hook?
初步的猜想是将符号表的对应地址值填为指定函数的地址,后续解析符号的时候,就将该符号的地址填充过去。但是这样存在问题,因为我们的函数是python函数,没有地址,所以无法直接跳转到我们的python函数。
根据问题1,是如何将hook的地址和python函数关联的?
unicorn本身的hook是只支持对某个地址区域进行hook的,只要执行到这个区域的代码,就会调用对应的python函数。但是这里是符号hook,而不是地址区域hook。
首先从emulator.hooker.write_function(sprintf)
开始分析,进去看看他干了什么。
先看看emulator内部的hooker对象是啥,进入到emulator的构造函数中:
可以看到他是Hooker的实例,再进入Hooker类,其构造函数部分的代码如下:
到这里,hooker对象就创建完成了。还是比较简单,就是抽象了一个专门的地址区域用于hook,同时在unicorn中给这段地址区域设置了UC_HOOK_CODE。
接下来是hook.write_function
:
简单来说,就是创建了一段特殊的代码区域,返回了这个区域的地址。这个代码区域有特定的id标识(为了区分不同的函数hook)
,且这段内存区域在unicorn中被设置了回调函数(hooker的构造函数中)
。
这个函数实现非常简单:
就是将write_function
返回的地址写入到符号表中,后续其他依赖这个函数的库会解析为对应的地址。也就是说,后续执行这个符号对应的函数的时候,就会跳转到write_function
中生成的代码区域。同时要注意,这个区域是被hook了的。所以,我们要去看看Hooker类中的_hook
方法是如何实现的,看看他做了什么处理
回答之前我们的提问:
如何实现的符号hook?
对于符号的hook确实是依赖于对符号表的hook实现的,但是其地址不能是python函数的地址。框架的作者专门为每个被hook的函数开辟了一块内存空间,放入一些特殊的汇编代码和一个函数的id,通过这个id能找到对应的python函数。同时这个内存区域是被unicorn的原生hook hook了的,会调用框架内部的处理函数。在该处理函数中,会从这个内存区域中去找到函数id,从而得到该符号对应的python函数进行调用。
通过前面的分析,我们已经知道了如何将符号和我们对应的python函数关联上。但是还存在一个问题,就是函数参数的问题,如何将函数参数传递给对应的python参数?这就要用到框架提供的@native_method
装饰器
装饰器本质上是一个可调用对象,它接收一个函数作为参数,并返回一个新函数或可调用对象。装饰器通常用于横切关注点(cross-cutting concerns),如日志记录、权限检查、缓存等。
实例:
在这个示例中,@my_decorator
应用于 say_hello
函数,等价于 say_hello = my_decorator(say_hello)
。当你调用 say_hello("Alice")
时,实际执行的是 wrapper
函数。
源代码如下:
所以,在我们的代码中:
调用sprintf之前,实际上调用的是native_method方法,该方法对sprintf进行了装饰。
而其native_method_wrapper装饰的实现就是在获取函数参数的个数,然后从寄存器中获取对应的参数,传递给python。
这个框架中的@native_method装饰器的作用是解析函数参数,从寄存器或者内存中读取参数再传递给python,但是他的实现是比较简陋的。本质上是利用inspect获取python的函数原型再决定读取几个参数。
比如,如果我们要完整的实现sprintf的hook就不太行,因为sprintf的参数是不定的,函数原型中不能知道具体有几个参数。只能解析spriintf的format来确定参数数量,再读取参数。当然实现起来有点麻烦,后续尝试写一下。
@native_method
def
sprintf(mu, buffer_addr, format_addr, arg1, arg2):
print
(
"sprintf(%x,%x,%x,%x)"
%
(buffer_addr, format_addr, arg1, arg2))
emulator.modules.add_symbol_hook(
"sprintf"
, emulator.hooker.write_function(sprintf)
+
1
)
@native_method
def
sprintf(mu, buffer_addr, format_addr, arg1, arg2):
print
(
"sprintf(%x,%x,%x,%x)"
%
(buffer_addr, format_addr, arg1, arg2))
emulator.modules.add_symbol_hook(
"sprintf"
, emulator.hooker.write_function(sprintf)
+
1
)
HOOK_MEMORY_BASE
=
0x20000000
HOOK_MEMORY_SIZE
=
0x00200000
self
.hooker
=
Hooker(
self
, HOOK_MEMORY_BASE, HOOK_MEMORY_SIZE)
HOOK_MEMORY_BASE
=
0x20000000
HOOK_MEMORY_SIZE
=
0x00200000
self
.hooker
=
Hooker(
self
, HOOK_MEMORY_BASE, HOOK_MEMORY_SIZE)
from
keystone
import
Ks, KS_ARCH_ARM, KS_MODE_THUMB
from
unicorn
import
*
from
unicorn.arm_const
import
*
STACK_OFFSET
=
8
class
Hooker:
def
__init__(
self
, emu, base_addr, size):
self
._emu
=
emu
self
._keystone
=
Ks(KS_ARCH_ARM, KS_MODE_THUMB)
self
._size
=
size
self
._current_id
=
0xFF00
self
._hooks
=
dict
()
self
._hook_magic
=
base_addr
self
._hook_start
=
base_addr
+
4
self
._hook_current
=
self
._hook_start
self
._emu.uc.hook_add(UC_HOOK_CODE,
self
._hook,
None
,
self
._hook_start,
self
._hook_start
+
size)
from
keystone
import
Ks, KS_ARCH_ARM, KS_MODE_THUMB
from
unicorn
import
*
from
unicorn.arm_const
import
*
STACK_OFFSET
=
8
class
Hooker:
def
__init__(
self
, emu, base_addr, size):
self
._emu
=
emu
self
._keystone
=
Ks(KS_ARCH_ARM, KS_MODE_THUMB)
self
._size
=
size
self
._current_id
=
0xFF00
self
._hooks
=
dict
()
self
._hook_magic
=
base_addr
self
._hook_start
=
base_addr
+
4
self
._hook_current
=
self
._hook_start
self
._emu.uc.hook_add(UC_HOOK_CODE,
self
._hook,
None
,
self
._hook_start,
self
._hook_start
+
size)
def
write_function(
self
, func):
hook_id
=
self
._get_next_id()
hook_addr
=
self
._hook_current
asm
=
"PUSH {R4,LR}\n"
\
"MOV R4, #"
+
hex
(hook_id)
+
"\n"
\
"MOV R4, R4\n"
\
"POP {R4,PC}"
asm_bytes_list, asm_count
=
self
._keystone.asm(bytes(asm, encoding
=
'ascii'
))
if
asm_count !
=
4
:
raise
ValueError(
"Expected asm_count to be 4 instead of %u."
%
asm_count)
self
._emu.uc.mem_write(hook_addr, bytes(asm_bytes_list))
self
._hook_current
+
=
len
(asm_bytes_list)
self
._hooks[hook_id]
=
func
return
hook_addr
def
write_function(
self
, func):
hook_id
=
self
._get_next_id()
hook_addr
=
self
._hook_current
asm
=
"PUSH {R4,LR}\n"
\
"MOV R4, #"
+
hex
(hook_id)
+
"\n"
\
"MOV R4, R4\n"
\
"POP {R4,PC}"
asm_bytes_list, asm_count
=
self
._keystone.asm(bytes(asm, encoding
=
'ascii'
))
if
asm_count !
=
4
:
raise
ValueError(
"Expected asm_count to be 4 instead of %u."
%
asm_count)
self
._emu.uc.mem_write(hook_addr, bytes(asm_bytes_list))
self
._hook_current
+
=
len
(asm_bytes_list)
self
._hooks[hook_id]
=
func
return
hook_addr
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!