Emotet: signature-based evasion & malleable executable

Analysis by Daniele Capponi Cyber Security Analyst Swascan

We recently came across an interesting finding concerning Emotet’s infection kill-chain, which usually starts with the phishing email followed by the download of an excel macro then its execution, which acts as the DLL dropper and finally leads to malware infection.

Signature

We had a look inside the malicious excel document. The dropper had an incorporated list of domains to grab the malware from, in order to increase the download chances if one or more of them were to be eventually unreachable.

Here are the URLs we found inside the document’s  xl/sharedStrings.xml  file:

Almost every one of them was reachable and serving the executable.

Once downloaded the sample we started to look for as many IOCs as possible, so we noticed it had a unique hash signature. We fed it to Virus Total, which took its time to digest the sample (it was a new one!) and then gave us a malicious score of 49 out of 69, so no worries.

The interesting part was actually the methodology implemented by threat actors to evade the signature-based detection. We double checked every one of them to catch indicators, and we noticed that every time we downloaded the DLL, it had a different hash so we grubbed two more, all of which had the same size (#IOCs).

(synth3sis㉿sthsvm)-[~/Emotet]$ sha256sum *.dll
d676b92e998dd3c2dfb6e51f8218019347c8b77d621fbf105fae3f7d7e2cb829  X7Nwjq.dll
7a11140df855a04f149152da55819c4a18ded6eff3eaf09cb80332cf24343b42  FHTz6WZe2JM.dll
d91fa3bfa7ea9265375b4f89100c54900f083daa115f025203121091a950afc6  B5tcHyPEujNbv.dll

(synth3sis㉿sthsvm)-[~/Emotet]$ du -sb *.dll
626688  X7Nwjq.dll
626688  FHTz6WZe2JM.dll
626688  B5tcHyPEujNbv.dll

Then, we analyzed the differences among their HEX dump and here is what we found:

(synth3sis㉿sthsvm)-[~/Emotet]$ diff X7Nwjq.dll.xxd FHTz6WZe2JM.dll.xxd
20799,20801c20799,20801
< 00052430  00 00 00 00 00 00 00 00  31 00 00 00 ee b1 51 09  |........1.....Q.|
< 00052440  04 2b d8 a4 bf cf a8 b7  b0 45 5d 86 a3 60 4a 6b  |.+.......E]..`Jk|
< 00052450  63 54 45 82 cf 33 3e eb  0b ab 0f b8 00 00 00 00  |cTE..3>.........|
---
> 00052430  00 00 00 00 00 00 00 00  31 00 00 00 4e f6 a8 65  |........1...N..e|
> 00052440  69 93 98 3f a7 fc 31 0b  1f c6 7e 58 96 45 78 6c  |i..?..1...~X.Exl|
> 00052450  b7 5a 62 8b da 4b a2 a5  df 56 f0 22 00 00 00 00  |.Zb..K...V."....|

	# B5tcHyPEujNbv.dll:
> 00052430  00 00 00 00 00 00 00 00  31 00 00 00 48 bb 9a 0f  |........1...H...|
> 00052440  54 44 07 cb f5 4f c6 b1  e6 e8 3c 22 22 37 cd fb  |TD...O....<""7..|
> 00052450  99 8e 6a d4 3a 20 f6 24  c8 bd 12 c8 00 00 00 00  |..j.: .$........|

It appears that at offset  5243C  they show variations, more precisely exactly 32 bytes vary (until  5245B  included), which at first sight it seemed randomly generated and lacking relevant information.

Analyzing the PE’s entropy, as expected,  .text  and  .rdata  sections are packed:

Executable’ sequence

We wondered what was the technical mechanism behind this interesting behaviour and how could be implemented, so we started doing some research and we came across the concept of Cobalt Strike Malleable PEs which gave me some insight on C2 agents and malleable PE configurations.

Regardless, my goal was to write a piece of code to give me proof on how we could modify some section of the package in response to a web request and eventually embed configurations in it. In other words, without having to re-compile the source code every time. The thing about this stuff was that potentially this PE, once deployed could have been sent all over the network without being detected by signature-based detection systems, such as basic AVs or NIDS/NIPS of some sort.

At this point we did further research and studies just to be able to reproduce this mechanism by writing the right program and create our basic proof of concept.

So, we wrote a C++ sample which implements a static pre-allocated buffer used to keep track of the sequence to be later editable. We used the same  X7Nwjq.dll  byte array so we disposed exactly 32 bytes for it.

What it does is:

  1. Allocate a 32 static buffer
  2. Dump the sequence to  sequence.bin  file
  3. Print out confirmation

Here is  seqdumper.cpp  code:

#include <iostream>
#include <fstream>

static unsigned char sequence_buf[32]  = {
    0xEE, 0xB1, 0x51, 0x09, 0x04, 0x2B, 0xD8, 0xA4, 
    0xBF, 0xCF, 0xA8, 0xB7, 0xB0, 0x45, 0x5D, 0x86,
    0xA3, 0x60, 0x4A, 0x6B, 0x63, 0x54, 0x45, 0x82,
    0xCF, 0x33, 0x3E, 0xEB, 0x0B, 0xAB, 0x0F, 0xB8
};

int main() {
    std::ofstream outfile;
    outfile.open("sequence.bin", std::ios_base::binary);
    std::cout << "+ writing down " << sizeof(sequence_buf) << " bytes sequence_buf\n";
    // write down byte by byte to avoid extra ones to be dumped at the end
    for (int i = 0; i < sizeof(sequence_buf); i++) {
        outfile.write((char*)(sequence_buf + i * sizeof(sequence_buf[0])),
            sizeof(sequence_buf[0]));
    }
    std::cout << "+ \"sequence.bin\" dumped\n";
    outfile.close();
    return 0;
}

Then compiled and ran it.

(synth3sis㉿sthsvm)-[~/Emotet]$ g++ seqdumper.cpp -o agent && ./agent
+ writing down 32 bytes sequence
+ "sequence.bin" dumped

(synth3sis㉿sthsvm)-[~/Emotet]$ xxd sequence.bin
00000000: eeb1 5109 042b d8a4 bfcf a8b7 b045 5d86  ..Q..+.......E].
00000010: a360 4a6b 6354 4582 cf33 3eeb 0bab 0fb8  .`JkcTE..3>.....

