freeBuf
记一次安卓测试多限制绕过
2023-05-09 16:55:02
所属地 广东省

某app存在加固以及frida检测和root检测,同时在测试中发现其使用了证书绑定、双向证书认证、数据加密。

frida检测绕过

最开始使用某混淆过的frida发现还是会被检测到,先使用frida hook dlopen查看加载的so,dlopen主要用于加载一些系统的库,可以看到打开了libc.so库,基于libc.so的检测一般分为两种一种是文件读写类,如maps和状态等,另一种就是线程类,通过dlopen_ext也可以知道libexec和libexecmain很有可能就是检测frida的so。

1683621904_645a08104054acbb37190.png!small?1683621905057

由上文我们知道是libexec或libexecmain调用了pthread_create,创建了相关检测线程,因此使用Thread.backtrace([context, backtracer]): 抓取当前线程的调用堆栈,通过打印出堆栈获取so文件名称。

1683621919_645a081f33284bc5439f5.png!small?1683621919981

此时可以确认是libexec调用了pthread_create创建了相关检测方法,而我们要找的方法是程序崩溃前pthread_create创建的方法。然后通过获取的相关动态地址减去libexec基地址即可定位相关方法的偏移量,最终在pthread_create创建检测线程时将对应方法替换掉即可。

function getunfrida() {
    var pthaddr = Module.findExportByName("libc.so","pthread_create");
    var timeaddr=Module.findExportByName("libc.so","time");
    Interceptor.attach(pthaddr,{
        onEnter: function(args){
            var baseaddr=Module.findBaseAddress("libexec.so");
            if(args[2]-baseaddress==270080 ||args[2]-baseaddress==193128 ||args[2]-baseaddress==193072){
                args[2]=timeaddr;
            }
        },onLeave: function(retval){
    }
});
}

root检测绕过

在root后的手机打开提示设备已root,并自动退出程序,对代码进行脱壳,在jadx中查找相关代码,checkroot方法检测是否root。

1683621953_645a084126e7bbf1108a2.png!small?1683621954377

hook掉该方法,结果程序又弹出来一个环境存在风险又退出了,原来还有一个checkmagisk方法。1683621976_645a08580dc65dbbabc4f.png!small?1683621976776

编写hook脚本,拦截这两个方法。

Java.perform(function(){
		let LoginActivity = Java.use("com.xxxx.LoginActivity");
		LoginActivity["checkroot"].overload('java.lang.Object').implementation = function () {
			console.log('checkroot is pass');
		};
		LoginActivity["checkmagisk"].implementation = function () {
    		console.log('checkmagisk is pass');
		};
    });

1683622005_645a087520cef404773e7.png!small?1683622005784

双向证书认证绕过

两个方法都hook掉,然后发现抓不到数据包,查看apk的network_security_config.xml资源文件,该app信任用户证书。

1683622018_645a0882d182340c50942.png!small?1683622019390

时间关系这里最开始没看root检测的相关方法,直接使用提权漏洞获取root运行的frida脚本,即使app只信任系统证书,通过漏洞获取root shell命令行的测试机也可以通过临时挂载一个内存文件系统,并将系统证书导入其中。

1683623152_645a0cf01b8a813b0ca39.png!small?1683623152624

charles监听16666。

1683622037_645a0895e6138e40fe5bc.png!small?1683622038755

手机通过postern转发16666 socks5到charles,配置External Proxy到burp。

1683622051_645a08a3e29ace5739c7e.png!small?1683622052812

在burp上查看数据发现数据包返回400,判断其可能使用了双向证书认证,可以直接试试密码自吐。

function hook_KeyStore_load() {
    Java.perform(function () {
        var StringClass = Java.use("java.lang.String");
        var KeyStore = Java.use("java.security.KeyStore");
        KeyStore.load.overload('java.security.KeyStore$LoadStoreParameter').implementation = function (arg0) {
            console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            console.log("KeyStore.load1:", arg0);
            this.load(arg0);
        };
        KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (arg0, arg1) {
            console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            console.log("KeyStore.load2:", arg0, arg1 ? StringClass.$new(arg1) : null);
            this.load(arg0, arg1);
        };

        var AssetManager = Java.use("android.content.res.AssetManager");
        AssetManager["open"].overload('java.lang.String', 'int').implementation = function (fileName, accessMode) {
            console.log('open is called' + ', ' + 'fileName: ' + fileName );
            let ret = this.open(fileName, accessMode);
            return ret;
            };

    });
}

