This commit is contained in:
yrp
2019-05-07 20:41:14 -07:00
commit f0992b888b
3 changed files with 447 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
# bdump
A windbg-js plugin to dump the cpu state.
## Usage
```
0: kd> .scriptload c:\path\to\bdump.js
0: kd> !bdump "c:\\path\\to\\dump"
```
This will create two files:
- `dump\mem.dmp` is a standard windows dumpfile. Note that this is _not_ a
minidump!
- `dump\regs.json` is a JSON object containing all the register values encoded
as hex strings. E.g
```json
{"rax": "0x41414141", "rbx": "0x42424242", ... }
```
There is also a `cpu-jtoy.py` script which converts the registers from JSON to
YAML. This file is vanilla YAML and is used to allow hex encoding of integers
and avoid the 53-bit limitation in some JSON implementations. Hopefully with
BigInt support in more JS engines soon I can drop this...
This script will also add in the AVX register state with all values zeroed.
## Caveats
- doesn't currently capture `xmm`/`ymm`/`zmm` register states
- doesn't currently capture `mxcsr_mask`
- capturing segments (especially `tr` and `es`) is kinda sketchy
+333
View File
@@ -0,0 +1,333 @@
"use strict";
const log = e => host.diagnostics.debugLog(e);
const logln = e => log(e + '\n');
const hex = e => '0x' + e .toString(16);
const is_usermode = e => e.bitwiseShiftRight(63) == 1;
function usage() {
logln('[bdump] Usage: !bdump "C:\\\\path\\\\to\\\\dump"')
logln('[bdump] This will create a dump directory and fill it with a memory and register files');
logln('[bdump] NOTE: you must include the quotes and escape the backslashes!');
}
function __hex_obj(o) {
for (let k in o) {
// if limit is in our obj, its almost definitely a seg reg and we
// should recurse instead of hexing
// if 0 is in our obj, its probably an array and we should also
// recurse
if (typeof o[k] === 'object' && ('limit' in o[k] || 0 in o[k])) {
__hex_obj(o[k]);
} else if (typeof o[k] == 'boolean') {
continue;
} else {
o[k] = hex(o[k]);
}
}
}
function __create_dir(path) {
const Control = host.namespace.Debugger.Utility.Control;
Control.ExecuteCommand('.shell -i- mkdir ' + path);
}
function __rdmsr(msr_num) {
const Control = host.namespace.Debugger.Utility.Control;
let line = Control.ExecuteCommand('rdmsr ' + hex(msr_num)).Last();
// 1: kd> rdmsr 3a
// msr[3a] = 00000000`00000001
let chunks = line.split(' ');
return host.parseInt64(chunks[chunks.length - 1].replace('`', ''), 16);
}
// must run after __collect_segs
function __collect_msrs(regs) {
const msrs = {
"tsc": 0x10,
"apic_base": 0x1b,
"sysenter_cs": 0x174,
"sysenter_esp": 0x175,
"sysenter_eip": 0x176,
"pat": 0x277,
"efer": 0xc0000080,
"star": 0xc0000081,
"lstar": 0xc0000082,
"cstar": 0xc0000083,
"sfmask": 0xc0000084,
"kernel_gs_base": 0xc0000102,
"tsc_aux": 0xc0000103,
};
for (let msr in msrs) {
regs[msr] = __rdmsr(msrs[msr]);
}
// windbg shows these are zero because it stores the values in the msrs
const msr_fs_base = 0xc0000100;
regs.fs.base = __rdmsr(msr_fs_base);
const msr_gs_base = 0xc0000101;
regs.gs.base = __rdmsr(msr_gs_base);
}
function __collect_seg(n) {
const Control = host.namespace.Debugger.Utility.Control;
let r = {};
let line = Control.ExecuteCommand('dg ' + n).Last();
// Sel Base Limit Type l ze an es ng Flags
// ---- ----------------- ----------------- ---------- - -- -- -- -- --------
// 0033 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P Lo 000002fb
if (line.indexOf('Unable to get descriptor') != -1) {
logln('[bdump] could not recover ' + n + '!');
return null;
}
let chunks = line.split(' ');
r.present = chunks[9] == 'P';
r.selector = host.parseInt64(chunks[0], 16);
r.base = host.parseInt64(chunks[1].replace('`', ''), 16);
r.limit = host.parseInt64(chunks[2].replace('`', ''), 16);
r.attr = host.parseInt64(chunks[chunks.length - 1].replace('`', ''), 16);
return r;
}
// must run before __collect_msrs
function __collect_segs(regs) {
regs.es = __collect_seg('es');
regs.cs = __collect_seg('cs');
regs.ss = __collect_seg('ss');
regs.ds = __collect_seg('ds');
// these values below will be wrong -- reading msrs will give us the
// correct ones
regs.fs = __collect_seg('fs');
regs.gs = __collect_seg('gs');
regs.tr = __collect_seg('tr');
// our ghetto string parsing is wrong for tr, force it to true
regs.tr.present = true;
regs.ldtr = __collect_seg('ldtr');
}
function __collect_user(regs) {
const User = host.currentThread.Registers.User;
regs.rax = User.rax;
regs.rbx = User.rbx;
regs.rcx = User.rcx;
regs.rdx = User.rdx;
regs.rsi = User.rsi;
regs.rdi = User.rdi;
regs.rip = User.rip;
regs.rsp = User.rsp;
regs.rbp = User.rbp;
regs.r8 = User.r8;
regs.r9 = User.r9;
regs.r10 = User.r10;
regs.r11 = User.r11;
regs.r12 = User.r12;
regs.r13 = User.r13;
regs.r14 = User.r14;
regs.r15 = User.r15;
regs.rflags = User.efl;
if (is_usermode(regs.rip)) {
regs.dr0 = User.dr0;
regs.dr1 = User.dr1;
regs.dr2 = User.dr2;
regs.dr3 = User.dr3;
regs.dr6 = User.dr6;
regs.dr7 = User.dr7;
}
}
function __collect_fp(regs) {
const Fprs = host.currentThread.Registers.FloatingPoint;
regs.fpcw = Fprs.fpcw;
regs.fpsw = Fprs.fpsw;
regs.fptw = Fprs.fptw;
regs.fpst = Array();
regs.fpst[0] = Fprs.st0;
regs.fpst[1] = Fprs.st1;
regs.fpst[2] = Fprs.st2;
regs.fpst[3] = Fprs.st3;
regs.fpst[4] = Fprs.st4;
regs.fpst[5] = Fprs.st5;
regs.fpst[6] = Fprs.st6;
regs.fpst[7] = Fprs.st7;
}
function __collect_simd(regs) {
const Simd = host.currentThread.Registers.SIMD;
// XXX TODO XXX
if (is_usermode(regs.rip)) {
regs.mxcsr = Simd.mxcsr;
}
}
function __collect_kern(regs) {
const Kern = host.currentThread.Registers.Kernel;
regs.cr0 = Kern.cr0;
regs.cr2 = Kern.cr2;
regs.cr3 = Kern.cr3;
regs.cr4 = Kern.cr4;
regs.cr8 = Kern.cr8;
regs.xcr0 = Kern.xcr0;
regs.gdtr = {};
regs.gdtr.base = Kern.gdtr;
regs.gdtr.limit = Kern.gdtl;
regs.idtr = {};
regs.idtr.base = Kern.idtr;
regs.idtr.limit = Kern.idtl;
if (!is_usermode(regs.rip)) {
regs.dr0 = Kern.kdr0;
regs.dr1 = Kern.kdr1;
regs.dr2 = Kern.kdr2;
regs.dr3 = Kern.kdr3;
regs.dr6 = Kern.kdr6;
regs.dr7 = Kern.kdr7;
regs.mxcsr = Kern.kmxcsr;
}
}
function __fixup_regs(regs) {
// regs im not sure how to get out of windbg...
logln("[bdump] don't know how to get mxcsr_mask or fpop, setting to zero...");
regs.mxcsr_mask = 0;
regs.fpop = 0;
logln('[bdump]');
logln("[bdump] don't know how to get avx registers, skipping...");
logln('[bdump]');
// top 32 bits of tr are incorrectly either 0 or -1
// take the top bits from the gdtr and OR them in to get a valid tr
if (regs.tr.base.bitwiseShiftRight(32) == host.Int64(0xffffffff)
|| regs.tr.base.bitwiseShiftRight(32) == host.Int64(0)) {
logln('[bdump] tr.base is not cannonical...');
logln('[bdump] old tr.base: ' + hex(regs.tr.base));
const low32 = host.Int64(0xffffffff);
let new_tr = regs.tr.base.bitwiseAnd(low32);
const hi32 = host.parseInt64('0xffffffff00000000', 16);
const top_bits = regs.gdtr.base.bitwiseAnd(hi32);
new_tr = new_tr.bitwiseOr(top_bits);
regs.tr.base = new_tr;
logln('[bdump] new tr.base: ' + hex(regs.tr.base));
logln('[bdump]');
}
// need to make sure cs has that magic value set?
const flag = host.Int64(0x2000);
if (regs.cs.attr.bitwiseAnd(flag) != flag) {
logln('[bdump] setting flag 0x2000 on cs.attr...');
logln('[bdump] old cs.attr: ' + hex(regs.cs.attr));
regs.cs.attr = regs.cs.attr.bitwiseOr(flag);
logln('[bdump] new cs.attr: ' + hex(regs.cs.attr));
logln('[bdump]');
}
// kernel/user gs can be swapped when reading from msrs
if (is_usermode(regs.rip) != is_usermode(regs.gs.base)) {
logln("[bdump] rip and gs don't match kernel/user, swapping...");
const tmp = regs.kernel_gs_base;
regs.kernel_gs_base = regs.gs.base;
regs.gs.base = tmp;
logln('[bdump] rip: ' + hex(regs.rip));
logln('[bdump] new gs.base: ' + hex(regs.gs.base));
logln('[bdump] new kernel_gs_base: ' + hex(regs.kernel_gs_base));
logln('[bdump]');
}
// if es was lost to the void, copy ds
if (regs.es === null) {
logln("[bdump] could not recover es, copying ds...");
Object.assign(regs.es, regs.ds);
logln('[bdump]');
}
// turn everything into strings because javascript
__hex_obj(regs);
}
function __collect_regs() {
let regs = {};
__collect_user(regs);
__collect_segs(regs);
__collect_msrs(regs);
__collect_fp(regs);
__collect_simd(regs);
__collect_kern(regs);
return regs;
}
function __save_regs(path, regs) {
const Fs = host.namespace.Debugger.Utility.FileSystem;
const data = JSON.stringify(regs);
let file = Fs.CreateFile(path + '\\regs.json');
let writer = Fs.CreateTextWriter(file);
writer.WriteLine(data);
file.Close();
}
function __save_mem(path) {
const Control = host.namespace.Debugger.Utility.Control;
for (let line of Control.ExecuteCommand('.dump /ka ' + path + '\\mem.dmp')) {
logln('[bdump] ' + line);
}
}
function __bdump(path) {
if (path == undefined) {
usage();
return;
}
logln('[bdump] creating dir...');
__create_dir(path);
logln('[bdump] saving regs...');
const regs = __collect_regs();
logln('[bdump] register fixups...');
__fixup_regs(regs);
__save_regs(path, regs);
logln('[bdump] saving mem...');
__save_mem(path);
logln('[bdump] done!');
}
function initializeScript() {
usage();
return [
new host.apiVersionSupport(1, 2),
new host.functionAlias(__bdump, "bdump"),
];
}
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python
import json
import sys
import yaml
"""
This is an annoying hack -- Right now the windbg js dumps integer values as
hex strings, e.g.:
{ "rax": "0x4141" }
This is because js ints are 53 bit, so we can't actually store full 64b
registers. Writing a decoder for this in rust kind sucks for two reasons:
- We'd need to create a separate decode function for each bitsize we want
to decode, e.g. one for u16, u32, and u64
- We'd need to create a separate decode function for each array size we
want to decide, e.g. [u64; 4 vs [u64; 6]
While this is definitely doable, I'd rather just bang out a json to yaml
converter and have the mofo load yaml instead.
"""
def usage():
print('Usage: %s <cpu-state.json>' % sys.argv[0])
sys.exit(-1)
# json hex string decoder class
# https://stackoverflow.com/questions/45068797/how-to-convert-string-int-json-into-real-int-with-json-loads
class HexStr(json.JSONDecoder):
def decode(self, s):
result = super(HexStr, self).decode(s)
return self._decode(result)
def _decode(self, o):
if isinstance(o, str) or isinstance(o, unicode):
try:
return int(o, 0)
except ValueError:
try:
return float(o)
except ValueError:
return o
elif isinstance(o, dict):
return {k: self._decode(v) for k, v in o.items()}
elif isinstance(o, list):
return [self._decode(v) for v in o]
else:
return o
# yaml hex encoder functions
# https://stackoverflow.com/questions/9100662/how-to-print-integers-as-hex-strings-using-json-dumps-in-python/9101562#9101562
def hexint_presenter(dumper, data):
return dumper.represent_int('%#x' % data)
def unicode_representer(dumper, uni):
node = yaml.ScalarNode(tag=u'tag:yaml.org,2002:str', value=uni)
return node
yaml.add_representer(int, hexint_presenter)
yaml.add_representer(long, hexint_presenter)
yaml.add_representer(unicode, unicode_representer)
def main():
if len(sys.argv) != 2: usage()
json_cpu_file = sys.argv[1]
with open(json_cpu_file, 'rb') as h:
j = json.load(h, cls=HexStr)
# HACK HACK HACK
# remove when I acutally lift zmm state from windbg
j['zmm'] = []
for ii in range(32):
j['zmm'].append({ 'q': [0 for _ in range(8)] })
y = yaml.dump(j)
print(y)
if __name__ == '__main__':
main()