Skip to content

Latest commit

 

History

History
667 lines (419 loc) · 27.4 KB

Simple_JavaScript_malware_code_obfuscation_examples.md

File metadata and controls

667 lines (419 loc) · 27.4 KB

Simple JavaScript malware code deobfuscation walkthrough

WARNING! Files do_not_run.js and do_not_run_deobfuscated.js contains Windows-targeted malware written in JavaScript and prepared to run with Windows Script Host (WSH) engine and can potentially harm your machine if you'll try to run them directly.

As I do not have Windows machine and did not test it on any Windows version, I can't give any warranty those files are safe to run on Windows. If you will decide to run them, it's up to you.


Table of contents

A couple of days ago my colleague found a piece of malicious JavaScript on his Windows machine. Luckily, this code did not run and did not harm his system.

I decided to take a look at it and go through the code. What I've found were quite simple, but still interesting methods to obfuscate JavaScript code. I'd like to explain some of them as they base on some JavaScript less known quirks - they still can be found in documentation, however in production code we do not use it very often.

This writeup is intended for beginner JavaScript developers and/or malware analysis. I will explain everything while doing code deobfuscation step by step.

First look

If you open file do_not_run.js from this repository (it contains legacy malware code) in text editor (I strongly recommend you not to run it if you're on Windows machine :-) ) - you'll see a big mess, hard to read, hard to figure out what's going on - but it's still valid and working JavaScript code.

How it's even possible?

Entry point - IIFE

Everything starts from top-level IIFE (Immediately Invoked Function Expression). This is a way to run function in JavaScript without calling it directly.

Consider example:

function hello(message) {
	console.log(message)
}

If you try to run this code, nothing will happen. To make it work, you have to call function hello() passing some message as an argument:

function hello(message) {
	console.log(message)
}

hello('This is test')

Now, you should be able to see This is test message displayed in your browser (or Node) console.

Let's make IIFE from this code:

(function hello(message) {
	console.log(message)
})('This is test')

And some magic happen - even without calling hello() function, this piece of code works. How it's possible?

  • parenthesis around function definition makes a valid JavaScript expression from it. Expression is the simplest piece of code which returns a value (like 2+2 which is also valid JavaScript expression).
  • parenthesis with This is test runs the function passing an argument to it. Technically, (fn(x){})(x) is equivalent of fn(x) definition and call in one.

IIFE recommended read

https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch3.md#invoking-function-expressions-immediately


In our malware sample we have similar call:

(function(quhuvu6) {

	// ...
	
}("41553a304f0b442551284206" + "672651014d1e1a60127" + ...

This causes malware runs on its own; passing some very, very long ASCII string as an argument for IIFE. Then, first line inside this function is run:

var defiq = cicuza(quhuvu6);

The result saved in variable defiq is an array of decimal values, with about 9000 elements. Function, which transforms ASCII string into this array uses couple of tricks, which now we'll analyze in details.

Basic obfuscation methods - expressions, comma operator, parseInt() and toString() method

First, take a look at cicuza() function, before we'll go through and simplify it to more clean and readable version:

function cicuza(syhri) {
        var fahomyfo = [];
        for (var segovmiw4 = parseInt((0).toString(36)) /*CN1b367Z19XZqi8XgI67*/ ; segovmiw4 < syhri["l" + ("F", "T", "H", "e") + "n" + ("G", "n", "O", "g") + (29).toString(36) + ("u", "X", "U", "p", "h")]; segovmiw4 += parseInt((2).toString(36))) {
            fahomyfo[("E", "w", "f", "F", "p") + ("G", "i", "L", "u") + "s" + "h"](parseInt(syhri["s" + "u" + "b" + "s" + ("M", "h", "M", "U", "f", "t") + ("M", "q", "r")](segovmiw4, (85, 19, 84, 9, 2)), parseInt((42).toString(0x24)) /*uShFAoMcgqPvcds6w2xD*/ ));
        }
        return fahomyfo;
    };
    

First step - let's rename fahomyfo to something meaningful. Names like this are very common in malware - it's just some way to hide real purpose of each variable or function.

In cicuza() variable fahomyfo is declared as an array and then, after some logic in for loop - returned as a result. So let's name it result:

function cicuza(syhri) {
        var result = [];
        for (var segovmiw4 = parseInt((0).toString(36)) /*CN1b367Z19XZqi8XgI67*/ ; segovmiw4 < syhri["l" + ("F", "T", "H", "e") + "n" + ("G", "n", "O", "g") + (29).toString(36) + ("u", "X", "U", "p", "h")]; segovmiw4 += parseInt((2).toString(36))) {
            result[("E", "w", "f", "F", "p") + ("G", "i", "L", "u") + "s" + "h"](parseInt(syhri["s" + "u" + "b" + "s" + ("M", "h", "M", "U", "f", "t") + ("M", "q", "r")](segovmiw4, (85, 19, 84, 9, 2)), parseInt((42).toString(0x24)) /*uShFAoMcgqPvcds6w2xD*/ ));
        }
        return result;
    };

Ok, now, let's see what's going on in for loop. First part is to define initial value of variable which determines start value:

for (var segovmiw4 = parseInt((0).toString(36)) /*CN1b367Z19XZqi8XgI67*/ ; ....

So, what parseInt((0).toString(36)) does?

First call is (0).toString(36) - this code contains two chained operations:

  • (0) is an expression, which value is number 0
  • .toString(36) is a method which returns String representation of JavaScript object. Every object in JavaScript contains this method. When toString() is called on primitives, like 0 in this case, this primitive value is converted into object (of type Number), then toString() is called on this object and finally new primitive (string) is returned.

When toString() is called on Number object, additional argument can be passed. This argument is an integer between 2 and 36 specifying the base to use for representing numeric values. So, if toString(36) is called, argument represents base 36 - Hexatrigesimal system with digits represented by numbers 0-9 and letters a-z. Why it's important we'll see in further steps. Now, because 0 in any system is still 0, (0).toString(36) returns... 0.

  • second call, parseInt(0) returns 0 as well. parseInt() is a function which parses any passed string into Integer and returns it or, if such conversion is not possible, returns NaN (which is JavaScript representation of Not a Number value). In this case, it's just 0 parsed to Integer - results in 0 itself.

Finally, parseInt((0).toString(36)) is just a way to represent 0. But did you notice how many operations has to be done? This is what code obfuscation in JavaScript is all about - to make code as complicated as it's possible even if all we need is just 0.

Let's also rename segovmiw4 into i, which is popular name for iterator variable inside for loops.

So after our first analysis, our code becomes:

function cicuza(syhri) {
        var result = [];
        for (var i = 0; i < syhri["l" + ("F", "T", "H", "e") + "n" + ("G", "n", "O", "g") + (29).toString(36) + ("u", "X", "U", "p", "h")]; i += parseInt((2).toString(36))) {
            result[("E", "w", "f", "F", "p") + ("G", "i", "L", "u") + "s" + "h"](parseInt(syhri["s" + "u" + "b" + "s" + ("M", "h", "M", "U", "f", "t") + ("M", "q", "r")](i, (85, 19, 84, 9, 2)), parseInt((42).toString(0x24)) /*uShFAoMcgqPvcds6w2xD*/ ));
        }
        return result;
    };

Number.toString() documentation

http://devdocs.io/javascript/global_objects/number/tostring

Hexatrigesimal system converter

http://www.calculand.com/unit-converter/zahlen.php?zs=36


Now, let's focus on what's exactly going on in this piece of code:

i < syhri["l" + ("F", "T", "H", "e") + "n" + ("G", "n", "O", "g") + (29).toString(36) + ("u", "X", "U", "p", "h")];

We know that syhri is string. As we know, we can read strings using indexes like in Array type. String[0] means first character of the string, String[1] second character and so on. String has also a property length, which represents its length in bytes.

But, what's this strange ("F", "T", "H", "e") construction means? Figure it out:

  • as I mentioned earlier, everything inside () is an expression. ("F") is an expressions which value is char "F":

Expression

  • comma operator , separates values and only the last one is used. Consider this example:

Comma

Now, combine those two things together:

Expression

What was assigned to value was the last char (comma operator) returned from () expression.

Back to our malware, we can read four first chars inside i < syhri[...] which are: l, e, n and g. Fifth one is again construction explained earlier, but this time, toString(36) is called on Number 29 - so (29).toString(36) returns 29th cipher in Hexatrigesimal system, which is t. Last one expression returns h and finally, after concatenation with + operator - we can found syhri["length"] as a result.

Also we can rename syhri into something friendly, like val and deobfuscate last part of for loop - parseInt((2).toString(36)) is just 2.

Great, what we've got so far then?

function cicuza(val) {
        var result = [];
        for (var i = 0; i < val["length"]; i += 2) {
            result[("E", "w", "f", "F", "p") + ("G", "i", "L", "u") + "s" + "h"](parseInt(val["s" + "u" + "b" + "s" + ("M", "h", "M", "U", "f", "t") + ("M", "q", "r")](i, (85, 19, 84, 9, 2)), parseInt((42).toString(0x24)) /*uShFAoMcgqPvcds6w2xD*/ ));
        }
        return result;
    };

We simplify the whole for. Now, it's time to do the same with the expression inside the loop.

Using the same methods, we find result[("E", "w", "f", "F", "p") + ("G", "i", "L", "u") + "s" + "h"] is equal to result["push"]. What's that mean?

As we know, in JavaScript we can call any Object property, like methods, using a dot notation. For example, if we have an array named arr, we can call its push() method using .:

let arr = [] // declare Array object named arr
arr.push(10) // adds 10 as a first element of Array arr
arr.push(20) // adds 20 as a second element
console.log(arr) // prints [10, 20]

But, we can also call any method or Object property using [] operator. In this case, Object["propertyName"] is the same as Object.propertyName The code above can be simply rewrite into this one:

let arr = []    // declare Array object named arr
arr["push"](10) // adds 10 as a first element of Array arr
arr["push"](20) // adds 20 as a second element
console.log(arr) // prints [10, 20]

So result["push"] adds value to result array. And this value comes from following expression:

parseInt(val["s" + "u" + "b" + "s" + ("M", "h", "M", "U", "f", "t") + ("M", "q", "r")](i, (85, 19, 84, 9, 2)), parseInt((42).toString(0x24)) /*uShFAoMcgqPvcds6w2xD*/ )

Fragment ["s" + "u" + "b" + "s" + ("M", "h", "M", "U", "f", "t") + ("M", "q", "r")] returns name of substr function - one of the String object method which just returns part of the string. substr() accepts one or two arguments: first one is the index of first char of returned part and second one (optional) is a length of this part. If second argument is not passed, substr returns everything starting from position passed as first (and only) argument.

Consider examples:

let s = "Malware"
console.log(s.substr(0,2))   // Ma
console.log(s.substr(2,4))   // lwar
console.log(s.substr(3))     // ware

So far, we get:

parseInt(val["substr"](i, (85, 19, 84, 9, 2)), parseInt((42).toString(0x24)) /*uShFAoMcgqPvcds6w2xD*/ )

We can see that in this case two arguments are passed to val["substr"] and they are i which is for loop current iterator value and 2 (result of expression (85, 19, 84, 9, 2):

val["substr"](i, 2)

Last part is parseInt((42).toString(0x24)). As we know, it parses 42 into Integer using 0x24 base. 0x24 is a hexadecimal value equals decimal 36. So finally we get 42 converted into String using Hexatrigesimal system:

42

How it works? in Hexatrigesimal system we have 36 ciphers and 35 decimal is equal to z in hexatrigesimal. 36 becomes 10, 37 becomes 11, 38 becomes 12 and so on, so 42 becomes 16. As result of parseInt((42).toString(0x24)) is simply parseInt(16) - finally we get just 16 (because parseInt(16) equals 16 itself)

This is our deobfuscated cicuza() function (notice I've changed [] method calls into . notation):

    function cicuza(val) {
        var result = [];
        for (var i = 0; i < val.length; i += 2) {
            result.push(parseInt(val.substr(i, 2), 16));
        }
        return result;
    };

Now we can see what this function actually does. As val contains initial very, very long string passed as an argument to malware's top level IIFE, cicuza() iterates over it, substract every two characters, converts them from hexadecimal value into decimal one and finally pushes as an element of result array.

So as example - taking the first part of that very long string, which is "41553a304f0b442551284206" - 41 becomes 65, 55 becomes 85, 3a becomes 58, 30 becomes 48, 4f becomes 79 and so on. In the end, we end up with big array contains decimal values. And this array becomes as a value of defiq in our malware:

(...)
	var defiq = cicuza(quhuvu6);   // here we are so far :)
    var permy = "H@D~7a84O";
    var paghimqycgi = {
        getpy: "myqniroqa3"
    };
        
    (...)

substr() documentation

http://devdocs.io/javascript/global_objects/string/substr


Getting function constructor

Very common method in obfuscation of JavaScript malware is to hide any function definitions and calls. Why? Take a look at this code:

function doubleX(x) {
	return x * 2
}

doubleX(10) // returns 20

It's obvious what this code does. This is not something what can be considered as sophisticated piece of malware code rather...

Let's try to obfuscate it a little:

let xcf = new Function("x","return x * 2")

xcf(10)   // returns 20 as well

How it works?

Function is a special method in JavaScript, which constructs new function (it works as function constructor). As arguments it accepts list of arguments for function which should be returned and, as last argument - body of this created function.

So in our example with xcf - first argument passed into Function() is an argument for xcf, and second one is body of xcf. Finally, we get function works in the same way as doubleX().

Before we'll proceed, one important thing to mention here: every function in JavaScript has a property named constructor, which in fact is a Function() itself:

Function constructor

We've declared an array object. Then, we check that a.forEach method has its own property called constructor - and it is Function().

Why do we need this?

Because now, to define xcf() function, instead of calling new Function() you can do something like this:

let xcf = a.forEach.constructor("x","return x * 2")

xcf(10)  // yep, it works! 20

Can you see advantages of this? Malware can hide declarations of any function, under any name and static code analysis tools can't find any explicit function declarations!

Very similar method can be used to assign built-in methods to totally randomly named variables: :

let sdfgfdg = "".substr
sdfgfdg.call("malware",1,2)   // "al"

call() is a way to run our function. Consider following example:

"malware".substr(1,2)  // "al"

We call substr method on "malware" string. But we can also call function sdfgfdg, which we assign String.substr(), passing a string "malware" as an object on which our sdfgfdg has to be executed and also pass actual arguments.


call() and apply(), as well as bind() are quite advanced concepts in JavaScript. I strongly recommend to read about them in fantastic "You Don't Know JS" series by @getify (Kyle Simpson):

https://github.com/getify/You-Dont-Know-JS


Last problem for malware is to hide explicit constructor call.

Our malware does it as follows:

var xewubdiwhit = "kydka"[(12).toString(36) + (24).toString(36) + ("r", "w", "h", "Z", "n") + ("n", "X", "L", "s", "w", "s") + "t" + (27).toString(36) + "u" + "c" + ("d", "m", "b", "t") + ("z", "E", "z", "n", "o") + ("N", "J", "r")];

As "kydka" is a string, it has, as every object in JavaScript, method constructor - and word constructor is build using already known method with converting numbers to strings with hexatrigesimal system, () expressions returned last element from comma-separated list of characters and finally concatenates them using + (for string it means concatenation) operator:

var xewubdiwhit = "kydka"["constructor"]

Now, when we know what it does, let's refactor code above into something more readable:

var fnConstructor = String.constructor

Now, first lines of our deobfuscated malware are:

(function(quhuvu6) {
    var defiq = cicuza(quhuvu6);
    var permy = "H@D~7a84O";
    var paghimqycgi = {
        getpy: "myqniroqa3"
    };
    var fnConstructor = String.constructor;
    var tyttaluli = "mokzine";

    var dikol = [];
    var mirjokbynet = 1;  // (27, 50, 52, 21, 1) equals 1
    
    (...)

More on functions call() and apply()

http://devdocs.io/javascript/global_objects/function/call

http://devdocs.io/javascript/global_objects/function/apply


Logical operators tips and tricks

In our analysis we get into this fragment:

    while (mirjokbynet <= permy[("h", "g", "B", "W", "l") + "e" + (23).toString(0x24) + "g" + ("u", "Z", "W", "u", "t") + (17).toString(36)]) {
        dikol = (permy[("M", "H", "s") + ("C", "N", "D", "u") + (11).toString(0x24) + (28).toString(0x24) + ("T", "k", "t") + "r"](permy[(21).toString(0x24) + "e" + "n" + (16).toString(36) + ("L", "S", "x", "t") + ("I", "D", "h") /*Q5E278CBpoixvOtUNpix*/ ] - mirjokbynet))[("d", "R", "p", "s") + ("H", "K", "A", "s", "D", "p") + "l" + "i" + ("Y", "b", "h", "t") /*O7MOfVrRkP9RlXlfKLxi*/ ]('');
        for (var juqno = +!!false; juqno < defiq[("R", "t", "E", "l") + ("y", "e", "f", "R", "e") + (23).toString(36) + (16).toString(36) + "t" + "h" /*H3RMPYzEeu55OVeGgb1v*/ ]; juqno++) {
            defiq[juqno] = defiq[juqno] ^ dikol[juqno % dikol["l" + (14).toString(36) + "n" + (16).toString(0x24) + (29).toString(0x24) + ("X", "O", "c", "m", "h")]]["c" + ("G", "w", "R", "e", "h") + (10).toString(36) + ("A", "W", "V", "i", "r") + "C" + ("Z", "O", "W", "o") + ("R", "N", "A", "y", "d") + "e" + "A" + "t" /*YZz3OuivKuwgqjkFVKu0*/ ]((88, 53, 3, 90, 0));
        }
        mirjokbynet++;
    };

Apart of methods we discussed so far, we can spot here couple of strange logical operators usages.

First, let's simplify the code and deobfuscate all we can using already know techniques. I've renamed mirjokbynet into k and juqno into j as well:

    while (k <= permy.length) {
        dikol = (permy.substr(permy.length - k)).split('');
        for (var j = +!!false; j < defiq.length; j++) {
            defiq[j] = defiq[j] ^ dikol[j % dikol.length].charCodeAt(0);
        }
        k++;
    };

This looks better.

Now, we can follow code execution in this fragment of malware. As we can see, in while condition variable named permy is used and its definition assign "H@D~7a84O" string as its value.

It's time to figure out how this fragment works:

 while (k <= 9) {   // 9 is value of permy.length

Next line defines value for array dikol:

dikol = (permy.substr(permy.length - k)).split('');

As in this place value of k equals 0, dikol becomes the last element of permy string (which is splitted into an array by split('') call), so its value in first while iteration becomes [0]

Now, the for part:

for (var j = +!!false; j < defiq.length; j++) {
	defiq[j] = defiq[j] ^ dikol[j % dikol.length].charCodeAt(0);
}

Into what value +!!false evaluates?

  • initialy, it is false
  • then, first !, which is logical NOT (negation) changes it into true
  • and next ! again changes it into false
  • finally, + casts Boolean value false into Integer 0 (you can check this flow, step by step, in Chrome console):

False

So many efforts to get just one 0...

Next, we have another logical operators inside for loop. As we found, defiq is an array with decimal values (it's initial string passed into IIFE after couple of transformations we discovered earlier).

We will focus now on first element of defiq array, which is 65. The result of expression defiq[j] ^ dikol[j % dikol.length].charCodeAt(0) is calculated in several steps:

  • j is actual loop control value. For first element it will be 0, so the result of j % dikol.length is 0 (0 % 1 = 0). Remember that dikol.length in this iteration equals 1.
  • now, we have expression defiq[j] ^ dikol[0].charCodeAt(0) which evaluates into 65 ^ 79 (65 is first element of defiq array and 79 is a result of dikol[0].charCodeAt(0) - dikol[0] equals O (capital o), and 'O'.charCodeAt(0) equals 79
  • operator ^ is bitwise operator XOR (eXclusive OR). The result of expression 65 ^ 79 is 14.

Next, k++ increments value of k and the whole operation starts again. This loop lasts as long as whole defiq array is transformed into new values.


Logical and bitwise operators

! (logical NOT)

http://devdocs.io/javascript/operators/logical_operators#Logical_NOT

^ (bitwise XOR)

http://devdocs.io/javascript/operators/bitwise_operators#Bitwise_XOR

https://en.wikipedia.org/wiki/Bitwise_operation#XOR


Almost done!

Last fragment of malware should be easy to deobfuscate with all tricks we've learnt:

    for (var vaxofibcid = +!!false; vaxofibcid < defiq[(21).toString(0x24) + (14).toString(0x24) + ("o", "w", "n") + "g" + ("W", "w", "v", "K", "t", "t") + (17).toString(36)]; vaxofibcid++) {
        defiq[vaxofibcid] = "ms" ["c" + "o" + ("T", "L", "w", "T", "n") + ("p", "z", "K", "O", "P", "s") + (29).toString(0x24) + ("k", "B", "o", "L", "P", "r") + ("K", "b", "g", "t", "u") + (12).toString(36) + ("Y", "U", "T", "t") + (24).toString(0x24) + ("Y", "i", "r")][("p", "V", "E", "L", "G", "f") + "r" + ("X", "X", "o") + "m" + ("d", "R", "J", "A", "C") + "h" + (10).toString(0x24) + (27).toString(0x24) + (12).toString(0x24).toUpperCase() + "o" + ("K", "Y", "a", "v", "N", "d") + (14).toString(36) /*PBi4j6Mle9j71igjdR2P*/ ](defiq[vaxofibcid]);
    };


    tyttaluli = defiq[("Q", "Z", "U", "j") + ("H", "J", "o") + (18).toString(36) + ("v", "s", "I", "v", "N", "n") /*vdmIiGO523flagErARiC*/ ]('');
    paghimqycgi["t" + ("J", "g", "G", "o") + "S" + (29).toString(36) + ("E", "N", "p", "r") + "i" + (23).toString(36) + ("a", "g", "X", "v", "g") /*UnDf5WWU1YYgk8xYA8QT*/ ] = fnConstructor[(12).toString(36) + "o" + ("N", "B", "a", "u", "g", "n") + (28).toString(36) + "t" + "r" + "u" + (12).toString(0x24) + (29).toString(0x24) + ("O", "J", "g", "V", "H", "o") + (27).toString(0x24)](tyttaluli);
 	var gogoq = "1cf4d5" + paghimqycgi + "631a403f5";

becomes

for (var r = +!!false; r < defiq.length; r++) {
    defiq[r] = String.fromCharCode(defiq[r]);
};


t = defiq.join('');
v.toString = fnConstructor.constructor(t);
var gogoq = "1cf4d5" + v + "631a403f5";

The only purpose of all this code is to perform manipulation of an array, to finally get a string passed to nilse function:

    nilse(gogoq);


    function cicuza(val) {
        (...)
    };



    function nilse(tefkysab) {
        return (new Function()(tefkysab));
    };
    

And that's pretty all. Our deobfuscated malware sample looks now much more readable but it still can be quite hard to run it and make it works (I found that there's some error which does not allow to finish last transformation):

(function(quhuvu6) {
    var defiq = cicuza(quhuvu6);
    var permy = "H@D~7a84O";
    var v = {
        getpy: "myqniroqa3"
    };
    var fnConstructor = String.constructor;
    var t = "mokzine";

    var dikol = [];
    var k = 1;

    while (k <= permy.length) {
        dikol = (permy.substr(permy.length - k)).split('');
        for (var j = +!!false; j < defiq.length; j++) {
            defiq[j] = defiq[j] ^ dikol[j % dikol.length].charCodeAt(0);
        }
        k++;
    };

    for (var r = +!!false; r < defiq.length; r++) {
        defiq[r] = String.fromCharCode(defiq[r]);
    };


    t = defiq.join('');
    v.toString = fnConstructor.constructor(t);
    var gogoq = "1cf4d5" + v + "631a403f5";

    nilse(gogoq);


    function cicuza(val) {
        var result = [];
        for (var i = 0; i < val.length; i += 2) {
            result.push(parseInt(val.substr(i, 2), 16));
        }
        return result;
    };

    function nilse(tefkysab) {
        return (new Function()(tefkysab));
    };
}("41553a304f0b442(......)  // string cut for readability

Summary

Methods presented in this post are quite simple and based on JavaScript language syntax. Although they were found in real life code (infection vector was fake Chrome update - as I mentioned at the beginning of this post, the code has not been run thanks to Windows setting showing file extension and my colleague spots that this is not update installer but JS file and did not run it)

Main intention of this file was to trick anyone who download this code to click it. After all transformation we followed, the final version of this code becomes Windows Script Host (WSH) file which can for example download and run real malware or ransomware.

If you are curious how it will be possible, you can take a look at my previous post, where I'm going through RAA ransomware, popular JavaScript ransomware with CryptoJS build-in library and Locky malware dropper:

https://github.com/bl4de/research/blob/master/raa-ransomware-analysis/README.md


Windows Script Host

https://en.wikipedia.org/wiki/Windows\_Script\_Host


Comments? Thoughts? Questions?

If you have any questions or doubts - feel free to contact me:

Thank you for reading!

Best Regards,