分享
 
 
 

《Undocumented Windows 2000 Secrets》翻译 --- 第六章(6)

王朝system·作者佚名  2006-01-31
窄屏简体版  字體: |||超大  

第六章 在用户模式下调用内核API函数

翻译:Kendiv( fcczj@263.net )

更新:Saturday, May 14, 2005

声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。

实现内核API 的Thunks

其实,可替代简单内核API函数的基本框架已经存在。我称其为“Thunks”,这是Windows行话中常见的一个术语,它代表一小段代码,这段代码是一个前端函数,为在系统的不同空间中实现的函数提供服务。另一个常见的术语是“代理”,不过它与微软组件对象模型(COM)紧密相关,因此,如果在这里使用它可能会引起混淆。让我们先从两个非常简单的Windows 2000内存管理函数开始,这两个函数是我在开发w2k_call.dll模块时的主要实验对象,它们是:MmGetPhysicalAddress()和MmIsAddressValid()。列表6-24展示了在w2kCall64()和w2kCall08()的帮助下如何实现它们的Thunks。为了避免和原始的目标函数冲突,我在所有的Thunks名称前增加了一个下划线作为前缀。

PHYSICAL_ADDRESS WINAPI

_MmGetPhysicalAddress (PVOID BaseAddress)

{

PHYSICAL_ADDRESS pa;

pa.QuadPart = w2kCall64 (0, "MmGetPhysicalAddress", FALSE,

4, BaseAddress);

return pa;

}

// -----------------------------------------------------------------

BOOLEAN WINAPI

_MmIsAddressValid (PVOID VirtualAddress)

{

return w2kCall08 (FALSE, "MmIsAddressValid", FALSE,

4, VirtualAddress);

}

列表6-24. MmGetPhysicalAddress()和MmIsAddressValid()的Thunks示列

MmGetPhysicalAddress()接受一个32位的线性地址,返回一个64位的PHYSICAL_ADDRESS结构,该结构只是LARGE_INTEGER结构的一个别名。因此,该函数的Thunks需要调用w2kCall64(),这意味着将会向参数堆栈中传入四个字节的参数,而BaseAddress则作为Thunks的参数出现。在发生严重的IOCTL错误时,默认的返回值为0,这也是原始函数返回的出错值。由于MmGetPhysicalAddress()使用__stdcall约定,因此,fFastCall必须设为FALSE。MmIsAddressValid()的Thunks的实现方式与之类似,不同之处仅在于SpyCallEx()返回的第八个最低有效位,该位对应一个BOOLEAN数据类型。默认的返回值为FALSE,这是一种预防性的选择。MmIsAddressValid()的典型应用是在访问内存前调用它,以避免潜在的页错误(page fault)。所以,不能假定函数的实际返回值为TRUE,因为一个IOCTL错误将增大出现蓝屏的风险。

现在,让我们看一下在这一框架下导出变量是如何被访问的。列表6-25给出了两个Thunks:_NtBulidNumber()和_KeServiceDescriptorTable()。NtBulidNumber是ntoskrnl.exe导出的一个16位的WORD类型,因此,对应的w2k_call.dll接口函数为w2kCopy16()。在发生错误是,该Thunks返回0(如果你知道更合适的值,请告诉我J)。_KeServiceDescriptorTable()稍微有些不同,因为ntoskrnl.exe导出的KeServiceDescriptorTable所指向的结构体大于64位。此时,最好的选择就是返回KeServiceDescriptorTable自身的地址。因此,该Thunks将使用w2kCopyEP(),列表6-23给出了该函数。

WORD WINAPI

_NtBuildNumber (VOID)

{

return w2kCopy16 (0, "NtBuildNumber");

}

// -----------------------------------------------------------------

PSERVICE_DESCRIPTOR_TABLE WINAPI

_KeServiceDescriptorTable (VOID)

{

return w2kCopyEP (NULL, "KeServiceDescriptorTable");

}

列表6-25. NtBulidNumber和KeServiceDescriptorTable的Thunks

