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 -ncom.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等的解密。
|