《天书夜读:从汇编语言到windows内核编程》十二 继续探索windows内核(逆向windows内核)

时间:2022-12-24 01:14:02

1)内核代码反汇编方法:  

  a)打开虚拟机,使用调试模式启动

  b)在windbg中使用-u指令反汇编目标函数

  c)取得函数首址

  d)在Disassembly窗口中输入函数首址从而查看完整反汇编代码。

  如:输入kd> u IoGetDeviceAttachmentBaseRef时返回如下:

1 nt!IoGetDeviceAttachmentBaseRef:
2 804f1972 8bff mov edi,edi
3 804f1974 55 push ebp
4 804f1975 8bec  mov ebp,esp
5 804f1977 53 push ebx
6 804f1978 56 push esi
7 804f1979 6a0a  push 0Ah
8 804f197b 59 pop ecx
9 804f197c ff1528914d80 call dword ptr [nt!_imp_KeAcquireQueuedSpinLock (804d9128)]

  可见函数IoGetDeviceAttachmentBaseRef地址为“804f1972”,在Disassembly中输入“804f1972”可得完整反汇编代码如下:

 1 nt!IoGetDeviceAttachmentBaseRef:
2 804f1972 8bff mov edi,edi
3 804f1974 55 push ebp
4 804f1975 8bec mov ebp,esp
5 804f1977 53 push ebx
6 804f1978 56 push esi
7 804f1979 6a0a push 0Ah
8 804f197b 59 pop ecx
9 804f197c ff1528914d80 call dword ptr [nt!_imp_KeAcquireQueuedSpinLock (804d9128)]
10 804f1982 ff7508 push dword ptr [ebp+8]
11 804f1985 8ad8 mov bl,al
12 804f1987 e80c3c0000 call nt!IopGetDeviceAttachmentBase (804f5598)
13 804f198c 8bf0 mov esi,eax
14 804f198e 8bce mov ecx,esi
15 804f1990 e8495b0300 call nt!ObfReferenceObject (805274de)
16 804f1995 6a0a push 0Ah
17 804f1997 8ad3 mov dl,bl
18 804f1999 59 pop ecx
19 804f199a ff1530914d80 call dword ptr [nt!_imp_KeReleaseQueuedSpinLock (804d9130)]
20 804f19a0 8bc6 mov eax,esi
21 804f19a2 5e pop esi
22 804f19a3 5b pop ebx
23 804f19a4 5d pop ebp
24 804f19a5 c20400 ret 4

  在读反汇编代码时,期间调用的其它函数格式如“call    nt!IopGetDeviceAttachmentBase (804f5598)”,其中括号内“804f5598”就是 函数“IopGetDeviceAttachmentBase”所在的地址,在Disassembly输入该地址可进一步查看函数“IopGetDeviceAttachmentBase”的完整反汇编代码:

 1 nt!IopGetDeviceAttachmentBase:
2 804f5598 8bff mov edi,edi
3 804f559a 55 push ebp
4 804f559b 8bec mov ebp,esp
5 804f559d 8b4508 mov eax,dword ptr [ebp+8]
6 804f55a0 eb02 jmp nt!IopGetDeviceAttachmentBase+0xc (804f55a4)
7 804f55a2 8bc1 mov eax,ecx
8 804f55a4 8b88b0000000 mov ecx,dword ptr [eax+0B0h]
9 804f55aa 8b4918 mov ecx,dword ptr [ecx+18h]
10 804f55ad 85c9 test ecx,ecx
11 804f55af 75f1 jne nt!IopGetDeviceAttachmentBase+0xa (804f55a2)
12 804f55b1 5d pop ebp
13 804f55b2 c20400 ret 4

 

2)关于调用约定:微软内部函数常使用fastcall调用约定,与stdcall区别在于前两个参数分别通过寄存器ecx与edx传递。这里原书上作者调试的是vista系统,而本人调试的是xp系统,稍有出入,对IopGetDeviceAttachmentBase的调用代码为:

