美文网首页
iOS逆向实战--008:反汇编

iOS逆向实战--008:反汇编

作者: 帅驼驼 | 来源:发表于2021-04-12 10:46 被阅读0次
    OC的反汇编

    案例1:

    OC的方法调用

    打开Person.h,写入以下代码:

    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    
    @property(nonatomic,copy)NSString *name;
    
    @property(nonatomic,assign)int age;
    
    +(instancetype)person;
    
    @end
    

    打开Person.m,写入以下代码:

    #import "Person.h"
    
    @implementation Person
    
    +(instancetype)person{
       return [[Person alloc] init];
    }
    
    @end
    

    打开ViewController.m,写入以下代码:

    #import "ViewController.h"
    #import "Person.h"
    
    @implementation ViewController
    
    - (void)viewDidLoad {
    //    [super viewDidLoad];
       Person *p = [Person person];
    }
    
    @end
    

    真机运行项目,来到viewDidLoad方法

    • objc_msgSend函数有两个默认参数,id类型的selfSEL类型的_cmd
    • 两个参数分别对应x0x1

    查看x0寄存器

       0x1046de46c <+20>: adrp   x8, 2
       0x1046de470 <+24>: add    x8, x8, #0xec8            ; =0xec8 
       0x1046de474 <+28>: ldr    x0, [x8]
    
    • 使用adrp + add指令,计算地址为0x1046e0ec8,赋值x8
    • x8进行寻址,写入x0
    • x0对应id类型参数,id类型本质上是结构体指针,占8字节

    查看内存中的数据

    x 0x1046e0ec8
    -------------------------
    0x1046e0ec8: 38 0f 6e 04 01 00 00 00 b0 0f 6e 04 01 00 00 00  8.n.......n.....
    0x1046e0ed8: 08 00 00 00 10 00 00 00 10 00 00 00 00 00 00 00  ................
    
    • 读取8字节数据,小端模式,从右往左读取0x01046e0f38

    打印x0

    po 0x01046e0f38
    -------------------------
    Person
    
    • 打印的Person是类对象

    查看x1寄存器

       0x1046de478 <+32>: adrp   x8, 2
       0x1046de47c <+36>: add    x8, x8, #0xeb0            ; =0xeb0 
       0x1046de480 <+40>: ldr    x1, [x8]
    
    • 使用adrp + add指令,计算地址为0x1046e0eb0,赋值x8
    • x8进行寻址,写入x1
    • x1对应SEL类型参数,占8字节

    查看内存中的数据

    x 0x1046e0eb0
    -------------------------
    0x1046e0eb0: 10 c8 88 e2 01 00 00 00 18 f6 15 e3 01 00 00 00  ................
    0x1046e0ec0: 98 88 fd e2 01 00 00 00 38 0f 6e 04 01 00 00 00  ........8.n.....
    
    • 读取8字节数据,小端模式,从右往左读取0x01e288c810

    打印x1

    po (SEL)0x01e288c810
    -------------------------
    "person"
    
    • 打印的"person"是方法名称

    当汇编代码中出现objc_msgSend,表示调用了一个OC方法,读取x0x1便可知道调用了哪个对象的哪个方法

    案例2:

    allocinit函数

    使用iOS12.2系统调试,来到Person对象的person方法

    • 调用objc_alloc_init函数

    iOS12.2开始,系统优化allocinit函数,不再使用消息发送(objc_msgSend),直接调用优化后的objc_alloc_init函数


    使用iOS12.1系统调试

    • 分别调用objc_alloc函数和objc_msgSend函数

    调用objc_msgSend函数,实际上就是在调用init函数

    • 调用objc_msgSend函数,但这里没有对x0进行赋值。这说明在上一个调用函数objc_alloc中返回的x0,正是objc_msgSend函数将要使用的参数

    iOS10.0开始,系统优化alloc函数,直接调用优化后的objc_alloc函数。但对于init函数,依然使用objc_msgSend进行消息发送


    使用iOS9.1系统调试

    allocinit函数,调用两次objc_msgSend进行消息发送

    案例3:

    objc_storeStrong函数

    Person初始化完毕,x0返回一个实例对象。之后会调用objc_storeStrong函数

    • OC中,使用strong修饰的对象,都会调用此函数
    • viewDidLoad方法中,定义的局部变量p,就是一个强引用
    • 调用objc_storeStrong函数,并不一定会让对象的引用计数+1,也可能调用后直接销毁。例如:局部变量p,它没有被外部代码使用,出栈后就会销毁

    来到objc源码

    打开NSObject.mm文件,找到objc_storeStrong函数的实现

    void
    objc_storeStrong(id *location, id obj)
    {
       id prev = *location;
       if (obj == prev) {
           return;
       }
       objc_retain(obj);
       *location = obj;
       objc_release(prev);
    }
    
    • 参数location,二级指针,指向对象指针的指针
    • 参数obj,对象指针
    • 局部变量prev,将location指向的对象指针赋值给prev
    • 判断对象相等,直接return
    • 如果不等,当前对象obj引用计数+1
    • location指向obj
    • location指向的老对象释放

    objc_storeStrong函数的目的,对一个strong修饰的对象retain,对老对象release,等价于以下代码:

    Person *p = p1;
    p = p2;
    
    • p2赋值给pp2引用计数+1p1释放

    回到汇编代码

    查看location参数

       0x10058a3ac <+56>: add    x8, sp, #0x8              ; =0x8 
       0x10058a3b0 <+60>: str    x0, [sp, #0x8]
       0x10058a3b4 <+64>: mov    x0, x8
    
    • sp + #0x8地址写入x8
    • x0,即:局部变量p,入栈到sp + #0x8
    • x8写入x0,此时x0是指向局部变量p的指针地址

    查看obj参数

       0x10058a3b8 <+68>: mov    x8, #0x0
       0x10058a3bc <+72>: mov    x1, x8
    
    • #0x0,即:nil,写入x8
    • x8写入x1,此时x1nil

    此刻调用objc_storeStrong函数触发的逻辑:

    • prev被赋值为局部变量p
    • 判断对象不相等
    • 调用objc_retain函数,传入nil
    • location指向nil
    • 调用objc_release函数,传入局部变量p,将其释放
    工具反汇编

    真实场景下,对Mach-O进行分析,只能使用静态分析,无法使用lldb动态调试。所以要学会运用工具进行反汇编

    案例1:

    打开ViewController.m,写入以下代码:

    #import "ViewController.h"
    #import "Person.h"
    
    @implementation ViewController
    
    - (void)viewDidLoad {
    //    [super viewDidLoad];
       Person *p = [Person person];
       p.name = @"Zang";
       p.age = 18;
    }
    
    @end
    

    使用真机编译项目,将Mach-O文件拖到Hopper中,找到viewDidLoad函数

    • Hopper可以解析出方法、属性等名称,作为注释标记给开发者
    • 对于bl指令,也将跳转地址解析为对应的函数名称

    案例2:

    Mach-O中,找到Hopper转义出的注释

    找到objc_cls_ref_PersonMach-O中的位置

    0000000100006350         adrp       x8, #0x10000c000
    0000000100006354         add        x8, x8, #0xd58            ; >objc_cls_ref_Person
    
    • 使用adrp + add指令,计算地址为0x10000cd58,赋值x8

    打开Mach-O,找到0x10000cd58


    找到personselector,在Mach-O中的位置

    000000010000635c         adrp       x8, #0x10000c000
    0000000100006360         add        x8, x8, #0xd40             ; @selector(person)
    
    • 使用adrp + add指令,计算地址为0x10000cd40,赋值x8

    打开Mach-O,找到0x10000cd40


    找到person等字符串,在Mach-O中的位置

    打开Mach-O,找到0x100006a2c,存储了方法名称的字符串


    找到imp___stubs__objc_msgSendMach-O中的位置

    0000000100006368         bl         imp___stubs__objc_msgSend
    

    HopperviewDidLoad方法中,双击imp___stubs__objc_msgSend

                        imp___stubs__objc_msgSend:
    000000010000685c         nop             ; CODE XREF=-[ViewController viewDidLoad]+44, -[ViewController viewDidLoad]+92, -[ViewController viewDidLoad]+116
    0000000100006860         ldr        x16, #0x10000c028
    0000000100006864         br         x16
                           ; endp
    

    打开Mach-O,找到0x10000685c,存储的正是_objc_msgSend函数


    找到Zang字符串,在Mach-O中的位置

    000000010000638c         adrp       x2, #0x100008000
    0000000100006390         add        x2, x2, #0x8              ; @"Zang"
    
    • 使用adrp + add指令,计算地址为0x100008008,赋值x8

    HopperviewDidLoad方法中,双击Zang

           ; Section __cfstring
           ; Range: [0x100008008; 0x100008028[ (32 bytes)
           ; File offset : [32776; 32808[ (32 bytes)
           ;   S_REGULAR
    
                        cfstring_Zang:
    0000000100008008         dq         ___CFConstantStringClassReference, 0x7c8, 0x10000755d, 0x4 ; "Zang", DATA XREF=-[ViewController viewDidLoad]+84
    

    打开Mach-O,找到0x100008008,存储字符串常量Zang的相关信息

    案例3:

    使用Hopper查看代码的流程图

    打开ViewController.m,写入以下代码:

    #import "ViewController.h"
    
    @implementation ViewController
    
    void test1(bool b){
       if(b){
           test2(b);
       }
       else{
           test3();
       }
    }
    
    void test2(bool b){
       if(b){
           test3();
       }
       else{
           test4();
       }
    }
    
    void test3(){ NSLog(@"test3"); }
    void test4(){ NSLog(@"test4"); }
    
    - (void)viewDidLoad {
    //    [super viewDidLoad];
       
       int a = 1;
       int b = 2;
       
       if(a == b){
           test1(YES);
       }
       else{
           test2(NO);
       }
    }
    
    @end
    

    使用真机编译项目,将Mach-O文件拖到Hopper中,找到viewDidLoad函数

    Hopper中,点击CFG mode按钮

    切换到viewDidLoad函数的流程图,清晰的展示出不同的代码分支

    双击test1函数,切换到test1函数中的代码分支

    还原高级代码时,遇到复杂的逻辑分支,可以使用代码的流程图进行分析

    案例4:

    使用Hopper查看伪码模式

    Hopper中,点击Pseudo-code mode按钮

    查看Hopper帮我们还原的viewDidLoad函数的伪码

    int -[ViewController viewDidLoad]() {
       r31 = r31 - 0x30;
       *(r31 + 0x20) = r29;
       *(r31 + 0x28) = r30;
       *(r31 + 0x18) = r0;
       *(r31 + 0x10) = r1;
       *(r31 + 0xc) = zero_extend_64(0x1);
       *(r31 + 0x8) = zero_extend_64(0x2);
       if (*(r31 + 0xc) == *(r31 + 0x8)) {
               r0 = _test1(zero_extend_64(0x1) & 0x1);
       }
       else {
               r0 = _test2(zero_extend_64(0x0) & 0x1);
       }
       return r0;
    }
    
    Block的反汇编

    日常开发中,Block有时作为参数传递,有时作为返回值,但大多数情况下,Block会作为方法的参数回调

    在逆向分析中,需要定位Blockinvoke。只有找到invoke,才能找到回调方法中的代码逻辑

    案例1:

    查看GlobalBlock的汇编代码

    打开ViewController.m,写入以下代码:

    #import "ViewController.h"
    
    @implementation ViewController
    
    - (void)viewDidLoad {
    //    [super viewDidLoad];
    
       void(^block)(void) = ^() {
           NSLog(@"block");
       };
       
       block();
    }
    
    @end
    

    真机运行项目,来到viewDidLoad方法

    使用register read x0命令,查看x0寄存器

    x0 = 0x00000001029d8028  002--OC`__block_literal_global
    
    • x0存储的是一个GlobalBlock
    • Block内部不使⽤外部变量,或者只使⽤静态变量和全局变量,会形成一个GlobalBlock,它位于全局区Block

    使用po 0x00000001029d8028命令,打印内存中的数据

    <__NSGlobalBlock__: 0x1029d8028>
    signature: "v8@?0"
    invoke   : 0x1029d6384 (/private/var/containers/Bundle/Application/2B6AC788-34A7-46BB-A44B-2237AFD48A2A/002--OC.app/002--OC`__29-[ViewController viewDidLoad]_block_invoke)
    
    • NSGlobalBlockBlockisa
    • invoke : 0x1029d6384是代码实现的所在位置

    来到libclosure源码

    打开Block_private.h文件,找到Block的定义

    struct Block_layout {
       void *isa;
       volatile int32_t flags;
       int32_t reserved;
       BlockInvokeFunction invoke;
       struct Block_descriptor_1 *descriptor;
    };
    
    • Block是一个结构体
    • isa8字节flagsreserved共占8字节
    • 16字节之后,就是invoke,最后是descriptor描述

    回到汇编代码

    使用x/8g 0x1029d8028,查看内存中的数据

    • 16字节之后,即:invoke,地址和上面打印的0x1029d6384一致

    Hopper中,找到Blockinvoke

    使用真机编译项目,将Mach-O文件拖到Hopper中,找到viewDidLoad函数

    • Hopper直接标记出___block_literal_global

    双击___block_literal_global,找到Block的结构体对象

    双击invoke,找到invoke中的代码逻辑

                        ___29-[ViewController viewDidLoad]_block_invoke:
    0000000100006384         sub        sp, sp, #0x20               ; Objective C Block defined at 0x100008028, DATA XREF=0x100008038
    0000000100006388         stp        x29, x30, [sp, #0x10]
    000000010000638c         add        x29, sp, #0x10
    0000000100006390         str        x0, [sp, #0x8]
    0000000100006394         str        x0, sp
    0000000100006398         adrp       x0, #0x100008000            ; argument #1 for method imp___stubs__NSLog
    000000010000639c         add        x0, x0, #0x48               ; @"block"
    00000001000063a0         bl         imp___stubs__NSLog
    00000001000063a4         ldp        x29, x30, [sp, #0x10]
    00000001000063a8         add        sp, sp, #0x20
    00000001000063ac         ret
                           ; endp
    

    案例2:

    查看StackBlock的汇编代码

    打开ViewController.m,写入以下代码:

    - (void)viewDidLoad {
    //    [super viewDidLoad];
    
       int a = 1;
       void(^block)(void) = ^() {
           NSLog(@"block:%i", a);
       };
       
       block();
    }
    

    真机运行项目,来到viewDidLoad方法


    标记的第1段代码

       0x1021e62dc <+28>:  add    x9, sp, #0x8              ; =0x8 
       0x1021e62e0 <+32>:  adrp   x10, 2
       0x1021e62e4 <+36>:  ldr    x10, [x10]
       0x1021e62e8 <+40>:  str    x10, [sp, #0x8]
    
    • sp + #0x8的地址,赋值给x9
    • adrp指令,计算地址为0x1021e8000,赋值给x10
    • x10寻址,将值再写入x10
    • x10入栈sp + #0x8的位置,此时x9成为指针,指向x10

    使用x/8g 0x1021e8000,查看内存中的数据

    0x1021e8000: 0x00000001f0a50830 0x000000019b84ef94
    0x1021e8010: 0x0000000000000000 0x0000000000000024
    0x1021e8020: 0x00000001021e6a26 0x00000001021e6ad2
    0x1021e8030: 0x00000001f101a280 0x00000000000007d0
    

    使用po 0x00000001f0a50830命令,打印第一个8字节数据

    __NSStackBlock__
    
    • 存储的是一个StackBlock
    • 在内部使⽤局部变量或者OC属性,但是不能赋值给强引⽤或者
      Copy修饰的变量,它位于栈区Block

    标记的第2段代码

       0x1021e62f8 <+56>:  adrp   x10, 0
       0x1021e62fc <+60>:  add    x10, x10, #0x358          ; =0x358
    
    • 使用adrp + add指令,计算地址为0x1021e6358,赋值x10
    • 此处疑似是Blockinvoke

    使用dis -s 0x1021e6358指令,查看invoke的汇编代码

    002--OC方法的本质`__29-[ViewController viewDidLoad]_block_invoke:
       0x1021e6358 <+0>:  sub    sp, sp, #0x30             ; =0x30 
       0x1021e635c <+4>:  stp    x29, x30, [sp, #0x20]
       0x1021e6360 <+8>:  add    x29, sp, #0x20            ; =0x20 
       0x1021e6364 <+12>: stur   x0, [x29, #-0x8]
       0x1021e6368 <+16>: str    x0, [sp, #0x10]
       0x1021e636c <+20>: ldr    w8, [x0, #0x20]
       0x1021e6370 <+24>: mov    x0, x8
       0x1021e6374 <+28>: adrp   x9, 2
    

    Hopper中,查看StackBlock

    使用真机编译项目,将Mach-O文件拖到Hopper中,找到viewDidLoad函数

    • Hopper直接标记出invoke的位置
    • invoke下面是descriptor

    双击_block_invoke,找到invoke中的代码逻辑

                        ___29-[ViewController viewDidLoad]_block_invoke:
    0000000100006358         sub        sp, sp, #0x30                ; DATA XREF=-[ViewController viewDidLoad]+60
    000000010000635c         stp        x29, x30, [sp, #0x20]
    0000000100006360         add        x29, sp, #0x20
    0000000100006364         stur       x0, [x29, #-0x8]
    0000000100006368         str        x0, [sp, #0x10]
    000000010000636c         ldr        w8, [x0, #0x20]
    0000000100006370         mov        x0, x8
    0000000100006374         adrp       x9, #0x100008000
    0000000100006378         add        x9, x9, #0x30                ; cfstring_b
    000000010000637c         str        x0, [sp, #0x8]
    0000000100006380         mov        x0, x9
    0000000100006384         mov        x9, sp
    0000000100006388         ldr        x10, [sp, #0x8]
    000000010000638c         str        x10, x9
    0000000100006390         bl         imp___stubs__NSLog
    0000000100006394         ldp        x29, x30, [sp, #0x20]
    0000000100006398         add        sp, sp, #0x30
    000000010000639c         ret
                           ; endp
    

    通过汇编代码和invoke的位置来看,StackBlock案例1中的GlobalBlock区别很大

    总结

    OC的反汇编

    • 出现objc_msgSend,表示调用了一个OC方法,读取x0x1便可知道调用了哪个对象的哪个方法
    • 初始化的allocinit函数,在iOS12.2及更高版本,优化为objc_alloc_init函数
    • objc_storeStrong函数,对一个strong修饰的对象retain,对老对象release。有些情况下,也会直接释放对象

    工具反汇编

    • 运用Hopper工具,查看汇编代码、流程图、伪码模式
    • Hopper可以解析出方法、属性等名称,作为注释标记给开发者,本质上也是通过Mach-O读取相应数据

    Block的反汇编

    • Block的类型:全局区、堆区、栈区。重点掌握全局区和栈区Block的分析
    • 在逆向分析中,需要定位Blockinvoke。只有找到invoke,才能找到回调方法中的代码逻辑

    了解更多汇编指令

    相关文章

      网友评论

          本文标题:iOS逆向实战--008:反汇编

          本文链接:https://www.haomeiwen.com/subject/pvlikltx.html