逆向分析及修复稀土掘金iOS版客户端闪退bug

感觉是要搞个《逆向分析及修复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 进程打印中进行查找
SpringBoardApple用来管理我们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所有的targetaction

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,并传入callbackLoginCloud根据传入的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 的参数的类型,那么我们可以写个 tweakshook 这个 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

打印结果如下:


果然发现第二个字典中,出现了多个值为nullvalue,很有可能就是我们要找的点。那么这个字典是从哪里来的呢?想起当时打印XTGithubLoginViewController类的调用顺序的时候,发现的一个函数onAuthCompleted:,它返回的就是一个这样的字典,那么我们hook一下方法,过滤掉这些值为nullvalue看看:

-(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的内容,根据Xituimage的偏移得到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是因为通过keyself.descriptionuser中获取了一个没有的值,并对他调用了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就业形势,不提也罢,兴趣所致,跪着走完。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容