1 804f1982 ff7508          push    dword ptr [ebp+8]
2 804f1985 8ad8 mov bl,al //无关代码,忽略
3 804f1987 e80c3c0000 call nt!IopGetDeviceAttachmentBase (804f5598)

  可见,这里采用的依然是stdcall调用约定。这个并不重要,重要的是学会调试与解读内核代码的方法。此外,原书提到的用eax来传递参数真是莫名其妙,我猜测是某种代码优化搞的鬼,不然这与fastcall采用ecx与edx来传递参数相悖!

 

3)解读内核代码,首先查看WDK中IoGetDeviceAttachmentBaseRef函数原型:

1 PDEVICE_OBJECT  IoGetDeviceAttachmentBaseRef(
2 IN PDEVICE_OBJECT DeviceObject
3 );

  接受一个参数,类型为PDEVICE_OBJECT,然后对于DEVICE_OBJECT这个结构体的具体信息,也是查看WDK文档,也可以在VS20065中按F12追踪,也可在windbg查看,如“dt nt!_DRIVER_OBJECT”(推荐这种方式,偏移量和域名对应列出)。其解构如下:

 1 typedef struct _DEVICE_OBJECT {
2 CSHORT Type;
3 USHORT Size;
4 LONG ReferenceCount;
5 PDRIVER_OBJECT DriverObject;
6 PDEVICE_OBJECT NextDevice;
7 PDEVICE_OBJECT AttachedDevice;
8 PIRP CurrentIrp;
9 PIO_TIMER Timer;
10 ULONG Flags;
11 ULONG Characteristics;
12 __volatile PVPB Vpb;
13 PVOID DeviceExtension;
14 DEVICE_TYPE DeviceType;
15 CCHAR StackSize;
16 union {
17 LIST_ENTRY ListEntry;
18 WAIT_CONTEXT_BLOCK Wcb;
19 } Queue;
20 ULONG AlignmentRequirement;
21 KDEVICE_QUEUE DeviceQueue;
22 KDPC Dpc;
23 ULONG ActiveThreadCount;
24 PSECURITY_DESCRIPTOR SecurityDescriptor;
25 KEVENT DeviceLock;
26 USHORT SectorSize;
27 USHORT Spare1;
28 PDEVOBJ_EXTENSION DeviceObjectExtension;
29 PVOID Reserved;
30 } DEVICE_OBJECT, *PDEVICE_OBJECT;

  先不去看这个是什么东西,贴出来是因为反汇编代码中对结构体成员的引用使用的是偏移量,而我们必须查看这个结构体的原型说明才能知道某个具体的偏移量到底指代的是原来的哪个域。

   此外,IoGetDeviceAttachmentBaseRef函数调用了好几个其它的函数:KeAcquireQueuedSpinLock、IopGetDeviceAttachmentBase、ObfReferenceObject、KeReleaseQueuedSpinLock。其中第2个和第3个在WDK文档中无法查到,而第1个和第4个是提示“The KeReleaseQueuedSpinLock routine is reserved for system use.”,可见,并没有提供给我们有用的信息。

  要完全解读IoGetDeviceAttachmentBaseRef函数可能会有点繁琐,先把大概的框架打清楚(部分代码物理顺序被打乱以调整为逻辑顺序):

 1 ;标准现场保护
