出于好奇并且当做原生程序分析练习,对之前运行时自篡改dalvik字节码的程序delta.apk进行了逆向分析,了解了该程序的自篡改原理,现小结如下,如有错误希望大家指正! 参考链接:http://bbs.pediy.com/showthread.php?t=170381
年初,bluebox 发布一个可以在运行时修改自身 dalvik 字节码的 demo,在论坛的相关信息
http://bbs.pediy.com/showthread.php?t=170381。
该 demo 通过 native 层代码修改自身 dex 从而增加静态分析难度。 Demo 及伪加密解除代码链接如下
https://github.com/blueboxsecurity/DalvikBytecodeTampering
出于好奇,我对其简单逆向,并了解该程序的执行原理,并进行小记。
首先使用 Apktools,发现不能对其进行反编译。使用压缩包工具打开查看
后并尝试解包则遇到如下图 1 情形:
图 1
突然想起这或许就是之前论坛上有人遇到的 zip 伪加密。在 android 解析apk 时,由于忽略了加密 zip,直接跳过了加密头部的解析,因此通过修改 zip格式的加密标识,可以是实现伪加密。bluebox 也提供了 python 代码进行解压
缩。解压缩完成后将所有内容再次通过 zip 压缩成 apk,即可开始使用 VTS 对其分析。
使用 VTS 载入 delta.apk, 找到 activity(Lcom/bluebox/lab/poc/Action;),
看到有类的静态构造函数如图 2,对应于源代码 static{}代码段:
图 2
从该段代码中可以看出,在 delta.apk 主 activity 运行后,会加载自身native so,并执行该 so 下的 readmem()I 函数。由于该代码段在该类加载时最先执行的,因此余下的算法分析可以暂时不予考虑,我们重点关注如何运行时自
修改 dalvik 代码。打开 IDA 载入 libnet.so 进行静态分析,找到 native 对应的readmem 函数,该函数主要执行一个 search 函数,也就是该程序的主要核心。进入 search 函数,进行主要功能分析,代码流程图如下图 3:
图 3
关键点大致解释如下:
(1)sysconf 用于确定当前的系统变量之值,sys/sysconf.h 头文件可以看到#define _SC_PAGESIZE 0x0027。查看看该系统内存页大小,用于内存操作。 (2)findmagic 可知是查找 dex 加载在内存中的位置。 (3)get* 函数功能主要是定位相应代码的位置。 (4)mprotect 设置内存访问权限,以便对相应内存进行修改。 (5)memcpy 修改函数 dalvik 字节码。
接下来我们将对关键函数进行分析, 需要我们对 dex 文件可是有一定的了解,
循环调用 findmagic,该函数用于找到 dex 在内存中的起始地址,用于在内存中
分析 dex 结构
循环体:
.text:000011E4 loc_11E4 ; CODE XREF: search+2Aj .text:000011E4 ADDS R4, R4, R6 ;R6 存放对齐页大小 .text:000011E6 MOVS R5, R4 .text:000011E8 ADDS R5, #0x28 ;该页 map 偏移 0x28 的地址 .text:000011EA MOVS R0, R5 .text:000011EC BL findmagic ;查找是否存在 dex\n035 .text:000011F0 CMP R0, #0 ;判断是否找到 .text:000011F2 BEQ loc_11E4
Findmagic 函数:
.text:00000FBC EXPORT findmagic .text:00000FBC findmagic ; CODE XREF: search+24p .text:00000FBC .text:00000FBC dest = -0x1C .text:00000FBC var_14 = -0x14 .text:00000FBC .text:00000FBC PUSH {R4,R5,LR} .text:00000FBE LDR R4, =(__stack_chk_guard_ptr - 0xFC8) .text:00000FC0 LDR R1, =(aDex035 - 0xFCE) .text:00000FC2 SUB SP, SP, #0x14 .text:00000FC4 ADD R4, PC .text:00000FC6 LDR R4, [R4] .text:00000FC8 MOVS R5, R0 ;上层传参(比较的起始偏移) .text:00000FCA ADD R1, PC ; "dex\n035" DEX 起始标识 .text:00000FCC LDR R3, [R4] .text:00000FCE MOVS R2, #8 ; n .text:00000FD0 ADD R0, SP, #0x20+dest ; dest .text:00000FD2 STR R3, [SP,#0x20+var_14] .text:00000FD4 BLX memcpy ;参数:dest,起始标识,8;复制标识到缓冲区 .text:00000FD8 MOVS R2, #8 ; n .text:00000FDA MOVS R0, R5 ; s1 .text:00000FDC ADD R1, SP, #0x20+dest ; s2 .text:00000FDE BLX memcmp ;比较 s1 偏移是否存在标识 .text:00000FE2 LDR R2, [SP,#0x20+var_14] .text:00000FE4 NEGS R3, R0 .text:00000FE6 ADCS R0, R3 .text:00000FE8 LDR R3, [R4] .text:00000FEA CMP R2, R3 .text:00000FEC BNE loc_FF2 .text:00000FEE ADD SP, SP, #0x14 .text:00000FF0 POP {R4,R5,PC}
找到之后开始查找需要修改的代码地址,这需要熟悉 dex 文件中各种索引的关系:
我们可以查看相关的头文件来了解各个结构(字符,类型,函数,类等)对
应关系 , 相 关 头 文 件 位 于 源 码 \dalvik\libdex\DexClass.h 和\dalvik\libdex\DexFile.h 中,如果要修改对应类中的方法, 过程大致如下 (具体过程查看相关书籍材料) :
(1) 首先,要修改函数代码肯定对应于 dalvik 指令, 而结构 DexCode 存放了关
dex 代码的信息,我们的目标就是要
修改该结构相应的内容 。
struct DexCode { u2 registersSize; u2 insSize; u2 outsSize; u2 triesSize; u4 debugInfoOff; /* file offset to debug info stream */ u4 insnsSize; /* size of the insns array, in u2 units */u2 insns[1]; /*存放代码位置,也就是我们需要改动的地方*/ …… };
(2)如何找到对应dexcode结构,将由以下结构DexMethod的成员
指明了dexcode
结构偏移
struct DexMethod { u4 methodIdx; /* index to a method_id_item */ u4 accessFlags;u4 codeOff; /* file offset to a code_item */ };
该结构存放了对应函数有关的信息, 一个确定的函数就存在这么一个结构,确定
了这一个函数,找到这一个结构体,就可以沿着往下修改 dalvik 指令了。
(3)而找到这么一个结构,我们需要一个
确定的函数信息,包括所在的类,函
数签名,函数名字,返回类型等,当这些确定了,这个函数也就唯一确定了。
这也就是如下 get*函数的用途:
.text:000011F4 LDR R1, =(aLSavaLangStrin - 0x11FE) .text:000011F6 MOVS R0, R5 .text:000011F8 MOVS R2, #0x12 .text:000011FA ADD R1, PC ; "L 褬 ava/lang/String;" .text:000011FC BL getStrIdx .text:00001200 LDR R1, =(aAdd - 0x120A) .text:00001202 MOVS R2, #3 .text:00001204 MOVS R4, R0 ;R4 存放其的对应字符串 ID .text:00001206 ADD R1, PC ; "add" .text:00001208 MOVS R0, R5 .text:0000120A BL getStrIdx .text:0000120E MOVS R1, R4 .text:00001210 MOV R8, R0 ;R8 存放了 add 对应的 ID .text:00001212 MOVS R0, R5 .text:00001214 BL getTypeIdx .text:00001218 MOVS R4, R0 ;R4 存放了 string 对应的类型 ID .text:0000121A MOVS R1, R4 .text:0000121C MOVS R0, R5 .text:0000121E BL getClassItem ;找到 string 类的 Item ID .text:00001222 MOV R1, R8 .text:00001224 MOVS R6, R0 ;R6 存放 string 类 Item ID .text:00001226 MOVS R2, R4 .text:00001228 MOVS R0, R5 .text:0000122A BL getMethodIdx ;通过 R8,R4 找到;Ljava/lang/String;->add (Ljava/lang/String;)V; .text:0000122E MOVS R1, R6 .text:00001230 MOVS R2, R0 ;R2 add 函数 ID .text:00001232 MOVS R0, R5 .text:00001234 BL getCodeItem .text:00001238 MOVS R4, R0 .text:0000123A ADDS R4, #0x10 ;此时跳过 16 字节的位置找到要修改的位置 insns[1]
1)先找到 Ljava/lang/String;和 add 对应的字符串 ID。
2)在找 Ljava/lang/String;字符串对应的类型。
3)通过该类型找到对应的类 Ljava/lang/String;
4)接着寻找该类函数 ADD 对应的 ID
5)之后就找到了上文提到的 DexMethod 结构体
6)通过结构体找到了存放代码的缓冲区
也就是对应了 VTS 中解析到的类,如图 4:
图 4
也就是说该程序就是修改了 Ljava/lang/String;->add 函数代码
最后:
.text:0000124A MOVS R1, R7 ; len .text:0000124C MOVS R2, #3 ; prot .text:0000124E ADDS R0, #0x10 ; addr .text:00001250 BLX mprotect .text:00001254 LDR R1, =(inject_ptr - 0x125E) .text:00001256 MOVS R0, R4 ; dest .text:00001258 MOVS R2, #0xDE ; n .text:0000125A ADD R1, PC .text:0000125C LDR R1, [R1] ; src .text:0000125E BLX memcpy .text:00001262 POP {R2} .text:00001264 MOV R8, R2 .text:00001266 POP {R4-R7,PC} .text:00001266 ; End of function search
inject_ptr 也就是注入代码的二进制。
至此,有关的分析到此结束,如有错误请大家指正!
[培训]《安卓高级研修班(网课)》月薪三万计划,掌
握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法
上传的附件: