My Profile Photo

Giacomo Iengo


Hi, I'm a M.Sc. student in Computer Engineering. I believe that meaningful breakthroughs require deep understanding — and that's what I've always prioritized in my studies, contributions, and projects.


Don't break me

The challenge

   
Event AUCTF 2020
Challenge name Don't Break Me!
Challenge category Reversing
Challenge points 763
Executable dont_break_me

What does the executable do?

The executable asks for an input.

user@pc:~$ ./dont_break_me 
54 68 65 20 6d 61 6e 20 69 6e 20 62 6c 61 63 6b 20 66 6c 
65 64 20 61 63 72 6f 73 73 20 74 68 65 20 64 65 73 65 72
74 2c 20 61 6e 64 20 74 68 65 20 67 75 6e 73 6c 69 6e 67
65 72 20 66 6f 6c 6c 6f 77 65 64 2e
Input: DUMMYINPUT
Not quite

Don’t worry about all those numbers, we don’t really need them to complete the challenge. So, our input is passed as an argument to sym.encrypt, then the encrypted string returned by the function is compared with sym.imp.strcmp to a hardcoded key and if they are equal, it prints the flag. We can see this flow in radare2’s output.

│           0x000012f8      e853fdffff     call sym.imp.fgets 
│ ... skipping
│           0x00001321      50             push eax
│           0x00001322      e8a8010000     call sym.encrypt
│ ... skipping
│           0x00001350      ff75ec         push dword [s2]
│           0x00001353      ff75e8         push dword [s1]
│           0x00001356      e8d5fcffff     call sym.imp.strcmp 
│           0x0000135e      85c0           test eax, eax
│       ┌─< 0x00001360      7507           jne 0x1369
│       │   0x00001362      e889000000     call sym.print_flag

What can we do?

It seems that all we have to do is reverse the sym.encrypt function, so we can finally provide the correct input and get the flag. Unfortunately, as we can see from the official writeup, there are some antidebugging techniques, so debugging would be a little tricky.

But luckily, looking at the ltrace output, we can see the hardcoded key compared to our encrypted input.

user@kvm:~$ ltrace ./dont_break_me 
printf("Input: "Input: ) = 7
fgets( DUMMYINPUT
"DUMMYINPUT\n", 8192, 0xf7f4b5c0)
    ... skipping
strcmp("SASRRWSXBIEBCMPX", "LOIIESZHOX")
printf("Not quite"Not quite)

So the key is "SASRRWSXBIEBCMPX" and "DUMMYINPUT" apparently becomes "LOIIESZHOX". Now, we can try to input all printable ASCII characters for each position of the key, see what our input char becomes, and then compare it to that of the key until we get the right one. Here is the somewhat ugly script:

import re
import string
from pwn import *

secret = ''

for index in range(16):
	for guess in string.printable:
		p = process('ltrace ./dont_break_me', shell=True)
		data = p.recvuntil('fgets(')
		p.sendline(guess)
		data = p.recvuntil('(status 0) +++')
		line = re.findall(r'strcmp.*', data)

		try:
			key, ch = re.findall(r'"[A-Z]+"|"[A-Z]"', line[0])
		except:
			p.close()
			continue

		key = key.replace('"','')
		ch = ch.replace('"','')


		if ch == key[index]:
			secret += guess
			p.close()
			break			

		p.close()
		
log.info(secret)

After a long and ugly output, we get the key (c1cffqcnbgsbyuln) So, the last step is:

user@pc:~$ nc challenges.auctf.com 30005
54 68 65 20 6d 61 6e 20 69 6e 20 62 6c 61 63 6b 20 66 6c 
65 64 20 61 63 72 6f 73 73 20 74 68 65 20 64 65 73 65 72
74 2c 20 61 6e 64 20 74 68 65 20 67 75 6e 73 6c 69 6e 67
65 72 20 66 6f 6c 6c 6f 77 65 64 2e
Input: c1cffqcnbgsbyuln

auctf{static_or_dyn@mIc?_12923}

Thanks for reading!