[Bug 44893] SKSE64 fails to initialize, reports: "couldn' t allocate trampoline, no free space before image" ( virtual address space between KUSER_SHARED_DATA and main executable reported as reserved )

wine-bugs at winehq.org wine-bugs at winehq.org
Sat May 26 12:46:23 CDT 2018


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

Anastasius Focht <focht at gmx.net> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|UNCONFIRMED                 |NEW
          Component|-unknown                    |ntdll
           Keywords|                            |download, obfuscation
            Summary|SKSE64 fails to initialize  |SKSE64 fails to initialize,
                   |correctly                   |reports: "couldn't allocate
                   |                            |trampoline, no free space
                   |                            |before image" (virtual
                   |                            |address space between
                   |                            |KUSER_SHARED_DATA and main
                   |                            |executable reported as
                   |                            |reserved)
     Ever confirmed|0                           |1
                 CC|                            |focht at gmx.net

--- Comment #5 from Anastasius Focht <focht at gmx.net> ---
Hello folks,

confirming.

Download: http://skse.silverlock.org/download/archive/skse64_2_00_06.7z (to
match 1.5.23.0 runtime).

Trace log:

--- snip ---
$ pwd
/home/focht/.wine/drive_c/Program Files (x86)/Bethesda Softworks/The Elder
Scrolls V Skyrim - Special Edition

