Table of Content

在正文之前,先声明几个问题

  1. 不讨论逆向和盗版的界限,技术就是技术
  2. 不讨论 Surge 的定价,网上已经有很多讨论帖
  3. 不分享相关源代码,如果分享了,整个行为就变成了黑产,与第一条相悖
  4. 只讨论逆向工程的原理和思路,不过我讲的不一定对,毕竟刚接触逆向第二天

在进行逆向之前,你最好对汇编有点了解,不用精通,知道 寄存器SP 指针PC一些基本的运算符,这些基本就够了,剩下的水来土掩兵来将挡,用到的时候现学现卖呗

工具

我们要用到的工具大概有 lldbclass-dumpHopperRevealdumpdecrypted一台越狱的手机一台 Mac

上面说的不一定全,比如越狱手机需要装 sshCycript,等等,这些网上都有详细教程,我就不多说了

Generate Objective-C headers from Mach-O files.

顾名思义

Hopper is a reverse engineering tool for OS X and Linux, that lets you disassemble, and decompile your 32/64bits Intel Mac, Linux, Windows and iOS executables!

翻译成汉语就是这是一个反汇编工具,同样的工具还有 IDA,用法基本一样吧,不过我没用过

不得不提的是,Hopper 的 Lisence 只有 $89.00,已经很良心了,更良心的是不购买的话,免费版可以无限试用,只是每次只能用 30 分钟,对于刚入门的人,我觉得足够了

Reveal brings powerful runtime view debugging to iOS developers

Reveal 就不多说了,不光是逆向,正向开发也很有用

Dumps decrypted iPhone Applications to a file

三年前的祖传代码了,然而还能用。网上很多人说,9.1 SDK 编译的,向下兼容 9.0,言外之意不兼容更低的,实测是 10.0 SDK 编译的,能兼容到 9

实战

1. 砸壳

首先用 dumpdecrypted 砸壳,砸壳教程网上很多,基本都通用,编译好的动态库放到 document 目录的目的是,注入进程之后,仍然以该进程的用户来执行代码,所以能操作的目录只有 document

如果你想砸壳 Extension,也就是后缀 appex 的程序,就稍微麻烦一些了,因为 extension 进程不能独立运行,也没有 document 文件夹的写权限,那就需要改改 dumpdecrypted 的源代码,让他写到一个有权限的目录,编译之后放到 /Library/MobileSubstrate/DynamicLibraries/,加一个简单的配置文件,当程序启动的时候,这个 dylib 就会被加载,然后砸壳。

具体怎么改,我还不会,以后再研究,反正这次也用不上。

2. class-dump

实际上这步没有也行,因为逆向 Surge 的激活,只是很小的一部分内容,猜也能猜到,肯定关键字有 Activation

3. Reveal

实际上这步没有也是可以的,但是本着物尽其用的原则,我们打开 Reveal,会发现这些 cell 叫做 ResizeCell,这个名字太普通,有可能别的地方也会用到,放弃它。再往上看,有一个 BaseTableView,其实这个名字也很普通,抱着试试看的心态继续下去吧

4. 分析二进制

刚刚砸壳出来的二进制就派上用场了,拖到 Hopper,搜索 BaseTableView,发现并没什么卵用,随便翻翻看,诶似乎有个叫 SurgeMacViewController 的东西,直觉告诉我就是他了。

从这里我们就可以看出来,逆向工程,很多时候就是凭运气,加上细心耐心,再加上作者的疏忽。这些种种的不确定性,正是逆向工程的魅力所在。

再搜索 SurgeMacViewController,我们可以看到如下方法:

[SurgeMacViewController startDiscover]
[SurgeMacViewController browser:foundPeer:withDiscoveryInfo:]

猜都不用猜,这俩一定是了,上一个是开始发现近场设备,下面的是回调方法。

我们来看 startDiscover 的实现

上半部分(有简化)

                     -[SurgeMacViewController startDiscover]:
0000000100025564         stp        x24, x23, [sp, #-0x40]!                     ; Objective C Implementation defined at 0x1001dcd60 (instance)
0000000100025580         ldr        x20, [x8, #0x98]                            ; objc_cls_ref_KDProgressHUD,objc_class_KDProgressHUD
0000000100025584         adrp       x8, #0x1001fd000
0000000100025588         ldr        x1, [x8, #0x140]                            ; "view",@selector(view)
000000010002558c         bl         imp___stubs__objc_msgSend
00000001000255a0         ldr        x1, [x8, #0x700]                            ; "showHUDAddedTo:animated:",@selector(showHUDAddedTo:animated:)

可以看出来是启动了一个 Hud,就是传说中的菊花,或者风火轮

下半部分就是重点了

00000001000255e0         ldr        x0, [x8, #0x290]                            ; objc_cls_ref_MCPeerID,_OBJC_CLASS_$_MCPeerID
00000001000255e4         adrp       x8, #0x1001fd000
00000001000255e8         ldr        x21, [x8, #0xc8]                            ; "alloc",@selector(alloc)
00000001000255fc         ldr        x0, [x8, #0x50]                             ; objc_cls_ref_UIDevice,_OBJC_CLASS_$_UIDevice
0000000100025600         adrp       x8, #0x1001fd000
0000000100025604         ldr        x1, [x8, #0x4c0]                            ; "currentDevice",@selector(currentDevice)
000000010002561c         ldr        x1, [x8, #0x1d0]                            ; "name",@selector(name)
0000000100025634         ldr        x1, [x8, #0x660]                            ; "initWithDisplayName:",@selector(initWithDisplayName:)
000000010002565c         ldr        x0, [x8, #0x298]                            ; objc_cls_ref_MCNearbyServiceBrowser,_OBJC_CLASS_$_MCNearbyServiceBrowser
0000000100025668         adrp       x8, #0x1001fe000                            ; @selector(setBackgroundColor:)
000000010002566c         ldr        x1, [x8, #0x668]                            ; "initWithPeer:serviceType:",@selector(initWithPeer:serviceType:)
0000000100025670         adrp       x3, #0x1001cc000
0000000100025674         add        x3, x3, #0x150                              ; @"surge-activate"
00000001000256a0         ldr        x1, [x8, #0x200]                            ; "setDelegate:",@selector(setDelegate:)
00000001000256b4         ldr        x1, [x8, #0x670]                            ; "startBrowsingForPeers",@selector(startBrowsingForPeers)

同样被我简化过了,翻译成 OC 代码就是

NSString *displayName = [UIDevice currentDevice].name;
self.peer = [[MCPeerID alloc] initWithDisplayName:displayName];
self.serviceBrowser = [[MCNearbyServiceBrowser alloc] initWithPeer:self.peer serviceType:@"surge-activate"];
self.serviceBrowser.delegate = self;
[self.serviceBrowser startBrowsingForPeers];

接着重头戏就在回调方法了

- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary<NSString *,NSString *> *)info


0000000100025828         ldr        x1, [x8, #0x128]                            ; "objectForKeyedSubscript:",@selector(objectForKeyedSubscript:)
0000000100025830         add        x2, x2, #0x170                              ; @"request"
0000000100025838         bl         imp___stubs__objc_msgSend

可以看到返回的数据里面,有 request 字段,再往下分析

0000000100025860         adrp       x8, #0x1001fe000                            ; @selector(setBackgroundColor:)
0000000100025864         ldr        x1, [x8, #0x5d8]                            ; "initWithBase64EncodedString:options:",@selector(initWithBase64EncodedString:options:)
0000000100025868         movz       x3, #0x0
000000010002586c         mov        x2, x23
0000000100025870         bl         imp___stubs__objc_msgSend

000000010002587c         ldr        x1, [x8, #0x160]                            ; "length",@selector(length)

00000001000258a4         ldr        x27, [x8, #0x8b0]                           ; "dataWithBytes:length:",@selector(dataWithBytes:length:)

00000001000258c8         ldr        x1, [x8, #0xfe8]                            ; "rangeOfData:options:range:",@selector(rangeOfData:options:range:)

00000001000258fc         ldr        x20, [x8, #0x170]                           ; "bytes",@selector(bytes)

0000000100025944         ldr        x1, [x8, #0x4f8]                            ; "getBytes:length:",@selector(getBytes:length:)

00000001000259d4         ldr        x1, [x8, #0x6a0]                            ; "initWithPeer:securityIdentity:encryptionPreference:",@selector(initWithPeer:securityIdentity:encryptionPreference:)

00000001000259fc         ldr        x1, [x8, #0x6a8]                            ; "invitePeer:toSession:withContext:timeout:",@selector(invitePeer:toSession:withContext:timeout:)

@selector(invitePeer:toSession:withContext:timeout:) 这个就比较重要了,peer,session,context 三个参数填对之后,就坐等激活吧。

5. 调试小技巧

lldb 连上手机之后,连接上当前程序之后,执行 image list -o -f,来查看当前的 ASLR(Address space layout randomization),ASLR 简单说就是一个随机地址偏移,可以用来防范恶意程序对已知地址进行攻击。

那么问题就来了,真实地址是多少呢,当我们执行 image list -o -f 后,可以看到

(lldb) process connect connect://192.168.1.100:1234
Process 767 stopped
* thread #1: tid = 0x830a, 0x00000001990c8c30 libsystem_kernel.dylib`mach_msg_trap + 8, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x00000001990c8c30 libsystem_kernel.dylib`mach_msg_trap + 8
libsystem_kernel.dylib`mach_msg_trap:
->  0x1990c8c30 <+8>: ret    

libsystem_kernel.dylib`mach_msg_overwrite_trap:
    0x1990c8c34 <+0>: movn   x16, #0x1f
    0x1990c8c38 <+4>: svc    #0x80
    0x1990c8c3c <+8>: ret    
(lldb) image list -o -f


[  0] 0x0000000000004000 /var/mobile/Containers/Bundle/Application/8597C7A7-BBDB-4946-AC6F-2AAC1FCAF92F/Surge-iOS.app/Surge-iOS(0x0000000100004000)

0x0000000000004000 就是偏移量,基地址怎么找呢,看上面的汇编代码,以 @selector(initWithBase64EncodedString:options:) 为例,0000000100025864 就是基地址

0000000100025864         ldr        x1, [x8, #0x5d8]                            ; "initWithBase64EncodedString:options:",@selector(initWithBase64EncodedString:options:)
0000000100025868         movz       x3, #0x0
000000010002586c         mov        x2, x23
0000000100025870         bl         imp___stubs__objc_msgSend

但是这个地址一般没啥用,就像你断点打到一个方法名上面,看不到什么有价值的东西,紧接着下面的 imp___stubs__objc_msgSend 就有用了,如果你研究过 runtime 的话,会知道所有的 OC 方法,都是通过消息机制调用的,这个消息里面包含了发消息的对象,发消息的名字,消息的内容,它的基地址是 0x0000000100025870

那么答案就出来了,真实地址就是 基地址 + 偏移量,也就是 0x0000000100025870 + 0x0000000000004000 = 0x0000000100029870,我们在 lldb 打个断点

br s -a 0x0000000100029870

等程序运行到这里的时候,就停下来了,这个时候我们可以用 po (char *)$arg1,这样的方式,打印出想看的内容,arg1 arg2 依次递增,就是运行时的各个参数,(char *) 只是一个强制类型转换,有必要的时候要适当修改。如果想提前知道 arg1 arg2 代表什么,就随便写行代码,编译成 runtime 的代码,就可以分析出来每个参数的意思了

但是我 C++ 学的不好,这个方法用起来,打印出的结果也是很怪异,还有一种比较简单的方法,这里就需要用到 Theos 了,用 %hook,hook 这个函数,基本上就可以为所欲为了,想看什么看什么,看完之后,结合上面分析出的几个方法,基本可以理清逻辑

接下来的工作就是分析下如何处理收到的字符串,再把它包装好,丢给 Mac 即可,其他的工作交给 MultipeerConnectivity 就好

In The End

大概方法就是这样了,基本上也是一个完整的逆向流程,从 UI 分析到代码,从类名分析到二进制,再看汇编的实现,最后自己 mock 出一套类似的东西