Android逆向之破解某应用加密算法(动态调试so和hook so代码)

转载自:http://www.520monkey.com/archives/1310

一、样本静态分析

最近有位同学发了一个样本给我,主要是有一个解密方法,把字符串加密了,加解密方法都放在so中,所以之前也没怎么去给大家介绍arm指令和解密算法等知识,正好借助这个样本给大家介绍一些so加密方法的破解,首先我们直接在Java层看到加密信息,这个是这位同学直接告诉我这个类,我没怎么去搜了:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第1张图片

这个应用不知道干嘛的,但是他的防护做的还挺厉害的,之前我们介绍过小黄车应用内部也用了这种中文混淆变量和方法等操作,这里就不多解释了,这里主要看那个加密算法:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第2张图片

看到这里有一个加解密方法,传入字符串字节,返回加解密之后的字节数据,我们直接用IDA打开这个libwechat.so文件:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第3张图片

这里可惜没有收到Java_xxx这样的函数,说明他可能用了动态注册,所以就去搜JNI_OnLoad函数,所以这里注意大家以后如果打开so之后发现没有Java_xxx这样的函数开头一般都是在JNI_OnLoad中采用了动态注册方式,所以只需要找到JNI_OnLoad函数,然后找到RegisterNatives函数即可,不过在这个过程中我们需要转换JNIEnv指针信息:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第4张图片

这里大家如果看到类似于vXX+YY这样的,选中vXX变量,然后按Y按键,然后替换成JNIEnv*即可,我们如果手动注册过Native方法,都知道RegisterNatives函数的三个参数含义:

jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)

第一个参数:需要注册native函数的上层Java类

第二个参数:注册的方法结构体信息

第三个参数:需要注册的方法个数

这里当然是重点看第二个参数,这里当然也需要知道方法结构体信息:

typedef struct {
const char* name;
const char* signature;
void*       fnPtr;
} JNINativeMethod;

结构体包含三部分分别是:方法名、方法的签名、对应的native函数地址;那么这里我们肯定重点看第三部分,因为要找到具体的解密函数,这时候我们需要去对RegisterNatives函数查看他的实参值:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第5张图片

这里选中RegisterNatives函数名,然后右键选择Force call type即可:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第6张图片

这时候就看到了RegisterNatives的三个参数值,其实这里看到是四个,这个主要是调用方式的区别,因为我们还会看到有这种调用方式:(*JNIEnv)->RegisterNatives(JNIEnv env…),所以第一个参数其实是JNIEnv变量,这里就看第三个参数的地址就是需要注册方法的结构体信息,点击进入查看:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第7张图片

这里看到了方法名,方法签名以及对应的具体函数,这里主要看解密函数,找到decryptData即可,然后点击进入查看:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第8张图片

按下F5查看C语言代码:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第9张图片

继续点击进入查看:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第10张图片

这里就是实际的解密算法的地方了,大致看一下其实还是很简单的,就是有一个AES_CBC_128算法加解密的,我们用过这个算法都知道需要key和iv值,因为是128位的,所以这两个值肯定是16(128/8)个字节,这个是基础知识也是非常关键的知识,知道是16个字节对于后面分析破解非常关键。然后需要从解密之后的字节数组的最后一位获取实际字节的长度,最后构建byte数组返回给Java层即可。所以这里我们看到最重要的是如何获取aes解密的key和iv值。这里有很多种方式可以动态调试,可以hook。但是我们先不介绍这两种解密方式,我们先来看看另外一个问题。

 

二、调用so功能函数(修改指令)

我们在之前是不是有时候解密一个so算法,其实没不要真的知道他的解密算法,而是可以调用他的so然后直接解密出来数据即可,所以我们本文也来尝试做一下,为什么这么做因为在这个过程中我想给大家介绍一些知识点比如修改arm指令等,我们把这个应用的so拷贝到项目中,然后构建一个native类和方法,最后调用解密方法,发现调用直接出现崩溃信息:这时候我们发现在进入JNI_OnLoad挂了,说明JNI_OnLoad中做了一些东西检测:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第11张图片

看到JNI_OnLoad函数中有这两个函数调用,第一个我们都知道为了防止自己的进程被人恶意附加,就自己先占坑,这样别人就附加失败了,第二个看似也是类似功能,不过不用关心内部实现,我们为了后面动态调试成功,这里还是先把这两个函数干掉吧,这里干掉简单直接改成NOP空指令就可以了,就相当于没调用了。因为这两个函数的执行逻辑和返回结果和后面的逻辑是没任何关系的,所以可以这么做,如果有关系那只能修改返回值了。修改指令之前其实介绍过了,很简单先找到指令对应的偏移地址:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第12张图片

