├── Manifest ├── ManifestVersion.txt ├── config.xml └── Manifest.1.xml ├── basics ├── eval.js ├── extendmodel_1.js ├── Int64.js ├── extendmodel_2.js ├── readmemory.js ├── extendmodel_2_1.js ├── breakpoint.js ├── breakpoint2.js └── extendmodel.js ├── LICENSE ├── codecov ├── README.md └── codecov.js ├── README.md ├── policybuffer ├── README.md └── policybuffer.js ├── parse_eh_win64 ├── README.md └── parse_eh_win64.js ├── sm ├── README.md └── sm.js ├── telescope ├── README.md └── telescope.js └── gdt ├── README.md └── gdt.js /Manifest/ManifestVersion.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 1.0.0.0 3 | 1 4 | -------------------------------------------------------------------------------- /basics/eval.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - Dec 2017 2 | 3 | "use strict"; 4 | 5 | let logln = function (e) { 6 | host.diagnostics.debugLog(e + '\n'); 7 | } 8 | 9 | function invokeScript() { 10 | let Control = host.namespace.Debugger.Utility.Control; 11 | for(let Line of Control.ExecuteCommand('kp')) { 12 | logln('Line: ' + Line); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /basics/extendmodel_1.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - Dec 2017 2 | 3 | "use strict"; 4 | 5 | class ProcessModelParent { 6 | get DiaryOfAReverseEngineer() { 7 | return 'hello from ' + this.Name; 8 | } 9 | } 10 | 11 | function initializeScript() { 12 | return [new host.namedModelParent( 13 | ProcessModelParent, 14 | 'Debugger.Models.Process' 15 | )]; 16 | } -------------------------------------------------------------------------------- /Manifest/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /basics/Int64.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - Dec 2017 2 | 3 | "use strict"; 4 | 5 | let logln = function (e) { 6 | host.diagnostics.debugLog(e + '\n'); 7 | } 8 | 9 | function invokeScript() { 10 | let a = host.Int64(1337); 11 | let aplusone = a + 1; 12 | logln(aplusone.toString(16)); 13 | let b = host.parseInt64('0xdeadbeefbaadc0de', 16); 14 | let bplusone = b.add(1); 15 | logln(bplusone.toString(16)); 16 | let bplusonenothrow = b.convertToNumber() + 1; 17 | logln(bplusonenothrow); 18 | try { 19 | let bplusonethrow = b + 1; 20 | } catch(e) { 21 | logln(e); 22 | } 23 | logln(a.compareTo(1)); 24 | logln(a.compareTo(1337)); 25 | logln(a.compareTo(1338)); 26 | } 27 | -------------------------------------------------------------------------------- /basics/extendmodel_2.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - Dec 2017 2 | 3 | "use strict"; 4 | 5 | class DiaryOfAReverseEngineer { 6 | constructor(Process) { 7 | this.process = Process; 8 | } 9 | 10 | get Foo() { 11 | return 'Foo from ' + this.process.Name; 12 | } 13 | 14 | get Bar() { 15 | return 'Bar from ' + this.process.Name; 16 | } 17 | 18 | Add(a, b) { 19 | return a + b; 20 | } 21 | } 22 | 23 | class ProcessModelParent { 24 | get DiaryOfAReverseEngineer() { 25 | return new DiaryOfAReverseEngineer(this); 26 | } 27 | } 28 | 29 | function initializeScript() { 30 | return [new host.namedModelParent( 31 | ProcessModelParent, 32 | 'Debugger.Models.Process' 33 | )]; 34 | } -------------------------------------------------------------------------------- /basics/readmemory.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - Dec 2017 2 | 3 | "use strict"; 4 | 5 | let logln = function (e) { 6 | host.diagnostics.debugLog(e + '\n'); 7 | } 8 | 9 | function read_u64(addr) { 10 | return host.memory.readMemoryValues(addr, 1, 8)[0]; 11 | } 12 | 13 | function invokeScript() { 14 | let Regs = host.currentThread.Registers.User; 15 | let a = read_u64(Regs.rsp); 16 | logln(a.toString(16)); 17 | try { 18 | read_u64(0xdeadbeef); 19 | } catch(e) { 20 | logln(e); 21 | } 22 | let WideStr = host.currentProcess.Environment.EnvironmentBlock.ProcessParameters.ImagePathName.Buffer; 23 | logln(host.memory.readWideString(WideStr)); 24 | let WideStrAddress = WideStr.address; 25 | logln(host.memory.readWideString(WideStrAddress)); 26 | } 27 | -------------------------------------------------------------------------------- /basics/extendmodel_2_1.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - Dec 2017 2 | 3 | "use strict"; 4 | 5 | class DiaryOfAReverseEngineer { 6 | constructor(Process) { 7 | this.__process = process; 8 | } 9 | 10 | get Foo() { 11 | return 'Foo from ' + this.__process.Name; 12 | } 13 | 14 | get Bar() { 15 | return 'Bar from ' + this.__process.Name; 16 | } 17 | 18 | Add(a, b) { 19 | return a + b; 20 | } 21 | 22 | toString() { 23 | return 'Diary of a reverse-engineer'; 24 | } 25 | } 26 | 27 | class ProcessModelParent { 28 | get DiaryOfAReverseEngineer() { 29 | return new DiaryOfAReverseEngineer(this); 30 | } 31 | } 32 | 33 | function initializeScript() { 34 | return [new host.namedModelParent( 35 | ProcessModelParent, 36 | 'Debugger.Models.Process' 37 | )]; 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Axel Souchet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /basics/breakpoint.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - Dec 2017 2 | 3 | "use strict"; 4 | 5 | let logln = function (e) { 6 | host.diagnostics.debugLog(e + '\n'); 7 | } 8 | 9 | function handle_bp() { 10 | let Regs = host.currentThread.Registers.User; 11 | let Args = [Regs.rcx, Regs.rdx, Regs.r8]; 12 | let ArgsS = Args.map(c => c.toString(16)); 13 | let HeapHandle = ArgsS[0]; 14 | let Flags = ArgsS[1]; 15 | let Size = ArgsS[2]; 16 | logln('RtlAllocateHeap: HeapHandle: ' + HeapHandle + ', Flags: ' + Flags + ', Size: ' + Size); 17 | } 18 | 19 | function invokeScript() { 20 | let Control = host.namespace.Debugger.Utility.Control; 21 | let Regs = host.currentThread.Registers.User; 22 | let CurrentProcess = host.currentProcess; 23 | let BreakpointAlreadySet = CurrentProcess.Debug.Breakpoints.Any( 24 | c => c.OffsetExpression == 'ntdll!RtlAllocateHeap+0x0' 25 | ); 26 | 27 | if(BreakpointAlreadySet == false) { 28 | let Bp = Control.SetBreakpointAtOffset('RtlAllocateHeap', 0, 'ntdll'); 29 | Bp.Command = '.echo doare; dx @$scriptContents.handle_bp(); gc'; 30 | } else { 31 | logln('Breakpoint already set.'); 32 | } 33 | logln('Press "g" to run the target.'); 34 | // let Lines = Control.ExecuteCommand('gc'); 35 | // for(let Line of Lines) { 36 | // logln('Line: ' + Line); 37 | // } 38 | } 39 | -------------------------------------------------------------------------------- /basics/breakpoint2.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - Dec 2017 2 | 3 | "use strict"; 4 | 5 | let logln = function (e) { 6 | host.diagnostics.debugLog(e + '\n'); 7 | } 8 | 9 | function handle_bp() { 10 | let Regs = host.currentThread.Registers.User; 11 | let Args = [Regs.rcx, Regs.rdx, Regs.r8]; 12 | let ArgsS = Args.map(c => c.toString(16)); 13 | let HeapHandle = ArgsS[0]; 14 | let Flags = ArgsS[1]; 15 | let Size = ArgsS[2]; 16 | logln('RtlAllocateHeap: HeapHandle: ' + HeapHandle + ', Flags: ' + Flags + ', Size: ' + Size); 17 | if(Args[2].compareTo(0x100) > 0) { 18 | // stop execution if the allocation size is bigger than 0x100 19 | return true; 20 | } 21 | // keep the execution going if it's a small size 22 | return false; 23 | } 24 | 25 | function invokeScript() { 26 | let Control = host.namespace.Debugger.Utility.Control; 27 | let Regs = host.currentThread.Registers.User; 28 | let CurrentProcess = host.currentProcess; 29 | let HeapAlloc = host.getModuleSymbolAddress('ntdll', 'RtlAllocateHeap'); 30 | let BreakpointAlreadySet = CurrentProcess.Debug.Breakpoints.Any( 31 | c => c.Address == HeapAlloc 32 | ); 33 | if(BreakpointAlreadySet == false) { 34 | logln('RltAllocateHeap @ ' + HeapAlloc.toString(16)); 35 | Control.ExecuteCommand('bp /w "@$scriptContents.handle_bp()" ' + HeapAlloc.toString(16)); 36 | } else { 37 | logln('Breakpoint already set.'); 38 | } 39 | logln('Press "g" to run the target.'); 40 | } 41 | -------------------------------------------------------------------------------- /codecov/README.md: -------------------------------------------------------------------------------- 1 | # codecov.js 2 | 3 | `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. 4 | 5 | The file looks like the below: 6 | 7 | ```text 8 | ; TracePath: C:\work\codes\blazefox\js01.run 9 | ; c:\windows\system32\kernelbase.dll, 7fffb4ce0000, 293000 10 | kernelbase.dll+5df40 11 | kernelbase.dll+5df43 12 | kernelbase.dll+5df47 13 | kernelbase.dll+5df4b 14 | kernelbase.dll+5df4f 15 | ... 16 | ; c:\windows\system32\kernel32.dll, 7fffb6460000, b3000 17 | kernel32.dll+1f3a0 18 | kernel32.dll+21bb0 19 | kernel32.dll+1bb90 20 | kernel32.dll+1a280 21 | kernel32.dll+1a284 22 | kernel32.dll+1e640 23 | kernel32.dll+63a0 24 | ``` 25 | 26 | ## Usage 27 | 28 | Run `.scriptload codecov.js` to load the script. You can extract code-coverage using `!codecov "foo"`. 29 | 30 | ## Examples 31 | 32 | Extract code-coverage for every module having `kernel` in their name: 33 | 34 | ```text 35 | 0:000> !codecov "kernel" 36 | Looking for *kernel*.. 37 | Found 2 hits 38 | Found 7815 unique addresses in C:\WINDOWS\System32\KERNELBASE.dll 39 | Found 1260 unique addresses in C:\WINDOWS\System32\KERNEL32.DLL 40 | Writing C:\work\codes\tmp\js01.run.kernel.text... 41 | Done! 42 | @$codecov("kernel") 43 | 44 | 0:000> !codecov "kernel" 45 | Looking for *kernel*.. 46 | The output file C:\work\codes\tmp\js01.run.kernel.text already exists, exiting. 47 | @$codecov("kernel") 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- /basics/extendmodel.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - Dec 2017 2 | 3 | "use strict"; 4 | 5 | class Attribute { 6 | constructor(Process, Name, Value) { 7 | this.__process = Process; 8 | this.Name = Name; 9 | this.Value = Value; 10 | } 11 | 12 | toString() { 13 | let S = 'Process: ' + this.__process.Name + ', '; 14 | S += 'Name: ' + this.Name + ', '; 15 | S += 'Value: ' + this.Value; 16 | return S; 17 | } 18 | } 19 | 20 | class Attributes { 21 | constructor() { 22 | this.__attrs = []; 23 | } 24 | 25 | push(Attr) { 26 | this.__attrs.push(Attr); 27 | } 28 | 29 | *[Symbol.iterator]() { 30 | for (let Attr of this.__attrs) { 31 | yield Attr; 32 | } 33 | } 34 | 35 | toString() { 36 | return 'Attributes'; 37 | } 38 | } 39 | 40 | class Sub { 41 | constructor(Process) { 42 | this.__process = Process; 43 | } 44 | 45 | get SubFoo() { 46 | return 'SubFoo from ' + this.__process.Name; 47 | } 48 | 49 | get SubBar() { 50 | return 'SubBar from ' + this.__process.Name; 51 | } 52 | 53 | get Attributes() { 54 | let Attrs = new Attributes(); 55 | Attrs.push(new Attribute(this.__process, 'attr0', 'value0')); 56 | Attrs.push(new Attribute(this.__process, 'attr1', 'value0')); 57 | return Attrs; 58 | } 59 | 60 | toString() { 61 | return 'Sub module'; 62 | } 63 | } 64 | 65 | class DiaryOfAReverseEngineer { 66 | constructor(Process) { 67 | this.__process = Process; 68 | } 69 | 70 | get Foo() { 71 | return 'Foo from ' + this.__process.Name; 72 | } 73 | 74 | get Bar() { 75 | return 'Bar from ' + this.__process.Name; 76 | } 77 | 78 | Add(a, b) { 79 | return a + b; 80 | } 81 | 82 | get Sub() { 83 | return new Sub(this.__process); 84 | } 85 | 86 | toString() { 87 | return 'Diary of a reverse-engineer'; 88 | } 89 | } 90 | 91 | class ProcessModelParent { 92 | get DiaryOfAReverseEngineer() { 93 | return new DiaryOfAReverseEngineer(this); 94 | } 95 | } 96 | 97 | function initializeScript() { 98 | return [new host.namedModelParent( 99 | ProcessModelParent, 100 | 'Debugger.Models.Process' 101 | )]; 102 | } 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # windbg-scripts 2 | 3 | `windbg-scripts` is a collection of [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extensions for WinDbg. 4 | 5 | * [basics](basics/): various examples of basic usage of various APIs, 6 | * [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/)), 7 | * [telescope](telescope/): [telescope](https://gef.readthedocs.io/en/latest/commands/dereference/) like command for WinDbg, 8 | * [sm](sm/): pretty-printing of Spidermonkey `js::Value` and `JSObject` objects, 9 | * [codecov](codecov/): extract code-coverage out of a [TTD](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-overview) trace, 10 | * [policybuffer](policybuffer/): disassemble a Chrome policy buffer program, 11 | * [gdt](gdt/): dump the [Global Descriptor Table](https://wiki.osdev.org/Global_Descriptor_Table). 12 | 13 | ## Installing the script gallery 14 | 15 | 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: 16 | 17 | 1. Clone this GitHub repository, 18 | 19 | 2. Edit the `Manifest\config.xml` file and update the `LocalCacheRootFolder` path with a value that makes sense, 20 | 21 | 3. Open the debugger and import the gallery by running `.settings load c:\path\where\cloned\windbg-scripts\Manifest\config.xml` and `.settings save`. 22 | 23 | 4. Restart the debugger and you should be able to run `!telescope` as well as inspecting the gallery content from the data-model. 24 | 25 | ```text 26 | 0:000> dx -r1 Debugger.State.ExtensionGallery.ExtensionRepositories 27 | Debugger.State.ExtensionGallery.ExtensionRepositories 28 | [0x0] : overgallery 29 | [0x1] : LocalInstalled 30 | 31 | 0:000> dx -r1 Debugger.State.ExtensionGallery.ExtensionRepositories[0] 32 | Debugger.State.ExtensionGallery.ExtensionRepositories[0] : overgallery 33 | Name : overgallery 34 | ManifestVersion : 0x1 35 | URL 36 | Enabled : true 37 | Packages 38 | 39 | 0:000> dx -r1 Debugger.State.ExtensionGallery.ExtensionRepositories[0].Packages 40 | Debugger.State.ExtensionGallery.ExtensionRepositories[0].Packages 41 | [0x0] : Telescope 42 | 43 | 0:000> dx -r1 Debugger.State.ExtensionGallery.ExtensionRepositories[0].Packages[0] 44 | Debugger.State.ExtensionGallery.ExtensionRepositories[0].Packages[0] : Telescope 45 | Name : Telescope 46 | Version : 1.0.0.1 47 | Description : Telescope data dereference 48 | Size : 0 49 | IsDownloaded : true 50 | Components 51 | ``` 52 | -------------------------------------------------------------------------------- /policybuffer/README.md: -------------------------------------------------------------------------------- 1 | # policybuffer.js 2 | 3 | `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. 4 | 5 | ## Usage 6 | 7 | Run `.scriptload policybuffer.js` to load the script. You can invoke the disassembler feature with `!disasspolicy `. 8 | 9 | ## Examples 10 | 11 | * Dumping the File System policy of Firefox: 12 | 13 | ```text 14 | 0:017> !disasspolicy 0x00000131`d2142208 15 | !OP_NUMBER_AND_MATCH 16 | !OP_WSTRING_MATCH 17 | OP_ACTION 18 | 19 | !OP_NUMBER_AND_MATCH 20 | OP_WSTRING_MATCH 21 | OP_ACTION 22 | 23 | OP_WSTRING_MATCH 24 | OP_ACTION 25 | 26 | !OP_NUMBER_AND_MATCH 27 | OP_NUMBER_MATCH 28 | OP_WSTRING_MATCH 29 | OP_ACTION 30 | 31 | !OP_NUMBER_AND_MATCH 32 | OP_NUMBER_MATCH 33 | OP_WSTRING_MATCH 34 | OP_ACTION 35 | 36 | !OP_NUMBER_AND_MATCH 37 | OP_NUMBER_MATCH 38 | OP_WSTRING_MATCH 39 | OP_ACTION 40 | 41 | !OP_NUMBER_AND_MATCH 42 | OP_NUMBER_MATCH 43 | OP_WSTRING_MATCH 44 | OP_ACTION 45 | 46 | !OP_NUMBER_AND_MATCH 47 | OP_NUMBER_MATCH 48 | OP_WSTRING_MATCH 49 | OP_ACTION 50 | 51 | !OP_NUMBER_AND_MATCH 52 | OP_NUMBER_MATCH 53 | OP_WSTRING_MATCH 54 | OP_ACTION 55 | 56 | OP_WSTRING_MATCH 57 | OP_ACTION 58 | 59 | OP_WSTRING_MATCH 60 | OP_ACTION 61 | 62 | @$disasspolicy(0x00000131`d2142208) 63 | ``` 64 | -------------------------------------------------------------------------------- /parse_eh_win64/README.md: -------------------------------------------------------------------------------- 1 | # parse_eh_win64.js 2 | 3 | `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. 4 | 5 | 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/). 6 | 7 | ## Usage 8 | 9 | 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. 10 | 11 | ## Examples 12 | 13 | * At the process level, dumping `Function` objects and ordering them by the number of exception-handlers they define: 14 | 15 | ```text 16 | 0:002> dx @$curprocess.Functions.OrderByDescending(p => p.ExceptionHandlers.Count()) 17 | @$curprocess.Functions.OrderByDescending(p => p.ExceptionHandlers.Count()) 18 | [0x0] : RVA:7fffb64bebf0 -> RVA:7fffb64bf022, 12 exception handlers 19 | [0x1] : RVA:7fffb8bdff80 -> RVA:7fffb8be0b67, 11 exception handlers 20 | [0x2] : RVA:7fffb1df8114 -> RVA:7fffb1df8360, 9 exception handlers 21 | [0x3] : RVA:7fffa0111354 -> RVA:7fffa01115a0, 9 exception handlers 22 | [0x4] : RVA:7fffb2183044 -> RVA:7fffb2183290, 9 exception handlers 23 | [0x5] : RVA:7fffa0d41344 -> RVA:7fffa0d41590, 9 exception handlers 24 | [0x6] : RVA:7fffb6573020 -> RVA:7fffb6573356, 6 exception handlers 25 | [0x7] : RVA:7fffb4c71f94 -> RVA:7fffb4c720b4, 6 exception handlers 26 | [0x8] : RVA:7fffb65e5774 -> RVA:7fffb65e5894, 6 exception handlers 27 | [0x9] : RVA:7fffb660c62c -> RVA:7fffb660cf2e, 6 exception handlers 28 | [0xa] : RVA:7fffb6c6f014 -> RVA:7fffb6c6f134, 6 exception handlers 29 | [0xb] : RVA:7fffb8b9a350 -> RVA:7fffb8b9b39b, 6 exception handlers 30 | [0xc] : RVA:7fffb35168a0 -> RVA:7fffb3516efb, 5 exception handlers 31 | ``` 32 | 33 | * Dumping a `Function` object: 34 | 35 | ```text 36 | 0:002> dx -r1 @$curprocess.Functions[0] 37 | @$curprocess.Functions[0] : RVA:7ff67025a6d0 -> RVA:7ff67025a738, 1 exception handlers 38 | EHHandlerRVA : 0x9b9700 39 | EHHandler : 0x7ff6708f9700 40 | BeginRVA : 0x31a6d0 41 | EndRVA : 0x31a738 42 | Begin : 0x7ff67025a6d0 43 | End : 0x7ff67025a738 44 | ExceptionHandlers : __try {7ff67025a6fb -> 7ff67025a712} __except(EXCEPTION_EXECUTE_HANDLER) {7ff67025a736} 45 | ``` 46 | 47 | * At the module level, dumping `ExceptionHandler` objects: 48 | 49 | ```text 50 | 0:002> dx @$curprocess.Modules[0].ExceptionHandlers 51 | @$curprocess.Modules[0].ExceptionHandlers : Exception handlers 52 | [0x0] : __try {7ff67025a6fb -> 7ff67025a712} __except(EXCEPTION_EXECUTE_HANDLER) {7ff67025a736} 53 | [0x1] : __try {7ff6708f80b3 -> 7ff6708f813e} __except(7ff6708f93f2()) {7ff6708f813e} 54 | [0x2] : __try {7ff6708f90fd -> 7ff6708f9202} __except(7ff6708f9425()) {7ff6708f9202} 55 | [0x3] : __try {7ff6708f9236 -> 7ff 56 | ``` 57 | 58 | * Dumping an `ExceptionHandler` object: 59 | 60 | ```text 61 | 0:002> dx @$curprocess.Modules[0].ExceptionHandlers[0] 62 | @$curprocess.Modules[0].ExceptionHandlers[0] : __try {7ff67025a6fb -> 7ff67025a712} __except(EXCEPTION_EXECUTE_HANDLER) {7ff67025a736} 63 | Begin : 0x7ff67025a6fb 64 | End : 0x7ff67025a712 65 | HandlerAddress : 0x1 66 | JumpTarget : 0x7ff67025a736 67 | IsTryFinally : false 68 | HasFilter : false 69 | ``` 70 | 71 | * Dumping the current call-stack with EH information: 72 | 73 | ```text 74 | 0:002> !ehhandlers 75 | 5 stack frames, scanning for handlers... 76 | Frame 1: EHHandler: 7fffb8c1fc90: ntdll!_C_specific_handler: 77 | Except: 7fffb8c5ef1d: ntdll!DbgUiRemoteBreakin+0x4d: 78 | Frame 3: EHHandler: 7fffb8c1fc90: ntdll!_C_specific_handler: 79 | Except: 7fffb8bfa267: ntdll!RtlUserThreadStart+0x37: 80 | Filter: 7fffb8c38021: ntdll!RtlUserThreadStart$filt$0: 81 | @$ehhandlers() 82 | ``` 83 | 84 | -------------------------------------------------------------------------------- /sm/README.md: -------------------------------------------------------------------------------- 1 | # sm.js 2 | 3 | `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. 4 | 5 | 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. 6 | 7 | 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). 8 | 9 | ## Usage 10 | 11 | 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. 12 | 13 | ## Examples 14 | 15 | * 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]`: 16 | 17 | ```text 18 | 0:000> !smdump_jsvalue vp[2].asBits_ 19 | 1e5f10024c0: js!js::ArrayObject: Length: 10 20 | 1e5f10024c0: js!js::ArrayObject: Capacity: 10 21 | 1e5f10024c0: js!js::ArrayObject: Content: ['short', 13.37, new Map(...), ['loooooooooooooooooooooooooooooong', [0x1337, {'doare' : 'in d4 place'}]], false, null, undefined, true, atan2(), Math] 22 | @$smdump_jsvalue(vp[2].asBits_) 23 | ``` 24 | 25 | * Setting a breakpoint in code being JIT'd by IonMonkey: 26 | 27 | ```text 28 | 0:008> g 29 | Breakpoint 0 hit 30 | js!js::jit::CodeGenerator::visitBoundsCheck: 31 | 00007ff7`87d9e1a0 4156 push r14 32 | 33 | 0:000> !ion_insertbp 34 | unsigned char 0xcc '' 35 | unsigned int64 0x5b 36 | @$ion_insertbp() 37 | 38 | 0:000> g 39 | (1a60.f58): Break instruction exception - code 80000003 (first chance) 40 | 000003d9`ca67991b cc int 3 41 | 42 | 0:000> u . l2 43 | 000003d9`ca67991b cc int 3 44 | 000003d9`ca67991c 3bd8 cmp ebx,eax 45 | ``` 46 | 47 | * Figure out if an object lives in the Nursery heap: 48 | 49 | ```text 50 | 0:008> !in_nursery 0x19767e00df8 51 | Using previously cached JSContext @0x000001fe17318000 52 | 0x000001fe1731cde8: js::Nursery 53 | ChunkCountLimit: 0x0000000000000010 (16 MB) 54 | Capacity: 0x0000000000fffe80 bytes 55 | CurrentChunk: 0x0000019767e00000 56 | Position: 0x0000019767e00eb0 57 | Chunks: 58 | 00: [0x0000019767e00000 - 0x0000019767efffff] 59 | 01: [0x00001fa2aee00000 - 0x00001fa2aeefffff] 60 | 02: [0x0000115905000000 - 0x00001159050fffff] 61 | 03: [0x00002fc505200000 - 0x00002fc5052fffff] 62 | 04: [0x000020d078700000 - 0x000020d0787fffff] 63 | 05: [0x0000238217200000 - 0x00002382172fffff] 64 | 06: [0x00003ff041f00000 - 0x00003ff041ffffff] 65 | 07: [0x00001a5458700000 - 0x00001a54587fffff] 66 | ------- 67 | 0x19767e00df8 has been found in the js::NurseryChunk @0x19767e00000! 68 | 69 | 0:008> !in_nursery 0x00001fe174be810 70 | Using previously cached JSContext @0x000001fe17318000 71 | 0x000001fe1731cde8: js::Nursery 72 | ChunkCountLimit: 0x0000000000000010 (16 MB) 73 | Capacity: 0x0000000000fffe80 bytes 74 | CurrentChunk: 0x0000019767e00000 75 | Position: 0x0000019767e00eb0 76 | Chunks: 77 | 00: [0x0000019767e00000 - 0x0000019767efffff] 78 | 01: [0x00001fa2aee00000 - 0x00001fa2aeefffff] 79 | 02: [0x0000115905000000 - 0x00001159050fffff] 80 | 03: [0x00002fc505200000 - 0x00002fc5052fffff] 81 | 04: [0x000020d078700000 - 0x000020d0787fffff] 82 | 05: [0x0000238217200000 - 0x00002382172fffff] 83 | 06: [0x00003ff041f00000 - 0x00003ff041ffffff] 84 | 07: [0x00001a5458700000 - 0x00001a54587fffff] 85 | ------- 86 | 0x1fe174be810 hasn't been found be in any Nursery js::NurseryChunk. 87 | ``` 88 | -------------------------------------------------------------------------------- /codecov/codecov.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - 2 Feb 2018 2 | // 3 | // Example: 4 | // 0:000> !codecov "kernel" 5 | // Looking for *kernel*.. 6 | // Found 2 hits 7 | // Found 7815 unique addresses in C:\WINDOWS\System32\KERNELBASE.dll 8 | // Found 1260 unique addresses in C:\WINDOWS\System32\KERNEL32.DLL 9 | // Writing C:\work\codes\tmp\js01.run.kernel.text... 10 | // Done! 11 | // @$codecov("kernel") 12 | // 0:000> !codecov "kernel" 13 | // Looking for *kernel*.. 14 | // The output file C:\work\codes\tmp\js01.run.kernel.text already exists, exiting. 15 | // @$codecov("kernel") 16 | // 17 | 18 | 'use strict'; 19 | 20 | const log = host.diagnostics.debugLog; 21 | const logln = p => host.diagnostics.debugLog(p + '\n'); 22 | const hex = p => p.toString(16); 23 | 24 | function ExtractModuleName(ModulePath) { 25 | return ModulePath.slice( 26 | ModulePath.lastIndexOf('\\') + 1 27 | ); 28 | } 29 | 30 | function CodeCoverageModule(Module) { 31 | const CurrentSession = host.currentSession; 32 | const BaseAddress = Module.BaseAddress; 33 | const Size = Module.Size; 34 | 35 | const CoverageLines = CurrentSession.TTD.Memory( 36 | BaseAddress, 37 | BaseAddress.add(Size), 38 | 'EC' 39 | ); 40 | 41 | const Offsets = Array.from(CoverageLines).map( 42 | p => hex( 43 | p.Address.subtract(BaseAddress) 44 | ) 45 | ); 46 | 47 | return { 48 | 'Path' : Module.Name.toLowerCase(), 49 | 'Base' : BaseAddress, 50 | 'Size' : Size, 51 | 'Offsets' : Offsets 52 | }; 53 | } 54 | 55 | function CodeCov(ModulePattern) { 56 | const CurrentSession = host.currentSession; 57 | const CurrentProcess = host.currentProcess; 58 | const Utility = host.namespace.Debugger.Utility; 59 | 60 | if(!CurrentSession.Attributes.Target.IsTTDTarget) { 61 | logln('!codecov expects a TTD trace'); 62 | return; 63 | } 64 | 65 | if(ModulePattern == undefined) { 66 | logln('!codecov "pattern"'); 67 | return; 68 | } 69 | 70 | ModulePattern = ModulePattern.toLowerCase(); 71 | logln('Looking for *' + ModulePattern + '*..'); 72 | const Modules = CurrentProcess.Modules.Where( 73 | p => p.Name.toLowerCase().indexOf(ModulePattern) != -1 74 | ); 75 | 76 | if(Modules.Count() == 0) { 77 | logln('Could not find any matching module, exiting'); 78 | return; 79 | } 80 | 81 | const TracePath = CurrentSession.Attributes.Target.Details.DumpFileName; 82 | const TraceDir = TracePath.slice( 83 | 0, 84 | TracePath.lastIndexOf('\\') 85 | ); 86 | const TraceName = TracePath.slice( 87 | TracePath.lastIndexOf('\\') + 1 88 | ); 89 | const FilePath = TraceDir + '\\' + TraceName + '.' + ModulePattern + '.txt'; 90 | if(Utility.FileSystem.FileExists(FilePath)) { 91 | logln('The output file ' + FilePath + ' already exists, exiting.'); 92 | return; 93 | } 94 | 95 | const Metadata = { 96 | 'TracePath' : TracePath 97 | }; 98 | 99 | const CoverageModules = []; 100 | logln('Found ' + Modules.Count() + ' hits'); 101 | for(const Module of Modules) { 102 | const ModuleCoverage = CodeCoverageModule(Module); 103 | logln('Found ' + ModuleCoverage.Offsets.length + ' unique addresses in ' + Module.Name); 104 | CoverageModules.push(ModuleCoverage); 105 | } 106 | 107 | logln('Writing ' + FilePath + '...'); 108 | const FileHandle = Utility.FileSystem.CreateFile(FilePath, 'CreateAlways'); 109 | const Writer = Utility.FileSystem.CreateTextWriter(FileHandle); 110 | for(const [Name, Value] of Object.entries(Metadata)) { 111 | Writer.WriteLine('; ' + Name + ': ' + Value); 112 | } 113 | 114 | for(const Module of CoverageModules) { 115 | 116 | // 117 | // First write the metadata that is module specific. 118 | // 119 | 120 | Writer.WriteLine('; ' + Module.Path + ', ' + hex(Module.Base) + ', ' + hex(Module.Size)); 121 | 122 | // 123 | // Write down the offsets. 124 | // 125 | 126 | const ModuleName = ExtractModuleName(Module.Path); 127 | for(const Offset of Module.Offsets) { 128 | Writer.WriteLine(ModuleName + '+' + hex(Offset)); 129 | } 130 | } 131 | 132 | FileHandle.Close(); 133 | logln('Done!'); 134 | } 135 | 136 | function initializeScript() { 137 | return [ 138 | new host.apiVersionSupport(1, 2), 139 | new host.functionAlias( 140 | CodeCov, 141 | 'codecov' 142 | ) 143 | ]; 144 | } 145 | -------------------------------------------------------------------------------- /Manifest/Manifest.1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Telescope 5 | 1.0.0.1 6 | Telescope data dereference 7 | 8 | 9 | 10 | 11 | 12 | 13 | ]]> 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Gdt 27 | 1.0.0.1 28 | Gdt dump 29 | 30 | 31 | 32 | 33 | 34 | 35 | ]]> 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | sm 49 | 1.0.0.1 50 | Pretty printers for JS::Value and JSObject objects in SpiderMonkey 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | ]]> 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ]]> 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /telescope/README.md: -------------------------------------------------------------------------------- 1 | # telescope.js 2 | 3 | `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. 4 | 5 | Idea from [@\_\_awe](https://twitter.com/__awe). 6 | 7 | ## Usage 8 | 9 | Run `.scriptload telescope.js` to load the script. You can invoke the telescope feature with `!telescope ` or programatically via `dx @$createchain()`. 10 | 11 | ## Examples 12 | 13 | * From an x64 TTD execution trace: 14 | 15 | ```text 16 | 0:000> !telescope @rsp 17 | 0x0000005be1ffcec0|+0x0000: 0xe1000205e1ffdd48 (Unknown) 18 | 0x0000005be1ffcec8|+0x0008: 0x00007ff700000006 (Unknown) 19 | 0x0000005be1ffced0|+0x0010: 0x000001fce5928840 (VirtualAlloced) -> 0x0000005be1ffd0b8 (Stack) -> 0x000001fce5928840 (VirtualAlloced) [...] 20 | 0x0000005be1ffced8|+0x0018: 0x0000005be1ffdb68 (Stack) -> 0x000001fce5928840 (VirtualAlloced) -> 0x0000005be1ffd0b8 (Stack) -> 0x000001fce5928840 (VirtualAlloced) [...] 21 | 0x0000005be1ffcee0|+0x0020: 0x000001fce634afa0 (VirtualAlloced) -> 0x0000000800000b50 (Unknown) 22 | 0x0000005be1ffcee8|+0x0028: 0x00004a54b4bb11e0 (Unknown) 23 | 0x0000005be1ffcef0|+0x0030: 0x0000000000000008 (Unknown) 24 | 0x0000005be1ffcef8|+0x0038: 0x0000000000000000 (Unknown) 25 | 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 26 | 0x0000005be1ffcf08|+0x0048: 0x00007ff7766b4546 (js.exe (.text)) -> test rax,rax ; je js!mozilla::Vector::growStorageBy+0x395 (00007ff7`766b4805) 27 | @$telescope(@rsp) 28 | ``` 29 | 30 | * Accessing the chain programatically via `createchain`: 31 | 32 | ```text 33 | 0:000> dx @$createchain(0x0000005be1ffcf08) 34 | @$createchain(0x0000005be1ffcf08) : 0x00007ff7766b4546 (js.exe (.text)) -> test rax,rax ; je js!mozilla::Vector::growStorageBy+0x395 (00007ff7`766b4805) 35 | [0x0] : 0x00007ff7766b4546 (js.exe (.text)) 36 | [0x1] : test rax,rax ; je js!mozilla::Vector::growStorageBy+0x395 (00007ff7`766b4805) 37 | 38 | 0:000> dx -r1 @$createchain(0x0000005be1ffcf08)[0] 39 | @$createchain(0x0000005be1ffcf08)[0] : 0x00007ff7766b4546 (js.exe (.text)) 40 | Addr : 0x5be1ffcf08 41 | Value : 0x7ff7766b4546 42 | AddrRegion : Stack rw- 43 | ValueRegion : Image C:\work\codes\blazefox\js-release\js.exe (.text) r-x 44 | Name : js.exe (.text) 45 | Last : false 46 | 47 | 0:000> dx -r1 @$createchain(0x0000005be1ffcf08)[1] 48 | @$createchain(0x0000005be1ffcf08)[1] : test rax,rax ; je js!mozilla::Vector::growStorageBy+0x395 (00007ff7`766b4805) 49 | Addr : 0x7ff7766b4546 50 | Value : 0x2b6840fc08548 51 | AddrRegion : Image C:\work\codes\blazefox\js-release\js.exe (.text) r-x 52 | Name : Unknown 53 | Last : true 54 | ``` 55 | 56 | * From an x86 live-session: 57 | 58 | ```text 59 | 0:001> !telescope @esp 60 | 0x00d7ff44|+0x0000: 0x77dcb3a9 (ntdll.dll (.text)) -> jmp ntdll!DbgUiRemoteBreakin+0x42 (77dcb3b2) ; xor eax,eax 61 | 0x00d7ff48|+0x0004: 0x1911c0a3 (Unknown) 62 | 0x00d7ff4c|+0x0008: 0x77dcb370 (ntdll.dll (.text)) -> push 8 ; push offset ntdll!QueryRegistryValue+0x13d2 (77e29538) 63 | 0x00d7ff50|+0x000c: 0x77dcb370 (ntdll.dll (.text)) -> push 8 ; push offset ntdll!QueryRegistryValue+0x13d2 (77e29538) 64 | 0x00d7ff54|+0x0010: 0x00000000 (Unknown) 65 | 0x00d7ff58|+0x0014: 0x00d7ff48 (Stack) -> 0x1911c0a3 (Unknown) 66 | 0x00d7ff5c|+0x0018: 0x00000000 (Unknown) 67 | 0x00d7ff60|+0x001c: 0x00d7ffcc (Stack) -> 0x00d7ffe4 (Stack) -> 0xffffffff (Unknown) 68 | 0x00d7ff64|+0x0020: 0x77d986d0 (ntdll.dll (.text)) -> mov edi,edi ; push ebp 69 | 0x00d7ff68|+0x0024: 0x6e24aaeb (Unknown) 70 | @$telescope(@esp) 71 | ``` 72 | 73 | * From an x64 kernel live-session 74 | 75 | ``` 76 | kd> !telescope 0xfffff8000d2dca78 77 | 0xfffff8000d2dca78|+0x0000: 0x0000000000000000 (Unknown) 78 | 0xfffff8000d2dca80|+0x0008: 0x0000000000000000 (Unknown) 79 | 0xfffff8000d2dca88|+0x0010: 0x0000000000000000 (Unknown) 80 | 0xfffff8000d2dca90|+0x0018: 0xfffff8000d03e030 (Image ntkrnlmp.exe (.text)) -> sub rsp,28h ; and qword ptr [rsp+28h],0 81 | 0xfffff8000d2dca98|+0x0020: 0x0000000000000000 (Unknown) 82 | 0xfffff8000d2dcaa0|+0x0028: 0x0000000000000000 (Unknown) 83 | 0xfffff8000d2dcaa8|+0x0030: 0xfffff8000d2d9e48 (Image ntkrnlmp.exe (CACHEALI)) -> 0xfffff8000d2dcaa8 (Image ntkrnlmp.exe (CACHEALI)) [...] 84 | 0xfffff8000d2dcab0|+0x0038: 0xfffff8000d2d9e48 (Image ntkrnlmp.exe (CACHEALI)) -> 0xfffff8000d2dcaa8 (Image ntkrnlmp.exe (CACHEALI)) -> 0xfffff8000d2d9e48 (Image ntkrnlmp.exe (CACHEALI)) [...] 85 | 0xfffff8000d2dcab8|+0x0040: 0x0000000000000000 (Unknown) 86 | 0xfffff8000d2dcac0|+0x0048: 0x0000000000000000 (Unknown) 87 | @$telescope(0xfffff8000d2dca78) 88 | ``` 89 | -------------------------------------------------------------------------------- /gdt/README.md: -------------------------------------------------------------------------------- 1 | # gdt.js 2 | 3 | `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. 4 | 5 | 6 | ## Usage 7 | 8 | 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. 9 | 10 | ## Examples 11 | 12 | * Dumping the GDT entry that enables [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details): 13 | ``` 14 | 32.kd> !gdt @cs 15 | dt nt!_KGDTENTRY64 0xfffff8045215dfd0 16 | Base: [0x0 -> 0xffffffff] 17 | Type: Code Execute/Read Accessed (0xb) 18 | DPL: 0x3 19 | Present: 0x1 20 | Mode: 32b Compat 21 | @$gdt(@cs) 22 | 23 | 32.kd> dg @cs 24 | P Si Gr Pr Lo 25 | Sel Base Limit Type l ze an es ng Flags 26 | ---- ----------------- ----------------- ---------- - -- -- -- -- -------- 27 | 0023 00000000`00000000 00000000`ffffffff Code RE Ac 3 Bg Pg P Nl 00000cfb 28 | ``` 29 | 30 | * Dumping the GDT entry that allows 32-bit code to invoke 64-bit code: 31 | ``` 32 | 32.kd> !gdt 0x33 33 | dt nt!_KGDTENTRY64 0xfffff8045215dfe0 34 | Base: [0x0 -> 0x0] 35 | Type: Code Execute/Read Accessed (0xb) 36 | DPL: 0x3 37 | Present: 0x1 38 | Mode: 64b 39 | @$gdt(0x33) 40 | 41 | 32.kd> dg 33 42 | P Si Gr Pr Lo 43 | Sel Base Limit Type l ze an es ng Flags 44 | ---- ----------------- ----------------- ---------- - -- -- -- -- -------- 45 | 0033 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P Lo 000002fb 46 | ``` 47 | 48 | * Dumping the [Task State Segment](https://wiki.osdev.org/Task_State_Segment): 49 | ``` 50 | 32.kd> !gdt @tr 51 | dt nt!_KGDTENTRY64 0xfffff8045215dff0 52 | Base: [0xfffff8045215c000 -> 0xfffff8045215c067] 53 | Type: TSS64 Busy (0xb) 54 | DPL: 0x0 55 | Present: 0x1 56 | @$gdt(@tr) 57 | 58 | 32.kd> dg @tr 59 | P Si Gr Pr Lo 60 | Sel Base Limit Type l ze an es ng Flags 61 | ---- ----------------- ----------------- ---------- - -- -- -- -- -------- 62 | 0040 00000000`5215c000 00000000`00000067 TSS32 Busy 0 Nb By P Nl 0000008b 63 | ``` 64 | 65 | * 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: 66 | ``` 67 | 32.kd> !gdt @fs 68 | dt nt!_KGDTENTRY64 0xfffff8045215e000 69 | Base: [0x326000 -> 0x329c00] 70 | Type: Data Read/Write Accessed (0x3) 71 | DPL: 0x3 72 | Present: 0x1 73 | @$gdt(@fs) 74 | 75 | 32.kd> !teb 76 | Wow64 TEB32 at 0000000000326000 77 | ExceptionList: 00000000004ff59c 78 | StackBase: 0000000000500000 79 | StackLimit: 00000000004f2000 80 | SubSystemTib: 0000000000000000 81 | FiberData: 0000000000001e00 82 | ArbitraryUserPointer: 0000000000000000 83 | Self: 0000000000326000 84 | EnvironmentPointer: 0000000000000000 85 | ClientId: 0000000000001ad8 . 0000000000001adc 86 | RpcHandle: 0000000000000000 87 | Tls Storage: 0000000000834188 88 | PEB Address: 0000000000323000 89 | LastErrorValue: 0 90 | LastStatusValue: c000007c 91 | Count Owned Locks: 0 92 | HardErrorMode: 0 93 | ``` 94 | * Dumping the entire GDT on a Windows 10 64-bit Virtual Machine: 95 | ``` 96 | 32.kd> !gdt 97 | Dumping the GDT from 0xfffff8045215dfb0 to 0xfffff8045215e007.. 98 | [0]: dt nt!_KGDTENTRY64 0xfffff8045215dfb0 99 | Base: [0x0 -> 0x0] 100 | Type: Reserved (0x0) 101 | DPL: 0x0 102 | Present: 0x0 103 | [1]: dt nt!_KGDTENTRY64 0xfffff8045215dfb8 104 | Base: [0x0 -> 0x0] 105 | Type: Reserved (0x0) 106 | DPL: 0x0 107 | Present: 0x0 108 | [2]: dt nt!_KGDTENTRY64 0xfffff8045215dfc0 109 | Base: [0x0 -> 0x0] 110 | Type: Code Execute/Read Accessed (0xb) 111 | DPL: 0x0 112 | Present: 0x1 113 | Mode: 64b 114 | [3]: dt nt!_KGDTENTRY64 0xfffff8045215dfc8 115 | Base: [0x0 -> 0x0] 116 | Type: Data Read/Write Accessed (0x3) 117 | DPL: 0x0 118 | Present: 0x1 119 | [4]: dt nt!_KGDTENTRY64 0xfffff8045215dfd0 120 | Base: [0x0 -> 0xffffffff] 121 | Type: Code Execute/Read Accessed (0xb) 122 | DPL: 0x3 123 | Present: 0x1 124 | Mode: 32b Compat 125 | [5]: dt nt!_KGDTENTRY64 0xfffff8045215dfd8 126 | Base: [0x0 -> 0xffffffff] 127 | Type: Data Read/Write Accessed (0x3) 128 | DPL: 0x3 129 | Present: 0x1 130 | [6]: dt nt!_KGDTENTRY64 0xfffff8045215dfe0 131 | Base: [0x0 -> 0x0] 132 | Type: Code Execute/Read Accessed (0xb) 133 | DPL: 0x3 134 | Present: 0x1 135 | Mode: 64b 136 | [7]: dt nt!_KGDTENTRY64 0xfffff8045215dfe8 137 | Base: [0x0 -> 0x0] 138 | Type: Reserved (0x0) 139 | DPL: 0x0 140 | Present: 0x0 141 | [8]: dt nt!_KGDTENTRY64 0xfffff8045215dff0 142 | Base: [0xfffff8045215c000 -> 0xfffff8045215c067] 143 | Type: TSS64 Busy (0xb) 144 | DPL: 0x0 145 | Present: 0x1 146 | [9]: dt nt!_KGDTENTRY64 0xfffff8045215e000 147 | Base: [0x326000 -> 0x329c00] 148 | Type: Data Read/Write Accessed (0x3) 149 | DPL: 0x3 150 | Present: 0x1 151 | @$gdt() 152 | ``` -------------------------------------------------------------------------------- /gdt/gdt.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - October 8 2021 2 | 3 | 'use strict'; 4 | 5 | // 6 | // Small reminders from the manual intels on segments in x64: 7 | // - Because ES, DS, and SS segment registers are not used in 64-bit mode, their fields (base, limit, and attribute) in 8 | // segment descriptor registers are ignored. 9 | // - Selector.Index selects one of 8192 descriptors in the GDT or LDT. The processor multiplies 10 | // the index value by 8 (the number of bytes in a segment descriptor) and adds the result to the base 11 | // address of the GDT or LDT (from the GDTR or LDTR register, respectively). 12 | // - The first entry of the GDT is not used by the processor. 13 | // - The hidden descriptor register fields for FS.base and GS.base are physically mapped to MSRs in order to load all 14 | // address bits supported by a 64-bit implementation. Software with CPL = 0 (privileged software) can load all 15 | // supported linear-address bits into FS.base or GS.base using WRMSR. 16 | // 17 | 18 | const log = host.diagnostics.debugLog; 19 | const logln = p => log(`${p}\n`); 20 | const hex = p => `0x${p.toString(16)}`; 21 | 22 | const GdtSystemEntryTypes = new Map([ 23 | [0, 'Reserved'], 24 | [1, 'Reserved'], 25 | [2, 'LDT'], 26 | [3, 'Reserved'], 27 | [4, 'Reserved'], 28 | [5, 'Reserved'], 29 | [6, 'Reserved'], 30 | [7, 'Reserved'], 31 | [8, 'Reserved'], 32 | [9, 'TSS64 Available'], 33 | [10, 'Reserved'], 34 | [11, 'TSS64 Busy'], 35 | [12, 'CallGate64'], 36 | [13, 'Reserved'], 37 | [14, 'InterruptGate64'], 38 | [15, 'TrapGate64'], 39 | ]); 40 | 41 | const GdtNonSystemEntryTypes = new Map([ 42 | [0, 'Data Read-Only'], 43 | [1, 'Data Read-Only Accessed'], 44 | [2, 'Data Read/Write'], 45 | [3, 'Data Read/Write Accessed'], 46 | [4, 'Data Read-Only Expand-Down'], 47 | [5, 'Data Read-Only Expand-Down Accessed'], 48 | [6, 'Data Read/Write Expand-Down'], 49 | [7, 'Data Read/Write Expand-Down Accessed'], 50 | [8, 'Code Execute-Only'], 51 | [9, 'Code Execute-Only Accessed'], 52 | [10, 'Code Execute/Read'], 53 | [11, 'Code Execute/Read Accessed'], 54 | [12, 'Code Execute-Only Conforming'], 55 | [13, 'Code Execute-Only Conforming Accessed'], 56 | [14, 'Code Execute/Read Conforming'], 57 | [15, 'Code Execute/Read Conforming Accessed'], 58 | ]); 59 | 60 | const GdtEntryTypes = new Map([ 61 | [0, GdtSystemEntryTypes], 62 | [1, GdtNonSystemEntryTypes] 63 | ]); 64 | 65 | class GdtEntry { 66 | constructor(Addr) { 67 | this._Addr = Addr; 68 | const Entry = host.createPointerObject(Addr, 'nt', '_KGDTENTRY64*'); 69 | const LimitHigh = Entry.Bits.LimitHigh.bitwiseShiftLeft(16); 70 | const LimitLow = Entry.LimitLow; 71 | this._Limit = LimitHigh.add(LimitLow); 72 | // For whatever reason _KGDTENTRY64 is 5 bits long. The intel manuals describes 73 | // it as 4bits and the 'Descriptor type' bit. 74 | // We grab the lower 4 bits as the type, and the MSB as the 'Descriptor type'. 75 | this._Type = Entry.Bits.Type & 15; 76 | this._NonSystem = (Entry.Bits.Type >> 4) & 1; 77 | this._TypeS = GdtEntryTypes.get(this._NonSystem).get(this._Type); 78 | // Note that system descriptors in IA-32e mode are 16 bytes instead 79 | // of 8 bytes. 80 | this._Size = 8; 81 | if (!this._NonSystem && this._TypeS != 'Reserved') { 82 | this._Size = 16; 83 | } 84 | this._Dpl = Entry.Bits.Dpl; 85 | this._Granularity = Entry.Bits.Granularity; 86 | this._Present = Entry.Bits.Present; 87 | this._LongMode = Entry.Bits.LongMode; 88 | this._DefaultBig = Entry.Bits.DefaultBig; 89 | const BaseUpper = this._Size == 8 ? 0 : Entry.BaseUpper.bitwiseShiftLeft(32); 90 | const BaseHigh = Entry.Bytes.BaseHigh.bitwiseShiftLeft(24); 91 | const BaseMiddle = Entry.Bytes.BaseMiddle.bitwiseShiftLeft(16); 92 | const BaseLow = Entry.BaseLow; 93 | this._Base = BaseUpper.add(BaseHigh).add(BaseMiddle).add(BaseLow); 94 | const Flags1 = Entry.Bytes.Flags1; 95 | const Flags2 = Entry.Bytes.Flags2.bitwiseShiftLeft(8); 96 | this._Attrs = Flags2.add(Flags1); 97 | } 98 | 99 | toString() { 100 | const Increments = this._Granularity == 1 ? 1024 * 4 : 1; 101 | // For example, when the granularity flag is set, a limit of 0 results in 102 | // valid offsets from 0 to 4095. 103 | const Size = this._Limit * Increments + (this._Granularity ? 0xfff : 0); 104 | let S = `dt nt!_KGDTENTRY64 ${hex(this._Addr)} 105 | Base: [${hex(this._Base)} -> ${hex(this._Base.add(Size))}] 106 | Type: ${this._TypeS} (${hex(this._Type)}) 107 | DPL: ${hex(this._Dpl)} 108 | Present: ${hex(this._Present)} 109 | Atributes: ${hex(this._Attrs)}`; 110 | if (this._TypeS.startsWith('Code')) { 111 | S += ` 112 | Mode: ${this._LongMode ? '64b' : (this._DefaultBig ? '32b Compat' : '16b Compat')}` 113 | } 114 | return S; 115 | } 116 | } 117 | 118 | function GetGdt() { 119 | const Control = host.namespace.Debugger.Utility.Control; 120 | const [_, GdtrValue] = Control.ExecuteCommand('r @gdtr').First().split('='); 121 | const [__, GdtlValue] = Control.ExecuteCommand('r @gdtl').First().split('='); 122 | return [host.parseInt64(GdtrValue, 16), host.parseInt64(GdtlValue, 16)]; 123 | } 124 | 125 | function DumpGdtEntry(Addr) { 126 | return new GdtEntry(Addr); 127 | } 128 | 129 | function DumpAllGdt() { 130 | const [GdtBase, Gdtl] = GetGdt(); 131 | const GdtEnd = GdtBase.add(Gdtl); 132 | logln(`Dumping the GDT from ${hex(GdtBase)} to ${hex(GdtEnd)}..`); 133 | for (let CurrentEntry = GdtBase, Idx = 0; 134 | CurrentEntry.compareTo(GdtEnd) < 0; 135 | Idx++) { 136 | const Entry = DumpGdtEntry(CurrentEntry); 137 | logln(`[${Idx}]: ${Entry}`); 138 | CurrentEntry = CurrentEntry.add(Entry._Size); 139 | } 140 | } 141 | 142 | function DumpGdt(Selector) { 143 | const [GdtBase, _] = GetGdt(); 144 | const Index = Selector.bitwiseShiftRight(3); 145 | const Offset = Index.multiply(8); 146 | const EntryAddress = GdtBase.add(Offset); 147 | const Entry = DumpGdtEntry(EntryAddress); 148 | logln(Entry); 149 | } 150 | 151 | function Gdt(Selector) { 152 | const Attributes = host.currentSession.Attributes; 153 | const IsKernel = Attributes.Target.IsKernelTarget; 154 | // 155 | // XXX: Not sure how to do this better? 156 | // Attributes.Machine.PointerSize is 4 when running in a Wow64 thread :-/. 157 | // 158 | let Is64Bit = true; 159 | try { host.createPointerObject(0, 'nt', '_KGDTENTRY64*'); } catch(e) { Is64Bit = false; } 160 | if (!IsKernel || !Is64Bit) { 161 | logln('The running session is not a kernel session or it is not running a 64-bit OS, so exiting'); 162 | return; 163 | } 164 | 165 | if (Selector == undefined) { 166 | DumpAllGdt(); 167 | } else { 168 | DumpGdt(Selector); 169 | } 170 | } 171 | 172 | function initializeScript() { 173 | return [ 174 | new host.apiVersionSupport(1, 3), 175 | new host.functionAlias( 176 | Gdt, 177 | 'gdt' 178 | ), 179 | ]; 180 | } 181 | -------------------------------------------------------------------------------- /policybuffer/policybuffer.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - 5 March 2019 2 | // sandbox::PolicyBase::EvalPolicy 3 | 4 | 'use strict'; 5 | 6 | // 7 | // Utility functions. 8 | // 9 | 10 | const Log = host.diagnostics.debugLog; 11 | const Logln = p => host.diagnostics.debugLog(p + '\n'); 12 | const Hex = p => '0x' + p.toString(16); 13 | const ReadWstring = p => host.memory.readWideString(p); 14 | 15 | function ReadShort(Address) { 16 | let Value = null; 17 | try { 18 | Value = host.memory.readMemoryValues( 19 | Address, 1, 2 20 | )[0]; 21 | } catch(e) { 22 | } 23 | 24 | return Value; 25 | } 26 | 27 | function ReadDword(Address) { 28 | let Value = null; 29 | try { 30 | Value = host.memory.readMemoryValues( 31 | Address, 1, 4 32 | )[0]; 33 | } catch(e) { 34 | } 35 | 36 | return Value; 37 | } 38 | 39 | function ReadQword(Address) { 40 | let Value = null; 41 | try { 42 | Value = host.memory.readMemoryValues( 43 | Address, 1, 8 44 | )[0]; 45 | } catch(e) { 46 | } 47 | 48 | return Value; 49 | } 50 | 51 | function initializeScript() { 52 | return [ 53 | new host.apiVersionSupport(1, 3) 54 | ]; 55 | } 56 | 57 | // 58 | // Constants. 59 | // 60 | 61 | // The low-level policy is implemented using the concept of policy 'opcodes'. 62 | // An opcode is a structure that contains enough information to perform one 63 | // comparison against one single input parameter. For example, an opcode can 64 | // encode just one of the following comparison: 65 | // 66 | // - Is input parameter 3 not equal to NULL? 67 | // - Does input parameter 2 start with L"c:\\"? 68 | // - Is input parameter 5, bit 3 is equal 1? 69 | // 70 | // Each opcode is in fact equivalent to a function invocation where all 71 | // the parameters are known by the opcode except one. So say you have a 72 | // function of this form: 73 | // bool fn(a, b, c, d) with 4 arguments 74 | // 75 | // Then an opcode is: 76 | // op(fn, b, c, d) 77 | // Which stores the function to call and its 3 last arguments 78 | // 79 | // Then and opcode evaluation is: 80 | // op.eval(a) ------------------------> fn(a,b,c,d) 81 | // internally calls 82 | // 83 | // The idea is that complex policy rules can be split into streams of 84 | // opcodes which are evaluated in sequence. The evaluation is done in 85 | // groups of opcodes that have N comparison opcodes plus 1 action opcode: 86 | // 87 | // [comparison 1][comparison 2]...[comparison N][action][comparison 1]... 88 | // ----- evaluation order-----------> 89 | // 90 | // Each opcode group encodes one high-level policy rule. The rule applies 91 | // only if all the conditions on the group evaluate to true. The action 92 | // opcode contains the policy outcome for that particular rule. 93 | 94 | // https://dxr.mozilla.org/mozilla-central/source/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h#77 95 | // The following are the implemented opcodes. 96 | // enum OpcodeID { 97 | // OP_ALWAYS_FALSE, // Evaluates to false (EVAL_FALSE). 98 | // OP_ALWAYS_TRUE, // Evaluates to true (EVAL_TRUE). 99 | // OP_NUMBER_MATCH, // Match a 32-bit integer as n == a. 100 | // OP_NUMBER_MATCH_RANGE, // Match a 32-bit integer as a <= n <= b. 101 | // OP_NUMBER_AND_MATCH, // Match using bitwise AND; as in: n & a != 0. 102 | // OP_WSTRING_MATCH, // Match a string for equality. 103 | // OP_ACTION // Evaluates to an action opcode. 104 | // }; 105 | 106 | const OP_ALWAYS_FALSE = 0; 107 | const OP_ALWAYS_TRUE = 1; 108 | const OP_NUMBER_MATCH = 2; 109 | const OP_NUMBER_MATCH_RANGE = 3; 110 | const OP_NUMBER_AND_MATCH = 4; 111 | const OP_WSTRING_MATCH = 5; 112 | const OP_ACTION = 6; 113 | 114 | const Opcodes = { 115 | [OP_ALWAYS_FALSE] : 'OP_ALWAYS_FALSE', 116 | [OP_ALWAYS_TRUE] : 'OP_ALWAYS_TRUE', 117 | [OP_NUMBER_MATCH] : 'OP_NUMBER_MATCH', 118 | [OP_NUMBER_MATCH_RANGE] : 'OP_NUMBER_MATCH_RANGE', 119 | [OP_NUMBER_AND_MATCH] : 'OP_NUMBER_AND_MATCH', 120 | [OP_WSTRING_MATCH] : 'OP_WSTRING_MATCH', 121 | [OP_ACTION] : 'OP_ACTION' 122 | }; 123 | 124 | // https://dxr.mozilla.org/mozilla-central/source/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h 125 | // enum StringMatchOptions { 126 | // CASE_SENSITIVE = 0, // Pay or Not attention to the case as defined by 127 | // CASE_INSENSITIVE = 1, // RtlCompareUnicodeString windows API. 128 | // EXACT_LENGHT = 2 // Don't do substring match. Do full string match. 129 | // }; 130 | 131 | const MatchingOptions = { 132 | 0 : 'CASE_SENSITIVE', 133 | 1 : 'CASE_INSENSITIVE', 134 | 2 : 'EXACT_LENGTH', 135 | 3 : 'EXACT_LENGTH | CASE_INSENSITIVE' 136 | }; 137 | 138 | // These are the possible policy outcomes. Note that some of them might 139 | // not apply and can be removed. Also note that The following values only 140 | // specify what to do, not how to do it and it is acceptable given specific 141 | // cases to ignore the policy outcome. 142 | // enum EvalResult { 143 | // // Comparison opcode values: 144 | // EVAL_TRUE, // Opcode condition evaluated true. 145 | // EVAL_FALSE, // Opcode condition evaluated false. 146 | // EVAL_ERROR, // Opcode condition generated an error while evaluating. 147 | // // Action opcode values: 148 | // ASK_BROKER, // The target must generate an IPC to the broker. On the broker 149 | // // side, this means grant access to the resource. 150 | // DENY_ACCESS, // No access granted to the resource. 151 | // GIVE_READONLY, // Give readonly access to the resource. 152 | // GIVE_ALLACCESS, // Give full access to the resource. 153 | // GIVE_CACHED, // IPC is not required. Target can return a cached handle. 154 | // GIVE_FIRST, // TODO(cpu) 155 | // SIGNAL_ALARM, // Unusual activity. Generate an alarm. 156 | // FAKE_SUCCESS, // Do not call original function. Just return 'success'. 157 | // FAKE_ACCESS_DENIED, // Do not call original function. Just return 'denied' 158 | // // and do not do IPC. 159 | // TERMINATE_PROCESS, // Destroy target process. Do IPC as well. 160 | // }; 161 | 162 | const Actions = { 163 | 3 : 'ASK_BROKER', 164 | 4 : 'DENY_ACCESS', 165 | 5 : 'GIVE_READONLY', 166 | 6 : 'GIVE_ALLACCESS', 167 | 7 : 'GIVE_CACHED', 168 | 8 : 'GIVE_FIRST', 169 | 9 : 'SIGNAL_ALARM', 170 | 10 : 'FAKE_SUCCESS', 171 | 11 : 'FACE_ACCESS_DENIED', 172 | 12 : 'TERMINATE_PROCESS' 173 | }; 174 | 175 | // https://dxr.mozilla.org/mozilla-central/source/security/sandbox/chromium/sandbox/win/src/internal_types.h#19 176 | // enum ArgType { 177 | // INVALID_TYPE = 0, 178 | // WCHAR_TYPE, 179 | // UINT32_TYPE, 180 | // UNISTR_TYPE, 181 | // VOIDPTR_TYPE, 182 | // INPTR_TYPE, 183 | // INOUTPTR_TYPE, 184 | // LAST_TYPE 185 | // }; 186 | 187 | const ArgTypes = { 188 | 0 : 'INVALID_TYPE', 189 | 1 : 'WCHAR_TYPE', 190 | 2 : 'UINT32_TYPE', 191 | 3 : 'UNISTR_TYPE', 192 | 4 : 'VOIDPTR_TYPE', 193 | 5 : 'INPTR_TYPE', 194 | 6 : 'INOUTPTR_TYPE', 195 | 7 : 'LAST_TYPE' 196 | }; 197 | 198 | // Options that apply to every opcode. They are specified when creating 199 | // each opcode using OpcodeFactory::MakeOpXXXXX() family of functions 200 | // Do nothing special. 201 | const kPolNone = 0; 202 | 203 | // Convert EVAL_TRUE into EVAL_FALSE and vice-versa. This allows to express 204 | // negated conditions such as if ( a && !b). 205 | const kPolNegateEval = 1; 206 | 207 | // Zero the MatchContext context structure. This happens after the opcode 208 | // is evaluated. 209 | const kPolClearContext = 2; 210 | 211 | // Use OR when evaluating this set of opcodes. The policy evaluator by default 212 | // uses AND when evaluating. Very helpful when 213 | // used with kPolNegateEval. For example if you have a condition best expressed 214 | // as if(! (a && b && c)), the use of this flags allows it to be expressed as 215 | // if ((!a) || (!b) || (!c)). 216 | const kPolUseOREval = 4; 217 | 218 | // https://dxr.mozilla.org/mozilla-central/source/security/sandbox/chromium/sandbox/win/src/policy_params.h#36 219 | const ParameterNames = { 220 | 'OpenFile' : [ 221 | 'NAME', 'BROKER', 'ACCESS', 'DISPOSITION', 'OPTIONS' 222 | ], 223 | }; 224 | 225 | // 226 | // Code. 227 | // 228 | 229 | function DisassPolicyBuffer(PolicyBufferAddress, PolicyType) { 230 | let Ptr = PolicyBufferAddress; 231 | const PolicyBufferOpcodeCount = ReadQword(Ptr); 232 | Ptr += 8; 233 | for(let Idx = 0; Idx < PolicyBufferOpcodeCount; ++Idx) { 234 | 235 | // 236 | // Save off the current pointer as it is useful to compute 237 | // where the stored string is in memory for the OP_WSTRING_MATCH 238 | // opcode. 239 | // 240 | 241 | const OpcodePtr = Ptr; 242 | 243 | // 244 | // Unpack the opcode structure. 245 | // 246 | 247 | const OpcodeId = ReadDword(Ptr); 248 | Ptr += 4; 249 | const SelectedParameter = ReadShort(Ptr); 250 | Ptr += 2; 251 | const Options = ReadShort(Ptr); 252 | Ptr += 2; 253 | const Parameters = []; 254 | for(let InnerIdx = 0; InnerIdx < 4; ++InnerIdx) { 255 | Parameters.push(ReadQword(Ptr)); 256 | Ptr += 8; 257 | } 258 | 259 | // 260 | // Once we dumped the opcode, let's prettify its parameters. 261 | // 262 | 263 | const Operands = []; 264 | let FirstOperand = 'Param' + SelectedParameter; 265 | if(ParameterNames[PolicyType] != undefined) { 266 | FirstOperand = PolicyType + '::' + ParameterNames[PolicyType][SelectedParameter]; 267 | } 268 | 269 | Operands.push(FirstOperand); 270 | if(OpcodeId == OP_ALWAYS_TRUE || OpcodeId == OP_ALWAYS_FALSE) { 271 | } else if(OpcodeId == OP_NUMBER_MATCH) { 272 | const ArgType = ArgTypes[Parameters[1].asNumber()]; 273 | Operands.push(ArgType + '(' + Hex(Parameters[0]) + ')'); 274 | } else if(OpcodeId == OP_NUMBER_MATCH_RANGE) { 275 | Operands.push('LowerBound(' + Hex(Parameters[0]) + ')'); 276 | Operands.push('UpperBound(' + Hex(Parameters[1]) + ')'); 277 | } else if(OpcodeId == OP_NUMBER_AND_MATCH) { 278 | Operands.push(Hex(Parameters[0])); 279 | }else if(OpcodeId == OP_WSTRING_MATCH) { 280 | const Displacement = Parameters[0]; 281 | const StringAddress = OpcodePtr.add(Displacement); 282 | Operands.push('"' + ReadWstring(StringAddress) + '"'); 283 | Operands.push('Length(' + Hex(Parameters[1]) + ')'); 284 | Operands.push('Offset(' + Hex(Parameters[2]) + ')'); 285 | const MatchingOption = Parameters[3].asNumber(); 286 | Operands.push(MatchingOptions[MatchingOption]); 287 | } else if(OpcodeId == OP_ACTION) { 288 | 289 | // 290 | // The OP_ACTION is the only opcode that does not need a selected 291 | // parameter. 292 | // 293 | 294 | const Action = Actions[Parameters[0].asNumber()]; 295 | Operands[0] = Action; 296 | } 297 | 298 | // 299 | // Display the opcode and its operands. 300 | // 301 | 302 | const OpcodeIdStr = Opcodes[OpcodeId]; 303 | if(Options.bitwiseAnd(kPolNegateEval).compareTo(0) != 0) { 304 | Logln('!' + OpcodeIdStr + '<' + Operands.join(', ') + '>'); 305 | } else { 306 | Logln(OpcodeIdStr + '<' + Operands.join(', ') + '>'); 307 | } 308 | 309 | if(OpcodeId == OP_ACTION) { 310 | Logln(''); 311 | } 312 | } 313 | } 314 | 315 | function initializeScript() { 316 | return [ 317 | new host.apiVersionSupport(1, 3), 318 | new host.functionAlias( 319 | DisassPolicyBuffer, 320 | 'disasspolicy' 321 | ) 322 | ]; 323 | } 324 | 325 | -------------------------------------------------------------------------------- /parse_eh_win64/parse_eh_win64.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - Dec 2017 2 | 3 | "use strict"; 4 | 5 | let log = host.diagnostics.debugLog; 6 | let logln = function (e) { 7 | host.diagnostics.debugLog(e + '\n'); 8 | }; 9 | 10 | function read_u32(addr) { 11 | return host.memory.readMemoryValues(addr, 1, 4)[0]; 12 | } 13 | 14 | function read_u8(addr) { 15 | return host.memory.readMemoryValues(addr, 1, 1)[0]; 16 | } 17 | 18 | class ScopeRecord { 19 | // 0:000> dt SCOPE_RECORD 20 | // +0x000 BeginAddress : Uint4B 21 | // +0x004 EndAddress : Uint4B 22 | // +0x008 HandlerAddress : Uint4B 23 | // +0x00c JumpTarget : Uint4B 24 | constructor(ScopeRecordAddress) { 25 | this.Begin = read_u32(ScopeRecordAddress); 26 | this.End = read_u32(ScopeRecordAddress.add(0x4)); 27 | this.HandlerAddress = read_u32(ScopeRecordAddress.add(0x8)); 28 | this.JumpTarget = read_u32(ScopeRecordAddress.add(0xc)); 29 | } 30 | 31 | get IsTryFinally() { 32 | return this.JumpTarget.compareTo(0) == 0; 33 | } 34 | 35 | get HasFilter() { 36 | return this.IsTryFinally == false && 37 | this.HandlerAddress.compareTo(1) != 0; 38 | } 39 | 40 | InBound(Begin, End) { 41 | return this.Begin.compareTo(Begin) >= 0 && 42 | this.End.compareTo(End) < 0; 43 | } 44 | 45 | toString() { 46 | let S = ' __try {' 47 | S += this.Begin.toString(16) + ' -> ' + this.End.toString(16); 48 | S += '}'; 49 | if (this.IsTryFinally == true) { 50 | S += ' __finally {'; 51 | S += this.HandlerAddress.toString(16); 52 | } else { 53 | S += ' __except('; 54 | if (this.HasFilter == false) { 55 | S += 'EXCEPTION_EXECUTE_HANDLER'; 56 | } else { 57 | S += this.HandlerAddress.toString(16) + '()'; 58 | } 59 | S += ') {'; 60 | S += this.JumpTarget.toString(16); 61 | } 62 | S += '}'; 63 | return S; 64 | } 65 | } 66 | 67 | class Function { 68 | constructor(BaseAddress, EHHandler, Begin, End) { 69 | this.__BaseAddress = BaseAddress; 70 | this.EHHandlerRVA = EHHandler; 71 | this.EHHandler = BaseAddress.add(EHHandler); 72 | this.BeginRVA = Begin; 73 | this.EndRVA = End; 74 | this.Begin = BaseAddress.add(Begin); 75 | this.End = BaseAddress.add(End); 76 | this.ExceptionHandlers = []; 77 | } 78 | 79 | SetRecords(Records, KeepRVA = false) { 80 | this.ExceptionHandlers = Records; 81 | if (KeepRVA) { 82 | return; 83 | } 84 | 85 | for (let ExceptionHandler of this.ExceptionHandlers) { 86 | ExceptionHandler.Begin = ExceptionHandler.Begin.add(this.__BaseAddress); 87 | ExceptionHandler.End = ExceptionHandler.End.add(this.__BaseAddress); 88 | if (ExceptionHandler.JumpTarget.compareTo(0) != 0) { 89 | ExceptionHandler.JumpTarget = ExceptionHandler.JumpTarget.add(this.__BaseAddress); 90 | } 91 | if (ExceptionHandler.HandlerAddress.compareTo(1) != 0) { 92 | ExceptionHandler.HandlerAddress = ExceptionHandler.HandlerAddress.add(this.__BaseAddress); 93 | } 94 | } 95 | } 96 | 97 | toString() { 98 | let S = 'RVA:' + this.Begin.toString(16) + ' -> RVA:' + this.End.toString(16); 99 | S += ', ' + this.ExceptionHandlers.length + ' exception handlers'; 100 | return S; 101 | } 102 | } 103 | 104 | function ParseCSpecificHandlerDatas( 105 | ScopeCount, 106 | ScopeRecords, 107 | Function 108 | ) { 109 | // 0:000> ?? sizeof(SCOPE_RECORD) 110 | // unsigned int64 0x10 111 | let Records = []; 112 | let ScopeSize = ScopeCount.multiply(0x10); 113 | for (let i = 0; i < ScopeSize; i += 0x10) { 114 | let CurrentScope = ScopeRecords.add(i); 115 | let Record = new ScopeRecord(CurrentScope); 116 | if (Record.InBound(Function.BeginRVA, Function.EndRVA) == false) { 117 | return []; 118 | } 119 | Records.push(Record); 120 | } 121 | return Records; 122 | } 123 | 124 | function ExtractExceptionHandlersForModule( 125 | BaseAddress, 126 | KeepRVA = false 127 | ) { 128 | let EHANDLER = 1; 129 | let IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3; 130 | 131 | // 0:000> dt _IMAGE_DOS_HEADER e_lfanew 132 | // +0x03c e_lfanew : Int4B 133 | let NtHeaders = BaseAddress.add(read_u32(BaseAddress.add(0x3c))); 134 | 135 | // 0:000> dt _IMAGE_NT_HEADERS64 OptionalHeader 136 | // +0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER64 137 | // 0:000> dt _IMAGE_OPTIONAL_HEADER64 DataDirectory 138 | // +0x070 DataDirectory : [16] _IMAGE_DATA_DIRECTORY 139 | // 0:000> dt _IMAGE_DATA_DIRECTORY 140 | // +0x000 VirtualAddress : Uint4B 141 | // +0x004 Size : Uint4B 142 | let EntryExceptionDirectory = NtHeaders.add(0x18 + 0x70 + (IMAGE_DIRECTORY_ENTRY_EXCEPTION * 8)); 143 | let RuntimeFunctionEntry = BaseAddress.add(read_u32(EntryExceptionDirectory)); 144 | let SizeOfDirectory = read_u32(EntryExceptionDirectory.add(4)); 145 | let Functions = []; 146 | 147 | for (let i = 0; i < SizeOfDirectory; i += 0xC) { 148 | // 0:000> dt _IMAGE_RUNTIME_FUNCTION_ENTRY 149 | // +0x000 BeginAddress : Uint4B 150 | // +0x004 EndAddress : Uint4B 151 | // +0x008 UnwindInfoAddress : Uint4B 152 | // +0x008 UnwindData : Uint4B 153 | // 0:000> ?? sizeof(_IMAGE_RUNTIME_FUNCTION_ENTRY) 154 | // unsigned int64 0xc 155 | let CurrentEntry = RuntimeFunctionEntry.add(i); 156 | let BeginAddress = read_u32(CurrentEntry); 157 | let EndAddress = read_u32(CurrentEntry.add(4)); 158 | 159 | if (BeginAddress.compareTo(0) == 0 || EndAddress.compareTo(0) == 0) { 160 | continue; 161 | } 162 | 163 | // 0:000> dt UNWIND_INFO 164 | // +0x000 Version : Pos 0, 3 Bits 165 | // +0x000 Flags : Pos 3, 5 Bits 166 | // +0x001 SizeOfProlog : UChar 167 | // +0x002 CountOfCodes : UChar 168 | // +0x003 FrameRegister : Pos 0, 4 Bits 169 | // +0x003 FrameOffset : Pos 4, 4 Bits 170 | // +0x004 UnwindCode : [1] UNWIND_CODE 171 | let UnwindInfo = BaseAddress.add(read_u32(CurrentEntry.add(8))); 172 | let UnwindInfoFlags = read_u8(UnwindInfo).bitwiseShiftRight(3); 173 | if (UnwindInfoFlags.bitwiseAnd(EHANDLER).compareTo(0) == 0) { 174 | continue; 175 | } 176 | 177 | // 0:000> ?? sizeof(UNWIND_CODE) 178 | // unsigned int64 2 179 | let CountOfCodes = read_u8(UnwindInfo.add(2)); 180 | // For alignment purposes, this array will always have an even number of entries, 181 | // with the final entry potentially unused (in which case the array will be one 182 | // longer than indicated by the count of unwind codes field). 183 | let AlignedCountOfCodes = (CountOfCodes + 1) & ~1; 184 | 185 | // 0:000> dt UNWIND_INFO_END 186 | // +0x000 ExceptionHandler : Uint4B 187 | // +0x004 ExceptionData : Uint4B 188 | let UnwindInfoEnd = UnwindInfo.add(4 + (AlignedCountOfCodes * 2)); 189 | let ExceptionHandler = read_u32(UnwindInfoEnd); 190 | 191 | // 0:000> dt SEH_SCOPE_TABLE 192 | // +0x000 Count : Uint4B 193 | // +0x004 ScopeRecord : [1] SCOPE_RECORD 194 | let ScopeTable = UnwindInfoEnd.add(4); 195 | let ScopeCount = read_u32(ScopeTable); 196 | if (ScopeCount == 0) { 197 | continue; 198 | } 199 | 200 | let Records = ScopeTable.add(4); 201 | let CurrentFunction = new Function( 202 | BaseAddress, ExceptionHandler, 203 | BeginAddress, EndAddress 204 | ); 205 | Records = ParseCSpecificHandlerDatas(ScopeCount, Records, CurrentFunction); 206 | if (Records.length == 0) { 207 | continue; 208 | } 209 | 210 | CurrentFunction.SetRecords(Records, KeepRVA); 211 | Functions.push(CurrentFunction); 212 | } 213 | 214 | return Functions; 215 | } 216 | 217 | class ModelExceptionHandlers { 218 | constructor(Type, Inst) { 219 | this.__module = null; 220 | this.__process = null; 221 | if (Type == 'Module') { 222 | this.__module = Inst; 223 | } else { 224 | this.__process = Inst; 225 | } 226 | this.__handlers = null; 227 | // We do not parse the exception handlers information here 228 | // as this constructor is called everytime you do: 229 | // dx @$curprocess 230 | // so we will only parse the information when the user asked for it. 231 | } 232 | 233 | __ExceptionHandlers(Modules) { 234 | let Handlers = []; 235 | for (let Module of Modules) { 236 | let Functions = ExtractExceptionHandlersForModule(Module.BaseAddress); 237 | for (let Function of Functions) { 238 | Handlers = Handlers.concat(Function.ExceptionHandlers); 239 | } 240 | } 241 | return Handlers; 242 | } 243 | 244 | *[Symbol.iterator]() { 245 | if (this.__handlers == null) { 246 | // Only parse the infornmation once everytine we query it. 247 | let Modules = [this.__module]; 248 | if (this.__process != null) { 249 | Modules = this.__process.Modules; 250 | } 251 | this.__handlers = this.__ExceptionHandlers(Modules); 252 | } 253 | 254 | for (let Handler of this.__handlers) { 255 | yield Handler; 256 | } 257 | } 258 | 259 | toString() { 260 | return 'Exception handlers'; 261 | } 262 | } 263 | 264 | class ModelFunctions { 265 | constructor(Type, Inst) { 266 | this.__module = null; 267 | this.__process = null; 268 | if (Type == 'Module') { 269 | this.__module = Inst; 270 | } else { 271 | this.__process = Inst; 272 | } 273 | this.__functions = null; 274 | } 275 | 276 | __Functions(Modules) { 277 | let Functions = []; 278 | for (let Module of Modules) { 279 | let CurrentFunctions = ExtractExceptionHandlersForModule(Module.BaseAddress); 280 | Functions = Functions.concat(CurrentFunctions); 281 | } 282 | return Functions; 283 | } 284 | 285 | *[Symbol.iterator]() { 286 | if (this.__functions == null) { 287 | this.__functions = []; 288 | let Modules = [this.__module]; 289 | if (this.__process != null) { 290 | Modules = this.__process.Modules; 291 | } 292 | 293 | this.__functions = this.__Functions(Modules); 294 | } 295 | 296 | for (let Function of this.__functions) { 297 | yield Function; 298 | } 299 | } 300 | 301 | toString() { 302 | return 'Functions'; 303 | } 304 | } 305 | 306 | class __ProcessModelExtension { 307 | get ExceptionHandlers() { 308 | return new ModelExceptionHandlers('Process', this); 309 | } 310 | 311 | get Functions() { 312 | return new ModelFunctions('Process', this); 313 | } 314 | } 315 | 316 | class __ModuleModelExtension { 317 | get ExceptionHandlers() { 318 | return new ModelExceptionHandlers('Module', this); 319 | } 320 | 321 | get Functions() { 322 | return new ModelFunctions('Module', this); 323 | } 324 | } 325 | 326 | function BangEHHandlers() { 327 | let Control = host.namespace.Debugger.Utility.Control; 328 | let CurrentThread = host.currentThread; 329 | let CurrentProcess = host.currentProcess; 330 | let Registers = CurrentThread.Registers.User; 331 | 332 | let ReturnAddresses = [Registers.rip]; 333 | let Frames = CurrentThread.Stack.Frames; 334 | for (let Frame of Frames) { 335 | ReturnAddresses.push(Frame.Attributes.ReturnOffset); 336 | } 337 | 338 | logln(ReturnAddresses.length + ' stack frames, scanning for handlers...'); 339 | let Functions = Array.from(CurrentProcess.Functions); 340 | for (let Entry of ReturnAddresses.entries()) { 341 | let FrameNumber = host.Int64(Entry[0]); 342 | let ReturnAddress = Entry[1]; 343 | let Func = Functions.find( 344 | c => ReturnAddress.compareTo(c.Begin) >= 0 && 345 | ReturnAddress.compareTo(c.End) < 0 346 | ); 347 | 348 | if (Func == undefined) { 349 | continue; 350 | } 351 | 352 | let ExceptionHandlers = Array.from(Func.ExceptionHandlers); 353 | let ExceptionHandler = ExceptionHandlers.find( 354 | c => ReturnAddress.compareTo(c.Begin) >= 0 && 355 | ReturnAddress.compareTo(c.End) < 0 356 | ) 357 | 358 | if (ExceptionHandler == undefined) { 359 | continue; 360 | } 361 | 362 | let Filter = undefined; 363 | let EHHandler = Func.EHHandler; 364 | let Handler = ExceptionHandler.HandlerAddress; 365 | let Name = 'Finally'; 366 | if (ExceptionHandler.IsTryFinally == false) { 367 | if (ExceptionHandler.HasFilter) { 368 | Filter = ExceptionHandler.HandlerAddress; 369 | } 370 | Handler = ExceptionHandler.JumpTarget; 371 | Name = ' Except'; 372 | } 373 | 374 | let FormatAddress = function (Handler) { 375 | let S = Handler.toString(16) + ': '; 376 | S += Control.ExecuteCommand( 377 | 'u ' + Handler.toString(16) + ' l1' 378 | ).First(); 379 | return S; 380 | } 381 | 382 | logln('Frame ' + FrameNumber.toString(16) + ': EHHandler: ' + FormatAddress(EHHandler)); 383 | logln(' ' + Name + ': ' + FormatAddress(Handler)); 384 | if (Filter != undefined) { 385 | logln(' Filter: ' + FormatAddress(Filter)); 386 | } 387 | } 388 | } 389 | 390 | function initializeScript() { 391 | return [ 392 | new host.namedModelParent( 393 | __ProcessModelExtension, 394 | 'Debugger.Models.Process' 395 | ), 396 | new host.namedModelParent( 397 | __ModuleModelExtension, 398 | 'Debugger.Models.Module' 399 | ), 400 | new host.functionAlias( 401 | BangEHHandlers, 402 | 'ehhandlers' 403 | ) 404 | ]; 405 | } 406 | -------------------------------------------------------------------------------- /telescope/telescope.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - 7th December 2018 2 | 3 | 'use strict'; 4 | 5 | const log = host.diagnostics.debugLog; 6 | const logln = p => host.diagnostics.debugLog(p + '\n'); 7 | 8 | // 9 | // Config variables. 10 | // 11 | 12 | // This is the number of lines the !telescope command displays. 13 | const DefaultNumberOfLines = 10; 14 | 15 | // This is the number of instructions to disassemble when a code pointer is encountered. 16 | const DefaultNumberOfInstructions = 3; 17 | 18 | // This is the maximum number of characters displayed for strings in the !telescope output. 19 | const DefaultMaxStringLength = 15; 20 | 21 | // 22 | // Utility functions. 23 | // 24 | 25 | function ReadU64(Addr) { 26 | let Value = null; 27 | try { 28 | Value = host.memory.readMemoryValues( 29 | Addr, 1, 8 30 | )[0]; 31 | } catch (e) { 32 | } 33 | 34 | return Value; 35 | } 36 | 37 | function ReadU32(Addr) { 38 | let Value = null; 39 | try { 40 | Value = host.memory.readMemoryValues( 41 | Addr, 1, 4 42 | )[0]; 43 | } catch (e) { 44 | } 45 | 46 | return Value; 47 | } 48 | 49 | function ReadU16(Addr) { 50 | let Value = null; 51 | try { 52 | Value = host.memory.readMemoryValues( 53 | Addr, 1, 2 54 | )[0]; 55 | } catch (e) { 56 | } 57 | 58 | return Value; 59 | } 60 | 61 | function ReadString(Addr, MaxLength) { 62 | let Value = null; 63 | try { 64 | Value = host.memory.readString(Addr); 65 | } catch (e) { 66 | return null; 67 | } 68 | 69 | if (Value.length > MaxLength) { 70 | return Value.substr(0, MaxLength); 71 | } 72 | 73 | return Value; 74 | } 75 | 76 | function ReadWideString(Addr) { 77 | let Value = null; 78 | try { 79 | Value = host.memory.readWideString(Addr); 80 | } catch (e) { 81 | } 82 | 83 | return Value; 84 | } 85 | 86 | function Disassemble(Addr) { 87 | const Code = host.namespace.Debugger.Utility.Code; 88 | const Disassembler = Code.CreateDisassembler( 89 | PointerSize == 8 ? 'X64' : 'X86' 90 | ); 91 | const Instrs = Array.from(Disassembler.DisassembleInstructions(Addr).Take( 92 | DefaultNumberOfInstructions 93 | )); 94 | 95 | return Instrs.map( 96 | 97 | // 98 | // Clean up the assembly. 99 | // Turn the below: 100 | // 'mov rbx,qword ptr [00007FF8D3525660h] ; test rbx,rbx ; je 00007FF8D34FC2EB' 101 | // Into: 102 | // 'mov rbx,qword ptr [00007FF8D3525660h] ; test rbx,rbx ; je 00007FF8D34FC2EB' 103 | // 104 | 105 | p => p.toString().replace(/[ ]+/g, ' ') 106 | ).join(' ; '); 107 | } 108 | 109 | function FormatU64(Addr) { 110 | return '0x' + Addr.toString(16).padStart(16, '0'); 111 | } 112 | 113 | function FormatU32(Addr) { 114 | return '0x' + Addr.toString(16).padStart(8, '0'); 115 | } 116 | 117 | function FormatString(Str) { 118 | if (Str.length > DefaultMaxStringLength) { 119 | return Str.substr(0, DefaultMaxStringLength) + '...' 120 | } 121 | 122 | return Str; 123 | } 124 | 125 | function BitSet(Value, Bit) { 126 | return Value.bitwiseAnd(Bit).compareTo(0) != 0; 127 | } 128 | 129 | // 130 | // Initialization / global stuff. 131 | // 132 | 133 | let Initialized = false; 134 | let ReadPtr = null; 135 | let PointerSize = null; 136 | let FormatPtr = null; 137 | let IsTTD = false; 138 | let IsUser = false; 139 | let IsKernel = false; 140 | let VaSpace = []; 141 | 142 | function* SectionHeaders(BaseAddress) { 143 | if (IsKernel && ReadU32(BaseAddress) == null) { 144 | 145 | // 146 | // If we can't read the module, then..bail :(. 147 | // XXX: Fix this? Session space? Paged out? 148 | // 149 | 150 | logln('Cannot read ' + BaseAddress.toString(16) + ', skipping.'); 151 | return; 152 | } 153 | 154 | // 0:000> dt _IMAGE_DOS_HEADER e_lfanew 155 | // +0x03c e_lfanew : Int4B 156 | const NtHeaders = BaseAddress.add(ReadU32(BaseAddress.add(0x3c))); 157 | // 0:000> dt _IMAGE_NT_HEADERS64 FileHeader 158 | // +0x004 FileHeader : _IMAGE_FILE_HEADER 159 | // 0:000> dt _IMAGE_FILE_HEADER NumberOfSections SizeOfOptionalHeader 160 | // +0x002 NumberOfSections : Uint2B 161 | // +0x010 SizeOfOptionalHeader : Uint2B 162 | const NumberOfSections = ReadU16(NtHeaders.add(0x4 + 0x2)); 163 | const SizeOfOptionalHeader = ReadU16(NtHeaders.add(0x4 + 0x10)); 164 | // 0:000> dt _IMAGE_NT_HEADERS64 OptionalHeader 165 | // +0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER64 166 | const OptionalHeader = NtHeaders.add(0x18); 167 | const SectionHeaders = OptionalHeader.add(SizeOfOptionalHeader); 168 | // 0:000> ?? sizeof(_IMAGE_SECTION_HEADER) 169 | // unsigned int64 0x28 170 | const SizeofSectionHeader = 0x28; 171 | for (let Idx = 0; Idx < NumberOfSections; Idx++) { 172 | const SectionHeader = SectionHeaders.add( 173 | Idx.multiply(SizeofSectionHeader) 174 | ); 175 | // 0:000> dt _IMAGE_SECTION_HEADER Name 176 | // +0x000 Name : [8] UChar 177 | const Name = ReadString(SectionHeader, 8); 178 | // 0:000> dt _IMAGE_SECTION_HEADER VirtualAddress 179 | // +0x00c VirtualAddress : Uint4B 180 | const Address = BaseAddress.add( 181 | ReadU32(SectionHeader.add(0xc)) 182 | ); 183 | // 0:000> dt _IMAGE_SECTION_HEADER SizeOfRawData 184 | // +0x08 Misc : Uint4B 185 | // XXX: Take care of alignment? 186 | const VirtualSize = ReadU32(SectionHeader.add(0x08)); 187 | // 0:000> dt _IMAGE_SECTION_HEADER Characteristics 188 | // +0x024 Characteristics : Uint4B 189 | const Characteristics = ReadU32(SectionHeader.add(0x24)); 190 | const Properties = [ 191 | '-', 192 | '-', 193 | '-' 194 | ]; 195 | 196 | // The section can be read. 197 | const IMAGE_SCN_MEM_READ = host.Int64(0x40000000); 198 | if (BitSet(Characteristics, IMAGE_SCN_MEM_READ)) { 199 | Properties[0] = 'r'; 200 | } 201 | 202 | if (IsKernel) { 203 | const IMAGE_SCN_MEM_DISCARDABLE = host.Int64(0x2000000); 204 | if (BitSet(Characteristics, IMAGE_SCN_MEM_DISCARDABLE)) { 205 | Properties[0] = '-'; 206 | } 207 | } 208 | 209 | // The section can be written to. 210 | const IMAGE_SCN_MEM_WRITE = host.Int64(0x80000000); 211 | if (Characteristics.bitwiseAnd(IMAGE_SCN_MEM_WRITE).compareTo(0) != 0) { 212 | Properties[1] = 'w'; 213 | } 214 | 215 | // The section can be executed as code. 216 | const IMAGE_SCN_MEM_EXECUTE = host.Int64(0x20000000); 217 | if (Characteristics.bitwiseAnd(IMAGE_SCN_MEM_EXECUTE).compareTo(0) != 0) { 218 | Properties[2] = 'x'; 219 | } 220 | 221 | yield new _Region( 222 | Address, 223 | VirtualSize, 224 | Name, 225 | Properties.join('') 226 | ); 227 | } 228 | } 229 | 230 | function HandleTTD() { 231 | const CurrentSession = host.currentSession; 232 | 233 | // 234 | // Grab addressable chunks. 235 | // 236 | 237 | logln('Populating the VA space with TTD.Data.Heap..'); 238 | const CurrentThread = host.currentThread; 239 | const Position = CurrentThread.TTD.Position; 240 | const Chunks = CurrentSession.TTD.Data.Heap().Where( 241 | p => p.TimeStart.compareTo(Position) < 0 && 242 | p.Action == 'Alloc' 243 | ); 244 | 245 | for (const Chunk of Chunks) { 246 | VaSpace.push(new _Region( 247 | Chunk.Address, 248 | Chunk.Size, 249 | 'Heap', 250 | 'rw-' 251 | )); 252 | } 253 | 254 | // 255 | // Grab virtual allocated memory regions. 256 | // 257 | 258 | logln('Populating the VA space with VirtualAllocated regions..'); 259 | const VirtualAllocs = CurrentSession.TTD.Calls( 260 | 'kernelbase!VirtualAlloc' 261 | ).Where( 262 | p => p.TimeStart.compareTo(Position) < 0 263 | ); 264 | 265 | for (const VirtualAlloc of VirtualAllocs) { 266 | VaSpace.push(new _Region( 267 | VirtualAlloc.ReturnValue, 268 | VirtualAlloc.Parameters[1], 269 | 'VirtualAlloced', 270 | // XXX: parse access 271 | 'rw-' 272 | )); 273 | } 274 | 275 | // 276 | // Grab mapped view regions. 277 | // 278 | 279 | logln('Populating the VA space with MappedViewOfFile regions..'); 280 | const MapViewOfFiles = CurrentSession.TTD.Calls( 281 | 'kernelbase!MapViewOfFile' 282 | ).Where( 283 | p => p.TimeStart.compareTo(Position) < 0 284 | ); 285 | 286 | for (const MapViewOfFile of MapViewOfFiles) { 287 | VaSpace.push(new _Region( 288 | MapViewOfFile.ReturnValue, 289 | host.Int64(0x1000), 290 | 'MappedView', 291 | // XXX: parse access 292 | 'rw-' 293 | )); 294 | } 295 | } 296 | 297 | function HandleUser() { 298 | 299 | // 300 | // Enumerate the modules. 301 | // 302 | 303 | logln('Populating the VA space with modules..'); 304 | const CurrentProcess = host.currentProcess; 305 | for (const Module of CurrentProcess.Modules) { 306 | 307 | // 308 | // Iterate over the section headers of the module. 309 | // 310 | 311 | for (const Section of SectionHeaders(Module.BaseAddress)) { 312 | VaSpace.push(new _Region( 313 | Section.BaseAddress, 314 | Section.Size, 315 | 'Image ' + Module.Name + ' (' + Section.Name + ')', 316 | Section.Properties 317 | )); 318 | } 319 | 320 | // 321 | // Add a catch all in case a pointer points inside the PE but not 322 | // inside any sections (example of this is the PE header). 323 | // 324 | 325 | VaSpace.push(new _Region( 326 | Module.BaseAddress, 327 | Module.Size, 328 | 'Image ' + Module.Name, 329 | 'r--' 330 | )); 331 | } 332 | 333 | // 334 | // Enumerates the TEBs and the stacks. 335 | // 336 | 337 | logln('Populating the VA space with TEBs & thread stacks..'); 338 | for (const Thread of CurrentProcess.Threads) { 339 | const Teb = Thread.Environment.EnvironmentBlock; 340 | 341 | // 342 | // TEB! 343 | // 344 | // In the case where you have broken `ntdll` symbols, you might not have 345 | // the definition of the `_TEB` structure. In this case, the structured 346 | // `Teb` object above is undefined (like in issues #2). So we try to be resilient 347 | // against that in the below. 348 | // 349 | 350 | if (Teb == undefined) { 351 | const General = host.namespace.Debugger.State.PseudoRegisters.General; 352 | VaSpace.push(new _Region( 353 | General.teb.address, 354 | host.Int64(0x100), 355 | 'Teb of ' + Thread.Id.toString(16), 356 | 'rw-' 357 | )); 358 | 359 | continue; 360 | } 361 | 362 | VaSpace.push(new _Region( 363 | Teb.address, 364 | Teb.targetType.size, 365 | 'Teb of ' + Thread.Id.toString(16), 366 | 'rw-' 367 | )); 368 | 369 | // 370 | // Stacks! 371 | // 372 | 373 | const StackBase = Teb.NtTib.StackBase.address; 374 | const StackLimit = Teb.NtTib.StackLimit.address; 375 | VaSpace.push(new _Region( 376 | StackLimit, 377 | StackBase.subtract(StackLimit), 378 | 'Stack', 379 | 'rw-' 380 | )); 381 | } 382 | 383 | // 384 | // Get the PEB. Keep in mind we can run into the same symbol problem with the 385 | // PEB - so account for that. 386 | // 387 | 388 | logln('Populating the VA space with the PEB..'); 389 | const Peb = CurrentProcess.Environment.EnvironmentBlock; 390 | 391 | if (Peb == undefined) { 392 | const General = host.namespace.Debugger.State.PseudoRegisters.General; 393 | VaSpace.push(new _Region( 394 | General.peb.address, 395 | host.Int64(0x1000), 396 | 'Peb', 397 | 'rw-' 398 | )); 399 | 400 | logln(`/!\\ Several regions have been skipped because nt!_TEB / nt!_PEB aren't available in your symbols.`); 401 | } else { 402 | VaSpace.push(new _Region( 403 | Peb.address, 404 | Peb.targetType.size, 405 | 'Peb', 406 | 'rw-' 407 | )); 408 | } 409 | } 410 | 411 | function HandleKernel() { 412 | 413 | // 414 | // Enumerate the kernel modules. 415 | // 416 | 417 | logln('Populating the VA space with kernel modules..'); 418 | const CurrentSession = host.currentSession; 419 | const SystemProcess = CurrentSession.Processes.First( 420 | p => p.Name == 'System' 421 | ); 422 | 423 | const MmUserProbeAddress = ReadPtr( 424 | host.getModuleSymbolAddress('nt', 'MmUserProbeAddress') 425 | ); 426 | 427 | const KernelModules = SystemProcess.Modules.Where( 428 | p => p.BaseAddress.compareTo(MmUserProbeAddress) > 0 429 | ); 430 | 431 | for (const Module of KernelModules) { 432 | 433 | // 434 | // Iterate over the section headers of the module. 435 | // 436 | 437 | for (const Section of SectionHeaders(Module.BaseAddress)) { 438 | VaSpace.push(new _Region( 439 | Section.BaseAddress, 440 | Section.Size, 441 | 'Driver ' + Module.Name + ' (' + Section.Name + ')', 442 | Section.Properties 443 | )); 444 | } 445 | 446 | // 447 | // Add a catch all in case a pointer points inside the PE but not 448 | // inside any sections (example of this is the PE header). 449 | // 450 | 451 | VaSpace.push(new _Region( 452 | Module.BaseAddress, 453 | Module.Size, 454 | 'Driver ' + Module.Name, 455 | 'r--' 456 | )); 457 | } 458 | } 459 | 460 | function InitializeVASpace() { 461 | if (IsUser) { 462 | HandleUser(); 463 | } 464 | 465 | if (IsTTD) { 466 | 467 | // 468 | // If we have a TTD target, let's do some more work. 469 | // 470 | 471 | HandleTTD(); 472 | } 473 | 474 | if (IsKernel) { 475 | HandleKernel(); 476 | } 477 | } 478 | 479 | function InitializeWrapper(Funct) { 480 | return Arg => { 481 | if (!Initialized) { 482 | const CurrentSession = host.currentSession; 483 | 484 | // 485 | // Initialize the ReadPtr function according to the PointerSize. 486 | // 487 | 488 | PointerSize = CurrentSession.Attributes.Machine.PointerSize; 489 | ReadPtr = PointerSize.compareTo(8) == 0 ? ReadU64 : ReadU32; 490 | FormatPtr = PointerSize.compareTo(8) == 0 ? FormatU64 : FormatU32; 491 | const TargetAttributes = CurrentSession.Attributes.Target; 492 | IsTTD = TargetAttributes.IsTTDTarget; 493 | IsUser = TargetAttributes.IsUserTarget; 494 | IsKernel = TargetAttributes.IsKernelTarget; 495 | 496 | // 497 | // One time initialization! 498 | // 499 | 500 | Initialized = true; 501 | } 502 | 503 | // 504 | // Once initialization is done, call into the function. 505 | // 506 | 507 | return Funct(Arg); 508 | }; 509 | } 510 | 511 | 512 | // 513 | // The meat! 514 | // 515 | 516 | class _Region { 517 | constructor(BaseAddress, Size, Name, Properties) { 518 | this.Name = Name; 519 | this.BaseAddress = BaseAddress; 520 | this.EndAddress = this.BaseAddress.add(Size); 521 | this.Size = Size; 522 | this.Properties = Properties; 523 | this.Executable = false; 524 | this.Readable = false; 525 | this.Writeable = false; 526 | if (Properties.indexOf('r') != -1) { 527 | this.Readable = true; 528 | } 529 | 530 | if (Properties.indexOf('w') != -1) { 531 | this.Writeable = true; 532 | } 533 | 534 | if (Properties.indexOf('x') != -1) { 535 | this.Executable = true; 536 | } 537 | 538 | } 539 | 540 | In(Addr) { 541 | const InBounds = Addr.compareTo(this.BaseAddress) >= 0 && 542 | Addr.compareTo(this.EndAddress) < 0; 543 | return InBounds; 544 | } 545 | 546 | toString() { 547 | const Prop = [ 548 | this.Readable ? 'r' : '-', 549 | this.Writeable ? 'w' : '-', 550 | this.Executable ? 'x' : '-' 551 | ]; 552 | 553 | return this.Name + ' ' + Prop.join(''); 554 | } 555 | } 556 | 557 | function AddressToRegion(Addr) { 558 | 559 | // 560 | // Map the address space with VA regions. 561 | // 562 | 563 | const Hits = VaSpace.filter( 564 | p => p.In(Addr) 565 | ); 566 | 567 | // 568 | // Now, let's get the most precise region information by ordering 569 | // the hits by size. 570 | // 571 | 572 | const OrderedHits = Hits.sort( 573 | (a, b) => a.Size.compareTo(b.Size) 574 | ); 575 | 576 | // 577 | // Return the most precise information we have! 578 | // 579 | 580 | return OrderedHits[0]; 581 | } 582 | 583 | class _ChainEntry { 584 | constructor(Addr, Value) { 585 | this.Addr = Addr; 586 | this.Value = Value; 587 | this.AddrRegion = AddressToRegion(this.Addr); 588 | this.ValueRegion = AddressToRegion(this.Value); 589 | if (this.ValueRegion == undefined) { 590 | this.Name = 'Unknown'; 591 | } else { 592 | 593 | // 594 | // Just keep the file name and strips off the path. 595 | // 596 | 597 | this.Name = this.ValueRegion.Name; 598 | this.Name = this.Name.substring(this.Name.lastIndexOf('\\') + 1); 599 | } 600 | this.Last = false; 601 | } 602 | 603 | Equals(Entry) { 604 | return this.Addr.compareTo(Entry.Addr) == 0; 605 | } 606 | 607 | toString() { 608 | const S = FormatPtr(this.Value) + ' (' + this.Name + ')'; 609 | if (!this.Last) { 610 | return S; 611 | } 612 | 613 | // 614 | // We only provide disassembly if we know that the code is executeable. 615 | // And in order to know that, we need to have a valid `AddrRegion`. 616 | // 617 | 618 | if (this.AddrRegion != undefined && this.AddrRegion.Executable) { 619 | return Disassemble(this.Addr); 620 | } 621 | 622 | // 623 | // If we have a string stored in a heap allocation what happens is the following: 624 | // - The extension does not know about heap, so `AddrRegion` for such a pointer 625 | // would be `undefined`. 626 | // - Even though it is undefined, we would like to display a string if there is any, 627 | // instead of just the first qword. 628 | // So to enable the scenario to work, we allow to enter the below block with an `AddrRegion` 629 | // that is undefined. 630 | // 631 | 632 | if (this.AddrRegion == undefined || this.AddrRegion.Readable) { 633 | 634 | const IsPrintable = p => { 635 | return p != null && 636 | // XXX: ugly AF. 637 | p.match(/^[a-z0-9!"#$%&'()*+,/\\.:;<=>?@\[\] ^_`{|}~-]+$/i) != null && 638 | p.length > 5 639 | }; 640 | 641 | // 642 | // Maybe it points on a unicode / ascii string? 643 | // 644 | 645 | const Ansi = ReadString(this.Addr); 646 | 647 | if (IsPrintable(Ansi)) { 648 | return `${FormatPtr(this.Addr)} (Ascii(${FormatString(Ansi)}))`; 649 | } 650 | 651 | const Wide = ReadWideString(this.Addr); 652 | if (IsPrintable(Wide)) { 653 | return `${FormatPtr(this.Addr)} (Unicode(${FormatString(Wide)}))`; 654 | } 655 | } 656 | 657 | // 658 | // If we didn't find something better, fallback to the regular 659 | // output. 660 | // 661 | 662 | return S; 663 | } 664 | } 665 | 666 | class _Chain { 667 | constructor(Addr) { 668 | this.__Entries = []; 669 | this.__HasCycle = false; 670 | this.__Addr = Addr; 671 | while (this.FollowPtr()) { }; 672 | this.__Length = this.__Entries.length; 673 | 674 | // 675 | // Tag the last entry as 'last'. 676 | // 677 | 678 | if (this.__Length >= 1) { 679 | this.__Entries[this.__Length - 1].Last = true; 680 | } 681 | } 682 | 683 | FollowPtr() { 684 | 685 | // 686 | // Attempt to follow the pointer. 687 | // 688 | 689 | const Value = ReadPtr(this.__Addr); 690 | if (Value == null) { 691 | 692 | // 693 | // We are done following pointers now! 694 | // 695 | 696 | return false; 697 | } 698 | 699 | // 700 | // Let's build an entry and evaluate what we want to do with it. 701 | // 702 | 703 | const Entry = new _ChainEntry(this.__Addr, Value); 704 | const DoesEntryExist = this.__Entries.find( 705 | p => p.Equals(Entry) 706 | ); 707 | 708 | if (DoesEntryExist) { 709 | 710 | // 711 | // If we have seen this Entry before, it means there's a cycle 712 | // and we will stop there. 713 | // 714 | 715 | this.__HasCycle = true; 716 | return false; 717 | } 718 | 719 | // 720 | // This Entry is of interest, so let's add it in our list. 721 | // 722 | 723 | this.__Entries.push(Entry); 724 | this.__Addr = Value; 725 | return true; 726 | } 727 | 728 | toString() { 729 | if (this.__Entries.length == 0) { 730 | return ''; 731 | } 732 | 733 | // 734 | // Iterate over the chain. 735 | // 736 | 737 | let S = this.__Entries.join(' -> '); 738 | 739 | // 740 | // Add a little something if we have a cycle so that the user knows. 741 | // 742 | 743 | if (this.__HasCycle) { 744 | S += ' [...]'; 745 | } 746 | 747 | return S; 748 | } 749 | 750 | *[Symbol.iterator]() { 751 | for (const Entry of this.__Entries) { 752 | yield Entry; 753 | } 754 | } 755 | } 756 | 757 | function CreateChain(Addr) { 758 | 759 | // 760 | // Initialize the VA space. 761 | // 762 | 763 | InitializeVASpace(); 764 | 765 | const Chain = new _Chain(Addr); 766 | VaSpace = []; 767 | return Chain; 768 | } 769 | 770 | function Telescope(Addr) { 771 | if (Addr == undefined) { 772 | logln('!telescope '); 773 | return; 774 | } 775 | 776 | // 777 | // Initialize the VA space. 778 | // 779 | 780 | InitializeVASpace(); 781 | 782 | const CurrentSession = host.currentSession; 783 | const Lines = DefaultNumberOfLines; 784 | const PointerSize = CurrentSession.Attributes.Machine.PointerSize; 785 | const FormatOffset = p => '0x' + p.toString(16).padStart(4, '0'); 786 | 787 | for (let Idx = 0; Idx < Lines; Idx++) { 788 | const Offset = PointerSize.multiply(Idx); 789 | const CurAddr = Addr.add(Offset); 790 | const Chain = new _Chain(CurAddr); 791 | const Header = FormatPtr(CurAddr) + '|+' + FormatOffset(Offset); 792 | logln(Header + ': ' + Chain.toString()); 793 | } 794 | 795 | VaSpace = []; 796 | } 797 | 798 | function initializeScript() { 799 | return [ 800 | new host.apiVersionSupport(1, 3), 801 | new host.functionAlias( 802 | InitializeWrapper(Telescope), 803 | 'telescope' 804 | ), 805 | new host.functionAlias( 806 | InitializeWrapper(CreateChain), 807 | 'createchain' 808 | ) 809 | ]; 810 | } 811 | -------------------------------------------------------------------------------- /sm/sm.js: -------------------------------------------------------------------------------- 1 | // Axel '0vercl0k' Souchet - 24-June-2018 2 | // 3 | // Example: 4 | // * from the interpreter: 5 | // Math.atan2(['short', 13.37, new Map([[ 1, 'one' ],[ 2, 'two' ]]), ['loooooooooooooooooooooooooooooong', [0x1337, {doare:'in d4 place'}]], false, null, undefined, true, Math.atan2, Math]) 6 | // 7 | // * from the debugger: 8 | // js!js::math_atan2: 9 | // 00007ff6`0227e140 56 push rsi 10 | // 0:000> !smdump_jsvalue vp[2].asBits_ 11 | // 1e5f10024c0: js!js::ArrayObject: Length: 10 12 | // 1e5f10024c0: js!js::ArrayObject: Capacity: 10 13 | // 1e5f10024c0: js!js::ArrayObject: Content: ['short', 13.37, new Map(...), ['loooooooooooooooooooooooooooooong', [0x1337, {'doare' : 'in d4 place'}]], false, null, undefined, true, atan2(), Math] 14 | // @$smdump_jsvalue(vp[2].asBits_) 15 | // 16 | 17 | 'use strict'; 18 | 19 | let Module = null; 20 | 21 | const logln = p => host.diagnostics.debugLog(p + '\n'); 22 | const hex = p => '0x' + p.toString(16).padStart(16, '0'); 23 | const JSVAL_TAG_SHIFT = host.Int64(47); 24 | const JSVAL_PAYLOAD_MASK = host.Int64(1).bitwiseShiftLeft(JSVAL_TAG_SHIFT).subtract(1); 25 | const CLASS_NON_NATIVE = host.Int64(0x40000); 26 | const FLAG_DELEGATE = host.Int64(8); 27 | 28 | const JSVAL_TYPE_DOUBLE = host.Int64(0x1fff0); 29 | const JSVAL_TYPE_INT32 = host.Int64(0x1fff1); 30 | const JSVAL_TYPE_BOOLEAN = host.Int64(0x1fff2); 31 | const JSVAL_TYPE_UNDEFINED = host.Int64(0x1fff3); 32 | const JSVAL_TYPE_NULL = host.Int64(0x1fff4); 33 | const JSVAL_TYPE_MAGIC = host.Int64(0x1fff5); 34 | const JSVAL_TYPE_STRING = host.Int64(0x1fff6); 35 | const JSVAL_TYPE_SYMBOL = host.Int64(0x1fff7); 36 | const JSVAL_TYPE_OBJECT = host.Int64(0x1fffc); 37 | 38 | const INLINE_CHARS_BIT = host.Int64(1 << 6); 39 | const LATIN1_CHARS_BIT = host.Int64(1 << 9); 40 | 41 | const JSID_TYPE_MASK = host.Int64(0x7); 42 | const JSID_TYPE_STRING = host.Int64(0x0); 43 | const JSID_TYPE_INT = host.Int64(0x1); 44 | const JSID_TYPE_VOID = host.Int64(0x2); 45 | const JSID_TYPE_SYMBOL = host.Int64(0x4); 46 | 47 | const SLOT_MASK = host.Int64(0xffffff); 48 | const FIXED_SLOTS_SHIFT = host.Int64(24); 49 | 50 | const FunctionConstants = { 51 | 0x0001 : 'INTERPRETED', 52 | 0x0004 : 'EXTENDED', 53 | 0x0008 : 'BOUND_FUN', 54 | 0x0010 : 'WASM_OPTIMIZED', 55 | 0x0020 : 'HAS_GUESSED_ATOM/HAS_BOUND_FUNCTION_NAME_PREFIX', 56 | 0x0040 : 'LAMBDA', 57 | 0x0080 : 'SELF_HOSTED', 58 | 0x0100 : 'HAS_INFERRED_NAME', 59 | 0x0200 : 'INTERPRETED_LAZY', 60 | 0x0400 : 'RESOLVED_LENGTH', 61 | 0x0800 : 'RESOLVED_NAME', 62 | }; 63 | 64 | const FunctionKindConstants = { 65 | 0 : 'NORMAL_KIND', 66 | 1 : 'ARROW_KIND', 67 | 2 : 'METHOD_KIND', 68 | 3 : 'CLASSCONSTRUCTOR_KIND', 69 | 4 : 'GETTER_KIND', 70 | 5 : 'SETTER_KIND', 71 | 6 : 'ASMJS_KIND' 72 | }; 73 | 74 | const Tag2Names = { 75 | [JSVAL_TYPE_DOUBLE] : 'Double', 76 | [JSVAL_TYPE_INT32] : 'Int32', 77 | [JSVAL_TYPE_STRING] : 'String', 78 | [JSVAL_TYPE_UNDEFINED] : 'Undefined', 79 | [JSVAL_TYPE_BOOLEAN] : 'Boolean', 80 | [JSVAL_TYPE_NULL] : 'Null', 81 | [JSVAL_TYPE_OBJECT] : 'Object', 82 | [JSVAL_TYPE_SYMBOL] : 'Symbol', 83 | [JSVAL_TYPE_MAGIC] : 'Magic', 84 | }; 85 | 86 | // 87 | // Read a uint64_t integer from Addr. 88 | // 89 | 90 | function read_u64(Addr) { 91 | return host.memory.readMemoryValues(Addr, 1, 8)[0]; 92 | } 93 | 94 | // 95 | // Mirror the functionality of ::fromElements. 96 | // 97 | 98 | function heapslot_to_objectelements(Addr) { 99 | // static ObjectElements* fromElements(HeapSlot* elems) { 100 | // return reinterpret_cast(uintptr_t(elems) - sizeof(ObjectElements)); 101 | // } 102 | const ObjectElementsSize = host.getModuleType(Module, 'js::ObjectElements').size; 103 | const ObjectElements = host.createPointerObject( 104 | Addr.subtract(ObjectElementsSize), 105 | Module, 106 | 'js::ObjectElements*' 107 | ); 108 | 109 | return ObjectElements; 110 | } 111 | 112 | // 113 | // Is Byte printable? 114 | // 115 | 116 | function printable(Byte) { 117 | return Byte >= 0x20 && Byte <= 0x7e; 118 | } 119 | 120 | // 121 | // Return a string describing Byte; either a \x41 or its ascii representation 122 | // 123 | 124 | function byte_to_str(Byte) { 125 | if(printable(Byte)) { 126 | return String.fromCharCode(Byte); 127 | } 128 | 129 | return '\\x' + Byte.toString(16).padStart(2, '0'); 130 | } 131 | 132 | // 133 | // Is this jsid an integer? 134 | // 135 | 136 | function jsid_is_int(Propid) { 137 | const Bits = Propid.value.asBits; 138 | return Bits.bitwiseAnd(JSID_TYPE_MASK).compareTo(JSID_TYPE_INT) == 0; 139 | } 140 | 141 | // 142 | // Is this jsid a string? 143 | // 144 | 145 | function jsid_is_string(Propid) { 146 | const Bits = Propid.value.asBits; 147 | return Bits.bitwiseAnd(JSID_TYPE_MASK).compareTo(JSID_TYPE_STRING) == 0; 148 | } 149 | 150 | // 151 | // Retrieve a property from a Shape; returns an actual integer/string based 152 | // on the propid_. 153 | // 154 | 155 | function get_property_from_shape(Shape) { 156 | // XXX: expose a smdump_jsid 157 | const Propid = Shape.propid_; 158 | if(jsid_is_int(Propid)) { 159 | return Propid.value.asBits.bitwiseShiftRight(1); 160 | } 161 | 162 | if(jsid_is_string(Propid)) { 163 | return new __JSString(Propid.value.asBits); 164 | } 165 | 166 | // XXX: todo 167 | } 168 | 169 | function jsvalue_to_instance(Addr) { 170 | const JSValue = new __JSValue(Addr); 171 | if(!Tag2Names.hasOwnProperty(JSValue.Tag)) { 172 | return 'Dunno'; 173 | } 174 | 175 | const Name = Tag2Names[JSValue.Tag]; 176 | const Type = Names2Types[Name]; 177 | return new Type(JSValue.Payload); 178 | } 179 | 180 | class __JSMagic { 181 | constructor(Addr) { 182 | this._Addr = Addr; 183 | } 184 | 185 | toString() { 186 | return 'magic'; 187 | } 188 | 189 | Logger(Content) { 190 | logln(this._Addr.toString(16) + ': JSVAL_TYPE_MAGIC: ' + Content); 191 | } 192 | 193 | Display() { 194 | this.Logger(this); 195 | } 196 | } 197 | 198 | class __JSArgument { 199 | // * ArgumentsObject instances use the following reserved slots: 200 | // * 201 | // * INITIAL_LENGTH_SLOT 202 | // * Stores the initial value of arguments.length, plus a bit indicating 203 | // * whether arguments.length and/or arguments[@@iterator] have been 204 | // * modified. Use initialLength(), hasOverriddenLength(), and 205 | // * hasOverriddenIterator() to access these values. If arguments.length has 206 | // * been modified, then the current value of arguments.length is stored in 207 | // * another slot associated with a new property. 208 | // * DATA_SLOT 209 | // * Stores an ArgumentsData*, described above. 210 | // * MAYBE_CALL_SLOT 211 | // * Stores the CallObject, if the callee has aliased bindings. See 212 | // * the ArgumentsData::args comment. 213 | // * CALLEE_SLOT 214 | // * Stores the initial arguments.callee. This value can be overridden on 215 | // * mapped arguments objects, see hasOverriddenCallee. 216 | // */ 217 | // class ArgumentsObject : public NativeObject { 218 | // protected: 219 | // static const uint32_t INITIAL_LENGTH_SLOT = 0; 220 | // static const uint32_t DATA_SLOT = 1; 221 | // static const uint32_t MAYBE_CALL_SLOT = 2; 222 | // static const uint32_t CALLEE_SLOT = 3; 223 | constructor(Addr) { 224 | this._Addr = Addr; 225 | this._Obj = host.createPointerObject( 226 | Addr, 227 | Module, 228 | 'js::ArgumentsObject*' 229 | ); 230 | 231 | const INITIAL_LENGTH_SLOT = host.Int64(0); 232 | const DATA_SLOT = host.Int64(1); 233 | const MAYBE_CALL_SLOT = host.Int64(2); 234 | const CALLEE_SLOT = host.Int64(3); 235 | const ArgumentsObjectSize = this._Obj.dereference().targetType.size; 236 | this._SlotAddress = this._Obj.address.add(ArgumentsObjectSize); 237 | 238 | const InitialLengthSlot = read_u64(this._SlotAddress.add( 239 | INITIAL_LENGTH_SLOT.multiply(8) 240 | )); 241 | this._InitialLength = new __JSInt32(InitialLengthSlot)._Value; 242 | 243 | this._Data = read_u64(this._SlotAddress.add( 244 | DATA_SLOT.multiply(8) 245 | )).bitwiseShiftLeft(1); 246 | } 247 | 248 | toString() { 249 | return 'Arguments(..)'; 250 | } 251 | 252 | Logger(Content) { 253 | logln(this._Addr.toString(16) + ': js!js::ArgumentsObject: ' + Content); 254 | } 255 | 256 | Display() { 257 | this.Logger('InitialLength: ' + this._InitialLength); 258 | this.Logger(' Data: ' + hex(this._Data) + ' (js!js::ArgumentsData)'); 259 | } 260 | } 261 | 262 | class __JSNull { 263 | constructor(Addr) { 264 | this._Addr = Addr; 265 | } 266 | 267 | toString() { 268 | return 'null'; 269 | } 270 | 271 | Logger(Content) { 272 | logln(this._Addr.toString(16) + ': JSVAL_TYPE_NULL: ' + Content); 273 | } 274 | 275 | Display() { 276 | this.Logger(this); 277 | } 278 | } 279 | 280 | class __JSUndefined { 281 | constructor(Addr) { 282 | this._Addr = Addr; 283 | } 284 | 285 | toString() { 286 | return 'undefined'; 287 | } 288 | 289 | Logger(Content) { 290 | logln(this.Addr.toString(16) + ': JSVAL_TYPE_UNDEFINED: ' + Content); 291 | } 292 | 293 | Display() { 294 | this.Logger(this); 295 | } 296 | } 297 | 298 | class __JSBoolean { 299 | constructor(Addr) { 300 | this._Addr = Addr; 301 | this._Value = Addr.compareTo(1) == 0 ? true : false; 302 | } 303 | 304 | toString() { 305 | return this._Value.toString(); 306 | } 307 | 308 | Logger(Content) { 309 | logln(this._Addr.toString(16) + ': JSVAL_TYPE_BOOLEAN: ' + Content); 310 | } 311 | 312 | Display() { 313 | this.Logger(this); 314 | } 315 | } 316 | 317 | class __JSInt32 { 318 | constructor(Addr) { 319 | this._Addr = Addr; 320 | this._Value = Addr.bitwiseAnd(0xffffffff); 321 | } 322 | 323 | toString() { 324 | return '0x' + this._Value.toString(16); 325 | } 326 | 327 | Logger(Content) { 328 | logln(this._Addr.toString(16) + ': JSVAL_TYPE_INT32: ' + Content); 329 | } 330 | 331 | Display() { 332 | this.Logger(this); 333 | } 334 | } 335 | 336 | class __JSString { 337 | constructor(Addr) { 338 | this._Obj = host.createPointerObject( 339 | Addr, 340 | Module, 341 | 'JSString*' 342 | ); 343 | /* 344 | * The Flags Word 345 | * 346 | * The flags word stores both the string's type and its character encoding. 347 | * 348 | * If LATIN1_CHARS_BIT is set, the string's characters are stored as Latin1 349 | * instead of TwoByte. This flag can also be set for ropes, if both the 350 | * left and right nodes are Latin1. Flattening will result in a Latin1 351 | * string in this case. 352 | */ 353 | const Flags = this._Obj.d.flags_; 354 | const IsLatin1 = Flags.bitwiseAnd(LATIN1_CHARS_BIT).compareTo(0) != 0; 355 | const IsInline = Flags.bitwiseAnd(INLINE_CHARS_BIT).compareTo(0) != 0; 356 | let Address = null; 357 | if(IsInline) { 358 | 359 | // 360 | // inlineStorageLatin1 and inlineStorageTwoByte are in a union and 361 | // as a result are at the same address 362 | // 363 | 364 | Address = this._Obj.d.inlineStorageLatin1.address; 365 | } else { 366 | 367 | // 368 | // Same as above with nonInlineStorageLatin1 and nonInlineStorageTwoByte. 369 | // 370 | 371 | Address = this._Obj.d.s.u2.nonInlineCharsLatin1.address; 372 | } 373 | 374 | let Length = Flags.bitwiseShiftRight(32); 375 | if(!IsLatin1) { 376 | Length *= 2; 377 | } 378 | 379 | this._String = Array.from(host.memory.readMemoryValues( 380 | Address, 381 | Length, 382 | 1 383 | )).map( 384 | p => byte_to_str(p) 385 | ).join(''); 386 | } 387 | 388 | toString() { 389 | return "'" + this._String + "'"; 390 | } 391 | 392 | Logger(Content) { 393 | logln(this._Obj.address.toString(16) + ': js!JSString: ' + Content); 394 | } 395 | 396 | Display() { 397 | this.Logger(this); 398 | } 399 | } 400 | 401 | class __JSValue { 402 | constructor(Addr) { 403 | this._Addr = Addr; 404 | this._Tag = this._Addr.bitwiseShiftRight(JSVAL_TAG_SHIFT); 405 | this._IsDouble = this._Tag.compareTo(JSVAL_TYPE_DOUBLE) < 0; 406 | this._Payload = this._Addr.bitwiseAnd(JSVAL_PAYLOAD_MASK); 407 | } 408 | 409 | get Payload() { 410 | if(this._IsDouble) { 411 | return this._Addr; 412 | } 413 | 414 | return this._Payload; 415 | } 416 | 417 | get Tag() { 418 | if(this._IsDouble) { 419 | return JSVAL_TYPE_DOUBLE; 420 | } 421 | 422 | return this._Tag; 423 | } 424 | } 425 | 426 | class __JSArray { 427 | constructor(Addr) { 428 | this._Obj = host.createPointerObject( 429 | Addr, 430 | Module, 431 | 'js::ArrayObject*' 432 | ); 433 | // XXX: why doesn't it work? 434 | // this.Obj.elements_.value.address 435 | this._Content = this._Obj.elements_.address; 436 | this._Header = heapslot_to_objectelements(this._Content); 437 | // The flags word stores both the flags and the number of shifted elements. 438 | // Allow shifting 2047 elements before actually moving the elements. 439 | const NumShiftedElementsBits = host.Int64(11); 440 | const NumShiftedElementsShift = host.Int64(32).subtract(NumShiftedElementsBits); 441 | this._Flags = this._Header.flags; 442 | this._NumShifted = this._Flags.bitwiseShiftRight(NumShiftedElementsShift) 443 | this._Length = this._Header.length; 444 | this._Capacity = this._Header.capacity; 445 | this._InitializedLength = this._Header.initializedLength; 446 | } 447 | 448 | toString() { 449 | const Max = 10; 450 | const Content = []; 451 | for(let Idx = 0; Idx < Math.min(Max, this._InitializedLength); ++Idx) { 452 | const Addr = this._Content.add(Idx * 8); 453 | const JSValue = read_u64(Addr); 454 | const Inst = jsvalue_to_instance(JSValue); 455 | Content.push(Inst.toString()); 456 | } 457 | 458 | return '[' + Content.join(', ') + (this._Length > Max ? ', ...' : '') + ']'; 459 | } 460 | 461 | Logger(Content) { 462 | logln(this._Obj.address.toString(16) + ': js!js::ArrayObject: ' + Content); 463 | } 464 | 465 | Display() { 466 | this.Logger(' Length: ' + this._Length); 467 | this.Logger(' Capacity: ' + this._Capacity); 468 | this.Logger('InitializedLength: ' + this._InitializedLength); 469 | this.Logger(' NumShifted: ' + this._NumShifted + ' (flags: ' + hex(this._Flags) + ')'); 470 | this.Logger(' Content: ' + this); 471 | } 472 | } 473 | 474 | class __JSFunction { 475 | constructor(Addr) { 476 | this._Obj = host.createPointerObject( 477 | Addr, 478 | Module, 479 | 'JSFunction*' 480 | ); 481 | 482 | this._Atom = this._Obj.atom_.value.address; 483 | this._Name = ''; 484 | if(this._Atom.compareTo(0) != 0) { 485 | this._Name = new __JSString(this._Atom).toString().slice(1, -1); 486 | } 487 | 488 | this._Name += '()'; 489 | this._Flags = this._Obj.flags_; 490 | } 491 | 492 | toString() { 493 | return this._Name; 494 | } 495 | 496 | get Flags() { 497 | const S = []; 498 | for(const Key in FunctionConstants) { 499 | if(this._Flags.bitwiseAnd(host.parseInt64(Key)).compareTo(0) != 0) { 500 | S.push(FunctionConstants[Key]); 501 | } 502 | } 503 | 504 | const Kind = (this._Flags >> 13) & 7; 505 | S.push(FunctionKindConstants[Kind]); 506 | return S.join(' | '); 507 | } 508 | 509 | Logger(Content) { 510 | logln(this._Obj.address.toString(16) + ': js!JSFunction: ' + Content); 511 | } 512 | 513 | Display() { 514 | this.Logger(this); 515 | this.Logger('Flags: ' + this.Flags); 516 | } 517 | } 518 | 519 | class __JSSymbol { 520 | constructor(Addr) { 521 | this._Obj = host.createPointerObject( 522 | Addr, 523 | Module, 524 | 'js::Symbol*' 525 | ); 526 | } 527 | 528 | toString() { 529 | const Desc = new __JSString(this._Obj.description_.address); 530 | return 'Symbol(' + Desc + ')'; 531 | } 532 | 533 | Logger(Content) { 534 | logln(this.Obj_.address.toString(16) + ': js!js::Symbol: ' + Content); 535 | } 536 | 537 | Display() { 538 | this.Logger(this); 539 | } 540 | } 541 | 542 | class __JSArrayBuffer { 543 | constructor(Addr) { 544 | this._Obj = host.createPointerObject( 545 | Addr, 546 | Module, 547 | 'js::ArrayBufferObject*' 548 | ); 549 | 550 | const ArrayBufferObjectSize = host.getModuleType(Module, 'js::ArrayBufferObject').size; 551 | // static const uint8_t DATA_SLOT = 0; 552 | // static const uint8_t BYTE_LENGTH_SLOT = 1; 553 | const ByteLengthSlotAddr = Addr.add(ArrayBufferObjectSize).add(1 * 8); 554 | const ByteLengthSlot = read_u64(ByteLengthSlotAddr); 555 | this._ByteLength = new __JSInt32(ByteLengthSlot)._Value; 556 | // static const uint8_t FIRST_VIEW_SLOT = 2; 557 | // static const uint8_t FLAGS_SLOT = 3; 558 | const FlagsAddr = Addr.add(ArrayBufferObjectSize).add(3 * 8); 559 | const FlagsSlot = read_u64(FlagsAddr); 560 | this._Flags = new __JSInt32(FlagsSlot)._Value; 561 | } 562 | 563 | get Flags() { 564 | // enum BufferKind { 565 | // PLAIN = 0, // malloced or inline data 566 | // WASM = 1, 567 | // MAPPED = 2, 568 | // EXTERNAL = 3, 569 | // KIND_MASK = 0x3 570 | // }; 571 | // enum ArrayBufferFlags { 572 | // // The flags also store the BufferKind 573 | // BUFFER_KIND_MASK = BufferKind::KIND_MASK, 574 | // DETACHED = 0x4, 575 | // // The dataPointer() is owned by this buffer and should be released 576 | // // when no longer in use. Releasing the pointer may be done by freeing, 577 | // // invoking a dereference callback function, or unmapping, as 578 | // // determined by the buffer's other flags. 579 | // // 580 | // // Array buffers which do not own their data include buffers that 581 | // // allocate their data inline, and buffers that are created lazily for 582 | // // typed objects with inline storage, in which case the buffer points 583 | // // directly to the typed object's storage. 584 | // OWNS_DATA = 0x8, 585 | // // This array buffer was created lazily for a typed object with inline 586 | // // data. This implies both that the typed object owns the buffer's data 587 | // // and that the list of views sharing this buffer's data might be 588 | // // incomplete. Any missing views will be typed objects. 589 | // FOR_INLINE_TYPED_OBJECT = 0x10, 590 | // // Views of this buffer might include typed objects. 591 | // TYPED_OBJECT_VIEWS = 0x20, 592 | // // This PLAIN or WASM buffer has been prepared for asm.js and cannot 593 | // // henceforth be transferred/detached. 594 | // FOR_ASMJS = 0x40 595 | // }; 596 | const BufferKinds = { 597 | 0 : 'PLAIN', 598 | 1 : 'WASM', 599 | 2 : 'MAPPED', 600 | 3 : 'EXTERNAL' 601 | }; 602 | 603 | const BufferKind = BufferKinds[this._Flags.bitwiseAnd(3).asNumber()]; 604 | const ArrayBufferFlags = [ 605 | 'BufferKind(' + BufferKind + ')' 606 | ]; 607 | 608 | const ArrayBufferFlagsConstants = { 609 | [0x04] : 'DETACHED', 610 | [0x08] : 'OWNS_DATA', 611 | [0x10] : 'FOR_INLINE_TYPED_OBJECT', 612 | [0x20] : 'TYPED_OBJECT_VIEWS', 613 | [0x40] : 'FOR_ASMJS' 614 | }; 615 | 616 | for(const Key in ArrayBufferFlagsConstants) { 617 | if(this._Flags.bitwiseAnd(host.parseInt64(Key)).compareTo(0) != 0) { 618 | ArrayBufferFlags.push(ArrayBufferFlagsConstants[Key]); 619 | } 620 | } 621 | 622 | return ArrayBufferFlags.join(' | '); 623 | } 624 | 625 | get ByteLength() { 626 | return this._ByteLength; 627 | } 628 | 629 | toString() { 630 | return 'ArrayBuffer({ByteLength:' + this._ByteLength + ', ...})'; 631 | } 632 | 633 | Logger(Content) { 634 | logln(this._Obj.address.toString(16) + ': js!js::ArrayBufferObject: ' + Content); 635 | } 636 | 637 | Display() { 638 | this.Logger('ByteLength: ' + this.ByteLength); 639 | this.Logger(' Flags: ' + this.Flags); 640 | this.Logger(' Content: ' + this); 641 | } 642 | } 643 | 644 | class __JSTypedArray { 645 | constructor(Addr) { 646 | this._Obj = host.createPointerObject( 647 | Addr, 648 | Module, 649 | 'js::TypedArrayObject*' 650 | ); 651 | 652 | const Group = this._Obj.group_.value; 653 | this._TypeName = host.memory.readString(Group.clasp_.name) 654 | const Sizes = { 655 | 'Float64Array' : 8, 656 | 'Float32Array' : 4, 657 | 'Uint32Array' : 4, 658 | 'Int32Aray' : 4, 659 | 'Uint16Array' : 2, 660 | 'Int16Array' : 2, 661 | 'Uint8Array' : 1, 662 | 'Uint8ClampedArray' : 1, 663 | 'Int8Array' : 1 664 | }; 665 | this._ElementSize = Sizes[this._TypeName]; 666 | 667 | const TypedArrayObjectSize = host.getModuleType(Module, 'js::TypedArrayObject').size; 668 | // static const size_t BUFFER_SLOT = 0; 669 | // static const size_t LENGTH_SLOT = 1; 670 | const LengthSlotAddr = Addr.add(TypedArrayObjectSize).add(1 * 8); 671 | const LengthSlot = read_u64(LengthSlotAddr); 672 | this._Length = new __JSInt32(LengthSlot)._Value; 673 | this._ByteLength = this._Length * this._ElementSize; 674 | // static const size_t BYTEOFFSET_SLOT = 2; 675 | const ByteOffsetSlotAddr = Addr.add(TypedArrayObjectSize).add(2 * 8); 676 | const ByteOffsetSlot = read_u64(ByteOffsetSlotAddr); 677 | this._ByteOffset = new __JSInt32(ByteOffsetSlot)._Value; 678 | // static const size_t RESERVED_SLOTS = 3; 679 | } 680 | 681 | get Type() { 682 | return this._TypeName; 683 | } 684 | 685 | get ByteOffset() { 686 | return this._ByteOffset; 687 | } 688 | 689 | get ByteLength() { 690 | return this._ByteLength; 691 | } 692 | 693 | get Length() { 694 | return this._Length; 695 | } 696 | 697 | toString() { 698 | return this._TypeName + '({Length:' + this._Length + ', ...})'; 699 | } 700 | 701 | Logger(Content) { 702 | logln(this._Obj.address.toString(16) + ': js!js::TypedArrayObject: ' + Content); 703 | } 704 | 705 | Display() { 706 | this.Logger(' Type: ' + this.Type); 707 | this.Logger(' Length: ' + this.Length); 708 | this.Logger('ByteLength: ' + this.ByteLength); 709 | this.Logger('ByteOffset: ' + this.ByteOffset); 710 | this.Logger(' Content: ' + this); 711 | } 712 | } 713 | 714 | class __JSMap { 715 | constructor(Addr) { 716 | this._Addr = Addr; 717 | } 718 | 719 | // XXX: TODO 720 | toString() { 721 | return 'new Map(...)'; 722 | } 723 | 724 | Logger(Content) { 725 | logln(this._Addr.toString(16) + ': js!js::MapObject: ' + Content); 726 | } 727 | 728 | Display() { 729 | this.Logger('Content: ' + this); 730 | } 731 | } 732 | 733 | class __JSDouble { 734 | constructor(Addr) { 735 | this._Addr = Addr; 736 | } 737 | 738 | toString() { 739 | const U32 = new Uint32Array([ 740 | this._Addr.getLowPart(), 741 | this._Addr.getHighPart() 742 | ]); 743 | const F64 = new Float64Array(U32.buffer); 744 | return F64[0]; 745 | } 746 | 747 | Logger(Content) { 748 | logln(this._Addr.toString(16) + ': JSVAL_TYPE_DOUBLE: ' + Content); 749 | } 750 | 751 | Display() { 752 | this.Logger(this); 753 | } 754 | } 755 | 756 | const Names2Types = { 757 | 'Function' : __JSFunction, 758 | 'Array' : __JSArray, 759 | 'ArrayBuffer' : __JSArrayBuffer, 760 | 'Map' : __JSMap, 761 | 'Int32' : __JSInt32, 762 | 'String' : __JSString, 763 | 'Boolean' : __JSBoolean, 764 | 'Null' : __JSNull, 765 | 'Undefined' : __JSUndefined, 766 | 'Symbol' : __JSSymbol, 767 | 'Double' : __JSDouble, 768 | 'Magic' : __JSMagic, 769 | 'Arguments' : __JSArgument, 770 | 771 | 'Float64Array' : __JSTypedArray, 772 | 'Float32Array' : __JSTypedArray, 773 | 'Uint32Array' : __JSTypedArray, 774 | 'Int32Array' : __JSTypedArray, 775 | 'Uint16Array' : __JSTypedArray, 776 | 'Int16Array' : __JSTypedArray, 777 | 'Uint8Array' : __JSTypedArray, 778 | 'Uint8ClampedArray' : __JSTypedArray, 779 | 'Int8Array' : __JSTypedArray 780 | }; 781 | 782 | class __JSObject { 783 | /* JSObject.h 784 | * A JavaScript object. 785 | * 786 | * This is the base class for all objects exposed to JS script (as well as some 787 | * objects that are only accessed indirectly). Subclasses add additional fields 788 | * and execution semantics. The runtime class of an arbitrary JSObject is 789 | * identified by JSObject::getClass(). 790 | * 791 | * The members common to all objects are as follows: 792 | * 793 | * - The |group_| member stores the group of the object, which contains its 794 | * prototype object, its class and the possible types of its properties. 795 | * 796 | * - The |shapeOrExpando_| member points to (an optional) guard object that JIT 797 | * may use to optimize. The pointed-to object dictates the constraints 798 | * imposed on the JSObject: 799 | * nullptr 800 | * - Safe value if this field is not needed. 801 | * js::Shape 802 | * - All objects that might point |shapeOrExpando_| to a js::Shape 803 | * must follow the rules specified on js::ShapedObject. 804 | * JSObject 805 | * - Implies nothing about the current object or target object. Either 806 | * of which may mutate in place. Store a JSObject* only to save 807 | * space, not to guard on. 808 | */ 809 | constructor(Addr) { 810 | this._Addr = Addr; 811 | this._Obj = host.createPointerObject( 812 | this._Addr, 813 | Module, 814 | 'JSObject*' 815 | ); 816 | 817 | this._Properties = []; 818 | const Group = this._Obj.group_.value; 819 | this._ClassName = host.memory.readString(Group.clasp_.name); 820 | const NonNative = Group.clasp_.flags.bitwiseAnd(CLASS_NON_NATIVE).compareTo(0) != 0; 821 | if(NonNative) { 822 | return; 823 | } 824 | 825 | const Shape = host.createPointerObject( 826 | this._Obj.shapeOrExpando_.address, 827 | Module, 828 | 'js::Shape*' 829 | ); 830 | 831 | const NativeObject = host.createPointerObject(Addr, Module, 'js::NativeObject*'); 832 | 833 | if(this._ClassName == 'Array') { 834 | 835 | // 836 | // Optimization for 'length' property if 'Array' cf 837 | // js::ArrayObject::length / js::GetLengthProperty 838 | // 839 | 840 | const ObjectElements = heapslot_to_objectelements(NativeObject.elements_.address); 841 | this._Properties.push('length : ' + ObjectElements.length); 842 | return; 843 | } 844 | 845 | // 846 | // Walk the list of Shapes and get the property names 847 | // 848 | 849 | const Properties = {}; 850 | let CurrentShape = Shape; 851 | while(CurrentShape.parent.value.address.compareTo(0) != 0) { 852 | const SlotIdx = CurrentShape.immutableFlags.bitwiseAnd(SLOT_MASK).asNumber(); 853 | Properties[SlotIdx] = get_property_from_shape(CurrentShape); 854 | CurrentShape = CurrentShape.parent.value; 855 | } 856 | 857 | // 858 | // Walk the slots to get the values now (check NativeGetPropertyInline/GetExistingProperty) 859 | // 860 | 861 | const NativeObjectTypeSize = host.getModuleType(Module, 'js::NativeObject').size; 862 | const NativeObjectElements = NativeObject.address.add(NativeObjectTypeSize); 863 | const NativeObjectSlots = NativeObject.slots_.address; 864 | const Max = Shape.immutableFlags.bitwiseShiftRight(FIXED_SLOTS_SHIFT).asNumber(); 865 | for(let Idx = 0; Idx < Object.keys(Properties).length; Idx++) { 866 | 867 | // 868 | // Check out NativeObject::getSlot() 869 | // 870 | 871 | const PropertyName = Properties[Idx]; 872 | let PropertyValue = undefined; 873 | let ElementAddr = undefined; 874 | if(Idx < Max) { 875 | ElementAddr = NativeObjectElements.add(Idx * 8); 876 | } else { 877 | ElementAddr = NativeObjectSlots.add((Idx - Max) * 8); 878 | } 879 | 880 | const JSValue = read_u64(ElementAddr); 881 | PropertyValue = jsvalue_to_instance(JSValue); 882 | this._Properties.push(PropertyName + ' : ' + PropertyValue); 883 | } 884 | } 885 | 886 | get Properties() { 887 | return this._Properties; 888 | } 889 | 890 | get ClassName() { 891 | return this._ClassName; 892 | } 893 | 894 | toString() { 895 | if(this._ClassName != 'Object' && Names2Types.hasOwnProperty(this._ClassName)) { 896 | const Type = Names2Types[this._ClassName]; 897 | return new Type(this._Addr).toString(); 898 | } 899 | 900 | if(this._ClassName != 'Object') { 901 | return this._ClassName; 902 | } 903 | 904 | if(this._Properties != undefined && this._Properties.length > 0) { 905 | return '{' + this._Properties.join(', ') + '}'; 906 | } 907 | 908 | if(this._ClassName == 'Object') { 909 | return '[Object]'; 910 | } 911 | 912 | return 'Dunno'; 913 | } 914 | 915 | Logger(Content) { 916 | logln(this._Addr.toString(16) + ': js!JSObject: ' + Content); 917 | } 918 | 919 | Display() { 920 | this.Logger('Content: ' + this); 921 | 922 | // 923 | // If the class name is not Object then it means the toString() method 924 | // might already have displayed the properties. 925 | // {foo:'bar'} VS Math. 926 | // 927 | 928 | if(this._ClassName != 'Object') { 929 | this.Logger('Properties: {' + this._Properties.join(', ') + '}'); 930 | } 931 | } 932 | } 933 | 934 | Names2Types['Object'] = __JSObject; 935 | 936 | function smdump_jsobject(Addr, Type = null) { 937 | init(); 938 | 939 | if(Addr.hasOwnProperty('address')) { 940 | Addr = Addr.address; 941 | } 942 | 943 | let ClassName; 944 | if(Type == 'Object' || Type == null) { 945 | const JSObject = new __JSObject(Addr); 946 | ClassName = JSObject.ClassName; 947 | if(!Names2Types.hasOwnProperty(ClassName)) { 948 | JSObject.Display(); 949 | } 950 | } else { 951 | ClassName = Type; 952 | } 953 | 954 | if(Names2Types.hasOwnProperty(ClassName)) { 955 | const Inst = new Names2Types[ClassName](Addr); 956 | Inst.Display(); 957 | } 958 | } 959 | 960 | function smdump_jsvalue(Addr) { 961 | init(); 962 | 963 | if(Addr == undefined) { 964 | logln('!smdump_jsvalue '); 965 | return; 966 | } 967 | 968 | // 969 | // Ensure Addr is an unsigned value. If we don't do this 970 | // the shift operations don't behave the way we want them to. 971 | // 972 | 973 | Addr = Addr.bitwiseAnd(host.parseInt64('0xffffffffffffffff')); 974 | const JSValue = new __JSValue(Addr); 975 | if(!Tag2Names.hasOwnProperty(JSValue.Tag)) { 976 | logln('Tag ' + JSValue.Tag.toString(16) + ' not recognized'); 977 | return; 978 | } 979 | 980 | const Name = Tag2Names[JSValue.Tag]; 981 | return smdump_jsobject(JSValue.Payload, Name); 982 | } 983 | 984 | function init() { 985 | if(Module != null) { 986 | return; 987 | } 988 | 989 | const Xul = host.currentProcess.Modules.Any( 990 | p => p.Name.toLowerCase().endsWith('xul.dll') 991 | ); 992 | 993 | if(Xul) { 994 | Module = 'xul.dll'; 995 | logln('Detected xul.dll, using it as js module.'); 996 | return; 997 | } 998 | 999 | Module = 'js.exe'; 1000 | } 1001 | 1002 | function ion_insertbp() { 1003 | // XXX: Having the current frame would be better.. but not sure if 1004 | // this is something possible? 1005 | const CurrentThread = host.currentThread; 1006 | const LowestFrame = CurrentThread.Stack.Frames[0]; 1007 | const LocalVariables = LowestFrame.LocalVariables; 1008 | const CodeGenerator = LocalVariables.this; 1009 | if(CodeGenerator === undefined || 1010 | CodeGenerator.targetType.toString() != 'js::jit::CodeGenerator *') { 1011 | logln('The script expects `this` to be a js::jit::CodeGenerator in the lowest frame.'); 1012 | return; 1013 | } 1014 | 1015 | const JITBuffer = CodeGenerator.masm.masm.m_formatter.m_buffer.m_buffer; 1016 | // XXX: So sounds like I can't do JITBuffer.mBegin[JITBuffer.mLength] = 0xcc, 1017 | // so here I am writing ugly things :x 1018 | const BreakpointAddress = JITBuffer.mBegin.address.add(JITBuffer.mLength); 1019 | logln(`JIT buffer is at ${hex(JITBuffer.mBegin.address)}`); 1020 | logln(`Writing breakpoint at ${hex(BreakpointAddress)}`); 1021 | host.evaluateExpression(`*(char*)${hex(BreakpointAddress)} = 0xcc`); 1022 | JITBuffer.mLength += 1; 1023 | } 1024 | 1025 | let Context = undefined; 1026 | function dump_nursery_stats(Nursery) { 1027 | logln(`${hex(Nursery.address)}: js::Nursery`); 1028 | const ChunkCountLimit = Nursery.chunkCountLimit_; 1029 | const NurseryChunk = host.createPointerObject( 1030 | 0, 1031 | Module, 1032 | 'js::NurseryChunk*' 1033 | ); 1034 | const ChunkSize = NurseryChunk.dereference().targetType.size; 1035 | const MaxSize = ChunkCountLimit.multiply(ChunkSize); 1036 | logln(` ChunkCountLimit: ${hex(ChunkCountLimit)} (${MaxSize / 1024 / 1024} MB)`); 1037 | const Capacity = Nursery.capacity_; 1038 | logln(` Capacity: ${hex(Capacity)} bytes`) 1039 | const CurrentChunk = Nursery.currentStartPosition_; 1040 | logln(` CurrentChunk: ${hex(CurrentChunk)}`); 1041 | const Position = Nursery.position_; 1042 | logln(` Position: ${hex(Position)}`); 1043 | logln(' Chunks:'); 1044 | const Chunks = Nursery.chunks_; 1045 | for(let Idx = 0; Idx < Chunks.mLength; Idx++) { 1046 | const Chunk = Chunks.mBegin[Idx]; 1047 | const StartAddress = Chunk.data.address; 1048 | const EndAddress = StartAddress.add(ChunkSize).subtract(1); 1049 | const PaddedIdx = Idx.toString().padStart(2, '0'); 1050 | logln(` ${PaddedIdx}: [${hex(StartAddress)} - ${hex(EndAddress)}]`); 1051 | } 1052 | } 1053 | 1054 | function in_nursery(Addr) { 1055 | init(); 1056 | 1057 | if(Addr == undefined) { 1058 | logln('!in_nursery '); 1059 | return; 1060 | } 1061 | 1062 | // 1063 | // Find 'cx' the JSContext somewhere.. 1064 | // 1065 | 1066 | if(Context == undefined) { 1067 | const CurrentThread = host.currentThread; 1068 | for(const Frame of CurrentThread.Stack.Frames) { 1069 | const Parameters = Frame.Parameters; 1070 | if(Parameters == undefined) { 1071 | continue; 1072 | } 1073 | 1074 | Context = Parameters.cx; 1075 | if(Context == undefined || 1076 | Context.targetType.toString() != 'JSContext *') { 1077 | continue; 1078 | } 1079 | 1080 | logln(`Caching JSContext @${hex(Context.address)} for next times.`); 1081 | break; 1082 | } 1083 | } else { 1084 | logln(`Using previously cached JSContext @${hex(Context.address)}`); 1085 | } 1086 | 1087 | if(Context == undefined) { 1088 | logln('Could not locate a JSContext in this call-stack.'); 1089 | return; 1090 | } 1091 | 1092 | // 1093 | // Get the Nursery. 1094 | // 1095 | 1096 | const Nursery = Context.runtime_.value.gc.nursery_.value; 1097 | const NurseryChunk = host.createPointerObject( 1098 | 0, 1099 | Module, 1100 | 'js::NurseryChunk*' 1101 | ); 1102 | const ChunkUseableSize = NurseryChunk.data.targetType.size; 1103 | 1104 | // 1105 | // Dump some stats about the Nursery. 1106 | // 1107 | 1108 | dump_nursery_stats(Nursery); 1109 | logln('-------'); 1110 | 1111 | // 1112 | // Iterate through the chunk regions. 1113 | // 1114 | 1115 | const Chunks = Nursery.chunks_; 1116 | let FoundChunk = undefined; 1117 | for(let Idx = 0; Idx < Chunks.mLength; Idx++) { 1118 | const Chunk = Chunks.mBegin[Idx]; 1119 | const StartAddress = Chunk.data.address; 1120 | const EndAddress = StartAddress.add(ChunkUseableSize); 1121 | if(Addr.compareTo(StartAddress) >= 0 && 1122 | Addr.compareTo(EndAddress) < 0) { 1123 | FoundChunk = Chunk; 1124 | break; 1125 | } 1126 | } 1127 | 1128 | if(FoundChunk != undefined) { 1129 | logln(`${hex(Addr)} has been found in the js::NurseryChunk @${hex(FoundChunk.data.address)}!`); 1130 | return; 1131 | } 1132 | 1133 | logln(`${hex(Addr)} hasn't been found be in any Nursery js::NurseryChunk.`); 1134 | } 1135 | 1136 | function initializeScript() { 1137 | return [ 1138 | new host.apiVersionSupport(1, 3), 1139 | new host.functionAlias( 1140 | smdump_jsvalue, 1141 | 'smdump_jsvalue' 1142 | ), 1143 | new host.functionAlias( 1144 | smdump_jsobject, 1145 | 'smdump_jsobject' 1146 | ), 1147 | new host.functionAlias( 1148 | ion_insertbp, 1149 | 'ion_insertbp' 1150 | ), 1151 | new host.functionAlias( 1152 | in_nursery, 1153 | 'in_nursery' 1154 | ) 1155 | ]; 1156 | } 1157 | --------------------------------------------------------------------------------