你可以想象当我发现这些Thunks居然真的可用时我是多么兴奋!当时我想:我要尝试调用一些最低层的函数---这些函数都与低层硬件绑定在一起,用于进行读写I/O端口等操作。很幸运,我先前设计的SpyModuleSymbolEx()函数(参见列表6-11)允许解析任何系统模块的导出符号,这包括内核模式的驱动程序。我的下一个任务就是调用Windows 2000硬件抽象层(Hardware Abstraction Layer,HAL)的导出函数。在检查完hal.dll导出节(export section)中的所有符号后,我决定尝试两个较简单的函数以保证可以和底层硬件直接对话:HalMakeBeep()和HalQueryRealTimeClock()。通过可编程的定时器以及并行I/O(Parallel I/O,PIO,其I/O地址为0x0042、0x0043和0x0061)可以控制PC喇叭的发声,因此HalMakeBeep()是测试与硬件相关的函数的Thunk的理想候选人。

列表6-26给出了_HalMakeBeep() Thunk的实现代码,得益于w2kCall08()辅助函数,实现代码非常简单。HalMakeBeep()可以根据所要求的强度使PC喇叭发出蜂鸣声。如果强度参数为0,喇叭将停止发声。如果传入的强度值有效,则函数返回TRUE。确切的说,0或者大于18的强度值都是有效的。这里要注意的是,在调用w2kCall08()时指定的字符串包含目标模块的名字,这里的目标模块为hal.dll。列表6-24和列表6-25中均未指定模块,因为它们所引用的函数均由ntoskrnl.exe导出,而ntoskrnl.exe是默认的模块。

尽管HalMakeBeep()是一个非常简单的函数,我还是非常非常高兴得看到_HalMakeBeep()终于可以工作了。PC喇叭可以按我的要求发出声音了!而这时在Windows 2000而不是DOS下实现的,这证明了Win32应用程序可以调用HAL函数(这些函数直接访问硬件)。我将我在DOS下编写的Beep Sequencer移植到了Windows 2000中,列表6-27给出了其代码。w2kBeep()可发出一个指定强度和持续时间的单音节。w2kBeepEx()使用一组强度值/持续时间并顺序的演奏它们,直到遇到0强度。这两个函数均有w2k_call.dll导出。可能你会使用它们为Win32程序增加DOS风格的背景音乐。

BOOLEAN WINAPI

_HalMakeBeep (DWORD Pitch)

{

return w2kCall08 (FALSE, "hal.dll!HalMakeBeep", FALSE,

4, Pitch);

}

列表6-26. HalMakeBeep()的Thunks

BOOL WINAPI

w2kBeep (DWORD dDuration,

DWORD dPitch)

{

BOOL fOk = TRUE;

if (!_HalMakeBeep (dPitch)) fOk = FALSE;

Sleep (dDuration);

if (!_HalMakeBeep (0 )) fOk = FALSE;

return fOk;

}

// -----------------------------------------------------------------

BOOL WINAPI

w2kBeepEx (DWORD dData,

...)

{

PDWORD pdData;

BOOL fOk = TRUE;

for (pdData = &dData; pdData [0]; pdData += 2)

{

if (!w2kBeep (pdData [0], pdData [1])) fOk = FALSE;

}

return fOk;

}

列表6-27. 一个简单的Beep音序程序

接下来,我将尝试更有用的函数,如HalQueryRealTimeClock()。我记得在一个DOS程序中访问主板上的真实时钟(real-time clock)曾经是很困难的。这需要读/写硬件上的一对I/O端口。列表6-28给出了HalQueryRealTimeClock()及其兄弟HalSetRealTimeClock()的Thunks,同时还给出了这两个函数所处理的TIME_FIELDS结构。在ntddk.h中定义了TIME_FIELDS结构。

typedef struct _TIME_FIELDS {

CSHORT Year;

CSHORT Month;

CSHORT Day;

CSHORT Hour;

CSHORT Minute;

CSHORT Second;

CSHORT Milliseconds;

CSHORT Weekday;

} TIME_FIELDS, *PTIME_FIELDS;

// -----------------------------------------------------------------

VOID WINAPI

_HalQueryRealTimeClock (PTIME_FIELDS TimeFields)

{

w2kCallV (NULL, "hal.dll!HalQueryRealTimeClock", FALSE,

4, TimeFields);

return;

}

