The technical analysis is related to the TAU-TIN for the same malware which can be located in this post. 

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


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.


In this sample the overlay data is split into the following sections:


Figure 2: Overlay structure

Overlay Offset





Global variables


Encrypted strings


Encrypted imports


Encrypted C2 configuration

Table 2: Overlay structure offsets


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


def main():

    with open(sys.argv[1], ‘rb’) as f:, os.SEEK_END)

        byte =

        overlay_length = struct.unpack(‘<I’, byte[4:8])[0]

        overlay_offset = – overlay_length, os.SEEK_END)

        overlay =

        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(‘Overlay Offset: %s’ % hex(data_offset + 0x334))


        # 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(‘Overlay Offset: %s’ % hex(data_offset + import_offset))


        # 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(‘C2 Config’)

        print(‘Overlay Offset: %s’ % hex(config))


if __name__ == ‘__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.


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. 


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.






Defense Evasion

Deobfuscate/Decode Files or Information



System Information Discovery



System Network Configuration Discovery



System Owner/User Discovery


Command And Control

Commonly Used Port


Command And Control

Custom Command and Control Protocol


Command And Control

Standard Cryptographic Protocol


Indicators of Compromise (IOCs)






Crosswalk 32-bit executable



Crosswalk 32-bit executable



Crosswalk 64-bit executable



Crosswalk 64-bit DLL






Yara Rules

rule apt41_crosswalk_x86_2019_Q3 : TAU CN APT



        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 = “

        rule_version = 1

        yara_version = “3.10.0”

        Confidence = “Prod”

        Priority = “Medium”

        TLP = “White”

        exemplar_hashes = “300519fa1af5c36371ab438405eb641f184bd2f491bdf24f04e5ca9b86d1b39c, db866ef07dc1f2e1df1e6542323bc672dd245d88c0ee91ce0bd3da2c95aedf68”


        // call    $+5

        // pop     ebx

        // sub     ebx, 5

        $b1 = { e8 00 00 00 00 5b 83 eb 05}


        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



rule apt41_crosswalk_x64_2019_Q3 : TAU CN APT



        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 = “

        rule_version = 1

        yara_version = “3.10.0”

        Confidence = “Prod”

        Priority = “Medium”

        TLP = “White”

        exemplar_hashes = “f6d0cd5b6aa6ccea3ba3cb63b26420f6579d4a07164944e1013e093c521c5687, 9d0ac935b9e0d6c86fc2904477638af6e4b68d020c2956912e5109cc6219c08f”


        // mov     rax, [rsp+0]

        // retn

        $b1 = { 48 8b 04 24 }


        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