HTB Forensics Challenge: Windows Infinity Edge
Moving away from media reviews this post is a writeup of how I solved the Windows Infinity Edge (WIE) Capture the Flag (CTF) challenge hosted by Hack The Box (HTB). This is a forensics related question, particularly pertaining to incident response. Unfortunately, I did not write this up as I solved it, meaning there will likely be leaps in logic that are not necessarily clear from what is shown. Hopefully in the future, I will keep notes as I work through similar problems.
This challenge is one of the most enjoyable ones that I have done. It does not employ cheap tricks or obscure techniques (like utilizing exceptionally rare forms of steganography like hiding data in audio). Despite this, the challenge was still generously time-consuming due to the amount of layers of puzzles and persistence required to reach the flag at the core.
Normally it is only accepted to post a write-up if it is protected until the challenge expires by some secret that only someone that finished the challenge could provide. However, since my audience is non-existent, this blog is not indexed, and and if I had an audience it would not be CTF-runners, I will neglect to do this.
The Challenge
From the official description of the challenge:
A motivated APT (advanced persistent threat) group has breached our company and utilized custom tooling. We’ve identified the implants on compromised systems and remediated the infection using advanced AntiVirus X. However, one server seems clean but has been exhibiting suspicious traffic. Can you spot something we could have missed while cleaning this system?
Pcap
We are first given a packet capture (pcap) of traffic going to and from a server: 2019-11-12_21_00_30-EXT07.pcap
. Immediately we go into Wireshark (or the investigator’s tool of choice) to parse and browse the pcap.
There is immediately suspicious traffic that should draw the attention of any investigator: a GET
and POST
request from 109.70.100.19
to an upload page hosted on our server, /webapp/upload.aspx
.

After a delay of 32 seconds, the attack begins with the post request being formed. To make this easier to read, the chain of requests can be viewed as a TCP stream in Wireshark. This lets us move up a level of abstraction, allowing us to focus on the human-readable and fully formed data rather than fragments of it from packet to packet.

