A strong Win32 developer may
Outputdebugstring ()API functions are more familiar than debugger, which enables your program to communicate with 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 running", "breakpoint", or "attach to process" that can be found in ms visual c or in real interactive development environments. In a sense, no matter what program implements the Protocol, it 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 folder
- Application Usage
- Protocol
- Permission problems
- Implementation Details
- Confused
- Unixwiz.net tool: dbmutex
Application Usage
<Windows. h>File declaresOutputdebugstring ()The two version numbers of the function-one for ASCII and the other for Unicode-unlike the vast majority of Win32 APIs, the original version number 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 be displayed in the debugger, assuming a debugger is available. It is often used to build and send a message:
sprintf(msgbuf, "Cannot open file %s [err=%ld]/n", fname, GetLastError());OutputDebugString(msgbuf);
In the actual environment, many of us will create a front-end function to agree that we use the printf format. The followingOdprintf ()The Function Format the string to 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);}
So it is very easy to use in the Code:
...odprintf("Cannot open file %s [err=%ld]", fname, GetLastError());...
We have been using this for many years.
Protocol
The data transferred between the application and the debugger is completed through a 4 kb shared memory block, and there is a mutually exclusive volume and two event objects to protect the issue of others. 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 |
The amount of mutual exclusion 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 reject the execution.
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 runs 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 get an exclusive question.
- IngDbwin_bufferSegment to memory: if the request is not found, no debugger is executing 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.
- Released mutually exclusive volumes.
- Close event objects and segment objects, but retain mutually exclusive volumes of handles for future use.
It is easier on the debugger. The amount of mutual exclusion is not required. If the event object and/or shared memory object already exist, it is assumed that other debuggers are already running. There is only one Debugger in the system at will.
- 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 feel that this is by no means a low-consumption Method for sending information, and the execution speed of the application will be affected by the debugger.
Permission problems
We 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.
Objects that are mutually exclusive will survive 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. Since this object is widely shared, it must be given an explicit permission to allow anyone to use it. In fact, the "default" License almost never applies. This problem is counted as the first problem we observed in NT 3.51 and NT 4.0.
At that time, the correction was to use a widely open DACL to create mutually exclusive volumes, so as to allow anyone else to ask 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 mutually exclusive amount, that is, to haveSynchronizePermission. The permission listed above is completely correct for all users and users in the region.
Just suppose someone observesCreatemutex ()When an object already exists, you will find something strange. In this case, Win32 is like the following call:
OpenMutex(MUTEX_ALL_ACCESS, FALSE, "DBWinMutex");
Although we only needSynchronizeThe caller asks, but it still assumes that the caller wants to doNo matter what(Mutex_all_access). Because non-administrators do not have these permissions-only a few of the above columns-the number of mutual exclusion cannot be opened or obtainedOutputdebugstring ()If you do not do anything, you will return it quietly.
Even running all software development as an administrator is not a complete correction method: assuming there are other users (such as services) that are not executed as administrators, the configuration is not permitted, their debugging information will be lost.
We feel that the real correction requires MicrosoftCreatemutex ()Add a sequence number. Assume that the object already exists and is used for implicitOpenmutex ()The called question mask. 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 mutually exclusive volumes and install a new DACL. This DACL is maintained even after the program exits. It only requires another program to maintain a handle that contains the DACL.
... // open the mutex that we‘re going to adjust HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "DBWinMutex"); // create SECURITY_DESCRIPTOR with an explicit, empty DACL // that allows full access to everybody SECURITY_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 DACL SetKernelObjectSecurity(hMutex, DACL_SECURITY_INFORMATION, &sd); ...
This is clearly the right path, but we still need to find a place to place this logic. It can be placed in a small program that is executed 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 has done: It starts during system boot, opens or creates mutually exclusive volumes, and then sets the Object Security to agree to a wide range of referers. Then sleep until the system is shut down and the mutually exclusive volume 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 feel 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 creation of mutually exclusive functions.
It is helpful to skip most error checks: if things get worse, it will release all allocated resources and exit, just as there is no debugger. The purpose is to display General behaviors rather than the complete reverse project of the Code.
"Setup" function-name is ours-create mutually exclusive volume or open it when it already exists. Some efforts have been made to set the security of mutually exclusive objects so that no one can use them, although we will see that they are not completely correct.
• Outputdebugstring.txt
Confused
Some may feel that this is a security issue, but it is not. Non-administrator userIndeedHave proper useOutputdebugstring ()Only because of the common problem that "requests are much more than other required permissions", a reasonable request is rejected because of an incorrect form.
However, this is not intentional, just like most of these problems. Most of the errors are that the developer explicitly requests many others (such as "mutex_all_access"), and this mask is composedCreatemutex ()ActionImplicit. This makes it more difficult to avoid Win32 API modification.
---
When analyzingOutputdebugstringa ()How can a non-Administrator weaken the system becomes apparent. Once mutual exclusion 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 the time-out occurs, the application will give up. This seems to be a careful precaution. If the debugging system is busy, it is used to avoid starvation.
However, in the earlier step, there is no such timeout setting for mutual exclusion. Suppose that no matter what process in the system-including non-privileged processes-can open this mutually exclusive volume with the synchronize permission and do not release it, all other processes that attempt to obtain the mutually exclusive volume will be suspended infinitely.
Our research shows that all types of programs send arbitrary 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 ()It will be a path to denial of service (the Translator's note: This sentence is not completely clear, please refer to 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 number). If you call functions of the "A" version number, they automatically switch from ASCII to Unicode.
HoweverOutputdebugstringFinally, the data in the memory buffer is passed to the debugger as ASCII, which has a pair opposite to A/W. 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 number to implement it:
OutputDebugStringA("Got here to place X");