[Bug 30155] SafeDisc v2.05.030 fails due to driver dispatch routine status and irp.IoStatus.u.Status differing (Command & Conquer: Red Alert 2)

wine-bugs at winehq.org wine-bugs at winehq.org
Sat Mar 24 06:27:28 CDT 2012


http://bugs.winehq.org/show_bug.cgi?id=30155

Anastasius Focht <focht at gmx.net> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Keywords|                            |obfuscation
          Component|-unknown                    |ntoskrnl
            Summary|secdrv.sys from SafeDisc    |SafeDisc v2.05.030 fails
                   |v2.05.030 does not work     |due to driver dispatch
                   |                            |routine status and
                   |                            |irp.IoStatus.u.Status
                   |                            |differing (Command &
                   |                            |Conquer: Red Alert 2)

--- Comment #6 from Anastasius Focht <focht at gmx.net> 2012-03-24 06:27:28 CDT ---
Hello Stefan,

I bought the game for few bucks as this stuff can only be properly analyzed by
live debugging ;-)
Thanks for the logs.

--- snip ---
0009:Call KERNEL32.CreateFileA(0032f1dc
"\\\\.\\Secdrv",c0000000,00000003,00000000,00000003,00000080,00000000)
ret=0033107b
...
0009:Ret  KERNEL32.CreateFileA() retval=0000002c ret=0033107b
0009:Call
KERNEL32.DeviceIoControl(0000002c,ef002407,009b1fb0,00000514,009b24c4,00000c18,0032f2e4,00000000)
ret=003310d4
...
trace:ntoskrnl:process_ioctl ioctl ef002407 device 0x11aac8 in_size 1300
out_size 3096
err:ntoskrnl:process_ioctl Input buffer 0: 00000002
err:ntoskrnl:process_ioctl Input buffer 1: 00000002
err:ntoskrnl:process_ioctl Input buffer 2: 00000000
err:ntoskrnl:process_ioctl Input buffer 3: 0000003e
err:ntoskrnl:process_ioctl Input buffer 4: db8ce543
err:ntoskrnl:process_ioctl Input buffer 5: 4f190d3a
err:ntoskrnl:process_ioctl Input buffer 6: a82e94fd
err:ntoskrnl:process_ioctl Input buffer 7: 3cbb7c84
0031:Call ntdll.NtGetTickCount() ret=7ec9b650
0031:Ret  ntdll.NtGetTickCount() retval=0000045b ret=7ec9b650
0031:Call driver dispatch 0x540402 (device=0x11aac8,irp=0x53e7c0)
trace:ntoskrnl:__regs_IofCompleteRequest 0x53e7c0 0
trace:ntoskrnl:IoCompleteRequest 0x53e7c0 0
0031:Ret  driver dispatch 0x540402 (device=0x11aac8,irp=0x53e7c0)
retval=00000000
...
0009:Ret  KERNEL32.DeviceIoControl() retval=00000000 ret=003310d4
--- snip ---