// -----------------------------------------------------------------

VOID WINAPI

_HalSetRealTimeClock (PTIME_FIELDS TimeFields)

{

w2kCallV (NULL, "hal.dll!HalSetRealTimeClock", FALSE,

4, TimeFields);

return;

}

列表6-28. HalQueryRealTimeClock()和HalSetRealTimeClock()的Thunks

列表6-29提供了一个使用_HalQueryRealTimeClock()的典型程序,该程序在控制台窗口中显示当前日期和时间。

VOID WINAPI DisplayTime(void)

{

TIME_FIELDS tf;

_HalQueryRealTimeClock(&tf);

printf(L"\r\nData/Time: %02hd-%02hd-%04hd%02hd:%02hd:%02hd\r\n",

tf.Month, tf.Day, tf.Year,

tf.Hour, tf.Minute, tf.Second);

return;

}

列表6-29. 显示当前日期和时间

尽管内核调用接口可以工作,但仍然有些遗憾。很多年来,我们都被告知Windows NT/2000是一个安全的操作系统,在那里应用程序是不能为所欲为的。大多数Win32开发人员都已远离硬件了。经验更丰富些的NT开发人员至少知道如何通过ntdll.dll调用Native API函数。现在,使用本书提供的DLL,所有的Win32开发人员都可以调用任意的内核函数,就像调用Win32 API函数那样。这是Windows 2000内核的一个安全漏洞吗?不,这并不是。只有让应用程序什么都不能访问的操作系统才是100%安全的,不过这样的操作系统也没有什么实际价值。一旦我们可通过某种途径影响系统,那么系统将变得易于受到攻击。一但操作系统供应商允许第三方开发人员向系统中添加组件,就有可能获取通向内核的桥梁,就像w2k_spy.sys/w2k_call.dll。不存在100%安全的操作系统,除非该系统不与它周围的环境进行交互。

数据访问的支持函数

我向w2k_call.dll中加入了几打内核API函数的Thunk。例如,由Windows 2000运行时库导出的所有字符串管理函数都可通过w2k_call.dll来调用。不过,你在实验这些预定义的Thunks或者你自己增加的thunks,你会发现从用户模式调用内核API函数与调用普通的Win32函数还是有些不同。这里介绍的内核调用接口的简易性掩盖了调用程序仍然运行于用户模式、仅有有限的特权这一事实。例如,程序调用的内核函数可能会一个返回指向UNICODE_STRING结构的指针,而这一指针很有可能指向的是内核内存空间。任何读取该字符串的尝试都将引发一个异常,并导致程序终止,这是由于指令试图读取的地址是被禁止访问的地址。为了解决这一问题,我给w2k_call.dll增加了支持函数,用来更容易的访问内核API调用中大多数常见的数据类型。

列表6-30给出的w2kSpyRead()函数是一个通用函数,它可以将任意的内存数据块复制到调用者提供的缓冲区中。该函数依赖于w2k_spy.sys提供的IOCTL函数-----SPY_IO_MEMORY_BLOCK(在第四章里简要的介绍过该函数)。可以使用w2kSpyRead()读取位于内核空间中的结构体的任意部分。这里要特别注意的是,如果提供的内存块的地址范围无效,则对w2kSpyRead()的调用将失败。这里的无效指的是没有与指定地址相关的物理内存或页面文件。w2kSpyClone()是w2kSpyRead()的增强版本,w2kSpyClone()可以自动分配大小适当的缓冲区,然后将内核数据复制到该缓冲区中。

BOOL WINAPI w2kSpyRead (PVOID pBuffer,

PVOID pAddress,

DWORD dBytes)

{

SPY_MEMORY_BLOCK smb;

BOOL fOk = FALSE;

if ((pBuffer != NULL) && (pAddress != NULL) && dBytes)

{

ZeroMemory (pBuffer, dBytes);

smb.pAddress = pAddress;

smb.dBytes = dBytes;

fOk = w2kSpyControl (SPY_IO_MEMORY_BLOCK,

&smb, SPY_MEMORY_BLOCK_,

pBuffer, dBytes);

}

return fOk;

}

