Jump to content
Nytro

Miniduke still duking it out

Recommended Posts

At the end of April Microsoft announced that a vulnerability in Word was actively being exploited. This vulnerability occurred in parsing RTF files and was assigned CVE-2014-1761, a thorough analysis of which can be found on the HP Security Research blog. We have since seen multiple cases where this exploit is used to deliver malware and one was particularly interesting as it contained a new variant of MiniDuke (also known as Win32/SandyEva).

MiniDuke was first discussed by Kaspersky in March 2013 in their paper The MiniDuke Mystery: PDF 0-day Government Spy Assembler 0x29A Micro Backdoorand shortly after in a paper by Bitdefender. Some of the characteristics of MiniDuke — such as its small size (20 KB), its crafty use of assembly programming, and the use of zero-day exploits for distribution — made it an intriguing threat. Although the backdoor is still quite similar to its previous versions, some important changes were made since last year, the most notable being the introduction of a secondary component written in JScript to contact a C&C server via Twitter.

1-exploitdoc.png

The RTF exploit document

The exploit document was named Proposal-Cover-Sheet-English.rtf and is quite bland when compared to the documents that were used in 2013, which were of a political nature. We received the document on April 8th, only three days after the compilation of the MiniDuke payload, dated April 5th in the PE header. The payload remains quite small at only 24 KB.

The functionality of the shellcode which is executed by triggering the vulnerability is rather simple and straightforward. After decrypting itself and obtaining the addresses of some functions exported by kernel32.dll, it decrypts and drops the payload in the %TEMP% directory in a file named “a.l” which is subsequently loaded by calling kernel32!LoadLibraryA.

An interesting thing about the shellcode is that before transferring control to any API function it checks the first bytes of the function in order to detect hooks and debugger breakpoints which may be set by security software and monitoring tools. If any of these are found the shellcode skips the first 5 bytes of the function being called by manually executing prologue instructions (mov edi, edi; push ebp; mov ebp, esp) and then jumping to the function code as illustrated below.

2-shellcode.png

The next graph presents the execution flow of this malware when the exploitation is successful. As mentioned previously this version of the MiniDuke payload comes with two modules which we refer to as the main module and the TwitterJS module.

3-schema-1024x335.png

Execution flow of MiniDuke

[h=1]Main Component[/h] [h=2]Installation[/h]

Once MiniDuke receives control it checks that the host process is not rundll32.exe and whether the current directory is %TEMP%. If either of those conditions is met the malware assumes it is run for the first time and it proceeds with its installation onto the system. MiniDuke gathers information about the system and encrypts its configuration based on that information, a method also used by OSX/Flashback (this process is called watermarking by Bitdefender). The end result is that it is impossible to retrieve the configuration of an encrypted payload if analyzing it on a different computer. The information collected on infection has not changed since the previous version and consists of the following values:

  • volume serial number (obtained from kernel32!GetVolumeInformationA)
  • CPU information (obtained with the cpuidinstruction)
  • computer name (obtained from kernel32!GetComputerNameA)

Once the encrypted version of the malware is created, it is written into a file in the %ALLUSERSPROFILE%\Application Data directory. The name of the file is randomly picked from the following values (you can find this listing and those of the next screenshots on the VirusRadar description:

4-filename.png

The filename extension is also picked randomly from the following list: 5-fileext.png To persist on the infected system after reboots, the malware creates a hidden .LNK file in the “Startup” directory pointing to the modified main module. The name of the .LNK file is randomly drawn from the following values: 6-lnkname.png

The .LNKfile is created using a COM object with the IShellLinkA interface and contains the following command: “C:\Windows\system32\rundll32.exe %path_to_main_module%, export_function” Which gives something like: C:\Windows\system32\rundll32.exe C:\DOCUME~1\ALLUSE~1\APPLIC~1\data.cat, IlqUenn.

[h=2]Operation[/h] When the malware is loaded by rundll32.exe and the current directory isn’t %TEMP%, the malware starts with gathering the same system information as described in the “Installation” section to decrypt configuration information. As with the previous version of MiniDuke, it checks for the presence of the following processes in the system:

7-processes.png

If any of these are found in the system the configuration information will be decrypted incorrectly, i.e. the malware will run on the system without any communication to C&C servers. If the configuration data is decrypted correctly, MiniDuke retrieves the Twitter page of [b @FloydLSchwartz does exist but has only retweets and no strings with the special tag.

8-twitter1.png

As the next step, MiniDuke gathers the following information from the infected systems:

  • computer name and user domain name
  • country code of the infected host IP address obtained from http://www.geoiptool.com
  • OS version information
  • domain controller name, user name, groups a user account belongs to
  • a list of AV products installed onto the system
  • Internet proxy configuration
  • version of MiniDuke

This information is then sent to the C&C server along with the request to download a payload. The final URL used to communicate with the C&C server looks like this: <url_start>/create.php?<rnd_param>=<system_info> Those tokens are derived as follows:

  • url_start – the URL retrieved from the twitter account
  • rnd_param – randomly generated of lower case alphabet characters parameter name in the query string of the URL
  • system_info – base64 encoded and encrypted system information

An example of such a URL is given below:

9-ur-1024x18.png The payload is downloaded in the file named “fdbywu” using the urlmon!URLDownloadToFileA API: 10-download.png

The downloaded payload is a fake GIF8 file containing encrypted executable. The malware processes the downloaded file in the same way as previous samples of MiniDuke: it verifies the integrity of the file using RSA-2048, then decrypts it, stores in a file and finally executes it. The RSA-2048 public key to verify integrity of the executable inside the GIF file is the same as in the previous version of MiniDuke.

[h=2]Twitter Generation Algorithm[/h] In the event that MiniDuke is unable to retrieve a C&C URL from this account, it generates a username to search for based on the current date. The search query changes roughly every seven days and is similar to the backup mechanism in previous versions that was using Google searches. A Python implementation of the algorithm can be found in Appendix B.

[h=2]TwitterJS component[/h] The TwitterJS module is extracted by creating a copy of the Windows DLL cryptdll.dll, injecting a block of code into it and redirecting the exported functions to this code. Here is how the export address table of the patched binary looks after modifications.

11-exports.png

This file is then stored in an Alternate Data Stream (ADS) in NTUSER.DAT in the %USERPROFILE% folder. Finally this DLL is registered as the Open command when a drive is open, which has the effect of starting the bot every time the user opens a disk drive. Below you can find the content of the init.cmd script used by MiniDuke to install TwitterJS module onto the system.

12-install.png

When loaded, TwitterJS instantiates the JScript COM object and decrypts a JScript file containing the core logic of the module.

13-jscript1.png

Prior to executing it, MiniDuke applies a light encoding to the script: The next images show the result of two separate obfuscations, we can see that the variables have different values. This is probably done to thwart security systems that scan at the entry points of the JScript engine.

14-obfuscation1.png

Result of first obfuscation

15-obfuscation2.png

Result of second obfuscation

The purpose of this script is to use Twitter to find a C&C and retrieve JScript code to execute. It first generates a Twitter user to search for; this search term changes every 7 days and is actually a match to the real account name, not the Twitter account name. The bot then visits the Twitter profiles returned by the search and looks for links that end with “.xhtml“. When one is found, it replaces “.xhtml” with “.php” and fetches that link. Information about the computer is embedded in the Accept HTTP header.

16-twitterjs1-1024x127.png

The first link on the retrieved page should contain base64 data; the name attribute of the link is used as a rolling XOR key to decrypt the JScript code. Finally, MiniDuke calculates a hash of the fetched script and compares it with a hardcoded hash in the TwitterJS script. If they match, the fetched script is executed by calling eval().

17-twitterjs2.png

[h=2]

The tale of the broken SHA-1[/h] The code hashing algorithm used by the component looks very much like SHA-1 but outputs different hashes (you can find the complete implementation in Appendix B. We decided to search for what was changed in the algorithm; one of our working hypotheses was that the algorithm might have been altered to make collisions feasible. We couldn’t find an obvious difference; all the constants and the steps of the algorithm were as expected. Then we noticed that for short messages only the second 32-bit word was different when compared to the original SHA-1.

SHA1(‘test’) : a94a8fe5ccb19ba61c4c0873d391e987982fbbd3

TwitterJS_SHA1(‘test’) : a94a8fe5dce4f01c1c4c0873d391e987982fbbd3

By examining how this 2nd word was generated we finally discovered that this was caused by a scope issue. As shown below the SHA-1 function used a variable named f: the function Z() is then called which also uses a variable named f without the var keyword, causing it to be treated as a global variable rather than local to the function. The end result is that the value of f is also changed in the SHA-1 function which affects the value of the 2nd word for that round and ultimately the whole hash for long messages.

image2-300x166.jpg

A likely explanation of how this problem came to be is that the variable names were changed to single letters using an automated tool prior to embedding it in the payload. The 2 f variables probably had different names in the original script which avoided the issue. So this leaves us with two takeaways: 1) The difference in the hashing algorithm was unintentional and 2) Always declare your local variables with the var keyword. ;-)

[h=2]Twitter DGA accounts[/h] We generated the list of Twitter search terms for 2013-2014 and checked if any of those were registered. At the moment only one exists, @AA2ADcAOAA, which is the TwitterJS account that was generated between August 21st and 27th 2013. This account has no tweets. In an effort to discover potential victims, we registered the Twitter accounts corresponding to the current week both for the main and TwitterJS components and set up tweets with encrypted URLs so that an infected computer would reach out to our server. So far we have received connections via the TwitterJS accounts from four computers located in Belgium, France and the UK. We have contacted national CERTs to notify the affected parties. We detect the RTF exploit document as Win32/Exploit.CVE-2014-1761.D and the MiniDuke components as Win32/SandyEva.G.

[h=3]Appendix A: SHA-1 hashes[/h] [TABLE]

[TR]

[TD]SHA-1[/TD]

[TD]Description[/TD]

[/TR]

[TR]

[TD]58be4918df7fbf1e12de1a31d4f622e570a81b93[/TD]

[TD]RTF with Word exploit CVE-2014-1761[/TD]

[/TR]

[TR]

[TD]b27f6174173e71dc154413a525baddf3d6dea1fd[/TD]

[TD]MiniDuke main component (before config encryption)[/TD]

[/TR]

[TR]

[TD]c059303cd420dc892421ba4465f09b892de93c77[/TD]

[TD]TwitterJS javascript code[/TD]

[/TR]

[/TABLE]

Appendix B: DGA algorithms

main component DGA

import sys
import hashlib
import datetime
from array import *
import base64

def init_params(date_time):

duke_string = “vSkadnDljYE74fFk”

to_hash = array(“B”)

for ix in xrange(0×40):

to_hash.append(0)

for ix in xrange(len(duke_string)):

to_hash[ix + 8] = ord(duke_string[ix])

to_hash[0] = (date_time.day / 7)

to_hash[2] = date_time.month

to_hash[4] = date_time.year & 0xFF

to_hash[5] = date_time.year >> 8

return to_hash



def hash_data(to_hash):

m = hashlib.md5()
m.update(to_hash.tostring())
hash_val = m.digest()
first_dword = ord(hash_val[0]) | (ord(hash_val[1]) << 8) | (ord(hash_val[2])<<16) I (ord(hash_val[3]) << 24)
last_dword = ord(hash_val[12]) | (ord(hash_val[13]) << 8) | (ord(hash_val[14]) <<16) | (ord(hash_val[15]) << 24)
new_dword = (first_dword + last_dword) & 0xFFFFFFFF
to_append = array(“B”, [new_dword & 0xFF, (new_dword >> 8) & 0xFF, (new_dword >>16) & 0xFF, (new_dword >> 24) & 0xFF])
return hash_val + to_append.tostring()

def generate_twitter_dga(date_time):

to_hash = init_params(date_time)
hash_val = hash_data(to_hash)
hash_val_encoded = base64.b64encode(hash_val)
dga_len = ord(hash_val_encoded[0]) | (ord(hash_val_encoded[1]) << 8) | (ord(hash_val_encoded[2]) << 16) | (ord(hash_val_encoded[3]) << 24)
dga_len = dga_len % 6
dga_len += 7
if hash_val_encoded[0] <= 0×39:

hash_val_encoded[0] = 0×41

dga_res = “”

for i in xrange(dga_len):

if hash_val_encoded[i] == ‘+’:

dga_res += ‘a’

elif hash_val_encoded[i] == ‘/’:

dga_res += ’9?

else:

dga_res += hash_val_encoded[i]

return dga_res

start_date = datetime.datetime.strptime(sys.argv[1], “%Y-%m-%d”)
number_of_weeks = long(sys.argv[2])
for ix in xrange(number_of_weeks):

print generate_twitter_dga(start_date + datetime.timedelta(days=7 * ix))

[h=3]TwitterJS DGA[/h]

function twitterjs_sha1(s) {

x = Q(s)
z = s.length * 8;
x[z >> 5] |= 0×80 << (24 – z % 32);
x[((z + 64 >> 9) << 4) + 15] = z;
w = Array(80);
a = 1732584193;
b = -271733879;
c = -1732584194;
d = 271733878;
e = -1009589776;
for (i = 0; i < x.length; i += 16) {

q = a;
f = b;
g = c;
h = d;
k = e;
for (j = 0; j < 80; j++) {

if (j < 16) w[j] = x[i + j];
else w[j] = Y(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
t = Z(Z(Y(a, 5), N(j, b, c, d)), Z(Z(e, w[j]), (j < 20) ? 1518500249 : (j < 40) ? 1859775393 : (j < 60) ? -1894007588 : -899497514));
e = d
d = c;
c = Y(b, 30);
b = a;
a = t

}
a = Z(a, q);
b = Z(b, f);
c = Z(c, g);
d = Z(d, h);
e = Z(e, k)

}
return Array(a, b, c, d, e)

}

function Z(x, y) {

f = 0xffff;
l = (x & f) + (y & f);
m = (x >> 16) + (y >> 16) + (l >> 16);
return (m << 16) | (l & f)

}

function N(t, b, c, d) {

if (t < 20) return (b & c) | ((~ & d);
if (t < 40) return b ^ c ^ d;
if (t < 60) return (b & c) | (b & d) | (c & d);
return b ^ c ^ d

}
function Y(node_base_64, c) {

return (node_base_64 << c) | (node_base_64 >>> (32 – c))

}
function Q(s) {

p = Array(s.length >> 2);
for (i = 0; i < s.length * 8; i += 8) p[i >> 5] |= (s.charCodeAt(i / 8) & 0xff) << (24 – i % 32);
return p

}
node_base_64 = new ActiveXObject(‘MSXML2.DOMDocument’).createElement(‘base64?);

node_base_64.dataType = ‘bin.base64?;

for (var year = 2013; year <= 2014; year++) {

for (var month = 0; month < 12; month++) {

for (var week = 0; week <= 4; week++) {

hash = twitterjs_sha1(” + week + month + year);

y = new ActiveXObject(‘ADODB.Stream’);
y.Open;
y.Type = 2;
y.WriteText(” + hash[0] + hash[1] + hash[2]);
y.Position = 0;
y.Type = 1;
// convert the date_seed to Base64
node_base_64.nodeTypedValue = y.Read;
y.Close;
username = node_base_64.text.replace(/.*([A-Za-z]\w{14})/, ‘$1?).substr(0, 7 + week);
WScript.echo(year + ‘-’ + (month+1) + ” week ” + (week+1) + ” : ” + username);

}

}

}

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...