网上经常看到有通过apktool将apk中的dex反编译成smali格式的文件,以便分析功能实现与破—解,确没怎么看到oat文件反通过oatdump反编译的,所以就写了一篇这样的文档。声明一下oat文件也是可以反编译的。本来想着通过oatdump处理之后,既可以看到dex代码又可以看到相应的汇编实现。反编译之后发现其实不然,难道是因为系统是eng版本的原因,有机会再验证看看。
先看一下这两个代码片段
1. JNI代码: jni/com_android_hello_HelloActivity.cpp
#include <jni.h> #include <JNIHelp.h> extern "C" void Java_com_android_hello_HelloActivity_sayHello(JNIEnv *, jobject) { __builtin_trap(); }
使APP出现native crash
2. Java代码: src/com/android/hello/HelloActivity.java
public class HelloActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); sayHello(); } static { System.loadLibrary("hello-jni"); } private static native void sayHello(); }
加载JNI库libhello-jni.so, 并且在app launch时调用JNI方法sayHello()。
APK安装时会执行dex2oat命令,生成oat文件:
03-11 23:06:38.748 11835 11902 D PackageManager: Renaming /data/app/vmdl111617587.tmp to /data/app/com.android.hello-1
03-11 23:06:38.774 11835 11902 I PackageManager: Running dexopt on: /data/app/com.android.hello-1/base.apk pkg=com.android.hello isa=arm vmSafeMode=false
03-11 23:06:38.800 18221 18221 I dex2oat : /system/bin/dex2oat --zip-fd=6 --zip-location=/data/app/com.android.hello-1/base.apk --oat-fd=7 --oat-location=/data/dalvik-cache/arm/data@app@com.android.hello-1@base.apk@classes.dex --instruction-set=arm --instruction-set-features=div --runtime-arg -Xms64m --runtime-arg -Xmx512m --compiler-filter=interpret-only --swap-fd=8
03-11 23:06:38.818 18221 18221 I dex2oat : Decided to run without swap.
03-11 23:06:38.838 18221 18221 I dex2oat : dex2oat took 38.211ms (threads: 4) arena alloc=0B java alloc=7KB native alloc=75KB free=32KB
oat文件是什么:
$ adb pull /data/dalvik-cache/arm/data@app@com.android.hello-1@base.apk@classes.dex 1236 KB/s (12720 bytes in 0.010s) $ file data\@app\@com.android.hello-1\@base.apk\@classes.dex data@app@com.android.hello-1@base.apk@classes.dex: ELF 32-bit LSB shared object, ARM, version 1 (GNU/Linux), dynamically linked, stripped
是一个ELF可执行文件。执行readelf/arm-linux-androideabi-readelf命令看看:
$ arm-linux-androideabi-readelf -a data\@app\@com.android.hello-1\@base.apk\@classes.dex ELF Header: Magic: 7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - GNU ABI Version: 0 Type: DYN (Shared object file) Machine: ARM Version: 0x1 Entry point address: 0x0 Start of program headers: 52 (bytes into file) Start of section headers: 12400 (bytes into file) Flags: 0x5000000, Version5 EABI Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 5 Size of section headers: 40 (bytes) Number of section headers: 8 Section header string table index: 7 Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .dynsym DYNSYM 000000d4 0000d4 000040 10 A 2 0 4 [ 2] .dynstr STRTAB 00000114 000114 00004f 01 A 0 0 1 [ 3] .hash HASH 00000164 000164 000020 04 A 1 0 4 [ 4] .rodata PROGBITS 00001000 001000 001000 00 A 0 0 4096 [ 5] .text PROGBITS 00002000 002000 000094 00 AX 0 0 4096 [ 6] .dynamic DYNAMIC 00003000 003000 000038 08 A 1 0 4096 [ 7] .shstrtab STRTAB 00000000 003038 000038 01 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) There are no section groups in this file. Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x00000034 0x00000034 0x000a0 0x000a0 R 0x4 LOAD 0x000000 0x00000000 0x00000000 0x02000 0x02000 R 0x1000 LOAD 0x002000 0x00002000 0x00002000 0x00094 0x00094 R E 0x1000 LOAD 0x003000 0x00003000 0x00003000 0x00038 0x00038 RW 0x1000 DYNAMIC 0x003000 0x00003000 0x00003000 0x00038 0x00038 RW 0x1000 Section to Segment mapping: Segment Sections... 00 01 .dynsym .dynstr .hash .rodata 02 .text 03 .dynamic 04 .dynamic Dynamic section at offset 0x3000 contains 7 entries: Tag Type Name/Value 0x00000004 (HASH) 0x164 0x00000005 (STRTAB) 0x114 0x00000006 (SYMTAB) 0xd4 0x0000000b (SYMENT) 16 (bytes) 0x0000000a (STRSZ) 79 (bytes) 0x0000000e (SONAME) Library soname: [data@app@com.android.hello-1@base.apk@classes.dex] 0x00000000 (NULL) 0x0 There are no relocations in this file. There are no unwind sections in this file. Symbol table '.dynsym' contains 4 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00001000 4096 OBJECT GLOBAL DEFAULT 4 oatdata 2: 00002000 148 OBJECT GLOBAL DEFAULT 5 oatexec 3: 00002090 4 OBJECT GLOBAL DEFAULT 5 oatlastword Histogram for bucket list length (total of 2 buckets): Length Number % of total Coverage 0 0 ( 0.0%) 1 1 ( 50.0%) 33.3% 2 1 ( 50.0%) 100.0% No version information found in this file.
能使用objdump/arm-linux-androideabi-objdum反编译吗:
$ arm-linux-androideabi-objdump -d data\@app\@com.android.hello-1\@base.apk\@classes.dex data@app@com.android.hello-1@base.apk@classes.dex: file format elf32-littlearm Disassembly of section .text: 00002000 <oatexec>: ... 200c: 30 00 00 00 e0 4d 00 00 00 00 00 00 78 00 00 00 0....M......x... 201c: 2d e9 e0 4d ad f2 14 0d 00 90 4f f0 01 0c cd f8 -..M......O..... 202c: 08 c0 d9 f8 c8 c0 cd f8 04 c0 0d f2 04 0c c9 f8 ................ 203c: c8 c0 d0 f8 08 c0 cd f8 0c c0 c9 f8 8c d0 4f f0 ..............O. 204c: 00 0c c9 f8 90 c0 48 46 d0 f8 94 c1 e0 47 04 90 ......HF.....G.. 205c: 0d f2 0c 01 d9 f8 98 00 dd f8 00 c0 dc f8 28 c0 ..............(. 206c: e0 47 04 98 49 46 d1 f8 9c c1 e0 47 d9 f8 7c c0 .G..IF.....G..|. 207c: bc f1 00 0f 03 d1 0d f2 14 0d bd e8 e0 8d 60 46 ..............`F 208c: d9 f8 34 c2 ..4. 00002090 <oatlastword>: 2090: e0 47 00 be .G..
没什么内容。
最后,我们用oatdump命令查看一下:
$ oatdump --oat-file=data\@app\@com.android.hello-1\@base.apk\@classes.dex
MAGIC:
oat
045
CHECKSUM:
0x864eeade
INSTRUCTION SET:
Thumb2
INSTRUCTION SET FEATURES:
div
DEX FILE COUNT:
1
EXECUTABLE OFFSET:
0x00001000
INTERPRETER TO INTERPRETER BRIDGE OFFSET:
0x00000000
INTERPRETER TO COMPILED CODE BRIDGE OFFSET:
0x00000000
JNI DLSYM LOOKUP OFFSET:
0x00000000
PORTABLE IMT CONFLICT TRAMPOLINE OFFSET:
0x00000000
PORTABLE RESOLUTION TRAMPOLINE OFFSET:
0x00000000
PORTABLE TO INTERPRETER BRIDGE OFFSET:
0x00000000
QUICK GENERIC JNI TRAMPOLINE OFFSET:
0x00000000
QUICK IMT CONFLICT TRAMPOLINE OFFSET:
0x00000000
QUICK RESOLUTION TRAMPOLINE OFFSET:
0x00000000
QUICK TO INTERPRETER BRIDGE OFFSET:
0x00000000
IMAGE PATCH DELTA:
0 (0x00000000)
IMAGE FILE LOCATION OAT CHECKSUM:
0x8223fe58
IMAGE FILE LOCATION OAT BEGIN:
0x70dd0000
KEY VALUE STORE:
dex2oat-cmdline = --zip-fd=6 --zip-location=/data/app/com.android.hello-1/base.apk --oat-fd=7 --oat-location=/data/dalvik-cache/arm/data@app@com.android.hello-1@base.apk@classes.dex --instruction-set=arm --instruction-set-features=div --runtime-arg -Xms64m --runtime-arg -Xmx512m --compiler-filter=interpret-only --swap-fd=8
dex2oat-host = Arm
image-location = /data/dalvik-cache/arm/system@framework@boot.art
pic = false
SIZE:
4244
OatDexFile:
location: /data/app/com.android.hello-1/base.apk
checksum: 0x2e3d006c
0: Lcom/android/hello/HelloActivity; (offset=0x00000578) (type_idx=2) (StatusVerified) (OatClassSomeCompiled)
0: void com.android.hello.HelloActivity.<clinit>() (dex_method_idx=2)
DEX CODE:
0x0000: const-string v0, "hello-jni" // string@10
0x0002: invoke-static {v0}, void java.lang.System.loadLibrary(java.lang.String) // method@6
0x0005: return-void
OatMethodOffsets (offset=0x00000000)
code_offset: 0x00000000
gc_map: (offset=0x00000000)
OatQuickMethodHeader (offset=0x00000000)
mapping_table: (offset=0x00000000)
vmap_table: (offset=0x00000000)
QuickMethodFrameInfo
frame_size_in_bytes: 0
core_spill_mask: 0x00000000
fp_spill_mask: 0x00000000
CODE: (code_offset=0x00000000 size_offset=0x00000000 size=0)
NO CODE!
1: void com.android.hello.HelloActivity.<init>() (dex_method_idx=3)
DEX CODE:
0x0000: invoke-direct {v0}, void android.app.Activity.<init>() // method@0
0x0003: return-void
OatMethodOffsets (offset=0x00000000)
code_offset: 0x00000000
gc_map: (offset=0x00000000)
OatQuickMethodHeader (offset=0x00000000)
mapping_table: (offset=0x00000000)
vmap_table: (offset=0x00000000)
QuickMethodFrameInfo
frame_size_in_bytes: 0
core_spill_mask: 0x00000000
fp_spill_mask: 0x00000000
CODE: (code_offset=0x00000000 size_offset=0x00000000 size=0)
NO CODE!
2: void com.android.hello.HelloActivity.sayHello() (dex_method_idx=5)
DEX CODE:
OatMethodOffsets (offset=0x00000584)
code_offset: 0x0000101d
gc_map: (offset=0x00000000)
OatQuickMethodHeader (offset=0x00001000)
mapping_table: (offset=0x00000000)
vmap_table: (offset=0x00000000)
QuickMethodFrameInfo
frame_size_in_bytes: 48
core_spill_mask: 0x00004de0 (r5, r6, r7, r8, r10, r11, r14)
fp_spill_mask: 0x00000000
CODE: (code_offset=0x0000101d size_offset=0x00001018 size=120)...
0x0000101c: e92d4de0 push {r5, r6, r7, r8, r10, r11, lr}
0x00001020: f2ad0d14 subw sp, sp, #20
0x00001024: 9000 str r0, [sp, #0]
0x00001026: f04f0c01 mov.w r12, #1
0x0000102a: f8cdc008 str.w r12, [sp, #8]
0x0000102e: f8d9c0c8 ldr.w r12, [r9, #200] ; top_handle_scope
0x00001032: f8cdc004 str.w r12, [sp, #4]
0x00001036: f20d0c04 addw r12, sp, #4
0x0000103a: f8c9c0c8 str.w r12, [r9, #200]
0x0000103e: f8d0c008 ldr.w r12, [r0, #8]
0x00001042: f8cdc00c str.w r12, [sp, #12]
0x00001046: f8c9d08c str.w sp, [r9, #140]
0x0000104a: f04f0c00 mov.w r12, #0
0x0000104e: f8c9c090 str.w r12, [r9, #144]
0x00001052: 4648 mov r0, r9
0x00001054: f8d0c194 ldr.w r12, [r0, #404]
0x00001058: 47e0 blx r12
0x0000105a: 9004 str r0, [sp, #16]
0x0000105c: f20d010c addw r1, sp, #12
0x00001060: f8d90098 ldr.w r0, [r9, #152] ; jni_env
0x00001064: f8ddc000 ldr.w r12, [sp, #0]
0x00001068: f8dcc028 ldr.w r12, [r12, #40]
0x0000106c: 47e0 blx r12
0x0000106e: 9804 ldr r0, [sp, #16]
0x00001070: 4649 mov r1, r9
0x00001072: f8d1c19c ldr.w r12, [r1, #412]
0x00001076: 47e0 blx r12
0x00001078: f8d9c07c ldr.w r12, [r9, #124] ; exception
0x0000107c: f1bc0f00 cmp.w r12, #0
0x00001080: d103 bne +6 (0x0000108a)
0x00001082: f20d0d14 addw sp, sp, #20
0x00001086: e8bd8de0 pop {r5, r6, r7, r8, r10, r11, pc}
0x0000108a: 4660 mov r0, r12
0x0000108c: f8d9c234 ldr.w r12, [r9, #564] ; pDeliverException
0x00001090: 47e0 blx r12
0x00001092: be00
3: void com.android.hello.HelloActivity.onCreate(android.os.Bundle) (dex_method_idx=4)
DEX CODE:
0x0000: invoke-super {v0, v1}, void android.app.Activity.onCreate(android.os.Bundle) // method@1
0x0003: invoke-static {}, void com.android.hello.HelloActivity.sayHello() // method@5
0x0006: return-void
OatMethodOffsets (offset=0x00000000)
code_offset: 0x00000000
gc_map: (offset=0x00000000)
OatQuickMethodHeader (offset=0x00000000)
mapping_table: (offset=0x00000000)
vmap_table: (offset=0x00000000)
QuickMethodFrameInfo
frame_size_in_bytes: 0
core_spill_mask: 0x00000000
fp_spill_mask: 0x00000000
CODE: (code_offset=0x00000000 size_offset=0x00000000 size=0)
NO CODE!
看来oat文件中既包含dex代码,又包启汇编代码,不知道是不是因为我手机是eng版本的。相关系统属性如下(Nexus4 android-5.1.1_r17(lmy48t):
[dalvik.vm.dex2oat-Xms]: [64m] [dalvik.vm.dex2oat-Xmx]: [512m] [dalvik.vm.dex2oat-filter]: [interpret-only] [dalvik.vm.dexopt-flags]: [m=y] [dalvik.vm.image-dex2oat-Xms]: [64m] [dalvik.vm.image-dex2oat-Xmx]: [64m] [dalvik.vm.image-dex2oat-filter]: [verify-none]
相关的参考文档,ART and Dalvik: https://source.android.com/devices/tech/dalvik/index.html
1. Bytecode Format: https://source.android.com/devices/tech/dalvik/dalvik-bytecode.html
2. .Dex Format: https://source.android.com/devices/tech/dalvik/dex-format.html
3. Instruction Formats: https://source.android.com/devices/tech/dalvik/instruction-formats.html