感觉是要搞个《逆向分析及修复app》系列的节奏啊
故事概要
大家好,我又来了。上次给大家分享了《逆向及修复最新iOS版少数派客户端的闪退bug》,@了一些iOS界的大神,没想到受到了大家的转发和关注,还真有点小激动。同时也在微博的评论下收到了稀土掘金的欢迎,还给我开了专栏权限,希望可以到稀土掘金上分享写作。备感荣幸的同时,突然发现,一直在稀土掘金上面看文章的我,一直没有注册账户(好像注册了但是忘了密码,在1Password中也没有记录,看来是很久之前登陆过了)。这也是这次故事的开始,收到稀土掘金的评论后,马上打开了早已不知道什么时候下载的 掘金 app。果然,没有登陆记录,作为开发者马上用 GitHub 授权进行登陆,尴尬的事情发生了,在授权成功后,app闪退了。好尴尬啊,好吧稀土掘金上的处女座就献给你了。
跟之前写《逆向及修复最新iOS版少数派客户端的闪退bug》文章时不同,在文章写到这里的时候,我其实还没有完全分析完崩溃的原因。我是分析途中决定开始写这篇文章的,决定一边分析一遍写,这样才会尽可能的保留分析的全过程,当然最后能不能分析到关键点,且看下去吧,因为我也不知道。
问题描述
- 最新版 4.1.3 稀土掘金 app 在使用 github 登陆成功后会 crash
- 是在登陆成功后才 crash,因为再次进入后会发现已经登陆成功
- 可能不是所有人都会遇到这个bug,根据后来的分析,可能是Github的某个个人信息中缺少某些设置导致的授权后crash
着手分析
找到崩溃原因
分析一个bug的开始,当然是从崩溃原因找起,前一篇文章,我们使用了lldb调试,在触发 app 崩溃的时候,从 Terminal 找到了关键词:EXE_BAD_ACCESS
。这次,我们使用最简单的,查看系统日志来定位崩溃原因,使用最新的 Mac,打开控制台,连接手机并清空多余的系统日志打印,然后触发崩溃,观察控制台中输出的内容(如果输出日志过多,可以使用进程名Xitu
作为关键词,进行过滤)。如果可以还是不要使用关键词过滤,因为不是所有的崩溃都是由于 app 本身的问题导致的,正如签名破坏的 app 在没有越狱的手机上会安装失败,这个安装失败的原因查找就需要在日志中的 SpringBoard
进程打印中进行查找
(SpringBoard
是Apple
用来管理我们iPhone应用的一个应用,可以理解为我们 PC 端的桌面,它还负责应用的桌面排列、管理通知中心等)。
正如我们希望的一样,崩溃的原因最常见不过了:
这个崩溃就很有意思了,开发者对NSNull
调用了length
方法,因为找不到该方法而崩溃!当然开发者肯定是不会主动对NSNull
类型调用length
方法,猜想:
- 开发不知道对象会变成
NSNull
类型,说明这个对象可能是在运行时确定的 - 调用
length
,说明开发者认为这个类应该是属于NSString
类型的
大胆猜测:结合这个崩溃是发生在登陆的时候,需要从网络获取登陆信息,那么会不会是程序在对获取到的登陆信息进行处理的时候导致的崩溃(比如登陆用户的一些信息没有设置,所以程序从登陆信息里取到的是空值)。
分析入口
1.找到登陆界面的控制类
分析从该登陆界面出发,使用cycript注入app后,打印找到该界面的控制器:
susnms-iPhone:~ root# ps -e | grep Xitu
24240 ?? 0:00.00 (Xitu.Widget)
24241 ?? 0:00.00 (XituShare)
24242 ?? 0:00.00 (Xitu)
24283 ?? 0:02.03 /var/containers/Bundle/Application/994C4217-88DD-4F55-A016-55BDD5998C49/Xitu.app/Xitu
24288 ttys000 0:00.01 grep Xitu
susnms-iPhone:~ root# cycript -p 24283 common.cy ; cycript -p 24283
cy# currentVCWithKeyWindow ()
#"<XTGithubLoginViewController: 0x15e0ef340>"
cy#
currentVCWithKeyWindow() 来自我自己写的一个脚本,你可以前往common.cy下载。下载之后,将其放到
/var/root
目录下即可。
使用时,只需要在cycript注入的时候加上即可。
其中还有一些好用的函数,比如快速打印UIControl
所有的target
和action
2.查看登陆界面的调用流程
使用class-dump
等工具,导出app
的所有类头文件,使用logify.pl
工具生成XTGithubLoginViewController
类的所有hook
函数,使用theos
编写安装插件后,触发崩溃,然后在控制台查找该类的函数调用逻辑。如下:
可以发现,程序是在-[XTGithubLoginViewController viewWillDisappear:]
调用之后才收到的崩溃的通知。所以很有可能崩溃的原因不是XTGithubLoginViewController
导致的,而是在XTGithubLoginViewController
类返回登陆信息后。
仔细研究XTGithubLoginViewController
的调用过程,发现了几个有意思的方法,调用顺序如下:
- -(void)setCallBack:
- -(id)onAuthCompleted:
- -(id)callBack
callback
!太熟悉不过了,这不就是我们经常在开发中使用的设置回调block
的时候使用的参数名吗!!并且,该callback
在类初始化的时候被设置,类方法-(id)onAuthCompleted:
调用之后才被回调。结合这个方法名onAuthCompleted
,这个逻辑是不是很像:我们在登陆成功之后,使用block传回了我们的登陆信息进行处理。
分析到这里,其实我们已经可以使用lldb调试,打印该block
的内存地址,然后去掉内存地址偏移后,在Hopper
中查看block的实现,看是否该block导致的崩溃。但是作为逆向的学习,我们可以先继续深入,查看一下这个block的传递调用过程。
继续深入callback
的回调过程
既然该回调是在XTGithubLoginViewController
被初始化的时候被设置的,那么我们先找到是谁初始化的该Github
登陆控制器:
使用cycript
注入打印该控制器类名,并尝试github
的授权登录入口:
susnms-iPhone:~ root# cycript -p Xitu common.cy ; cycript -p Xitu
cy# currentVCWithKeyWindow ()
#"<XTLoginViewController: 0x12e43f740>"
cy# [#0x12e43f740 github
githubBt githubLogin:
cy# [#0x12e43f740 githubLogin: nil]
发现成功触发登录,所以可以确定githubLogin:
方法确实是登录的入口
1.githubLogin:
实现
在Hopper
中查看该方法的实现:
-[XTLoginViewController githubLogin:]:
0000000100194c24 sub sp, sp, #0x50 ; Objective C Implementation defined at 0x1009d2708 (instance method), DATA XREF=0x1009d2708
0000000100194c28 stp x20, x19, [sp, #0x30]
0000000100194c2c stp x29, x30, [sp, #0x40]
0000000100194c30 add x29, sp, #0x40
0000000100194c34 mov x19, x0
0000000100194c38 adrp x8, #0x100ab4000 ; @selector(detectSocketStatus)
0000000100194c3c ldr x0, [x8, #0x878] ; objc_cls_ref_LoginCloud,__objc_class_LoginCloud_class
0000000100194c40 adrp x8, #0x100aa0000 ; @selector(computeTime:)
0000000100194c44 ldr x1, [x8, #0xd0] ; "singleClass",@selector(singleClass)
0000000100194c48 bl imp___stubs__objc_msgSend ; login = [LoginCloud singleClass]
0000000100194c4c mov x29, x29
0000000100194c50 bl imp___stubs__objc_retainAutoreleasedReturnValue
0000000100194c54 mov x20, x0
0000000100194c58 adrp x8, #0x100914000
0000000100194c5c ldr x8, [x8, #0x448] ; __NSConcreteStackBlock_100914448,__NSConcreteStackBlock
0000000100194c60 str x8, [sp, #0x8]
0000000100194c64 movz w8, #0xc200
0000000100194c68 stp w8, wzr, [sp, #0x10]
0000000100194c6c adr x8, #0x100194cc4
0000000100194c70 nop
0000000100194c74 str x8, [sp, #0x18]
0000000100194c78 adrp x8, #0x100923000
0000000100194c7c add x8, x8, #0x9d0 ; 0x1009239d0
0000000100194c80 str x8, [sp, #0x20]
0000000100194c84 mov x0, x19
0000000100194c88 bl imp___stubs__objc_retain
0000000100194c8c str x0, [sp, #0x28]
0000000100194c90 adrp x8, #0x100aa7000 ; @selector(isTableViewScrolledToBottom)
0000000100194c94 ldr x1, [x8, #0x848] ; "githubLoginCallback:",@selector(githubLoginCallback:)
0000000100194c98 add x2, sp, #0x8
0000000100194c9c mov x0, x20
0000000100194ca0 bl imp___stubs__objc_msgSend ; [login githubLoginCallback: block]
0000000100194ca4 mov x0, x20
0000000100194ca8 bl imp___stubs__objc_release
0000000100194cac ldr x0, [sp, #0x28]
0000000100194cb0 bl imp___stubs__objc_release
0000000100194cb4 ldp x29, x30, [sp, #0x40]
0000000100194cb8 ldp x20, x19, [sp, #0x30]
0000000100194cbc add sp, sp, #0x50
0000000100194cc0 ret
内容很简单,调用了单例类,并传入回调callback参数:
LoginCloud *login = [LoginCloud singleClass];
[login githubLoginCallBack: callback];
2.[LoginCloud singleClass]实现
在Hopper
查看如下:
-[LoginCloud githubLoginCallback:]:
sub sp, sp, #0x80 ; Objective C Implementation defined at 0x1009c7cc8 (instance method), DATA XREF=0x1009c7cc8
stp x26, x25, [sp, #0x30]
stp x24, x23, [sp, #0x40]
stp x22, x21, [sp, #0x50]
stp x20, x19, [sp, #0x60]
stp x29, x30, [sp, #0x70]
add x29, sp, #0x70
mov x22, x0
mov x0, x2
bl imp___stubs__objc_retain
mov x21, x0
adrp x8, #0x100ab4000 ; @selector(detectSocketStatus)
ldr x0, [x8, #0x6a0] ; objc_cls_ref_UIStoryboard,_OBJC_CLASS_$_UIStoryboard
adrp x8, #0x100aa0000 ; @selector(computeTime:)
ldr x1, [x8, #0x788] ; "storyboardWithName:bundle:",@selector(storyboardWithName:bundle:)
adrp x2, #0x100949000 ; @"%@ %@ %@"
add x2, x2, #0x500 ; @"Login"
movz x3, #0x0
bl imp___stubs__objc_msgSend ; loginSb = [UIStoryboard storyboardWithName: @"Login" bundle: nil]
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x19, x0
adrp x8, #0x100aa0000 ; @selector(computeTime:)
ldr x1, [x8, #0x790] ; "instantiateViewControllerWithIdentifier:",@selector(instantiateViewControllerWithIdentifier:)
adrp x2, #0x10094f000 ; @"nickname"
add x2, x2, #0xd00 ; @"githubVC"
bl imp___stubs__objc_msgSend ; githubVC = [loginSb instantiateViewControllerWithIdentifier: @"githubVC"]
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x20, x0
adrp x8, #0x100914000
ldr x8, [x8, #0x448] ; __NSConcreteStackBlock_100914448,__NSConcreteStackBlock
str x8, sp
movz w8, #0xc200
stp w8, wzr, [sp, #0x8]
adr x8, #0x10015ccbc
nop
str x8, [sp, #0x10]
adrp x8, #0x100921000
add x8, x8, #0x6f0 ; 0x1009216f0
stp x8, x21, [sp, #0x18]
mov x0, x21
bl imp___stubs__objc_retain
mov x21, x0
mov x0, x22
bl imp___stubs__objc_retain
str x0, [sp, #0x28]
adrp x8, #0x100aa6000 ; @selector(hasText)
ldr x1, [x8, #0x440] ; "setCallBack:",@selector(setCallBack:)
mov x2, sp
mov x0, x20 ; [githubVC setCallBack: black]
bl imp___stubs__objc_msgSend
adrp x8, #0x100ab4000 ; @selector(detectSocketStatus)
ldr x0, [x8, #0x938] ; objc_cls_ref_UINavigationController,_OBJC_CLASS_$_UINavigationController
adrp x8, #0x100a9f000
ldr x1, [x8, #0xd78] ; "alloc",@selector(alloc)
bl imp___stubs__objc_msgSend
adrp x8, #0x100aa1000 ; @selector(setUpdatedAt:)
ldr x1, [x8, #0x948] ; "initWithRootViewController:",@selector(initWithRootViewController:)
mov x2, x20
bl imp___stubs__objc_msgSend
mov x22, x0
adrp x8, #0x100ab4000 ; @selector(detectSocketStatus)
ldr x0, [x8, #0x820] ; objc_cls_ref_UIApplication,_OBJC_CLASS_$_UIApplication
adrp x8, #0x100aa1000 ; @selector(setUpdatedAt:)
ldr x1, [x8, #0x2d8] ; "sharedApplication",@selector(sharedApplication)
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x23, x0
adrp x8, #0x100a9f000
ldr x1, [x8, #0xf88] ; "delegate",@selector(delegate)
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x24, x0
adrp x8, #0x100aa1000 ; @selector(setUpdatedAt:)
ldr x1, [x8, #0x930] ; "window",@selector(window)
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x25, x0
adrp x8, #0x100aa1000 ; @selector(setUpdatedAt:)
ldr x1, [x8, #0x938] ; "rootViewController",@selector(rootViewController)
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x26, x0
mov x0, x25
bl imp___stubs__objc_release
mov x0, x24
bl imp___stubs__objc_release
mov x0, x23
bl imp___stubs__objc_release
adrp x8, #0x100aa0000 ; @selector(computeTime:)
ldr x1, [x8, #0x220] ; "presentViewController:animated:completion:",@selector(presentViewController:animated:completion:)
orr w3, wzr, #0x1
mov x0, x26
mov x2, x22
movz x4, #0x0
bl imp___stubs__objc_msgSend
mov x0, x26
bl imp___stubs__objc_release
mov x0, x22
bl imp___stubs__objc_release
ldr x0, [sp, #0x28]
bl imp___stubs__objc_release
ldr x0, [sp, #0x20]
bl imp___stubs__objc_release
mov x0, x21
bl imp___stubs__objc_release
mov x0, x20
bl imp___stubs__objc_release
mov x0, x19
bl imp___stubs__objc_release
ldp x29, x30, [sp, #0x70]
ldp x20, x19, [sp, #0x60]
ldp x22, x21, [sp, #0x50]
ldp x24, x23, [sp, #0x40]
ldp x26, x25, [sp, #0x30]
add sp, sp, #0x80
ret
解释如下:
UIStoryboard *loginSb = [UIStoryboard storyboardWithName: @"Login" bundle: nil]
UIViewController *githubVC = [loginSb instantiateViewControllerWithIdentifier: @"githubVC"]
[githubVC setCallBack: black]
UINavigatinController *nav = [[UINavigatinController alloc] initWithRootViewControler: githubVC];
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
[vc presentViewController: nav animaed: YES completion: nil];
- 初始化
UIStoryBoard
,并从中初始化控制器githubVC
- 给
githubVC
控制器设置回调block(另一个callback参数回调) - 显示控制器
其实这里的githubVC
应该就是XTGithubLoginViewController
类型,我们可以使用cycript
确认一下:
cy# var sb = [UIStoryboard storyboardWithName: @"Login" bundle: nil]
#"<UIStoryboard: 0x12e0ebf00>"
cy# [#0x12e0ebf00 instantiateViewControllerWithIdentifier: @"githubVC"]
#"<XTGithubLoginViewController: 0x12e382e00>"
现在callback
的传递应该很清楚了:点击 github 登录按钮时,调用单例LoginCloud
,并传入callback
,LoginCloud
根据传入的callback
初始化登录控制器XTGithubLoginViewController
,并设置另外的一个callback
回调,XTGithubLoginViewController
在登陆成功后,通过该callback
进行回调。
分析callback实现
block有点特殊,它也是一个对象类型。在这里我们涉及到了两个block的传递,为了方便之后的分析,我们可以先将两个block的实现地址和传递的参数类型都打印出来。接下来的操作中可能会因为程序多次重启导致文中上下显示的内存地址不一致的情况,我每次都会进行说明。
1.获取block的函数地址
因为我们知道这个callback
作为参数传给了-[LoginCloud githubLoginCallback:]
方法,所以lldb连接服务(如何lldb可以看我的第一篇文章)后,我们在这个方法上打个断点,同理设置-[XTGithubLoginViewController setCallBack:]
断点。
根据Block
的内存结构可以查找block
的函数实现地址和参数返回值类型,具体可以看这篇文章。开源就是好,节省步骤,我们可以使用facebook
开源的chisel。触发断点后,获取到block
的内存地址后,使用pblock
打印block:
获取到第一个 block 的函数实现地址:0x0000000100194cc4
参数及返回值:void ^(bool, NSString *)
获取到第二个 block 的函数实现地址:0x000000010015ccbc
参数及返回值:void ^(NSDictionary *, ThirdLoginModel *, NSDictionary *)
。
2.获取block
的具体传回参数值
知道了 block 的参数的类型,那么我们可以写个 tweaks
来 hook
这个 block 然后打印一下,传递的这些参数内容,根据回传顺序,我们先打印第二个block
:
%hook XTGithubLoginViewController
-(callBackType)callBack {
callBackType block = ^(NSDictionary *dic1, ThirdLoginModel *model, NSDictionary *dic2) {
HBLogInfo(@"dic1: %@, model: %@, dic2: %@", dic1, [model debugDescription], dic2);
};
return block;
}
%end // end hook
打印结果如下:
果然发现第二个字典中,出现了多个值为null
的value
,很有可能就是我们要找的点。那么这个字典是从哪里来的呢?想起当时打印XTGithubLoginViewController
类的调用顺序的时候,发现的一个函数onAuthCompleted:
,它返回的就是一个这样的字典,那么我们hook
一下方法,过滤掉这些值为null
的value
看看:
-(NSDictionary *)onAuthCompleted:(id)arg1 {
HBLogInfo(@"%s", __func__);
NSDictionary *result = %orig;
NSMutableDictionary *dict = [result mutableCopy];
for (NSString *key in result.allKeys) {
if ([result[key] isKindOfClass:[NSNull class]]) {
HBLogInfo(@"%s key: %@", __func__, key);
[dict removeObjectForKey:key];
}
}
HBLogInfo(@"result: %@", dict);
return dict;
}
打包,安装后发现,并没有修复这个bug,还是报找不到length
方法的日志。看来我们还没有找到 bug 点。
3.获得 block 的具体实现过程
在第二个回调上下断点,触发后,进入实现内部,根据lldb打印出实现内部的每个方法的方法调用者和函数名、参数值。过程如下(主要是找到了这个block
的实现地址后,在Hopper中找到这个代码块,可以发现对于这个 block 的内部方法,Hopper是没有注释调用者、selector、参数的,我们可以对Hopper中的这些代码中的所有显示为objc_msgSend
的地方下一个断点,一一触发,分别打印每个方法的调用者和调用方法,传递参数等信息),主要过程如下:
解释后主要是以下过程:
// swift class
ZEHud *hud = [Xitu.ZEHud sharedInstance];
[hud showHud];
[AVUser loginWithAuthData: dict platform: @"github" block: block] // block Imp: 0x000000010015cdf8 Signature: void ^(AVUser *, NSError *);
可以看到,其中也有一个block
,打印一下block的内容,根据Xitu
image的偏移得到block实现的地址:0x100208df8-0x00000000000ac000=0x10015CDF8
,在Hopper中找到:
在这个block上下断点,c
运行后来到该断点出:si
进入该实现内部,断点定位到如图中的唯一一个objc_msgSend
方法上,获取其调用信息:
可以看到该方法-[LoginCloud thirdLogRefreshCurrentUserDatatype:ThirdData:Error:CallBack:]
中还有一个block
参数,继续打印:
可以发现得到的block
:
0x0000000100194cc4
,void ^(bool, NSString *)
。
和我们得到的第一个block
是同一个。
既然第二个block
参数还没有分析完,第一个block
已经迫不及待的回来了,那么我们先在Hopper中查看一下这个block的实现:
分析到这里ni
后app不小心退出了,那么我们在这里重新启动,既然已经知道这个block
的地址,可以直接通过查看Hopper
内的每个objc_msgSend
的地址下断点:
可以知道该block的调用如下:
login = [LoginCloud singleClass];
[login refreshGithubLogin];
[XTLoginViewController callback];
既然分析到了这里,程序还没有崩溃,那么说程序这之前都没有问题,那么问题可能出在了XTLoginViewController
这个callback
中,那么我们打印一下这个block
:
得到:0x00000001001935d0
,void ^(bool, NSString *)
。
那么我们继续深入,打印一下这个block
的实现,步骤如上,如果不想每个objc_msgSend
都手动打断点,可以在这个block
上打断点,然后si
,进入后,一步一步向下执行,等到运行到objc_msgSend
的时候,打印这个方法的内容。但是在这里下到的断点都没有执行程序就已经崩溃了!!!说明什么?说明这个传入的callback
还没有执行,程序已经crash了。
- 从正向开发的角度,传入这个
block
,可能是在程序满足一定条件的时候才会回调这个block
,来传递信息或处理一些事情。 - 那么程序应该是崩溃在这个
block
被执行前,我们需要查看一下这个函数的内部实现。 - 可能后知后觉了,现在仔细想想,我前面说的分析到这个函数时,使用
ni
下一步后,程序不小心退出了!!,所以应该不是不小心退出了,而是这个函数的内部实现导致了崩溃。
确定bug点
分析到了这个方法-[LoginCloud thirdLogRefreshCurrentUserDatatype:ThirdData:Error:CallBack:]
,那么我们去Hopper
中看看它的内部实现:
果然,如图看到的,我们看到了之前在控制台输出的崩溃的关键词length
,既然找到了这里,我们利用这个调用函数的地址:0x10015d228
来下一个断点,看看调用者是谁,是不是真的是null,从而导致的bug。
重新启动,lldb下断点:
如图,调用者为null,并且ni
后,完美的崩溃了。文章写了这么多,终于找到这个bug点了!!!激动啊。
找到了bug点,那么我们应该研究一下如何修复这个bug,首先我们需要知道为什么开发者会用这个null,调用了length,程序发生了什么导致了这个调用者为null
。
我们来仔细看一下,这个函数调用所在的代码块的汇编代码:
loc_10015d1a8:
add x8, sp, #0xb0 ; CODE XREF=-[LoginCloud thirdLogRefreshCurrentUserDatatype:ThirdData:Error:CallBack:]+660
stp xzr, x8, [sp, #0xb0]
orr w8, wzr, #0x30
stp w28, w8, [sp, #0xc0]
adr x8, #0x10015d84c
nop
fmov d0, x8
adr x8, #0x10015d85c
nop
ins v0, x8
add x8, sp, #0xb0
stur q0, [x8, #0x18]
ldr x0, [x25, #0x618]
mov x24, x27
mov x1, x24
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x23, x0
adrp x2, #0x10094b000 ; @"Z"
add x2, x2, #0xc60 ; @"self_description"
mov x1, x26
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
str x0, [sp, #0xd8]
mov x0, x23
bl imp___stubs__objc_release
ldr x8, [sp, #0xb8]
ldr x0, [x8, #0x28]
adrp x8, #0x100aa0000 ; @selector(computeTime:)
ldr x1, [x8, #0x340] ; "length",@selector(length)
bl imp___stubs__objc_msgSend
ldr x20, [sp, #0x18]
cmp x0, #0x24
b.lo loc_10015d24c
可以看到,ldr x0, [x8, #0x28]
。这个null是从[x8, #0x28]
加载的。但是我们在这个代码块中(包括整个-[LoginCloud thirdLogRefreshCurrentUserDatatype:ThirdData:Error:CallBack:]
方法内)都没有找到。这个情况就比较特殊了,自从学习逆向以来,还是第一次碰到这种情况。该调用者是从一个从来没有被写入过内容的寄存器中读取的。应该也是因为这个原因才导致读取的内容为null吧。
换个思路,我们结合上下文找线索。我们翻译一下上下文方法调用的汇编代码:
AVUser *user = [AVUser currentUser];
NSString *name = [user valueForKey: @"username"];
NSString *headImg = [user ValueForKey: @"avatar_large"];
NSString *descrip = [user ValueForKey: @"self.description"];
//...
我们可以使用cycript
来打印一下这个user
是什么:
然后顺便把汇编中的内容,打印一下:
果然,其中有一个打印是null
。而且正好出现在length
调用的上方:
所以很有可能,这个bug是因为通过key
:self.description
从user
中获取了一个没有的值,并对他调用了length
导致的。
那么我们通过theos
创建一个插件,然后hook
一下这个方法,返回一个我们设置了值的AVUser
对象,
%hook AVUser
+(id)currentUser {
AVUser *user = %orig;
id descri = [user valueForKey: @"self_description"];
if ([descri isKindOfClass: [NSNull class]]) {
HBLogWarn(@"the value for key: self_description is null");
[user updateValue: @"" forKey: @"self_description"];
}
return user;
}
%end
安装后,再次登陆发现登陆成功,并没有crash
,终于修复了这个bug,发现已经写了不少字了。
总结
最后的修复bug的tweaks
可以在这里下载XituHook。
其实在我自己分析的时候,分析的内容还要多,也比较杂。逆向分析正是这样,我们需要顺着程序执行的顺序分析,但是事情往往不如我们想的这样简单,分析的岔路很多,特别是这里,涉及到了多个block
的回调,需要对block
的实现原理及其内存结果比较熟悉才行。
在分析的过程中,分析到的还有一些文中没有写到的block
和方法,并且在其中发现了length
关键词,激动不已啊,但是当我深入分析的时候发现,开发者都做了防护
if ([obj isKindOfClass:[NSNull class]]) {
obj = @"";
}
预防出现NSNull的情况,所以都不是 crash
的罪魁祸首。我猜测self_description
是获取的 github 上的某个个人信息(根据名字推测是自我介绍?)。如果想分析的话也可以,需要从AVUser
这个类如果获得这些信息开始分析。但是我想本文分享到这里已经差不多了。
写在最后
求工作,求工作,求工作,重要的事情说三遍。现在的iOS就业形势,不提也罢,兴趣所致,跪着走完。
网友评论
谢谢!