2 804f1972 8bff mov edi,edi
3 804f1974 55 push ebp
4 804f1975 8bec mov ebp,esp
5 ;从代码末尾的pop操作可以看到,这里还是现场保护
6 804f1977 53 push ebx
7 804f1978 56 push esi
8 ;ecx = 0AH,不知道干什么用,因为反汇编KeAcquireQueuedSpinLock并没有发现使用它,原书作者说是调用参数,我不太苟同
9 804f1979 6a0a push 0Ah
10 804f197b 59 pop ecx
11 ;bl = call KeAcquireQueuedSpinLock
12 804f197c ff1528914d80 call dword ptr [nt!_imp_KeAcquireQueuedSpinLock (804d9128)]
13 804f1985 8ad8 mov bl,al
14 ;假定pDriveObject = dword ptr [ebp+8],即IoGetDeviceAttachmentBaseRef接受的PDEVICE_OBJECT类型参数
15 ;ecx = call IopGetDeviceAttachmentBase( pDriveObject )
16 804f1982 ff7508 push dword ptr [ebp+8]
17 804f1987 e80c3c0000 call nt!IopGetDeviceAttachmentBase (804f5598)
18 804f198c 8bf0 mov esi,eax
19 804f198e 8bce mov ecx,esi
20 ;查看ObfReferenceObject反汇编发现使用到ecx,所以ecx作为参数传递,fastcall调用方式
21 ;eax = call ObfReferenceObject(ecx)
22 804f1990 e8495b0300 call nt!ObfReferenceObject (805274de)
23 ;dl = bl = call KeAcquireQueuedSpinLock
24 ;ecx = 0AH,也不知道干什么用
25 ;返回值eax = KeReleaseQueuedSpinLock(eax,edx,esi)
26 804f1995 6a0a push 0Ah
27 804f1997 8ad3 mov dl,bl
28 804f1999 59 pop ecx
29 804f199a ff1530914d80 call dword ptr [nt!_imp_KeReleaseQueuedSpinLock (804d9130)]
30 804f19a0 8bc6 mov eax,esi
31 ;恢复现场
32 804f19a2 5e pop esi
33 804f19a3 5b pop ebx
34 804f19a4 5d pop ebp
35 804f19a5 c20400 ret 4

  所以大概流程如下:

1 PDEVICE_OBJECT  IoGetDeviceAttachmentBaseRef(PDEVICE_OBJECT  pDeviceObject )
2 {
3 Var1 = KeAcquireQueuedSpinLock();
4 Var2 = IopGetDeviceAttachmentBase( pDriveObject );
5 Var3 = ObfReferenceObject(Var2);
6 KeReleaseQueuedSpinLock(Var1, Var2,Var3);
7 return Var2;
8 }

  我没做过内核开发,从WDK文档中查看IoGetDeviceAttachmentBaseRef的功能是获取设备栈底的设备对象指针。再从各个函数名以及反汇编代码猜测和调整函数功能,应该是这样:

 1 PDEVICE_OBJECT  IoGetDeviceAttachmentBaseRef(PDEVICE_OBJECT  pDeviceObject )
2 {
3 //请求锁定设备链,返回的应该是一个锁类型,禁止其它线程操作
4 var_lock = KeAcquireQueuedSpinLock();
5 //取得设备栈底的设备对象指针
6 var_pBaseDriveObj = IopGetDeviceAttachmentBase( pDriveObject );
7 //对这个设备增加一次引用计数
8 ObfReferenceObject(var_pBaseDriveObj);
9 //释放锁
10 KeReleaseQueuedSpinLock(var_lock);
11 return var_pBaseDriveObj;
12 }

  以上部分原书并没有给出详细的说明,而这里也仅仅是自己的尝试解读,可能有很多错误!对IopGetDeviceAttachmentBase函数解读如下:

 1 ;标准现场保护
2 804f5598 8bff mov edi,edi
3 804f559a 55 push ebp
4 804f559b 8bec mov ebp,esp
5 ;eax = pDeviceObject (假定pDeviceObject为传入的参数)
6 804f559d 8b4508 mov eax,dword ptr [ebp+8]
7 804f55a0 eb02 jmp nt!IopGetDeviceAttachmentBase+0xc (804f55a4)
8 804f55a2 8bc1 mov eax,ecx
9 ;ecx = [[pDeviceObject + 0B0H] + 18H]
10 804f55a4 8b88b0000000 mov ecx,dword ptr [eax+0B0h]
11 804f55aa 8b4918 mov ecx,dword ptr [ecx+18h]
12 ;if (ecx != NULL) 循环
13 804f55ad 85c9 test ecx,ecx
14 804f55af 75f1 jne nt!IopGetDeviceAttachmentBase+0xa (804f55a2)
15 ;else
16 804f55b1 5d pop ebp

  使用“dt nt!_DEVICE_OBJECT”:kd> dt nt!_DEVICE_OBJECT

 1 +0x000 Type             : Int2B
