VirtualProtect and app crash: what's your interpretation?

Robert Baruch autophile at starband.net
Tue Dec 11 18:36:59 CST 2001


Here is my thought process on why the application crashes with a 
protection violation reading a section of memory.

I used IDA to disassemble the app. Here's the section where it reads 
from memory and crashes because of a protection violation:

00760D4A sub_760D4A      proc near               ; CODE XREF: 
sub_75FCB0+159^Xp
00760D4A                 push    ebp
00760D4B                 mov     eax, ds:dword_75D66C
00760D50                 mov     ebp, esp
00760D52                 sub     esp, 14h
00760D55                 push    ebx
;
; localvar <- *(long *)75D66C = 0x56000
;
00760D56                 mov     [ebp-4], eax
00760D59                 push    esi
00760D5A                 push    edi
00760D5B                 push    eax
00760D5C                 push    75D5F0h
00760D61                 call    sub_75FC7B
00760D66                 add     esp, 8
00760D69                 mov     ds:dword_75D720, eax
00760D6E                 movzx   ecx, ds:word_75D5F2
00760D75                 cmp     ecx, eax
00760D77                 jbe     loc_760EFC
00760D7D                 mov     ecx, ds:dword_75D704
00760D83                 imul    eax, 28h
00760D86                 mov     eax, [eax+ecx+34h]
00760D8A                 sub     eax, [ebp-4]
00760D8D                 cmp     ds:dword_75D734, 2
00760D94                 mov     [ebp-14h], eax
;
; If (*(long *)75D734) != 2 then skip the call to
; VirtualProtect
;
00760D97                 jnz     short loc_760DC4
;
; Push arguments to VirtualProtect(
;    LPVOID lpAddress,
;    SIZE_T dwSize,
;    DWORD flNewProtect,
;    PDWORD lpflOldProtect)
;
00760D99                 lea     eax, [ebp-8]
;
; lpflOldProtect is a local variable
;
00760D9C                 push    eax
;
; flNewProtect = 4 = PAGE_READWRITE
;
00760D9D                 push    4
;
; dwSize = size of region to protect = a local variable
;
00760D9F                 push    dword ptr [ebp-14h]
;
; lpAddress = base address of region to protect =
;   (*(long *)75D728) + localvar
;
00760DA2                 mov     eax, ds:dword_75D728
00760DA7                 add     eax, [ebp-4]
00760DAA                 push    eax
00760DAB                 call    ds:VirtualProtect
00760DB1                 test    eax, eax
;
; On success go to loc_760DC4
;
00760DB3                 jnz     short loc_760DC4
;
; This routine prints an error message and exits
;
00760DB5                 push    3
00760DB7                 push    75B470h
00760DBC                 call    sub_7602F6
00760DC1                 add     esp, 8
00760DC4
00760DC4 loc_760DC4:                             ; CODE XREF: 
sub_760D4A+4D^Xj
00760DC4                                         ; sub_760D4A+69^Xj
00760DC4                 mov     esi, ds:dword_75D728
00760DCA                 add     esi, [ebp-4]
;
; here is the instruction causing the fault:
;   eax <- *(0x10 + (*(long *)75D728) + localvar)
;
00760DCD                 mov     eax, [esi+10h]
00760DD0                 test    eax, eax
00760DD2                 jz      loc_760EB4
00760DD8
00760DD8 loc_760DD8:                             ; CODE XREF: 
sub_760D4A+164^Yj
00760DD8                 mov     ecx, ds:dword_75D728
00760DDE                 lea     edi, [ecx+eax]
00760DE1                 mov     eax, [esi+0Ch]
00760DE4                 add     eax, ecx
00760DE6                 push    eax
00760DE7                 mov     [ebp-10h], eax
00760DEA                 call    ds:LoadLibraryA
00760DF0                 mov     [ebp-0Ch], eax
00760DF3                 test    eax, eax
00760DF5                 jnz     short loc_760E04
00760DF7                 push    6
00760DF9                 push    dword ptr [ebp-10h]
00760DFC                 call    sub_7602F6
00760E01                 add     esp, 8


At this point, winedbg says

First chance exception: page fault on read access to 0x00456010 in 
32-bit code (0x00760dcd)

According to IDA, that section at 0x456000 is named .shrink1, is an 
uninitialized section of memory, and should be read/write:

00456000 ; Section 3. (virtual address 00056000)
00456000 ; Virtual size                  : 00002000 (   8192.)
00456000 ; Section size in file          : 00000000 (      0.)
00456000 ; Offset to raw data for section: 00000000
00456000 ; Flags C0000082: Noload Bss Readable Writable
00456000 ; Alignment     : 16 bytes by default
00456000
00456000 .shrink1        segment para public '' use32
00456000                 assume cs:.shrink1
00456000                 ;org 456000h



In addition, the trace of the last call to VirtualProtect shows this:

0806d398:Call 
kernel32.VirtualProtect(00459000,00000018,00000002,40616e74) ret=0075fd82
0806d398:trace:virtual:VirtualProtect 0x459000 00000018 00000002
0806d398:trace:virtual:VIRTUAL_SetProt 0x459000-0x459fff c-r--
View: 0x400000 - 0x765fff 28
       0x400000 - 0x400fff c-r--
       0x401000 - 0x458fff c----   <-- here is where the read is
       0x459000 - 0x459fff c-r--
       0x45a000 - 0x758fff c----
       0x759000 - 0x765fff c-rw-
0806d398:Ret  kernel32.VirtualProtect() retval=00000001 ret=0075fd82

Note that this call has a protect of 2, not 4. That means the branch at 
00760D97 must have been taken. That branch gets taken if
*(long *)75D734 is not equal to 2. gdb shows that the contents is 3. And 
where does this value come from?

It is loaded only in this subroutine:


0075F000 sub_75F000      proc near               ; CODE XREF: start+7C^Yp
0075F000                 call    ds:GetVersion
0075F006                 movzx   ecx, ah
0075F009                 movzx   edx, al
0075F00C                 mov     dword_75D738, ecx
0075F012                 mov     dword_75D730, edx
0075F018                 test    eax, 80000000h
0075F01D                 jz      short loc_75F03C
0075F01F                 cmp     edx, 4
0075F022                 jge     short loc_75F030
0075F024                 mov     dword_75D734, 1
0075F02E                 jmp     short locret_75F046
0075F030
0075F030 loc_75F030:                             ; CODE XREF: 
sub_75F000+22^Xj
0075F030                 mov     dword_75D734, 2
0075F03A                 jmp     short locret_75F046
0075F03C
0075F03C loc_75F03C:                             ; CODE XREF: 
sub_75F000+1D^Xj
0075F03C                 mov     dword_75D734, 3
0075F046
0075F046 locret_75F046:                          ; CODE XREF: 
sub_75F000+2E^Xj
0075F046                                         ; sub_75F000+3A^Xj
0075F046                 retn
0075F046 sub_75F000      endp


*(long *)75D738 is the major version of Windows, 4.
*(long *)75D730 is the minor version, 0.
*(long *)75D734 is a "level", 3 for NT/2000/XP, 2 for 95/98/Me, or 1 for 
win32s. It contains 3.

Thus, GetVersion is correctly returning NT 4.0.

This was the trace of the last call to GetVersion:

0806d398:Call kernel32.GetVersion() ret=0075f006
0806d398:Ret  kernel32.GetVersion() retval=05650004 ret=0075f006


So the funny thing is that the app skips the VirtualProtect (which would 
have made the memory have read/write access) only if the Windows version 
is not 95/98/Me. But I didn't get a crash when I ran this natively under 
2000 (which was running under VMWare)!

So next I need to find the place where the protection is placed on the 
memory range in question. The wine trace shows this:

0806d398:Call 
kernel32.VirtualProtect(00401000,00358000,00000001,40616e74) ret=0075fd22
0806d398:trace:virtual:VirtualProtect 0x401000 00358000 00000001
0806d398:trace:virtual:VIRTUAL_SetProt 0x401000-0x758fff c----
View: 0x400000 - 0x765fff 28
       0x400000 - 0x400fff c-r--
       0x401000 - 0x758fff c----
       0x759000 - 0x765fff c-rw-
0806d398:Ret  kernel32.VirtualProtect() retval=00000001 ret=0075fd22


And the listing around that address is:


