TrendMicro CTF 2017 - RE300

Sun, Jun 25, 2017

Writeups #ctf #tmctf #trendmicro #2017 #reversing

This was the third reversing challenge in TrendMicro CTF 2017. You can download it here (md5: 38256f02d260bca145e90e06e474175a).

To extract it, as per the same instructions as the original challenge:

$ openssl enc -d -aes-256-cbc -k aOw0ZxbUtoj9BdHYtxjn -in files15.enc -out files15.zip

# or if you have problems with the first command
$ openssl enc -d -aes-256-cbc -k aOw0ZxbUtoj9BdHYtxjn -in files15.enc -out files15.zip -md md5

# then,
$ unzip files15.zip

Unfortunately the challenge is now shut down, so I cannot quite remember the original description or hint but I do remember it had to do with Powershell. If I find it posted somewhere else later, I’ll update this!

Anyway, we are given a single file — G0AL.BAT, containing:

@echo off

set j=Thank_You_For_Joining_TMCTF2017
set k=Tested on Win7SP1 32-bit OS

set l=2eub2XQk9DHSsncxyWSLcTCLdgyLdhyLRgiLfiCLNjhPGHXzWQHR/+Fgi2wkJItFPItUKHgB6otKGItaIAHr4zRJizSLAe4x/zHA/KyEwHQHwc8NAcfr9Dt8JCh14YtaJAHrZosMS4taHAHriwSLAeiJRCQcYcOyCCnUieWJwmiOTg7sUuif////iUUEu37Y4nOHHCRS6I7///+
set m=moAsSAMOY%02maCkAOwBsdf%smadfdf9z///+qamogG8AZABSDGsaSSwhmzWMOYsA+/masdgmoKYqWTAGEAaQBuAC4ARAB//IAbA==
set n=LABNAmaodSDGASJOIHGI76msdm%ls:1qwerATSAYUDBGOSSnsAMIOLM//sogs+AuAFasgqQQYYHAFAZ2%:~QBtAGIAbABdABEA+
set o=JRQhobGwgQWgzMi5kaHVzZXIw24hcJAqJ5lb/VQSJwlC7qKJNvIccJFLoX////2hyb1ggaGRNaWNoVHJlbjHbiFwkConjaCF9WCBoZ2FpbmhzTWVBaCFJdEloaGVyZWhsbG9UaEZ7SGVoVE1DVDHJiEwkHonhMdJSU1FS/9AxwFD/VQg=


