接触RING 0之前,以为得学很多东西,一大堆驱动开发的知识。不过后来想了想,驱动壳等其他不直接访问硬件的程序为了兼容性,不可能真的直接访问硬件,也就是那些是基于硬件抽象层之上的,而且大部分使用的还是系统提供的API(RING0下使用的API称为NATIVE API)。事情一下子变简单了,除非你想通过逆向硬件厂商驱动,自己编写优化硬件或者超频程序。
虽然这是纯静态分析,但是我希望通过分析整个驱动,你会理解一些RING0下的机制,并且懂得在动态调试中应该如何下断点定位代码。
在开始之前,感谢rockhard的源代码和已编译好的驱动,这样我就可以不必学习WINDDK的使用了。你可以在下面链接的附件中得到:
http://bbs.pediy.com/showthread.php?s=&threadid=35626 初步实现系统级拦截应用程序取硬盘物理序列号
Rockhard发表上述文章时的目标是通过简单修改REGMON驱动部分的源代码完成拦截应用程序取硬盘物理序列号的功能,难免有不足之处。个人对源代码的不成熟评论并不针对Rockhard。
学习逆向时,我的方法是先看看高级语言代码编译后究竟是怎么样的。或许最后我还是得学习WINDDK的使用,编写代码,编译,反汇编,它会解答一些疑问。下面让我来以源码和反汇编代码对照的形式来说明RING0下的一些机制。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath )
DriverEntry,驱动程序的入口函数,驱动的一些初始化操作,将在这里进行。象RING3那样,程序通过堆栈访问DriverObject和RegistryPath。而在IDA,反汇编后是这样子:“; int __stdcall start(PDRIVER_OBJECT DriverObject,HANDLE Handle)”第二个参数的名字有点不同,不过不重要,我们知道,其实它是一样的。
.text:000105A0 push 7 .text:000105A2 pop ecx .text:000105A3 mov esi, offset s_DeviceHdhook ; "\\Device\\HDHOOK" .text:000105A8 lea edi, [ebp+regnameNt] .text:000105AE push 9 .text:000105B0 rep movsd .text:000105B2 movsw .text:000105B4 pop ecx .text:000105B5 mov esi, offset s_DosdevicesHdh ; "\\DosDevices\\HDHOOK" .text:000105BA lea edi, [ebp+regnameDos] .text:000105C0 push 206B6444h ; Tag .text:000105C5 rep movsd .text:000105C7 movsw .text:000105C9 mov esi, offset s_Start ; "Start" .text:000105CE lea edi, [ebp+SourceString] .text:000105D1 movsd .text:000105D2 movsd .text:000105D3 movsd .text:000105D4 mov esi, [ebp+Handle] .text:000105D7 movzx eax, word ptr [esi] .text:000105DA inc eax .text:000105DB inc eax .text:000105DC push eax ; NumberOfBytes .text:000105DD push 1 ; PoolType .text:000105DF call ds:ExAllocatePoolWithTag
前面一大堆代码,都是因为下面3个局部变量的定义,编译器会生成一段代码,先将这些字符移进堆栈,然后再使用。
WCHAR deviceNameBuffer[] = L"\\Device\\"DRIVER_NAME; WCHAR deviceLinkBuffer[] = L"\\DosDevices\\"DRIVER_NAME; WCHAR startValueBuffer[] = L"Start";
从逆向的角度来看,象这种静态字符变量,如果换成全局变量,或者可以获得更高的运行效率和更小的程序。值得注意的是NATIVE API的调用,第一个参数入栈后的几行代码,仍然是局部变量的初始化,这编译器让我想起扭曲变形的介绍。000105D4的代码是从堆栈中取得DriverEntry的第二参数,它是一个UNICODE_STRING结构,从MSDN中搜索到的说明(如无特别说明,一切资料都是从MSDN中搜索得到):
typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING *PUNICODE_STRING;
从000105D7处的代码来看,该结构在反汇编是:
WORD Length WORD MaximumLength DWORD Buffer(指向字符的指针)
通过逆向RtlInitUnicodeString,可知上述结构中的 MaximumLength成员,其实就相当于Length + sizeof(UNICODE_NULL)。关于调用ExAllocatePoolWithTag的第三参数PoolType:
typedef enum _POOL_TYPE { NonPagedPool, PagedPool, NonPagedPoolMustSucceed, DontUseThisType, NonPagedPoolCacheAligned, PagedPoolCacheAligned, NonPagedPoolCacheAlignedMustS } POOL_TYPE;
这里看起来跟PUSH 1好象没有什么关系,现在看看源代码:
registryPath.Buffer = ExAllocatePool( PagedPool, RegistryPath->Length + sizeof(UNICODE_NULL));
这里使用的参数是PagedPool,对于enum类型的定义,每个成员代表的数字是从0开始递增。PagedPool刚好在第二个位置,所以它是1。源代码使用的ExAllocatePool,反汇编后的代码使用的是ExAllocatePoolWithTag。MSDN的说法是ExAllocatePool已经被舍弃了,取代的是ExAllocatePoolWithTag(以标识申请内存)。调用API之后是对返回结果的判断:
if (!registryPath.Buffer) { return STATUS_INSUFFICIENT_RESOURCES; }
它对应的汇编代码是
.text:000105E5 mov edi, eax .text:000105E7 xor ebx, ebx .text:000105E9 cmp edi, ebx .text:000105EB mov [ebp+Path], edi .text:000105EE jnz short loc_105FA .text:000105EE .text:000105F0 mov eax, 0C000009Ah .text:000105F5 jmp loc_1075A
注意到000105F0,STATUS_INSUFFICIENT_RESOURCES=0C000009Ah,仍然使用EAX作为返回参数。接下来的源代码,终于看到编译优化了:
registryPath.Length = RegistryPath->Length + sizeof(UNICODE_NULL); registryPath.MaximumLength = registryPath.Length;
RtlZeroMemory( registryPath.Buffer, registryPath.Length );
RtlMoveMemory( registryPath.Buffer, RegistryPath->Buffer, RegistryPath->Length );
RtlZeroMemory( ¶mTable[0], sizeof(paramTable));
这里本来要调用2个API的。对于前一个RtlZeroMemory调用,编译器使用自己的代码来代替它:
.text:000105FA mov ax, [esi] .text:000105FD add ax, 2 .text:00010601 movzx ecx, ax .text:00010604 mov edx, ecx .text:00010606 xor eax, eax .text:00010608 shr ecx, 2 .text:0001060B rep stosd .text:0001060D mov ecx, edx .text:0001060F and ecx, 3 .text:00010612 rep stosb
先是4字节对齐的填0,然后使用AND取得除以4后的余数,继续填0。第二次调用,编译器同样使用自己的代码来实现这个功能:
.text:00010624 add esp, 0Ch .text:00010627 xor eax, eax .text:00010629 lea edi, [ebp+QueryTable] .text:0001062F push 0Eh .text:00010631 pop ecx .text:00010632 rep stosd
现在让我们再来看看系统中RtlZeroMemory的代码:
00402520: 57 PUSH EDI 00402521: 8B7C2408 MOV EDI, [ESP+08] 00402525: 8B4C240C MOV ECX, [ESP+0C] 00402529: 33C0 XOR EAX, EAX 0040252B: FC CLD ;这一句用来保证DF=0 0040252C: 8BD1 MOV EDX, ECX 0040252E: 83E203 AND EDX, 00000003 00402531: C1E902 SHR ECX, 02 00402534: F3AB REP STOSD 00402536: 0BCA OR ECX, EDX 00402538: 7504 JNZ 40253E;非4字节对齐,继续填0 0040253A: 5F POP EDI 0040253B: C20800 RETN 0008 0040253E: F3AA REP STOSB 00402540: 5F POP EDI 00402541: C20800 RETN 0008
1 2 下一页 |