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:
- Allocate a 32 static buffer
- Dump the sequence to sequence.bin file
- 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