#    This file is part of Metasm, the Ruby assembly manipulation suite
#    Copyright (C) 2006-2009 Yoann GUILLOT
#
#    Licence is LGPL, see LICENCE in the top-level directory

require 'metasm/os/main'
require 'metasm/debug'
require 'metasm/dynldr'

module Metasm
class WinAPI < DynLdr
	def self.api_not_found(lib, func)
		puts "could not find symbol #{func.name.inspect} in #{lib.inspect}" if $VERBOSE and not func.attributes.to_a.include?('optional')
	end

	new_api_c <<EOS, 'kernel32'
#line #{__LINE__}

typedef char CHAR;
typedef unsigned char BYTE;
typedef unsigned short WORD, USHORT;
typedef unsigned int UINT;
typedef long LONG;
typedef unsigned long ULONG, DWORD, *LPDWORD;
typedef int BOOL;
typedef unsigned long long DWORD64, ULONGLONG;

typedef intptr_t INT_PTR, LONG_PTR;
typedef uintptr_t UINT_PTR, ULONG_PTR, DWORD_PTR, SIZE_T;
typedef LONG_PTR LPARAM;
typedef UINT_PTR WPARAM;
typedef LONG_PTR LRESULT;
typedef const CHAR *LPSTR, *LPCSTR;
typedef void VOID, *PVOID, *LPVOID;

typedef void *HANDLE;
typedef void *HMODULE;

#define INVALID_HANDLE_VALUE (HANDLE)-1
#define DECLSPEC_IMPORT __declspec(dllimport)
#define WINUSERAPI DECLSPEC_IMPORT
#define WINBASEAPI DECLSPEC_IMPORT
#define WINAPI __stdcall
#define CALLBACK __stdcall
#define CONST const
#define ZEROOK __attribute__((zero_not_fail))
#define __in __attribute__((in))
#define __out __attribute__((out))
#define __opt __attribute__((opt))
#define __inout __in __out
#define __in_opt __in __opt
#define __out_opt __out __opt
#define __inout_opt __inout __opt

#define FORMAT_MESSAGE_FROM_SYSTEM     0x00001000
#define INFINITE            0xFFFFFFFF

#define PAGE_NOACCESS          0x01
#define PAGE_READONLY          0x02
#define PAGE_READWRITE         0x04
#define PAGE_WRITECOPY         0x08
#define PAGE_EXECUTE           0x10
#define PAGE_EXECUTE_READ      0x20
#define PAGE_EXECUTE_READWRITE 0x40
#define PAGE_EXECUTE_WRITECOPY 0x80
#define PAGE_GUARD            0x100
#define PAGE_NOCACHE          0x200
#define PAGE_WRITECOMBINE     0x400
#define MEM_COMMIT           0x1000
#define MEM_RESERVE          0x2000
#define MEM_DECOMMIT         0x4000
#define MEM_RELEASE          0x8000
#define MEM_FREE            0x10000
#define MEM_PRIVATE         0x20000
#define MEM_MAPPED          0x40000
#define MEM_RESET           0x80000
#define MEM_TOP_DOWN       0x100000
#define MEM_WRITE_WATCH    0x200000
#define MEM_PHYSICAL       0x400000
#define MEM_LARGE_PAGES  0x20000000
#define MEM_4MB_PAGES    0x80000000
#define SEC_FILE           0x800000
#define SEC_IMAGE         0x1000000
#define SEC_RESERVE       0x4000000
#define SEC_COMMIT        0x8000000
#define SEC_NOCACHE      0x10000000
#define SEC_LARGE_PAGES  0x80000000
#define MEM_IMAGE         SEC_IMAGE

#define DEBUG_PROCESS                     0x00000001
#define DEBUG_ONLY_THIS_PROCESS           0x00000002
#define CREATE_SUSPENDED                  0x00000004
#define DETACHED_PROCESS                  0x00000008
#define CREATE_NEW_CONSOLE                0x00000010
#define NORMAL_PRIORITY_CLASS             0x00000020
#define IDLE_PRIORITY_CLASS               0x00000040
#define HIGH_PRIORITY_CLASS               0x00000080
#define REALTIME_PRIORITY_CLASS           0x00000100
#define CREATE_NEW_PROCESS_GROUP          0x00000200
#define CREATE_UNICODE_ENVIRONMENT        0x00000400
#define CREATE_SEPARATE_WOW_VDM           0x00000800
#define CREATE_SHARED_WOW_VDM             0x00001000
#define CREATE_FORCEDOS                   0x00002000
#define BELOW_NORMAL_PRIORITY_CLASS       0x00004000
#define ABOVE_NORMAL_PRIORITY_CLASS       0x00008000
#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000
#define CREATE_BREAKAWAY_FROM_JOB         0x01000000
#define CREATE_PRESERVE_CODE_AUTHZ_LEVEL  0x02000000
#define CREATE_DEFAULT_ERROR_MODE         0x04000000
#define CREATE_NO_WINDOW                  0x08000000
#define PROFILE_USER                      0x10000000
#define PROFILE_KERNEL                    0x20000000
#define PROFILE_SERVER                    0x40000000
#define CREATE_IGNORE_SYSTEM_DEFAULT      0x80000000

#define STATUS_WAIT_0                    ((DWORD   )0x00000000L)
#define STATUS_ABANDONED_WAIT_0          ((DWORD   )0x00000080L)
#define STATUS_USER_APC                  ((DWORD   )0x000000C0L)
#define STATUS_TIMEOUT                   ((DWORD   )0x00000102L)
#define STATUS_PENDING                   ((DWORD   )0x00000103L)
#define DBG_EXCEPTION_HANDLED            ((DWORD   )0x00010001L)
#define DBG_CONTINUE                     ((DWORD   )0x00010002L)
#define STATUS_SEGMENT_NOTIFICATION      ((DWORD   )0x40000005L)
#define DBG_TERMINATE_THREAD             ((DWORD   )0x40010003L)
#define DBG_TERMINATE_PROCESS            ((DWORD   )0x40010004L)
#define DBG_CONTROL_C                    ((DWORD   )0x40010005L)
#define DBG_CONTROL_BREAK                ((DWORD   )0x40010008L)
#define DBG_COMMAND_EXCEPTION            ((DWORD   )0x40010009L)
#define STATUS_WX86_CONTINUE             ((DWORD   )0x4000001DL)
#define STATUS_WX86_SINGLE_STEP          ((DWORD   )0x4000001EL)
#define STATUS_WX86_BREAKPOINT           ((DWORD   )0x4000001FL)
#define STATUS_WX86_EXCEPTION_CONTINUE   ((DWORD   )0x40000020L)
#define STATUS_WX86_EXCEPTION_LASTCHANCE ((DWORD   )0x40000021L)
#define STATUS_WX86_EXCEPTION_CHAIN      ((DWORD   )0x40000022L)
#define STATUS_GUARD_PAGE_VIOLATION      ((DWORD   )0x80000001L)
#define STATUS_DATATYPE_MISALIGNMENT     ((DWORD   )0x80000002L)
#define STATUS_BREAKPOINT                ((DWORD   )0x80000003L)
#define STATUS_SINGLE_STEP               ((DWORD   )0x80000004L)
#define DBG_EXCEPTION_NOT_HANDLED        ((DWORD   )0x80010001L)
#define STATUS_ACCESS_VIOLATION          ((DWORD   )0xC0000005L)
#define STATUS_IN_PAGE_ERROR             ((DWORD   )0xC0000006L)
#define STATUS_INVALID_HANDLE            ((DWORD   )0xC0000008L)
#define STATUS_NO_MEMORY                 ((DWORD   )0xC0000017L)
#define STATUS_ILLEGAL_INSTRUCTION       ((DWORD   )0xC000001DL)
#define STATUS_NONCONTINUABLE_EXCEPTION  ((DWORD   )0xC0000025L)
#define STATUS_INVALID_DISPOSITION       ((DWORD   )0xC0000026L)
#define STATUS_ARRAY_BOUNDS_EXCEEDED     ((DWORD   )0xC000008CL)
#define STATUS_FLOAT_DENORMAL_OPERAND    ((DWORD   )0xC000008DL)
#define STATUS_FLOAT_DIVIDE_BY_ZERO      ((DWORD   )0xC000008EL)
#define STATUS_FLOAT_INEXACT_RESULT      ((DWORD   )0xC000008FL)
#define STATUS_FLOAT_INVALID_OPERATION   ((DWORD   )0xC0000090L)
#define STATUS_FLOAT_OVERFLOW            ((DWORD   )0xC0000091L)
#define STATUS_FLOAT_STACK_CHECK         ((DWORD   )0xC0000092L)
#define STATUS_FLOAT_UNDERFLOW           ((DWORD   )0xC0000093L)
#define STATUS_INTEGER_DIVIDE_BY_ZERO    ((DWORD   )0xC0000094L)
#define STATUS_INTEGER_OVERFLOW          ((DWORD   )0xC0000095L)
#define STATUS_PRIVILEGED_INSTRUCTION    ((DWORD   )0xC0000096L)
#define STATUS_STACK_OVERFLOW            ((DWORD   )0xC00000FDL)
#define STATUS_CONTROL_C_EXIT            ((DWORD   )0xC000013AL)
#define STATUS_FLOAT_MULTIPLE_FAULTS     ((DWORD   )0xC00002B4L)
#define STATUS_FLOAT_MULTIPLE_TRAPS      ((DWORD   )0xC00002B5L)
#define STATUS_REG_NAT_CONSUMPTION       ((DWORD   )0xC00002C9L)

