PwnCollege - Program Security - Memory Corruption
Login Leakage (Easy)
Leverage memory corruption to satisfy a simple constraint
Analysis
1 | hacker@program-security~login-leakage-easy:~$ /challenge/login-leakage-easy |
- 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 (空字节注入) 让判断逻辑失效就行。
- 如果我们在
input的第一个字节就写入\x00,strcmp就会认为我们的输入是一个空字符串""。 - 因为存在 buffer overflow,我们可以一路把
\x00填充过去,跨越那 486 bytes 的距离,顺便把目标password的第一个字节也覆盖成\x00。 - 这样一来,
password在strcmp眼里也变成了空字符串""。
strcmp("", "") == 0,密码校验直接
pass。
Exploit
1 | #!/usr/bin/env python3 |
Login Leakage (Hard)
Leverage memory corruption to satisfy a simple constraint
Analysis
1 | r2 -A -q -c "pdf @ sym.challenge" /challenge/login-leakage-hard |
这一题和 Easy 本质一样,还是利用 strcmp
的字符串终止规则,只是需要先从反汇编里把布局自己抠出来。
- 随机密码生成逻辑 (0x154f - 0x156a):
lea RAX, [path]:加载path缓冲区的基地址。lea RCX, [RAX + 0x192]:计算出目标地址path + 0x192。call sym.imp.read:从/dev/urandom读取 8 bytes 写入到path + 0x192。- 结论:真正的密码存放位置是相对于你输入缓冲区的偏移量
0x192处。
- 输入控制权交接 (0x159f - 0x15ab):
- 程序用
scanf("%lu")让你自己输入后续要读取的Payload size(nbyte)。没有做任何边界检查 (bounds checking),很典型的代码。
- 程序用
- buffer overflow 发生地 (0x15d2 - 0x15e1):
mov RSI, RAX(其中 RAX 是path的地址)call sym.imp.read:从标准输入 (fd=0) 读取你指定长度 (nbyte) 的数据,直接怼进path的头部。
- 致命的比较 (0x161b - 0x1636):
mov RDI, RAX(s1=path)mov RSI, RDX(s2=path + 0x192)call sym.imp.strcmp:程序居然又用了strcmp!
利用思路:
- 计算偏移:
0x192转换为十进制就是 402 bytes。 - 利用不受限制的
read,往缓冲区塞满\x00。 - 第 1 个
\x00会让path(你的输入) 被strcmp视为空字符串""。 - 第 403 个
\x00会精准覆盖掉path + 0x192(随机密码) 的第一个字节,让密码也被视为空字符串""。
Exploit
1 | #!/usr/bin/env python3 |
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 | hacker@program-security~bounds-breaker-easy:~$ /challenge/bounds-breaker-easy |
- buffer address:
0x7ffea42aa1b0 - return address:
0x7ffea42aa208
1 | hacker@program-security~bounds-breaker-easy:~$ nm /challenge/bounds-breaker-easy | grep 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 | #!/usr/bin/env python3 |
Bounds Breaker (Hard)
Analysis
1 | r2 -A -q -c "pdf @ sym.challenge" /challenge/bounds-breaker-hard |
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 | #!/usr/bin/env python3 |
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 | #!/usr/bin/env python3 |
Casting Catastrophe (Hard)
Analysis
1 | r2 -A -q -c "pdf @ sym.challenge" /challenge/casting-catastrophe-hard |
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 | #!/usr/bin/env python3 |
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=0x7fff67f0f7b0buffer_address=0x7fff67f0f760- padding = 80 bytes
We overwrite the low 2 bytes of the pointer with
0x5060.
Exploit
1 | #!/usr/bin/env python3 |
Pointer Problems (Hard)
Analysis
1 | hacker@program-security~pointer-problems-hard:~$ r2 -A -q -c "pdf @ sym.challenge" /challenge/pointer-problems-hard |
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 | #!/usr/bin/env python3 |
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 = 1480bytes - element size: 8 bytes
- index:
1480 / 8 = 185-> use-185
Exploit
1 | from pwn import * |
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。
winfunction: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 | from pwn import * |
Now You GOT It (Hard)
Leverage an Array to obtain the flag.
Analysis
Hard 版的关键变化是,不能像 Easy 那样直接把 puts@GOT
指到 win 入口,否则 win 自己内部先调用
puts,马上递归爆炸。
解法:offset jump (偏移跳转)
看看 win 函数开头:
1 | 0x00001e84 f30f1efa endbr64 |
如果我们把 puts@GOT 覆盖为 win_addr + 0x14
(即 0x1e98),就能绕过死循环。
Exploit
1 | from pwn import * |
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 | while (n < size) { |
buf->0x7fff2243c7e0n->0x7fff2243c83c(offset 92)- return address ->
0x7fff2243c858(offset 120)
我们覆盖 n 为 119,下一次 read 就会写入到
input + 120,即 return address 的位置。
Exploit
1 | from pwn import * |
Loop Lunacy (Hard)
Analysis
Hard 版还是同样的单字节循环写入,只是局部变量布局稍微绕一点。
buf->RBP - 0x40(64 bytes)n->RBP - 0x58buftonoffset = 48 bytes- target
win_authedoffset ->0x1481
Exploit
1 | from pwn import * |
Nosy Neighbor (Easy)
Overflow a buffer and leak the flag.
Analysis
这一题是典型的字符串越界泄露。printf("%s")
会一直打印到遇见 \x00 为止。
buf->0x7ffcec871790flag->0x7ffcec8717f1- offset -> 97 bytes
Exploit
1 | from pwn import * |
Nosy Neighbor (Hard)
Analysis
Hard 版也是同一类 bug,输入是从 path
开始写,真正的可打印缓冲区在 path + 0x6d。
- offset ->
0x6d-> 109
Exploit
1 | from pwn import * |
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。
- 第一发 Payload (Information Leak): 覆盖 canary
的最低字节 (
\x00),利用printf泄露剩余 7 字节。加入"REPEAT"触发递归。 - 第二发 Payload (Control Flow Hijack): 在第二层
challenge()中,利用泄露的 canary 构造 payload 绕过检查。
Exploit
1 | import sys |
Recursive Ruin (Hard)
Analysis
Hard 版同样是两阶段利用。区别只是缓冲区更小,偏移更短。
buf->RBP - 0x20canary->RBP - 8buftocanaryoffset = 24 bytes
Exploit
1 | import sys |
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 | from pwn import * |
Lingering Leftover (Hard)
Analysis
Hard 版也是同一个思路,从函数之间的栈复用关系里确认 flag 的位置。
buf->RBP - 0x200- flag read to
RBP - 0x160 + 0x56 - distance =
0x200 - 0x160 + 0x56 = 246bytes
Exploit
1 | from pwn import * |
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 | import sys |
Latent Leak (Hard)
Analysis
Hard 版需定位 canary copy 在栈上的位置。
buf->var_170h- canary copy at
RBP - 0x118 - distance = 88 bytes
Exploit
1 | import sys |
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。
buftocanaryoffset = 120 bytes
Exploit
1 | import itertools |
Fork Foolery (Hard)
Analysis
Hard 版同样是 fork server 爆破。
buftocanaryoffset = 120 bytes- target
win_authedoffset =0x9db
Exploit
1 | import itertools |