mirror of
https://github.com/ufrisk/LeechCore.git
synced 2026-05-22 16:56:54 +08:00
1229 lines
43 KiB
C
1229 lines
43 KiB
C
// leechcore.c : core implementation of the LeechCore physical memory acquisition library.
|
|
//
|
|
// (c) Ulf Frisk, 2020-2026
|
|
// Author: Ulf Frisk, pcileech@frizk.net
|
|
//
|
|
|
|
#include "leechcore.h"
|
|
#include "leechcore_device.h"
|
|
#include "leechcore_internal.h"
|
|
#include "oscompatibility.h"
|
|
#include "util.h"
|
|
#include "version.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Global Context and DLL Attach/Detach:
|
|
//-----------------------------------------------------------------------------
|
|
|
|
typedef struct tdLC_MAIN_CONTEXT {
|
|
CRITICAL_SECTION Lock;
|
|
HANDLE FLink;
|
|
} LC_MAIN_CONTEXT, *PLC_MAIN_CONTEXT;
|
|
|
|
LC_MAIN_CONTEXT g_ctx = { 0 };
|
|
|
|
_Success_(return) BOOL Device3380_Open(_Inout_ PLC_CONTEXT ctxLC, _Out_opt_ PPLC_CONFIG_ERRORINFO ppLcCreateErrorInfo);
|
|
_Success_(return) BOOL DeviceFile_Open(_Inout_ PLC_CONTEXT ctxLC, _Out_opt_ PPLC_CONFIG_ERRORINFO ppLcCreateErrorInfo);
|
|
_Success_(return) BOOL DeviceFPGA_Open(_Inout_ PLC_CONTEXT ctxLC, _Out_opt_ PPLC_CONFIG_ERRORINFO ppLcCreateErrorInfo);
|
|
_Success_(return) BOOL DeviceHIBR_Open(_Inout_ PLC_CONTEXT ctxLC, _Out_opt_ PPLC_CONFIG_ERRORINFO ppLcCreateErrorInfo);
|
|
_Success_(return) BOOL DevicePMEM_Open(_Inout_ PLC_CONTEXT ctxLC, _Out_opt_ PPLC_CONFIG_ERRORINFO ppLcCreateErrorInfo);
|
|
_Success_(return) BOOL DeviceVMM_Open(_Inout_ PLC_CONTEXT ctxLC, _Out_opt_ PPLC_CONFIG_ERRORINFO ppLcCreateErrorInfo);
|
|
_Success_(return) BOOL DeviceVMWare_Open(_Inout_ PLC_CONTEXT ctxLC, _Out_opt_ PPLC_CONFIG_ERRORINFO ppLcCreateErrorInfo);
|
|
_Success_(return) BOOL DeviceTMD_Open(_Inout_ PLC_CONTEXT ctxLC, _Out_opt_ PPLC_CONFIG_ERRORINFO ppLcCreateErrorInfo);
|
|
_Success_(return) BOOL LeechRpc_Open(_Inout_ PLC_CONTEXT ctxLC, _Out_opt_ PPLC_CONFIG_ERRORINFO ppLcCreateErrorInfo);
|
|
|
|
VOID LcCloseAll();
|
|
_Success_(return) BOOL LcReadContigious_Initialize(_In_ PLC_CONTEXT ctxLC);
|
|
VOID LcReadContigious_Close(_In_ PLC_CONTEXT ctxLC);
|
|
|
|
#ifdef _WIN32
|
|
BOOL WINAPI DllMain(_In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, _In_ PVOID lpvReserved)
|
|
{
|
|
if(fdwReason == DLL_PROCESS_ATTACH) {
|
|
ZeroMemory(&g_ctx, sizeof(LC_MAIN_CONTEXT));
|
|
InitializeCriticalSection(&g_ctx.Lock);
|
|
}
|
|
if(fdwReason == DLL_PROCESS_DETACH) {
|
|
LcCloseAll();
|
|
DeleteCriticalSection(&g_ctx.Lock);
|
|
ZeroMemory(&g_ctx, sizeof(LC_MAIN_CONTEXT));
|
|
}
|
|
return TRUE;
|
|
}
|
|
#endif /* _WIN32 */
|
|
#if defined(LINUX) || defined(MACOS)
|
|
__attribute__((constructor)) VOID LcAttach()
|
|
{
|
|
ZeroMemory(&g_ctx, sizeof(LC_MAIN_CONTEXT));
|
|
InitializeCriticalSection(&g_ctx.Lock);
|
|
}
|
|
|
|
__attribute__((destructor)) VOID LcDetach()
|
|
{
|
|
LcCloseAll();
|
|
DeleteCriticalSection(&g_ctx.Lock);
|
|
ZeroMemory(&g_ctx, sizeof(LC_MAIN_CONTEXT));
|
|
}
|
|
#endif /* LINUX || MACOS */
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Initialize / Close / Core functionality:
|
|
//-----------------------------------------------------------------------------
|
|
|
|
VOID LcLockAcquire(_In_ PLC_CONTEXT ctxLC)
|
|
{
|
|
if(!ctxLC->fMultiThread) { EnterCriticalSection(&ctxLC->Lock); }
|
|
}
|
|
|
|
VOID LcLockRelease(_In_ PLC_CONTEXT ctxLC)
|
|
{
|
|
if(!ctxLC->fMultiThread) { LeaveCriticalSection(&ctxLC->Lock); }
|
|
}
|
|
|
|
QWORD LcCallStart()
|
|
{
|
|
QWORD tmNow;
|
|
QueryPerformanceCounter((PLARGE_INTEGER)&tmNow);
|
|
return tmNow;
|
|
}
|
|
|
|
VOID LcCallEnd(_In_ PLC_CONTEXT ctxLC, _In_ DWORD fId, _In_ QWORD tmCallStart)
|
|
{
|
|
QWORD tmNow;
|
|
QueryPerformanceCounter((PLARGE_INTEGER)&tmNow);
|
|
InterlockedIncrement64(&ctxLC->CallStat.Call[fId].c);
|
|
InterlockedAdd64(&ctxLC->CallStat.Call[fId].tm, tmNow - tmCallStart);
|
|
}
|
|
|
|
/*
|
|
* Close a LeechCore handle and free any resources no longer needed.
|
|
*/
|
|
EXPORTED_FUNCTION VOID LcClose(_In_opt_ _Post_ptr_invalid_ HANDLE hLC)
|
|
{
|
|
PLC_CONTEXT ctxParent;
|
|
PLC_CONTEXT ctxLC = (PLC_CONTEXT)hLC;
|
|
if(!ctxLC || (ctxLC->version != LC_CONTEXT_VERSION)) { return; }
|
|
EnterCriticalSection(&g_ctx.Lock);
|
|
if(0 == --ctxLC->dwHandleCount) {
|
|
// detach from handles list
|
|
if(g_ctx.FLink == ctxLC) {
|
|
g_ctx.FLink = ctxLC->FLink;
|
|
} else {
|
|
ctxParent = (PLC_CONTEXT)g_ctx.FLink;
|
|
while(ctxParent) {
|
|
if(ctxParent->FLink == ctxLC) {
|
|
ctxParent->FLink = ctxLC->FLink;
|
|
break;
|
|
}
|
|
ctxParent = (PLC_CONTEXT)ctxParent->FLink;
|
|
}
|
|
}
|
|
LcLockAcquire(ctxLC);
|
|
LcReadContigious_Close(ctxLC);
|
|
if(ctxLC->pfnClose) { ctxLC->pfnClose(ctxLC); }
|
|
LcLockRelease(ctxLC);
|
|
ctxLC->version = 0;
|
|
DeleteCriticalSection(&ctxLC->Lock);
|
|
if(ctxLC->hDeviceModule) { FreeLibrary(ctxLC->hDeviceModule); }
|
|
LocalFree(ctxLC->pMemMap);
|
|
LocalFree(ctxLC);
|
|
}
|
|
LeaveCriticalSection(&g_ctx.Lock);
|
|
}
|
|
|
|
/*
|
|
* Close all LeechCore devices and contexts. This is done on DLL unload.
|
|
*/
|
|
VOID LcCloseAll()
|
|
{
|
|
EnterCriticalSection(&g_ctx.Lock);
|
|
while(g_ctx.FLink) {
|
|
LcClose(g_ctx.FLink);
|
|
}
|
|
LeaveCriticalSection(&g_ctx.Lock);
|
|
}
|
|
|
|
/*
|
|
* Create helper function to parse optional device configuration parameters.
|
|
* -- ctxLC
|
|
*/
|
|
VOID LcCreate_FetchDeviceParameter(_Inout_ PLC_CONTEXT ctxLC)
|
|
{
|
|
PLC_DEVICE_PARAMETER_ENTRY pe;
|
|
CHAR szDevice[MAX_PATH] = { 0 };
|
|
LPSTR szDelim, szParameters, szToken, szTokenContext = NULL;
|
|
memcpy(szDevice, ctxLC->Config.szDevice, _countof(szDevice));
|
|
if(!(szParameters = strstr(szDevice, "://"))) { return; }
|
|
szParameters += 3;
|
|
while((szToken = strtok_s(szParameters, ",;", &szTokenContext)) && (ctxLC->cDeviceParameter < LC_DEVICE_PARAMETER_MAX_ENTRIES)) {
|
|
szParameters = NULL;
|
|
if(!(szDelim = strstr(szToken, "="))) { continue; }
|
|
pe = &ctxLC->pDeviceParameter[ctxLC->cDeviceParameter];
|
|
strncpy_s(pe->szName, _countof(pe->szName), szToken, szDelim - szToken);
|
|
strncpy_s(pe->szValue, _countof(pe->szValue), szDelim + 1, _TRUNCATE);
|
|
pe->qwValue = Util_GetNumericA(pe->szValue);
|
|
if((0 == pe->qwValue) && !_stricmp(pe->szValue, "true")) {
|
|
pe->qwValue = 1;
|
|
}
|
|
ctxLC->cDeviceParameter++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Retrieve a device parameter by its name (if exists).
|
|
* -- ctxLc
|
|
* -- szName
|
|
* -- return
|
|
*/
|
|
EXPORTED_FUNCTION PLC_DEVICE_PARAMETER_ENTRY LcDeviceParameterGet(_In_ PLC_CONTEXT ctxLC, _In_ LPSTR szName)
|
|
{
|
|
for(DWORD i = 0; i < ctxLC->cDeviceParameter; i++) {
|
|
if(!_stricmp(szName, ctxLC->pDeviceParameter[i].szName)) {
|
|
return &ctxLC->pDeviceParameter[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Retrieve the numeric value of a device parameter (if exists).
|
|
* -- ctxLc
|
|
* -- szName
|
|
* -- return = the numeric value of the device parameter - 0 on fail.
|
|
*/
|
|
EXPORTED_FUNCTION QWORD LcDeviceParameterGetNumeric(_In_ PLC_CONTEXT ctxLC, _In_ LPSTR szName)
|
|
{
|
|
PLC_DEVICE_PARAMETER_ENTRY p = LcDeviceParameterGet(ctxLC, szName);
|
|
return p ? p->qwValue : 0;
|
|
}
|
|
|
|
/*
|
|
* Create helper function to fetch the correct device (and its create function).
|
|
* -- ctxLC
|
|
* -- cszDevice
|
|
* -- szDeviceAlt
|
|
* -- return
|
|
*/
|
|
_Success_(return)
|
|
BOOL LcCreate_FetchDevice_FromExternalModule(_Inout_ PLC_CONTEXT ctx, _In_opt_ DWORD cszDevice, _In_opt_ LPSTR szDeviceAlt)
|
|
{
|
|
CHAR uszModule[2 * MAX_PATH] = { 0 };
|
|
Util_GetPathLib(uszModule);
|
|
strcat_s(uszModule, sizeof(uszModule), "leechcore_device_");
|
|
if(szDeviceAlt) {
|
|
strcat_s(uszModule, sizeof(uszModule), szDeviceAlt);
|
|
} else {
|
|
strncat_s(uszModule, sizeof(uszModule), ctx->Config.szDevice, cszDevice);
|
|
}
|
|
strcat_s(uszModule, sizeof(uszModule), LC_LIBRARY_FILETYPE);
|
|
if((ctx->hDeviceModule = LoadLibraryU(uszModule))) {
|
|
if((ctx->pfnCreate = (BOOL(*)(PLC_CONTEXT, PPLC_CONFIG_ERRORINFO))GetProcAddress(ctx->hDeviceModule, "LcPluginCreate"))) {
|
|
if(szDeviceAlt) {
|
|
strcpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), szDeviceAlt);
|
|
} else {
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), ctx->Config.szDevice, cszDevice);
|
|
}
|
|
return TRUE;
|
|
} else {
|
|
FreeLibrary(ctx->hDeviceModule);
|
|
ctx->hDeviceModule = NULL;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Create helper function to fetch the correct device (and its create function).
|
|
* -- ctxLC
|
|
*/
|
|
VOID LcCreate_FetchDevice(_Inout_ PLC_CONTEXT ctx)
|
|
{
|
|
CHAR c;
|
|
DWORD cch, cszDevice = 0;
|
|
LPSTR szDeviceSpecial = NULL;
|
|
// 1: check against built-in devices:
|
|
if(0 == _strnicmp("grpc://", ctx->Config.szRemote, 7)) {
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), "grpc", _TRUNCATE);
|
|
ctx->pfnCreate = LeechRpc_Open;
|
|
return;
|
|
}
|
|
if(0 == _strnicmp("rpc://", ctx->Config.szRemote, 6)) {
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), "rpc", _TRUNCATE);
|
|
ctx->pfnCreate = LeechRpc_Open;
|
|
return;
|
|
}
|
|
if(0 == _strnicmp("smb://", ctx->Config.szRemote, 6)) {
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), "smb", _TRUNCATE);
|
|
ctx->pfnCreate = LeechRpc_Open;
|
|
return;
|
|
}
|
|
if(ctx->Config.szRemote[0]) { return; }
|
|
if((0 == _strnicmp("file", ctx->Config.szDevice, 4)) || (0 == _strnicmp("livekd", ctx->Config.szDevice, 6)) || (0 == _strnicmp("dumpit", ctx->Config.szDevice, 6))) {
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), "file", _TRUNCATE);
|
|
ctx->pfnCreate = DeviceFile_Open;
|
|
return;
|
|
}
|
|
if((0 == _strnicmp("fpga", ctx->Config.szDevice, 4)) || (0 == _strnicmp("rawudp://", ctx->Config.szDevice, 9))) {
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), "fpga", _TRUNCATE);
|
|
ctx->pfnCreate = DeviceFPGA_Open;
|
|
return;
|
|
}
|
|
if(0 == _strnicmp("hibr", ctx->Config.szDevice, 4)) {
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), "hibr", _TRUNCATE);
|
|
ctx->pfnCreate = DeviceHIBR_Open;
|
|
return;
|
|
}
|
|
if(0 == _strnicmp("usb3380", ctx->Config.szDevice, 7)) {
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), "usb3380", _TRUNCATE);
|
|
ctx->pfnCreate = Device3380_Open;
|
|
return;
|
|
}
|
|
if(0 == _stricmp("totalmeltdown", ctx->Config.szDevice)) {
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), "totalmeltdown", _TRUNCATE);
|
|
ctx->pfnCreate = DeviceTMD_Open;
|
|
return;
|
|
}
|
|
if(0 == _strnicmp("pmem", ctx->Config.szDevice, 4)) {
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), "pmem", _TRUNCATE);
|
|
ctx->pfnCreate = DevicePMEM_Open;
|
|
return;
|
|
}
|
|
if(0 == _strnicmp("vmm://", ctx->Config.szDevice, 6)) {
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), "vmm", _TRUNCATE);
|
|
ctx->pfnCreate = DeviceVMM_Open;
|
|
return;
|
|
}
|
|
if(0 == _strnicmp("vmware", ctx->Config.szDevice, 6)) {
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), "vmware", _TRUNCATE);
|
|
ctx->pfnCreate = DeviceVMWare_Open;
|
|
return;
|
|
}
|
|
// 2: check against separate device modules:
|
|
// 2.1: count device name length (and 'sanitize' againt disallowed chars).
|
|
while((c = ctx->Config.szDevice[cszDevice]) && (c != ':')) {
|
|
if(((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || ((c >= '0') && (c <= '9'))) {
|
|
cszDevice++;
|
|
} else {
|
|
cszDevice = 0;
|
|
break;
|
|
}
|
|
}
|
|
if(cszDevice && (cszDevice < 16) && LcCreate_FetchDevice_FromExternalModule(ctx, cszDevice, NULL)) {
|
|
return;
|
|
}
|
|
// 2.2: try special case modules:
|
|
cch = (DWORD)strlen(ctx->Config.szDevice);
|
|
// hyper-v saved state (.vmrs) file:
|
|
if((cch > 5) && (0 == _strnicmp(".vmrs", ctx->Config.szDevice + cch - 5, 5))) {
|
|
szDeviceSpecial = "hvsavedstate";
|
|
}
|
|
if(szDeviceSpecial && LcCreate_FetchDevice_FromExternalModule(ctx, 0, szDeviceSpecial)) {
|
|
return;
|
|
}
|
|
// 2.3: assume file is to be opened if no match for device name is found:
|
|
strncpy_s(ctx->Config.szDeviceName, sizeof(ctx->Config.szDeviceName), "file", _TRUNCATE);
|
|
ctx->pfnCreate = DeviceFile_Open;
|
|
}
|
|
|
|
#define ADDRDETECT_MAX 0x10
|
|
|
|
VOID LcCreate_MemMapInitAddressDetect_AddDefaultRange(_Inout_ PLC_CONTEXT ctxLC, _In_ QWORD paMax)
|
|
{
|
|
paMax = (paMax + 0xfff) & ~0xfff;
|
|
if(ctxLC->Config.fVolatile) {
|
|
LcMemMap_AddRange(ctxLC, 0, min(paMax, 0x000a0000), 0);
|
|
if(paMax > 0x00100000) {
|
|
LcMemMap_AddRange(ctxLC, 0x00100000, paMax - 0x00100000, 0x00100000);
|
|
}
|
|
} else {
|
|
LcMemMap_AddRange(ctxLC, 0, paMax, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create helper function to initialize memory map and auto-detect max address.
|
|
* -- ctxLC
|
|
*/
|
|
VOID LcCreate_MemMapInitAddressDetect(_Inout_ PLC_CONTEXT ctxLC)
|
|
{
|
|
BOOL fFPGA, fCheckTiny = FALSE;
|
|
PPMEM_SCATTER ppMEMs;
|
|
QWORD i, paCurrent = 0x100000000, cbChunk = 0x100000000;
|
|
if(LcMemMap_IsInitialized(ctxLC)) { return; }
|
|
if(ctxLC->Config.paMax) {
|
|
if(ctxLC->Config.paMax > 0x000000fffffff000) {
|
|
ctxLC->Config.paMax = 0x000000fffffff000;
|
|
}
|
|
LcCreate_MemMapInitAddressDetect_AddDefaultRange(ctxLC, ctxLC->Config.paMax);
|
|
return;
|
|
}
|
|
if(!LcAllocScatter1(ADDRDETECT_MAX + 1, &ppMEMs)) { return; }
|
|
// 1: detect topmost 4GB aligned address in 64GB scatter reads
|
|
while(TRUE) {
|
|
for(i = 0; i < ADDRDETECT_MAX; i++) {
|
|
ppMEMs[i]->qwA = paCurrent + i * cbChunk;
|
|
ppMEMs[i]->f = FALSE;
|
|
ppMEMs[i]->cb = 0x8;
|
|
}
|
|
LcReadScatter(ctxLC, ADDRDETECT_MAX, ppMEMs);
|
|
for(i = 0; i < ADDRDETECT_MAX; i++) {
|
|
if(ppMEMs[i]->f) {
|
|
paCurrent = ppMEMs[i]->qwA;
|
|
}
|
|
}
|
|
if(!ppMEMs[ADDRDETECT_MAX - 1]->f) {
|
|
break;
|
|
}
|
|
}
|
|
// 2: detect exact topmost address in progressively smaller scatter reads
|
|
fFPGA = (0 == _stricmp("fpga", ctxLC->Config.szDeviceName));
|
|
while(cbChunk > 0x1000) {
|
|
cbChunk = cbChunk >> 4;
|
|
for(i = 0; i < ADDRDETECT_MAX; i++) {
|
|
ppMEMs[i]->qwA = paCurrent + i * cbChunk;
|
|
ppMEMs[i]->f = FALSE;
|
|
}
|
|
if(fFPGA && (cbChunk == 0x1000)) {
|
|
// detect need for "tiny" PCIe algorithm of 128 bytes TLP.
|
|
ppMEMs[ADDRDETECT_MAX]->qwA = paCurrent;
|
|
fCheckTiny = TRUE;
|
|
LcReadScatter(ctxLC, ADDRDETECT_MAX + 1, ppMEMs);
|
|
} else {
|
|
LcReadScatter(ctxLC, ADDRDETECT_MAX, ppMEMs);
|
|
}
|
|
for(i = 0; i < ADDRDETECT_MAX; i++) {
|
|
if(ppMEMs[i]->f) {
|
|
paCurrent = ppMEMs[i]->qwA;
|
|
}
|
|
}
|
|
if(fCheckTiny && !ppMEMs[ADDRDETECT_MAX]->f) {
|
|
ctxLC->pfnSetOption(ctxLC, LC_OPT_FPGA_ALGO_TINY, 1);
|
|
lcprintfv(ctxLC, "FPGA: TINY PCIe TLP algrithm auto-selected!\n");
|
|
}
|
|
}
|
|
// 3: finish
|
|
if(paCurrent == 0x100000000) { paCurrent -= 0x1000; }
|
|
LcCreate_MemMapInitAddressDetect_AddDefaultRange(ctxLC, paCurrent + 0x1000);
|
|
LocalFree(ppMEMs);
|
|
}
|
|
|
|
/*
|
|
* Create a new LeechCore device according to the supplied configuration.
|
|
* CALLER LcMemFree: ppLcCreateErrorInfo
|
|
* -- pLcCreateConfig
|
|
* -- ppLcCreateErrorInfo = ptr to receive function allocated struct with error
|
|
* information upon function failure. This info may contain a user message
|
|
* requesting user action as an example.
|
|
* -- return
|
|
*/
|
|
_Success_(return != NULL)
|
|
EXPORTED_FUNCTION HANDLE LcCreateEx(_Inout_ PLC_CONFIG pLcCreateConfig, _Out_opt_ PPLC_CONFIG_ERRORINFO ppLcCreateErrorInfo)
|
|
{
|
|
PLC_CONTEXT ctxLC = NULL;
|
|
QWORD qwExistingHandle = 0, tmStart = LcCallStart();
|
|
if(ppLcCreateErrorInfo) { *ppLcCreateErrorInfo = NULL; }
|
|
if(!pLcCreateConfig || (pLcCreateConfig->dwVersion != LC_CONFIG_VERSION)) { return NULL; }
|
|
// check if open existing (primary) device:
|
|
if(!pLcCreateConfig->szRemote[0] && (0 == _strnicmp("existing", pLcCreateConfig->szDevice, 8))) {
|
|
if(0 == _strnicmp("existing://", pLcCreateConfig->szDevice, 11)) {
|
|
qwExistingHandle = Util_GetNumericA(pLcCreateConfig->szDevice + 11);
|
|
}
|
|
EnterCriticalSection(&g_ctx.Lock);
|
|
ctxLC = (PLC_CONTEXT)g_ctx.FLink;
|
|
while(qwExistingHandle && ctxLC && (qwExistingHandle != (QWORD)ctxLC)) {
|
|
ctxLC = ctxLC->FLink;
|
|
}
|
|
if(qwExistingHandle && (qwExistingHandle != (QWORD)ctxLC)) {
|
|
ctxLC = NULL;
|
|
}
|
|
if(ctxLC) {
|
|
memcpy(pLcCreateConfig, &ctxLC->Config, sizeof(LC_CONFIG));
|
|
InterlockedIncrement(&ctxLC->dwHandleCount);
|
|
}
|
|
LeaveCriticalSection(&g_ctx.Lock);
|
|
return ctxLC;
|
|
}
|
|
// initialize new leechcore context:
|
|
if(!(ctxLC = LocalAlloc(LMEM_ZEROINIT, sizeof(LC_CONTEXT)))) { return NULL; }
|
|
pLcCreateConfig->fRemote = FALSE;
|
|
memcpy(&ctxLC->Config, pLcCreateConfig, sizeof(LC_CONFIG));
|
|
InitializeCriticalSection(&ctxLC->Lock);
|
|
ctxLC->version = LC_CONTEXT_VERSION;
|
|
ctxLC->dwHandleCount = 1;
|
|
ctxLC->cMemMapMax = 0x20;
|
|
ctxLC->pMemMap = LocalAlloc(LMEM_ZEROINIT, ctxLC->cMemMapMax * sizeof(LC_MEMMAP_ENTRY));
|
|
ctxLC->fPrintf[0] = (ctxLC->Config.dwPrintfVerbosity & LC_CONFIG_PRINTF_ENABLED) ? TRUE : FALSE;
|
|
ctxLC->fPrintf[1] = (ctxLC->Config.dwPrintfVerbosity & LC_CONFIG_PRINTF_V) ? TRUE : FALSE;
|
|
ctxLC->fPrintf[2] = (ctxLC->Config.dwPrintfVerbosity & LC_CONFIG_PRINTF_VV) ? TRUE : FALSE;
|
|
ctxLC->fPrintf[3] = (ctxLC->Config.dwPrintfVerbosity & LC_CONFIG_PRINTF_VVV) ? TRUE : FALSE;
|
|
LcCreate_FetchDeviceParameter(ctxLC);
|
|
LcCreate_FetchDevice(ctxLC);
|
|
if(!ctxLC->pfnCreate || !ctxLC->pfnCreate(ctxLC, ppLcCreateErrorInfo) || !LcReadContigious_Initialize(ctxLC)) {
|
|
LcClose(ctxLC);
|
|
return NULL;
|
|
}
|
|
if(!ctxLC->Config.fRemote) {
|
|
LcCreate_MemMapInitAddressDetect(ctxLC);
|
|
ctxLC->Config.paMax = LcMemMap_GetMaxAddress(ctxLC);
|
|
ctxLC->Config.fWritable = (ctxLC->pfnWriteScatter != NULL) || (ctxLC->pfnWriteContigious != NULL);
|
|
}
|
|
ctxLC->CallStat.dwVersion = LC_STATISTICS_VERSION;
|
|
QueryPerformanceFrequency((PLARGE_INTEGER)&ctxLC->CallStat.qwFreq);
|
|
memcpy(pLcCreateConfig, &ctxLC->Config, sizeof(LC_CONFIG));
|
|
lcprintfvv(ctxLC, "LeechCore v%i.%i.%i: Open Device: %s\n", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, ctxLC->Config.szDeviceName);
|
|
// add new leechcore context to global list and return:
|
|
EnterCriticalSection(&g_ctx.Lock);
|
|
ctxLC->FLink = g_ctx.FLink;
|
|
g_ctx.FLink = ctxLC;
|
|
LeaveCriticalSection(&g_ctx.Lock);
|
|
LcCallEnd(ctxLC, LC_STATISTICS_ID_OPEN, tmStart);
|
|
return ctxLC;
|
|
}
|
|
|
|
_Success_(return != NULL)
|
|
EXPORTED_FUNCTION HANDLE LcCreate(_Inout_ PLC_CONFIG pLcCreateConfig)
|
|
{
|
|
return LcCreateEx(pLcCreateConfig, NULL);
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Allocate/Free MEM_SCATTER:
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/*
|
|
* Free LeechCore allocated memory such as memory allocated by the
|
|
* LcAllocScatter functions.
|
|
* -- pv
|
|
*/
|
|
EXPORTED_FUNCTION VOID LcMemFree(_Frees_ptr_opt_ PVOID pv)
|
|
{
|
|
LocalFree(pv);
|
|
}
|
|
|
|
/*
|
|
* Allocate and pre-initialize empty MEMs including a 0x1000 buffer for each
|
|
* pMEM. The result should be freed by LcFree when its no longer needed.
|
|
* The 0x1000-sized per-MEM memory buffers are contigious between MEMs in order.
|
|
* -- cMEMs
|
|
* -- pppMEMs = pointer to receive ppMEMs
|
|
* -- return
|
|
*/
|
|
_Success_(return)
|
|
EXPORTED_FUNCTION BOOL LcAllocScatter1(_In_ DWORD cMEMs, _Out_ PPMEM_SCATTER *pppMEMs)
|
|
{
|
|
DWORD i, o = 0;
|
|
PBYTE pb, pbData;
|
|
PMEM_SCATTER pMEMs, *ppMEMs;
|
|
if(!(pb = LocalAlloc(LMEM_ZEROINIT, cMEMs * (sizeof(PMEM_SCATTER) + sizeof(MEM_SCATTER) + 0x1000)))) { return FALSE; }
|
|
ppMEMs = (PPMEM_SCATTER)pb;
|
|
pMEMs = (PMEM_SCATTER)(pb + cMEMs * (sizeof(PMEM_SCATTER)));
|
|
pbData = pb + cMEMs * (sizeof(PMEM_SCATTER) + sizeof(MEM_SCATTER));
|
|
for(i = 0; i < cMEMs; i++) {
|
|
ppMEMs[i] = pMEMs + i;
|
|
pMEMs[i].version = MEM_SCATTER_VERSION;
|
|
pMEMs[i].cb = 0x1000;
|
|
pMEMs[i].pb = pbData + o;
|
|
o += 0x1000;
|
|
}
|
|
*pppMEMs = ppMEMs;
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Allocate and pre-initialize empty MEMs excluding the 0x1000 buffer which
|
|
* will be accounted towards the pbData buffer in a contiguous way.
|
|
* The result should be freed by LcFree when its no longer needed.
|
|
* -- cbData = size of pbData (must be cMEMs * 0x1000)
|
|
* -- pbData = buffer used for MEM.pb
|
|
* -- cMEMs
|
|
* -- pppMEMs = pointer to receive ppMEMs
|
|
* -- return
|
|
*/
|
|
_Success_(return)
|
|
EXPORTED_FUNCTION BOOL LcAllocScatter2(_In_ DWORD cbData, _Inout_updates_opt_(cbData) PBYTE pbData, _In_ DWORD cMEMs, _Out_ PPMEM_SCATTER *pppMEMs)
|
|
{
|
|
DWORD i, o = 0;
|
|
PBYTE pb;
|
|
PMEM_SCATTER pMEMs, *ppMEMs;
|
|
if(cbData > (cMEMs << 12)) { return FALSE; }
|
|
if(!(pb = LocalAlloc(LMEM_ZEROINIT, cMEMs * (sizeof(PMEM_SCATTER) + sizeof(MEM_SCATTER))))) { return FALSE; }
|
|
ppMEMs = (PPMEM_SCATTER)pb;
|
|
pMEMs = (PMEM_SCATTER)(pb + cMEMs * (sizeof(PMEM_SCATTER)));
|
|
for(i = 0; i < cMEMs; i++) {
|
|
ppMEMs[i] = pMEMs + i;
|
|
pMEMs[i].version = MEM_SCATTER_VERSION;
|
|
pMEMs[i].cb = 0x1000;
|
|
pMEMs[i].pb = pbData + o;
|
|
o += 0x1000;
|
|
}
|
|
*pppMEMs = ppMEMs;
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Allocate and pre-initialize empty MEMs excluding the 0x1000 buffer which
|
|
* will be accounted towards the pbData buffer in a contiguous way.
|
|
* -- pbDataFirstPage = optional buffer of first page
|
|
* -- pbDataLastPage = optional buffer of last page
|
|
* -- cbData = size of pbData
|
|
* -- pbData = buffer used for MEM.pb except first/last if exists
|
|
* -- cMEMs
|
|
* -- pppMEMs = pointer to receive ppMEMs
|
|
* -- return
|
|
*/
|
|
_Success_(return)
|
|
EXPORTED_FUNCTION BOOL LcAllocScatter3(_Inout_updates_opt_(0x1000) PBYTE pbDataFirstPage, _Inout_updates_opt_(0x1000) PBYTE pbDataLastPage, _In_ DWORD cbData, _Inout_updates_opt_(cbData) PBYTE pbData, _In_ DWORD cMEMs, _Out_ PPMEM_SCATTER *pppMEMs)
|
|
{
|
|
DWORD i, o = 0;
|
|
PBYTE pb;
|
|
PMEM_SCATTER pMEMs, *ppMEMs;
|
|
if(pbDataFirstPage) { cbData += 0x1000; }
|
|
if(pbDataLastPage) { cbData += 0x1000; }
|
|
if(cbData > (cMEMs << 12)) { return FALSE; }
|
|
if(!(pb = LocalAlloc(LMEM_ZEROINIT, cMEMs * (sizeof(PMEM_SCATTER) + sizeof(MEM_SCATTER))))) { return FALSE; }
|
|
ppMEMs = (PPMEM_SCATTER)pb;
|
|
pMEMs = (PMEM_SCATTER)(pb + cMEMs * (sizeof(PMEM_SCATTER)));
|
|
for(i = 0; i < cMEMs; i++) {
|
|
ppMEMs[i] = pMEMs + i;
|
|
pMEMs[i].version = MEM_SCATTER_VERSION;
|
|
pMEMs[i].cb = 0x1000;
|
|
if(pbDataFirstPage && (i == 0)) {
|
|
pMEMs[i].pb = pbDataFirstPage;
|
|
} else if(pbDataLastPage && (i == cMEMs - 1)) {
|
|
pMEMs[i].pb = pbDataLastPage;
|
|
} else {
|
|
pMEMs[i].pb = pbData + o;
|
|
o += 0x1000;
|
|
}
|
|
}
|
|
*pppMEMs = ppMEMs;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// READ CONTIGIOUS FUNCTIONALITY BELOW:
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/*
|
|
* Perform a contigious read from an underlying device instance.
|
|
* -- ctxRC
|
|
*/
|
|
VOID LcReadContigious_DeviceRead(PLC_READ_CONTIGIOUS_CONTEXT ctxRC)
|
|
{
|
|
DWORD i, o, cbRead;
|
|
PMEM_SCATTER pMEM;
|
|
ctxRC->ctxLC->pfnReadContigious(ctxRC);
|
|
cbRead = ctxRC->cbRead;
|
|
for(i = 0, o = 0; ((i < ctxRC->cMEMs) && (cbRead >= ctxRC->ppMEMs[i]->cb)); i++) {
|
|
pMEM = ctxRC->ppMEMs[i];
|
|
memcpy(pMEM->pb, ctxRC->pb + o, pMEM->cb);
|
|
pMEM->f = TRUE;
|
|
o += pMEM->cb;
|
|
cbRead -= pMEM->cb;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Main thread loop for multi-chunked/threaded linear reads.
|
|
* -- ctxRC
|
|
* -- return
|
|
*/
|
|
DWORD LcReadContigious_ThreadProc(PLC_READ_CONTIGIOUS_CONTEXT ctxRC)
|
|
{
|
|
while(ctxRC->ctxLC->RC.fActive) {
|
|
WaitForSingleObject(ctxRC->hEventWakeup, INFINITE);
|
|
if(!ctxRC->ctxLC->RC.fActive) { break; }
|
|
LcReadContigious_DeviceRead(ctxRC);
|
|
SetEvent(ctxRC->hEventFinish);
|
|
}
|
|
SetEvent(ctxRC->hEventFinish);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Perform a read of the linear memory specified onto the supplied MEMs.
|
|
* -- ctxLC
|
|
* -- cMEMs
|
|
* -- ppMEMs
|
|
* -- paBase
|
|
* -- cb
|
|
* -- fSingleThreaded
|
|
*/
|
|
VOID LcReadContigious_Read(_In_ PLC_CONTEXT ctxLC, _In_ DWORD cMEMs, _Inout_ PPMEM_SCATTER ppMEMs, _In_ QWORD paBase, _In_ DWORD cb, _In_ BOOL fSingleThreaded)
|
|
{
|
|
DWORD i;
|
|
PLC_READ_CONTIGIOUS_CONTEXT ctxRC;
|
|
if(!ctxLC->RC.fActive) { return; }
|
|
if(fSingleThreaded) {
|
|
ctxRC = ctxLC->RC.ctx[0];
|
|
} else {
|
|
i = WaitForMultipleObjects(ctxLC->ReadContigious.cThread, ctxLC->RC.hEventFinish, FALSE, INFINITE) - WAIT_OBJECT_0;
|
|
if(!ctxLC->RC.fActive || (i >= ctxLC->ReadContigious.cThread)) { return; }
|
|
ctxRC = ctxLC->RC.ctx[i];
|
|
ResetEvent(ctxRC->hEventFinish);
|
|
}
|
|
ctxRC->cbRead = 0;
|
|
ctxRC->cMEMs = cMEMs;
|
|
ctxRC->ppMEMs = ppMEMs;
|
|
ctxRC->paBase = paBase;
|
|
ctxRC->cb = cb;
|
|
if(fSingleThreaded) {
|
|
LcReadContigious_DeviceRead(ctxRC);
|
|
} else {
|
|
SetEvent(ctxRC->hEventWakeup);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Condense scattered MEMs into as large linear read-chunks as possible and
|
|
* schedule these chunks for reading using either single-threaded read or
|
|
* multi-threaded read - as configured and as optimal.
|
|
* MEMs are assumed to have their memory map translation/validation completed.
|
|
* NB! MUST BE CALLED SINGLE THREADED (per device instance).
|
|
* -- ctxLC
|
|
* -- cMEMs
|
|
* -- ppMEMs
|
|
*/
|
|
VOID LcReadContigious_ReadScatterGather(_In_ PLC_CONTEXT ctxLC, _In_ DWORD cMEMs, _Inout_ PPMEM_SCATTER ppMEMs)
|
|
{
|
|
PMEM_SCATTER pMEM;
|
|
QWORD i, iBase = 0, paBase = 0;
|
|
DWORD cbChunkSizeLimit, c = 0, cbCurrent = 0;
|
|
BOOL fSingleThreaded, fFirst = TRUE;
|
|
fSingleThreaded = (ctxLC->ReadContigious.cThread == 1);
|
|
cbChunkSizeLimit = ctxLC->ReadContigious.cbChunkSize;
|
|
if((ctxLC->ReadContigious.cThread > 1) && ctxLC->ReadContigious.fLoadBalance) {
|
|
cbChunkSizeLimit = min(cbChunkSizeLimit, max(0x00010000, 0x1000 * (cMEMs / ctxLC->ReadContigious.cThread)));
|
|
}
|
|
for(i = 0; i < cMEMs; i++) {
|
|
pMEM = ppMEMs[i];
|
|
if(!MEM_SCATTER_ADDR_ISVALID(pMEM)) { continue; }
|
|
if(c == 0) {
|
|
if(pMEM->cb && !pMEM->f) {
|
|
c = 1;
|
|
iBase = i;
|
|
paBase = pMEM->qwA;
|
|
cbCurrent = pMEM->cb;
|
|
}
|
|
} else if((paBase + cbCurrent == pMEM->qwA) && (cbCurrent < cbChunkSizeLimit)) {
|
|
c++;
|
|
cbCurrent += pMEM->cb;
|
|
} else {
|
|
fFirst = FALSE;
|
|
LcReadContigious_Read(ctxLC, c, ppMEMs + iBase, paBase, cbCurrent, fSingleThreaded);
|
|
c = 0;
|
|
if(pMEM->cb && !pMEM->f) {
|
|
c = 1;
|
|
iBase = i;
|
|
paBase = pMEM->qwA;
|
|
cbCurrent = pMEM->cb;
|
|
}
|
|
}
|
|
}
|
|
fSingleThreaded = fSingleThreaded || fFirst;
|
|
if(c) {
|
|
LcReadContigious_Read(ctxLC, c, ppMEMs + iBase, paBase, cbCurrent, fSingleThreaded);
|
|
}
|
|
if(!fSingleThreaded && ctxLC->RC.fActive) {
|
|
WaitForMultipleObjects(ctxLC->ReadContigious.cThread, ctxLC->RC.hEventFinish, TRUE, INFINITE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Try closing the ReadContigious sub-system for a specific device instance.
|
|
* -- ctxLC
|
|
*/
|
|
VOID LcReadContigious_Close(_In_ PLC_CONTEXT ctxLC)
|
|
{
|
|
DWORD i;
|
|
PLC_READ_CONTIGIOUS_CONTEXT ctxRC;
|
|
ctxLC->RC.fActive = FALSE;
|
|
for(i = 0; i < ctxLC->ReadContigious.cThread; i++) {
|
|
if(!ctxLC->RC.ctx[i] || !ctxLC->RC.ctx[i]->hEventWakeup) { break; }
|
|
SetEvent(ctxLC->RC.ctx[i]->hEventWakeup);
|
|
}
|
|
for(i = 0; i < ctxLC->ReadContigious.cThread; i++) {
|
|
if(!ctxLC->RC.ctx[i]) { break; }
|
|
ctxRC = ctxLC->RC.ctx[i];
|
|
ctxLC->RC.ctx[i] = NULL;
|
|
if(ctxRC->hEventWakeup) { SetEvent(ctxRC->hEventWakeup); }
|
|
if(ctxRC->hEventFinish) { WaitForSingleObject(ctxRC->hEventFinish, INFINITE); }
|
|
if(ctxRC->hEventFinish) { CloseHandle(ctxRC->hEventFinish); }
|
|
if(ctxRC->hEventWakeup) { CloseHandle(ctxRC->hEventWakeup); }
|
|
if(ctxRC->hThread) { CloseHandle(ctxRC->hThread); }
|
|
LocalFree(ctxRC);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialize the ReadContigious sub-system for a specific device instance.
|
|
* -- ctxLC
|
|
* -- return
|
|
*/
|
|
_Success_(return)
|
|
BOOL LcReadContigious_Initialize(_In_ PLC_CONTEXT ctxLC)
|
|
{
|
|
DWORD i;
|
|
PLC_READ_CONTIGIOUS_CONTEXT ctxRC;
|
|
if(!ctxLC->pfnReadContigious) { return TRUE; }
|
|
if(!ctxLC->ReadContigious.cThread) { ctxLC->ReadContigious.cThread = 1; } // default: single-threaded.
|
|
if(!ctxLC->ReadContigious.cbChunkSize) { ctxLC->ReadContigious.cbChunkSize = 0x01000000; } // default: 16MB buffer / thread.
|
|
ctxLC->ReadContigious.cThread = min(8, ctxLC->ReadContigious.cThread); // max 8 threads in parallel.
|
|
ctxLC->ReadContigious.cbChunkSize = min(0x01000000, ctxLC->ReadContigious.cbChunkSize); // max 16MB buffer / thread.
|
|
ctxLC->RC.fActive = TRUE;
|
|
for(i = 0; i < ctxLC->ReadContigious.cThread; i++) {
|
|
if(!(ctxRC = ctxLC->RC.ctx[i] = LocalAlloc(0, sizeof(LC_READ_CONTIGIOUS_CONTEXT) + ctxLC->ReadContigious.cbChunkSize + 0x1000))) { goto fail; }
|
|
ZeroMemory(ctxRC, sizeof(LC_READ_CONTIGIOUS_CONTEXT));
|
|
ctxRC->ctxLC = ctxLC;
|
|
if(ctxLC->ReadContigious.cThread > 1) {
|
|
ctxRC->iRL = i;
|
|
if(!(ctxRC->hEventWakeup = CreateEvent(NULL, FALSE, FALSE, FALSE))) { goto fail; }
|
|
if(!(ctxRC->hEventFinish = ctxLC->RC.hEventFinish[i] = CreateEvent(NULL, TRUE, TRUE, FALSE))) { goto fail; }
|
|
if(!(ctxRC->hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)LcReadContigious_ThreadProc, ctxRC, 0, NULL))) { goto fail; }
|
|
}
|
|
}
|
|
return TRUE;
|
|
fail:
|
|
LcReadContigious_Close(ctxLC);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// READ / WRITE FUNCTIONALITY BELOW:
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/*
|
|
* Read memory in a scattered non-contiguous way. This is recommended for reads.
|
|
* -- hLC
|
|
* -- cMEMs
|
|
* -- ppMEMs
|
|
*/
|
|
EXPORTED_FUNCTION VOID LcReadScatter(_In_ HANDLE hLC, _In_ DWORD cMEMs, _Inout_ PPMEM_SCATTER ppMEMs)
|
|
{
|
|
PLC_CONTEXT ctxLC = (PLC_CONTEXT)hLC;
|
|
QWORD i, tmStart = LcCallStart();
|
|
if(!ctxLC || ctxLC->version != LC_CONTEXT_VERSION) { return; }
|
|
if(ctxLC->Config.fRemote && ctxLC->pfnReadScatter) {
|
|
// REMOTE
|
|
ctxLC->pfnReadScatter(ctxLC, cMEMs, ppMEMs);
|
|
} else {
|
|
// LOCAL LEECHCORE
|
|
// 1: TRANSLATE
|
|
for(i = 0; i < cMEMs; i++) {
|
|
MEM_SCATTER_STACK_PUSH(ppMEMs[i], ppMEMs[i]->qwA);
|
|
}
|
|
LcMemMap_TranslateMEMs(ctxLC, cMEMs, ppMEMs);
|
|
// 2: FETCH
|
|
LcLockAcquire(ctxLC);
|
|
if(ctxLC->pfnReadScatter) {
|
|
ctxLC->pfnReadScatter(ctxLC, cMEMs, ppMEMs);
|
|
} else if(ctxLC->RC.fActive) {
|
|
LcReadContigious_ReadScatterGather(ctxLC, cMEMs, ppMEMs);
|
|
}
|
|
LcLockRelease(ctxLC);
|
|
// 3: RESTORE
|
|
for(i = 0; i < cMEMs; i++) {
|
|
ppMEMs[i]->qwA = MEM_SCATTER_STACK_POP(ppMEMs[i]);
|
|
}
|
|
}
|
|
LcCallEnd(ctxLC, LC_STATISTICS_ID_READSCATTER, tmStart);
|
|
}
|
|
|
|
/*
|
|
* Read memory in a contiguous way. Note that if multiple memory segments are
|
|
* to be read LcReadScatter() may be more efficient.
|
|
* -- hLC,
|
|
* -- pa
|
|
* -- cb
|
|
* -- pb
|
|
* -- return
|
|
*/
|
|
_Success_(return)
|
|
EXPORTED_FUNCTION BOOL LcRead(_In_ HANDLE hLC, _In_ QWORD pa, _In_ DWORD cb, _Out_writes_(cb) PBYTE pb)
|
|
{
|
|
QWORD i, o, paBase, cMEMs;
|
|
PPMEM_SCATTER ppMEMs = NULL;
|
|
BOOL fFirst, fLast, f, fResult = FALSE;
|
|
BYTE pbFirst[0x1000] = { 0 }, pbLast[0x1000] = { 0 };
|
|
PLC_CONTEXT ctxLC = (PLC_CONTEXT)hLC;
|
|
QWORD tmStart = LcCallStart();
|
|
if(!ctxLC || ctxLC->version != LC_CONTEXT_VERSION) { return FALSE; }
|
|
if(cb == 0) { return TRUE; }
|
|
cMEMs = ((pa & 0xfff) + cb + 0xfff) >> 12;
|
|
if(cMEMs == 0) { return FALSE; }
|
|
fFirst = (pa & 0xfff) || (cb < 0x1000);
|
|
fLast = (cMEMs > 1) && ((pa + cb) & 0xfff);
|
|
f = LcAllocScatter3(
|
|
fFirst ? pbFirst : NULL,
|
|
fLast ? pbLast : NULL,
|
|
cb - (fFirst ? 0x1000 - (pa & 0xfff) : 0) - (fLast ? (pa + cb) & 0xfff : 0),
|
|
pb + ((pa & 0xfff) ? 0x1000 - (pa & 0xfff) : 0),
|
|
(DWORD)cMEMs,
|
|
&ppMEMs
|
|
);
|
|
if(!f) { goto fail; }
|
|
paBase = pa & ~0xfff;
|
|
for(i = 0; i < cMEMs; i++) {
|
|
ppMEMs[i]->qwA = paBase + (i << 12);
|
|
}
|
|
LcReadScatter(hLC, (DWORD)cMEMs, ppMEMs);
|
|
for(i = 0; i < cMEMs; i++) {
|
|
if(!ppMEMs[i]->f) { goto fail; }
|
|
}
|
|
if(fFirst) {
|
|
o = pa & 0xfff;
|
|
memcpy(pb, ppMEMs[0]->pb + o, min(cb, 0x1000 - (SIZE_T)o));
|
|
}
|
|
if(fLast) {
|
|
o = ppMEMs[cMEMs - 1]->qwA;
|
|
memcpy(pb + (SIZE_T)(o - pa), ppMEMs[cMEMs - 1]->pb, (SIZE_T)(pa + cb - o));
|
|
}
|
|
fResult = TRUE;
|
|
fail:
|
|
LocalFree(ppMEMs);
|
|
LcCallEnd(ctxLC, LC_STATISTICS_ID_READ, tmStart);
|
|
return fResult;
|
|
}
|
|
|
|
/*
|
|
* Write scatter memory in a contigious way - helper function for LcWriteScatter_GatherContigious().
|
|
* -- ctxLC
|
|
* -- cMEMs
|
|
* -- ppMEMs
|
|
* -- cbWrite
|
|
*/
|
|
VOID LcWriteScatter_GatherContigious2(_In_ PLC_CONTEXT ctxLC, _In_ DWORD cMEMs, _Inout_ PPMEM_SCATTER ppMEMs, _In_ DWORD cbWrite)
|
|
{
|
|
DWORD i;
|
|
if(ctxLC->pfnWriteContigious(ctxLC, ppMEMs[0]->qwA, cbWrite, ppMEMs[0]->pb)) {
|
|
for(i = 0; i < cMEMs; i++) {
|
|
ppMEMs[i]->f = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Write scatter memory in a contigious way.
|
|
* -- ctxLC
|
|
* -- cMEMs
|
|
* -- ppMEMs
|
|
*/
|
|
VOID LcWriteScatter_GatherContigious(_In_ PLC_CONTEXT ctxLC, _In_ DWORD cMEMs, _Inout_ PPMEM_SCATTER ppMEMs)
|
|
{
|
|
DWORD c = 0, cbCurrent;
|
|
QWORD i, iBase = 0, paBase;
|
|
PMEM_SCATTER pMEM;
|
|
for(i = 0; i < cMEMs; i++) {
|
|
pMEM = ppMEMs[i];
|
|
if(pMEM->f || !MEM_SCATTER_ADDR_ISVALID(pMEM)) { continue; }
|
|
if(c == 0) {
|
|
c = 1;
|
|
iBase = i;
|
|
paBase = pMEM->qwA;
|
|
cbCurrent = pMEM->cb;
|
|
} else if(paBase + cbCurrent == pMEM->qwA) {
|
|
c++;
|
|
cbCurrent += pMEM->cb;
|
|
} else {
|
|
LcWriteScatter_GatherContigious2(ctxLC, c, ppMEMs + iBase, cbCurrent);
|
|
c = 1;
|
|
iBase = i;
|
|
paBase = pMEM->qwA;
|
|
cbCurrent = pMEM->cb;
|
|
}
|
|
}
|
|
if(c) {
|
|
LcWriteScatter_GatherContigious2(ctxLC, c, ppMEMs + iBase, cbCurrent);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Write memory in a scattered non-contiguous way.
|
|
* -- hLC
|
|
* -- cMEMs
|
|
* -- ppMEMs
|
|
*/
|
|
EXPORTED_FUNCTION VOID LcWriteScatter(_In_ HANDLE hLC, _In_ DWORD cMEMs, _Inout_ PPMEM_SCATTER ppMEMs)
|
|
{
|
|
PLC_CONTEXT ctxLC = (PLC_CONTEXT)hLC;
|
|
QWORD i, tmStart = LcCallStart();
|
|
if(!ctxLC || ctxLC->version != LC_CONTEXT_VERSION) { return; }
|
|
if(!ctxLC->pfnWriteScatter && !ctxLC->pfnWriteContigious) { return; }
|
|
if(!cMEMs) { return; }
|
|
if(ctxLC->Config.fRemote && ctxLC->pfnWriteScatter) {
|
|
// REMOTE
|
|
ctxLC->pfnWriteScatter(ctxLC, cMEMs, ppMEMs);
|
|
} else {
|
|
// LOCAL LEECHCORE
|
|
// 1: TRANSLATE
|
|
for(i = 0; i < cMEMs; i++) {
|
|
MEM_SCATTER_STACK_PUSH(ppMEMs[i], ppMEMs[i]->qwA);
|
|
}
|
|
LcMemMap_TranslateMEMs(ctxLC, cMEMs, ppMEMs);
|
|
// 2: FETCH
|
|
LcLockAcquire(ctxLC);
|
|
if(ctxLC->pfnWriteScatter) {
|
|
ctxLC->pfnWriteScatter(ctxLC, cMEMs, ppMEMs);
|
|
} else {
|
|
LcWriteScatter_GatherContigious(ctxLC, cMEMs, ppMEMs);
|
|
}
|
|
LcLockRelease(ctxLC);
|
|
// 3: RESTORE
|
|
for(i = 0; i < cMEMs; i++) {
|
|
ppMEMs[i]->qwA = MEM_SCATTER_STACK_POP(ppMEMs[i]);
|
|
}
|
|
}
|
|
LcCallEnd(ctxLC, LC_STATISTICS_ID_WRITESCATTER, tmStart);
|
|
}
|
|
|
|
/*
|
|
* Write memory in a contiguous way.
|
|
* -- hLC
|
|
* -- pa
|
|
* -- cb
|
|
* -- pb
|
|
* -- return
|
|
*/
|
|
_Success_(return)
|
|
EXPORTED_FUNCTION BOOL LcWrite(_In_ HANDLE hLC, _In_ QWORD pa, _In_ DWORD cb, _In_reads_(cb) PBYTE pb)
|
|
{
|
|
BOOL fResult = FALSE;
|
|
PBYTE pbBuffer = NULL;
|
|
DWORD i = 0, oA = 0, cbP, cMEMs;
|
|
PMEM_SCATTER pMEM, pMEMs, *ppMEMs;
|
|
PLC_CONTEXT ctxLC = (PLC_CONTEXT)hLC;
|
|
QWORD tmStart = LcCallStart();
|
|
if(!ctxLC || ctxLC->version != LC_CONTEXT_VERSION) { goto fail; }
|
|
// allocate
|
|
cMEMs = (DWORD)(((pa & 0xfff) + cb + 0xfff) >> 12);
|
|
if(!(pbBuffer = (PBYTE)LocalAlloc(LMEM_ZEROINIT, cMEMs * (sizeof(MEM_SCATTER) + sizeof(PMEM_SCATTER))))) { goto fail; }
|
|
pMEMs = (PMEM_SCATTER)pbBuffer;
|
|
ppMEMs = (PPMEM_SCATTER)(pbBuffer + cMEMs * sizeof(MEM_SCATTER));
|
|
// prepare pages
|
|
while(oA < cb) {
|
|
cbP = 0x1000 - ((pa + oA) & 0xfff);
|
|
cbP = min(cbP, cb - oA);
|
|
ppMEMs[i] = pMEM = pMEMs + i;
|
|
pMEM->version = MEM_SCATTER_VERSION;
|
|
pMEM->qwA = pa + oA;
|
|
pMEM->cb = cbP;
|
|
pMEM->pb = pb + oA;
|
|
oA += cbP;
|
|
i++;
|
|
}
|
|
// write and verify result
|
|
LcWriteScatter(hLC, cMEMs, ppMEMs);
|
|
for(i = 0; i < cMEMs; i++) {
|
|
if(!ppMEMs[i]->f) {
|
|
break;
|
|
}
|
|
}
|
|
fResult = TRUE;
|
|
fail:
|
|
LocalFree(pbBuffer);
|
|
LcCallEnd(ctxLC, LC_STATISTICS_ID_WRITE, tmStart);
|
|
return fResult;
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// GET / SET / COMMAND FUNCTIONALITY BELOW:
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/*
|
|
* Helper function for LcGetOption.
|
|
*/
|
|
_Success_(return)
|
|
BOOL LcGetOption_DoWork(_In_ PLC_CONTEXT ctxLC, _In_ QWORD fOption, _Out_ PQWORD pqwValue)
|
|
{
|
|
*pqwValue = 0;
|
|
switch(fOption & 0xffffffff00000000) {
|
|
case LC_OPT_CORE_PRINTF_ENABLE:
|
|
*pqwValue = ctxLC->fPrintf[LC_PRINTF_ENABLE] ? 1 : 0;
|
|
return TRUE;
|
|
case LC_OPT_CORE_VERBOSE:
|
|
*pqwValue = ctxLC->fPrintf[LC_PRINTF_V] ? 1 : 0;
|
|
return TRUE;
|
|
case LC_OPT_CORE_VERBOSE_EXTRA:
|
|
*pqwValue = ctxLC->fPrintf[LC_PRINTF_VV] ? 1 : 0;
|
|
return TRUE;
|
|
case LC_OPT_CORE_VERBOSE_EXTRA_TLP:
|
|
*pqwValue = ctxLC->fPrintf[LC_PRINTF_VVV] ? 1 : 0;
|
|
return TRUE;
|
|
case LC_OPT_CORE_VERSION_MAJOR:
|
|
*pqwValue = VERSION_MAJOR;
|
|
return TRUE;
|
|
case LC_OPT_CORE_VERSION_MINOR:
|
|
*pqwValue = VERSION_MINOR;
|
|
return TRUE;
|
|
case LC_OPT_CORE_VERSION_REVISION:
|
|
*pqwValue = VERSION_REVISION;
|
|
return TRUE;
|
|
case LC_OPT_CORE_ADDR_MAX:
|
|
*pqwValue = LcMemMap_GetMaxAddress(ctxLC);
|
|
return TRUE;
|
|
case LC_OPT_CORE_STATISTICS_CALL_COUNT:
|
|
if((DWORD)fOption > LC_STATISTICS_ID_MAX) { return FALSE; }
|
|
*pqwValue = ctxLC->CallStat.Call[(DWORD)fOption].c;
|
|
return TRUE;
|
|
case LC_OPT_CORE_STATISTICS_CALL_TIME:
|
|
if((DWORD)fOption > LC_STATISTICS_ID_MAX) { return FALSE; }
|
|
*pqwValue = ctxLC->CallStat.Call[(DWORD)fOption].tm;
|
|
return TRUE;
|
|
case LC_OPT_CORE_VOLATILE:
|
|
*pqwValue = ctxLC->Config.fVolatile ? 1 : 0;
|
|
return TRUE;
|
|
case LC_OPT_CORE_READONLY:
|
|
*pqwValue = ctxLC->Config.fWritable ? 0 : 1;
|
|
return TRUE;
|
|
}
|
|
if(ctxLC->pfnGetOption) {
|
|
return ctxLC->pfnGetOption(ctxLC, fOption, pqwValue);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Set an option as defined by LC_OPT_*. (R option).
|
|
* -- hLC
|
|
* -- fOption
|
|
* -- cbData
|
|
* -- pbData
|
|
* -- pcbData
|
|
*/
|
|
_Success_(return)
|
|
EXPORTED_FUNCTION BOOL LcGetOption(_In_ HANDLE hLC, _In_ QWORD fOption, _Out_ PQWORD pqwValue)
|
|
{
|
|
PLC_CONTEXT ctxLC = (PLC_CONTEXT)hLC;
|
|
QWORD tmStart = LcCallStart();
|
|
BOOL fResult;
|
|
if(!ctxLC || ctxLC->version != LC_CONTEXT_VERSION) { return FALSE; }
|
|
LcLockAcquire(ctxLC);
|
|
fResult = ctxLC->Config.fRemote ?
|
|
ctxLC->pfnGetOption(ctxLC, fOption, pqwValue) :
|
|
LcGetOption_DoWork(ctxLC, fOption, pqwValue);
|
|
LcLockRelease(ctxLC);
|
|
LcCallEnd(ctxLC, LC_STATISTICS_ID_GETOPTION, tmStart);
|
|
return fResult;
|
|
}
|
|
|
|
/*
|
|
* Helper function for LcSetOption.
|
|
*/
|
|
_Success_(return)
|
|
BOOL LcSetOption_DoWork(_In_ PLC_CONTEXT ctxLC, _In_ QWORD fOption, _In_ QWORD qwValue)
|
|
{
|
|
switch(fOption) {
|
|
case LC_OPT_CORE_PRINTF_ENABLE:
|
|
ctxLC->fPrintf[LC_PRINTF_ENABLE] = qwValue ? TRUE : FALSE;
|
|
return TRUE;
|
|
case LC_OPT_CORE_VERBOSE:
|
|
ctxLC->fPrintf[LC_PRINTF_V] = qwValue ? TRUE : FALSE;
|
|
return TRUE;
|
|
case LC_OPT_CORE_VERBOSE_EXTRA:
|
|
ctxLC->fPrintf[LC_PRINTF_VV] = qwValue ? TRUE : FALSE;
|
|
return TRUE;
|
|
case LC_OPT_CORE_VERBOSE_EXTRA_TLP:
|
|
ctxLC->fPrintf[LC_PRINTF_VVV] = qwValue ? TRUE : FALSE;
|
|
return TRUE;
|
|
}
|
|
if(ctxLC->pfnSetOption) {
|
|
return ctxLC->pfnSetOption(ctxLC, fOption, qwValue);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Get an option as defined by LC_OPT_*. (W option).
|
|
* -- hLC
|
|
* -- fOption
|
|
* -- cbData
|
|
* -- pbData
|
|
*/
|
|
_Success_(return)
|
|
EXPORTED_FUNCTION BOOL LcSetOption(_In_ HANDLE hLC, _In_ QWORD fOption, _In_ QWORD qwValue)
|
|
{
|
|
PLC_CONTEXT ctxLC = (PLC_CONTEXT)hLC;
|
|
QWORD tmStart = LcCallStart();
|
|
BOOL fResult;
|
|
if(!ctxLC || ctxLC->version != LC_CONTEXT_VERSION) { return FALSE; }
|
|
LcLockAcquire(ctxLC);
|
|
fResult = ctxLC->Config.fRemote ?
|
|
ctxLC->pfnSetOption(ctxLC, fOption, qwValue) :
|
|
LcSetOption_DoWork(ctxLC, fOption, qwValue);
|
|
LcLockRelease(ctxLC);
|
|
LcCallEnd(ctxLC, LC_STATISTICS_ID_SETOPTION, tmStart);
|
|
return fResult;
|
|
}
|
|
|
|
/*
|
|
* Helper function for LcCommand.
|
|
*/
|
|
_Success_(return)
|
|
BOOL LcCommand_DoWork(_In_ PLC_CONTEXT ctxLC, _In_ QWORD fOption, _In_ DWORD cbDataIn, _In_reads_opt_(cbDataIn) PBYTE pbDataIn, _Out_opt_ PBYTE *ppbDataOut, _Out_opt_ PDWORD pcbDataOut)
|
|
{
|
|
if(ppbDataOut) { *ppbDataOut = NULL; }
|
|
if(pcbDataOut) { *pcbDataOut = 0; }
|
|
switch(fOption) {
|
|
case LC_CMD_STATISTICS_GET:
|
|
if(!ppbDataOut) { return FALSE; }
|
|
if(!(*ppbDataOut = LocalAlloc(0, sizeof(LC_STATISTICS)))) { return FALSE; }
|
|
if(pcbDataOut) { *pcbDataOut = sizeof(LC_STATISTICS); }
|
|
memcpy(*ppbDataOut, &ctxLC->CallStat, sizeof(LC_STATISTICS));
|
|
return TRUE;
|
|
case LC_CMD_MEMMAP_GET_STRUCT:
|
|
if(!ppbDataOut) { return FALSE; }
|
|
return LcMemMap_GetRangesAsStruct(ctxLC, ppbDataOut, pcbDataOut);
|
|
case LC_CMD_MEMMAP_SET_STRUCT:
|
|
if(!cbDataIn || !pbDataIn) { return FALSE; }
|
|
return LcMemMap_SetRangesFromStruct(ctxLC, (PLC_MEMMAP_ENTRY)pbDataIn, cbDataIn / sizeof(LC_MEMMAP_ENTRY));
|
|
case LC_CMD_MEMMAP_GET:
|
|
if(!ppbDataOut) { return FALSE; }
|
|
return LcMemMap_GetRangesAsText(ctxLC, ppbDataOut, pcbDataOut);
|
|
case LC_CMD_MEMMAP_SET:
|
|
if(!pbDataIn || !cbDataIn) { return FALSE; }
|
|
return LcMemMap_SetRangesFromText(ctxLC, pbDataIn, cbDataIn);
|
|
}
|
|
if(ctxLC->pfnCommand) {
|
|
return ctxLC->pfnCommand(ctxLC, fOption, cbDataIn, pbDataIn, ppbDataOut, pcbDataOut);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Execute a command and retrieve a result (if any) at the same time.
|
|
* NB! If *ppbDataOut contains a memory allocation on exit this should be free'd
|
|
* by calling LcMemFree().
|
|
* CALLER LcFreeMem: *ppbDataOut
|
|
* -- hLC
|
|
* -- fCommand
|
|
* -- cbDataIn
|
|
* -- pbDataIn
|
|
* -- ppbDataOut
|
|
* -- pcbDataOut
|
|
*/
|
|
_Success_(return)
|
|
EXPORTED_FUNCTION BOOL LcCommand(_In_ HANDLE hLC, _In_ QWORD fCommand, _In_ DWORD cbDataIn, _In_reads_opt_(cbDataIn) PBYTE pbDataIn, _Out_opt_ PBYTE *ppbDataOut, _Out_opt_ PDWORD pcbDataOut)
|
|
{
|
|
PLC_CONTEXT ctxLC = (PLC_CONTEXT)hLC;
|
|
QWORD tmStart = LcCallStart();
|
|
BOOL fResult;
|
|
if(!ctxLC || ctxLC->version != LC_CONTEXT_VERSION) { return FALSE; }
|
|
LcLockAcquire(ctxLC);
|
|
fResult = ctxLC->Config.fRemote ?
|
|
ctxLC->pfnCommand(ctxLC, fCommand, cbDataIn, pbDataIn, ppbDataOut, pcbDataOut) :
|
|
LcCommand_DoWork(ctxLC, fCommand, cbDataIn, pbDataIn, ppbDataOut, pcbDataOut);
|
|
LcLockRelease(ctxLC);
|
|
LcCallEnd(ctxLC, LC_STATISTICS_ID_COMMAND, tmStart);
|
|
return fResult;
|
|
}
|