Analysis of ShadowHammer ASUS Attack First Stage Payload

Posted on 28 March 2019 by Alex Davies and Matt Hillman

Kaspersky recently published an advisory about a supply chain attack dubbed “Operation ShadowHammer.” [1] The advisory describes how attackers compromised ASUS in order to distribute targeted malware via the ASUS Live Update Utility. This post provides an analysis of the first stage payloads used in this attack.



On March 25th 2019, Kaspersky released this high level advisory ( describing the attack against ASUS:


“In January 2019, we discovered a sophisticated supply chain attack involving the ASUS Live Update Utility. The attack took place between June and November 2018 and according to our telemetry, it affected a large number of users.…
The goal of the attack was to surgically target an unknown pool of users, which were identified by their network adapters’ MAC addresses. To achieve this, the attackers had hardcoded a list of MAC addresses in the trojanized samples and this list was used to identify the actual intended targets of this massive operation”


The original advisory contains lots more useful information, but technical details were limited at this early stage. To learn more about the attack we decided to investigate the payloads further.


History of Activity

The Kaspersky post references a zip file that is a copy of the ASUS Live Update Utility. Inside this zip file were three files, two MSIs, and a file called Setup.exe. By reviewing the history of these files on VirusTotal and examining the files themselves it was confirmed that shellcode had been inserted within the legitimate Setup.exe and the code modified to redirect execution.


We analysed historic samples from VirusTotal to gain a better understanding of the attacker's actions over time. Kaspersky reported that this attack ran from June to November 2018, this appeared to be true based on the samples submitted to VirusTotal. The first malicious sample can be seen on the 29th June 2018 and the most recent on 17th November 2018.

 ShadowHammer ASUS VirusTotal Sample

A high-level analysis of these samples found that at least two different backdoor variants were deployed. From June to September the attackers used an unencoded payload along with a patched WinMain to redirect execution.

Older Unencoded ShadowHammer Payload

From September onwards a stealthier backdoor was deployed, that included an obfuscated shellcode payload and decoder with execution via the function _crtExitProcess. All samples were also found to use the same C2 channel involving asushotfix[.]com which was first registered on 5th May 2018 with an IP address 141.105.71[.]116 located in Russia.


Pivoting on this ip address the following additional domains were also found:


Domain First Seen
host2[.]infoyoushouldknow[.]biz 2013-04-27
nano2[.]baeflix[.]xyz 2016-03-24
asushotfix[.]com 2018-05-22
www[.]asushotfix[.]com 2018-07-13
homeabcd[.]com 2018-09-05
simplexoj[.]com 2018-09-11


It is unclear what role these domains played however there is a strong possibility they were also used in the ASUS attack or by the same threat group in other attacks.


In the next sections we’ll take a deeper dive into the sample referenced by Kaspersky MD5:55a7aa5f0e52ba4d78c145811c830107 which included the obfuscated payload.


Loading the Shellcode

At a high level the Setup.exe binary appeared to be a legitimate file. It was signed, meta information matched legitimate files and the majority of the code matched other legitimate setup files. However, when comparing a legitimate Setup.exe with the malicious one we find the code has been patched to divert execution from _crtCoreExitProcess to a new function.

 ShadowHammer _crtCoreExitProcess


This new function (which we renamed to drop_shellcode) contains the code to extract, decode and execute the embedded payload. By placing the diversion at the end of the Setup.exe file right before the ExitProcess this will ensure the legitimate file runs as expected reducing the chance of discovery.


Investigating the shellcode dropping function, we find that it begins by allocating memory within the Setup.exe process with a VirtualAlloc call, then copies embedded shellcode into the allocated memory:

 ShadowHammer Shellcode Stage 1 VirtualAlloc


Interestingly this first step only copies the first 16 bytes of the payload into memory before decoding them. These bytes actually contain the size of the payload which is then passed to a second VirtualAlloc call. The main shellcode is then written, decoded and executed.


The decoding routine won’t be analysed here, but similar code has been used by Winnti previously.


Analysing the Shellcode

 According to our analysis so far, the shellcode performs the following actions:


1. Resolves library functions it needs to call later.


a. First kernel32’s base address is found by traversing structures in the PEB and matching the module name by checking for the k, l and dot (.) characters.


b. The modules PE table is parsed to find the export table.


c. Functions hashed with a custom function, and matched by iterating through each export.


d. Functions in other modules are found in the same way, but with the help of LoadLibraryExW to get the base address; this function is one of the first things located in kernel32 at the start.


2. MAC addresses are found from the machine by calling IPHLPAPI.GetAdaptersAddresses.


3. The MAC addresses are hashed with MD5.


4. The MD5 hashes are compared against a hardcoded list.


a. If no match is found, a mysterious IDX file is dropped to disk.


5. If a MAC address matches, a second stage payload is downloaded from a URL using a proxy aware API call. This goes directly into RWX memory and is called.


More details of each of these steps follows below.


Function Resolution

The shellcode starts by locating some library functions that it wants to use. This is broadly a two step process, first looking for LoadLibraryExW and GetProcAddress from kernel32.dll, before resolving further functions from a number of DLLs later, armed with the address of LoadLibraryExW to use on the second stage.


For the first step, the base address of kernel32.dll is required. To find this, the Thread Information Block (TIB) is used to navigate structures and ultimately locate InInitializationOrderModuleList which contains a list of loaded modules in the process.


The structures queried to get here are:

 TIB -> PEB -> Ldr -> InInitializationOrderModuleList

In fact, InInitializationOrderModuleList is of type _LIST_ENTRY, which is a doubly-linked list, and its “Flink” (or forward link) is followed to traverse this list of modules. Each entry includes a BaseDllName field, and this field is checked in each entry to see if it matches kernel32.dll.


But in the spirit of obfuscation they do not directly check if the name is “kernel32.dll”. Instead, they check for the presence of the k, l, and dot (.) in the appropriate locations in the string (checking each letter twice, once for lower case and again for upper case). And in fact, they only check the first byte of each 2-byte Unicode character, which works in practice but is certainly not the official way to compare Unicode characters.


This whole process can be seen in the commented code below:

ShadowHammer find kernel32 from PEB

Once kernel32.dll’s entry is found, its DllBase field can be read, giving the base address of the module. This is used with a function in the shellcode that accepts a module base address and a custom hash value for a function name. This function parses the PE header from the module in memory to locate the exports table. It then iterates through each export and runs a simple custom hash-like function on the name. When the matching hash value is found, the target function has been located in the export table, without needing to include the function name directly in the code. The address of the function is saved from the export table for later use.


This export table searching is shown commented below, with the hash code in the grey block:

ShadowHammer finding function by hashed export name

The function resolution's second step uses the same shellcode routine to look for hashed function names in the export table. But as it needs to call several other DLLs it uses LoadLibraryExW which it got in the first step to get the base address of the modules.


Below is where all the other hash values for function names are found in code, commented with the module and function name they correspond to:

ShadowHammer function name hashes resolved


These function addresses are saved in a structure that the rest of the code often accesses via a register base pointer. To help see what function is being called you can use the following offsets:













































Knowing these offsets and defining them makes the code a lot more readable. We go from this:

ShadowHammer function call with no context

To this:

ShadowHammer function call with context

In case anyone finds it useful, some Python code to help produce these hashes and find matches against real function names is provided here (slightly abbreviated):


import numpy

# We expect, and require, that int_scalars overflow occurs, so ignore

find_hashes = [
0x431A42C9, 0x0C2CBC15A, ... function hashes ...

names = [ ... list of exported functions in target DLLs ... ]

hashes_2s_compliment = {}
for hash in find_hashes:
twoscomp = hash
if twoscomp >= 1<<31: twoscomp -= 1<<32
hashes_2s_compliment[twoscomp] = hash

mul_by = numpy.int32(0x21)
for name in names:
name_hash = numpy.int32(0)
for char in name:
name_hash = name_hash * mul_by
name_hash += numpy.int32(ord(char))
if name_hash in hashes_2s_compliment:
print('{}: {}'.format(hex(hashes_2s_compliment[name_hash]), name))


MAC Addresses

Armed with these functions the shellcode continues its work, moving on to the MAC validation phase. Here we can see it getting the MD5 hash of MAC addresses on the machine by calling a function within the shellcode we have called get_macs_and_md5. This is called twice. The first time gets the number of MAC addresses to help it allocate the right amount of memory to store all the MD5 hashes. The second time it actually generates and stores the MD5 hashes.

 ShadowHammer getting machine MAC addresses2


MAC addresses are obtained by calling GetAdaptersAddresses with AF_UNSPEC to get all interfaces.

ShadowHammer GetAdaptersAddresses call


And the actual MD5 calls:

ShadowHammer MD5 calls

These MD5 hashes are then checked against a set of hashes hardcoded into the shellcode like in the example below:

ShadowHammer hardcoded MAC address MD5 

 This shows the branches taken depending on whether there was a MAC address match or not, right at the end of the entry function in the shellcode:

ShadowHammer branches depending on MAC address match

Stage 2 Payload Download and Execute

If there is a MAC address match, the shellcode proceeds to download a second stage from the internet. The URL used for this stage is found hardcoded as a set of constant values, which are little-endian, so the string fragments look backwards when forced to display as ASCII below:

ShadowHammer stage 2 URL


Which gives the URL:


The URL is opened with a proxy-aware function:

 ShadowHammer proxy aware URL open


Data is downloaded from the URL directly into a memory region allocated read/write/execute, and finally the stage 2 code is called:

ShadowHammer stage 2 download into RWX memory

At the time of analysis the second stage payload was no longer available from the callback URL. It is likely further information will become available over the coming weeks.


Detection of ShadowHammer

There are several indicators defensive teams can hunt for including the hashes of files, dropped files, and network-based IOCs.


SHA-256 (along with the month it was seen)


  • bca9583263f92c55ba191140668d8299ef6b760a1e940bddb0a7580ce68fef82 June
  • 6aedfef62e7a8ab7b8ab3ff57708a55afa1a2a6765f86d581bc99c738a68fc74 July
  • ac0711afee5a157d084251f3443a40965fc63c57955e3a241df866cfc7315223 July
  • e78e8d384312b887c01229a69b24cf201e94997d975312abf6486b3363405e9d Sep
  • 736bda643291c6d2785ebd0c7be1c31568e7fa2cfcabff3bd76e67039b71d0a8 Sep
  • 9bac5ef9afbfd4cd71634852a46555f0d0720b8c6f0b94e19b1778940edf58f6 Sep
  • 9a72f971944fcb7a143017bc5c6c2db913bbb59f923110198ebd5a78809ea5fc Oct
  • 357632ee16707502ddb74497748af0ec1dec841a5460162cb036cfbf3901ac6f Oct
  • 9842b08e0391f3fe11b3e73ca8fa97f0a20f90b09c83086ad0846d81c8819713 Nov


Dropped Files

For systems not matching the MAC address filter, an idx file is created two levels up relative to the Setup.exe current directory, for example:


  • C:\Program Files (x86)\ASUS\ASUS Live Update\Temp\6\Setup.exe
  • C:\Program Files (x86)\ASUS\ASUS Live Update\idx.ini


  • host2[.]infoyoushouldknow[.]biz
  • nano2[.]baeflix[.]xyz
  • asushotfix[.]com
  • www[.]asushotfix[.]com
  • homeabcd[.]com
  • simplexoj[.]com
  • 141.105.71[.]116
  • hxxps://asushotfix[.]com/logo[.]jpg
  • hxxps://asushotfix[.]com/logo2[.]jpg 


PDB Indicator
  • June sample - D:\C++\AsusShellCode\Release\AsusShellCode.pdb



The ShadowHammer attack is a great example of a supply chain attack where a threat actor abused a trusted update utility to distribute malware across the globe in a targeted way. As mentioned in the Kaspersky analysis the attack shares similarities with those performed by the BARIUM group suggesting a continuation and even escalation in the scale and sophistication of their operations.


From a defensive perspective, the significant time it took to uncover this attack demonstrates that the actions taken in the first stage of the incident are stealthy and difficult to detect. But it is quite possible that noisier indicators will be discovered as more information about the second stage payload is released.


To provide support for real-time and retrospective detection, it is strongly recommended that organisations deploy endpoint monitoring and response with an EDR, agent as this can give the visibility and control needed to combat such threats.