0x01 过frida检测
frida可以说是逆向里面很受欢迎的工具了,你可以在运行的时候得到几乎你想要的所有东西,函数地址、内存数据、java实例,根据我们的需要去修改程序的运行逻辑等等,但是太流行也不好,迎来了各种检测。
- ptrace占坑、进程名检测、端口检测。(这绕过太简单了)
- D-Bus通信协议的检测。
- maps、fd检测。
- App中线程名的检测。
直接拿出App,看看他到底怎么检测的。节省时间,直接用hluda-server,修改一下运行端口,以spawn方式注入frida。(hluda-server的好处在于,他所生成的各种so库名字,去掉了frida等特征字段,可以很好的绕过maps和fd的检测。)
直接给我干掉了??猜测有没有可能是D-Bus通信协议的检测,App向每一个端口都发送了D-Bus认证消息,那肯定会利用strcmp( )或者strstr( )函数进行检测回复的消息。那么就hook一下看看。
同样的方法hook一下strstr函数
不仅没有任何输出,app还是直接给我干掉了。
思考一下,不是D-Bus协议检测、不是ptrace占坑、进程名检测、端口检测、fd和maps检测利用hluda绕过了,emm,等等不一定,跟师傅讨论一下,又搜了几个文章,发现有的app检测非常恶心,只要是maps和fd中存在/data/local/tmp/,甚至只有tmp的字段,app就给kill掉。因为这个目录对于安卓逆向工作来说,是一个比较敏感的目录。hluda-server和frida-server都会在/data/local/tmp/目录下生成一个包含frida所需要的so库等文件。所以当app一旦发现了加载了/data/local/tmp下的任何东西,直接就挂掉。
那怎么办呢?让该文件夹生成到别的目录下,有一个-d参数,试了好多次,有些问题,一直都是在tmp目录下递归生成。所以便想到,你既然去检测maps,肯定是要读取里面的内容,然后寻找是否有该目录的字段咯。
那就直接hook open函数,将原程序的maps文件中一切带有tmp的行都过滤掉,剩余的内容输出到另一个文件中,最后修改open的返回值,指向新生成的文件。完美!
function main() { const openPtr = Module.getExportByName('libc.so', 'open'); const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']); var readPtr = Module.findExportByName("libc.so", "read"); var read = new NativeFunction(readPtr, 'int', ['int', 'pointer', "int"]); var fakePath = "/data/datamaps"; var file = new File(fakePath, "w"); var buffer = Memory.alloc(512); Interceptor.replace(openPtr, new NativeCallback(function (pathnameptr, flag) { var pathname = Memory.readUtf8String(pathnameptr); var realFd = open(pathnameptr, flag); if (pathname.indexOf("maps") != 0) { while (parseInt(read(realFd, buffer, 512)) !== 0) { var oneLine = Memory.readCString(buffer); if (oneLine.indexOf("tmp") === -1) { file.write(oneLine); } } var filename = Memory.allocUtf8String(fakePath); return open(filename, flag); } var fd = open(pathnameptr, flag); return fd; }, 'int', ['pointer', 'int']));}setImmediate(main)
完美过掉!
0x02 SO层算法逆向
1、抓个包看看,里面都有啥东西?
登录的时候,用户名:123456789,密码:123456
根据字段名字的分析,重点关注的是sign字段(看着像是个hash散列),其次这个password应该是经过加密处理的。
2、so算法逆向(passwrod参数)
既然是分析so层算法,具体的Java层的分析和定位就不浪费时间分析了。那么如何定位so文件和具体的函数呢。在Java层在和so层函数交互的时候,就是通过的JNI的机制,所以在so层函数加密数据之后,一定会把加密后的数据返回,通过JNIEnv下的NewStringUTF函数返回给Java层,所以hook一下这个函数,并且输出堆栈。
function hook_NewStringUTF(){ var artModule = Process.findModuleByName("libart.so"); var symbols = artModule.enumerateSymbols(); var newStringUTF = null; for (let i = 0; i < symbols.length; i++) { let symbol = symbols[i]; if(symbol.name.indexOf("NewStringUTF") != -1 && symbol.name.indexOf("Check") == -1){ console.log(symbol.name); newStringUTF = symbol.address; } } Interceptor.attach(newStringUTF, { onEnter : function(args){ console.log(args[1].readCString()); console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n') },onLeave : function(retval){ } })}
观看输出,尤其是堆栈信息,发现是libNativeHelper.so中的后面的是函数在文件中的偏移地址。
IDA反编译找到相应函数,反编译之后进行分析,怎么分析最快呢,当然是从你已经拥有的数据去反推未知的数据。(从后往前分析)
找到了NewStringUTF的调用,v19就是我们的FfQn1pwmgRY=,那么v19是怎么来的(猜测可能是base64,验证一下),找到上面的sub_1FEC,去里面看看。
看到这里面有一串字符,和base64很像。hook一下看看
输入
输出
难道上面的16进制数据,就是编码前的数据?经过验证之后,这个函数就是base64的编码函数,编码的数据以16进制形式传入。
那这个 15f427d69c268116 数据又是什么?继续向上找
进入这个函数一看,看不懂,不知道是干啥的太复杂了,hook看看。
这第一个参数不就是我们传入的密码么
第二个参数不就是刚才的16进制数据吗,其他参数看不懂了,猜测该函数应该是某种加密
而还有一个v22 = 0xEFCDAB9078563412LL;难道是某种密钥或者IV?
v23里面的数据很多,这是什么东西,跟踪v23的有关函数去看看
进入函数里面查看,发现感觉有点眼熟,是DES加密?dest又是什么,dest是通过a3传过来的,经过hook之后,a3的数据是***************(程序的密钥,不方便展出)。
这个时候就有很多想法了,一个简单的数据,经过函数处理之后,出现了大量的内容,同时v23还是加密函数中的参数。难道是子密钥的生成??感觉很强烈,进入查看果然很像!
那这么一看v23就是子密钥咯,那v22很有可能就是IV向量了,去验证一下
验证是正确的(一定要注意字节序的问题)。
最终得出结论,将我们输入的密码,经过DES/CBC模式加密后,再经过base64编码就是password的值。
0x03 so算法逆向(sign参数)
根据最开始的hook NewStringUTF,找到对应的函数位置。
不再一步步的分析了,直接找到特征
好像是MD5的初始化常量啊。
hook
可以得到明文,然后对比一下数据包中
可以看到是 captcha + captchaId + dateline + deviceIdentifier + info + password + username + “ef2vx#sf*^FlklSD*9sdf(m$&qw%d7po” 拼接起来的数据。
这里的数据就是经过md5加密后的内容,验证一下。
可以得到结论,sign的值就是将数据包中的
captcha + captchaId + dateline + deviceIdentifier + info + password + username + “ef2vx#sf*^FlklSD*9sdf(m$&qw%d7po”
拼接之后,在进行md5散列的结果。
至此,整个逆向结束。
来源地址:https://blog.csdn.net/weixin_43889136/article/details/127713563