感觉好久没发文章了,来一篇。
快捷键
F5
汇编转伪cShift + F12
快速查看so中的字符串Ctrl + F12
可以预览代码流程块Ctrl + f
搜索y
改名 等同右键 Rename lvarCtrl + s
在IDA View页面中查看so的所有段信息,在调试页面查看程序所有so文件映射到内存的基地址G
快速跳转指定内存位置x
查看交叉函数(调用处)\
右键 hide casts 美化代码的快捷键空格
切换视图P
DCB数据转 arm
还原JNI函数名方法
。解决方法非常简单,只需要对JNIEnv指针做一个类型转换即可。比如说上面提到a1和v4指针,比如我们把 a1 替换为 JNIEnv*:
其实经常看的应该知道 这里668 就是 NewStringUTF
这里是常见 JNIEnv 方法对应数字
对应数字 | 函数 | 详细 |
---|---|---|
662 | NewStringUTF | |
672 | GetStringUTFLength | jsize(*)(JNIEnv*, jstring) |
662 | NewStringUTFChars | const char* (*)(JNIEnv*, jstring, jboolean*) |
662 | ReleaseStringUTFChars | void (*)(JNIEnv*, jstring, const char*) |
662 | GetArrayLength | jsize (*) (JNIEnv*, jarray) |
662 | NewObjectArray | jobjectArray (*)(JnIEnv*, jsize, jclass, jobject) |
常识
- 有的时候IDA识别不出来一个函数的参数,我们需要先跳转进去,再退回来!
00
代表结尾 比如下图真正内容是前面红框中
ARM基础
其实这类型的文章,网上太多了,我这里就只是简单的说几个常见 的 这文章不错
ARM中的寄存器
名称 | 含义 |
---|---|
R0-R3 | 用于函数参数及返回值的传递 |
R4-R6, R8, R10-R11 | 没有特殊规定,就是普通的通用寄存器 |
R7 | 栈帧指针(Frame Pointer) |
R9 | 操作系统保留 |
R12 | IP(intra-procedure scratch ) |
R13 | SP(stack pointer),是栈顶指针 |
R14 | LR(link register),存放函数的返回地址 |
R15 | PC(program counter),指向当前指令地址 |
常用指令
名称 | 含义 |
---|---|
ADD | 加指令 |
SUB | 减指令 |
STR | 把寄存器内容存到栈上去 |
LDR | 把栈上内容载入一寄存器中 |
.W | 是一个可选的指令宽度说明符。它不会影响为此指令的行为,它只是确保生成 32 位指令。Infocenter.arm.com的详细信息 |
BL | 执行函数调用,并把使lr指向调用者(caller)的下一条指令,即函数的返回地址 |
BLX | 同上,但是在ARM和thumb指令集间切换 |
CMP | 指令进行比较两个操作数的大小 |
寻址
立即数寻址
也叫立即寻址,是一种特殊的寻址方式,操作数本身包含在指令中,只要取出指令也就取到了操作数。这个操作数叫做立即数,对应的寻址方式叫做立即寻址。例如:
1 | MOV R0,#64 ;R0 ← 64 |
寄存器寻址
寄存器寻址就是利用寄存器中的数值作为操作数,也称为寄存器直接寻址。
1 | ADD R0,R1, R2 ;R0 ← R1 + R2 |
寄存器间接寻址
寄存器间接寻址就是把寄存器中的值作为地址,再通过这个地址去取得操作数,操作数本身存放在存储器中。
1 | LDR R0,[R1] ;R0 ←[R1] |
寄存器偏移寻址
这是ARM指令集特有的寻址方式,它是在寄存器寻址得到操作数后再进行移位操作,得到最终的操作数。
1 | MOV R0,R2,LSL #3 ;R0 ← R2 * 8 ,R2的值左移3位,结果赋给R0。 |
寄存器基址变址寻址
寄存器基址变址寻址又称为基址变址寻址,它是在寄存器间接寻址的基础上扩展来的。它将寄存器(该寄存器一般称作基址寄存器)中的值与指令中给出的地址偏移量相加,从而得到一个地址,通过这个地址取得操作数。
多寄存器寻址
这种寻址方式可以一次完成多个寄存器值的传送。
1 | LDMIA R0,{R1,R2,R3,R4} ;R1←[R0],R2←[R0+4],R3←[R0+8],R4←[R0+12] |
堆栈寻址
堆栈是一种数据结构,按先进后出(First In Last Out,FILO)的方式工作,使用堆栈指针(Stack Pointer, SP)指示当前的操作位置,堆栈指针总是指向栈顶。
1 | STMFD SP!,{R1-R7, LR} ;将R1-R7, LR压入堆栈。满递减堆栈。 |
示例
C 代码如下
1 |
|
ARM对应如下1
2
3
4
5
6
7add r0, r1 将参数a和参数b相加再把结果赋值给r0
ldr.w r12, [sp] 把最的一个参数f从栈上装载到r12寄存器
add r0, r2 把参数c累加到r0上
ldr.w r9, [sp, #4] 把参数e从栈上装载到r9寄存器
add r0, r3 累加d累加到r0
add r0, r12 累加参数f到r0
add r0, r9 累加参数e到r0
常见c
calloc
申请内存memset
从数组中取指定长度,(如果参数是0 就不用管)*a1
表示读取a1指针指向的内存内容sub_5ED4
函数名起头为 sub 的是因为符号表被隐藏ida无法识别, 后面跟的16进制数值是该函数的内存地址
动态调试
前置需求
- 使用 arm 架构来调试,以你为IDA7一下不支持其他
- 真机的情况下,请开启调试模式先
上传安卓服务端
拷贝ida下的 文件到手机目录中 data/local/tmp/
下
给这个文件执行权限
1 | // 【真机开启调试模式】 |
打开待调试app
附加进程(最好是打开2个ida 一个静态对比、一个动态)
我这里是 IDA7.0
- 打开一个空白 IDA 项目
- Debugger -> Attach -> 选择安卓
上面的端口,就是你转发的端口号
反调试策略
- 自行附加,让调试无法附加
在so中加上这行代码即可:ptrace(PTRACE_TRACEME, 0, 0, 0);
- 签名校验, 本地校验和服务器校验双飞!
- 用系统api判断应用调试状态属性,属于基础判断
- 检查android_server调式端口信息和进程信息, 反IDA的有效方式
- 检查自身status中的TracerPid字段,防止被其他进程附加调试
如果要做到更安全点,记得把反调试方案放到native层中,时机最早,一般在JNI_OnUnload函数里面,为了更安全点,native中的函数可以自己手动注册,函数名自己混淆一下也是可以的。现在一些加固平台为了更有效的防护,启动的多进程之间的防护监听,多进程一起参与反调试方案,这种方式对于破解难度就会增大,但是也不是绝对安全的。
处理有反调试的方法
这只是通常请下的处理方法,有很多情况需要自己处理
- 查看apk是否为可调式状态,可以使用aapt命令查看他的AndroidManifest.xml文件中的android:debuggeable属性是否为true,如果不是debug状态,那么就需要手动的添加这个属性,然后回编译,在签名打包从新安装
- 使用adb shell am start -D -n com.yaotong.crackme/.MainActivity 命令启动程序,出于wait Debug状态
- 打开IDA,进行进程附加,进入到调试页面
- 使用 jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700 命令attach之前的debug状态,让程序正常运行
- 设置Debug Option选项,设置Suspend on library start/exit/Suspend on library load/unload/Suspend on process entry point选项
- 点击运行按钮或者F9键,程序运行停止在linker模块中,这时候表示so文件加载进来了,我们通过Ctrl+S和G键跳转到JNI_OnLoad函数出,进行下断点
- 然后继续运行,进入JNI_OnLoad断点处,使用F8进行单步调试,F7进行单步跳入调试,找到反调试代码处
- 然后使用二进制软件修改反调试代码为nop指令,即00值
- 修改之后,在替换原来的so文件,进行回编译,从新签名打包安装即可
- 按照上面的无反调试的so代码步骤即可
总结:
现在很多应用防止别的进程调试或者注入,通常会用自我检测装置,原理就是循环检测/proc/[mypid]/status文件,查看他的TracerPid字段是否为0,如果不为0,表示被其他进程trace了,那么这时候就直接退出程序。因为现在的IDA调试时需要进程的注入,进程注入现在都是使用Linux中的ptrace机制,那么这里的TracePid就可以记录trace的pid,我们可以发现我们的程序被那个进程注入了,或者是被他在调试。进而采取一些措施。
IDA 调试原理
首先他得在被调试端安放一个程序,用于IDA端和调试设备通信,这个程序就是android_server,因为要附加进程,所以这个程序必须要用root身份运行,这个程序起来之后,就会开启一个端口23946,我们在使用adb forward进行端口转发到远程调试端,这时候IDA就可以和调试端的android_server进行通信了。后面获取设备的进程列表,附加进程,传递调试信息,都可以使用这个通信机制完成即可。IDA可以获取被调试的进程的内存数据,一般是在 /proc/[pid]maps 文件中,所以我们在使用Ctrl+S可以查看所有的so文件的基地址,可以遍历maps文件即可做到。