forked from 0vercl0k/windbg-scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from 0vercl0k/master
gdt.js: display segment attributes (0vercl0k#4)
- Loading branch information
Showing
4 changed files
with
361 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
# gdt.js | ||
|
||
`gdt.js` is a [JavaScript](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/javascript-debugger-scripting) debugger extension for WinDbg that dumps the [Global Descriptor Table](https://wiki.osdev.org/Global_Descriptor_Table) on 64-bit kernels. I wrote this extension because I always find the output of the `dg` command confusing, if not broken. | ||
|
||
|
||
## Usage | ||
|
||
Run `.scriptload gdt.js` to load the script. You can dump a specific entry by passing the segment selector to the `!gdt` command, or it will dump the entire table if nothing is passed. Run `!wow64exts.sw` if you are running the script while being in the context of a [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details) thread. | ||
|
||
## Examples | ||
|
||
* Dumping the GDT entry that enables [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details): | ||
``` | ||
32.kd> !gdt @cs | ||
dt nt!_KGDTENTRY64 0xfffff8045215dfd0 | ||
Base: [0x0 -> 0xffffffff] | ||
Type: Code Execute/Read Accessed (0xb) | ||
DPL: 0x3 | ||
Present: 0x1 | ||
Mode: 32b Compat | ||
@$gdt(@cs) | ||
32.kd> dg @cs | ||
P Si Gr Pr Lo | ||
Sel Base Limit Type l ze an es ng Flags | ||
---- ----------------- ----------------- ---------- - -- -- -- -- -------- | ||
0023 00000000`00000000 00000000`ffffffff Code RE Ac 3 Bg Pg P Nl 00000cfb | ||
``` | ||
|
||
* Dumping the GDT entry that allows 32-bit code to invoke 64-bit code: | ||
``` | ||
32.kd> !gdt 0x33 | ||
dt nt!_KGDTENTRY64 0xfffff8045215dfe0 | ||
Base: [0x0 -> 0x0] | ||
Type: Code Execute/Read Accessed (0xb) | ||
DPL: 0x3 | ||
Present: 0x1 | ||
Mode: 64b | ||
@$gdt(0x33) | ||
32.kd> dg 33 | ||
P Si Gr Pr Lo | ||
Sel Base Limit Type l ze an es ng Flags | ||
---- ----------------- ----------------- ---------- - -- -- -- -- -------- | ||
0033 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P Lo 000002fb | ||
``` | ||
|
||
* Dumping the [Task State Segment](https://wiki.osdev.org/Task_State_Segment): | ||
``` | ||
32.kd> !gdt @tr | ||
dt nt!_KGDTENTRY64 0xfffff8045215dff0 | ||
Base: [0xfffff8045215c000 -> 0xfffff8045215c067] | ||
Type: TSS64 Busy (0xb) | ||
DPL: 0x0 | ||
Present: 0x1 | ||
@$gdt(@tr) | ||
32.kd> dg @tr | ||
P Si Gr Pr Lo | ||
Sel Base Limit Type l ze an es ng Flags | ||
---- ----------------- ----------------- ---------- - -- -- -- -- -------- | ||
0040 00000000`5215c000 00000000`00000067 TSS32 Busy 0 Nb By P Nl 0000008b | ||
``` | ||
|
||
* Dumping the [Thread Environment Block](https://en.wikipedia.org/wiki/Win32_Thread_Information_Block) of a [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details) thread: | ||
``` | ||
32.kd> !gdt @fs | ||
dt nt!_KGDTENTRY64 0xfffff8045215e000 | ||
Base: [0x326000 -> 0x329c00] | ||
Type: Data Read/Write Accessed (0x3) | ||
DPL: 0x3 | ||
Present: 0x1 | ||
@$gdt(@fs) | ||
32.kd> !teb | ||
Wow64 TEB32 at 0000000000326000 | ||
ExceptionList: 00000000004ff59c | ||
StackBase: 0000000000500000 | ||
StackLimit: 00000000004f2000 | ||
SubSystemTib: 0000000000000000 | ||
FiberData: 0000000000001e00 | ||
ArbitraryUserPointer: 0000000000000000 | ||
Self: 0000000000326000 | ||
EnvironmentPointer: 0000000000000000 | ||
ClientId: 0000000000001ad8 . 0000000000001adc | ||
RpcHandle: 0000000000000000 | ||
Tls Storage: 0000000000834188 | ||
PEB Address: 0000000000323000 | ||
LastErrorValue: 0 | ||
LastStatusValue: c000007c | ||
Count Owned Locks: 0 | ||
HardErrorMode: 0 | ||
``` | ||
* Dumping the entire GDT on a Windows 10 64-bit Virtual Machine: | ||
``` | ||
32.kd> !gdt | ||
Dumping the GDT from 0xfffff8045215dfb0 to 0xfffff8045215e007.. | ||
[0]: dt nt!_KGDTENTRY64 0xfffff8045215dfb0 | ||
Base: [0x0 -> 0x0] | ||
Type: Reserved (0x0) | ||
DPL: 0x0 | ||
Present: 0x0 | ||
[1]: dt nt!_KGDTENTRY64 0xfffff8045215dfb8 | ||
Base: [0x0 -> 0x0] | ||
Type: Reserved (0x0) | ||
DPL: 0x0 | ||
Present: 0x0 | ||
[2]: dt nt!_KGDTENTRY64 0xfffff8045215dfc0 | ||
Base: [0x0 -> 0x0] | ||
Type: Code Execute/Read Accessed (0xb) | ||
DPL: 0x0 | ||
Present: 0x1 | ||
Mode: 64b | ||
[3]: dt nt!_KGDTENTRY64 0xfffff8045215dfc8 | ||
Base: [0x0 -> 0x0] | ||
Type: Data Read/Write Accessed (0x3) | ||
DPL: 0x0 | ||
Present: 0x1 | ||
[4]: dt nt!_KGDTENTRY64 0xfffff8045215dfd0 | ||
Base: [0x0 -> 0xffffffff] | ||
Type: Code Execute/Read Accessed (0xb) | ||
DPL: 0x3 | ||
Present: 0x1 | ||
Mode: 32b Compat | ||
[5]: dt nt!_KGDTENTRY64 0xfffff8045215dfd8 | ||
Base: [0x0 -> 0xffffffff] | ||
Type: Data Read/Write Accessed (0x3) | ||
DPL: 0x3 | ||
Present: 0x1 | ||
[6]: dt nt!_KGDTENTRY64 0xfffff8045215dfe0 | ||
Base: [0x0 -> 0x0] | ||
Type: Code Execute/Read Accessed (0xb) | ||
DPL: 0x3 | ||
Present: 0x1 | ||
Mode: 64b | ||
[7]: dt nt!_KGDTENTRY64 0xfffff8045215dfe8 | ||
Base: [0x0 -> 0x0] | ||
Type: Reserved (0x0) | ||
DPL: 0x0 | ||
Present: 0x0 | ||
[8]: dt nt!_KGDTENTRY64 0xfffff8045215dff0 | ||
Base: [0xfffff8045215c000 -> 0xfffff8045215c067] | ||
Type: TSS64 Busy (0xb) | ||
DPL: 0x0 | ||
Present: 0x1 | ||
[9]: dt nt!_KGDTENTRY64 0xfffff8045215e000 | ||
Base: [0x326000 -> 0x329c00] | ||
Type: Data Read/Write Accessed (0x3) | ||
DPL: 0x3 | ||
Present: 0x1 | ||
@$gdt() | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
// Axel '0vercl0k' Souchet - October 8 2021 | ||
|
||
'use strict'; | ||
|
||
// | ||
// Small reminders from the manual intels on segments in x64: | ||
// - Because ES, DS, and SS segment registers are not used in 64-bit mode, their fields (base, limit, and attribute) in | ||
// segment descriptor registers are ignored. | ||
// - Selector.Index selects one of 8192 descriptors in the GDT or LDT. The processor multiplies | ||
// the index value by 8 (the number of bytes in a segment descriptor) and adds the result to the base | ||
// address of the GDT or LDT (from the GDTR or LDTR register, respectively). | ||
// - The first entry of the GDT is not used by the processor. | ||
// - The hidden descriptor register fields for FS.base and GS.base are physically mapped to MSRs in order to load all | ||
// address bits supported by a 64-bit implementation. Software with CPL = 0 (privileged software) can load all | ||
// supported linear-address bits into FS.base or GS.base using WRMSR. | ||
// | ||
|
||
const log = host.diagnostics.debugLog; | ||
const logln = p => log(`${p}\n`); | ||
const hex = p => `0x${p.toString(16)}`; | ||
|
||
const GdtSystemEntryTypes = new Map([ | ||
[0, 'Reserved'], | ||
[1, 'Reserved'], | ||
[2, 'LDT'], | ||
[3, 'Reserved'], | ||
[4, 'Reserved'], | ||
[5, 'Reserved'], | ||
[6, 'Reserved'], | ||
[7, 'Reserved'], | ||
[8, 'Reserved'], | ||
[9, 'TSS64 Available'], | ||
[10, 'Reserved'], | ||
[11, 'TSS64 Busy'], | ||
[12, 'CallGate64'], | ||
[13, 'Reserved'], | ||
[14, 'InterruptGate64'], | ||
[15, 'TrapGate64'], | ||
]); | ||
|
||
const GdtNonSystemEntryTypes = new Map([ | ||
[0, 'Data Read-Only'], | ||
[1, 'Data Read-Only Accessed'], | ||
[2, 'Data Read/Write'], | ||
[3, 'Data Read/Write Accessed'], | ||
[4, 'Data Read-Only Expand-Down'], | ||
[5, 'Data Read-Only Expand-Down Accessed'], | ||
[6, 'Data Read/Write Expand-Down'], | ||
[7, 'Data Read/Write Expand-Down Accessed'], | ||
[8, 'Code Execute-Only'], | ||
[9, 'Code Execute-Only Accessed'], | ||
[10, 'Code Execute/Read'], | ||
[11, 'Code Execute/Read Accessed'], | ||
[12, 'Code Execute-Only Conforming'], | ||
[13, 'Code Execute-Only Conforming Accessed'], | ||
[14, 'Code Execute/Read Conforming'], | ||
[15, 'Code Execute/Read Conforming Accessed'], | ||
]); | ||
|
||
const GdtEntryTypes = new Map([ | ||
[0, GdtSystemEntryTypes], | ||
[1, GdtNonSystemEntryTypes] | ||
]); | ||
|
||
class GdtEntry { | ||
constructor(Addr) { | ||
this._Addr = Addr; | ||
const Entry = host.createPointerObject(Addr, 'nt', '_KGDTENTRY64*'); | ||
const LimitHigh = Entry.Bits.LimitHigh.bitwiseShiftLeft(16); | ||
const LimitLow = Entry.LimitLow; | ||
this._Limit = LimitHigh.add(LimitLow); | ||
// For whatever reason _KGDTENTRY64 is 5 bits long. The intel manuals describes | ||
// it as 4bits and the 'Descriptor type' bit. | ||
// We grab the lower 4 bits as the type, and the MSB as the 'Descriptor type'. | ||
this._Type = Entry.Bits.Type & 15; | ||
this._NonSystem = (Entry.Bits.Type >> 4) & 1; | ||
this._TypeS = GdtEntryTypes.get(this._NonSystem).get(this._Type); | ||
// Note that system descriptors in IA-32e mode are 16 bytes instead | ||
// of 8 bytes. | ||
this._Size = 8; | ||
if (!this._NonSystem && this._TypeS != 'Reserved') { | ||
this._Size = 16; | ||
} | ||
this._Dpl = Entry.Bits.Dpl; | ||
this._Granularity = Entry.Bits.Granularity; | ||
this._Present = Entry.Bits.Present; | ||
this._LongMode = Entry.Bits.LongMode; | ||
this._DefaultBig = Entry.Bits.DefaultBig; | ||
const BaseUpper = this._Size == 8 ? 0 : Entry.BaseUpper.bitwiseShiftLeft(32); | ||
const BaseHigh = Entry.Bytes.BaseHigh.bitwiseShiftLeft(24); | ||
const BaseMiddle = Entry.Bytes.BaseMiddle.bitwiseShiftLeft(16); | ||
const BaseLow = Entry.BaseLow; | ||
this._Base = BaseUpper.add(BaseHigh).add(BaseMiddle).add(BaseLow); | ||
const Flags1 = Entry.Bytes.Flags1; | ||
const Flags2 = Entry.Bytes.Flags2.bitwiseShiftLeft(8); | ||
this._Attrs = Flags2.add(Flags1); | ||
} | ||
|
||
toString() { | ||
const Increments = this._Granularity == 1 ? 1024 * 4 : 1; | ||
// For example, when the granularity flag is set, a limit of 0 results in | ||
// valid offsets from 0 to 4095. | ||
const Size = this._Limit * Increments + (this._Granularity ? 0xfff : 0); | ||
let S = `dt nt!_KGDTENTRY64 ${hex(this._Addr)} | ||
Base: [${hex(this._Base)} -> ${hex(this._Base.add(Size))}] | ||
Type: ${this._TypeS} (${hex(this._Type)}) | ||
DPL: ${hex(this._Dpl)} | ||
Present: ${hex(this._Present)} | ||
Atributes: ${hex(this._Attrs)}`; | ||
if (this._TypeS.startsWith('Code')) { | ||
S += ` | ||
Mode: ${this._LongMode ? '64b' : (this._DefaultBig ? '32b Compat' : '16b Compat')}` | ||
} | ||
return S; | ||
} | ||
} | ||
|
||
function GetGdt() { | ||
const Control = host.namespace.Debugger.Utility.Control; | ||
const [_, GdtrValue] = Control.ExecuteCommand('r @gdtr').First().split('='); | ||
const [__, GdtlValue] = Control.ExecuteCommand('r @gdtl').First().split('='); | ||
return [host.parseInt64(GdtrValue, 16), host.parseInt64(GdtlValue, 16)]; | ||
} | ||
|
||
function DumpGdtEntry(Addr) { | ||
return new GdtEntry(Addr); | ||
} | ||
|
||
function DumpAllGdt() { | ||
const [GdtBase, Gdtl] = GetGdt(); | ||
const GdtEnd = GdtBase.add(Gdtl); | ||
logln(`Dumping the GDT from ${hex(GdtBase)} to ${hex(GdtEnd)}..`); | ||
for (let CurrentEntry = GdtBase, Idx = 0; | ||
CurrentEntry.compareTo(GdtEnd) < 0; | ||
Idx++) { | ||
const Entry = DumpGdtEntry(CurrentEntry); | ||
logln(`[${Idx}]: ${Entry}`); | ||
CurrentEntry = CurrentEntry.add(Entry._Size); | ||
} | ||
} | ||
|
||
function DumpGdt(Selector) { | ||
const [GdtBase, _] = GetGdt(); | ||
const Index = Selector.bitwiseShiftRight(3); | ||
const Offset = Index.multiply(8); | ||
const EntryAddress = GdtBase.add(Offset); | ||
const Entry = DumpGdtEntry(EntryAddress); | ||
logln(Entry); | ||
} | ||
|
||
function Gdt(Selector) { | ||
const Attributes = host.currentSession.Attributes; | ||
const IsKernel = Attributes.Target.IsKernelTarget; | ||
// | ||
// XXX: Not sure how to do this better? | ||
// Attributes.Machine.PointerSize is 4 when running in a Wow64 thread :-/. | ||
// | ||
let Is64Bit = true; | ||
try { host.createPointerObject(0, 'nt', '_KGDTENTRY64*'); } catch(e) { Is64Bit = false; } | ||
if (!IsKernel || !Is64Bit) { | ||
logln('The running session is not a kernel session or it is not running a 64-bit OS, so exiting'); | ||
return; | ||
} | ||
|
||
if (Selector == undefined) { | ||
DumpAllGdt(); | ||
} else { | ||
DumpGdt(Selector); | ||
} | ||
} | ||
|
||
function initializeScript() { | ||
return [ | ||
new host.apiVersionSupport(1, 3), | ||
new host.functionAlias( | ||
Gdt, | ||
'gdt' | ||
), | ||
]; | ||
} |