0x1 渗透测试瓶颈
目前,碰到越来越多的大客户都会将核心资产业务集中在统一的APP上,或者对自己比较重要的APP,如自己的主业务,办公APP进行加壳,流量加密,投入了很多精力在移动端的防护上。
而现在挖漏洞除了拿到shell以外,客户又要求可以尽可能的挖到核心业务系统的漏洞,并将漏洞范围订在主域名,核心业务系统现在又基本集中在移动端,移动端现在都会进行APP加壳,流量加密。这就导致无法进行平常渗透测试过程,像老生常谈的中间人攻击,进行拦截,篡改数据包就很难进行。
接下来就尝试解决中间人攻击的问题,目标是
1.看到明文的request和response的数据包;
2.做到可以拦截,篡改数据包。
0x2 frida
frida是平台原生app的Greasemonkey,说的专业一点,就是一种动态插桩工具,可以插入一些代码到原生app的内存空间去,(动态地监视和修改其行为),这些原生平台可以是Win、Mac、Linux、Android或者iOS。而且frida还是开源的。
环境需要越狱的IOS或者ROOT的Android。安装的版本需要一致
MAC:
越狱Iphone:
通过USB链接越狱手机,可以执行frida-ps -aU 就代表环境安装成功
0x3 越狱检测绕过
启动目标APP时,APP自身会进行环境检测,如果处于越狱环境会提示如下:
点击“我知道了”就直接退出APP。
所以先尝试先绕过第一步越狱环境检测。可以先尝试搜索包含“jail,jeil,jb,break"关键字的函数
关于函数追踪可以使用frida-trace,如:
# Trace recv* and send* APIs in Safari
$ frida-trace -i "recv*" -i "send*" Safari
# Trace ObjC method calls in Safari
$ frida-trace -m "-[NSView drawRect:]" Safari
# Launch SnapChat on your iPhone and trace crypto API calls
$ frida-trace -U -f com.toyopagroup.picaboo -I "libcommonCrypto*"
burp的插件brida也支持对函数名进行检索hook,和"Jail"相关的越狱检测函数如下:
**** Result of the search of Jail OBJC: +[BLYDevice isJailBreak] OBJC: +[IFlySystemInfo isJailbroken] OBJC: +[UIScreen _shouldDisableJail] OBJC: +[UIStatusBarWindow isIncludedInClassicJail] OBJC: -[_UIHostedWindow _isConstrainedByScreenJail] OBJC: -[_UIRootWindow _isConstrainedByScreenJail] OBJC: -[_UISnapshotWindow _isConstrainedByScreenJail] OBJC: -[BLYDevice isJailbroken] OBJC: -[BLYDevice setJailbrokenStatus:] OBJC: -[RCCountly isJailbroken] OBJC: -[UIClassicWindow _isConstrainedByScreenJail] OBJC: -[UIDevice isJailbroken] OBJC: -[UIStatusBarWindow _isConstrainedByScreenJail] OBJC: -[UITextEffectsWindowHosted _isConstrainedByScreenJail] OBJC: -[UIWindow _clampPointToScreenJail:] OBJC: -[UIWindow _isConstrainedByScreenJail]
想将目标定在“OBJC: +[BLYDevice isJailBreak]”
frida启动APP,并加载脚本的命令如下:
frida -U -f com.x.x -l js-scripts
js脚本编写可以看官方文档:https://frida.re/docs/javascript-api/
//hook传入值,ObjC: args[0] = self, args[1] = selector, args[2-n] = arguments Interceptor.attach(myFunction.implementation, { onEnter: function(args) { var myString = new ObjC.Object(args[2]); console.log("String argument: " + myString.toString()); } }); //hook返回值, Interceptor.attach(Module.getExportByName('libc.so', 'read'), { onEnter: function (args) { this.fileDescriptor = args[0].toInt32(); }, onLeave: function (retval) { if (retval.toInt32() > 0) { /* do something with this.fileDescriptor */ }}});
定义js脚本后,尝试hook出“OBJC: +[BLYDevice isJailBreak]”的传入值和返回值,
function hook_specific_method_of_class(className, funcName) { var hook = ObjC.classes[className][funcName]; Interceptor.attach(hook.implementation, { onEnter: function(args) { // args[0] is self // args[1] is selector (SEL "sendMessageWithText:") // args[2] holds the first function argument, an NSString console.log("\n\t[*] Class Name: " + className); console.log("[*] Method Name: " + funcName); //For viewing and manipulating arguments //console.log("\t[-] Value1: "+ObjC.Object(args[2])); //console.log("\t[-] Value2: "+(ObjC.Object(args[2])).toString()); console.log("\t[-]arg value "+args[2]); Interceptor.attach(hook.implementation, { onLeave: function(retval) { console.log("[*] Class Name: " + className); console.log("[*] Method Name: " + funcName); console.log("\t[-] Return Value: " + retval); }} ); } }); } //Your class name and function name here hook_specific_method_of_class("BLYDevice", "- isJailbroken")
篡改后,发现未能绕过,可能不是这个函数做最终的逻辑判断,想到竟然都弹窗提示了,和UI有关系。
那么可能是“OBJC: -[UIDevice isJailbroken]这个类,最终构造绕过越狱检测代码如下:
if (ObjC.available) { try { var className = "UIDevice"; var funcName = "- isJailbroken"; var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]');//目标类+方法 Interceptor.attach(hook.implementation, { onLeave: function(retval) { console.log("[*] Class Name: " + className); console.log("[*] Method Name: " + funcName); console.log("\t[-] Return Value: " + retval);//输出原本的返回值 var newretval = ptr("0x0") retval.replace(newretval)//替换新的返回值 console.log("\t[-] New Return Value: " + newretval) }} ); } catch(err) { console.log("[!] Exception2: " + err.message); } } else { console.log("Objective-C Runtime is not available!"); }
执行结果如下:
成功绕过。
0x4 HOOK加解密函数
越狱检测绕过后,进一步开始尝试定位加解密的函数。
关于定位加解密函数这块在Android可以尝试使用traceview去分析追踪函数。
https://developer.android.google.cn/studio/profile/traceview
IOS可以尝试使用runtime去追踪函数,uidump从界面按钮入手,Nslog日志等位置入手,或者直接找相关关键字的函数去入手。
例如crypt(decryot,encrypt),HTTP,Network,目标厂商的名字简写找不到,可以尝试搜索NSString系统库等。
这边推荐一个大佬的GitHub项目。使用可以参考这个GitHub项目,非常好用,先用之前写好的绕过越狱检测的脚本启动APP,这边通过查找函数名找到对方关键的加解密函数“*encryptor”。
github项目:https://github.com/lyxhh/lxhToolHTTPDecrypt
hook此函数的所有方法,在点击登录按钮后,观察到有请求的数据包被当做参数传入到-[XXEncryptor RSAEncrypt:]方法内,并返回了加密后的字符串。-[XXEncryptor setRSAPublicKey:]根据定义的方法名判断应该是RSA公钥信息。
其他方法则去处理了返回包。如-[XXEncrytor AESDecrypt:]方法,将服务端返回的加密字段,使用AES对称解密解密为明文。
之前我们在Hook请求包函数的时候发现明文的数据包里面带有aeskey,说明此处的逻辑应该是:
本地生成aeskey代入到request包->使用定义的RSA公钥加密request->发送到服务端并解密request后->处理请求包内容,并使用AESkey加密Response返回到客户端->客户端在使用Aeskey解密服务端的Response包。
大概是这么一个流程,事实也证明返回包确实可以使用hook到的aeskey进行解密。
后面的思路是hook[XXEncrytor AESDecrypt:]解密方法去解密请求包和返回包,返回包是可以解,但是突然想到请求包是RSA非对称的,需要私钥。想尝试在客户端找到RSA的私钥或者RSA解密方法,结果也确实有RSADecrypt方法。
但是事实是,从头到尾这个方法都没有被使用过,没有参数被传入,也没有返回值。所以想,可能本地不做请求包的解密。那么调用他的函数解密返回包可行,但解密请求包不行。但是咱们之前是有Hook到明文的request,可以再request被传入到-[XXEncryptor RSAEncrypt]方法前,先去修改arg。
具体操作方法可以参考lyxhh,将加密前的请求包转入Burp后就可以实现篡改数据了。
lyxhh:https://github.com/lyxhh/lxhToolHTTPDecrypt
新手的话可以先用la0s的JS,先看看对方是不是使用了IOS统一封装的Crypto库,js脚本如下:
JS:https://la0s.github.io/2018/12/07/iOS_Crypto/
/ Intercept the CCCrypt call. Interceptor.attach(Module.findExportByName('libcommonCrypto.dylib', 'CCCrypt'), { onEnter: function (args) { // Save the arguments this.operation = args[0] this.CCAlgorithm = args[1] this.CCOptions = args[2] this.keyBytes = args[3] this.keyLength = args[4] this.ivBuffer = args[5] this.inBuffer = args[6] this.inLength = args[7] this.outBuffer = args[8] this.outLength = args[9] this.outCountPtr = args[10] console.log('CCCrypt(' + 'operation: ' + this.operation +', ' + 'CCAlgorithm: ' + this.CCAlgorithm +', ' + 'CCOptions: ' + this.CCOptions +', ' + 'keyBytes: ' + this.keyBytes +', ' + 'keyLength: ' + this.keyLength +', ' + 'ivBuffer: ' + this.ivBuffer +', ' + 'inBuffer: ' + this.inBuffer +', ' + 'inLength: ' + this.inLength +', ' + 'outBuffer: ' + this.outBuffer +', ' + 'outLength: ' + this.outLength +', ' + 'outCountPtr: ' + this.outCountPtr +')') if (this.operation == 0) { // Show the buffers here if this an encryption operation console.log("In buffer:") console.log(hexdump(ptr(this.inBuffer), { length: this.inLength.toInt32(), header: true, ansi: true })) console.log("Key: ") console.log(hexdump(ptr(this.keyBytes), { length: this.keyLength.toInt32(), header: true, ansi: true })) console.log("IV: ") console.log(hexdump(ptr(this.ivBuffer), { length: this.keyLength.toInt32(), header: true, ansi: true })) } }, onLeave: function (retVal) { if (this.operation == 1) { // Show the buffers here if this a decryption operation console.log("Out buffer:") console.log(hexdump(ptr(this.outBuffer), { length: Memory.readUInt(this.outCountPtr), header: true, ansi: true })) console.log("Key: ") console.log(hexdump(ptr(this.keyBytes), { length: this.keyLength.toInt32(), header: true, ansi: true })) console.log("IV: ") console.log(hexdump(ptr(this.ivBuffer), { length: this.keyLength.toInt32(), header: true, ansi: true })) } } })
如果只能hook到部分明文流量,再考虑去对方定义的函数里去找关键的加密函数,如这个APP的关键的XXEncryptor类。