Reverse Engineering Writeup: ASCWG Finals 2020

Arab Security Cyber Wargames Finals 2020 was held on September 12th in Nile Ritz Cairo.

group

Photo Credit: Arab Security Conference.

This time I was the author of three of the five challenges presented in the finals. Here are the writeups for them.

Challenge 1 - EZ

Challenge Difficulty: Easy

Challenge Link: Drive

We are presented with a Linux 64-bit stripped executable called ez.

file

Running the file prints nothing useful. Let’s take a look at in IDA.

ida

The first few instructions look like this. The values being moved into rax, rdx, and the places after them are interesting we can get back to them later. but for now there seems to be a huge string being loaded into rdx. let’s take a look at it using strings to get it in clean ASCII.

strings

It looks like some obfuscated shell code or something of that sort. Especially that we can see a lot of $ signs, pipes, and the command “sed”. Let’s try and echo that from BASH to try and de-obfuscate it and see what it will produce.

deobf

It seems like it is printing something to /tmp/f, let’s try and cat it.

cat

Well there we go 😃 no actual reversing needed.

Flag: ASCWG{b45h_0bfu5c4710n}

Bonus: Getting the Flag from the Binary itself

Remember the bytes being moved into memory at the beginning of the program? That’s the XORd flag. We can take the bytes to a python shell and do the magic there.

xor

python

Challenge 2 - easy-101

Challenge Difficulty: Easy

Challenge Link: Drive

This challenge was written by Eng. Mohammad Khreesha, not me.

We are presented with a Linux 64-bit executable called easy-101.

file

Running it tells us to “Try harder!!”, okay then.

Try Harder

After spamming a couple of different arguments, it kept printing “drink water!!”. the output in ltrace and strace didn’t seem to differ too. So let’s open it up in IDA.

drink water

On opening the binary in IDA, we can see that all it does is validate that you have some form of input that has 3 dashes ‘-‘ and then continues to the same block with the big arrays every time.

ida graph

In that same block, we can find that it starts adding the flag format.

flag format ida

We can set a breakpoint at the part when the closing brace is added.

closing brace

After that, we can click on var_21 to see the flag.

flag

We could also reverse engineer the parts between “jmp loc_A3A” and the var_21 line to see how it gets the flag from the big arrays and then write code for that using Python or anything else.

While this was what I originally did, it turned out to be way too much effort for a really easy challenge.

BigArray[0] = "AFLdfgArTkdwelkfkmkasFFEWSWasCFsfsdfewRRWDSCFAWDHg";
BigArray[1] = "sddgrlksjpothjafkgpOEWFKQW0EOG9AWRI90VA8RUGWE9R8BIhDJFOIBSDF";
BigArray[2] = "SDSL;FKOKEIER0T9W85690843GJDKVJASFQLIGUQWO;ECLDskvjar;lgksjg1dflb";
BigArray[3] = "kjhKKHJLKlkjhlksriufer9erudskjhLKHJW3EROIWUEOIUkjkhsdllkejgrgpdffSljk";
BigArray[4] = "sdrdgkj4598fgjkksdffvggvljkt66yhpooijjKJWEROJISDOVIHEERGNoqiqwjedqwkd_jaasfefn";
BigArray[5] = "wewro3k34j4r39vjwewlkrfjwefoiasjALDLADSLFKWJEFWk23eqwqfweoryirut0h9dfbjlkwelkw1kj3e3f";
BigArray[6] = "xdlkjefwi9eru8239r2efjkldsvjlwk4g439berflklkjwff1982eiuqukKJSDFKJWEEF0OSDVJLKSKSLKJEFSJW0";
BigArray[7] = "098765432QWERTYUIOPASDFGHJKLZXCVBNM,W453948573498EWIFUERRGKHJFGBKDJGNBDDGksljgerogijdflkb_ckdfbj";
BigArray[8] = "dflfkgdjflgkjdflgsktjhworepgqajrlvLKLSADDKGJWREOGSJIFDLBKXDFGJBLKEKRTHHJOPSFGBJLSKFDGBJSLsdlfkdf4ldkfgj";
BigArray[9] = "DSLDFKGJWREGPOIWERJBSDFLKJBDFGLBKJklsdjff3948tueg98idduvvoierrguj0eer9gjgodfbbojiereoigsdfkgjdfdfdfdfdfNdb";
BigArray[10] = "dglkjerge9r0gwe9fwfoiuiSKDFJFWER9GDEORIJGDFOIBJRTOIBJDFOBIKJER90GGU3U3EGF09USEDGOISDFJGDOetewrtwertwfwFIJB_DF";
'''
.
. Cut for brevity
.
'''
BigArray[25] = "flag dfsd dsf sdf "
flag = ['A', 'S', 'C', 'W', 'G', '{']

