前言
通过之前的【Mac逆向工程-入门1】我们对Mac逆向有一定的认识。
大致思路:
界面分析 -> 动态分析 -> 静态分析 -> 动态库注入 ->打包重新签名等。
目录
–
汇编知识
1、寄存器(又称缓存)
寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。
一般寄存器:AX、BX、CX、DX
AX:累积暂存器,BX:基底暂存器,CX:计数暂存器,DX:资料暂存器。
EAX 是”累加器”(accumulator), 它是很多加法乘法指令的缺省寄存器。
EBX 是”基地址”(base)寄存器, 在内存寻址时存放基地址。
ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
R0-R3:用于函数参数及返回值的传递,调用函数的时候,参数先从R0依次传递。
R4-R6, R8, R10-R11:没有特殊规定,就是普通的通用寄存器。
R7:栈帧指针(Frame Pointer),相对与SP,R7就是栈底,在进入新一个栈帧之后先把原来的R7压栈,然后R7保存当前SP。
R9:操作系统保留。
R12:又叫IP(intra-procedure scratch ), 要说清楚要费点笔墨,参见http://blog.csdn.net/gooogleman/article/details/3529413
R13:又叫SP(stack pointer),是栈顶指针。
R14:又叫LR(link register),存放函数的返回地址。
R15:又叫PC(program counter),指向当前指令地址。
CPSR:当前程序状态寄存器(Current Program State Register),在用户状态下存放像condition标志中断禁用等标志的。
对于x86架构,字母e用作名称前缀,指示各寄存器大小为32位;对于x86_64寄存器,字母r用作名称前缀,指示各寄存器大小为64位。
是64位,相对32位的x86来说,标识符发生了变化,比如:从原来的ebp变成了rbp。

总结:
X86-64有16个64位寄存器,分别是:rax,rbx,rcx,rdx,esi,edi,rbp,rsp,r8,r9,r10,r11,r12,r13,r14,r15。
其中:
rax(accumulator): 可用于存放函数返回值。rbp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址。rsp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址。ip(instruction pointer): 指向当前执行指令的下一条指令。rdi,rsi,rdx,rcx,r8,r9用作函数参数,依次对应第1参数,第2参数。。。rbx,rbp,r12,r13,14,15用作数据存储,遵循被调用者使用规则,简单说就是随便用,调用子函数之前要备份它,以防他被修改。r10,r11用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存。
2、常用指令
mov(数据传送指令)
c语言里的赋值
在汇编语言中,MOV指令是数据传送指令,也是最基本的编程指令,用于将一个数据从源地址传送到目标地址(寄存器间的数据传送本质上也是一样的)。其特点是不破坏源地址单元的内容。
MOV R0,R1 ; // R0 = R1;mov扩展 (原码,补码,反码,溢出,零标志位等)。
比如10的原码就是00001010,+10的原码是00001010,最高位的0代表 这个数是正数(最高位就是符号位). -10的原码就是10001010,最高位的1代表这个数是负数。
+10的原码是00001010,那他的反码,补码都和原码相同 也是00001010,原因是正数的原 反 补码相同。
-10的原码是10001010,那他的反码是11110101,也就是符号位不变,其他位0变1,1变0. 他的补码是在反码的基础上,最低位加1,也就是11110110。
mov.w .w只是强调是32位架构。
movs是mov的扩展,最后字母s代表影响标志位。
lea (数据传送指令)
将源操作数、即存储单元的有效地址(偏移地址)传送到目的操作数,同mov指令类似。
当源操作数很简单的情况下,完全可以用mov指令代替lea指令,如lea esi,Buffer,完全可以用指令mov esi,offset Buffer代替;但当源操作数稍微复杂一点的话,单用mov指令就代替不了了,至少要用到算术运算指令。指令集中提供lea指令,就是为了减少这些计算上的麻烦。
lea edx, [ebx+eax*4+3]call(调用指令)
常见的CPU的CALL指令的功能,就是以下两点:
将下一条指令的所在地址(即当时程序计数器PC的内容)入栈。
并将子程序的起始地址送入PC(于是CPU的下一条指令就会转去执行子程序)。
je jne jb jg jmp 等(跳转指令)
if (a == b) // 相当于je if (a != b) // 相当于jne if (a > b) // 相当于jg if (a < b) // 相当于jbtest
测试一方寄存器是否为空。
test ecx, ecx jz somewhere 如果ecx为零,设置ZF零标志为1,Jz跳转 test rbx, rbx je 0x10000ac78 if(self) { }sub(subtract减法指令)
sub ax,bx ;//ax = ax - bxadd(加法指令)
add ax,bxret(返回指令)
相当于return。
实例:
push ebp ;ebp入栈 压入函数调用方的EBP,Old EBP
mov ebp, esp ;因为esp是堆栈指针,无法暂借使用,所以得用ebp来存取堆栈 令当前EBP指向栈顶,此时的栈顶指向Old EBP
sub esp, 0xxxxx ;开创了一个新的栈帧,而大小是0xxxxx个字节 在栈上开辟一些空间,存放局部变量等
add esp, 0xxxxx ;堆栈使用完毕,“还”回0xxxxx个字节给系统
mov esp, ebp ;恢复esp的值
pop ebp ;ebp出栈
ret
调用一个函数时,先将堆栈原先的基址(EBP)入栈,以保存之前任务的信息。然后将栈顶指针的值赋给EBP,将之前的栈顶作为新的基址(栈底),然后再这个基址上开辟相应的空间用作被调用函数的堆栈。函数返回后,从EBP中可取出之前的ESP值,使栈顶恢复函数调用前的位置;再从恢复后的栈顶可弹出之前的EBP值,因为这个值在函数调用前一步被压入堆栈。这样,EBP和ESP就都恢复了调用前的位置,堆栈恢复函数调用前的状态。
3、Hopper出的伪代码分析

