cakemaker

dump_cfguard_bitmap.js

Mar 30th, 2025 (edited)
145
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 15.36 KB | Science | 0 0
  1. //
  2. // dump_cfguard_bitmap.js
  3. // Copyright (c) 2025 sixtyvividtails; provided under the MIT license.
  4. //
  5. // This script provides 2 commands for kCFG: !check_cfguard "funcAddress" and !dump_cfguard_bitmap "module", "filePath".
  6. // That can be used to analyze discrepancies between GFIDS in the image and actual cfguard bitmap.
  7. // Note kCFG is only enabled if VBS is enabled ("Memory Integrity" option).
  8. //
  9. // I've quoted reason why it can be important to check for discrepancies in image GFIDS vs actual kCFG bitmap, see that
  10. // in this script thread: https://x.com/sixtyvividtails/status/1906356148543877183
  11. //
  12. // But before delving into that, you may want to check out practical kCFG bypass by Slowerzs:
  13. // https://blog.slowerzs.net/posts/keyjumper/
  14. //
  15. //
  16. //
  17. // Usage examples:
  18. //
  19. // kd> !check_cfguard "nt!longjmp"
  20. // [+] address 0xfffff806e32b8a90 is aligned, cfgbits: 1; it's VALID call target
  21. //
  22. // kd> !dump_cfguard_bitmap "nt", "C:/stuff/cfguard_bitmap_ntoskrnl.bin"
  23. // [ ] dumping cfguard bitmap for VA range 0xfffff806e2e00000 L0x144f000 to C:/stuff/cfguard_bitmap_ntoskrnl.bin
  24. // [+] cfguard bitmap dump complete
  25. //
  26. // kd> !dump_cfguard_bitmap "FFFF8000`12345678 L100000", "C:/stuff/cfguard_bitmap_region1.bin"
  27. // [ ] dumping cfguard bitmap for VA range 0xffff800012345640 L0x100040 to C:/stuff/cfguard_bitmap_region1.bin
  28. // [+] cfguard bitmap dump complete
  29. //
  30. //
  31. //
  32. // To properly check stuff you also need IDA Pro GFIDS script, which I don't provide (Ilfak broke my toolset again).
  33. // But here's a quick sanity check script for nt (assuming no unaligned funcs, which was the case a year ago):
  34. //
  35. // data = open('cfguard_bitmap_ntoskrnl.bin', 'rb').read()
  36. // for ofs, c in enumerate(data):
  37. //     if c in (0x00,0x01,0x04,0x05, 0x10,0x11,0x14,0x15, 0x40,0x41,0x44,0x45, 0x50,0x51,0x54,0x55):
  38. //         continue
  39. //     print(f'unexpected cfguard bits: {c:02X}; cfg offset: {ofs:06X}')
  40. //     for i in range(4):
  41. //         if (c & 3) not in [0, 1]:  # 3 is also valid for unaligned, but there should be none for ntoskrnl
  42. //             print(f'  cfguard bits: {c&3}, image paragraph offset: {ofs*0x40+i*0x10:08X}')
  43. //         c >>= 2
  44. //
  45. "use strict";
  46.  
  47.  
  48. //----------------------------------------------------------------------------------------------------------------------
  49. // Common helpers for ring0.
  50. //----------------------------------------------------------------------------------------------------------------------
  51.  
  52. var _ptrSize = 0;       // size of the pointer (4/8), to be filled during script init
  53. var _int64CompareTo;    // host.Int64.compareTo function, for type detection
  54.  
  55.  
  56. // Initializes common script globals.
  57. function _init_common_globals()
  58. {
  59.     _ptrSize = host.namespace.Debugger.State.DebuggerVariables.cursession.Attributes.Machine.PointerSize;
  60.     _int64CompareTo = host.Int64(1).compareTo;
  61. }
  62.  
  63.  
  64. // Prints out stuff.
  65. function print(...args)
  66. {
  67.     args.push('\n');
  68.     host.diagnostics.debugLog(...args);
  69. }
  70.  
  71.  
  72. // Executes debugger native command.
  73. // Returns array of output strings from that command.
  74. // WARNING: don't use untrusted commands, they enable arbitrary code execution on both host and target.
  75. function exec_command(cmd)
  76. {
  77.     return host.namespace.Debugger.Utility.Control.ExecuteCommand(cmd);
  78. }
  79.  
  80.  
  81. // Evaluates token in windbg engine to numeric value, using "? token".
  82. // Returns host.Int64 on success.
  83. // WARNING: don't use untrusted tokens, they enable arbitrary code execution on both host and target.
  84. function eval_token_unsafe(token)
  85. {
  86.     let parsedToken = exec_command(`? ${token}`).First();
  87.     let lastEqu = parsedToken.lastIndexOf(` = `);
  88.     if (lastEqu === -1)
  89.     {
  90.         print(`[x] can't evaluate token: ${token}`);
  91.        return null;        // LATER: throw when it unsucks
  92.    }
  93.    return host.parseInt64(parsedToken.substring(lastEqu + 3).trim().replace('`', ''), 0x10);
  94. }
  95.  
  96.  
  97. // Resolves into (regionStart, size) from strings like "nt" or "FFFF8000`12345678 L100000".
  98. // Returns {start, size} object, both elements are host.Int64.
  99. // WARNING: don't use untrusted 'imageNameOrRange' values, they enable arbitrary code execution on both host and target.
  100. function resolve_module_range_unsafe(imageNameOrRange)
  101. {
  102.     let lastL = imageNameOrRange.toUpperCase().lastIndexOf(' L');
  103.     let firstToken, secondToken;
  104.     if (lastL !== -1)
  105.     {
  106.         firstToken = imageNameOrRange.substring(0, lastL).trim();
  107.         secondToken = imageNameOrRange.substring(lastL + 2).trim();
  108.     }
  109.     else
  110.         firstToken = imageNameOrRange.trim();
  111.  
  112.     let start = eval_token_unsafe(firstToken);
  113.     let size;
  114.     if (secondToken !== undefined)
  115.         size = eval_token_unsafe(secondToken);
  116.     else
  117.         size = host.Int64(get_u32(start.add(get_u32(start.add(0x3C)) + 0x50)));    // peHeader.SizeOfImage
  118.    
  119.     return {start, size};
  120. }
  121.  
  122.  
  123. // Resolves address string to numeric value, using "? address".
  124. // Returns host.Int64 on success.
  125. // WARNING: don't use untrusted address strings, they enable arbitrary code execution on both host and target.
  126. function resolve_address_unsafe(address)
  127. {
  128.     if (typeof address === `number`)
  129.         return host.Int64(address);
  130.     if (address.compareTo === _int64CompareTo)
  131.         return address;     // assume it's already Int64
  132.     if (typeof address !== `string`)
  133.     {
  134.         print(`[x] can't resolve address, type unexpected: ${typeof address}, value: ${address}`);
  135.        return null;        // LATER: throw when it unsucks
  136.    }
  137.    return eval_token_unsafe(address);
  138. }
  139.  
  140.  
  141. // Resolves address (supposedly safe).
  142. // If 'address' is a number of host.Int64, returns Int64.
  143. // If 'address' is a string, resolves it to a symbol address (also dereferencing it if it starts with '*').
  144. function resolve_address(address)
  145. {
  146.    if (typeof address === `number`)
  147.        return host.Int64(address);
  148.    if (address.compareTo === _int64CompareTo)
  149.        return address;     // assume it's already Int64
  150.     if (typeof address !== `string`)
  151.     {
  152.         print(`[x] can't resolve address, type unexpected: ${typeof address}, value: ${address}`);
  153.        return null;        // LATER: throw when it unsucks
  154.    }
  155.    let derefsCount = 0;
  156.    let resolvedAddress = address;
  157.    for (; resolvedAddress.startsWith('*'); ++derefsCount)
  158.        resolvedAddress = resolvedAddress.slice(1);
  159.    let parts = resolvedAddress.split('!', 2);
  160.    let moduleName = parts.length === 2? parts[0]: `nt`;
  161.    let symName = parts.length === 2? parts[1]: resolvedAddress;
  162.    resolvedAddress = host.getModuleSymbolAddress(moduleName, symName);
  163.    for (; derefsCount; --derefsCount)
  164.        resolvedAddress = host.memory.readMemoryValues(resolvedAddress, 1, _ptrSize)[0];
  165.    if (resolvedAddress === null)
  166.    {
  167.        print(`[x] can't resolve address, data resolved to null: ${address}`);
  168.         return null;        // LATER: throw when it unsucks
  169.     }
  170.     return resolvedAddress;
  171. }
  172.  
  173.  
  174. // Retrieves object from debugee memory, with optionally specified type.
  175. function get_object(symLocation, symType = null)
  176. {
  177.     let address = resolve_address(symLocation);
  178.     if (symType === null)           // || symType === undefined
  179.         return address;
  180.     let typeObject = symType;       // e.g. host.getModuleType(moduleName, typeName)
  181.     if (typeof symType === 'string')
  182.     {
  183.         let parts = symType.split('!', 2);
  184.         let moduleName = parts.length === 2? parts[0]: 'nt';
  185.         let typeName = parts.length === 2? parts[1]: symType;
  186.         typeObject = host.getModuleType(moduleName, typeName);
  187.     }
  188.     //return host.createPointerObject(address, typeObject);
  189.     return host.createTypedObject(address, typeObject);
  190. }
  191.  
  192.  
  193. // Retrieves some data from the target memory.
  194. // Use non-zero 'extent' arg to retrieve array of data instead of single item.
  195. function get_data(address, itemSize, extent = 0)
  196. {
  197.     address = resolve_address(address);
  198.     return extent?
  199.         host.memory.readMemoryValues(address, extent, itemSize):    // array of items
  200.         host.memory.readMemoryValues(address, 1, itemSize)[0];      // single item
  201. }
  202.  
  203. // Tries to retrieves some data from the target memory. On failure returns null.
  204. // Use non-zero 'extent' arg to retrieve array of data instead of single item.
  205. function try_get_data(address, itemSize, extent = 0)
  206. {
  207.     try
  208.     {
  209.         return get_data(address, itemSize, extent);
  210.     }
  211.     catch (error)
  212.     {
  213.         return null;
  214.     }
  215. }
  216.  
  217. // Retrieves 8-bit value(s) from the target memory.
  218. function get_u8(address, extent = 0)
  219. {
  220.     return get_data(address, 1, extent);
  221. }
  222.  
  223. // Tries to retrieve 8-bit value(s) from the target memory. On failure returns null.
  224. function try_get_u8(address, extent = 0)
  225. {
  226.     return try_get_data(address, 1, extent);
  227. }
  228.  
  229. // Retrieves 16-bit value(s) from the target memory.
  230. function get_u16(address, extent = 0)
  231. {
  232.     return get_data(address, 2, extent);
  233. }
  234.  
  235. // Tries to retrieve 16-bit value(s) from the target memory. On failure returns null.
  236. function try_get_u16(address, extent = 0)
  237. {
  238.     return try_get_data(address, 2, extent);
  239. }
  240.  
  241. // Retrieves 32-bit value(s) from the target memory.
  242. function get_u32(address, extent = 0)
  243. {
  244.     return get_data(address, 4, extent);
  245. }
  246.  
  247. // Tries to retrieve 32-bit value(s) from the target memory. On failure returns null.
  248. function try_get_u32(address, extent = 0)
  249. {
  250.     return try_get_data(address, 4, extent);
  251. }
  252.  
  253. // Retrieves 64-bit value(s) from the target memory.
  254. function get_u64(address, extent = 0)
  255. {
  256.     return get_data(address, 8, extent);
  257. }
  258.  
  259. // Tries to retrieve 64-bit value(s) from the target memory. On failure returns null.
  260. function try_get_u64(address, extent = 0)
  261. {
  262.     return try_get_data(address, 8, extent);
  263. }
  264.  
  265. // Retrieves ptr-sized value(s) from the target memory.
  266. function get_ptr(address, extent = 0)
  267. {
  268.     return get_data(address, _ptrSize, extent);
  269. }
  270.  
  271. // Tries to retrieve ptr-sized value(s) from the target memory. On failure returns null.
  272. function try_get_ptr(address, extent = 0)
  273. {
  274.     return try_get_data(address, _ptrSize, extent);
  275. }
  276.  
  277.  
  278. // Formats value as hex.
  279. function to_hex(value, pad = null)
  280. {
  281.     let s = value.toString(16).toUpperCase();
  282.     let padDefault = (s.length + 1) & -2;
  283.     if (typeof pad !== 'string')
  284.         return s.padStart((pad === null? padDefault: pad), '0');
  285.     let padValue = '0';
  286.     if (pad.startsWith('0'))
  287.         pad = pad.slice(1);
  288.     else if (pad.startsWith(' '))
  289.     {
  290.         padValue = ' ';
  291.         pad = pad.slice(1);
  292.     }
  293.     let padNum = pad.parseInt(pad);
  294.     return s.padStart((isNaN(padNum) || pad.length == 0)? padDefault: padNum, padValue);
  295. }
  296.  
  297.  
  298. //----------------------------------------------------------------------------------------------------------------------
  299. // Globals.
  300. //----------------------------------------------------------------------------------------------------------------------
  301.  
  302. var _cfgBitmap = null;  // cfguard bitmap start, value of nt!guard_icall_bitmap variable
  303.  
  304.  
  305. //----------------------------------------------------------------------------------------------------------------------
  306. // Main script funcs.
  307. //----------------------------------------------------------------------------------------------------------------------
  308.  
  309. // Reads data page by page, skipping pages we can't read.
  310. // That'll skip missing or paged out data.
  311. function read_bytes_skipping_unreadable(base, size)
  312. {
  313.     let data = new Uint8Array(new ArrayBuffer(size));
  314.     let offset = 0;
  315.     let toRead = 0x1000 - base.bitwiseAnd(0xFFF).asNumber();
  316.     let leftToRead = size;
  317.     while (leftToRead != 0)
  318.     {
  319.         if (toRead > leftToRead)
  320.             toRead = leftToRead;
  321.         let chunk = try_get_u8(base, toRead);
  322.         if (chunk)
  323.             data.set(chunk, offset);
  324.         base = base.add(toRead);
  325.         leftToRead -= toRead;
  326.         offset += toRead;
  327.         toRead = 0x1000;
  328.     }
  329.     return data;
  330. }
  331.  
  332.  
  333. function dump_cfguard_bitmap(imageNameOrRange, filePath)
  334. {
  335.     let range = resolve_module_range_unsafe(imageNameOrRange);
  336.     if (!filePath)
  337.         filePath = host.namespace.Debugger.Utility.FileSystem.TempDirectory + "\\cfguard_dump.*.bin";
  338.     filePath = filePath.replace('*', imageNameOrRange.trim().replace(' ', '_'));
  339.     // currently each 0x10 bytes of VA space represented by 2 bits in the cfguard bitmap, i.e. 0x40 bytes per byte
  340.     let rangeStart = range.start.bitwiseAnd(~range.start.bitwiseAnd(0x3F)); // align down
  341.     let rangeSize = range.size.add(range.start.bitwiseAnd(0x3F)).add(0x3F); // inc by align down delta + 0x3F
  342.     rangeSize = rangeSize.subtract(rangeSize.bitwiseAnd(0x3F));             // with +3F above, it's just align up
  343.     let dumpSize = rangeSize.divide(0x40);
  344.     if (rangeStart != range.start || rangeSize != range.size)
  345.         print(`[!] had to adjust cfguard dump range to get aligned addresses`);
  346.     print(`[ ] dumping cfguard bitmap for VA range ${rangeStart} L${rangeSize} to ${filePath}`);
  347.     if (dumpSize == 0 || dumpSize > 0x80000000)
  348.     {
  349.         print(`[x] dumpSize is unexpected: either 0 or way too large: ${dumpSize}`);
  350.         return;
  351.     }
  352.    
  353.     let data = read_bytes_skipping_unreadable(_cfgBitmap.add(rangeStart.bitwiseShiftRight(6)), dumpSize.asNumber());
  354.     let file = host.namespace.Debugger.Utility.FileSystem.CreateFile(filePath, `CreateAlways`);
  355.     try
  356.     {
  357.         file.WriteBytes(data);
  358.         print(`[+] cfguard bitmap dump complete`);
  359.     }
  360.     catch (error)
  361.     {
  362.         print(`[x] failed to write cfguard bitmap: ${error.message}`);
  363.     }
  364.     finally
  365.     {
  366.         file.Close();
  367.     }
  368. }
  369.  
  370.  
  371. function check_cfguard(functionAddress)
  372. {
  373.     let address = resolve_address_unsafe(functionAddress);
  374.     if (address.compareTo(0) == 0)
  375.         return;     // error already printed out
  376.     let aligned = (address.bitwiseAnd(0x0F).asNumber())? `unaligned`: `aligned`;
  377.     let cfgBits = try_get_u8(_cfgBitmap.add(address.bitwiseShiftRight(6)));
  378.     if (cfgBits === null)
  379.         cfgBits = 0;
  380.     cfgBits = 3 & (cfgBits >> (2 * address.bitwiseShiftRight(4).bitwiseAnd(3).asNumber()));
  381.     let valid = `VALID`;
  382.     let c = `+`;
  383.     if (cfgBits == 2)
  384.     {
  385.         valid = `##_BROKEN_##`;   // export-suppressed AFAIR, but currently not in ring0
  386.         c = `!`;
  387.     }
  388.     else if (cfgBits == 0 || (cfgBits == 1 && aligned == `unaligned`))
  389.     {
  390.         valid = `INVALID`;
  391.         c = `-`;
  392.     }
  393.     let aswell = ``;
  394.     if (cfgBits == 3)
  395.         aswell = `, as well as any byte in that 0x10-byte block`;
  396.     print(`[${c}] address ${address} is ${aligned}, cfgbits: ${cfgBits}; it's ${valid} call target${aswell}`);
  397. }
  398.  
  399.  
  400. //----------------------------------------------------------------------------------------------------------------------
  401. // Init.
  402. //----------------------------------------------------------------------------------------------------------------------
  403.  
  404. // Known name; invoked by Windbg on .scriptload and .scriptrun.
  405. function initializeScript()
  406. {
  407.    return [
  408.        new host.apiVersionSupport(1, 9),
  409.        new host.functionAlias(dump_cfguard_bitmap, "dump_cfguard_bitmap"),
  410.        new host.functionAlias(check_cfguard, "check_cfguard"),
  411.    ];
  412. }
  413.  
  414.  
  415. // Known name; invoked by Windbg on .scriptrun.
  416. function invokeScript()
  417. {
  418.    _init_common_globals();
  419.    _cfgBitmap = get_ptr("nt!_guard_icall_bitmap");
  420.    if (_cfgBitmap.compareTo(0) == 0)
  421.        print(`[x] kCFG is not enabled on this system, please make sure VBS is on ("Memory Integrity" in options)`);
  422. }
  423.  
Advertisement
Add Comment
Please, Sign In to add comment