前言

通过之前的【Mac逆向工程-入门1】我们对Mac逆向有一定的认识。

大致思路:

界面分析 -> 动态分析 -> 静态分析 -> 动态库注入 ->打包重新签名等。

目录

汇编知识

1、寄存器(又称缓存)

寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。

一般寄存器:AXBXCXDX

AX:累积暂存器,BX:基底暂存器,CX:计数暂存器,DX:资料暂存器。

EAX 是”累加器”(accumulator), 它是很多加法乘法指令的缺省寄存器。

EBX 是”基地址”(base)寄存器, 在内存寻址时存放基地址。

ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。

R0-R3:用于函数参数及返回值的传递,调用函数的时候,参数先从R0依次传递。

R4-R6, R8, R10-R11:没有特殊规定,就是普通的通用寄存器。

R7:栈帧指针(Frame Pointer),相对与SPR7就是栈底,在进入新一个栈帧之后先把原来的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位寄存器,分别是:raxrbxrcxrdxesiedirbprspr8r9r10r11r12r13r14r15

其中:

  • rax(accumulator): 可用于存放函数返回值。
  • rbp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址。
  • rsp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址。
  • ip(instruction pointer): 指向当前执行指令的下一条指令。
  • rdirsirdxrcxr8r9 用作函数参数,依次对应第1参数,第2参数。。。
  • rbxrbpr12r131415 用作数据存储,遵循被调用者使用规则,简单说就是随便用,调用子函数之前要备份它,以防他被修改。
  • r10r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存。

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) // 相当于jb
    
  • test

    测试一方寄存器是否为空。

    test ecx, ecx
    jz somewhere
    
    如果ecx为零,设置ZF零标志为1,Jz跳转
    test       rbx, rbx
    je         0x10000ac78
    if(self) {
    }
    
  • sub(subtract减法指令)

    sub ax,bx ;//ax = ax - bx  
    
  • add(加法指令)

    add ax,bx
    
  • ret(返回指令)

    相当于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添加方法或属性。