iOS逆向 04:OC反汇编(下)

简介: iOS逆向 04:OC反汇编(下)

本文主要理解OC对象反汇编,以及block常见类型的反汇编


OC反汇编


创建一个Person类,并在main函数中初始化一个Person对象

@interface Person : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) int age;
+ (instancetype)person;
@end
@implementation Person
+ (instancetype)person{
    return [[self alloc] init];
}
@end
<!--main.m中-->
int main(int argc, char * argv[]) {
    Person *p = [Person person];
    return 0;
}

运行,查看其汇编代码

image.png


1、静态调试


通过adrp+add获取地址,分别读取x0,x1


  • 读取x0,读出来是Person:x 0x100c68eb0 + po 0x0100c68f98

image.png

读取x1,读取出来是person方法:x 0x100c68e88 + p (SEL)0x01c019aef5

image.png


2、动态调试


通过一步一步执行汇编,来验证x0、x1是否如静态调试的结果一致?

image.png

通过调试发现,是一致的,其实这里的x0、x1就是 objc_msgSend的隐藏参数(id self,SEL _cmd)


下面我们继续调试汇编


  • 点击step into,直接进入[Person person]方法(注意:这里不同iOS版本,多看到的汇编代码是有所区别的)
  • 从这里看到ios13.4系统的alloc、init并不会走objc_msgSend

image.png

ios11版本中,可以看到objc_msgSend,其本质是在调用init方法

image.png

动态调试进行验证,结果如下所示,是一致的

image.png

查看此时的x0,已经是一个实例对象,因为alloc开辟了内存,已经分配了空间,具体的内部实现可以查看这篇文章iOS-底层原理 02:alloc & init & new 源码分析

image.png

  • 疑问:为什么版本不同,调用不一样呢?
  • 在不同的版本下,系统在运行时是不一样的。因为系统对alloc 、init进行了优化
  • 接着往下看,点击step out 跳出[Person person],此时返回值在x0中

image.png

执行到bl ... objc_storeStrong,objc_storeStrong是OC中用strong修饰的对象底层都是调用这个函数,详情可以看这篇文章iOS-底层原理 10:strong©&weak底层分析 以及 方法签名和attribute简写含义


疑问:我们此时并没有使用strong修饰?:此时的局部变量p在此时就相当于一个强引用,是默认的。且这个方法执行完成后,相当于销毁p

image.png


查看此时的x0、x1,相当于objc_storeStrong(&p,nil),将nil进行retain,将nil等于p(即 p=nil),p进行释放

image.png

  • 查看objc_storeStrong源码
  • 目的:对一个strong修饰的对象进行retain +1,对一个老的对象进行release
  • 为什么是指针? 因为函数是值传递,而函数内部需要修改p的值
/*
 - id *location 指向对象的指针  本质上是 &p(即局部变量地址)
 - id obj 对象
 目的:对一个strong修饰的对象进行retain +1,对一个老的对象进行release
 为什么是指针? 因为函数是值传递,而函数内部需要修改p的值
 */
void
objc_storeStrong(id *location, id obj)
{
    //prev 相当于p ,因为location是 &p
    id prev = *location;
    //第二个参数 == 第一个参数,直接return
    if (obj == prev) {
        return;
    }
    //retain+1
    objc_retain(obj);
    //修改p的值,指向第二个对象
    *location = obj;
    //释放老对象
    objc_release(prev);
}

相当于


Person *p = p1;
p = p2;//此时p1释放,p2retain+1

所以以上汇编中的objc_storeStrong(&p,nil)的实现代码如下


objc_storeStrong(&p,nil){
    id prev = p;
    if nil == p{
        return;
    }
    objc_retain(nil);
    p = nil;//指针指向nil
    objc_release(p);//释放堆空间
}


下面来进行动态验证,发现Person对象指向nil

image.png

[[self alloc] init] 优化过程


  • 在最初的版本(iOS9)中,相当于两次消息发送 objc_msgSend
  • iOS11版本 是一次消息发送 objc_alloc + objc_msgSend
  • iOS13.5.1以上版本,已经没有objc_msgSend,而是objc_alloc_init

image.png

以上是LLDB动态调试Person *p = [Person person]; //objc_msgSend x0,x1


通过工具看复杂的OC代码


在上述OC代码的基础上增加一些代码,然后再来静态分析

int main(int argc, char * argv[]) {
    Person *p = [Person person]; //objc_msgSend x0,x1
    p.name = @"CJL";
    p.age = 18;
    return 0;
}
  • CMD + B 编译程序,生成mach-o文件,并找到该文件
  • 通过Hopper反汇编mach-o文件,main函数的分析如下

image.png

双击objc_cls_ref_Person,查看p的地址,是000000010000ce88,是在Data段

image.png

