逆向分析 Surge for Mac 的激活机制

iOS Sep 24, 2016

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

  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 出一套类似的东西

Tags

Jie Li

🚘 On-road / 📉 US Stock / 💻 Full Stack Developer / 🎓 Grad Student / ®️ ENTJ

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.