Skip to content

Commit fa6b298

Browse files
Merge pull request #79 from kirk-sayre-work/master
8/24/2022 changes from the kirk-sayre-work dev fork of box-js.
2 parents e41c4ae + 995d01a commit fa6b298

File tree

9 files changed

+6768
-2403
lines changed

9 files changed

+6768
-2403
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Box.js will emulate a Windows JScript environment, print a summary of the emulat
4646
4747
>If you wish to automate the analysis, you can use the return codes - documented in `integrations/README.md` - to distinguish between different types of errors.
4848
49+
## Analysis Fails Due to Missing 'document' Object
50+
51+
The box-js repository from git includes a `boilerplate.js` file. This file defines some stubbed versions of common browser objects such as document. Try rerunning your analysis with the `--prepended-code=DIR/boilerplate.js` option, where `DIR` is the directory of the cloned box-js repository. The `--prepended-code` option tells box-js to prepend the JavaScript in the given file to the sample being analyzed.
52+
4953
## Batch usage
5054

5155
While box.js is typically used on single files, it can also run batch analyses. You can simply pass a list of files or folders to analyse:
@@ -83,6 +87,7 @@ cat ./*.results/active_urls.json | sort | uniq
8387
current directory)
8488
--preprocess Preprocess the original source code (makes reverse engineering easier, but takes
8589
a few seconds)
90+
--prepended-code Prepend the JavaScript in the given file to the sample prior to sandboxing
8691
--unsafe-preprocess More aggressive preprocessing. Often results in better code, but can break on
8792
some edge cases (eg. redefining prototypes)
8893
--no-kill Do not kill the application when runtime errors occur

analyze.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ function lacksBinary(name) {
6060
}
6161

6262
function rewrite(code) {
63+
64+
// box-js is assuming that the JS will be run on Windows with cscript or wscript.
65+
// Neither of these engines supports strict JS mode, so remove those calls from
66+
// the code.
67+
code = code.replace(/"use strict"/g, '"STRICT MODE NOT SUPPORTED"');
68+
69+
// Some samples (for example that use JQuery libraries as a basis to which to
70+
// add malicious code) won't emulate properly for some reason if there is not
71+
// an assignment line at the start of the code. Add one here (this should not
72+
// change the behavior of the code).
73+
code = "__bogus_var_name__ = 12;\n\n" + code;
74+
6375
if (code.match("@cc_on")) {
6476
lib.debug("Code uses conditional compilation");
6577
if (!argv["no-cc_on-rewrite"]) {
@@ -444,6 +456,7 @@ const sandbox = {
444456
//Blob : Blob,
445457
logJS: lib.logJS,
446458
logIOC: lib.logIOC,
459+
logUrl: lib.logUrl,
447460
ActiveXObject,
448461
dom,
449462
alert: (x) => {},

boilerplate.js

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
const parse = require("node-html-parser").parse;
2+
3+
function extractJSFromHTA(s) {
4+
const root = parse("" + s);
5+
items = root.querySelectorAll('script');
6+
r = "";
7+
var chunkNum = 0;
8+
for (let i1 = 0; i1 < items.length; ++i1) {
9+
item = items[i1];
10+
for (let i2 = 0; i2 < item.childNodes.length; ++i2) {
11+
chunkNum += 1;
12+
child = item.childNodes[i2]
13+
attrs = ("" + child.parentNode.rawAttrs).toLowerCase();
14+
if (!attrs.includes("vbscript")) {
15+
r += "// Chunk #" + chunkNum + "\n" + child._rawText + "\n\n";
16+
}
17+
}
18+
}
19+
return r;
20+
}
21+
22+
var location = {
23+
24+
/*
25+
Location.ancestorOrigins
26+
Is a static DOMStringList containing, in reverse order, the origins
27+
of all ancestor browsing contexts of the document associated with
28+
the given Location object.
29+
*/
30+
ancestorOrigins: '',
31+
32+
/*
33+
Location.href
34+
Is a stringifier that returns a USVString containing the entire
35+
URL. If changed, the associated document navigates to the new
36+
page. It can be set from a different origin than the associated
37+
document.
38+
*/
39+
href: 'http://mylegitdomain.com:2112/and/i/have/a/path.php',
40+
41+
/*
42+
Location.protocol
43+
Is a USVString containing the protocol scheme of the URL, including
44+
the final ':'.
45+
*/
46+
protocol: 'http:',
47+
48+
/*
49+
Location.host
50+
Is a USVString containing the host, that is the hostname, a ':', and
51+
the port of the URL.
52+
*/
53+
host: 'mylegitdomain.com:2112',
54+
55+
/*
56+
Location.hostname
57+
Is a USVString containing the domain of the URL.
58+
*/
59+
hostname: 'mylegitdomain.com',
60+
61+
/*
62+
Location.port
63+
Is a USVString containing the port number of the URL.
64+
*/
65+
port: '2112',
66+
67+
/*
68+
Location.pathname
69+
Is a USVString containing an initial '/' followed by the path of the URL.
70+
*/
71+
pathname: '/and/i/have/a/path.php',
72+
73+
/*
74+
Location.search
75+
Is a USVString containing a '?' followed by the parameters or
76+
"querystring" of the URL. Modern browsers provide URLSearchParams
77+
and URL.searchParams to make it easy to parse out the parameters
78+
from the querystring.
79+
*/
80+
search: '',
81+
82+
/*
83+
Location.hash
84+
Is a USVString containing a '#' followed by the fragment identifier
85+
of the URL.
86+
*/
87+
hash: '',
88+
89+
/*
90+
Location.origin Read only
91+
Returns a USVString containing the canonical form of the origin of
92+
the specific location.
93+
*/
94+
origin: 'http://mylegitdomain.com:2112',
95+
96+
replace: function (url) {
97+
logIOC('Window Location', {url}, "The script changed the window location URL.");
98+
logUrl('Window Location', {url});
99+
},
100+
101+
// The location.reload() method reloads the current URL, like the Refresh button.
102+
reload: function() {},
103+
};
104+
105+
var window = {
106+
eval: function(cmd) { eval(cmd); },
107+
resizeTo: function(a,b){},
108+
moveTo: function(a,b){},
109+
close: function(){},
110+
atob: function(s){
111+
return new Buffer(s, 'base64').toString('ascii');
112+
},
113+
setTimeout: function(f, i) {},
114+
location: location,
115+
localStorage: {
116+
// Users and session to distinguish and generate statistics about website traffic.
117+
"___utma" : undefined,
118+
// Users and session to distinguish and generate statistics about website traffic.
119+
"__utma" : undefined,
120+
// Determine new sessions and visits and generate statistics about website traffic.
121+
"__utmb" : undefined,
122+
// Determine new sessions and visits and generate statistics about website traffic.
123+
"__utmc" : undefined,
124+
// Process user requests and generate statistics about the website traffic.
125+
"__utmt" : undefined,
126+
// Store customized variable data at visitor level and generate statistics about the website traffic.
127+
"__utmv" : undefined,
128+
// To record the traffic source or campaign how users ended up on the website.
129+
"__utmz" : undefined,
130+
},
131+
};
132+
133+
var document = {
134+
documentMode: 8, // Fake running in IE8
135+
referrer: 'https://bing.com/',
136+
location: location,
137+
//parentNode: window.document.parentNode,
138+
getElementById : function(id) {
139+
140+
var char_codes_to_string = function (str) {
141+
var codes = ""
142+
for (var i = 0; i < str.length; i++) {
143+
codes += String.fromCharCode(str[i])
144+
}
145+
return codes
146+
}
147+
148+
/* IDS_AND_DATA */
149+
150+
for (var i = 0; i < ids.length; i++) {
151+
if (char_codes_to_string(ids[i]) == id) {
152+
return {
153+
innerHTML: char_codes_to_string(data[i])
154+
}
155+
}
156+
}
157+
158+
// got nothing to return
159+
return {
160+
innerHTML: ""
161+
}
162+
},
163+
write: function (content) {
164+
logIOC('DOM Write', {content}, 'The script wrote to the DOM')
165+
eval.apply(null, [extractJSFromHTA(content)]);
166+
},
167+
appendChild: function(node) {
168+
logIOC('DOM Append', {node}, "The script appended an HTML node to the DOM")
169+
eval(extractJSFromHTA(node));
170+
},
171+
insertBefore: function(node) {
172+
logIOC('DOM Insert', {node}, "The script inserted an HTML node on the DOM")
173+
eval(extractJSFromHTA(node));
174+
},
175+
getElementsByTagName: function(tag) {
176+
var func = function(item) {
177+
logIOC('DOM Append', {item}, "The script added a HTML node to the DOM");
178+
return "";
179+
};
180+
181+
// Return a dict that maps every tag name to the same fake element.
182+
fake_dict = {};
183+
fake_dict = new Proxy(fake_dict, {
184+
get(target, phrase) { // intercept reading a property from dictionary
185+
return {
186+
"appendChild" : func,
187+
"insertBefore" : func,
188+
"parentNode" : {
189+
"appendChild" : func,
190+
"insertBefore" : func,
191+
},
192+
};
193+
}
194+
});
195+
return fake_dict;
196+
},
197+
createElement: function(tag) {
198+
var fake_elem = {
199+
set src(url) {
200+
logIOC('Remote Script', {url}, "The script set a remote script source.");
201+
logUrl('Remote Script', {url});
202+
},
203+
log: []
204+
};
205+
return fake_elem;
206+
},
207+
createTextNode: function(text) {
208+
//return window.document.createTextNode(text)
209+
},
210+
addEventListener: function(tag, func) {}
211+
};
212+
213+
function atob(s) {
214+
var b = new Buffer(s, 'base64');
215+
return b.toString();
216+
};
217+
218+
class _WidgetInfo {
219+
constructor(a1, a2, a3, a4, a5) {}
220+
}
221+
222+
var _WidgetManager = {
223+
_Init: function(a1, a2, a3) {},
224+
_SetDataContext: function(a1) {},
225+
_RegisterWidget: function(a1, a2) {}
226+
}
227+
228+
var navigator = {
229+
userAgent: 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; Tablet PC 2.0; InfoPath.3)'
230+
}
231+
232+
// We are acting like cscript when emulating. JS in cscript does not
233+
// implement Array.reduce().
234+
Array.prototype.reduce = function(a, b) {
235+
throw "CScript JScript has no Array.reduce() method."
236+
};

