Reversing Challenge Writeup

This is a quick write-up for a reversing challenge that I created for my company's internal CTF. I'm writing this write-up as if I'm solving the challenge.

Challenge - It was the Intern('s payload!)

Looks like the intern created their first payload. I'll be surprised if this even passes windows defender, but you never know. Reverse the payload and find the flag.

file: internpayload.dll -

point: 100

1 - Initial Analysis

Upon downloading the DLL, let's take a look at it through pestudio. One of the things to check for a malicious DLL (in an easy reversing challenge) would be exported functions.

Aside from the traditional DllMain, we see two interesting exported functions - NimMain and VoidFunc.

If you have been following the open-source offsec tooling scene, you'll be familiar with these two exported functions. If not, a quick googling on these reveals their secret.

It look like VoidFunc is one of required functions that is used for the infamous Invoke-ReflectivePEInjection powershell script. Indeed, while "Ctrl+F"-ing through the source code, the following comment was found:

The function name expected in the DLL for the prewritten FuncReturnType's is as follows:
WString    : WStringFunc
String     : StringFunc
Void       : VoidFunc

So it looks like VoidFunc is an exported function that is "expected" in a DLL to be executed through Invoke-ReflectivePEInjection. Technically it's not 100% required if you look through more source code, but we don't need to read through the entire source code here.

NimMain on the other hand, looks like an exported function that is created when the PE file is created through a language called nim-lang. For more details, check out the official repo -

In short, we can assume (for now) that this file is compiled from the nimlang language into a DLL, which is specially created to be ran inside the Invoke-ReflectivePEInjection powershell script. That's why it has that VoidFunc function exported.

Lastly, run strings on the DLL just in case, just for some sanity check and a quick static analysis. Warning - This part is a bit ctf-y.

string ./internspayload.dll 

@how do I actually run this dll tho ->
@xcon flag secret hint 

Strings reveals a hint on how to run this dll. Looks like instead of using the old Powersploit's Invoke-ReflectivePEInjection that has not been updated in the past 6 years, the challenge is suggesting to use the most up-to-date and maintained BCSecurity's Empire C2's version.

This is something that most red teamers know, but blue teams might not be aware of these little nuances. To prevent unfair advantages, I added that hint just in case.

2 - Just Run it

Not sure where to go from here - let's just run this thing and do some dynamic analysis. Turn off Defender, import Invoke-ReflectivePEInjection. Go through the help menu to figure out how to use this thing.

Set-MpPreference -DisableRealtimeMonitoring $true;
iex(New-Object net.webclient).downloadstring('');
Get-Help Invoke-ReflectivePEInjection -Examples

Running the payload with one of the examples shown from get-help outputs the following result, with a notepad process spawned.

PS C:\Windows\system32> Invoke-ReflectivePEInjection -PEPath C:\opt\xcon-internpayload\internpayload.dll

[*] nopers am//zzz: true
[*] nopers EE TEE DoubleU: true
xcon flag secret hint
how do I actually run this dll tho ->
assembly = stageone, Version=, Culture=neutral, PublicKeyToken=null

A notepad process was spawned, and it looks like a messagebox shellcode was injected. Additionally, based on the assembly = stageone, Version,, it looks like a .NET assembly by the name stageone was reflectively loaded inside powershell.

In short,

  1. Invoke-ReflectivePEInjection reflectively self-injected internpayload.dll
  2. internpayload.dll reflectively loaded stageone .NET assembly inside powershell
  3. stageone then spawned a notepad process, and injects its shellcode -> popping a messagebox - which is useless and not the flag.

3 - Memory Dumping

For a quick win, let's dump the memory of powershell to get that stageone assembly. I used pe-sieve for this.

.\pe-sieve64.exe /pid <powershell-pid> /data 3

< ... snip ... > 
PID: 8592

Total scanned:      118
Skipped:            0
Hooked:             4
Replaced:           0
Hdrs Modified:      0
IAT Hooks:          0
Implanted:          5
Implanted PE:       5
Implanted shc:      0
Unreachable files:  0
Other:              0
Total suspicious:   9

Indeed, one of the .exe file that was carved out seem to be stageone.exe.

Let's take a closer look at stageone with dnspy or ilspy, since it is a .NET assembly.

It looks like it's obfuscated with confuserEx, based on the Assembly information - [module: ConfusedBy("Confuser.Core 1.6.0+447341964f")]. However, we do see a strange file called finalStageSC in the resources section. Could this be the finalstage shellcode? Maybe stageone is a malware that executes finalStageSC from the Resources section using the findresource + loadresource + lockresource combo?

Before we dive deeper, save the finalStageSC on-disk with dnspy/ilspy.

Btw, how do we reverse a shellcode? What even is this shellcode made out of?

A quick VT result shows that it might be a shellcode created with "Donut" - . However, this might be false positive. Also, I don't know how to reverse shellcodes, yet alone Donut'ed shellcodes. And I thought this was an easy 100 point reversing challenge - wtf?  

4 - Just Run It, Again

I have no idea, but if this is an easy 100 point reversing challenge, maybe we can just run the shellcode and call it a day. The worst outcome is the malware sandbox/VM goes wrong. I can revert the VM in to the previous snapshot.

The following C# code was used, but any shellcode injector written in c/c++ is fine.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Text;

namespace godonut
    class Program
        public static string ByteArrayToString(byte[] ba)
            StringBuilder hex = new StringBuilder(ba.Length * 2);
            foreach (byte b in ba)
                hex.AppendFormat("0x{0:x2},", b);
            return hex.ToString();

        static void Main(string[] args)
            byte[] sc = System.IO.File.ReadAllBytes(@"c:\dev\finalstage-sc");

            var process = Process.Start("C:\\Windows\\System32\\notepad.exe");
            var pid = process.Id;

            IntPtr procHandle = OpenProcess(ProcessAccessFlags.All, false, pid);
            IntPtr alloc = VirtualAllocEx(procHandle, IntPtr.Zero, (uint)sc.Length, 0x1000 | 0x2000, 0x04);
            bool wPMemoryResult = WriteProcessMemory(procHandle, alloc, sc, (uint)sc.Length, out IntPtr byteWritten);
            bool vPResult = VirtualProtectEx(procHandle, alloc, (uint)sc.Length, 0x20, out uint oldProtect);
            IntPtr cRResult = CreateRemoteThread(procHandle, IntPtr.Zero, (UInt32)0, alloc, IntPtr.Zero, (UInt32)0, out IntPtr threadId);

            Console.WriteLine("\n" + "Press Enter to shut me down!");


        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId);

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out IntPtr lpNumberOfBytesWritten);

        static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);

        static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out IntPtr lpThreadId);

        public enum ProcessAccessFlags : uint
            All = 0x001F0FFF,
            Terminate = 0x00000001,
            CreateThread = 0x00000002,
            VirtualMemoryOperation = 0x00000008,
            VirtualMemoryRead = 0x00000010,
            VirtualMemoryWrite = 0x00000020,
            DuplicateHandle = 0x00000040,
            CreateProcess = 0x000000080,
            SetQuota = 0x00000100,
            SetInformation = 0x00000200,
            QueryInformation = 0x00000400,
            QueryLimitedInformation = 0x00001000,
            Synchronize = 0x00100000

And we got the flag.


This was a very last-minute reversing challenge I created the day before the ctf. That's why it has that ctf-y guessing work here and there. But at the same time, most of the tools and analysis required was mostly googling, strings, pestudio, and dnspy - so I don't think it required too much of a guessing or ctf-y type of thinking.

The challenge was designed to implement some of the relatively newer tradecraft like nimlang, .NET, and donut'ed shellcode (the latter two are questionably "new" since it's from 2018). At the same time, the challenge was designed to be relatively easy because I have zero knowledge on reversing - so I can't really create legitimate low-level reversing challenge with assembly codes and ida pro/ghidra shenanigans.

Anyways, happy hacking!

Show Comments