http://bugs.winehq.org/show_bug.cgi?id=20466
Anastasius Focht focht@gmx.net changed:
What |Removed |Added ---------------------------------------------------------------------------- Keywords| |obfuscation Status|UNCONFIRMED |NEW CC| |focht@gmx.net Component|-unknown |ntdll Summary|Brothers In Arms Hell's |Brothers in Arms: Hell's |Highway fails to start |Highway crashes on startup | |(TLS slot index allocation | |must start at non-zero | |indexes) Ever confirmed|0 |1
--- Comment #26 from Anastasius Focht focht@gmx.net --- Hello folks,
confirming, I bought the game for a few bucks and had a look at it :)
First, it seems there exist at least two variants of this game - one with SecuROM protection (the 'gore' variant) and one without DRM scheme (the 'non-gore' variant).
'PC_Release_Ship':
--- snip --- -=[ ProtectionID v0.6.5.5 OCTOBER]=- (c) 2003-2013 CDKiLLER & TippeX Build 31/10/13-21:09:09 Ready... Scanning -> Z:\home\focht.wine\drive_c\Program Files\Ubisoft\Gearbox Software\Brothers in Arms - Hell's Highway\Binaries\biahh.exe File Type : 32-Bit Exe (Subsystem : Win GUI / 2), Size : 30102144 (01CB5280h) Byte(s) -> File Appears to be Digitally Signed @ Offset 01CB4000h, size : 01280h / 04736 byte(s) [File Heuristics] -> Flag : 00000100000000000000000000000101 (0x04000005) [Entrypoint Section Entropy] : 6.65 [Debug Info] Characteristics : 0x0 | TimeDateStamp : 0x48D5096D | MajorVer : 0 / MinorVer : 0 -> (0.0) Type : 2 -> CodeView | Size : 0x6F (111) AddressOfRawData : 0x213201C | PointerToRawData : 0x1B3E01C CvSig : 0x53445352 | SigGuid A8D678DE-5D6F-4786-BD2F11415E52AE0D Age : 0xA | Pdb : r:\gbxbuilder\Build268\Incremental\Binaries\Lib\PC_Release_Ship\PCLaunch-SumacGame.pdb
[!] SecuROM Detected - Version 07.38.0006 [!] Possible CD/DVD-Key or Serial Check -> cdkey [CompilerDetect] -> Visual C++ 8.0 (Visual Studio 2005) - Scan Took : 1.417 Second(s) [00000062Fh tick(s)] [533 scan(s) done]
--- snip ---
'PC_Release_Ship_LowGore' (german):
--- snip --- -=[ ProtectionID v0.6.5.5 OCTOBER]=- (c) 2003-2013 CDKiLLER & TippeX Build 31/10/13-21:09:09 Ready...
Scanning -> Z:\home\focht.wine\drive_c\Program Files\Ubisoft\Gearbox Software\Brothers in Arms - Hell's Highway\Binaries\biahh.exe File Type : 32-Bit Exe (Subsystem : Win GUI / 2), Size : 25448448 (01845000h) Byte(s) [File Heuristics] -> Flag : 00000100000000000000000000000000 (0x04000000) [Entrypoint Section Entropy] : 6.55 [Debug Info] Characteristics : 0x0 | TimeDateStamp : 0x48E26A2C | MajorVer : 0 / MinorVer : 0 -> (0.0) Type : 2 -> CodeView | Size : 0x79 (121) AddressOfRawData : 0x150B6B8 | PointerToRawData : 0x150B6B8 CvSig : 0x53445352 | SigGuid 1C8247A3-5444-42E8-B870DCBAED28AF99 Age : 0x1 | Pdb : c:\Workspace\sumac_releases_final_pc\Binaries\Lib\PC_Release_Ship_LowGore\PCLaunch-SumacGame.pdb
[!] Possible CD/DVD-Key or Serial Check -> cdkey [CompilerDetect] -> Visual C++ 8.0 (Visual Studio 2005) [!] File appears to have no protection or is using an unknown protection - Scan Took : 1.321 Second(s) [00000068Fh tick(s)] [533 scan(s) done] --- snip ---
Both versions exhibit the same problem (crash/backtrace).
Short version: I came to conclusion this is actually a bug in the game engine itself which just works due to the way Windows allocates/manages TLS indexes.
The crash happens shortly after the PhysX SDK got initialized. TLS slots are used in many places in the engine. At one point of the crash, data from one TLS slot accessed and the returned data is interpreted (casted) to member data. Unfortunately the data (pointer) from this slot doesn't belong to the game engine but Wine itself because it reserved the slot and data early before main entry point.
TLS slot data retrieval:
--- snip --- ... 004042C0 55 PUSH EBP 004042C1 8BEC MOV EBP,ESP 004042C3 6A FF PUSH -1 004042C5 68 58895101 PUSH biahh.01518958 004042CA 64:A1 00000000 MOV EAX,DWORD PTR FS:[0] 004042D0 50 PUSH EAX 004042D1 83EC 0C SUB ESP,0C 004042D4 53 PUSH EBX 004042D5 56 PUSH ESI 004042D6 57 PUSH EDI 004042D7 A1 C0E0A501 MOV EAX,DWORD PTR DS:[1A5E0C0] 004042DC 33C5 XOR EAX,EBP 004042DE 50 PUSH EAX 004042DF 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-C] 004042E2 64:A3 00000000 MOV DWORD PTR FS:[0],EAX 004042E8 8BF9 MOV EDI,ECX 004042EA A1 1048A901 MOV EAX,DWORD PTR DS:[1A94810] ; *bad* TLS index 0 004042EF 50 PUSH EAX 004042F0 FF15 44F16701 CALL DWORD PTR DS:[<&KERNEL32.TlsGetValue>] 004042F6 85C0 TEST EAX,EAX ; not zero -> problem! 004042F8 75 78 JNZ SHORT biahh.00404372 --- snip ---
The problem is the slot index stored/referenced at 0x1A94810 After searching the whole process address space for all instructions referencing this address I found two code chunks:
Code that ought to allocate and zero-init the slot:
--- snip --- 0063605E CC INT3 0063605F CC INT3 00636060 FF15 D8F16701 CALL DWORD PTR DS:[<&KERNEL32.TlsAlloc>] 00636066 6A 00 PUSH 0 00636068 50 PUSH EAX 00636069 A3 1048A901 MOV DWORD PTR DS:[1A94810],EAX 0063606E FF15 40F16701 CALL DWORD PTR DS:[<&KERNEL32.TlsSetValue>] 00636074 C3 RETN 00636075 CC INT3 00636076 CC INT3 --- snip ---
Code that sets the TLS slot value:
--- snip --- 00401F10 55 PUSH EBP 00401F11 8BEC MOV EBP,ESP 00401F13 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8] 00401F16 8B0D 1048A901 MOV ECX,DWORD PTR DS:[1A94810] 00401F1C 50 PUSH EAX 00401F1D 51 PUSH ECX 00401F1E FF15 40F16701 CALL DWORD PTR DS:[<&KERNEL32.TlsSetValue>] 00401F24 5D POP EBP 00401F25 C2 0400 RETN 4 --- snip ---
Looks good? In theory yes. In practice there are no references to these chunks to be found. It's dead code.
There are many similar code chunks for other TLS slots/indexes which can be either found by looking at +relay log (return addresses) or searching for referencing callers in disassembly. The only code that actually references the 'magic' slot is the function at 0x004042C0.
The initial value of 0x1A94810 is zero = TLS index zero. As already said earlier, Wine builtins allocate TLS indexes in their dll main/startup phase, hence index zero is taken and initialized to non-zero value. Due to the bug in the game, slot 0 data is read and misinterpreted, leading to later crash.
Fortunately the Win32 API specification itself says there is no guarantee that application code can grab TLS index zero.
MSDN: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686801%28v=vs.85%2...
--- snip --- The threads of the process can use the TLS index in subsequent calls to the TlsFree, TlsSetValue, or TlsGetValue functions. The value of the TLS index should be treated as an opaque value; do not assume that it is an index into a zero-based array. --- snip ---
Either slot indexes that are allocated/exposed by TlsXXX API must start at non-zero values -> ntdll change, keeping index zero 'reserved' (with zero init value!).
A quick hack that also seems to be sufficient for the game is to 'steal' index zero for any further public allocation during process startup -> kernel32 ('singleton' TlsAlloc in process attach) before any higher level Wine builtins can grab it.
Both approaches allow to run the game flawlessly, no more crashes.
Regards