0075FCB0 sub_75FCB0      proc near               ; CODE XREF: 
sub_7605E2+47^Yp
0075FCB0                 push    ebp
0075FCB1                 mov     ebp, esp
0075FCB3                 sub     esp, 4
0075FCB6                 cmp     dword_75D734, 2
0075FCBD                 push    esi
;
; If "level" is not 2, call bunches of VirtualProtects.
;
0075FCBE                 jnz     short loc_75FCE4
0075FCC0                 mov     eax, dword_75D728
0075FCC5                 push    eax
0075FCC6                 push    75D5F0h
0075FCCB                 push    dword_75D72C
0075FCD1                 push    dword_75B1B4
0075FCD7                 call    sub_7617D3
0075FCDC                 add     esp, 10h
0075FCDF                 jmp     loc_75FDAD
0075FCE4
0075FCE4 loc_75FCE4:                             ; CODE XREF: 
sub_75FCB0+E^Xj
0075FCE4                 mov     eax, dword_75D63C
0075FCE9                 add     eax, dword_75D728
0075FCEF                 push    eax
0075FCF0                 push    dword_75D728
0075FCF6                 call    sub_75F07E
0075FCFB                 add     esp, 8
;
; This is VirtualProtect call from above:
;
; base address = *(long *)75D624 + *(long *)75D728
; size = *(long *)75D63C - *(long *)75D624
; access = PAGE_NOACCESS
; oldaccessptr = localvar
;
0075FCFE                 lea     ecx, [ebp-4]
0075FD01                 mov     eax, dword_75D63C
0075FD06                 push    ecx
0075FD07                 push    1
0075FD09                 sub     eax, dword_75D624
0075FD0F                 push    eax
0075FD10                 mov     eax, dword_75D624
0075FD15                 add     eax, dword_75D728
0075FD1B                 push    eax
0075FD1C                 call    ds:VirtualProtect
0075FD22                 test    eax, eax
0075FD24                 jnz     short loc_75FD35
;
0075FD26                 push    7
0075FD28                 push    75B3FCh
0075FD2D                 call    sub_7602F6
0075FD32                 add     esp, 8
0075FD35
0075FD35 loc_75FD35:                             ; CODE XREF: 
sub_75FCB0+74^Xj
0075FD35                 mov     esi, dword_75D6AC
0075FD3B                 push    esi
0075FD3C                 push    75D5F0h
0075FD41                 call    sub_75FC7B
0075FD46                 add     esp, 8
;
0075FD49                 mov     [ebp-4], eax
0075FD4C                 movzx   eax, word_75D5F2
0075FD53                 cmp     eax, [ebp-4]
0075FD56                 jbe     short loc_75FD95
;
; This is the last VirtualProtect call.
; base address = esi + *(long *)dword_75D728
;              = 0x459000
; size = *(long *)75D6B0
;      = 0x18
; new = the return value of sub_75FC23
;     = 2 = PAGE_READONLY
; oldptr = ?
;
0075FD58                 lea     eax, [ebp-4]
0075FD5B                 push    eax
0075FD5C                 push    dword ptr [ebp-4]
0075FD5F                 push    dword_75D704
0075FD65                 call    sub_75FC23
0075FD6A                 add     esp, 8
0075FD6D                 push    eax
0075FD6E                 mov     eax, dword_75D728
0075FD73                 push    dword_75D6B0
0075FD79                 add     eax, esi
0075FD7B                 push    eax
0075FD7C                 call    ds:VirtualProtect
0075FD82                 test    eax, eax
0075FD84                 jnz     short loc_75FD95
;
0075FD86                 push    7
0075FD88                 push    75B400h
0075FD8D                 call    sub_7602F6
0075FD92                 add     esp, 8
0075FD95
0075FD95 loc_75FD95:                             ; CODE XREF: 
sub_75FCB0+A6^Xj
0075FD95                                         ; sub_75FCB0+D4^Xj
0075FD95                 mov     eax, dword_75D664
0075FD9A                 push    eax
0075FD9B                 push    75D5F0h
0075FDA0                 call    sub_75FC7B
0075FDA5                 add     esp, 8
0075FDA8                 mov     dword_75D5E4, eax


Note that *(long *)75D728 was also mentioned in the skipped 
VirtualProtect call. That address contains 0x400000, which is the base 
of the memory-mapped executable:

0806d398:trace:module:map_image mapped PE file at 0x400000-0x766000
0806d398:trace:module:map_image mapping section .rdata at 0x459000 off 
400 size
200 flags 50000040
0806d398:trace:module:map_image mapping section .shrink3 at 0x759000 off 
7600 size 200 flags 40000042
0806d398:trace:module:map_image clearing 0x759200 - 0x75a000
0806d398:trace:module:map_image mapping section .rdata at 0x75a000 off 
301000 size 200 flags 40000040
0806d398:trace:module:map_image mapping section .data at 0x75b000 off 
800 size 1600 flags c0000040
0806d398:trace:module:map_image clearing 0x75c600 - 0x75d000
0806d398:trace:module:map_image mapping section .idata at 0x75e000 off 
1e00 size a00 flags c0000040
0806d398:trace:module:map_image mapping section .load at 0x75f000 off 
2800 size
3200 flags 68040020
0806d398:trace:module:map_image mapping section .reloc at 0x763000 off 
5a00 size 800 flags 42000040
0806d398:trace:module:map_image mapping section .rsrc at 0x764000 off 
6200 size
1400 flags 50000040
0806d398: dup_handle( src_process=-1, src_handle=24, dst_process=-1, 
access=00000000, inherit=0, options=2 )
0806d398: dup_handle() = 0 { handle=28, fd=-1 }
View: 0x400000 - 0x765fff 28
       0x400000 - 0x765fff c-rw-

That address is filled via a call to VirtualQuery, storing the 
allocation base address of the virtual memory space containing the 
address 0x75D5F0.

Why is the app trying to read from 0x456010? Where did it get the 
0x56000 offset from? That offset was stored in 0x75D66C. That address is 
not mentioned explicitly anywhere else, but looking at the memory around 
that value, it looks like there is an array of file segment info there.

So this call:

;
; This is VirtualProtect call from above:
;
; base address = *(long *)75D624 + *(long *)75D728
; size = *(long *)75D63C - *(long *)75D624
; access = PAGE_NOACCESS
; oldaccessptr = localvar
;


actually sets PAGE_NOACCESS from the beginning of a segment to the end 
of the segment. That segment is at 0x1000, and the next segment in the 
array starts at 0x359000.

At this point I'm totally nonplussed.

Does anyone know of an NT/2000 debugger that works? I tried Borland's 
free TurboDebugger 5.5 but that failed to run the program.

--Rob










More information about the wine-users mailing list