[Bug 48518] Unrailed! v0.9 (.NET 4.7 game) crashes on startup ('NtProtectVirtualMemory' fails to change page protection of anonymous file mapping from read-write to read-write-execute)

WineHQ Bugzilla wine-bugs at winehq.org
Sun Jan 26 14:24:59 CST 2020


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

Anastasius Focht <focht at 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 at gmx.net
           Keywords|                            |dotnet, obfuscation

--- Comment #3 from Anastasius Focht <focht at 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  ¸....... at .......
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  .. at ...... ......
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  .. at ......@......
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 at .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  ............ at ..à
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  ............ at ..@
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/9a9a1821a34d10bb3e96ce1e42a8d046133f0958:/dlls/ntdll/virtual.c#l1052

--- 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/f448be618bbb752dd7747086db36465834805186
("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/3a5ee02735d49a808f3f3fc3a3f39d8e14089a52
("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

-- 
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