[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