shell.aspx
We can see what appears to be a standard, simple upload page likely intended for internal use returned by the server in blue. Looking further in the TCP stream we can see that a C# file named shell.aspx
was uploaded. This is already a smoking gun so early into the investigation. We now know how the server was compromised. Future requests can be seen made to this uploaded shell page, another disaster for the security team managing this server besides the existence of the upload page in the first place. Unfortunately, the code making up this shell.aspx
is unfriendly to human eyes, so we have to ply at it to figure out exactly what this code does. Incident response does not stop at identifying the threat, but figuring out how it happened, how it works, how to fix it, and how to prevent it from happening again.
1void Page_Load(object sender, EventArgs e)
2{
3 string p = "4d65bdbad183f00203b1e80cf96fba549663dabeab12fab153a921b346975cdd";
4 string r = Request.Form["data"];
5 ulong[] int_arr = {37342,815148223, ...};
6 ulong[] int_arr_r = {169589,341464, ...};
7 for (int i=0; i<int_arr.Length; i++) int_arr[i] = (int_arr[i] * 345300 + int_arr_r[i]);
8 byte[] a = new byte[int_arr.Length * 8];
9 System.Buffer.BlockCopy(int_arr, 0, a, 0, a.Length);
10 Assembly aS = Assembly.Load(a);
11 object o = aS.CreateInstance("SharPy");
12 MethodInfo mi = o.GetType().GetMethod("Run");
13 object[] iN = new object[] {r, p};
14 object oU = mi.Invoke(o, iN);
15 Response.Write(oU);
16}
before any effort is put into deobfuscating this, there are already tell-tale signs of what exactly this code is accomplishing. Due to personal heuristic, seeing SharPy sets off alarms, as I know this to be a webshell. Given that we CreateInstance()
a copy of SharPy, it is logical to assume that the byte array a
contains the binary data that constructs an assembly with the SharPy class in it (or something along those lines). In turn, looking further back we can see that a
is constructed by performing operations on int_arr
and int_arr_r
concurrently within the for
loop. This is a common technique for obfuscation, as it is hard for an antivirus to recognize the signature of an executable so thoroughly mutilated.
Continuing to analyze before attempting to deobfuscate, we can see that SharPy has a Run
function that is alled with the parameters p
and r
. r
can be seen to be the request data, which allows for the attacker to send dynamic instructions to the shell. p
is an interesting variable because it is just a string of hex characters. In addition to it being named p
, this likely could be intended to be a passcode or encryption key, but the exact function remains to be seen.
I am not a C# or ASP.NET developer, so the exact inner workings of many functions are not known to me. Luckily, everything in this code is clear enough to facilitate putting everything together to slightly deobfuscate to the following somewhat readable code.
1void Page_Load(object sender, EventArgs e)
2{
3 string password_questionmark = "4d65bdbad183f00203b1e80cf96fba549663dabeab12fab153a921b346975cdd";
4 string request_data = Request.Form["data"];
5
6 ulong[] data_half_a = {37342,815148223, ...};
7 ulong[] data_half_b = {169589,341464, ...};
8
9 // Transform the meaningless data halves into one coherent a whole representation of an assembly
10 for (int i = 0; i < data_half_a.Length; i++) {
11 data_half_a[i] = (data_half_a[i] * 345300 + data_half_b[i]);
12 }
13
14 byte[] array_of_bits = new byte[data_half_a.Length * 8];
15 System.Buffer.BlockCopy(data_half_a, 0, a, 0, array_of_bits.Length); // Copy bytes from data to array_of_bits
16
17 // Load the array_of_bits as an assembly
18 Assembly aS = Assembly.Load(array_of_bits);
19
20 // Create a SharPy class from this assembly
21 object SharPy = aS.CreateInstance("SharPy");
22
23 // Select the Run function from SharPy
24 MethodInfo Run = SharPy.GetType().GetMethod("Run");
25
26 // Execute Run with parameters request_data and password_questionmark
27 object[] Input = new object[] {request_data, password_questionmark};
28 object SharPy_Output = Run.Invoke(SharPy, Input);
29
30 // Write the output of Run() to the request sender
31 Response.Write(SharPy_Output);
32}
SharPy
At this point, the next step I took was to actually create the SharPy assembly using the data provided so that I could get a glance into how that part of the payload worked. It is extremely important to note at this point that the resulting assembly is presumed to be malware, and should both never be ran, and should not ever be put on a real host. For this purpose, all work was done in a virtual machine.
⚠️ Read above!! ⚠️
This accomplish this, I essentially copied parts of the above code into an online tool for compiling or running .NET code. .NET Fiddle was extremely useful, as I did not have to go through the labors of getting my virtual machine to compile and run .NET code (which, for me, has been a problem in the past).

Executing the code will print out a completely non-threatening Base64 string which I copied over to CyberChef to handle. CyberChef is an amazing tool for quickly handling, parsing, and transforming data, especially in ephemeral and non-persistent ways (which for the purposes of handling malware, I personally prefer). Loading a recipe to convert that copied input from Base64, we are greated with what is clearly an executable file for Windows.

An obvious step during any CTF at this point is to examine this new executable for any hints of the flag. Unfortunately, no string searches or cursory glancing at the assembly yielded any flag. However, we could get an idea of what functions and classes are called and exist within this assembly.
...
AESEnc
AESDec
.ctor
System.Runtime.CompilerServices
CompilationRelaxationsAttribute
RuntimeCompatibilityAttribute
runtime_compiler_aes
hexString
String
get_Length
Byte
Substring
System.Globalization
NumberStyles
Parse
plain
System.Security.Cryptography
Rijndael
Create
SymmetricAlgorithm
set_Key
set_IV
get_Key
get_IV
ICryptoTransform
CreateEncryptor
System.IO
MemoryStream
CryptoStream
Stream
CryptoStreamMode
Write
IDisposable
Dispose
ToArray
encrypted
CreateDecryptor
StreamReader
TextReader
ReadToEnd
System.Text
Encoding
get_UTF8
GetBytes
code
password
...
Now without having any exact idea of how the program works, we know that it utilized AES encryption. Conveniently, there is no other encryption method mentioned, and we can see various functions related to AES (set_Key, set_IV, Rijndael, etc.). Additionally, at the bottom of the excerpt shown above we see some functions, classes, and variable names related to text processing (Systen.Text, get_UTF8, etc.). Interestingly two variables (presumed to be) can be seen: code
and password
. There are a lot of heuristic things that can be taken from this to suggest that these might be the inputs of the Run
function seen previously.
Since we know this is a .NET assembly that we are examining, there are plenty of tools to thoroughly examine (and even attempt to decompile!) it. A program such as ILSpy could be used to decompile this assembly successfully to confirm the previous suspicions.

