Sunday, November 19, 2017

HXP CTF 2017 - "revenge_of_the_zwiebel" Reversing 100 Writeup

revenge_of_the_zwiebel - 100 pts + 4 bonus pts ( 31 solves ):

After executing the binary it prints : "Input Key:" and waits for us to enter the flag. The routine printing the "Input Key:" message is executed at initialization alongside a sub-routine implementing the ptrace anti-debugging trick. Since we're going to debug the binary, I patched the anti-debugging sub-routine's address with nullsub_1.


After setting up remote debugging under IDA and supplying some random input to the binary we see a call to some code that was stored in executable memory.
 

IDA sometimes has trouble displaying memory under its debugger, so let's setup a manual memory region and set it as an executable 64-bit segment.

Now we should be able to view the entirety of the bytes copied to memory.
In the figure below, the code that is executed starts by checking if the 4th bit of input[0xC] is set. If it's not set, the message ":(" is printed and the process exits.


However, if the bit is set the code proceeds to decrypt the next block and also XOR the subsequent blocks that are still encrypted. (see figure below)


There's also a second test, implemented in some blocks, which involves the NOT instruction (figure below). This means that the 3rd bit of input[0x11] must not be set.

The amount of code executed is huge and doing this manually is impossible. So, we have two options : 

1.  Either dump the encrypted data, decrypt it statically and then build the flag by automatically reading the disassembly.
2.  Automate the IDA debugger to save the index plus the bits that must be set and guarantee that everything will be executed by continually patching ECX in order not to take the JECXZ jump.

Even if the 2nd attempt would take longer to complete, I chose to implement it. If I ever do the first one, I'll be sharing it here too :)

So, what the script below does is create a dictionary where the key is the character's position and the value is an array of bits that must be set. I simply ignore the NOT case, since we only care about the bits that must be set.

For example if the character at index 2 needs to have bits : 0, 4 and 7 set, the dictionary entry would look like this : {2: [1, 16, 80]}
After the process terminates, we proceed to OR the numbers of each array which gives us the flag in the end.

Here's the script that must be executed under IDA to get the flag.
Script runtime : approx. 15 minutes
Full script : here

# HXP CTF 2017 - revenge_of_the_zwiebel 100 pts
# Writeup link : https://rce4fun.blogspot.com/2017/11/hxp-ctf-2017-revengeofthezwiebel.html
# Souhail Hammou
from idc import *
from idaapi import *
def AddIfNotInDict(dict,index):
if index == -1:
raise Exception("Invalid index value !")
if index not in dict:
dict[index] = []
bin_dict = {}
RunTo(BeginEA())
GetDebuggerEvent(WFNE_SUSP,-1)
RunTo(0x4006A3) # CALL ECX
GetDebuggerEvent(WFNE_SUSP,-1)
StepInto()
GetDebuggerEvent(WFNE_SUSP,-1)
block_active = 0
is_not = 0
index = -1
try :
while True:
#read the current instruction
inst = GetDisasm(GetRegValue("RIP"))
if "mov cl, [rax+" in inst:
block_active = 1
try :
index = int(inst.split("+")[1].split("]")[0].split('h')[0],16)
except IndexError:
index = 0
AddIfNotInDict(bin_dict,index)
if block_active == 1:
if "not" in inst:
#NOT is executed before the AND instruction
#The bit is not set in the byte, so no need to save it
is_not = 1
elif "and" in inst and is_not == 0:
#we need to save the bit that must be set
bit = int(inst.split(",")[1].split(" ")[1].split("h")[0],16)
#The index was set previously at the MOV CL, [RAX+X] instruction
bin_dict[index].append(bit)
elif "jecxz" in inst :
#we reset our variables when we reach the JXCZ instruction
SetRegValue(1,"RCX") #do not branch
is_not = 0
block_active = 0
StepOver() #Next instruction
GetDebuggerEvent(WFNE_SUSP,-1)
except:
#process terminated
sweet_flag = ''
for index,bits in bin_dict.iteritems():
c = 0
for bit in bits:
c |= bit
sweet_flag += chr(c)
print "FLAG IS : " + sweet_flag
# FLAG IS : hxp{1_5m3ll_l4zyn355}
view raw ida_solve.py hosted with ❤ by GitHub
Follow me on Twitter : here

HXP CTF 2017 - "dont_panic" Reversing 100 Writeup

dont_panic - 100 pts + 15 bonus pts ( 19 solves ):
The file is a GO binary. After locating the main function, by stepping in the debugger, I found that the binary expects a command line argument of 42 characters.
For each character it calls a sub-routine sub_47B760 that returns a calculated byte corresponding to the one supplied. This byte is then compared to the one it corresponds to in a hardcoded array of bytes, which clearly represents the encrypted flag.

I didn't really look into how the values are calculated since GO binaries tend to be messy and I didn't really have to do it in order to solve the challenge. The thing is, the program branches to the block that displays the fail message ("Nope") as soon as it finds that one character is wrong. This opens room for brute-forcing since we can brute-force the flag character by character dynamically.

I used python with GDB to do so. Here's the solution script :
full script : here