$ WINEDEBUG=+seh,+relay,+virtual,+module wine64 ./skse64_loader.exe >>log.txt
2>&1
...
0036:Call KERNEL32.LoadLibraryA(000d6650 "C:\\Program Files (x86)\\Bethesda
Softworks\\The Elder Scrolls V Skyrim - Special Edition\\\\skse64_1_5_23.dll")
ret=00b814b3 
...
0036:Call PE DLL (proc=0x615c4d74,module=0x61530000
L"skse64_1_5_23.dll",reason=PROCESS_ATTACH,res=(nil)) 
...
0036:Call KERNEL32.CreateFileW(000db630 L"C:\\users\\focht\\My Documents\\My
Games\\Skyrim Special
Edition\\SKSE\\skse64.log",40000000,00000001,0023e898,00000002,00000080,00000000)
ret=615dafd3
0036:Ret  KERNEL32.CreateFileW() retval=000000a8 ret=615dafd3 
...
0036:Call KERNEL32.GetModuleHandleA(00000000) ret=615c3062
0036:Ret  KERNEL32.GetModuleHandleA() retval=140000000 ret=615c3062
0036:Call KERNEL32.VirtualQuery(13fffffff,0023f420,00000030) ret=615c3094
0036:Ret  KERNEL32.VirtualQuery() retval=00000030 ret=615c3094
0036:Call KERNEL32.VirtualQuery(13fffefff,0023f420,00000030) ret=615c3094
0036:Ret  KERNEL32.VirtualQuery() retval=00000030 ret=615c3094
0036:Call KERNEL32.VirtualQuery(13fffdfff,0023f420,00000030) ret=615c3094
0036:Ret  KERNEL32.VirtualQuery() retval=00000030 ret=615c3094
0036:Call KERNEL32.VirtualQuery(13fffcfff,0023f420,00000030) ret=615c3094
0036:Ret  KERNEL32.VirtualQuery() retval=00000030 ret=615c3094
0036:Call KERNEL32.VirtualQuery(13fffbfff,0023f420,00000030) ret=615c3094
0036:Ret  KERNEL32.VirtualQuery() retval=00000030 ret=615c3094 
...
0036:Ret  KERNEL32.VirtualQuery() retval=00000030 ret=615c3094
0036:Call KERNEL32.VirtualQuery(c8001fff,0023f420,00000030) ret=615c3094
0036:Ret  KERNEL32.VirtualQuery() retval=00000030 ret=615c3094
0036:Call KERNEL32.VirtualQuery(c8000fff,0023f420,00000030) ret=615c3094
0036:Ret  KERNEL32.VirtualQuery() retval=00000030 ret=615c3094
0036:Call KERNEL32.GetLastError() ret=615d2c55
0036:Ret  KERNEL32.GetLastError() retval=000000b7 ret=615d2c55
0036:Call KERNEL32.WriteFile(000000a8,0023ddd0,00000038,0023ddc0,00000000)
ret=615d582c
0036:Ret  KERNEL32.WriteFile() retval=00000001 ret=615d582c
0036:Call KERNEL32.WriteFile(000000a8,0023ddd0,00000002,0023ddc0,00000000)
ret=615d582c
0036:Ret  KERNEL32.WriteFile() retval=00000001 ret=615d582c
0036:Call KERNEL32.GetLastError() ret=615d2c55
0036:Ret  KERNEL32.GetLastError() retval=000000b7 ret=615d2c55
0036:Call KERNEL32.GetLastError() ret=615d2c55
0036:Ret  KERNEL32.GetLastError() retval=000000b7 ret=615d2c55
0036:Call KERNEL32.WriteFile(000000a8,0023de30,00000055,0023de20,00000000)
ret=615d582c
0036:Ret  KERNEL32.WriteFile() retval=00000001 ret=615d582c
0036:Call KERNEL32.WriteFile(000000a8,0023de30,00000002,0023de20,00000000)
ret=615d582c
0036:Ret  KERNEL32.WriteFile() retval=00000001 ret=615d582c
0036:Call KERNEL32.GetLastError() ret=615d2c55
0036:Ret  KERNEL32.GetLastError() retval=000000b7 ret=615d2c55
0036:Ret  PE DLL (proc=0x615c4d74,module=0x61530000
L"skse64_1_5_23.dll",reason=PROCESS_ATTACH,res=(nil)) retval=1
0036:trace:module:process_attach (L"skse64_1_5_23.dll",(nil)) - END
...
--- snip --- 

skse64.log:

--- snip ---
SKSE64 runtime: initialize (version = 2.0.6 01050170 01D3F4E856187FD2, os = 6.1
(7601))
imagebase = 0000000140000000
reloc mgr imagebase = 0000000140000000
couldn't allocate trampoline, no free space before image
couldn't create branch trampoline. this is fatal. skipping remainder of init
process. 
--- snip ---

The are two problems, one being a Wine problem and the other one some SKSE64
brain damage that just works by chance on Windows due to OS loader behaviour.

'skse64_1_5_23.dll' has a preferred load base address of 0x180000000.
'skse64_1_5_23.dll' gets relocated to some lower 32-bit address 0x61530000 due
to 'binkw64.dll' having the same fixed load base address (dll imports resolved
before injection). NOTE: 'binkw64.dll' is not marked as relocatable.

--- snip ---
Wine-dbg>info share
Module    Address                    Debug info    Name (239 modules)
PE              360000-          54c000    Deferred        steam_api64
PE              b80000-          bb5000    Deferred        skse64_steam_loader
PE            61530000-        61665000    Deferred        skse64_1_5_23
ELF            7a800000-        7aa9c000    Deferred        opengl32<elf>
  \-PE            7a8b0000-        7aa9c000    \               opengl32
ELF            7b400000-        7b882000    Dwarf           kernel32<elf>
  \-PE            7b460000-        7b882000    \               kernel32
ELF            7bc00000-        7bdbf000    Dwarf           ntdll<elf>
  \-PE            7bc80000-        7bdbf000    \               ntdll
ELF            7c000000-        7c004000    Deferred        <wine-loader>
PE           140000000-       143726000    Export          skyrimse
PE           180000000-       180069000    Deferred        binkw64
ELF        7f500d5ed000-    7f500d7f3000    Deferred        libnss_dns.so.2
ELF        7f500d7f3000-    7f500d9f7000    Deferred       
libnss_mdns4_minimal.so.2
ELF        7f500d9f7000-    7f500dc09000    Deferred        libnss_files.so.2
ELF        7f500dc09000-    7f500de12000    Deferred        libffi.so.6 
...
--- snip ---

Relevant disassembly snippet SKSE64 (annotated):

--- snip ---
...
0000000180093040 | mov     qword ptr ss:[rsp + 10], rbx  
000000018009304A | push    rdi                           
000000018009304B | sub     rsp, 50                       
000000018009304F | mov     rax, r8                     
0000000180093052 | mov     rbx, rcx                            
0000000180093055 | test    r8, r8                              
0000000180093058 | jne     skse64_1_5_23.180093062             
000000018009305A | xor     ecx, ecx                            
000000018009305C | call    qword ptr ds:[<&GetModuleHandleA>]  
0000000180093062 | cmp     qword ptr ds:[rbx], 0               
0000000180093066 | lea     rsi, qword ptr ds:[rax - 78000000] ; low_end
000000018009306D | lea     rdi, qword ptr ds:[rax - 1]  ; low_start = start-1
0000000180093071 | jne     skse64_1_5_23.18009315A             
0000000180093077 | mov     qword ptr ss:[rsp + 60], rbp        
000000018009307C | xor     ebp, ebp                            
000000018009307E | nop                                         
0000000180093080 | mov     r8d, 30                      ; dwLength
0000000180093086 | lea     rdx, qword ptr ss:[rsp + 20] ; lpBuffer
000000018009308B | mov     rcx, rdi                     ; lpAddress
000000018009308E | call    qword ptr ds:[<&VirtualQuery>]  
0000000180093094 | test    rax, rax                      
0000000180093097 | je      skse64_1_5_23.18009313D      
000000018009309D | cmp     dword ptr ss:[rsp + 40], 10000 ; State == MEM_FREE
00000001800930A5 | jne     skse64_1_5_23.18009310F        
00000001800930A7 | mov     rcx, qword ptr ss:[rsp + 38] ; RegionSize 
00000001800930AC | cmp     rcx, 10000                     
00000001800930B3 | jb      skse64_1_5_23.18009310F       
00000001800930B5 | lea     rdi, qword ptr ds:[rcx - 10000] 
00000001800930BC | mov     edx, 10000                   ; dwSize
00000001800930C1 | add     rdi, qword ptr ss:[rsp + 20] ; Buffer.BaseAddress
00000001800930C6 | mov     r9d, 40                      ; flProtect
00000001800930CC | mov     rcx, rdi                     ; lpAddress
00000001800930CF | mov     r8d, 3000                    ; flAllocationType
00000001800930D5 | call    qword ptr ds:[<&VirtualAlloc>] 
00000001800930DB | mov     qword ptr ds:[rbx], rax 
00000001800930DE | test    rax, rax                 
00000001800930E1 | je      skse64_1_5_23.1800930F1             
00000001800930E3 | mov     qword ptr ds:[rbx + 8], 10000       
00000001800930EB | mov     qword ptr ds:[rbx + 10], rbp        
00000001800930EF | jmp     skse64_1_5_23.18009310F             
00000001800930F1 | call    qword ptr ds:[<&GetLastError>]      
00000001800930F7 | mov     r8d, 10000
; "trampoline alloc %016I64Xx%016I64X failed (%08X)"
00000001800930FD | lea     rcx, qword ptr ds:[1800DCCD0] 
0000000180093104 | mov     r9d, eax                            
0000000180093107 | mov     rdx, rdi                 
000000018009310A | call    skse64_1_5_23.180004F10  ; LogMessage    
000000018009310F | mov     rcx, qword ptr ds:[rbx]  
0000000180093112 | test    rcx, rcx                
0000000180093115 | jne     skse64_1_5_23.18009311F 
0000000180093117 | mov     rdi, qword ptr ss:[rsp + 20]  
000000018009311C | dec     rdi    
000000018009311F | cmp     rdi, rsi 
0000000180093122 | jb      skse64_1_5_23.18009312F             
0000000180093124 | test    rcx, rcx                            
0000000180093127 | je      skse64_1_5_23.180093080             
000000018009312D | jmp     skse64_1_5_23.180093151
; "couldn't allocate trampoline, no free space before image"
000000018009312F | lea     rcx, qword ptr ds:[1800DCD30]
0000000180093136 | call    skse64_1_5_23.180004F10  ; LogMessage               
000000018009313B | jmp     skse64_1_5_23.180093151             
000000018009313D | call    qword ptr ds:[<&GetLastError>]      
0000000180093143 | mov     edx, eax
; "VirtualQuery failed: %08X"
0000000180093145 | lea     rcx, qword ptr ds:[1800DCCB0]
000000018009314C | call    skse64_1_5_23.180004F10  ; LogMessage          
0000000180093151 | mov     rbp, qword ptr ss:[rsp + 60]        
0000000180093156 | cmp     qword ptr ds:[rbx], 0               
000000018009315A | mov     rbx, qword ptr ss:[rsp + 68]        
000000018009315F | setne   al                                  
0000000180093162 | mov     rsi, qword ptr ss:[rsp + 70]        
0000000180093167 | add     rsp, 50
000000018009316B | pop     rdi
000000018009316C | ret
--- snip ---

The hooker searches for free memory regions directly below the main executable
mapping (on 64-bit usually 0x140000000).

Address space layout on 64-bit target process:

--- snip ---
Address           Size              Info                              
...
0000000061530000  0000000000001000  skse64_1_5_23.dll                 
0000000061531000  00000000000B6000   ".text"                          
00000000615E7000  0000000000057000   ".rdata"                         
000000006163E000  0000000000013000   ".data"                          
0000000061651000  000000000000B000   ".pdata", ".gfids"               
000000006165C000  0000000000001000   ".tls"                           
000000006165D000  0000000000008000   ".rsrc", ".reloc"                
0000000061670000  0000000000030000                                    
00000000616A0000  0000000003FD0000  Reserved (0000000061670000)       
0000000065670000  0000000000400000                                    
0000000065A70000  0000000000001000                                    
0000000065A71000  0000000000001000                                    
0000000065A72000  00000000000FE000  Thread 37 Stack                   
0000000065B70000  0000000000004000                                    
0000000065B80000  0000000000001000                                    
0000000065B81000  0000000000001000                                    
0000000065B82000  00000000000FE000  Thread 38 Stack                   
0000000065C80000  0000000000004000                                    
0000000065C90000  0000000000001000                                    
0000000065C91000  0000000000001000                                    
0000000065C92000  00000000000FE000  Thread 39 Stack                   
0000000065D90000  0000000000004000                                    
0000000065DA0000  0000000000001000                                    
0000000065DA1000  0000000000001000                                    
0000000065DA2000  00000000000FE000  Thread 3A Stack                   
0000000065EA0000  0000000000004000                                    
0000000065EB0000  0000000000001000                                    
0000000065EB1000  0000000000001000                                    
0000000065EB2000  00000000000FE000  Thread 3B Stack                   
0000000065FB0000  0000000000004000                                    
0000000065FC0000  0000000000001000                                    
0000000065FC1000  0000000000001000                                    
0000000065FC2000  00000000000FE000  Thread 3C Stack                   
00000000660C0000  0000000000004000                                    
00000000660D0000  0000000000001000                                    
00000000660D1000  0000000000001000                                    
00000000660D2000  00000000000FE000  Thread 3D Stack                   
00000000661D0000  0000000000004000                                    
00000000661E0000  0000000000001000                                    
00000000661E1000  0000000000001000                                    
00000000661E2000  00000000000FE000  Thread 3E Stack                   
00000000662E0000  0000000000004000                                    
00000000662E4000  000000000020E000                                    
0000000066500000  0000000000001000                                    
0000000066501000  0000000000001000                                    
0000000066502000  00000000000FE000  Thread 50 Stack                   
0000000066600000  0000000000004000                                    
0000000066610000  0000000000001000                                    
0000000066611000  0000000000001000                                    
0000000066612000  00000000001FE000  Thread 51 Stack                   
0000000066810000  0000000000004000                                    
0000000066920000  0000000000001000                                    
0000000066921000  0000000000001000                                    
0000000066922000  00000000001FE000  Thread 52 Stack                   
0000000066B20000  0000000000004000                                    
0000000066B30000  0000000000001000                                    
0000000066B31000  0000000000001000                                    
0000000066B32000  00000000001FE000  Thread 53 Stack                   
0000000066D30000  0000000000004000                                    
0000000066D40000  0000000000001000                                    
0000000066D41000  0000000000001000                                    
0000000066D42000  00000000001FE000  Thread 54 Stack                   
0000000066F40000  0000000000004000                                    
0000000066F50000  0000000000001000                                    
0000000066F51000  0000000000001000                                    
0000000066F52000  00000000001FE000  Thread 55 Stack                   
0000000067150000  0000000000004000                                    
0000000067160000  0000000000001000                                    
0000000067161000  0000000000001000                                    
0000000067162000  00000000001FE000  Thread 56 Stack                   
0000000067360000  0000000000004000                                    
0000000067494000  00000000003E9000                                    
000000006787D000  00000000001F4000                                    
0000000067A71000  0000000000101000                                    
0000000067B72000  0000000000081000                                    
0000000067C23000  0000000000081000                                    
0000000067CB0000  00000000001FB000                                    
0000000067EB0000  0000000000110000                                    
0000000067FC0000  0000000000001000                                    
000000007A8B0000  0000000000001000  opengl32.dll                      
000000007A8B1000  00000000001DC000   ".text", ".reloc", ".rsrc"       
000000007AA8D000  0000000000002000  opengl32.dll                      
000000007AA8F000  0000000000001000  opengl32.dll                      
000000007AA90000  000000000000C000  opengl32.dll                      
000000007B460000  0000000000001000  kernel32.dll                      
000000007B461000  000000000026F000   ".text", ".reloc", ".rsrc"       
000000007B6D0000  000000000000B000  kernel32.dll                      
000000007B6DB000  0000000000001000  kernel32.dll                      
000000007B6DC000  00000000001A6000  kernel32.dll                      
000000007BC80000  0000000000001000  ntdll.dll                         
000000007BC81000  000000000011F000   ".text", ".reloc", ".rsrc"       
000000007BDA0000  0000000000001000  ntdll.dll                         
000000007BDA1000  000000000001E000  ntdll.dll                         
000000007BDBF000  0000000004141000  Reserved                          
000000007FF10000  0000000000001000                                    
000000007FF20000  0000000000001000                                    
000000007FF30000  0000000000001000                                    
000000007FF40000  0000000000001000                                    
000000007FF50000  0000000000001000                                    
000000007FF51000  0000000000081000                                    
000000007FFE0000  0000000000010000  KUSER_SHARED_DATA                 
000000007FFF0000  00000000C0010000  Reserved                          
0000000140000000  0000000000001000  skyrimse.exe                      
0000000140001000  0000000001521000   ".text"                          
0000000141522000  00000000008B3000   ".rdata"                         
0000000141DD5000  00000000016E8000   ".data"                          
00000001434BD000  0000000000159000   ".pdata"                         
0000000143616000  0000000000005000   ".tls", ".text"                  
000000014361B000  00000000000E6000   ".gfids", ".rsrc", ".reloc"      
0000000143701000  0000000000025000   ".bind"                          
0000000180000000  0000000000001000  binkw64.dll                       
...
--- snip ---

As said, it searches top-down until it finds a free 64KB chunk between
[image_load_base-1...image_load_base-0x78000000]. Starting with 4K chunk size,
increasing in steps. The search range value is hard-coded.

Unfortunately Wine treats the virtual address space for 64-bit between default
executable mapping (0x140000000) and KUSER_SHARED_DATA (0x7FFE0000) as reserved
but not committed memory hence the hooker fails to find a free chunk.

https://source.winehq.org/git/wine.git/blob/HEAD:/dlls/ntdll/virtual.c#l2747

--- snip ---
2747 /* retrieve state for a free memory area; callback for
wine_mmap_enum_reserved_areas */
2748 static int get_free_mem_state_callback( void *start, size_t size, void
*arg )
2749 {
2750     MEMORY_BASIC_INFORMATION *info = arg;
2751     void *end = (char *)start + size;
2752 
2753     if ((char *)info->BaseAddress + info->RegionSize < (char *)start)
return 0;
2754 
2755     if (info->BaseAddress >= end)
2756     {
2757         if (info->AllocationBase < end) info->AllocationBase = end;
2758         return 0;
2759     }
2760 
2761     if (info->BaseAddress >= start || start <= address_space_start)
2762     {
2763         /* it's a real free area */
2764         info->State             = MEM_FREE;
2765         info->Protect           = PAGE_NOACCESS;
2766         info->AllocationBase    = 0;
2767         info->AllocationProtect = 0;
2768         info->Type              = 0;
2769         if ((char *)info->BaseAddress + info->RegionSize > (char *)end)
2770             info->RegionSize = (char *)end - (char *)info->BaseAddress;
2771     }
2772     else /* outside of the reserved area, pretend it's allocated */
2773     {
2774         info->RegionSize        = (char *)start - (char
*)info->BaseAddress;
2775         info->State             = MEM_RESERVE;
2776         info->Protect           = PAGE_NOACCESS;
2777         info->AllocationProtect = PAGE_NOACCESS;
2778         info->Type              = MEM_PRIVATE;
2779     }
2780     return 1;
2781 }
--- snip ---

I've modified the code the treat the address space above KUSER_SHARED_DATA also
as "really free (tm)" and it allowed the hooker to continue.

Unfortunately the hooker has a fatal flaw.
When it tries to allocate memory for the codegen buffer, it uses the same piece
of code but now with the own module base as top-down search start.

NOTE: Trace done with patched Wine.

--- snip ---
...
002e:Call KERNEL32.VirtualQuery(13fff0fff,0023f420,00000030) ret=615c3094
002e:trace:virtual:get_free_mem_state_callback start=0x140000000,
info->BaseAddress=0x13fff0000, address_space_start=0x10000
002e:trace:virtual:NtQueryVirtualMemory info->BaseAddress=0x13fff0000,
info->RegionSize=0x10000, info->State=0x10000
002e:Ret  KERNEL32.VirtualQuery() retval=00000030 ret=615c3094
002e:Call KERNEL32.VirtualAlloc(13fff0000,00010000,00003000,00000040)
ret=615c30db
002e:trace:virtual:NtAllocateVirtualMemory 0xffffffffffffffff 0x13fff0000
00010000 3000 00000040
002e:trace:virtual:VIRTUAL_DumpView View: 0x13fff0000 - 0x13fffffff (valloc)
002e:trace:virtual:VIRTUAL_DumpView       0x13fff0000 - 0x13fffffff c-rwx
002e:Ret  KERNEL32.VirtualAlloc() retval=13fff0000 ret=615c30db
002e:Call KERNEL32.VirtualQuery(6152ffff,0023f420,00000030) ret=615c3094
002e:trace:virtual:get_free_mem_state_callback start=0x10000,
info->BaseAddress=0x6152f000, address_space_start=0x10000
002e:trace:virtual:NtQueryVirtualMemory info->BaseAddress=0x6152f000,
info->RegionSize=0x1000, info->State=0x10000
002e:Ret  KERNEL32.VirtualQuery() retval=00000030 ret=615c3094
002e:Call KERNEL32.GetLastError() ret=615d2c55
002e:Ret  KERNEL32.GetLastError() retval=000000b7 ret=615d2c55 
...
--- snip ---

If the chunk is marked as free, the hooker checks the region size and goes in
next iteration for larger chunk until it finds 64 KB free (after checking
search range).
Unfortunately there is an underflow in the range comparison logic (hard-coded
0x78000000). If the 64-bit hooker dll is mapped to low 32-bit range, the code
can't work by design.
On Windows, the hooker dll likely relocated to some > 32-bit address space
range, hence the problem never occurs.

Anyway, there is still one valid problem in Wine - even if the hooker is kinda
broken.

$ sha1sum skse64_2_00_06.7z 
c1467dd97dac9046fadb6f7b18ab68efea528b21  skse64_2_00_06.7z

$ du -sh skse64_2_00_06.7z 
732K    skse64_2_00_06.7z

$ wine --version
wine-3.9

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