成功获取证书密码。

1683622071_645a08b7657aa4d3b0045.png!small?1683622072850

当然也可以通过代码定位相关代码(已简化),通过代码可以知道密码来源于getValue方法。

1683622086_645a08c64541f59db7b17.png!small?1683622086898

编写脚本hook后即可获取证书的密码。

1683622107_645a08db0238d7d3dfc1c.png!small?1683622107431

获取证书密码后在资源文件找到对应的两个证书文件,将其导入burp中。

1683622124_645a08ec8eae73f5572e5.png!small?1683622125115

这里也导入一下charles。

1683622136_645a08f827a01c35f4211.png!small?1683622136713

成功抓到域名1的数据包。

1683622150_645a09061c79a176e312b.png!small?1683622150939

但是点击登录抓不到域名2的数据包,此时数据包发现存在显示8099端口的socket协议的数据包。

1683622162_645a09128d5bdc2fb94e8.png!small?1683622163051

在charles的proxy setting中添加该非标准https端口。

1683622187_645a092ba86b9f6b794c0.png!small?1683622188328

再次点击登录成功抓到登录的数据包。

1683622199_645a09372c7bbbbc90b23.png!small?1683622199732

加密算法绕过

通过charles自动转发到burp方便测试,同时能看到数据存在加密。

1683622211_645a0943a46286557059a.png!small?1683622212706

定位到加密算法位置。

1683622226_645a0952648f97e747238.png!small?1683622226925

编写hook脚本,在frida绕过脚本里面增加sm4加密hook代码。

Java.perform(function(){
		let hooksm4 = Java.use("com.xx.xxxx");
		hooksm4["sm4De"].implementation = function (cipherStr, sm4Key) {
    		console.log('sm4De is called' + ', ' + 'cipherStr: ' + cipherStr + ', ' + 'sm4Key: ' + sm4Key);
    		let ret = this.sm4Decrypt(cipherStr, sm4Key);
    		console.log('sm4De ret value is ' + ret);
    		return ret;
};
    });

通过hook即可获取加密前内容和国密sm4算法的密钥。

1683622246_645a0966cee965eab7db1.png!small?1683622247430

当然通过hook出的密钥发现密钥为动态密钥,编写固定密钥后即可开始测试,后续测试发现域名3和域名4都存在证书绑定。

1683622279_645a098752e97182a9807.png!small?1683622279987

因此还要在frida绕过脚本里面再加上证书绑定绕过代码。

function checkTrustedRecursive() {
    Java.perform(function () {
        var array_list = Java.use("java.util.ArrayList");
        var TrustManagerImpl_Activity_1 = Java.use('com.android.org.conscrypt.TrustManagerImpl');
        TrustManagerImpl_Activity_1.checkTrustedRecursive.implementation = function(certs, ocspData, tlsSctData, host, clientAuth, untrustedChain, trustAnchorChain, used) {
            console.log('[+] Bypassing TrustManagerImpl (Android > 7) checkTrustedRecursive check: '+ host);
            return array_list.$new();
        };
});
}

1683622296_645a09980d12611b36635.png!small?1683622296912

此时即可抓到全部的数据包。

1683622313_645a09a91ba2b55bd99bd.png!small?1683622313662

补充

中间测试时frida spawn模式启动绕过脚本后虽然能hook出证书的密码,但是程序卡死在启动页面无法正常进去app,不使用spawn模式又时候出现Failed to attach: unable to access process with pid 7854 due to system restrictions; try `sudo sysctl kernel.yama.ptrace_scope=0`, or run Frida as root的错误。

1683622326_645a09b6bd49948625be2.png!small?1683622327327

看样子是还存在双进程保护,测试几次最终通过先执行spawn模式启动,程序卡死,弹出是继续等待还是退出,点击退出。

1683622339_645a09c3296fdf2c263ab.png!small?1683622340326

在程序崩溃退出后先不重新打开app,执行frida Attach模式(此时可以加载上且不会提示找不到该程序)。

1683622351_645a09cfba00b03fc3d54.png!small?1683622352338

打开app此时成功进去app,并且脚本执行成功(很玄学)。

1683622370_645a09e213b39c578ff5c.png!small?1683622371514

本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
文章目录