blob: 7c3f0b45a201a2f7d71628802f05718793689c29 [file] [log] [blame]
/* **********************************************************
* Copyright (c) 2011-2023 Google, Inc. All rights reserved.
* Copyright (c) 2004-2007 VMware, Inc. All rights reserved.
* **********************************************************/
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of VMware, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#ifdef X64
# error 64-bit not yet supported (issue #118)
#endif
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#define RC1_HACK 1
static int verbose = 1;
/* Avoid flushing issues on Windows, and keep warnings and info messages
* all in the same order, by always going to STDERR
*/
#define PRINT(...) fprintf(stderr, __VA_ARGS__)
#define INFO(level, ...) \
do { \
if (verbose >= (level)) { \
PRINT(__VA_ARGS__); \
} \
} while (0)
#define WARN(...) INFO(0, __VA_ARGS__)
/* alignment helpers */
#define ALIGNED(x, alignment) ((((uint)x) & ((alignment)-1)) == 0)
#define ALIGN_FORWARD(x, alignment) ((((uint)x) + ((alignment)-1)) & (~((alignment)-1)))
#define ALIGN_BACKWARD(x, alignment) (((uint)x) & (~((alignment)-1)))
#define PAD(length, alignment) (ALIGN_FORWARD((length), (alignment)) - (length))
/* check if all bits in mask are set in var */
#define TESTALL(mask, var) (((mask) & (var)) == (mask))
/* check if any bit in mask is set in var */
#define TESTANY(mask, var) (((mask) & (var)) != 0)
/* check if a single bit is set in var */
#define TEST TESTANY
#define BUFFER_SIZE_BYTES(buf) sizeof(buf)
#define BUFFER_SIZE_ELEMENTS(buf) (BUFFER_SIZE_BYTES(buf) / sizeof(buf[0]))
#define BUFFER_LAST_ELEMENT(buf) buf[BUFFER_SIZE_ELEMENTS(buf) - 1]
#define NULL_TERMINATE_BUFFER(buf) BUFFER_LAST_ELEMENT(buf) = 0
typedef int bool;
typedef unsigned int uint;
typedef LONG NTSTATUS;
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define false FALSE
#define true TRUE
#define PAGE_SIZE 0x1000
#define ALLOCATION_GRANULARITY 0x10000
#define INVALID_File (NULL)
#define GET_NTDLL(NtFunction, signature) NTSYSAPI NTSTATUS NTAPI NtFunction signature
typedef struct _UNICODE_STRING {
/* Length field is size in bytes not counting final 0 */
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor; // Points to type SECURITY_DESCRIPTOR
PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
#define InitializeObjectAttributes(p, n, a, r, s) \
{ \
(p)->Length = sizeof(OBJECT_ATTRIBUTES); \
(p)->RootDirectory = r; \
(p)->Attributes = a; \
(p)->ObjectName = n; \
(p)->SecurityDescriptor = s; \
(p)->SecurityQualityOfService = NULL; \
}
#define OBJ_CASE_INSENSITIVE 0x00000040L
#define OBJ_KERNEL_HANDLE 0x00000200L
typedef ULONG ACCESS_MASK;
#ifndef X64
# ifndef _W64
# define _W64
# endif
typedef _W64 long LONG_PTR, *PLONG_PTR;
typedef _W64 unsigned long ULONG_PTR, *PULONG_PTR;
#endif
typedef LONG KPRIORITY;
typedef ULONG_PTR KAFFINITY;
typedef struct _PROCESS_BASIC_INFORMATION {
NTSTATUS ExitStatus;
char *PebBaseAddress;
ULONG_PTR AffinityMask;
KPRIORITY BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;
typedef PROCESS_BASIC_INFORMATION *PPROCESS_BASIC_INFORMATION;
typedef struct _CLIENT_ID {
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID;
typedef CLIENT_ID *PCLIENT_ID;
typedef struct _THREAD_BASIC_INFORMATION { // Information Class 0
NTSTATUS ExitStatus;
PNT_TIB TebBaseAddress;
CLIENT_ID ClientId;
KAFFINITY AffinityMask;
KPRIORITY Priority;
KPRIORITY BasePriority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;
typedef enum {
MEMORY_RESERVE_ONLY = MEM_RESERVE,
MEMORY_COMMIT = MEM_RESERVE | MEM_COMMIT
} memory_commit_status_t;
typedef struct _IO_STATUS_BLOCK {
NTSTATUS Status;
// PVOID Pointer - Reserved, supposedly in a union with Status. see MSDN
ULONG Information; // MSDN says it is ULONG_PTR
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
typedef struct _USER_STACK {
PVOID FixedStackBase;
PVOID FixedStackLimit;
PVOID ExpandableStackBase;
PVOID ExpandableStackLimit;
PVOID ExpandableStackBottom;
} USER_STACK, *PUSER_STACK;
#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020
typedef enum _THREADINFOCLASS {
ThreadBasicInformation,
ThreadTimes,
ThreadPriority,
ThreadBasePriority,
ThreadAffinityMask,
ThreadImpersonationToken,
ThreadDescriptorTableEntry,
ThreadEnableAlignmentFaultFixup,
ThreadEventPair_Reusable,
ThreadQuerySetWin32StartAddress,
ThreadZeroTlsCell,
ThreadPerformanceCount,
ThreadAmILastThread,
ThreadIdealProcessor,
ThreadPriorityBoost,
ThreadSetTlsArrayAddress,
ThreadIsIoPending,
ThreadHideFromDebugger,
MaxThreadInfoClass
} THREADINFOCLASS;
typedef enum _PROCESSINFOCLASS {
ProcessBasicInformation,
ProcessQuotaLimits,
ProcessIoCounters,
ProcessVmCounters,
ProcessTimes,
ProcessBasePriority,
ProcessRaisePriority,
ProcessDebugPort,
ProcessExceptionPort,
ProcessAccessToken,
ProcessLdtInformation,
ProcessLdtSize,
ProcessDefaultHardErrorMode,
ProcessIoPortHandlers, // Note: this is kernel mode only
ProcessPooledUsageAndLimits,
ProcessWorkingSetWatch,
ProcessUserModeIOPL,
ProcessEnableAlignmentFaultFixup,
ProcessPriorityClass,
ProcessWx86Information,
ProcessHandleCount,
ProcessAffinityMask,
ProcessPriorityBoost,
ProcessDeviceMap,
ProcessSessionInformation,
ProcessForegroundInformation,
ProcessWow64Information,
MaxProcessInfoClass
} PROCESSINFOCLASS;
typedef struct _DESCRIPTOR_TABLE_ENTRY {
ULONG Selector;
LDT_ENTRY Descriptor;
} DESCRIPTOR_TABLE_ENTRY, *PDESCRIPTOR_TABLE_ENTRY;
#ifndef DR_PARAM_IN
# define DR_PARAM_IN
#endif
#ifndef DR_PARAM_OUT
# define DR_PARAM_OUT
#endif
#ifndef DR_PARAM_INOUT
# define DR_PARAM_INOUT
#endif
/************************************************************************/
static bool
nt_remote_allocate_virtual_memory(HANDLE process, void **base, uint size, uint prot,
memory_commit_status_t commit)
{
NTSTATUS res;
GET_NTDLL(NtAllocateVirtualMemory,
(DR_PARAM_IN HANDLE ProcessHandle, DR_PARAM_INOUT PVOID * BaseAddress,
DR_PARAM_IN ULONG ZeroBits, DR_PARAM_INOUT PULONG AllocationSize,
DR_PARAM_IN ULONG AllocationType, DR_PARAM_IN ULONG Protect));
uint sz = size;
assert(ALIGNED(*base, PAGE_SIZE));
res = NtAllocateVirtualMemory(process, base, 0 /* zero bits */, &sz, commit, prot);
assert(sz >= size);
return NT_SUCCESS(res);
}
static bool
query_thread_info(HANDLE h, THREAD_BASIC_INFORMATION *info)
{
NTSTATUS res;
GET_NTDLL(NtQueryInformationThread,
(DR_PARAM_IN HANDLE ThreadHandle,
DR_PARAM_IN THREADINFOCLASS ThreadInformationClass,
DR_PARAM_OUT PVOID ThreadInformation,
DR_PARAM_IN ULONG ThreadInformationLength,
DR_PARAM_OUT PULONG ReturnLength OPTIONAL));
int got;
memset(info, 0, sizeof(THREAD_BASIC_INFORMATION));
res = NtQueryInformationThread(h, ThreadBasicInformation, info,
sizeof(THREAD_BASIC_INFORMATION), &got);
assert(!NT_SUCCESS(res) || got == sizeof(THREAD_BASIC_INFORMATION));
return NT_SUCCESS(res);
}
static bool
query_process_info(HANDLE h, PROCESS_BASIC_INFORMATION *info)
{
NTSTATUS res;
GET_NTDLL(NtQueryInformationProcess,
(DR_PARAM_IN HANDLE ProcessHandle,
DR_PARAM_IN PROCESSINFOCLASS ProcessInformationClass,
DR_PARAM_OUT PVOID ProcessInformation,
DR_PARAM_IN ULONG ProcessInformationLength,
DR_PARAM_OUT PULONG ReturnLength OPTIONAL));
int got;
memset(info, 0, sizeof(PROCESS_BASIC_INFORMATION));
res = NtQueryInformationProcess(h, ProcessBasicInformation, info,
sizeof(PROCESS_BASIC_INFORMATION), &got);
assert(!NT_SUCCESS(res) || got == sizeof(PROCESS_BASIC_INFORMATION));
return NT_SUCCESS(res);
}
static bool
set_win32_start_addr(HANDLE h, void **start_addr)
{
NTSTATUS res;
GET_NTDLL(
NtSetInformationThread,
(DR_PARAM_IN HANDLE ThreadHandle, DR_PARAM_IN THREADINFOCLASS ThreadInfoClass,
DR_PARAM_IN PVOID ThreadInformation, DR_PARAM_IN ULONG ThreadInformationLength));
res = NtSetInformationThread(h, ThreadQuerySetWin32StartAddress, start_addr,
sizeof(*start_addr));
if (!NT_SUCCESS(res)) {
INFO(1, "setting thread start addr failed with 0x%08x\n", res);
}
return NT_SUCCESS(res);
}
static bool
wchar_to_unicode(PUNICODE_STRING dst, PCWSTR src)
{
NTSTATUS res;
GET_NTDLL(RtlInitUnicodeString,
(DR_PARAM_INOUT PUNICODE_STRING DestinationString,
DR_PARAM_IN PCWSTR SourceString));
res = RtlInitUnicodeString(dst, src);
return NT_SUCCESS(res);
}
static uint
remove_writecopy(uint prot)
{
uint other = prot & (PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE);
prot &= ~(PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE);
switch (prot) {
case PAGE_READONLY:
case PAGE_READWRITE:
case PAGE_EXECUTE:
case PAGE_EXECUTE_READ:
case PAGE_EXECUTE_READWRITE: break;
case PAGE_WRITECOPY: prot = PAGE_READWRITE; break;
case PAGE_EXECUTE_WRITECOPY: prot = PAGE_EXECUTE_READWRITE; break;
}
return (prot | other);
}
static bool
prot_is_readable(uint prot)
{
prot &= ~(PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE);
switch (prot) {
case PAGE_READONLY:
case PAGE_READWRITE:
case PAGE_WRITECOPY:
case PAGE_EXECUTE:
case PAGE_EXECUTE_READ:
case PAGE_EXECUTE_READWRITE:
case PAGE_EXECUTE_WRITECOPY: return true;
}
return false;
}
static bool
prot_is_writable(uint prot)
{
prot &= ~(PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE);
return (prot == PAGE_READWRITE || prot == PAGE_WRITECOPY ||
prot == PAGE_EXECUTE_READWRITE || prot == PAGE_EXECUTE_WRITECOPY);
}
static char *
mem_state_string(DWORD state)
{
switch (state) {
case 0: return "none";
case MEM_COMMIT: return "COMMIT";
case MEM_FREE: return "FREE";
case MEM_RESERVE: return "RESERVE";
}
return "<error>";
}
static char *
mem_type_string(DWORD type)
{
switch (type) {
case 0: return "none";
case MEM_IMAGE: return "IMAGE";
case MEM_MAPPED: return "MAPPED";
case MEM_PRIVATE: return "PRIVATE";
}
return "<error>";
}
static char *
prot_string(DWORD prot)
{
DWORD ignore_extras = prot & ~(PAGE_GUARD | PAGE_NOCACHE | PAGE_WRITECOMBINE);
switch (ignore_extras) {
case PAGE_NOACCESS: return "----";
case PAGE_READONLY: return "r---";
case PAGE_READWRITE: return "rw--";
case PAGE_WRITECOPY: return "rw-c";
case PAGE_EXECUTE: return "--x-";
case PAGE_EXECUTE_READ: return "r-x-";
case PAGE_EXECUTE_READWRITE: return "rwx-";
case PAGE_EXECUTE_WRITECOPY: return "rwxc";
}
return "<error>";
}
void
dump_mbi(MEMORY_BASIC_INFORMATION *mbi)
{
PRINT("BaseAddress: %p\n"
"AllocationBase: %p\n"
"AllocationProtect: %08x %s\n"
"RegionSize: %08x\n"
"State: %08x %s\n"
"Protect: %08x %s\n"
"Type: %08x %s\n",
mbi->BaseAddress, mbi->AllocationBase, mbi->AllocationProtect,
prot_string(mbi->AllocationProtect), mbi->RegionSize, mbi->State,
mem_state_string(mbi->State), mbi->Protect, prot_string(mbi->Protect),
mbi->Type, mem_type_string(mbi->Type));
}
/********************************************************************************/
typedef struct _mem_map {
char *old;
char *new;
} mem_map;
static int map_count = 0;
static mem_map map[4097];
static int pending_map_count = 0;
static char *pending_map[4097];
static void
add_mapped_addr(char *old, char *new)
{
assert(map_count < BUFFER_SIZE_ELEMENTS(map));
map[map_count].new = new;
map[map_count].old = old;
map_count++;
}
static void
add_pending_mapped_addr(char *addr)
{
assert(pending_map_count < BUFFER_SIZE_ELEMENTS(map));
pending_map[pending_map_count++] = addr;
}
static bool
is_pending_mapped_addr(char *addr)
{
int i;
for (i = 0; i < pending_map_count; i++) {
if (pending_map[i] == addr)
return true;
}
return false;
}
/* we can get NULL from a bad TEB so use this instead */
#define FAIL ((char *)-1)
static char *
get_mapped_addr(char *old)
{
int i;
for (i = 0; i < map_count; i++) {
if (map[i].old == old)
return map[i].new;
}
return FAIL;
}
static char *
get_original_addr(char *new)
{
int i;
for (i = 0; i < map_count; i++) {
if (map[i].new == new)
return map[i].old;
}
return FAIL;
}
/* NOTE - sum users implicitly assume this is exactly 1 page */
static char buf[4096];
/* Note, because of my sloppy file format, type may or may not have a
* description string, so we skip it and just eat the line, we can't do
* anything with the type information anyways */
static bool
read_mbi(FILE *file, MEMORY_BASIC_INFORMATION *mbi)
{
int res;
char *resc;
char dummy_buf[128];
memset(mbi, 0, sizeof(MEMORY_BASIC_INFORMATION));
res = fscanf(file,
"\nBaseAddress=%p\nAllocationBase=%p\n"
"AllocationProtect=0x%08x %*s\nRegionSize=0x%08x\n"
"State=0x%08x %*s\nProtect=0x%08x %*s\n",
&(mbi->BaseAddress), &(mbi->AllocationBase), &(mbi->AllocationProtect),
&(mbi->RegionSize), &(mbi->State), &(mbi->Protect));
if (res != 6)
return false;
/* eat the type line */
resc = fgets(dummy_buf, sizeof(dummy_buf), file);
assert(resc != NULL);
return true;
}
static void
print_descriptor(DESCRIPTOR_TABLE_ENTRY *entry)
{
/* IA-32 SDM Volume 3A, section 3.4.5, "Segment Descriptors".
* The segment type is determined by the S bit (0=system,
* 1=code or data) and the 4-bit type field:
*/
static const char *types[] = { "Data RO ", "Data RO, acc ",
"Data R/W, ", "Data R/W, acc ",
"Data RO, down ", "Data RO, down, acc ",
"Data R/W, down ", "Data R/W, down, acc ",
"Code EO ", "Code EO, acc ",
"Code E/R ", "Code E/R, acc ",
"Code EO, conf ", "Code EO, conf, acc ",
"Code E/RO, conf ", "Code E/RO, conf, acc" };
LDT_ENTRY *descr = &entry->Descriptor;
uint base = (descr->BaseLow | (descr->HighWord.Bits.BaseMid << 16) |
(descr->HighWord.Bits.BaseHi << 24));
uint limit = descr->LimitLow | (descr->HighWord.Bits.LimitHi << 16);
if (descr->HighWord.Bits.Granularity == 1) {
limit = limit << 12 | 0xfff;
}
PRINT("\t%04x ", entry->Selector);
PRINT("%08x ", base);
PRINT("%08x ", limit);
/* The definition for LDT_ENTRY combines the S and type fields
* into a five bit field. */
if (TEST(0x10, descr->HighWord.Bits.Type)) {
PRINT("%s ", types[descr->HighWord.Bits.Type & 0xf]);
} else {
PRINT("System ");
}
PRINT(" %x ", descr->HighWord.Bits.Dpl);
PRINT(" %x ", descr->HighWord.Bits.Default_Big);
PRINT("%s ", descr->HighWord.Bits.Granularity == 1 ? "4kb" : " 1b");
PRINT(" %x ", descr->HighWord.Bits.Pres);
PRINT("%x ", descr->HighWord.Bits.Reserved_0);
PRINT(" %x\n", descr->HighWord.Bits.Sys);
}
static void
print_descriptors(DESCRIPTOR_TABLE_ENTRY entries[6])
{
int i;
PRINT("\n\tSel Base Limit Type Dpl D/B Gran Pres L Sys\n"
"\t---- -------- -------- -------------------- --- --- ---- ---- - ---\n");
for (i = 0; i < 6; i++) {
/* Intel SDM vol. 3A, section 3.4.2: the first entry in the LDT
* is null and not used by the processor */
if (entries[i].Selector != 0) {
print_descriptor(&entries[i]);
}
}
PRINT("\n");
}
static void
insert_entry(uint selector, uint word1, uint word2, DESCRIPTOR_TABLE_ENTRY entries[6])
{
int i;
/* Intel SDM vol. 3A, section 3.4.2: the first entry in the LDT
* is null and not used by the processor */
if (selector != 0) {
for (i = 0; i < 6; i++) {
/* don't insert duplicates */
if (entries[i].Selector == selector)
return;
/* 0 indicates empty slot in entries[] */
if (entries[i].Selector == 0) {
entries[i].Selector = selector;
*((PULONG)&entries[i].Descriptor) = word1;
*((PULONG)&entries[i].Descriptor.HighWord) = word2;
return;
}
}
}
}
/* macro to read descriptor info and insert it in a table */
#define read_descriptor_and_insert(name, reg) \
{ \
fgets(line, sizeof(line), file); \
res = sscanf(line, name "=0x%04x (0x%08x 0x%08x)\n", &reg, &word1, &word2); \
assert(res == 3 || res == 1); \
if (res == 3) \
insert_entry(reg, word1, word2, entries); \
}
static void
read_threads(FILE *file, bool create, HANDLE hProc)
{
HANDLE hThread;
DWORD thread_id, new_id, res;
THREAD_BASIC_INFORMATION thread_info;
CLIENT_ID cid;
OBJECT_ATTRIBUTES oa;
USER_STACK stack = { 0 };
CONTEXT cxt;
GET_NTDLL(NtCreateThread,
(DR_PARAM_OUT PHANDLE ThreadHandle, DR_PARAM_IN ACCESS_MASK DesiredAccess,
DR_PARAM_IN POBJECT_ATTRIBUTES ObjectAttributes,
DR_PARAM_IN HANDLE ProcessHandle, DR_PARAM_OUT PCLIENT_ID ClientId,
DR_PARAM_IN PCONTEXT ThreadContext, DR_PARAM_IN PUSER_STACK UserStack,
DR_PARAM_IN BOOLEAN CreateSuspended));
if (create) {
cxt.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
/* initialize, unsafe but should be ok right? */
GetThreadContext(GetCurrentThread(), &cxt);
InitializeObjectAttributes(&oa, NULL, OBJ_CASE_INSENSITIVE, NULL, NULL);
}
while (fscanf(file, "Thread=0x%08x\n", &thread_id)) {
bool valid_state = true, valid_handle_state = false;
uint handle_rights;
char *teb;
bool valid_selectors = false;
uint Cs, Ss, Ds, Es, Fs, Gs;
DESCRIPTOR_TABLE_ENTRY entries[6] = {
0,
};
uint word1, word2;
char line[64];
long pos;
void *win32_start_addr = 0;
res = fscanf(file, "TEB=%p\n", &teb);
assert(res == 1);
res = fscanf(file, "HandleRights=0x%08x\n", &handle_rights);
if (res == 1) {
valid_handle_state = true;
}
res = fscanf(file, "Eax=0x%08x, Ebx=0x%08x, Ecx=0x%08x, Edx=0x%08x\n", &cxt.Eax,
&cxt.Ebx, &cxt.Ecx, &cxt.Edx);
if (res == 4) {
res = fscanf(file, "Esi=0x%08x, Edi=0x%08x, Esp=0x%08x, Ebp=0x%08x\n",
&cxt.Esi, &cxt.Edi, &cxt.Esp, &cxt.Ebp);
assert(res == 4);
res = fscanf(file, "EFlags=0x%08x, Eip=0x%08x\n\n", &cxt.EFlags, &cxt.Eip);
assert(res == 2);
/* look for segment registers and associated descriptors */
pos = ftell(file);
fgets(line, sizeof(line), file);
res = sscanf(line, "Cs=0x%04x (0x%08x 0x%08x)\n", &Cs, &word1, &word2);
if (res == 3 || res == 1) {
valid_selectors = true;
if (res == 3)
insert_entry(Cs, word1, word2, entries);
read_descriptor_and_insert("Ss", Ss);
read_descriptor_and_insert("Ds", Ds);
read_descriptor_and_insert("Es", Es);
read_descriptor_and_insert("Fs", Fs);
read_descriptor_and_insert("Gs", Gs);
} else {
/* put file position back a line */
fseek(file, pos, SEEK_SET);
}
/* look for the win32 start address */
pos = ftell(file);
res = fscanf(file, "Win32StartAddr=%p\n", &win32_start_addr);
if (res != 1) {
/* set start addr to 0 so we know right away if the thread
* doesn't have a start address in the ldmp.
*/
win32_start_addr = NULL;
/* put file position back a line */
fseek(file, pos, SEEK_SET);
}
} else {
valid_state = false;
fgets(buf, sizeof(buf), file);
/* only show error when creating to avoid duplicate messages */
if (create) {
WARN("\nError reading thread state for original thread tid=0x%04x\n",
thread_id);
WARN("%s", buf);
}
fgets(buf, sizeof(buf), file); /* read in the newline */
/* clear context integer registers */
cxt.Eax = 0;
cxt.Ebx = 0;
cxt.Ecx = 0;
cxt.Edx = 0;
cxt.EFlags = 0;
cxt.Edi = 0;
cxt.Esi = 0;
cxt.Esp = 0;
cxt.Ebp = 0;
cxt.Eip = 0;
}
if (create) {
res = NtCreateThread(&hThread, THREAD_ALL_ACCESS, &oa, hProc, &cid, &cxt,
&stack, TRUE);
assert(NT_SUCCESS(res));
res = set_win32_start_addr(hThread, &win32_start_addr);
if (!res)
WARN("unable to set thread start address to %p\n", win32_start_addr);
res = query_thread_info(hThread, &thread_info);
assert(res);
new_id = (uint)thread_info.ClientId.UniqueThread;
INFO(1,
"created thread tid=0x%04x with TEB=%p original tid=0x%04x with "
"TEB=%p\n",
new_id, thread_info.TebBaseAddress, thread_id, teb);
if (valid_selectors) {
INFO(1, "\tcs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x\n", Cs, Ss, Ds,
Es, Fs, Gs);
}
if (entries[0].Selector != 0) {
print_descriptors(entries);
}
if (valid_handle_state) {
INFO(1, "\tHandleRights=0x%08x\n", handle_rights);
}
if (teb == NULL) {
WARN("\twill be unable to copy over TEB\n");
} else {
add_mapped_addr(teb, (char *)thread_info.TebBaseAddress);
}
if (!valid_state)
WARN("\tnew thread's register state is invalid\n\n");
CloseHandle(hThread);
} else {
/* prevent this region from being copied over till we know
* where to put it */
add_pending_mapped_addr(teb);
}
}
}
static HANDLE
create_process(char *path)
{
HANDLE hProc, hSection, hFile;
UNICODE_STRING uexe;
OBJECT_ATTRIBUTES oa;
IO_STATUS_BLOCK iosb;
NTSTATUS res;
wchar_t abs_path[MAX_PATH];
wchar_t wpath[MAX_PATH];
GET_NTDLL(
NtCreateProcess,
(DR_PARAM_OUT PHANDLE ProcessHandle, DR_PARAM_IN ACCESS_MASK DesiredAccess,
DR_PARAM_IN POBJECT_ATTRIBUTES ObjectAttributes,
DR_PARAM_IN HANDLE InheritFromProcessHandle, DR_PARAM_IN BOOLEAN InheritHandles,
DR_PARAM_IN HANDLE SectionHandle OPTIONAL, DR_PARAM_IN HANDLE DebugPort OPTIONAL,
DR_PARAM_IN HANDLE ExceptionPort OPTIONAL));
GET_NTDLL(NtOpenFile,
(DR_PARAM_OUT PHANDLE FileHandle, DR_PARAM_IN ACCESS_MASK DesiredAccess,
DR_PARAM_IN POBJECT_ATTRIBUTES ObjectAttributes,
DR_PARAM_OUT PIO_STATUS_BLOCK IoStatusBlock, DR_PARAM_IN ULONG ShareAccess,
DR_PARAM_IN ULONG OpenOptions));
GET_NTDLL(NtCreateSection,
(DR_PARAM_OUT PHANDLE SectionHandle, DR_PARAM_IN ACCESS_MASK DesiredAccess,
DR_PARAM_IN POBJECT_ATTRIBUTES ObjectAttributes,
DR_PARAM_IN PLARGE_INTEGER SectionSize OPTIONAL, DR_PARAM_IN ULONG Protect,
DR_PARAM_IN ULONG Attributes, DR_PARAM_IN HANDLE FileHandle));
/* convert to absolute path which is required for create_process() */
_snwprintf(wpath, BUFFER_SIZE_ELEMENTS(wpath), L"%hs", path);
NULL_TERMINATE_BUFFER(wpath);
GetFullPathName(wpath, BUFFER_SIZE_ELEMENTS(abs_path), abs_path, NULL);
NULL_TERMINATE_BUFFER(abs_path);
/* create dummy process */
_snwprintf(wpath, BUFFER_SIZE_ELEMENTS(wpath), L"\\??\\%s", abs_path);
NULL_TERMINATE_BUFFER(wpath);
INFO(2, "dummy exe path = %ls\n", wpath);
wchar_to_unicode(&uexe, wpath);
InitializeObjectAttributes(&oa, &uexe, OBJ_CASE_INSENSITIVE, NULL, NULL);
if (!NT_SUCCESS(NtOpenFile(&hFile, FILE_EXECUTE | SYNCHRONIZE, &oa, &iosb,
FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT))) {
WARN("failed to open dummy process exe file\n");
exit(0);
}
oa.ObjectName = 0;
res = NtCreateSection(&hSection, SECTION_ALL_ACCESS, &oa, 0, PAGE_EXECUTE, SEC_IMAGE,
hFile);
if (!NT_SUCCESS(res)) {
WARN("failed to create section with error=0x%08x\n", res);
}
res = NtCreateProcess(&hProc, PROCESS_ALL_ACCESS, &oa, GetCurrentProcess(), true,
hSection, 0, 0);
if (!NT_SUCCESS(res)) {
WARN("failed to create dummy process with error=0x%08x\n", res);
exit(0);
}
return hProc;
}
static void *highest_address_copied = NULL;
static bool reached_vsyscall_page = false;
/* if just_mapped then only copies over mapped regions, otherwise copies over
* non-mapped regions only */
static void
copy_memory(FILE *file, bool just_mapped, HANDLE hProc)
{
MEMORY_BASIC_INFORMATION mbi;
DWORD res;
fpos_t pos;
bool do_copy;
while (true) {
res = fgetpos(file, &pos);
assert(res == 0);
res = read_mbi(file, &mbi);
if (!res)
break;
if (mbi.State != MEM_FREE) {
char *allocation_base = (char *)mbi.AllocationBase;
uint allocation_protect = mbi.AllocationProtect;
uint allocation_size = 0;
char *target;
fpos_t last_mbi_pos; /* for unknown TEB backing out */
/* we can't handle write copy flag! remove it */
allocation_protect = remove_writecopy(allocation_protect);
INFO(2, "allocation base = %p, protect = 0x%08x\n", allocation_base,
allocation_protect);
do {
allocation_size += mbi.RegionSize;
/* scan past data */
if (prot_is_readable(mbi.Protect)) {
/* FIXME : hack, rc1 core doesn't check for guard page so
* sometimes memory is copied and sometimes it isn't if
* is guard! (why?), post rc1 should check
* MEM_COMMIT && !guard && is_readable */
assert(mbi.State == MEM_COMMIT);
if (!TEST(PAGE_GUARD, mbi.Protect)) {
res = fseek(file, mbi.RegionSize, SEEK_CUR);
assert(res == 0);
} else {
#if RC1_HACK
fpos_t hack_pos;
MEMORY_BASIC_INFORMATION hack_mbi;
res = fgetpos(file, &hack_pos);
assert(res == 0);
if (!read_mbi(file, &hack_mbi)) {
res = fsetpos(file, &hack_pos);
assert(res == 0);
res = fseek(file, mbi.RegionSize, SEEK_CUR);
assert(res == 0);
} else {
res = fsetpos(file, &hack_pos);
assert(res == 0);
}
#endif
}
}
res = fgetpos(file, &last_mbi_pos);
assert(res == 0);
} while (read_mbi(file, &mbi) && mbi.State != MEM_FREE &&
mbi.AllocationBase == allocation_base);
/* see if we should copy over this allocation */
target = get_mapped_addr(allocation_base);
do_copy = (just_mapped && target != FAIL) ||
(!just_mapped && target == FAIL &&
!is_pending_mapped_addr(allocation_base));
if (!do_copy) {
res = fsetpos(file, &last_mbi_pos);
assert(res == 0);
continue;
}
/* get allocation */
if (target == FAIL) {
if (!ALIGNED(allocation_base, ALLOCATION_GRANULARITY)) {
/* probably a TEB for a thread that wasn't in the all
* threads list at time of ldmp */
WARN("Probable TEB for unknown thread region (or x64 PEB/TEB?) "
"addr %p size 0x%08x\n",
allocation_base, allocation_size);
}
target = allocation_base;
/* FIXME : why can't we use VirtualAllocEx? it fails with
* code 487 == INVALID_ADDRESS */
res = nt_remote_allocate_virtual_memory(
hProc, &target, allocation_size, allocation_protect, MEMORY_COMMIT);
if (!res) {
target = NULL;
res = nt_remote_allocate_virtual_memory(
hProc, &target, allocation_size, allocation_protect,
MEMORY_COMMIT);
}
if (!res) {
WARN("ERROR: unable to allocate memory at %p size 0x%08x, "
"SKIPPING\n",
allocation_base, allocation_size);
res = fsetpos(file, &last_mbi_pos);
assert(res == 0);
continue;
}
assert(target != NULL);
if (target != allocation_base) {
WARN("ERROR: unable to allocate memory at %p size 0x%08x\n\t "
"will be copied to %p instead\n",
allocation_base, allocation_size, target);
}
INFO(2, "target = %p, base = %p\n", target, allocation_base);
}
INFO(2, "size=0x%08x\n", allocation_size);
INFO(2, "target=%p\n", target);
/* reset file pointer */
res = fsetpos(file, &pos);
assert(res == 0);
/* walk memory */
#define TARGET_ADDR (target + ((char *)mbi.BaseAddress - allocation_base))
res = fgetpos(file, &pos);
assert(res == 0);
while (read_mbi(file, &mbi) && mbi.State != MEM_FREE &&
mbi.AllocationBase == allocation_base) {
if ((uint)mbi.BaseAddress > (uint)highest_address_copied)
highest_address_copied = mbi.BaseAddress;
if (mbi.State == MEM_RESERVE) {
res = VirtualFreeEx(hProc, TARGET_ADDR, mbi.RegionSize, MEM_DECOMMIT);
if (!res && TARGET_ADDR == (char *)0x7ffe1000) {
WARN("unable to make post vsyscall/shared user data page "
"0x7ffe1000 reserve, skipping\n");
/* in case break out, save position */
res = fgetpos(file, &pos);
assert(res == 0);
continue;
}
assert(res);
} else {
uint old_prot;
assert(mbi.State = MEM_COMMIT);
if (!TEST(PAGE_GUARD, mbi.Protect) && prot_is_readable(mbi.Protect)) {
uint i;
/* copy over memory */
res = VirtualProtectEx(hProc, TARGET_ADDR, mbi.RegionSize,
PAGE_READWRITE, &old_prot);
if (!res && TARGET_ADDR == (char *)0x7ffe0000) {
WARN("unable to copy over vsyscall/shared user data page "
"0x7ffe0000, skipping\n");
reached_vsyscall_page = true;
res = fseek(file, mbi.RegionSize, SEEK_CUR);
assert(res == 0);
/* in case break out, save position */
res = fgetpos(file, &pos);
assert(res == 0);
continue;
}
assert(res);
assert(mbi.RegionSize % sizeof(buf) == 0);
for (i = 0; i < mbi.RegionSize; i += sizeof(buf)) {
uint written;
res = fread(buf, 1, sizeof(buf), file);
assert(res == sizeof(buf));
res = WriteProcessMemory(hProc, TARGET_ADDR + i, buf,
sizeof(buf), &written);
assert(res && written == sizeof(buf));
}
} else {
#if RC1_HACK
/* FIXME : rc1 hack, might have memory from guard page
* somehow */
fpos_t hack_pos;
MEMORY_BASIC_INFORMATION hack_mbi;
res = fgetpos(file, &hack_pos);
assert(res == 0);
if (!read_mbi(file, &hack_mbi)) {
res = fsetpos(file, &hack_pos);
assert(res == 0);
res = fseek(file, mbi.RegionSize, SEEK_CUR);
assert(res == 0);
} else {
res = fsetpos(file, &hack_pos);
assert(res == 0);
}
#endif
}
res = VirtualProtectEx(hProc, TARGET_ADDR, mbi.RegionSize,
remove_writecopy(mbi.Protect), &old_prot);
assert(res);
}
/* in case break out, save position */
res = fgetpos(file, &pos);
assert(res == 0);
}
/* roll back last mbi */
res = fsetpos(file, &pos);
assert(res == 0);
#undef TARGET_ADDR
}
}
}
static void
usage(const char *msg)
{
PRINT("%s\nusage: ldmp [-verbose <N>] <.ldmp file> <dummy executable>\n", msg);
PRINT("example: bin32/ldmp logs/hello.exe.5124.0000000.ldmp bin32/dummy.exe\n");
exit(-1);
}
/* creates a debuggable process that matches (roughly) the process that was
* used to generate the .ldmp file, prints out a mapping of thread_ids from
* the dump to the new process.
* ldmp.exe <.ldmp file> <windows path to dummy.exe (absolute, local drive)> */
DWORD __cdecl main(DWORD argc, char *argv[], char *envp[])
{
FILE *file;
HANDLE hProc;
PROCESS_BASIC_INFORMATION info;
MEMORY_BASIC_INFORMATION mbi;
char *pb;
void *drbase = 0;
DWORD res;
fpos_t thread_start_pos;
DWORD i;
for (i = 1; i < argc; i++) {
if (argv[i][0] == '-') {
if (strcmp(argv[i], "-verbose") == 0) {
if (i >= argc - 1)
usage("-verbose takes an integer");
verbose = atoi(argv[++i]);
} else
usage("unknown option");
} else
break;
}
if (i >= argc - 1)
usage("missing arguments");
INFO(1, "opening ldump file %s\n", argv[i]);
file = fopen(argv[i], "rb");
if (file == NULL) {
WARN("unable to find file %s\n", argv[i]);
exit(-1);
}
hProc = create_process(argv[i + 1]);
if (GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCreateThreadEx")) {
/* XXX i#397: on Vista in the past we saw NtCreateThread fail
* with STATUS_INVALID_IMAGE_FORMAT, but it seems to be
* working now on win7 so we'll stick with it rather than
* cobbling together an approach that uses NtCreateThreadEx.
*/
WARN("ldmp.exe may not work fully on Vista+ (i#397)\n");
}
/* read peb (& message if there is one) */
res = fscanf(file, "PEB=%p\n", &pb);
if (res != 1) {
uint length = 0;
/* newer format, starts with a message prefaced with number of bytes
* in message */
/* case 8912: don't read a newline here as it will gobble the next newline
* if the msg is empty; instead we add 1 to the length and read the newline
* w/ the msg.
*/
res = fscanf(file, "0x%08x", &length);
assert(res == 1);
length = length + 1; /* newline after length, see comment above */
/* FIXME - should never happen, but we could handle by doing this in
* pieces */
assert(length < sizeof(buf));
if (length >= sizeof(buf))
length = sizeof(buf) - 1;
res = fread(buf, 1, length, file);
assert(res == length);
buf[length] = '\0';
/* Remember that buf has a newline as its first char */
INFO(1,
"\n**************************************************\n"
"Message:\n%s\n**************************************************\n",
buf);
res = fscanf(file, "PEB=%p\n", &pb);
}
assert(res == 1);
res = query_process_info(hProc, &info);
assert(res);
INFO(1, "\ncreated dummy process pid=%d with PEB=%p original PEB=%p\n",
info.UniqueProcessId, info.PebBaseAddress, pb);
/* read dynamorio.dll base, present only in format after case 5366 */
if (fscanf(file, "dynamorio.dll=%p\n", &drbase) == 1) {
/* for custom scripting */
INFO(1, "\ndynamorio.dll=%p\n", drbase);
/* or more likely */
INFO(1,
"\nRun this command, or attach non-invasively from an existing windbg:\n"
"windbg -pv -p %d -c '.reload dynamorio.dll=%p'\n\n",
info.UniqueProcessId, drbase);
}
add_mapped_addr(pb, (char *)info.PebBaseAddress);
/* add shared user data (win2k) / syscall page (xp/03) to map list */
add_mapped_addr((char *)0x7ffe0000, (char *)0x7ffe0000);
/* iterate through threads without creating to build up pending
* teb mappings */
res = fgetpos(file, &thread_start_pos);
assert(res == 0);
read_threads(file, false, hProc);
INFO(1, "\n");
/* free memory in dummy process */
pb = NULL;
while (VirtualQueryEx(hProc, pb, &mbi, sizeof(mbi)) == sizeof(mbi)) {
if (mbi.State == MEM_FREE || get_original_addr(mbi.AllocationBase) != FAIL) {
if ((uint)mbi.BaseAddress + mbi.RegionSize < (uint)mbi.BaseAddress)
break;
pb = (char *)mbi.BaseAddress + mbi.RegionSize;
} else {
if (!VirtualFreeEx(hProc, mbi.AllocationBase, 0, MEM_RELEASE)) {
/* try an unmap */
GET_NTDLL(
NtUnmapViewOfSection,
(DR_PARAM_IN HANDLE ProcessHandle, DR_PARAM_IN PVOID BaseAddress));
if (!NT_SUCCESS(NtUnmapViewOfSection(hProc, mbi.AllocationBase))) {
if (get_original_addr(mbi.AllocationBase) == FAIL) {
/* this happens in wow64 w/ the x64 PEB */
WARN("Unable to free memory region (x64 PEB?):\n");
dump_mbi(&mbi);
}
pb += mbi.RegionSize;
} else {
INFO(2, "unmapped allocation at %p\n", mbi.AllocationBase);
}
} else {
INFO(2, "freed memory at %p\n", mbi.AllocationBase);
}
}
}
INFO(1, "finished freeing memory, starting copy over\n");
/* chomp any error line (such as all threads list freed) */
if (fscanf(file, "<%512[a-zA-Z1-9 ]>", &buf))
WARN("%s\n", buf);
/* copy over directly corresponding (non-mapped) memory regions */
copy_memory(file, false, hProc);
/* now create threads (can't do this before since we have no control over
* where the teb's are placed and they might conflict with one of the
* copied over regions above */
res = fsetpos(file, &thread_start_pos);
assert(res == 0);
read_threads(file, true, hProc);
INFO(1, "\n");
/* now copy over mapped allocations */
/* first chomp off any error line, we already printed it once above */
fscanf(file, "<%512[a-zA-Z1-9 ]>", &buf);
copy_memory(file, true, hProc);
if (!reached_vsyscall_page) {
WARN("ERROR: failed to reach shared_user_data/vsyscall page, ldmp likely "
"truncated.\n"
" Memory above %p is likely unavailable or incorrect.\n\n",
highest_address_copied);
}
INFO(1, "finished\n");
CloseHandle(hProc);
return 0;
}