DefconCTF Quals - RE (ELF Crumble)
As is usual, I took way longer on this than I should have. Unfortunately, the challenges are down so I can’t provide a screenshot of the challenge. It said something along the lines of:
We made you a welcome banner but we dropped it and it shattered to pieces. Good thing none of the instructions are lost! You can probably put it back together with some glue.
You’re given a file named “pieces”.
I download it, run $ file pieces and see that’s it’s a gzip file. I run $ gzip -d pieces to unzip it. Now it’s a tar file, so I run $ tar xvf broken, and we finally get to the files.
I get a folder called pieces out, with a file called broken and 8 files consecutively names fragment_#.dat.
Let’s run file on broken.
root@kali:~/Desktop# file broken broken: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4cd47d8a237a3139d1884b3ef52f6ed387c75772, not stripped
so it’s a 32-bit binary. I try running it only to get a no file or folder exists, assuming it’s because it’s broken. Let’s look at what the fragments have in them.
root@kali:~/Desktop# cat fragment_1.dat �U��������E��#�U�����E�� �U��Љʈ�E��}�~א��U��S��
Yup, can’t make sense of that. Let’s go look at broken in Ida.
Well, it literally opens right up to a bunch of pop eaxs, which are equivalent to 58 in hex and “x” in ascii. This is the case for all the relevant functions (namely, f1, f2, f3, recover_flag, and main). Since the prompt said the instructions are still around, they must be somewhere else.
At first, I thought they were hidden within the binary itself. I looked through the comments or any function that doesn’t look like it didn’t belong. Then it hit me. I had assumed the fragments were the flag that needed to be decoded by the broken program.
No, they were the instructions.
I did a $ hexdump -v -e ‘/1 “%02X”’ fragment_1.dat » out of all the fragment files into an output file, formatting them with -e so hexdump didn’t change their format and stop any repeating hex from being omitted with -v. This part is important if you’re using hexdump because hexdump makes the hex into little endian byte-length chunks, which flips their places for example, 5b 5d becomes 5d5b leading you to have the wrong code. I took the resulting hex to the handy dandy Online x86 Diassembler at https://defuse.ca/online-x86-assembler.htm.
64: 5b pop ebx 65: 5d pop ebp 66: c3 ret 67: 55 push ebp 68: 89 e5 mov ebp,esp 6a: 83 ec 10 sub esp,0x10 6d: e8 e0 01 00 00 call 0x252 72: 05 dc 18 00 00 add eax,0x18dc 77: c7 45 fc 00 00 00 00 mov DWORD PTR [ebp-0x4],0x0 ....
looks like real instructions!
So from here, I waste my time because I have no respect for myself. I didn’t think I could possibly figure out how to manually find their positions in the code with 8 fragments having 4k permutations, so I wrote a brute forcer. The code is as follows:
import binascii import itertools import os with open('broken', 'rb') as f: hexdata = binascii.hexlify(f.read()) with open('fragment_1.dat', 'rb') as f: f1 = binascii.hexlify(f.read()) with open('fragment_2.dat', 'rb') as f: f2 = binascii.hexlify(f.read()) with open('fragment_3.dat', 'rb') as f: f3 = binascii.hexlify(f.read()) with open('fragment_4.dat', 'rb') as f: f4 = binascii.hexlify(f.read()) with open('fragment_5.dat', 'rb') as f: f5 = binascii.hexlify(f.read()) with open('fragment_6.dat', 'rb') as f: f6 = binascii.hexlify(f.read()) with open('fragment_7.dat', 'rb') as f: f7 = binascii.hexlify(f.read()) with open('fragment_8.dat', 'rb') as f: f8 = binascii.hexlify(f.read()) files = [f1,f2,f3,f4,f5,f6,f7,f8] # cut out the x's # put the new combinations in the middle # combine the beginning, new middle, and end, then save to file # run the file # loop for that each combination # the beginning hex for the pop eax section str1 = "5858" # the ending hex for the pop eax section str2 = "8b04" # get the positions strindex1 = hexdata.index(str1) strindex2 = hexdata.index(str2) # separate the beginning and end beginning = hexdata[0:strindex1] end = hexdata[strindex2:] # iterate through permutations for subset in itertools.permutations(files): with open("broken", "wb") as fout: fout.write(binascii.unhexlify(beginning) + binascii.unhexlify(''.join(subset)) + binascii.unhexlify(end)) os.system("./broken" + " >> out") os.system("rm broken")
I run this, cat the output file, and nothing. NOTHING. Okay, you know what, time to go to sleep. The next day, I decided to cat out all the fragments to try to get a clue.
root@kali:~/Desktop# cat fragment_3.dat �ȋ@�E�e��E�1��E�Did �E�You �E�FinD�E� the�E� GlUf�E�e?�E��E��� �E�P�T������E����E����E����E��E��ЉU��E����u��E����E����E����� QW�u��u��u�VRP������P
Is this really the fucking flag? I try submit “Did You FinD the GlUe?” in a couple of ways. False flag.
At this point, I started talking to a friend over Slack who was also doing the challenge, and he said “Well the 8th one starts with what main should start with, so that’s obviously the start for main,” (that was actually incorrect) which made me think, maybe I can do this manually.
The way I figured this out was just logical reasoning. I looked at the lengths, order, and generally how the function should look like in assembly to decide how to piece them together. The order of the functions were f1, f2, f3, recover_flag, and main, so instructions for each would also be in this order, and the beginning for one function could be in the middle the other’s fragment.
- Each function has to start with push ebp; mov ebp, esp to set up each frame, so fragment 8 has to be the first fragment, since it’s the only fragment that starts with these instructions, and this would be beginning of f1.
- f1 is the longest function, and the only fragment that didn’t have a push ebp, mov ebp, esp in it was fragment 7, so this must be the middle of f1 and what follows fragment 8.
- This would probably fill up most of function, so I needed a fragment that had a leave; ret pretty high up in the insutrctions, and fragment 1 matched this criteria.
I copied and replaced the 58s with the hex from the three fragments on HxD. Make sure you right click and copy from the hex side of the screen, and then right click and “paste write” on the hex side, or else you can end up pasting the hex as ascii.
Then I checked it on Ida to see if it looks right. The easiest way to check if I had gotten the right combination was checking to see if the sp-analysis failed went away on Ida.
I followed this reasoning and some guessing and checking until I got the fragment order of 8, 7, 1, 5, 6, 2, 3, 4.
I saved the final file, and tried to run it. Still no file or folder exists, but this time I’m sure it’s supposed to run. Oh it’s a 32-bit file and I’m on a 64-bit Kali. I install the architecture for x32 ($ dpkg –add-architecture i386 $ apt-get update if you were wondering how), and run it.
root@kali:~/Desktop# ./broken welcOOOme
SUCCESS. Wait.. so why didn’t my brute forcer work?
Oh yeah, I hadn’t installed the 32-bit architecture. I run the brute forcer and cat the output file
root@kali:~/Desktop# python exploit.py root@kali:~/Desktop# cat out welcOOOme
I’m not crying, you are.