The walk-through begins with Sample A.
Its DriverEntry starts at 0x105F0 and ends at 0x106AD. It fi rst initializes a UNICODE_STRING structure with the strings \Device\fsodhfn2m and \DosDevices\
fsodhfn2m. In kernel mode, most strings are described using the UNICODE_STRING structure:
typedef struct _UNICODE_STRING { USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
It is initialized through the RtlInitUnicodeString API. The “Device” string is a device name in the object manager; the “DosDevices” string is used as a symbolic link to the actual device name. The Windows object manager main- tains and organizes objects in a fi lesystem-like structure with the root at “\”.
There are well-defi ned directories such as \Devices, \BaseNamedObjects, \ KernelObjects, and so on. \DosDevices is an alias for the \?? directory; it is there because when user-mode applications specify the path to an object they want to access, the \??\ is prepended to it; \?? contains symbolic links point- ing to the real object. For example, when a user wants to access “c:\test.txt” through the CreateFile API, the actual path sent to the kernel is “\??\c:\
test.txt”; because “c:” is a symbolic link to \Device\HarddiskVolume2 (it may vary on your system), the whole path will eventually resolve to \Device\
HarddiskVolume2\test.txt. The symbolic link is necessary because user-mode APIs usually access devices through the \?? directory; if there were no symbolic links there, the device may not be accessible to user-mode apps.
After initializing the two strings, it proceeds to create the actual device object.
IoCreateDevice is defi ned as follows:
NTSTATUS
IoCreateDevice(
IN PDRIVER_OBJECT DriverObject, IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceName OPTIONAL, IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
OUT PDEVICE_OBJECT *DeviceObject );
DriverObject is the caller’s DRIVER_OBJECT; it is the driver object that the new device object is associated with. DeviceExtensionSize is how many bytes of non- paged pool memory should be allocated for the driver-specifi c structure. Because it is a user-defi ned structure, it is very important to recover its fi elds. DeviceName is the native device name. DeviceType is one of the pre-defi ned FILE_DEVICE_*
types; if the device does not fall into a generic category, FILE_DEVICE_UNKNOWN is used instead. DeviceCharacteristics refers to the device characteristic;
most of the time you will see FILE_DEVICE_SECURE_OPEN. Exclusive determines whether there can be more than one handle to the device. DeviceObject receives the actual device object.
From the disassembly, you can decompile the fi rst basic block and its exiting condition as follows:
01: UNICODE_STRING devname;
02: UNICODE_STRING symname;
03:
04: NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, \ PUNICODE_STRING regpath) 05: {
06: NTSTATUS status;
07: PDEVICE_OBJECT devobj;
08:
09: RtlInitUnicodeString(&devname, L"\\Device\\fsodhfn2m");
10: RtlInitUnicodeString(&symname, L"\\DosDevices\\fsodhfn2m");
11: status = IoCreateDevice(
12: DriverObject, 13: 0,
14: &devname,
15: FILE_DEVICE_UNKNOWN, 16: FILE_DEVICE_SECURE_OPEN, 17: FALSE,
18: &devobj);
19: if (!NT_SUCCESS(status)) { 20: return status; // loc_106A3 21: }
22: }
NT_SUCCESS() is a common macro that checks if status is greater than or equal to 0. After successfully creating the object, it proceeds to the following:
01: .text:00010643 mov ecx, [ebp+DriverObject]
02: .text:00010646 mov dword ptr [ecx+38h], offset sub_10300 03: .text:0001064D mov edx, [ebp+DriverObject]
04: .text:00010650 mov dword ptr [edx+40h], offset sub_10300 05: .text:00010657 mov eax, [ebp+DriverObject]
06: .text:0001065A mov dword ptr [eax+70h], offset sub_10300
07: .text:00010661 mov ecx, [ebp+DriverObject]
08: .text:00010664 mov dword ptr [ecx+34h], offset sub_10580 09: .text:0001066B push offset SymbolicLinkName ; SymbolicLinkName 10: .text:00010670 call ds:IoDeleteSymbolicLink
11: .text:00010676 push offset DestinationString ; DeviceName 12: .text:0001067B push offset SymbolicLinkName ; SymbolicLinkName 13: .text:00010680 call ds:IoCreateSymbolicLink
14: .text:00010686 mov [ebp+var_4], eax 15: .text:00010689 cmp [ebp+var_4], 0 16: .text:0001068D jge short loc_106A1
Lines 1–8 set some DRIVER_OBJECT fi elds to two function pointers. What is at offset 0x38, 0x40, 0x70, and 0x34?
0: kd> dt _DRIVER_OBJECT nt!_DRIVER_OBJECT
+0x000 Type : Int2B +0x002 Size : Int2B
+0x004 DeviceObject : Ptr32 _DEVICE_OBJECT ...
+0x034 DriverUnload : Ptr32 void +0x038 MajorFunction : [28] Ptr32 long
Offset 0x34 is the DriverUnload routine; now, you know that the driver sup- ports dynamic unloading and sub_10580 is the unload routine. Offset 0x38 is the beginning of the MajorFunction array; recall that this is an array of IRP dispatch handlers. Because there is a maximum of 28 generic IRP major func- tions, the MajorFunction array has 28 members. The fi rst index is 0, which corresponds to IRP_MJ_CREATE; hence, you know that sub_10300 is the handler for that IRP. Offset 0x40 is the third element in the MajorFunction array (index 2); this corresponds to IRP_MJ_CLOSE, and sub_10300 is reused as the handler.
Offset 0x70 is the 16th element in the array (index 0xe), which corresponds to IRP_MJ_DEVICE_CONTROL, and sub_10300 is the handler. At this point, you know that sub_10300 is the handler for the read, close, and device control IRP.
Lines 10–13 delete any existing symbolic link and create a new one to point to the device object previously created.
You can now continue decompiling this block in DriverEntry as follows:
01: DriverObject->MajorFunction[IRP_MJ_READ] = sub_10300;
02: DriverObject->MajorFunction[IRP_MJ_CLOSE] = sub_10300;
03: DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = sub_10300;
04: DriverObject->DriverUnload = sub_10580;
05:
06: IoDeleteSymbolicLink(&symname);
07: status = IoCreateSymbolicLink(&symname, &devname);
08: if (!NT_SUCCESS(status)) { 09: ... // block .text:0001068F 10: return status;
11: }
12: return status;
To make life easier, you can rename sub_10300 as IRP_ReadCloseDeviceIo and sub_10580 as DRV_Unload.
The next block at 0x1068F deletes the previously created device object if the symbolic link creation fails. Note that it is getting the device object from the driver object instead of using the pointer passed to IoCreateDevice. You can decompile this block as follows:
01: IoDeleteDevice(DriverObject->DeviceObject);
That completes decompilation of this rootkit’s DriverEntry. To summarize what has been learned so far:
■ The driver creates a device object named \Device\fsodhfn2m.
■ It supports dynamic unloading and the unload routine is sub_10580 (renamed to DRV_Unload).
■ It supports IRP_MJ_READ, IRP_MJ_WRITE, and IRP_MJ_DEVICE_CONTROL opera- tions, and sub_10300 is the handler (renamed to IRP_ReadCloseDeviceIo).
■ It creates a symbolic link to the device object. If that fails, the driver returns an error.
The next step is to understand what the DriverUnload routine does. The WDK defi nes the prototype for the driver unload routine as follows:
VOID Unload(
PDRIVER_OBJECT *DriverObject );
After some minor massaging, our unload routine looks like this:
01: .text:00010580 ; void __stdcall DRV_Unload(PDRIVER_OBJECT drvobj) 02: .text:00010580 DRV_Unload proc near
03: .text:00010580
04: .text:00010580 drvobj= dword ptr 8 05: .text:00010580
06: .text:00010580 push ebp 07: .text:00010581 mov ebp, esp
08: .text:00010583 push offset SymbolicLinkName ; SymbolicLinkName 09: .text:00010588 call ds:IoDeleteSymbolicLink
10: .text:0001058E mov eax, [ebp+drvobj]
11: .text:00010591 mov ecx, [eax+DRIVER_OBJECT.DeviceObject]
12: .text:00010594 push ecx ; DeviceObject 13: .text:00010595 call ds:IoDeleteDevice
14: .text:0001059B pop ebp 15: .text:0001059C retn 4 16: .text:0001059C DRV_Unload endp
The preceding can be decompiled to the following:
01: VOID DRV_Unload(PDRIVER_OBJECT drvobj) 02: {
03: IoDeleteSymbolicLink(&symname);
04: IoDeleteDevice(drvobj->DeviceObject);
05: }
As previously stated, an important key to understanding a driver’s functional- ity is through its IRP dispatch handlers. Analyzing _IRP_ReadCloseDeviceIo, we start at the beginning:
01: .text:00010300 ; NTSTATUS __stdcall IRP_ReadCloseDeviceIO(
PDEVICE_OBJECT devobj, PIRP Irp) 02: .text:00010300 IRP_ReadCloseDeviceIO proc near 03: .text:00010300 var_14= dword ptr -14h
04: .text:00010300 var_10= dword ptr -10h 05: .text:00010300 var_C= dword ptr -0Ch 06: .text:00010300 var_8= dword ptr -8 07: .text:00010300 var_4= dword ptr -4 08: .text:00010300 devobj= dword ptr 8 09: .text:00010300 Irp= dword ptr 0Ch 10: .text:00010300
11: .text:00010300 push ebp 12: .text:00010301 mov ebp, esp 13: .text:00010303 sub esp, 14h 14: .text:00010306 mov [ebp+var_4], 0 15: .text:0001030D mov eax, [ebp+Irp]
16: .text:00010310 mov ecx, [ebp+var_4]
17: .text:00010313 mov [eax+18h], ecx 18: .text:00010316 mov edx, [ebp+Irp]
19: .text:00010319 mov dword ptr [edx+1Ch], 0 20: .text:00010320 mov eax, [ebp+Irp]
21: .text:00010323 mov ecx, [eax+60h]
22: .text:00010326 mov [ebp+var_10], ecx 23: .text:00010329 mov edx, [ebp+var_10]
24: .text:0001032C movzx eax, byte ptr [edx]
25: .text:0001032F cmp eax, 0Eh
26: .text:00010332 jnz short loc_1037D
We already know its prototype because it is the same for all IRP handlers.
When analyzing IRP handlers, you need to be cognizant of a few facts:
■ An IRP is a dynamic structure with an array of IO_STACK_LOCATION after its header.
■ Most of the IRP parameters are in the IO_STACK_LOCATION (including its IRP major/minor number).
■ A driver accesses its IO_STACK_LOCATION using the IoGetCurrent IrpStacLocation routine. Because this routine is forced-inline, you must
recognize it through its inlined patterns. It is a common coding pattern to retrieve the IO_STACK_LOCATION in the beginning of an IRP handler.
Lines 15–17 read the IRP structure and write a 0 to a fi eld at offset 0x18. Looking at the IRP structure you see the following:
0: kd> dt nt!_IRP
+0x000 Type : Int2B +0x002 Size : Uint2B ...
+0x00c AssociatedIrp : <unnamed-tag>
...
+0x018 IoStatus : _IO_STATUS_BLOCK +0x000 Status : Int4B
+0x000 Pointer : Ptr32 Void +0x004 Information : Uint4B ...
+0x020 RequestorMode : Char ...
+0x040 Tail : <unnamed-tag>
An IO_STATUS_BLOCK structure stores status information about an IRP:
typedef struct _IO_STATUS_BLOCK { union {
NTSTATUS Status;
PVOID Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
An IRP handler typically sets the Status fi eld to indicate whether the IRP was successful or requires further processing. Information stores request-specifi c information for the IRP; a driver may use it to store a pointer to a buffer or set the completion status. Pointer is reserved.
Hence, you know that line 17 sets the IRP->IoStatus.Status fi eld to 0 and that the local variable var_4 is of type NTSTATUS. Lines 18–19 access the IRP structure and write a 0 at offset 0x1c, which is the Information fi eld in IoStatus. This is simply setting IRP->IoStatus.Information to 0. Lines 20–22 access offset 0x60 in the IRP structure and save its address in a local variable. The IRP structure is fi lled with unions in the Tail fi eld (starting at offset 0x40), so it can be somewhat confusing to determine which union fi eld member is accessed.
Let’s dump some of the unions:
0: kd> dt nt_IRP Tail.Overlay.
+0x040 Tail : +0x000 Overlay :
+0x000 DeviceQueueEntry : _KDEVICE_QUEUE_ENTRY +0x000 DriverContext : [4] Ptr32 Void
+0x010 Thread : Ptr32 _ETHREAD +0x014 AuxiliaryBuffer : Ptr32 Char +0x018 ListEntry : _LIST_ENTRY
+0x020 CurrentStackLocation : Ptr32 _IO_STACK_LOCATION +0x020 PacketType : Uint4B
+0x024 OriginalFileObject : Ptr32 _FILE_OBJECT
This indicates that offset 0x60 could be either a pointer to an IO_STACK_
LOCATION or an unsigned integer indicating the packet type. We can make an educated guess that it is the CurrentStackLocation fi eld because of the code context (occurring at the beginning of an IRP handler). Furthermore, we know that the inlined routine IoGetCurrentIrpStackLocation is defi ned as follows:
FORCEINLINE
PIO_STACK_LOCATION
IoGetCurrentIrpStackLocation(PIRP Irp) {
return Irp->Tail.Overlay.CurrentStackLocation;
}
Therefore, lines 20–22 are saving the current IO_STACK_LOCATION to a local variable. The local variable _var_10 is of the type PIO_STACK_LOCATION.
N O T E Many of these functions are declared as FORCEINLINE and thus will never appear as call destinations—i.e., you will never see see the symbol
IoGetCurrentIrpStackLocation in the assembly code. We recommend that you write a simple driver using these forced-inline routines so that you can get used to the code pattern.
Lines 23–25 access the fi rst byte at offset 0 in the IO_STACK_LOCATION using the MOVZX instruction. This indicates that fi eld is of type unsignedchar. From the IRP section, we know that this is the MajorFunction fi eld. Line 5 checks whether the MajorFunction number is 0xe, i.e., IRP_MJ_DEVICE_CONTROL.
You can now decompile the fi rst block of IRP_ReadCloseIo as follows:
NTSTATUS IRP_ReadCloseIo(PDEVICE_OBJECT devobj, PIRP Irp) {
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION isl;
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = 0;
isl = IoGetCurrentIrpStackLocation(Irp);
if (isl->MajorFunction != IRP_MJ_DEVICE_CONTROL) { ... // loc_1037D
}
... // .text:00010334 }
Next, we analyze block 0x10334, which executes if major code is IRP_MJ _DEVICE_CONTROL:
01: .text:00010334 mov ecx, [ebp+var_10]
02: .text:00010337 mov edx, [ecx+0Ch]
03: .text:0001033A mov [ebp+var_C], edx 04: .text:0001033D mov eax, [ebp+Irp]
05: .text:00010340 mov ecx, [eax+0Ch]
06: .text:00010343 mov [ebp+var_8], ecx 07: .text:00010346 mov edx, [ebp+Irp]
08: .text:00010349 mov dword ptr [edx+1Ch], 644h 09: .text:00010350 mov eax, [ebp+var_C]
10: .text:00010353 mov [ebp+var_14], eax 11: .text:00010356 cmp [ebp+var_14], 22C004h 12: .text:0001035D jz short loc_10361
In the previous paragraph, we deduced that var_10 is of type PIO_STACK_
LOCATION. Lines 1–2 access offset 0xC of the IO_STACK_LOCATION. Again, recall that an IO_STACK_LOCATION contains the I/O request parameters, which are all stored in unions. How do you determine which union to use? We know that it will use the DeviceIoControl fi eld because we are processing an IRP_MJ_
DEVICE_CONTROL request. Also, the IoControlField is at offset 0xC from the base of IO_STACK_LOCATION:
1: kd> dt nt!_IO_STACK_LOCATION Parameters.
+0x004 Parameters :
+0x000 Create : <unnamed-tag>
+0x000 CreatePipe : <unnamed-tag>
+0x000 CreateMailslot : <unnamed-tag>
+0x000 Read : <unnamed-tag>
+0x000 Write : <unnamed-tag>
+0x000 QueryDirectory : <unnamed-tag>
...
+0x000 DeviceIoControl : <unnamed-tag>
...
1: kd> dt nt!_IO_STACK_LOCATION Parameters.DeviceIoControl.
+0x004 Parameters : +0x000 DeviceIoControl :
+0x000 OutputBufferLength : Uint4B +0x004 InputBufferLength : Uint4B +0x008 IoControlCode : Uint4B +0x00c Type3InputBuffer : Ptr32 Void
Therefore, lines 1–3 retrieve the IoControlCode fi eld and save it in var_C, which we now know is of type ULONG.
Lines 4–6 access offset 0xC in an IRP and save the pointer to a local variable var_8. From the previous section, we know that at offset 0xC is the AssociatedIrp union:
1: kd> dt nt!_IRP AssociatedIrp.
+0x000 MasterIrp : Ptr32 _IRP +0x000 IrpCount : Int4B +0x000 SystemBuffer : Ptr32 Void
Which of the three fi elds should you use? Given the current information, you cannot tell. The context required to determine the proper fi eld is in lines 9–12, which retrieve the saved IOCTL code (var_C) and compare it against 0x22c004. You know that an IOCTL code encodes device type, function code, access, and buffering method. Hence, after decoding 0x22c004, you know the following:
■ Device type is FILE_DEVICE_UNKNOWN (0x22).
■ The IOCTL code is 0x1.
■ Access is (FILE_READ_DATA | FILE_WRITE_DATA).
■ Buffering method is METHOD_BUFFERED.
Recall that we are in an IOCTL handler and that drivers must specify a buffer- ing method when defi ning the IOCTL code. For buffered I/O, the SystemBuffer fi eld points to a non-paged pool buffer storing the user input. We can now say that lines 4–6 access the SystemBuffer fi eld.
Lines 7–8 write 0x644 to offset 0x1c inside an IRP, which is the IRP->IoStatus.
Information fi eld. It is unclear why the author chose this value.
Given this information, you know that the control code must have been constructed this way:
#define IOCTL_1 CTL_CODE(FILE_DEVICE_UNKNOWN, 1, METHOD_BUFFERED, \ FILE_READ_DATA | FILE_WRITE_DATA)
Because we have not fully analyzed or understood the IOCTL operation, we gave it the generic IOCTL_1 name. This block can now be decompiled as follows:
PVOID userinput = Irp->AssociatedIrp.SystemBuffer;
Irp->IoStatus.Information = (ULONG_PTR) 0x644;
if (isl->Parameters.DeviceIoControl.IoControlCode == IOCTL_1) {
... // loc_10361 }
... // 0001035F
To understand what the IOCTL does, we need to analyze loc_10361 and the function sub_103B0. However, before doing that, let’s fi nish the nearby blocks fi rst (as they are simpler):
// remember var_4 is status local variable (type NTSTATUS) 01: .text:0001035F jmp short loc_1036C
02: .text:00010361 loc_10361:
03: .text:00010361 mov ecx, [ebp+var_8] ; 04: .text:00010364 push ecx
05: .text:00010365 call IOCTL_1_handler 06: .text:0001036A jmp short loc_1037D 07: .text:0001036C loc_1036C:
08: .text:0001036C mov [ebp+var_4], 0C0000010h 09: .text:00010373 mov edx, [ebp+Irp]
10: .text:00010376 mov dword ptr [edx+1Ch], 0 11: .text:0001037D loc_1037D:
12: .text:0001037D cmp [ebp+var_4], 103h 13: .text:00010384 jz short loc_1039A
14: .text:00010386 xor dl, dl ; PriorityBoost 15: .text:00010388 mov ecx, [ebp+Irp] ; Irp
16: .text:0001038B call ds:IofCompleteRequest 17: .text:00010391 mov eax, [ebp+Irp]
18: .text:00010394 mov ecx, [ebp+var_4]
19: .text:00010397 mov [eax+18h], ecx 20: .text:0001039A loc_1039A:
21: .text:0001039A mov eax, [ebp+var_4]
22: .text:0001039D mov esp, ebp 23: .text:0001039F pop ebp 24: .text:000103A0 retn 8
25: .text:000103A0 IRP_ReadCloseDeviceIO endp
You enter 0x1035F if the IOCTL code does not match up. It immediately jumps to line 7, which sets the local status variable to 0xC0000010, which is STATUS_INVALID_OPERATION; and Irp->IoStatus.Information to 0. Next, in line 11, it checks whether the local status is 0x103 (STATUS_PENDING); this block is actually redundant because the status variable in this function can only have two values (STATUS_SUCCESS or STATUS_INVALID_OPERATION). When an IRP is marked with STATUS_PENDING, it means that the operation is incomplete and is awaiting completion from another driver. This occurs often in drivers so it is wise to remember the magic constant 0x103. If the status is STATUS_PENDING, the handler immediately returns with that status (line 13 and 20). Otherwise, it calls IoCompleteRequest to mark the IRP completed and saves the status in IRP->IoStatus.Status (line 19) and returns it. This is actually a bug because a driver should set the IoStatusBlock fi eld before completing the request; once an IRP is completed, it should not be touched again. These blocks can be decom- piled as follows:
status = STATUS_INVALID_OPERATION;
Irp->IoStatus.Information = 0;
if (status == STATUS_PENDING) { return status;
}
IoCompleteRequest(Irp, IO_NO_INCREMENT);
Irp->IoStatus.Status = status;
return status;
Returning to the IOCTL_1_handler routine, note that it calls only two other functions: sub_10460 and sub_10550. sub_10550 is a small leaf routine so we will analyze that fi rst:
01: .text:00010550 ; void __stdcall sub_10550(PMDL Mdl, PVOID BaseAddress) 02: .text:00010550 sub_10550 proc near
03: .text:00010550 push ebp 04: .text:00010551 mov ebp, esp 05: .text:00010553 mov eax, [ebp+Mdl]
06: .text:00010556 push eax ; MemoryDescriptorList 07: .text:00010557 mov ecx, [ebp+BaseAddress]
08: .text:0001055A push ecx ; BaseAddress 09: .text:0001055B call ds:MmUnmapLockedPages
10: .text:00010561 mov edx, [ebp+Mdl]
11: .text:00010564 push edx ; MemoryDescriptorList 12: .text:00010565 call ds:MmUnlockPages
13: .text:0001056B mov eax, [ebp+Mdl]
14: .text:0001056E push eax ; Mdl 15: .text:0001056F call ds:IoFreeMdl
16: .text:00010575 pop ebp 17: .text:00010576 retn 8 18: .text:00010576 sub_10550 endp
This function unmaps, unlocks, and frees an MDL. It is unclear what the MDLs describe because we have not analyzed the other routines. This function can be decompiled as follows:
void UnmapMdl(PMDL mdl, PVOID baseaddr) {
MmUnmapLockedPages(baseaddr, mdl);
MmUnlockPages(mdl);
IoFreeMdl(mdl);
}
sub_10460 is another leaf routine involving MDLs; its main functionality is to create, lock, and map an MDL for a given buffer and length. Its prototype is as follows:
PVOID MapMdl(PMDL *mdl, PVOID VirtualAddress, ULONG Length);
By default, the disassembler was not able to infer the fi rst parameter’s type.
You can tell that it is a PMDL * because of instruction at 0x1049D. The assembly listing is shown here but without line-by-line commentary, as it is very simple:
01: .text:00010460 ; PVOID __stdcall MapMdl(PMDL *mdl, PVOID VirtualAddress, ULONG Length) 02: .text:00010460 MapMdl proc near
03: .text:00010460 push ebp 04: .text:00010461 mov ebp, esp 05: .text:00010463 push 0FFFFFFFFh 06: .text:00010465 push offset unk_10748 07: .text:0001046A push offset _except_handler3
08: .text:0001046F mov eax, large fs:0 09: .text:00010475 push eax
10: .text:00010476 mov large fs:0, esp 11: .text:0001047D add esp, 0FFFFFFF0h 12: .text:00010480 push ebx
13: .text:00010481 push esi 14: .text:00010482 push edi
15: .text:00010483 mov [ebp+var_18], esp
16: .text:00010486 push 0 ; Irp
17: .text:00010488 push 0 ; ChargeQuota 18: .text:0001048A push 0 ; SecondaryBuffer 19: .text:0001048C mov eax, [ebp+Length]
20: .text:0001048F push eax ; Length 21: .text:00010490 mov ecx, [ebp+VirtualAddress]
22: .text:00010493 push ecx ; VirtualAddress 23: .text:00010494 call ds:IoAllocateMdl
24: .text:0001049A mov edx, [ebp+mdl]
25: .text:0001049D mov [edx], eax 26: .text:0001049F mov eax, [ebp+mdl]
27: .text:000104A2 cmp dword ptr [eax], 0 28: .text:000104A5 jnz short loc_104AE 29: .text:000104A7 xor eax, eax
30: .text:000104A9 jmp loc_10534
31: .text:000104AE loc_104AE:
32: .text:000104AE mov [ebp+var_4], 0
33: .text:000104B5 push 1 ; Operation 34: .text:000104B7 push 0 ; AccessMode 35: .text:000104B9 mov ecx, [ebp+mdl]
36: .text:000104BC mov edx, [ecx]
37: .text:000104BE push edx ; MemoryDescriptorList 38: .text:000104BF call ds:MmProbeAndLockPages
39: .text:000104C5 mov [ebp+var_4], 0FFFFFFFFh 40: .text:000104CC jmp short loc_104F6
41: .text:000104CE loc_104CE:
42: .text:000104CE mov eax, 1 43: .text:000104D3 retn
44: .text:000104D4 loc_104D4:
45: .text:000104D4 mov esp, [ebp+var_18]
46: .text:000104D7 mov eax, [ebp+mdl]
47: .text:000104DA mov ecx, [eax]
48: .text:000104DC push ecx ; Mdl 49: .text:000104DD call ds:IoFreeMdl
50: .text:000104E3 mov [ebp+var_20], 0
51: .text:000104EA mov [ebp+var_4], 0FFFFFFFFh 52: .text:000104F1 mov eax, [ebp+var_20]
53: .text:000104F4 jmp short loc_10534
54: .text:000104F6 loc_104F6:
55: .text:000104F6 push 10h ; Priority
56: .text:000104F8 push 0 ; BugCheckOnFailure 57: .text:000104FA push 0 ; BaseAddress