Oscur Writeup Reverse Engineering Midnight CTF 2025
Introduction
We are given a PE file:
Click here to download it: Oscur.exe
Initial Analysis
Let’s start by analyzing the main function:
There’s a bunch of puts and sleep calls that aren’t very interesting. We notice a VirtualAlloc and a memcopy, which looks like typical malware behavior. What’s interesting is that the memcopy function takes 3 arguments: destination, source, and size.
Here we have a source that starts with “MZ” and has a significant size (0xd1600).
Discovering Hidden PE File
Let’s examine what data is being copied into the process memory:
There’s a bunch of data here. At first glance, nothing seems particularly interesting, but remember when I mentioned the data starts with “MZ”? We can see that along with several ASCII characters like ‘text’ or ‘data’.
According to the Wikipedia page on file signatures, files starting with the magic bytes “MZ” are DOS MZ executables and their descendants (including NE and PE).
Extracting the Hidden Executable
So what’s next? We need to decompile this data. I exported the data in hex dump format and wrote a script to rebuild the DOS MZ executable:
1
2
3
4
5
6
7
8
9
with open('reverse/PE.txt', 'r') as f:
hex_data = f.read().strip()
binary_data = bytes.fromhex(hex_data)
with open('reconstructed.exe', 'wb') as f:
f.write(binary_data)
print("YAY!")
Now I have another .exe to decompile!
Analyzing the Second PE File
After searching through this new PE file, I found an interesting function:
The text in this message box is identified as lpText, so let’s analyze what is supposed to be printed, starting with the function that I renamed bytes_arr (because I already know what it does).
Obviously, the bytes_arr function creates… a bytes array. The bytes are:
1
0x7a, 0x32, 0x8f, 0xb9, 0x52, 0x8f, '!', 'b', 'T', '<', 0xb4, 0x91, 0x6f, 0x8b, 0x04, 0x70, 0x37
This looks like a secret key. Don’t bother reversing the last part of the code - it’s just junk meant to waste our time.
Digging Deeper into the Functions
Let’s analyze the function ‘sus_func_wrapper’ that takes this bytes_arr as a parameter:
As the name suggests, this is just a wrapper - pretty useless. Let’s go deeper:
Now we’re getting somewhere! This function ‘sus_func’:
- Reads a byte at address arg1 + 0x11 and zero-extends it to uint64_t
- If this byte is non-zero:
- Calls even_more_sus_func(arg1, 0x11, 0xd53df29ffdb7137)
- Sets result = arg1
- Clears the byte at address arg1 + 0x11 by setting it to 0
- Returns result
Let’s now analyze what ‘even_more_sus_func’ does:
The function ‘even_more_sus_func’ takes three int64_t parameters and returns a void pointer:
- Initializes i to nullptr (zero)
- Loops while i is less than arg2 (0x11)
- For each iteration:
- Performs a XOR operation:
*(i + arg1) ^= (arg3 >> ((zx.q(i.d) & 7) << 3)).b
- This XORs memory at address i + arg1 with a byte from arg3 shifted by a value derived from the current position
- Performs a XOR operation:
- Increments i by 1 each iteration
- Returns the final value of i
This is likely a simple encryption/decryption function that transforms a memory region using a rotating 8-byte key pattern (the & 7 part) from arg3 (the hardcoded hex value) as the key.
Putting the Pieces Together
So we have all the pieces now. The bytes:
1
0x7a, 0x32, 0x8f, 0xb9, 0x52, 0x8f, '!', 'b', 'T', '<', 0xb4, 0x91, 0x6f, 0x8b, 0x04, 0x70, 0x37
are XORed with the key 0xd53df29ffdb7137
using the full key length with the rotating pattern.
Decrypting the Flag
I wrote a simple script to decrypt the message:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
encrypted = [0x7a, 0x32, 0x8f, 0xb9, 0x52, 0x8f, ord('!'), ord('b'), ord('T'), ord('<'), 0xb4, 0x91, 0x6f, 0x8b, 0x04, 0x70, 0x37]
key = 0xd53df29ffdb7137
flag = ""
for i in range(len(encrypted)):
# 8 bits
shift = (i & 7) << 3
key_byte = (key >> shift) & 0xFF
# XOR
decrypted = encrypted[i] ^ key_byte
# add to flag
flag += chr(decrypted)
print("Flag:", flag)
And with that, I obtained the flag:
1
Flag: MCTF{ProcMonFTW}
Conclusion
This was a really great challenge! I loved the nested file concept - having a PE file hidden inside another PE file was a clever touch. It took me some time to understand everything properly in the second PE file, but the puzzle pieces came together nicely in the end.
The challenge involved several key reverse engineering concepts:
- File format analysis
- Hidden payload extraction
- Function analysis
- Simple cryptography (XOR encryption)
- Script-based decryption
Thanks for reading, see you next time!!!