[Bug 46957] Safecracker (1998) installer freezes after selection of installation directory ( broken installer, local buffer from previous stack frame/ out of scope data passed to Win32 API)

wine-bugs at winehq.org wine-bugs at winehq.org
Sat May 4 05:44:03 CDT 2019


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

Anastasius Focht <focht at gmx.net> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                URL|                            |https://archive.org/downloa
                   |                            |d/SCDEMO_201606/SC_DEMO.EXE
          Component|-unknown                    |kernel32
     Ever confirmed|0                           |1
                 CC|                            |focht at gmx.net
            Summary|Safecracker (1998):         |Safecracker (1998)
                   |Installer freezes on wine   |installer freezes after
                   |versions above 3.21         |selection of installation
                   |                            |directory (broken
                   |                            |installer, local buffer
                   |                            |from previous stack
                   |                            |frame/out of scope data
                   |                            |passed to Win32 API)
           Keywords|regression                  |download, Installer
             Status|UNCONFIRMED                 |NEW

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

confirming. The installer is horribly broken and just works by pure chance on
some systems. Even with your reported Wine 3.21 or older "working", it doesn't
work for me for obvious reasons with any Wine version.

--- quote ---
When run with debug flag the installer does not freeze but crashes instead.
--- quote ---

That's expected. Usage of relay thunks leads to different stack usage which is
a key aspect in this bug.

