Android逆向之
发布时间: 2023-07-06
一、前言

今天我们继续来看破解apk的相关知识,在前一篇:Eclipse动态调试smali源码破解apk我们今天主要来看如何使用IDA来调试Android中的native源码,因为现在一些app,为了安全或者效率问题,会把一些重要的功能放到native层,那么这样一来,我们前篇说到的Eclipse调试smali源码就显得很无力了,因为核心的都在native层,Android中一般native层使用的是so库文件,所以我们这篇就来介绍如何调试so文件的内容,从而让我们破解成功率达到更高的一层。

二、知识准备

我们在介绍如何调试so文件的时候,先来看一下准备知识:

面的时候,ctrl+s可以快速定位到我们想要调试的so文件映射到内存的地址:


因为一般一个程序,肯定会包含多个so文件的,比如系统的so就有好多的,一般都是在/system/lib下面,当然也有我们自己的so,这里我们看到这里的开始位置和结束位置就是这个so文件映射到内存中:


这里我们可以使用cat命令查看一个进程的内存映射信息:cat /proc/[pid]/maps

我们看到映射信息中有多so文件,其实这个不是多个so文件,而是so文件中对应的不同Segement信息被映射到内存中的,一般是代码段,数据段等,因为我们需要调试代码,所以我们只关心代码段,代码段有一个特点就是具有执行权限x,所以我们只需要找到权限中有x的那段数据即可。

4、G快捷键:在IDA调试页面的时候,我们可以使用S键快速跳转到指定的内存位置


这里的跳转地址,是可以算出来的,比如我现在想跳转到A函数,然后下断点,那么我们可以使用上面说到的ctrl+s查找到so文件的内存开始的基地址,然后再用IDA View中查看A函数对应的相对地址,相加就是绝对地址,然后跳转到即可,比如这里的:

Java_cn_wjdiankong_encryptdemo_MainActivity_isEquals 函数的IDA View中的相对地址(也就是so文件的地址):E9C


上面看到so文件映射到内存的基地址:74FE4000


那么跳转地址就是:74FE4000+E9C=74FE4E9C

注意:

一般这里的基地址只要程序没有退出,在运行中,那么他的值就不会变,因为程序的数据已经加载到内存中了,基地址不会变的,除非程序退出,又重新运行把数据加载内存中了,同时相对地址是永远不会变的,只有在修改so文件的时候,文件的大小改变了,可能相对地址会改变,其他情况下不会改变,相对地址就是数据在整个so文件中的位置。


这里我们可以看到函数映射到内存中的绝对地址了。

注意:

有时候我们发现跳转到指定位置之后,看到的全是DCB数据,这时候我们选择函数地址,点击P键就可以看到arm指令源码了:


5、调试快捷键:F8单步调试,F7单步进入调试


上面找到函数地址之后,我们可以下断点了,下断点很简单,点击签名的绿色圈点,变成红色条目即可,然后我们可以点击F9快捷键,或者是点击运行按钮,即可运行程序:


其中还有暂停和结束按钮。我们运行之后,然后在点击so的native函数,触发断点逻辑:


这时候,我们看到进入调试界面,点击F8可以单步调试,看到有一个PC指示器,其实在arm中PC是一个特殊的寄存器,用来存储当前指令的地址,这个下面会介绍到。

好了到这里,我们就大致说了一下关于IDA在调试so文件的时候,需要用到的快捷键:

1、Shift+F12快速查看so文件中包含的字符串信息

2、F5快捷键可以将arm指令转化成可读的C代码,这里同时可以使用Y键,修改JNIEnv的函数方法名

3、Ctrl+S有两个用途,在IDA View页面中可以查看so文件的所有段信息,在调试页面可以查看程序所有so文件映射到内存的基地址

4、G键可以在调试界面,快速跳转到指定的绝对地址,进行下断点调试,这里如果跳转到目的地址之后,发现是DCB数据的话,可以在使用P键,进行转化即可,关于DCB数据,下面会介绍的。

5、F7键可以单步进入调试,F8键可以单步调试


面中看到Java_cn_wjdiankong_encryptdemo_MainActivity_isEquals 函数的指令代码:


我们可以简单的分析一下这段指令代码:

1 、PUSH {r3-r7,lr} 是保存r3,r4,r5,r6,r7,lr 的值到内存的栈中,那么最后当执行完某操作后,你想返回到lr指向的地方执行,当然要给pc了,因为pc保留下一条CPU即将执行的指令,只有给了pc,下一条指令才会执行到lr指向的地方

pc:程序寄存器,保留下一条CPU即将执行的指令
lr: 连接返回寄存器,保留函数返回后,下一条应执行的指令

这个和函数最后面的POP {r3-r7,pc}是相对应的。

2 、然后是调用了strlen,malloc,strcpy等系统函数,在每次使用BLX和BL指令调用这些函数的时候,我们都发现了一个规律:就是在调用他们之前一般都是由MOV指令,用来传递参数值的,比如这里的R5里面存储的就是strlen函数的参数,R0就是is_number函数的参数,所以我们这样分析之后,在后面的动态调试的过程中可以得到函数的入口参数值,这样就能得到一些重要信息


3 、在每次调用有返回值的函数之后的命令,一般都是比较指令,比如CMP,CBZ,或者是strcmp等,这里是我们破解的突破点,因为一般加密再怎么牛逼,最后比较的参数肯定是正确的密码(或者是正确的加密之后的密码)和我们输入的密码(或者是加密之后的输入密码),我们在这里就可以得到正确密码,或者是加密之后的密码:


到这里,我们就分析完了native层的密码比较函数:Java_cn_wjdiankong_encryptdemo_MainActivity_isEquals

如果觉得上面的ARM指令看的吃力,可以使用F5键,查看他的C语言代码:


我们这里看到其实有两个函数是核心点:

1 is_number函数,这个函数我们看名字应该猜到是判断是不是数字,我们可以使用F5键,查看他对应的C语言代码:


这里简单一看,主要是看return语句和if判断语句,看到这里有一个循环,然后获取_BYTE*这里地址的值,并且自增加一,然后存到v2中,如果v3为 \0 的话,就结束循环,然后做一次判断,就是v2-48是否大于9,那么这里我们知道48对应的是ASCII中的数字0,所以这里可以确定的是就是:用一个循环遍历_BYTE*这里存的字符串是否为数字串。

2 get_encrypt_str函数,这个函数我们看到名字可以猜测,他是获取我们输入的密码加密之后的值,再次使用F5快捷键查看:


这里我们看到,首先是一个if语句,用来判断传递的参数是否为NULL,如果是的话,直接返回,不是的话,使用strlen函数获取字符串的长度保存到v2中,然后使用malloc申请一块堆内存,首指针保存到result,大小是v2+1也就是传递进来的字符串长度+1,然后就开始进入循环,首指针result,赋值给i指针,开始循环,v3是通过v1-1获取到的,就是函数传递进来字符串的地址,那么v6就是获取传递进来字符串的字符值,然后减去48,赋值给v7,这里我们可以猜到了,这里想做字符转化,把char转化成int类型,继续往下看,如果v6==48的话,v7=1,也就是说这里如果遇到字符 0 ,就赋值1,在往下看,看到我们上面得到的v7值,被用来取key_src数组中的值,那么这里我们双击key_src变量,就跳转到了他的值地方,果不其然,这里保存了一个字符数组,看到他的长度正好是18,那么这里我们应该明白了,这里通过传递进来的字符串,循环遍历字符串,获取字符,然后转化成数字,在倒序获取key_src中的字符,保存到result中。然后返回。

好了,到这里我们就分析完了这两个重要的函数的功能,一个是判断输入的内容是否为数字字符串,一个是通过输入的内容获取密码内容,然后和正确的加密密码:ssBCqpBssP 作比较。


我们选择Debugger选项,选择Attach,看到有很多debugger,所以说IDA工具真的很强大,做到很多debugger的兼容,可以调试很多平台下的程序。这里我们选择Android debugger:


这里看到,端口是写死的:23946,不能进行修改,所以上面的adb forward进行端口转发的时候必须是23946。这里PC本地机就是调试端,所以host就是本机的ip地址:127.0.

微信