Posts FwordCTF 2021
Post
Cancel

FwordCTF 2021

This is my writeup for Time Machine reverse challenge from FwordCTF 2021, I enjoyed the CTF so much there were 4 reverse challenges and I solved 2 of them, Unfortunately I did not have much time to look at the other two challenges but I’m sure they’re awesome too and will tackle them later!

(NOTE: I got the flag 8 minutes after the CTF 😅)

Time Machine is my first nanomites challenge to solve ever that is why I want to share this experience for people who are new to this exciting concept and here is the official writeup with the challenge source code by my friend and the challenge author joezid

Let’s go back to the past!

meme 1


Time Machine

Time Machine

I checked the binary Time_Machine.exe using PEscope and It’s a 64-bit binary with interesting libraries as libgcc_s_seh-1.dll and libstdc++-6.dll, I also notices interesting APIs like: CreateProcessA, VirtualAlloc, ReadProcessMemory and WriteProcessMemory:

PEscope

By running the binary and monitoring the behavior through process hacker while the binary is waiting for input I noticed a child process which is expected from the APIs above:

process hacker

Then to understand the behavior even more I used tiny tracer to monitor the APIs calls and I noticed this pattern being repeated after CreateProcessA:

tiny tracer

I moved to IDA and there was a small main function that checks the value of the environment variable joezidsecret if null it continues with re_debugger function otherwise it will call re_debuggee function with the argv as argument:

IDA main function

Debuggee

I went for reversing the re_debuggee function and it prints Enter The Flag: then takes user input then copies a region in memory to an intermediate memory, allocates a new region with VirtualAlloc, and copies the intermediate memory to the newly allocated region which is a shellcode and finally calling that shellcode with the user input as argument:

IDA re_debuggee function

I was curious to see the shellcode so I sat a breakpoint on the conditional branch in main and flipped the zero flag bit to enter the re_debugee function:

debugging mode 1

And another breakpoint on the call to the shellcode:

debugging mode 2

Stepping in to the shellcode revealed a chunk of assembly instructions:

debugging mode 3

This chunk does the following:

  • loads the first byte from the user input into r12b register
  • XORing a weird value with 0x1337
  • rotating right the result by 0xD bits
  • setting r13 register to 0
  • executing ud2 which is the defined “undefined” opcode that crashes the program!

Since the argument of the size of the previous memcpy and VirtualAlloc is 0x3855 that means the shellcode size is 14421 bytes, so I converted the following chunk to code:

debugging mode 4

It’s pretty much the same previous chunk the only difference is the weird value that MOVes to r11 register!

I played some time with the values and converted 2 more chunks, and I noticed a difference on the 4-th chunk that r13 register is set to 1 not 0, that’s interesting:

debugging mode 5

I wrote an IDApython script to extract the shellcode assembly instructions:

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
import idc
from idaapi import *

SHELLCODE_SIZE = 0x3855
MOV_R12B = 0x8A44
UD2_OPCODE_LENGTH = 2
START = 0x170000

ea = START

with open("instructions.txt", "w") as f:
    while True:

        # mov r12b, <letter>
        if Word(ea) == MOV_R12B:
            MakeCode(ea)
            MakeFunction(ea)

        # getting the assembly instructions
        inst = idc.generate_disasm_line(ea, 0)
        f.write(inst + "\n")

        # advance ea pointer to next chunk
        if GetMnem(ea) == "ud2":
            ea += UD2_OPCODE_LENGTH
        else:
            ea = NextHead(ea)

        # if last ud2 opcode
        if ea >= START + (SHELLCODE_SIZE - UD2_OPCODE_LENGTH):
            break

print("=== DONE GETTING INSTRUCTIONS ===")

Then I noticed the mov r13, 1 instruction is present only once in the 10 chunks for each letter which makes it an indicator of a true value for the weird value MOVed to r11 as it’s also ends with 337 as in 83BDBECD337

I changed EIP to the address of the chunk that returns 1 for the first letter and after going through the XOR and ROR the result in r11 was 41DEDF66 which is not meaningful so I assumed it’s a hash:

debugging mode 6