set p=GUAZQBwACgAOQAwACkAOwBGOALAHIAeQB7AGYAdQBuAGMAdABpAG8AbgAgAGcAZABlAGwAZQBnAGEAdABlAHsAUABhAHIAYQBtACAAKABbAFAAYQByAGEAbQBlAHQAZQByACgAUABvAHMAaQBGOALAGkAbwBuADGOALAMAAsAEGOALAYQBuAGQAYQBGOALAG8AcgB5ADGOALAJABUAHIAdQBlACkAXQAgAFsAVAB5AHAAZQBbAFGOALAXQAgACQAUABhAHIAYQBtAGUAdABlAHIAcwAsAFsAUABhAHIAYQBtAGUAdABlAHIAKABQAG8AcwBpAHQAaQBvAG4APQAxACkAXQAgAFsAVAB5AHAAZQBdACAAJABSAGUAdAB1AHIAbgBUAHkAcABlADGOALAWwBWAG8AaQBkAFGOALAKQA7ACQAVAB5AHAAZQBCAHUAaQBsAGQAZQByADGOALAWwBBAHAAcABEAG8AbQBhAGkAbgBdADoAOgBDAHUAcgByAGUAbgBGOALAEQAbwBtAGEAaQBuAC4ARABlAGYAaQBuAGUARAB5AG4AYQBtAGkAYwBBAHMAcwBlAGGOALAYgBsAHkAKAAoAE4AZQB3ACGOALATwBiAGoAZQBjAHQAIABTAHkAcwBGOALAGUAbQAuAFIAZQBmAGwAZQBjAHQAaQBvAG4ALgBBAHMAcwBlAGGOALAYgBsAHkATgBhAGGOALAZQAoACIAUgBlAGYAbABlAGMAdABlAGQARABlAGwAZQBnAGEAdABlACIAKQApACwAWwBTAHkAcwBGOALAGUAbQAuAFIAZQBmAGwAZQBjAHQAaQBvAG4ALgBFAGGOALAaQBGOALAC4AQQBzAHMAZQBtAGIAbAB5AEIAdQBpAGwAZABlAHIAQQBjAGMAZQBzAHMAXQA6ADoAUgB1AG4AKQAuAEQAZQBmAGkAbgBlAEQAeQBuAGEAbQBpAGMATQBvAGQAdQBsAGUAKAAiAEkAbgBNAGUAbQBvAHIAeQBNAG8AZAB1AGwAZQAiACwAJABmAGEAbABzAGUAKQAuAEQAZQBmAGkAbgBlAFQAeQBwAGUAKAAiAFgAWABYACIALAAiAEMAbABhAHMAcwAsAFAAdQBiAGwAaQBjACwAUwBlAGEAbABlAGQALABBAG4Acw^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%0gBSAHUAbgBGOALAGkAbQBlAC4ASQBuAHQAZQByAG8AcABTAGUAcgB2AGkAYwBlAHMALgBIAGEAbgBkAGwAZQBSAGUAZgBdACgATgBlAHcALQBPAGIAagBlAGMAdAAgAFMAeQBzAHQAZQBtAC4AUgB1AG4AdABpAGGOALAZQAuAEkAbgBGOALAGUAcgBvAHAAUwBlAHIAdgBpAGMAZQBzAC4ASABhAG4AZABsAGUAUgBlAGYAKAAoAE4AZQB3ACGOALATwBiAGoAZQBjAHQAIABJAG4AdABQAHQAcgApACwAJABVAG4AcwBhAGYAZQBOAGEAdABpAHYAZQBNAGUAdABoAG8AZABzAC4ARwBlAHQATQBlAHQAaABvAGQAKAAiAEcAZQBGOALAEGOALAbwBkAHUAbABlAEgAYQBuAGQAbABlACIAKQAuAEkAbgB2AG8AawBlACgAJABuAHUAbABsACwAQAAoACQATQBvAGQAdQBsAGUAKQApACkAKQAsACQAUAByAG8AYwBlAGQAdQByAGUAKQApADsAfQBbAEIAeQBGOALAGUAWwBdAFGOALAJABzAGMAMwAyACAAPQAgAFsAUwB5AHMAdABlAGGOALALgBDAG8AbgB2AGUAcgBGOALAFGOALAOgA6AEYAcgBvAGGOALAQgBhAHMAZQA2ADQAUwBGOALAHIAaQBuAGcAKAAkAGUAbgB2ADoAbAArACQAZQBuAHYAOgBPACkAOwAkAGEAPQBHAGUAdAAtAEQAYQBGOALAGUAOwBpAGYAKAAkAGEALgBNAG8AbgBGOALAGgAIAAtAGcAZQAgADIAKQB7AGUAeABpAHQAOwB9AFsAVQBpAG4AdAAzADIAWwBdAFGOALAIAAkAG8AcAA9ADAAOwAkAHIAPQAoAFsAUwB5AHMAdABlAGGOALALgBSAHUAbgBGOALAGkAbQBlAC4ASQBuAHQAZQByAG8AcABTAGUAcgB2AGkAYwBlAHMALgBNAGEAcgBzAGgAYQBsAFGOALAOgA6AEcAZQBGOALAEQAZQBsAGUAZwBhAHQAZQBGAG8AcgBGAHUAbgBjAHQAaQBvAG4AUABvAGkAbgBGOALAGUAcgAoACgAZwBwAHIAbwBjACAAawBlAHIAbgBlAGwAMwAyAC4AZABsAGwAIABWAGkAcgBGOALAHUAYQBsAFAAcgBvAHQAZQBjAHQAKQAsACgAZwBkAGUAbABlAGcAYQBGOALAGUAIABAACgAWwBCAHkAdABlAFsAXQBdACwAWwBVAEkAbgBGOALADMAMgBdACwAWwBVAEkAbgBGOALADMAMgBdACwAWwBVAEkAbgBGOALADMAMgBbAFGOALAXQApACAAKABbAEkAbgBGOALAFAAdAByAFGOALAKQApACkAKQAuAEkAbgB2AG8AawBlACgAJABzAGMAMwAyACwAJABzAGMAMwAyAC4ATABlAG4AZwBGOALAGgALAAwAHgANAAwACwAJABvAHAAKQA7AGkAZgAoACQAcgAgACGOALAZQBxACAAMAApAHsAJABwAHIAPQAoAFsAUwB5AHMAdABlAGGOALALgBSAHUAbgBGOALAGkAbQBlAC4ASQBuAHQAZQByAG8AcABTAGUAcgB2AGkAYwBlAHMALgBNAGEAcgBzAGgAYQBsAFGOALAOgA6AEcAZQBGOALAEQAZQBsAGUAZwBhAHQAZQBGAG8AcgBGAHUAbgBjAHQAaQBvAG4AUABvAGkAbgBGOALAGUAcgAoACgAZwBwAHIAbwBjACAAawBlAHIAbgBlAGwAMwAyAC4AZABsAGwAIABWAGkAcgBGOALAHUAYQBsAEEAbABsAG8AYwApACwAKABnAGQAZQBsAGUAZwBhAHQAZQAgAEAAKABbAEkAbgBGOALAFAAdAByAFGOALALABbAFUASQBuAHQAMwAyAFGOALALABbAFUASQBuAHQAMwAyAFGOALALABbAFUASQBuAHQAMwAyAFGOALAKQAgACgAWwBVAEkAbgBGOALADMAMgBdACkAKQApACkALgBJAG4AdgBvAGsAZQAoADAALAAkAHMAYwAzADIALgBMAGUAbgBnAHQAaAAsADAAeAAzADAAMAAwACwAMAB4ADQAMAApADsAaQBmACgAJABwAHIAIAAtAG4AZQAgADAAKQB7ACQAbQBlAGGOALAcwBlAHQAPQAoAFsAUwB5AHMAdABlAGGOALALgBSAHUAbgBGOALAGkAbQBlAC4ASQBuAHQAZQByAG8AcABTAGUAcgB2AGkAYwBlAHMALgBNAGEAcgBzAGgAYQ^BsAFGOALAOgA6AEcAZQBGOALAEQAZQBsAGUAZwBhAHQAZQBGAG8AcgBGAHUAbgBjAHQAaQBvAG4AUABvAGkAbgBGOALAGUAcgAoACgAZwBwAHIAbwBjACAAbQBzAHYAYwByAHQALgBkAGwAbAAgAGGOALAZQBtAHMAZQBGOALACkALAAoAGcAZABlAGwAZQBnAGEAdABlACAAQAAoAFsAVQBJAG4AdAAzADIAXQAsAFsAVQBJAG4AdAAzADIAXQAsAFsAVQBJAG4AdAAzADIAXQApACAAKABbAEkAbgBGOALAFAAdAByAFGOALAKQApACkAKQA7AGYAbwByACAAKAAkAGkAPQAwADsAJABpACAALQBsAGUAIAAoACQAcwBjADMAMgAuAEwAZQBuAGcAdABoACGOALAMQApADsAJABpACsAKwApACAAewAkAGGOALAZQBtAHMAZQBGOALAC4ASQBuAHYAbwBrAGUAKAAoACQAcAByACsAJABpACkALAAgACQAcwBjADMAMgBbACQAaQBdACwAIAAxACkAfQA7ACgAWwBTAHkAcwBGOALAGUAbQAuAFIAdQBuAHQAaQBtAGUALgBJAG4AdABlAHIAbwBwAFMAZQByAHYAaQBjAGUAcwAuAEGOALAYQByAHMAaABhAGwAXQA6ADoARwBlAHQARABlAGwAZQBnAGEAdABlAEYAbwByAEYAdQBuAGMAdABpAG8AbgBQAG8AaQBuAHQAZQByACgAKABnAHAAcgBvAGMAIABrAGUAcgBuAGUAbAAzADIALgBkAGwAbAAgAEMAcgBlAGEAdABlAFQAaAByAGUAYQBkACkALAAoAGcAZABlAGwAZQBnAGEAdABlACAAQAAoAFsASQBuAHQAUABGOALAHIAXQAsAFsAVQBJAG4AdAAzADIAXQAsAFsAVQBJAG4AdAAzADIAXQAsAFsAVQBJAG4AdAAzADIAXQAsAFsAVQBJAG4AdAAzADIAXQAsAFsASQBuAHQAUABGOALAHIAXQApACAAKABbAEkAbgBGOALAFAAdAByAFGOALAKQApACkAKQAuAEkAbgB2AG8AawBlACgAMAAsADAALAAkAHAAcgAsACQAcAByACwAMAAsADAAKQA7AHGOALAfQBlAGwAcwBlAHsAKABbAFMAeQBzAHQAZQBtAC4AUgB1AG4AdABpAGGOALAZQAuAEkAbgBGOALAGUAcgBvAHAAUwBlAHIAdgBpAGMAZQBzAC4ATQBhAHIAcwBoAGEAbABdADoAOgBHAGUAdABEAGUAbABlAGcAYQBGOALAGUARgBvAHIARgB1AG4AYwBGOALAGkAbwBuAFAAbwBpAG4AdABlAHIAKAAoAGcAcAByAG8AYwAgAGsAZQByAG4AZQBsADMAMgAuAGQAbABsACAAQwByAGUAYQBGOALAGUAVABoAHIAZQBhAGQAKQAsACgAZwBkAGUAbABlAGcAYQBGOALAGUAIABAACgAWwBJAG4AdABQAHQAcgBdACwAWwBVAEkAbgBGOALADMAMgBdACwAWwBCAHkAdABlAFsAXQBdACwAWwBCAHkAdABlAFsAXQBdACwAWwBVAEkAbgBGOALADMAMgBdACwAWwBJAG4AdABQAHQAcgBdACkAIAAoAFsASQBuAHQAUABGOALAHIAXQApACkAKQApAC4ASQBuAHYAbwBrAGUAKAAwACwAMAAsACQAcwBjADMAMgAsACQAcwBjADMAMgAsADAALAAwACkAOwB9AHMAbABlAGUAcAAoADEAMgAwADAAKQA7AHGOALAYwBhAHQAYwBoAHsAfQBlAHgAaQBGOALADsA"


