CSCG - Writeup for the ‘Intro to Reversing’ challenges
This post is a write-up for the three intro reversing challenges for the CSCG 2020. It should give you an idea of how to approach such challenges. Since those challenges were especially made for beginners, feel free to try them out yourself, before reading through this write-up.
First, I want to give a bit of background information on the CSCG:
The CSCG (Cyber Security Challenge Germany) is a CTF Challenge especially targetting german students and young professionals. But the CTF is open for everyone. You can read more about it on the official challenge website.
Introduction
Let’s see what kind of challenges we are dealing with in this case.
This is a introductory challenge for beginners which are eager to learn reverse engineering of linux binaries. The three stages of this challenge will increase in difficulty. But for a gentle introduction, we have you covered: Check out the video of LiveOverflow or follow the authors step by step guide to solve the first part of the challenge.
So, as I said in the first paragraph, it’s a set of rather easy challenges. The creators even provide us with walkthroughs/step-by-step guides. It should help newcomers get a feel for how CTFs work in general. Let’s take a look at the challenges.
Intro to Reversing 1
Information about the challenge
Difficulty: Baby
Description: Once you solved the challenge locally, grab your real flag at: nc hax1.allesctf.net 9600
Challenge files: intro_rev1.zip
Local Analysis
After downloading and extracting the challenge files it’s always a good idea to check the files you are dealing with.
rico@Anonymous:~/rev/rev1$ tree
.
├── flag
└── rev1
0 directories, 2 files
We can inspect the file types with the file
command.
rico@Anonymous:~/rev/rev1$ file flag
flag: ASCII text
rico@Anonymous:~/rev/rev1$ file rev1
rev1: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=c26549fbcc84a4199635818d97bd48b69eea5fb2, not stripped
Great, we now know that we are dealing with an ELF binary. The flag file contains a dummy flag (CSCG{real_flag_is_on_the_server}
). Let’s try to see what happens, when we execute the binary.
rico@Anonymous:~/rev/rev1$ ./rev1
Give me your password:
asd
Thats not the password!
Okay, so the binary asks for a password, that we obviously don’t know (yet). Since this is a challenge with the difficulty of “Baby”, we can assume that we don’t need deep reversing experiences yet. Those challenges mostly consist of reading a password from the binary itself.
To check a binary for ascii strings, there is a tool named strings
.
rico@Anonymous:~/rev/rev1$ strings rev1
[...]
[]A\A]A^A_
Give me your password:
y0u_5h3ll_p455
Thats the right password!
Flag: %s
Thats not the password!
./flag
flag
File "%s" not found. If this happens on remote, report to an admin. Exiting...
[...]
There we can find the strings we have just seen. And right in between we find a strange string: y0u_5h3ll_p455
. This looks a lot like a password. So without further ado, let’s just try that password.
rico@Anonymous:~/rev/rev1$ ./rev1
Give me your password:
y0u_5h3ll_p455
Thats the right password!
Flag: CSCG{real_flag_is_on_the_server}
YAY 🎉! The password is correct and the binary prints out the content of the flag file.
Getting the flag from the server
If the real flag were contained in the flag file, it would be waaay too easy, because you could simply read that file. So we now need to enter the password on the server, and it will print the correct flag. To do that, we use nc to connect to the remote server.
rico@Anonymous:~/rev/rev1$ nc hax1.allesctf.net 9600
Give me your password:
y0u_5h3ll_p455
Thats the right password!
Flag: CSCG{ez_pz_reversing_squ33zy}
There, we got the flag. We can enter it on the challenge website now.
Intro to Reversing 2
Information about the challenge
Difficulty: Baby
Description: Once you solved the challenge locally, grab your real flag at: nc hax1.allesctf.net 9601
Challenge files: intro_rev2.zip
Local Analysis
We’re doing the exact same stuff as in part 1. Checking downloaded files, we found out that it’s still an ELF file, and we’re again trying to find hard-coded passwords in the binary with the strings
tool.
rico@Anonymous:~/rev/rev2$ strings rev2
[...]
[]A\A]A^A_
Give me your password:
Thats the right password!
Flag: %s
Thats not the password!
./flag
flag
File "%s" not found. If this happens on remote, report to an admin. Exiting...
[...]
Hmm, now the password is not hard-coded into the binary anymore. Makes sense, the second challenge should be somewhat different from the first one, right? This is where we actually start using reversing tools. I chose to use IDA for this task, but there is also Ghidra, which does an awesome job at reversing as well. Both will get you to your goal.
Alright, so we load the binary into the disassembler and find the main function. Inside the main function, we’ll find a loop. I already renamed the variables to improve readability.
On the right side, we can see that the input we enter is being altered. Each input byte is decreased by 0x77. The result is stored back into memory. Eventually, that altered data is being compared (_strcmp
) with a string (or rather data) stored at “unk_AB0”. That data is stored in the .rodata
segment of the binary at the offset 0xAB0. Hence, the name “unk_AB0”, that IDA gave that memory address.
So that means that someone took the password beforehand, subtracted 0x77 from each byte, and saved that as a variable in their code. This is somewhat like the Caesar cipher, only that it also generates unprintable characters. To revert this, we need to check the data stored at offset 0xAB0 and add 0x77 to each byte.
That leads us to the following string:
0xFC 0xFD 0xEA 0xC0 0xBA 0xEC 0xE8 0xFD 0xFB 0xBD 0xF7 0xBE 0xEF 0xB9 0xFB 0xF6 0xBD 0xC0 0xBA 0xB9 0xF7 0xE8 0xF2 0xFD 0xE8 0xF2 0xFC 0x00
Now let’s create a small Python script to manipulate the raw data and turn it back into a readable string. Mind that just adding 0x77 to some values will leave us with values > 1 byte (e.g., 0xE8 + 0x77 = 0x15F). So we need to make sure to ignore all bits that exceed one byte (=> 0x5F). We can achieve that by AND-ing the result with 0xFF. Also, remember to remove the 0-byte. It only indicates the end of the string.
arr = [0xFC, 0xFD, 0xEA, 0xC0, 0xBA, 0xEC, 0xE8, 0xFD, 0xFB, 0xBD, 0xF7, 0xBE, 0xEF, 0xB9, 0xFB, 0xF6, 0xBD, 0xC0, 0xBA, 0xB9, 0xF7, 0xE8, 0xF2, 0xFD, 0xE8, 0xF2, 0xFC]
output = []
for b in arr:
new_value = (b + 0x77) & 0xFF # Ignore bits > 1 byte
output.append(new_value)
print("Hex: " + " ".join(hex(n) for n in output))
print("Ascii: " + "".join(unichr(n) for n in output))
The script will now add 0x77 to each value, extract only the 8th lowest bit so that we are left with a single byte, and then append the result to another array. We use that array to print the raw hex and the ASCII representation of the data.
Hex: 0x73 0x74 0x61 0x37 0x31 0x63 0x5f 0x74 0x72 0x34 0x6e 0x35 0x66 0x30 0x72 0x6d 0x34 0x37 0x31 0x30 0x6e 0x5f 0x69 0x74 0x5f 0x69 0x73
Ascii: sta71c_tr4n5f0rm4710n_it_is
That leaves us with the password: sta71c_tr4n5f0rm4710n_it_is
Getting the flag from the server
We’re doing the exact same thing as before: connect to the CTF server via nc and enter the password. It will print our flag.
rico@Anonymous:~/rev/rev2$ nc hax1.allesctf.net 9601
Give me your password:
sta71c_tr4n5f0rm4710n_it_is
Thats the right password!
Flag: CSCG{1s_th4t_wh4t_they_c4ll_on3way_transf0rmati0n?}
Nice. Another flag!
Intro to Reversing 3
Let’s check the final intro challenge. I kept this one a bit shorter because the general procedure should be known by now.
Information about the challenge
Difficulty: Baby
Description: Once you solved the challenge locally, grab your real flag at: nc hax1.allesctf.net 9602
Challenge files: intro_rev3.zip
Local Analysis
After checking the file type and strings again, we load the binary into our disassembler (again). In our main function, we again find a loop. In that loop, we now do a bit more than in the second challenge.
It’s some sort of algorithm. But what does that algorithm do? We can see that we load one char of our input into edx. Then we load the loop_counter into eax. We then add 0x0A (10) to the loop counter and xor that new value with the input char. Finally, we subtract 2 from the result. In C, this could look like this:
for ( loop_counter = 0; loop_counter < input_len - 1; ++loop_counter )
{
input_buf[loop_counter] ^= loop_counter + 10;
input_buf[loop_counter] -= 2;
}
The stored input in the binary looks like that:
0x6C 0x70 0x60 0x37 0x61 0x3C 0x71 0x4C 0x77 0x1E 0x6B 0x48 0x6F 0x70 0x74 0x28 0x66 0x2D 0x66 0x2A 0x2C 0x6F 0x7D 0x56 0x0F 0x15 0x4A 0x00
Now we somehow need to reverse that algorithm. And the best way to do that is to recreate it in a language you prefer, but in reverse order. We start from the stored byte and add 2, to undo the subtraction. To undo the xor part, we xor the result with the loop counter (+10). Very simple.
arr = [0x6C, 0x70, 0x60, 0x37, 0x61, 0x3C, 0x71, 0x4C, 0x77, 0x1E, 0x6B, 0x48, 0x6F, 0x70, 0x74, 0x28, 0x66, 0x2D, 0x66, 0x2A, 0x2C, 0x6F, 0x7D, 0x56, 0x0F, 0x15, 0x4A]
output = []
for counter, b in enumerate(arr):
new_value = ((counter + 0x0A) ^ (b + 2)) & 0xFF # Ignore bits > 1 byte
output.append(new_value)
print("Hex: " + " ".join(hex(n) for n in output))
print("Ascii: " + "".join(unichr(n) for n in output))
Executing our script will again give us our password.
Hex: 0x64 0x79 0x6e 0x34 0x6d 0x31 0x63 0x5f 0x6b 0x33 0x79 0x5f 0x67 0x65 0x6e 0x33 0x72 0x34 0x74 0x31 0x30 0x6e 0x5f 0x79 0x33 0x34 0x68
Ascii: dyn4m1c_k3y_gen3r4t10n_y34h
Getting the flag from the server
Same game as before.
rico@Anonymous:~/rev/rev3$ nc hax1.allesctf.net 9602
Give me your password:
dyn4m1c_k3y_gen3r4t10n_y34h
Thats the right password!
Flag: CSCG{pass_1_g3ts_a_x0r_p4ss_2_g3ts_a_x0r_EVERYBODY_GETS_A_X0R}
Lovely! We solved all three intro challenges. I am happy that you kept reading up until this point. If you are interested, you might want to play some CTFs as well now. A good starting point is ctftime.