The same sequence is embedded in  agent  , more precisely in this case at offset  30a0.

(synth3sis㉿sthsvm)-[~/Emotet]$ xxd agent |grep -A1 "eeb1"
000030a0: eeb1 5109 042b d8a4 bfcf a8b7 b045 5d86  ..Q..+.......E].
000030b0: a360 4a6b 6354 4582 cf33 3eeb 0bab 0fb8  .`JkcTE..3>.....

Malleable executable

Once we got the address, we developed a python script to overwrite, or patch, exactly that sequence.

#!/usr/bin/env python3

import sys
from os.path import exists

def main():
    if len(sys.argv) <= 1:
        sys.exit("Usage: python3 patchbin.py /path/to/binfile")
    if not exists(sys.argv[1]):
        sys.exit("File does not exists")

    execfile = sys.argv[1]
    patch = b"'7h475_7h3_N3w_''53qu3nc_1nj3c7'"

    with open(execfile, 'rb') as ef:
        data = bytearray(ef.read())
    ef.close()

    # Look for the sequence's first 4 bytes
    offset = data.find(b"\xee\xb1\x51\x09")
    print("-> Patching file at offset " + str(offset))

    with open(execfile, 'r+b') as ef:
        ef.seek(offset)
        ef.write(patch)
    ef.close()
    print("-> " + execfile + " patched")

if __name__ == "__main__":
    main()

Finally, we executed  agent  which dropped its sequence to  sequence.bin  binary file.

(synth3sis㉿sthsvm)-[~/Emotet]$ ./agent
+ writing down 32 bytes sequence
+ "sequence.bin" dumped

(synth3sis㉿sthsvm)-[~/Emotet]$ cat sequence.bin |xxd
00000000: eeb1 5109 042b d8a4 bfcf a8b7 b045 5d86  ..Q..+.......E].
00000010: a360 4a6b 6354 4582 cf33 3eeb 0bab 0fb8  .`JkcTE..3>.....

(synth3sis㉿sthsvm)-[~/Emotet]$ sha256 agent
fd19322c68ba5bbf6ddae23b7a60ad760620d9aafef1f678ca49a7b945e95faf  agent

Then patched the executable.

(synth3sis㉿sthsvm)-[~/Emotet]$ ./patchbin.py agent
-> Patching file at offset 12448
-> agent patched

(synth3sis㉿sthsvm)-[~/Emotet]$ sha256sum agent
82e359b8cf4108b9dfc4367ba46a09be98874b1260406035aa4a84ae0d0d16b4  agent

The executable’s signature has changed. When executed, it dumps the patched byte array we set.

(synth3sis㉿sthsvm)-[~/Emotet]$ ./agent
+ writing down 32 bytes sequence
+ "sequence.bin" dumped

(synth3sis㉿sthsvm)-[~/Emotet]$ cat sequence.bin |xxd
00000000: 2737 6834 3735 5f37 6833 5f4e 3377 5f27  '7h475_7h3_N3w_'
00000010: 2735 3371 7533 6e63 5f31 6e6a 3363 3727  '53qu3nc_1nj3c7'

The original work can be found here

Security Advisory: Alt-n Security Gateway (CVE-2022-25356)
Security Advisory: Libnmap <= 0.7.2 (CVE-2022-30284)

Cyber Incident Swascan Emergency

Contact us for immediate support

The undersigned, as data subject, DECLARES that I have read and understood the content of the privacy policy pursuant to Article 13, GDPR. AGREE to the processing of data in relation to the sending by the Data Controller of commercial and / or promotional communications relating to (i) own products / services, or (ii) products / services offered by third parties.
The consent given may be revoked at any time by contacting the Data Controller at the addresses provided in the aforementioned privacy policy.