3 minute read


I went to the CTF with a new laptop, and so I didn’t have everything setup. It means that I didn’t have a Windows VM available, so no dynamic analysis for me ! It’s fine, I tend to prefer analysing stastically because it forces you to understand deeply what’s going on, instead of just watching the stack/registers for a string !

I also take this opportunity to showcase how I use Ghidra to give a more detailed analysis.

So, let’s analyse this little malware, shall we ?


Satan helped me to create a new malware :), finding what it does will lead you to the flag !

As it’s a CTF challenge, I didn’t do the usual MD5/SHA reconnaissance, I opened it straight into Ghidra.

The beginning of the functions looks like this :

A lot of things is happening, but we don’t really care : most of it is the CRT making various checks.

What we want to look further is FUN_140001c00

We got two things going on :

  • A function that uses the GS register and the unicode for “ntdll.dll”
  • Another function that uses the return value from the first function

💡 : FS and GS are two segments registers. You can use the FS register on Windows 32-bit to access the TEB, and the GS register for Windows 64-bit. You can read more about segment registers and why they aren’t used anymore here : https://stackoverflow.com/questions/10810203/what-is-the-fs-gs-register-intended-for

So now that we know that the in_GS_OFFSET relates to the TEB, we just need to know what the 0x60 offset relates to. We have the answer here : 0x60 offset points to the PEB

So the two parameters for the function FUN_140001000 are actually :

  • unicode “ntdll.dll”
  • PEB

With that information, it makes it easier to understand what’s going on here : This functions searches for the ntdll base address by enumerating the InMemoryOrderModuleList.

Going back to the calling function, we understand it deeper :

With that in mind, we can move forward to the second function. In it, we can see what looks like stack-strings, and a function called several times with some arguments not changing :

Looking into this function, it’s what we got :

It’s a simple xor decryption routine. A little clean-up :

💡 : Little tip when dealing with stackstring in Ghidra, once you know the size of the data, assign it as an array. For example, local_60 seems to have a size of 24, as we can see in the xor_decode function. Retyping the variable to a byte[24] give an easier way to visualize :

Now we can decode the variables with the xor key 0x7861786f which gives us :

  • NtAllocateVirtualMemory
  • NtWriteVirtualMemory
  • NtCreateThreadEx
  • NtWaitForSingleObject

You might have see it, but one decryption uses a different key than the rest. Decoding it, it isn’t a string… What can this be ? We will check it later.

I forgot to ask : with the API names we just decrypted and the description of the challenge, did you found what’s next ? No ? Let’s continue and see what happens.

The API names are used as parameters for two functions after their decryption: FUN_14000010f0 and FUN_140001200


We can see here that FUN_14000010f0 looks in NTDLL Export Directory for the address of the function passed as the third parameter.

FUN_140001200 then looks at the retrieved address and validates that it’s a syscall stub.

Wait, syscall I said ? With those APIs, what we just saw and the description, we have enough clues to understand what’s going on : It’s an implentation of Hell’s Gate !

If you don’t know about Hell’s Gate, it’s a technique used to bypass EDR hooks. If you want to dig deeper into Hell’s Gate and/or EDR bypass, be sure to check this article by AliceCliment as everything is beautifuly explained !

Here is little drawing of the inner working of the program :

Now we just need to decrypt the payload and understand what it does in order to find the flag !

I have done it using this cyberchef recipe. The output is the shellcode we want.

Opening it in Ghidra, and manually disassemble gives this :

Be careful, you can’t XOR decode this one in Cyberchef. To do so I used this python one liner :

>>>bytes.fromhex(hex(0x7fb9e16be26c4d79 ^ 0x298a623990e3f1b)[2:]).decode('utf-8')[::-1]


And here we go ! We got our flag !

Wrapping up

I really liked this challenge. First because it wasn’t a crackme like often in the Reverse category, and more interesting it’s a technique I haven’t reversed yet so it’s pretty cool !