[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