Android逆向 之 IDA静态分析so(一)
2020-06-10 本文已影响0人
Sharkchilli
概述
本文使用IDA对android调用so进行静态分析,以此实验掌握so层的一些分析技巧。
前置条件
ARM 汇编 (虚拟机为armebi-v7a)
IDA的基本使用
JNI开发基础
Android中调用so
# direct methods
# 加载so库
.method static constructor <clinit>()V
.locals 1
.prologue
.line 26
const-string v0, "verify"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
.line 27
return-void
.end method
#定义native 方法
.method public native verify([B)Ljava/lang/String;
#调用so层方法
invoke-virtual {v1, v2}, Lcom/example/cctf/MainActivity;->verify([B)Ljava/lang/String;
当我们反编译java代码为smali代码,看到以上类似的代码,就应该能猜得其关键逻辑是在so层进行处理的,这个时候我们就需要对so进行调试分析。
静态分析
so文件一般在apk中的lib文件夹中,将其解压出来,丢到IDA进行静态分析。
![](https://img.haomeiwen.com/i23598658/eae2ecc5273eeeb7.png)
![](https://img.haomeiwen.com/i23598658/d1f9ed991fd43b42.png)
![](https://img.haomeiwen.com/i23598658/2d3a4909d4107619.png)
进入Exports
![](https://img.haomeiwen.com/i23598658/9658dbabbda5458d.png)
可以看到有一个JNI_OnLoad且没有以java开头的导出函数,所以这里基本可以断定是动态注册。
进入后代码如下:
![](https://img.haomeiwen.com/i23598658/1a71d70bd303d457.png)
我们先使用IDA 导入C/C++头文件,添加头文件中的结构体,使用此结构体中的函数替换反汇编中的偏移,使文件可读性更好
点击IDA Pro 主界面上的“Structures”选项卡 然后按下Insert键打开“Create structure/union”对话框,点击界面上的"Add standard structure"按钮,在打开的结构体选择对话框中选择JNINativeInterface并点击OK返回,同理JNIInvokeInterface结构体_JNIEnv和_JavaVm也导入进来;
![](https://img.haomeiwen.com/i23598658/508a9bee7ded1eeb.png)
![](https://img.haomeiwen.com/i23598658/600b17b4a44d105b.png)
如果没有结构体请导入jni.h头文件,可参考以下教程:
报错的h文件都注释掉就好了。
https://blog.csdn.net/u010382106/article/details/44960243
这时候回到汇编窗口,右键相关函数就可以转成对应的方法了:
![](https://img.haomeiwen.com/i23598658/70c3b899de1b5529.png)
按F5进入,c/c++窗口:
将函数的参数转化为正确的参数,鼠标点击参数,然后右键选中 Convert to Struct *
![](https://img.haomeiwen.com/i23598658/0ef230d147f68e79.png)
这里当然转成JavaVM
![](https://img.haomeiwen.com/i23598658/bb9c1309f1818438.png)
#其中代码如下,我们可以猜测到v6为JNIEnv
!a1->functions->GetEnv((JavaVM *)a1, (void **)&v6, 65540)
将v6转成JNIEnv
#这句可以知道v3也是JNIEnv
v3 = v6
最终分析代码如下:
int __fastcall JNI_OnLoad(_JavaVM *a1, int a2, int a3)
{
_JNIEnv *v3; // r5@2
int v4; // r1@3
int result; // r0@4
_JNIEnv *v6; // [sp+4h] [bp-14h]@1
int v7; // [sp+8h] [bp-10h]@1
v7 = a3;
v6 = 0;
if ( !a1->functions->GetEnv((JavaVM *)a1, (void **)&v6, 65540)
&& (v3 = v6) != 0
&& (v4 = ((int (*)(void))v6->functions->FindClass)()) != 0 )
{
//这里RegisterNatives原型为functions->RegisterNatives(this, clazz, methods, nMethods);
//由此可以知道
//v4是类名称
//off_4014是注册native数组的地址
//1 是数组的个数
result = (((int (__fastcall *)(_JNIEnv *, int, char **, signed int))v3->functions->RegisterNatives)(
v3,
v4,
off_4014,
1) >> 31) | 0x10004;
}
else
{
result = -1;
}
return result;
}
我们现在知道了off_4014是注册native数组的地址,我们就进去看看它组成的函数地址在哪
![](https://img.haomeiwen.com/i23598658/13b110169047840d.png)
可以看出这个就是Android代码中调用的函数
刚好都是4个字节,直接进入byte_CA5中,这个就是我们的verify函数地址:
![](https://img.haomeiwen.com/i23598658/ea1741c40dc17b6a.png)
这里大家应该都看不懂了,我也看不懂了0.0。
这是因为ida把它识别为了数据,我们先设置一下16进制代码显示。
在Option->General中,设置为4bytes:
![](https://img.haomeiwen.com/i23598658/80bfadaf1b14a579.png)
效果如下:
![](https://img.haomeiwen.com/i23598658/45b62c90281443e1.png)
现在我们在函数开始地址按快捷键C将代码翻译为,ARM汇编。
![](https://img.haomeiwen.com/i23598658/3a6d6252d0b22834.png)
结果是这样的,这汇编代码一看就不对劲啊!
这是因为这一段代码是thumb指令,而ida识别为arm指令集了
修改:按alt+G,然后让value=1 为thumb指令,= 0为arm指令。
![](https://img.haomeiwen.com/i23598658/bad13c6a1059b9db.png)
修改后如下:
![](https://img.haomeiwen.com/i23598658/46725ec7a0843f89.png)
在代码入口,右键creat fun
按F5进入c和c++代码
![](https://img.haomeiwen.com/i23598658/c3e8b8f353dee91a.png)
这个其实依然不是真正的代码,所以要结合下一章动态调试分析