cmd /c "powershell -command "$a=get-date;$t=[system.Text.Encoding]::UTF8.GetBytes('fzEvD');for ($i=0;$i -le ($t.Length-1);$i++){$t[$i]=$t[$i]-$a.hour} $d=[system.Text.Encoding]::UTF8.GetString($t);[Environment]::SetEnvironmentVariable('q', $d, 'User');"
cmd /c "powershell -enc %q%%p:GOAL=0%  > NUL
echo %j%
echo %q%
set j=
set k=
set l=
set m=
set n=
set o=
set p=

The first thing that is important to realize is that this is a batch file, not a powershell file. Because of this, we must analyze what is going on first as a batch file (even though there is Powershell code executed inside of it).

First, a bunch of variables are being set to things that kinda look like incomplete base64. Also, the variable p is being set with a long string and has a weird straggling " at the end — this will make sense later. Next, a powershell script is run that has the following condensed into one line:

$date = Get-Date;
$str = [System.Text.Encoding]::UTF8.GetBytes('fzEvD');

for($i = 0; $i -le ($str.Length - 1); $i++) { 
    $str[$i] = $str[$i] - $date.hour;
}

$shiftedStr = [System.Text.Encoding]::UTF8.GetString($t);
[Environment]::SetEnvironmentVariable('q', $shiftedStr, 'User');