#HXP CTF 2017 - dont_panic 100 pts
#Writeup link : https://rce4fun.blogspot.com/2017/11/hxp-ctf-2017-dontpanic-reversing-100.html
#Souhail Hammou
import gdb
CHAR_SUCCESS = 0x47B976
NOPE = 0x47BA23
gdb.execute("set pagination off")
gdb.execute("b*0x47B976") #Success for a given character
gdb.execute("b*0x47BA23") #Block displaying "Nope"
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-+*{}'"
flag = list('A'*42) #junk
for i in range(0,len(flag)) :
for c in charset:
flag[i] = c
# the number of times we need to hit the
# success bp for the previous correct characters
success_hits = i
gdb.execute("r " + '"' + "".join(flag) + '"')
while success_hits > 0 :
gdb.execute('c')
success_hits -= 1
#we break either on success or on fail
rip = int(gdb.parse_and_eval("$rip"))
if rip == CHAR_SUCCESS:
break #right one. To the next character
if rip == NOPE: #added for clarity
continue
print("".join(flag))
#flag : hxp{k3eP_C4lM_AnD_D0n't_P4n1c__G0_i5_S4F3}
view raw go_solve.py hosted with ❤ by GitHub

After a minute or so, we get the flag.

HXP CTF 2017 - "Fibonacci" Reversing 100 Writeup

Fibonacci - 100 pts + 6 bonus pts ( 45 solves ):
This binary is supposed to print the flag directly into the screen. However, it will take a very very long time to print the whole flag since the output is based on the calculation of fibonacci numbers recursively.
For each bit of the encoded flag (length = 33 stored at 00000000004007E0), the fibonacci number of that bit's position is calculated : this means that it will calculate fibonacci values for numbers from 0 to 263.
This is not all. Since the flag needs to be decoded, each call to the fibonacci sub-routine expects a pointer to a bit value which is XORed with a calculated bit from the resulting fibonacci number. Keep in mind that the fibonacci implementation is recursive, and thus we expects this boolean value to be XORed multiple times for greater numbers.
When the fibonacci sub-routine returns to the main function, the corresponding bit of the encrypted flag is XORed with the calculated bit value.

The solution that came in mind is to modify the fibonacci implementation so as to save both the calculated bit value and the resulting fibonacci number for a given number. So instead of recursing and re-calculating the fibonacci number of a previously calculated one (in a previous call for a previous bit of the flag), we simply load the result of the calculation and XOR the current output bit with the one we already saved.

The solution is implemented in the script below. All modifications done to the original function are commented.
Full script : here

/*
Souhail Hammou
HXP CTF 2017 - Fibonacci 100 pts
Writeup : https://rce4fun.blogspot.com/2017/11/hxp-ctf-2017-fibonacci-reversing-100.html
*/
#include <stdio.h>
#include <stdlib.h>
#define _BYTE unsigned char
#define BYTEn(x, n) (*((_BYTE*)&(x)+n))
#define BYTE1(x) BYTEn(x, 1)
unsigned int saved_res_for_v[264][2];
unsigned int v_index;
unsigned int fibonacci_solve(int number, _BYTE *pbool);
int main()
{
unsigned int i,j;
unsigned char barray[] = {0x49,0x7E,0x07,0xE2,0x9C,0xCC,0xC9,0x2B,0xFC,0x34,0x04,0x6F,0x4E,0x12,0x00,0x50,0xA9,0x02,0x3A,0xBA,0xC2,0x8E,0x41,0x99,0x98,0xF5,0x8D,0x51,0x4D,0xA6,0xC6,0x43,0x12};
unsigned char flag_char;
unsigned int counter = 0; //RBX
for ( i = 0; i < sizeof(barray) ; i++)
{
flag_char = barray[i];
for ( j = 0 ; j < 8 ; j++)
{
_BYTE b = 0;
unsigned int fibo = fibonacci_solve(counter+j,&b);
//save data for this value
saved_res_for_v[counter+j][0] = b; //save bit
saved_res_for_v[counter+j][1] = fibo; //save fibonacci result
flag_char ^= b << j;
}
counter += 8;
printf("%c",flag_char);
}
}
unsigned int fibonacci_solve(int number, _BYTE *pbool)
{
_BYTE *v2;
unsigned int v3;
unsigned int result;
unsigned int v5;
unsigned int v6;
unsigned int v7;
unsigned int xor_res;
v2 = pbool;
if ( number )
{
if ( number == 1 )
{
result = fibonacci_solve(0, pbool);
v5 = result - ((result >> 1) & 0x55555555);
v6 = ((result - ((result >> 1) & 0x55555555)) >> 2) & 0x33333333;
}
else
{
//if (number - 2) is saved
if ( saved_res_for_v[number-2][1] != 0 )
{
//update b to reflect the value
*pbool ^= saved_res_for_v[number-2][0];
//don't recalculate, load the fibo value
v3 = saved_res_for_v[number-2][1];
}
else
{
//calculate the value normally
v3 = fibonacci_solve(number - 2, pbool);
}
//if number-1 is saved
if ( saved_res_for_v[number-1][1] != 0 )
{
//update b to reflect the value
*pbool ^= saved_res_for_v[number-1][0];
//don't recalculate, load the fibo value
result = v3 + saved_res_for_v[number-1][1];
}
else
{
//calculate the value normally
result = v3 + fibonacci_solve(number - 1, pbool);
}
v5 = result - ((result >> 1) & 0x55555555);
v6 = ((result - ((result >> 1) & 0x55555555)) >> 2) & 0x33333333;
}
v7 = v6 + (v5 & 0x33333333) + ((v6 + (v5 & 0x33333333)) >> 4);
*v2 ^= ((BYTE1(v7) & 0xF) + (v7 & 0xF) + (unsigned __int8)((((v7 >> 8) & 0xF0F0F) + (v7 & 0xF0F0F0F)) >> 16)) & 1;
}
else
{
*pbool ^= 1u;
result = 1;
}
return result;
}
view raw solve.c hosted with ❤ by GitHub

We immediately get the flag when we run the program.