之前遇到Arm
的题都比较简单,但是每次还需要去查寄存器、传参等基础知识,对其掌握并不牢固。所以,这里就总结一下常见题型吧。
arm环境搭建
qemu安装
1 | #安装qemu |
安装gdb plugin
1 | peda-arm: https://github.com/alset0326/peda-arm |
Arm库文件
对于大多数题目都会给运行 Arm
的 libc
库和ld
文件,但是有些题目如果没有给 ld
文件,那么可以考虑去下载和 libc
版本对应的 ld-linux-armhf.so.3
文件。下载命令为:
1 | sudo apt-get install libc6-armhf-cross |
然后在 /usr/arm-linux-gnueabihf/
文件夹下即可找到运行所需的库文件。
也可以先搜索相关库文件:
1 | apt search "libc6-" | grep "arm“ |
如果我们想在 Pwntools
中兼容 arm
的 asm
环境,需要使用如下命令:
1 | apt search binutils | grep [arch] |
安装 binutils
对应架构环境。
运行Arm
Arm文件也分为大小端序,运行时需要选择对应架构的qemu
。同时也要区分64位 和 32位的 运行命令也是不同的。
如果要运行32Arm
,可以使用如下命令:
1 | qemu-arm 或 qemu-arm-static |
运行64位 Arm,可以使用如下命令:
1 | qemu-aarch64 |
当然,qemu
都分为 系统模式和用户模式。除了上面使用用户模式运行程序外,我们也可以直接起一个qemu
:
1 | sudo qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2 console=ttyAMA0" -net nic,macaddr=52:54:00:12:34:56 -net tap -nographic |
当然,问题和我在mips环境搭建
一文中所提到的一样,即如果要联通外网需要进行网络配置。
安装qemu-system
1 | sudo apt-get install qemu qemu-user-static qemu-system uml-utilities bridge-utils |
配置qemu-system网络
qemu-system
模式配置网络常见的方法是 tap 桥接。
安装网络配置的依赖文件:
1 | sudo apt install uml-utilities bridge-utils |
修改Ubuntu主机网络接口配置文件:
1 | sudo vim /etc/network/interfaces |
创建并编写qemu网络接口启动脚本
1 | sudo vim /etc/qemu-ifup |
保存文件后使用如下命令修改qemu-ifup的权限:
1 | sudo chmod a+x /etc/qemu-ifup |
重启网络使配置生效:
1 | sudo /etc/init.d/networking restart |
启动桥接网络:
1 | sudo ifdown ens33 && sudo ifup br0 |
运行之后,桥br0代替ens33接管了ubuntu虚拟机的网口。
arm基础知识
64位寄存器
X0-X7 :传递子程序的参数和返回值,使用时不需要保存,多余的参数用堆栈传递,64位的返回结果保存在 X0 中;
X8 :用于保存子程序的返回地址,使用时不需要保存;
X9-X15 : 临时寄存器,也可叫可变寄存器,子程序使用时不需要保存
X16 - X17 : 子程序内部调用寄存器(IPx),使用时不需要保存,尽量不使用。
X18 : 平台寄存器,其使用与平台相关,尽量不要使用
X19 - X28 :临时寄存器,子程序使用时必须保存
X29 :帧指针寄存器(FP),用于连接栈帧,使用时必须保存
X30 :链接寄存器(LR),用于保存子程序的返回地址
X31:堆栈指针寄存器(SP),用于指向每个函数的栈顶
32位寄存器
r0 - r3 :用于函数调用传参,32位最多支持4个入参,当多余4个参数时将通过压栈方式进行传递,栈的方式为先进后出,当参数大于4个时,入栈顺序与参数顺序正好相反,子程序返回前无需恢复 r0 - r3的,32位的返回结果保存在 r0 中;
r4 - r11 :用于保存局部变量,函数进入后首先就是将 r4 - 411 入栈保存,然后才能用于本函数使用,本函数使用完之后,要讲之前栈保存的数据恢复到 r4 - r11 中;
r7 :系统调用时,存放系统调用号,有时用于作为 FP 使用,FP又叫 frame pinter 即栈基指针,主要在函数中保存当前函数的栈起始位置,用于堆栈回溯;
r13 :SP,即栈指针寄存器,主要用于指向当前程序栈顶、配合指令 pop/push
r14 :LR,即链接寄存器,主要用于存放函数的返回地址
r15 :PC,即程序寄存器,主要用于存放CPU取值的地址,是取值地址,不是当前运行地址。
Arm指令简介
汇编语言由指令构成,而指令是主要的构建块。ARM指令通常后跟一个或两个操作数,并且通常使用以下模板:
MNEMONIC {S} {condition} {Rd}, Operand1, Operand2
说明如下:
1 | MNEMONIC -指令简称 |
注意,由于ARM指令集的灵活性,并非所有指令都使用模板中提供的所有字段。其中,条件字段与CPSR
寄存器的值紧密相关,或者确切说,与寄存器内特定位的值紧密相关。
Opreand2
被称为灵活操作数,因为我们可以以多种形式使用它,例如,可以将这些表达式用作Operand2
:
1 | #123 - 立即数 |
常见指令为例:
1 | ADD R0, R1, R2 - R1和R2相加,结果存储到R0 |
Codegate2018_melong
程序分析
1 | _DWORD *__fastcall write_diary(_DWORD *result, void *a2) |
此处存在栈溢出,这里的 size
是由我们前面的堆块 size
决定,如果堆块 size
输入为 -1,那么malloc
函数会返回 NULL
,而这里 read
函数即可以 读取无限长字符串。
利用分析
这里,我们直接利用csu
来布置参数和 函数调用。
1 | csu_front_addr = 0x11d00 |
csu
会随着编译版本不同,而改变。但是每个版本都可以用来布置参数和执行函数,需要具体分析一下。
EXP
1 | # encoding=utf-8 |
Shanghai2018 – baby_arm
程序分析
1 | __int64 sub_400818() |
1 | ssize_t sub_4007F0() |
程序漏洞很明显,存在一个栈溢出。而且读取时是从xbp
后面开始输入,也就是自然就绕过了canary
的检查。
利用分析
这道题的难点在于,不知道远程的libc
版本,也就是不好直接用泄露libc
地址来ROP
执行 system
。
程序中,我们可以看到有 mprotect
函数,那么思路很明显是想让我们使用shellcode
了。
那么思路就是:先将shellcode
布置在栈上,然后使用rop
执行 mprotect
修改 shellcode
的可执行权限,最后使用 ROP
跳转执行 shellcode
。
这里 ROP
,我们仍然是使用 csu
来布置。我尝试过直接使用 csu
来调用 mprotect_got
,但是却调用失败了,因为此时 mprotect_got
还没有初始化为 mprotect
函数地址。后面用了网上通用的方法,现在栈上写上 mprotect_plt
的地址,然后将 该内存地址 的值赋给 x3
。
EXP
1 | from pwn import * |
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2020/12/29/Arm基础学习/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!