#define EXCEPTION_DEBUG_EVENT       1
#define CREATE_THREAD_DEBUG_EVENT   2
#define CREATE_PROCESS_DEBUG_EVENT  3
#define EXIT_THREAD_DEBUG_EVENT     4
#define EXIT_PROCESS_DEBUG_EVENT    5
#define LOAD_DLL_DEBUG_EVENT        6
#define UNLOAD_DLL_DEBUG_EVENT      7
#define OUTPUT_DEBUG_STRING_EVENT   8
#define RIP_EVENT                   9

#define MAX_PATH 260

#define EXCEPTION_NONCONTINUABLE 0x1    // Noncontinuable exception
#define EXCEPTION_MAXIMUM_PARAMETERS 15 // maximum number of exception parameters

typedef struct _EXCEPTION_RECORD {
	DWORD ExceptionCode;
	DWORD ExceptionFlags;		// noncontinuable
	struct _EXCEPTION_RECORD *ExceptionRecord;
	PVOID ExceptionAddress;
	DWORD NumberParameters;
	ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;

typedef struct _EXCEPTION_RECORD32 {
	DWORD ExceptionCode;
	DWORD ExceptionFlags;
	DWORD ExceptionRecord;
	DWORD ExceptionAddress;
	DWORD NumberParameters;
	DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD32, *PEXCEPTION_RECORD32;

typedef struct _EXCEPTION_RECORD64 {
	DWORD ExceptionCode;
	DWORD ExceptionFlags;
	DWORD64 ExceptionRecord;
	DWORD64 ExceptionAddress;
	DWORD NumberParameters;
	DWORD __unusedAlignment;
	DWORD64 ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD64, *PEXCEPTION_RECORD64;

typedef struct _EXCEPTION_DEBUG_INFO {
	EXCEPTION_RECORD ExceptionRecord;
	DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;

typedef struct _EXCEPTION_DEBUG_INFO32 {
	EXCEPTION_RECORD32 ExceptionRecord;
	DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO32;

typedef struct _EXCEPTION_DEBUG_INFO64 {
	EXCEPTION_RECORD64 ExceptionRecord;
	DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO64;

typedef struct _CREATE_THREAD_DEBUG_INFO {
	HANDLE hThread;
	LPVOID lpThreadLocalBase;
	LPVOID lpStartAddress;
} CREATE_THREAD_DEBUG_INFO, *LPCREATE_THREAD_DEBUG_INFO;

typedef struct _CREATE_PROCESS_DEBUG_INFO {
	HANDLE hFile;
	HANDLE hProcess;
	HANDLE hThread;
	LPVOID lpBaseOfImage;
	DWORD  dwDebugInfoFileOffset;
	DWORD  nDebugInfoSize;
	LPVOID lpThreadLocalBase;
	LPVOID lpStartAddress;
	LPVOID lpImageName;
	WORD fUnicode;
} CREATE_PROCESS_DEBUG_INFO, *LPCREATE_PROCESS_DEBUG_INFO;

typedef struct _EXIT_THREAD_DEBUG_INFO {
	DWORD dwExitCode;
} EXIT_THREAD_DEBUG_INFO, *LPEXIT_THREAD_DEBUG_INFO;

typedef struct _EXIT_PROCESS_DEBUG_INFO {
	DWORD dwExitCode;
} EXIT_PROCESS_DEBUG_INFO, *LPEXIT_PROCESS_DEBUG_INFO;

typedef struct _LOAD_DLL_DEBUG_INFO {
	HANDLE hFile;
	LPVOID lpBaseOfDll;
	DWORD dwDebugInfoFileOffset;
	DWORD nDebugInfoSize;
	LPVOID lpImageName;
	WORD fUnicode;
} LOAD_DLL_DEBUG_INFO, *LPLOAD_DLL_DEBUG_INFO;

typedef struct _UNLOAD_DLL_DEBUG_INFO {
	LPVOID lpBaseOfDll;
} UNLOAD_DLL_DEBUG_INFO, *LPUNLOAD_DLL_DEBUG_INFO;

typedef struct _OUTPUT_DEBUG_STRING_INFO {
	LPSTR lpDebugStringData;
	WORD fUnicode;
	WORD nDebugStringLength;
} OUTPUT_DEBUG_STRING_INFO, *LPOUTPUT_DEBUG_STRING_INFO;

typedef struct _RIP_INFO {
	DWORD dwError;
	DWORD dwType;
} RIP_INFO, *LPRIP_INFO;

typedef struct _DEBUG_EVENT {
	DWORD dwDebugEventCode;
	DWORD dwProcessId;
	DWORD dwThreadId;
	// DWORD pad64; (implicit)
	union {
		EXCEPTION_DEBUG_INFO Exception;
		EXCEPTION_DEBUG_INFO32 Exception32;
		CREATE_THREAD_DEBUG_INFO CreateThread;
		CREATE_PROCESS_DEBUG_INFO CreateProcess;
		EXIT_THREAD_DEBUG_INFO ExitThread;
		EXIT_PROCESS_DEBUG_INFO ExitProcess;
		LOAD_DLL_DEBUG_INFO LoadDll;
		UNLOAD_DLL_DEBUG_INFO UnloadDll;
		OUTPUT_DEBUG_STRING_INFO DebugString;
		RIP_INFO RipInfo;
	} u;
} DEBUG_EVENT, *LPDEBUG_EVENT;

// XXX conflict with structure name..
#define CONTEXT_I386    0x00010000
#define CONTEXT_AMD64   0x00100000

#define CONTEXT_CONTROL             0x00000001L // SS:SP, CS:IP, FLAGS, BP
#define CONTEXT_INTEGER             0x00000002L // AX, BX, CX, DX, SI, DI
#define CONTEXT_SEGMENTS            0x00000004L // DS, ES, FS, GS
#define CONTEXT_FLOATING_POINT      0x00000008L // 387 state
#define CONTEXT_DEBUG_REGISTERS     0x00000010L // DB 0-3,6,7
#define CONTEXT_EXTENDED_REGISTERS  0x00000020L // cpu specific extensions
#define CONTEXT_FULL (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS)
#define CONTEXT_ALL (CONTEXT_FULL | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS)

#define CONTEXT_I386_FULL CONTEXT_I386 | CONTEXT_FULL
#define CONTEXT_I386_ALL  CONTEXT_I386 | CONTEXT_ALL
#define CONTEXT_AMD64_FULL CONTEXT_AMD64 | CONTEXT_FULL
#define CONTEXT_AMD64_ALL  CONTEXT_AMD64 | CONTEXT_ALL

#define MAXIMUM_SUPPORTED_EXTENSION     512
#define SIZE_OF_80387_REGISTERS      80

typedef struct _FPREG { BYTE b[10]; } FPREG;
typedef struct _XMMREG { ULONGLONG lo, hi; } XMMREG;

typedef struct _FLOATING_SAVE_AREA {
	WORD    ControlWord;
	WORD    res0;
	WORD    StatusWord;
	WORD    res1;
	WORD    TagWord;
	WORD    res2;
	DWORD   ErrorOffset;
	WORD    ErrorSelector;
	WORD    ErrorOpcode;
	DWORD   DataOffset;
	WORD    DataSelector;
	WORD    res3;
	FPREG   St[8];
	DWORD   Cr0NpxState;
} FLOATING_SAVE_AREA, *PFLOATING_SAVE_AREA;

typedef struct _CONTEXT_I386 {
	DWORD ContextFlags;
	DWORD   Dr0;
	DWORD   Dr1;
	DWORD   Dr2;
	DWORD   Dr3;
	DWORD   Dr6;
	DWORD   Dr7;
	FLOATING_SAVE_AREA FloatSave;
	DWORD   SegGs;
	DWORD   SegFs;
	DWORD   SegEs;
	DWORD   SegDs;
	DWORD   Edi;
	DWORD   Esi;
	DWORD   Ebx;
	DWORD   Edx;
	DWORD   Ecx;
	DWORD   Eax;
	DWORD   Ebp;
	DWORD   Eip;
	DWORD   SegCs;
	DWORD   EFlags;
	DWORD   Esp;
	DWORD   SegSs;

	XMMREG  Xmm[8];

	BYTE   ExtendedRegisters[24*16];
} *LPCONTEXT_I386, *LPCONTEXT;

typedef struct _CONTEXT_AMD64 {
	DWORD64 P1Home;	 // Register parameter home addresses.
	DWORD64 P2Home;
	DWORD64 P3Home;
	DWORD64 P4Home;
	DWORD64 P5Home;
	DWORD64 P6Home;

	DWORD ContextFlags;
	DWORD MxCsr;

	WORD   SegCs;
	WORD   SegDs;
	WORD   SegEs;
	WORD   SegFs;
	WORD   SegGs;
	WORD   SegSs;
	DWORD RFlags;

	DWORD64 Dr0;
	DWORD64 Dr1;
	DWORD64 Dr2;
	DWORD64 Dr3;
	DWORD64 Dr6;
	DWORD64 Dr7;

	DWORD64 Rax;
	DWORD64 Rcx;
	DWORD64 Rdx;
	DWORD64 Rbx;
	DWORD64 Rsp;
	DWORD64 Rbp;
	DWORD64 Rsi;
	DWORD64 Rdi;
	DWORD64 R8;
	DWORD64 R9;
	DWORD64 R10;
	DWORD64 R11;
	DWORD64 R12;
	DWORD64 R13;
	DWORD64 R14;
	DWORD64 R15;
	DWORD64 Rip;

	WORD ControlWord;
	WORD StatusWord;
	BYTE TagWord;
	BYTE resv1;
	WORD ErrorOpcode;
	DWORD ErrorOffset;
	WORD ErrorSelector;
	WORD resv2;
	DWORD DataAtOffset;
	WORD DataSelector;
	WORD resv3;
	DWORD MxCsr_f;
	DWORD MxCsrMask;
	XMMREG ST[8];
	XMMREG Xmm[16];

	XMMREG Vector[26];
	DWORD64 VectorControl;

	DWORD64 DebugControl;
	DWORD64 LastBranchToRip;
	DWORD64 LastBranchFromRip;
	DWORD64 LastExceptionToRip;
	DWORD64 LastExceptionFromRip;
} *LPCONTEXT_AMD64;

typedef struct _EXCEPTION_POINTERS {
	PEXCEPTION_RECORD ExceptionRecord;
	LPCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

#define STANDARD_RIGHTS_REQUIRED         (0x000F0000L)
#define SYNCHRONIZE                      (0x00100000L)

#define PROCESS_TERMINATE         (0x0001)
#define PROCESS_CREATE_THREAD     (0x0002)
#define PROCESS_SET_SESSIONID     (0x0004)
#define PROCESS_VM_OPERATION      (0x0008)
#define PROCESS_VM_READ           (0x0010)
#define PROCESS_VM_WRITE          (0x0020)
#define PROCESS_DUP_HANDLE        (0x0040)
#define PROCESS_CREATE_PROCESS    (0x0080)
#define PROCESS_SET_QUOTA         (0x0100)
#define PROCESS_SET_INFORMATION   (0x0200)
#define PROCESS_QUERY_INFORMATION (0x0400)
#define PROCESS_SUSPEND_RESUME    (0x0800)
#define PROCESS_QUERY_LIMITED_INFORMATION (0x1000)
#define PROCESS_ALL_ACCESS        (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF)

#define THREAD_TERMINATE               (0x0001)
#define THREAD_SUSPEND_RESUME          (0x0002)
#define THREAD_GET_CONTEXT             (0x0008)
#define THREAD_SET_CONTEXT             (0x0010)
#define THREAD_SET_INFORMATION         (0x0020)
#define THREAD_QUERY_INFORMATION       (0x0040)
#define THREAD_SET_THREAD_TOKEN        (0x0080)
#define THREAD_IMPERSONATE             (0x0100)
#define THREAD_DIRECT_IMPERSONATION    (0x0200)
#define THREAD_SET_LIMITED_INFORMATION (0x0400)
#define THREAD_QUERY_LIMITED_INFORMATION (0x0800)
#define THREAD_ALL_ACCESS         (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x3FF)


typedef struct _STARTUPINFOA {
	DWORD   cb;
	LPSTR   lpReserved;
	LPSTR   lpDesktop;
	LPSTR   lpTitle;
	DWORD   dwX;
	DWORD   dwY;
	DWORD   dwXSize;
	DWORD   dwYSize;
	DWORD   dwXCountChars;
	DWORD   dwYCountChars;
	DWORD   dwFillAttribute;
	DWORD   dwFlags;
	WORD    wShowWindow;
	WORD    cbReserved2;
	LPVOID  lpReserved2;
	HANDLE  hStdInput;
	HANDLE  hStdOutput;
	HANDLE  hStdError;
} STARTUPINFOA, *LPSTARTUPINFOA;

typedef struct _PROCESS_INFORMATION {
	HANDLE hProcess;
	HANDLE hThread;
	DWORD dwProcessId;
	DWORD dwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;


WINAPI
DWORD
GetVersion(VOID);

WINBASEAPI
HANDLE
WINAPI
OpenProcess(
	__in DWORD dwDesiredAccess,
	__in BOOL bInheritHandle,
	__in DWORD dwProcessId
);

WINBASEAPI
HANDLE
WINAPI
GetCurrentProcess(VOID);

WINBASEAPI
DWORD
WINAPI
GetCurrentProcessId(VOID);

WINBASEAPI
BOOL
WINAPI
TerminateProcess(
	__in HANDLE hProcess,
	__in UINT uExitCode);

WINBASEAPI
BOOL
WINAPI
GetExitCodeProcess(
	__in  HANDLE hProcess,
	__out LPDWORD lpExitCode);

WINBASEAPI
HANDLE
WINAPI
CreateRemoteThread(
	__in      HANDLE hProcess,
	__in_opt  LPVOID lpThreadAttributes,
	__in      DWORD dwStackSize,
	__in      LPVOID lpStartAddress,
	__in_opt  LPVOID lpParameter,
	__in      DWORD dwCreationFlags,
	__out_opt LPDWORD lpThreadId);

/* Vista onwards only..
WINBASEAPI
DWORD
WINAPI
GetThreadId(
	__in HANDLE Thread);
*/

WINBASEAPI
DWORD
WINAPI
GetProcessId(
	__in HANDLE Process);

WINBASEAPI
HANDLE
WINAPI
OpenThread(
	__in DWORD dwDesiredAccess,
	__in BOOL bInheritHandle,
	__in DWORD dwThreadId);

WINBASEAPI
BOOL
WINAPI
TerminateThread(
	__in HANDLE hThread,
	__in DWORD dwExitCode);

WINBASEAPI
BOOL
WINAPI
GetExitCodeThread(
	__in  HANDLE hThread,
	__out LPDWORD lpExitCode);

ZEROOK
WINBASEAPI
DWORD
WINAPI
GetLastError(VOID);

ZEROOK
WINBASEAPI
BOOL
WINAPI
ReadProcessMemory(
	__in      HANDLE hProcess,
	__in      LPVOID lpBaseAddress,
	__out     LPVOID lpBuffer,
	__in      DWORD nSize,
	__out_opt DWORD *lpNumberOfBytesRead);

WINBASEAPI
BOOL
WINAPI
WriteProcessMemory(
	__in      HANDLE hProcess,
	__in      LPVOID lpBaseAddress,
	__in      LPVOID lpBuffer,
	__in      DWORD nSize,
	__out_opt DWORD *lpNumberOfBytesWritten);

WINBASEAPI
BOOL
WINAPI
GetThreadContext(
	__in    HANDLE hThread,
	__inout LPCONTEXT lpContext);

WINBASEAPI
BOOL
WINAPI
SetThreadContext(
	__in HANDLE hThread,
	__in LPCONTEXT lpContext);

WINBASEAPI
BOOL
WINAPI
__attribute__((optional))
Wow64GetThreadContext(
	__in    HANDLE hThread,
	__inout LPCONTEXT_I386 lpContext);

WINBASEAPI
BOOL
WINAPI
__attribute__((optional))
Wow64SetThreadContext(
	__in    HANDLE hThread,
	__inout LPCONTEXT_I386 lpContext);

ZEROOK
WINBASEAPI
DWORD
WINAPI
SuspendThread(
	__in HANDLE hThread);

ZEROOK
WINBASEAPI
DWORD
WINAPI
__attribute__((optional))
Wow64SuspendThread(
	__in HANDLE hThread);

ZEROOK
WINBASEAPI
DWORD
WINAPI
ResumeThread(
	__in HANDLE hThread);

WINBASEAPI
VOID
WINAPI
DebugBreak(VOID);

ZEROOK
WINBASEAPI
BOOL
WINAPI
WaitForDebugEvent(
	__in LPDEBUG_EVENT lpDebugEvent,
	__in DWORD dwMilliseconds);

WINBASEAPI
BOOL
WINAPI
ContinueDebugEvent(
	__in DWORD dwProcessId,
	__in DWORD dwThreadId,
	__in DWORD dwContinueStatus);

WINBASEAPI
BOOL
WINAPI
DebugActiveProcess(
	__in DWORD dwProcessId);

WINBASEAPI
BOOL
WINAPI
DebugActiveProcessStop(
	__in DWORD dwProcessId);

WINBASEAPI
BOOL
WINAPI
DebugSetProcessKillOnExit(
	__in BOOL KillOnExit);

WINBASEAPI
BOOL
WINAPI
DebugBreakProcess (
	__in HANDLE Process);

ZEROOK
WINBASEAPI
DWORD
WINAPI
FormatMessageA(
	DWORD dwFlags,
	LPVOID lpSource,
	DWORD dwMessageId,
	DWORD dwLanguageId,
	LPSTR lpBuffer,
	DWORD nSize,
	LPVOID Arguments);

WINBASEAPI
BOOL
WINAPI
CreateProcessA(
	__in_opt    LPCSTR lpApplicationName,
	__inout_opt LPSTR lpCommandLine,
	__in_opt    LPVOID lpProcessAttributes,
	__in_opt    LPVOID lpThreadAttributes,
	__in        BOOL bInheritHandles,
	__in        DWORD dwCreationFlags,
	__in_opt    LPVOID lpEnvironment,
	__in_opt    LPCSTR lpCurrentDirectory,
	__in        LPSTARTUPINFOA lpStartupInfo,
	__out       LPPROCESS_INFORMATION lpProcessInformation);

WINBASEAPI
BOOL
WINAPI
CloseHandle(
	__in HANDLE hObject);

WINBASEAPI
LPVOID
WINAPI
VirtualAllocEx(
	__in     HANDLE hProcess,
	__in_opt LPVOID lpAddress,
	__in     SIZE_T dwSize,
	__in     DWORD flAllocationType,
	__in     DWORD flProtect);

WINBASEAPI
BOOL
WINAPI
VirtualFreeEx(
	__in HANDLE hProcess,
	__in LPVOID lpAddress,
	__in SIZE_T dwSize,
	__in DWORD  dwFreeType);

WINBASEAPI
BOOL
WINAPI
VirtualProtectEx(
	__in  HANDLE hProcess,
	__in  LPVOID lpAddress,
	__in  SIZE_T dwSize,
	__in  DWORD flNewProtect,
	__out LPDWORD lpflOldProtect);

#define TH32CS_SNAPHEAPLIST 0x00000001
#define TH32CS_SNAPPROCESS  0x00000002
#define TH32CS_SNAPTHREAD   0x00000004
#define TH32CS_SNAPMODULE   0x00000008
#define TH32CS_SNAPMODULE32 0x00000010
#define TH32CS_INHERIT      0x80000000

HANDLE
WINAPI
CreateToolhelp32Snapshot(
	DWORD dwFlags,
	DWORD th32ProcessID
);

typedef struct tagPROCESSENTRY32
{
	DWORD   dwSize;
	DWORD   cntUsage;
	DWORD   th32ProcessID;
	ULONG_PTR th32DefaultHeapID;
	DWORD   th32ModuleID;
	DWORD   cntThreads;
	DWORD   th32ParentProcessID;
	LONG    pcPriClassBase;
	DWORD   dwFlags;
	CHAR    szExeFile[MAX_PATH];
} PROCESSENTRY32;

BOOL
WINAPI
Process32First(
	HANDLE hSnapshot,
	PROCESSENTRY32 *lppe
);
BOOL
WINAPI
ZEROOK
Process32Next(
    HANDLE hSnapshot,
    PROCESSENTRY32 *lppe
);


typedef struct tagTHREADENTRY32
{
	DWORD   dwSize;
	DWORD   cntUsage;
	DWORD   th32ThreadID;       // this thread
	DWORD   th32OwnerProcessID; // Process this thread is associated with
	LONG    tpBasePri;
	LONG    tpDeltaPri;
	DWORD   dwFlags;
} THREADENTRY32, * LPTHREADENTRY32;

BOOL
WINAPI
Thread32First(
	HANDLE hSnapshot,
	LPTHREADENTRY32 lpte
);
BOOL
WINAPI
ZEROOK
Thread32Next(
	HANDLE hSnapshot,
	LPTHREADENTRY32 lpte
);


typedef struct tagHEAPLIST32
{
	SIZE_T dwSize;
	DWORD  th32ProcessID;   // owning process
	ULONG_PTR  th32HeapID;      // heap (in owning process context!)
	DWORD  dwFlags;
} HEAPLIST32, LPHEAPLIST32;

#define HF32_DEFAULT      1  // process default heap
#define HF32_SHARED       2  // is shared heap

BOOL
WINAPI
Heap32ListFirst(
     HANDLE hSnapshot,
     LPHEAPLIST32 lphl
);
BOOL
WINAPI
ZEROOK
Heap32ListNext(
     HANDLE hSnapshot,
     LPHEAPLIST32 lphl
);


typedef struct tagMODULEENTRY32
{
	DWORD   dwSize;
	DWORD   th32ModuleID;
	DWORD   th32ProcessID;
	DWORD   GlblcntUsage;
	DWORD   ProccntUsage;
	ULONG_PTR modBaseAddr;
	DWORD   modBaseSize;
	HMODULE hModule;
	char    szModule[256];
	char    szExePath[MAX_PATH];
} MODULEENTRY32;

BOOL
WINAPI
Module32First(
	HANDLE hSnapshot,
	MODULEENTRY32 *lpme
);
BOOL
WINAPI
ZEROOK
Module32Next(
	HANDLE hSnapshot,
	MODULEENTRY32 *lpme
);


typedef struct _MEMORY_BASIC_INFORMATION32 {
	DWORD BaseAddress;
	DWORD AllocationBase;
	DWORD AllocationProtect;	// initial (alloc time) prot
	DWORD RegionSize;
	DWORD State;	// MEM_FREE/COMMIT/RESERVE
	DWORD Protect;	// PAGE_EXECUTE_READWRITE etc
	DWORD Type;	// MEM_IMAGE/MAPPED/PRIVATE
} MEMORY_BASIC_INFORMATION32, *PMEMORY_BASIC_INFORMATION32;

typedef struct _MEMORY_BASIC_INFORMATION64 {
	ULONGLONG BaseAddress;
	ULONGLONG AllocationBase;
	DWORD     AllocationProtect;
	DWORD     __alignment1;
	ULONGLONG RegionSize;
	DWORD     State;
	DWORD     Protect;
	DWORD     Type;
	DWORD     __alignment2;
} MEMORY_BASIC_INFORMATION64, *PMEMORY_BASIC_INFORMATION64;

SIZE_T
WINAPI
ZEROOK
VirtualQueryEx(
	HANDLE hProcess,
	LPVOID lpAddress,
	PMEMORY_BASIC_INFORMATION32 lpBuffer,
	SIZE_T dwLength	// sizeof lpBuffer
);

typedef struct _LDT_ENTRY {
	WORD  LimitLow;
	WORD  BaseLow;
	union {
		struct {
		BYTE BaseMid;
		BYTE Flags1;
		BYTE Flags2;
		BYTE BaseHi;
	} Bytes;
	struct {
		DWORD BaseMid  :8;
		DWORD Type  :5;
		DWORD Dpl  :2;
		DWORD Pres  :1;
		DWORD LimitHi  :4;
		DWORD Sys  :1;
		DWORD Reserved_0  :1;
		DWORD Default_Big  :1;
		DWORD Granularity  :1;
		DWORD BaseHi  :8;
	} Bits;
	} HighWord;
} LDT_ENTRY;

BOOL
WINAPI
GetThreadSelectorEntry(
	HANDLE hThread,
	DWORD dwSelector,
	LDT_ENTRY *lpSelectorEntry
);

BOOL
WINAPI
__attribute__((optional))
IsWow64Process(
	HANDLE hProcess,
	BOOL *wow64
);

EOS

	new_api_c <<EOS, 'advapi32'
#line #{__LINE__}

#define SE_PRIVILEGE_ENABLED_BY_DEFAULT (0x00000001L)
#define SE_PRIVILEGE_ENABLED            (0x00000002L)
#define SE_PRIVILEGE_REMOVED            (0X00000004L)
#define SE_PRIVILEGE_USED_FOR_ACCESS    (0x80000000L)

#define TOKEN_ASSIGN_PRIMARY    (0x0001)
#define TOKEN_DUPLICATE         (0x0002)
#define TOKEN_IMPERSONATE       (0x0004)
#define TOKEN_QUERY             (0x0008)
#define TOKEN_QUERY_SOURCE      (0x0010)
#define TOKEN_ADJUST_PRIVILEGES (0x0020)
#define TOKEN_ADJUST_GROUPS     (0x0040)
#define TOKEN_ADJUST_DEFAULT    (0x0080)
#define TOKEN_ADJUST_SESSIONID  (0x0100)

typedef struct _LUID {
	DWORD LowPart;
	LONG HighPart;
} LUID, *PLUID;

typedef struct _LUID_AND_ATTRIBUTES {
	LUID Luid;
	DWORD Attributes;
} LUID_AND_ATTRIBUTES, * PLUID_AND_ATTRIBUTES;

typedef struct _TOKEN_PRIVILEGES {
	DWORD PrivilegeCount;
	LUID_AND_ATTRIBUTES Privileges[1];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;


BOOL
WINAPI
LookupPrivilegeNameA(
	__in_opt LPCSTR lpSystemName,
	__in     PLUID   lpLuid,
	__out    LPSTR lpName,
	__inout  LPDWORD cchName);

BOOL
WINAPI
LookupPrivilegeValueA(
	__in_opt LPCSTR lpSystemName,
	__in     LPCSTR lpName,
	__out    PLUID  lpLuid);

BOOL
WINAPI
AdjustTokenPrivileges (
	__in      HANDLE TokenHandle,
	__in      BOOL DisableAllPrivileges,
	__in_opt  PTOKEN_PRIVILEGES NewState,
	__in      DWORD BufferLength,
	__out     PTOKEN_PRIVILEGES PreviousState,
	__out_opt DWORD *ReturnLength);

BOOL
WINAPI
OpenProcessToken (
	__in  HANDLE ProcessHandle,
	__in  DWORD DesiredAccess,
	__out HANDLE *TokenHandle);


BOOL
WINAPI
OpenThreadToken (
	__in  HANDLE ThreadHandle,
	__in  DWORD DesiredAccess,
	__in  BOOL OpenAsSelf,
	__out HANDLE *TokenHandle);
EOS
	SE_DEBUG_NAME = 'SeDebugPrivilege'

	new_api_c <<EOS, 'ntdll'
#line #{__LINE__}

typedef LONG NTSTATUS;

typedef enum _PROCESSINFOCLASS {
    ProcessBasicInformation,
    ProcessQuotaLimits,
    ProcessIoCounters,
    ProcessVmCounters,
    ProcessTimes,
    ProcessBasePriority,
    ProcessRaisePriority,
    ProcessDebugPort,
    ProcessExceptionPort,
    ProcessAccessToken,
    ProcessLdtInformation,
    ProcessLdtSize,
    ProcessDefaultHardErrorMode,
    ProcessIoPortHandlers,
    ProcessPooledUsageAndLimits,
    ProcessWorkingSetWatch,
    ProcessUserModeIOPL,
    ProcessEnableAlignmentFaultFixup,
    ProcessPriorityClass,
    ProcessWx86Information,
    ProcessHandleCount,
    ProcessAffinityMask,
    ProcessPriorityBoost,
    ProcessDeviceMap,
    ProcessSessionInformation,
    ProcessForegroundInformation,
    ProcessWow64Information,
    ProcessImageFileName,
    ProcessLUIDDeviceMapsEnabled,
    ProcessBreakOnTermination,
    ProcessDebugObjectHandle,
    ProcessDebugFlags,
    ProcessHandleTracing
} PROCESSINFOCLASS;

typedef enum _THREADINFOCLASS {
    ThreadBasicInformation,
    ThreadTimes,
    ThreadPriority,
    ThreadBasePriority,
    ThreadAffinityMask,
    ThreadImpersonationToken,
    ThreadDescriptorTableEntry,
    ThreadEnableAlignmentFaultFixup,
    ThreadEventPair_Reusable,
    ThreadQuerySetWin32StartAddress,
    ThreadZeroTlsCell,
    ThreadPerformanceCount,
    ThreadAmILastThread,
    ThreadIdealProcessor,
    ThreadPriorityBoost,
    ThreadSetTlsArrayAddress,
    ThreadIsIoPending,
    ThreadHideFromDebugger,
    ThreadBreakOnTermination
} THREADINFOCLASS;

typedef enum _MEMORYINFOCLASS {
    MemoryBasicInformation,
    MemoryDunnoLol,
    MemoryMapFileName
} MEMORYINFOCLASS;

typedef struct _CLIENT_ID {
    HANDLE UniqueProcess;
    HANDLE UniqueThread;
} CLIENT_ID;

typedef struct _PROCESS_BASIC_INFORMATION {
    PVOID Reserved1;
    PVOID PebBaseAddress;
    PVOID Reserved2[2];
    ULONG_PTR UniqueProcessId;
    PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;

typedef struct _THREAD_BASIC_INFORMATION {
    NTSTATUS ExitStatus;
    PVOID TebBaseAddress;
    CLIENT_ID ClientId;
    ULONG_PTR AffinityMask;
    LONG Priority;
    LONG BasePriority;
} THREAD_BASIC_INFORMATION;

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    USHORT *Buffer;
} UNICODE_STRING;

ZEROOK
NTSTATUS
WINAPI
NtQueryInformationProcess(
	HANDLE ProcessHandle,
	PROCESSINFOCLASS ProcessInformationClass,
	PVOID ProcessInformation,
	ULONG ProcessInformationLength,
	ULONG *ReturnLength
);

ZEROOK
NTSTATUS
WINAPI
NtQueryInformationThread (
	HANDLE ThreadHandle,
	THREADINFOCLASS ThreadInformationClass,
	PVOID ThreadInformation,
	ULONG ThreadInformationLength,
	ULONG *ReturnLength
);

ZEROOK
NTSTATUS
WINAPI
NtQueryVirtualMemory (
	HANDLE ProcessHandle,
	PVOID BaseAddress,
	MEMORYINFOCLASS MemoryInformationClass,
	PVOID MemoryInformation,
	ULONG MemoryInformationLength,
	ULONG *ReturnLength
);

EOS

	# convert a native function return value
	# if the native does not have the zero_not_fail attribute, convert 0
	#  to nil, and print a message on stdout
	def self.convert_ret_c2rb(fproto, ret)
		@last_err_msg = nil
		if ret == 0 and not fproto.has_attribute 'zero_not_fail'
			# save error msg so that last_error_msg returns the same thing if called again
			puts "WinAPI: error in #{fproto.name}: #{@last_err_msg = last_error_msg}" if $VERBOSE
			puts caller if $DEBUG
			nil
		else super(fproto, ret)
		end
	end

	# retrieve the textual error message relative to GetLastError
	def self.last_error_msg(errno = nil)
		return @last_err_msg if @last_err_msg
		errno ||= getlasterror
		message = ' '*512
		if formatmessagea(FORMAT_MESSAGE_FROM_SYSTEM, nil, errno, 0, message, message.length, nil) == 0
			message = 'unknown error %x' % errno
		else
			message = message[0, message.index(?\0)] if message.index(?\0)
			message.chomp!
		end
		message
	end
end

class WinOS < OS
	class Process < OS::Process
		attr_accessor :ppid
		def initialize(pid, handle=nil)
			@pid = pid
			@handle = handle
		end

		# on-demand cached openprocess(ALL_ACCESS) handle
		def handle
			@handle ||= WinAPI.openprocess(WinAPI::PROCESS_ALL_ACCESS, 0, @pid)
		end
		attr_writer :handle

		# return/create a WindowsRemoteString
		def memory
			@memory ||= WindowsRemoteString.new(handle)
		end
		attr_writer :memory

		def debugger
			@debugger ||= WinDebugger.new(@pid)
		end
		attr_writer :debugger

		# return 32 for 32bit process, 64 for 64bit process
		# populates iswow64
		def cpusz
			@cpusz ||= (
				byte = 0.chr*8
				if WinAPI.respond_to?(:iswow64process) and WinAPI.iswow64process(handle, byte)
					# os supports iswow64process, so target may be 64bits
					if byte != 0.chr*8
						@iswow64 = true
						32
					else
						@iswow64 = false
						WinAPI.host_cpu.size
					end
				else
					WinAPI.host_cpu.size
				end
			)
		end
		attr_accessor :iswow64

		# returns the memory address size of the target process
		# if the target is a wow64 process (32bit process under a 64bit os), return 64
		def addrsz
			@addrsz ||= ((cpusz == 32 and iswow64) ? 64 : cpusz)
		end

		def modules
			WinOS.list_modules(@pid)
		end

		def threads
			WinOS.list_threads(@pid)
		end

		def heaps
			WinOS.list_heaps(@pid)
		end

		# return a list of [addr_start, length, perms]
		def mappings
			addr = 0
			list = []
			info = WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{WinAPI.host_cpu.size}")
			path = [0xff].pack('C') * 512

			hcache = heaps

			while WinAPI.virtualqueryex(handle, addr, info, info.sizeof) != 0
				addr += info.regionsize
				next unless info.state & WinAPI::MEM_COMMIT > 0

				prot = {
					WinAPI::PAGE_NOACCESS => '---',
					WinAPI::PAGE_READONLY => 'r--',
					WinAPI::PAGE_READWRITE => 'rw-',
					WinAPI::PAGE_WRITECOPY => 'rw-',
					WinAPI::PAGE_EXECUTE => '--x',
					WinAPI::PAGE_EXECUTE_READ => 'r-x',
					WinAPI::PAGE_EXECUTE_READWRITE => 'rwx',
					WinAPI::PAGE_EXECUTE_WRITECOPY => 'rwx'
				}[info[:protect] & 0xff]
				prot = prot.sub('r', '-') + 'g' if info[:protect] & WinAPI::PAGE_GUARD > 0
				prot << 'p' if info[:type]    & WinAPI::MEM_PRIVATE > 0

				if h = hcache[info.baseaddress]
					a = []
					a << 'default' if h[:default]
					a << 'shared' if h[:shared]
					a << 'heap'
					#a << h[:flags].to_s(16)
					cmt = '[' + a.join(' ') + ']'
				elsif WinAPI.ntqueryvirtualmemory(handle, info.baseaddress, WinAPI::MEMORYMAPFILENAME, path, path.length, 0) == 0
					us = WinAPI.decode_c_struct('UNICODE_STRING', path)
					s = WinAPI.decode_c_ary('USHORT', us['Length']/2, WinAPI.memory_read(us['Buffer'], us['MaximumLength']))
					cmt = s.to_strz
				else
					cmt = ''
				end

				list << [info.baseaddress, info.regionsize, prot, cmt]
			end

			list
		end

		def peb_base
			@peb_base ||=
			if WinAPI.respond_to?(:ntqueryinformationprocess)
				pinfo = WinAPI.alloc_c_struct('PROCESS_BASIC_INFORMATION')
				if WinAPI.ntqueryinformationprocess(handle, WinAPI::PROCESSBASICINFORMATION, pinfo, pinfo.sizeof, 0) == 0
					pinfo.pebbaseaddress
				end
			else
				# pre-NT: all pebs should have the same addr
				WinAPI.new_func_asm('unsigned get_peb(void)', 'mov eax, fs:[30h] ret') { WinAPI.get_peb }
			end
		end
		attr_writer :peb_base

		def terminate(exitcode=0)
			WinAPI.terminateprocess(handle, exitcode)
		end
	end

	class Thread
		attr_accessor :tid
		attr_accessor :process

		def initialize(tid, handle=nil, process=nil)
			@tid = tid
			@handle = handle
			@process = process
		end

		def handle
			@handle ||= WinAPI.openthread(WinAPI::THREAD_ALL_ACCESS, 0, @tid)
		end
		attr_writer :handle

		# return the address of the TEB for the target thread
		def teb_base
			@teb_base ||=
			if WinAPI.respond_to?(:ntqueryinformationthread)
				tinfo = WinAPI.alloc_c_struct('THREAD_BASIC_INFORMATION')
				if WinAPI.ntqueryinformationthread(handle, WinAPI::THREADBASICINFORMATION, tinfo, tinfo.sizeof, 0) == 0
					tinfo.tebbaseaddress
				end
			else
				fs = context { |c| c[:fs] }
				ldte = WinAPI.alloc_c_struct('LDT_ENTRY')
				if WinAPI.getthreadselectorentry(handle, fs, ldte)
					ldte.baselow | (ldte.basemid << 16) | (ldte.basehi << 24)
				end
			end
		end
		attr_writer :teb_base

		# increment the suspend count of the target thread - stop at >0
		def suspend
			if WinAPI.host_cpu.size == 64 and process and process.iswow64
				WinAPI.wow64suspendthread(handle)
			else
				WinAPI.suspendthread(handle)
			end
		end

		# decrease the suspend count of the target thread - runs at 0
		def resume
			WinAPI.resumethread(handle)
		end

		def terminate(exitcode=0)
			WinAPI.terminatethread(handle, exitcode)
		end

		# returns a Context object. Can be reused, refresh the values with #update (target thread must be suspended)
		# if a block is given, suspend the thread, update the context, yield it, and resume the thread
		def context
			@context ||= Context.new(self, :all)
			if block_given?
				suspend
				begin
					@context.update
					yield @context
				ensure
					resume
				end
			else
				@context
			end
		end
		attr_writer :context

		class Context
			def initialize(thread, kind=:all)
				@handle = thread.handle
				tg = (thread.process ? thread.process.cpusz : 32)
				@getcontext = :getthreadcontext
				@setcontext = :setthreadcontext
				if tg == 32
					# XXX check CS under wow64 ?
					@context = WinAPI.alloc_c_struct('_CONTEXT_I386')
					@context.contextflags = WinAPI::CONTEXT_I386_ALL	# XXX kind ?
					if WinAPI.host_cpu.shortname == 'x64' and thread.process and thread.process.iswow64
						@getcontext = :wow64getthreadcontext
						@setcontext = :wow64setthreadcontext
					end
				else
					@context = WinAPI.alloc_c_struct('_CONTEXT_AMD64')
					@context.contextflags = WinAPI::CONTEXT_AMD64_ALL	# XXX kind ?
				end
			end

			# retrieve the actual context structure (so we can pass to API's like StackWalk64)
			def c_struct
				@context
			end
			
			# update the context to reflect the current thread reg values
			# call only when the thread is suspended
			def update
				WinAPI.send(@getcontext, @handle, @context)
			end

			def [](k)
				case k.to_s
				when /^[cdefgs]s$/i
					@context["seg#{k}"]
				when /^st(\d?)$/i
					v = @context['st'][$1.to_i]
					buf = v.str[v.stroff, 10]
					# TODO check this, 'D' is 8byte wide
					buf.unpack('D')[0]
				# TODO when /^ymm(\d+)$/i
				when /^xmm(\d+)$/i
					v = @context['xmm'][$1.to_i]
					(v.hi << 64) | v.lo
				when /^mmx?(\d)$/i
					# XXX probably in st(0/7)
					@context['xmm'][$1.to_i].lo
				else
					@context[k]
				end
			end

			def []=(k, v)
				case k.to_s
				when /^[cdefgs]s$/i
					@context["seg#{k}"] = v
				when /^st(\d?)$/i
					# TODO check this, 'D' is 8byte wide
					buf = [v, 0, 0].pack('DCC')
					@context['st'][$1.to_i][0, 10] = buf
				# TODO when /^ymm(\d+)$/i
				when /^xmm(\d+)$/i
					kk = @context['xmm'][$1.to_i]
					kk.lo = v & ((1<<64)-1)
					kk.hi = (v>>64) & ((1<<64)-1)
				when /^mmx?(\d)$/i
					# XXX st(7-$1) ?
					@context['xmm'][$1.to_i].lo = v
				else
					@context[k] = v
				end
				WinAPI.send(@setcontext, @handle, @context)
			end

			def method_missing(m, *a)
				if m.to_s[-1] == ?=
					return super(m, *a) if a.length != 1
					send '[]=', m.to_s[0...-1], a[0]
				else
					return super(m, *a) if a.length != 0
					send '[]', m
				end
			end
		end
	end

class << self
	# try to enable debug privilege in current process
	def get_debug_privilege
		# TODO use real structs / new_func_c
		htok = [0].pack('L')
		return if not WinAPI.openprocesstoken(WinAPI.getcurrentprocess(), WinAPI::TOKEN_ADJUST_PRIVILEGES | WinAPI::TOKEN_QUERY, htok)
		luid = [0, 0].pack('LL')
		return if not WinAPI.lookupprivilegevaluea(nil, WinAPI::SE_DEBUG_NAME, luid)

		# priv.PrivilegeCount = 1;
		# priv.Privileges[0].Luid = luid;
		# priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
		priv = luid.unpack('LL').unshift(1).push(WinAPI::SE_PRIVILEGE_ENABLED).pack('LLLL')
		return if not WinAPI.adjusttokenprivileges(htok.unpack('L').first, 0, priv, 0, nil, nil)

		true
	end

	# returns an array of Processes with pid/ppid/path filled
	def list_processes
		h = WinAPI.createtoolhelp32snapshot(WinAPI::TH32CS_SNAPPROCESS, 0)
		list = []
		pe = WinAPI.alloc_c_struct('PROCESSENTRY32', :dwsize => :size)
		return if not WinAPI.process32first(h, pe)
		loop do
			p = Process.new(pe.th32processid)
			p.ppid = pe.th32parentprocessid
			p.path = pe.szexefile.to_strz
			list << p if p.pid != 0
			break if WinAPI.process32next(h, pe) == 0
		end
		WinAPI.closehandle(h)
		list
	end

	# retrieve the list of Modules for a process with addr/size/path filled
	def list_modules(pid)
		h = WinAPI.createtoolhelp32snapshot(WinAPI::TH32CS_SNAPMODULE, pid)
		return [] if h == WinAPI::INVALID_HANDLE_VALUE
		list = []
		me = WinAPI.alloc_c_struct('MODULEENTRY32', :dwsize => :size)
		return [] if not WinAPI.module32first(h, me)
		loop do
			m = Process::Module.new
			m.addr = me.modbaseaddr
			m.size = me.modbasesize
			m.path = me.szexepath.to_strz
			list << m
			break if WinAPI.module32next(h, me) == 0
		end
		WinAPI.closehandle(h)
		list
	end

	# returns the list of thread ids of the system, optionally filtering by pid
	def list_threads(pid=nil)
		h = WinAPI.createtoolhelp32snapshot(WinAPI::TH32CS_SNAPTHREAD, 0)
		list = []
		te = WinAPI.alloc_c_struct('THREADENTRY32', :dwsize => :size)
		return [] if not WinAPI.thread32first(h, te)
		loop do
			list << te.th32threadid if not pid or te.th32ownerprocessid == pid
			break if WinAPI.thread32next(h, te) == 0
		end
		WinAPI.closehandle(h)
		list
	end

	# returns the heaps of the process, from a toolhelp snapshot SNAPHEAPLIST
	# this is a hash
	# heap_addr => { :flags => integer (heap flags)
	#                :shared => bool (from flags)
	#                :default => bool (from flags) }
	def list_heaps(pid)
		h = WinAPI.createtoolhelp32snapshot(WinAPI::TH32CS_SNAPHEAPLIST, pid)
		return [] if h == WinAPI::INVALID_HANDLE_VALUE
		ret = {}
		he = WinAPI.alloc_c_struct('HEAPLIST32', :dwsize => :size)
		return [] if not WinAPI.heap32listfirst(h, he)
		loop do
			hash = ret[he.th32heapid] = { :flags => he.dwflags }
			hash[:default] = true if hash[:flags] & WinAPI::HF32_DEFAULT == WinAPI::HF32_DEFAULT
			hash[:shared]  = true if hash[:flags] & WinAPI::HF32_SHARED  == WinAPI::HF32_SHARED
			# TODO there are lots of other flags in there ! like 0x1000 / 0x8000
			break if WinAPI.heap32listnext(h, he) == 0
		end
		WinAPI.closehandle(h)
		ret
	end


	# create a debugger for the target pid/path
	def create_debugger(path)
		WinDebugger.new(path)
	end

	# Injects a shellcode into the memory space of targetproc
	# target is a WinOS::Process
	# shellcode may be a String (raw shellcode) or an EncodedData
	# With an EncodedData, unresolved relocations are solved using
	# exports of modules from the target address space ; also the
	# shellcode need not be position-independant.
	def inject_shellcode(target, shellcode)
		raise 'cannot open target memory' if not remote_mem = target.memory
		return if not injectaddr = WinAPI.virtualallocex(target.handle, 0, shellcode.length,
				WinAPI::MEM_COMMIT | WinAPI::MEM_RESERVE, WinAPI::PAGE_EXECUTE_READWRITE)
		puts 'remote buffer at %x' % injectaddr if $VERBOSE

		if shellcode.kind_of? EncodedData
			fixup_shellcode_relocs(shellcode, target, remote_mem)
			shellcode.fixup! shellcode.binding(injectaddr)
			r = shellcode.reloc.values.map { |r_| r_.target }
			raise "unresolved shellcode relocs #{r.join(', ')}" if not r.empty?
			shellcode = shellcode.data
		end

		# inject the shellcode
		remote_mem[injectaddr, shellcode.length] = shellcode

		injectaddr
	end

	def fixup_shellcode_relocs(shellcode, target, remote_mem)
		ext = shellcode.reloc_externals
		binding = {}
		while e = ext.pop
			next if binding[e]
			next if not lib = WindowsExports::EXPORT[e]	# XXX could scan all exports... LoadLibrary ftw
			next if not m = target.modules.find { |m_| m_.path.downcase.include? lib.downcase }
			lib = LoadedPE.load(remote_mem[m.addr, 0x1000_0000])
			lib.decode_header
			lib.decode_exports
			lib.export.exports.each { |e_|
				next if not e_.name or not e_.target
				binding[e_.name] = m.addr + lib.label_rva(e_.target)
			}
			shellcode.fixup! binding
		end
	end

	# creates a new thread in the target process, with the specified start address
	def createthread(target, startaddr)
		WinAPI.createremotethread(target.handle, 0, 0, startaddr, 0, 0, 0)
	end

	# calls inject_shellcode and createthread
	def inject_run_shellcode(target, shellcode)
		raise "failed to inject shellcode" if not addr = inject_shellcode(target, shellcode)
		createthread(target, addr)
	end

	# returns a Process associated to the process handle
	def open_process_handle(h)
		pid = WinAPI.getprocessid(h) rescue 0
		Process.new(pid, h)
	end

	# returns the Process associated to pid if it is alive
	def open_process(pid)
		Process.new(pid) if check_process(pid)
	end

	# returns true if the process pid exists and we can open it with QUERY_INFORMATION
	def check_process(pid)
		if h = WinAPI.openprocess(WinAPI::PROCESS_QUERY_INFORMATION, 0, pid)
			WinAPI.closehandle(h)
			true
		end
	end

	# returns the Thread associated to a tid if it is alive
	def open_thread(tid)
		Thread.new(tid) if check_tid(tid)
	end

	# check if the thread is alive and can be read with QUERY_INFO
	# and optionally if it belongs to pid
	def check_tid(tid, pid=nil)
		if h = WinAPI.openthread(WinAPI::THREAD_QUERY_INFORMATION, 0, tid)
			WinAPI.closehandle(h)
			not pid or list_threads(pid).include?(tid)
		end
	end

	# returns the [major, minor] version of the windows os
	def version
		v = WinAPI.getversion
		[v & 0xff, (v>>8) & 0xff]
	end
end	# class << self
end

class WindowsRemoteString < VirtualString
	def self.open_pid(pid, access = nil)
		if access
			handle = WinAPI.openprocess(access, 0, pid)
		else
			handle = WinAPI.openprocess(WinAPI::PROCESS_ALL_ACCESS, 0, pid)
			if not handle
				puts "cannot openprocess ALL_ACCESS pid #{pid}, try ro" if $VERBOSE
				handle = WinAPI.openprocess(WinAPI::PROCESS_VM_READ, 0, pid)
			end
		end
		raise "OpenProcess(#{pid}): #{WinAPI.last_error_msg}" if not handle

		new(handle)
	end

	attr_accessor :handle

	# returns a virtual string proxying the specified process memory range
	# reads are cached (4096 aligned bytes read at once)
	# writes are done directly (if handle has appropriate privileges)
	def initialize(handle, addr_start=0, length=nil)
		@handle = handle
		length ||= 1 << (WinOS.open_process_handle(@handle).addrsz rescue 32)
		super(addr_start, length)
	end

	def dup(addr = @addr_start, len = @length)
		self.class.new(@handle, addr, len)
	end

	def rewrite_at(addr, data)
		WinAPI.writeprocessmemory(@handle, addr, data, data.length, nil)
	end

	def get_page(addr, len=@pagelength)
		page = [0].pack('C')*len
		return if WinAPI.readprocessmemory(@handle, addr, page, len, 0) == 0
		page
	end
end

# this class implements a high-level API over the Windows debugging primitives
class WinDebugger < Debugger
	attr_accessor :os_process, :os_thread,
		:auto_fix_fs_bug,
		# is current exception handled? (arg to pass to continuedbgevt)
		# if it has the special value :suspended, it means that the thread
		# is to be restarted through resume and not continuedbgevt
		:continuecode

	attr_accessor :callback_unloadlibrary, :callback_debugstring, :callback_ripevent

	def initialize(pidpath=nil)
		super()
		@pid_stuff_list << :os_process
		@tid_stuff_list << :os_thread << :ctx << :continuecode

		@auto_fix_fs_bug = false

		return if not pidpath

		begin
			npid = Integer(pidpath)
			attach(npid)
		rescue ArgumentError
			create_process(pidpath)
		end

		check_target until pid
	end

	def shortname; 'windbg'; end

	def attach(npid)
		WinAPI.debugactiveprocess(npid)
		WinAPI.debugsetprocesskillonexit(0) if WinAPI.respond_to?(:debugsetprocesskillonexit)
		100.times {
			check_target
			break if pid
		}
		raise "attach failed" if not pid
	end

	def create_process(target)
		startupinfo = WinAPI.alloc_c_struct('STARTUPINFOA', :cb => :size)
		processinfo = WinAPI.alloc_c_struct('PROCESS_INFORMATION')
		flags  = WinAPI::DEBUG_PROCESS
		flags |= WinAPI::DEBUG_ONLY_THIS_PROCESS if not trace_children
		target = target.dup if target.frozen?	# eg ARGV
		h = WinAPI.createprocessa(nil, target, nil, nil, 0, flags, nil, nil, startupinfo, processinfo)
		raise "CreateProcess: #{WinAPI.last_error_msg}" if not h

		set_context(processinfo.dwprocessid, processinfo.dwthreadid)
		@os_process = WinOS::Process.new(processinfo.dwprocessid, processinfo.hprocess)
		@os_thread  = WinOS::Thread.new(processinfo.dwthreadid, processinfo.hthread, @os_process)
		initialize_osprocess
		check_target
	end

	# called whenever we receive a handle to a new process being debugged, after initialisation of @os_process
	def initialize_osprocess
		initialize_cpu
		initialize_memory
		initialize_disassembler
	end

	def initialize_newpid
		raise "non-existing pid #@pid" if pid and not WinOS.check_process(@pid)
		super()
		# os_process etc wait for CREATE_THREAD_DBGEVT
	end

	def initialize_newtid
		super()
		# os_thread etc wait for CREATE_THREAD_DBGEVT
		@continuecode = WinAPI::DBG_CONTINUE	#WinAPI::DBG_EXCEPTION_NOT_HANDLED
	end

	def initialize_cpu
		# wait until we receive the CREATE_PROCESS_DBGEVT message
		return if not @os_process
		case WinAPI.host_cpu.shortname
		when 'ia32', 'x64'
			@cpu = Ia32.new(os_process.cpusz)
		else
			raise 'unsupported architecture'
		end
	end

	def initialize_memory
		return if not @os_process
		@memory = os_process.memory
	end

	def mappings
		os_process.mappings
	end

	def modules
		os_process.modules
	end

	def list_processes
		WinOS.list_processes
	end

	def list_threads
		os_process.threads
	end

	def check_pid(pid)
		WinOS.check_process(pid)
	end

	def check_tid(tid)
		# dont raise() on the first set_context when os_proc is not set yet
		return true if not os_process
		super(tid)
	end

	def ctx
		if not @ctx
			# swapin_tid => gui.swapin_tid => getreg before we init os_thread in EventCreateThread
			return Hash.new(0) if not os_thread
			@ctx = os_thread.context
			@ctx.update
		end
		@ctx
	end

	def invalidate
		@ctx = nil
		super()
	end

	def get_reg_value(r)
		ctx[r]
	end

	def set_reg_value(r, v)
		if @state == :running
			suspend
			ctx[r] = v
			resume
		else
			ctx[r] = v
		end
	end

	def do_continue(*a)
		@cpu.dbg_disable_singlestep(self)
		if @continuecode == :suspended
			resume
		else
			@state = :running
			WinAPI.continuedebugevent(@pid, @tid, @continuecode)
		end
	end

	def do_singlestep(*a)
		@cpu.dbg_enable_singlestep(self)
		if @continuecode == :suspended
			resume
		else
			@state = :running
			WinAPI.continuedebugevent(@pid, @tid, @continuecode)
		end
	end

	def do_enable_bpm(bp)
		@bpm_info ||= WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{WinAPI.host_cpu.size}")
		WinAPI.virtualqueryex(os_process.handle, bp.address, @bpm_info, @bpm_info.sizeof)
		# TODO save original page perms, check bpm type (:w -> vprotect(PAGE_READONLY)), handle multiple bpm on same page
		WinAPI.virtualprotectex(os_process.handle, bp.address, bp.internal[:len], @bpm_info[:protect] | WinAPI::PAGE_GUARD, @bpm_info)
	end

	def do_disable_bpm(bp)
		@bpm_info ||= WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{WinAPI.host_cpu.size}")
		WinAPI.virtualqueryex(os_process.handle, bp.address, @bpm_info, @bpm_info.sizeof)
		WinAPI.virtualprotectex(os_process.handle, bp.address, bp.internal[:len], @bpm_info[:protect] & ~WinAPI::PAGE_GUARD, @bpm_info)
	end

	def update_dbgev(ev)
		# XXX ev is static, copy all necessary values before yielding to something that may call check_target
		set_context ev.dwprocessid, ev.dwthreadid
		invalidate
		@continuecode = WinAPI::DBG_CONTINUE

		# XXX reinterpret ev as struct32/64 depending on os_process.addrsz ?
		case ev.dwdebugeventcode
		when WinAPI::EXCEPTION_DEBUG_EVENT
			st = ev.exception
			str = st.exceptionrecord
			stf = st.dwfirstchance	# non-zero = first chance

			@state = :stopped
			@info = "exception"

			# DWORD ExceptionCode;
			# DWORD ExceptionFlags;
			# struct _EXCEPTION_RECORD *ExceptionRecord;
			# PVOID ExceptionAddress;
			# DWORD NumberParameters;
			# ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
			case str.exceptioncode
			when WinAPI::STATUS_ACCESS_VIOLATION, WinAPI::STATUS_GUARD_PAGE_VIOLATION
				if @auto_fix_fs_bug and ctx.fs != 0x3b
					# fix bug in xpsp1 where fs would get a random value in a debugee
					log "wdbg: #{pid}:#{tid} fix fs bug" if $DEBUG
					ctx.fs = 0x3b
					resume_badbreak
					return
				end
				mode = case str.exceptioninformation[0]
				       when 0; :r
				       when 1; :w
				       when 8; :x
				       end
				addr = str.exceptioninformation[1]
				evt_exception(:type => 'access violation', :st => str, :firstchance => stf,
					      :fault_addr => addr, :fault_access => mode)
			when WinAPI::STATUS_BREAKPOINT, WinAPI::STATUS_WX86_BREAKPOINT
				# we must ack ntdll interrupts on process start
				# but we should not mask process-generated exceptions by default..
				evt_bpx
			when WinAPI::STATUS_SINGLE_STEP, WinAPI::STATUS_WX86_SINGLE_STEP
				evt_hwbp_singlestep
			else
				@status_name ||= WinAPI.cp.lexer.definition.keys.grep(/^STATUS_/).
						sort.inject({}) { |h, c| h.update WinAPI.const_get(c) => c }
				type = @status_name[str.exceptioncode] || str.exceptioncode.to_s(16)
				evt_exception(:type => type, :st => str, :firstchance => stf)
			end

		when WinAPI::CREATE_THREAD_DEBUG_EVENT
			st = ev.createthread
			@os_thread ||= WinOS::Thread.new(@tid, st.hthread, os_process)
			@os_thread.teb_base = st.lpthreadlocalbase if st.lpthreadlocalbase.to_i != 0
			evt_newthread(:st => st)

		when WinAPI::CREATE_PROCESS_DEBUG_EVENT
			# XXX 32 vs 64 struct undecidable before we get hprocess..
			st = ev.createprocess
			if not @os_process
				@os_process = WinOS::Process.new(@pid, st.hprocess)
				@os_thread ||= WinOS::Thread.new(@tid, st.hthread, os_process)
				initialize_osprocess
			else
				@os_thread ||= WinOS::Thread.new(@tid, st.hthread, os_process)
			end
			@os_thread.teb_base = st.lpthreadlocalbase if st.lpthreadlocalbase.to_i != 0
			hfile = st.hfile
			evt_newprocess(:st => st)
			WinAPI.closehandle(hfile)

		when WinAPI::EXIT_THREAD_DEBUG_EVENT
			st = ev.exitthread
			evt_endthread(:exitcode => st.dwexitcode)

		when WinAPI::EXIT_PROCESS_DEBUG_EVENT
			st = ev.exitprocess
			evt_endprocess(:exitcode => st.dwexitcode)

		when WinAPI::LOAD_DLL_DEBUG_EVENT
			st = ev.loaddll
			hfile = st.hfile
			evt_loadlibrary(:address => st.lpbaseofdll, :st => st)
			WinAPI.closehandle(hfile)

		when WinAPI::UNLOAD_DLL_DEBUG_EVENT
			st = ev.unloaddll
			evt_unloadlibrary(:address => st.lpbaseofdll)

		when WinAPI::OUTPUT_DEBUG_STRING_EVENT
			st = ev.debugstring
			str = WinAPI.decode_c_ary("__int#{st.funicode != 0 ? 16 : 8}", st.ndebugstringlength, @memory, st.lpdebugstringdata) if st.lpdebugstringdata
			str = str.to_array.pack('C*') rescue str.to_array.pack('v*')
			evt_debugstring(:string => str, :st => str)

		when WinAPI::RIP_EVENT
			st = ev.ripinfo
			evt_ripevent(:st => st)
		end
	end

	def evt_debugstring(info={})
		@state = :stopped
		@info = "debugstring"

		log "Debugstring: #{info[:string].inspect}"

		callback_debugstring[info] if callback_debugstring

		# allow callback to skip this call to continue() by setting info[:nocontinue] = true
		continue unless info[:nocontinue]
	end

	def evt_unloadlibrary(info={})
		@state = :stopped
		@info = "unload library"

		callback_unloadlibrary[info] if callback_unloadlibrary

		continue unless info[:nocontinue]
	end

	def evt_ripevent(info={})
		@state = :stopped
		@info = "rip_event"	# wtf?

		callback_ripevent[info] if callback_ripevent

		continue unless info[:nocontinue]
	end

	def do_check_target
		do_waitfordebug(0)
	end

	def do_wait_target
		do_waitfordebug(WinAPI::INFINITE)
	end

	def do_waitfordebug(timeout)
		@dbg_eventstruct ||= WinAPI.alloc_c_struct('_DEBUG_EVENT')
		if WinAPI.waitfordebugevent(@dbg_eventstruct, timeout) != 0
			update_dbgev(@dbg_eventstruct)
			true
		end
	end

	def del_tid
		# tell Windows to release the THREAD object
		WinAPI.continuedebugevent(@pid, @tid, @continuecode)
		super()
	end

	# do nothing, windows will send us a EXIT_PROCESS event
	def del_tid_notid
		nil while do_waitfordebug(10) and !@tid
	end

	def del_pid
		# tell Windows to release the PROCESS object
		WinAPI.debugactiveprocessstop(@pid) if WinAPI.respond_to?(:debugactiveprocessstop)
		super()
	end

	def break
		return if @state != :running
		# debugbreak() will create a new thread to 0xcc, but wont touch existing threads
		suspend
	end

	def suspend
		os_thread.suspend
		invalidate
		@state = :stopped
		@info = 'thread suspended'
		@continuecode = :suspended
	end

	def resume
		@state = :running
		@info = nil
		os_thread.resume
	end

	def detach
		del_all_breakpoints
		if not WinAPI.respond_to? :debugactiveprocessstop
			raise 'detach not supported'
		end
		# handle pending bp events
		# TODO check_target needs the Breakpoint objects...
		#pid = @pid ; 50.times { check_target } ; self.pid = pid

		# if we detach after a dbgevt and before calling continuedbgevent, the thread
		# may receive unhandled exceptions (eg BPX) and crash the process right after detach
		each_tid { do_continue if @state == :stopped }
		del_pid
	end

	def kill(exitcode=0)
		os_process.terminate(exitcode)
	end

	def pass_current_exception(doit = true)
		return if @continuecode == :suspended
		@continuecode = (doit ? WinAPI::DBG_EXCEPTION_NOT_HANDLED : WinAPI::DBG_CONTINUE)
	end
end
end
