Back to the HCU Papers
WIN32 - Inside Debug API
------------------------
(Things you need to know: the mysterious CONTEXT structure)
by Iceman, 20 March 1998
I'm very proud that my previous work "Tweaking with memory in Windows 95" was good
enough to open a new section ,"+HCU's PAPERS", at Fravia's.That's very stimulating ,so I'm
back! I hope that will be many other contributors to this section.Let's bring light in the
shadows!
BTW, Fravia ,I really like the picture for your new section.I'm with you boys,now
and forever!And one more thing:for now one I will send you my essays in .htm format.No more
plain text files!
In this document I will focus on Debug API functions.I think that it is an
interesting chapter,who worth a closer lock.
Note:In order to use those functions in Windows NT your user must have debug
privileges access right granted.I don't know for sure but it seems that NT does not grant
this for default to administrators.
I have started to work on part two of "Tweaking with memory in Windows 95" this
time I want to present a VxD aprroach.It's nice to write self-modifying code using the linker
trick(Make code section read-write at link time).But wouldn't be nicer to relay only on VxD calls
to tweak with memory leaving that damned section write protected and without ANY high-level
calls to functions like VirtualProtect? The target will be this time the virtual memory manger
itself.Anyone out there who could HELP?.
The document has the following structure:
Chapter1:Functions and structures.
Chapter2:Debug events.
Chapter3:Creating or attaching a process for being debugged.
Chapter4:The main loop: WaitForDebugEvent - ContinueDebugEvent.
Chapter5.Handling debug events.
Chapter6:GetThreadContext & Set ThreadContext(Advanced stuff)
6.1 Thread Contexts explained
6.2 Injecting code in another process.
Chapter7:Notes
===============================================================================================
Chapter1:Functions and structures
----------------------------------
A good WIN32 API reference for future reference is OK.I don't explain here all
the parameters those functions take, nor all structures.But for now,let's see the API:
ContinueDebugEvent
DebugActiveProcess
DebugBreak
FatalExit
FlushInstructionCache
GetThreadContext
GetThreadSelectorEntry
IsDebuggerPresent
OutputDebugString
ReadProcessMemory
IsDebuggerPresent
SetThreadContext
WaitForDebugEvent
WriteProcessMemory
As we can see , two old friends:WriteProcessMemory & ReadProcessMemory.We all know
them very well,so let's go further.
FlushInstructionCache ,IsDebuggerPresent,OutputDebugString? Self-explanatory.
DebugActiveProcess
------------------
This function allows a debugger to attach to an active process.
BOOL DebugActiveProcess(
DWORD dwProcessId
);
Parameters:
DWORD dwProcessId: PID of process to attach
WaitForDebugEvent
-----------------
This function allow the debugger to wait until a debug event heapens
in target process.
BOOL WaitForDebugEvent(
LPDEBUG_EVENT lpDebugEvent,
DWORD dwMilliseconds
);
Parameters:
LPDEBUG_EVENT lpDebugEvent: Pointer to a DEBUG_EVENT type structure.
This struct will receive info about the
debug event trapped.
DWORD dwMilliseconds: Number of ms. to wait.Could be INFINITE.
If it is INFINITE WaitForDebugEvent does
not return until a debug event occurs.
ContinueDebugEvent
------------------
This function allow the debugger to resume a thread that previously raised a
debug event.
BOOL ContinueDebugEvent(
DWORD dwProcessId,
DWORD dwThreadId,
DWORD dwContinueStatus
);
Parameters:
DWORD dwProcessId: PID of process beeing debugged
DWORD dwThreadId: TID of thread to be resumed
DWORD dwContinueStatus : DWORD that specify how the thread will
continue.Two values defined:
DBG_CONTINUE & DBG_EXCEPTION_NOT_HANDLED.
Debug Break
----------
This function causes a breakpoint in current process.
VOID DebugBreak(VOID);
FatalExit
---------
This function force the exit of caller process,transferring execution to debugger
VOID FatalExit(
int ExitCode
);
Parameters:
int ExitCode : Exit code
GetThreadContext & Set Thread context
-------------------------------------
Those functions are used to retrieve & set the context of a thread.See chapter 6.
BOOL GetThreadContext(
HANDLE hThread,
LPCONTEXT lpContext
);
BOOL SetThreadContext(
HANDLE hThread,
CONST CONTEXT *lpContext
);
Parameters:
HANDLE hThread: A handle to thread whose context is being read
or set.
LPCONTEXT lpContext: Pointer to a CONTEXT structure to receive /set
context info.
GetThreadSelectorEntry
----------------------
The GetThreadSelectorEntry function retrieves a descriptor table entry for
the specified selector and thread.
BOOL GetThreadSelectorEntry(
HANDLE hThread,
DWORD dwSelector,
LPLDT_ENTRY lpSelectorEntry
);
Parameters:
HANDLE hThread: A handle to thread containing specified
selector.
DWORD dwSelector: Selector Number
LPLDT_ENTRY lpSelectorEntry Pointer to a structure that receive
descriptor table.
================================================================================================
Chapter2:Debug events.
----------------------
From Debug API functions point of view a debug events is an object used to communicate
with the debugger.When a debug event occurs in target process the OS inform the debugger about
this.The debugger use WaitForDebugEvent to retrieve info about the event that occurred in target
process(See chapter 5).Following debug events exists:
1.CREATE_PROCESS_DEBUG_EVENT & EXIT_PROCESS_DEBUG_EVENT raised every time than a new
process is created/destroyd by the process being debugged.
2.CREATE_THREAD_DEBUG_EVENT & EXIT_CREATE_THREAD_DEBUG_EVENT raised whenever a new
thread object is created/destroyed by the process being debugged.
3.LOAD_DLL_DEBUG_EVENT & UNLOAD_DLL_DEBUG_EVENT generated whenever the target loads/
unloads a dll.
4.OUTPUT_DEBUG_STRING_EVENT generated than target calls OutputDebugString.
5.EXCEPTION_DEBUG_EVENT generated when an exception occurs in target process.This
include breakpoint instructions such INT 3 , DivideOverflow .....
6.RIP_DEBUG_EVENT generated when a RIP exception occurs.
WaitForDebugEvent receives the debug event and returns information about the event
in a DEBUG_EVENT structure.This structure is defined as below in WIN32 AP:
typedef struct _DEBUG_EVENT {
DWORD dwDebugEventCode;
DWORD dwProcessId;
DWORD dwThreadId;
union {
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
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;
The member dwDebugEventCode contains a value indicating which kind of debug events
was ocureed.The dwProcessId member contain the PID of process in which the debug event occurred.
The union u member is a classic C/C++ union.It is reflected in a structure whose type is
determined by DWORD dwDebugEventCode.This structure contains extended information about the
event that ocurred.I don't list all of them here because it's pointless.
Also note that a CREATE_PROCESS_DEBUG_EVENT is generated than a debugger attach to a
target process.
================================================================================================
Chapter3:Creating or attaching a process for being debugged
------------------------------------------------------------
In this short chapter I present you how to create a process for being debugged,or
how to attach to an already running process.
3.1:Creating a process for being debugged
Use CreateProcess to create the process being debugged.Call this function
with dwCreationFlags parameter with one of following values DEBUG_PROCESS or
DEBUG_ONLY_THIS_PROCESS.If target process is created with DEBUG_PROCESS creation
flag than the debugger will receive events from all processes crated by target
process.If dwCreationFlags=DEBUG_ONLY_THIS_PROCESS than the debugger will receive
debug events only from target process ignoring child processes.As usually you
can use PROCESS_INFORMATION structure to ret rive handles to both the created
process and it's primary thread as well as the PID an TID(for primary thread).
3.2:Attaching to an already running process
Use DebugActiveProcess function.If this function returns successfully you
are attached to target as if you called CreateProcess with DEBUG_ONLY_THIS_PROCESS
flag.
Note that in WindowsNT DebugActiveProcess can easily fail if we try to attach to a
process that was created with a security descriptor that denies requested access.In WIN95
the only thing you have to worry is to pas a valid PID to DebugActiveProcess.That's it man!
NT has better security.
Attaching to a process is an elegant method but sometimes the loader method is the
only solution.It's up to you what method to use.For a simple game trainer it's OK to attach
but if you really want to do cool things...,better use the loader method.It gives you full
control over the target process and it's threads.
================================================================================================
Chapter4:The main loop: WaitForDebugEvent - ContinueDebugEvent
--------------------------------------------------------------
A minimum skeleton for using Debug API function is easy to implement.All you have to
do is to create a process for being debugged and the implement code to watch for debug events.
I call the part responsible with watching debug events "The Main Loop".Why?Because is very
simple to implement as a While loop.The functions you have to use for this are
WaitForDebugEvent - ContinueDebugEvent. As we have seen before WaitForDebugEvent waits for a
certain amount of time for a debug event to occur in target process.If a debug event does not
occur in this time the function times-out and return FALSE. If a debug events occurs than
this function return TRUE,fill a DEBUG_EVENT type structure with info about event type and
freeze the thread in witch the debug event ocurred.The programmer is responsible to perform
event type checking and take appropriate meassures.After the specific code for handling debug
event is executed we have to use ContinueDebugEvent to resume thread execution and wait for
other events to occure.Another thing to worry: the only thread witch is allowed to call
WaitForDebugEvent is the thread who created or attached to target process.So let's see some
code:
PROCESS_INFORMATION pi;
STARTUP_INFO si;
DEBUG_EVENT devent;
if(CreateProcess( 0 , "target.exe" , 0 , 0 ,FALSE ,DEBUG_ONLY_THIS_PROCESS , 0 ,0 ,
&si , &pi))
while(TRUE)
{
{
if (WaitForDebugEvent( &devent , 150)) // wait 150 ms for debug event
{
switch(devent.dwDebugEventCode)
{
case CREATE_PROCESS_DEBUG_EVENT:
// your handler here
break;
case EXIT_PROCESS_DEBUG_EVENT:
// your handler here
break;
case EXCEPTION_DEBUG_EVENT:
// your handler here
break;
}
ContinueDebugEvent(devent.dwProcessId , devent.dwThreadId , DBG_CONTINUE);
}
else
{
// other operations
}
}
} // while end here
else
{
MessageBox(0,"Unexpected load error","Fatal Error" ,MB_OK);
}
=================================================================================================
Chapter5.Handling debug events
-------------------------------
In previous example we can see that how we can trap debug events and take appropriate
actions using case/switch C /C++ statements.Each debug event has a personal handler who
gets executed when corresponding debug event occurs.More information about the debug event
can be found in union u member of DEBUG_EVENT.As a example let's the structure corresponding
to EXCEPTION_DEBUG_EVENT.I choose this because encountering breakpoints and tracing through
code generates an exception debug event.See a API reference for other events.
typedef struct _EXCEPTION_DEBUG_INFO
{
EXCEPTION_RECORD ExceptionRecord;
DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO;
In this case we have to retrieve data we need from another structure,member of
EXCEPTION_DEBUG_INFO structure.This is EXCEPTION_RECORD structure in which , finally ,
we can find all data we need about trapped exception.Let's see:
typedef struct _EXCEPTION_RECORD
{
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
DWORD ExceptionCode: Specifyes the type of exception
ExceptionFlags : 0 if exception is a continuable exception
EXCEPTION_NONCONTINUABLE if exception is not continuable.
ExceptionRecord : Pointer to an associated EXCEPTION_RECORD structure
PVOID ExceptionAddress: Pointer to the address where exception occurred
DWORD NumberParameters: Number of parameters defined in ExceptionInformation
ExceptionInformation: Additional 32 bit array.For most exception is undefined.
Using the information from those structures we can find all we need.We can retrieve
the thread there exception occurred , type of exception , if we can continue execution or not,
the address where exception occurred and others.
Note that trying to continue a EXCEPTION_NONCONTINUABLE exception type will
generate a EXCEPTION_NONCONTINUABLE_EXCEPTION exception.
Currently used exceptions are EXCEPTION_BREAKPOINT and EXCEPTION_SINGLE_STEP.The
first exception is raised on a breakpoint hit, the seconds signalizes that trace trap
signals that one instruction has been executed.
Using a similar mechanism you can gather information about threads , dll's used
by running process and other things.
===============================================================================================
Chapter6:GetThreadContext & Set ThreadContext(Advanced stuff)
-------------------------------------------------------------
6.1 Thread Contexts explained
-----------------------------
I really enjoyed writing this chapter.All others are things easy to figure out.Don't
scare it not really difficult to understand what's going on in this chapter.Before to present
those two functions and their use I want to remember some basic things about processes and
threads.
In WIN32 philosophy a process is a object who has an private address space , code ,
data , and a primary thread.Each process has at the very beginning only one thread.From the
primary thread we can later create other threads which run in the same address space.Contrary
to the popularly belief a process does NOT execute any kind of code.The threads are the objects
who executes the code.The thread objects share the same address space and resources but they
have individual contexts.What means that? Windows95 and WindowsNT are multitasking AND
multithread operating systems.The OS seems to run all threads in the same time , but this is
not true. Every individual thread is scheduled for execution for a short time , and the the
OS save the thread state in a structure called CONTEXT structure and goes for the next thread.
The information saved in this structure represents the thread context and is formed by:
- threads machine registers (CPU registers)
- the kernel stack and the user stack address
- thread environment block address.
The the OS encounter again our thread it restores it's context info from associated
structures and resume execution like nothing happened.
Ok,so let's see the CONTEXT structure.Unfortunately seems that Microsoft does not
include info about this structure API help files. The structure is documented at minimum
in winnt.h header file in Watcom compilers(can be elsewhere in others.Keep looking).Keep in
mind that this structure is hardware dependent so expect different implementations for
x86 , Alpha...
typedef struct _CONTEXT {
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;
} CONTEXT;
typedef struct _FLOATING_SAVE_AREA {
DWORD ControlWord;
DWORD StatusWord;
DWORD TagWord;
DWORD ErrorOffset;
DWORD ErrorSelector;
DWORD DataOffset;
DWORD DataSelector;
BYTE RegisterArea[SIZE_OF_80387_REGISTERS];
DWORD Cr0NpxState;
} FLOATING_SAVE_AREA;
typedef FLOATING_SAVE_AREA *PFLOATING_SAVE_AREA;
DWORD ContextFlags: the folowing values are defined:
CONTEXT_CONTROL // SS:SP, CS:IP, FLAGS, BP
CONTEXT_INTEGER // AX, BX, CX, DX, SI, DI
CONTEXT_SEGMENTS // DS, ES, FS, GS
CONTEXT_FLOATING_POINT // 387 state
CONTEXT_DEBUG_REGISTERS // DB 0-3,6,7
CONTEXT_FULL=(CONTEXT_CONTROL | CONTEXT_INTEGER > CONTEXT_SEGMENTS)
Watch out CONTEXT_FULL does not include CONTEXT_DEBUG_REGISTERS and
CONTEXT_FLOATING_POINT.You must specify them independently.It's huge and ugly, isn't it?
Ok we know now how a CONTEX structure is looking.Now let's see what can we do with
this monster.First let's talk a little about GetThreadContext & SetThreadContext.
The function GetThreadContext is used to get a thread context.
BOOL GetThreadContext(
HANDLE hThread,
LPCONTEXT lpContext
);
hThread is the handle of thread whose context is to be retrieved.
LPCONTEXT lpContext is a pointer to a CONTEXT structure.
PRIOR TO USE GetThreadContext you MUST initialize ContextFlags member with
the appropriate flag.Use This to set the amount of info to retrieve.For example if
you specify CONTEXT_CONTROL value for ContextFlags only SS:SP, CS:IP, FLAGS, BP
will be saved.
The function SetThreadContext is used to set the thread context.
BOOL SetThreadContext(
HANDLE hThread,
CONST CONTEXT *lpContext
);
Like before hThread is a handle to destination thread and CONST CONTEXT
*lpContext is a pointer to a CONTEXT structure.The amount of information restored
is determined by ContextFlags member.
You may want to consider another thing.NEVER try to set a thread context
while the thread is running.I consider this "one way ticket to the hell".Use
SuspendThread to stop a running thread.Later after you set the context you
can use ResumeThread function.Warning: using ResumeThread does not guarantee
that the target thread will indeed resume executions.Why?Every thread have an
thread suspend count.When the thread is running the counter is 0.Every time when
we use SuspendThread this counter is incremented by one.So we call SuspendThread.
The counter will be updated to 1.But W_95 and W_NT are multithread enviroments
so another thread can call too SuspendThread on our thread.Now the counter
is 2.Calling ResumeThread once will have only one effect.The counter is again 1.
Thread execution is not resumed until the thread suspend count is 0.(ResumeThread function
decrements the suspend counter ).So how can we be sure that the thread resumed execution?
Simple.Examine the return value. If it's 0 then the thread was not suspended.If it's
1 the thread was suspended but resumed execution.If it's greater than 1 the thread suspend
counter was decremented but the thread was not resumed.A value of 0xffffffff means that
ResumeThread failed.
6.2 Injecting code in another process.
--------------------------------------
Now let's take a deep breath.We are almost through.As usually I want to present
you an interesting trick.Let's inject some code into another process address space.We know
how,but first let's talk about a little impediment.We need some committed memory to store
our brand new code.A VirtualAllocEx function was not provided in WIN95 API.It seems that
this one along with it's companion VirtualFreeEx exists under NT.
If our code is very little we can use the space provided by our compilers: The MS-DOS stub of
the PE files,copyright strings or even unnecessary data strings(I dont care too much if in Help
About the program says that it was developed by "!^$##@*^$f76").Another method is to
save a code page of target process , overwrite with new code , execute new code , restore
code page.Let's see this step by step.
1. Use CreateProcess to create a process for being debugged.
2. Build the "Main Loop" WaitForDebugEvent - ContinueDebugEvent
3. Stop the target thread. Use SuspendThread.
4. Use VirtualProtectEx to set a read-write permission to target page
5. Use ReadProcessMemory to save the target page.
6. Use GetThreadContext to save the thread context.
7. Use WriteProcessMemory to write new code page.
8 Make sure that the last instruction in the new code is a INT 3.We need this to
take control when our code finished.The INT 3 will be trapped by our little
debugger-like application EXCEPTION_DEBUG_EVENT.Make sure that is a
EXCEPTION_BREAKPOINT and has occurred at the address there our INT 3 resides.
9. Make a temporary copy of saved CONTEXT structure.
10. Set the new eip in the temporary CONTEXT structure (You now what is eip , didn't
you?
11.Resume execution of the thread.Watch it executing our new code.When INT 3 gets
executed our little loader will trap it.The target thread is stopped.
12.Restore the original code page using WriteProcessMemory.
13.Restore the protection attributes on target page.
14.Use SetThreadContext to set thread context from the first CONTEXT structure.
15.Resume thread.
If we need that our resides in target process address space at the same time with the
original code,and our code is BIG we have to commit some memory in the target process address
space.The code to call VirtuallAlloc is very small,so use previous method to call VirtualAlloc
in the context of the target process.This will commit memory in target's address space and
return a pointer to it.Several kb should be more than enough,so don't be a fool and start
to commit cosmic values like 10 Mb.I wonder if there is another method to implement a
VirtualAllocEx under WIN95.I keep looking.Anyone now if VirtualAllocEx is implemented
in Memphis(future Windows 98)?.
If you ever need to convert a segment relative address in linear virtual address you
can use GetThreadSelectorEntry.
Final words: !!WATCH!! the stack.DON'T mess IT.If you do , you will be sorry.
================================================================================================
Chapter7.Notes
--------------
1.Any corrections and additions are wellcomed.Please append them at the end of this
document and also include your name (or your nickname ).Slightly editing minor mistakes and
typos is admitted in-place and without notice.
You are deep inside fravia's page of reverse engineering,
choose your way out:
Back to the HCU Papers
homepage
links
search_forms
+ORC
students' essays
academy database
reality cracking
how to search
javascript wars
tools
anonymity academy
cocktails
antismut CGI-scripts
mail_fravia+
Is reverse engineering legal?