This script will take the string fzEvD and shift the characters down by whatever hour it is currently — 0 to 23. It then takes this new string, and sets it to the user environment variable q. Then, the batch script performs some string transformations and executes a base64-encoded powershell script.

Examining the string transformations,

cmd /c "powershell -enc %q%%p:GOAL=0%  > NUL

This command only has a " at the beginning because it is closed off by the " at the end of p — this is what I was referring to earlier. Next, %q% is simply going to insert the value of q which was set by the powershell script. After this, %p:GOAL=0% is going to insert the value of p but it is going to replace every instance of the string GOAL with 0 — something seemed very repetitive in that “base64”! Lastly, the " finishes off the script and is all piped into NUL, making the output hidden if it fails! This is deceptive because there is an echo at the beginning saying Thank_You_For_Joining_TMCTF2017, making you think you just got the flag! BAMBOOZLERS! Now, we need to figure out what that base64 encoded script is doing. To do that, we need to figure out what hour of the day this was intended to be run at to get the correct beginning portion of the base64 script. I put together a small python script that would do this for me by checking every possible hour and performing the shift. Then it would base64 decode the entire value with the shifted string at the front and print it out — if it would even decode at all.

Here was my script:

# the base "p" that we have with all "GOAL" replaced with "0" --- trimmed for readability
base = 'GUAZQBwACgAOQAwACkAOwB0AH...' 

