In this post I will explain my solutions for the challenges on the Ciberseg ‘19 CTF. Specifically, these are the ones corresponding to the reverse engineering category.

Ciberseg is an annual congress which takes place in the University of Alcalá de Henares. The truth is that previous years it has been always fun, and this year wasn’t less :) Also, the first places were disputed hard and there were last-time surprises :D (in the end, I literally won at the last hour by just a few points).

Anyways, these are the challenges and their solutions. For those that need it, I’ll also leave the necessary resources that we where given to try the challenge by yourselves.

# 1.- Doom 5 Alpha (25 points)

The description of this challenge states:

The latest Doom version has been leaked, but I don’t have the key :(

And we’re given the binary where we have to get the key from: doom5_alpha.

When we execute it, we’re presented a message that says (translated from Spanish) To play this game you need a license.

The first thing that we do is to take a quick look at the code, to form ourselves an idea of what is the program doing:

$objdump -M intel -d doom5_alpha (...) 00000000000011be <main>: 11be: 55 push %rbp 11bf: 48 89 e5 mov %rsp,%rbp 11c2: 48 83 ec 30 sub$0x30,%rsp
11c6:	48 8b 05 d3 2e 00 00    mov    0x2ed3(%rip),%rax        # 40a0 <stdout@@GLIBC_2.2.5>
11cd:	48 89 c1                mov    %rax,%rcx
11d0:	ba 2d 00 00 00          mov    $0x2d,%edx 11d5: be 01 00 00 00 mov$0x1,%esi
11da:	48 8d 3d 27 0e 00 00    lea    0xe27(%rip),%rdi        # 2008 <_IO_stdin_used+0x8>
11e1:	e8 8a fe ff ff          callq  1070 <fwrite@plt>
11e6:	48 8b 15 c3 2e 00 00    mov    0x2ec3(%rip),%rdx        # 40b0 <stdin@@GLIBC_2.2.5>
11ed:	48 8d 45 d0             lea    -0x30(%rbp),%rax
11f1:	be 21 00 00 00          mov    $0x21,%esi 11f6: 48 89 c7 mov %rax,%rdi 11f9: e8 52 fe ff ff callq 1050 <fgets@plt> 11fe: 48 8d 45 d0 lea -0x30(%rbp),%rax 1202: 48 89 c6 mov %rax,%rsi 1205: 48 8d 3d 54 2e 00 00 lea 0x2e54(%rip),%rdi # 4060 <pass> 120c: e8 4f fe ff ff callq 1060 <strcmp@plt> 1211: 85 c0 test %eax,%eax 1213: 75 43 jne 1258 <main+0x9a> 1215: 48 8d 3d 1c 0e 00 00 lea 0xe1c(%rip),%rdi # 2038 <_IO_stdin_used+0x38> 121c: b8 00 00 00 00 mov$0x0,%eax
1221:	e8 1a fe ff ff          callq  1040 <printf@plt>
1226:	48 8d 3d ab 14 00 00    lea    0x14ab(%rip),%rdi        # 26d8 <_IO_stdin_used+0x6d8>
122d:	e8 fe fd ff ff          callq  1030 <puts@plt>
1232:	48 8d 3d 4f 2e 00 00    lea    0x2e4f(%rip),%rdi        # 4088 <flag>
1239:	e8 37 ff ff ff          callq  1175 <xor>
123e:	48 8d 35 43 2e 00 00    lea    0x2e43(%rip),%rsi        # 4088 <flag>
1245:	48 8d 3d ab 14 00 00    lea    0x14ab(%rip),%rdi        # 26f7 <_IO_stdin_used+0x6f7>
124c:	b8 00 00 00 00          mov    $0x0,%eax 1251: e8 ea fd ff ff callq 1040 <printf@plt> 1256: eb 0c jmp 1264 <main+0xa6> 1258: 48 8d 3d a9 14 00 00 lea 0x14a9(%rip),%rdi # 2708 <_IO_stdin_used+0x708> 125f: e8 cc fd ff ff callq 1030 <puts@plt> 1264: b8 00 00 00 00 mov$0x0,%eax
1269:	c9                      leaveq
126a:	c3                      retq
126b:	0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
(...)

We can see that it first calls fwrite (line :11e1) to show the message asking for the license key. Then, it calls fgets to read the access code and it compares that with <pass>, which is whatever string is located in 0x4060. If they match, (line :1213), it keeps going on to calculate the flag and show it on screen.

There are multiple ways of solving this challenge: debug with gdb and modify $eip to bypass the key comparison, so it goes directly to calculate the flag (<xor>, called in line :1239)… Or we can see what string is in 0x4060 to introduce the correct value and let the program execute normally. To see the string used in the comparison (the license key), we can use objdump again and search in section .data:$ objdump -s -j .data doom5_alpha

doom5_alpha:     file format elf64-x86-64

Contents of section .data:
4040 00000000 00000000 48400000 00000000  ........H@......
4050 00000000 00000000 00000000 00000000  ................
4060 38383931 34353332 64667238 34373334  88914532dfr84734
4070 6865666f 346b3564 32333835 37333435  hefo4k5d23857345
4080 00000000 00000000 2818190b 3d071d11  ........(...=...
4090 05130000                             ....

When we try to execute the program again using that value (from 0x4060 to 0x4080, before the null terminator), we can check that the key is correct and we get our flag:

$./doom5_alpha Para jugar este juego necesitas una licencia 88914532dfr84734hefo4k5d23857345 +-----------------------------------------------------------------------------+ | | |\ -~ / \ / | |~~__ | \ | \/ /\ /| | -- | \ | / \ / \ / | | |~_| \ \___|/ \/ / | |--__ | -- |\________________________________/~~\~~| / \ / \ | | |~~--__ |~_|____|____|____|____|____|____|/ / \/|\ / \/ \/| | | |~--_|__|____|____|____|____|____|_/ /| |/ \ / \ / | |___|______|__|_||____|____|____|____|____|__[]/_|----| \/ \ / | | \mmmm : | _|___|____|____|____|____|____|___| /\| / \ / \ | | B :_--~~ |_|____|____|____|____|____|____| | |\/ \ / \ | | __--P : | / / / | \ / \ /\| |~~ | : | / ~~~ | \ / \ / | | | |/ .-. | /\ \ / | | | / | | |/ \ /\ | | | / | | -_ \ / \ | +-----------------------------------------------------------------------------+ | | /| | | 2 3 4 | /~~~~~\ | /| |_| .... ......... | | | ~|~ | % | | | ~J~ | | ~|~ % |_| .... ......... | | AMMO | HEALTH | 5 6 7 | \===/ | ARMOR |#| .... ......... | +-----------------------------------------------------------------------------+ Correcto.. Esta es tu Flag!!!! flag{ArrgPirata} Easy peasy :D The flag is: flag{ArrgPirata}. # 2.- Negative (200 points) The description of this challenge states: In the UAH we’ve created a highly secure app whose code is inaccessible. Also, attached, there’s this file. Once we’ve extracted its contents we obtain a binary called ciberseg-ctf-19 incredibly heavy (110 MB just to show three little windows…) and lasts like a whole year to start, apart from the added 150 MB for some resources with names like chrome_100_percent.pak or LICENSES.chromium.html that give some pointers to how the application was made. Also, inside the directory resources/ there’s a file that don’t give more room to any doubt: electron.asar. Oh, God! The infamous Electron. I’m not going to start ranting about Electron because I won’t finish in a month. I just want to declare my profound dislike about Electron :( Maybe I take some time to write a little post, or something. Who knows? Anyways, our goal now is to reverse engineer it to get the source code back. Apparently, it’s archived with something called asar and, the same way it was compressed, it can be decompressed to get the original source code simply by executing the command asar extract resources/app.asar ../extracted. Inside the directory where we’ve extracted everything, there’s a file called main.js which contains the main (duh) code of the application. In this case, as it’s so simple, we immediately see where the password is being checked: // (...) ipcMain.on('password', (event, arg) => { console.error(arg) // prints "ping" if(arg == Buffer.from("MTI2NWVmNmJjY2RhYzc5OTg1MzhiOTBjOGYxMjVjZjk4M2RiN2ZmZjE3OGUzNWRlMDY4MWQzNDQzM2QxMWM2YQ==", 'base64').toString('ascii')){ let flag = cipher.decrypt('c93ae864e1b525ab1c64a02e7996ea52', arg); dialog.showMessageBox({title: "Congratulations", message: "The flag is", detail: flag}); } else { mainWindow.setSize(800, 800) mainWindow.loadFile('logo.svg') } }) // (...) The value of the password, once decoded in Base64, is 1265ef6bccdac7998538b90c8f125cf983db7fff178e35de0681d34433d11c6a. If we introduce that value in the window asking for the password, we get the flag: The flag is: flag{show_the_code}. # 3.- Argument (250 points) The description for this challenge states: Because people agree speaking We also get attached the binary with which we have to work. As the name of the challenge implies, this has something to do with the arguments we pass to the executable. I have to admit that I spent a lot of time studying the code; but in the end it’s just a matter of looking for cmp instructions to see with which value it’s compared at each time and from there reconstruct the desired string, one character at a time. The first checks are upon the number of arguments. The requirements are: • $\fn_cm \left( argc \gg 1 \right) \mathrel{\&} 1 \neq 0$ That means that the number of arguments (including argc [0], which is the program name) divided by two must be odd. • $\fn_cm \left\{\begin{matrix} & (5 \mod argc) \neq 0 & \\ & (7 \mod argc) \neq 0 & \\ & (10 \mod argc) \neq 0 & \end{matrix} \right.$ A valid number of arguments, for example, is 2 ($\fn_cm argc = 3$, including the name of the program). After figuring out the appropriate number of arguments, the next step is to know its value. I’d usually use IDA, but the demo version doesn’t like 64-bit binaries, so I’ll use the Hopper demo, which is also a great program. The call graph returned by Hopper is the following one: The left side, marked in red, corresponds with the comparison, character by character, of the first argument with the string flag:. Each of the boxes at the bottom are meant to check one of the different letters, and they’re compared according to an index iterating across argv [1]. Then, we have the right side, marked in green. This side is a little more tricky; but, basically, it compares argv [2] also character by character; but this time it does it against an in-memory string which is filled right in the box that’s before entering the green zone. Its code is: mov qword [rbp+var_35], 0x0 mov dword [rbp+var_2D], 0x0 mov byte [rbp+var_29], 0x0 mov byte [rbp+var_35], 0x6c mov byte [rbp+var_34], 0x61 mov byte [rbp+var_33], 0x63 mov byte [rbp+var_32], 0x64 mov byte [rbp+var_31], 0x72 mov byte [rbp+var_30], 0x69 mov byte [rbp+var_2F], 0x65 mov byte [rbp+var_2E], 0x74 mov byte [rbp+var_2D], 0x6f mov byte [rbp+var_2C], 0x67 mov byte [rbp+var_2B], 0x66 mov qword [rbp+var_28], 0x8 mov dword [rbp+var_1C], 0x0 mov dword [rbp+var_18], 0x0 This adds the value lacdrietogf into the variable. But, be careful! This is not the value for the second argument. The comparisons are made according to an index that changes (not sequentially) and selects the proper value. This string is more like a query table, or something like that. The thing is, after all this comparisons we have the value of this second argument: retorcido (Spanish for twisted). Quite twisted, indeed XD$ ./a43b59111fef20ed7f8e2e53482076b99acea606.bin flag: retorcido
¡Bien hecho!

Our flag is flag{retorcido}.

# 4.- Get your Nintendo PowerGlove™ and start playing (300 points)

The description of this challenge states:

Everybody tells me that this Super Mario level is unbeatable, but I can go till the end without any problem. If you can’t beat it, you don’t deserve to be a Gamer.

We also get this file attached.

When we open it (I used the emulator nestopia and it works without any problem in my Arch Linux), we see that it’s a Super Mario Bros 3 ROM. However, if we try to beat the first level, we can see that there’s something wrong, and we find with a cliff with an enormous hole, impossible to jump.

What I did was to search in the interwebs for some program that could allow me to edit the levels in Super Mario Bros 3 to make the hole a little smaller, or anything that could be useful to me. After trying a couple of editors, I found SMB3 workshop which turned out to be a complete wonder. With this program I don’t even need to edit the level to beat it because, when trying to edit the next level, we can directly see the solution:

Even though the organizers had completely different idea to solve it XD

This is the write-up that they sent once the CTF finished, seeing that everybody solve the challenge using already existing tools (someone did edit the memory, though). It’s only available in Spanish:

Whichever method we used, the flag is: flag{goomba}.

# 5.- Cuisine revolution (300 points)

The description for this challenge states:

The software added in this cooking utensil is highly complex

Also, we’re given the binary we have to reverse engineer: crackme2.

As in the third challenge, we begin by taking a look at the binary to roughly see what is its functioning:

$objdump -d crackme2 (...) 0000000000001212 <main>: 1212: 55 push %rbp 1213: 48 89 e5 mov %rsp,%rbp 1216: 48 83 ec 10 sub$0x10,%rsp
121a:	b9 00 00 00 00          mov    $0x0,%ecx 121f: ba 01 00 00 00 mov$0x1,%edx
1224:	be 00 00 00 00          mov    $0x0,%esi 1229: bf 00 00 00 00 mov$0x0,%edi
122e:	b8 00 00 00 00          mov    $0x0,%eax 1233: e8 18 fe ff ff callq 1050 <ptrace@plt> 1238: 48 85 c0 test %rax,%rax 123b: 79 16 jns 1253 <main+0x41> 123d: 48 8d 3d c4 0d 00 00 lea 0xdc4(%rip),%rdi # 2008 <_IO_stdin_used+0x8> 1244: e8 e7 fd ff ff callq 1030 <puts@plt> 1249: b8 00 00 00 00 mov$0x0,%eax
124e:	e9 a2 00 00 00          jmpq   12f5 <main+0xe3>
1253:	48 8b 05 06 2e 00 00    mov    0x2e06(%rip),%rax        # 4060 <stdout@@GLIBC_2.2.5>
125a:	48 89 c1                mov    %rax,%rcx
125d:	ba 2d 00 00 00          mov    $0x2d,%edx 1262: be 01 00 00 00 mov$0x1,%esi
1267:	48 8d 3d d2 0d 00 00    lea    0xdd2(%rip),%rdi        # 2040 <_IO_stdin_used+0x40>
126e:	e8 ed fd ff ff          callq  1060 <fwrite@plt>
1273:	48 8b 15 f6 2d 00 00    mov    0x2df6(%rip),%rdx        # 4070 <stdin@@GLIBC_2.2.5>
127a:	48 8d 45 f5             lea    -0xb(%rbp),%rax
127e:	be 0a 00 00 00          mov    $0xa,%esi 1283: 48 89 c7 mov %rax,%rdi 1286: e8 b5 fd ff ff callq 1040 <fgets@plt> 128b: 48 8d 45 f5 lea -0xb(%rbp),%rax 128f: 48 89 c7 mov %rax,%rdi 1292: e8 ce fe ff ff callq 1165 <xor> 1297: 48 8d 45 f5 lea -0xb(%rbp),%rax 129b: 48 8d 35 a6 2d 00 00 lea 0x2da6(%rip),%rsi # 4048 <pass> 12a2: 48 89 c7 mov %rax,%rdi 12a5: e8 04 ff ff ff callq 11ae <compare> 12aa: 85 c0 test %eax,%eax 12ac: 75 22 jne 12d0 <main+0xbe> 12ae: 48 8b 05 ab 2d 00 00 mov 0x2dab(%rip),%rax # 4060 <stdout@@GLIBC_2.2.5> 12b5: 48 89 c1 mov %rax,%rcx 12b8: ba 33 00 00 00 mov$0x33,%edx
12bd:	be 01 00 00 00          mov    $0x1,%esi 12c2: 48 8d 3d a7 0d 00 00 lea 0xda7(%rip),%rdi # 2070 <_IO_stdin_used+0x70> 12c9: e8 92 fd ff ff callq 1060 <fwrite@plt> 12ce: eb 20 jmp 12f0 <main+0xde> 12d0: 48 8b 05 89 2d 00 00 mov 0x2d89(%rip),%rax # 4060 <stdout@@GLIBC_2.2.5> 12d7: 48 89 c1 mov %rax,%rcx 12da: ba 0a 00 00 00 mov$0xa,%edx
12df:	be 01 00 00 00          mov    $0x1,%esi 12e4: 48 8d 3d b9 0d 00 00 lea 0xdb9(%rip),%rdi # 20a4 <_IO_stdin_used+0xa4> 12eb: e8 70 fd ff ff callq 1060 <fwrite@plt> 12f0: b8 00 00 00 00 mov$0x0,%eax
12f5:	c9                      leaveq
12f6:	c3                      retq
12f7:	66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
12fe:	00 00
(...)

The most interesting bit of this challenge is that it start calling ptrace in :1233 to detect if there’s any debugger attached. If there is, it directly ends the execution. It’s not really a problem, of course, because we can simply modify $eip or$eflags and we can pretend there was no anti-debugging taking place. It’s just something we have to be aware of, nothing more.

After checking if it’s being executing directly or being debugged, it prints a string and waits for the user input (en :1286). Then it calls the xor function with our string and compares the result with something that’s in memory.

It seems to be as easy as using gdb, jumping over the ptrace check and looking at the value returned by xor.

Oversimplifying it, this is what this function does:

void xor (char * str_in)
{
int i;
char a, b;

for (i = 0; i <= 8; i++)
{
a = str_in [i]
b = i + 0x69	// i + 105 I guess that this value is as good as any other... XD

str_in [i] = a ^ b
}
}

The answer, then, is to see the contents of the string being compared with the counter (whose values we already know: 0x69, 0x61, 0x6b…). The obfuscated string is in the data, so we can see its value:

$objdump -s -j .data crackme2 crackme2: file format elf64-x86-64 Contents of section .data: 4038 00000000 00000000 40400000 00000000 ........@@...... 4048 3a060a1c 0e060000 500000 :.......P.. Now it’s just a matter of calculating the XOR of that value with the ounter. For instance, we can do it with just a couple of lines in Python: # Python 3.7.2 (...) # [...] on linux # Type "help", "copyright", "credits" or "license" for more information. >>> a = '\x3a\x06\x0a\x1c\x0e\x06\x00\x00\x50' >>> b = [ chr (i) for i in range (0x69, 0x69 + 9, 1) ] >>> b ['i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q'] >>> "".join ([ chr ( ord (a [i]) ^ ord (b [i]) ) for i in range (8) ]) 'Slapchop' >>> "".join ([ chr ( ord (a [i]) ^ ord (b [i]) ) for i in range (9) ]) 'Slapchop!' >>> Then we just have to check that it’s indeed the correct value and see what the program returns (the output is translated from Spanish):$ ./crackme2
This is going to Fascinate you!! Give me the password: Slapchop!