原文地址:《"BLIND" Reversing - A Look At The Blind iOS App》
译者:hello1900@知道创宇404实验室
“Blind是一款用于工作场合的匿名社区应用。” 换句话说,作为一名员工,如果有“畅所欲言”或以匿名方式抨击雇主或同事的打算(当然这种情况极为常见),那么Blind可能是你的不二之选。这款有趣的小应用能够帮助我们透过事物表象了解真实情况。
我重点关注应用本身,因此使用Linkedin账户而非工作邮件注册。这样一来就产生了一些访问限制。此外,我也并未花时间了解所有功能,仅考虑一些自己看来具有核心功能的组件,所以难免存在局限性。
环境搭建如下:
-运行iOS 9.3.3的越狱iPhone 5S
-jtool
-IDA Pro
-Hopper
-BurpSuite Pro
-Frida
首先,这款应用不具备任何越狱检测例行程序。对于你的不屑一顾我暂时保持缄默。我开始也不认为缺少此类检查本身是安全问题,但从更宽泛、深入的防御观点来看却不尽然。
第二个观察结果是该应用没有通过SSL证书验证(证书固定)检查远程端点的真实性,因此可以通过中间人(MiTM)攻击监听并篡改数据。事实上,情况并不像听起来那么糟。原因如下:1. 攻击者必须欺骗用户在设备上安装恶意证书;2. 该应用对发送至后端的数据进行加密。
另一方面,攻击者大多数情况下需要先欺骗用户安装恶意证书。对此,我表示赞同,因为确实存在旨在简化流程的工具。Sensepost上的博客文章(https://sensepost.com/blog/2016/too-easy-adding-root-cas-to-ios-devices/)就是一个很好的例子。
最后,该应用程序提供两种登录选项:工作电子邮件与LinkedIn验证(见下图)。如上所述,我倾向使用后者。副作用是攻击者可在证书未固定的情况下捕获LinkedIn登陆凭据,当然是在安装恶意证书的前提下。
解决这些问题后就可以获得二进制文件、开始逆向了。我使用 dumpdecrypted dylib,仅需 ssh登录设备并运行以下代码:
root@Jekyl (/var/root)# su mobile mobile@Jekyl (/var/mobile/Documents)# DYLD_INSERT_LIBRARIES=/var/root/dumpdecrypted.dylib /private/var/containers/Bundle/Application/3C411AB3-6018-4604-97D2-DC2A546EAB85/teamblind.app/teamblind mach-o decryption dumper DISCLAIMER: This tool is only meant for security research purposes, not for application crackers. [+] detected 64bit ARM binary in memory. [+] offset to cryptid found: @0x1000d4f28(from 0x1000d4000) = f28 [+] Found encrypted data at address 00004000 of length 7995392 bytes - type 1. [+] Opening /private/var/containers/Bundle/Application/3C411AB3-6018-4604-97D2-DC2A546EAB85/teamblind.app/teamblind for reading. [+] Reading header [+] Detecting header type [+] Executable is a plain MACH-O image [+] Opening teamblind.decrypted for writing. [+] Copying the not encrypted start of the file [+] Dumping the decrypted data into the file [+] Copying the not encrypted remainder of the file [+] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset f28 [+] Closing original file [+] Closing dump file
应注意使用root权限在iOS 9.3.3版本运行DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib
可杀掉被注入进程:
root@Jekyl (/var/root)# DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /private/var/containers/Bundle/Application/3C411AB3-6018-4604-97D2-DC2A546EAB85/teamblind.app/teamblind zsh: killed DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib root@Jekyl (/var/root)#
解决办法是先切换至手机再cd至上图/var/mobile/Document。还应注意我们之所以能够注入自己的 dylib 是因为Blind应用没有 __RESTRICT Segment。
LC_SEGMENT_64 Mem: 0x100008000-0x100008000 __RESTRICT Mem: 0x100008000-0x100008000 __RESTRICT.__restrict
这是个null segment (size 0),用于通知 DLYD 不要相信任何 DLYD* 环境变量。
我在查看二进制文件时通常会转储字符串并搜索URL端点,然后用该列表确认Burpsuite流量。
macho-reverser:BLIND macho-reverser$ jtool -d __TEXT.__cstring teamblind.decrypted | grep "http" Address : 0x1006dcfd0 = Offset 0x6dcfd0 0x1006df366: https://api.linkedin.com/v1/people/~:(id,email-address,first-name,last-name,headline,num-connections,industry,summary,specialties,positions:(id,title,summary,start-date,end-date,is-current,company:(id,name,universal-name,type,size,industry,ticker,email-domains)),educations:(id,school-name,field-of-study,start-date,end-date,degree,activities,notes),associations,interests,num-recommenders,date-of-birth,publications:(id,title,publisher:(name),authors:(id,name),date,url,summary),patents:(id,title,summary,number,status:(id,name),office:(name),inventors:(id,name),date,url),languages:(id,language:(name),proficiency:(level,name)),skills:(id,skill:(name)),certifications:(id,name,authority:(name),number,start-date,end-date),courses:(id,name,number),recommendations-received:(id,recommendation-type,recommendation-text,recommender),honors-awards,three-current-positions,three-past-positions,volunteer)?format=json 0x1006df80e: http://us.teamblind.com 0x1006e19ad: https://api.linkedin.com/v1/people/~:(id,email-address)?format=json 0x1006e75df: https://m.facebook.com/settings/email 0x1006e760c: https://www.linkedin.com/m/settings/email 0x1006ea5ec: https://docs.google.com/forms/d/e/1FAIpQLSc_J26TtkDL7HXcLeFXC2jy6lb1PmJSPnh51_ng7fr1638p_Q/viewform 0x1006ee9c3: https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=%@&scope=%@&state=%@&redirect_uri=%@ 0x1006f4865: https://krapi.teamblind.com 0x1006f4881: https://usapi.teamblind.com 0x1006f489d: http://kr.stage.teamblind.com:8080 0x1006f48c0: http://us.stage.teamblind.com:8080 0x1006f48e3: http://dev.teamblind.com:8080 0x1006f4901: http://us.dev.teamblind.com:8080 0x1006f4922: https://kr.teamblind.com 0x1006f493b: https://us.teamblind.com 0x1006f4954: https://krnotifier.teamblind.com 0x1006f4975: https://usnotifier.teamblind.com ----
也可以用它获取其他潜在目标列表。于是,启动Burpsuite并检查流量。如前文所述,Blind应用能否实现证书固定并没有那么重要,因为该应用对发送至后端的数据进行加密。下图为常用请求示例。
唯一能够确定的是早前识别的链接。所以说我们实际上处于“盲”状态。但如果数据经过加密,那么加密秘钥的存储或生成的方式与位置是什么?能否看到应用发送至服务器的明文数据?
为了回答这些问题,首先转储类,看看是否存在有价值的内容。列出代码段后发现关于Objective-C的引用:
macho-reverser:BLIND macho-reverser$ jtool -l teamblind.decrypted LC 00: LC_SEGMENT_64 Mem: 0x000000000-0x100000000 __PAGEZERO LC 01: LC_SEGMENT_64 Mem: 0x100000000-0x1007a4000 __TEXT Mem: 0x100007a90-0x100663f18 __TEXT.__text (Normal) Mem: 0x100663f18-0x10066723c __TEXT.__stubs (Symbol Stubs) Mem: 0x10066723c-0x10066a560 __TEXT.__stub_helper (Normal) Mem: 0x10066a560-0x100671ec0 __TEXT.__const Mem: 0x100671ec0-0x1006dcfc9 __TEXT.__objc_methname (C-String Literals) Mem: 0x1006dcfd0-0x10074ca58 __TEXT.__cstring (C-String Literals) Mem: 0x10074ca58-0x100754bb2 __TEXT.__objc_classname (C-String Literals) Mem: 0x100754bb2-0x100767daa __TEXT.__objc_methtype (C-String Literals) Mem: 0x100767daa-0x100768e18 __TEXT.__ustring Mem: 0x100768e18-0x100788c4c __TEXT.__gcc_except_tab Mem: 0x100788c50-0x10078b967 __TEXT.__swift3_typeref Mem: 0x10078b968-0x10078c6a0 __TEXT.__swift3_capture Mem: 0x10078c6a0-0x10078d720 __TEXT.__swift3_fieldmd Mem: 0x10078d720-0x10078e67d __TEXT.__swift3_reflstr Mem: 0x10078e680-0x10078edc8 __TEXT.__swift3_assocty Mem: 0x10078edc8-0x10078f3c8 __TEXT.__swift2_proto Mem: 0x10078f3c8-0x10078f478 __TEXT.__swift2_types Mem: 0x10078f478-0x10078f4dc __TEXT.__swift3_builtin Mem: 0x10078f4dc-0x1007a3d20 __TEXT.__unwind_info Mem: 0x1007a3d20-0x1007a4000 __TEXT.__eh_frame LC 02: LC_SEGMENT_64 Mem: 0x1007a4000-0x100980000 __DATA Mem: 0x1007a4000-0x1007a4ba8 __DATA.__got (Non-Lazy Symbol Ptrs) Mem: 0x1007a4ba8-0x1007a6dc0 __DATA.__la_symbol_ptr (Lazy Symbol Ptrs) Mem: 0x1007a6dc0-0x1007a6e00 __DATA.__mod_init_func (Module Init Function Ptrs) Mem: 0x1007a6e00-0x1007cfd20 __DATA.__const Mem: 0x1007cfd20-0x10080f300 __DATA.__cfstring Mem: 0x10080f300-0x100811498 __DATA.__objc_classlist (Normal) Mem: 0x100811498-0x1008114d0 __DATA.__objc_nlclslist (Normal) Mem: 0x1008114d0-0x100811890 __DATA.__objc_catlist (Normal) Mem: 0x100811890-0x1008118e8 __DATA.__objc_nlcatlist (Normal) Mem: 0x1008118e8-0x1008123b0 __DATA.__objc_protolist Mem: 0x1008123b0-0x1008123b8 __DATA.__objc_imageinfo Mem: 0x1008123b8-0x10092bf38 __DATA.__objc_const Mem: 0x10092bf38-0x100944b20 __DATA.__objc_selrefs (Literal Pointers) Mem: 0x100944b20-0x100944c88 __DATA.__objc_protorefs Mem: 0x100944c88-0x100946ee0 __DATA.__objc_classrefs (Normal) Mem: 0x100946ee0-0x100948918 __DATA.__objc_superrefs (Normal) Mem: 0x100948918-0x10094ee80 __DATA.__objc_ivar Mem: 0x10094ee80-0x100965188 __DATA.__objc_data ------
我们可以通过jtool - JCOLOR=1 jtool -v -d objc teamblind.decrypted
的 objc 选项转储类与方法。
还需要说明一点,虽然本文使用IDA,读者完全可以根据自身反汇编需要使用jtool,例如研究某个特殊类时输入jtool -d UserControl:getSecretUserDefaultString: teamblind.decrypted
。
现在,我们已经成功拦截流量,但如何破解加密流量成为难题。不妨先找出加密实现方法。如上文所述,Blind允许通过工作邮件或LinkedIn账户登录。登录后将看到创建账户选项:
应用设置可在com.teamblind.blind.plist
中查找,具体位置在 /private/var/mobile/Containers/Data/Application/<app_id>/Library/Preferences/com.tea mblind.blind.plist
。此时,检查文件就会发现其中包含明文电子邮件以及登录时填写的公司信息。可以使用plutil实用程序读取文件。
一旦输入密码、用户名,点击“开始”后就是另一番景象了。
现在,你的电子邮件不再以明文存储而是经过加密,新增了密码与其他几个值。牢记千万不要在plist文件中存储密码等敏感信息。敏锐的读者可能会注意到我并没有隐藏password_enc
值。秘钥名称以_enc
结尾表示该值可能被加密,但事实是否果真如此?另外,应注意该值是加密过程必不可少的一部分,具体原因将在后文介绍。接下来,我们将继续探索关于这个值的更多细节。
“加密”密码仅是一个md5哈希值,可以在AuthCompleteViewController的requestPassword中看到。
在 0x000000010004EB50 位置得到用户提供值后计算 0x000000010004EB8C 位置的md5 哈希值。为了证实这一点,我们在与上文plist取值相同处使用Python。现在,我的超级密码一目了然。
>>> import hashlib >>> m = hashlib.md5() >>> m.update("password#1") >>> print m.hexdigest() 5486b4af453c7830dcea12f347137b07 >>>
为了确定需要检查的类,我首先来到账户创建页面,用cycript 确定ViewController外观:
root@Jekyl (/var/root)# ps aux | grep blind mobile 4136 0.1 5.8 815696 59532 ?? Ss 4:10PM 0:06.85 /var/containers/Bundle/Application/3C411AB3-6018-4604-97D2-DC2A546EAB85/teamblind.app/teamblind root 4139 0.0 0.0 657104 212 s000 R+ 4:11PM 0:00.01 grep blind root@Jekyl (/var/root)# cycript -p 4136 cy# [[[UIWindow keyWindow] rootViewController] _printHierarchy].toString() "<UINavigationController 0x15615d000>, state: appeared, view: <UILayoutContainerView 0x157415a30>\n | <RootViewController 0x1570db260>, state: disappeared, view: <UIView 0x157337ab0> not in the window\n | <AuthCompleteViewController 0x155f587c0>, state: appeared, view: <UIView 0x155da07a0>" cy#
别忘了你的电子邮件经过加密处理。加密惯例也在requestPassword 方法中呈现。你的电子邮件首先通过 plist(NSUserDefaults) 找回,然后传输至NSString ([NSString encryptHES256:]) encryptHES256 方法。
encryptHES256方法在过渡至AES256EncryptWithKey方法(涵盖加密过程)前生成带有密码简单异或的加密秘钥和一些“随机”值。从技术角度看,此方法调用另一个功能,但整体情况已趋向明朗,不难发现“随机性”。
稍微借助Frida就可以看到实际效果。对于还不了解 Frida的读者,我强烈建议您将这款工具添加至兵器库并在使用go脚本前检查Frida CodeShare。当然,代码运行前的例行检查无论何时都是必要步骤。
通过Frida与CodeShare ObjC method observer脚本,我们能够观察到AES256EncryptWithKey方法起到的作用:
macho-reverser:BLIND macho-reverser$ frida -U --codeshare mrmacete/objc-method-observer -f com.teamblind.blind ____ / _ | Frida 10.6.15 - A world-class dynamic instrumentation framework | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about 'object' . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/ Spawned `com.teamblind.blind`. Use %resume to let the main thread start executing! [iPhone::com.teamblind.blind]-> %resume [iPhone::com.teamblind.blind]-> observeSomething('*[* *AES256EncryptWithKey:*]'); (0x125fcdca0) -[NSData AES256EncryptWithKey:] AES256EncryptWithKey: password#1^0123456789abcdefghijk 0x1001b25cc teamblind!0x11e5cc 0x1000e2c7c teamblind!0x4ec7c ----
现在,密码与电子邮件加密方式已知。基本说来,重复以上步骤就能找到其他加密值。接下来,了解实际流量。
如上文所述,设备出口流量监控结果显示,所有请求均包括一个载荷。在IDA中搜索字符串后发现多数请求参数在[NetworkControl encRequestWithParams:showAlert:completionBlock:failBlock:] 方法中设置。
此方法首先尝试找回之前生成的加密秘钥与初始化向量(IV),如果失败则调用 EncriptControl 类 makeKeyAndIvForEnc (-[EncriptControl makeKeyAndIvForEnc])
方法。没错,是带有IV的Encript。 或许可称之为隐匿式安全……:)
这种做法的有趣之处在于加密秘钥通过用户密码与硬编码值组合生成。还记得之前提到的加密密码(password_enc)吗?该方法首先尝试将其找回:
根据硬编码值生成另一个md5哈希值:
如果涉及用户密码找回问题,则再生成一个哈希值:
最后,秘钥设置完毕,以 hash1+hash2 或 hash1+password_enc 组合结尾。
在此例中,加密秘钥应为 md5("QkdEhdk") + md5(“password#1"),并由此得到“c07bcdc2 3522ed81 fb76db0c 0c4387cf 5486b4af 453c7830 dcea12f3 47137b07”。
该方法的其余部分用于设置初始向量(IV):
NetworkControl 类 encRequestWithParams 方法通过调用EncriptControl 类makeKeyAndIvForEnc设置加密。设置完毕后使用encRequestWithParams 方法调用 EncriptControl 类makePayloadDataWithJsonString。该方法使用之前提及的加密秘钥与IV调用CocoaSecurity aesEncryp,结果返回base64 编码密文,也就是Burp呈现的内容。
暂时返回 jtool -d objc dump,注意 EncriptControl 类实例变量:
收集到所有线索后编写Frida脚本,获得实例变量,即加密秘钥、明文数据与相应密文:
if(ObjC.available){ var makeKandIv = ObjC.classes.EncriptControl["- makePayloadDataWithJsonString:"]; Interceptor.attach(makeKandIv.implementation, { onEnter: function(args) { /* Get Class/Params */ var obj = ObjC.Object(args[0]); var params = ObjC.Object(args[2]); /* Get ivars */ var ivar = obj.$ivars; // Print ivars values console.log("-----------------------------------------------------------\n"); console.log("_encKey: " + ivar["_encKey"] + "\n"); console.log("_encIv: " + ivar["_encIv"] + "\n"); console.log("_encIvStr: " + ivar["_encIvStr"] + "\n"); console.log("_encKeyForDM: " + ivar["_encKeyForDM"] + "\n"); console.log("_encKeyForDM: " + ivar["_encIvForDM"] + "\n"); console.log("-----------------------------------------------------------\n"); console.log("PARAMS: " + params); }, onLeave: function onLeave(retval) { console.log("Encrypted Payload: " + new ObjC.Object(retval).toString() + "\n"); } }); }
现在变成:
此外,连接EncriptControl 类convertDictionaryEncWithResultStr: 方法后打印出服务器响应明文。这时需要考虑将Brida Burpsuite 插件用于其他选项。
Ok,今天就到此为止。如前文所述,我没有注册Blind 账户,因此无法使用会员功能。但这些都无妨,我只对Burp上的一些数据感兴趣。考虑到应用程序性质与要求,我打算探索更多内容。因此,并无恶意流量发送至Blind服务器。
Happy hacking!!!
本文可用于围绕Blind应用开展进一步调研。对于提供信息的采用,本博客不承担任何责任。