[Bug 46030] Trackmania Unlimiter 1.3.x for TrackMania United Forever 2.11.26 crashes at account selection screen (heap manager differences, incorrect assumptions of mod on internal game engine data structures)
wine-bugs at winehq.org
wine-bugs at winehq.org
Mon Oct 22 13:45:24 CDT 2018
https://bugs.winehq.org/show_bug.cgi?id=46030
Anastasius Focht <focht at gmx.net> changed:
What |Removed |Added
----------------------------------------------------------------------------
Keywords| |download, obfuscation
Summary|Trackmania Unlimiter |Trackmania Unlimiter 1.3.x
|crashes at startup |for TrackMania United
| |Forever 2.11.26 crashes at
| |account selection screen
| |(heap manager differences,
| |incorrect assumptions of
| |mod on internal game engine
| |data structures)
Ever confirmed|0 |1
URL| |http://www.kemot0055.boo.pl
| |/tm/unlimiter/TMInfinity_1.
| |3.0.1.zip
Status|UNCONFIRMED |NEW
CC| |focht at gmx.net
--- Comment #1 from Anastasius Focht <focht at gmx.net> ---
Hello folks,
it seems OP is talking about an extension/mod to the "TrackMania United
Forever" game. There are multiple titles:
https://appdb.winehq.org/objectManager.php?sClass=application&iId=7064
* TMN TrackMania Nations
* TMNF TrackMania Nations Forever
* TMO TrackMania Original
* TMS TrackMania Sunrise
* TMU TrackMania United
* TMUF TrackMania United Forever
The game name/version info is buried in the attached log file.
Game download is mentioned here:
http://www.tm-forum.com/viewtopic.php?t=30755
Download: http://files.trackmaniaforever.com/tmunitedforever_setup.exe
The actual mod "Trackmania Unlimiter 1.3.x" is mentioned here:
http://tm.maniazones.com/forum/index.php?topic=43895.0
Download: http://www.kemot0055.boo.pl/tm/unlimiter/TMInfinity_1.3.0.1.zip
It seems the mod uses 'TMInfinity.exe' launcher process which injects
'TMInfinity.dll' into the main game process using
'CreateRemoteThread'/'LoadLibrary' mechanism (also making assumptions about
fixed load addresses for core libs) .
--- snip ---
Base Module Party Path
00400000 tmforever.exe User C:\Program Files
(x86)\TmUnitedForever\TmForever.exe
00E30000 d3dx9_30.dll System C:\windows\syswow64\d3dx9_30.dll
01710000 tminfinity.dll User C:\Program Files
(x86)\TmUnitedForever\TMInfinity.dll
026F0000 wrap_oal.dll User C:\Program Files
(x86)\TmUnitedForever\wrap_oal.dll
10000000 openal32.dll User C:\Program Files
(x86)\TmUnitedForever\OpenAL32.dll
18000000 binkw32.dll User C:\Program Files
(x86)\TmUnitedForever\binkw32.dll
799E0000 dsound.dll System C:\windows\system32\dsound.dll
79BE0000 msvcr100.dll System C:\windows\system32\msvcr100.dll
79CD0000 msvcp100.dll System C:\windows\system32\msvcp100.dll
7A820000 opengl32.dll System C:\windows\system32\opengl32.dll
7ABB0000 winepulse.drv System C:\windows\system32\winepulse.drv
...
--- snip ---
--- snip ---
$ pwd
/home/focht/.wine/drive_c/Program Files (x86)/TmUnitedForever
...
$ WINEDEBUG=+seh,+relay wine ./TMInfinity.exe >>log.txt 2>&1
...
002a:Call KERNEL32.CreateProcessA(00402108
"TmForever.exe",00000000,00000000,00000000,00000000,00000000,00000000,00000000,0033fdd8,0033fe1c)
ret=0040109a
...
002c:Call KERNEL32.__wine_kernel_init() ret=7bc59f9d
002a:Ret KERNEL32.CreateProcessA() retval=00000001 ret=0040109a
002a:Call user32.WaitForInputIdle(00000054,ffffffff) ret=004010af
...
002a:Call KERNEL32.OpenProcess(001fffff,00000000,0000002b) ret=004010c0
002a:Ret KERNEL32.OpenProcess() retval=0000005c ret=004010c0
...
002a:Call KERNEL32.VirtualAllocEx(0000005c,00000000,0000000e,00003000,00000004)
ret=004010d4
...
002a:Ret KERNEL32.VirtualAllocEx() retval=01700000 ret=004010d4
...
002a:Call
KERNEL32.WriteProcessMemory(0000005c,01700000,0040218c,0000000e,00000000)
ret=004010e7
...
002a:Ret KERNEL32.WriteProcessMemory() retval=00000001 ret=004010e7
002a:Call KERNEL32.GetModuleHandleA(004021f8 "kernel32.dll") ret=004010f7
002a:Ret KERNEL32.GetModuleHandleA() retval=7b420000 ret=004010f7
002a:Call KERNEL32.GetProcAddress(7b420000,004021e8 "LoadLibraryA")
ret=004010fe
002a:Ret KERNEL32.GetProcAddress() retval=7b426c5c ret=004010fe
002a:Call
KERNEL32.CreateRemoteThread(0000005c,00000000,00000000,7b426c5c,01700000,00000000,00000000)
ret=0040110f
...
002a:Ret KERNEL32.CreateRemoteThread() retval=00000064 ret=0040110f
...
0030:Starting thread proc 0x7b426c5c (arg=0x1700000)
0030:Call KERNEL32.LoadLibraryA(01700000 "TMInfinity.dll") ret=7bc81644
...
0030:Call KERNEL32.VirtualProtect(00401000,00727000,00000020,024bfb1c)
ret=01739024
0030:Ret KERNEL32.VirtualProtect() retval=00000001 ret=01739024
0030:Ret PE DLL (proc=0x173aafe,module=0x1710000
L"TMInfinity.dll",reason=PROCESS_ATTACH,res=(nil)) retval=1
0030:Ret KERNEL32.LoadLibraryA() retval=01710000 ret=7bc81644
...
--- snip ---
The mod hooks very deeply into the engine and makes assumptions about game
engine internal data structures (in-memory layout). Tidbit: It patches internal
functions directly at fixed offsets (not at entry) which is very dangerous as
any game update will break the mod unless it uses dynamic disassembler/opcode
pattern search/matching techniques.
--- snip ---
tmforever.sub_69AF30: | ; internal function, no export
0069AF30 | push FFFFFFFF |
0069AF32 | push <tmforever.sub_AB1D20> |
0069AF37 | mov eax,dword ptr fs:[0] |
0069AF3D | push eax |
0069AF3E | sub esp,10 |
0069AF41 | push esi |
0069AF42 | mov eax,dword ptr ds:[CCC150] |
0069AF47 | xor eax,esp |
0069AF49 | push eax |
0069AF4A | lea eax,dword ptr ss:[esp+18] |
0069AF4E | mov dword ptr fs:[0],eax |
0069AF54 | lea esi,dword ptr ds:[ecx+6C] |
0069AF57 | push 0 |
0069AF59 | mov ecx,esi |
0069AF5B | mov dword ptr ss:[esp+24],0 |
0069AF63 | call <tmforever.sub_7EC070> |
0069AF68 | jmp tminfinity.17257F0 | ; patched by mod for redirection
0069AF6D | lea ecx,dword ptr ss:[esp+C] | ; return from mod trampoline
0069AF71 | call <tmforever.sub_428A00> |
0069AF76 | push 1 |
0069AF78 | push 2F |
0069AF7A | lea ecx,dword ptr ss:[esp+10] |
0069AF7E | mov byte ptr ss:[esp+28],1 |
0069AF83 | call <tmforever.sub_900190> |
0069AF88 | lea ecx,dword ptr ss:[esp+8] |
0069AF8C | push ecx |
0069AF8D | mov ecx,esi |
0069AF8F | call <tmforever.sub_420E20> |
0069AF94 | push 1 |
0069AF96 | push 2F |
0069AF98 | lea ecx,dword ptr ss:[esp+30] |
0069AF9C | call <tmforever.sub_900210> |
0069AFA1 | test eax,eax |
0069AFA3 | je tmforever.69AFF8 |
0069AFA5 | jmp tmforever.69AFB0 |
0069AFA7 | lea esp,dword ptr ss:[esp] |
0069AFAE | mov edi,edi |
0069AFB0 | mov edx,dword ptr ss:[esp+2C] |
0069AFB4 | mov eax,dword ptr ss:[esp+28] |
0069AFB8 | lea ecx,dword ptr ss:[esp+10] |
0069AFBC | push ecx |
0069AFBD | lea ecx,dword ptr ss:[esp+C] |
0069AFC1 | mov dword ptr ss:[esp+14],edx |
0069AFC5 | mov dword ptr ss:[esp+18],eax |
0069AFC9 | call <tmforever.sub_8FF6D0> |
0069AFCE | push 1 |
0069AFD0 | push 2F |
0069AFD2 | lea ecx,dword ptr ss:[esp+10] |
0069AFD6 | call <tmforever.sub_900190> |
0069AFDB | lea edx,dword ptr ss:[esp+8] |
0069AFDF | push edx |
0069AFE0 | mov ecx,esi |
0069AFE2 | call <tmforever.sub_420E20> |
0069AFE7 | push 1 |
0069AFE9 | push 2F |
0069AFEB | lea ecx,dword ptr ss:[esp+30] |
0069AFEF | call <tmforever.sub_900210> |
0069AFF4 | test eax,eax |
0069AFF6 | jne tmforever.69AFB0 |
0069AFF8 | mov ecx,dword ptr ss:[esp+C] |
0069AFFC | mov eax,dword ptr ds:[BBF7B8] |
0069B001 | cmp ecx,eax |
0069B003 | je tmforever.69B02B |
0069B005 | test byte ptr ds:[ecx-1],80 |
0069B009 | lea eax,dword ptr ds:[ecx-1] |
0069B00C | je tmforever.69B011 |
0069B00E | lea eax,dword ptr ds:[ecx-4] |
0069B011 | push eax |
0069B012 | call <tmforever.sub_402F7B> |
0069B017 | mov eax,dword ptr ds:[BBF7B8] |
0069B01C | add esp,4 |
0069B01F | mov dword ptr ss:[esp+8],0 |
0069B027 | mov dword ptr ss:[esp+C],eax |
0069B02B | mov ecx,dword ptr ss:[esp+2C] |
0069B02F | cmp ecx,eax |
0069B031 | je tmforever.69B048 |
0069B033 | test byte ptr ds:[ecx-1],80 |
0069B037 | lea eax,dword ptr ds:[ecx-1] |
0069B03A | je tmforever.69B03F |
0069B03C | lea eax,dword ptr ds:[ecx-4] |
0069B03F | push eax |
0069B040 | call <tmforever.sub_402F7B> |
0069B045 | add esp,4 |
0069B048 | mov ecx,dword ptr ss:[esp+18] |
0069B04C | mov dword ptr fs:[0],ecx |
0069B053 | pop ecx |
0069B054 | pop esi |
0069B055 | add esp,1C |
0069B058 | ret 8 |
...
007EC070 | mov eax,dword ptr ss:[esp+4] |
007EC074 | mov dword ptr ds:[ecx],eax |
007EC076 | ret 4 |
--- snip ---
Mod trampoline:
--- snip ---
017257F0 | mov eax,esi |
017257F2 | sub eax,6C | ; offset data struct start
017257F5 | push eax |
017257F6 | call tminfinity.1725810 | ; modify/inject engine data
017257FB | add esp,4 |
017257FE | lea eax,dword ptr ss:[esp+28] |
01725802 | push eax |
01725803 | jmp dword ptr ds:[17501F4] | ; retpol: tmforever.0069AF6D
--- snip ---
Data struct:
--- snip ---
$-8 0A859E50 000000A0 ; block size (round)
$-4 0A859E54 04455355 ; heap in-use magic 'USE'
$ ==> 0A859E58 00B7DB1C
$+4 0A859E5C 00000000
$+8 0A859E60 00000000
$+C 0A859E64 00000000
$+10 0A859E68 00000000
$+14 0A859E6C 04F54F38
$+18 0A859E70 40000733
$+1C 0A859E74 40002683
$+20 0A859E78 40000A10
$+24 0A859E7C 00000000
$+28 0A859E80 40000733
$+2C 0A859E84 40002683
$+30 0A859E88 40000A10
$+34 0A859E8C 0000000D
$+38 0A859E90 0A8599F1 ; "Decors/Signs/"
$+3C 0A859E94 00000005
$+40 0A859E98 040000A5
$+44 0A859E9C 00000050
$+48 0A859EA0 04F5000A
$+4C 0A859EA4 FFFFFFFF
$+50 0A859EA8 FFFFFFFF
$+54 0A859EAC 00000000
$+58 0A859EB0 00000000
$+5C 0A859EB4 00000000
$+60 0A859EB8 00000000
$+64 0A859EBC 0000000D
$+68 0A859EC0 0A859641 ; "Decors/Signs/"
$+6C 0A859EC4 00000000 ; base offset from engine, prev: 0, hooker: 0x1
$+70 0A859EC8 00000000 ; prev: 0, hooker: 0x0A859A20 -> see follow dump
$+74 0A859ECC 00000000 ; prev: 0, hooker: 0x1
$+78 0A859ED0 04F52CE0
$+7C 0A859ED4 11B5542F
$+80 0A859ED8 11B5542E
$+84 0A859EDC 00000001
$+88 0A859EE0 00000001
$+8C 0A859EE4 FFFFFFFF
$+90 0A859EE8 00000000
$+94 0A859EEC 00000000
$+98 0A859EF0 00000000
$+9C 0A859EF4 04F52170
$+A0 0A859EF8 000004C1
$+A4 0A859EFC 45455246 ; heap-free magic
--- snip ---
Data block 0x0A859A20 allocated and inserted by mod:
--- snip ---
$-8 0A859A18 00000012 ; block len (rounded)
$-4 0A859A1C 08455355 ; heap in-use magic 'USE'
$ ==> 0A859A20 00000006 ; data length
$+4 0A859A24 0A859A39 ; "Alpine" (data)
$+8 0A859A28 04F53FE8
$+C 0A859A2C 04F53EE0
$+10 0A859A30 00000028
$+14 0A859A34 20455355 ; next block
--- snip ---
Example data block allocated and inserted when mod is not active:
--- snip ---
$-8 00000010 ; block len (rounded)
$-4 04455355 ; heap in-use magic 'USE'
$ ==> 00000001 ; flag: will be later accessed
$+4 00000006 ; data length
$+8 0A859F19 ; "Decors"
$+C 04F54E50
$+10 00000010
$+14 08455355 ; next block
--- snip ---
For whatever reason the mod allocates/uses an incorrect struct layout which has
one member missing. Wine's heap-in-use magic data is mistakenly used as first
member data by the engine, causing some erroneous calculations, leading to the
crash.
--- snip ---
...
002c:Call ntdll.RtlAllocateHeap(013c0000,00000000,00000008) ret=00407a97
002c:Ret ntdll.RtlAllocateHeap() retval=04f174b8 ret=00407a97
002c:Call ntdll.RtlAllocateHeap(013c0000,00000000,00000008) ret=00407a97
002c:Ret ntdll.RtlAllocateHeap() retval=04f174d0 ret=00407a97
002c:Call msvcr100.memcpy(04f174d1,0a709c69,00000006) ret=0173a22b
002c:Ret msvcr100.memcpy() retval=04f174d1 ret=0173a22b
002c:Call ntdll.RtlFreeHeap(013c0000,00000000,0a709c68) ret=00405bd4
002c:Ret ntdll.RtlFreeHeap() retval=00000001 ret=00405bd4
002c:Call ntdll.RtlAllocateHeap(013c0000,00000000,0000000f) ret=00407a97
002c:Ret ntdll.RtlAllocateHeap() retval=0a709c68 ret=00407a97
002c:Call ntdll.RtlAllocateHeap(013c0000,00000000,00000014) ret=00407a97
002c:Ret ntdll.RtlAllocateHeap() retval=04f174e8 ret=00407a97
002c:Call ntdll.RtlAllocateHeap(013c0000,00000000,00000008) ret=00407a97
002c:Ret ntdll.RtlAllocateHeap() retval=04f17508 ret=00407a97
002c:trace:seh:raise_exception code=c0000005 flags=0 addr=0x4683e3 ip=004683e3
tid=002c
002c:trace:seh:raise_exception info[0]=00000000
002c:trace:seh:raise_exception info[1]=471c0f5c
002c:trace:seh:raise_exception eax=422a9aa8 ebx=04f174ec ecx=471c0f58
edx=00000003 esi=471c0f58 edi=0a709d8c
002c:trace:seh:raise_exception ebp=0033f600 esp=0033f5cc cs=0023 ds=002b
es=002b fs=0063 gs=006b flags=00210212
002c:trace:seh:call_stack_handlers calling handler at 0x407bf0 code=c0000005
flags=0
002c:trace:seh:call_stack_handlers handler at 0x407bf0 returned 1
002c:trace:seh:call_stack_handlers calling handler at 0xa812eb code=c0000005
flags=0
--- snip ---
--- snip ---
tmforever.sub_403151: | internal function
00403151 | push C |
00403153 | push tmforever.C68148 |
00403158 | call <tmforever.sub_407B88> | ; stack canary check
0040315D | and dword ptr ss:[ebp-1C],0 |
00403161 | mov esi,dword ptr ss:[ebp+C] | ; esi = 0x8
00403164 | mov eax,esi |
00403166 | imul eax,dword ptr ss:[ebp+10] | ; invld 08455355 (magic) * 8
0040316A | add dword ptr ss:[ebp+8],eax | ; 0A859A10 + 422A9AA8 = 4CB034B8
0040316D | and dword ptr ss:[ebp-4],0 |
00403171 | dec dword ptr ss:[ebp+10] | ; invld 08455354 (08455355-1)
00403174 | js tmforever.403181 | ; sign bit not trigged in Wine
00403176 | sub dword ptr ss:[ebp+8],esi |
00403179 | mov ecx,dword ptr ss:[ebp+8] | ; invld address
0040317C | call dword ptr ss:[ebp+14] | ; 004683E0
0040317F | jmp tmforever.403171 |
00403181 | mov dword ptr ss:[ebp-1C],1 |
00403188 | mov dword ptr ss:[ebp-4],FFFFFFFE|
0040318F | call <tmforever.sub_40319C> |
00403194 | call <tmforever.sub_407BCD> |
00403199 | ret 10 |
--- snip ---
Why does this work on Windows? No idea. It most likely works just by chance if
the last DWORD from the preceding memory block has a value range that triggers
a specific code path (00403171: dec, sign bit -> 8xxxxxxx -> jmp). It's still
incorrect but it doesn't lead to a crash.
I made a very ugly hack as further proof: cutting the most significant bit from
Wine's heap manager 'ARENA_INUSE' struct 'unused_bytes' member (8 bit) and
"repurposed" it to trigger the other code path via sign jump (bit set to 1).
The mod/game works after that. Profile selection -> offline game -> create new
game.
The game/mod wouldn't survive a millisecond on Windows when run with heap
validation active (pageheap/gflags). As a sad fact, gazillion of other
apps/games wouldn't survive either ;-)
This is what I came up with after some hours of debugging. IMHO
INVALID/WONTFIX. I'm open for alternative analysis/conclusions though, bring it
on ;-)
$ sha1sum tmunitedforever_setup.exe
c5e45181d9bfd96254616ec3c8c896dbde7d3b23 tmunitedforever_setup.exe
$ du -sh tmunitedforever_setup.exe
1.2G tmunitedforever_setup.exe
$ sha1sum TMInfinity_1.3.0.1.zip
7498369dc5227d45791f7325c5fff8a00b26c5da TMInfinity_1.3.0.1.zip
$ du -sh TMInfinity_1.3.0.1.zip
19M TMInfinity_1.3.0.1.zip
$ wine --version
wine-3.18-118-g377243b411
Regards
--
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