https://bugs.winehq.org/show_bug.cgi?id=48518
Anastasius Focht focht@gmx.net changed:
What |Removed |Added ---------------------------------------------------------------------------- Status|UNCONFIRMED |NEW Summary|Unrailed! produces |Unrailed! v0.9 (.NET 4.7 |unhandled exception and |game) crashes on startup |crashes |('NtProtectVirtualMemory' | |fails to change page | |protection of anonymous | |file mapping from | |read-write to | |read-write-execute) Component|-unknown |ntdll Ever confirmed|0 |1 CC| |focht@gmx.net Keywords| |dotnet, obfuscation
--- Comment #3 from Anastasius Focht focht@gmx.net --- Hello folks,
confirming. Found some distributed "backup" of the game to reproduce at least the current issue.
Prerequisite: 'winetricks -q dotnet472'
Relevant part of trace log:
--- snip --- $ pwd /home/focht/Downloads/Unrailed.v0.9
$ WINEDEBUG=+seh,+relay,+virtual wine ./UnrailedGame.exe >>log.txt 2>&1 ... 010e:Call KERNEL32.GetProcAddress(7b410000,1b33c29c "VirtualProtect") ret=00c2fc00 010e:Ret KERNEL32.GetProcAddress() retval=7b42ae58 ret=00c2fc00 010e:Call KERNEL32.FlushInstructionCache(ffffffffffffffff,643f8443818,00000010) ret=00aa29ae 010e:Call ntdll.NtFlushInstructionCache(ffffffffffffffff,643f8443818,00000010) ret=7b03693b 010e:Ret ntdll.NtFlushInstructionCache() retval=00000000 ret=7b03693b 010e:Ret KERNEL32.FlushInstructionCache() retval=00000001 ret=00aa29ae 010e:Call KERNEL32.VirtualProtect(1b010400,0018f000,00000040,0052d130) ret=643f8443f73 010e:Call ntdll.NtProtectVirtualMemory(ffffffffffffffff,0052cfc0,0052cfb8,00000040,0052d130) ret=7b027138 010e:trace:virtual:NtProtectVirtualMemory 0xffffffffffffffff 0x1b010400 0018f000 00000040 010e:Ret ntdll.NtProtectVirtualMemory() retval=c0000045 ret=7b027138 010e:Call ntdll.RtlNtStatusToDosError(c0000045) ret=7b027146 010e:Ret ntdll.RtlNtStatusToDosError() retval=00000057 ret=7b027146 010e:Ret KERNEL32.VirtualProtect() retval=00000000 ret=643f8443f73 010e:Call KERNEL32.GetLastError() ret=00aa15a2 010e:Ret KERNEL32.GetLastError() retval=00000057 ret=00aa15a2 010e:Call ntdll.RtlAllocateHeap(00010000,00000000,000000f8) ret=00a9554a 010e:Ret ntdll.RtlAllocateHeap() retval=0197d100 ret=00a9554a 010e:Call ntdll.RtlPcToFileHeader(0124b208,0052cad0) ret=014f2ab4 010e:Ret ntdll.RtlPcToFileHeader() retval=00a90000 ret=014f2ab4 010e:Call KERNEL32.RaiseException(e06d7363,00000001,00000004,0052ca90) ret=014f2af3 010e:Call ntdll.memcpy(0052c958,0052ca90,00000020) ret=7b00f4c6 010e:Ret ntdll.memcpy() retval=0052c958 ret=7b00f4c6 010e:trace:seh:raise_exception code=e06d7363 flags=1 addr=0x7b00f4d5 ip=7b00f4d5 tid=010e 010e:trace:seh:raise_exception info[0]=0000000019930520 010e:trace:seh:raise_exception info[1]=000000000052cb70 010e:trace:seh:raise_exception info[2]=000000000124b208 010e:trace:seh:raise_exception info[3]=0000000000a90000 010e:trace:seh:raise_exception rax=000000000052c958 rbx=0000000000000000 rcx=000000000052c938 rdx=0000000000000036 010e:trace:seh:raise_exception rsi=0000000000000004 rdi=000000000052ca00 rbp=000000000052ca38 rsp=000000000052c910 010e:trace:seh:raise_exception r8=0000000000000000 r9=000000000052c110 r10=0000000000000000 r11=0000000000000000 010e:trace:seh:raise_exception r12=0000000000000000 r13=0000000000000001 r14=000000000052cb70 r15=00000000015f78f8 010e:trace:seh:call_vectored_handlers calling handler at 0xb59250 code=e06d7363 flags=1 --- snip ---
The failing 'VirtualProtect' call is located in some .NET JIT code. Finding the origin of the memory by going back in trace log:
--- snip --- ... 010e:Call KERNEL32.CreateFileW(011c8600 L"",80000000,00000001,00000000,00000003,00000080,00000000) ret=00b8bfc0 010e:Ret KERNEL32.CreateFileW() retval=ffffffffffffffff ret=00b8bfc0 010e:Call KERNEL32.CreateFileMappingW(ffffffffffffffff,00000000,00000004,00000000,003e5a00,00000000) ret=00febd21 010e:Call ntdll.NtCreateSection(0052e1e0,000f0007,0052e1e8,0052e1d8,00000004,08000000,00000000) ret=7b04b019 010e:Ret ntdll.NtCreateSection() retval=00000000 ret=7b04b019 010e:Call ntdll.RtlNtStatusToDosError(00000000) ret=7b04b038 010e:Ret ntdll.RtlNtStatusToDosError() retval=00000000 ret=7b04b038 010e:Ret KERNEL32.CreateFileMappingW() retval=00000130 ret=00febd21 010e:Call KERNEL32.MapViewOfFileEx(00000130,000f001f,00000000,00000000,00000000,00000000) ret=00b96457 010e:Call ntdll.NtMapViewOfSection(00000130,ffffffffffffffff,0052e208,00000000,00000000,0052e1d0,0052e200,00000001,00000000,00000004) ret=7b026d01 010e:trace:virtual:NtMapViewOfSection handle=0x130 process=0xffffffffffffffff addr=(nil) off=000000000 size=0 access=4 010e:trace:virtual:map_view got mem in reserved area 0x1b010000-0x1b3f6000 010e:trace:virtual:virtual_map_section handle=0x130 size=3e6000 offset=000000000 010e:trace:virtual:VIRTUAL_DumpView View: 0x1b010000 - 0x1b3f5fff (anonymous) 010e:trace:virtual:VIRTUAL_DumpView 0x1b010000 - 0x1b3f5fff c-rw- 010e:Ret ntdll.NtMapViewOfSection() retval=00000000 ret=7b026d01 010e:Ret KERNEL32.MapViewOfFileEx() retval=1b010000 ret=00b96457 ... --- snip ---
The memory area is anonymous memory (file mapping) at 0x1b010000 in the process address space.
Parts of the managed .NET assembly:
--- snip --- // Type: _4sgiTxIqQ8maPxGSFcYmFFBYGYM // Assembly: UnrailedGame, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null // MVID: 8FD63CAA-B1C6-4D94-8703-4655ABAA1F63 // Assembly location: Z:\home\focht\Downloads\Unrailed.v0.9\UnrailedGame.exe // Compiler-generated code is shown
using System; using System.Reflection; using System.Runtime.CompilerServices;
internal class _4sgiTxIqQ8maPxGSFcYmFFBYGYM { private static _zFvPhRL2RLPGX2ZpD7I3hEbaXgN _TMX9g0CoekmUjGQB0oaKurgmQWM;
private static void _LagA0lbNI4I46JARxIiLC5ECoYq(string[] _param0) { _4sgiTxIqQ8maPxGSFcYmFFBYGYM._0eJatpggl7nekPM96PDQb8T6RtY jatpggl7nekPm96PdQb8T6RtY = new _4sgiTxIqQ8maPxGSFcYmFFBYGYM._0eJatpggl7nekPM96PDQb8T6RtY();
_4sgiTxIqQ8maPxGSFcYmFFBYGYM._TMX9g0CoekmUjGQB0oaKurgmQWM = new _zFvPhRL2RLPGX2ZpD7I3hEbaXgN(new _3GbLvZYM2hyeWZHGE5UFn7uEvpA());
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler((object) _4sgiTxIqQ8maPxGSFcYmFFBYGYM._TMX9g0CoekmUjGQB0oaKurgmQWM, __methodptr(_T7U6IvOm8pJhslyE6V35ccAXdZG));
jatpggl7nekPm96PdQb8T6RtY._b4OUvg4bYHxlIh7ZcdvGUVdTPqr = Assembly.Load(_4sgiTxIqQ8maPxGSFcYmFFBYGYM._ajwbSUdbsEa1FZNLVpVku0EIsAB(_Vc5bJANLQ4nGyC8qs2JJsAn8DbN._hNdcDdJPVSIKTVmI7o2cf7ai46db));
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler((object) jatpggl7nekPm96PdQb8T6RtY, __methodptr(_U0VgHKqB6NSSPdYoWwY5jvUOQfj));
Action<string[]> action = (Action<string[]>) Delegate.CreateDelegate(typeof (Action<string[]>), jatpggl7nekPm96PdQb8T6RtY._b4OUvg4bYHxlIh7ZcdvGUVdTPqr.EntryPoint);
AppDomain.CurrentDomain.UnhandledException -= new UnhandledExceptionEventHandler((object) _4sgiTxIqQ8maPxGSFcYmFFBYGYM._TMX9g0CoekmUjGQB0oaKurgmQWM, __methodptr(_T7U6IvOm8pJhslyE6V35ccAXdZG));
action(_param0); } ... --- snip ---
Even though heavily obfuscated, one can see the assembly is loaded from byte stream into memory.
Dumping the memory range from location 0x1b010000 to disk shows it's indeed an executable:
--- snip --- 1B010000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ..........ÿÿ.. 1B010010 B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ¸.......@....... 1B010020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 1B010030 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 ................ 1B010040 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68 ..º..´.Í!¸.LÍ!Th 1B010050 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F is program canno 1B010060 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20 t be run in DOS 1B010070 6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00 mode....$....... 1B010080 50 45 00 00 64 86 03 00 72 5A 01 5E 00 00 00 00 PE..d...rZ.^.... 1B010090 00 00 00 00 F0 00 22 00 0B 02 08 00 00 4A 24 00 ....ð."......J$. 1B0100A0 00 0C 1A 00 00 00 00 00 00 00 00 00 00 20 19 00 ............. .. 1B0100B0 00 00 40 00 00 00 00 00 00 20 00 00 00 02 00 00 ..@...... ...... 1B0100C0 04 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 ................ 1B0100D0 00 A0 3E 00 00 04 00 00 00 00 00 00 02 00 60 85 . >...........`. 1B0100E0 00 00 40 00 00 00 00 00 00 40 00 00 00 00 00 00 ..@......@...... 1B0100F0 00 00 10 00 00 00 00 00 00 20 00 00 00 00 00 00 ......... ...... 1B010100 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 ................ 1B010110 00 00 00 00 00 00 00 00 00 80 3D 00 F6 1A 01 00 ..........=.ö... 1B010120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 1B010130 00 00 00 00 00 00 00 00 4C 23 19 00 1C 00 00 00 ........L#...... 1B010140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 1B010150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 1B010160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 1B010170 00 00 00 00 00 00 00 00 00 20 19 00 48 00 00 00 ......... ..H... 1B010180 00 00 00 00 00 00 00 00 26 48 66 6D 40 0E 43 0F ........&Hfm@.C. 1B010190 B8 EE 18 00 00 20 00 00 00 F0 18 00 00 04 00 00 ¸î... ...ð...... 1B0101A0 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 E0 ............@..à 1B0101B0 2E 74 65 78 74 00 00 00 7C 48 24 00 00 20 19 00 .text...|H$.. .. 1B0101C0 00 4A 24 00 00 F4 18 00 00 00 00 00 00 00 00 00 .J$..ô.......... 1B0101D0 00 00 00 00 20 00 00 60 2E 72 73 72 63 00 00 00 .... ..`.rsrc... 1B0101E0 F6 1A 01 00 00 80 3D 00 00 1C 01 00 00 3E 3D 00 ö.....=......>=. 1B0101F0 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40 ............@..@ 1B010200 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ... 1B0103E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 1B0103F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 1B010400 B3 6B 1C 37 DE 69 64 8C EB 8D BD 64 5A FE 1A C7 ³k.7Þid.ë.½dZþ.Ç 1B010410 1F A8 A3 D3 D8 72 63 2F 78 93 BF C6 CD 6C 31 9A .¨£ÓØrc/x.¿ÆÍl1. 1B010420 52 B6 DC D8 71 DC E4 AF 9C 4A 87 CC F5 92 AD 08 R¶ÜØqÜä¯.J.Ìõ... 1B010430 6B 1C 97 B9 C6 B3 E7 32 8D BD E4 F4 FE 1A 47 72 k..¹Æ³ç2.½äôþ.Gr 1B010440 CC 93 57 4B F7 91 9F F5 30 B6 78 A2 77 26 D6 04 Ì.WK÷..õ0¶x¢w&Ö. --- snip ---
Metadata of dumped assembly:
--- snip --- using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning;
// Assembly Unrailed, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null // MVID: 28E78273-0A99-4FCC-B2A6-6DC67A3CE97F // Assembly references: // mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 // MonoGame.Framework, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null // System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 // System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 // PhotonLoadbalancingApi, Version=4.1.2.14, Culture=neutral, PublicKeyToken=null // Photon3DotNet, Version=4.1.2.14, Culture=neutral, PublicKeyToken=null // System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a // System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a // Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed // Module references: // kernel32 // kernel32.dll // DbgHelp.dll // user32.dll // PhotonBridge.dll // fmod // fmodstudio // discord_game_sdk // steam_api64
[assembly: Extension] [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7", FrameworkDisplayName = ".NET Framework 4.7")] [assembly: AssemblyMetadata("GitHash", "0.9-670-g088d0763")] [assembly: ComVisible(true)] [assembly: AssemblyVersion("0.0.0.0")]
[module: ConfusedBy("Confuser.Core 1.2.0+4110faee9d")] --- snip ---
Tidbit: 'ConfusedBy' is an open-source, free protector for .NET applications.
In this case it's the successor: https://mkaring.github.io/ConfuserEx/ Git commit sha1 refers to: https://github.com/mkaring/ConfuserEx/tree/v1.2.0
---
Coming back to the original problem... The file mapping/view is created with read-write. Later, the page protection of the view is changed to add executable permissions which makes sense since this a managed .NET assembly.
So why do we get 'STATUS_INVALID_PAGE_PROTECTION' here?
Adding more debug traces to Wine code and using +server debug channel yields:
--- snip --- ... 002c:Call ntdll.NtMapViewOfSection(00000144,ffffffffffffffff,0052e208,00000000,00000000,0052e1d0,0052e200,00000001,00000000,00000004) ret=7b026d01 002c:trace:virtual:NtMapViewOfSection handle=0x144 process=0xffffffffffffffff addr=(nil) off=000000000 size=0 access=4 002c:trace:virtual:virtual_map_section protect=0x4, access=0x2 002c: get_mapping_info( handle=0144, access=00000002 ) 002c: get_mapping_info() = 0 { size=003e6000, flags=08000000, shared_file=0000, image={} } 002c: get_handle_fd( handle=0144 ) 002c: *fd* 0144 -> 30 002c: get_handle_fd() = 0 { type=1, cacheable=1, access=000f0007, options=00000020 } 002c:trace:virtual:get_vprot_flags OK: protect=0x4, vprot=0x3 002c:trace:virtual:map_view got mem in reserved area 0x1b420000-0x1b806000 002c:trace:virtual:virtual_map_section handle=0x144 size=3e6000 vprot=0x8000023 offset=000000000 002c: map_view( mapping=0144, access=00000002, base=1b420000, size=003e6000, start=00000000 ) 002c: map_view() = 0 002c:trace:virtual:VIRTUAL_DumpView View: 0x1b420000 - 0x1b805fff (anonymous) 002c:trace:virtual:VIRTUAL_DumpView 0x1b420000 - 0x1b805fff c-rw- 002c:Ret ntdll.NtMapViewOfSection() retval=00000000 ret=7b026d01 002c:Ret KERNEL32.MapViewOfFileEx() retval=1b420000 ret=00b96457 .... 002c:Call KERNEL32.VirtualProtect(1b420400,0018f000,00000040,0052d130) ret=643f8443f73 002c:Call ntdll.NtProtectVirtualMemory(ffffffffffffffff,0052cfc0,0052cfb8,00000040,0052d130) ret=7b027138 002c:trace:virtual:NtProtectVirtualMemory 0xffffffffffffffff 0x1b420400 0018f000 00000040 002c:trace:virtual:get_vprot_flags OK: protect=0x40, vprot=0x7 002c:trace:virtual:set_protection base=0x1b420000, size=0x190000, view->protect=0x8000023, access=0x7 ... --- snip ---
https://source.winehq.org/git/wine.git/blob/9a9a1821a34d10bb3e96ce1e42a8d046...
--- snip --- 1052 /*********************************************************************** 1053 * set_protection 1054 * 1055 * Set page protections on a range of pages 1056 */ 1057 static NTSTATUS set_protection( struct file_view *view, void *base, SIZE_T size, ULONG protect ) 1058 { 1059 unsigned int vprot; 1060 NTSTATUS status; 1061 1062 if ((status = get_vprot_flags( protect, &vprot, view->protect & SEC_IMAGE ))) return status; 1063 if (is_view_valloc( view )) 1064 { 1065 if (vprot & VPROT_WRITECOPY) return STATUS_INVALID_PAGE_PROTECTION; 1066 } 1067 else 1068 { 1069 BYTE access = vprot & (VPROT_READ | VPROT_WRITE | VPROT_EXEC); 1070 if ((view->protect & access) != access) return STATUS_INVALID_PAGE_PROTECTION; 1071 } 1072 1073 if (!VIRTUAL_SetProt( view, base, size, vprot | VPROT_COMMITTED )) return STATUS_ACCESS_DENIED; 1074 return STATUS_SUCCESS; 1075 } --- snip ---
view->protect: 0x8000023 = SEC_COMMIT | VPROT_COMMITTED | VPROT_WRITE | VPROT_READ
access: 0x0000007 = VPROT_EXEC | VPROT_WRITE | VPROT_READ
Adding 'VPROT_EXEC' should be ok. I don't know why this is currently treated as incompatible protection change.
The commit that introduced the helper:
https://source.winehq.org/git/wine.git/commitdiff/f448be618bbb752dd7747086db... ("ntdll: Verify page protection against the mapping protections in VirtualAlloc and VirtualProtect.")
The commit message says "This partially reverts 3a5ee02735d49a808f3f3fc3a3f39d8e14089a52."
https://source.winehq.org/git/wine.git/commitdiff/3a5ee02735d49a808f3f3fc3a3... ("ntdll: Add an access check for file mappings.")
With the problem fixed the game runs much further, loading assemblies etc. It crashes later but that could be attributed to the "special" version of the game I found online. I guess OP will test again with the original Steam game when the bug ought to be fixed.
$ wine --version wine-5.0-144-g9a9a1821a3
Regards