实战:2015移动安全挑战赛 iOS 第1题

前言

2015移动安全挑战赛是由看雪和阿里巴巴联合举办的面向移动安全爱好者的比赛,感谢逆向工程界的前辈看雪论坛和非常重视技术积累的阿里巴巴为推动移动安全在国内的发展和普及所作出的贡献!
这次实战的目的,是带大家一起做一做这次比赛的第1个iOS题,感受一下iOS逆向工程在比赛实战中的运用,如果你有更好的解题思路,欢迎参与讨论:wink:
附第1题下载:AliCrackme_1.ipa (314.7 KB)

安装ipa

不知为何,这个ipa在我的iPhone 5, iOS 8.1.2上安装之后无法启动,每次启动均会闪退;我采用了ldid重签名和把app放在/Applications下的方案,都没有解决闪退的问题,不知道是不是出题人故意留给我们的一道坎?我没有深究这个问题,而是尝试在我的iPhone 4s, iOS 6.1.3安装此ipa,竟意外发现app可以成功打开了,如图所示:

把玩app

安装成功之后,我们先随便操作一下,看看针对不同的输入,这个app会给我们怎样的反馈。先直接点击“进入”,发现此app直接退出了;再次打开app,输入snakeninny,结果出现了“密码错误”的提示,如图所示:


跟我一起思考——
错误提示一定是由代码控制的;既然弹出了错误提示,说明在弹出错误提示的代码之前,一定有输入是否错误的判断;它应该就是我们的输入与正确答案是否相同的判断。因为点击“进入”之后弹框,那么判断操作一定是在按钮的响应函数之后发生的;如果能够找到这个按钮响应函数,就可以顺藤摸瓜,找到这个判断。
那么我们今天的任务,就是找到这个判断的代码,从而找到正确答案。开始操作!

用Cycript找到“进入”按钮的响应函数

这个步骤在书上已经不知道重复多少遍了,接下来的操作主要以代码表示,伴以少量解说,跟紧喽!

FunMaker-4s:~ root# ps -e
  PID TTY           TIME CMD
    1 ??         0:31.21 /sbin/launchd
   26 ??         5:53.88 /usr/libexec/UserEventAgent (System)
...
 3060 ??         0:02.52 /var/mobile/Applications/3E693339-7347-4129-887D-1DBE0C0587AE/level1.app/level1
 3079 ??         0:00.02 /usr/libexec/launchproxy /usr/sbin/sshd -i
 3082 ??         0:00.28 sshd: root@ttys000 
 3083 ttys000    0:00.04 -sh
 3084 ttys000    0:00.01 ps -e
FunMaker-4s:~ root# cycript -p level1
cy# ?expand
expand == true
cy# [[UIApp keyWindow] recursiveDescription]
@"<UIWindow: 0x1fde5d30; frame = (0 0; 320 480); autoresize = W+H; layer = <UIWindowLayer: 0x1fde5e30>>
...
   |    | <UIButton: 0x1fd5acd0; frame = (110 348; 100 30); opaque = NO; layer = <CALayer: 0x1fd5ac50>>
   |    |    | <UIButtonLabel: 0x20872890; frame = (31 4; 37 22); text = '\xe8\xbf\x9b\xe5\x85\xa5'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x208729f0>>
   |    | <UIImageView: 0x1fd59030; frame = (0 420; 125.5 40); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x1fd596e0>>
   |    | <UILabel: 0x1fd58820; frame = (246 430; 64 20); text = '\xe9\x98\xbf\xe9\x87\x8c\xe9\x92\xb1\xe7\x9b\xbe'; clipsToBounds = YES; userInteractionEnabled = NO; layer = <CALayer: 0x1fd587f0>>
   |    | <UIImageView: 0x1fd57c40; frame = (218 427.5; 25 25); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x1fd57ca0>>"
cy# button = #0x1fd5acd0
#"<UIButton: 0x1fd5acd0; frame = (110 348; 100 30); opaque = NO; layer = <CALayer: 0x1fd5ac50>>"
cy# [button setHidden:YES]

“进入”按钮不见了,界面变成了这样:


继续操作:

