Threat Analysis Unit

CB Threat Analysis Unit: Technical Analysis of “Crosswalk”

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.

image1.png

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:

image4.png

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:

  1. Allocate a buffer to hold 72 bytes
  2. Zero fill the buffer
  3. Copy the generated UUID into the buffer
  4. Generate a MD5 hash of the buffer
  5. Use CryptDeriveKey to create a AES-128 bit key
  6. Encrypt the UUID using the new AES-128 bit key
  7. Allocate a second buffer to hold 144 bytes
  8. Zero fill the second buffer
  9. Copy the encrypted UUID into the second buffer
  10. 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.

image3.png

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:

image2.png

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
}