for i in range(50):
    flag.append('.')

inputLength=6
indexing = 0;

while inputLength < 0x1f:
    if indexing == 0:
        flag[inputLength] = BigArray[0][8];
    else:
        strlen = len(BigArray[indexing-1])
        print(str(indexing) +  " " + str(strlen))
        flag[inputLength] = BigArray[indexing][strlen];

    inputLength = inputLength + 1
    indexing = indexing + 1

    for a in flag[0:31]:
        print(a, end='')
    print("}")


This gets us the same output:

final flag

Flag: ASCWG{Th1S_1S_4N_e4Sy_R3v3Rs1Ng}

Challenge 3 - mov

Challenge Difficulty: Medium

Challenge Link: Drive

We are presented with a Linux 32-bit executable called mov. Running it doesn’t say anything useful except that it is “moving” something.

mov run

Opening the program in IDA, intuitively shows us that it consists of mov instructions only. That would be hell to debug.

mov ida

r0-1

r0-2

r0-3

If we look around the binary we can see a lot of values in the ASCII range being moved into R0.

We can also open it in Ghidra’s decompiler and see something very similar but easier to understand.

ghidra

If we extract all these values in order, we get the following string.

uanfsf_SConoAmibv}iW_s{uct_o!G

Knowing that out flag format is ASCWG{} and that usually in CTF flags words are separated by _ or -, we can reorder the character to end up with the flag

Flag: ASCWG{mov_obfuscation_is_fun!}

Challenge 4 - Hydra 1: KeyValidate

Challenge Difficulty: Hard

Challenge Link: Drive

We are presented with a Linux 32-bit stripped executable called KeyValidate. Running it asks for a key.

file

Opening it in IDA, we can see that it uses MD5, this could be useful later on.

md5

Checking the main function, we find that it takes they key using getline() after the prompt.

getline

After that it gets into the validation loop.

loop

We can check for references to MD5() and we find that it is called inside Sub_11FD. This may be an indicator that MD5 is used for validation. We can run it with input “1234” and set a breakpoint right after the MD5 call to see what is being hashed.

md5

We can see that the produced hash in arg_4 is the hash for “1” which is the first character of our input.

hash of 1

md5

After the return from that function we can see that a huge array of bytes is being loaded into a variable.

MD5Arr

After that, it compares the bytes of our inputs after they have been hashed, byte by byte compared to the required hashes stored in the binary. If it meets a wrong byte it returns.

The hashes stored in the binary are not the actual hashes, each byte has been XORd with its index ANDed with 0xFF. We can conclude this by checking the code for the validation.

XORdHashes[i] = Hashes[i] ^ ((i&0xFF))

We can take that large array and do the exact process again (the perks of XOR) to get the original hashes and then break them on hashes.com.

hashes = [0xe3, 0x59, 0xed, 0xa7, 0x8d, 0xf0, 0x86, 0x65, 0xf9, ........]
for a in range(len(hahes)):
    if a%16 == 0:
        print("")
    print(str('{:02x}'.format(hashes[a]^(a&0xFF))), end='')

hashes

After replacing each corresponding hash with its character, we get the string: the f1@g !s encrypt3d w!tH a l0g!c g@t3.

Plugging this into KeyValidate, we get the final flag.

final flag kv

Flag: ASCWG{the f1@g !s encrypt3d w!tH a l0g!c g@t3}

This challenge was joint with the challenge Hydra 2 which is a Forensics challenge written by 0x01g33k. Here’s a link to his writeup on the forensics challenges.

I hope you all enjoyed these challenges as much as I enjoyed writing them. See you in the next CTF!