FireEye recently reported on APT41, a Chinese state sponsored espionage group. The group has been documented as targeting healthcare, high-tech, and telecommunications companies for traditional corporate espionage purposes. Additionally this group has also targeted companies in the video game industry for financial gain. Crosswalk is a modular backdoor application that gathers system information and is capable of executing shell code in response to C2 messages.
Technical Analysis
This technical analysis focuses on the 32-bit version of the Crosswalk malware. The metadata for the sample analyzed is in the table below.
File Name : 300519fa1af5c36371ab438405eb641f184bd2f491bdf24f04e5ca9b86d1b39c File Size : 116,285 bytes MD5 : 11c476c7db48f1b02ecc5ae93f63e46d SHA1 : afbdd49d2d0d056643a3090769f0ae9f77ad2469 SHA256 : 300519fa1af5c36371ab438405eb641f184bd2f491bdf24f04e5ca9b86d1b39c SSDEEP: : 3072:477S8sKLvJXPoJ/2WM14GeWkWW8D6JWdR/Nbnv4:AKc9PW/pWdbbnv4 Compiled Time : Wed May 31 06:53:47 2017 UTC PE Sections (5) : Name Size MD5 .text 65,024 81a8f57e4cb4f410a5b9ce5835113520 .rdata 26,112 f141ba254c99448053acecc42a385300 .data 2,560 c5a45cd4d468c3863d68810686021f03 .rsrc 512 889ceaed2190c978cc08097da65233dc .reloc 4,096 1a8f7770da80e190f2b6d96d234bb05a + 0x18400 16,957 4629b9455407d97f4cc1f83e0ad550b0 None Magic : PE32 executable (console) Intel 80386, for MS Windows |
Table 1: Crosswalk 32-bit metadata
Loading
Looking at the metadata we can tell that the binary has an overlay. This means there is extra data appended to the end of the file outside of what the PE header lists.
The last section ends at 0x18400 but the full size of the file is 0x1c63d bytes. When the malware starts, it begins by opening a handle to its own executable. It then seeks to 12 bytes from the end of the file. It reads this data into a newly allocated memory buffer. This data can be seen in the following hex dump.
Figure 1: Crosswalk 32-bit end of file
The structure of the data is as follows:
struct trailer {
uint32_t trailer_size;
uint32_t overlay_size;
uint32_t zero;
};
The loading code will then subtract the trailerOffset and the trailer.overlay_size values from the file size of the binary and seek to that location. From there a new thread is created starting execution at the beginning of the overlay data.
Initialization
In this sample the overlay data is split into the following sections:
Figure 2: Overlay structure
Overlay Offset | Type |
0x0000 | Code |
0x3416 | Global variables |
0x374a | Encrypted strings |
0x3c62 | Encrypted imports |
0x41b1 | Encrypted C2 configuration |
Table 2: Overlay structure offsets
Decryption
The overlay code itself is not packed or encrypted but the strings, imports and C2 configuration are encoded with a simple one byte XOR key. Each of the three encrypted sections use a different XOR key. All three of the keys can be found near the start of the global variables. The following code can be used to decrypt and display the encrypted information.
import hexdump import os import struct import sys TRAILER_LENGTH = 0xc def main(): with open(sys.argv[1], 'rb') as f: f.seek(-TRAILER_LENGTH, os.SEEK_END) byte = f.read(TRAILER_LENGTH) overlay_length = struct.unpack('<I', byte[4:8])[0] overlay_offset = f.seek(-TRAILER_LENGTH - overlay_length, os.SEEK_END) overlay = f.read(overlay_length) data_offset = struct.unpack('<I', overlay[0xa:0xe])[0] key1 = overlay[data_offset + 0x10] key2 = overlay[data_offset + 0x11] key3 = overlay[data_offset + 0x12] # Strings data_length = struct.unpack('<I', overlay[data_offset + 0x334:data_offset + 0x338])[0] data_decoded = '' for i in range(0, data_length): data_decoded += chr(overlay[data_offset + 0x338 + i] ^ key3) print('') print('Strings') print('Overlay Offset: %s' % hex(data_offset + 0x334)) hexdump.hexdump(data_decoded.encode()) # Imports import_offset = struct.unpack('<I', overlay[data_offset + 0x20:data_offset + 0x24])[0] import_length = struct.unpack('<I', overlay[data_offset + 0x30:data_offset + 0x34])[0] imports_decoded = '' for i in range(0, import_length): imports_decoded += chr(overlay[data_offset + import_offset + i] ^ key1) print('') print('Imports') print('Overlay Offset: %s' % hex(data_offset + import_offset)) hexdump.hexdump(imports_decoded.encode()) # C2 Config config = data_offset + import_offset + import_length config_decoded = '' for i in range(0, 56 + 72): config_decoded += chr(overlay[config + i] ^ key2) print('') print('C2 Config') print('Overlay Offset: %s' % hex(config)) hexdump.hexdump(config_decoded.encode()) if __name__ == '__main__': main() |
Table 3: Decryption sample code
After decrypting the data and resolving imports the malware proceeds to gather information about the host it’s running on. The following information is retrieved using standard win32 API calls:
- A generated UUID
- Local IP address
- Windows version number
- User name
- Computer name
Key Generation
The generated UUID is then used to generate a client session key for the malware. The session key is derived by doing the following:
- Allocate a buffer to hold 72 bytes
- Zero fill the buffer
- Copy the generated UUID into the buffer
- Generate a MD5 hash of the buffer
- Use CryptDeriveKey to create a AES-128 bit key
- Encrypt the UUID using the new AES-128 bit key
- Allocate a second buffer to hold 144 bytes
- Zero fill the second buffer
- Copy the encrypted UUID into the second buffer
- Use CryptDeriveKey to create a final AES-128 bit client session key for the malware
After the client session key is initialized the code starts up a thread to handle network communication.
Network Communication
The network thread first attempts to connect to the C2 server defined in the encrypted sections of the overlay. If it can connect then it will send out a beacon to the C2 server sending across the clients generated UUID information so that the server can derive the appropriate session key.
Figure 3: Malware beacon data
The network data is sent out on the port configured in the encrypted C2 configuration. In the case of this sample it’s port 443. Even though it’s using a standard HTTPS port the malware doesn’t attempt to use the HTTPS protocol at all.
The first 5 bytes in the beacon packet are always the same. After that comes a 32-bit little endian integer representing the command type and then another 32-bit little endian integer representing the length of the following command data. The beacon above has the following message information:
Command: 0x65
Length: 0xd8 UUID: 09965677-B0F7-478C-6736BA029FFEC8 |
Table 4: Beacon data
The rest of the data in the beacon packet seems to be used by the server to verify that it can derive the clients session key correctly. After the initial data is the MD5 hash of the UUID and then following that is the encrypted version of the UUID. The UUID is the only information that the C2 server actually needs to derive the clients session key.
Receive Thread
The receive thread sits in a loop waiting on responses from the C2 server. Below you can see the majority of the command handling code:
Figure 4: Receive thread command handling
The command handling code expects to receive command 0x64 before any other commands so that it can initialize the server’s session key. Command 0x64 seems to be the same sort of data as the beacon packet described above. It’s a way for the server to send across it’s unique UUID so that the client can derive a server session key. After this command is sent all further command data is encrypted. When the client sends out packets it encrypts the data with the server session key and when it’s reading data from the server it decrypts it with it’s client session key.
Commands
TAU continues to analyze this sample to determine all of the command structure. However the commands that are provided highlight the malware’s capabilities.
Command 0x64
This command indicates that the server is sending over it’s session information so that the client can derive a server session key. After the servers session key is generated the client sends back a response of command 0x6f along with the information about the host that was collected during initialization.
Command 0x6e
There is a case statement for this command but it doesn’t do anything. Based on the relation of the commands received and values sent back this would seem to be a request for host information.
Command 0x78
Command 0x78 attempts to lookup a plugin entry and execute it from the list of initialized plugins. If the specific plugin ID is not found then a new entry in the plugin list is created for later initialization by the 0x7a command. After it completes the client sends back a response of command 0x79.
Command 0x7a
This command allows the C2 server to send shell code plugins to the client. The data structure for the plugin shellcode is the same as the main overlay data. The plugin is sent to the client and then initialized. The same dynamic import resolution process that was used during the main applications initialization is used during plugin initialization. If the plugin was successfully initialized then the client sends back command 0x7b to acknowledge that it finished.
Command 0x82
This command allows the C2 server to clear one or multiple plugins. If the plugin ID specified is 0x78 then all plugin entries will be cleared.
Command 0xa0
When the client receives this command it will shutdown the network thread.
Heartbeat Thread
If the receive thread is running and session keys have been generated then every ten seconds the heartbeat thread sends out a packet to the server with the command set to 0x8d. If the receive thread isn’t running then the client sends command 0xa1 to the server. Since command 0xa0 is the server asking the client to stop the network thread, command 0xa1 might simply be a notification to the server that network communication is not currently running.
MITRE ATT&CK TIDs
TID | Tactic | Description |
T1140 | Defense Evasion | Deobfuscate/Decode Files or Information |
T1082 | Discovery | System Information Discovery |
T1016 | Discovery | System Network Configuration Discovery |
T1033 | Discovery | System Owner/User Discovery |
T1043 | Command And Control | Commonly Used Port |
T1094 | Command And Control | Custom Command and Control Protocol |
T1032 | Command And Control | Standard Cryptographic Protocol |
Indicators of Compromise (IOCs)
Indicator | Type | Context |
300519fa1af5c36371ab438405eb641f184bd2f491bdf24f04e5ca9b86d1b39c | SHA256 | Crosswalk 32-bit executable |
db866ef07dc1f2e1df1e6542323bc672dd245d88c0ee91ce0bd3da2c95aedf68 | SHA256 | Crosswalk 32-bit executable |
f6d0cd5b6aa6ccea3ba3cb63b26420f6579d4a07164944e1013e093c521c5687 | SHA256 | Crosswalk 64-bit executable |
9d0ac935b9e0d6c86fc2904477638af6e4b68d020c2956912e5109cc6219c08f | SHA256 | Crosswalk 64-bit DLL |
160.16.85.174 | TCP/443 | C2 |
45.32.226.32 | TCP/443 | C2 |
Yara Rules
rule apt41_crosswalk_x86_2019_Q3 : TAU CN APT { meta: author = "CarbonBlack Threat Research" // sknight date = "2019-Aug-21" Validity = 10 severity = 10 TID = "T1016, T1032, T1033, T1043, T1082, T1094, T1140" description = "APT41 Crosswalk" link = "https://twitter.com/MrDanPerez/status/1159459082534825986" rule_version = 1 yara_version = "3.10.0" Confidence = "Prod" Priority = "Medium" TLP = "White" exemplar_hashes = "300519fa1af5c36371ab438405eb641f184bd2f491bdf24f04e5ca9b86d1b39c, db866ef07dc1f2e1df1e6542323bc672dd245d88c0ee91ce0bd3da2c95aedf68" strings: // call $+5 // pop ebx // sub ebx, 5 $b1 = { e8 00 00 00 00 5b 83 eb 05} condition: uint16(0) == 0x5A4D and // MZ header check uint32(filesize-0xc) == 0xc and uint32(filesize-0x8) > 0x100 and uint32(filesize-0x8) < filesize and filesize < 500KB and $b1 } rule apt41_crosswalk_x64_2019_Q3 : TAU CN APT { meta: author = "CarbonBlack Threat Research" // sknight date = "2019-Aug-21" Validity = 10 severity = 10 TID = "T1016, T1032, T1033, T1043, T1082, T1094, T1140" description = "APT41 Crosswalk" link = "https://twitter.com/MrDanPerez/status/1159459082534825986" rule_version = 1 yara_version = "3.10.0" Confidence = "Prod" Priority = "Medium" TLP = "White" exemplar_hashes = "f6d0cd5b6aa6ccea3ba3cb63b26420f6579d4a07164944e1013e093c521c5687, 9d0ac935b9e0d6c86fc2904477638af6e4b68d020c2956912e5109cc6219c08f" strings: // mov rax, [rsp+0] // retn $b1 = { 48 8b 04 24 } condition: uint16(0) == 0x5A4D and // MZ header check uint32(filesize-0x20) == 0x20 and uint32(filesize-0x1c) > 0x100 and uint32(filesize-0x1c) < filesize and filesize < 500KB and $b1 } |