#define LOWORD(l) ((WORD)((DWORD_PTR)(l) & 0xffff))
#define HIWORD(l) ((WORD)((DWORD_PTR)(l) >> 16))
这是windef.h头文件中对宏LOWORD和HIWORD的定义。
作用分别是取出无符号长整型参数的高16位和低16位。
因为一个长整型占32位,其中高低16位的值可能有不同的意义,需要通过这2个宏分别取出来使用。取出来的结果是一个无符号短整型的值。
其原理正如定义那样,取低16位的宏LOWORD使用按位与操作符与数字0xffff运算,而数字0xffff是一个低16位全为1的数字,那么对其位与操作可以得到参数的低16位。
而取高16位的宏HIWORD则更简单,只需将参数右移16位,剩下的就是原高16位的值了。
Objective-C的编译器在编译后会在每个方法中加两个隐藏的参数:
- 一个是_cmd,当前方法的一个SEL指针。
另一个就是用的比较多的self,指向当前对象的一个指针。
NSLog(@"%@",NSStringFromSelector(_cmd));
总结:
LOWORD()得到一个32bit数的低16bit。
HIWORD()得到一个32bit数的高16bit。
LOBYTE()得到一个16bit数最低(最右边)那个字节。
HIBYTE()得到一个16bit数最高(最左边)那个字节。
跟踪方法
我们hook主要是hook方法,然后找到执行的方法中去做我们想做的事情。
如何快速定位某个Class执行到的方法?
1、我一般先用界面工具Interface Inspector先找到当前的Class
2、查找当前执行的方法(核心)
dtrace(建议)
可以通过它来监控应用程序或者内核的调用。
格式:
sudo dtrace -n ‘objc$target:ClassXX::entry{}’ -p 进程id(PID))
示例:
sudo dtrace -n ‘objc$target: STPreferencesWindowController::entry{}’ -p 6840
安装: // 安装时间较长
git clone git://github.com/frida/frida.git cd frida make
点击了设置页面的browse...按钮
触发的方法是:
browseDefaultFolder:方法。
frida(不建议使用,打印的太频繁)
可以通过这个工具来跟踪指定方法或者指定类的调用过程。
格式:
frida-trace -m "-[ClassXX *]" 程序名称示例:
frida-trace -m “-[STPreferencesWindowController *]” Sourcetree
安装:
sudo easy_install pip // 先安装pip sudo pip install frida
lldb
通过打断点去调试出信息日志。
猜

资源文件
由于使用的技术是通过动态库的方式植入到应用中,但动态库即使你在拖入的时候勾选了target,但实际查看时是没有被勾选的,也就是说通过动态库的方式是打包不进去资源文件。
如何把资源文件实践在应用中?
查看应用:
xx/Contents/Resources

发现Resources文件夹存放的都是资源文件。
直接把需要的资源文件拖进去如何?
1、图片、plist等资源直接拖进去是可行的。
2、xib文件直接拖进去是不能被找到的,原因是打包后的xib文件成了nib文件。
如何成为nib文件?
随便建个Mac应用,在Resources找到这个nib,然后再拖入到需要显示的应用中即可。
当然你也可以通过绝对路劲的方式去载入,这种方式不灵活也不方便。
总结
如果想研究汇编里面指令知识,最好写个Demo然后Hopper分析里面的代码。
通过动态库注入的方式修改应用,一般是先找到当前页面的
Class,然后找到执行的方法。修改执行的操作可以通过Hopper软件,
Modify -> Assemble Instruction…或Window/Hexadecimal Editor直接双击修改执行的指令或通过hook的方式(常用方式)去修改。对于一些资源文件的载入需要拖入到
xx/Contents/Resources文件夹中即可。通过hook方式,一般采用分类的方式为某个
Class添加方法或属性。