├── 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