Repository: 0vercl0k/windbg-scripts Branch: master Commit: f8e01a9783e5 Files: 26 Total size: 124.7 KB Directory structure: gitextract_yxreilaj/ ├── LICENSE ├── Manifest/ │ ├── Manifest.1.xml │ ├── ManifestVersion.txt │ └── config.xml ├── README.md ├── basics/ │ ├── Int64.js │ ├── breakpoint.js │ ├── breakpoint2.js │ ├── eval.js │ ├── extendmodel.js │ ├── extendmodel_1.js │ ├── extendmodel_2.js │ ├── extendmodel_2_1.js │ └── readmemory.js ├── codecov/ │ ├── README.md │ └── codecov.js ├── gdt/ │ ├── README.md │ └── gdt.js ├── parse_eh_win64/ │ ├── README.md │ └── parse_eh_win64.js ├── policybuffer/ │ ├── README.md │ └── policybuffer.js ├── sm/ │ ├── README.md │ └── sm.js └── telescope/ ├── README.md └── telescope.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Axel Souchet Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Manifest/Manifest.1.xml ================================================ Telescope 1.0.0.1 Telescope data dereference ]]> Gdt 1.0.0.1 Gdt dump ]]> sm 1.0.0.1 Pretty printers for JS::Value and JSObject objects in SpiderMonkey ]]> ]]> ================================================ FILE: Manifest/ManifestVersion.txt ================================================ 1 1.0.0.0 1 ================================================ FILE: Manifest/config.xml ================================================ ================================================ FILE: README.md ================================================ # windbg-scripts `windbg-scripts` is a collection of [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extensions for WinDbg. * [basics](basics/): various examples of basic usage of various APIs, * [parse_eh_win64](parse_eh_win64/): example of extending the data-model with exception handling related information (cf [Debugger data model, Javascript & x64 exception handling](https://doar-e.github.io/blog/2017/12/01/debugger-data-model/)), * [telescope](telescope/): [telescope](https://gef.readthedocs.io/en/latest/commands/dereference/) like command for WinDbg, * [sm](sm/): pretty-printing of Spidermonkey `js::Value` and `JSObject` objects, * [codecov](codecov/): extract code-coverage out of a [TTD](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-overview) trace, * [policybuffer](policybuffer/): disassemble a Chrome policy buffer program, * [gdt](gdt/): dump the [Global Descriptor Table](https://wiki.osdev.org/Global_Descriptor_Table). ## Installing the script gallery If you would like to have `telescope` and `sm` loaded every time your debugger starts instead of loading the extensions manually follow the below steps: 1. Clone this GitHub repository, 2. Edit the `Manifest\config.xml` file and update the `LocalCacheRootFolder` path with a value that makes sense, 3. Open the debugger and import the gallery by running `.settings load c:\path\where\cloned\windbg-scripts\Manifest\config.xml` and `.settings save`. 4. Restart the debugger and you should be able to run `!telescope` as well as inspecting the gallery content from the data-model. ```text 0:000> dx -r1 Debugger.State.ExtensionGallery.ExtensionRepositories Debugger.State.ExtensionGallery.ExtensionRepositories [0x0] : overgallery [0x1] : LocalInstalled 0:000> dx -r1 Debugger.State.ExtensionGallery.ExtensionRepositories[0] Debugger.State.ExtensionGallery.ExtensionRepositories[0] : overgallery Name : overgallery ManifestVersion : 0x1 URL Enabled : true Packages 0:000> dx -r1 Debugger.State.ExtensionGallery.ExtensionRepositories[0].Packages Debugger.State.ExtensionGallery.ExtensionRepositories[0].Packages [0x0] : Telescope 0:000> dx -r1 Debugger.State.ExtensionGallery.ExtensionRepositories[0].Packages[0] Debugger.State.ExtensionGallery.ExtensionRepositories[0].Packages[0] : Telescope Name : Telescope Version : 1.0.0.1 Description : Telescope data dereference Size : 0 IsDownloaded : true Components ``` ================================================ FILE: basics/Int64.js ================================================ // Axel '0vercl0k' Souchet - Dec 2017 "use strict"; let logln = function (e) { host.diagnostics.debugLog(e + '\n'); } function invokeScript() { let a = host.Int64(1337); let aplusone = a + 1; logln(aplusone.toString(16)); let b = host.parseInt64('0xdeadbeefbaadc0de', 16); let bplusone = b.add(1); logln(bplusone.toString(16)); let bplusonenothrow = b.convertToNumber() + 1; logln(bplusonenothrow); try { let bplusonethrow = b + 1; } catch(e) { logln(e); } logln(a.compareTo(1)); logln(a.compareTo(1337)); logln(a.compareTo(1338)); } ================================================ FILE: basics/breakpoint.js ================================================ // Axel '0vercl0k' Souchet - Dec 2017 "use strict"; let logln = function (e) { host.diagnostics.debugLog(e + '\n'); } function handle_bp() { let Regs = host.currentThread.Registers.User; let Args = [Regs.rcx, Regs.rdx, Regs.r8]; let ArgsS = Args.map(c => c.toString(16)); let HeapHandle = ArgsS[0]; let Flags = ArgsS[1]; let Size = ArgsS[2]; logln('RtlAllocateHeap: HeapHandle: ' + HeapHandle + ', Flags: ' + Flags + ', Size: ' + Size); } function invokeScript() { let Control = host.namespace.Debugger.Utility.Control; let Regs = host.currentThread.Registers.User; let CurrentProcess = host.currentProcess; let BreakpointAlreadySet = CurrentProcess.Debug.Breakpoints.Any( c => c.OffsetExpression == 'ntdll!RtlAllocateHeap+0x0' ); if(BreakpointAlreadySet == false) { let Bp = Control.SetBreakpointAtOffset('RtlAllocateHeap', 0, 'ntdll'); Bp.Command = '.echo doare; dx @$scriptContents.handle_bp(); gc'; } else { logln('Breakpoint already set.'); } logln('Press "g" to run the target.'); // let Lines = Control.ExecuteCommand('gc'); // for(let Line of Lines) { // logln('Line: ' + Line); // } } ================================================ FILE: basics/breakpoint2.js ================================================ // Axel '0vercl0k' Souchet - Dec 2017 "use strict"; let logln = function (e) { host.diagnostics.debugLog(e + '\n'); } function handle_bp() { let Regs = host.currentThread.Registers.User; let Args = [Regs.rcx, Regs.rdx, Regs.r8]; let ArgsS = Args.map(c => c.toString(16)); let HeapHandle = ArgsS[0]; let Flags = ArgsS[1]; let Size = ArgsS[2]; logln('RtlAllocateHeap: HeapHandle: ' + HeapHandle + ', Flags: ' + Flags + ', Size: ' + Size); if(Args[2].compareTo(0x100) > 0) { // stop execution if the allocation size is bigger than 0x100 return true; } // keep the execution going if it's a small size return false; } function invokeScript() { let Control = host.namespace.Debugger.Utility.Control; let Regs = host.currentThread.Registers.User; let CurrentProcess = host.currentProcess; let HeapAlloc = host.getModuleSymbolAddress('ntdll', 'RtlAllocateHeap'); let BreakpointAlreadySet = CurrentProcess.Debug.Breakpoints.Any( c => c.Address == HeapAlloc ); if(BreakpointAlreadySet == false) { logln('RltAllocateHeap @ ' + HeapAlloc.toString(16)); Control.ExecuteCommand('bp /w "@$scriptContents.handle_bp()" ' + HeapAlloc.toString(16)); } else { logln('Breakpoint already set.'); } logln('Press "g" to run the target.'); } ================================================ FILE: basics/eval.js ================================================ // Axel '0vercl0k' Souchet - Dec 2017 "use strict"; let logln = function (e) { host.diagnostics.debugLog(e + '\n'); } function invokeScript() { let Control = host.namespace.Debugger.Utility.Control; for(let Line of Control.ExecuteCommand('kp')) { logln('Line: ' + Line); } } ================================================ FILE: basics/extendmodel.js ================================================ // Axel '0vercl0k' Souchet - Dec 2017 "use strict"; class Attribute { constructor(Process, Name, Value) { this.__process = Process; this.Name = Name; this.Value = Value; } toString() { let S = 'Process: ' + this.__process.Name + ', '; S += 'Name: ' + this.Name + ', '; S += 'Value: ' + this.Value; return S; } } class Attributes { constructor() { this.__attrs = []; } push(Attr) { this.__attrs.push(Attr); } *[Symbol.iterator]() { for (let Attr of this.__attrs) { yield Attr; } } toString() { return 'Attributes'; } } class Sub { constructor(Process) { this.__process = Process; } get SubFoo() { return 'SubFoo from ' + this.__process.Name; } get SubBar() { return 'SubBar from ' + this.__process.Name; } get Attributes() { let Attrs = new Attributes(); Attrs.push(new Attribute(this.__process, 'attr0', 'value0')); Attrs.push(new Attribute(this.__process, 'attr1', 'value0')); return Attrs; } toString() { return 'Sub module'; } } class DiaryOfAReverseEngineer { constructor(Process) { this.__process = Process; } get Foo() { return 'Foo from ' + this.__process.Name; } get Bar() { return 'Bar from ' + this.__process.Name; } Add(a, b) { return a + b; } get Sub() { return new Sub(this.__process); } toString() { return 'Diary of a reverse-engineer'; } } class ProcessModelParent { get DiaryOfAReverseEngineer() { return new DiaryOfAReverseEngineer(this); } } function initializeScript() { return [new host.namedModelParent( ProcessModelParent, 'Debugger.Models.Process' )]; } ================================================ FILE: basics/extendmodel_1.js ================================================ // Axel '0vercl0k' Souchet - Dec 2017 "use strict"; class ProcessModelParent { get DiaryOfAReverseEngineer() { return 'hello from ' + this.Name; } } function initializeScript() { return [new host.namedModelParent( ProcessModelParent, 'Debugger.Models.Process' )]; } ================================================ FILE: basics/extendmodel_2.js ================================================ // Axel '0vercl0k' Souchet - Dec 2017 "use strict"; class DiaryOfAReverseEngineer { constructor(Process) { this.process = Process; } get Foo() { return 'Foo from ' + this.process.Name; } get Bar() { return 'Bar from ' + this.process.Name; } Add(a, b) { return a + b; } } class ProcessModelParent { get DiaryOfAReverseEngineer() { return new DiaryOfAReverseEngineer(this); } } function initializeScript() { return [new host.namedModelParent( ProcessModelParent, 'Debugger.Models.Process' )]; } ================================================ FILE: basics/extendmodel_2_1.js ================================================ // Axel '0vercl0k' Souchet - Dec 2017 "use strict"; class DiaryOfAReverseEngineer { constructor(Process) { this.__process = process; } get Foo() { return 'Foo from ' + this.__process.Name; } get Bar() { return 'Bar from ' + this.__process.Name; } Add(a, b) { return a + b; } toString() { return 'Diary of a reverse-engineer'; } } class ProcessModelParent { get DiaryOfAReverseEngineer() { return new DiaryOfAReverseEngineer(this); } } function initializeScript() { return [new host.namedModelParent( ProcessModelParent, 'Debugger.Models.Process' )]; } ================================================ FILE: basics/readmemory.js ================================================ // Axel '0vercl0k' Souchet - Dec 2017 "use strict"; let logln = function (e) { host.diagnostics.debugLog(e + '\n'); } function read_u64(addr) { return host.memory.readMemoryValues(addr, 1, 8)[0]; } function invokeScript() { let Regs = host.currentThread.Registers.User; let a = read_u64(Regs.rsp); logln(a.toString(16)); try { read_u64(0xdeadbeef); } catch(e) { logln(e); } let WideStr = host.currentProcess.Environment.EnvironmentBlock.ProcessParameters.ImagePathName.Buffer; logln(host.memory.readWideString(WideStr)); let WideStrAddress = WideStr.address; logln(host.memory.readWideString(WideStrAddress)); } ================================================ FILE: codecov/README.md ================================================ # codecov.js `codecov.js` is a [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extension for WinDbg that allows to extract code-coverage out of a [TTD](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-overview) trace. It generates a text file with every offsets in a module that have been executed during the recording. The file looks like the below: ```text ; TracePath: C:\work\codes\blazefox\js01.run ; c:\windows\system32\kernelbase.dll, 7fffb4ce0000, 293000 kernelbase.dll+5df40 kernelbase.dll+5df43 kernelbase.dll+5df47 kernelbase.dll+5df4b kernelbase.dll+5df4f ... ; c:\windows\system32\kernel32.dll, 7fffb6460000, b3000 kernel32.dll+1f3a0 kernel32.dll+21bb0 kernel32.dll+1bb90 kernel32.dll+1a280 kernel32.dll+1a284 kernel32.dll+1e640 kernel32.dll+63a0 ``` ## Usage Run `.scriptload codecov.js` to load the script. You can extract code-coverage using `!codecov "foo"`. ## Examples Extract code-coverage for every module having `kernel` in their name: ```text 0:000> !codecov "kernel" Looking for *kernel*.. Found 2 hits Found 7815 unique addresses in C:\WINDOWS\System32\KERNELBASE.dll Found 1260 unique addresses in C:\WINDOWS\System32\KERNEL32.DLL Writing C:\work\codes\tmp\js01.run.kernel.text... Done! @$codecov("kernel") 0:000> !codecov "kernel" Looking for *kernel*.. The output file C:\work\codes\tmp\js01.run.kernel.text already exists, exiting. @$codecov("kernel") ``` ================================================ FILE: codecov/codecov.js ================================================ // Axel '0vercl0k' Souchet - 2 Feb 2018 // // Example: // 0:000> !codecov "kernel" // Looking for *kernel*.. // Found 2 hits // Found 7815 unique addresses in C:\WINDOWS\System32\KERNELBASE.dll // Found 1260 unique addresses in C:\WINDOWS\System32\KERNEL32.DLL // Writing C:\work\codes\tmp\js01.run.kernel.text... // Done! // @$codecov("kernel") // 0:000> !codecov "kernel" // Looking for *kernel*.. // The output file C:\work\codes\tmp\js01.run.kernel.text already exists, exiting. // @$codecov("kernel") // 'use strict'; const log = host.diagnostics.debugLog; const logln = p => host.diagnostics.debugLog(p + '\n'); const hex = p => p.toString(16); function ExtractModuleName(ModulePath) { return ModulePath.slice( ModulePath.lastIndexOf('\\') + 1 ); } function CodeCoverageModule(Module) { const CurrentSession = host.currentSession; const BaseAddress = Module.BaseAddress; const Size = Module.Size; const CoverageLines = CurrentSession.TTD.Memory( BaseAddress, BaseAddress.add(Size), 'EC' ); const Offsets = Array.from(CoverageLines).map( p => hex( p.Address.subtract(BaseAddress) ) ); return { 'Path' : Module.Name.toLowerCase(), 'Base' : BaseAddress, 'Size' : Size, 'Offsets' : Offsets }; } function CodeCov(ModulePattern) { const CurrentSession = host.currentSession; const CurrentProcess = host.currentProcess; const Utility = host.namespace.Debugger.Utility; if(!CurrentSession.Attributes.Target.IsTTDTarget) { logln('!codecov expects a TTD trace'); return; } if(ModulePattern == undefined) { logln('!codecov "pattern"'); return; } ModulePattern = ModulePattern.toLowerCase(); logln('Looking for *' + ModulePattern + '*..'); const Modules = CurrentProcess.Modules.Where( p => p.Name.toLowerCase().indexOf(ModulePattern) != -1 ); if(Modules.Count() == 0) { logln('Could not find any matching module, exiting'); return; } const TracePath = CurrentSession.Attributes.Target.Details.DumpFileName; const TraceDir = TracePath.slice( 0, TracePath.lastIndexOf('\\') ); const TraceName = TracePath.slice( TracePath.lastIndexOf('\\') + 1 ); const FilePath = TraceDir + '\\' + TraceName + '.' + ModulePattern + '.txt'; if(Utility.FileSystem.FileExists(FilePath)) { logln('The output file ' + FilePath + ' already exists, exiting.'); return; } const Metadata = { 'TracePath' : TracePath }; const CoverageModules = []; logln('Found ' + Modules.Count() + ' hits'); for(const Module of Modules) { const ModuleCoverage = CodeCoverageModule(Module); logln('Found ' + ModuleCoverage.Offsets.length + ' unique addresses in ' + Module.Name); CoverageModules.push(ModuleCoverage); } logln('Writing ' + FilePath + '...'); const FileHandle = Utility.FileSystem.CreateFile(FilePath, 'CreateAlways'); const Writer = Utility.FileSystem.CreateTextWriter(FileHandle); for(const [Name, Value] of Object.entries(Metadata)) { Writer.WriteLine('; ' + Name + ': ' + Value); } for(const Module of CoverageModules) { // // First write the metadata that is module specific. // Writer.WriteLine('; ' + Module.Path + ', ' + hex(Module.Base) + ', ' + hex(Module.Size)); // // Write down the offsets. // const ModuleName = ExtractModuleName(Module.Path); for(const Offset of Module.Offsets) { Writer.WriteLine(ModuleName + '+' + hex(Offset)); } } FileHandle.Close(); logln('Done!'); } function initializeScript() { return [ new host.apiVersionSupport(1, 2), new host.functionAlias( CodeCov, 'codecov' ) ]; } ================================================ FILE: gdt/README.md ================================================ # gdt.js `gdt.js` is a [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extension for WinDbg that dumps the [Global Descriptor Table](https://wiki.osdev.org/Global_Descriptor_Table) on 64-bit kernels. I wrote this extension because I always find the output of the `dg` command confusing, if not broken. ## Usage Run `.scriptload gdt.js` to load the script. You can dump a specific entry by passing the segment selector to the `!gdt` command, or it will dump the entire table if nothing is passed. Run `!wow64exts.sw` if you are running the script while being in the context of a [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details) thread. ## Examples * Dumping the GDT entry that enables [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details): ``` 32.kd> !gdt @cs dt nt!_KGDTENTRY64 0xfffff8045215dfd0 Base: [0x0 -> 0xffffffff] Type: Code Execute/Read Accessed (0xb) DPL: 0x3 Present: 0x1 Mode: 32b Compat @$gdt(@cs) 32.kd> dg @cs P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- ----------------- ----------------- ---------- - -- -- -- -- -------- 0023 00000000`00000000 00000000`ffffffff Code RE Ac 3 Bg Pg P Nl 00000cfb ``` * Dumping the GDT entry that allows 32-bit code to invoke 64-bit code: ``` 32.kd> !gdt 0x33 dt nt!_KGDTENTRY64 0xfffff8045215dfe0 Base: [0x0 -> 0x0] Type: Code Execute/Read Accessed (0xb) DPL: 0x3 Present: 0x1 Mode: 64b @$gdt(0x33) 32.kd> dg 33 P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- ----------------- ----------------- ---------- - -- -- -- -- -------- 0033 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P Lo 000002fb ``` * Dumping the [Task State Segment](https://wiki.osdev.org/Task_State_Segment): ``` 32.kd> !gdt @tr dt nt!_KGDTENTRY64 0xfffff8045215dff0 Base: [0xfffff8045215c000 -> 0xfffff8045215c067] Type: TSS64 Busy (0xb) DPL: 0x0 Present: 0x1 @$gdt(@tr) 32.kd> dg @tr P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- ----------------- ----------------- ---------- - -- -- -- -- -------- 0040 00000000`5215c000 00000000`00000067 TSS32 Busy 0 Nb By P Nl 0000008b ``` * Dumping the [Thread Environment Block](https://en.wikipedia.org/wiki/Win32_Thread_Information_Block) of a [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details) thread: ``` 32.kd> !gdt @fs dt nt!_KGDTENTRY64 0xfffff8045215e000 Base: [0x326000 -> 0x329c00] Type: Data Read/Write Accessed (0x3) DPL: 0x3 Present: 0x1 @$gdt(@fs) 32.kd> !teb Wow64 TEB32 at 0000000000326000 ExceptionList: 00000000004ff59c StackBase: 0000000000500000 StackLimit: 00000000004f2000 SubSystemTib: 0000000000000000 FiberData: 0000000000001e00 ArbitraryUserPointer: 0000000000000000 Self: 0000000000326000 EnvironmentPointer: 0000000000000000 ClientId: 0000000000001ad8 . 0000000000001adc RpcHandle: 0000000000000000 Tls Storage: 0000000000834188 PEB Address: 0000000000323000 LastErrorValue: 0 LastStatusValue: c000007c Count Owned Locks: 0 HardErrorMode: 0 ``` * Dumping the entire GDT on a Windows 10 64-bit Virtual Machine: ``` 32.kd> !gdt Dumping the GDT from 0xfffff8045215dfb0 to 0xfffff8045215e007.. [0]: dt nt!_KGDTENTRY64 0xfffff8045215dfb0 Base: [0x0 -> 0x0] Type: Reserved (0x0) DPL: 0x0 Present: 0x0 [1]: dt nt!_KGDTENTRY64 0xfffff8045215dfb8 Base: [0x0 -> 0x0] Type: Reserved (0x0) DPL: 0x0 Present: 0x0 [2]: dt nt!_KGDTENTRY64 0xfffff8045215dfc0 Base: [0x0 -> 0x0] Type: Code Execute/Read Accessed (0xb) DPL: 0x0 Present: 0x1 Mode: 64b [3]: dt nt!_KGDTENTRY64 0xfffff8045215dfc8 Base: [0x0 -> 0x0] Type: Data Read/Write Accessed (0x3) DPL: 0x0 Present: 0x1 [4]: dt nt!_KGDTENTRY64 0xfffff8045215dfd0 Base: [0x0 -> 0xffffffff] Type: Code Execute/Read Accessed (0xb) DPL: 0x3 Present: 0x1 Mode: 32b Compat [5]: dt nt!_KGDTENTRY64 0xfffff8045215dfd8 Base: [0x0 -> 0xffffffff] Type: Data Read/Write Accessed (0x3) DPL: 0x3 Present: 0x1 [6]: dt nt!_KGDTENTRY64 0xfffff8045215dfe0 Base: [0x0 -> 0x0] Type: Code Execute/Read Accessed (0xb) DPL: 0x3 Present: 0x1 Mode: 64b [7]: dt nt!_KGDTENTRY64 0xfffff8045215dfe8 Base: [0x0 -> 0x0] Type: Reserved (0x0) DPL: 0x0 Present: 0x0 [8]: dt nt!_KGDTENTRY64 0xfffff8045215dff0 Base: [0xfffff8045215c000 -> 0xfffff8045215c067] Type: TSS64 Busy (0xb) DPL: 0x0 Present: 0x1 [9]: dt nt!_KGDTENTRY64 0xfffff8045215e000 Base: [0x326000 -> 0x329c00] Type: Data Read/Write Accessed (0x3) DPL: 0x3 Present: 0x1 @$gdt() ``` ================================================ FILE: gdt/gdt.js ================================================ // Axel '0vercl0k' Souchet - October 8 2021 'use strict'; // // Small reminders from the manual intels on segments in x64: // - Because ES, DS, and SS segment registers are not used in 64-bit mode, their fields (base, limit, and attribute) in // segment descriptor registers are ignored. // - Selector.Index selects one of 8192 descriptors in the GDT or LDT. The processor multiplies // the index value by 8 (the number of bytes in a segment descriptor) and adds the result to the base // address of the GDT or LDT (from the GDTR or LDTR register, respectively). // - The first entry of the GDT is not used by the processor. // - The hidden descriptor register fields for FS.base and GS.base are physically mapped to MSRs in order to load all // address bits supported by a 64-bit implementation. Software with CPL = 0 (privileged software) can load all // supported linear-address bits into FS.base or GS.base using WRMSR. // const log = host.diagnostics.debugLog; const logln = p => log(`${p}\n`); const hex = p => `0x${p.toString(16)}`; const GdtSystemEntryTypes = new Map([ [0, 'Reserved'], [1, 'Reserved'], [2, 'LDT'], [3, 'Reserved'], [4, 'Reserved'], [5, 'Reserved'], [6, 'Reserved'], [7, 'Reserved'], [8, 'Reserved'], [9, 'TSS64 Available'], [10, 'Reserved'], [11, 'TSS64 Busy'], [12, 'CallGate64'], [13, 'Reserved'], [14, 'InterruptGate64'], [15, 'TrapGate64'], ]); const GdtNonSystemEntryTypes = new Map([ [0, 'Data Read-Only'], [1, 'Data Read-Only Accessed'], [2, 'Data Read/Write'], [3, 'Data Read/Write Accessed'], [4, 'Data Read-Only Expand-Down'], [5, 'Data Read-Only Expand-Down Accessed'], [6, 'Data Read/Write Expand-Down'], [7, 'Data Read/Write Expand-Down Accessed'], [8, 'Code Execute-Only'], [9, 'Code Execute-Only Accessed'], [10, 'Code Execute/Read'], [11, 'Code Execute/Read Accessed'], [12, 'Code Execute-Only Conforming'], [13, 'Code Execute-Only Conforming Accessed'], [14, 'Code Execute/Read Conforming'], [15, 'Code Execute/Read Conforming Accessed'], ]); const GdtEntryTypes = new Map([ [0, GdtSystemEntryTypes], [1, GdtNonSystemEntryTypes] ]); class GdtEntry { constructor(Addr) { this._Addr = Addr; const Entry = host.createPointerObject(Addr, 'nt', '_KGDTENTRY64*'); const LimitHigh = Entry.Bits.LimitHigh.bitwiseShiftLeft(16); const LimitLow = Entry.LimitLow; this._Limit = LimitHigh.add(LimitLow); // For whatever reason _KGDTENTRY64 is 5 bits long. The intel manuals describes // it as 4bits and the 'Descriptor type' bit. // We grab the lower 4 bits as the type, and the MSB as the 'Descriptor type'. this._Type = Entry.Bits.Type & 15; this._NonSystem = (Entry.Bits.Type >> 4) & 1; this._TypeS = GdtEntryTypes.get(this._NonSystem).get(this._Type); // Note that system descriptors in IA-32e mode are 16 bytes instead // of 8 bytes. this._Size = 8; if (!this._NonSystem && this._TypeS != 'Reserved') { this._Size = 16; } this._Dpl = Entry.Bits.Dpl; this._Granularity = Entry.Bits.Granularity; this._Present = Entry.Bits.Present; this._LongMode = Entry.Bits.LongMode; this._DefaultBig = Entry.Bits.DefaultBig; const BaseUpper = this._Size == 8 ? 0 : Entry.BaseUpper.bitwiseShiftLeft(32); const BaseHigh = Entry.Bytes.BaseHigh.bitwiseShiftLeft(24); const BaseMiddle = Entry.Bytes.BaseMiddle.bitwiseShiftLeft(16); const BaseLow = Entry.BaseLow; this._Base = BaseUpper.add(BaseHigh).add(BaseMiddle).add(BaseLow); const Flags1 = Entry.Bytes.Flags1; const Flags2 = Entry.Bytes.Flags2.bitwiseShiftLeft(8); this._Attrs = Flags2.add(Flags1); } toString() { const Increments = this._Granularity == 1 ? 1024 * 4 : 1; // For example, when the granularity flag is set, a limit of 0 results in // valid offsets from 0 to 4095. const Size = this._Limit * Increments + (this._Granularity ? 0xfff : 0); let S = `dt nt!_KGDTENTRY64 ${hex(this._Addr)} Base: [${hex(this._Base)} -> ${hex(this._Base.add(Size))}] Type: ${this._TypeS} (${hex(this._Type)}) DPL: ${hex(this._Dpl)} Present: ${hex(this._Present)} Atributes: ${hex(this._Attrs)}`; if (this._TypeS.startsWith('Code')) { S += ` Mode: ${this._LongMode ? '64b' : (this._DefaultBig ? '32b Compat' : '16b Compat')}` } return S; } } function GetGdt() { const Control = host.namespace.Debugger.Utility.Control; const [_, GdtrValue] = Control.ExecuteCommand('r @gdtr').First().split('='); const [__, GdtlValue] = Control.ExecuteCommand('r @gdtl').First().split('='); return [host.parseInt64(GdtrValue, 16), host.parseInt64(GdtlValue, 16)]; } function DumpGdtEntry(Addr) { return new GdtEntry(Addr); } function DumpAllGdt() { const [GdtBase, Gdtl] = GetGdt(); const GdtEnd = GdtBase.add(Gdtl); logln(`Dumping the GDT from ${hex(GdtBase)} to ${hex(GdtEnd)}..`); for (let CurrentEntry = GdtBase, Idx = 0; CurrentEntry.compareTo(GdtEnd) < 0; Idx++) { const Entry = DumpGdtEntry(CurrentEntry); logln(`[${Idx}]: ${Entry}`); CurrentEntry = CurrentEntry.add(Entry._Size); } } function DumpGdt(Selector) { const [GdtBase, _] = GetGdt(); const Index = Selector.bitwiseShiftRight(3); const Offset = Index.multiply(8); const EntryAddress = GdtBase.add(Offset); const Entry = DumpGdtEntry(EntryAddress); logln(Entry); } function Gdt(Selector) { const Attributes = host.currentSession.Attributes; const IsKernel = Attributes.Target.IsKernelTarget; // // XXX: Not sure how to do this better? // Attributes.Machine.PointerSize is 4 when running in a Wow64 thread :-/. // let Is64Bit = true; try { host.createPointerObject(0, 'nt', '_KGDTENTRY64*'); } catch(e) { Is64Bit = false; } if (!IsKernel || !Is64Bit) { logln('The running session is not a kernel session or it is not running a 64-bit OS, so exiting'); return; } if (Selector == undefined) { DumpAllGdt(); } else { DumpGdt(Selector); } } function initializeScript() { return [ new host.apiVersionSupport(1, 3), new host.functionAlias( Gdt, 'gdt' ), ]; } ================================================ FILE: parse_eh_win64/README.md ================================================ # parse_eh_win64.js `parse_eh_win64.js` is a [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extension for WinDbg that shows examples of how to extending the data-model with exception-handling related information for 64 bits executables. More background is available in this article: [Debugger data model, Javascript & x64 exception handling](https://doar-e.github.io/blog/2017/12/01/debugger-data-model/). ## Usage Run `.scriptload parse_eh_win64.js` to load the script. The script extends the `Debugger.Models.Process`, `Debugger.Models.Module` models and also exposes the `!ehhandlers` command. ## Examples * At the process level, dumping `Function` objects and ordering them by the number of exception-handlers they define: ```text 0:002> dx @$curprocess.Functions.OrderByDescending(p => p.ExceptionHandlers.Count()) @$curprocess.Functions.OrderByDescending(p => p.ExceptionHandlers.Count()) [0x0] : RVA:7fffb64bebf0 -> RVA:7fffb64bf022, 12 exception handlers [0x1] : RVA:7fffb8bdff80 -> RVA:7fffb8be0b67, 11 exception handlers [0x2] : RVA:7fffb1df8114 -> RVA:7fffb1df8360, 9 exception handlers [0x3] : RVA:7fffa0111354 -> RVA:7fffa01115a0, 9 exception handlers [0x4] : RVA:7fffb2183044 -> RVA:7fffb2183290, 9 exception handlers [0x5] : RVA:7fffa0d41344 -> RVA:7fffa0d41590, 9 exception handlers [0x6] : RVA:7fffb6573020 -> RVA:7fffb6573356, 6 exception handlers [0x7] : RVA:7fffb4c71f94 -> RVA:7fffb4c720b4, 6 exception handlers [0x8] : RVA:7fffb65e5774 -> RVA:7fffb65e5894, 6 exception handlers [0x9] : RVA:7fffb660c62c -> RVA:7fffb660cf2e, 6 exception handlers [0xa] : RVA:7fffb6c6f014 -> RVA:7fffb6c6f134, 6 exception handlers [0xb] : RVA:7fffb8b9a350 -> RVA:7fffb8b9b39b, 6 exception handlers [0xc] : RVA:7fffb35168a0 -> RVA:7fffb3516efb, 5 exception handlers ``` * Dumping a `Function` object: ```text 0:002> dx -r1 @$curprocess.Functions[0] @$curprocess.Functions[0] : RVA:7ff67025a6d0 -> RVA:7ff67025a738, 1 exception handlers EHHandlerRVA : 0x9b9700 EHHandler : 0x7ff6708f9700 BeginRVA : 0x31a6d0 EndRVA : 0x31a738 Begin : 0x7ff67025a6d0 End : 0x7ff67025a738 ExceptionHandlers : __try {7ff67025a6fb -> 7ff67025a712} __except(EXCEPTION_EXECUTE_HANDLER) {7ff67025a736} ``` * At the module level, dumping `ExceptionHandler` objects: ```text 0:002> dx @$curprocess.Modules[0].ExceptionHandlers @$curprocess.Modules[0].ExceptionHandlers : Exception handlers [0x0] : __try {7ff67025a6fb -> 7ff67025a712} __except(EXCEPTION_EXECUTE_HANDLER) {7ff67025a736} [0x1] : __try {7ff6708f80b3 -> 7ff6708f813e} __except(7ff6708f93f2()) {7ff6708f813e} [0x2] : __try {7ff6708f90fd -> 7ff6708f9202} __except(7ff6708f9425()) {7ff6708f9202} [0x3] : __try {7ff6708f9236 -> 7ff ``` * Dumping an `ExceptionHandler` object: ```text 0:002> dx @$curprocess.Modules[0].ExceptionHandlers[0] @$curprocess.Modules[0].ExceptionHandlers[0] : __try {7ff67025a6fb -> 7ff67025a712} __except(EXCEPTION_EXECUTE_HANDLER) {7ff67025a736} Begin : 0x7ff67025a6fb End : 0x7ff67025a712 HandlerAddress : 0x1 JumpTarget : 0x7ff67025a736 IsTryFinally : false HasFilter : false ``` * Dumping the current call-stack with EH information: ```text 0:002> !ehhandlers 5 stack frames, scanning for handlers... Frame 1: EHHandler: 7fffb8c1fc90: ntdll!_C_specific_handler: Except: 7fffb8c5ef1d: ntdll!DbgUiRemoteBreakin+0x4d: Frame 3: EHHandler: 7fffb8c1fc90: ntdll!_C_specific_handler: Except: 7fffb8bfa267: ntdll!RtlUserThreadStart+0x37: Filter: 7fffb8c38021: ntdll!RtlUserThreadStart$filt$0: @$ehhandlers() ``` ================================================ FILE: parse_eh_win64/parse_eh_win64.js ================================================ // Axel '0vercl0k' Souchet - Dec 2017 "use strict"; let log = host.diagnostics.debugLog; let logln = function (e) { host.diagnostics.debugLog(e + '\n'); }; function read_u32(addr) { return host.memory.readMemoryValues(addr, 1, 4)[0]; } function read_u8(addr) { return host.memory.readMemoryValues(addr, 1, 1)[0]; } class ScopeRecord { // 0:000> dt SCOPE_RECORD // +0x000 BeginAddress : Uint4B // +0x004 EndAddress : Uint4B // +0x008 HandlerAddress : Uint4B // +0x00c JumpTarget : Uint4B constructor(ScopeRecordAddress) { this.Begin = read_u32(ScopeRecordAddress); this.End = read_u32(ScopeRecordAddress.add(0x4)); this.HandlerAddress = read_u32(ScopeRecordAddress.add(0x8)); this.JumpTarget = read_u32(ScopeRecordAddress.add(0xc)); } get IsTryFinally() { return this.JumpTarget.compareTo(0) == 0; } get HasFilter() { return this.IsTryFinally == false && this.HandlerAddress.compareTo(1) != 0; } InBound(Begin, End) { return this.Begin.compareTo(Begin) >= 0 && this.End.compareTo(End) < 0; } toString() { let S = ' __try {' S += this.Begin.toString(16) + ' -> ' + this.End.toString(16); S += '}'; if (this.IsTryFinally == true) { S += ' __finally {'; S += this.HandlerAddress.toString(16); } else { S += ' __except('; if (this.HasFilter == false) { S += 'EXCEPTION_EXECUTE_HANDLER'; } else { S += this.HandlerAddress.toString(16) + '()'; } S += ') {'; S += this.JumpTarget.toString(16); } S += '}'; return S; } } class Function { constructor(BaseAddress, EHHandler, Begin, End) { this.__BaseAddress = BaseAddress; this.EHHandlerRVA = EHHandler; this.EHHandler = BaseAddress.add(EHHandler); this.BeginRVA = Begin; this.EndRVA = End; this.Begin = BaseAddress.add(Begin); this.End = BaseAddress.add(End); this.ExceptionHandlers = []; } SetRecords(Records, KeepRVA = false) { this.ExceptionHandlers = Records; if (KeepRVA) { return; } for (let ExceptionHandler of this.ExceptionHandlers) { ExceptionHandler.Begin = ExceptionHandler.Begin.add(this.__BaseAddress); ExceptionHandler.End = ExceptionHandler.End.add(this.__BaseAddress); if (ExceptionHandler.JumpTarget.compareTo(0) != 0) { ExceptionHandler.JumpTarget = ExceptionHandler.JumpTarget.add(this.__BaseAddress); } if (ExceptionHandler.HandlerAddress.compareTo(1) != 0) { ExceptionHandler.HandlerAddress = ExceptionHandler.HandlerAddress.add(this.__BaseAddress); } } } toString() { let S = 'RVA:' + this.Begin.toString(16) + ' -> RVA:' + this.End.toString(16); S += ', ' + this.ExceptionHandlers.length + ' exception handlers'; return S; } } function ParseCSpecificHandlerDatas( ScopeCount, ScopeRecords, Function ) { // 0:000> ?? sizeof(SCOPE_RECORD) // unsigned int64 0x10 let Records = []; let ScopeSize = ScopeCount.multiply(0x10); for (let i = 0; i < ScopeSize; i += 0x10) { let CurrentScope = ScopeRecords.add(i); let Record = new ScopeRecord(CurrentScope); if (Record.InBound(Function.BeginRVA, Function.EndRVA) == false) { return []; } Records.push(Record); } return Records; } function ExtractExceptionHandlersForModule( BaseAddress, KeepRVA = false ) { let EHANDLER = 1; let IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3; // 0:000> dt _IMAGE_DOS_HEADER e_lfanew // +0x03c e_lfanew : Int4B let NtHeaders = BaseAddress.add(read_u32(BaseAddress.add(0x3c))); // 0:000> dt _IMAGE_NT_HEADERS64 OptionalHeader // +0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER64 // 0:000> dt _IMAGE_OPTIONAL_HEADER64 DataDirectory // +0x070 DataDirectory : [16] _IMAGE_DATA_DIRECTORY // 0:000> dt _IMAGE_DATA_DIRECTORY // +0x000 VirtualAddress : Uint4B // +0x004 Size : Uint4B let EntryExceptionDirectory = NtHeaders.add(0x18 + 0x70 + (IMAGE_DIRECTORY_ENTRY_EXCEPTION * 8)); let RuntimeFunctionEntry = BaseAddress.add(read_u32(EntryExceptionDirectory)); let SizeOfDirectory = read_u32(EntryExceptionDirectory.add(4)); let Functions = []; for (let i = 0; i < SizeOfDirectory; i += 0xC) { // 0:000> dt _IMAGE_RUNTIME_FUNCTION_ENTRY // +0x000 BeginAddress : Uint4B // +0x004 EndAddress : Uint4B // +0x008 UnwindInfoAddress : Uint4B // +0x008 UnwindData : Uint4B // 0:000> ?? sizeof(_IMAGE_RUNTIME_FUNCTION_ENTRY) // unsigned int64 0xc let CurrentEntry = RuntimeFunctionEntry.add(i); let BeginAddress = read_u32(CurrentEntry); let EndAddress = read_u32(CurrentEntry.add(4)); if (BeginAddress.compareTo(0) == 0 || EndAddress.compareTo(0) == 0) { continue; } // 0:000> dt UNWIND_INFO // +0x000 Version : Pos 0, 3 Bits // +0x000 Flags : Pos 3, 5 Bits // +0x001 SizeOfProlog : UChar // +0x002 CountOfCodes : UChar // +0x003 FrameRegister : Pos 0, 4 Bits // +0x003 FrameOffset : Pos 4, 4 Bits // +0x004 UnwindCode : [1] UNWIND_CODE let UnwindInfo = BaseAddress.add(read_u32(CurrentEntry.add(8))); let UnwindInfoFlags = read_u8(UnwindInfo).bitwiseShiftRight(3); if (UnwindInfoFlags.bitwiseAnd(EHANDLER).compareTo(0) == 0) { continue; } // 0:000> ?? sizeof(UNWIND_CODE) // unsigned int64 2 let CountOfCodes = read_u8(UnwindInfo.add(2)); // For alignment purposes, this array will always have an even number of entries, // with the final entry potentially unused (in which case the array will be one // longer than indicated by the count of unwind codes field). let AlignedCountOfCodes = (CountOfCodes + 1) & ~1; // 0:000> dt UNWIND_INFO_END // +0x000 ExceptionHandler : Uint4B // +0x004 ExceptionData : Uint4B let UnwindInfoEnd = UnwindInfo.add(4 + (AlignedCountOfCodes * 2)); let ExceptionHandler = read_u32(UnwindInfoEnd); // 0:000> dt SEH_SCOPE_TABLE // +0x000 Count : Uint4B // +0x004 ScopeRecord : [1] SCOPE_RECORD let ScopeTable = UnwindInfoEnd.add(4); let ScopeCount = read_u32(ScopeTable); if (ScopeCount == 0) { continue; } let Records = ScopeTable.add(4); let CurrentFunction = new Function( BaseAddress, ExceptionHandler, BeginAddress, EndAddress ); Records = ParseCSpecificHandlerDatas(ScopeCount, Records, CurrentFunction); if (Records.length == 0) { continue; } CurrentFunction.SetRecords(Records, KeepRVA); Functions.push(CurrentFunction); } return Functions; } class ModelExceptionHandlers { constructor(Type, Inst) { this.__module = null; this.__process = null; if (Type == 'Module') { this.__module = Inst; } else { this.__process = Inst; } this.__handlers = null; // We do not parse the exception handlers information here // as this constructor is called everytime you do: // dx @$curprocess // so we will only parse the information when the user asked for it. } __ExceptionHandlers(Modules) { let Handlers = []; for (let Module of Modules) { let Functions = ExtractExceptionHandlersForModule(Module.BaseAddress); for (let Function of Functions) { Handlers = Handlers.concat(Function.ExceptionHandlers); } } return Handlers; } *[Symbol.iterator]() { if (this.__handlers == null) { // Only parse the infornmation once everytine we query it. let Modules = [this.__module]; if (this.__process != null) { Modules = this.__process.Modules; } this.__handlers = this.__ExceptionHandlers(Modules); } for (let Handler of this.__handlers) { yield Handler; } } toString() { return 'Exception handlers'; } } class ModelFunctions { constructor(Type, Inst) { this.__module = null; this.__process = null; if (Type == 'Module') { this.__module = Inst; } else { this.__process = Inst; } this.__functions = null; } __Functions(Modules) { let Functions = []; for (let Module of Modules) { let CurrentFunctions = ExtractExceptionHandlersForModule(Module.BaseAddress); Functions = Functions.concat(CurrentFunctions); } return Functions; } *[Symbol.iterator]() { if (this.__functions == null) { this.__functions = []; let Modules = [this.__module]; if (this.__process != null) { Modules = this.__process.Modules; } this.__functions = this.__Functions(Modules); } for (let Function of this.__functions) { yield Function; } } toString() { return 'Functions'; } } class __ProcessModelExtension { get ExceptionHandlers() { return new ModelExceptionHandlers('Process', this); } get Functions() { return new ModelFunctions('Process', this); } } class __ModuleModelExtension { get ExceptionHandlers() { return new ModelExceptionHandlers('Module', this); } get Functions() { return new ModelFunctions('Module', this); } } function BangEHHandlers() { let Control = host.namespace.Debugger.Utility.Control; let CurrentThread = host.currentThread; let CurrentProcess = host.currentProcess; let Registers = CurrentThread.Registers.User; let ReturnAddresses = [Registers.rip]; let Frames = CurrentThread.Stack.Frames; for (let Frame of Frames) { ReturnAddresses.push(Frame.Attributes.ReturnOffset); } logln(ReturnAddresses.length + ' stack frames, scanning for handlers...'); let Functions = Array.from(CurrentProcess.Functions); for (let Entry of ReturnAddresses.entries()) { let FrameNumber = host.Int64(Entry[0]); let ReturnAddress = Entry[1]; let Func = Functions.find( c => ReturnAddress.compareTo(c.Begin) >= 0 && ReturnAddress.compareTo(c.End) < 0 ); if (Func == undefined) { continue; } let ExceptionHandlers = Array.from(Func.ExceptionHandlers); let ExceptionHandler = ExceptionHandlers.find( c => ReturnAddress.compareTo(c.Begin) >= 0 && ReturnAddress.compareTo(c.End) < 0 ) if (ExceptionHandler == undefined) { continue; } let Filter = undefined; let EHHandler = Func.EHHandler; let Handler = ExceptionHandler.HandlerAddress; let Name = 'Finally'; if (ExceptionHandler.IsTryFinally == false) { if (ExceptionHandler.HasFilter) { Filter = ExceptionHandler.HandlerAddress; } Handler = ExceptionHandler.JumpTarget; Name = ' Except'; } let FormatAddress = function (Handler) { let S = Handler.toString(16) + ': '; S += Control.ExecuteCommand( 'u ' + Handler.toString(16) + ' l1' ).First(); return S; } logln('Frame ' + FrameNumber.toString(16) + ': EHHandler: ' + FormatAddress(EHHandler)); logln(' ' + Name + ': ' + FormatAddress(Handler)); if (Filter != undefined) { logln(' Filter: ' + FormatAddress(Filter)); } } } function initializeScript() { return [ new host.namedModelParent( __ProcessModelExtension, 'Debugger.Models.Process' ), new host.namedModelParent( __ModuleModelExtension, 'Debugger.Models.Module' ), new host.functionAlias( BangEHHandlers, 'ehhandlers' ) ]; } ================================================ FILE: policybuffer/README.md ================================================ # policybuffer.js `policybuffer.js` is a [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extension for WinDbg that disassembles Policy buffer programs used by the Chromium sandbox. Those programs are used to evaluate policy decision. ## Usage Run `.scriptload policybuffer.js` to load the script. You can invoke the disassembler feature with `!disasspolicy `. ## Examples * Dumping the File System policy of Firefox: ```text 0:017> !disasspolicy 0x00000131`d2142208 !OP_NUMBER_AND_MATCH !OP_WSTRING_MATCH OP_ACTION !OP_NUMBER_AND_MATCH OP_WSTRING_MATCH OP_ACTION OP_WSTRING_MATCH OP_ACTION !OP_NUMBER_AND_MATCH OP_NUMBER_MATCH OP_WSTRING_MATCH OP_ACTION !OP_NUMBER_AND_MATCH OP_NUMBER_MATCH OP_WSTRING_MATCH OP_ACTION !OP_NUMBER_AND_MATCH OP_NUMBER_MATCH OP_WSTRING_MATCH OP_ACTION !OP_NUMBER_AND_MATCH OP_NUMBER_MATCH OP_WSTRING_MATCH OP_ACTION !OP_NUMBER_AND_MATCH OP_NUMBER_MATCH OP_WSTRING_MATCH OP_ACTION !OP_NUMBER_AND_MATCH OP_NUMBER_MATCH OP_WSTRING_MATCH OP_ACTION OP_WSTRING_MATCH OP_ACTION OP_WSTRING_MATCH OP_ACTION @$disasspolicy(0x00000131`d2142208) ``` ================================================ FILE: policybuffer/policybuffer.js ================================================ // Axel '0vercl0k' Souchet - 5 March 2019 // sandbox::PolicyBase::EvalPolicy 'use strict'; // // Utility functions. // const Log = host.diagnostics.debugLog; const Logln = p => host.diagnostics.debugLog(p + '\n'); const Hex = p => '0x' + p.toString(16); const ReadWstring = p => host.memory.readWideString(p); function ReadShort(Address) { let Value = null; try { Value = host.memory.readMemoryValues( Address, 1, 2 )[0]; } catch(e) { } return Value; } function ReadDword(Address) { let Value = null; try { Value = host.memory.readMemoryValues( Address, 1, 4 )[0]; } catch(e) { } return Value; } function ReadQword(Address) { let Value = null; try { Value = host.memory.readMemoryValues( Address, 1, 8 )[0]; } catch(e) { } return Value; } function initializeScript() { return [ new host.apiVersionSupport(1, 3) ]; } // // Constants. // // The low-level policy is implemented using the concept of policy 'opcodes'. // An opcode is a structure that contains enough information to perform one // comparison against one single input parameter. For example, an opcode can // encode just one of the following comparison: // // - Is input parameter 3 not equal to NULL? // - Does input parameter 2 start with L"c:\\"? // - Is input parameter 5, bit 3 is equal 1? // // Each opcode is in fact equivalent to a function invocation where all // the parameters are known by the opcode except one. So say you have a // function of this form: // bool fn(a, b, c, d) with 4 arguments // // Then an opcode is: // op(fn, b, c, d) // Which stores the function to call and its 3 last arguments // // Then and opcode evaluation is: // op.eval(a) ------------------------> fn(a,b,c,d) // internally calls // // The idea is that complex policy rules can be split into streams of // opcodes which are evaluated in sequence. The evaluation is done in // groups of opcodes that have N comparison opcodes plus 1 action opcode: // // [comparison 1][comparison 2]...[comparison N][action][comparison 1]... // ----- evaluation order-----------> // // Each opcode group encodes one high-level policy rule. The rule applies // only if all the conditions on the group evaluate to true. The action // opcode contains the policy outcome for that particular rule. // https://dxr.mozilla.org/mozilla-central/source/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h#77 // The following are the implemented opcodes. // enum OpcodeID { // OP_ALWAYS_FALSE, // Evaluates to false (EVAL_FALSE). // OP_ALWAYS_TRUE, // Evaluates to true (EVAL_TRUE). // OP_NUMBER_MATCH, // Match a 32-bit integer as n == a. // OP_NUMBER_MATCH_RANGE, // Match a 32-bit integer as a <= n <= b. // OP_NUMBER_AND_MATCH, // Match using bitwise AND; as in: n & a != 0. // OP_WSTRING_MATCH, // Match a string for equality. // OP_ACTION // Evaluates to an action opcode. // }; const OP_ALWAYS_FALSE = 0; const OP_ALWAYS_TRUE = 1; const OP_NUMBER_MATCH = 2; const OP_NUMBER_MATCH_RANGE = 3; const OP_NUMBER_AND_MATCH = 4; const OP_WSTRING_MATCH = 5; const OP_ACTION = 6; const Opcodes = { [OP_ALWAYS_FALSE] : 'OP_ALWAYS_FALSE', [OP_ALWAYS_TRUE] : 'OP_ALWAYS_TRUE', [OP_NUMBER_MATCH] : 'OP_NUMBER_MATCH', [OP_NUMBER_MATCH_RANGE] : 'OP_NUMBER_MATCH_RANGE', [OP_NUMBER_AND_MATCH] : 'OP_NUMBER_AND_MATCH', [OP_WSTRING_MATCH] : 'OP_WSTRING_MATCH', [OP_ACTION] : 'OP_ACTION' }; // https://dxr.mozilla.org/mozilla-central/source/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h // enum StringMatchOptions { // CASE_SENSITIVE = 0, // Pay or Not attention to the case as defined by // CASE_INSENSITIVE = 1, // RtlCompareUnicodeString windows API. // EXACT_LENGHT = 2 // Don't do substring match. Do full string match. // }; const MatchingOptions = { 0 : 'CASE_SENSITIVE', 1 : 'CASE_INSENSITIVE', 2 : 'EXACT_LENGTH', 3 : 'EXACT_LENGTH | CASE_INSENSITIVE' }; // These are the possible policy outcomes. Note that some of them might // not apply and can be removed. Also note that The following values only // specify what to do, not how to do it and it is acceptable given specific // cases to ignore the policy outcome. // enum EvalResult { // // Comparison opcode values: // EVAL_TRUE, // Opcode condition evaluated true. // EVAL_FALSE, // Opcode condition evaluated false. // EVAL_ERROR, // Opcode condition generated an error while evaluating. // // Action opcode values: // ASK_BROKER, // The target must generate an IPC to the broker. On the broker // // side, this means grant access to the resource. // DENY_ACCESS, // No access granted to the resource. // GIVE_READONLY, // Give readonly access to the resource. // GIVE_ALLACCESS, // Give full access to the resource. // GIVE_CACHED, // IPC is not required. Target can return a cached handle. // GIVE_FIRST, // TODO(cpu) // SIGNAL_ALARM, // Unusual activity. Generate an alarm. // FAKE_SUCCESS, // Do not call original function. Just return 'success'. // FAKE_ACCESS_DENIED, // Do not call original function. Just return 'denied' // // and do not do IPC. // TERMINATE_PROCESS, // Destroy target process. Do IPC as well. // }; const Actions = { 3 : 'ASK_BROKER', 4 : 'DENY_ACCESS', 5 : 'GIVE_READONLY', 6 : 'GIVE_ALLACCESS', 7 : 'GIVE_CACHED', 8 : 'GIVE_FIRST', 9 : 'SIGNAL_ALARM', 10 : 'FAKE_SUCCESS', 11 : 'FACE_ACCESS_DENIED', 12 : 'TERMINATE_PROCESS' }; // https://dxr.mozilla.org/mozilla-central/source/security/sandbox/chromium/sandbox/win/src/internal_types.h#19 // enum ArgType { // INVALID_TYPE = 0, // WCHAR_TYPE, // UINT32_TYPE, // UNISTR_TYPE, // VOIDPTR_TYPE, // INPTR_TYPE, // INOUTPTR_TYPE, // LAST_TYPE // }; const ArgTypes = { 0 : 'INVALID_TYPE', 1 : 'WCHAR_TYPE', 2 : 'UINT32_TYPE', 3 : 'UNISTR_TYPE', 4 : 'VOIDPTR_TYPE', 5 : 'INPTR_TYPE', 6 : 'INOUTPTR_TYPE', 7 : 'LAST_TYPE' }; // Options that apply to every opcode. They are specified when creating // each opcode using OpcodeFactory::MakeOpXXXXX() family of functions // Do nothing special. const kPolNone = 0; // Convert EVAL_TRUE into EVAL_FALSE and vice-versa. This allows to express // negated conditions such as if ( a && !b). const kPolNegateEval = 1; // Zero the MatchContext context structure. This happens after the opcode // is evaluated. const kPolClearContext = 2; // Use OR when evaluating this set of opcodes. The policy evaluator by default // uses AND when evaluating. Very helpful when // used with kPolNegateEval. For example if you have a condition best expressed // as if(! (a && b && c)), the use of this flags allows it to be expressed as // if ((!a) || (!b) || (!c)). const kPolUseOREval = 4; // https://dxr.mozilla.org/mozilla-central/source/security/sandbox/chromium/sandbox/win/src/policy_params.h#36 const ParameterNames = { 'OpenFile' : [ 'NAME', 'BROKER', 'ACCESS', 'DISPOSITION', 'OPTIONS' ], }; // // Code. // function DisassPolicyBuffer(PolicyBufferAddress, PolicyType) { let Ptr = PolicyBufferAddress; const PolicyBufferOpcodeCount = ReadQword(Ptr); Ptr += 8; for(let Idx = 0; Idx < PolicyBufferOpcodeCount; ++Idx) { // // Save off the current pointer as it is useful to compute // where the stored string is in memory for the OP_WSTRING_MATCH // opcode. // const OpcodePtr = Ptr; // // Unpack the opcode structure. // const OpcodeId = ReadDword(Ptr); Ptr += 4; const SelectedParameter = ReadShort(Ptr); Ptr += 2; const Options = ReadShort(Ptr); Ptr += 2; const Parameters = []; for(let InnerIdx = 0; InnerIdx < 4; ++InnerIdx) { Parameters.push(ReadQword(Ptr)); Ptr += 8; } // // Once we dumped the opcode, let's prettify its parameters. // const Operands = []; let FirstOperand = 'Param' + SelectedParameter; if(ParameterNames[PolicyType] != undefined) { FirstOperand = PolicyType + '::' + ParameterNames[PolicyType][SelectedParameter]; } Operands.push(FirstOperand); if(OpcodeId == OP_ALWAYS_TRUE || OpcodeId == OP_ALWAYS_FALSE) { } else if(OpcodeId == OP_NUMBER_MATCH) { const ArgType = ArgTypes[Parameters[1].asNumber()]; Operands.push(ArgType + '(' + Hex(Parameters[0]) + ')'); } else if(OpcodeId == OP_NUMBER_MATCH_RANGE) { Operands.push('LowerBound(' + Hex(Parameters[0]) + ')'); Operands.push('UpperBound(' + Hex(Parameters[1]) + ')'); } else if(OpcodeId == OP_NUMBER_AND_MATCH) { Operands.push(Hex(Parameters[0])); }else if(OpcodeId == OP_WSTRING_MATCH) { const Displacement = Parameters[0]; const StringAddress = OpcodePtr.add(Displacement); Operands.push('"' + ReadWstring(StringAddress) + '"'); Operands.push('Length(' + Hex(Parameters[1]) + ')'); Operands.push('Offset(' + Hex(Parameters[2]) + ')'); const MatchingOption = Parameters[3].asNumber(); Operands.push(MatchingOptions[MatchingOption]); } else if(OpcodeId == OP_ACTION) { // // The OP_ACTION is the only opcode that does not need a selected // parameter. // const Action = Actions[Parameters[0].asNumber()]; Operands[0] = Action; } // // Display the opcode and its operands. // const OpcodeIdStr = Opcodes[OpcodeId]; if(Options.bitwiseAnd(kPolNegateEval).compareTo(0) != 0) { Logln('!' + OpcodeIdStr + '<' + Operands.join(', ') + '>'); } else { Logln(OpcodeIdStr + '<' + Operands.join(', ') + '>'); } if(OpcodeId == OP_ACTION) { Logln(''); } } } function initializeScript() { return [ new host.apiVersionSupport(1, 3), new host.functionAlias( DisassPolicyBuffer, 'disasspolicy' ) ]; } ================================================ FILE: sm/README.md ================================================ # sm.js `sm.js` is a [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extension for WinDbg that allows to dump both `js::Value` and `JSObject` [Spidermonkey](https://github.com/mozilla/gecko-dev/tree/master/js) objects. It works on crash-dumps, live debugging, and TTD traces. The extension detects automatically if it is running from the [Javascript shell](https://github.com/mozilla/gecko-dev/tree/master/js/src/shell) (in which case `js.exe` is the module hosting the JavaScript engine code) or from Firefox directly (in which case `xul.dll` is the module hosting the JavaScript engine code). Private symbol information for the module hosting the JavaScript engine code is required. It only supports x64 version of spidermonkey. It has been used and tested against spidermonkey during end of 2018 - but should work fine on newer versions assuming core data-structures haven't changed a whole lot (and if they do, it should be fairly easy to adapt anyway). ## Usage Run `.scriptload sm.js` to load the script. You can dump `js::Value` with `!smdump_jsvalue` and `JSObject` with `!smdump_jsobject`. You can insert a software breakpoint in a JIT buffer with `!ion_insertbp` and `!in_nursery` to figure out if an object lives inside the Nursery heap. ## Examples * Dumping the `js::Value` associated to the following JavaScript object `['short', 13.37, new Map([[ 1, 'one' ],[ 2, 'two' ]]), ['loooooooooooooooooooooooooooooong', [0x1337, {doare:'in d4 place'}]], false, null, undefined, true, Math.atan2, Math]`: ```text 0:000> !smdump_jsvalue vp[2].asBits_ 1e5f10024c0: js!js::ArrayObject: Length: 10 1e5f10024c0: js!js::ArrayObject: Capacity: 10 1e5f10024c0: js!js::ArrayObject: Content: ['short', 13.37, new Map(...), ['loooooooooooooooooooooooooooooong', [0x1337, {'doare' : 'in d4 place'}]], false, null, undefined, true, atan2(), Math] @$smdump_jsvalue(vp[2].asBits_) ``` * Setting a breakpoint in code being JIT'd by IonMonkey: ```text 0:008> g Breakpoint 0 hit js!js::jit::CodeGenerator::visitBoundsCheck: 00007ff7`87d9e1a0 4156 push r14 0:000> !ion_insertbp unsigned char 0xcc '' unsigned int64 0x5b @$ion_insertbp() 0:000> g (1a60.f58): Break instruction exception - code 80000003 (first chance) 000003d9`ca67991b cc int 3 0:000> u . l2 000003d9`ca67991b cc int 3 000003d9`ca67991c 3bd8 cmp ebx,eax ``` * Figure out if an object lives in the Nursery heap: ```text 0:008> !in_nursery 0x19767e00df8 Using previously cached JSContext @0x000001fe17318000 0x000001fe1731cde8: js::Nursery ChunkCountLimit: 0x0000000000000010 (16 MB) Capacity: 0x0000000000fffe80 bytes CurrentChunk: 0x0000019767e00000 Position: 0x0000019767e00eb0 Chunks: 00: [0x0000019767e00000 - 0x0000019767efffff] 01: [0x00001fa2aee00000 - 0x00001fa2aeefffff] 02: [0x0000115905000000 - 0x00001159050fffff] 03: [0x00002fc505200000 - 0x00002fc5052fffff] 04: [0x000020d078700000 - 0x000020d0787fffff] 05: [0x0000238217200000 - 0x00002382172fffff] 06: [0x00003ff041f00000 - 0x00003ff041ffffff] 07: [0x00001a5458700000 - 0x00001a54587fffff] ------- 0x19767e00df8 has been found in the js::NurseryChunk @0x19767e00000! 0:008> !in_nursery 0x00001fe174be810 Using previously cached JSContext @0x000001fe17318000 0x000001fe1731cde8: js::Nursery ChunkCountLimit: 0x0000000000000010 (16 MB) Capacity: 0x0000000000fffe80 bytes CurrentChunk: 0x0000019767e00000 Position: 0x0000019767e00eb0 Chunks: 00: [0x0000019767e00000 - 0x0000019767efffff] 01: [0x00001fa2aee00000 - 0x00001fa2aeefffff] 02: [0x0000115905000000 - 0x00001159050fffff] 03: [0x00002fc505200000 - 0x00002fc5052fffff] 04: [0x000020d078700000 - 0x000020d0787fffff] 05: [0x0000238217200000 - 0x00002382172fffff] 06: [0x00003ff041f00000 - 0x00003ff041ffffff] 07: [0x00001a5458700000 - 0x00001a54587fffff] ------- 0x1fe174be810 hasn't been found be in any Nursery js::NurseryChunk. ``` ================================================ FILE: sm/sm.js ================================================ // Axel '0vercl0k' Souchet - 24-June-2018 // // Example: // * from the interpreter: // Math.atan2(['short', 13.37, new Map([[ 1, 'one' ],[ 2, 'two' ]]), ['loooooooooooooooooooooooooooooong', [0x1337, {doare:'in d4 place'}]], false, null, undefined, true, Math.atan2, Math]) // // * from the debugger: // js!js::math_atan2: // 00007ff6`0227e140 56 push rsi // 0:000> !smdump_jsvalue vp[2].asBits_ // 1e5f10024c0: js!js::ArrayObject: Length: 10 // 1e5f10024c0: js!js::ArrayObject: Capacity: 10 // 1e5f10024c0: js!js::ArrayObject: Content: ['short', 13.37, new Map(...), ['loooooooooooooooooooooooooooooong', [0x1337, {'doare' : 'in d4 place'}]], false, null, undefined, true, atan2(), Math] // @$smdump_jsvalue(vp[2].asBits_) // 'use strict'; let Module = null; const logln = p => host.diagnostics.debugLog(p + '\n'); const hex = p => '0x' + p.toString(16).padStart(16, '0'); const JSVAL_TAG_SHIFT = host.Int64(47); const JSVAL_PAYLOAD_MASK = host.Int64(1).bitwiseShiftLeft(JSVAL_TAG_SHIFT).subtract(1); const CLASS_NON_NATIVE = host.Int64(0x40000); const FLAG_DELEGATE = host.Int64(8); const JSVAL_TYPE_DOUBLE = host.Int64(0x1fff0); const JSVAL_TYPE_INT32 = host.Int64(0x1fff1); const JSVAL_TYPE_BOOLEAN = host.Int64(0x1fff2); const JSVAL_TYPE_UNDEFINED = host.Int64(0x1fff3); const JSVAL_TYPE_NULL = host.Int64(0x1fff4); const JSVAL_TYPE_MAGIC = host.Int64(0x1fff5); const JSVAL_TYPE_STRING = host.Int64(0x1fff6); const JSVAL_TYPE_SYMBOL = host.Int64(0x1fff7); const JSVAL_TYPE_OBJECT = host.Int64(0x1fffc); const INLINE_CHARS_BIT = host.Int64(1 << 6); const LATIN1_CHARS_BIT = host.Int64(1 << 9); const JSID_TYPE_MASK = host.Int64(0x7); const JSID_TYPE_STRING = host.Int64(0x0); const JSID_TYPE_INT = host.Int64(0x1); const JSID_TYPE_VOID = host.Int64(0x2); const JSID_TYPE_SYMBOL = host.Int64(0x4); const SLOT_MASK = host.Int64(0xffffff); const FIXED_SLOTS_SHIFT = host.Int64(24); const FunctionConstants = { 0x0001 : 'INTERPRETED', 0x0004 : 'EXTENDED', 0x0008 : 'BOUND_FUN', 0x0010 : 'WASM_OPTIMIZED', 0x0020 : 'HAS_GUESSED_ATOM/HAS_BOUND_FUNCTION_NAME_PREFIX', 0x0040 : 'LAMBDA', 0x0080 : 'SELF_HOSTED', 0x0100 : 'HAS_INFERRED_NAME', 0x0200 : 'INTERPRETED_LAZY', 0x0400 : 'RESOLVED_LENGTH', 0x0800 : 'RESOLVED_NAME', }; const FunctionKindConstants = { 0 : 'NORMAL_KIND', 1 : 'ARROW_KIND', 2 : 'METHOD_KIND', 3 : 'CLASSCONSTRUCTOR_KIND', 4 : 'GETTER_KIND', 5 : 'SETTER_KIND', 6 : 'ASMJS_KIND' }; const Tag2Names = { [JSVAL_TYPE_DOUBLE] : 'Double', [JSVAL_TYPE_INT32] : 'Int32', [JSVAL_TYPE_STRING] : 'String', [JSVAL_TYPE_UNDEFINED] : 'Undefined', [JSVAL_TYPE_BOOLEAN] : 'Boolean', [JSVAL_TYPE_NULL] : 'Null', [JSVAL_TYPE_OBJECT] : 'Object', [JSVAL_TYPE_SYMBOL] : 'Symbol', [JSVAL_TYPE_MAGIC] : 'Magic', }; // // Read a uint64_t integer from Addr. // function read_u64(Addr) { return host.memory.readMemoryValues(Addr, 1, 8)[0]; } // // Mirror the functionality of ::fromElements. // function heapslot_to_objectelements(Addr) { // static ObjectElements* fromElements(HeapSlot* elems) { // return reinterpret_cast(uintptr_t(elems) - sizeof(ObjectElements)); // } const ObjectElementsSize = host.getModuleType(Module, 'js::ObjectElements').size; const ObjectElements = host.createPointerObject( Addr.subtract(ObjectElementsSize), Module, 'js::ObjectElements*' ); return ObjectElements; } // // Is Byte printable? // function printable(Byte) { return Byte >= 0x20 && Byte <= 0x7e; } // // Return a string describing Byte; either a \x41 or its ascii representation // function byte_to_str(Byte) { if(printable(Byte)) { return String.fromCharCode(Byte); } return '\\x' + Byte.toString(16).padStart(2, '0'); } // // Is this jsid an integer? // function jsid_is_int(Propid) { const Bits = Propid.value.asBits; return Bits.bitwiseAnd(JSID_TYPE_MASK).compareTo(JSID_TYPE_INT) == 0; } // // Is this jsid a string? // function jsid_is_string(Propid) { const Bits = Propid.value.asBits; return Bits.bitwiseAnd(JSID_TYPE_MASK).compareTo(JSID_TYPE_STRING) == 0; } // // Retrieve a property from a Shape; returns an actual integer/string based // on the propid_. // function get_property_from_shape(Shape) { // XXX: expose a smdump_jsid const Propid = Shape.propid_; if(jsid_is_int(Propid)) { return Propid.value.asBits.bitwiseShiftRight(1); } if(jsid_is_string(Propid)) { return new __JSString(Propid.value.asBits); } // XXX: todo } function jsvalue_to_instance(Addr) { const JSValue = new __JSValue(Addr); if(!Tag2Names.hasOwnProperty(JSValue.Tag)) { return 'Dunno'; } const Name = Tag2Names[JSValue.Tag]; const Type = Names2Types[Name]; return new Type(JSValue.Payload); } class __JSMagic { constructor(Addr) { this._Addr = Addr; } toString() { return 'magic'; } Logger(Content) { logln(this._Addr.toString(16) + ': JSVAL_TYPE_MAGIC: ' + Content); } Display() { this.Logger(this); } } class __JSArgument { // * ArgumentsObject instances use the following reserved slots: // * // * INITIAL_LENGTH_SLOT // * Stores the initial value of arguments.length, plus a bit indicating // * whether arguments.length and/or arguments[@@iterator] have been // * modified. Use initialLength(), hasOverriddenLength(), and // * hasOverriddenIterator() to access these values. If arguments.length has // * been modified, then the current value of arguments.length is stored in // * another slot associated with a new property. // * DATA_SLOT // * Stores an ArgumentsData*, described above. // * MAYBE_CALL_SLOT // * Stores the CallObject, if the callee has aliased bindings. See // * the ArgumentsData::args comment. // * CALLEE_SLOT // * Stores the initial arguments.callee. This value can be overridden on // * mapped arguments objects, see hasOverriddenCallee. // */ // class ArgumentsObject : public NativeObject { // protected: // static const uint32_t INITIAL_LENGTH_SLOT = 0; // static const uint32_t DATA_SLOT = 1; // static const uint32_t MAYBE_CALL_SLOT = 2; // static const uint32_t CALLEE_SLOT = 3; constructor(Addr) { this._Addr = Addr; this._Obj = host.createPointerObject( Addr, Module, 'js::ArgumentsObject*' ); const INITIAL_LENGTH_SLOT = host.Int64(0); const DATA_SLOT = host.Int64(1); const MAYBE_CALL_SLOT = host.Int64(2); const CALLEE_SLOT = host.Int64(3); const ArgumentsObjectSize = this._Obj.dereference().targetType.size; this._SlotAddress = this._Obj.address.add(ArgumentsObjectSize); const InitialLengthSlot = read_u64(this._SlotAddress.add( INITIAL_LENGTH_SLOT.multiply(8) )); this._InitialLength = new __JSInt32(InitialLengthSlot)._Value; this._Data = read_u64(this._SlotAddress.add( DATA_SLOT.multiply(8) )).bitwiseShiftLeft(1); } toString() { return 'Arguments(..)'; } Logger(Content) { logln(this._Addr.toString(16) + ': js!js::ArgumentsObject: ' + Content); } Display() { this.Logger('InitialLength: ' + this._InitialLength); this.Logger(' Data: ' + hex(this._Data) + ' (js!js::ArgumentsData)'); } } class __JSNull { constructor(Addr) { this._Addr = Addr; } toString() { return 'null'; } Logger(Content) { logln(this._Addr.toString(16) + ': JSVAL_TYPE_NULL: ' + Content); } Display() { this.Logger(this); } } class __JSUndefined { constructor(Addr) { this._Addr = Addr; } toString() { return 'undefined'; } Logger(Content) { logln(this.Addr.toString(16) + ': JSVAL_TYPE_UNDEFINED: ' + Content); } Display() { this.Logger(this); } } class __JSBoolean { constructor(Addr) { this._Addr = Addr; this._Value = Addr.compareTo(1) == 0 ? true : false; } toString() { return this._Value.toString(); } Logger(Content) { logln(this._Addr.toString(16) + ': JSVAL_TYPE_BOOLEAN: ' + Content); } Display() { this.Logger(this); } } class __JSInt32 { constructor(Addr) { this._Addr = Addr; this._Value = Addr.bitwiseAnd(0xffffffff); } toString() { return '0x' + this._Value.toString(16); } Logger(Content) { logln(this._Addr.toString(16) + ': JSVAL_TYPE_INT32: ' + Content); } Display() { this.Logger(this); } } class __JSString { constructor(Addr) { this._Obj = host.createPointerObject( Addr, Module, 'JSString*' ); /* * The Flags Word * * The flags word stores both the string's type and its character encoding. * * If LATIN1_CHARS_BIT is set, the string's characters are stored as Latin1 * instead of TwoByte. This flag can also be set for ropes, if both the * left and right nodes are Latin1. Flattening will result in a Latin1 * string in this case. */ const Flags = this._Obj.d.flags_; const IsLatin1 = Flags.bitwiseAnd(LATIN1_CHARS_BIT).compareTo(0) != 0; const IsInline = Flags.bitwiseAnd(INLINE_CHARS_BIT).compareTo(0) != 0; let Address = null; if(IsInline) { // // inlineStorageLatin1 and inlineStorageTwoByte are in a union and // as a result are at the same address // Address = this._Obj.d.inlineStorageLatin1.address; } else { // // Same as above with nonInlineStorageLatin1 and nonInlineStorageTwoByte. // Address = this._Obj.d.s.u2.nonInlineCharsLatin1.address; } let Length = Flags.bitwiseShiftRight(32); if(!IsLatin1) { Length *= 2; } this._String = Array.from(host.memory.readMemoryValues( Address, Length, 1 )).map( p => byte_to_str(p) ).join(''); } toString() { return "'" + this._String + "'"; } Logger(Content) { logln(this._Obj.address.toString(16) + ': js!JSString: ' + Content); } Display() { this.Logger(this); } } class __JSValue { constructor(Addr) { this._Addr = Addr; this._Tag = this._Addr.bitwiseShiftRight(JSVAL_TAG_SHIFT); this._IsDouble = this._Tag.compareTo(JSVAL_TYPE_DOUBLE) < 0; this._Payload = this._Addr.bitwiseAnd(JSVAL_PAYLOAD_MASK); } get Payload() { if(this._IsDouble) { return this._Addr; } return this._Payload; } get Tag() { if(this._IsDouble) { return JSVAL_TYPE_DOUBLE; } return this._Tag; } } class __JSArray { constructor(Addr) { this._Obj = host.createPointerObject( Addr, Module, 'js::ArrayObject*' ); // XXX: why doesn't it work? // this.Obj.elements_.value.address this._Content = this._Obj.elements_.address; this._Header = heapslot_to_objectelements(this._Content); // The flags word stores both the flags and the number of shifted elements. // Allow shifting 2047 elements before actually moving the elements. const NumShiftedElementsBits = host.Int64(11); const NumShiftedElementsShift = host.Int64(32).subtract(NumShiftedElementsBits); this._Flags = this._Header.flags; this._NumShifted = this._Flags.bitwiseShiftRight(NumShiftedElementsShift) this._Length = this._Header.length; this._Capacity = this._Header.capacity; this._InitializedLength = this._Header.initializedLength; } toString() { const Max = 10; const Content = []; for(let Idx = 0; Idx < Math.min(Max, this._InitializedLength); ++Idx) { const Addr = this._Content.add(Idx * 8); const JSValue = read_u64(Addr); const Inst = jsvalue_to_instance(JSValue); Content.push(Inst.toString()); } return '[' + Content.join(', ') + (this._Length > Max ? ', ...' : '') + ']'; } Logger(Content) { logln(this._Obj.address.toString(16) + ': js!js::ArrayObject: ' + Content); } Display() { this.Logger(' Length: ' + this._Length); this.Logger(' Capacity: ' + this._Capacity); this.Logger('InitializedLength: ' + this._InitializedLength); this.Logger(' NumShifted: ' + this._NumShifted + ' (flags: ' + hex(this._Flags) + ')'); this.Logger(' Content: ' + this); } } class __JSFunction { constructor(Addr) { this._Obj = host.createPointerObject( Addr, Module, 'JSFunction*' ); this._Atom = this._Obj.atom_.value.address; this._Name = ''; if(this._Atom.compareTo(0) != 0) { this._Name = new __JSString(this._Atom).toString().slice(1, -1); } this._Name += '()'; this._Flags = this._Obj.flags_; } toString() { return this._Name; } get Flags() { const S = []; for(const Key in FunctionConstants) { if(this._Flags.bitwiseAnd(host.parseInt64(Key)).compareTo(0) != 0) { S.push(FunctionConstants[Key]); } } const Kind = (this._Flags >> 13) & 7; S.push(FunctionKindConstants[Kind]); return S.join(' | '); } Logger(Content) { logln(this._Obj.address.toString(16) + ': js!JSFunction: ' + Content); } Display() { this.Logger(this); this.Logger('Flags: ' + this.Flags); } } class __JSSymbol { constructor(Addr) { this._Obj = host.createPointerObject( Addr, Module, 'js::Symbol*' ); } toString() { const Desc = new __JSString(this._Obj.description_.address); return 'Symbol(' + Desc + ')'; } Logger(Content) { logln(this.Obj_.address.toString(16) + ': js!js::Symbol: ' + Content); } Display() { this.Logger(this); } } class __JSArrayBuffer { constructor(Addr) { this._Obj = host.createPointerObject( Addr, Module, 'js::ArrayBufferObject*' ); const ArrayBufferObjectSize = host.getModuleType(Module, 'js::ArrayBufferObject').size; // static const uint8_t DATA_SLOT = 0; // static const uint8_t BYTE_LENGTH_SLOT = 1; const ByteLengthSlotAddr = Addr.add(ArrayBufferObjectSize).add(1 * 8); const ByteLengthSlot = read_u64(ByteLengthSlotAddr); this._ByteLength = new __JSInt32(ByteLengthSlot)._Value; // static const uint8_t FIRST_VIEW_SLOT = 2; // static const uint8_t FLAGS_SLOT = 3; const FlagsAddr = Addr.add(ArrayBufferObjectSize).add(3 * 8); const FlagsSlot = read_u64(FlagsAddr); this._Flags = new __JSInt32(FlagsSlot)._Value; } get Flags() { // enum BufferKind { // PLAIN = 0, // malloced or inline data // WASM = 1, // MAPPED = 2, // EXTERNAL = 3, // KIND_MASK = 0x3 // }; // enum ArrayBufferFlags { // // The flags also store the BufferKind // BUFFER_KIND_MASK = BufferKind::KIND_MASK, // DETACHED = 0x4, // // The dataPointer() is owned by this buffer and should be released // // when no longer in use. Releasing the pointer may be done by freeing, // // invoking a dereference callback function, or unmapping, as // // determined by the buffer's other flags. // // // // Array buffers which do not own their data include buffers that // // allocate their data inline, and buffers that are created lazily for // // typed objects with inline storage, in which case the buffer points // // directly to the typed object's storage. // OWNS_DATA = 0x8, // // This array buffer was created lazily for a typed object with inline // // data. This implies both that the typed object owns the buffer's data // // and that the list of views sharing this buffer's data might be // // incomplete. Any missing views will be typed objects. // FOR_INLINE_TYPED_OBJECT = 0x10, // // Views of this buffer might include typed objects. // TYPED_OBJECT_VIEWS = 0x20, // // This PLAIN or WASM buffer has been prepared for asm.js and cannot // // henceforth be transferred/detached. // FOR_ASMJS = 0x40 // }; const BufferKinds = { 0 : 'PLAIN', 1 : 'WASM', 2 : 'MAPPED', 3 : 'EXTERNAL' }; const BufferKind = BufferKinds[this._Flags.bitwiseAnd(3).asNumber()]; const ArrayBufferFlags = [ 'BufferKind(' + BufferKind + ')' ]; const ArrayBufferFlagsConstants = { [0x04] : 'DETACHED', [0x08] : 'OWNS_DATA', [0x10] : 'FOR_INLINE_TYPED_OBJECT', [0x20] : 'TYPED_OBJECT_VIEWS', [0x40] : 'FOR_ASMJS' }; for(const Key in ArrayBufferFlagsConstants) { if(this._Flags.bitwiseAnd(host.parseInt64(Key)).compareTo(0) != 0) { ArrayBufferFlags.push(ArrayBufferFlagsConstants[Key]); } } return ArrayBufferFlags.join(' | '); } get ByteLength() { return this._ByteLength; } toString() { return 'ArrayBuffer({ByteLength:' + this._ByteLength + ', ...})'; } Logger(Content) { logln(this._Obj.address.toString(16) + ': js!js::ArrayBufferObject: ' + Content); } Display() { this.Logger('ByteLength: ' + this.ByteLength); this.Logger(' Flags: ' + this.Flags); this.Logger(' Content: ' + this); } } class __JSTypedArray { constructor(Addr) { this._Obj = host.createPointerObject( Addr, Module, 'js::TypedArrayObject*' ); const Group = this._Obj.group_.value; this._TypeName = host.memory.readString(Group.clasp_.name) const Sizes = { 'Float64Array' : 8, 'Float32Array' : 4, 'Uint32Array' : 4, 'Int32Aray' : 4, 'Uint16Array' : 2, 'Int16Array' : 2, 'Uint8Array' : 1, 'Uint8ClampedArray' : 1, 'Int8Array' : 1 }; this._ElementSize = Sizes[this._TypeName]; const TypedArrayObjectSize = host.getModuleType(Module, 'js::TypedArrayObject').size; // static const size_t BUFFER_SLOT = 0; // static const size_t LENGTH_SLOT = 1; const LengthSlotAddr = Addr.add(TypedArrayObjectSize).add(1 * 8); const LengthSlot = read_u64(LengthSlotAddr); this._Length = new __JSInt32(LengthSlot)._Value; this._ByteLength = this._Length * this._ElementSize; // static const size_t BYTEOFFSET_SLOT = 2; const ByteOffsetSlotAddr = Addr.add(TypedArrayObjectSize).add(2 * 8); const ByteOffsetSlot = read_u64(ByteOffsetSlotAddr); this._ByteOffset = new __JSInt32(ByteOffsetSlot)._Value; // static const size_t RESERVED_SLOTS = 3; } get Type() { return this._TypeName; } get ByteOffset() { return this._ByteOffset; } get ByteLength() { return this._ByteLength; } get Length() { return this._Length; } toString() { return this._TypeName + '({Length:' + this._Length + ', ...})'; } Logger(Content) { logln(this._Obj.address.toString(16) + ': js!js::TypedArrayObject: ' + Content); } Display() { this.Logger(' Type: ' + this.Type); this.Logger(' Length: ' + this.Length); this.Logger('ByteLength: ' + this.ByteLength); this.Logger('ByteOffset: ' + this.ByteOffset); this.Logger(' Content: ' + this); } } class __JSMap { constructor(Addr) { this._Addr = Addr; } // XXX: TODO toString() { return 'new Map(...)'; } Logger(Content) { logln(this._Addr.toString(16) + ': js!js::MapObject: ' + Content); } Display() { this.Logger('Content: ' + this); } } class __JSDouble { constructor(Addr) { this._Addr = Addr; } toString() { const U32 = new Uint32Array([ this._Addr.getLowPart(), this._Addr.getHighPart() ]); const F64 = new Float64Array(U32.buffer); return F64[0]; } Logger(Content) { logln(this._Addr.toString(16) + ': JSVAL_TYPE_DOUBLE: ' + Content); } Display() { this.Logger(this); } } const Names2Types = { 'Function' : __JSFunction, 'Array' : __JSArray, 'ArrayBuffer' : __JSArrayBuffer, 'Map' : __JSMap, 'Int32' : __JSInt32, 'String' : __JSString, 'Boolean' : __JSBoolean, 'Null' : __JSNull, 'Undefined' : __JSUndefined, 'Symbol' : __JSSymbol, 'Double' : __JSDouble, 'Magic' : __JSMagic, 'Arguments' : __JSArgument, 'Float64Array' : __JSTypedArray, 'Float32Array' : __JSTypedArray, 'Uint32Array' : __JSTypedArray, 'Int32Array' : __JSTypedArray, 'Uint16Array' : __JSTypedArray, 'Int16Array' : __JSTypedArray, 'Uint8Array' : __JSTypedArray, 'Uint8ClampedArray' : __JSTypedArray, 'Int8Array' : __JSTypedArray }; class __JSObject { /* JSObject.h * A JavaScript object. * * This is the base class for all objects exposed to JS script (as well as some * objects that are only accessed indirectly). Subclasses add additional fields * and execution semantics. The runtime class of an arbitrary JSObject is * identified by JSObject::getClass(). * * The members common to all objects are as follows: * * - The |group_| member stores the group of the object, which contains its * prototype object, its class and the possible types of its properties. * * - The |shapeOrExpando_| member points to (an optional) guard object that JIT * may use to optimize. The pointed-to object dictates the constraints * imposed on the JSObject: * nullptr * - Safe value if this field is not needed. * js::Shape * - All objects that might point |shapeOrExpando_| to a js::Shape * must follow the rules specified on js::ShapedObject. * JSObject * - Implies nothing about the current object or target object. Either * of which may mutate in place. Store a JSObject* only to save * space, not to guard on. */ constructor(Addr) { this._Addr = Addr; this._Obj = host.createPointerObject( this._Addr, Module, 'JSObject*' ); this._Properties = []; const Group = this._Obj.group_.value; this._ClassName = host.memory.readString(Group.clasp_.name); const NonNative = Group.clasp_.flags.bitwiseAnd(CLASS_NON_NATIVE).compareTo(0) != 0; if(NonNative) { return; } const Shape = host.createPointerObject( this._Obj.shapeOrExpando_.address, Module, 'js::Shape*' ); const NativeObject = host.createPointerObject(Addr, Module, 'js::NativeObject*'); if(this._ClassName == 'Array') { // // Optimization for 'length' property if 'Array' cf // js::ArrayObject::length / js::GetLengthProperty // const ObjectElements = heapslot_to_objectelements(NativeObject.elements_.address); this._Properties.push('length : ' + ObjectElements.length); return; } // // Walk the list of Shapes and get the property names // const Properties = {}; let CurrentShape = Shape; while(CurrentShape.parent.value.address.compareTo(0) != 0) { const SlotIdx = CurrentShape.immutableFlags.bitwiseAnd(SLOT_MASK).asNumber(); Properties[SlotIdx] = get_property_from_shape(CurrentShape); CurrentShape = CurrentShape.parent.value; } // // Walk the slots to get the values now (check NativeGetPropertyInline/GetExistingProperty) // const NativeObjectTypeSize = host.getModuleType(Module, 'js::NativeObject').size; const NativeObjectElements = NativeObject.address.add(NativeObjectTypeSize); const NativeObjectSlots = NativeObject.slots_.address; const Max = Shape.immutableFlags.bitwiseShiftRight(FIXED_SLOTS_SHIFT).asNumber(); for(let Idx = 0; Idx < Object.keys(Properties).length; Idx++) { // // Check out NativeObject::getSlot() // const PropertyName = Properties[Idx]; let PropertyValue = undefined; let ElementAddr = undefined; if(Idx < Max) { ElementAddr = NativeObjectElements.add(Idx * 8); } else { ElementAddr = NativeObjectSlots.add((Idx - Max) * 8); } const JSValue = read_u64(ElementAddr); PropertyValue = jsvalue_to_instance(JSValue); this._Properties.push(PropertyName + ' : ' + PropertyValue); } } get Properties() { return this._Properties; } get ClassName() { return this._ClassName; } toString() { if(this._ClassName != 'Object' && Names2Types.hasOwnProperty(this._ClassName)) { const Type = Names2Types[this._ClassName]; return new Type(this._Addr).toString(); } if(this._ClassName != 'Object') { return this._ClassName; } if(this._Properties != undefined && this._Properties.length > 0) { return '{' + this._Properties.join(', ') + '}'; } if(this._ClassName == 'Object') { return '[Object]'; } return 'Dunno'; } Logger(Content) { logln(this._Addr.toString(16) + ': js!JSObject: ' + Content); } Display() { this.Logger('Content: ' + this); // // If the class name is not Object then it means the toString() method // might already have displayed the properties. // {foo:'bar'} VS Math. // if(this._ClassName != 'Object') { this.Logger('Properties: {' + this._Properties.join(', ') + '}'); } } } Names2Types['Object'] = __JSObject; function smdump_jsobject(Addr, Type = null) { init(); if(Addr.hasOwnProperty('address')) { Addr = Addr.address; } let ClassName; if(Type == 'Object' || Type == null) { const JSObject = new __JSObject(Addr); ClassName = JSObject.ClassName; if(!Names2Types.hasOwnProperty(ClassName)) { JSObject.Display(); } } else { ClassName = Type; } if(Names2Types.hasOwnProperty(ClassName)) { const Inst = new Names2Types[ClassName](Addr); Inst.Display(); } } function smdump_jsvalue(Addr) { init(); if(Addr == undefined) { logln('!smdump_jsvalue '); return; } // // Ensure Addr is an unsigned value. If we don't do this // the shift operations don't behave the way we want them to. // Addr = Addr.bitwiseAnd(host.parseInt64('0xffffffffffffffff')); const JSValue = new __JSValue(Addr); if(!Tag2Names.hasOwnProperty(JSValue.Tag)) { logln('Tag ' + JSValue.Tag.toString(16) + ' not recognized'); return; } const Name = Tag2Names[JSValue.Tag]; return smdump_jsobject(JSValue.Payload, Name); } function init() { if(Module != null) { return; } const Xul = host.currentProcess.Modules.Any( p => p.Name.toLowerCase().endsWith('xul.dll') ); if(Xul) { Module = 'xul.dll'; logln('Detected xul.dll, using it as js module.'); return; } Module = 'js.exe'; } function ion_insertbp() { // XXX: Having the current frame would be better.. but not sure if // this is something possible? const CurrentThread = host.currentThread; const LowestFrame = CurrentThread.Stack.Frames[0]; const LocalVariables = LowestFrame.LocalVariables; const CodeGenerator = LocalVariables.this; if(CodeGenerator === undefined || CodeGenerator.targetType.toString() != 'js::jit::CodeGenerator *') { logln('The script expects `this` to be a js::jit::CodeGenerator in the lowest frame.'); return; } const JITBuffer = CodeGenerator.masm.masm.m_formatter.m_buffer.m_buffer; // XXX: So sounds like I can't do JITBuffer.mBegin[JITBuffer.mLength] = 0xcc, // so here I am writing ugly things :x const BreakpointAddress = JITBuffer.mBegin.address.add(JITBuffer.mLength); logln(`JIT buffer is at ${hex(JITBuffer.mBegin.address)}`); logln(`Writing breakpoint at ${hex(BreakpointAddress)}`); host.evaluateExpression(`*(char*)${hex(BreakpointAddress)} = 0xcc`); JITBuffer.mLength += 1; } let Context = undefined; function dump_nursery_stats(Nursery) { logln(`${hex(Nursery.address)}: js::Nursery`); const ChunkCountLimit = Nursery.chunkCountLimit_; const NurseryChunk = host.createPointerObject( 0, Module, 'js::NurseryChunk*' ); const ChunkSize = NurseryChunk.dereference().targetType.size; const MaxSize = ChunkCountLimit.multiply(ChunkSize); logln(` ChunkCountLimit: ${hex(ChunkCountLimit)} (${MaxSize / 1024 / 1024} MB)`); const Capacity = Nursery.capacity_; logln(` Capacity: ${hex(Capacity)} bytes`) const CurrentChunk = Nursery.currentStartPosition_; logln(` CurrentChunk: ${hex(CurrentChunk)}`); const Position = Nursery.position_; logln(` Position: ${hex(Position)}`); logln(' Chunks:'); const Chunks = Nursery.chunks_; for(let Idx = 0; Idx < Chunks.mLength; Idx++) { const Chunk = Chunks.mBegin[Idx]; const StartAddress = Chunk.data.address; const EndAddress = StartAddress.add(ChunkSize).subtract(1); const PaddedIdx = Idx.toString().padStart(2, '0'); logln(` ${PaddedIdx}: [${hex(StartAddress)} - ${hex(EndAddress)}]`); } } function in_nursery(Addr) { init(); if(Addr == undefined) { logln('!in_nursery '); return; } // // Find 'cx' the JSContext somewhere.. // if(Context == undefined) { const CurrentThread = host.currentThread; for(const Frame of CurrentThread.Stack.Frames) { const Parameters = Frame.Parameters; if(Parameters == undefined) { continue; } Context = Parameters.cx; if(Context == undefined || Context.targetType.toString() != 'JSContext *') { continue; } logln(`Caching JSContext @${hex(Context.address)} for next times.`); break; } } else { logln(`Using previously cached JSContext @${hex(Context.address)}`); } if(Context == undefined) { logln('Could not locate a JSContext in this call-stack.'); return; } // // Get the Nursery. // const Nursery = Context.runtime_.value.gc.nursery_.value; const NurseryChunk = host.createPointerObject( 0, Module, 'js::NurseryChunk*' ); const ChunkUseableSize = NurseryChunk.data.targetType.size; // // Dump some stats about the Nursery. // dump_nursery_stats(Nursery); logln('-------'); // // Iterate through the chunk regions. // const Chunks = Nursery.chunks_; let FoundChunk = undefined; for(let Idx = 0; Idx < Chunks.mLength; Idx++) { const Chunk = Chunks.mBegin[Idx]; const StartAddress = Chunk.data.address; const EndAddress = StartAddress.add(ChunkUseableSize); if(Addr.compareTo(StartAddress) >= 0 && Addr.compareTo(EndAddress) < 0) { FoundChunk = Chunk; break; } } if(FoundChunk != undefined) { logln(`${hex(Addr)} has been found in the js::NurseryChunk @${hex(FoundChunk.data.address)}!`); return; } logln(`${hex(Addr)} hasn't been found be in any Nursery js::NurseryChunk.`); } function initializeScript() { return [ new host.apiVersionSupport(1, 3), new host.functionAlias( smdump_jsvalue, 'smdump_jsvalue' ), new host.functionAlias( smdump_jsobject, 'smdump_jsobject' ), new host.functionAlias( ion_insertbp, 'ion_insertbp' ), new host.functionAlias( in_nursery, 'in_nursery' ) ]; } ================================================ FILE: telescope/README.md ================================================ # telescope.js `telescope.js` is a [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extension for WinDbg that mirrors the `dereference`/`telescope` command from [GEF](https://github.com/hugsy/gef). It works on crash-dumps, live debugging, and TTD traces. Both for user and kernel-mode. Idea from [@\_\_awe](https://twitter.com/__awe). ## Usage Run `.scriptload telescope.js` to load the script. You can invoke the telescope feature with `!telescope ` or programatically via `dx @$createchain()`. ## Examples * From an x64 TTD execution trace: ```text 0:000> !telescope @rsp 0x0000005be1ffcec0|+0x0000: 0xe1000205e1ffdd48 (Unknown) 0x0000005be1ffcec8|+0x0008: 0x00007ff700000006 (Unknown) 0x0000005be1ffced0|+0x0010: 0x000001fce5928840 (VirtualAlloced) -> 0x0000005be1ffd0b8 (Stack) -> 0x000001fce5928840 (VirtualAlloced) [...] 0x0000005be1ffced8|+0x0018: 0x0000005be1ffdb68 (Stack) -> 0x000001fce5928840 (VirtualAlloced) -> 0x0000005be1ffd0b8 (Stack) -> 0x000001fce5928840 (VirtualAlloced) [...] 0x0000005be1ffcee0|+0x0020: 0x000001fce634afa0 (VirtualAlloced) -> 0x0000000800000b50 (Unknown) 0x0000005be1ffcee8|+0x0028: 0x00004a54b4bb11e0 (Unknown) 0x0000005be1ffcef0|+0x0030: 0x0000000000000008 (Unknown) 0x0000005be1ffcef8|+0x0038: 0x0000000000000000 (Unknown) 0x0000005be1ffcf00|+0x0040: 0x0000005be1ffdbc8 (Stack) -> 0x000001fce6cb3eb8 (VirtualAlloced) -> 0x00007ff77704e920 (js.exe (.rdata)) -> 0x00007ff776755aa0 (js.exe (.text)) -> mov rax,qword ptr [rcx-18h] ; test byte ptr [rax+23h],2 0x0000005be1ffcf08|+0x0048: 0x00007ff7766b4546 (js.exe (.text)) -> test rax,rax ; je js!mozilla::Vector::growStorageBy+0x395 (00007ff7`766b4805) @$telescope(@rsp) ``` * Accessing the chain programatically via `createchain`: ```text 0:000> dx @$createchain(0x0000005be1ffcf08) @$createchain(0x0000005be1ffcf08) : 0x00007ff7766b4546 (js.exe (.text)) -> test rax,rax ; je js!mozilla::Vector::growStorageBy+0x395 (00007ff7`766b4805) [0x0] : 0x00007ff7766b4546 (js.exe (.text)) [0x1] : test rax,rax ; je js!mozilla::Vector::growStorageBy+0x395 (00007ff7`766b4805) 0:000> dx -r1 @$createchain(0x0000005be1ffcf08)[0] @$createchain(0x0000005be1ffcf08)[0] : 0x00007ff7766b4546 (js.exe (.text)) Addr : 0x5be1ffcf08 Value : 0x7ff7766b4546 AddrRegion : Stack rw- ValueRegion : Image C:\work\codes\blazefox\js-release\js.exe (.text) r-x Name : js.exe (.text) Last : false 0:000> dx -r1 @$createchain(0x0000005be1ffcf08)[1] @$createchain(0x0000005be1ffcf08)[1] : test rax,rax ; je js!mozilla::Vector::growStorageBy+0x395 (00007ff7`766b4805) Addr : 0x7ff7766b4546 Value : 0x2b6840fc08548 AddrRegion : Image C:\work\codes\blazefox\js-release\js.exe (.text) r-x Name : Unknown Last : true ``` * From an x86 live-session: ```text 0:001> !telescope @esp 0x00d7ff44|+0x0000: 0x77dcb3a9 (ntdll.dll (.text)) -> jmp ntdll!DbgUiRemoteBreakin+0x42 (77dcb3b2) ; xor eax,eax 0x00d7ff48|+0x0004: 0x1911c0a3 (Unknown) 0x00d7ff4c|+0x0008: 0x77dcb370 (ntdll.dll (.text)) -> push 8 ; push offset ntdll!QueryRegistryValue+0x13d2 (77e29538) 0x00d7ff50|+0x000c: 0x77dcb370 (ntdll.dll (.text)) -> push 8 ; push offset ntdll!QueryRegistryValue+0x13d2 (77e29538) 0x00d7ff54|+0x0010: 0x00000000 (Unknown) 0x00d7ff58|+0x0014: 0x00d7ff48 (Stack) -> 0x1911c0a3 (Unknown) 0x00d7ff5c|+0x0018: 0x00000000 (Unknown) 0x00d7ff60|+0x001c: 0x00d7ffcc (Stack) -> 0x00d7ffe4 (Stack) -> 0xffffffff (Unknown) 0x00d7ff64|+0x0020: 0x77d986d0 (ntdll.dll (.text)) -> mov edi,edi ; push ebp 0x00d7ff68|+0x0024: 0x6e24aaeb (Unknown) @$telescope(@esp) ``` * From an x64 kernel live-session ``` kd> !telescope 0xfffff8000d2dca78 0xfffff8000d2dca78|+0x0000: 0x0000000000000000 (Unknown) 0xfffff8000d2dca80|+0x0008: 0x0000000000000000 (Unknown) 0xfffff8000d2dca88|+0x0010: 0x0000000000000000 (Unknown) 0xfffff8000d2dca90|+0x0018: 0xfffff8000d03e030 (Image ntkrnlmp.exe (.text)) -> sub rsp,28h ; and qword ptr [rsp+28h],0 0xfffff8000d2dca98|+0x0020: 0x0000000000000000 (Unknown) 0xfffff8000d2dcaa0|+0x0028: 0x0000000000000000 (Unknown) 0xfffff8000d2dcaa8|+0x0030: 0xfffff8000d2d9e48 (Image ntkrnlmp.exe (CACHEALI)) -> 0xfffff8000d2dcaa8 (Image ntkrnlmp.exe (CACHEALI)) [...] 0xfffff8000d2dcab0|+0x0038: 0xfffff8000d2d9e48 (Image ntkrnlmp.exe (CACHEALI)) -> 0xfffff8000d2dcaa8 (Image ntkrnlmp.exe (CACHEALI)) -> 0xfffff8000d2d9e48 (Image ntkrnlmp.exe (CACHEALI)) [...] 0xfffff8000d2dcab8|+0x0040: 0x0000000000000000 (Unknown) 0xfffff8000d2dcac0|+0x0048: 0x0000000000000000 (Unknown) @$telescope(0xfffff8000d2dca78) ``` ================================================ FILE: telescope/telescope.js ================================================ // Axel '0vercl0k' Souchet - 7th December 2018 'use strict'; const log = host.diagnostics.debugLog; const logln = p => host.diagnostics.debugLog(p + '\n'); // // Config variables. // // This is the number of lines the !telescope command displays. const DefaultNumberOfLines = 10; // This is the number of instructions to disassemble when a code pointer is encountered. const DefaultNumberOfInstructions = 3; // This is the maximum number of characters displayed for strings in the !telescope output. const DefaultMaxStringLength = 15; // // Utility functions. // function ReadU64(Addr) { let Value = null; try { Value = host.memory.readMemoryValues( Addr, 1, 8 )[0]; } catch (e) { } return Value; } function ReadU32(Addr) { let Value = null; try { Value = host.memory.readMemoryValues( Addr, 1, 4 )[0]; } catch (e) { } return Value; } function ReadU16(Addr) { let Value = null; try { Value = host.memory.readMemoryValues( Addr, 1, 2 )[0]; } catch (e) { } return Value; } function ReadString(Addr, MaxLength) { let Value = null; try { Value = host.memory.readString(Addr); } catch (e) { return null; } if (Value.length > MaxLength) { return Value.substr(0, MaxLength); } return Value; } function ReadWideString(Addr) { let Value = null; try { Value = host.memory.readWideString(Addr); } catch (e) { } return Value; } function Disassemble(Addr) { const Code = host.namespace.Debugger.Utility.Code; const Disassembler = Code.CreateDisassembler( PointerSize == 8 ? 'X64' : 'X86' ); const Instrs = Array.from(Disassembler.DisassembleInstructions(Addr).Take( DefaultNumberOfInstructions )); return Instrs.map( // // Clean up the assembly. // Turn the below: // 'mov rbx,qword ptr [00007FF8D3525660h] ; test rbx,rbx ; je 00007FF8D34FC2EB' // Into: // 'mov rbx,qword ptr [00007FF8D3525660h] ; test rbx,rbx ; je 00007FF8D34FC2EB' // p => p.toString().replace(/[ ]+/g, ' ') ).join(' ; '); } function FormatU64(Addr) { return '0x' + Addr.toString(16).padStart(16, '0'); } function FormatU32(Addr) { return '0x' + Addr.toString(16).padStart(8, '0'); } function FormatString(Str) { if (Str.length > DefaultMaxStringLength) { return Str.substr(0, DefaultMaxStringLength) + '...' } return Str; } function BitSet(Value, Bit) { return Value.bitwiseAnd(Bit).compareTo(0) != 0; } // // Initialization / global stuff. // let Initialized = false; let ReadPtr = null; let PointerSize = null; let FormatPtr = null; let IsTTD = false; let IsUser = false; let IsKernel = false; let VaSpace = []; function* SectionHeaders(BaseAddress) { if (IsKernel && ReadU32(BaseAddress) == null) { // // If we can't read the module, then..bail :(. // XXX: Fix this? Session space? Paged out? // logln('Cannot read ' + BaseAddress.toString(16) + ', skipping.'); return; } // 0:000> dt _IMAGE_DOS_HEADER e_lfanew // +0x03c e_lfanew : Int4B const NtHeaders = BaseAddress.add(ReadU32(BaseAddress.add(0x3c))); // 0:000> dt _IMAGE_NT_HEADERS64 FileHeader // +0x004 FileHeader : _IMAGE_FILE_HEADER // 0:000> dt _IMAGE_FILE_HEADER NumberOfSections SizeOfOptionalHeader // +0x002 NumberOfSections : Uint2B // +0x010 SizeOfOptionalHeader : Uint2B const NumberOfSections = ReadU16(NtHeaders.add(0x4 + 0x2)); const SizeOfOptionalHeader = ReadU16(NtHeaders.add(0x4 + 0x10)); // 0:000> dt _IMAGE_NT_HEADERS64 OptionalHeader // +0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER64 const OptionalHeader = NtHeaders.add(0x18); const SectionHeaders = OptionalHeader.add(SizeOfOptionalHeader); // 0:000> ?? sizeof(_IMAGE_SECTION_HEADER) // unsigned int64 0x28 const SizeofSectionHeader = 0x28; for (let Idx = 0; Idx < NumberOfSections; Idx++) { const SectionHeader = SectionHeaders.add( Idx.multiply(SizeofSectionHeader) ); // 0:000> dt _IMAGE_SECTION_HEADER Name // +0x000 Name : [8] UChar const Name = ReadString(SectionHeader, 8); // 0:000> dt _IMAGE_SECTION_HEADER VirtualAddress // +0x00c VirtualAddress : Uint4B const Address = BaseAddress.add( ReadU32(SectionHeader.add(0xc)) ); // 0:000> dt _IMAGE_SECTION_HEADER SizeOfRawData // +0x08 Misc : Uint4B // XXX: Take care of alignment? const VirtualSize = ReadU32(SectionHeader.add(0x08)); // 0:000> dt _IMAGE_SECTION_HEADER Characteristics // +0x024 Characteristics : Uint4B const Characteristics = ReadU32(SectionHeader.add(0x24)); const Properties = [ '-', '-', '-' ]; // The section can be read. const IMAGE_SCN_MEM_READ = host.Int64(0x40000000); if (BitSet(Characteristics, IMAGE_SCN_MEM_READ)) { Properties[0] = 'r'; } if (IsKernel) { const IMAGE_SCN_MEM_DISCARDABLE = host.Int64(0x2000000); if (BitSet(Characteristics, IMAGE_SCN_MEM_DISCARDABLE)) { Properties[0] = '-'; } } // The section can be written to. const IMAGE_SCN_MEM_WRITE = host.Int64(0x80000000); if (Characteristics.bitwiseAnd(IMAGE_SCN_MEM_WRITE).compareTo(0) != 0) { Properties[1] = 'w'; } // The section can be executed as code. const IMAGE_SCN_MEM_EXECUTE = host.Int64(0x20000000); if (Characteristics.bitwiseAnd(IMAGE_SCN_MEM_EXECUTE).compareTo(0) != 0) { Properties[2] = 'x'; } yield new _Region( Address, VirtualSize, Name, Properties.join('') ); } } function HandleTTD() { const CurrentSession = host.currentSession; // // Grab addressable chunks. // logln('Populating the VA space with TTD.Data.Heap..'); const CurrentThread = host.currentThread; const Position = CurrentThread.TTD.Position; const Chunks = CurrentSession.TTD.Data.Heap().Where( p => p.TimeStart.compareTo(Position) < 0 && p.Action == 'Alloc' ); for (const Chunk of Chunks) { VaSpace.push(new _Region( Chunk.Address, Chunk.Size, 'Heap', 'rw-' )); } // // Grab virtual allocated memory regions. // logln('Populating the VA space with VirtualAllocated regions..'); const VirtualAllocs = CurrentSession.TTD.Calls( 'kernelbase!VirtualAlloc' ).Where( p => p.TimeStart.compareTo(Position) < 0 ); for (const VirtualAlloc of VirtualAllocs) { VaSpace.push(new _Region( VirtualAlloc.ReturnValue, VirtualAlloc.Parameters[1], 'VirtualAlloced', // XXX: parse access 'rw-' )); } // // Grab mapped view regions. // logln('Populating the VA space with MappedViewOfFile regions..'); const MapViewOfFiles = CurrentSession.TTD.Calls( 'kernelbase!MapViewOfFile' ).Where( p => p.TimeStart.compareTo(Position) < 0 ); for (const MapViewOfFile of MapViewOfFiles) { VaSpace.push(new _Region( MapViewOfFile.ReturnValue, host.Int64(0x1000), 'MappedView', // XXX: parse access 'rw-' )); } } function HandleUser() { // // Enumerate the modules. // logln('Populating the VA space with modules..'); const CurrentProcess = host.currentProcess; for (const Module of CurrentProcess.Modules) { // // Iterate over the section headers of the module. // for (const Section of SectionHeaders(Module.BaseAddress)) { VaSpace.push(new _Region( Section.BaseAddress, Section.Size, 'Image ' + Module.Name + ' (' + Section.Name + ')', Section.Properties )); } // // Add a catch all in case a pointer points inside the PE but not // inside any sections (example of this is the PE header). // VaSpace.push(new _Region( Module.BaseAddress, Module.Size, 'Image ' + Module.Name, 'r--' )); } // // Enumerates the TEBs and the stacks. // logln('Populating the VA space with TEBs & thread stacks..'); for (const Thread of CurrentProcess.Threads) { const Teb = Thread.Environment.EnvironmentBlock; // // TEB! // // In the case where you have broken `ntdll` symbols, you might not have // the definition of the `_TEB` structure. In this case, the structured // `Teb` object above is undefined (like in issues #2). So we try to be resilient // against that in the below. // if (Teb == undefined) { const General = host.namespace.Debugger.State.PseudoRegisters.General; VaSpace.push(new _Region( General.teb.address, host.Int64(0x100), 'Teb of ' + Thread.Id.toString(16), 'rw-' )); continue; } VaSpace.push(new _Region( Teb.address, Teb.targetType.size, 'Teb of ' + Thread.Id.toString(16), 'rw-' )); // // Stacks! // const StackBase = Teb.NtTib.StackBase.address; const StackLimit = Teb.NtTib.StackLimit.address; VaSpace.push(new _Region( StackLimit, StackBase.subtract(StackLimit), 'Stack', 'rw-' )); } // // Get the PEB. Keep in mind we can run into the same symbol problem with the // PEB - so account for that. // logln('Populating the VA space with the PEB..'); const Peb = CurrentProcess.Environment.EnvironmentBlock; if (Peb == undefined) { const General = host.namespace.Debugger.State.PseudoRegisters.General; VaSpace.push(new _Region( General.peb.address, host.Int64(0x1000), 'Peb', 'rw-' )); logln(`/!\\ Several regions have been skipped because nt!_TEB / nt!_PEB aren't available in your symbols.`); } else { VaSpace.push(new _Region( Peb.address, Peb.targetType.size, 'Peb', 'rw-' )); } } function HandleKernel() { // // Enumerate the kernel modules. // logln('Populating the VA space with kernel modules..'); const CurrentSession = host.currentSession; const SystemProcess = CurrentSession.Processes.First( p => p.Name == 'System' ); const MmUserProbeAddress = ReadPtr( host.getModuleSymbolAddress('nt', 'MmUserProbeAddress') ); const KernelModules = SystemProcess.Modules.Where( p => p.BaseAddress.compareTo(MmUserProbeAddress) > 0 ); for (const Module of KernelModules) { // // Iterate over the section headers of the module. // for (const Section of SectionHeaders(Module.BaseAddress)) { VaSpace.push(new _Region( Section.BaseAddress, Section.Size, 'Driver ' + Module.Name + ' (' + Section.Name + ')', Section.Properties )); } // // Add a catch all in case a pointer points inside the PE but not // inside any sections (example of this is the PE header). // VaSpace.push(new _Region( Module.BaseAddress, Module.Size, 'Driver ' + Module.Name, 'r--' )); } } function InitializeVASpace() { if (IsUser) { HandleUser(); } if (IsTTD) { // // If we have a TTD target, let's do some more work. // HandleTTD(); } if (IsKernel) { HandleKernel(); } } function InitializeWrapper(Funct) { return Arg => { if (!Initialized) { const CurrentSession = host.currentSession; // // Initialize the ReadPtr function according to the PointerSize. // PointerSize = CurrentSession.Attributes.Machine.PointerSize; ReadPtr = PointerSize.compareTo(8) == 0 ? ReadU64 : ReadU32; FormatPtr = PointerSize.compareTo(8) == 0 ? FormatU64 : FormatU32; const TargetAttributes = CurrentSession.Attributes.Target; IsTTD = TargetAttributes.IsTTDTarget; IsUser = TargetAttributes.IsUserTarget; IsKernel = TargetAttributes.IsKernelTarget; // // One time initialization! // Initialized = true; } // // Once initialization is done, call into the function. // return Funct(Arg); }; } // // The meat! // class _Region { constructor(BaseAddress, Size, Name, Properties) { this.Name = Name; this.BaseAddress = BaseAddress; this.EndAddress = this.BaseAddress.add(Size); this.Size = Size; this.Properties = Properties; this.Executable = false; this.Readable = false; this.Writeable = false; if (Properties.indexOf('r') != -1) { this.Readable = true; } if (Properties.indexOf('w') != -1) { this.Writeable = true; } if (Properties.indexOf('x') != -1) { this.Executable = true; } } In(Addr) { const InBounds = Addr.compareTo(this.BaseAddress) >= 0 && Addr.compareTo(this.EndAddress) < 0; return InBounds; } toString() { const Prop = [ this.Readable ? 'r' : '-', this.Writeable ? 'w' : '-', this.Executable ? 'x' : '-' ]; return this.Name + ' ' + Prop.join(''); } } function AddressToRegion(Addr) { // // Map the address space with VA regions. // const Hits = VaSpace.filter( p => p.In(Addr) ); // // Now, let's get the most precise region information by ordering // the hits by size. // const OrderedHits = Hits.sort( (a, b) => a.Size.compareTo(b.Size) ); // // Return the most precise information we have! // return OrderedHits[0]; } class _ChainEntry { constructor(Addr, Value) { this.Addr = Addr; this.Value = Value; this.AddrRegion = AddressToRegion(this.Addr); this.ValueRegion = AddressToRegion(this.Value); if (this.ValueRegion == undefined) { this.Name = 'Unknown'; } else { // // Just keep the file name and strips off the path. // this.Name = this.ValueRegion.Name; this.Name = this.Name.substring(this.Name.lastIndexOf('\\') + 1); } this.Last = false; } Equals(Entry) { return this.Addr.compareTo(Entry.Addr) == 0; } toString() { const S = FormatPtr(this.Value) + ' (' + this.Name + ')'; if (!this.Last) { return S; } // // We only provide disassembly if we know that the code is executeable. // And in order to know that, we need to have a valid `AddrRegion`. // if (this.AddrRegion != undefined && this.AddrRegion.Executable) { return Disassemble(this.Addr); } // // If we have a string stored in a heap allocation what happens is the following: // - The extension does not know about heap, so `AddrRegion` for such a pointer // would be `undefined`. // - Even though it is undefined, we would like to display a string if there is any, // instead of just the first qword. // So to enable the scenario to work, we allow to enter the below block with an `AddrRegion` // that is undefined. // if (this.AddrRegion == undefined || this.AddrRegion.Readable) { const IsPrintable = p => { return p != null && // XXX: ugly AF. p.match(/^[a-z0-9!"#$%&'()*+,/\\.:;<=>?@\[\] ^_`{|}~-]+$/i) != null && p.length > 5 }; // // Maybe it points on a unicode / ascii string? // const Ansi = ReadString(this.Addr); if (IsPrintable(Ansi)) { return `${FormatPtr(this.Addr)} (Ascii(${FormatString(Ansi)}))`; } const Wide = ReadWideString(this.Addr); if (IsPrintable(Wide)) { return `${FormatPtr(this.Addr)} (Unicode(${FormatString(Wide)}))`; } } // // If we didn't find something better, fallback to the regular // output. // return S; } } class _Chain { constructor(Addr) { this.__Entries = []; this.__HasCycle = false; this.__Addr = Addr; while (this.FollowPtr()) { }; this.__Length = this.__Entries.length; // // Tag the last entry as 'last'. // if (this.__Length >= 1) { this.__Entries[this.__Length - 1].Last = true; } } FollowPtr() { // // Attempt to follow the pointer. // const Value = ReadPtr(this.__Addr); if (Value == null) { // // We are done following pointers now! // return false; } // // Let's build an entry and evaluate what we want to do with it. // const Entry = new _ChainEntry(this.__Addr, Value); const DoesEntryExist = this.__Entries.find( p => p.Equals(Entry) ); if (DoesEntryExist) { // // If we have seen this Entry before, it means there's a cycle // and we will stop there. // this.__HasCycle = true; return false; } // // This Entry is of interest, so let's add it in our list. // this.__Entries.push(Entry); this.__Addr = Value; return true; } toString() { if (this.__Entries.length == 0) { return ''; } // // Iterate over the chain. // let S = this.__Entries.join(' -> '); // // Add a little something if we have a cycle so that the user knows. // if (this.__HasCycle) { S += ' [...]'; } return S; } *[Symbol.iterator]() { for (const Entry of this.__Entries) { yield Entry; } } } function CreateChain(Addr) { // // Initialize the VA space. // InitializeVASpace(); const Chain = new _Chain(Addr); VaSpace = []; return Chain; } function Telescope(Addr) { if (Addr == undefined) { logln('!telescope '); return; } // // Initialize the VA space. // InitializeVASpace(); const CurrentSession = host.currentSession; const Lines = DefaultNumberOfLines; const PointerSize = CurrentSession.Attributes.Machine.PointerSize; const FormatOffset = p => '0x' + p.toString(16).padStart(4, '0'); for (let Idx = 0; Idx < Lines; Idx++) { const Offset = PointerSize.multiply(Idx); const CurAddr = Addr.add(Offset); const Chain = new _Chain(CurAddr); const Header = FormatPtr(CurAddr) + '|+' + FormatOffset(Offset); logln(Header + ': ' + Chain.toString()); } VaSpace = []; } function initializeScript() { return [ new host.apiVersionSupport(1, 3), new host.functionAlias( InitializeWrapper(Telescope), 'telescope' ), new host.functionAlias( InitializeWrapper(CreateChain), 'createchain' ) ]; }