# each hour value possible
for k in xrange(24):
    # shift all the characters in the front portion
    front = ''.join(chr(ord(c) - k) for c in 'fzEvD')
    try:
        # insert it into the string and remove all these random 
        # NUL bytes I discovered after debugging what was being printed out
        out = base64.b64decode('{}{}'.format(front, base)).replace('\x00', '')

        # we want it to spell out the start of the code being "sleep(90);"
        # this will make sure we have it de-mangled
        if out.startswith('sleep'):
            print k
    except:
        pass

After running this, we find that the k hour value we want is 3! Once we base64-decoded the string and stripped it of NUL bytes, we get this script:

sleep(90);try{function gdelegate{Param ([Parameter(Position=0,Mandatory=$True)] [Type[]] $Parameters,[Parameter(Position=1)] [Type] $ReturnType=[Void]);$TypeBuilder=[AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName("ReflectedDelegate")),[System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule("InMemoryModule",$false).DefineType("XXX","Class,Public,Sealed,AnsiClass,AutoClass",[System.MulticastDelegate]);$TypeBuilder.DefineConstructor("RTSpecialName,HideBySig,Public",[System.Reflection.CallingConventions]::Standard,$Parameters).SetImplementationFlags("Runtime,Managed");$TypeBuilder.DefineMethod("Invoke","Public,HideBySig,NewSlot,Virtual",$ReturnType,$Parameters).SetImplementationFlags("Runtime,Managed");return $TypeBuilder.CreateType();}function gproc{Param ([Parameter(Position=0,Mandatory=$True)] [String] $Module,[Parameter(Position=1,Mandatory=$True)] [String] $Procedure);$SystemAssembly=[AppDomain]::CurrentDomain.GetAssemblies()|Where-Object{$_.GlobalAssemblyCache -And $_.Location.Split("\\")[-1].Equals("System.dll")};$UnsafeNativeMethods=$SystemAssembly.GetType("Microsoft.Win32.UnsafeNativeMethods");return $UnsafeNativeMethods.GetMethod("GetProcAddress").Invoke($null,@([Syste\x02Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr),$UnsafeNativeMethods.GetMethod("GetModuleHandle").Invoke($null,@($Module)))),$Procedure));}[Byte[]]$sc32 = [System.Convert]::FromBase64String($env:l+$env:O);$a=Get-Date;if($a.Month -ge 2){exit;}[Uint32[]] $op=0;$r=([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc kernel32.dll VirtualProtect),(gdelegate @([Byte[]],[UInt32],[UInt32],[UInt32[]]) ([IntPtr])))).Invoke($sc32,$sc32.Length,0x40,$op);if($r -eq 0){$pr=([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc kernel32.dll VirtualAlloc),(gdelegate @([IntPtr],[UInt32],[UInt32],[UInt32]) ([UInt32])))).Invoke(0,$sc32.Length,0x3000,0x40);if($pr -ne 0){$memset=([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc msvcrt.dll memset),(gdelegate @([UInt32],[UInt32],[UInt32]) ([IntPtr]))));for ($i=0;$i -le ($sc32.Length-1);$i++) {$memset.Invoke(($pr+$i), $sc32[$i], 1)};([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc kernel32.dll CreateThread),(gdelegate @([IntPtr],[UInt32],[UInt32],[UInt32],[UInt32],[IntPtr]) ([IntPtr])))).Invoke(0,0,$pr,$pr,0,0);}}else{([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((gproc kernel32.dll CreateThread),(gdelegate @([IntPtr],[UInt32],[Byte[]],[Byte[]],[UInt32],[IntPtr]) ([IntPtr])))).Invoke(0,0,$sc32,$sc32,0,0);}sleep(1200);}catch{}exit;