2 +0x002 Size : Uint2B
3 +0x004 ReferenceCount : Int4B
4 +0x008 DriverObject : Ptr32 _DRIVER_OBJECT
5 +0x00c NextDevice : Ptr32 _DEVICE_OBJECT
6 +0x010 AttachedDevice : Ptr32 _DEVICE_OBJECT
7 +0x014 CurrentIrp : Ptr32 _IRP
8 +0x018 Timer : Ptr32 _IO_TIMER
9 +0x01c Flags : Uint4B
10 +0x020 Characteristics : Uint4B
11 +0x024 Vpb : Ptr32 _VPB
12 +0x028 DeviceExtension : Ptr32 Void
13 +0x02c DeviceType : Uint4B
14 +0x030 StackSize : Char
15 +0x034 Queue : __unnamed
16 +0x05c AlignmentRequirement : Uint4B
17 +0x060 DeviceQueue : _KDEVICE_QUEUE
18 +0x074 Dpc : _KDPC
19 +0x094 ActiveThreadCount : Uint4B
20 +0x098 SecurityDescriptor : Ptr32 Void
21 +0x09c DeviceLock : _KEVENT
22 +0x0ac SectorSize : Uint2B
23 +0x0ae Spare1 : Uint2B
24 +0x0b0 DeviceObjectExtension : Ptr32 _DEVOBJ_EXTENSION
25 +0x0b4 Reserved : Ptr32 Void

  查看0B0H偏移对应与域“DeviceObjectExtension”,类型声明为“Ptr32 _DEVOBJ_EXTENSION”,则这是一个“DEVOBJ_EXTENSION”的结构体指针,继续“dt nt!_DEVOBJ_EXTENSION”可查看到偏移18H对应与域“AttachedTo”,这个域的类型声明竟然是“Ptr32 _DEVICE_OBJECT”,呵呵,看来又指向了一个DEVICE_OBJECT结构体。继续:kd> dt nt!_DEVOBJ_EXTENSION

 1 +0x000 Type             : Int2B
2 +0x002 Size : Uint2B
3 +0x004 DeviceObject : Ptr32 _DEVICE_OBJECT
4 +0x008 PowerFlags : Uint4B
5 +0x00c Dope : Ptr32 _DEVICE_OBJECT_POWER_EXTENSION
6 +0x010 ExtensionFlags : Uint4B
7 +0x014 DeviceNode : Ptr32 Void
8 +0x018 AttachedTo : Ptr32 _DEVICE_OBJECT
9 +0x01c StartIoCount : Int4B
10 +0x020 StartIoKey : Int4B
11 +0x024 StartIoFlags : Uint4B
12 +0x028 Vpb : Ptr32 _VPB

  所以逆向后大概流程为:

1 PDEVICE_OBJECT  IopGetDeviceAttachmentBase (PDEVICE_OBJECT  pDeviceObject )
2 {
3 //遍历设备栈,直到最后一个设备对象
4 PDEVICE_OBJECT pNextDeviceObj = pDeviceObject;
5 while((pNextDeviceObj = pDeviceObject-> DeviceObjectExtension -> AttachedTo) != NULL )
6 pDeviceObject = pNextDeviceObj;
7 return pDeviceObject;
8 }

 

4)在没有符号表的情况下,check版本可以查看IDA反汇编结果中的INIT段中的start过程,其中的关键jmp就是DriverEntry入口函数,在free版本相对还要简单些。右键改名即可。

5)64位情况下简要知识:

  a)同一个寄存器不同部分:rax(64位)、eax(低32位)、ax(低16位)、al(低8位)、ah(8到15位)。新增通用寄存器r8、r9

  b)64位下的char,short,int和ULONG等数据类型与32位下位数相同,不同的是指针类型扩展到了64位。

  c)内核函数由rcx、rdx、r8、r9分别传递第1~4个参数,其它的用堆栈传递。

  d)64位下堆栈的移动是通过sub rsp,XX的方式实现一次性预留一片临时存储区的,而恢复堆栈则通过add rsp,XXX,从而提高了效率。

  关于64的任何东西,都没有测试过,仅仅归纳原书列出重要区别点,但是需要提醒,这个作者经常犯错,这些知识点参考参考就好,一切以实际测试为准!