由于市面上几乎没有arm相关书籍,所以推荐看官方文档。虽然是英文的,看不下去也要硬看,毕竟搞这方面的还是得有啃英文文档/书籍的能力。
另外,再推荐一个翻译网站:https://www.deepl.com/zh/translator
arm 手册下载地址:
https://developer.arm.com/documentation/ddi0487/latest
https://developer.arm.com/documentation/ddi0602/2023-12/?lang=en
使用 kali + vscode + ndk-build。
ndk-build 可以使用 android studio 下载。
开发目录结构:
ARM64?
?|--?Android.mk
?|--?Application.mk
?|--?arm64.c
?|--?build.sh
?|--?push.sh
?|--?connect.sh
关于 Android.mk 与 Application.mk 的知识,可以参照官方文档:
https://developer.android.com/ndk/guides/build?hl=zh-cn
这里只简单介绍必要的。
LOCAL_PATH?:=?$(call?my-dir)
include?$(CLEAR_VARS)
#LOCAL_ARM_MODE?:=?arm
LOCAL_MODULE?:=?arm64
LOCAL_SRC_FILES?:=?arm64.c
include?$(BUILD_EXECUTABLE)
#include?#(BUILD_SHARED_LIBRARY)
LOCAL_PATH:此变量用于指定当前文件的路径。必须在?Android.mk
?文件开头定义此变量。
CLEAR_VARS:此变量用于清除LOCAL_PATH变量外的许多LOCAL_***变量(例如:LOCAL_MODULE、LOCAL_SRC_FILES等)。这是非常有必要的,因为所有的编译文件都在同一个GUN MKAE执行环境中,所有的变量都是全局变量,不清除容易引起解析错误。
LOCAL_MODULE:此变量用于存储模块名称。指定的名称在所有模块名称中必须唯一,并且不得包含任何空格。
LOCAL_ARM_MODE:默认情况下,构建系统会以 thumb 模式生成 ARM 目标二进制文件,其中每条指令都是 16 位宽,并与?thumb/
?目录中的 STL 库链接。将此变量定义为?arm
?会强制构建系统以 32 位?arm
?模式生成模块的对象文件。
LOCAL_SRC_FILES:此变量包含构建系统生成模块时所用的源文件列表。
BUILD_EXECUTABLE:根据您列出的源文件构建目标可执行文件。
BUILD_SHARED_LIBRARY:根据您列出的源文件构建目标共享库。
APP_ABI?:=?arm64-v8a
APP_BUILD_SCRIPT?:=?Android.mk
APP_PLATFORM?:=?android-26
APP_ABI:默认情况下,NDK 构建系统会为所有非弃用 ABI 生成代码。您可以使用?APP_ABI
?设置为特定 ABI 生成代码。
指令集 | 值 |
---|---|
32 位 ARMv7 | APP_ABI := armeabi-v7a |
64 位 ARMv8 (AArch64) | APP_ABI := arm64-v8a |
x86 | APP_ABI := x86 |
x86-64 | APP_ABI := x86_64 |
所有支持的 ABI(默认) | APP_ABI := all |
APP_BUILD_SCRIPT:默认情况下,ndk-build 假定 Android.mk 文件位于项目根目录的相对路径?jni/Android.mk
?中。如需从其他位置加载 Android.mk 文件,请将?APP_BUILD_SCRIPT
?设置为 Android.mk 文件的绝对路径。
APP_PLATFORM:APP_PLATFORM
?会声明构建此应用所面向的 Android API 级别,并对应于应用的?minSdkVersion
。例如,?android-16
?说明无法运行在低于Android 4.1(API 级别 16)的设备上。
#include?<stdio.h>
int?main()
{
????while?(1)
????{
????????getchar();
????????printf("hello\n");
????}
????return?0;
}
这里就是我们的测试代码,会将它编译成可执行文件,然后push到设备上去运行。我们可以使用 IDA 来观察汇编代码。
export?ANDROID_NDK=/root/Android/Sdk/ndk/21.4.7075529
$ANDROID_NDK/ndk-build?NDK_PROJECT_PATH=.?NDK_APPLICATION_MK=Application.mk
ndk-build
?脚本位于 NDK 安装目录顶层。所以我们定义一个变量表示其目录,然后直接使用即可。
NDK_APPLICATION_MK=<file>
?:使用?NDK_APPLICATION_MK
?变量指向的特定?Application.mk
?文件进行构建。
如果Android.mk
和Application.mk
所在目录的名字不是jni
,需要通过变量指定 :NDK_PROJECT_PATH=.
adb?connect?192.168.3.12:5555
adb?push?obj/local/arm64-v8a/arm64?/data/local/tmp/arm64
adb?shell?"chmod?777?/data/local/tmp/arm64"
将生成的可执行文件 push 到 设备的指定位置。
注意生成的可执行文件有两个:
├──?libs
│???└──?arm64-v8a
│???????└──?arm64
├──?obj
│???└──?local
│???????└──?arm64-v8a
│???????????├──?arm64
│???????????└──?objs
│???????????????└──?arm64
│???????????????????├──?arm64.o
│???????????????????└──?arm64.o.d
obj/local/arm64-v8a/arm64
?这个可执行文件是带调试信息的。
libs/arm64-v8a/arm64
?这个可执行文件是经过 strip 了的,不带符号与调试信息。
我们学习时使用带调试信息的即可。
adb?connect?192.168.3.12:5555
#adb?shell?"/data/local/tmp/android_server64"
连接设备,打开 IDA 的 server。
然后我们在有 IDA 的主机上连接设备,转发端口,就可以进行调试了。
前面我们说了,这个选项是用来表明生成的目标文件里面,它的汇编代码是使用 arm 还是 thumb 指令。
我们测试一下 arm 的效果:
LOCAL_ARM_MODE?:=?arm
其main函数汇编如下:
.text:0000000000000714 ; =============== S U B R O U T I N E =======================================
.text:0000000000000714
.text:0000000000000714 ; Attributes: noreturn bp-based frame
.text:0000000000000714
.text:0000000000000714 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000000000714 EXPORT main
.text:0000000000000714 main ; DATA XREF: LOAD:0000000000000438↑o
.text:0000000000000714 ; .got:main_ptr↓o
.text:0000000000000714
.text:0000000000000714 var_10= -0x10
.text:0000000000000714 var_s0= 0
.text:0000000000000714
.text:0000000000000714 ; __unwind {
.text:0000000000000714 F3 0F 1E F8 STR X19, [SP,#-0x10+var_10]!
.text:0000000000000718 FD 7B 01 A9 STP X29, X30, [SP,#0x10+var_s0]
.text:000000000000071C FD 43 00 91 ADD X29, SP, #0x10
.text:0000000000000720 13 00 00 90 73 E2 1C 91 ADRL X19, aHello ; "hello"
.text:0000000000000720
.text:0000000000000728
.text:0000000000000728 loc_728 ; CODE XREF: main+20↓j
.text:0000000000000728 C6 FF FF 97 BL .getchar
.text:0000000000000728
.text:000000000000072C E0 03 13 AA MOV X0, X19 ; s
.text:0000000000000730 CC FF FF 97 BL .puts
.text:0000000000000730
.text:0000000000000734 FD FF FF 17 B loc_728
.text:0000000000000734 ; } // starts at 714
.text:0000000000000734
.text:0000000000000734 ; End of function main
.text:0000000000000734
.text:0000000000000734 ; .text ends
.text:0000000000000734
可以看到都是 4 个字节的指令,除了?ADRL
?,查看文档发现并没有这个指令。网上搜索了一下,发现这是一个伪指令,最终会将其转换为两条加载指令。所以,算下来,main 函数体的指令其实都是4个指令的。
再测试一下 thumb 指令的效果:
LOCAL_ARM_MODE := thumb
发现,指令并没有变化,这是为啥呢?我们看下面这个表:
可以看到,thumb 指令只存在于 armeabi-v7a 里面。
现在的应用在googleplay与国内商店的推动下,都已采用了 arm64-v8a,所以我们可以不用太关心 thumb 指令了。
想要看一下 thumb 指令,我们在 Application.mk 里面设置一下:
APP_ABI := arm64-v8a armeabi-v7a
这样就可以生成两个可执行文件,使用 ida 打开 armeabi-v7a 下的文件,查看其 main 函数:
.text:000005FC ; =============== S U B R O U T I N E =======================================
.text:000005FC
.text:000005FC ; Attributes: noreturn bp-based frame
.text:000005FC
.text:000005FC ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:000005FC EXPORT main
.text:000005FC main ; DATA XREF: .text:000005A8↑o
.text:000005FC ; .got:main_ptr↓o
.text:000005FC ; __unwind {
.text:000005FC D0 B5 PUSH {R4,R6,R7,LR}
.text:000005FE 02 AF ADD R7, SP, #8
.text:00000600 03 4C LDR R4, =(aHello - 0x606) ; "hello"
.text:00000602 7C 44 ADD R4, PC ; "hello"
.text:00000602
.text:00000604
.text:00000604 loc_604 ; CODE XREF: main+12↓j
.text:00000604 FF F7 9C EF BLX getchar
.text:00000604
.text:00000608 20 46 MOV R0, R4 ; s
.text:0000060A FF F7 A0 EF BLX puts
.text:0000060A
.text:0000060E F9 E7 B loc_604
.text:0000060E
.text:0000060E ; End of function main
.text:0000060E
.text:0000060E ; ---------------------------------------------------------------------------
非常明显的变长指令,毕竟 thumb 指令的出现就是为了减少指令的长度。
调试一下这个程序,可以从寄存器窗口观察到 T flag 的值是1:
有些情况下,如果程序加了壳,或者说搞了自解密,可能会导致 IDA 错误识别指令。本来是 thumb 却识别成了 arm,或者反过来将 arm 识别成了 thumb。这个时候就需要我们手动将指令模式改过来。
在对应汇编位置,我们按 ALT + G 快捷键即可更改指令编码格式:
将 T 的值改成 1 就是 thumb 格式,改成 0 就是 arm 格式。