在使用Radare2静态分析apk(2)末尾通过Radare2分析出一段ARM64汇编代码,这篇文通过分析这段汇编代码来来了解下ARM64汇编。
完整的代码
; UNKNOWN XREF fromunk @
┌ 156: sym.Java_com_example_myapplication_MainActivity_stringFromJNI (int64_targ1, int64_targ2);
│ ; var int64_tvar_28h @ x29-0x28
│ ; var int64_tvar_bp_8h @ x29-0x8
│ ; var int64_tvar_0h @ sp+0x0
│ ; var int64_tvar_8h @ sp+0x8
│ ; var int64_tvar_10h @ sp+0x10
│ ; varchar*var_18h @ sp+0x18
│ ; var int64_tvar_0h_2 @ sp+0x30
│ ; var int64_tvar_20h @ sp+0x40
│ ; var int64_tvar_60h @ sp+0x60
│ ; var int64_tvar_60h_2 @ sp+0x68
│ ; arg int64_targ1 @ x0
│ ; arg int64_targ2 @ x1
│ 0x0000f08cffc301d1 sub sp, sp, 0x70
│ 0x0000f090fd7b06a9stpx29, x30, [var_60h]
│ 0x0000f094 fd830191 add x29, var_60h
│ 0x0000f098 48d03bd5 mrs x8, tpidr_el0
│ 0x0000f09c081540f9ldrx8, [x8, 0x28]; aav.0x00000028
│ ; [0x28:4]=0x341f8
│ 0x0000f0a0a8831ff8sturx8, [var_bp_8h]
│ 0x0000f0a4a0831df8sturx0, [var_28h]; arg1
│ 0x0000f0a8e11b00f9strx1, [var_0h_2]; arg2
│ 0x0000f0ac c10000b0 adrp x1, 0x28000
│ 0x0000f0b0 21201191 add x1, x1, 0x448 ; 0x28448 ; "Hello from C++"; section..rodata
│ 0x0000f0b4a88300d1 sub x8, var_20h
│ 0x0000f0b8 e00308aa mov x0, x8
│ 0x0000f0bce80f00f9strx8, [var_18h]
│ 0x0000f0c094ffff97blfcn.0000ef10
│ 0x0000f0c4a0835df8ldurx0, [var_28h]
│ 0x0000f0c8e80f40f9ldrx8, [var_18h]; aav.0x00000018
│ ; [0x18:4]=0xf050 pc ; "P\xf0"
│ 0x0000f0cce00b00f9strx0, [var_10h]
│ 0x0000f0d0e00308aa mov x0, x8 ; int64_targ1
│ 0x0000f0d440000094 blfcn.0000f1d4; fcn.0000f1d4(0x0)
│ 0x0000f0d8e80b40f9ldrx8, [var_10h]; aav.0x00000010
│ ; [0x10:4]=0xb70003
│ 0x0000f0dce00700f9strx0, [var_8h]
│ 0x0000f0e0 e00308aa mov x0, x8
│ 0x0000f0e4e10740f9ldrx1, [var_8h]; aav.0x00000008
│ ; [0x8:4]=0
│ 0x0000f0e8e6feff97blfcn.0000ec80
│ 0x0000f0ece00300f9strx0, [sp]
│ ┌─< 0x0000f0f001000014b 0xf0f4
│ │ ; CODE XREF fromsym.Java_com_example_myapplication_MainActivity_stringFromJNI @ 0xf0f0
│ └─> 0x0000f0f4a08300d1 sub x0, var_20h
│ 0x0000f0f82affff97blfcn.0000eda0
│ 0x0000f0fc48d03bd5 mrs x8, tpidr_el0
│ 0x0000f100081540f9ldrx8, [x8, 0x28]; aav.0x00000028
│ ; [0x28:4]=0x341f8
│ 0x0000f104a9835ff8ldurx9, [var_bp_8h]
│ 0x0000f108 080109eb subs x8, x8, x9
│ ┌─< 0x0000f10ca1010054 b.ne0xf140; likely
│ ┌──< 0x0000f11001000014b 0xf114
│ ││ ; CODE XREF fromsym.Java_com_example_myapplication_MainActivity_stringFromJNI @ 0xf110
│ └──> 0x0000f114e00340f9ldrx0, [sp]
│ │ 0x0000f118fd7b46a9ldpx29, x30, [var_60h]
│ │ 0x0000f11c ffc30191 add sp, sp, 0x70 ; 0x178000
│ │ 0x0000f120 c0035fd6 ret
..
││ ; CODE XREF fromunk @
│ │ ; CODE XREF fromsym.Java_com_example_myapplication_MainActivity_stringFromJNI @ 0xf10c
└ └─> 0x0000f14014ffff97 bl sym.imp.__stack_chk_fail ; void__stack_chk_fail(void) ; sym.std::__ndk1::basic_string_char__std::__ndk1::char_traits_char___std::__ndk1::allocator_char___::basic_string_decltype_nullptr___char_const
└ ; void__stack_chk_fail(void)
ARM处理器用到的指令集分为 ARM 和 THUMB 两种。ARM指令长度固定为32bit,THUMB指令长度固定为16bit。上面代码的所有指令都是32位的,所以都是ARM指令。
代码分析
1. 开辟堆栈空间`sub sp, sp, 0x70`
2. 存在一对数据 `stp x29, x30, [var_60h]`
3.x29与[var_60h]相加 `add x29, var_60h`
4.保存状态tpidr_el0到x8寄存器 `mrs x8, tpidr_el0`
5. 将x8+0x28内存中的值加载到x8寄存器 `ldr x8, [x8, 0x28] `
6. 将x8寄存器保存起来,保存到var_bp_8h指向的内存中
7. 将第一个参数x0保存到var_bp_8h指向的内存中
8. 将第二个参数x1保存到var_0h_2指向的内存中
9. 将x1寄存器指向只读字符串"Hello from C++"
10. 将x8寄存器与var_20h相减法,把结果赋值给x8寄存器
11. 将x8赋值给x0
12. 将x8寄存器保存到var_18h指向的内存中
13. 调用函数fcn.0000ef10,没有处理返回值,暂时不用关注
14. 将第一个参数给x0寄存器,上面函数的返回值没有什么实际的用途呀,这里直接把x0寄存器覆盖了
15. 恢复x8寄存器的值
16. 将x0寄存器的值保存起来,放到var_10h指向的内存中
17. 将x8寄存器赋值给x0
18. 调用fcn.0000f1d4
19. 恢复x8寄存器的值
20. 将上述函数的返回值x0保存到var_8h指向的内存中
21. 将x8赋值给x0
22. 将var_8h中的值赋值给x1寄存器
23. 调用函数fcn.0000ec80
24. 将返回值x0保存到sp指向的内存中
25. 跳转到0xf0f4,也就是下一条指令
26. 将x0与var_20h相减,结果赋值给x0
27. 调用fcn.0000eda0
28. 读取tpidr_el0状态到x8寄存器
29. 将x8+0x28内存中的值加载到x8寄存器 `ldr x8, [x8, 0x28] `
30. 加载var_bp_8h中的值到x9寄存器
31. 判断x8的值是否发生变化,如果发生变化,走失败流程,这个应该是使用cookie技术防止堆栈攻击的技术
32. 如果没有发生变化,跳转到0xf114
33. 将sp内存中的值加载到x0寄存器中,用作返回值
34. 恢复x29,x30的值
35. 恢复堆栈
36. ret返回
通过上面的分析,可知最终的返回值是通过调用函数fcn.0000ec80。这个函数其中的一个参数是字符串处理函数fcn.0000f1d4的返回值。另一个参数是x8的值,通过分析可知x8寄存器正是函数Java_com_example_myapplication_MainActivity_stringFromJNI的第一个参数,类型是JNIEnv。由此推断fcn.0000ec80很可能是JNIEnv相关函数。
查看fcn.0000ec80
; CALLXREF fromsym.Java_com_example_myapplication_MainActivity_stringFromJNI @ 0xf0e8
┌ 16: fcn.0000ec80();
│ 0x0000ec80 300100d0 adrp x16, obj.typeinfo_for_decltype_nullptr__const ; 0x34000
│ 0x0000ec84 119a46f9 ldr x17, [x16, 0xd30] ; [0x34d30:4]=0xeb50 fcn.0000eb50 ; "P\xeb"
│ 0x0000ec88 10c23491 add x16, x16, 0xd30 ; 0x34d30 ; "P\xeb"
└ 0x0000ec8c 20021fd6 br x17
跳转到0x34d30,通过pd命令查看相应的内容。很显然这个函数就是NewStringUTF。这涉及到got表和plt表,在深入理解GOT覆写技术 中对GOT表进行了介绍。在后续文章中还会重点讲解android got表。使用pdg命令decompiler一下更加直观。
查看fcn.0000ef10
; CALLXREF fromsym.Java_com_example_myapplication_MainActivity_stringFromJNI @ 0xf0c0
┌ 16: fcn.0000ef10();
│ 0x0000ef10 300100d0 adrp x16, obj.typeinfo_for_decltype_nullptr__const ; 0x34000
│ 0x0000ef14 113e47f9 ldr x17, [x16, 0xe78] ; [0x34e78:4]=0xeb50 fcn.0000eb50 ; "P\xeb"
│ 0x0000ef18 10e23991 add x16, x16, 0xe78 ; 0x34e78 ; "P\xeb"
└ 0x0000ef1c 20021fd6 br x17
最终定位到basic_string<decltype(nullptr)>(char const*)。**查看basic_string函数信息,可知有2个参数,其中x0也就是参数1是x8寄存器的值,x1指向字符串”Hello from C++”**。这个函数的功能相当于string.c_str()。c_str()函数返回一个指向正规C字符串的指针常量, 内容与本string串相同。
[0x0000eb50]> pdf@ 0x0000f144
; UNKNOWN XREF fromaav.0x00001f0c @ +0x664
┌ 88: sym.std::__ndk1::basic_string_char__std::__ndk1::char_traits_char___std::__ndk1::allocator_char___::basic_string_decltype_nullptr___char_const (int64_targ1, int64_targ2);
│ ; var int64_tvar_10h @ x29-0x10
│ ; var int64_tvar_8h @ x29-0x8
│ ; var int64_tvar_sp_8h @ sp+0x8
│ ; var int64_tvar_sp_10h @ sp+0x10
│ ; varchar*var_18h @ sp+0x18
│ ; var int64_tvar_30h @ sp+0x30
│ ; var int64_tvar_30h_2 @ sp+0x38
│ ; arg int64_targ1 @ x0
│ ; arg int64_targ2 @ x1
│ 0x0000f144ff0301d1 sub sp, sp, 0x40; std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >::basic_string<decltype(nullptr)>(charconst*)
│ 0x0000f148fd7b03a9stpx29, x30, [var_30h]
│ 0x0000f14c fdc30091 add x29, var_30h
│ 0x0000f150a0831ff8sturx0, [var_8h]; arg1
│ 0x0000f154a1031ff8sturx1, [var_10h]; arg2
│ 0x0000f158a8835ff8ldurx8, [var_8h]
│ 0x0000f15c e00308aa mov x0, x8
│ 0x0000f160e80f00f9strx8, [var_18h]
│ 0x0000f164cbfeff97blfcn.0000ec90
│ 0x0000f168a1035ff8ldurx1, [var_10h]
│ 0x0000f16ca0035ff8ldurx0, [var_10h]
│ 0x0000f170e10b00f9strx1, [var_sp_10h]
│ 0x0000f1749fffff97blfcn.0000eff0
│ 0x0000f178e80f40f9ldrx8, [var_18h]; aav.0x00000018
│ ; [0x18:4]=0xf050 pc ; "P\xf0"
│ 0x0000f17ce00700f9strx0, [var_sp_8h]
│ 0x0000f180 e00308aa mov x0, x8
│ 0x0000f184e10b40f9ldrx1, [var_sp_10h]; aav.0x00000010
│ ; [0x10:4]=0xb70003
│ 0x0000f188e20740f9ldrx2, [var_sp_8h]; aav.0x00000008
│ ; [0x8:4]=0
│ 0x0000f18ca1ffff97blfcn.0000f010
│ 0x0000f190fd7b43a9ldpx29, x30, [var_30h]
│ 0x0000f194 ff030191 add sp, sp, 0x40 ; 0x178000
└ 0x0000f198 c0035fd6 ret
查看fcn.0000f1d4
; CALLXREF fromsym.Java_com_example_myapplication_MainActivity_stringFromJNI @ 0xf0d4
┌ 36: fcn.0000f1d4 (char*arg1);
│ ; varchar*var_8h @ sp+0x8
│ ; var int64_tvar_10h @ sp+0x10
│ ; var int64_tvar_10h_2 @ sp+0x18
│ ; arg char*arg1 @ x0
│ 0x0000f1d4ff8300d1 sub sp, sp, 0x20
│ 0x0000f1d8fd7b01a9stpx29, x30, [var_10h]
│ 0x0000f1dc fd430091 add x29, var_10h
│ 0x0000f1e0e00700f9strx0, [var_8h]; arg1
│ 0x0000f1e4e00740f9ldrx0, [var_8h]; aav.0x00000008
│ ; [0x8:4]=0; int64_targ1
│ 0x0000f1e8d8020094blfcn.0000fd48
│ 0x0000f1ecfd7b41a9ldpx29, x30, [var_10h]
│ 0x0000f1f0 ff830091 add sp, sp, 0x20 ; 0x178000
└ 0x0000f1f4 c0035fd6 ret
通过分析大概推测是混淆函数,没什么实际的作用,从上面的代码中可以能看出x0的值也就是返回值没有发生任何的改变。
查看fcn.0000eda0
; CALLXREF fromsym.Java_com_example_myapplication_MainActivity_stringFromJNI @ 0xf0f8
; CALLXREF fromsym.Java_com_example_myapplication_MainActivity_stringFromJNI @ +0xa4
┌ 16: fcn.0000eda0();
│ 0x0000eda0 300100d0 adrp x16, obj.typeinfo_for_decltype_nullptr__const ; 0x34000
│ 0x0000eda4 11e246f9 ldr x17, [x16, 0xdc0] ; [0x34dc0:4]=0xeb50 fcn.0000eb50 ; "P\xeb"
│ 0x0000eda8 10023791 add x16, x16, 0xdc0 ; 0x34dc0 ; "P\xeb"
└ 0x0000edac 20021fd6 br x17
最终定位到~basic_string()方法。
IDA查看
通过下图可知与上面的分析基本一致。
ARM指令和寄存器
ARM指令
MRS指令的格式为: MRS{条件} 通用寄存器,程序状态寄存器(CPSR或SPSR) MRS指令用亍将程序状态寄存器的内容传送到通用寄存器中。
STUR (SIMD&FP): Store SIMD&FP register (unscaled offset).
跳转指令: B ;跳转指令,可带条件跳转与cmp配合使用 BL ;带返回的跳转指令, 返回地址保存到LR(X30) BLR ; 带返回的跳转指令,跳转到指令后边跟随寄存器中保存的地址(例:blr x8 ;跳转到x8保存的地址中去执行),并且把当前PC+4回写到X30。 BR: 与BLR差别在于有无回写。 RET ;子程序返回指令,返回地址默认保存在LR(X30)
ADRP Xd, lable(Address Page) 符号扩展一个21位的offset, 向左移动12位 PC的值的低12位 清零, 然后 把 这两者相加, 结果写入到Xd寄存器 用来得到一块含有 lable的4KB对齐 内存区域的base地址 (也就是说lable所在的地址,一定落在这个4KB的内存区域里, 指令助记符里Page也就是这个意思), 可用来寻址 +/- 4GB的范围。
ARM寄存器
ARM64 有34个寄存器,包括31个通用寄存器、SP、PC、CPSR。 寄存器 | 位数 | 描述 | ——– | —– | —– x0-x30 | 64| 通用寄存器,如果有需要可以当做32bit使用:W0-W30 FP(X29) | 64| 保存栈帧地址(栈底指针) LR(x30) | 64|通常称X30为程序链接寄存器,保存子程序结束后需要执行的下一条指令 SP | 64 |保存栈指针,使用 SP/WSP来进行对SP寄存器的访问。 PC |64|程序计数器,俗称PC指针,总是指向即将要执行的下一条指令,在arm64中,软件是不能改写PC寄存器的。 CPSR |64 |状态寄存器
程序状态寄存器
CPSR (Current Program Status Register),各个bit的含义如下图:SPSR (Saved Program Status Register),在异常状态下使用,当发生异常时,会把CPSR的内容写入SPSR, 等异常恢复之后,又会把SPSR写到CPSR中。
公众号
更多内容,欢迎关注我的微信公众号:无情剑客。
来源:freebuf.com 2020-12-06 13:13:24 by: 无情剑客Burning
请登录后发表评论
注册