[Bug 36486] New: Age of Wushu: frequent display of in-game "Don't speed up!" dialog tip disrupts gameplay (Wine fails kernel32.GetTickCount API entry hook check)

wine-bugs at winehq.org wine-bugs at winehq.org
Sun May 18 17:07:42 CDT 2014


https://bugs.winehq.org/show_bug.cgi?id=36486

            Bug ID: 36486
           Summary: Age of Wushu: frequent display of in-game "Don't speed
                    up!" dialog tip disrupts gameplay (Wine fails
                    kernel32.GetTickCount API entry hook check)
           Product: Wine
           Version: 1.7.19
          Hardware: x86
                OS: Linux
            Status: NEW
          Severity: normal
          Priority: P2
         Component: kernel32
          Assignee: wine-bugs at winehq.org
          Reporter: focht at gmx.net

Hello folks,

during investigation of bug 36465 I found this goodie, deserving it's own bug
of course :)

Searching the net reveals similar reports for Windows and MAC:

http://www.ageofwushu.com/forums/viewtopic.php?f=29&t=7339

http://www.ageofwushu.com/forums/viewtopic.php?f=30&t=14469

http://portingteam.com/topic/9284-age-of-wushu-dont-speed-up-tip-spam/

This disguised message is in fact the result of a failed API hook check.

The vendor obviously doesn't want to go into technical details/reveal this, see
the support forums/site for answers to this problem.

Enter Wine and get the answers :)

One hint is contained in the game log file 'bin/trace.log'

With relay thunks emitted (+relay channel):

--- snip ---
...
[2014-05-18 22:01:13.573] Time Func Pos Begin QueryPerformanceCounter=54
QueryPerformanceFrequency=54 GetTickCount=54 timeGetTime=54 timeSetEvent=54
SetTimer=54
...
--- snip ---

without relay thunks:

--- snip ---
[2014-05-18 22:23:02.818] Time Func Pos Begin QueryPerformanceCounter=8d
QueryPerformanceFrequency=8d GetTickCount=55 timeGetTime=55 timeSetEvent=8d
SetTimer=55
--- snip ---

The hex codes are the first opcode byte of each API entry.

The protection code makes a snapshot of certain API entries on startup.
The entry bytes are stored for later comparison to detect dynamic hooking
(through dll injection for example).

--- snip ---
...
0056:Call KERNEL32.GetModuleHandleA(11a8eef0 "kernel32.dll") ret=1134c3b1
0056:Ret  KERNEL32.GetModuleHandleA() retval=7b810000 ret=1134c3b1
0056:Call KERNEL32.GetProcAddress(7b810000,11a8eed4 "QueryPerformanceCounter")
ret=1134c3c3
0056:Ret  KERNEL32.GetProcAddress() retval=7b824afc ret=1134c3c3
0056:Call KERNEL32.GetModuleHandleA(11a8eef0 "kernel32.dll") ret=1134c3d9
0056:Ret  KERNEL32.GetModuleHandleA() retval=7b810000 ret=1134c3d9
0056:Call KERNEL32.GetProcAddress(7b810000,11a8eeb4
"QueryPerformanceFrequency") ret=1134c3e5
0056:Ret  KERNEL32.GetProcAddress() retval=7b824b14 ret=1134c3e5
0056:Call KERNEL32.GetModuleHandleA(11a8eef0 "kernel32.dll") ret=1134c3fb
0056:Ret  KERNEL32.GetModuleHandleA() retval=7b810000 ret=1134c3fb
0056:Call KERNEL32.GetProcAddress(7b810000,11a8eea4 "GetTickCount")
ret=1134c407
0056:Ret  KERNEL32.GetProcAddress() retval=7b82374c ret=1134c407
0056:Call KERNEL32.GetModuleHandleA(11a8ee98 "WINMM.dll") ret=1134c41d
0056:Ret  KERNEL32.GetModuleHandleA() retval=f7220000 ret=1134c41d
0056:Call KERNEL32.GetProcAddress(f7220000,11a8ee88 "timeGetTime") ret=1134c429
0056:Ret  KERNEL32.GetProcAddress() retval=7b82374c ret=1134c429
0056:Call KERNEL32.GetModuleHandleA(11a8ee98 "WINMM.dll") ret=1134c43f
0056:Ret  KERNEL32.GetModuleHandleA() retval=f7220000 ret=1134c43f
0056:Call KERNEL32.GetProcAddress(f7220000,11a8ee78 "timeSetEvent")
ret=1134c44b
0056:Ret  KERNEL32.GetProcAddress() retval=f7226d44 ret=1134c44b
0056:Call KERNEL32.GetModuleHandleA(11a8ee68 "USER32.dll") ret=1134c461
0056:Ret  KERNEL32.GetModuleHandleA() retval=7eca0000 ret=1134c461
0056:Call KERNEL32.GetProcAddress(7eca0000,11a8ee5c "SetTimer") ret=1134c46d
0056:Ret  KERNEL32.GetProcAddress() retval=7ecb0798 ret=1134c46d
...
--- snip ---

Runtime check of API entries every two seconds:

