http://bugs.winehq.org/show_bug.cgi?id=30155
Anastasius Focht focht@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@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/70dcc417601e2e3e9ae1215690f22f7b1...
--- 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