Using the Debug function to implement the API function tracking If we can write a similar debugger function, this debugger needs to implement our requirements for tracking monitoring tools, that is, automatically record the input and output parameters, and automatically let the target process continues to run. Below we will introduce a solution that can simply output monitoring results in the case of a function prototype - use the debug function to monitor the monitor of API functions.
Use the debug function to achieve monitoring the API function
Everyone knows that VC can be used to debug procedures, in addition to debugging the Debug program, of course, you can also debug the Release program (debug the Release program is assembly code). If you know the entry address of the function, simply set the breakpoint on the function portal. When the program calls the function of setting the breakpoint, the VC will suspend the operation of the target program, you can get all the you want to get the target program memory. Thing. In general, as long as you have enough patience and perseverance, as well as some assembly knowledge, it can be done to monitor the input and output parameters of the API function.
However, since the VC debugger will suspend the operation of the target program during each breakpoint, too many pauses for the target program cannot endure for monitoring tasks. Therefore, there will be no too many people really use the VC debugger as a good API function monitor.
If the VC debugger can automatically output the stack value when you automatically output breakpoints (the function's input parameters), the stack value is automatically output at the end of the function, which is the output parameters of the function. ) And the value of the CPU register (that is, the function return value) and does not pause the target program. Everything is automatic without us to intervene. Will you use it as a monitor? I will.
I don't know how to make VC like this (maybe VC can be like this, but I don't know. Please let me know if anyone knows, thank you), but I know that it is obvious that the VC is also the task of completing the debugger by calling the Windows API function, and These functions can clearly realize my requirements. What I need to do is to use these API functions, write a simple debugger, automatically output monitoring results when the target program breakpoint occurs and automatically restores the operation of the target program.
Obviously, if you use a VC debugger as a monitor, you can get a simple input output parameter and function run results without knowing the target function, and because the monitoring code does not inject the target program, there will be no monitoring target function and monitoring code. Conflict. The VC debugger obviously tracks the recursive function, or tracks the DLL module to call the DLL itself function, and the EXE calls its own functions. As long as you know the entrance address of the target function, you can track (monitor EXE own functions) You can refer to the MAP file to get the address of the EXE internal function by selecting the output MAP file when generating an EXE module. Without I heard that VC can't debug multi-threaded, up to me to say that debug multi-thread is more troublesome - Certificate multi-thread is to debug. Obviously, the VC can also debug code in dllmain. These, it is already possible to prove that our goals can be achieved by debugging functions.
How to write a program that realize our goals? What debug functions are needed?
First, let the target program enter the debugged status:
For a start-up process, use the debugactiveProcess function to capture the target process and enter the target process into the debugged state.
Bool DebugActiveProcess (DWORD DWPROCESSID);
The parameter dWProcessID is the process ID of the target process. How to get a running process ID through the TOOLHELP series function or PSAPI library function introduced in many articles, this is no longer repeated. For server programs, since there is no permission that cannot capture the target process, you can get debug privileges by lifting the privileges of the monitor, capture the target process (the user must have debug privileges). For starting a new program, set the necessary parameters to enter the debugged state by setting the necessary parameters through the CreateProcess function.
BOOL CreateProcess (LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation);
For details, please refer to MSDN, here I only introduce the parameters we are interested in. Here and the general usage are different as the debugging program DWCREATIONFLAGS must be set to debug_process or debug_only_this_process. This start-up target program will enter the debugged state. Here, DEBUG_PROCESS and DEBUG_ONLY_THIS_PROCESS are explained. Debug_only_this_process is only debugging the target process, while the debug_process parameter not only debugs the target process, but debugs all the sub-process initiated by the target process. For example: Start B.exe in A.EXE, if you start with debug_only_this_process, monitoring processes only debug a.exe does not debug B.exe, if it is debug_process, it will debug a.exe and b.exe. For the sake of simplicity, this article only discusses the case where the start-up parameter is debug_only_this_process.
Instructions:
STARTUPINFO ST = {0}; process_information pro = {0}; st.cb = sizeof (st); CreateProcess (null, pszcmd, null, null, false, debug_only_this_process, null, szpath, & st, & pro)); // Close Handle --- These handles are no longer used in the debugger, so CloseHandle (Pro.hthread); CloseHandle (Pro.hprocess);
Second, monitoring procedures entering the debugged state:
The target process enters the debugged state, the debugger (here the debugger is our monitoring program, will no longer explain), which is responsible for debugging the debugged operation of the debugged program. The debugger obtains debug messages from the debugged program via the waitfordeBugevent function, the debugger is processed according to the resulting debug message, and the debug process will be paused until the debugger notified the debugger to continue running through the ContinueDebugevent function.
BOOL WAITFORDEBUGEVENT (LPDebug_event LPDebugEvent, // debug Event information dWord dwmilliseconds // time-out value);
The debug message can be obtained in the parameter lpDebugevent, which is required to note that the function must be the same thread that the function must enter the debug status of the target program. That is to say, the thread called through the debugactiveProcess or CreateProcess is a thread. In addition, I like to set dwmilliseconds to -1 (unlimited waiting). So I usually use the CreateProcess and WaitFordeBugevent functions in a new thread. typedef struct _DEBUG_EVENT {DWORD dwDebugEventCode; DWORD dwProcessId; DWORD dwThreadId; union {EXCEPTION_DEBUG_INFO Exception; CREATE_THRECheck the input System.out.println ( "/ nREQUEST: / n"); message.writeTo (System.out); System.out.println (); // Close the connection connection.close ();} catch (exception e) {system.out.println (E.getMessage ());}}}
Set breakpoints for the target process:
Our goal is to monitor the input and output of the API function, then you should first know which API letters in the DLL module and the entrance address of these APIs. In the previous, the broad API also includes an unidentified internal function. If you have a DLL module debug version and debug connection file (PDB file), you can also get information about the internal function according to debug information.
· Get the function name and function entry address
There are many ways to obtain the entry address of the function through the program. For DLLs compiled with VC, if it is a debug version, you can get debugging information through the ImageHLP library function, and analyze the entry address of the function. If there is no DEBUG version, you can also get the entry address of the function by analyzing the export function table.
1. Use the ImageHLP library function to get the DEBUG version of the function name and function entry address.
You can use the ImageHLP library function to analyze Debug information, the associated function is Syminitialize, SymeNumerateSymbols, and undecorateSymbolname. For details, please refer to the instructions and usages of these functions in MSDN. However, using imageHLP can only analyze the programs compiled with VC, and the programs compiled by C Builder cannot be analyzed in this way.
2. DLL Export Table Gets the function export function name and the entry address of the function.
In most cases, we still want to monitor the Release version of the input and output parameters. After all, the Debug version is not the product we finally supplied to the user. Debug and Release have different compilation conditions that result in different results, and discuss in many BBSs. So, I think tracking monitoring Release version is more practical.
The export function name is active on the MSDN by analyzing the DLL export table. Note About derived tables You can refer to articles about the PE structure.
3. Get the COM interface through the OLE function
You can also analyze the interface functions provided by the OLE function to analyze the DLL. The interface function is not exported through a DLL export table. You can analyze the COM interface through the loadTypelib function, get the entrance address of the COM record interface, so you can monitor the call of the COM interface. This is what the API Hook cannot be implemented. Here I don't intend to analyze the way the COM interface is. On the MSDN, you can find the relevant source code to modify your goal by searching the LoadTypelib Sample Keyword.
Here is the program that automatically analyzes the target module to get the DLL export function, as the purpose of our monitoring, these work is just to get a series of function names and function addresses. The function name is just a name that makes us easy to identify the function. The function entry address is our truly concern. In other words, if you can make sure that an address must be a function (including internal function) entry address, you can define your own name, add it to your function management table, you can also achieve monitoring The function of the input and output parameters of this function. This is why the monitoring function of implementing the EXE internal function. If you have an MAP file generated when EXE compile (you can choose to generate a map file when compile), you can get the MAP file to get the inlet address of the internal function, add the internal function to your function management table. (The name of a function is not meaningful for Funa or funb for monitoring functions, but the name is Funa or the name of FUNB is meaningful for monitoring monitoring results. You can complete the MessageBox function to monitor results. It is output with the name of FUNA, so when you monitor some internal no name, you can define your own name). · Set breakpoints at the function entry address
Setting the breakpoint is very simple, just write 0xCC (INT 3) to the specified address. When this program runs to the specified address, the debug interrupt information notification debugger is generated. Modifying the memory data of the specified process can be done by the WriteProcessMemory function. Since the general case is protected as a program code segment, there is still a function to be used. VirtualProtectex. In actual conditions, when the trial break occurs, the debugger should write the original code back to the debugged program.
Unsigned char setbreakpoint (dword padd, unsigned char code) {unsigned char b; bool rc; dword dwread, dwoldflg; // 0x80000000 or more address is a system common area, can not modify if (PADD> = 0x80000000 || PADD == 0 Return code; // Get the original code RC = ReadProcessMemory (_GHDebug, padd, & b, sizeof (byte), & dwread); // The original code is the same, there is no need to modify if IF (RC == 0 || b == code) return code; // modify the page protection attribute VirtualProtectEx (_ghDebug, pAdd, sizeof (unsigned char), PAGE_READWRITE, & dwOldFlg); // modified object code WriteProcessMemory (_ghDebug, pAdd, & code, sizeof (unsigned char ), & dwread); // Restore the page number protection attribute VirtualProtectex (_GHDebug, padd, sizeof (unsigned char), dwoldflg, & dwoldflg); Return B;}
When setting breakpoints, you must save the original code so that you can restore the code when you recover breakpoints. General usage is: set breakpoint m_code = setbreakpoint (pFunadd, 0xcc); recovery breakpoint: setbreakpoint (pfunadd, m_code); remember, each function entry address code can be different, you should save each breakpoint address A original code, there will be no errors when recovering.
Ok, now the breakpoint has been set in the target program. When the target program is called, a debug interrupt information notification debugger will be generated. We have to write our debug interrupt programs in the debugger. Write debug interrupt handler
When the debugger is interrupted, an Exception_Debug_event information notification debugger is generated. The Exception_Debug_info structure will be populated at the same time.
typedef struct _EXCEPTION_DEBUG_INFO {EXCEPTION_RECORD ExceptionRecord; DWORD dwFirstChance;} EXCEPTION_DEBUG_INFO, * LPEXCEPTION_DEBUG_INFO; typedef struct _EXCEPTION_RECORD {DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD * ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; ULONG_PTR ExceptionInformation [EXCEPTION_MAXIMUM_PARAMETERS];} EXCEPTION_RECORD, * PEXCEPTION_RECORD;
In this structure, we are more interested in generating interrupt address exceptionAddress and generating information code ExceptionCode. Information code related to our task in the information code is:
Exception_breakpoint: breakpoint interrupt information code eXception_single_step: single-step interrupt information code
The breakpoint interruption is due to we produced when we set the breakpoint 0xcc code running. Since the interrupt is generated, we must write the original code back to the debugged program to continue running. However, once the code is written back to the target program so that when the target program calls the function again, we will only achieve a monitoring. Therefore, after we must write the original code back to the debugged procedure, we should let the debugged program have been single-step, and then generate a single-step interrupt debugging information. In single-step interrupt processing, we will write the 0xcc code to the entry address of the function, so that it can guarantee that an interrupt is generated again.
First, we must prepare for the starting line ID and thread handles before interrupting processing. To manage single-step interrupt processing, we must also maintain a process based on thread-based single-step addresses, which allows the debugged program to have multi-threaded features. - We cannot guarantee that the single-step runtime is not interrupted by other threads of the process.
/ / We use a MAP to manage the relationship between thread ID and thread handles // simultaneously use a MAP management function address and breakpoint TypedEf Map
DWORD BUF [4]; // Take 4 parameters readprocessMemory (_GHDebug, (void *) (Context.esp 4), & buf, sizeof (buf), & dwread;
Then BUF [0] is the first parameter, BUF [1] is the second parameter. . . Note, when the function is called in Funa (int A, char * p, openfilename * pof) function, BUF [0] = a, buf [1] = P Here BUF [1] is the pointer of P instead of P, if you I hope to access the contents of P, you must obtain the contents of P again through the ReadProcessMemory function. This must also be the same for structural pointers:
// Get the contents of P: CHAR PBUF [256]; ReadProcessMemory (_GHDebug, (Void *) (BUF [1]), & PBUF, SIZEOF (PBUF), & DWREAD); // Get POF content: OpenFileName OfreadProcessMemory (_GHDebug, (VOID *), & OF, OF, SIZEOF (OF), & DWREAD; if there is a pointer in the structure, the content of the pointer must be read in the same way as the content of P is debugged. The memory of the program. In general, you must realize that all content of the monitoring target program is a memory read operation of the target process, which is the memory address of the target process, not the address of the debug process. Obviously, when the debugging process generates an interrupt information in the function entry, the debugger can only get the function's input parameters, and cannot get the output parameters we want and return value! In order to achieve our goal, we must generate interrupts, acquire the output parameters and return values of the function when the function call is completed. When processing a function inlet interrupt, a breakpoint of the return address of the function must be set. Thus, when the function returns, the output parameter of the function and the return value are returned. Refer to the source code of the appendix for the implementation of the appendix.
You can write your own simple debug monitoring programs through the source code of Appendix. Of course, there are several problems because I have not explained here. One is the process of the function returns a breakpoint. For example, Try, Catch's processing must be redesigned with the structure of return_fun_stack, considering that some defect processing can still solve this problem. Another problem is that there is no relationship with the entrance breakpoint of the function and the return breakpoint. This problem is better resolved, just redesign RETURN_FUN, FUN_BREAK_MAP and other structures can associate them. Since I am here as long as it is to analyze how to implement interrupt debugging processing, these perfect procedures work by readers to track.
About Win9x system
The careful readers can find a problem in the setbreakpoint function, that is, the entry address of the function cannot be greater than 0x80000000. In this way, we know that the space of 0x80000000 or more is a systematic space, we generally cannot modify the procedures for these spaces, otherwise it will affect the system's work. In the NT environment, all DLLs are loaded under 0x80000000, and the code to modify the 0x80000000 will not have an impact on other processes. Therefore, all DLL functions can be monitored in NT. However, under Win9X, kernel32.dll, user32.dll, gdi32.dll, etc., is loaded into spaces of 0x80000000 or more, modifying the code for these spaces to destroy the system. So, do you not monitor the function of these DLL modules under 9x?
Indeed, monitoring of setting breakpoints at the entry at the Win9X platform cannot be utilized. We must implement this function with additional methods. In the previous discussion, the method of modifying the module import table via the API Hook can implement the entry of the API to modify the inlet of your monitoring program, or monitor functionality. If the API Hook is used, it must be known that the function prototype must be written for each function, and the flexibility is restricted. And our goal is whether there is how many DLLs, no matter how many export functions in DLL, we can achieve our monitoring function without modifying our procedure. So, the API Hook cannot complete our goal, but we can use the modified solution to implement the target. First, modify the import table, point the function's call address to our monitoring code, in the monitor code, we don't have to program the function, just simply call JMP XXXX. Then, when you set a breakpoint, it is not set in the entry point of the function, but is set on our monitor code. Thus, when our module calls the system API function, monitoring is achieved. The principle of modification is as shown in the figure, suppose our monitoring code is 0x20000000 space of the target process. While analyzing the DLL export table, the address of the export table function will be calculated, set to JMP XXXX in the monitor code. Code. This way we are writing to the address of the monitor code when modifying the Import Table of the EXE module. When the target program calls the MessageBox function, the program will first jump to the monitor code to perform the JMP instruction to the USER32.DLL MessageBox entry address. After doing so, we want to monitor the call of the Messagebox function, just set the breakpoint at the 0x200000000 of the monitoring code, it has reached the purpose of monitoring. Limited to the reason, it will not be discussed here.
Extended application