…..wow. Okay, that is a mess. Let’s clean that up, shall we?

sleep(90);
try {
    function gdelegate {
        Param(
            [Parameter(Position=0,Mandatory=$True)]
            [Type[]] $Parameters,
            
            [Parameter(Position=1)]
            [Type] $ReturnType=[Void]
        );
        
        $TypeBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly(
            (New-Object System.Reflection.AssemblyName("ReflectedDelegate")),
            [System.Reflection.Emit.AssemblyBuilderAccess]::Run
        ).DefineDynamicModule("InMemoryModule", $false).DefineType(
            "XXX", "Class,Public,Sealed,AnsiClass,AutoClass", 
            [System.MulticastDelegate]
        );
        
        $TypeBuilder.DefineConstructor(
            "RTSpecialName,HideBySig,Public", 
            [System.Reflection.CallingConventions]::Standard,
            $Parameters
        ).SetImplementationFlags("Runtime,Managed");
        
        $TypeBuilder.DefineMethod(
            "Invoke", "Public,HideBySig,NewSlot,Virtual",
            $ReturnType, $Parameters
        ).SetImplementationFlags("Runtime,Managed");
        
        return $TypeBuilder.CreateType();
    }
    
    function gproc {
        Param(
            [Parameter(Position=0, Mandatory=$True)]
            [String] $Module,
            
            [Parameter(Position=1, Mandatory=$True)]
            [String] $Procedure
        );

        $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object{$_.GlobalAssemblyCache -And $_.Location.Split("\")[-1].Equals("System.dll")};

        $UnsafeNativeMethods = $SystemAssembly.GetType("Microsoft.Win32.UnsafeNativeMethods");
        
        return $UnsafeNativeMethods.GetMethod("GetProcAddress").Invoke(
            $null,
            @([SysteRuntime.InteropServices.HandleRef](
                New-Object System.Runtime.InteropServices.HandleRef(
                    (New-Object IntPtr),
                    $UnsafeNativeMethods.GetMethod("GetModuleHandle").Invoke($null, @($Module))
                )
            ), $Procedure)
        );
    }
    
    [Byte[]] $sc32 = [System.Convert]::FromBase64String($env:l + $env:O);
    
    [Uint32[]] $op = 0;
    $r = ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
            (gproc kernel32.dll VirtualProtect),
            (gdelegate @([Byte[]], [UInt32], [UInt32], [UInt32[]]) ([IntPtr]))
        )
    ).Invoke($sc32, $sc32.Length, 0x40, $op);
    
    if($r -eq 0) {
        $pr = ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
                (gproc kernel32.dll VirtualAlloc),
                (gdelegate @([IntPtr], [UInt32], [UInt32],[UInt32]) ([UInt32]))
            )
        ).Invoke(0, $sc32.Length, 0x3000, 0x40);
        
        if($pr -ne 0) {
            $memset=([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
                        (gproc msvcrt.dll memset),
                        (gdelegate @([UInt32],[UInt32],[UInt32]) ([IntPtr]))
                )
            );
            
            for ($i = 0; $i -le ($sc32.Length - 1); $i++) {
                $memset.Invoke(($pr + $i), $sc32[$i], 1)};
                ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
                    (gproc kernel32.dll CreateThread),
                    (gdelegate @([IntPtr], [UInt32], [UInt32], [UInt32], [UInt32], [IntPtr]) ([IntPtr]))
                )).Invoke(0, 0, $pr, $pr, 0, 0);
        }
    } else {
        ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
            (gproc kernel32.dll CreateThread),
            (gdelegate @([IntPtr],[UInt32],[Byte[]],[Byte[]],[UInt32],[IntPtr]) ([IntPtr]))
        )).Invoke(0, 0, $sc32, $sc32, 0, 0);
    }
        
    sleep(1200);
} catch{}

exit;

Note: I appologize for this formatting if some of it isn’t valid powershell, mine is a bit weak so I can’t vouch for its accuracy but this was the easiest to read.