Looking to at the Run function, which is what know is called we can see that our suspicious were exactly correct. ILSpy can even do us an immense favor and reveal the IV used in the AES encryption. Additionally, it can be seen that the password is the key for the AES encryption.
1byte[] key = ConvertHexStringToByteArray(password);
2byte[] iV = new byte[16]
3{
4 105, 110, 102, 105, 110, 105, 116, 121, 95, 101,
5 100, 103, 101, 104, 116, 98
6};
Back to the Pcap
At this point, there is not much more information to clearly get from the dropped shell.aspx
and the SharPy assembly. However, we have barely scratched the surface of the pcap we started with. We can begin to see POST
requests sent to the shell.aspx
page, and thus we can examine them like before. Looking at the data within these requests, we can see Base64 encoded data being sent to and from the server. Unfortunately, this data is very obviously encrypted upon decoding. However, we already know a couple pieces of the puzzle. We know the algorithm used is AES, which is symmetric key. We also now know the key to be hard coded in the shell.aspx
page, and the IV to be hard coded in the SharPy assembly.
Key = 4d65bdbad183f00203b1e80cf96fba549663dabeab12fab153a921b346975cdd
IV =infinity_edgehtb
With this information, we can now surely tackle anything in the stream of request sent to and from the server!
To test this, I created a CyberChef recipe that would automatically decrypt a message based on our supposed parameters and make it more readable.

