--- documentation/safedisc.txt Thu Jan 1 01:00:00 1970 +++ documentation/safedisc.txt Fri May 3 00:49:13 2002 @@ -0,0 +1,165 @@ +SafeDisc FAQ + +What is SafeDisc ? +================== + +SafeDisc is a CD copy protection system designed for Microsoft Windows. It is +widely used, especially to protect games. There are currently 2 main versions +of SafeDisc around: SafeDisc 1 and SafeDisc 2. + + +Is my program protected by SafeDisc ? +===================================== + +Look at the root directory of the program CD. The following files are present +on SafeDisc 1 protected programs: + +secdrv.sys +drvmgt.dll +clcd16.dll +clcd32.dll +clokspl.exe +dplayerx.dll (not present on the very first version of SafeDisc) + +In addition to these files, a .icd file contains the encrypted binary, while +the main executable (usually game.exe) is only a wrapper used to load and +decrypt the .icd file. Those two files are sometimes not stored directly in +the root directory of the CD but only in the location of the installed +program on your hard drive. + +For SafeDisc 2, only secdrv.sys and drvmgt.dll can be found on the CD. The +encrypted binary and dplayerx.dll are now stored inside the wrapper (usually +game.exe). + + +How do I run SafeDisc protected programs with Wine ? +==================================================== + +If your version of SafeDisc is supported (see below), your program will run +out-of-the-box (at least for SafeDisc related code :-) if you use NT mode +(--winver nt40, --winver win2k or --winver winxp). Pick the version of NT that +your program is the most likely to support (winxp with a 5 years old program +isn't a good idea). + + +How do I find out which version of SafeDisc my program uses ? +============================================================= + +There is no publicaly available version numbering for SafeDisc. However, it +seems that the version number is stored in the executable as 3 unsigned 32-bit +integers. Using an hexadecimal editor, locate the following byte pattern in +the wrapper (game.exe) + +> 426f475f 202a3930 2e302621 21202059 BoG_ *90.0&!! Y +> 793e0000 y>.. + +There should be 3 unsigned integers right after that, which are respectively +the version, subversion an revision number. + +On some versions of SafeDisc there are 3 null integers following the pattern, +before the version number. You'll then have to look at the 3 unsigned 32-bit +integers right after + +> 426f475f 202a3930 2e302621 21202059 BoG_ *90.0&!! Y +> 793e0000 00000000 00000000 00000000 y>.............. + +Don't forget that the numbers are stored in little endian and in hexadecimal. +To convert numbers stored in little endian format, invert the 4 bytes of the +32 bits integer (e.g. 2d000000 becomes 0000002d which is 45 in decimal). + +So, for instance, a version of 1.35.0 (0x00000001 0x00000023 0x00000000) would +be coded as (in hex) + +> 01 00 00 00 23 00 00 00 00 00 00 00 + +For SafeDisc 1 you can alternatively check the size of the dplayerx.dll file. + +dplayerx.dll size SafeDisc version number +------------------------------------------------------- +156.160 bytes 1.11.0 +165.888 bytes 1.35.0 +172.544 bytes 1.40.4 +173.568 bytes 1.45.0 + +Another very important information is the secdrv.sys version number, which is +unfortunately not easy to determine. For known secdrv.sys versions, you can +check the file size according to the table below. + +secdrv.sys size secdrv.sys version number +--------------------------------------------------------- +14.304 bytes 1.3.0 (SafeDisc 1.11.0) +14.368 bytes 1.3.0 (SafeDisc 1.35.0) +10.848 bytes 1.3.0 (SafeDisc 1.40.4, 1.45.0) +18.768 bytes 2.2.0 (SafeDisc 2.5.30) + +If you have another version of SafeDisc please contribute to these tables. + + +Which version of SafeDisc are currently supported ? +=================================================== + +This hasn't been determined yet. SafeDisc support for Wine has been developped +using a game protected with SafeDisc 1.35.0. SafeDisc 1.40.4 and 1.45.0 have +been reported to work too. All SafeDisc versions which use secdrv.sys 1.3.0 +should work. + +Lower version of secdrv.sys might work too. SafeDisc 2 is not supported yet. + + +How does SafeDisc 1 work ? +========================== + +SafeDisc encrypts the real executable into a .icd file, and uses a wrapper to +decrypt the executable. + +The wrapper contains 3 code sections: `.text', `.txt' and `.txt2'. `.txt' is +encrypted. + +The wrapper starts by decrypting itself, using the checksum of `.text', the +binary content of `.txt2' and some values which depend on debugger detection +tests. If a debugger is loaded, if a software breakpoint is set in the first 8 +bytes of any of the kernel32.dll functions, or if the `.text' and `.txt2' +sections have been modified (this includes setting a software breakpoint in +the code), the `.txt' section won't be decrypted correctly and a crash will +occur. + +On NT, the process of detecting a debugger involves loading the kernel-space +driver secdrv.sys, which I implemented as a user-space code for Wine. On +Windows 95, 98 or Me, it involves executing arbitrary code in ring 0 mode +(kernel mode). This is not supported by Wine (as the underlying OS isn't as +broken as win9x), so that's why you have to use NT mode. + +When the `.txt' section has been decrypted, the wrapper will then check for +the CD key using direct SCSI operations on the CD driver. If the CD key +doesn't match the expected value, a message box pops up to ask you to insert +the original CD in the drive. + +The wrapper then loads and starts clokspl.exe for a still unknown purpose. + +The last stage consists in creating the game.icd in a suspended state. The +suspended process memory is then written with some initialization code, and +SetThreadContext is called to jump to that code. The initialization code will +load dplayerx.dll (which is encrypted in the same way as the wrapper, with 3 +code sections), and will decrypt the main executable. Control is then +transfered to the main executable, and the game starts. + +Have you ever tried to rename game.icd to game.exe, and run it ? It will +crash, because WinMain is encrypted. It needs to be decrypted by the +initialization code, which is found in the wrapper and in dplayerx.dll. + +That's pretty much all I know about SafeDisc. Don't ask me how to remove +SafeDisc from a game. I don't know how to do so. + +If you have more information, especially about debugger detection and +secdrv.sys, please let me know. + + +My version of SafeDisc is not supported. What should I do ? +=========================================================== + +Implement support for it :-) + +For unsupported SafeDisc 1 versions you will probably 'only' need to implement +the secdrv.sys that comes with your program. For SafeDisc 2 things are more +difficult. I'm currently working on that, but please be patient. + --- include/secdrv.h Thu Jan 1 01:00:00 1970 +++ include/secdrv.h Sat Apr 6 23:32:54 2002 @@ -0,0 +1,58 @@ +/* + * SafeDisc copy protection driver + * + * Copyright 2002 Laurent Pinchart + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __WINE_SECDRV_H +#define __WINE_SECDRV_H + +#include "windef.h" + +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; + +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; + +#define SECDRV_CMD_INFO_DR (0x0000003c) +#define SECDRV_CMD_INFO_IDT (0x0000003d) +#define SECDRV_CMD_SETUP (0x0000003e) + +extern BOOL SECDRV_DeviceIo_SafeDisc( LPVOID lpvInBuffer, DWORD cbInBuffer, + LPVOID lpvOutBuffer, DWORD cbOutBuffer ); + +#endif /* __WINE_SECDRV_H */ --- win32/secdrv.c Thu Jan 1 01:00:00 1970 +++ win32/secdrv.c Thu May 23 20:52:08 2002 @@ -0,0 +1,206 @@ +/* + * SafeDisc copy protection driver + * + * Copyright 2002 Laurent Pinchart + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" +#include "wine/port.h" + +#include +#include +#include + +#include "windef.h" +#include "winbase.h" +#include "winreg.h" +#include "winerror.h" +#include "file.h" +#include "winioctl.h" +#include "winnt.h" +#include "secdrv.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(vxd); + +static BOOL SECDRV_GetDRInfo( LPVOID lpvInBuffer, DWORD cbInBuffer, + LPVOID lpvOutBuffer, LPDWORD lpCbOutBuffer ); +static BOOL SECDRV_GetIdtInfo( LPVOID lpvInBuffer, DWORD cbInBuffer, + LPVOID lpvOutBuffer, LPDWORD lpCbOutBuffer ); +static BOOL SECDRV_Setup( LPVOID lpvInBuffer, DWORD cbInBuffer, + LPVOID lpvOutBuffer, LPDWORD lpCbOutBuffer ); +static VOID SECDRV_BuildVerificationData( LPVOID lpBuffer ); + +static unsigned int contextDr1 = 0x00000000; +static unsigned int contextDr7 = 0x00000400; + +BOOL SECDRV_DeviceIo_SafeDisc( LPVOID lpvInBuffer, DWORD cbInBuffer, + LPVOID lpvOutBuffer, DWORD cbOutBuffer ) +{ + PSECDRV_IOC_IN_BUFFER pInBuffer = (PSECDRV_IOC_IN_BUFFER)lpvInBuffer; + PSECDRV_IOC_OUT_BUFFER pOutBuffer = (PSECDRV_IOC_OUT_BUFFER)lpvOutBuffer; + BOOL retv; + + TRACE( "Secdrv %ld.%ld.%ld\n", + pInBuffer->dwVersionMajor, + pInBuffer->dwVersionMinor, + pInBuffer->dwVersionPatch ); + + if ( ! pInBuffer || ! pOutBuffer ) + { + FIXME( "pInBuffer: %p pOutBuffer: %p\n", pInBuffer, pOutBuffer ); + return FALSE; + } + + if ( cbInBuffer != 0x514 ) + { + FIXME( "cbInBuffer: %lx\n", cbInBuffer ); + return FALSE; + } + + if ( cbOutBuffer != 0x610 && cbOutBuffer != 0xc18 ) + { + FIXME( "cbOutBuffer: %lx\n", cbOutBuffer ); + return FALSE; + } + + retv = FALSE; + + switch( pInBuffer->dwCommand ) { + case SECDRV_CMD_INFO_DR: + TRACE( "SECDRV_CMD_INFO_DR\n" ); + retv = SECDRV_GetDRInfo( pInBuffer->bUserData, pInBuffer->cbUserData, + pOutBuffer->bUserData, &pOutBuffer->cbUserData ); + break; + case SECDRV_CMD_INFO_IDT: + TRACE( "SECDRV_CMD_INFO_IDT\n" ); + retv = SECDRV_GetIdtInfo( pInBuffer->bUserData, pInBuffer->cbUserData, + pOutBuffer->bUserData, &pOutBuffer->cbUserData ); + break; + case SECDRV_CMD_SETUP: + TRACE( "SECDRV_CMD_INFO_SETUP\n" ); + retv = SECDRV_Setup( pInBuffer->bUserData, pInBuffer->cbUserData, + pOutBuffer->bUserData, &pOutBuffer->cbUserData ); + break; + default: + FIXME( "unsupported command %lx\n", pInBuffer->dwCommand ); + break; + } + + if ( retv ) + { + pOutBuffer->dwVersionMajor = 3; + pOutBuffer->dwVersionMinor = 5; + pOutBuffer->dwVersionPatch = 0; + SECDRV_BuildVerificationData( pOutBuffer->bVerificationData ); + } + + return retv; +} + +static VOID SECDRV_BuildVerificationData( LPVOID lpBuffer ) +{ + DWORD dwRandom = 0xf367ac7f; + LPDWORD lpDwBuffer = (LPDWORD)lpBuffer; + int i; + + /* lpDwBuffer[0] should be initialized with KeTickCount.LowPart. + * If done, KeTickCount.LowPart should also be accessible from user + * space by a read operation at address 0x7ffe0000. + */ + lpDwBuffer[0] = 0x12345678; + + for ( i = 3; i != 0; --i ) + { + dwRandom = 0x361962e9 - dwRandom * 0x0d5acb1b; + lpDwBuffer[i] = dwRandom; + lpDwBuffer[0] ^= dwRandom; + } +} + +static BOOL SECDRV_GetDRInfo( LPVOID lpvInBuffer, DWORD cbInBuffer, + LPVOID lpvOutBuffer, LPDWORD lpCbOutBuffer ) +{ + unsigned int dwDebugRegister; + + if ( cbInBuffer != 0 ) + { + FIXME( "cbInBuffer != 0\n" ); + return FALSE; + } + + if ( contextDr1 == 0xbd331200 ) + { + dwDebugRegister = 0x3871dd10; + } + else + { + dwDebugRegister = contextDr7; + } + + *(LPDWORD)lpvOutBuffer = dwDebugRegister & 0x00000500; + *lpCbOutBuffer = 4; + + return TRUE; +} + +static BOOL SECDRV_GetIdtInfo( LPVOID lpvInBuffer, DWORD cbInBuffer, + LPVOID lpvOutBuffer, LPDWORD lpCbOutBuffer ) +{ + struct _IDTR { + WORD limit; + DWORD base; + } idtr; + + ULONGLONG *idt; + DWORD dwOffset; + + if ( cbInBuffer != 0 ) + return FALSE; + + asm( "sidtl %0" : "=m"(idtr) ); + idt = (ULONGLONG*)idtr.base; + + /* dwOffset = ( idt[3] & 0x0000ffff ) - ( idt[1] & 0x0000ffff ); */ + dwOffset = 0x1000; + + if ( dwOffset > 0x100 ) + { + *(LPDWORD)lpvOutBuffer = 0x2c8; + } + else + { + *(LPDWORD)lpvOutBuffer = dwOffset; + contextDr1 = 0xbd331200; + } + + *lpCbOutBuffer = 4; + + return TRUE; +} + +static BOOL SECDRV_Setup( LPVOID lpvInBuffer, DWORD cbInBuffer, + LPVOID lpvOutBuffer, LPDWORD lpCbOutBuffer ) +{ + if ( cbInBuffer != 0 ) + return FALSE; + + *(LPDWORD)lpvOutBuffer = 0x5278d11b; + *lpCbOutBuffer = 4; + + return TRUE; +} Index: dlls/ntdll/Makefile.in =================================================================== RCS file: /home/wine/wine/dlls/ntdll/Makefile.in,v retrieving revision 1.27 diff -u -r1.27 Makefile.in --- dlls/ntdll/Makefile.in 14 May 2002 20:55:01 -0000 1.27 +++ dlls/ntdll/Makefile.in 23 May 2002 18:54:33 -0000 @@ -93,6 +93,7 @@ $(TOPOBJDIR)/win32/init.c \ $(TOPOBJDIR)/win32/kernel32.c \ $(TOPOBJDIR)/win32/newfns.c \ + $(TOPOBJDIR)/win32/secdrv.c \ $(TOPOBJDIR)/win32/time.c \ cdrom.c \ critsection.c \ Index: win32/device.c =================================================================== RCS file: /home/wine/wine/win32/device.c,v retrieving revision 1.62 diff -u -r1.62 device.c --- win32/device.c 23 May 2002 19:35:18 -0000 1.62 +++ win32/device.c 23 May 2002 19:42:22 -0000 @@ -40,6 +40,7 @@ #include "msdos.h" #include "miscemu.h" #include "stackframe.h" +#include "secdrv.h" #include "wine/server.h" #include "wine/debug.h" @@ -105,6 +106,12 @@ LPVOID lpvOutBuffer, DWORD cbOutBuffer, LPDWORD lpcbBytesReturned, LPOVERLAPPED lpOverlapped); + +static BOOL DeviceIo_SECDRV (DWORD dwIoControlCode, + LPVOID lpvInBuffer, DWORD cbInBuffer, + LPVOID lpvOutBuffer, DWORD cbOutBuffer, + LPDWORD lpcbBytesReturned, + LPOVERLAPPED lpOverlapped); /* * VxD names are taken from the Win95 DDK */ @@ -251,6 +258,9 @@ /* WINE additions, ids unknown */ { "MONODEBG.VXD", 0x4242, NULL, DeviceIo_MONODEBG }, + /* SafeDisc copy protection */ + { "SECDRV", 0xef00, NULL, NULL }, + { NULL, 0, NULL, NULL } }; @@ -480,6 +490,12 @@ SetLastError( ERROR_CALL_NOT_IMPLEMENTED ); return FALSE; break; + case CTL_CODE( 0xef00, 0x901, METHOD_NEITHER, FILE_ANY_ACCESS ): + return DeviceIo_SECDRV( dwIoControlCode, + lpvInBuffer, cbInBuffer, + lpvOutBuffer, cbOutBuffer, + lpcbBytesReturned, lpOverlapped ); + break; default: FIXME( "ignored dwIoControlCode=%08lx\n",dwIoControlCode); SetLastError( ERROR_CALL_NOT_IMPLEMENTED ); @@ -1502,6 +1518,31 @@ } return TRUE; } + +/* this is used by SafeDisc copy protection */ +static BOOL DeviceIo_SECDRV(DWORD dwIoControlCode, + LPVOID lpvInBuffer, DWORD cbInBuffer, + LPVOID lpvOutBuffer, DWORD cbOutBuffer, + LPDWORD lpcbBytesReturned, + LPOVERLAPPED lpOverlapped) +{ + switch ((dwIoControlCode >> 2) & 0x0fff) { + case 0x901: /* SafeDisc */ + return SECDRV_DeviceIo_SafeDisc(lpvInBuffer, cbInBuffer, + lpvOutBuffer, cbOutBuffer); + default: + FIXME("(%ld,%p,%ld,%p,%ld,%p,%p): stub\n", + dwIoControlCode, + lpvInBuffer,cbInBuffer, + lpvOutBuffer,cbOutBuffer, + lpcbBytesReturned, + lpOverlapped + ); + break; + } + return FALSE; +} + /* pccard */ static BOOL DeviceIo_PCCARD (DWORD dwIoControlCode, LPVOID lpvInBuffer, DWORD cbInBuffer,