Flash CTF – I Heard You Liked Loaders

This challenge is a fairly simple dynamic reversing challenge with a fun twist, instead of a single shellcode loader, the first loader loads a second loader.

Methodology

Actually solving this challenge is not super difficult (though making it was not the easiest thing I’ve done haha), you mostly just need to step through execution in a debugger and bypass the anti-debugger check at the beginning. You also have to watch the instructions, because the flag is never printed to the screen or put in memory at once.

Executing The Shellcode Initially

The wrapper around the shellcode is a simple c launcher, similar to

int main() {
    char shellcode[] = "\xbf\x01...";
    void *exec = mmap(0, 1024, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    memcpy(exec, shellcode, sizeof(shellcode) / sizeof(shellcode[0]));
    ((void(*)())exec)();
    return 0;
}

All we really need to do for this section is launch the application in gdb (I prefer pwndbg personally, but any debugger should work), find the call rax instruction in the disassembly for main, and then break on it. in GDB, you can do this with disassemble main and break *main+775. Once the breakpoint is added, run the program with r, then step into the shellcode with s.

Shellcode Stage 1

At the beginning of the shellcode we just jmped to, we see that there is a call to PTRACE, attempting to determine if the application is being debugged.

0x7ffff7fc0000    mov    edi, 1        EDI => 1
0x7ffff7fc0005    xor    rsi, rsi      RSI => 0
0x7ffff7fc0008    xor    rdx, rdx      RDX => 0
0x7ffff7fc000b    xor    r10, r10      R10 => 0
0x7ffff7fc000e    mov    eax, 0x65     EAX => 0x65
0x7ffff7fc0013    syscall 
0x7ffff7fc0015    cmp    rax, 0
0x7ffff7fc0019    jne    0x7ffff7fc001d

This is super easy to bypass though, we just step (repeatedly) to the syscall instruction, then overwrite the returned rax value with set $rax=0, after that we can continue as normally, as far as the shellcode is concerned, it’s not being debugged.

The next section of the shellcode contains some complex xor decoding loop, we could write a decode function in a language such as python

def decode(encoded):
    decoded = []
    prev_byte = 0x90  # Starting control value for the first prev_byte

    for i in range(len(encoded) - 1):  # Exclude the null terminator
        current_byte = encoded[i]
        next_byte = encoded[i + 1]

        # Apply the decoder's formula
        decoded_byte = (current_byte ^ next_byte ^ prev_byte) - 7 & 0xFF
        decoded.append(decoded_byte)

        # Update prev_byte to the newly decoded byte
        prev_byte = decoded_byte

    return bytes(decoded)

but it’s far easier to just step through the loop over and over until we reach the decoded instructions. (There’s also a secret int3 instruction at the start of the next loader, so you can just continue instead)

Shellcode Stage 2

After we finally got past the first loader, we find ourselves in another loader. This one is a bit simpler, but there’s no point in writing a decoder, we can just step through the loop until it finishes again, no secret int3 this time, so you manually have to step through, or set a breakpoint for after the loop.

Shellcode Stage 3

The final section of shellcode doesn’t seem to do much, and just segfaults. Here’s the code:

0x7ffff7fc0091    nop    
0x7ffff7fc0092    nop    
0x7ffff7fc0093    nop    
0x7ffff7fc0094    mov    ebx, 0x6174654d     EBX => 0x6174654d
0x7ffff7fc0099    mov    ebx, 0x7b465443     EBX => 0x7b465443
0x7ffff7fc009e    mov    ebx, 0x6564346d     EBX => 0x6564346d
0x7ffff7fc00a3    mov    ebx, 0x34306c5f     EBX => 0x34306c5f
0x7ffff7fc00a8    mov    ebx, 0x5f723364     EBX => 0x5f723364
0x7ffff7fc00ad    mov    ebx, 0x72755f34     EBX => 0x72755f34
0x7ffff7fc00b2    mov    ebx, 0x61306c5f     EBX => 0x61306c5f
0x7ffff7fc00b7    mov    ebx, 0x7d723364     EBX => 0x7d723364
0x7ffff7fc00bc    xor    rbx, rbx            RBX => 0
0x7ffff7fc00bf    mov    eax, 0x3c           EAX => 0x3c
0x7ffff7fc00c4    xor    ecx, eax            ECX => 0
0x7ffff7fc00c6    retf

Well the segfault looks to be because it’s retf instead of ret, who knows why. What’s more exciting, is the mov ebx, value instructions. If we take those numbers and decode them as little endian 4 byte values, we get four bytes of the flag in each!

Decoding all of the values gives us our flag.

Flag

MetaCTF{m4de_l04d3r_4_ur_l0ad3r}