--- snip ---
$ WINEDEBUG=+seh,+relay wine ./SC_DEMO.EXE >>log.txt 2>&1
...
0054:Call KERNEL32.CreateProcessA(00000000,00172628
"C:\\users\\focht\\Temp\\~0000.exe
Z:\\home\\focht\\DOWN~NTG\\SC_DEMO.cfg",00000000,00000000,00000000,00000000,00000000,00000000,0190f420,0190f410)
ret=f754b8df
...
0056:Call KERNEL32.__wine_kernel_init() ret=7bc77f9f
0056:Ret  KERNEL32.__wine_kernel_init() retval=7b47aca4 ret=7bc77f9f
0054:Ret  KERNEL32.CreateProcessA() retval=00000001 ret=f754b8df 
...
0056:Call KERNEL32.lstrlenA(0032ef2c "c:\\SC_DEMO") ret=00410a9d
0056:Ret  KERNEL32.lstrlenA() retval=0000000a ret=00410a9d
0056:Call KERNEL32.lstrlenA(0032eec8 "c:\\SC_DEMO\\Xtras") ret=00410aa9
0056:Ret  KERNEL32.lstrlenA() retval=00000010 ret=00410aa9
0056:Call KERNEL32.lstrcpyA(0032ec00,0032ef2c "c:\\SC_DEMO") ret=00410ac1
0056:Ret  KERNEL32.lstrcpyA() retval=0032ec00 ret=00410ac1
0056:Call KERNEL32.lstrcpyA(0032ef2c,0032ec00 "c:\\SC_DEMO\\Xtras")
ret=00410b40
0056:Ret  KERNEL32.lstrcpyA() retval=0032ef2c ret=00410b40
0056:Call KERNEL32.SetCurrentDirectoryA(0032ef2c "c:\\SC_DEMO\\XT\xf22")
ret=00422701
0056:Ret  KERNEL32.SetCurrentDirectoryA() retval=00000000 ret=00422701
0056:Call KERNEL32.GetLastError() ret=0042277f
0056:Ret  KERNEL32.GetLastError() retval=00000002 ret=0042277f
0056:Call KERNEL32.GetLastError() ret=00424b68
0056:Ret  KERNEL32.GetLastError() retval=00000002 ret=00424b68
0056:Call KERNEL32.GetLastError() ret=00424b68
0056:Ret  KERNEL32.GetLastError() retval=00000002 ret=00424b68
0056:Call KERNEL32.CreateDirectoryA(0032ef2c "c:\\SC_DEMO\\XT\xf22",00000000)
ret=004227ad
0056:Ret  KERNEL32.CreateDirectoryA() retval=00000001 ret=004227ad
...
<endless looping due to Win32 API failure (corrupted buffers)> 
--- snip ---

Disassembly:

--- snip ---
00410AE3  PUSH EBP
00410AE4  MOV EBP,ESP
00410AE6  SUB ESP,12C
00410AEC  PUSH EBX
00410AED  PUSH ESI
00410AEE  PUSH EDI
00410AEF  MOV ESI,DWORD PTR SS:[ARG.1]
00410AF2  CMP BYTE PTR DS:[ESI+1],3A
00410AF6  JNE SHORT 00410B04
00410AF8  MOV AL,BYTE PTR DS:[ESI]
00410AFA  OR AL,20
00410AFC  MOVSX EAX,AL
00410AFF  SUB EAX,60
00410B02  JMP SHORT 00410B09
00410B04  MOV EAX,DWORD PTR DS:[44E210]
00410B09  CMP DWORD PTR DS:[44E5BC],EAX
00410B0F  JE SHORT 00410B16
00410B11  MOV DWORD PTR DS:[44E5BC],EAX
00410B16  MOV EDI,DWORD PTR DS:[<&KERNEL32.lstrcatA>]
00410B1C  MOV EBX,DWORD PTR SS:[ARG.2]
00410B1F  MOV BYTE PTR SS:[LOCAL.75],0
00410B26  PUSH EBX                  ; arg2 = 0032DE4C
00410B27  PUSH ESI                  ; arg1 = 004469A8 = "c:\SC_DEMO"
00410B28  CALL 00410A70             ; genius "strdup" using local stack
00410B2D  ADD ESP,8
00410B30  TEST EAX,EAX              ; stack buffer within 00410A70 scope (!)
00410B32  JZ 00410BBC
00410B38  PUSH EAX                  ; src = 0032DBF0
00410B39  PUSH EBX                  ; dest = 0032DE4C
00410B3A  CALL DWORD PTR DS:[<&KERNEL32.lstrcpyA>]
00410B40  CMP BYTE PTR DS:[EBX+1],3A
00410B44  JNE SHORT 00410B54
00410B46  CMP BYTE PTR DS:[EBX+2],0
00410B4A  JNE SHORT 00410B54
00410B4C  MOV BYTE PTR DS:[EBX+2],5C
00410B50  MOV BYTE PTR DS:[EBX+3],0
00410B54  PUSH EBX
00410B55  CALL 004216C0
00410B5A  ADD ESP,4
00410B5D  CMP EAX,3
00410B60  JBE SHORT 00410B26
00410B62  PUSH EBX
00410B63  CALL 004226F0
00410B68  ADD ESP,4
00410B6B  TEST EAX,EAX
00410B6D  JZ SHORT 00410B26
00410B6F  PUSH EBX
00410B70  CALL 004227A0
00410B75  ADD ESP,4
00410B78  TEST EAX,EAX
00410B7A  JNZ SHORT 00410BA4
00410B7C  PUSH OFFSET 004464F4 ; ASCII "DIRECTORY: ""
00410B81  LEA EAX,[LOCAL.75]
00410B87  PUSH EAX
00410B88  CALL EDI
00410B8A  PUSH EBX
00410B8B  LEA EAX,[LOCAL.75]
00410B91  PUSH EAX
00410B92  CALL EDI
00410B94  PUSH OFFSET 0044557C ; ASCII """
00410B99  LEA EAX,[LOCAL.75]
00410B9F  PUSH EAX
00410BA0  CALL EDI
00410BA2  JMP SHORT 00410B26
00410BA4  PUSH 30              ; Type = MB_OK|MB_ICONEXCLAMATION ...
00410BA6  PUSH 0               ; Caption = NULL
00410BA8  PUSH OFFSET 0044E670 ; Text = "Invalid directory."
00410BAD  PUSH 0               ; hOwner = NULL
00410BAF  CALL DWORD PTR DS:[<&USER32.MessageBoxA>]
00410BB5  MOV EAX,2
00410BBA  JMP SHORT 00410BDF
00410BBC  PUSH EBX
00410BBD  CALL 004226F0
00410BC2  ADD ESP,4
00410BC5  CMP BYTE PTR SS:[LOCAL.75],0
00410BCC  JE SHORT 00410BDD
00410BCE  LEA EAX,[LOCAL.75]
00410BD4  PUSH EAX
00410BD5  CALL 00419802
00410BDA  ADD ESP,4
00410BDD  XOR EAX,EAX
00410BDF  POP EDI
00410BE0  POP ESI
00410BE1  POP EBX
00410BE2  MOV ESP,EBP
00410BE4  POP EBP
00410BE5  RETN
--- snip ---

The genius "strdup":

--- snip ---
00410A70  PUSH EBP
00410A71  XOR EAX,EAX
00410A73  MOV EBP,ESP
00410A75  MOV ECX,40
00410A7A  SUB ESP,104                   ; reserve local buffer
00410A80  MOV BYTE PTR SS:[LOCAL.65],0
00410A87  PUSH ESI
00410A88  PUSH EDI
00410A89  LEA EDI,[LOCAL.65+1]
00410A8F  REP STOS DWORD PTR ES:[EDI]   ; zero out local buffer
00410A91  STOS WORD PTR ES:[EDI]
00410A93  STOS BYTE PTR ES:[EDI]
00410A94  PUSH DWORD PTR SS:[ARG.2]     ; String => [ARG.2] "C:\" = 3
00410A97  CALL DWORD PTR DS:[<&KERNEL32.lstrlenA>]
00410A9D  MOV EDI,EAX
00410A9F  MOV ESI,DWORD PTR SS:[ARG.1]
00410AA2  PUSH ESI                      ; String => [ARG.1] "C:\SC_DEMO" = 10
00410AA3  CALL DWORD PTR DS:[<&KERNEL32.lstrlenA>]
00410AA9  CMP EAX,EDI
00410AAB  JNE SHORT 00410AB1
00410AAD  XOR EAX,EAX
00410AAF  JMP SHORT 00410ADD
00410AB1  PUSH DWORD PTR SS:[ARG.2]     ; Src => [ARG.2] = "C:\"
00410AB4  LEA ECX,[LOCAL.65]            ; buffer = 0032DBF0
00410ABA  PUSH ECX                      ; Dest => OFFSET LOCAL.65
00410ABB  CALL DWORD PTR DS:[<&KERNEL32.lstrcpyA>]
00410AC1  MOV AL,BYTE PTR DS:[EDI+ESI]
00410AC4  MOV BYTE PTR SS:[EDI+EBP-104],AL
00410ACB  INC EDI
00410ACC  MOV AL,BYTE PTR DS:[EDI+ESI]
00410ACF  TEST AL,AL
00410AD1  JZ SHORT 00410AD7
00410AD3  CMP AL,5C
00410AD5  JNE SHORT 00410AC1
00410AD7  LEA EAX,[LOCAL.65]            ; that braindamage ...
00410ADD  POP EDI
00410ADE  POP ESI
00410ADF  MOV ESP,EBP                   ; local buf > stack top
00410AE1  POP EBP
00410AE2  RETN
--- snip ---

As soon as 00410ADF is executed (stack frame removed, local stack var = out of
scope), any APC delivered to that thread will potentially corrupt the buffer.
Easy to demonstrate by debugging. Single step -> signal/exception handling ->
corrupts buffer.

Even with some hackery (I can't find the ticket right now), that avoids
touching the immediate range above current ESP during signal/exception
handling, it won't work.

After the function return, 'kernel32.lstrcpyA' is executed (see first snippet
-> 00410B3A). 

https://source.winehq.org/git/wine.git/blob/HEAD:/dlls/kernel32/string.c#l76

--- snip ---
$ objdump -S -d
/home/focht/projects/wine/mainline-install-x86_64/lib/wine/kernel32.dll.so |
awk -F"\n" -v RS="\n\n" '$1 ~ /lstrcpyA/'

7b491530 <lstrcpyA>:
/***********************************************************************
 *           lstrcpyA   (KERNEL32.@)
 *           lstrcpy    (KERNEL32.@)
 */
LPSTR WINAPI lstrcpyA( LPSTR dst, LPCSTR src )
{
7b491530:    8d 4c 24 04              lea    0x4(%esp),%ecx
7b491534:    83 e4 f0                 and    $0xfffffff0,%esp
7b491537:    ff 71 fc                 pushl  -0x4(%ecx)
7b49153a:    55                       push   %ebp
7b49153b:    89 e5                    mov    %esp,%ebp
7b49153d:    53                       push   %ebx
7b49153e:    51                       push   %ecx
7b49153f:    81 ec d8 00 00 00        sub    $0xd8,%esp
7b491545:    8b 01                    mov    (%ecx),%eax
    __TRY
    {
        /* this is how Windows does it */
        memmove( dst, src, strlen(src)+1 );
    }
    __EXCEPT_PAGE_FAULT
7b491547:    c7 85 48 ff ff ff f0     movl   $0x7b4a5af0,-0xb8(%ebp)
7b49154e:    5a 4a 7b 
{
7b491551:    89 85 34 ff ff ff        mov    %eax,-0xcc(%ebp)
7b491557:    8b 41 04                 mov    0x4(%ecx),%eax
    __EXCEPT_PAGE_FAULT
7b49155a:    6a 00                    push   $0x0
{
7b49155c:    89 85 30 ff ff ff        mov    %eax,-0xd0(%ebp)
    __EXCEPT_PAGE_FAULT
7b491562:    8d 85 54 ff ff ff        lea    -0xac(%ebp),%eax
7b491568:    50                       push   %eax
7b491569:    e8 fc ff ff ff           call   7b49156a <lstrcpyA+0x3a>
...
--- snip ---

There is quite an amount of stack space required by SEH setup (exception
registration record, signal buffer, various ptrs) which now overlaps/reuses the
area of former stack frame. Lets assume that one is not touched (no exception)
hence the buffer content is still there ...

The next Win32 API call will for sure corrupt it in between the final native
API (ntdll) call: 'kernel32.SetCurrentDirectoryA'

https://source.winehq.org/git/wine.git/blob/HEAD:/dlls/kernel32/path.c#l1813

--- snip ---
objdump -S -d
/home/focht/projects/wine/mainline-install-x86_64/lib/wine/kernel32.dll.so |
awk -F"\n" -v RS="\n\n" '$1 ~ /SetCurrentDirectoryA/'
7b478dc0 <SetCurrentDirectoryA>:
{
7b478dc0:    8d 4c 24 04              lea    0x4(%esp),%ecx
7b478dc4:    83 e4 f0                 and    $0xfffffff0,%esp
7b478dc7:    ff 71 fc                 pushl  -0x4(%ecx)
7b478dca:    55                       push   %ebp
7b478dcb:    89 e5                    mov    %esp,%ebp
7b478dcd:    53                       push   %ebx
7b478dce:    51                       push   %ecx
7b478dcf:    83 ec 18                 sub    $0x18,%esp
    if (!(dirW = FILE_name_AtoW( dir, FALSE ))) return FALSE;
7b478dd2:    6a 00                    push   $0x0
7b478dd4:    ff 31                    pushl  (%ecx)
7b478dd6:    e8 65 a2 fd ff           call   7b453040 <FILE_name_AtoW>
7b478ddb:    83 c4 10                 add    $0x10,%esp
7b478dde:    31 d2                    xor    %edx,%edx
7b478de0:    85 c0                    test   %eax,%eax
7b478de2:    74 26                    je     7b478e0a
<SetCurrentDirectoryA+0x4a>
    RtlInitUnicodeString( &strW, dirW );
7b478de4:    83 ec 08                 sub    $0x8,%esp
7b478de7:    8d 5d f0                 lea    -0x10(%ebp),%ebx
7b478dea:    50                       push   %eax
7b478deb:    53                       push   %ebx
7b478dec:    e8 df 20 fc ff           call   7b43aed0 <RtlInitUnicodeString>
    status = RtlSetCurrentDirectory_U( &strW );
7b478df1:    83 ec 04                 sub    $0x4,%esp
7b478df4:    53                       push   %ebx
7b478df5:    e8 e6 21 fc ff           call   7b43afe0
<RtlSetCurrentDirectory_U>
7b478dfa:    89 c3                    mov    %eax,%ebx
    if (status != STATUS_SUCCESS) SetLastError( RtlNtStatusToDosError(status)
);
7b478dfc:    83 c4 0c                 add    $0xc,%esp
7b478dff:    85 c0                    test   %eax,%eax
7b478e01:    75 1d                    jne    7b478e20
<SetCurrentDirectoryA+0x60>
    return !status;
7b478e03:    31 d2                    xor    %edx,%edx
7b478e05:    85 db                    test   %ebx,%ebx
7b478e07:    0f 94 c2                 sete   %dl
}
7b478e0a:    8d 65 f8                 lea    -0x8(%ebp),%esp
7b478e0d:    89 d0                    mov    %edx,%eax
7b478e0f:    59                       pop    %ecx
7b478e10:    5b                       pop    %ebx
7b478e11:    5d                       pop    %ebp
7b478e12:    8d 61 fc                 lea    -0x4(%ecx),%esp
7b478e15:    c2 04 00                 ret    $0x4
7b478e18:    90                       nop
7b478e19:    8d b4 26 00 00 00 00     lea    0x0(%esi,%eiz,1),%esi
    if (status != STATUS_SUCCESS) SetLastError( RtlNtStatusToDosError(status)
);
7b478e20:    83 ec 0c                 sub    $0xc,%esp
7b478e23:    50                       push   %eax
7b478e24:    e8 ff 20 fc ff           call   7b43af28 <RtlNtStatusToDosError>
7b478e29:    64 a3 34 00 00 00        mov    %eax,%fs:0x34
7b478e2f:    83 c4 0c                 add    $0xc,%esp
7b478e32:    eb cf                    jmp    7b478e03
<SetCurrentDirectoryA+0x43>
--- snip ---

One of the leaf functions (FILE_name_AtoW, RtlInitUnicodeString, ..) will
always touch the area of the former app 00410A70 sub-routine stack frame,
corrupting the contents.

Unfortunately there is not much that can be done about here.

ProtectionID scan of the offending executable '~0000.exe':

--- snip ---
-=[ ProtectionID v0.6.9.0 DECEMBER]=-
(c) 2003-2017 CDKiLLER & TippeX
Build 24/12/17-21:05:42
Ready...
Scanning -> C:\users\focht\Temp\~0000.exe
File Type : 32-Bit Exe (Subsystem : Win GUI / 2), Size : 345088 (054400h)
Byte(s) | Machine: 0x14C (I386)
Compilation TimeStamp : 0x327E7F39 -> Mon 04th Nov 1996 23:41:45 (GMT)
[TimeStamp] 0x327E7F39 -> Mon 04th Nov 1996 23:41:45 (GMT) | PE Header | - |
Offset: 0x00000088 | VA: 0x00400088 | -
[File Heuristics] -> Flag #1 : 00000000000001001100000000000000 (0x0004C000)
[Entrypoint Section Entropy] : 6.41 (section #0) ".text   " | Size : 0x3ABE6
(240614) byte(s)
[DllCharacteristics] -> Flag : (0x0000) -> NONE
[SectionCount] 6 (0x6) | ImageSize 0x5D000 (380928) byte(s)
[VersionInfo] Company Name : 2020 Software
[VersionInfo] Product Name : 2020 Software Install
[VersionInfo] Product Version : 5. 0. 0. 1
[VersionInfo] File Description : Install
[VersionInfo] File Version : 5. 0. 0. 1
[VersionInfo] Original FileName : Install.exe
[VersionInfo] Internal Name : Install
[VersionInfo] Legal Copyrights : Copyright © 1996
[ModuleReport] [IAT] Modules -> VERSION.dll | WINMM.dll | MPR.dll |
KERNEL32.dll | USER32.dll | GDI32.dll | comdlg32.dll | WINSPOOL.DRV |
ADVAPI32.dll | SHELL32.dll | COMCTL32.dll | ole32.dll
[CompilerDetect] -> Visual C++ 3.0
[!] File appears to have no protection or is using an unknown protection
- Scan Took : 0.244 Second(s) [0000000F4h (244) tick(s)] [506 of 580 scan(s)
done]
--- snip ---

$ sha1sum SC_DEMO.EXE 
f3ceb9d47776c7a9a9909c634b9ee4d976967338  SC_DEMO.EXE

$ du -sh SC_DEMO.EXE 
18M    SC_DEMO.EXE

$ wine --version
wine-4.7-177-gb479382737

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