emulator/ADODBConnection.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const lib = require("../lib");
2+
const iconv = require("iconv-lite");
3+
4+
function ADODBConnection() {
5+
6+
this.open = function(query, conn) {
7+
console.log("adodb.connection open()");
8+
console.log(query);
9+
console.log(conn);
10+
};
11+
12+
this.cursorlocation1 = function(arg) {
13+
console.log("adodb.connection cursorlocation()");
14+
console.log(arg);
15+
};
16+
17+
this.close = () => {};
18+
}
19+
20+
module.exports = function() {
21+
return new Proxy(new ADODBConnection(), {
22+
get: function(target, name) {
23+
name = name.toLowerCase();
24+
switch (name) {
25+
case "size":
26+
case "length":
27+
return target.buffer.length;
28+
case "readtext":
29+
return target.buffer;
30+
default:
31+
if (name in target) return target[name];
32+
lib.kill(`ADODBConnection.${name} not implemented!`);
33+
}
34+
},
35+
set: function(a, b, c) {
36+
b = b.toLowerCase();
37+
a[b] = c;
38+
return true;
39+
},
40+
});
41+
};

emulator/ADODBStream.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ function ADODBStream() {
4242
return this.buffer;
4343
};
4444
this.write = this.writetext = function(text) {
45-
if (this.charset)
45+
if (this.charset) {
4646
this.buffer = iconv.encode(text, this.charset);
47-
else
47+
} else
4848
this.buffer = text;
4949
};
5050
this.loadfromfile = function(filename) {
@@ -56,7 +56,8 @@ function ADODBStream() {
5656
this.tojson = function(data) {
5757
console.log(data);
5858
return "[1]";
59-
}
59+
};
60+
this.flush = function() {};
6061
this.copyto = (target) => target.write(this.buffer);
6162
}
6263