然后用010Editor工具打开so文件,找到这个地址:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第13张图片

怎么修改成NOP指令呢?有一个牛逼的网站在线转换arm为hex值:http://armconverter.com:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第14张图片

这里看到转换BLX指令的HEX正好和上面看到的HEX值对应上了,这里修改NOP指令:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第15张图片

看到NOP指令对应的HEX值是C046,那就修改吧:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第16张图片

这里注意需要把那两条指令的所有HEX全部改成NOP指令,保存再用IDA打开查看:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第17张图片

修改成功,这两个函数就等于没调用了,在运行调用so还是崩溃,这时候需要想到的是有签名校验,而巧合的是在搜索JNI的时候无意发现了这个函数:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第18张图片

当然如果大家想知道so中有没有签名校验,可以直接Shift+F12查找字符串内容”signatures”:

一般有这类字符串信息都有签名校验功能了,我们继续看上面那个签名校验函数:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第19张图片

果然这里会获取签名信息,然后比对返回1表示正确的签名信息,这里我们不要直接修改返回值和那个v5变量值,因为我们知道strcmp函数执行的结果是-1,0,1;这里明显是需要让返回值是0才可以,那不如直接修改v3的初始值为1即可,修改方法和上面的指令修改类似:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第20张图片

记住这个便宜地址,然后去010Editor工具中查看:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第21张图片

然后把赋值修改成1:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第22张图片

然后去010Editor修改即可:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第23张图片

修改之后保存,用IDA打开so:

看到已经修改成功了,然后在F5查看伪代码:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第24张图片

这里不管签名对不对,都直接返回1了,修改了之后我们在运行发现还是报错,这个需要再去看JNI_OnLoad函数了:

这里需要获取一个Java层的类,所以我们在工程中新建这个类即可,这个类可以没有任何方法:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第25张图片

然后运行成功,看看解密之后的内容是啥:

看到解密之后的内容是个字符串version内容,到此我们就成功的过掉了so中的一些检测调用so解密出来内容了,那么在这个过程中我们依然可以学到很多东西:

第一、修改指令,如果不想让一个函数执行,只需要把跳转指令修改成NOP空指令即可,前提是这个函数的执行结果和后面的逻辑没有半毛钱的关系,如果有那么就需要修改函数的返回值,一般需要修改跳转指令之后的MOVS指令的寄存器值,如果简单点可以直接修改变量的初始化值,比如这里的过掉签名校验。

第二、如果快速的知道so中是否有签名校验功能,可以直接在字符串列表中搜索”signatures”即可,现在也有很多应用会在so中调用Java层的类信息,所以需要去看JNI_OnLoad中arm指令,或者直接搜索字符串列表,因为一般Java层类信息,都是xxx/yyy/zzz/MMM这样的字符串格式,通过肉眼排查也是可以的。

 

三、动态调试so获取解密算法

虽然我们成功的调用了so解密出内容了,但是这个不是本文的重点,本文的重点是把这个解密算法弄出来,不过在之前已经分析了大概,我们只需要弄到aes的key和iv值即可,这里有两种方式一种是用Frida进行hook操作,一种是动态调试,这里动态调试非常简单,前提是用我们上面已经修改过指令的so包,不然内部有一些反调试检测。为了方便用我们的demo工程进行动态调试即可:

第一步:运行手机端的android_server

第二步:端口转发

adb forward tcp:23946 tcp:23946

第三步:调试运行程序

adb shell am start -D -n cn.wjdiankong.awwechathack/.MainActivity

第四步:打开IDA附加进程

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第26张图片
第五步:设置Debugger Option中勾选上加载so断点

这里其实是为了防止JNI_OnLoad函数中有一些检测,我们需要断点调试找到这些检测,但是因为我们之前已经手动的把检测给弄掉了,所以这里勾不勾选都无所谓了。

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第27张图片

第六步:等待调试jdb连接

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8600

这里的端口号需要从DDMS中查看,是个红色小蜘蛛:

第七步:IDA中运行程序或者按F9

这里可以一路往下按,因为会加载很多系统so文件,一路按下去直到加载到了我们自己的so,这里没有检测直接过去即可

运行成功就jdb连接成功了。

第八步:在Module中找到需要调试的so文件

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第28张图片