cy# [button allTargets]
[NSSet setWithArray:@[#"<ViewController: 0x20869990>"]]]
cy# [button allControlEvents]
64
cy# [button actionsForTarget:#0x20869990 forControlEvent:64]
@["onClick"]

好了,按钮的响应函数是[ViewController onClick],开始分析汇编代码。

从[ViewController onClick]出发,找到答案

[ViewController onClick]的汇编执行流程如图所示:


整个流程并不复杂(比书上的几个例子要简单得多:grinning_face_with_smiling_eyes:),大致浏览一遍,你就一定会注意到下图这个红色的方块:

它的汇编代码是:

很明显,这段汇编代码从textField里读text,转换成UTF8String之后和某个东西作判断,并根据判断结果跳转。这个现象跟我们上一节作出的预测不谋而合,我们在这段代码的开头下个断点,然后单步执行,看看每个objc_msgSend都是做的什么操作。
先用debugserver挂载level1:

FunMaker-4s:~ root# debugserver *:1234 -a level1
debugserver-199 for armv7.
Listening to port 1234...

然后用LLDB连过去:

192:~ snakeninny$ lldb
(lldb) process connect connect://localhost:1234
2015-10-19 16:29:37.015 lldb[15434:956234] Metadata.framework [Error]: couldn't get the client port
Process 3112 stopped
* thread #1: tid = 0x1e03, 0x3c606eb4 libsystem_kernel.dylib`mach_msg_trap + 20, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x3c606eb4 libsystem_kernel.dylib`mach_msg_trap + 20
libsystem_kernel.dylib`mach_msg_trap:
->  0x3c606eb4 <+20>: pop    {r4, r5, r6, r8}
    0x3c606eb8 <+24>: .long  0xe12fff1e                ; unknown opcode

libsystem_kernel.dylib`mach_msg_overwrite_trap:
    0x3c606ebc <+0>:  mov    r12, sp
    0x3c606ec0 <+4>:  push   {r4, r5, r6, r8}
(lldb) c
Process 3112 resuming

在0000B76A下断点(这些操作也都在书上重复过无数遍了,下面还是以码代字):

(lldb) image list -o -f
[  0] 0x00054000 /var/mobile/Applications/3E693339-7347-4129-887D-1DBE0C0587AE/level1.app/level1(0x0000000000058000)
[  1] 0x00077000 /Library/MobileSubstrate/MobileSubstrate.dylib(0x0000000000077000)
[  2] 0x0328e000 /Users/snakeninny/Library/Developer/Xcode/iOS DeviceSupport/6.1.3 (10B329)/Symbols/System/Library/Frameworks/Foundation.framework/Foundation
[  3] 0x0328e000 /Users/snakeninny/Library/Developer/Xcode/iOS DeviceSupport/6.1.3 (10B329)/Symbols/System/Library/Frameworks/UIKit.framework/UIKit
...
(lldb) br s -a '0x00054000+0x0000B76A'
Breakpoint 1: where = level1`___lldb_unnamed_function36$$level1 + 202, address = 0x0005f76a
(lldb) 

点击按钮,触发断点:

Process 3112 stopped
* thread #1: tid = 0x1e03, 0x0005f76a level1`___lldb_unnamed_function36$$level1 + 202, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0005f76a level1`___lldb_unnamed_function36$$level1 + 202
level1`___lldb_unnamed_function36$$level1:
->  0x5f76a <+202>: movw   r0, #0x346c
    0x5f76e <+206>: mov    r10, r4
    0x5f770 <+208>: movt   r0, #0x1
    0x5f774 <+212>: ldr.w  r8, [sp, #0x10]
(lldb) ni
...
Process 3112 stopped
* thread #1: tid = 0x1e03, 0x0005f792 level1`___lldb_unnamed_function36$$level1 + 242, queue = 'com.apple.main-thread', stop reason = instruction step over
    frame #0: 0x0005f792 level1`___lldb_unnamed_function36$$level1 + 242
level1`___lldb_unnamed_function36$$level1:
->  0x5f792 <+242>: movw   r1, #0x3492
    0x5f796 <+246>: movt   r1, #0x1
    0x5f79a <+250>: add    r1, pc
    0x5f79c <+252>: ldr    r5, [r1]
(lldb) po $r0
snakeninny

(lldb) ni
...
Process 3112 stopped
* thread #1: tid = 0x1e03, 0x0005f7a0 level1`___lldb_unnamed_function36$$level1 + 256, queue = 'com.apple.main-thread', stop reason = instruction step over
    frame #0: 0x0005f7a0 level1`___lldb_unnamed_function36$$level1 + 256
level1`___lldb_unnamed_function36$$level1:
->  0x5f7a0 <+256>: blx    0x6ff18                   ; symbol stub for: objc_msgSend
    0x5f7a4 <+260>: mov    r4, r0
    0x5f7a6 <+262>: mov    r0, r6
    0x5f7a8 <+264>: mov    r1, r5
(lldb) p (char *)$r1
(char *) $3 = 0x39eb1d27 "UTF8String"
(lldb) ni
Process 3112 stopped
* thread #1: tid = 0x1e03, 0x0005f7a4 level1`___lldb_unnamed_function36$$level1 + 260, queue = 'com.apple.main-thread', stop reason = instruction step over
    frame #0: 0x0005f7a4 level1`___lldb_unnamed_function36$$level1 + 260
level1`___lldb_unnamed_function36$$level1:
->  0x5f7a4 <+260>: mov    r4, r0
    0x5f7a6 <+262>: mov    r0, r6
    0x5f7a8 <+264>: mov    r1, r5
    0x5f7aa <+266>: blx    0x6ff18                   ; symbol stub for: objc_msgSend
(lldb) p (char *)$r0
(char *) $4 = 0x1c520110 "snakeninny"
(lldb) ni
Process 3112 stopped
...
* thread #1: tid = 0x1e03, 0x0005f7aa level1`___lldb_unnamed_function36$$level1 + 266, queue = 'com.apple.main-thread', stop reason = instruction step over
    frame #0: 0x0005f7aa level1`___lldb_unnamed_function36$$level1 + 266
level1`___lldb_unnamed_function36$$level1:
->  0x5f7aa <+266>: blx    0x6ff18                   ; symbol stub for: objc_msgSend
    0x5f7ae <+270>: mov    r5, r0
    0x5f7b0 <+272>: ldrb   r0, [r5]
    0x5f7b2 <+274>: cmp    r0, #0x0
(lldb) p (char *)$r1
(char *) $5 = 0x39eb1d27 "UTF8String"
(lldb) po $r0
Sp4rkDr0idKit

(lldb) ni
Process 3112 stopped
* thread #1: tid = 0x1e03, 0x0005f7ae level1`___lldb_unnamed_function36$$level1 + 270, queue = 'com.apple.main-thread', stop reason = instruction step over
    frame #0: 0x0005f7ae level1`___lldb_unnamed_function36$$level1 + 270
level1`___lldb_unnamed_function36$$level1:
->  0x5f7ae <+270>: mov    r5, r0
    0x5f7b0 <+272>: ldrb   r0, [r5]
    0x5f7b2 <+274>: cmp    r0, #0x0
    0x5f7b4 <+276>: beq    0x5f7d6                   ; <+310>
(lldb) p (char *)$r0
(char *) $7 = 0x1d016640 "Sp4rkDr0idKit"
...

看到了一个可疑的字符串“Sp4rkDr0idKit”,你应该已经猜到了,它就是我们的答案(不信的话自己试一下 :wink: )。知其然,还要知其所以然,我们继续分析,看看正确的判断逻辑是怎样的。

分析判断逻辑

顺着上面的代码,我们接着分析:


每一行汇编是什么意思,我都已经标注在图里了,强烈建议你先自己分析一遍,有不明白的地方再看图,然后自己再分析一遍,直到自己能独立分析为止。
值得一提的是,这个判断逻辑有一个bug——即我们输入的字符串,只需要含有“Sp4rkDr0idKit”这个前缀就可以了,而不用全文匹配这个字符串,如图所示:

很荣幸snakeninny也能为拯救人类出一份力 :relaxed:

结语

从2015年开始,iOS方向的安全问题开始层出不穷,不管是带有后门的抢红包插件,还是植根开发工具的XcodeGhost,都给封闭且“安全”的苹果生态系统敲响了安全的警钟。当我在比赛的网站上提交第1题的答案时,看到早已有多位朋友提交了正确答案,让我知道自己不是一个人在战斗;当我对第2题束手无策时,曾经找我请教问题的一位在读研究生已经接近正确答案了,后浪已经开始把我们前浪拍死在沙滩上:joy:这些朋友平常大都不在微博等社交平台上咋呼着炫技,而是默默钻研着技术,为这个社区贡献着自己的一份力量,“闷声发大财”。我最欣赏的就是这些为人低调,但做事高调的无名英雄,是他们推动着这个行业的发展。向所有默默耕耘,努力成长的朋友们致敬,衷心祝愿大家都能达到目标,实现理想:+1:

6 个赞

哈哈第一道题我也做出来了,一直卡在第二题,期待狗神第二题的分析,Kernel以前基本没接触过

第二题坑在大小写上了:joy:

我也分析出来了,完全靠自己,感觉很有成就感,哈哈,贴下cycript的试验代码。可以根据这个清楚的看出代码逻辑。

  1. 以长度初始化CipherKey。

  2. 设置最原始的加密字串。

  3. 解密得到下一轮的原始加密字串。

  4. 用固定密码解密最原始字串。

  5. 重复5次,依次为4,3,2,1,0.

    My-iPhone-5C:~ root# cycript -p level1
    cy# ccm = [[Ceasar_CipherModel alloc] initWithCipherKey:4]
    #"<Ceasar_CipherModel: 0x16735fe0>"
    cy# longstring = @“mrMZAbjtZozDOGI9UeeH6g0iLHNnTNsFyzS0tYca4R3KkaQ0doxdDVuxZ7HoqYOcxFhgDiEvdGKix95VJNEUP8rdox4cm7GHVkbVcTJPmrTtH7hompW+xjTgGg2zQhs0tUGQ8lCggev2SNoWcaUOUU==”
    @“mrMZAbjtZozDOGI9UeeH6g0iLHNnTNsFyzS0tYca4R3KkaQ0doxdDVuxZ7HoqYOcxFhgDiEvdGKix95VJNEUP8rdox4cm7GHVkbVcTJPmrTtH7hompW+xjTgGg2zQhs0tUGQ8lCggev2SNoWcaUOUU==”
    cy# [ccm setCodedMessage:longstring]
    cy# [ccm decrypt]
    cy# origmsg = [ccm originalMessage]
    @“inIVWxfpVkvZKCE9QaaD6c0eHDJjPJoBuvO0pUyw4N3GgwM0zktzZRqtV7DkmUKytBdcZeArzCGet95RFJAQL8nzkt4yi7CDRgxRyPFLinPpD7dkilS+tfPcCc2vMdo0pQCM8hYccar2OJkSywQKQQ==”
    cy# psw = @“ZGlhb2RhX2ppYW5rYW5nCg==”
    @“ZGlhb2RhX2ppYW5rYW5nCg==”
    cy# longstring = [AESCrypt decrypt:origmsg password:psw]
    @“hDmx1/d5KNhr1BBYQlRNVsZSEaOdw4MtKTpT3082x/x9lZucw0qEm+UhMaOVuoSLyqD1x0elXGXqM4nFSP3W8khfyg1ynDEwLhLt12m68U8=”
    cy#
    cy#
    cy#
    cy#
    cy# ccm = [[Ceasar_CipherModel alloc] initWithCipherKey:3]
    #"<Ceasar_CipherModel: 0x166c3e40>"
    cy# [ccm setCodedMessage:longstring]
    cy# [ccm decrypt]
    cy# origmsg = [ccm originalMessage]
    @“eAju1/a5HKeo1YYVNiOKSpWPBxLat4JqHQmQ3082u/u9iWrzt0nBj+ReJxLSrlPIvnA1u0biUDUnJ4kCPM3T8hecvd1vkABtIeIq12j68R8=”
    cy# longstring = [AESCrypt decrypt:origmsg password:psw]
    @“e1s6fwEoaC3l/4VLi1DA4KKPJdGcGWK3elMxPqOuG7MNa9fcfWu6gpui+m3q1akL”
    cy#
    cy#
    cy#
    cy#
    cy# ccm = [[Ceasar_CipherModel alloc] initWithCipherKey:2]
    #"<Ceasar_CipherModel: 0x166ebbd0>"
    cy# [ccm setCodedMessage:longstring]
    cy# [ccm decrypt]
    cy# origmsg = [ccm originalMessage]
    @“c1q6duCmyA3j/4TJg1BY4IINHbEaEUI3cjKvNoMsE7KLy9dadUs6ensg+k3o1yiJ”
    cy# longstring = [AESCrypt decrypt:origmsg password:psw]
    @“4p2eb81lORtnnduYgcAc3pxfqGh8Fybny9NFnTzYJ6B=”
    cy#
    cy#
    cy#
    cy#
    cy# ccm = [[Ceasar_CipherModel alloc] initWithCipherKey:1]
    #"<Ceasar_CipherModel: 0x166e53b0>"
    cy# [ccm setCodedMessage:longstring]
    cy# [ccm decrypt]
    cy# origmsg = [ccm originalMessage]
    @“4o2da81kNQsmmctXfbZb3owepFg8Examx9MEmSyXI6A=”
    cy# longstring = [AESCrypt decrypt:origmsg password:psw]
    @“QNEcNAUUYKq5mMZJTh3J5w==”
    cy#
    cy#
    cy#
    cy# ccm = [[Ceasar_CipherModel alloc] initWithCipherKey:0]
    #"<Ceasar_CipherModel: 0x1650db90>"
    cy# [ccm setCodedMessage:longstring]
    cy# [ccm decrypt]
    cy# origmsg = [ccm originalMessage]
    @“QNEcNAUUYKq5mMZJTh3J5w==”
    cy# longstring = [AESCrypt decrypt:origmsg password:psw]
    @“Sp4rkDr0idKit”
    cy#

3 个赞

我也没接触过,搜索引擎即可搞定! http://www.blogfshare.com/ali-ios-game.html

额~~~是正版IDA pro吗,分析出来的代码结构那么清晰,传说中的F5键??或者说windows的版本,感觉界面和Mac版的不太一样,我使用的是demo版的。。。

Windows的,,,网上有F5版

要是有Mac偷跑版就好了,不升级都无所谓,花点钱也行啊,正版太贵了,windows太夸张了,得整俩电脑,多折腾啊。

用hopper disassembler吧

支持,赞一个。

赞,有个问题,我是新手。。用IDA逆向ipa的时候没有任何汇编代码,是不是这个ipa文件加密了,运行时才解密,但是我用hopper disassembler就有汇编代码