Files
MemProcFS/vmm/modules/m_proc_console.c
2026-01-04 15:00:03 +01:00

731 lines
30 KiB
C

// m_proc_console.c : implementation of the console info built-in module.
//
// (c) Ulf Frisk, 2024-2026
// Author: Ulf Frisk, pcileech@frizk.net
//
#include "modules.h"
#include "../version.h"
#include "../vmmwinobj.h"
#include "../vmmwinreg.h"
#include "../charutil.h"
typedef struct tdOB_MCON_CONTEXT {
OB ObHdr;
BOOL fValid;
DWORD cbText; // Length in bytes of uszText (excl. NULL).
LPSTR uszText; // The console text.
} OB_MCON_CONTEXT, *POB_MCON_CONTEXT;
#define MCON_MAX_HEAP_ENTRIES 512
#define MCON_SCREEN_MAX_CHARS 0x00400000 // 4M
#define MCON_INIT_BUFFER_SIZE 0x00800000 // 8M
typedef struct tdMCON_INIT_CONTEXT {
BOOL f32;
BOOL fSuccess;
DWORD dwBuild;
VMM_MODULE_ID MID;
PVMM_PROCESS pProcess;
QWORD vaAllocation;
DWORD cbAllocation;
DWORD cbAllocationInitialOffset;
DWORD cchBufferText;
BYTE pbAllocation[MCON_INIT_BUFFER_SIZE];
union {
WCHAR wszBufferText[MCON_SCREEN_MAX_CHARS];
BYTE pbBufferText[MCON_INIT_BUFFER_SIZE * 3];
};
} MCON_INIT_CONTEXT, *PMCON_INIT_CONTEXT;
/*
* "Compact" a wide-char null-terminated buffer by removing all trailing spaces
* and excessive newlines (more than 3 newlines in a row + newlines at the end).
* -- wsz: The wide-char buffer to compress.
* -- return: the number of chars in the wise-char buffer (excl. NULL).
*/
DWORD MCON_Init_CompactConsoleBuffer(_Inout_ LPWSTR wsz)
{
WCHAR ch;
BOOL fSkipToNewLine = FALSE;
DWORD iDst = 0, iSrc = 0, iNoSpace = 0;
while(TRUE) {
ch = wsz[iSrc++];
if(0xff == ch) { ch = 0x20; }
if(0xff == (ch >> 8)) { fSkipToNewLine = TRUE; }
if(fSkipToNewLine && (ch != 0x0A) && (ch != 0)) { continue; }
wsz[iDst] = ch;
if(ch == 0) {
while(iDst && wsz[iDst - 1] == 0x0A) { iDst--; }
wsz[iDst] = 0;
return iDst ? iDst - 1 : 0;
}
if((ch != 0x20) && (ch != 0x0A)) {
iNoSpace = iDst;
}
if(ch == 0x0A) {
iDst = iNoSpace + 1;
if((iDst >= 3) && (wsz[iDst - 1] == 0x0A) && (wsz[iDst - 2] == 0x0A) && (wsz[iDst - 3] == 0x0A)) {
iDst--;
}
wsz[iDst] = ch;
iNoSpace = iDst;
fSkipToNewLine = FALSE;
}
iDst++;
}
}
// ----------------------------------------------------------------------------
// WINDOWS 11 26100+ CONSOLE PARSING:
// ---
// Windows 22 26100 (24H2) stores the console buffer in a single allocation.
// The buffer is stored as an array of row records. Each row record contains
// the text to be recovered inline. The record length may vary, but fuzzing
// of the record length is possible since the header format is known.
// ----------------------------------------------------------------------------
typedef struct tdMCON_WIN11_26100_ROW_RECORD_HEAD {
QWORD vaText0;
QWORD qwZero;
QWORD vaText;
QWORD qwUnk1;
QWORD vaTextEnd;
QWORD qwUnk2;
QWORD vaUnk;
QWORD qwUnk3[6];
WORD wUnk1;
WORD wContinueNext;
DWORD dwUnk1;
} MCON_WIN11_26100_ROW_RECORD_HEAD, *PMCON_WIN11_26100_ROW_RECORD_HEAD;
_Success_(return)
BOOL MCON_Initialize_Win11_26100_ValidateRecord(_In_ PMCON_WIN11_26100_ROW_RECORD_HEAD pR, _In_ QWORD vaR, _In_ DWORD cbR)
{
if(!VMM_UADDR64_8(pR->vaText) || !VMM_UADDR64_8(pR->vaText0) || !VMM_UADDR64_8(pR->vaTextEnd) || !VMM_UADDR64_8(pR->vaUnk) || (pR->qwZero != 0)) { return FALSE; }
if((pR->vaText < vaR) || (pR->vaTextEnd < pR->vaText) || (pR->vaTextEnd >= vaR + cbR)) { return FALSE; }
return TRUE;
}
/*
* Parse a Windows 11 26100/24H2 console window:
* In Win11 24H2 the console screen exists as an array of row records.
* But it's possible to fuzz it from the record header.
*/
VOID MCON_Initialize_Win11_26100(_In_ VMM_HANDLE H, _Inout_ PMCON_INIT_CONTEXT ctx)
{
PBYTE pbRecordArray;
QWORD vaRecordArray, vaR;
DWORD cbR, iR, cR, cbText, cbTextTotal;
PMCON_WIN11_26100_ROW_RECORD_HEAD pR;
// set initial data:
if((ctx->cbAllocation < 0x1000) || (ctx->cbAllocation >= MCON_INIT_BUFFER_SIZE)) { return; }
VmmRead2(H, ctx->pProcess, ctx->vaAllocation, ctx->pbAllocation, ctx->cbAllocation, VMM_FLAG_ZEROPAD_ON_FAIL);
pbRecordArray = ctx->pbAllocation;
vaRecordArray = ctx->vaAllocation;
// test initial record validity & fuzz record length:
cbR = 0x1000;
vaR = vaRecordArray;
pR = (PMCON_WIN11_26100_ROW_RECORD_HEAD)pbRecordArray;
if(!MCON_Initialize_Win11_26100_ValidateRecord(pR, vaR, cbR)) { return; }
cbR = 0x100;
while(TRUE) {
vaR = vaRecordArray + cbR;
pR = (PMCON_WIN11_26100_ROW_RECORD_HEAD)(pbRecordArray + cbR);
if(MCON_Initialize_Win11_26100_ValidateRecord(pR, vaR, cbR)) {
break;
}
if(cbR == 0xff0) { return; }
cbR += 0x10;
}
cR = ctx->cbAllocation / cbR;
// iterate over records:
cbTextTotal = 0;
for(iR = 0; iR < cR; iR++) {
vaR = vaRecordArray + iR * cbR;
pR = (PMCON_WIN11_26100_ROW_RECORD_HEAD)(pbRecordArray + iR * cbR);
if(!MCON_Initialize_Win11_26100_ValidateRecord(pR, vaR, cbR)) { break; }
cbText = (DWORD)(pR->vaTextEnd - pR->vaText);
if(cbText > 0x1000) { break; }
if(cbText) {
memcpy(ctx->pbBufferText + cbTextTotal, (PBYTE)pR + (pR->vaText - vaR), cbText);
}
cbTextTotal += cbText;
if(pR->wContinueNext) {
// line continuations may be space-padded, assume spaces should be removed.
while((cbTextTotal > 2) && (ctx->pbBufferText[cbTextTotal - 2] == 0x20) && (ctx->pbBufferText[cbTextTotal - 1] == 0x00)) { cbTextTotal -= 2; }
} else {
// add newline:
ctx->pbBufferText[cbTextTotal++] = 0x0A;
ctx->pbBufferText[cbTextTotal++] = 0x00;
}
if(cbTextTotal >= (MCON_SCREEN_MAX_CHARS / 2) - 0x2000) { break; }
}
ctx->pbBufferText[cbTextTotal++] = 0x00;
ctx->pbBufferText[cbTextTotal++] = 0x00;
ctx->cchBufferText = cbTextTotal >> 1;
}
// ----------------------------------------------------------------------------
// WINDOWS 10 17763 --> WINDOWS 11 22631 CONSOLE PARSING:
// ---
// The formats differ slightly, the Windows 11 format relies on a single
// allocation which may or may not have data in-line in the record. The
// Windows 10 format relies on a pointer array with external text, but
// the record format is similar so we can use the same parsing function.
// ----------------------------------------------------------------------------
typedef struct tdMCON_OFFSETS {
WORD cb;
WORD cbHeader;
WORD oPartialLine;
WORD oCountText;
WORD oPtrSelf;
WORD oPtrText;
WORD oPtrScreenInfo;
} MCON_OFFSETS, *PMCON_OFFSETS;
/*
* Parse a Windows 10 / 11 console window built upon the 3-char wchar format.
* In Windows 11 everything is by default stored in one big buffer, but text
* references may be external as well.
* In Windows 10 things are stored in individual allocations with external
* text, but callers of this function should have prepared the buffer to
* contain records (array-wise) in the Windows 11 format.
*/
VOID MCON_Initialize_Win1011_RowRecordParse(_In_ VMM_HANDLE H, _In_ PMCON_OFFSETS off, _Inout_ PMCON_INIT_CONTEXT ctx)
{
WCHAR ch;
BOOL f32 = ctx->f32;
BOOL f, fPartialLine = FALSE, fExternalText = FALSE;
DWORD i, j, cb, cbo = 0, iRowRecord;
QWORD va, vaCurrent, vaSelf, vaRowText, vaScreenInfo, vaScreenInfoThis;
PBYTE pb, pbRowText;
DWORD cchRowText, cchTextTotal = 0, cRowRecords = 0;
PVMMOB_SCATTER hObScatter = NULL;
// set initial data:
va = ctx->vaAllocation;
cb = ctx->cbAllocation;
pb = ctx->pbAllocation;
cbo = ctx->cbAllocationInitialOffset;
ctx->cchBufferText = 0;
// test record validity by verifying ptr to "screen info" main object.
vaScreenInfo = VMM_PTR_OFFSET(f32, pb, cbo + off->oPtrScreenInfo);
if(!VMM_UADDR_8_16(f32, vaScreenInfo) || (vaScreenInfo != VMM_PTR_OFFSET(f32, pb, cbo + off->oPtrScreenInfo + off->cb))) { return; }
// validate and count row records:
while(TRUE) {
if(cbo + off->cb >= cb) { break; }
vaCurrent = va + cbo;
cchRowText = *(PWORD)(pb + cbo + off->oCountText);
vaRowText = VMM_PTR_OFFSET(f32, pb, cbo + off->oPtrText);
vaSelf = VMM_PTR_OFFSET(f32, pb, cbo + off->oPtrSelf);
vaScreenInfoThis = VMM_PTR_OFFSET(f32, pb, cbo + off->oPtrScreenInfo);
// sanity checks:
if(!VMM_UADDR64(vaRowText)) { break; }
if(vaCurrent != vaSelf) { break; }
if(vaScreenInfo != vaScreenInfoThis) { break; }
if(cchTextTotal + cchRowText >= MCON_SCREEN_MAX_CHARS + 2) { break; }
// valid record:
cRowRecords++;
cchTextTotal += cchRowText + 1;
fExternalText = fExternalText || (vaRowText < vaCurrent) || (vaRowText + cchRowText * 3 > vaCurrent + off->cb);
cbo += off->cb;
}
if((cchTextTotal < 0x20) || (cRowRecords < 8)) { return; }
if(fExternalText) {
// read record-external text (if necessary):
cchTextTotal = 0;
hObScatter = VmmScatter_Initialize(H, VMM_FLAG_SCATTER_FORCE_PAGEREAD);
if(!hObScatter) { return; }
// prepare & execute scatter read:
for(iRowRecord = 0; iRowRecord < cRowRecords; iRowRecord++) {
cbo = ctx->cbAllocationInitialOffset + iRowRecord * off->cb;
cchRowText = *(PWORD)(pb + cbo + off->oCountText);
vaRowText = VMM_PTR_OFFSET(f32, pb, cbo + off->oPtrText);
fPartialLine = *(PWORD)(pb + cbo + off->oPartialLine) ? TRUE : FALSE;
f = VmmScatter_PrepareEx(hObScatter, vaRowText, cchRowText * 3, ctx->pbBufferText + cchTextTotal * 3, NULL);
if(!f) {
Ob_DECREF_NULL(&hObScatter);
return;
}
cchTextTotal += cchRowText;
if(!fPartialLine) {
ctx->pbBufferText[cchTextTotal * 3] = 0x0A;
ctx->pbBufferText[cchTextTotal * 3 + 1] = 0x00;
ctx->pbBufferText[cchTextTotal * 3 + 2] = 0x00;
cchTextTotal++;
}
}
VmmScatter_Execute(hObScatter, ctx->pProcess);
Ob_DECREF_NULL(&hObScatter);
// parse individual 3-byte characters & finish up:
pbRowText = ctx->pbBufferText;
for(i = 0, j = 0; i < cchTextTotal; i++) {
ch = *(PWCHAR)(pbRowText + j);
ctx->wszBufferText[ctx->cchBufferText++] = ch;
if(!ch) { break; }
j += 3;
}
ctx->wszBufferText[ctx->cchBufferText] = 0;
if(ctx->cchBufferText < 0x20) {
ctx->cchBufferText = 0;
}
return;
} else {
// read record-internal text:
for(iRowRecord = 0; iRowRecord < cRowRecords; iRowRecord++) {
cbo = ctx->cbAllocationInitialOffset + iRowRecord * off->cb;
cchRowText = *(PWORD)(pb + cbo + off->oCountText);
vaRowText = VMM_PTR_OFFSET(f32, pb, cbo + off->oPtrText);
fPartialLine = *(PWORD)(pb + cbo + off->oPartialLine) ? TRUE : FALSE;
// parse individual 3-byte characters:
pbRowText = pb + (vaRowText - va);
for(i = 0, j = 0; i < cchRowText; i++) {
ch = *(PWCHAR)(pbRowText + j);
ctx->wszBufferText[ctx->cchBufferText++] = ch;
if(!ch) { return; }
j += 3;
}
if(!fPartialLine) {
ctx->wszBufferText[ctx->cchBufferText++] = 0x0A;
}
}
ctx->wszBufferText[ctx->cchBufferText] = 0;
return;
}
}
/*
* Windows 11 specific functionality.
* In Windows 11 the buffer does not usually start at the beginning of the
* buffer so we'll have to find the start of the buffer first.
*/
VOID MCON_Initialize_Win11_2(_In_ VMM_HANDLE H, _In_ PMCON_OFFSETS off, _Inout_ PMCON_INIT_CONTEXT ctx)
{
QWORD vaScreenInfo = 0;
DWORD cbo = 0;
// skip forward:
while((cbo + 0x1000 <= ctx->cbAllocation) && !memcmp(ctx->pbAllocation + cbo, H->ZERO_PAGE, 0x1000)) { cbo += 0x1000; }
cbo += off->cbHeader;
if(cbo + 0x1000 > ctx->cbAllocation) { return; }
// test record validity by verifying ptr to "screen info" main object.
vaScreenInfo = VMM_PTR_OFFSET(ctx->f32, ctx->pbAllocation, cbo + off->oPtrScreenInfo);
if(!VMM_UADDR64_16(vaScreenInfo) || (vaScreenInfo != VMM_PTR_OFFSET(ctx->f32, ctx->pbAllocation, cbo + off->oPtrScreenInfo + off->cb))) { return; }
// call main parsing function:
ctx->cbAllocationInitialOffset = cbo;
MCON_Initialize_Win1011_RowRecordParse(H, off, ctx);
}
/*
* Parse a Windows 11 console window.
* In Windows 11 everything is by default stored in one big buffer, but text
* references may be external as well. Windows 11 have two different sets of
* offsets so we need to try both. In reality this changed mid-build 22621.
*/
VOID MCON_Initialize_Win11(_In_ VMM_HANDLE H, _Inout_ PMCON_INIT_CONTEXT ctx)
{
MCON_OFFSETS off = { 0 };
// load buffer:
if(ctx->cbAllocation >= MCON_INIT_BUFFER_SIZE) { return; }
if(!VmmRead(H, ctx->pProcess, ctx->vaAllocation, ctx->pbAllocation, ctx->cbAllocation)) { return; }
// try default offsets:
off.cb = 0x1E0;
off.cbHeader = 0x60;
off.oPtrText = 0x00;
off.oCountText = 0x08;
off.oPtrSelf = 0x188;
off.oPartialLine = 0x1D0;
off.oPtrScreenInfo = 0x1D8;
ctx->cchBufferText = 0;
MCON_Initialize_Win11_2(H, &off, ctx);
if((ctx->cchBufferText < 0x20) && (ctx->dwBuild >= 22621)) {
// try alternate offsets:
off.cb = 0x1D8;
off.oPartialLine = 0x1C8;
off.oPtrScreenInfo = 0x1D0;
MCON_Initialize_Win11_2(H, &off, ctx);
}
}
VOID MCON_Initialize_Win10(_In_ VMM_HANDLE H, _Inout_ PMCON_INIT_CONTEXT ctx)
{
MCON_OFFSETS off = { 0 };
BOOL f32 = ctx->f32;
DWORD cbo;
QWORD va, va2;
DWORD cbA;
PBYTE pbA = NULL;
DWORD cbPtr = ctx->f32 ? 4 : 8;
DWORD cbRcd = ctx->f32 ? 0x30 : 0x60;
BYTE pbRcd[0x60] = { 0 };
PVMMOB_SCATTER hObScatter = NULL;
DWORD i, cboSrc = 0, cboDst = 0, cRowRecords = 0;
QWORD vaScreenInfo = 0;
BOOL fInitialVerify = FALSE;
// init offsets:
if(ctx->f32) {
off.cb = 0x30;
off.cbHeader = 0x10;
off.oPtrText = 0x04;
off.oCountText = 0x20;
off.oPtrSelf = 0x10;
off.oPartialLine = 0x00;
off.oPtrScreenInfo = 0x2c;
} else {
off.cb = 0x60;
off.cbHeader = 0x10;
off.oPtrText = 0x08;
off.oCountText = 0x40;
off.oPtrSelf = 0x20;
off.oPartialLine = 0x00;
off.oPtrScreenInfo = 0x58;
}
// read allocation, which is the pointer array:
cbA = ctx->cbAllocation;
pbA = ctx->pbAllocation;
if((cbA <= 0x00004000) || (cbA >= 0x00100000)) { return; }
VmmRead2(H, ctx->pProcess, ctx->vaAllocation + off.cbHeader, pbA, cbA - off.cbHeader, VMM_FLAG_ZEROPAD_ON_FAIL);
// try to verify allocation. start of array seems to be a bit fuzzy,
// so we'll have to fuzz the correct start of the buffer first.
for(cboSrc = 0; cboSrc < 8 * cbPtr; cboSrc += cbPtr) {
va = VMM_PTR_OFFSET(f32, pbA, cboSrc);
if(!VMM_UADDR_8_16(f32, va)) { continue; }
if(!VmmRead(H, ctx->pProcess, va, pbRcd, cbRcd)) { continue; }
if(va != VMM_PTR_OFFSET(ctx->f32, pbRcd, off.oPtrSelf)) { continue; }
va2 = VMM_PTR_OFFSET(ctx->f32, pbRcd, off.oPtrScreenInfo);
if(!VMM_UADDR_8_16(f32, va2)) { continue; }
if(va2 == vaScreenInfo) {
fInitialVerify = TRUE;
break;
}
vaScreenInfo = va2;
}
if(!fInitialVerify) { return; }
cboSrc -= cbPtr;
// Walk the pointer array and scatter read the entries into the buffer.
// It's not an issue to use the same buffer since the scatter read will
// only overwrite the buffer when executed.
hObScatter = VmmScatter_Initialize(H, VMMDLL_FLAG_ZEROPAD_ON_FAIL | VMM_FLAG_SCATTER_PREPAREEX_NOMEMZERO);
if(!hObScatter) { return; }
while(TRUE) {
va = VMM_PTR_OFFSET(f32, pbA, cboSrc);
if(!VMM_UADDR_8_16(f32, va)) { break; }
VmmScatter_PrepareEx(hObScatter, va, off.cb, pbA + cboDst, NULL);
cboSrc += cbPtr;
cboDst += off.cb;
cRowRecords++;
if(cboDst > MCON_INIT_BUFFER_SIZE - 0x100) { break; }
}
ctx->cbAllocation = cboDst;
VmmScatter_Execute(hObScatter, ctx->pProcess);
Ob_DECREF_NULL(&hObScatter);
// fix-up the self-ptr to support self-validation in row-record-parse:
for(i = 0; i < cRowRecords; i++) {
cbo = i * off.cb;
if(f32) {
*(PDWORD)(pbA + cbo + off.oPtrSelf) = (DWORD)(ctx->vaAllocation + cbo);
} else {
*(PQWORD)(pbA + cbo + off.oPtrSelf) = ctx->vaAllocation + cbo;
}
}
// call main parsing function:
MCON_Initialize_Win1011_RowRecordParse(H, &off, ctx);
}
// ----------------------------------------------------------------------------
// WINDOWS 7 7600 -> WINDOWS 10 17134 CONSOLE PARSING:
// ----------------------------------------------------------------------------
VOID MCON_Initialize_Win7(_In_ VMM_HANDLE H, _Inout_ PMCON_INIT_CONTEXT ctx)
{
BOOL f, f32 = ctx->f32;
DWORD cbA, cRcd, cboDst = 0;
PBYTE pbA = NULL;
DWORD iRcd;
PBYTE pbRcd;
PVMMOB_SCATTER hObScatter = NULL;
QWORD vaRowText, va;
DWORD cbRowText, cbTextTotal = 0;
struct {
DWORD cb;
DWORD cbHeader;
WORD oCountText;
WORD oPtrText;
WORD oPtr10;
WORD oRcdId;
} off = { 0 };
// init offsets:
if(f32) {
off.cb = 0x1c;
off.oPtrText = 0x08;
if(ctx->dwBuild >= 10240) { off.cb = 0x24; off.oPtrText = 0x08; off.oPtr10 = 0x1c; off.oRcdId = 0x20; }
if(ctx->dwBuild >= 14393) { off.cb = 0x20; off.oPtrText = 0x04; off.oPtr10 = 0x18; off.oRcdId = 0x1c; }
if(ctx->dwBuild >= 15063) { off.cb = 0x1c; off.oPtrText = 0x04; off.oPtr10 = 0x14; off.oRcdId = 0x18; }
if(ctx->dwBuild >= 17134) { off.cb = 0x20; off.oPtrText = 0x04; off.oPtr10 = 0x14; off.oRcdId = 0x1c; off.cbHeader = 0x04; }
} else {
off.cb = 0x28;
off.oPtrText = 0x08;
if(ctx->dwBuild >= 10240) { off.cb = 0x38; off.oPtr10 = 0x28; off.oRcdId = 0x30; }
if(ctx->dwBuild >= 17134) { off.cb = 0x40; off.oPtr10 = 0x28; off.oRcdId = 0x38; off.cbHeader = 0x08; }
}
// read allocation, which is the pointer array:
cbA = ctx->cbAllocation;
pbA = ctx->pbAllocation;
cRcd = (ctx->cbAllocation - off.cbHeader) / off.cb;
if((cbA <= 0x00001000) || (cbA >= 0x00400000)) { return; }
if(cbA != (cRcd * off.cb + off.cbHeader)) { return; }
if(cRcd > 0x8000) { return; }
if(!VmmRead(H, ctx->pProcess, ctx->vaAllocation + off.cbHeader, pbA, cbA - off.cbHeader)) { return;}
// verify array allocation:
for(iRcd = 0; iRcd < cRcd; iRcd++) {
pbRcd = pbA + iRcd * off.cb;
va = VMM_PTR_OFFSET(f32, pbRcd, off.oPtrText);
if(!VMM_UADDR(f32, va) || (va & 1)) { return; }
if(off.oPtr10) {
va = VMM_PTR_OFFSET(f32, pbRcd, off.oPtr10);
if(!VMM_UADDR(f32, va) || (va & 1)) { return; }
}
if(off.oRcdId && (iRcd != *(PDWORD)(pbRcd + off.oRcdId))) { return; }
if(*(PWORD)(pbRcd + off.oCountText) >= 0x8000) { return; }
}
// Walk the pointer array and scatter read the entries into the text buffer.
hObScatter = VmmScatter_Initialize(H, VMMDLL_FLAG_ZEROPAD_ON_FAIL | VMMDLL_FLAG_SCATTER_PREPAREEX_NOMEMZERO);
if(!hObScatter) { return; }
for(iRcd = 0; iRcd < cRcd; iRcd++) {
pbRcd = pbA + iRcd * off.cb;
cbRowText = (DWORD)(*(PWORD)(pbRcd + off.oCountText)) << 1;
vaRowText = VMM_PTR_OFFSET(f32, pbRcd, off.oPtrText);
if(((cbTextTotal + cbRowText) >> 1) >= MCON_SCREEN_MAX_CHARS + 2) { break; }
if(cbRowText) {
f = VmmScatter_PrepareEx(hObScatter, vaRowText, cbRowText, ctx->pbBufferText + cbTextTotal, NULL);
if(!f) {
Ob_DECREF(hObScatter);
return;
}
cbTextTotal += cbRowText;
}
ctx->pbBufferText[cbTextTotal++] = 0x0A;
ctx->pbBufferText[cbTextTotal++] = 0x00;
}
VmmScatter_Execute(hObScatter, ctx->pProcess);
Ob_DECREF_NULL(&hObScatter);
// finalize buffer:
ctx->pbBufferText[cbTextTotal++] = 0x00;
ctx->pbBufferText[cbTextTotal++] = 0x00;
ctx->cchBufferText = cbTextTotal >> 1;
if(ctx->cchBufferText < 0x20) {
ctx->cchBufferText = 0;
}
}
// ----------------------------------------------------------------------------
// MAIN INITIALIZATION FUNCTIONALITY:
// ----------------------------------------------------------------------------
VOID MCON_Initialize_CleanupCB(_In_ PVOID pOb)
{
LocalFree(((POB_MCON_CONTEXT)pOb)->uszText);
}
/*
* Initialize a context for the given process.
* CALLER DECREF: return
*/
_Success_(return != NULL)
POB_MCON_CONTEXT MCON_Initialize(_In_ VMM_HANDLE H, _In_ VMM_MODULE_ID MID, _In_ PVMM_PROCESS pProcess)
{
DWORD i, cb, cchBufferTextPre;
POB_MCON_CONTEXT ctxUser = NULL;
PMCON_INIT_CONTEXT ctxInit = NULL;
PVMMOB_MAP_VAD pObVadMap = NULL;
PVMM_MAP_VADENTRY peVad;
PVMMOB_MAP_HEAPALLOC pObHeapAllocMap = NULL;
PVMM_MAP_HEAPALLOCENTRY peHeapAlloc;
VMMSTATISTICS_LOG Statistics = { 0 };
// alloc:
VmmStatisticsLogStart(H, MID, LOGLEVEL_6_TRACE, pProcess, &Statistics, "INIT_CONSOLE");
if(!(ctxInit = LocalAlloc(LMEM_ZEROINIT, sizeof(MCON_INIT_CONTEXT)))) { goto fail; }
if(!(ctxUser = Ob_AllocEx(H, 'MCON', LMEM_ZEROINIT, sizeof(OB_MCON_CONTEXT), MCON_Initialize_CleanupCB, NULL))) { goto fail; }
ctxInit->f32 = H->vmm.f32 || pProcess->win.fWow64;
ctxInit->MID = MID;
ctxInit->pProcess = pProcess;
ctxInit->dwBuild = H->vmm.kernel.dwVersionBuild;
// find allocation and dispatch to os-dependent parsing function:
if(ctxInit->dwBuild >= 22000) {
// WIN11 21H2 - 23H2:
// strategy is to find large vad allocations which are private, read, write and also "unmarked" as in unknown.
if(!VmmMap_GetVad(H, pProcess, &pObVadMap, VMM_VADMAP_TP_FULL) || !pObVadMap->cMap) {
VmmLog(H, MID, LOGLEVEL_5_DEBUG, "Fail: Unable to retrieve VAD map. PID:[%i]", pProcess->dwPID);
goto fail;
}
for(i = 0; i < pObVadMap->cMap; i++) {
peVad = pObVadMap->pMap + i;
cb = (DWORD)(peVad->vaEnd - peVad->vaStart);
if(!peVad->fPrivateMemory || peVad->uszText[0]) { continue; }
if(peVad->fFile || peVad->fHeap || peVad->fImage || peVad->fPageFile || peVad->fStack || peVad->fTeb || peVad->vaFileObject) { continue; }
if((cb < 0x50000) || (cb > MCON_INIT_BUFFER_SIZE)) { continue; }
if((ctxInit->dwBuild < 26100) && (peVad->CommitCharge < 0x10)) { continue; }
VmmLog(H, MID, LOGLEVEL_6_TRACE, "Info: Try parse candidate buffer. PID:[%i] va:[%llx]", pProcess->dwPID, peVad->vaStart);
// try parse the potential console buffer:
ctxInit->cchBufferText = 0;
ctxInit->vaAllocation = peVad->vaStart;
ctxInit->cbAllocation = (DWORD)(peVad->vaEnd - peVad->vaStart);
if(ctxInit->dwBuild >= 26100) {
MCON_Initialize_Win11_26100(H, ctxInit);
} else {
MCON_Initialize_Win11(H, ctxInit);
}
ctxInit->vaAllocation = peVad->vaStart;
if(ctxInit->cchBufferText > 0x10) { break; }
}
} else {
// WIN7 -> WIN10 1803:
// WIN10 1809 -> WIN10 23H2:
// strategy is to find large heap allocations.
if(!VmmMap_GetHeapAlloc(H, pProcess, 0, &pObHeapAllocMap) || !pObHeapAllocMap->cMap) {
VmmLog(H, MID, LOGLEVEL_5_DEBUG, "Fail: Unable to retrieve heap allocation map. PID:[%i]", pProcess->dwPID);
goto fail;
}
for(i = 0; i < pObHeapAllocMap->cMap; i++) {
peHeapAlloc = pObHeapAllocMap->pMap + i;
if((peHeapAlloc->cb <= 0x1000) || (peHeapAlloc->cb >= 0x00100000) || !(peHeapAlloc->cb & 0xfff)) { continue; }
VmmLog(H, MID, LOGLEVEL_6_TRACE, "Info: Try parse candidate buffer. PID:[%i] va:[%llx] size:[%x]", pProcess->dwPID, peHeapAlloc->va, peHeapAlloc->cb);
// try parse the potential console buffer:
ctxInit->vaAllocation = peHeapAlloc->va;
ctxInit->cbAllocation = peHeapAlloc->cb;
ctxInit->cchBufferText = 0;
ctxInit->fSuccess = FALSE;
if(ctxInit->dwBuild >= 17763) {
MCON_Initialize_Win10(H, ctxInit);
} else {
MCON_Initialize_Win7(H, ctxInit);
}
ctxInit->vaAllocation = peHeapAlloc->va;
if(ctxInit->cchBufferText > 0x10) { break; }
}
}
// any recovered text buffer may have lots of space-padding and newlines.
// compact the buffer to remove any unnecessary padding and newlines.
if(ctxInit->cchBufferText > 0x10) {
cchBufferTextPre = ctxInit->cchBufferText;
ctxInit->cchBufferText = MCON_Init_CompactConsoleBuffer(ctxInit->wszBufferText);
ctxInit->fSuccess = TRUE;
VmmLog(H, MID, LOGLEVEL_6_TRACE, "Info: Parsed buffer. PID:[%i] va:[%llx] chars:[%x] chars-compressed:[%x] verdict:[%s]", pProcess->dwPID, ctxInit->vaAllocation, cchBufferTextPre, ctxInit->cchBufferText, ctxInit->fSuccess ? "success" : "fail");
}
// finalize context:
// convert the wchar buffer to utf-8 and store in the context.
if(ctxInit->fSuccess && CharUtil_WtoU(ctxInit->wszBufferText, ctxInit->cchBufferText, NULL, 0, &ctxUser->uszText, &ctxUser->cbText, CHARUTIL_FLAG_ALLOC | CHARUTIL_FLAG_BAD_UTF8CP_SOFTFAIL)) {
ctxUser->fValid = TRUE;
if(ctxUser->cbText) { ctxUser->cbText--; }
VmmLog(H, MID, LOGLEVEL_6_TRACE, "Success: Parse console buffer. PID:[%i] va:[%llx] length:[%x]", pProcess->dwPID, ctxInit->vaAllocation, ctxUser->cbText);
} else {
VmmLog(H, MID, LOGLEVEL_6_TRACE, "Fail: Parse console buffer. PID:[%i]", pProcess->dwPID);
}
fail:
Ob_DECREF(pObHeapAllocMap);
Ob_DECREF(pObVadMap);
LocalFree(ctxInit);
VmmStatisticsLogEnd(H, &Statistics, "INIT_CONSOLE");
return ctxUser;
}
/*
* Retrieve the context for the given process.
* CALLER DECREF: return
*/
_Success_(return != NULL)
POB_MCON_CONTEXT MCON_GetContext(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP)
{
POB_MCON_CONTEXT ctxOb = NULL;
PVMM_PROCESS pProcess = ctxP->pProcess;
if(!pProcess) { return NULL; }
if(!(ctxOb = ObMap_GetByKey((POB_MAP)ctxP->ctxM, pProcess->dwPID))) {
EnterCriticalSection(&H->vmm.LockPlugin);
if(!(ctxOb = ObMap_GetByKey((POB_MAP)ctxP->ctxM, pProcess->dwPID))) {
ctxOb = MCON_Initialize(H, ctxP->MID, pProcess);
ObMap_Push((POB_MAP)ctxP->ctxM, pProcess->dwPID, ctxOb);
}
LeaveCriticalSection(&H->vmm.LockPlugin);
}
return ctxOb;
}
// ----------------------------------------------------------------------------
// MODULE BASE FUNCTIONALITY:
// ----------------------------------------------------------------------------
_Success_(return == STATUS_SUCCESS)
NTSTATUS M_ProcConsole_Read(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP, _Out_ PBYTE pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset)
{
NTSTATUS nt = VMMDLL_STATUS_FILE_INVALID;
POB_MCON_CONTEXT ctxOb = MCON_GetContext(H, ctxP);
if(CharUtil_StrEquals(ctxP->uszPath, "console.txt", TRUE) && ctxOb && ctxOb->fValid) {
nt = Util_VfsReadFile_FromPBYTE(ctxOb->uszText, ctxOb->cbText, pb, cb, pcbRead, cbOffset);
}
Ob_DECREF(ctxOb);
return nt;
}
BOOL M_ProcConsole_List(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP, _Inout_ PHANDLE pFileList)
{
POB_MCON_CONTEXT ctxOb = MCON_GetContext(H, ctxP);
if(!ctxP->uszPath[0] && ctxOb && ctxOb->fValid) {
VMMDLL_VfsList_AddFile(pFileList, "console.txt", ctxOb->cbText, NULL);
}
Ob_DECREF(ctxOb);
return TRUE;
}
VOID M_ProcConsole_Close(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP)
{
Ob_DECREF(ctxP->ctxM);
}
VOID M_ProcConsole_Notify(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP, _In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_opt_ DWORD cbEvent)
{
if(fEvent == VMMDLL_PLUGIN_NOTIFY_REFRESH_SLOW) {
ObMap_Clear((POB_MAP)ctxP->ctxM);
}
}
BOOL M_ProcConsole_VisibleModule(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP)
{
PVMM_PROCESS pProcess = ctxP->pProcess;
return pProcess && (*(PQWORD)pProcess->szName == 0x2e74736f686e6f63) && ((*(PDWORD)(pProcess->szName + 8) == 0x0657865)); // "conhost." "exe\0"
}
/*
* Initialization function. The module manager shall call into this function
* when the module shall be initialized. If the module wish to initialize it
* shall call the supplied pfnPluginManager_Register function.
* NB! the module does not have to register itself - for example if the target
* operating system or architecture is unsupported.
* -- pRI
*/
VOID M_ProcConsole_Initialize(_In_ VMM_HANDLE H, _Inout_ PVMMDLL_PLUGIN_REGINFO pRI)
{
if((pRI->magic != VMMDLL_PLUGIN_REGINFO_MAGIC) || (pRI->wVersion != VMMDLL_PLUGIN_REGINFO_VERSION)) { return; }
if(pRI->sysinfo.dwVersionBuild < 7600) { return; } // WIN7 and later is supported.
if(!(pRI->reg_info.ctxM = (PVMMDLL_PLUGIN_INTERNAL_CONTEXT)ObMap_New(H, OB_MAP_FLAGS_OBJECT_OB))) { return; } // internal module context
strcpy_s(pRI->reg_info.uszPathName, 128, "\\console"); // module name
pRI->reg_info.fRootModule = FALSE; // module shows in root directory
pRI->reg_info.fProcessModule = TRUE; // module shows in process directory
pRI->reg_fn.pfnVisibleModule = M_ProcConsole_VisibleModule; // Visible module function supported
pRI->reg_fn.pfnNotify = M_ProcConsole_Notify; // Notify function supported
pRI->reg_fn.pfnList = M_ProcConsole_List; // List function supported
pRI->reg_fn.pfnRead = M_ProcConsole_Read; // Read function supported
pRI->reg_fn.pfnClose = M_ProcConsole_Close; // Close function supported
pRI->pfnPluginManager_Register(H, pRI);
}