--- snip ---
...
0056:Call KERNEL32.GetModuleHandleA(11a8eef0 "kernel32.dll") ret=1134c4f5
0056:Ret  KERNEL32.GetModuleHandleA() retval=7b810000 ret=1134c4f5
0056:Call KERNEL32.GetProcAddress(7b810000,11a8eed4 "QueryPerformanceCounter")
ret=1134c505
0056:Ret  KERNEL32.GetProcAddress() retval=7b824afc ret=1134c505
0056:Call KERNEL32.GetModuleHandleA(11a8eef0 "kernel32.dll") ret=1134c2fb
0056:Ret  KERNEL32.GetModuleHandleA() retval=7b810000 ret=1134c2fb
0056:Call KERNEL32.GetProcAddress(7b810000,11a8eeb4
"QueryPerformanceFrequency") ret=1134c30b
0056:Ret  KERNEL32.GetProcAddress() retval=7b824b14 ret=1134c30b
0056:Call KERNEL32.GetModuleHandleA(11a8eef0 "kernel32.dll") ret=1134c2fb
0056:Ret  KERNEL32.GetModuleHandleA() retval=7b810000 ret=1134c2fb
0056:Call KERNEL32.GetProcAddress(7b810000,11a8eea4 "GetTickCount")
ret=1134c30b
0056:Ret  KERNEL32.GetProcAddress() retval=7b82374c ret=1134c30b
0056:Call KERNEL32.GetModuleHandleA(11a8ee98 "WINMM.dll") ret=1134c32b
0056:Ret  KERNEL32.GetModuleHandleA() retval=f7220000 ret=1134c32b
0056:Call KERNEL32.GetProcAddress(f7220000,11a8ee88 "timeGetTime") ret=1134c33b
0056:Ret  KERNEL32.GetProcAddress() retval=7b82374c ret=1134c33b
0056:Call KERNEL32.GetModuleHandleA(11a8ee98 "WINMM.dll") ret=1134c2fb
0056:Ret  KERNEL32.GetModuleHandleA() retval=f7220000 ret=1134c2fb
0056:Call KERNEL32.GetProcAddress(f7220000,11a8ee78 "timeSetEvent")
ret=1134c30b
0056:Ret  KERNEL32.GetProcAddress() retval=f7226d44 ret=1134c30b
0056:Call KERNEL32.GetModuleHandleA(11a8ee68 "USER32.dll") ret=1134c2fb
0056:Ret  KERNEL32.GetModuleHandleA() retval=7eca0000 ret=1134c2fb
0056:Call KERNEL32.GetProcAddress(7eca0000,11a8ee5c "SetTimer") ret=1134c30b
0056:Ret  KERNEL32.GetProcAddress() retval=7ecb0798 ret=1134c30b
...
--- snip ---

Following is the list of API functions and their opcode checks.
call/jmp opcode bytes are treated as hook.

--- snip ---
kernel32.dll!QueryPerformanceCounter 
  -> [0]=0xE8 | [0]=0xE9 | [0]=0xFF | ![0]=<snapshot>

kernel32.dll!QueryPerformanceFrequency
  -> [0]=0xE8 | [0]=0xE9 | [0]=0xFF | ![0]=<snapshot>

kernel32.dll!GetTickCount
  -> [0]=0xE8 | [0]=0xE9 | [0]=0xFF | ![0]=<snapshot>

WINMM.dll!timeGetTime
  -> [0]=0xE8 | [0]=0xE9 | [0]=0xFF | ![0]=<snapshot> | [7]=0xE8 | [7]=0xE9 |
[7]=0xFF

WINMM.dll!timeSetEvent
  -> [0]=0xE8 | [0]=0xE9 | [0]=0xFF | ![0]=<snapshot>

user32.dll!SetTimer
  -> [0]=0xE8 | [0]=0xE9 | [0]=0xFF | ![0]=<snapshot>
--- snip ---

The 'WINMM.dll!timeGetTime' entry point gets a special treatment and this the
problem.

Wine 'winmm.spec'

--- snip ---
@ stdcall timeGetTime() kernel32.GetTickCount 
--- snip ---

Forwarded. Dumping the target with Winedbg gives:

--- snip ---
Wine-dbg>x/10b GetTickCount             
0x7b8480d9 GetTickCount:  55 89 e5 53 83 e4 f0 e8 ab 75
--- snip ---

Disassembly:

--- snip ---
7B8480D9  55               PUSH EBP
7B8480DA  89E5             MOV EBP,ESP
7B8480DC  53               PUSH EBX
7B8480DD  83E4 F0          AND ESP,FFFFFFF0
7B8480E0  E8 AB75FDFF      CALL KERNEL32.__x86.get_pc_thunk.bx
7B8480E5  81C3 1B2F0700    ADD EBX,72F1B
7B8480EB  E8 72FFFFFF      CALL KERNEL32.GetTickCount64
7B8480F0  8B5D FC          MOV EBX,DWORD PTR SS:[EBP-4]
7B8480F3  C9               LEAVE
7B8480F4  C3               RETN
--- snip ---

*eeek* .. 'entry[7]' has indeed value 0xE8 hence the check fails.
The PIC code (setup of PIC register) in function prolog causes the harm here.

If you avoid the call opcode at 'entry[7]' everything is fine (for example
making this entry hotpatchable, inline GetTickCount64, use wrapper).

I already tested this. Wine code is no longer (mis)detected as hook and the
game runs fine without any "Speedup" spam.

$ sha1sum AgeofWushu_download.exe 
a7101c50ce8deb33008da4ce044fca5e3add721d  AgeofWushu_download.exe

$ du -sh AgeofWushu_download.exe 
1.9M    AgeofWushu_download.exe

$ wine --version
wine-1.7.19

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