Tofsee is a multi-purpose malware with wide array of capabilities – it can mine bitcoins, send emails, steal credentials, perform DDoS attacks, and more. All of this is possible because of its modular nature.
We have already published about Tofsee/Gheg a few months ago – https://www.cert.pl/en/news/single/tofsee-en. Reading or at least skimming it is probably required to fully understand this post. Note that it is meant as an extension of that research, focusing on plugin functionality that we previously ignored. We will shortly summarize each plugin and highlight its most important features.
The post is rather long – for the impatient, list of hashes and table of contents in one:
|Resource Id||DLL name||DLL MD5 hash|
Original filename: p:\cmf5\small2\plugins\plg_ddos\ddos.cpp
This plugin can perform DDOS attacks. Implemented attacks are not very complicated, for example request spamming (HTTP Flood):
Or plain old SYN flood (using PassThru driver, aka grabb module).
We haven’t observed any DDoS activity from Tofsee yet, so this plugin is probably not used by the botmaster.
Configuration from the C&C for this plugin is very simple:
The binary contains a lot of strings, what simplifies analysis greatly:
Original filename: z:\cmf5\small2\plugins\plg_antibot\plugin.cpp
Now, this is an interesting plugin, because it removes other malware from victim’s computer.
- enumerate processes and kill ones that may be dangerous (search by configured names)
- search SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects registry branch, and remove bad browser helper objects
- enumerate mutexes and kill processes that own them (search by mutex names).
List of browser helper objects removed by this module (downloaded from C&C):
Original filename: p:\\cmf5\\small2\\plugins\\plg_sniff\\sniff.cpp
Related config section:
Communication is sniffed and replaced using PassThru driver (accessible through named pipe “\\\\.\\PassThru”)
mail.sniff enables stealing mail addresses from incoming e-mails. Mail addresses are stolen from “From” and “To” fields. It also looks for entities “%40″,”#64″,”#064” in content (looking for “@” char).
ftp.sniff and pop.sniff enables POP3 and FTP credentials stealing. The plugin is looking for “user” and “pass” protocol commands, gets authentication data and sends through Passthru driver.
mail.replace functionality replaces incoming e-mails using a specified template (stored in ‘mailbody’ key of config)
Template example that we received (despite this function being turned off right now):
It leaves original “From” and “To” headers (%FROM_LINE, %TO_LINE), has the ability to leave original subject (%SUBJ, %_SUBJ), and original timestamps (%DATE, %P5DATE, %M5DATE).
Original filename: p:\\cmf5\\small2\\plugins\\plg_proxy\\plugin.cpp
This plugin listens for TCP connections on 0.0.0.0:1080 and provides multithreaded SOCKS proxy server. The sample we analyzed identifies itself in Proxy-Agent HTTP header as WinRoute Pro/4.1.
Traffic is redirected to addresses specified at a proxy_cfg section, separately for each region.
Addresses are specified as a reference to a work_srv list or directly.
In proxy_cfg we can also find some defined timeouts for a socket.
When any value is missing in configuration, binary has some sane defaults inside.
Plugin also adds port mapping using UPNP, disguising itself as Skype:
Strings from the binary give a little more insight about the purpose of this plugin:
Original filename: z:\cmf5\small2\plugins\plg_protect\plugin.cpp
This plugin downloads and installs malicious service in system:
Malicious service binary is obfuscated with “state-of-the-art encryption algorithm” – i.e. negating every byte:
Md5 of decrypted backdoor = 49642f1d1b1673a40f5fa6263a66d056. This file is protected by packer, and it’s the only packed binary that we observed during our analysis of Tofsee – it suggests that the binary could’ve been created by another actor and reused in Tofsee.
Original filename: z:\cmf5\cmf5\small2\plugins\plg_locs\plg.cpp
This plugin steals network credentials for Microsoft Outlook:
After extracting them from the registry, they are decrypted and used to send more emails. Additionally, it generates email in form [computer name]@mail.ru and attempts to send emails using it (with raw SMTP protocol).
Strings from binary:
This is HTTP server plugin. It masquerades as Apache/2.2.15 (Win32). It can serve files, probably for other bots.
It is able to blacklist some IPs – probably security analysts (for example Forcepoint and Google are banned).
Configuration for this module, fetched from the C&C:
Original filename: p:\cmf5\small2\plugins\plg_text\plg_text.cpp
Very short plugin, it is able to process email templates downloaded from C&C.
Very important module – it generates and sends emails. It’s probably biggest module and code is rather complicated sometimes.
Most interesting thing about it is the fact that it uses its own dedicated scripting language for generating messages. Script example, received from C&C:
If someone recognizes this as a real scripting language, we’d be grateful for the information. We have never seen something like this, so we analyzed interpreter of this language.
The syntax is rather simple, but very assemblish and primitive. We hope that malware authors are generating this scripts from a higher level language because writing something like this must really hurt one’s sanity ;].
A lot of opcodes are supported – take a look at this (simplified) parsing function for example:
We didn’t reverse all of them, but few most important ones are:
- C ip:port – Connect
- L lbl – Create Label lbl.
- J lbl – Jump to label lbl.
- v name value – Create variable name and assign value value.
- W text – Write something to output – in this case to final email.
- I lbl condition – If condition is satisfied than jump to lbl
Additionally wrapping text in “”” allows for newlines and escape sequences in it, and __v(XX)__ is a variable interpolation.
Again, few from the most interesting strings from that binary:
We thought that IfYouAreReadingThisYouHaveTooMuchFreeTime is an easter egg for us, malware analysts, but it turns out that it’s just a strange quirk related to hotmail authentication.
Configuration for this module, fetched from C&C:
This plugin checks if a bot is listed as a spambot and blacklisted. In the config we observed following DNSBLs (DNS-based Blackhole Lists) were supplied:
DNSBL is a service based on DNS used for publishing IP addresses of spam senders. If spam server uses DNSBL filters, it will do a DNS request to DNSBL domain with each incoming SMTP connection. Technical details are outside of the scope of this post, but any interested reader can take a look at http://www.us.sorbs.net/using.shtml or https://en.wikipedia.org/wiki/DNSBL.
Checking DNSBL is implemented with gethostbyname:
Configuration for this module, fetched from C&C:
This is (as the name suggests) cryptocurrency miner. This plugin only coordinates the work, but it has few accompanying binaries, that perform the dirty work.
One binary, called grabb, is distributed straight from the C&C. Other binaries are downloadable through URLs specified in configs – in theory. In practice, servers distributing miners seem to be dead, so we were not able to download miners.
Miner “verifies” that has really downloaded right binary, but hashing was probably too difficult for malware creators to implement, so they settled on size verification – for example, they are check that cores_gt_1 binary has exactly 223744 bytes.
We didn’t analyze it in-depth because crypto miners are boring enough, and strings from binary give enough information about inner workings anyway:
And the rest can be read from the configuration, fetched from C&C:
This short plugin processes malicious attachments – encodes them with base64 and appends to emails.
Nothing interesting here, as can be seen in hardcoded strings:
Configuration for this module, fetched from the C&C:
This plugin is used to spread Tofsee through social media: Facebook, Twitter and Skype communicator.
First, it extracts xs, datr, c_user (and more) cookies.
Exact method depends on the browser, but generally plugin reads cookies stored on disk by the browser – for example cookies.sqlite from \Mozilla\Firefox\Profiles, for Firefox. Supported browsers are Chrome, IE, Firefox, Safari, and Opera.
After that, plugin uses that cookies to impersonate user in facebook API:
List of friends is downloaded through API and a message is sent to them. Format of message is stored in configuration, for example:
‘fb.message1’: ‘%SPRD_TEXT1|%LANG_ID| %SPRD_URL1’
Twitter is handled very similarly: cookies are stolen, followers are downloaded by API call to https://twitter.com/followers, and messages are sent.
VKontakte also seems to be supported, but that functionality is optional and held in another plugin. This module only checks if VK is enabled in config and calls handler (that can be initialized from another plugin), if it’s defined. Malware creators usually don’t like to attack Russia, so this function is disabled and VKontakte plugin is not distributed.
Plugin can also spread itself through Skype, but reverse engineering Skype protocol was clearly too hard for malware authors, so plugin waits until Skype is started, and then sends windows messages to Skype window:
The plugin has dozens of strings hardcoded, so analyzing it in disassembler is a breeze. Few more interesting groups:
References to the OCR plugin – to avoid captchas:
Strings related to Facebook spread:
Strings related to cookie stealing:
Strings related to Skype hijacking:
And Twitter spread:
Finally, things needed to send stolen cookies somewhere:
Rich functionality means rich configuration from the C&C:
This plugin uses methods more than 15 years old, and tries to spread Tofsee through… infected USB drives! This doesn’t sound like an effective idea for A.D. 2017, but despite that, the plugin is still enabled.
First it copies malicious binary into RECYCLER\<random_gibberish>.exe file on the USB drive, then sets READONLY and SYSTEM attributes on that file, and finally writes malicious autorun.inf file:
The malicious binary that will be spread is downloaded from the internet (see also sys.dll plugin and %FIREURL variable).
Nothing too interesting in hardcoded strings, except operation logs:
Configuration for this module, fetched from the C&C:
This plugin seems to be a downloader or rather an updater. It sends requests, depending on a value of the %FIREURL configuration variable.
Example values of the %FIREURL variable (one per line):
Variables are expanded recursively, and %SYS_RN means \r\n of course, so first possible value can be read as:
If we send this request to that IP address on port 80, we will get yet another malicious binary. Different requests lead to different binaries.
If a request is invalid, or not supported, following image is sent instead:
We appreciate the humor.
Nothing surprising in hardcoded strings:
Configuration for this module, fetched from the C&C:
Additionally the %FIREURL variable from config is used.
This plugin tries to locate iexplore.exe process. If this succeeds, it injects DLL file called IEStub.dll to this process.
IEStub.dll hooks a lot of functions from iexplorer. List of hooked functions:
Hooks intercept called functions and can change their parameters. We haven’t analyzed hooks in depth, but most of them seem to be loggers intercepting “interesting” data from parameters – We haven’t observed any web injects served by Tofsee.
For completeness, interesting hardcoded strings:
Configuration for this module, fetched from the C&C:
Original filename: p:\cmf5\small2\plugins\plg_p2p\plg_p2p.cpp
This plugin is rather short. Despite promising name, it’s rather boring – opening a port on a router and listening for connection is the most important thing it does. It doesn’t implement any commands, this is left for the main module to handle.
Like almost every module, it logs to %TMP%\log_%s.txt, and when this fails falls back to C:\log.txt.
Also adds port mapping using UPnP, in the same way as plugin 4 (proxyR.dll).
Configuration for this module, fetched from the C&C:
If we look on Ramnit’s history, it’s hard to exactly pin down which malware family it actually belongs to. One thing is certain, it’s not a new threat. It emerged in 2010, transferred by removable drives within infected executables and HTML files.
A year later, a more dangerous version was released. It contained a part of recently leaked Zeus source code, which allowed Ramnit to become a banking trojan.
These days, it has become much more sophisticated by utilizing a number of malicious activities including:
- Performing Man-in-the-Browser attacks
- Stealing FTP credentials and browser cookies
- Using DGA (Domain Generation Algorithm) to find the C&C (Command and Control) server
- Using privilege escalation
- Adding AV exceptions
- Uploading screenshots of sensitive information
- encrypt large (>4bytes) chunk data using RC4 with a key recovered from the XOR decryption
- create packed chunks from data parts
- concatenate all chunks together
- wrap the output in packet layer
Module Antivirus Trusted Module v2.0 (AVG, Avast, Nod32, Norton, Bitdefender)
Module Cookie Grabber v0.2 (no mask)
Despite Europol’s shut down of 300 C&C servers in 2015, it’s still going strong, recently being distributed by RIG EK via seamless gates.
The main binary is packed like a matryoshka – a custom packing method first and then UPX.
Despite being encrypted, extracting the binary from the packer is pretty straight-forward – all one needs to do is to set a breakpoint right after the binary decrypts the code and before it jumps into it.
And if we now navigate to the newly unpacked code section we’ll find the binary right after the loader assembly:
The unpacked binary (after UPX decompression) consists of 3 general functions:
If the current user is not already an admin and the process is not running with admin privileges it tries to perform privilege escalation.
Malware contains exploits for CVE-2013-3660 (patched in MS13-053) and CVE-2014-4113 (patched in MS14-058) vulnerabilities, however before it actually tries to run the payload, registry checks are performed to make sure that the host system is indeed vulnerable to said CVEs:
If the exploits succeed or the program is already running with high privileges, a “TRUE” value is stored in a hardcoded random-looking registry key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\jfghdug_ooetvtgk, which is later used in the CheckBypassed function.
This function checks if previously mentioned registry key is set. If not and process has admin privileges, updates it. Assuming the exploit has worked, Ramnit then adds registry keys to evade Windows’ security systems detection (see Obfuscation/Evasion):
The routine coordinates ApplyExploit and CheckBypassed – if they both run successfully it creates two svchost.exe processes and writes rmnsoft.dll and modules.dll into them respectively.
Important detail: the binary executes CheckBypassed before ApplyExploit, so the binary has to be executed again in order to make any further progress. This trick outsmarts many single-run malware analysis systems, such as Cuckoo.
Ramnit encrypts its network communication using RC4 algorithm. Key for RC4 and botnet name are encrypted using xor with a hardcoded password.
XOR encryption is pretty standard, the only catch is that it skips key’s first char and then reverses the key.
XOR function calls:
Ciphertext lengths are almost always too long and we have to rely on null termination:
DGA config seems to be always declared at the beginning of the data section:
Program copies itself into C:\Users\User\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\.
Ramnit generates a list of domains by using a LCG algorithm with a hardcoded seed:
Generating a domain:
DGA recreated in Python:
Ramnit connects to C&C servers through port 443, but don’t let that fool you – it doesn’t use HTTPS, but its own protocol instead:
So if we’d like to send a packet containing some data, we would:
Some of available commands:
|Command||Byte Value||Short Description|
|COMMAND_OK||0x01||Server’s response that the command executed successfully|
|GET_DNSCHANGER||0x11||Get DNS-changer payload|
|UPLOAD_COOKIES||0x15||Upload stolen cookies (zip format)|
|GET_MODULE||0x21||Get a specific module|
|GET_MODULE_LIST||0x23||Get a list of downloadable modules|
|VERIFY_HOST||0x51||Check if the host is able to send a signed message|
|REGISTER_BOT||0xe2||Register bot (send two MD5s)|
|UPLOAD_INFO_GET_COMMANDS||0xe8||Upload detailed machine info|
When a bot wants to register itself it sends two encrypted md5 hashes, the data structure of which is following:
If C&C responds with a success packet (00ff0100000001), malware follows up with a empty 0x51 command. Signature from the response is verified using a hardcoded public RSA key. If there is a mismatch – the execution stops.
The program can request a list of modules and then download each one individually:
Antivirus Trusted Module v2.0
Adds exceptions to a fixed list of anti-virus software (AVG Anti-Virus, BitDefender, Avast, ESET NOD32 Antivirus, Norton AntiVirus)
Chrome reinstall module (x64-x86) v0.1
Uninstalls Google Chrome
and installs it again:
Cookie Grabber v0.2 (no mask)
Steals cookies from various hardcoded locations and sends a zip with results to the C&C through rmnsoft.dll.
Used for performing Man-in-the-Browser attacks and hooking HTTP functions.
Webinjects are a relatively new addition to Ramnit. They utilize a standard Zeus format:
Obfuscation / Evasion
Ramnit attempts to hide itself from Windows Defender by adding following registry values:
‘NOPs’ are inserted in random functions, which makes them difficult to find using e.g. Yara rule:
During writing of this article we’ve noticed a variation of Ramnit called clickbideu in an Italian spam campaign.
Its loader is completely different, but the communication module (rmnsoft.dll) has remained somewhat unchanged with only some minor differences:
DGA cycles between 3 hardcoded TLDs instead of just one:
Also new version seems to be using different port – 8001, although we’ve also seen usage of port 442.
Additionally, a different value (“fE4hNy1O”) is used for calculating the second md5.
DGA domains for analyzed configs:
Nymaim was discovered in 2013. At that time it was only a dropper used to distribute TorrentLocker. In February 2016 it became popular again after incorporating leaked ISFB code, dubbed Goznym. This incarnation of Nymaim was interesting for us because it gained banking capabilities and became a serious threat in Poland. Because of this, we researched it in depth and we were able to track Nymaim activities since then.
However a lot of things have changed during the last two months. Most notably, Avalanche fast-flux network (which was central to Nymaim operations) was taken down and that struck a serious blow to Nymaim activity. For two weeks everything went silent and even today Nymaim is a shadow of its former self. Although it’s still active in Germany (with new injects), we haven’t observed any serious recent activity in Poland.
This topic is really well researched by other teams, but it’s still interesting enough to be worth mentioning. Nymaim is heavily obfuscated with a custom obfuscator – to the point that analysis is almost impossible. For example typical code after obfuscation looks like this:
But with some effort we can make sense of it. There are a lot of obfuscation techniques used, so we’ll cover them one by one:
First of all, registers are usually not pushed directly onto the stack, but helper function “push_cpu_register” is used. For example push_cpu_register(0x53) is equivalent to pushing ebx and push_cpu_register(0x50) is equivalent to pushing eax. Constants are not always the same, but registers are always in the same order (standard x86 ordering).
Additionally, most constants in code gets obfuscated too – for example mov eax, 25 can be changed to:
The constant used in the example is 8CBFB5DA, but there’s nothing special about it – it’s a random dword value, generated just for the purpose of obfuscating this constant. The only thing that matters is the result of the operation (0x25 in this case).
Additionally there other similar obfuscating functions are used sometimes – for example sub_*_from_eax and add_*_to_eax.
Last but not least, the control flow is heavily obfuscated. There are a lot of control flow obfuscation methods used, but all boil down to simple transformation – call X and jmp X are transformed to at least two pushes. This obfuscation is in fact very similar to previous one – instead of jumping to 0x42424242, malware calls function detour with two parameters: 0x40404040 and 0x02020202. The detour adds it’s parameters and jumps to the result. In pseudoasm instead of:
There exists also a slight variation of this method – instead of pushing two constants, sometimes only one constant is pushed and machine code after a call opcode is used instead of a second constant (detour uses return address as a pointer to the second constant).
To sum up, previously pasted obfuscated code should be read like this:
With this in mind, we created our own deobfuscator. This was quite a long time ago and since then other solutions have shown up. Our deobfuscator probably isn’t the best, but is easily modifiable for our needs and it has some unique (as far as we know) features that we need, for example it imports recovery and decrypting encrypted strings stored in binary. Other deobfuscators include mynaim and ida-patchwork Nevertheless, with our deobfuscator we are able to untangle that messy code to something manageable:
When it comes to Nymaim obfuscation capabilities it’s not nearly over. For example external functions are not called directly, instead of it an elaborate wrapper is used:
This wrapper pushes hash of function name on the stack and jumps to the next dispatcher (even though call opcode is used, this code never returns here):
A second dispatcher pushes hash of a dll name on the stack and jumps to the helper function:
And finally real dispatcher is executed:
Additionally, real return address from API is obfuscated – return address is set to call ebx somewhere in the ntdll (real return address is somewhere in ebx by then, of course). Most tools are very confused by it. Let’s just say, it’s very frustrating when debugging and/or single stepping.
But wait, there’s more! As we have seen, short constants are obfuscated with simple mathematical operations, but what about longer constants, for example strings? Fear not, malware authors have a solution for that too. Almost every constant used in the program is stored in a special data section. When Nymaim needs to use one of that constants, it is using special encrypted_memcpy function. At heart it is not very complicated:
Inner workings of memcpy_and_decrypt are not that complicated either. Our reimplementation of the encryption algorithm in Python is only few lines long:
We only need to extract constants used for the encryption (they differ between executables) – they are hidden in these portions of code:
(These functions are not obfuscated, so extraction can be done with simple pattern matching).
But encryption of every constant was not good enough. Malware authors decided that they can do better than that – why don’t encrypt the code too? That’s not very often used, but few critical functions are stored encrypted and decrypted just before calling. Quite an unusual approach, that’s for sure. Ok, let’s leave obfuscation at that.
After deobfuscation, the code is easier to analyze and we can get to interesting things. First of all, we’d like to extract static configuration from binaries, especially things like:
- C&C addresses
- DGA hashes
- Encryption keys
- Malware version
- Other stuff needed for communication
How hard can that be? Turns out that harder than it looks – because this information is not just stored in the encrypted data section.
Fortunately, this time the encryption algorithm is rather simple.
We just need to point nymaim_config_crypt to the start of encrypted static config and everything will just work.
How do we know where static config starts? Well… We tried few clever approaches (matching code, etc), but they weren’t reliable enough for us. Finally, we solved this problem with a simplest possible solution – we just try every possible index in binary and try to decrypt from there. This may sound dumb (and it is), but with few trivial heuristics (static config won’t take 3 bytes of space, neither will it take 3 megabytes) this is quite fast – less than 1s on typical binary – and works every time.
Despite this, after decrypting static config we get a structure, which is is quite nice and easy to parse. It consists of multiple consecutive “chunks”, each with assigned type, length and data (for those familiar with file formats, this is something very similar to PNG, or wav, or any other RIFF).
Graphically this looks like this:
And chunks are laid consecutive in static config block:
So we can quickly traverse through all chunks of a static config with a simple five-liner:
Snippet from process_chunk (hash == chunk_type):
After initial parsing the static config looks like this:
(By the way, in this article chunk types are usually represented byte-order, i.e. big endian)
And in a more human readable form with most interesting chunks interpreted:
There is more than one “kind” of Nymaims. As of now we distinguish between three kinds:
- dropper – first Nymaim that gets executed on the system. This is the only type distributed directly to victims.
- payload – module responsible for most of the “real work” – web injects for example
- bot_peer – module responsible for P2P communication. It tries to become supernode in the botnet.
These are all one kind of malware and all of them share the same codebase, except few specialized functions. For example our static config extractor works on all of them, just like our deobfuscator and they all use the same network protocol.
Dropper role is simple. It performs few sanity checks – for example:
- Makes sure that it’s not virtualized or incubated
- Compares current date to “expiration time” from static config
- Checks that DNS works as it should (by trying to resolve microsoft.com and google.com)
If something isn’t right, the dropper shuts down and the infection doesn’t happen.
The second check is especially annoying, because if you want to infect yourself Nymaim has to be really “fresh” – older executables won’t work. Even if you override check in the binary, this is also validated server-side and the payload won’t be downloaded.
If we want to connect to a Nymaim instance, we need to know the IP address of peer/C&C. Static config contains (among others) two interesting pieces of information:
- DNS server (virtually always it’s 184.108.40.206 and 220.127.116.11).
- C&C domain name (for example ejdqzkd.com or sjzmvclevg.com).
Nymaim is resolving that domain, but returned A records are not real C&C addresses – they are used in another algorithm to get a real IP address. We won’t reproduce that code here, but there is a great article from Talos on that topic. If someone is interested only in the DGA code, it can be found here:
When dropper obtains C&C address, it starts real communication. It downloads two important binaries and a lot more:
- payload – banker module (responsible for web injects – passive member of botnet)
- optional bot module (it is trying to open ports on a router and become an active part of a botnet. When it fails to do so, it removes itself from a system).
- few additional malicious binaries (VNC, password stealers, etc – not very interesting for us).
Payload is very different from dropper when it comes to network communication:
- No hardcoded domain
- But has DGA
- And P2P
The payload’s DGA algorithm is really simple – characters are generated one by one with simple pseudo-random function (variation of xorshift). Initial state of DGA depends only on seed (stored in static config) and the current date, so we can easily predict it for any given binary. Additionally, researchers from Talos have bruteforced valid seeds, simplifying the task of domain prediction even more.
First of all, few examples why we suspected from the start that there is something else besides DGA:
We have taken one of our binaries that hadn’t behaved like the payload, unpacked it, deobfuscated and reverse engineered it. But even without in-depth analysis, we’ve found a lot of hints that P2P may be happening. For example we can find strings typical for adding exception to Windows Firewall (and of course – that’s what malware did when executed on a real machine).
Another suspicious behavior is opening ports on a router with help of UPNP. Because of this, infected devices from around the world can connect to it directly.
And finally something even more outstanding. As we have seen, the malware presents itself as the Nginx in the “Server” header. Where does this header come from? Directly from the binary:
We implemented tracker for the botnet (more about that later) and with the data we obtained, we concluded that this probably is a single botnet, but with geolocated injects (for example Polish and US injects are very similar). Distribution of IPs we found is similar to what other researchers have determined (we have found more PL nodes and less US than others, but that’s probably because the botnet is geolocated and we were more focused on Poland).
49.9% (~7.5k) of found supernodes were in Poland, 30% (~4.5k) in Germany and 15.7% (~2.2k) in the US.
And now for something more technical. This is an example of a typical Nymaim request (P2P and C2 communication use the same protocol internally):
- Host header is taken from the static config
- Randomized POST variable name and path
- POST variable value = encrypted request (base64 encoded)
- User-Agent and rest of the headers are generated by WinHTTP (so headers are not very unique and it’s impossible to detect Nymaim network requests by using only them).
- This isn’t really Nginx, just pretending.
- Everything except the data section is hardcoded
- Data = encrypted request
Encrypted messages have very specific format:
A lower nibble of the first byte is equal to a length of the salt and a lower nibble of the second byte is equal to the length of the padding. Everything between the salt and the padding is the encrypted message. To decrypt it, we need to concatenate the key with the salt – and use that password with the rc4 algorithm.
It can be easily decrypted using Python (but we had to reverse engineer that algorithm first):
After decrypting a message, we get something with a format very similar to the static config (i.e. a sequence of consecutive chunks):
Each chunk has its type, length and raw data:
We can process decrypted message with almost exactly the same code as code for static config:
And this is the basic code used for parsing the message. Each chunk type needs to be processed a bit differently. Interestingly, parsing message is recursive, because some chunk types can contain other lists of chunks, which in turn can contain other lists of chunks, etc. Unfortunately, important chunks have another layer of encryption and compression. At the end of an encrypted chunk we can find special RSA encrypted (or rather – signed) header. After decryption (unsigning) of the header, we can recover a md5 hash and length of the decrypted data and most important of all – a Serpent cipher key used to encrypt the data.
After the decryption we will stumble upon another packing method – decrypted data is compressed with APLIB32. This structure is very similar to the one used by ISFB – firstly we have magic ‘ARCH’, then length of compressed data, length of uncompressed data and crc32 – all of them are dwords (4 bytes).
Again, it’s nothing Python can’t deal with. We quickly hacked this function to recover real data hidden underneath:
With this function we finally managed to hit the jackpot. We decrypted all of the interesting artifacts passed over the wire, most importantly additional downloaded binaries, web filters and injects.
An example request, after dissection, may look like this:
As we can see, quite a lot of things is passed around here. There are a lot of fingerprinting everywhere and some information about current state.
Responses are often more elaborate, but for the sake of presentation, let’s dissect a simple one:
An infected machine gets to know its public IP address, IP addresses (and listening ports) of its peers and the active domain. Additionally it is usually ordered to sleep for some time (usually 90 seconds when some files are pending to be transmitted and 280 seconds when nothing special happens).
Here is the list of types of chunks that we can parse and understand:
|chunk hash||short description|
|014e2be0||fingerprint 2 + timestamps|
|22451ed7||crcs of last received chunks of type be8ec514 and 0282aa05|
|b873dfe0||probably “enabled” flag (can be only 1 or 0)|
|0c526e8b||nested chunk (decrypt with nymaim_config_crypt, unpack with aplib, recursively repeat parsing)|
|875c2fbf||plain (non-encrypted) executable|
|08750ec5||nested chunk (decrypt with nymaim_config_crypt, unpack with aplib, recursively repeat parsing)|
|1f5e1840||injects (decrypt with serpent, unpack with aplib, parse ISFB binary format)|
|76daea91||dropper handshake (marker, without data)|
|be8ec514||list of peer IPs|
|138bee04||list of peer IPs|
|1a701ad9||encrypted binary (decrypt with serpent, unpack with aplib, save)|
|30f01ee5||encrypted binary (decrypt with serpent, unpack with aplib, save)|
|3bbc6128||encrypted binary (decrypt with serpent, unpack with aplib, save)|
|39bc61ae||encrypted binary (decrypt with serpent, unpack with aplib, save)|
|261dc56c||encrypted binary (decrypt with serpent, unpack with aplib, save)|
|a01fc56c||encrypted binary (decrypt with serpent, unpack with aplib, save)|
|cae9ea25||nested chunk (decrypt with nymaim_config_crypt, unpack with aplib, recursively repeat parsing)|
|0282aa05||nested chunk (decrypt with nymaim_config_crypt, unpack with aplib, recursively repeat parsing)|
|8de8f7e6||datetime (purpose is unknown, it’s always few days ahead of current date)|
|3e5a221c||list of additional binaries that was downloaded|
|5babb165||payload handshake (marker, without data)|
|b84216c7||public IP of infected machine|
|cb0e30c4||number of seconds to sleep|
|f31cc18f||additional CRC32s of downloaded binaries|
|920f2f0c||injects (decrypt with serpent, unpack with aplib, parse ISFB binary format)|
|930f2f0c||injects (decrypt with serpent, unpack with aplib, parse ISFB binary format)|
This may seem like a lot, but there are a lot of things we didn’t try to understand (we ignored most of dword-sized or always-zero chunks).
After extracting everything from communication we can finally look at injects. For example Polish ones:
(304 different injects, as of today)
Or US injects:
(393 different injects, as of today)
- Payload 2016-10-20, 9d6cb537d65240bbe417815243e56461, version 90032
- Dropper 2016-10-20, a395c8475ad51459aeaf01166e333179, version 80018
- Payload 2016-10-05, 744d184bf8ea92270f77c6b2eea28896, version 90019
- Payload 2016-10-04, 6b31500ddd7a55a8882ebac03d731a3e, version 90012
- Dropper 2016-04-12, cb3d058a78196e5c80a8ec83a73c2a79, version 80017
- Dropper 2016-04-09, 8a9ae9f4c96c2409137cc361fc5740e9, version 80016
Repository with our tools: nymaim-tools