Post

ASCWG Finals Reverse Engineering Challenges write up

Write up for ASCWG Finals Reverse Engineering Challenge.

ASCWG Finals Reverse Engineering Challenges write up

Introduction

I’m happy to announce that I’ve participated in the finals of Arab Security Cyber War Games and achieved 9th place

During the competition I’ve solved 2 Reverse engineering challenges both were first bloods and one of them is the only solve during the competition

Starting with the easy one;

trst

A windows exe file, on the first look using IDA this is the main function

Right after I saw these three jumps, I immediately identified the anti-disassembly technique used here: Jump Instructions with the Same Target

In this case, there are two options: either debug while single-stepping or nop the first instruction after all the incoming jnz instructions.

However, I didn’t use either of them XD. To get “first blood,” you need to think of the fastest way and that’s exactly what I did.

I only single-stepped twice to see what was actually happening in the code and found that it was moving characters one by one to a location near ebp.

What I did next was look for the ebp address in the dump and step out from the function to see the final result.

And that’s where I found the flag.

10100111

This challenge was a Linux ELF file

Looking at the pseudo-code of the main function, it seems that the program will ask for a flag as input, check its length (43 characters), compare the first and last parts of it, and then, for the rest of the flag, perform some encryption before finally comparing it to a hardcoded string.

As you can see in the second picture, after examining the function where the hardcoded string is used, I found a memcmp call, indicating that it’s definitely a comparison function.

My approach was to first try giving it a random 43-character string and observe the final result of the comparison after the encryption algorithm was applied. So, I started debugging, set a breakpoint there, and examined the rdi register in memory.

Example flag used: ASCWG{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}

I did a lot of testing by modifying the example flag in ways that could help me identify a pattern or determine which characters of my flag influenced the encrypted output. After doing this many times, I was able to recognize a pattern.

First character0
Second character12
Third character24
Fourth character25
Fifth character13
Sixth character1
Seventh character2
Eighth’s character14
Ninth’s character26
Tenth’s character27
Eleventh’s character15

And so on (I hope you can recognize the pattern by now).

After this, my solution was to brute-force the flag since I already knew the encrypted bytes. I could debug the program, set a breakpoint at that address, read the rdi register to get my encrypted flag, and compare it with the known bytes to generate the full flag.

I created a GDB script for this task, and here it is. I’ll explain how it works through the comments:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import gdb
import string

# Break at the desired address where we want to inspect the value at the memory location pointed by RDI
gdb.execute('b *0x555555556b56')

# Define the check list that contains the expected values (in hexadecimal)
check = [0x3B, 0x36, 0x64, 0x61, 0x3B, 0x34, 0x36, 0x34, 0x34, 0x67, 0x33, 0x60, 0x36, 0x64, 0x35, 0x32,
         0x37, 0x33, 0x32, 0x33, 0x37, 0x38, 0x34, 0x37, 0x33, 0x31, 0x37, 0x38, 0x33, 0x31, 0x30, 0x36,
         0x32, 0x37, 0x32, 0x34]

# Placeholder flag with an initial guess (same length as the expected flag, starting with 'ASCWG{')
flag = 'ASCWG{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}'

# Starting index for the flag characters (after 'ASCWG{', which is index 6)
c = 6
# Corresponding index for check list
check_index = 0

# Position for iterating through possible flag characters
p = 0

# Define the possible character set for the flag
st = string.printable

while c < len(flag) - 1:  # Continue until the entire flag is recovered (excluding the final '}' in the flag)
    flag = list(flag)  # Convert flag to a list for easy character manipulation
    flag[c] = st[p]  # Set the current character in the flag
    flag = ''.join(flag)  # Convert list back to string

    # Write the current flag guess to the 'flag' file
    with open('flag', 'w') as a:
        a.write(flag)

    # Run the program with the current flag input, ensure the program restarts each time
    gdb.execute('set confirm off')  # Disable confirmations for commands
    gdb.execute('set pagination off')  # Disable pagination in gdb
    gdb.execute('run < flag')  # Run the program again after each flag guess
    for i in range(2): # The reason of this here is because the memcmp was being hit 2 times before the one I was looking for ( first 2 times was for the flag format check at the beginning )
        gdb.execute('c')

    sequence = [0, 12, 24, 25, 13, 1, 2, 14, 26, 27, 15, 3, 4, 16, 28, 29, 17, 5, 6, 18, 30, 31, 19, 7, 8, 20, 32, 33, 21, 9, 10, 22, 34, 35, 23, 11, 12, 22] # Pattern sequence

    result = sequence[check_index]

    rdi_offset = f'x/1bx $rdi+{result}' # check the value that points to current pattern index
    rdi_value = gdb.execute(rdi_offset, to_string=True).split()[1]
    byte_at_rdi = int(rdi_value, 16)


    # Get the expected byte from the check list for the current index
    expected_byte = check[result]
    print(f"Checking: Byte at RDI={byte_at_rdi}, Expected Byte={expected_byte} at index {c} with char {st[p]}")

    # If the byte at RDI matches the expected byte in the checklist, the character is correct
    if byte_at_rdi == expected_byte:
        # Write the correct character to the flag and move to the next index
        print(f"Character found: {flag[c]} at index {c}")
        c += 1  # Move to the next character in the flag
        check_index += 1  # Move to the next byte in the check list
        p = 0  # Reset the position for the character set
    else:
        # Move to the next possible character in the set
        p += 1

    # If we have tried all characters and didn't find a match, something went wrong
    if p >= len(st):
        print(f"Failed to find character at index {c}, exiting...")
        break

print(f"Recovered flag: {flag}")

Time to run the script and wait for the flag to be cooked

Here we go

My write-up ends here. Thank you for reading, and I hope you found it helpful. If you have any questions or comments, feel free to contact me on LinkedInDiscordGitHub.


"When you give up, that's when the game ends." — Mitsuyoshi Anzai
This post is licensed under CC BY 4.0 by the author.