一、前言
今天开始我们后续会开展三篇左右的arm指令学习,因为之前一直都有同学和我说有没有详细的arm指令分析,这个之前的确一直没有好的样本,有的人可能说可以用自己写的C代码然后反编译so来学习,那样因为都知道了原始代码所以解析来就没啥难度,这一次正好用最右这个app的签名算法作为样本学习,为什么用它的呢?因为之前有同学找我分析了一下,分析过程中觉得是个学习指令的好样本就分享给大家,因为他的签名算法稍微有点复杂,所以就分为三篇来介绍,希望通过这一系列的文章之后大家能够了解到arm指令的常规语法,能够简单的分析so代码逻辑。
二、逆向分析
接下来我们首先看这个应用的签名算法调用的so和方法,这个后续会提供样本,我们用Jadx打开应用找到他的native方法:
然后找到了so文件之后,用IDA直接打开,找到核心的加密函数sub_5070,这个后面文章会详细介绍如何找到这个函数的,因为本文的目的是为了解析arm指令集的知识,我们查看这个函数的伪代码:
通过分析,第一个参数是加密内容,第二个参数是长度,第三个参数是加密之后的字符串:
我们本文就要把这段代码解析出来,看着其实也不复杂,但是这个加密算法后续还有好几个这样的循环操作,都是字节的位操作,首先不着急,我们先把初始化的代码看一下:
这里有一段初始化代码,不过我们通过简单分析之后发现其实没那么难,这一段可以直接阅读以下,这里会判断当前字符串的信息是否大于64,如果小于64就把后面的数据补0,这里可以看到是分组进行数据操作,一组数据是64个字节,会把加密数据用分组的形式进行操作。
三、Hook加密函数
我们先用Frida去hook这个函数,把加密的信息打印出来然后在分析arm指令,最后用代码实现这个算法来看看结果是否正确:
看到这里把参数和结果都打印出来了,看到我们在后续的分析过程中会动态调试就用这个原始加密信息,然后看看hook代码:
这里看到一个小技巧:我们在之前的Frdia框架学习中知道如何hook未导出的函数,都是通过查看maps文件得到so地址,然后加上需要hook的函数地址就是这个函数的绝对地址了,但是这样每次都要查看maps文件太麻烦了,所以这里可以借助已经导出的函数绝对地址减去相对地址就是基地址,然后在加上需要hook的未导出的函数相对地址就是这个函数的绝对地址了:
这里看到JNI_OnLoad是一个导出函数,然后减去他的相对地址就是so在内存中的基地址了,最后加上我们需要hook的函数地址即可。不了解Frida框架的同学可以看这篇文章:Android中的Hook家族Frida框架用法解析;
四、动态调试
有了这个原始加密信息,接下来我们就开始动态调试来解读那段arm指令吧,我们还是按照以前的套路,把应用的so拷贝到自己的工程中,然后把native的类原封不动的拷贝过来:
然后运行开始我们的动态调试,关于动态调试不想在多说了,可以购买我的逆向大黄书《Android应用安全防护和逆向分析》书里面会详细介绍这个操作步骤:
第一步:运行android_server
第二步:转发端口和运行应用(Debug模式)
第三步:IDA附件进程
第四步:运行jdb连接调试
然后这里记得在设置一下Debug Option:
然后一路往下运行F9即可,点击工程中的按钮,加载so即可:
在模块窗口中找到so的基地址,加上我们需要调试的函数相对地址就是这个函数的绝对地址:
这时候发现不是常规的arm指令,可以按P键进行显示:
然后就可以下断点调试了,一路往下按F7即可单步调试,到第一个知识点的地方:一般MOVW指令是赋值的地方,这里看到我们常见的指令后面都有一个W,比如MOV->MOVW其实含义都差不多:
这里看到会赋值四个常量值给R8,R9,R10,R12寄存器,往下走之后看看寄存器的内容:
发现了这四个赋值之后的内容,我们在用代码把静态代码的地方打印一下:
这里有多处赋值,不要关心只要在乎一次赋值就好:
看到这里打印的值就是上面的寄存器中的值,所以这里大家以后再静态分析代码看到常量的时候对应的指令都是MOV。继续往下走:
在往下的过程中会发现有几处是跳转指令,这里大家记住如果发现有跳转指令最后在指令的后面一条指令打上断点,这样为了防止跳转不回来了,发现想回来的话可以按F9到下个断点即可。
这里发现有一个常量字符串应该是加密数据,但是这个和我们Frida进行hook的不一样呀?这里郁闷了一段时间结果发现,是代码初始化的时候也会走到这里,所以我们直接F9过了这一次执行,又到了sub_5070的地方,这时候发现数据就对了:
然后往下走发现就到了那个第一个位操作循环了:
这个地方逻辑其实就是对原始数据进行操作,这里我们可以直接把这段代码拷贝出去即可,然后在来静态分析初始化代码:
就这样我们可以把初始化代码用C代码实现出来了:
这里看到实现代码很简单的,继续往下走动态调试:
这里就到了我们需要详细分析的地方了,看到这里有几个非常重要的指令:
AND指令进行逻辑”与”操作;
ORR指令进行逻辑”或”操作;
EOR指令进行逻辑”异或”操作;
ORN指令进行逻辑”或非”操作;
BIC指令进行”RN AND NOT RM”操作;
我们直接动态调试看结果:
首先这几个位操作,操作的数据是我们开始初始化的四个常量值,继续往下走:
一般我们遇到LDR指令,都会对应的寄存器内容,因为LDR指令是把内存中的常量地址中的数据加载到指定寄存器中,这里看到这个字符串信息其实就是我们加密的数据,这里一次会加载四个字节:
看到加载到R12寄存器中,继续往下走,这里的ADD,CMP,MOV等指令就不多介绍了,因为太简单了:
又看到一个LDR指令,继续查看寄存器的地址内容发现了一个类似于密钥库的数据,我们再回到静态代码块中:
看到这里的确用到一个常量池,每次取出16字节,一共64个字节密钥库数据,我们把密钥库数据摘取出来即可:
继续往下走:
这里发现了一条关键指令就是循环右移操作:
我们把这一部分代码用C语言实现一下,然后打印结果看到了和上面的动态调试结果是一样的,所以我们就把第一部分指令解析完成了,继续往下看:
又到了一个循环右移20位的地方了,看到操作后的结果,我们在C代码中打印看看结果对不对:
看到结果是一致的,到这里我们已经过了一半了,后面还有两处循环右移操作,其实指令都差不多,这里就不在多分析了,可以看看操作后的结果,一位是循环了四次我们都查看一下:
看到这四次循环的结果,然后在把C代码打印结果是否一致:
看到了吧解密数据结果一致,所以到这里我们就把第一部分的循环结果解析出来了,本文的内容到这里就结束了,因为每一步调试都截图了,所以图片也很多写的过程中也难受,希望看完之后点赞分享。
五、指令总结
本文看完之后,我们学习到了一些新的指令和特殊指令的处理办法:
- 1、现在很多加密算法都喜欢用字节的位操作,比如与或非、异或等,这些指令是AND/ORR/EOR/BIC等。
- 2、一般看到LDR指令都要想到是把一个常量地址中的值赋值给当前寄存器,这时候可以习惯性的查看加载地址的常量值。比如这里的密钥库信息。
- 3、对于跳转指令BL等,一定要记得在他的下一条指令打断点为了防止跳转过去之后回不来了,这样就很难受。
- 4、IDA打开so之后记得F5查看伪代码,为了快速实现加密算法,有时候没必要读懂每一行arm指令,只要用关键代码实现了即可,每一步代码执行之后通过打印值和动态调试之后的结果进行比对是否一致。
本文的目的只有一个就是学习更多的逆向技巧和思路,如果有人利用本文技术去进行非法商业获取利益带来的法律责任都是操作者自己承担,和本文以及作者没关系,本文涉及到的代码项目可以去编码美丽小密圈自取,欢迎加入小密圈一起学习探讨技术
六、总结
关于最右app的签名算法解析全过程等这一系列文章解析完了在来说,本文解密用的是C语言,用Xcode跑的,大家可以用自己喜欢的编译器跑就好了,看so代码的时候不是表示每一行arm指令都要看懂,而且大部分其实都能看到,有一些不常见的可能需要慢慢看和打印看结果。期待后续的两篇文章继续看如何解析后续的加密操作。
《Android应用安全防护和逆向分析》
点击立即购买:京东 天猫
更多内容:点击这里
关注微信公众号,最新技术干货实时推送