// -----------------------------------------------------------------

PVOID WINAPI w2kSpyClone (PVOID pAddress,

DWORD dBytes)

{

PVOID pBuffer = NULL;

if ((pAddress != NULL) && dBytes &&

((pBuffer = w2kMemoryCreate (dBytes)) != NULL) &&

(!w2kSpyRead (pBuffer, pAddress, dBytes)))

{

pBuffer = w2kMemoryDestroy (pBuffer);

}

return pBuffer;

}

列表6-30. 通用数据访问函数

读取字符串需要稍微复杂些。内核组件使用的常见字符串类型是UNICODE_STRING,该结构由一个字符串缓冲区指针、有关缓冲区大小的信息和当前字符串占用的字节数构成。读取一个UNICODE_STRING结构通常需要两步来完成。第一,必须复制UNICODE_STRING结构以确定字符串缓冲区的大小和基地址。第二,读取实际的字符串数据。为了简化这一常见任务,w2k_call.dll提供了一组函数(见列表6-31)来完成这一工作。

w2kStringAnsi()和w2kStringUnicode()分别用于分配并初始化空的ANSI_STRING和UNICODE_STRING结构,在初始化后的结构中将包含指定大小的字符串缓冲区。为了简单起见,字符串的头部和缓冲区使用一个内存块。这些结构体可用于字符串复制,w2kStringClone()给出的一个范例。w2kStringClone()可在用户内存空间中创建指定UNICODE_STRING结构的一个精确副本。副本的MaximumLength通常与原始结构相同,除非原始字符串结构中的参数不一致。例如,如果MaximumLength小于或等于Length成员,则MaximumLength将被视为无效,实际的最大长度将为Length+2。不过,副本的MaximumLength永远不会小于原始的MaximumLength。

PANSI_STRING WINAPI w2kStringAnsi (DWORD dSize)

{

PANSI_STRING pasData = NULL;

if ((pasData = w2kMemoryCreate (ANSI_STRING_ + dSize))

!= NULL)

{

pasData->Length = 0;

pasData->MaximumLength = (WORD) dSize;

pasData->Buffer = PTR_ADD (pasData, ANSI_STRING_);

if (dSize) pasData->Buffer [0] = 0;

}

return pasData;

}

// -----------------------------------------------------------------

PUNICODE_STRING WINAPI w2kStringUnicode (DWORD dSize)

{

DWORD dSize1 = dSize * WORD_;

PUNICODE_STRING pusData = NULL;

if ((pusData = w2kMemoryCreate (UNICODE_STRING_ + dSize1))

!= NULL)

{

pusData->Length = 0;

pusData->MaximumLength = (WORD) dSize1;

pusData->Buffer = PTR_ADD (pusData, UNICODE_STRING_);

if (dSize) pusData->Buffer [0] = 0;

}

return pusData;

}

// -----------------------------------------------------------------

PUNICODE_STRING WINAPI w2kStringClone (PUNICODE_STRING pusSource)

{

DWORD dSize;

UNICODE_STRING usCopy;

PUNICODE_STRING pusData = NULL;

if (w2kSpyRead (&usCopy, pusSource, UNICODE_STRING_))

{

dSize = max (usCopy.Length + WORD_,

usCopy.MaximumLength) / WORD_;

if (((pusData = w2kStringUnicode (dSize)) != NULL) &&

usCopy.Length && (usCopy.Buffer != NULL))

{

if (w2kSpyRead (pusData->Buffer, usCopy.Buffer,

usCopy.Length))

{

pusData->Length = usCopy.Length;

pusData->Buffer [usCopy.Length / WORD_] = 0;

}

else

{

pusData = w2kMemoryDestroy (pusData);

}

}

}

return pusData;

}

列表6-31. 字符串管理函数

将内核字符串复制到应用程序内存空间的另一方法是使用内核运行时函数。例如,你可以使用w2k_call.dll提供的_RtlInitUnicodeString()和_RtlCopyUnicodeString() Thunks来完成这一任务。不过,调用w2kStringClone()将更容易些,因为该函数可以自动分配复制字符串所需的内存。

…………待续……….

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有