The SafeDisc driver input buffer (structures similar as of
http://www.winehq.org/pipermail/wine-patches/2002-April/002146.html)

--- snip ---
typedef struct _SECDRV_IOC_IN_BUFFER
{
    DWORD dwVersionMajor;
    DWORD dwVersionMinor;
    DWORD dwVersionPatch;

    DWORD dwCommand;
    BYTE bVerificationData[0x400];

    DWORD cbUserData;
    BYTE  bUserData[0x100];
} SECDRV_IOC_IN_BUFFER, *PSECDRV_IOC_IN_BUFFER;
--- snip ---

--- snip ---
err:ntoskrnl:process_ioctl Input buffer 0: 00000002
err:ntoskrnl:process_ioctl Input buffer 1: 00000002
err:ntoskrnl:process_ioctl Input buffer 2: 00000000
--- snip ---

driver version 2.2.0 (SafeDisc 2.5.30)

--- snip ---
err:ntoskrnl:process_ioctl Input buffer 3: 0000003e
--- snip ---

-> SECDRV_CMD_SETUP

0x3E handler:

In the "setup" phase handler the driver checks for:

in-buffer, out-buffer ptrs != NULL
in-buffer == 0x514
out-buffer == 0xC18

In the next step the client side KeTickCount (KSYSTEM_TIME) tick value which is
in the first DWORD (buffer 4) is decoded.
Basically it's an XOR loop with predefined constants on client and driver side.

--- snip ---
err:ntoskrnl:process_ioctl Input buffer 4: db8ce543
err:ntoskrnl:process_ioctl Input buffer 5: 4f190d3a
err:ntoskrnl:process_ioctl Input buffer 6: a82e94fd
err:ntoskrnl:process_ioctl Input buffer 7: 3cbb7c84
--- snip ---

There is nothing special about the input data, despite reading the KeTickCount
on server side it doesn't compare it in setup phase.

Instead the driver fills the return buffer:

--- snip ---
typedef struct _SECDRV_IOC_OUT_BUFFER
{
    DWORD dwVersionMajor;
    DWORD dwVersionMinor;
    DWORD dwVersionPatch;

    BYTE  bVerificationData[0x400];

    DWORD cbUserData;
    BYTE  bUserData[0x200];
} SECDRV_IOC_OUT_BUFFER, *PSECDRV_IOC_OUT_BUFFER;
--- snip ---

--- snip ---
Address     Value
00127D28    00000002 ; major
00127D2C    00000002 ; minor
00127D30    00000000 ; patchlevel
00127D34    DB9D783C ; tickcount ^ XOR'd with seeds
00127D38    4F190D3A ; XOR seed1
00127D3C    A82E94FD ; XOR seed2
00127D40    3CBB7C84 ; XOR seed3
--- snip ---

The "KeTickCount read is done using following snippet:

--- snip ---
00542007    A1 88025400     MOV EAX,DWORD PTR DS:[<&ntoskrnl_exe.KeTickCount>]
0054200C    8945 F0         MOV DWORD PTR SS:[EBP-10],EAX
0054200F    8B45 F0         MOV EAX,DWORD PTR SS:[EBP-10]
00542012    8B40 04         MOV EAX,DWORD PTR DS:[EAX+4]     ; High1Time
00542015    8945 F8         MOV DWORD PTR SS:[EBP-8],EAX
00542018    8B45 F0         MOV EAX,DWORD PTR SS:[EBP-10]
0054201B    8B00            MOV EAX,DWORD PTR DS:[EAX]       ; LowPart
0054201D    8945 F4         MOV DWORD PTR SS:[EBP-0C],EAX
00542020    8B45 F0         MOV EAX,DWORD PTR SS:[EBP-10]
00542023    8B4D F8         MOV ECX,DWORD PTR SS:[EBP-8]     ; High1Time
00542026    3B48 08         CMP ECX,DWORD PTR DS:[EAX+8]     ; High1Time ==
High2Time (if (KeTickCount->High1Time == KeTickCount.High2Time) break)
00542029    75 E4           JNE SHORT 0054200F
--- snip ---

cbUserData = 0x5278D11B

--- snip ---
00543820    8B45 FC         MOV EAX,DWORD PTR SS:[EBP-4]
00543823    C700 1BD17852   MOV DWORD PTR DS:[EAX],5278D11B
--- snip ---

There was not much "business logic" code after filling the output buffer.
Many obfuscation related jumps and call/rets to make debugging a misery ;-)

At a certain point the driver set:

irp.IoStatus.Information = 0

--- snip ---
00541E46    66:8365 F0 00   AND WORD PTR SS:[EBP-10],0000
--- snip ---

and irp.IoStatus.u.Status = 0xC0000001 (STATUS_UNSUCCESSFUL)

--- snip ---
00540978    8B45 0C         MOV EAX,DWORD PTR SS:[EBP+0C]
0054097B    C700 010000C0   MOV DWORD PTR DS:[EAX],C0000001
00540981    EB 07           JMP SHORT 0054098A
--- snip ---

