[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