I then knew it’s time to go to the re_debugger function, and this is the thing about nanomites, the parent acts like a debugger and runs the wanted chunk that exists in the child that acts as the debuggee, and the child at the end of the chunk causes a signal or crash (ud2 here) to return the execution to the parent which as a debugger have access to the registers of the debuggee, that prevents attaching a debugger to the child since it’s already being debugged (at least a debugger which uses the same APIs as parent)

Debugger

At first the re_debugger function it sets the environment variable joezidsecret=1 then creates the child process using CreateProcessA API:

IDA re_debugger function

Then it enters a loop that includes some switch cases for the debug event code to handle the exception thrown by the debuggee and track the state:

IDA re_debugger function 2

By inspecting the functions called in the cases or an easier approach viewing the strings, the string Correct Flag :) is interesting:

IDA strings

The XREF to this string is in the function re_check_flag and it seems very interesting:

IDA re_check_flag function

Combining this with my understanding of the debuggee makes it clear, it’s basically:

  • reading the r12 register (the i-th letter in user input)
  • hashing the letter
  • comparing the result with r11 (result from XOR and ROR in the debuggee)
  • if the hashes equal and the r13 register is set to 1, then a counter increments
  • if the counter equals 39, it prints out Correct Flag :)

So the solution approach is now obvious:

  • mapping printable letters to its corresponding hashes
  • getting the values MOVed to r11 from the instructions where r13=1 and
  • XORing and rotating these values to get the hashes of the flag letters
  • mapping each flag letter to the plain printable letter

Since I already have the shellcode instructions, I’m missing the hashes for printable characters, one way to get them is to get the hashing algorithm code, but that seems easy so I wanted to challenge myself in IDApython, I set a breakpoint on the function and I made an IDApython script to make the executable do this job for me, by making re_hashing function execute normally and saving both the plain character (the function argument rcx) and its hash (the return value eax) in a text file!:

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
import idc
from idaapi import *

FUNC_ADDR = 0x40185D

with open("hashes.txt", "a") as f:
    for l in range(39):

        plain = Byte(GetRegValue("rcx"))

        step_over()
        GetDebuggerEvent(WFNE_SUSP, -1)

        enc_hash = GetRegValue("eax")

        f.write(chr(plain) + " " + hex(enc_hash)[2:] + "\n")

        for _ in range(10):
            run_to(FUNC_ADDR)
            GetDebuggerEvent(WFNE_SUSP, -1)

        print("=== [%d] hashing letter '%c' ===" % (l, chr(plain)))

        if plain == ord("}"):
            break

I entered the input as the possible printable character that can show in a flag consisting of 70 characters:

input

It did ran until letter C because the flag is 39 characters so It didn’t complete so I provided it with the rest in the second time:

input again

Now what’s left is to decrypt the flag hashes and map them to original letters and here comes the part for this little python script:

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
def xor(val, key):
    return val ^ key

def ror(val, shamt):
    return ((val & (2**64 - 1)) >> shamt % 64) | (val << (64 - (shamt % 64)) & (2**64 - 1))

def decrypt(val, key, shamt):
    return ror(xor(val, key), shamt)



hashes = {}

# adding the letters and their hashes in the dictionary
with open("hashes.txt", "r") as f:

    for line in f.readlines():
        line = line.rstrip('\n').split()

        if "L" in line[1]:
            line[1] = line[1][:-1]

        hashes[int(line[1],16)] = line[0]


values = []

# getting the encrypted hash values from the shellcode instructions
with open("instructions.txt", "r") as f:

    insts = f.readlines()
    for i in range(0, len(insts)):
        inst = insts[i].rstrip('\n').split()
        if inst[-1] == "1":
            mov_r11_val = insts[i-3].rstrip('\n').split()
            val = int(mov_r11_val[-1][:-1], 16)
            values.append(val)

KEY = 0x1337
SHAMT = 0xD

flag = ""

for value in values:
    enc_val = decrypt(value, KEY, SHAMT)
    flag += hashes[enc_val]

print(flag)

Running the script reveals the flag:

the flag

Flag: FWORDctf{W3_4t_th3_t0p_4g41n_n0w_wh4t?}

Now, back to the future!

meme 2

This post is licensed under CC BY 4.0 by the author.