游戏客户端开发基础-移动篇(视频习题)
GitHub - noxke/TencentGameClientOpenCourse: 腾讯游戏客户端公开课2023 腾讯菁英班
1.静态分析
Java层
使用jadx打开题目gslab.demo.apk
文件,查看AndroidManifest.xml
文件获取软件入口activity为com.example.x86demo.MainActivity
查看该入口activity类的onCreate
方法
TextView对象tv
设置的字符串来自stringFromJNI
方法
查看其余部分,stringFromJNI
是native-lib
中的native函数
Native层
该软件提供了armabi-v7a
和x86
两种处理器架构的libnative-lib.so
文件,此处对arm架构so文件进行分析
使用readelf命令查看libnative-lib.so
的导出函数

stringFromJNI
导出函数的偏移地址为0x08bdd
,完整导出名称为Java_com_example_x86demo_MainActivity _stringFromJNI
arm指令集每条指令长度为32位,存在指令对其,指令地址低两位均为0,而此处偏移地址最低位为1,表示处理器执行到该处时进入Thumb模式,指令长度变为16位或32位,实际指令偏移地址为
0x8bdc
使用ida32打开libnative-lib.so
文件,定位到导出函数,指令长度大多为16位,表示处理器执行该函数时处于Thumb模式
反编译查看该函数

程序使用C++的string类构造string对象并返回,注意到除了sub_E36E2CA6
函数外其它函数均为库函数,因此选择该函数进行hook
2.动态调试
使用真机(android 13)进行调试,调试工具为android studio和ida
Java层
对stringFromJNI
调用处的smali代码下断点,连接设备进行调试

程序在断点处断下,单步跳过查看stringFromJNI
函数的返回值(v1寄存器)

函数返回结果为字符串对象Hello from C++
继续运行程序,屏幕显示Hello from C++
字符串
Native层
开启adb端口转发,将ida的dbgsrv上传到手机,进入shell切换到root权限启动dbgsrv
android studio上开启调试,程序在smali代码断下
ida对stringFromJNI
函数下断点,连接调试器并附加到com.example.x86demo
进程,按F9继续运行

android studio单步执行,ida上程序在stringFromJNI
函数断下

此处处理器处于Thumb模式
断点到sub_E3722CA6
函数内部

函数只做了参数传递并调用sub_E3723258
函数,sub_E3722CA6
的参数为R0
寄存器,值为0xFF98C0E8
,显然为一个地址,跳转到该地址处

猜测该位置为一个结构体,结构体第一个整数为0x11,第二个整数为0x0E,第三个整数为0x77EB6850
,第三个数明显为地址,跳转到该地址处

该地址处为字符串Hello from C++
,对该字符串进行修改测试

ida使用F9运行,android studio单步断下,查看stringFromJNI
返回值

返回值变为了上面修改的字符串,继续运行程序,屏幕上显示修改后的字符串

3.使用ptrace注入并hook程序
上述分析中选择
sub_E36E2CA6
函数进行hook,该函数偏移为0x8ca6
,后续称为proc
该函数的调用关系为
onCreate->stringFromJNI->proc
,由于该函数在程序启动时执行,几乎没有等待时间,所以在正常执行的情况下不能够保证100%hook成功
本题使用traphook的方式修改proc函数的参数,思路如下:
ptrace在proc函数开始位置设置软件断点
waitpid捕获进程停止信息
ptrace获取proc函数参数R0寄存器,得到字符串地址
ptrace修改字符串地址处的字符串
ptrace禁用断点,恢复程序执行
代码实现
ptrace函数需要待附加进程的pid,使用pidof com.example.x86demo
命令可以获得软件的pid
想要hook proc
函数,需要知道其内存中的加载地址,在静态分析中得到了其偏移地址proc_offset = 0x8ca6
,还需要libnative-lib.so
文件加载的基地址才能计算出proc
函数的地址,安卓进程加载的库文件信息可以在/proc/pid/maps
文件中找到

如图,本次程序运行libnative-lib.so
的加载地址为lib_base = e381d000
,proc
函数的地址为proc_addr = lib_base + proc_offset
c语言中使用popen函数执行命令行目录并获取结果
1 |
|
使用ptrace附加到进程并将proc
函数第一条指令修改为软件断点,设置断点前需要保存原始指令,确保后续能够使程序正确运行,设置断点后恢复进程运行
1 |
|
当处理器执行到软件断点时会发出SIGTRAP
信号,使用waitpid捕获断点信号,并获取寄存器信息,判断PC寄存器地址是否为断点地址
1 |
|
当程序在设置的断点处断下后,寄存器R0的值为proc函数的参数,其值为地址,R0+8
为字符串地址,使用ptrace修改R0+8
处的字符串,实现对stringFromJNI
返回值的修改
完成修改后需要将断点恢复为原始指令
1 |
|
编译程序并上传到手机测试

x86的hook实现与arm的实现基本相同,除了
proc
函数的偏移地址不同,以及断点指令不同以外,还需要注意x86处理器在执行int3断点后会将EIP+1,捕获断点后除了将断点指令恢复,还需要将EIP寄存器恢复