@@ -69,6 +70,7 @@ module.exports = function() {
6970
case "length":
7071
return target.buffer.length;
7172
default:
73+
lib.info(`Script called ADODBStream.${name}`);
7274
if (name in target) return target[name];
7375
lib.kill(`ADODBStream.${name} not implemented!`);
7476
}

emulator/WMI.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,11 +360,16 @@ module.exports.GetObject = function(name) {
360360
lib.kill(`Not implemented: WMI.Get(${className})`);
361361
return _class;
362362
},
363+
Run: command => {
364+
lib.logIOC("WMI.GetObject.Run", command, "The script executed a command.");
365+
lib.logSnippet(lib.getUUID(), {as: "command"}, command);
366+
return "";
367+
},
363368
}, {
364369
get(target, name) {
365-
console.log("^^^^^^^^^^^");
366-
console.log(target);
367-
console.log(name);
370+
//console.log("^^^^^^^^^^^");
371+
//console.log(target);
372+
//console.log(name);
368373
if (name in target) return target[name];
369374
lib.kill(`WMI.GetObject.${name} not implemented!`);
370375
},

emulator/WScriptShell.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ function WScriptShell() {
8282
StdOut: new TextStream(`<output of ${cmd}>`),
8383
};
8484
};
85-
85+
8686
if (!this._reg_entries) {
8787
this._reg_entries = require("system-registry");
88-
88+
8989
// lacks the HKEY_CURRENT_USER reg key by default (y tho?)
9090
this._reg_entries["HKEY_CURRENT_USER"] = {}
9191
}
@@ -139,7 +139,8 @@ function WScriptShell() {
139139
}
140140
else {
141141
lib.warning(`Unknown registry key ${key}!`);
142-
return "";
142+
//return "";
143+
throw("Registry key not found.");
143144
}
144145
};
145146

0 commit comments

Comments
 (0)