After several hours of debugging I came to conclusion it doesn't have anything
to do with the client input, especially the KeTickCount field encoded in
buffer.
This is done on purpose by driver.

The IoCompleteRequest() call from driver within dispatch routine should not
change the returned IRP status field, it's basically a no-op in this case.

The problem is that Wine doesn't anticipate a case where a driver returns
success (0) from dispatch function while the irp.IoStatus.u.Status is non-zero.
Wine only looks at irp.IoStatus.u.Status.

http://source.winehq.org/git/wine.git/blob/70dcc417601e2e3e9ae1215690f22f7b1e0b8a9b:/dlls/ntoskrnl.exe/ntoskrnl.c#l185

--- snip ---
 136 /* process an ioctl request for a given device */
 137 static NTSTATUS process_ioctl( DEVICE_OBJECT *device, ULONG code, void
*in_buff, ULONG in_size,
 138                                void *out_buff, ULONG *out_size )
 139 {
 140     IRP irp;
...
 185     KeQueryTickCount( &count );  /* update the global KeTickCount */
 186 
 187     if (TRACE_ON(relay))
 188         DPRINTF( "%04x:Call driver dispatch %p (device=%p,irp=%p)\n",
 189                  GetCurrentThreadId(), dispatch, device, &irp );
 190 
 191     status = dispatch( device, &irp );
 192 
 193     if (TRACE_ON(relay))
 194         DPRINTF( "%04x:Ret  driver dispatch %p (device=%p,irp=%p)
retval=%08x\n",
 195                  GetCurrentThreadId(), dispatch, device, &irp, status );
 196 
 197     *out_size = (irp.IoStatus.u.Status >= 0) ? irp.IoStatus.Information :
0;
 198     if ((code & 3) == METHOD_BUFFERED)
 199     {
 200         memcpy( out_buff, irp.AssociatedIrp.SystemBuffer, *out_size );
 201         HeapFree( GetProcessHeap(), 0, irp.AssociatedIrp.SystemBuffer );
 202     }
 203     return irp.IoStatus.u.Status;
 204 }
--- snip ---

Wine looks at irp.IoStatus.u.Status >= 0, resets out_size hence the out buffer
is never copied and ioctl fails due to returned status.

Microsoft has some information on handling of IRP's:

http://support.microsoft.com/kb/320275

This is our case:

--- snip ---
Scenario 5: Complete the IRP in the dispatch routine
This scenario shows how to complete an IRP in the dispatch routine.

Important When you complete an IRP in the dispatch routine, the return status
of the dispatch routine should match the status of the value that is set in the
IoStatus block of the IRP (Irp->IoStatus.Status).

NTSTATUS
DispatchRoutine_5(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    // 
    // <-- Process the IRP here.
    // 
    Irp->IoStatus.Status = STATUS_XXX;
    Irp->IoStatus.Information = YYY;
    IoCompletRequest(Irp, IO_NO_INCREMENT);
    return STATUS_XXX;
}
--- snip ---

The SafeDisc driver doesn't follow Irp->IoStatus.Status == return dispatch
status scheme but the ioctl yet succeeds on Windows (this as this game has been
reported to work on Windows).
I'm not sure if this is intentional or oversight.

For testing I've modified the status evaluation code.

If the returned dispatch function status doesn't match Irp.IoStatus.u.Status
then set irp.IoStatus.u.Status to dispatch status (0) and don't reset out_size.
Still have it copy irp.AssociatedIrp.SystemBuffer to out_buff ((code & 3) ==
METHOD_BUFFERED).

With these changes the SafeDisc driver works.
You will see a lot of ioctls following the initial setup phase.

The game installer requires 16bpp mode setting in X server, be prepared.

Regards

-- 
Configure bugmail: http://bugs.winehq.org/userprefs.cgi?tab=email
Do not reply to this email, post in Bugzilla using the
above URL to reply.
------- You are receiving this mail because: -------
You are watching all bug changes.



More information about the wine-bugs mailing list