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