24H@CTF - Beware.js
My write up for Beware, a reverse challenge from 24H@CTF.
This was the preamble to Beware, a medium challenge categorized as reverse during the 24H@CTF :
Our malware detection system has spotted this script. Can you analyze it to see how it behaves? :) Disclaimer : The script is harmless but you must use a virtual machine if you want to run it or analyze it dynamically.
450 points, 11 solves
Reconnaissance
We are given
beware.js
. Let's take a look at it :
From the first glance, I noticed a few things : - this looks like regular JavaScript ; - the names of variables and functions are obscured with Latin looking words ; - the strings are not plaintext ; and - there's a lot of maths going on there.
Seeing the probably encrypted string and how the function is adding and subtracting characters together, I'll a take a guess and assume that this is some sort of decryption function used to evade detection.
Scrolling down a bit, I saw something that is not standard JS:
On line 33, I noticed
WScript.CreateObject
. This is a function that does not exist in regular JavaScript and because of some prior knowledge, I know that this must be JScript, a scripting language commonly used for malware stagers on Microsoft Windows.
I continued scrolling down and noticed something weird on line 83 :
There is a call to
nuptias()
(the decryption function) with only one parameter instead of two. That felt weird when I saw that, so I decided to take a second look at the function declaration
A closer look at nuptias()
The two parameters are called
inpigre
and
artuum
. The first one seems to be a string and the second seems to be an integer. But look closely at line 3 :
artuum
is reassigned, which means that the second parameter has no impact on the output of the function... If you calculate the value of
artuum
, you get that it is equal to 0.
The decryption start from the second-to-last character of the string because
sunt
, an iterator, is initialized to that index. If it's the first iteration, the characters at
inpigre[sunt]
and
inpigre[sunt+1]
are parsed as a hexadecimal number, are interpreted as a character and appended to the end of
manibus
. If it's not the first iteration, the same thing happen but the value of the previous character is subtracted from it and
artuum
is incremented by one. In the end,
manibus
is reversed and concatenated to form a string that is returned.
Now that I know that the second parameter is useless, let's see if that means I can ignore all the maths.
Is math useless ?
$ grep -Po '^var \K\w+(?= = 0x.*)' beware.js
rabiem
respectu
ferebatur
That command returns the name of the first three variables, those that are used to do the maths. I want to know if these variables are something other than a second parameter for the decryptor.
$ grep -Ev '^(|var )(rabiem|respectu|ferebatur)' beware.js|grep -E 'rabiem|respectu|ferebatur'
var dextris = WScript.CreateObject(nuptias(cavatis[3], rabiem));
var rogati = WScript.CreateObject(nuptias(cavatis[5], respectu));
var pergunt = WScript.CreateObject(nuptias(cavatis[4], ferebatur));
dextris.RegWrite(nuptias(cavatis[0]), nuptias(cavatis[7], respectu));
adversando = dextris.expandEnvironmentStrings(nuptias(cavatis[1], rabiem));
adversando += nuptias(cavatis[2], rabiem);
rogati.WriteText(nuptias(cavatis[6], respectu));
That command will
grep
every line that does not start with an assignment of either of these three variables, but will grab every line that uses it. This allows me to know how those variables are used outside of their ridiculous amount of assignments. From the result of that command, I know that they are only used as a second argument to
nuptias()
, so I can safely just ignore those.
Summary
Let's synthesize the information I have so far : - I have a file that looks like it might be a stager for some malware ; - almost all the objects' names are replaced with Latin looking words ; - there is a lot of useless maths ; and - there is a function that decrypt a string.
With that in mind, let's write a deobfuscator!
Deobfuscation
beware.js
After some coding, I have completed my deobfuscator . You can look at the code, I've written a lot of comments to guide you through it.
As for the deobfuscated code, here's what I got :
var dextris = WScript.CreateObject('wscript.shell');
var rogati = WScript.CreateObject('ADODB.Stream');
var pergunt = WScript.CreateObject('Scripting.FileSystemObject');
dextris.RegWrite('HKCU\\Software\\G00gl3\\key', 'I4m_the_de0bfusc4t10n_key');
adversando = dextris.expandEnvironmentStrings('%APPDATA%');
dextris.CurrentDirectory = adversando;
adversando += '\\encoded_file.jse';
rogati.Open();
rogati.Type = 2;
rogati.Position = 0;
rogati.WriteText('try {a();} catch (aa) {eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!\'\'.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return\'\\\\w+\'};c=1};while(c--)if(k[c])p=p.replace(new RegExp(\'\\\\b\'+e(c)+\'\\\\b\',\'g\'),k[c]);return p}(\'u 8(a){7 b=[];7 j=0;h(7 i=a.e-2;i>=0;i-=2){F(i===a.e-2){b.k(l.m(v(a.f(i)+a.f(i+1),16)))}G{b.k(l.m(v(a.f(i)+a.f(i+1),16)-b[j].w(0)));j++}}x b.H().y(\\\'\\\')};u z(a,b){7 c=0;n=[];h(7 i=0;i);
rogati.SaveToFile(adversando, 2);
rogati.Close();
dextris.Run('"' + adversando + '"', 0, false);
pergunt.DeleteFile(WScript.ScriptFullName);
The script begins by creating objects to run commands (
wscript.shell
) and interact with the file system (
ADODB.Stream
and
Scripting.FileSystemObject
). It then stores some some of decryption key to the registry and open
%APPDATA%\encoded_file.jse
to write some packed JavaScript. Once that is done, it runs the newly written file and delete itself. At first, we supposed that this file was malicious, but now we know that it certainly is.
%APPDATA%\encoded_file.jse
I copied the
rogati.WriteText
string and unpacked it using a little python snippet. This snippet needs
jsbeautifier
installed. That can be done by running
pip install jsbeautifier
. When running, you just paste the string and it will write the unpacked version to `decoded_file.js :
python -c "from jsbeautifier import Beautifier;p=input();print(Beautifier().beautify(eval(p)))" > decoded_file.js
When I opened
decoded_file.js
, I immediately recognized
urbes()
as being the decryptor that we saw earlier. When I looked at
nominibus()
, I needed a bit more time.
nominibus()
seems like it's initializing
c
by adding together all the characters from
b
together. Then, there is the decryption phase where it XORs each character from
a
individually with
c
and append them to
rem
. It returns the resulting array as a string. For
nominibus()
, both parameters seems useful.
Let's write some deobfuscator for this new script based on what I had made earlier. After running the code through it, I get that output :
var perfusorumque = [2397, 2379, 2423, 2340, 2401, 2424, 2416, 2379, 2428, 2336, 2402, 2343, 2379, 2422, 2343, 2343, 2426, 2379, 2336, 2379, 2393, 2336, 2424, 2341, 2423, 2341, 2340, 2401, 2407, 2379, 2375, 2423, 2406, 2341, 2404, 2400];
var autem = WScript.CreateObject('wscript.shell');
var vexillum = 'FLAG-{' + nominibus(perfusorumque, autem.RegRead('HKCU\\Software\\G00gl3\\key')) + '}';
autem.RegDelete('HKCU\\Software\\G00gl3\\');
var laetabatur = new ActiveXObject('wscript.shell');
laetabatur.Popup('Beware of me!');
laetabatur = WScript.CreateObject('Scripting.FileSystemObject');
laetabatur.DeleteFile(WScript.ScriptFullName);
The script creates a
wscript.shell
object, decrypt and print the flag, delete the registry key that contained the decryption key, launch a popup that says
Beware of me!
and finally delete itself. In an out-of-CTF context, I could say that this script is inoffensive and move on, but there is a line with
FLAG-{
and a call to what I've called
xor_decrypt
in my deobfuscation script.
To get the rest of the flag, I added a feature to my script that makes it possible to call that function when the script is run without arguments. Using the value stored in the register in the last script, I ran :
$ ./beware-deobfuscator2.py
xor_decrypt(arg1, arg2)
arg1=[2397, 2379, 2423, 2340, 2401, 2424, 2416, 2379, 2428, 2336, 2402, 2343, 2379, 2422, 2343, 2343, 2426, 2379, 2336, 2379, 2393, 2336, 2424, 2341, 2423, 2341, 2340, 2401, 2407, 2379, 2375, 2423, 2406, 2341, 2404, 2400]
arg2=I4m_the_de0bfusc4t10n_key
I_c0uld_h4v3_b33n_4_M4l1c10us_Scr1pt
There we have it, the encrypted part of the flag. Putting all the parts together, we get :
FLAG-{I_c0uld_h4v3_b33n_4_M4l1c10us_Scr1pt}
Lessons learned
I think that this challenge was entertaining. This was the first time I've written a deobfuscator, but it was definitely not necessary. A simple
Search and Replace
in your favorite editor and a Node.js interpreter would be enough to quickly find the flag, but I think there is more to learn by looking at it as if it was more than just a challenge. Like that, the next time I encounter obfuscated code that can't be solved with search-and-replacing, I'll be a step ahead.
Thanks to @Edouard#6067 from PolyHx for this challenge :-)