I started looking at what this was doing and I pretty quickly identified that it was spawning a process with Windows API calls from powershell that would execute some kind of payload. How? You’ll notice that these functions basically define the C functions that are being accessed by powershell and which DLLs to use for them. Then, this line stands out and it is really the entry point for this script

[Byte[]] $sc32 = [System.Convert]::FromBase64String($env:l + $env:O);

This is going to take the environment variables l and O and convert them from base64 into a byte array called $sc32….recall that there is a string in the original G0AL.BAT that said Tested on Win7SP1 32-bit OS — this was intended for a 32-bit OS, so $sc32 probably stands for shellcode 32-bit. Right, so this is gonna be what we wanna know what’s happening with. So next we have,

[Uint32[]] $op = 0;
$r = ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
        (gproc kernel32.dll VirtualProtect),
        (gdelegate @([Byte[]], [UInt32], [UInt32], [UInt32[]]) ([IntPtr]))
    )
).Invoke($sc32, $sc32.Length, 0x40, $op);

This function is defined in the Windows API Documentation as shown:

BOOL WINAPI VirtualProtect(
  _In_  LPVOID lpAddress,
  _In_  SIZE_T dwSize,
  _In_  DWORD  flNewProtect,
  _Out_ PDWORD lpflOldProtect
);

As you can see, this is getting the function VirtualProtect from kernel32.dll, defining its parameters as byte[], uint32, uint32, uint32[], and its return type as an int*, and then calling the function with the shellcode, the length of the shellcode, 0x40 which indicates PAGE_EXECUTE_READWRITE and then 0 or NULL as the final parameter — not sure why this is there as it only takes 3 parameters. Then we have this block of code:

if($r -eq 0) {
    $pr = ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
            (gproc kernel32.dll VirtualAlloc),
            (gdelegate @([IntPtr], [UInt32], [UInt32],[UInt32]) ([UInt32]))
        )
    ).Invoke(0, $sc32.Length, 0x3000, 0x40);
    
    if($pr -ne 0) {
        $memset=([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
                    (gproc msvcrt.dll memset),
                    (gdelegate @([UInt32],[UInt32],[UInt32]) ([IntPtr]))
            )
        );
        
        for ($i = 0; $i -le ($sc32.Length - 1); $i++) {
            $memset.Invoke(($pr + $i), $sc32[$i], 1)};
            ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
                (gproc kernel32.dll CreateThread),
                (gdelegate @([IntPtr], [UInt32], [UInt32], [UInt32], [UInt32], [IntPtr]) ([IntPtr]))
            )).Invoke(0, 0, $pr, $pr, 0, 0);
    }
} else {
    ([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(
        (gproc kernel32.dll CreateThread),
        (gdelegate @([IntPtr],[UInt32],[Byte[]],[Byte[]],[UInt32],[IntPtr]) ([IntPtr]))
    )).Invoke(0, 0, $sc32, $sc32, 0, 0);
}

Really, all this does is say “Hey, if I was unable to just reserve the size needed for the shellcode and make it read/write/execute”. Then it will memset each byte of the VirtualAlloc’d memory and create a thread for each execution of VirtualAlloc every time it does memset. If it was able to reserve the size needed for the shellcode, it would simply do CreateThread for the shellcode.

Now, we need to know what that shellcode is in order to figure out what’s going on in this code. If we combine l and O together, we get the base64 string:

2eub2XQk9DHSsncxyWSLcTCLdgyLdhyLRgiLfiCLNjhPGHXzWQHR/+Fgi2wkJItFPItUKHgB6otKGItaIAHr4zRJizSLAe4x/zHA/KyEwHQHwc8NAcfr9Dt8JCh14YtaJAHrZosMS4taHAHriwSLAeiJRCQcYcOyCCnUieWJwmiOTg7sUuif////iUUEu37Y4nOHHCRS6I7///+JRQhobGwgQWgzMi5kaHVzZXIw24hcJAqJ5lb/VQSJwlC7qKJNvIccJFLoX////2hyb1ggaGRNaWNoVHJlbjHbiFwkConjaCF9WCBoZ2FpbmhzTWVBaCFJdEloaGVyZWhsbG9UaEZ7SGVoVE1DVDHJiEwkHonhMdJSU1FS/9AxwFD/VQg=

Which decodes to…

In [1]: import base64

In [2]: base64.b64decode('2eub2XQk9DHSsncxyWSLcTCLdgyLdhyLRgiLfiCLNjhPGHXzWQHR/+Fgi2wkJItFPItUKHgB6otKGItaIAHr4zRJizSLAe4x/zHA/KyEwHQHwc8NAcfr9Dt8JCh14YtaJAHrZosMS4taHAHriwSLAeiJRCQcYcOyCCnUieWJwmiOTg7
    ...: sUuif////iUUEu37Y4nOHHCRS6I7///+JRQhobGwgQWgzMi5kaHVzZXIw24hcJAqJ5lb/VQSJwlC7qKJNvIccJFLoX////2hyb1ggaGRNaWNoVHJlbjHbiFwkConjaCF9WCBoZ2FpbmhzTWVBaCFJdEloaGVyZWhsbG9UaEZ7SGVoVE1DVDHJiEwkHonhMdJS
    ...: U1FS/9AxwFD/VQg=')
Out[2]: '\xd9\xeb\x9b\xd9t$\xf41\xd2\xb2w1\xc9d\x8bq0\x8bv\x0c\x8bv\x1c\x8bF\x08\x8b~ \x8b68O\x18u\xf3Y\x01\xd1\xff\xe1`\x8bl$$\x8bE<\x8bT(x\x01\xea\x8bJ\x18\x8bZ \x01\xeb\xe34I\x8b4\x8b\x01\xee1\xff1\xc0\xfc\xac\x84\xc0t\x07\xc1\xcf\r\x01\xc7\xeb\xf4;|$(u\xe1\x8bZ$\x01\xebf\x8b\x0cK\x8bZ\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89D$\x1ca\xc3\xb2\x08)\xd4\x89\xe5\x89\xc2h\x8eN\x0e\xecR\xe8\x9f\xff\xff\xff\x89E\x04\xbb~\xd8\xe2s\x87\x1c$R\xe8\x8e\xff\xff\xff\x89E\x08hll Ah32.dhuser0\xdb\x88\\$\n\x89\xe6V\xffU\x04\x89\xc2P\xbb\xa8\xa2M\xbc\x87\x1c$R\xe8_\xff\xff\xffhroX hdMichTren1\xdb\x88\\$\n\x89\xe3h!}X hgainhsMeAh!ItIhherehlloThF{HehTMCT1\xc9\x88L$\x1e\x89\xe11\xd2RSQR\xff\xd01\xc0P\xffU\x08'

I looked up this initial byte sequence — D9 EB 9B D9 — and found a snort rule on Github!

# alert ip $EXTERNAL_NET any -> $HOME_NET any (msg:"INDICATOR-SHELLCODE Metasploit payload windows_messagebox"; content:"|D9 EB 9B D9 74 24 F4 31 D2 B2 77 31 C9 64 8B 71 30 8B 76 0C 8B 76 1C 8B 46 08 8B 7E 20 8B 36 38 4F 18 75 F3 59 01 D1 FF E1 60 8B 6C 24 24 8B 45|"; fast_pattern:only; classtype:shellcode-detect; sid:30472; rev:1;)

So this is a Metasploit payload for a windows message box, huh? I dumped it into a file and was hoping to see something I could disassemble, but it didn’t look like it was going to be that easy :/

$ file shellcode
shellcode: data

So then I decided to do it the hard way, by hand!

$ strings shellcode
;|$(u
hll Ah32.dhuser0
hroX hdMichTren1
h!}X hgainhsMeAh!ItIhherehlloThF{HehTMCT1
RSQR

Let’s split this out a bit…h!}X hgainhsMeAh!ItIhherehlloThF{HehTMCT1 looks like all the h are gonna be split between words. So, I started piecing it together that every time an h ocurred, it was time for a new split in the string. There was an h after every 4 characters! Now we have something like this:

!}X
gain
sMeA
!ItI
here
lloT
F{He
TMCT
1

I think that 1 and X are junk indicating the front and end of the word or something, so now we can see that this is sorta the flag, but backwards. If we concatenate to the end of the string in order from bottom to top, we get the flag: TMCTF{HelloThere!ItIsMeAgain!}

I definitely need to brush up on my powershell…

~Joel