Android逆向之Native层分析
字数 1061 2025-08-22 18:37:22
Android Native层逆向分析技术详解
一、Native层分析概述
Android应用的核心保护逻辑和算法通常会被放在Native层(so库)中,主要原因包括:
- 增加逆向分析难度
- 保护关键业务逻辑
- 隐藏native方法注册过程
- 实现壳保护机制
本文以分析libshella_3.0.0.0.so文件为例,重点讲解以下技术点:
- init_array节的解密操作
- JNI_OnLoad函数的保护机制
- 通过/proc/self/maps获取so基址的技术
- 动态解密和内存操作技术
二、init_array节解密分析
1. 函数初始化
STMFD SP!, {R11,LR} ; 保存上一个栈帧的帧地址和返回地址
ADD R11, SP, #4 ; 设置当前栈帧
SUB SP, SP, #0x48 ; 开辟0x48字节的栈空间
STR R0, [R11,#var_48] ; 参数入栈
STR R1, [R11,#var_4C] ; 参数入栈
2. 获取so基址
通过ELF头特征0x464C457C(即"\x7FELF")来定位so基址:
LDR R3, =(init_array - 0x763FA964)
ADD R3, PC, R3 ; 获取init_array函数地址
BIC R3, R3, #0xFF0 ; 对齐到0x1000边界
BIC R3, R3, #0xF ; 得到so基址
STR R3, [R11,#var_8] ; 存储基址到局部变量
loc_763FA97C:
LDR R3, [R11,#var_8] ; 加载基址
LDR R2, [R3] ; 读取ELF头
LDR R3, =unk_464C457F ; 加载ELF魔数
CMP R2, R3 ; 比较是否为ELF文件
BNE loc_763FA970 ; 不是则继续查找
loc_763FA970:
LDR R3, [R11,#var_8] ; 加载当前基址
SUB R3, R3, #0x1000 ; 向上移动一个内存页
STR R3, [R11,#var_8] ; 存储新基址
B loc_763FA97C ; 继续检查
3. 解析程序头表
LDR R3, [R11,#var_8] ; so基址
LDR R2, [R3,#0x1C] ; 读取程序头表偏移
ADD R3, R2, R3 ; 计算程序头表地址
STR R3, [R11,#var_C] ; 存储程序头表地址
4. 遍历程序头表
while (*(unkown *)(var_28 + 0x2c) > var_10) {
if (*(unkown *)var_C == 1 && *(unkown *)(var_C + 0x18) == 5) {
// 可加载且可读可执行段
var_1C = (*(unkown *)(var_C + 0x8) + *(unkown *)(var_C + 0x10) + 0xFFF) & 0xFFFFF000;
} else if (*(unkown *)var_C == 1 && *(unkown *)(var_C + 0x18) == 6) {
// 可加载且可读可写段
var_20 = *(DWORD *)(var_C + 8) & 0xFFFFF000;
var_24 = (*(DWORD *)(var_C + 8) + *(DWORD *)(var_C + 0x10) + 0xFFF) & 0xFFFFF000;
break;
}
var_10++;
var_C += 0x20;
}
5. 内存属性修改与解密
// 修改内存属性为可读写
mprotect(*(unkown *)var_30 + 0x1000, ((*(unkown *)var_30 / 0x1000) + 1) * 0x1000, 3);
// 解密循环
for (var_18 = 0x1000; var_18 <= var_3C; var_18++) {
var_31 = *(BYTE *)(var_30 + var_18);
var_2A = (unsigned __int8)(((var_12 - var_13) ^ var_18 + var_14) ^ var_11);
*(BYTE *)(var_30 + var_18) ^= (char)var_2A;
*(BYTE *)(var_30 + var_18) += (unsigned __int8)((var_13 & var_14) ^ var_12);
var_11 += (unsigned __int8)(var_12 + var_13 - var_14) & var_31 & var_18;
var_12 += (unsigned __int8)(var_18 + var_11) ^ var_31;
var_13 ^= (unsigned __int8)(var_31 - var11) ^ var_18;
var_14 += (unsigned __int8)(var_18) - (var_31 + var_11);
}
// 恢复内存属性为可读可执行
mprotect(var_38 + var_30, (var_40 / 0x1000 + 1) * 0x1000, 5);
sub_B39E47E8(var_38 + var_30, var_40); // 缓存更新
三、/proc/self/maps遍历技术
1. 初始化操作
// 打开/proc/self/maps文件
LDR R3, =(aProcSelfMaps - 0xA54EF5D0)
ADD R3, PC, R3 ; "/proc/self/maps"
MOV R0, R3
LDR R3, =(unk_A54EFA5C - 0xA54EF5DC)
ADD R3, PC, R3 ; "r"
MOV R1, R3
BL fopen
STR R0, [R11,#var_8] ; 存储文件指针
// 初始化缓冲区
memset(&var_40C, 0, 0x400);
memset(&var_80C, 0, 0x400);
2. 读取和解析maps文件
while (feof(var_8) == 0) {
fgets(&var_40C, 0x400, var_8);
sscanf(var_40C, "%lx-%lx %s %s %s %s %s",
&var_810, &var_814, var_834, var_830, var_82C, var_828, var_824);
// 检查是否找到目标so区域
if (var_810 > var_C || var_814 <= var_C) {
fclose(var_8);
return 0;
}
}
四、动态解密与加载技术
1. 内存映射与初始化
// 映射内存区域
var_C = mmap(var_2C, var_F4, 0, 0x22, 0xFFFFFFFF, 0);
// 计算加载偏移
var_30 = var_C - var_2C;
var_210 = var_C;
var_180 = var_30;
2. 动态解密流程
// 初始化解压结构
while (deflateInit(&var_22AC, 0xFFFFFFF1, "1.2.3", 0x38) != 0);
// 解密循环
while (*(unkown *)(var_10 + 0xC) > var_8) {
// 计算读取长度
if (var_8 + 0x1000 <= *(unkown *)(var_10 + 0xC))
var_4C = 0x1000;
else
var_4C = *(unkown *)(var_10 + 0xC) - var_8;
// 读取加密数据
pread(var_28, &var_229C, var_4C, var_8 + var_22DC + *(unkown *)(var_10 + 8));
// 解密数据
decrypt("Tx:12345Tx:12345", &var_229C, var_50, 0x10);
// 解压数据
var_54 = inflate_0(&var_229C, 0);
// 更新指针
var_8 += var_4C;
}
// 刷新缓存
for (var_1C = var_38; var_18 + var_38 > var_1C; var_1C += 0x400)
cacheflush(var_1C, 0x400);
五、关键技术与总结
1. 汇编分析技巧
- 控制流分析:重点查看CMP、CMN等条件判断指令,确定程序分支
- 函数调用:分析BL/BLX指令,向上查找参数传递过程
- 栈帧分析:识别局部变量和参数在栈中的布局
- 循环识别:
- for循环:条件判断在底部,修改循环变量的代码在循环体之前
- while循环:条件判断成功后直接跳转到循环体
2. 常用ARM指令模式
- 内存对齐计算:
ADD Rd,0xFFF+BIC Rd,0xFFF= 对齐到0x1000边界 - 内存属性修改:使用mprotect系统调用切换内存区域的读写执行属性
- 缓存更新:使用cacheflush或类似机制确保解密后的代码生效
3. 反调试与保护技术
- 动态解密:运行时解密关键代码段,增加静态分析难度
- 自修改代码:修改内存中的代码段,对抗调试器断点
- 多重映射:通过/proc/self/maps检测内存布局,防止内存dump
- 环境检测:检查ro.build.version.sdk等属性,识别模拟器或调试环境
4. 逆向分析建议
- 优先分析init_array和JNI_OnLoad函数
- 跟踪内存属性修改操作(mprotect调用)
- 监控/proc/self/maps的读取行为
- 关注异常的内存读写模式
- 使用动态调试结合静态分析,特别关注解密循环
通过掌握这些Native层分析技术,可以有效应对Android应用中的各种保护机制,为安全分析和漏洞挖掘奠定基础。