-
-
[旧帖] 菜鸟整理so分析,arm 汇编,IDA静态分析 0.00雪花
-
发表于: 2015-8-3 21:43 2442
-
So 静态(arm 汇编,IDA静态分析等)
R7:栈帧指针(Frame Pointer).指向前一个保存的栈帧(stack frame)和链接寄存器(link register, lr)在栈上的地址
R13:又叫SP(stack pointer),是栈顶指针
R14:又叫LR(link register),存放函数的返回地址。
R15:又叫PC(program counter),指向当前指令地址
这条指令(bl)对函数进行调用。请记住被调用函数需要的参数已经存储到相关的寄存器中了(r0和r1)。这条指令的执行一般被当做一个分支(branch)。可以理解为执行带链接的分支,也就是说,在跳转到分支之前,会将lr(link register)的值设置为当前函数中将要执行的下一条指令,当从分支(被调函数)中返回时,通过lr中的值可以知道当前函数执行到哪里了。
blx中的x标示交换“exchange”,意思是如果有必要,处理器将对指令集模式进行切换。
返回值(存储在r0中)
mov r0, r1 => r0 = r1
mov r0, #10 => r0 = 10
ldr r0, [sp] => r0 = *sp
str r0, [sp] => *sp = r0
str 把寄存器内容存到栈上去
ldr 把栈上内容载入一寄存器中
add r0, r1, r2 => r0 = r1 + r2
add r0, r1 => r0 = r0 + r1
push {r0, r1, r2} => 将 r0, r1 和 r2push到栈中.
pop {r0, r1, r2} => 将3个值从栈中pop出来,并存放到r0, r1 和 r2中.
b_label => pc = _label
bl _label => lr = pc + 4; pc = _label
self和_cmd占用了r0和r1寄存器。它存储着当前执行方法的selector
每次调用Objective-C方法时,都由objc_msgSend方法处理消息的派送。该方法根据传递的消息类型在类的方法列表中查找被调用方法的实现。objc_msgSend方法:
id objc_msgSend(id self, SEL _cmd, ...)
MOV R1, #0
这里将 5 位 opcode 分成了两部分——前 3 位 001 是固定的,后 2 位用于标识 4 中不同的操作: mov, cmp, add, sub。所以 mov 指令的 opcode 二进制表示为 00100;这里 Rd 为 R1,所以 8~10 位为 001;同理, 0~7 为就 0000 0000。所以 MOVS R1, #0 的 2 进制表示为: 0010 0001 0000 0000 = 0x 21 00。
http://blog.csdn.net/zolovegd/article/details/1826192
http://blog.csdn.net/gooogleman/article/details/3758555(opcode学习帖子)
.so文件(shared object) linux的动态链接库,
显示调用则是在主程序里使用dlopen、dlsym、dlerror、dlclose等系统函数。
1.IDA计算出了成员变量的偏移地址并把symbol直接显示出来
IDA:__text:000026C4
mov ebx, ds:(_OBJC_IVAR_$_TestButton_m_model - 26C3h)[esi]
2.函数参数在IDA中被赋予名称,ebp+8为arg_0,ebp+12为arg_1。 arg即为argument的缩写,第n个参数在+号后面的偏移量不是绝对的
。在函数开头和代码中,名称都会直接替换掉实际偏移量。基本上arg_0都是self。
3.常数值型偏移地址被赋予名称,以loc_为前缀。
IDA:jmp short loc_2732
4.局部变量,即 ebp-xxx 会被命名为 var_xxx
搜索特征字符串。具体操作为:①快捷键Ctrl+S,打开搜索类型选择对话框-->双击Strings,跳到字符串段-->菜单项“Search-->Text”;
②快捷键Alt+T,打开文本搜索对话框,在String文本框中输入要搜索的字符串点击OK即可;
C的函数 ,抹掉了符号表
So 动态调试
so 被加载之后最开始执行的是.init_array 段的代码。然后才会去执行 jni_onload那么,在.init_array 处断下来便是很有必要的
1.启动 android_server
端口转发
adb forward tcp:23946 tcp:23946
3.调试启动adb shell am start -D -n com.scottgames.fnaf4/com.putaolab.ptsdk.activity.PTMainActivity
4.链接,下断点
Shift+F12 打开字符串窗口,搜索字符串: dlopen,找到 dlopen 函数的偏移 0xF30
动态调试的 IDA 中, G 跳转到: 400D3000+F30=400D3F30 处,下好断点
搜索字符串: calling
按 F9 运行
然后打开 Eclipse 或者 ddms
执行 jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
程序就会断在第一个断点处, F9 几次就段在 blx R4 处
F7 跟进就来到 init 段代码处:
1.IDA用32位,
2 ./android_server 要su
3 重启平板
4. <application android:allowBackup="true" android:debuggable="false" android:icon="@drawable/app_icon"
Dump
dvmLoadNativeCode 函数是加载和初始化 so 的函数, dvmDexFileOpenPartial 函数是对缓存 Dex 文件,该函数第一个参数就是解密后 dex 文件头内存地址,而第二个参数是该 dex 大小。
跳到 dvmDexFileOpenPartial 函数或 inflate 函数去下断,
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex);
第一个参数就是 dex 内存起始地址,第二个参数就是 dex 大小。所以在这个函数下断点可以直接 dump 出明文 dex
static main(void)
{
auto fp, dex_addr, end_addr;
fp = fopen("D:\\dump.dex", "wb");
end_addr = r0 + r1;
for ( dex_addr = r0; dex_addr < end_addr; dex_addr ++ )
fputc(Byte(dex_addr), fp);
}
遇到反调试
先查Pid ps -aux
cat /proc/xxxxx/status
就可以看tracerPid 如果不为零 及被调试过
当程序打开进程成功后使用 fgets 获得信息 当获得如下信息进我们将其修改为 0,原因:底层会调用 libc 库中的 fopen 函数打开 so 文件句柄,然后通过 fgets 函数读取进程状态值,就可以通过修改读取到的状态值绕过调试进程检测。
修改成0(Thumb 00 20 , 70 47
即 Mov R0,#0)
1.fopen—/proc/self/cmdline.debug.atrace.app_cmdlines
2.fgets—-包名
3.LoadNativeCode–加载 libexec.so
4.LoadNativeCode–加载 libexecmain.so
5.建立反调试线程(通过检查是否存在调试进程)
6.调用 fopen—-打开/proc/pid/status
7.调用 fgets—读取调试进程 pid
在 fgets 内部会调用 memchr 函数,和 memcpy 函数, memchr 函数完成以换行为分隔符, memcpy 将此次读取位置拷贝到目的缓冲区
脱壳
nop: 0xc046
INIT_ARRAY,JNI_OnLoad
壳入口 --> INIT_ARRAY--->解密第二层壳(JNI_OnLoad)---->解密原始so文件--->解压缩原始so的代码节。
http://www.52pojie.cn/thread-356096-1-1.html
__gnu_armfini_26 此函数是ELF的入口函数,此函数就完成了jni_onload和verify等的解密。
纯java开发
主要看支付方式,,搜索onbilling,pay,paysuccess,结合游戏出现的字符串提示,更改if跳转,或直接把oncancel,onfail等直接换为onsuccess,字符串混淆,把以上三个函数变为a(),a(I),b()作为失败,成功,取消等,利用goto去尝试,这类游戏都有一个关键的类,名称大概为paylistener或vlistener等,该listener类包括了三个摘要abstract定义的函数,或者直接是一个onfinish函数,后者显得更复杂一点。但只要稍微跟几层,往往关键修改地方在的都是如
b$1$2.smali这一层smali代码中。这里稍微补充一下改法,大概2种:
(1)如果你想从if跳转下手,假如代码是:
if eqz v1,.cond1
你想跳到cond1里,就直接改为 goto cond1,如果你想删除这个if跳转,直接删除代码即可。
如果你想从oncancel,onfail,onsuccess函数入手,就可以找到关键的中层代码,把oncancel,onfail换为onsuccess函数即可。
如果是支付宝的话,该类游戏进入支付界面时,都会提示你去
安装支付宝快捷支付服务,这里分为两种情况:如果你点击不安装支付宝服务,也就是你手机里
并没有支付宝的软件,它仍然跳出支付宝帐号登录界面,这时我们就比较简单,无须把精力放在
支付宝的破解上,一般情况下,关键的修改处在\apliy\sdk\....\v.smali或\apliy\sdk\....
\ResultStaus.smali的这个smali中,你会发现一个switch语句,如果你再仔细一点,就能知道
支付宝类支付的paycode也就是状态代码,如:9000代表成功,6001代表取消等等。本情况我们
不需要去过问paycode或satuscode支付标志状态代码,我们只要把精力放在那个switch语句上,
大概你会看到如下代码:
.switch data
0x1721 .switch_4
.....
0x2328 .switch_0
该switch语句我们分析一下,实际上就是代表了不同支付状态码的分支情况,以最后一句为例,
0x2328换为十进制就是9000,而我们网上分析,就知道, .switch_0代表成功。分析出来,
我们就好办多了,直接把.switch_4,.switch_3,.switch_2,.switch_1全换为.switch_0,
上述情况是比较理想的,当我们不安装支付宝时,依然软件有支付宝登录界面,我们越过支付宝就可以完美破解,但一般稍大点的游戏,在使用支付宝支付时,你如果取消安装,就直接退出支付界面,这种情况比较复杂,还是要从它提示的字符串信息,如“中途取消支付”操作,一点点分析,不但要把上面分析的
.switch_4,.switch_3,.switch_2,.switch_1全换为.switch_0,还要去分析破解支付宝的支付部分,基本上只要你修改后不出现“中途取消支付”,就是破解了,要去修改调用的子函数和if跳转。
coco2dx开发的游戏:
coco2dx开发的游戏关键也经常在smali层,有时会有签名验证。这里补充下签名验证,不仅仅是针对coco2dx开发的单机游戏,签名验证如果不写在so里,往往从smali层改下跳转就可以,麻烦的就要你去路径转向或hook,
如果coco2dx关键在smali里,就和纯java开发的一样,否则就要去找lua了,如果lua加密了,暂时先不处理
unity 3d开发的游戏
就算是单机游戏,也是单机中比较大的游戏,难度肯定又大了一点,unity 3d
开发的游戏关键有时在smali层,有时却在dll中。如果在smali中,就和纯java开发的一样,但
unity 3d开发的游戏关键修改处往往在Assemly-csharrp.dll里,包括了关键的初始金币量,
攻击力,支付等等。破解这类游戏要熟练使用Reflecter 8.2,当然最好会反混淆
R7:栈帧指针(Frame Pointer).指向前一个保存的栈帧(stack frame)和链接寄存器(link register, lr)在栈上的地址
R13:又叫SP(stack pointer),是栈顶指针
R14:又叫LR(link register),存放函数的返回地址。
R15:又叫PC(program counter),指向当前指令地址
这条指令(bl)对函数进行调用。请记住被调用函数需要的参数已经存储到相关的寄存器中了(r0和r1)。这条指令的执行一般被当做一个分支(branch)。可以理解为执行带链接的分支,也就是说,在跳转到分支之前,会将lr(link register)的值设置为当前函数中将要执行的下一条指令,当从分支(被调函数)中返回时,通过lr中的值可以知道当前函数执行到哪里了。
blx中的x标示交换“exchange”,意思是如果有必要,处理器将对指令集模式进行切换。
返回值(存储在r0中)
mov r0, r1 => r0 = r1
mov r0, #10 => r0 = 10
ldr r0, [sp] => r0 = *sp
str r0, [sp] => *sp = r0
str 把寄存器内容存到栈上去
ldr 把栈上内容载入一寄存器中
add r0, r1, r2 => r0 = r1 + r2
add r0, r1 => r0 = r0 + r1
push {r0, r1, r2} => 将 r0, r1 和 r2push到栈中.
pop {r0, r1, r2} => 将3个值从栈中pop出来,并存放到r0, r1 和 r2中.
b_label => pc = _label
bl _label => lr = pc + 4; pc = _label
self和_cmd占用了r0和r1寄存器。它存储着当前执行方法的selector
每次调用Objective-C方法时,都由objc_msgSend方法处理消息的派送。该方法根据传递的消息类型在类的方法列表中查找被调用方法的实现。objc_msgSend方法:
id objc_msgSend(id self, SEL _cmd, ...)
MOV R1, #0
这里将 5 位 opcode 分成了两部分——前 3 位 001 是固定的,后 2 位用于标识 4 中不同的操作: mov, cmp, add, sub。所以 mov 指令的 opcode 二进制表示为 00100;这里 Rd 为 R1,所以 8~10 位为 001;同理, 0~7 为就 0000 0000。所以 MOVS R1, #0 的 2 进制表示为: 0010 0001 0000 0000 = 0x 21 00。
http://blog.csdn.net/zolovegd/article/details/1826192
http://blog.csdn.net/gooogleman/article/details/3758555(opcode学习帖子)
.so文件(shared object) linux的动态链接库,
显示调用则是在主程序里使用dlopen、dlsym、dlerror、dlclose等系统函数。
1.IDA计算出了成员变量的偏移地址并把symbol直接显示出来
IDA:__text:000026C4
mov ebx, ds:(_OBJC_IVAR_$_TestButton_m_model - 26C3h)[esi]
2.函数参数在IDA中被赋予名称,ebp+8为arg_0,ebp+12为arg_1。 arg即为argument的缩写,第n个参数在+号后面的偏移量不是绝对的
。在函数开头和代码中,名称都会直接替换掉实际偏移量。基本上arg_0都是self。
3.常数值型偏移地址被赋予名称,以loc_为前缀。
IDA:jmp short loc_2732
4.局部变量,即 ebp-xxx 会被命名为 var_xxx
搜索特征字符串。具体操作为:①快捷键Ctrl+S,打开搜索类型选择对话框-->双击Strings,跳到字符串段-->菜单项“Search-->Text”;
②快捷键Alt+T,打开文本搜索对话框,在String文本框中输入要搜索的字符串点击OK即可;
C的函数 ,抹掉了符号表
So 动态调试
so 被加载之后最开始执行的是.init_array 段的代码。然后才会去执行 jni_onload那么,在.init_array 处断下来便是很有必要的
1.启动 android_server
端口转发
adb forward tcp:23946 tcp:23946
3.调试启动adb shell am start -D -n com.scottgames.fnaf4/com.putaolab.ptsdk.activity.PTMainActivity
4.链接,下断点
Shift+F12 打开字符串窗口,搜索字符串: dlopen,找到 dlopen 函数的偏移 0xF30
动态调试的 IDA 中, G 跳转到: 400D3000+F30=400D3F30 处,下好断点
搜索字符串: calling
按 F9 运行
然后打开 Eclipse 或者 ddms
执行 jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
程序就会断在第一个断点处, F9 几次就段在 blx R4 处
F7 跟进就来到 init 段代码处:
1.IDA用32位,
2 ./android_server 要su
3 重启平板
4. <application android:allowBackup="true" android:debuggable="false" android:icon="@drawable/app_icon"
Dump
dvmLoadNativeCode 函数是加载和初始化 so 的函数, dvmDexFileOpenPartial 函数是对缓存 Dex 文件,该函数第一个参数就是解密后 dex 文件头内存地址,而第二个参数是该 dex 大小。
跳到 dvmDexFileOpenPartial 函数或 inflate 函数去下断,
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex);
第一个参数就是 dex 内存起始地址,第二个参数就是 dex 大小。所以在这个函数下断点可以直接 dump 出明文 dex
static main(void)
{
auto fp, dex_addr, end_addr;
fp = fopen("D:\\dump.dex", "wb");
end_addr = r0 + r1;
for ( dex_addr = r0; dex_addr < end_addr; dex_addr ++ )
fputc(Byte(dex_addr), fp);
}
遇到反调试
先查Pid ps -aux
cat /proc/xxxxx/status
就可以看tracerPid 如果不为零 及被调试过
当程序打开进程成功后使用 fgets 获得信息 当获得如下信息进我们将其修改为 0,原因:底层会调用 libc 库中的 fopen 函数打开 so 文件句柄,然后通过 fgets 函数读取进程状态值,就可以通过修改读取到的状态值绕过调试进程检测。
修改成0(Thumb 00 20 , 70 47
即 Mov R0,#0)
1.fopen—/proc/self/cmdline.debug.atrace.app_cmdlines
2.fgets—-包名
3.LoadNativeCode–加载 libexec.so
4.LoadNativeCode–加载 libexecmain.so
5.建立反调试线程(通过检查是否存在调试进程)
6.调用 fopen—-打开/proc/pid/status
7.调用 fgets—读取调试进程 pid
在 fgets 内部会调用 memchr 函数,和 memcpy 函数, memchr 函数完成以换行为分隔符, memcpy 将此次读取位置拷贝到目的缓冲区
脱壳
nop: 0xc046
INIT_ARRAY,JNI_OnLoad
壳入口 --> INIT_ARRAY--->解密第二层壳(JNI_OnLoad)---->解密原始so文件--->解压缩原始so的代码节。
http://www.52pojie.cn/thread-356096-1-1.html
__gnu_armfini_26 此函数是ELF的入口函数,此函数就完成了jni_onload和verify等的解密。
纯java开发
主要看支付方式,,搜索onbilling,pay,paysuccess,结合游戏出现的字符串提示,更改if跳转,或直接把oncancel,onfail等直接换为onsuccess,字符串混淆,把以上三个函数变为a(),a(I),b()作为失败,成功,取消等,利用goto去尝试,这类游戏都有一个关键的类,名称大概为paylistener或vlistener等,该listener类包括了三个摘要abstract定义的函数,或者直接是一个onfinish函数,后者显得更复杂一点。但只要稍微跟几层,往往关键修改地方在的都是如
b$1$2.smali这一层smali代码中。这里稍微补充一下改法,大概2种:
(1)如果你想从if跳转下手,假如代码是:
if eqz v1,.cond1
你想跳到cond1里,就直接改为 goto cond1,如果你想删除这个if跳转,直接删除代码即可。
如果你想从oncancel,onfail,onsuccess函数入手,就可以找到关键的中层代码,把oncancel,onfail换为onsuccess函数即可。
如果是支付宝的话,该类游戏进入支付界面时,都会提示你去
安装支付宝快捷支付服务,这里分为两种情况:如果你点击不安装支付宝服务,也就是你手机里
并没有支付宝的软件,它仍然跳出支付宝帐号登录界面,这时我们就比较简单,无须把精力放在
支付宝的破解上,一般情况下,关键的修改处在\apliy\sdk\....\v.smali或\apliy\sdk\....
\ResultStaus.smali的这个smali中,你会发现一个switch语句,如果你再仔细一点,就能知道
支付宝类支付的paycode也就是状态代码,如:9000代表成功,6001代表取消等等。本情况我们
不需要去过问paycode或satuscode支付标志状态代码,我们只要把精力放在那个switch语句上,
大概你会看到如下代码:
.switch data
0x1721 .switch_4
.....
0x2328 .switch_0
该switch语句我们分析一下,实际上就是代表了不同支付状态码的分支情况,以最后一句为例,
0x2328换为十进制就是9000,而我们网上分析,就知道, .switch_0代表成功。分析出来,
我们就好办多了,直接把.switch_4,.switch_3,.switch_2,.switch_1全换为.switch_0,
上述情况是比较理想的,当我们不安装支付宝时,依然软件有支付宝登录界面,我们越过支付宝就可以完美破解,但一般稍大点的游戏,在使用支付宝支付时,你如果取消安装,就直接退出支付界面,这种情况比较复杂,还是要从它提示的字符串信息,如“中途取消支付”操作,一点点分析,不但要把上面分析的
.switch_4,.switch_3,.switch_2,.switch_1全换为.switch_0,还要去分析破解支付宝的支付部分,基本上只要你修改后不出现“中途取消支付”,就是破解了,要去修改调用的子函数和if跳转。
coco2dx开发的游戏:
coco2dx开发的游戏关键也经常在smali层,有时会有签名验证。这里补充下签名验证,不仅仅是针对coco2dx开发的单机游戏,签名验证如果不写在so里,往往从smali层改下跳转就可以,麻烦的就要你去路径转向或hook,
如果coco2dx关键在smali里,就和纯java开发的一样,否则就要去找lua了,如果lua加密了,暂时先不处理
unity 3d开发的游戏
就算是单机游戏,也是单机中比较大的游戏,难度肯定又大了一点,unity 3d
开发的游戏关键有时在smali层,有时却在dll中。如果在smali中,就和纯java开发的一样,但
unity 3d开发的游戏关键修改处往往在Assemly-csharrp.dll里,包括了关键的初始金币量,
攻击力,支付等等。破解这类游戏要熟练使用Reflecter 8.2,当然最好会反混淆
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
看原图
赞赏
雪币:
留言: