DISCLAIMER: The following post contains a virus sample. If you decide to mess with it you do so at your own risk. Do not go running it on your computer, at least use a VM.
Some random new “user” called @the_heat_man posted some files on the forums multiple times (after being deleted by mods) caliming it was a keygen for burpsuite. Many members of these forums were suspicious of it being malware. I, along with @Leeky, @dtm, @Cry0l1t3 and @anon3236228 (please let me know if I missed anyone) decided to reverse engineer it to see if it is. Surprisingly as well as containing a remote access trojan (RAT) it actually contains a working keygen. As such, for legal reasons I have not included a link to the original file.
The following is a writeup of the analysis of the RAT.
The keygen comes with a file called virus.txt
which contains what appears to be a link to a virus total scan of the keygen jarfile
However the hash on virus total is different to the actual file, indicating that it’s a scan of a different file
(NOTE: iirc when I originally visited the page all the scans were clean and the file name matched the file burp-loader-keygen1.7.31.jar
. The page says the last analysis was today, so that may have something to do with it.)
> shasum -a 256 burp-loader-keygen1.7.31.jar
1bf764e77a543def4c623e6e207b1b72999f6550cf49651b88d53f80ae10e4d7 burp-loader-keygen1.7.31.jar
Jars are stored as zip files so we can extract the jar with unzip.
> cp burp-loader-keygen1.7.31.jar burp-loader-keygen1.7.31.zip
> unzip burp-loader-keygen1.7.31.zip
Archive: burp-loader-keygen1.7.31.zip
creating: META-INF/
extracting: META-INF/MANIFEST.MF
creating: burploader/
extracting: burploader/Burploader.class
extracting: burploader/Data.bin
There is a class file which can be decompiled, I will use jad, which is installed on kali.
> jad burploader/Burploader.class
Here is the important part of the decompiled java code
Before this part is a base64 encoding of another jarfile contaning the keygen. That is stored in m. The decode function decodes the base64 and writes it to a file called Data.jar
This section of code contains powershell commands to download and run this powershell script http://imonty.cn/wp-includes/pomo/script/dcss/js.js
(the extension is .js but it is actually powershell)
and to also run the keygen.
Let’s download that code and have a look.
This drops two more files into a newly made c:\ProgramData\WindowsNT directory
The files are:
-
http://imonty.cn/wp-includes/pomo/script/dcss/co.js
saved asWindowsNT.ini
and -
http://imonty.cn/wp-includes/pomo/script/dcss/co.vbs
saved asWindowsNT.vbs
It then runs the visual basic script (co.vbs), so let’s look at that first
Here we have obfuscated visual basic code. The easiest method to deobfuscate is to replace the part of the code that executes with something that prints (This may not work in all cases, but it is a very useful technique). Here, clearly, the part that executes the deobfuscated code is the EVAL(ExEcUTE(www))
(circled)
(what this file is actually doing, is to split the long string at every *
and then evaluate the expressions in the resulting list, and turning the results of the math expressions into characters. Finally running the resulting string from concatenating those characters)
To print the string instead of executing we can replace the EVAL(ExEcUTE(www))
with wscript.echo www
and run it.
What this does is run the other downloaded file, co.js (saved as WindowsNT.ini) in powershell.
So let’s look at co.js
This file is large, so I have uploaded it here
I gziped and b64ed the file to make it upload nicely, you should do cat co.ps1.gzip.b64 | base64 -d | gunzip > co.ps1
to read it
I have also renamed co.js to co.ps1 to make the ./
in powershell work easier.
iex
(alias for invoke-expression) is a function that evaluates powershell code, so we need to replace it with write-output
to print it instead, then run the file.
The modified code should look like this
I was running in a new virtual machine, so I had to allow untrusted powershell scripts to execute.
After running powershell as an administrator:
PS E:\burp\burploader> ./co.ps1 > co.2.ps1
I uploaded the resulting file here
We have yet another layer of obfusication.
The beginning of this fie looks like this
This time it calls Invoke-Expression
instead of iex
, replace it with write-output
.
Now run it.
PS E:\burp\burploader> ./co.2.ps1 > co.3.ps1
The first thing to notice is that the new file has three sections separated by blank lines. I made the mistake of missing the first section and couldn’t find some needed code later (this was found by @leeky and @dtm). Instead of trying to deobfusicate the whole file at once (like I did) I learnt that it would have been better to split it 3 smaller files and do them one at a time.
First section:
The end of the section looks like this.
We don’t have something that looks as nice as invoke-expression
this time, however since the execution will be done last it is most likely that the call will be on one of the ends, the left with the deobfusicated code as an arguement, or the right, with the code piped into the standard input.
In this case the left just consists of a bracket, so let’s check the rightmost statement after a pipe (circled).
.( $PsHOmE[21]+$PShOMe[30]+'X')
Interesting. let’s see what $PsHOmE[21]+$PShOMe[30]+'X'
evaluates to.
PS E:\burp\burploader> $PsHOmE[21]+$PShOMe[30]+'X'
ieX
so we need to replace the .( $PsHOmE[21]+$PShOMe[30]+'X')
with write-output
The result is more obfusicated code.
Let’s repeat the same idea. What is .( $eNv:PuBliC[13]+$eNv:pUBLiC[5]+'x')
at the beginning
PS E:\burp\burploader> $eNv:PuBliC[13]+$eNv:pUBLiC[5]+'x'
iex
so replace it with write-output
After executing there is more obsusication, so do it again on the new file replacing
& ($pShoME[21]+$pShoME[34]+'X')
at the end with write-output
And again with &( $pShoME[21]+$pSHOMe[30]+'X')
at the beginning
Second section:
(starts with [String]::JoIN(’’,( [Char[]]( 127 ,105 )
The end of the second section looks like this
Replace .((gV '*mDR*').nAme[3,11,2]-joIn'')
at the end with write-output
It’s still obfusicated. So, look at the end .( $pShoME[4]+$PsHoMe[30]+'X')
, replace with write-output
and then execute.
And then we need to do this again, replacing &( ([sTrINg]$verbosePREFerencE)[1,3]+'x'-JOIN'')
near the beginning with write-output
And one last time, replacing . ( $Env:comsPec[4,15,25]-JOiN'')
at the end with write-output
Third section:
The start of the third section looks like this
Replace .( $PsHome[4]+$PShoME[34]+'X')
at the beginning with write-output
and execute.
This section now looks pretty readable, particually if we replace the function names.
This is as far as we can get with this method. The function names must be found through manual analysis and unmangling the variable names and content must be done through another method, such as manual or writing a script.
The output of concatenating all this back together is here
Here is a modification of the code I made without the messy variables and making up names for some of the functions.
Virustotal does not detect the powershell script as a virus here
Some heuristics, however, detect the dropper here
The first section just contains variables, however the names and values are very mangled.
From analysing the functions we worked out that the variable $dragon_middle
contains domains that the RAT will try to connect to (going through them until it can connect to one). The variables $private
and $public
contain encryption and decryption keys for data that is transfered by the RAT. @Leeky pulled these out by looping over the arrays and printing the contents. The results are here
@Cry0l1t3 Went through the domains and highlighted this one for having a different host than all the others.
@dtm made a more complete list of variables here I don’t know enough about windows to say how this was produced. But based on the format I think he used a command to print the environment (correct me if I’m wrong).
While it doesn’t have all the elements in $dragon_middle
it contains some other interesting variables such as the serivces it uses to find the victim’s ip https://api.ipify.org/
and country http://apinotes.com/ipaddress/ip.php?ip=
.
He also made a packet capture of it trying to contact a server.
The second section contains the code for encryption and decryption, and the 3rd section contains the rest of the code.
The RAT uses RSA to communicate to a server. Weirdly, I think the public key and private key have been named the wrong way around.
The public key and private key have different moduli which indicates they are most likely from different key pairs.
Messages sent to the server are encrypted with the server’s public key ($secret
in the code) and decrypted by the server’s private key (stored on the server)
Messages sent to the RAT are encrypted with the RAT’s public key (on the server) and decrypted with the RATs private key ($public
in the code)
In theory if this were done properly the message to the server wouldn’t be able to be decrypted (of course the RAT could be modified to just print it). However the keys use small primes and are thus weak.
When the RAT starts, the first thing it tries to do is gain persistence.
It does this by adding the location of the vbs file to the registry in the key HKCU:SOFTWARE\Microsoft\Windows\CurrentVersion\Run\DifenderUpdate
It then scedules the script at that location to be run on login.
I am not sure what the first part of the persistence function is doing. I think it looks like it is disabling protections in Word’s protected view, but I’m not sure why that would be needed.
Next the rat checks for debuggers by checking the names of running processes against a list of debuggers and other tools. If one is found it shuts down the computer
It then tries to connect to one of the servers listed in $dragon_middle
. Whenever the code errors in future it will also do this again (presumably because it thinks that server may be down or blocked)
Next it tries to register with the server before accepting and handling commands
The following commands are accepted
- reboot
reboots the computer - shutdown
shuts down the computer - clean
tries to wipe as much as possible from C:, D:, E:\ and F:\ before rebooting - screenshot
takes a screenshot and sends it to the server - upload
transfers a file from the server to the victim
If the command is not one of those it tries to execute it in powershell
I ran the keygen with the default execution policy (remember when I had to change it) and checked for the c:\ProgramData\WindowsNT directory to see if the RAT managed to run. The directory was not created so it seems it’s possible that it could be blocked by windows.
###Adendum:
I mentioned that the crypto is weak, this is how you break it.
I’m not going to cover the basics of how RSA works here, but there is an 0x00sec tutorial here and wikipedia has a lot of good info
In RSA two primes p
and q
are used to calculate n = p*q
the primes used should be kept secret. They are also used to calculate λ(n) = λ(p*q) = lcm(p-1, q-1)
. The public exponent e
and the private exponent d
are related to each other by the following equation d == e^(-1) (mod λ(n))
.
In the case of the RAT n
is small (as the result of using small primes) so it can easily be factored into p
and q
. They can then be used to calculate λ(n)
and then the last equation can be used to calculate d
from e
.
Using sage math
# from $private variable in rat
e = 959
n = 713
# factor n
# list(factor(n)) returns prime factors as a list of tuples of (factor, amount)
# we just want the factors
p, q = [a[0] for a in list(factor(n))]
# calculate λ(n)
l = lcm(p-1, q-1)
# calculate d
print('d = {}'.format(inverse_mod(e, l)))
gives d = 149
With the server’s private key we can write a script to decrypt messages sent to the server. If we use the same process to get the RAT’s public key we could MiTM the traffic between the RAT and the server.
Here is a decryption script in python
def decrypt(ciphertext):
key = 149
n = 713
decrypted = []
for i in range(0, len(ciphertext)):
num = int(ciphertext[i])
t = pow(num, key, n)
decrypted.append(chr(t))
return ''.join(decrypted)
nums = input().split()
print(decrypt([int(i) for i in nums]))
In @dtm’s pcap the following is sent to the server
340 362 396 383 105 598 219 362 581 362 518 73 35 73 504 220 515 665 504 515 515 35 515 518 133 335 316 665 515 665 220 665 316 181 665 335 515 38 335 335 335 316 362 663 362 145 180 396 637 383 219 362 581 362 180 383 432 432 145 219 367 362 590
Running that through the script gives the decryption as
{"TOKEN":"70e0a413a11e17db9313439c3b1fbbb9","ACTION":"COMMAND"}