Starting with the first message sent, I put the Base64 string in the input for this recipe and let it bake and was greeted with one of the most beautiful things.
1using System;
2using System.IO;
3using System.Diagnostics;
4using System.Text;
5public class SharPyShell {
6 private string GetTempDirectory() {
7 string tempDirectory = "";
8 string osTempDirectory = Environment.GetEnvironmentVariable("SYSTEMROOT") + "\\" + "Temp";
9 string osPublicDirectory = Environment.GetEnvironmentVariable("Public");
10 if (Directory.Exists(osTempDirectory))
11 tempDirectory = osTempDirectory;
12 else if (Directory.Exists(osPublicDirectory))
13 tempDirectory = osPublicDirectory;
14 else tempDirectory = @"C:\Windows\Temp";
15 return tempDirectory;
16 }
17
18 public byte[] ExecRuntime() {
19 string output_func = GetTempDirectory();
20 byte[] output_func_byte = Encoding.UTF8.GetBytes(output_func);
21 return(output_func_byte);
22 }
23
24}
So the way this attack works is now fully understood:
- An attacker finds an unprotected upload page on a dynamic content webserver
- An attacker uploads a SharPy webshell implementation
- An attacker begins communicating with the webshell with encrypted commands
- These commands are C# code, which is ran by SharPy.
Now that we feel like we finally understand everything, it is time to look for the flag. We go each request and perform our decryption, satisfied with our quick programatic method of performing it. However, we get to the end and we have only found two flags. Not only that but both were red herrings! What gives? Clearly we have to go back through and more closely analyze the payloads of each script to find what is going on.
The Grind
Even with our automated decryption process there is still a lot to take in. Each payload has to be analyzed and annoying we find some themselves are even droppers for more malware (such as some used for privilege escalation). There are obfuscated scripts and plenty more binaries to put together like we did for SharPy’s assembly. This is a lot of work and would involve a lot of writing up.
Stepping through each message, we continue until we find a very suspicious message to the server. This message consists of over 600,000 characters and is clearly different from the rest sent to the server. We can see that it is uploading a file and dropping it in the temporary folder that the attackers had made previously. The next few messages append several more large dumps of data to the same file in the temporary folder. Afterwards, a very small file is written to another temporary folder.
We then find some powershell payloads being delivered to utilize that payload that was dropped. Thankfully, this payload is not neccessary to finding the flag (but I still wasted an incredible amount of time replicating it and analyzing it).
Moving forward, privilege escalation exploits are performed with the payload that was dropped. Potentially a hint from that payload is that to reconstruct it required XOR’ing the two dropped files to obtain its final form. Following privilege escalation shellcode is dropped onto the server. At this point my memory gets hazy and my will to be exact dwindles as the amount of CyberChef tabs and recipes I have running consecutively become nauseating and I begin losing track of where I am and how I got there and where the world is and what year it is.
Thankfully, the code dropping the shellcode tells us how to interpret it, and it is just Gzipped data Base64 encoded. From this, we can quickly determine the shellcode to be a rather lengthy executable. Examining the strings, we can find it to be Juicy Potato, a privilege escalation tool. Parameters to run the tool with are also enclosed when the tool is dropped, so we shift our focus to that since examining such a large and obscure tool is almost certainly a waste of time. Our impatience is rewarded with a list of commands ran after escalation. We can find a xor.k
file dropped in C:
containing xGk89_ew
.
Struggling to maintain sanity as we can feel just how close we are to the finish line, we just have to find where this XOR key is used. We get anxious as the next few requests to the server do not contain anything relevant and we are almost at the end of the pcap file. Finally we find another shellcode drop, and we hope and pray that this is it and our wild journey has come to an end. Putting this in the 5-billionth CyberChef recipe formulated, we find that it contains…
nonsense.
Panic sets in as the remainer of the communication between the server and the attacker consists of the attacker cleaning their tracks. Nothing else of relevance is mentioned and you again reach the end of the incredibly long pcap with no flag to show for it. Convinced that something must have been missed you more closely analyze the second shellcode dropped and find 8 characters that restore your faith: c:\xor.k
. This file has become your god, wholly consuming your mind and existence, rendering your sole purpose for exposing the meaning behind it. Does the key exist here in such a short file? This file is no executable! Exhausted and running out of hope, we simply decide to XOR the contents of this file with the contents of xor.k
as the key.
It doesn’t work.
Your god laughs at you.
Your defiance to the absolute and simple nature of its predominant existence has been punished.
While we sit lost in despair a seraph creeps towards us. Its ontology reverberates in our mind. A flash of white. Memories of Introduction to Programming with C play in vivid detail. The ^
symbol fills your vision in multiplicity until the start of one is undifferentiated from the end of another. The seraph leaves you with the blessing of remembering how basic bitwise operations work.
You open the file you are XOR’ing one last time.
You have no idea where the actually XOR’ed data in the file resides.
You just know it is not at the start of it.
You put your cursor at the beginning and press delete
.
delete
.
delete
.
delete
.
cMd.exe /C echo HtB{F1n4lLy_y0u_cR0ss3d_tH3_edg3!] > ...
Conclusion
This was a fantastic CTF. It had no incredibly obscure tactics and was overwhelmingly fair. It gave plenty of opportunity for experts to sort of skip ahead due to heuristic understanding of the malware dropper, but then punished them for missing small details. All-in-all, this took me an ashamedly long time to work out wholly, but it was entertaining the whole way through. I am absolutely certain that the way I worked through this and solved it was not even close to “correct” or “efficient”, but it worked so /shrug.
I kind of got really lost at the end are started trying a ton of things, most of which I’ve forgotten since this was a challenge I had done several months ago. However, at the benefit of me (and at a cost to you), this allowed for the final parts of the CTF to become a narrative experience rather than an exact one.
Thank you for reading :)