通过MachOView打开mach-o分析,查找000000010000ce88,与Hopper中的显示是一致的

image.png

双击@selector(person),查看person方法的反汇编

image.png

双击0x10000cc68

image.png

双击“person”,地址为 0x10000752a

image.png

在mach中查找0x10000752a,所有方法的name都在CString

image.png


Block反汇编


定义一个block

int main(int argc, char * argv[]) {
    void(^block)(void) = ^(){
        NSLog(@"block");
    };
    block();
    return 0;
}

反汇编分析block的目的是想快速定位block的invoke,因为invoke中是实现代码,以下是block的汇编代码

image.png

查看x0是什么?:是一个__block_literal_global,是一个全局静态block(即block不引用block外部变量,在编译时期就可以确定内存的分配等操作,存在于可执行文件的常量区),其他详情也可查看iOS-底层原理 30:Block底层原理这篇文章


image.png

  • 以下是源码中block的定义,是一个结构体


struct Block_layout{
    void *isa;
    volatile int32_t flags; //contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    //imported variables
};

然后动态调试查看block的内存结构


image.png

是否可以通过hopper查看 adrp + add 是一个block?

答案是可以的

image.png

双击___block_literal_global

image.png

双击0x0000000100006838,查看invoke

image.png

双击0x0000000100008008,查看descriptor,和Block的源码结构类似

image.png

如果block引用了外部变量呢?


定义一个block,其中block引用了外部变量,查看此时的汇编代码

int main(int argc, char * argv[]) {
    int a = 10;
    void(^block)(void) = ^(){
        NSLog(@"block -- %d", a);
    };
    block();
    return 0;
}

1、lldb调试


  • 以下是代码的汇编

image.png

验证是否是block的isa指针

image.png

  • adrp x10, 2 获取指针地址
  • ldr x10, [x10]:取值
  • 查看此时block的内存,找到invoke(由于invoke是代码实现,所以需要由dis -s(将代码的汇编打印出来)查看)

image.png

2、静态分析


  • 通过hopper静态分析如下,以下是main函数的反汇编

image.png

双击___main_block_invoke,跳转至invoke的具体实现(并没有在main函数中,是单独的实现)

image.png双击___block_descriptor_36_e5_v8�?0l,是一个单独的描述


image.png


总结


  • [[self alloc] init] 优化过程
  • 在最初的版本(iOS9)中,相当于两次消息发送 objc_msgSend
  • iOS11版本 是一次消息发送 objc_alloc + objc_msgSend
  • iOS13.5.1以上版本,已经没有objc_msgSend,而是objc_alloc_init
  • 反汇编分析方式:
  • 通过LLDB动态调试
  • 通过Hopper + MachOView 静态分析


相关文章
|
Swift iOS开发
iOS OC混编Swift 后者无法走断点
iOS OC混编Swift 后者无法走断点
91 0
|
安全 数据安全/隐私保护 iOS开发
iOS 初探代码混淆(OC)
自己做iOS开发也有几年的时间了,平时做完项目基本就直接打包上传到Appstore上,然后做上架操作了。但是最近,客户方面提出了代码安全的要求。说是要做代码混淆,这方面的工作之前从来没有接触过。然后就上网查了一下,原来有很多应用程序都做了代码混淆。看来是我固步自封了......
iOS 初探代码混淆(OC)
|
存储 Unix 编译器
|
存储 算法 iOS开发
|
数据安全/隐私保护 iOS开发
iOS逆向小技能:解锁无密码的设备、判断设备是否锁定、锁定设备、打开某个程序
介绍lua 函数: runApp、closeApp、getScreenSize、getDeviceID、lua_exit、isFrontApp。
261 0
|
Swift iOS开发 容器
iOS 仿支付宝银行卡界面(支持Swift/OC)
在有支付相关的APP中,都有对应的钱包,虽然现在的支付宝,微信支付很流行,但是都是需要绑定自己的银行卡,那么这个银行卡的卡包页面该怎么实现呢?在网上找了许久也没有找到合适的,那就索性自己造轮子。
392 0
|
安全 算法 开发工具
iOS逆向-day11:代码混淆
iOS逆向-day11:代码混淆
845 0
iOS逆向-day11:代码混淆
|
编译器 API iOS开发
iOS逆向-day10:LLVM 编译器(下)
iOS逆向-day10:LLVM 编译器(下)
400 0
iOS逆向-day10:LLVM 编译器(下)
|
自然语言处理 前端开发 IDE
iOS逆向-day10:LLVM 编译器(上)
iOS逆向-day10:LLVM 编译器
224 0
iOS逆向-day10:LLVM 编译器(上)
|
安全 数据安全/隐私保护 iOS开发
iOS逆向-day9:签名机制(下)
iOS逆向-day9:签名机制(下)
343 0
iOS逆向-day9:签名机制(下)