标签:des style http io ar os 使用 sp for
本文首先是由下面事件促使的,我们观察到 OutputDebugString() 在管理员和非管理员用户试图一起工作或游戏时并不总是能可靠地工作(至少在 Win2000 上)。我们怀疑是一些相关的内核对象的权限问题,此间涉略了相当多不得不写下来的信息。
请注意,虽然我们使用了“调试器”这一术语,但不是从调试 API 的意义上来使用的:并没有“单步运行”、“断点”或者“附着到进程”等能够在 MS Visual C 或者一些真正的交互开发环境中找到的东西。从某种意义上来说,不论什么实现了协议的程序都是“调试器”。可能是一个很小的命令行工具,或者像来自于 SysInternals 那帮聪明的家伙们的 DebugView 那样的高级货。
<windows.h> 文件声明了 OutputDebugString() 函数的两个版本号 - 一个用于 ASCII,一个用于 Unicode - 不像绝大多数 Win32 API 一样,原始版本号是 ASCII。而大多数的 Win32 API 的原始版本号是 Unicode。
使用一个 NULL 结尾的字符串缓冲区简单调用 OutputDebugString() 将导致信息出如今调试器中,假设有调试器的话。构建一条信息并发送之的通经常使使用方法是:
sprintf(msgbuf, "Cannot open file %s [err=%ld]/n", fname, GetLastError()); OutputDebugString(msgbuf);
只是在实际环境中我们中的不少人会创建一个前端函数,以同意我们使用 printf 风格的格式化。以下的 odprintf() 函数格式化字符串,确保结尾有一个合适的回车换行(删除原来的行结尾),而且发送信息到调试器。
#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); }
于是在代码中使用它就非常easy:
... odprintf("Cannot open file %s [err=%ld]", fname, GetLastError()); ...
在应用程序和调试器之间传递数据是通过一个 4KB 大小的共享内存块完毕的,并有一个相互排斥量和两个事件对象用来保护对他的訪问。以下就是相关的四个内核对象:
对象名称 对象类型 DBWinMutex Mutex DBWIN_BUFFER Section (共享内存) DBWIN_BUFFER_READY Event DBWIN_DATA_READY Event
相互排斥量通常一直保留在系统中,其它三个对象仅当调试器要接收信息才出现。其实 - 假设一个调试器发现后三个对象已经存在,它会拒绝执行。
当 DBWIN_BUFFER 出现时,会被组织成下面结构。进程 ID 显示信息的来源,字符串数据填充这 4K 的剩余部分。依照约定,信息的末尾总是包含一个 NULL 字节。
struct dbwin_buffer { DWORD dwProcessId; char data[4096-sizeof(DWORD)]; };
当 OutputDebugString() 被应用调用时,它运行下面步骤。注意在任何位置的错误都将放弃整个事情,调试请求被觉得是什么也不做(不会发送字符串)。
在调试器端会简单一点。相互排斥量根本不须要,假设事件对象和/或共享内存对象已经存在,则假定其它调试器已经在执行。系统中随意时刻仅仅能存在一个调试器。
这使我们觉得这决不是一种低消耗的发送信息的方法,应用程序的执行速度会受到调试器的左右。
我们发现 OutputDebugString() 有时不可靠已经好几年了,并且我们十分不解为什么微软这么长时间也没把它搞好。奇怪的是,问题总是环绕着 DBWinMutex 对象出现,这就须要我们察看许可系统以找出为什么会这么麻烦。
相互排斥量对象会一直存活着直到使用它的最后一个程序关闭其句柄,故而它能在初始创建它的应用程序退出后保留相当长的时间。由于此对象被广泛地共享,所以它必须被赋予明白的许能够同意不论什么人使用它。其实,“缺省”许可差点儿从不适用,这一问题被计为在 NT 3.51 和 NT 4.0 中我们观察到的第一个问题。
当时的修正方法是使用一个广泛开放的 DACL 创建相互排斥量,以此来同意不论什么人訪问它,可是看样子在 Win2000 里这些许可被加强了。表面上它看起来是正确的,就像我们在下表中看到的:
SYSTEM MUTEX_ALL_ACCESS Administrators MUTEX_ALL_ACCESS Everybody SYNCHRONIZE | READ_CONTROL | MUTEX_QUERY_STATE
希望发送调试信息的应用仅仅须要等待和获取该相互排斥量的能力,也即体现为拥有 SYNCHRONIZE 权限。上列的许可对于全部參与的用户都是全然正确的。
只是假设有人观察 CreateMutex() 在对象已经存在时的行为,就会发现奇怪的事情。在这样的情况下,Win32 的表现就好像我们进行了例如以下调用:
OpenMutex(MUTEX_ALL_ACCESS, FALSE, "DBWinMutex");
虽然我们确实仅仅须要 SYNCHRONIZE 訪问,但它还是假定调用者要做不论什么事情(MUTEX_ALL_ACCESS)。由于非管理员没有这些权限 - 仅有上列的少许 - 相互排斥量不能被打开或者获取,于是 OutputDebugString() 不做不论什么事情就悄悄地返回了。
甚至将全部的软件开发都以管理员来执行也不是一个完整的修正方法:假设存在其它的用户(比如服务)以非管理员执行而许可配置不对,它们的调试信息将会丢失。
我们感觉真正的修正须要微软为 CreateMutex() 加入一个參数 - 假设对象已经存在时用于隐含的 OpenMutex() 调用的訪问掩码。或许某天我们会看到一个 CreateMutexEx(),但在此期间我们必须採用另外的方法。代之以,当对象已经存活于内存中时我们将硬性改变其上的许可配置。
这须要调用 SetKernelObjectSecurity(),下列程序片断展示一个程序怎样才干打开相互排斥量并安装一个新的 DACL。此 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);
...
这一方法明白地走向了正确的道路,但我们还须要找一个地方来放置此逻辑。把它放在一个一经请求即执行的小程序中是能够的,可是看起来它有可能被中断。我们的办法是写一个 Win32 服务来干这件事情。
我们的 dbmutex 工具完毕的就是这一工作:它在系统引导时启动,打开或者创建相互排斥量,然后设置对象的安全性以同意广泛的訪问。然后休眠直到系统关闭,在此过程中保持相互排斥量的打开状态。它不消耗不论什么 CPU 时间。
我们花了非常多时间使用 IDA Pro 深入到 Windows 2000 KERNEL32.DLL 的实现中,我们觉得,对于它在更精确的基础上究竟是怎样工作的已经有了良好的掌握。在这儿我们给出 OutputDebugString() 函数的伪代码(我们没有编译过它),以及创建相互排斥量的函数。
我们有益略去了大多数的错误检查:假设事情变糟了,它将释放全部已分配的资源并退出,就像没有调试器存在一样。目的是展示一般行为而不是对代码的完整的逆向project。
“setup” 函数 - 名字是我们起的 - 创建相互排斥量或者在已经存在时打开它。经过一些努力来设置相互排斥量对象的安全性以使不论什么人都能用它,虽然我们会看到事实上并没有全然正确地得到它。
一些人可能会感到这是一个安全性问题,事实上并非。非管理员用户确实拥有适当使用 OutputDebugString() 的全部权限,只是因为“请求比所需很多其它权限”这一常见问题,一个合理的请求因形成了错误的形态而被拒绝了。
但并不像大部分的这样的问题那样,这并不是是有意的。大多数的错误是开发者显式请求了很多其它(如“MUTEX_ALL_ACCESS”),而这次的掩码是由 CreateMutex() 的行为隐含的。这使得假设 Win32 API 不做修改的话更加难于避免。
---
当分析 KERNEL32.DLL 中的 OutputDebugStringA() 时,非管理员怎样可以有可能去削弱系统变得明显起来。一旦得到相互排斥量,一个要发送调试信息的应用会等待 DBWIN_BUFFER_READY 事件对象就绪最多十秒钟,假设超时则放弃。这看起来是一个慎重的防范措施,假设调试系统忙的话,用以避免被饿死。
但在更早的步骤里,等待相互排斥量,没有这种超时设定。假设系统中的不论什么进程 - 包含非特权进程 - 能够以请求 SYNCHRONIZE 权限打开此相互排斥量,而且不释放它,全部其它试图获取此相互排斥量的进程将会无限停止完蛋。
我们的研究表明,全部类型的程序都会发送任意的调试信息(比如,MusicMatch Jukebox 就有一个唠唠叨叨的键盘钩子),这些线程通过非常少的几行代码就能停止住。没有必要停止整个程序 - 可能还有其它的线程 - 但在实际中,开发者不计划使用 OutputDebugString() 将会是一条拒绝服务之路(译者注:此句没有全然明确,请參看原文)。
---
最奇怪的是,我们发现 OutputDebugString() 并不是一个天然的 Unicode 函数。大多数的 Win32 API 具有“真正的”使用了 Unicode 的函数(“W” 版本号),假设调用“A”版本号的函数则它们自己主动从 ASCII 转换到 UNICODE。
可是,由于 OutputDebugString 把在内存缓冲区中的数据终于是作为 ASCII 传递到调试器中的,它们具有相反于常规的 A/W 配对。这就暗示了假设要在 Unicode 程序里发送一个快捷信息到调试器,能够通过直接调用 “A” 版本号来实现:
OutputDebugStringA("Got here to place X");
标签:des style http io ar os 使用 sp for
原文地址:http://www.cnblogs.com/yxwkf/p/4085714.html