Table of Contents
This article is about writeups of Ramada and Recklinghausen. These challenges are categorized in the Reverse Engineering category and the difficulty is Easy. The same person created these challenges. Ramada is a 10-point challenge and Recklinghausen is a 20-point challenge. Therefore Recklinghausen is more complicated than Ramada as described in the description of Recklinghausen.
This is the 4th in a series of Beginner Reversing Challenges. If you are new to Reversing, you may want to solve Reykjavik, then Riyadh then Rangoon before solving this challenge. This is a 20 point challenge and is different in two ways from the previous 10 point challenges.
Ramada Writesup
This program requires the flag as an argument. The flag has to be CTFlearn{kernel}. I need to find the proper kernel value.
└─[0] <> ./RamadaWelcome to the CTFLearn Ramada Reversing Challenge! pid : 30858 0x0000788a ppid: 30361 0x00007699Usage: Ramada CTFlearn{kernel}
└─[1] <> ./Ramada CTFlearn{xxxxx}Welcome to the CTFLearn Ramada Reversing Challenge! pid : 31331 0x00007a63 ppid: 30361 0x00007699Your flag is the wrong length dude!static analysis with r2
I start doing a static analysis with r2 to see a function graph.
└─[0] <> r2 Ramada[0x00001280]> aaa[x] Analyze all flags starting with sym. and entry0 (aa)[x] Analyze function calls (aac)...
# list all functions[0x00001280]> afl...
[0x00001280]> s main[0x00001100]> VVFlag format validation
At first, the program checks if the input starts with CTFlearn.
mov ecx, 9lea rsi, str.CTFlearnmov rdi, rbprepe cmpsb byte [rsi], byte ptr [rdi]seta r12bsbb r12b, 0movsx r12d, r12btest r12d, r12djne 0x125dThen, it also checks the input ends with }.
mov rdi, rbpcall sym.imp.strlencmp byte [rbp + rax - 1], 0x7d ; 0x7d = '}'jne 0x121fFlag size
Second, the program checks the input size. The flag size must be 0x1f, which means 31.
cmp rax, 0x1fjne 0x120bFlag value validation
After that, the program calls the CheckFlag function which checks the flag value. I visualize the CheckFlag function.
[0x00001280]> s sym.CheckFlag_char_const_[0x00001370]> VVThere is a loop that checks each character of the flag. Based on the code below, I get this statement.
flag**3 = dword [$rsi+$rax*4]
movsx ecx, byte [rdi + rax] ; each char of the flag(v) in the loopmov edx, ecx ; edx = vimul edx, ecx ; edx *= vimul edx, ecx ; edx *= vcmp dword [rsi + rax*4], edxje 0x1380
As the result of the static analysis, I get the following these two things.
- The flag size is
31 - The flag value in the loop is
flag**3 = dword [$rsi+$rax*4]
exploit with gdb
Now I exploit the flag dynamically with a Python script and GDB. Here is the Python script.
import gdbimport math# setting a breakpoint where the program checks the flag value.gdb.execute('break *0x555555555396')FLAG_SIZE = 21dummy_flag = 'x' * FLAG_SIZEgdb.execute('run CTFlearn{{{}}}'.format(dummy_flag))
data = []
ans = "CTFlearn{"
def find_cube_root(v): return math.ceil(math.pow(v, 1/3))
for i in range(21): offset = i * 4 data = gdb.execute('x/xw $rsi+{}'.format(offset), to_string=True) v = data.replace("\n", "").split('\t')[1] c = chr(find_cube_root(int(v, 0))) ans += c
ans += "}"print(ans)I get the flag by running the script with GDB.
gdb Ramada -x ./exploit.pyRecklinghausen Writesup
This program requires the flag as an argument. The flag has to be CTFlearn{flag}. I need to find the proper flag value.
└─[0] <> ./RecklinghausenWelcome to the Recklinghausen Reversing Challenge!Compile Options: ${CMAKE_CXX_FLAGS} -O0 -fno-stack-protector -mno-sseUsage: Recklinghausen CTFlearn{flag}
└─[1] <> ./Recklinghausen CTFlearn{test}Welcome to the Recklinghausen Reversing Challenge!Compile Options: ${CMAKE_CXX_FLAGS} -O0 -fno-stack-protector -mno-sseSORRY You did not find the flag :-( : CTFlearn{test}static analysis with r2
I start doing a static analysis with r2 to see a function graph.
└─[0] <> r2 Recklinghausen[0x000012b0]> aaa[x] Analyze all flags starting with sym. and entry0 (aa)[x] Analyze function calls (aac)...
[0x000012b0]> afl...0x000010c0 14 494 main
[0x000012b0]> s main[0x000010c0]> VV
In the beginning, there is a time limit during checking whether the input has a flag or not. The program exits if it takes more than 2 seconds during this input checking. It’s probably for the case of using Debuggers.
call sym get_wall_time()...lea rdi, obj.buffermov rbp, rax ; store the time into $rbp...call sym get_wall_time()sub rax, rbpcmp rax, 2jg 0x1217 ; must be less than 2After that, the program calls the CheckMsg function with the input flag. This function returns a boolean, which seems to check the flag value. So I visualized this function.
mov rdi, r12call sym CheckMsg(char const*)test al, alje 0x11e2[0x00002390]> s sym.CheckMsg_char_const_[0x00002390]> VVFlag size
The CheckMsg checks the size of the flag compared with the size of obj.msg5 variable at first.
call sym.imp.strlenmovzx edx, byte [obj.msg5]mov r8, raxxor eax, eaxcmp rdx, r8jne 0x23e5Flag value validation
After the size check, there is a loop. The below part in the loop checks the flag value.
movzx edx, byte [rbx + rax] ; copy char of the flag into edxxor edx, esicmp byte [rdi + rax], dl ; byte [rdi + rax] == edx XOR esije 0x23d0
It checks whether the result of char of the flag XOR $rsi is equal to the byte value of $rdi+$rax*1, which means that each char of the flag must be equal to the result of $rsi XOR the byte value of $rdi+$rax . This logic can be described as the statement below.
char of the flag ^ $rsi = [$rdi+$rax*1] => char of the flag = [$rdi+$rax*1] ^ $rsiAs the result of the static analysis, I got the following two things.
- There is a size check of the flag
- There is a loop to check the flag
dynamic analysis with GDB
Based on the result of the static analysis, I want to check the actual values and get the flag.
- The flag size
- The actual flag that is checked during the loop
1. The flag size
In order to check the flag dynamically, I set a breakpoint where it checks the flag size.
gdb-peda$ break *0x00005555555563a9gdb-peda$ run CTFlearn{xxxx}When the program comes to the breakpoint, the rdx register has a value, 0x21, which is 33 as a digit. So the flag size is 33.
gdb-peda$ i register rdxrdx 0x21 0x212. The actual flag that is checked during the loop
In order to check the actual value, I set a breakpoint where it checks the value of the flag, then executes the program with 33 characters.
gdb-peda$ break *0x5555555563e1gdb-peda$ run CTFlearn{xxxxxxxxxxxxxxxxxxxxxxx}When the program comes to the break point for the first time, I check whether the logic below is correct.
char of the flag ^ $rsi = [$rdi+$rax*1] => char of the flag = [$rdi+$rax*1] ^ $rsiThe values of the corresponding registers are below.
gdb-peda$ i register $rsirsi 0x7e 0x7egdb-peda$ x/x $rdi+$rax0x5555555590e2 <msg5+2>: 0x3dWith these values above, I calculate the first character of the flag, which has to be C.
The result of the calculation is ‘C’, so the logic seems to be correct.
└─[0] <> printf '\x0x%X' $(( 0x7e ^ 0x3d )) | xxd -r -pCI also checked next some flags by continuing the debug and confirmed the flag starts with CTFlearn{.
Now it’s time to exploit it with a script.
exploit with gdb
Here is the script to get the flag. In order to continue the program until I gets the whole flag, I set the ZF flag manually.
import gdb
gdb.execute('break *0x5555555563e1')FLAG_SIZE = 33dummy_flag = 'x' * FLAG_SIZEgdb.execute('run {}'.format(dummy_flag))
flag = ''for i in range(FLAG_SIZE): # get a correct char rsi = gdb.parse_and_eval('$rsi') data = gdb.execute('x/x $rdi+$rax*1', to_string=True) v = data.split(":")[1] # flag ^ $rsi = $v => flag = $rsi ^ $v flag += chr(rsi ^ int(v, 16))
# set ZF flag eflags_raw = gdb.execute('i r $eflags', to_string=True) eflags = eflags_raw.split()[1] eflags = hex(int(eflags, 16) | (1 << 6)).strip("L") gdb.execute("set $eflags = {}".format(eflags), to_string=True)
gdb.execute('continue')
print(flag)I get the flag by running the script with GDB.
gdb Recklinghausen -x ./exploit.py