https://bugs.winehq.org/show_bug.cgi?id=48861
Bug ID: 48861 Summary: x64dbg fails to set debuggee command line via File -> Change Command Line menu Product: Wine Version: 5.5 Hardware: x86-64 OS: Linux Status: NEW Severity: normal Priority: P2 Component: kernelbase Assignee: wine-bugs@winehq.org Reporter: focht@gmx.net Distribution: ---
Hello folks,
https://github.com/x64dbg/x64dbg/wiki/Frequently-Asked-Questions
--- quote --- Q: Help, how do I pass arguments to the program I want to debug?
A: Use the File -> Change Command Line option: --- quote ---
This doesn't work under Wine. One has to start the debuggee using an undocumented way.
https://github.com/x64dbg/x64dbg/issues/210
The documentation task https://github.com/x64dbg/docs/issues/26 is still open since 2016.
Examples:
--- snip -- # starts x64dbg with 64-bit debuggee and args $ wine ./x96dbg.exe "c:\windows\system32\cmd.exe" /k echo "%PROCESSOR_ARCHITECTURE%"
# starts x32dbg with 32-bit debuggee and args $ wine ./x96dbg.exe "c:\windows\syswow64\cmd.exe" /k echo "%PROCESSOR_ARCHITECTURE%" --- snip ---
Anyway, the problem here is how Wine implements 'kernel32.GetCommandLine{A,W}' functions.
Starting with x64dbg sources...
https://github.com/x64dbg/x64dbg/blob/d2c62e69c2cd4d42cc4509612ad3cfd34e9142...
--- snip --- bool dbgsetcmdline(const char* cmd_line, cmdline_error_t* cmd_line_error) { // Make sure cmd_line_error is a valid pointer cmdline_error_t cmd_line_error_aux; if(cmd_line_error == NULL) cmd_line_error = &cmd_line_error_aux;
// Get the command line address if(!getcommandlineaddr(&cmd_line_error->addr, cmd_line_error)) return false; auto command_line_addr = cmd_line_error->addr;
// Convert the string to UTF-16 auto command_linewstr = StringUtils::Utf8ToUtf16(cmd_line); if(command_linewstr.length() >= 32766) //32766 is maximum character count for a null-terminated UNICODE_STRING command_linewstr.resize(32766); // Convert the UTF-16 string to ANSI auto command_linestr = Utf16ToAnsi(command_linewstr.c_str());
// Fill the UNICODE_STRING to be set in the debuggee UNICODE_STRING new_command_line; new_command_line.Length = USHORT(command_linewstr.length() * sizeof(WCHAR)); //max value: 32766 * 2 = 65532 new_command_line.MaximumLength = new_command_line.Length + sizeof(WCHAR); //max value: 65532 + 2 = 65534 new_command_line.Buffer = PWSTR(command_linewstr.c_str()); //allow cast from const because the UNICODE_STRING will not be used locally
// Allocate remote memory for both the UNICODE_STRING.Buffer and the (null terminated) ANSI buffer duint mem = MemAllocRemote(0, new_command_line.MaximumLength + command_linestr.size()); if(!mem) { cmd_line_error->type = CMDL_ERR_ALLOC_UNICODEANSI_COMMANDLINE; return false; }
// Write the UNICODE_STRING.Buffer to the debuggee (UNICODE_STRING.Length is used because the remote memory is zeroed) if(!MemWrite(mem, new_command_line.Buffer, new_command_line.Length)) { cmd_line_error->addr = mem; cmd_line_error->type = CMDL_ERR_WRITE_UNICODE_COMMANDLINE; return false; }
// Write the (null-terminated) ANSI buffer to the debuggee if(!MemWrite(mem + new_command_line.MaximumLength, command_linestr.data(), command_linestr.size())) { cmd_line_error->addr = mem + new_command_line.MaximumLength; cmd_line_error->type = CMDL_ERR_WRITE_ANSI_COMMANDLINE; return false; }
// Change the pointers to the command line if(!fixgetcommandlinesbase(mem, mem + new_command_line.MaximumLength, cmd_line_error)) return false;
// Put the remote buffer address in the UNICODE_STRING and write it to the PEB new_command_line.Buffer = PWSTR(mem); if(!MemWrite(command_line_addr, &new_command_line, sizeof(new_command_line))) { cmd_line_error->addr = command_line_addr; cmd_line_error->type = CMDL_ERR_WRITE_PEBUNICODE_COMMANDLINE; return false; }
// Copy command line copyCommandLine(cmd_line);
return true; } --- snip ---
Corresponding Wine trace log snippet:
--- snip --- ... 0009:Call KERNEL32.VirtualAllocEx(0000024c,00000000,00000087,00003000,00000040) ret=01dd445c 0009:Call ntdll.NtAllocateVirtualMemory(0000024c,0032ce3c,00000000,0032ce58,00003000,00000040) ret=7b022f18 0009: queue_apc( handle=024c, call={APC_VIRTUAL_ALLOC,addr==00000000,size=00000087,zero_bits_64=0,op_type=3000,prot=40} ) 0045: *wakeup* signaled=256 0009: queue_apc() = 0 { handle=02e8, self=0 } 0045: select( flags=2, cookie=7ffdb10c, timeout=infinite, prev_apc=0000, result={APC_VIRTUAL_QUERY,status=0,base=00330000,alloc_base=00330000,size=00001000,state=1,prot=2,alloc_prot=80,alloc_type=100}, data={WAIT,handles={0048}} ) 0045: select() = KERNEL_APC { timeout=infinite, call={APC_VIRTUAL_ALLOC,addr==00000000,size=00000087,zero_bits_64=0,op_type=3000,prot=40}, apc_handle=004c } 0009: select( flags=2, cookie=0032c9ac, timeout=infinite, prev_apc=0000, result={}, data={WAIT_ALL,handles={02e8}} ) 0009: select() = PENDING { timeout=infinite, call={APC_NONE}, apc_handle=0000 } 0045: select( flags=2, cookie=7ffdb10c, timeout=infinite, prev_apc=004c, result={APC_VIRTUAL_ALLOC,status=0,addr=00380000,size=00001000}, data={WAIT,handles={0048}} ) 0009: *wakeup* signaled=0 0045: select() = PENDING { timeout=infinite, call={APC_NONE}, apc_handle=0000 } 0009: get_apc_result( handle=02e8 ) 0009: get_apc_result() = 0 { result={APC_VIRTUAL_ALLOC,status=0,addr=00380000,size=00001000} } 0009:Ret ntdll.NtAllocateVirtualMemory() retval=00000000 ret=7b022f18 0009:Ret KERNEL32.VirtualAllocEx() retval=00380000 ret=01dd445c 0009:Call KERNEL32.WriteProcessMemory(0000024c,00380000,05ab7250,00000058,0032cea8) ret=0211b7b8 0009:Call ntdll.NtWriteVirtualMemory(0000024c,00380000,05ab7250,00000058,0032cea8) ret=7b023199 0009: write_process_memory( handle=024c, addr=00380000, data={22,00,43,00,3a,00,5c,00,77,00,69,00,6e,00,64,00,6f,00,77,00,73,00,5c,00,73,00,79,00,73,00,77,00,6f,00,77,00,36,00,34,00,5c,00,77,00,69,00,6e,00,65,00,6d,00,69,00,6e,00,65,00,2e,00,65,00,78,00,65,00,22,00,20,00,2d,00,74,00,65,00,73,00,74,00,61,00,72,00,67,00,31,00} ) 0045: *signal* signal=19 0009: write_process_memory() = 0 0009:Ret ntdll.NtWriteVirtualMemory() retval=00000000 ret=7b023199 0009:Ret KERNEL32.WriteProcessMemory() retval=00000001 ret=0211b7b8 0009:Call KERNEL32.WriteProcessMemory(0000024c,0038005a,05a989f8,0000002d,0032cea8) ret=0211b7b8 0009:Call ntdll.NtWriteVirtualMemory(0000024c,0038005a,05a989f8,0000002d,0032cea8) ret=7b023199 0009: write_process_memory( handle=024c, addr=0038005a, data={22,43,3a,5c,77,69,6e,64,6f,77,73,5c,73,79,73,77,6f,77,36,34,5c,77,69,6e,65,6d,69,6e,65,2e,65,78,65,22,20,2d,74,65,73,74,61,72,67,31,00} ) 0045: *signal* signal=19 0009: write_process_memory() = 0 0009:Ret ntdll.NtWriteVirtualMemory() retval=00000000 ret=7b023199 0009:Ret KERNEL32.WriteProcessMemory() retval=00000001 ret=0211b7b8 --- snip ---
Both, ANSI and UNICODE buffers are allocated and filled in debuggee process space.
https://github.com/x64dbg/x64dbg/blob/d2c62e69c2cd4d42cc4509612ad3cfd34e9142...
--- snip --- static bool fixgetcommandlinesbase(duint new_command_line_unicode, duint new_command_line_ascii, cmdline_error_t* cmd_line_error) { duint getcommandline;
if(!valfromstring("kernelBase:GetCommandLineA", &getcommandline)) { if(!valfromstring("kernel32:GetCommandLineA", &getcommandline)) { cmd_line_error->type = CMDL_ERR_GET_GETCOMMANDLINE; return false; } } if(!patchcmdline(getcommandline, new_command_line_ascii, cmd_line_error)) return false;
if(!valfromstring("kernelbase:GetCommandLineW", &getcommandline)) { if(!valfromstring("kernel32:GetCommandLineW", &getcommandline)) { cmd_line_error->type = CMDL_ERR_GET_GETCOMMANDLINE; return false; } } if(!patchcmdline(getcommandline, new_command_line_unicode, cmd_line_error)) return false;
return true; } --- snip ---
Corresponding Wine trace log snippet:
--- snip --- ... 0009:Call KERNEL32.LoadLibraryExW(05ab78d8 L"Z:\home\focht\projects\wine\mainline-install-x86_64\lib\wine\kernelbase.dll",00000000,00000001) ret=01e0dbdf ... 0009:Ret KERNEL32.LoadLibraryExW() retval=7b000000 ret=01e0dbdf ... 0009:Call KERNEL32.GetProcAddress(7b000000,05ab78bb "GetCommandLineA") ret=01e0b046 0009:Ret KERNEL32.GetProcAddress() retval=7b0036ec ret=01e0b046 0009:Call KERNEL32.FreeLibrary(7b000000) ret=01e0ddf9 0009:Ret KERNEL32.FreeLibrary() retval=00000001 ret=01e0ddf9 ... 0009:Call KERNEL32.ReadProcessMemory(0000024c,7b0036ec,0032ce2c,00000064,0032ce08) ret=0211b6bc 0009:Call ntdll.NtReadVirtualMemory(0000024c,7b0036ec,0032ce2c,00000064,0032ce08) ret=7b022cf9 0009: read_process_memory( handle=024c, addr=7b0036ec ) 0045: *signal* signal=19 0009: read_process_memory() = 0 { data={8b,ff,55,8b,ec,5d,68,36,01,0c,00,b8,00,50,07,7b,50,ff,50,04,c2,00,00,90,90,90,90,90,90,90,90,90,8b,ff,55,8b,ec,5d,68,37,01,0c,00,b8,00,50,07,7b,50,ff,50,04,c2,00,00,90,90,90,90,90,90,90,90,90,8b,ff,55,8b,ec,5d,68,38,01,0a,00,b8,00,50,07,7b,50,ff,50,04,c2,08,00,90,90,90,90,90,90,90,90,90,8b,ff,55,8b} } 0009:Ret ntdll.NtReadVirtualMemory() retval=00000000 ret=7b022cf9 0009:Ret KERNEL32.ReadProcessMemory() retval=00000001 ret=0211b6bc ... --- snip ---
x32dbg reads the opcodes of 32-bit 'kernelbase.GetCommandLineA' and is not happy about it.
--- snip --- static bool patchcmdline(duint getcommandline, duint new_command_line, cmdline_error_t* cmd_line_error) { duint command_line_stored = 0; unsigned char data[100];
cmd_line_error->addr = getcommandline; if(!MemRead(cmd_line_error->addr, & data, sizeof(data))) { cmd_line_error->type = CMDL_ERR_READ_GETCOMMANDLINEBASE; return false; }
#ifdef _WIN64 /* 00007FFC5B91E3C8 | 48 8B 05 19 1D 0E 00 | mov rax,qword ptr ds:[7FFC5BA000E8] 00007FFC5B91E3CF | C3 | ret | This is a relative offset then to get the symbol: next instruction of getmodulehandle (+7 bytes) + offset to symbol (the last 4 bytes of the instruction) */ if(data[0] != 0x48 || data[1] != 0x8B || data[2] != 0x05 || data[7] != 0xC3) { cmd_line_error->type = CMDL_ERR_CHECK_GETCOMMANDLINESTORED; return false; } DWORD offset = * ((DWORD*) & data[3]); command_line_stored = getcommandline + 7 + offset; #else //x86 /* 750FE9CA | A1 CC DB 1A 75 | mov eax,dword ptr ds:[751ADBCC] | 750FE9CF | C3 | ret | */ if(data[0] != 0xA1 || data[5] != 0xC3) { cmd_line_error->type = CMDL_ERR_CHECK_GETCOMMANDLINESTORED; return false; } command_line_stored = * ((duint*) & data[1]); #endif
//update the pointer in the debuggee if(!MemWrite(command_line_stored, &new_command_line, sizeof(new_command_line))) { cmd_line_error->addr = command_line_stored; cmd_line_error->type = CMDL_ERR_WRITE_GETCOMMANDLINESTORED; return false; }
return true; } --- snip ---
Wine source:
https://source.winehq.org/git/wine.git/blob/9bcb1f5195d1e65e0e7afb288d36fee7...
--- snip --- 1008 static STARTUPINFOW startup_infoW; 1009 static char *command_lineA; 1010 static WCHAR *command_lineW; --- snip ---
https://source.winehq.org/git/wine.git/blob/9bcb1f5195d1e65e0e7afb288d36fee7...
--- snip --- 1043 /*********************************************************************** 1044 * GetCommandLineA (kernelbase.@) 1045 */ 1046 LPSTR WINAPI DECLSPEC_HOTPATCH GetCommandLineA(void) 1047 { 1048 return command_lineA; 1049 } 1050 1051 1052 /*********************************************************************** 1053 * GetCommandLineW (kernelbase.@) 1054 */ 1055 LPWSTR WINAPI DECLSPEC_HOTPATCH GetCommandLineW(void) 1056 { 1057 return NtCurrentTeb()->Peb->ProcessParameters->CommandLine.Buffer; 1058 } --- snip ---
Depending on which compiler (GCC or LLVM/MingW) is used to build Wine, the following code is generated:
=== GCC ===
--- snip -- $ gcc -v Using built-in specs. COLLECT_GCC=/usr/bin/gcc COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/8/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-redhat-linux Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,objc,obj-c++,ada,go,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --with-isl --enable-libmpx --enable-offload-targets=nvptx-none --without-cuda-driver --enable-gnu-indirect-function --enable-cet --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux Thread model: posix gcc version 8.3.1 20190223 (Red Hat 8.3.1-2) (GCC) --- snip ---
--- snip --- $ gdb mainline-install-x86_64/lib64/wine/kernelbase.dll.so -batch -ex 'disassemble GetCommandLineA' Dump of assembler code for function GetCommandLineA: 0x000000007b05fd18 <+0>: push %rbp 0x000000007b05fd19 <+1>: mov 0x738a0(%rip),%rax # 0x7b0d35c0 <command_lineA> 0x000000007b05fd20 <+8>: mov %rsp,%rbp 0x000000007b05fd23 <+11>: and $0xfffffffffffffff0,%rsp 0x000000007b05fd27 <+15>: leaveq 0x000000007b05fd28 <+16>: retq End of assembler dump. --- snip ---
--- snip --- $ gdb mainline-install-x86_64/lib64/wine/kernelbase.dll.so -batch -ex 'disassemble GetCommandLineW' Dump of assembler code for function GetCommandLineW: 0x000000007b05fd58 <+0>: push %rbp 0x000000007b05fd59 <+1>: mov %gs:0x30,%rax 0x000000007b05fd62 <+10>: mov 0x60(%rax),%rax 0x000000007b05fd66 <+14>: mov %rsp,%rbp 0x000000007b05fd69 <+17>: and $0xfffffffffffffff0,%rsp 0x000000007b05fd6d <+21>: mov 0x20(%rax),%rax 0x000000007b05fd71 <+25>: mov 0x78(%rax),%rax 0x000000007b05fd75 <+29>: leaveq 0x000000007b05fd76 <+30>: retq End of assembler dump. --- snip ---
NOTE: offset-0x4 to work around dwarf symbol processing bug.
--- snip --- $ gdb mainline-install-x86_64/lib/wine/kernelbase.dll.so -batch -ex 'disassemble GetCommandLineA-0x4' Dump of assembler code for function GetCommandLineA: 0x7b060620 <+0>: mov %edi,%edi 0x7b060622 <+2>: push %ebp 0x7b060623 <+3>: mov %esp,%ebp 0x7b060625 <+0>: pop %ebp 0x7b060626 <+1>: mov 0x7b0d4a20,%eax 0x7b06062b <+6>: ret End of assembler dump. --- snip ---
--- snip --- $ gdb mainline-install-x86_64/lib/wine/kernelbase.dll.so -batch -ex 'disassemble GetCommandLineW-0x4' Dump of assembler code for function GetCommandLineW: 0x7b060640 <+0>: mov %edi,%edi 0x7b060642 <+2>: push %ebp 0x7b060643 <+3>: mov %esp,%ebp 0x7b060645 <+0>: mov %fs:0x18,%eax 0x7b06064b <+6>: mov 0x30(%eax),%eax 0x7b06064e <+9>: pop %ebp 0x7b06064f <+10>: mov 0x10(%eax),%eax 0x7b060652 <+13>: mov 0x44(%eax),%eax 0x7b060655 <+16>: ret End of assembler dump. --- snip ---
=== LLVM MinGW ===
--- snip --- $ clang -v clang version 10.0.0 (https://github.com/llvm/llvm-project.git c49194969430f0ee817498a7000a979a7a0ded03) Target: x86_64-unknown-linux-gnu Thread model: posix InstalledDir: /home/focht/projects/llvm-mingw-20191230-ubuntu-16.04/bin Found candidate GCC installation: /usr/lib/gcc/x86_64-redhat-linux/8 Selected GCC installation: /usr/lib/gcc/x86_64-redhat-linux/8 Candidate multilib: .;@m64 Candidate multilib: 32;@m32 Selected multilib: .;@m64 --- snip ---
--- snip --- $ gdb mainline-install-x86_64/lib/wine/kernelbase.dll -batch -ex 'disassemble GetCommandLineA' Dump of assembler code for function _GetCommandLineA@0: 0x7b0313e0 <+0>: push %ebp 0x7b0313e1 <+1>: mov %esp,%ebp 0x7b0313e3 <+3>: mov 0x7b076bf8,%eax 0x7b0313e8 <+8>: pop %ebp 0x7b0313e9 <+9>: ret End of assembler dump. --- snip ---
--- snip --- $ gdb mainline-install-x86_64/lib/wine/kernelbase.dll -batch -ex 'disassemble GetCommandLineW' Dump of assembler code for function _GetCommandLineW@0: 0x7b0313f0 <+0>: push %ebp 0x7b0313f1 <+1>: mov %esp,%ebp 0x7b0313f3 <+3>: mov %fs:0x18,%eax 0x7b0313f9 <+9>: mov 0x30(%eax),%eax 0x7b0313fc <+12>: mov 0x10(%eax),%eax 0x7b0313ff <+15>: mov 0x44(%eax),%eax 0x7b031402 <+18>: pop %ebp 0x7b031403 <+19>: ret End of assembler dump. --- snip ---
--- snip --- $ gdb mainline-install-x86_64/lib64/wine/kernelbase.dll -batch -ex 'disassemble GetCommandLineA' Dump of assembler code for function GetCommandLineA: 0x000000007b0389b0 <+0>: mov 0x4de49(%rip),%rax # 0x7b086800 0x000000007b0389b7 <+7>: retq End of assembler dump. --- snip ---
--- snip --- $ gdb mainline-install-x86_64/lib64/wine/kernelbase.dll -batch -ex 'disassemble GetCommandLineW' Dump of assembler code for function GetCommandLineW: 0x000000007b0389c0 <+0>: mov %gs:0x30,%rax 0x000000007b0389c9 <+9>: mov 0x60(%rax),%rax 0x000000007b0389cd <+13>: mov 0x20(%rax),%rax 0x000000007b0389d1 <+17>: mov 0x78(%rax),%rax 0x000000007b0389d5 <+21>: retq End of assembler dump. --- snip ---
=====
General problems:
(1) asymmetric design of kernelbase GetCommandLineA/W commmand buffer handling
The ANSI variant returns the command line buffer address directly while the UNICODE variant goes through the PEB.
The symmetric design was changed in 2003 with https://source.winehq.org/git/wine.git/commitdiff/b53b5bcb50e75263e8bbffed34... ("- fixed a couple of bugs in ntdll...") to be asymmetrical.
A big squash-like commit, doing major refactoring/various bugfixing = several things in one go. No mentioning of this specific design change. Part of Wine 0.9 release.
(2) HOTPATCH entry
Commit https://source.winehq.org/git/wine.git/commitdiff/911e50849a9bc05ab11d896a3b... ("kernel32: Move process startup information functions to kernelbase.") added it. Part of Wine 4.16 release.
I couldn't find a bug report about hooking problems of GetCommandLineA/W which requires those to be hotpatch-compatible entries. I guess this was added for no special reason :|
(3) compiler choice/optimization settings, influencing generated code
---
Fixing above problems is not that easy by just returning 'command_lineW' in 'W' variant and removing DECLSPEC_HOTPATCH because of (3).
Generated code:
--- snip --- $ gdb mainline-install-x86_64/lib/wine/kernelbase.dll -batch -ex 'disassemble GetCommandLineA' Dump of assembler code for function _GetCommandLineA@0: 0x7b0313e0 <+0>: push %ebp 0x7b0313e1 <+1>: mov %esp,%ebp 0x7b0313e3 <+3>: mov 0x7b076bf8,%eax 0x7b0313e8 <+8>: pop %ebp 0x7b0313e9 <+9>: ret End of assembler dump. --- snip ---
--- snip --- $ gdb mainline-install-x86_64/lib/wine/kernelbase.dll -batch -ex 'disassemble GetCommandLineW' Dump of assembler code for function _GetCommandLineW@0: 0x7b0313f0 <+0>: push %ebp 0x7b0313f1 <+1>: mov %esp,%ebp 0x7b0313f3 <+3>: mov 0x7b076bf4,%eax 0x7b0313f8 <+8>: pop %ebp 0x7b0313f9 <+9>: ret End of assembler dump. --- snip ---
--- snip --- $ gdb mainline-install-x86_64/lib64/wine/kernelbase.dll -batch -ex 'disassemble GetCommandLineA' Dump of assembler code for function GetCommandLineA: 0x000000007b0389b0 <+0>: mov 0x4de49(%rip),%rax # 0x7b086800 0x000000007b0389b7 <+7>: retq End of assembler dump. --- snip ---
--- snip --- $ gdb mainline-install-x86_64/lib64/wine/kernelbase.dll -batch -ex 'disassemble GetCommandLineW' Dump of assembler code for function GetCommandLineW: 0x000000007b0389c0 <+0>: mov 0x4de31(%rip),%rax # 0x7b0867f8 0x000000007b0389c7 <+7>: retq End of assembler dump. --- snip ---
The 64-bit variant looks good, but 32-bit is no-go. To have full control over the generated code, small __i386__ and __x86_64__ assembly implementations for A and W variant each are sufficient.
NOTE: the assembly for __x86_64__ needs to use RIP-relative addressing (PIE)
-> movq command_lineW(%rip), %rax
* encoding: [0x48,0x8b,0x05,A,A,A,A] * kind: reloc_riprel_4byte_movq_load
With the small assembly functions in place, x64dbg and x32dbg were able to set the debuggee command line via menu.
If assembly is not an option, consider changing A/W variant to be symmetrical to make this bug report somewhat useful in the end (but still WONTFIX).
$ sha1sum snapshot_2020-02-11_01-07.zip 03fa18ffb1cbdd46a6b3c9b7e19332e7e2024edc snapshot_2020-02-11_01-07.zip
$ du -sh snapshot_2020-02-11_01-07.zip 31M snapshot_2020-02-11_01-07.zip
$ wine --version wine-5.5
Regards