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}