找到之后双击进入so文件。

第九步:找到需要调试的函数

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第29张图片

找到需要调试的函数之后,点击进入查看即可,到这里我们就给我们需要调试的函数下好了断点信息,上面的几个步骤都是基本操作,因为之前已经介绍了很多遍了,这里就不再多介绍了,如果想了解更多内容可以购买本人的逆向大黄书《Android应用安全防护和逆向分析》;然后我们在通过静态分析获取到我们那个aes解密算法地方:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第30张图片

我们可以通过静态分析so找到那个需要调试的函数地址:

看到这里双开IDA的好处是动静结合非常方便,这时候我们只需要查看函数调用前的参数值也就是那几个MOV指令的寄存器值:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第31张图片

R0寄存器保存的是需要解密的内容:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第32张图片

这里看到后面的几个参数很可能就是key或者是iv值,我们点击查看R3寄存器的详细值:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第33张图片

看到这里的数据有点特别,首先是16个字节的不知道是啥可能是iv或者是key值,但是后面接着是一个16字节的值,所以这里看到这两个16字节的值可能就是我们想要的key和iv值了,所以这里有一个重要的知识点就是key和iv肯定是16字节值,因为用的是aes_cbc_128的加密方式。可以继续往下看:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第34张图片

看到这个不确定是iv还是key的值,接着往下走:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第35张图片

看到这个解密后的内容就开心多了,而且看到后面的值是9,也就是总长度16-9=7也就是version的长度,这里的总长度是固定的16,因为Java层传递的字节数组长度是16。

 

四、Hook获取解密算法

好了到这里我们成功的获取到了需要的key值和iv值,但是不确定他们是具体的值,这个简单,我们用Java代码写一个然后尝试互换彼此即可,但是到这里就结束了吗?其实不然,因为按照我的性格我会通过一个案例把我知道的我会的统统告诉大家,所以我们用另外一种方式获取key和iv值,因为看到上面的动态调试虽然靠谱但是过于繁琐,所以这里就用之前介绍过的Frida框架来hook这个解密函数直接打印他的几个参数值,关于Frida来hook功能不了解的同学可以查看这一篇文章:Hook神器Frida介绍;这里我们看到这个加密函数是导出的:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第36张图片

然后写一下frida的hook脚本:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第37张图片

然后我们就开始运行了,当然前提是你得安装好Frida环境,具体内容看我的那一篇文章即可:

第一步:运行手机端的frida-server

第二步:转发端口

第三步:运行hook脚本

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第38张图片

运行之后我们就看到数据了,通过和上面的IDA动态调试出来的数据也是一致的,而且发现这种hook方式太无敌了,非常方便快捷,太好用了。

 

五、Java代码实现解密算法

接下来我们就把这个key和iv放到Java代码中运行一下吧:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第39张图片

这里不确定key和iv值,互换一下尝试就弄出来了:

Android逆向之破解某应用加密算法(动态调试so和hook so代码)_第40张图片

然后运行就成功解密了:

 

六、技术总结

到这里我们终于把so中的加密算法解密出来了,本文的内容非常多,因为我不想给大家只是一个结果,我在这个过程中我遇到的问题和解决办法以及我学到的东西我都想告诉你们,接下来我们总结本文的内容:

第一、修改指令,如果不想让一个函数执行,只需要把跳转指令修改成NOP空指令即可,前提是这个函数的执行结果和后面的逻辑没有半毛钱的关系,如果有那么就需要修改函数的返回值,一般需要修改跳转指令之后的MOVS指令的寄存器值,如果简单点可以直接修改变量的初始化值,比如这里的过掉签名校验。

第二、如果快速的知道so中是否有签名校验功能,可以直接在字符串列表中搜索”signatures”即可,现在也有很多应用会在so中调用Java层的类信息,所以需要去看JNI_OnLoad中arm指令,或者直接搜索字符串列表,因为一般Java层类信息,都是xxx/yyy/zzz/MMM这样的字符串格式,通过肉眼排查也是可以的。

第三、动态调试so的步骤还是需要熟悉的,但是有时候为了方便快捷Frida直接hook得到结果也是一个非常不错的选择。

第四、对于一些常规的加密算法的特点一定要知道,这个是对于一个破解者最基本的素质要求,本文如果知道了aes_cbc_128的加密方式的key和iv都是16个字节对于本文来说是非常关键的。没事别看那些步兵骑兵啥的,多看看加密算法的特点。

你可能感兴趣的:(android,逆向工程)