Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //
- // dump_cfguard_bitmap.js
- // Copyright (c) 2025 sixtyvividtails; provided under the MIT license.
- //
- // This script provides 2 commands for kCFG: !check_cfguard "funcAddress" and !dump_cfguard_bitmap "module", "filePath".
- // That can be used to analyze discrepancies between GFIDS in the image and actual cfguard bitmap.
- // Note kCFG is only enabled if VBS is enabled ("Memory Integrity" option).
- //
- // I've quoted reason why it can be important to check for discrepancies in image GFIDS vs actual kCFG bitmap, see that
- // in this script thread: https://x.com/sixtyvividtails/status/1906356148543877183
- //
- // But before delving into that, you may want to check out practical kCFG bypass by Slowerzs:
- // https://blog.slowerzs.net/posts/keyjumper/
- //
- //
- //
- // Usage examples:
- //
- // kd> !check_cfguard "nt!longjmp"
- // [+] address 0xfffff806e32b8a90 is aligned, cfgbits: 1; it's VALID call target
- //
- // kd> !dump_cfguard_bitmap "nt", "C:/stuff/cfguard_bitmap_ntoskrnl.bin"
- // [ ] dumping cfguard bitmap for VA range 0xfffff806e2e00000 L0x144f000 to C:/stuff/cfguard_bitmap_ntoskrnl.bin
- // [+] cfguard bitmap dump complete
- //
- // kd> !dump_cfguard_bitmap "FFFF8000`12345678 L100000", "C:/stuff/cfguard_bitmap_region1.bin"
- // [ ] dumping cfguard bitmap for VA range 0xffff800012345640 L0x100040 to C:/stuff/cfguard_bitmap_region1.bin
- // [+] cfguard bitmap dump complete
- //
- //
- //
- // To properly check stuff you also need IDA Pro GFIDS script, which I don't provide (Ilfak broke my toolset again).
- // But here's a quick sanity check script for nt (assuming no unaligned funcs, which was the case a year ago):
- //
- // data = open('cfguard_bitmap_ntoskrnl.bin', 'rb').read()
- // for ofs, c in enumerate(data):
- // if c in (0x00,0x01,0x04,0x05, 0x10,0x11,0x14,0x15, 0x40,0x41,0x44,0x45, 0x50,0x51,0x54,0x55):
- // continue
- // print(f'unexpected cfguard bits: {c:02X}; cfg offset: {ofs:06X}')
- // for i in range(4):
- // if (c & 3) not in [0, 1]: # 3 is also valid for unaligned, but there should be none for ntoskrnl
- // print(f' cfguard bits: {c&3}, image paragraph offset: {ofs*0x40+i*0x10:08X}')
- // c >>= 2
- //
- "use strict";
- //----------------------------------------------------------------------------------------------------------------------
- // Common helpers for ring0.
- //----------------------------------------------------------------------------------------------------------------------
- var _ptrSize = 0; // size of the pointer (4/8), to be filled during script init
- var _int64CompareTo; // host.Int64.compareTo function, for type detection
- // Initializes common script globals.
- function _init_common_globals()
- {
- _ptrSize = host.namespace.Debugger.State.DebuggerVariables.cursession.Attributes.Machine.PointerSize;
- _int64CompareTo = host.Int64(1).compareTo;
- }
- // Prints out stuff.
- function print(...args)
- {
- args.push('\n');
- host.diagnostics.debugLog(...args);
- }
- // Executes debugger native command.
- // Returns array of output strings from that command.
- // WARNING: don't use untrusted commands, they enable arbitrary code execution on both host and target.
- function exec_command(cmd)
- {
- return host.namespace.Debugger.Utility.Control.ExecuteCommand(cmd);
- }
- // Evaluates token in windbg engine to numeric value, using "? token".
- // Returns host.Int64 on success.
- // WARNING: don't use untrusted tokens, they enable arbitrary code execution on both host and target.
- function eval_token_unsafe(token)
- {
- let parsedToken = exec_command(`? ${token}`).First();
- let lastEqu = parsedToken.lastIndexOf(` = `);
- if (lastEqu === -1)
- {
- print(`[x] can't evaluate token: ${token}`);
- return null; // LATER: throw when it unsucks
- }
- return host.parseInt64(parsedToken.substring(lastEqu + 3).trim().replace('`', ''), 0x10);
- }
- // Resolves into (regionStart, size) from strings like "nt" or "FFFF8000`12345678 L100000".
- // Returns {start, size} object, both elements are host.Int64.
- // WARNING: don't use untrusted 'imageNameOrRange' values, they enable arbitrary code execution on both host and target.
- function resolve_module_range_unsafe(imageNameOrRange)
- {
- let lastL = imageNameOrRange.toUpperCase().lastIndexOf(' L');
- let firstToken, secondToken;
- if (lastL !== -1)
- {
- firstToken = imageNameOrRange.substring(0, lastL).trim();
- secondToken = imageNameOrRange.substring(lastL + 2).trim();
- }
- else
- firstToken = imageNameOrRange.trim();
- let start = eval_token_unsafe(firstToken);
- let size;
- if (secondToken !== undefined)
- size = eval_token_unsafe(secondToken);
- else
- size = host.Int64(get_u32(start.add(get_u32(start.add(0x3C)) + 0x50))); // peHeader.SizeOfImage
- return {start, size};
- }
- // Resolves address string to numeric value, using "? address".
- // Returns host.Int64 on success.
- // WARNING: don't use untrusted address strings, they enable arbitrary code execution on both host and target.
- function resolve_address_unsafe(address)
- {
- if (typeof address === `number`)
- return host.Int64(address);
- if (address.compareTo === _int64CompareTo)
- return address; // assume it's already Int64
- if (typeof address !== `string`)
- {
- print(`[x] can't resolve address, type unexpected: ${typeof address}, value: ${address}`);
- return null; // LATER: throw when it unsucks
- }
- return eval_token_unsafe(address);
- }
- // Resolves address (supposedly safe).
- // If 'address' is a number of host.Int64, returns Int64.
- // If 'address' is a string, resolves it to a symbol address (also dereferencing it if it starts with '*').
- function resolve_address(address)
- {
- if (typeof address === `number`)
- return host.Int64(address);
- if (address.compareTo === _int64CompareTo)
- return address; // assume it's already Int64
- if (typeof address !== `string`)
- {
- print(`[x] can't resolve address, type unexpected: ${typeof address}, value: ${address}`);
- return null; // LATER: throw when it unsucks
- }
- let derefsCount = 0;
- let resolvedAddress = address;
- for (; resolvedAddress.startsWith('*'); ++derefsCount)
- resolvedAddress = resolvedAddress.slice(1);
- let parts = resolvedAddress.split('!', 2);
- let moduleName = parts.length === 2? parts[0]: `nt`;
- let symName = parts.length === 2? parts[1]: resolvedAddress;
- resolvedAddress = host.getModuleSymbolAddress(moduleName, symName);
- for (; derefsCount; --derefsCount)
- resolvedAddress = host.memory.readMemoryValues(resolvedAddress, 1, _ptrSize)[0];
- if (resolvedAddress === null)
- {
- print(`[x] can't resolve address, data resolved to null: ${address}`);
- return null; // LATER: throw when it unsucks
- }
- return resolvedAddress;
- }
- // Retrieves object from debugee memory, with optionally specified type.
- function get_object(symLocation, symType = null)
- {
- let address = resolve_address(symLocation);
- if (symType === null) // || symType === undefined
- return address;
- let typeObject = symType; // e.g. host.getModuleType(moduleName, typeName)
- if (typeof symType === 'string')
- {
- let parts = symType.split('!', 2);
- let moduleName = parts.length === 2? parts[0]: 'nt';
- let typeName = parts.length === 2? parts[1]: symType;
- typeObject = host.getModuleType(moduleName, typeName);
- }
- //return host.createPointerObject(address, typeObject);
- return host.createTypedObject(address, typeObject);
- }
- // Retrieves some data from the target memory.
- // Use non-zero 'extent' arg to retrieve array of data instead of single item.
- function get_data(address, itemSize, extent = 0)
- {
- address = resolve_address(address);
- return extent?
- host.memory.readMemoryValues(address, extent, itemSize): // array of items
- host.memory.readMemoryValues(address, 1, itemSize)[0]; // single item
- }
- // Tries to retrieves some data from the target memory. On failure returns null.
- // Use non-zero 'extent' arg to retrieve array of data instead of single item.
- function try_get_data(address, itemSize, extent = 0)
- {
- try
- {
- return get_data(address, itemSize, extent);
- }
- catch (error)
- {
- return null;
- }
- }
- // Retrieves 8-bit value(s) from the target memory.
- function get_u8(address, extent = 0)
- {
- return get_data(address, 1, extent);
- }
- // Tries to retrieve 8-bit value(s) from the target memory. On failure returns null.
- function try_get_u8(address, extent = 0)
- {
- return try_get_data(address, 1, extent);
- }
- // Retrieves 16-bit value(s) from the target memory.
- function get_u16(address, extent = 0)
- {
- return get_data(address, 2, extent);
- }
- // Tries to retrieve 16-bit value(s) from the target memory. On failure returns null.
- function try_get_u16(address, extent = 0)
- {
- return try_get_data(address, 2, extent);
- }
- // Retrieves 32-bit value(s) from the target memory.
- function get_u32(address, extent = 0)
- {
- return get_data(address, 4, extent);
- }
- // Tries to retrieve 32-bit value(s) from the target memory. On failure returns null.
- function try_get_u32(address, extent = 0)
- {
- return try_get_data(address, 4, extent);
- }
- // Retrieves 64-bit value(s) from the target memory.
- function get_u64(address, extent = 0)
- {
- return get_data(address, 8, extent);
- }
- // Tries to retrieve 64-bit value(s) from the target memory. On failure returns null.
- function try_get_u64(address, extent = 0)
- {
- return try_get_data(address, 8, extent);
- }
- // Retrieves ptr-sized value(s) from the target memory.
- function get_ptr(address, extent = 0)
- {
- return get_data(address, _ptrSize, extent);
- }
- // Tries to retrieve ptr-sized value(s) from the target memory. On failure returns null.
- function try_get_ptr(address, extent = 0)
- {
- return try_get_data(address, _ptrSize, extent);
- }
- // Formats value as hex.
- function to_hex(value, pad = null)
- {
- let s = value.toString(16).toUpperCase();
- let padDefault = (s.length + 1) & -2;
- if (typeof pad !== 'string')
- return s.padStart((pad === null? padDefault: pad), '0');
- let padValue = '0';
- if (pad.startsWith('0'))
- pad = pad.slice(1);
- else if (pad.startsWith(' '))
- {
- padValue = ' ';
- pad = pad.slice(1);
- }
- let padNum = pad.parseInt(pad);
- return s.padStart((isNaN(padNum) || pad.length == 0)? padDefault: padNum, padValue);
- }
- //----------------------------------------------------------------------------------------------------------------------
- // Globals.
- //----------------------------------------------------------------------------------------------------------------------
- var _cfgBitmap = null; // cfguard bitmap start, value of nt!guard_icall_bitmap variable
- //----------------------------------------------------------------------------------------------------------------------
- // Main script funcs.
- //----------------------------------------------------------------------------------------------------------------------
- // Reads data page by page, skipping pages we can't read.
- // That'll skip missing or paged out data.
- function read_bytes_skipping_unreadable(base, size)
- {
- let data = new Uint8Array(new ArrayBuffer(size));
- let offset = 0;
- let toRead = 0x1000 - base.bitwiseAnd(0xFFF).asNumber();
- let leftToRead = size;
- while (leftToRead != 0)
- {
- if (toRead > leftToRead)
- toRead = leftToRead;
- let chunk = try_get_u8(base, toRead);
- if (chunk)
- data.set(chunk, offset);
- base = base.add(toRead);
- leftToRead -= toRead;
- offset += toRead;
- toRead = 0x1000;
- }
- return data;
- }
- function dump_cfguard_bitmap(imageNameOrRange, filePath)
- {
- let range = resolve_module_range_unsafe(imageNameOrRange);
- if (!filePath)
- filePath = host.namespace.Debugger.Utility.FileSystem.TempDirectory + "\\cfguard_dump.*.bin";
- filePath = filePath.replace('*', imageNameOrRange.trim().replace(' ', '_'));
- // currently each 0x10 bytes of VA space represented by 2 bits in the cfguard bitmap, i.e. 0x40 bytes per byte
- let rangeStart = range.start.bitwiseAnd(~range.start.bitwiseAnd(0x3F)); // align down
- let rangeSize = range.size.add(range.start.bitwiseAnd(0x3F)).add(0x3F); // inc by align down delta + 0x3F
- rangeSize = rangeSize.subtract(rangeSize.bitwiseAnd(0x3F)); // with +3F above, it's just align up
- let dumpSize = rangeSize.divide(0x40);
- if (rangeStart != range.start || rangeSize != range.size)
- print(`[!] had to adjust cfguard dump range to get aligned addresses`);
- print(`[ ] dumping cfguard bitmap for VA range ${rangeStart} L${rangeSize} to ${filePath}`);
- if (dumpSize == 0 || dumpSize > 0x80000000)
- {
- print(`[x] dumpSize is unexpected: either 0 or way too large: ${dumpSize}`);
- return;
- }
- let data = read_bytes_skipping_unreadable(_cfgBitmap.add(rangeStart.bitwiseShiftRight(6)), dumpSize.asNumber());
- let file = host.namespace.Debugger.Utility.FileSystem.CreateFile(filePath, `CreateAlways`);
- try
- {
- file.WriteBytes(data);
- print(`[+] cfguard bitmap dump complete`);
- }
- catch (error)
- {
- print(`[x] failed to write cfguard bitmap: ${error.message}`);
- }
- finally
- {
- file.Close();
- }
- }
- function check_cfguard(functionAddress)
- {
- let address = resolve_address_unsafe(functionAddress);
- if (address.compareTo(0) == 0)
- return; // error already printed out
- let aligned = (address.bitwiseAnd(0x0F).asNumber())? `unaligned`: `aligned`;
- let cfgBits = try_get_u8(_cfgBitmap.add(address.bitwiseShiftRight(6)));
- if (cfgBits === null)
- cfgBits = 0;
- cfgBits = 3 & (cfgBits >> (2 * address.bitwiseShiftRight(4).bitwiseAnd(3).asNumber()));
- let valid = `VALID`;
- let c = `+`;
- if (cfgBits == 2)
- {
- valid = `##_BROKEN_##`; // export-suppressed AFAIR, but currently not in ring0
- c = `!`;
- }
- else if (cfgBits == 0 || (cfgBits == 1 && aligned == `unaligned`))
- {
- valid = `INVALID`;
- c = `-`;
- }
- let aswell = ``;
- if (cfgBits == 3)
- aswell = `, as well as any byte in that 0x10-byte block`;
- print(`[${c}] address ${address} is ${aligned}, cfgbits: ${cfgBits}; it's ${valid} call target${aswell}`);
- }
- //----------------------------------------------------------------------------------------------------------------------
- // Init.
- //----------------------------------------------------------------------------------------------------------------------
- // Known name; invoked by Windbg on .scriptload and .scriptrun.
- function initializeScript()
- {
- return [
- new host.apiVersionSupport(1, 9),
- new host.functionAlias(dump_cfguard_bitmap, "dump_cfguard_bitmap"),
- new host.functionAlias(check_cfguard, "check_cfguard"),
- ];
- }
- // Known name; invoked by Windbg on .scriptrun.
- function invokeScript()
- {
- _init_common_globals();
- _cfgBitmap = get_ptr("nt!_guard_icall_bitmap");
- if (_cfgBitmap.compareTo(0) == 0)
- print(`[x] kCFG is not enabled on this system, please make sure VBS is on ("Memory Integrity" in options)`);
- }
Advertisement
Add Comment
Please, Sign In to add comment