Strong Win32 developers may Outputdebugstring () You are familiar with API functions. Program Talk to the debugger. It is easier than creating a log file and can be used by all "real" debuggers. The mechanism for applications to talk to the debugger is quite simple, and this article will reveal how the whole thing works. This article is first prompted by the following events. We observe thatOutputdebugstring ()It does not always work reliably (at least on win2000) When administrators and non-administrator users try to work together or play games ). We suspect that it is related to the permission of kernel objects, which involves a lot of information that has to be written down. Please note that although we use the term "Debugger", it is not used in the sense of debugging the API: there is no such thing as "one-step execution", "breakpoint", or "attach to process" that can be found in ms visual c or in real interactive development environments. In a sense, any program that implements the protocol is a "Debugger ". It may be a very small command line tool, or something as advanced as the debugview of the smart guys from sysinternals. Content directory
- Application Usage
- Protocol
- Permission problems
- Implementation Details
- Confused
- Unixwiz.net tool: dbmutex
Application Usage <Windows. h>File declaresOutputdebugstring ()Two versions of the function-one for ASCII and the other for Unicode-unlike the vast majority of Win32 APIs, the original version is ASCII. The original version of most Win32 APIS is Unicode. Simple call using a string buffer ending with nullOutputdebugstring ()This will cause information to appear in the debugger, if any. The common usage of building and sending a piece of information is:
Sprintf (msgbuf, "cannot open file % s [err = % ld] \ n", fname, getlasterror (); outputdebugstring (msgbuf );
However, in the actual environment, many of us will create a front-end function to allow us to use printf-style formatting. The followingOdprintf ()The function is used to format the string, ensure that there is a proper line break at the end (delete the end of the original line), and send the information to the debugger.
# Include <stdio. h> # include <stdarg. h> # include <ctype. h> void _ cdecl odprintf (const char * format ,...) {char Buf [4096], * P = Buf; va_list ARGs; va_start (ARGs, format); P + = _ vsnprintf (p, sizeof Buf-1, format, argS ); va_end (ARGs); While (P> Buf & isspace (P [-1]) * -- p = '\ 0'; * P ++ =' \ R '; * P ++ = '\ n'; * P =' \ 0'; outputdebugstring (BUF );}
ThereforeCodeIt is easy to use in:
... Odprintf ("cannot open file % s [err = % ld]", fname, getlasterror ());...
We have been using this for many years. Protocol Data is transmitted between the application and the debugger through a 4 kb shared memory block, and there is a mutex and two event objects to protect access to them. The following are four kernel objects:
Object Name |
Object Type |
Dbwinmutex |
Mutex |
Dbwin_buffer |
Section (shared memory) |
Dbwin_buffer_ready |
Event |
Dbwin_data_ready |
Event |
Mutex is usually kept in the system, and the other three objects only appear when the debugger wants to receive information. In fact, if a debugger finds that the last three objects already exist, it will refuse to run. When dbwin_buffer appears, it is organized into the following structure. The process ID displays the source of information, and the string data is filled with the remaining 4 K. According to the Conventions, the end of the informationAlwaysContains a NULL byte.
Struct dbwin_buffer {DWORD dwprocessid; char data [4096-sizeof (DWORD)] ;};
WhenOutputdebugstring ()When the application is called, it performs the following steps. Note that errors at any location will discard the entire process and the debugging request will be considered as nothing (no strings will be sent ).
- OpenDbwinmutexAnd wait until we have obtained exclusive access.
- IngDbwin_bufferSegment to memory: If not found, no debugger is running and the entire request is ignored.
- OpenDbwin_buffer_readyAndDbwin_data_readyEvent object. Like shared memory segments, the absence of objects means that no debugger is available.
- WaitDbwin_buffer_readyThe event object is in a signal state, indicating that the memory buffer is no longer occupied. Most of the time, this event object is in a signal state as soon as it is checked, but wait for the buffer to be ready for no more than 10 seconds (timeout will discard the request ).
- Copy the data until the memory buffer is close to 4 kb, and then save the current process ID. Always place a NULL byte to the end of the string.
- SetDbwin_data_readyThe event object tells the debugger that the buffer is ready. The debugger removes it from there.
- Release mutex.
- Close the event object and segment object, but retain the mutex handle for future use.
It is easier on the debugger. Mutex is not required at all. If the event object and/or shared memory object already exist, it is assumed that other debuggers are already running. The system can only have one debugger at any time.
- Create shared memory segments and two event objects. If it fails, exit.
- SetDbwin_buffer_readyEvent object, the application knows that the buffer zone is available.
- WaitDbwin_data_readyThe event object changes to a signal state.
- Extract the string ending with the process ID and null from the memory buffer.
- Go to step 2.
This makes us think that this is by no means a low-consumption Method for sending information, and the running speed of the application will be affected by the debugger. Permission problemsWe found thatOutputdebugstring ()Sometimes unreliableSeveral yearsAnd we have no idea why Microsoft hasn't done it well for so long. The strange thing is that the problem is always aroundDbwinmutexThis requires us to check the license system to find out why it is so troublesome. The mutex object remains alive until the last program that uses it closes its handle, so it can be retained for a long time after the application that initially created it exits. Because this object is widely shared, it must be explicitly authorized to allow anyone to use it. In fact, the "default" license is almost never applicable, and this problem is counted as the first problem we observed in NT 3.51 and NT 4.0. The correction method at that time was to use a widely open DACL to create mutex to allow anyone to access it, but it seems that these licenses have been enhanced in Win2000. On the surface, it looks correct, as we can see in the following table:
System |
Mutex_all_access |
Administrators |
Mutex_all_access |
Everybody |
Synchronize | read_control | mutex_query_state |
Applications that want to send debugging information only need to wait for and obtain the mutex, that is, to haveSynchronizePermission. The permission listed above is completely correct for all participating users. However, if someone observesCreatemutex ()When an object already exists, you will find something strange. In this case, Win32 is like calling the following code:
Openmutex (Mutex_all_access, False, "dbwinmutex ");
Although we only needSynchronizeBut it still assumes that the caller wantsAnything(Mutex_all_access). Because non-administrators do not have these permissions-only a few of the above columns-the mutex cannot be opened or obtainedOutputdebugstring ()Return quietly without doing anything. Even running all software development as an administrator is not a complete correction method: if other users (such as services) are running as non-Administrators, the configuration is not permitted to be correct, their debugging information will be lost. We feel that the real correction requires MicrosoftCreatemutex ()Add a parameter-if the object already exists, it is used for implicitOpenmutex ()The access mask of the call. Maybe one day we will seeCreatemutexex ()But we must use another method during this period. When the object is already in memory, we will change the license configuration on it. This requires callingSetkernelobjectsecurity ()The following program snippets show how a program can open mutex and install a new DACL. This DACL is maintained even after the program exits, As long as any other program maintains a handle with it (this should be a mutex.
... // Open the mutex that we're re going to adjusthandle hmutex =Openmutex(Mutex_all_access, false, "dbwinmutex"); // create security_descriptor with an explicit, empty DACL // that allows full access to everybodysecurity_descriptor SD;Initializesecuritydescriptor(& SD, security_descriptor_revision );Setsecuritydescriptordacl(& SD, // ADDR of SD true, // true = DACL present null ,//... but it's empty (wide open) False); // DACL explicitly set, not defaulted // plug in the new DACLSetkernelobjectsecurity(Hmutex, dacl_security_information, & SD );...
This method clearly follows the correct path, but we still need to find a place to place this logic. It is possible to put it in a small program that runs upon a request, but it seems that it may be interrupted. Our solution is to write a Win32 service to do this. This is what our dbmutex tool does: it starts during system boot, opens or creates mutex, and then sets the object security to allow extensive access. Then sleep until the system is shut down and the mutex is turned on during this process. It does not consume any CPU time. Implementation Details We spent a lot of time using IDA pro to go deep into the implementation of Windows 2000 kernel32.dll. We believe that we have a good grasp of how it works on a more accurate basis. Here we provideOutputdebugstring ()The pseudo code of the function (we have not compiled it) and the function that creates the mutex. We intentionally omitted most of the error checks: if something gets worse, it will release all allocated resources and exit, just as there is no debugger. The purpose is to demonstrate General behaviors rather than complete reverse engineering of the Code. "Setup" function-name is ours-create a mutex or open it if it already exists. After some efforts to set the security of mutex object so that anyone can use it, although we will see that it is not completely correct. • Outputdebugstring.txt Confused Some may feel that this is a security issue, but it is not. Non-administrator userIndeedHave proper useOutputdebugstring ()All permissions, but a reasonable request is rejected due to an incorrect form due to the common problem of "more permissions than required. However, this is not intended as it is in most cases. Most of the errors are that the developer explicitly requests more (such as "mutex_all_access"), and the mask isCreatemutex ()ActionImplicit. This makes it more difficult to avoid Win32 API changes. --- When analyzingOutputdebugstringa ()How can a non-Administrator weaken the system becomes apparent. Once the mutex is obtained, an application that sends debugging information will wait for a maximum of 10 seconds for the dbwin_buffer_ready event object to be ready. If it times out, it will give up. This seems to be a cautious precaution. If the debugging system is busy, it is used to avoid starvation. But in the earlier step, wait for the mutex, and there is no such timeout setting. If any process in the system-including a non-privileged process-can open the mutex with the synchronize permission and do not release it, all other processes that attempt to obtain the mutex will stop the process infinitely. Our research shows that all types of programs send random debugging information (for example, musicmatch jukebox has a choppy keyboard hook ), these threads can be stopped by a few lines of code. There is no need to stop the entire program-there may be other threads-but in reality, developers do not plan to useOutputdebugstring ()This will be a path to denial of service (for more information, see the original article ). --- The most strange thing is that we foundOutputdebugstring ()It is not a natural Unicode function. Most Win32 APIs use Unicode functions ("W" version). If you call functions of the "A" version, they are automatically converted from ASCII to Unicode. However, becauseOutputdebugstringThe data in the memory buffer is finally passed to the debugger as ASCII, which has a pair opposite to the conventional A/W pair. This implies that if you want to send a shortcut message to the debugger In the Unicode program, you can directly call the "A" version to implement it:
Outputdebugstringa ("got here to place X ");
|