[Bug 33162] Acrobat Reader 11 crashes on start (native API application virtualization, NtProtectVirtualMemory removes execute page protection on its own code)

wine-bugs at winehq.org wine-bugs at winehq.org
Sun Mar 10 11:28:39 CDT 2013


http://bugs.winehq.org/show_bug.cgi?id=33162

Anastasius Focht <focht at gmx.net> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Keywords|                            |obfuscation
                 CC|                            |focht at gmx.net
          Component|-unknown                    |ntdll
            Summary|Acrobat Reader 11 (Polish)  |Acrobat Reader 11 crashes
                   |crashes on start)           |on start (native API
                   |                            |application virtualization,
                   |                            |NtProtectVirtualMemory
                   |                            |removes execute page
                   |                            |protection on its own code)

--- Comment #2 from Anastasius Focht <focht at gmx.net> 2013-03-10 11:28:39 CDT ---
Hello folks,

finally an interesting bug ... among many boring ones :-)

It seems  Acrobat Reader 11 employs an application virtualization scheme at
native API level -> 'sandboxing'.
The parent process launches a child process 'sandbox' which gets heavily
patched at startup.
Many native API entries get detoured.

Child process start:

--- snip ---
0009:fixme:advapi:CreateProcessAsUserW 0xc4 L"C:\\Program Files\\Adobe\\Reader
11.0\\Reader\\AcroRd32.exe" L"\"C:\\Program Files\\Adobe\\Reader
11.0\\Reader\\AcroRd32.exe\" --channel=8.1.111197294 --type=renderer" (nil)
(nil) 0 0x0100040c 0x565b60 (null) 0x32f4fc 0x32f548 - semi- stub 
0009:trace:process:create_process_impl app L"C:\\Program Files\\Adobe\\Reader
11.0\\Reader\\AcroRd32.exe" cmdline L"\"C:\\Program Files\\Adobe\\Reader
11.0\\Reader\\AcroRd32.exe\" --channel=8.1.111197294 --type=renderer" 
...
0009: new_process() = 0 { info=00cc, pid=0028, phandle=00d0, tid=0029,
thandle=00d4 }
...
0009: *wakeup* signaled=0
0029: *sent signal* signal=10
0029: init_process_done() = 0
--- snip ---

Successful patch sequence of ntdll "NtCreateMutant" in child process:

--- snip ---
0009: read_process_memory( handle=00d0, addr=7bc812b2 )
0029: *signal* signal=19
0009: read_process_memory() = 0 {
data={55,89,e5,57,56,53,83,e4,f0,81,ec,b0,00,00,00,e8} }
0009: read_process_memory( handle=00d0, addr=7bc812b2 )
0029: *signal* signal=19
0009: read_process_memory() = 0 {
data={55,89,e5,57,56,53,83,e4,f0,81,ec,b0,00,00,00,e8} }
0009: write_process_memory( handle=00d0, addr=0068f210,
data={55,89,e5,57,56,53,83,e4,f0,81,ec,b0,00,00,00,e8,00,00,00,00,00,00,00,00,83,ec,08,52,8b,54,24,0c,89,54,24,08,c7,44,24,0c,10,f2,68,00,c7,44,24,04,a0,4a,40,00,5a,c3}
)
0029: *signal* signal=19
0009: write_process_memory() = 0
0009:trace:virtual:NtProtectVirtualMemory 0xd0 0x7bc812b2 0000000c 00000008
0009: queue_apc( handle=00d0,
call={APC_VIRTUAL_PROTECT,addr=7bc812b2,size=0000000c,prot=8} )
0029: *wakeup* signaled=192
0009: queue_apc() = 0 { handle=00c8, self=0 }
0029: select( flags=4, cookie=7ffdb29c, signal=0000, prev_apc=0000,
timeout=1ce1d98c58f8b8a (-0.0116980), result={}, handles={} )
0029: select() = USER_APC { timeout=1ce1d98c58f8b8a (-0.0116980),
call={APC_VIRTUAL_PROTECT,addr=7bc812b2,size=0000000c,prot=8}, apc_handle=0024
}
0029:trace:virtual:NtProtectVirtualMemory 0xffffffff 0x7bc812b2 0000000c
00000008
0009: select( flags=4, cookie=0032f2ac, signal=0000, prev_apc=0000,
timeout=infinite, result={}, handles={00c8} )
0029:trace:virtual:VIRTUAL_SetProt 0x7bc81000-0x7bc81fff c-rW-
0009: select() = PENDING { timeout=infinite, call={APC_NONE}, apc_handle=0000 }
0029:trace:virtual:VIRTUAL_DumpView View: 0x7bc10000 - 0x7bce3fff (system)
0029:trace:virtual:VIRTUAL_DumpView       0x7bc10000 - 0x7bc10fff c-r--
0029:trace:virtual:VIRTUAL_DumpView       0x7bc11000 - 0x7bc80fff c-r-x
0029:trace:virtual:VIRTUAL_DumpView       0x7bc81000 - 0x7bc81fff c-rW-
0029:trace:virtual:VIRTUAL_DumpView       0x7bc82000 - 0x7bcc7fff c-r-x
0029:trace:virtual:VIRTUAL_DumpView       0x7bcc8000 - 0x7bce3fff c-rw-
0029: select( flags=4, cookie=7ffdb29c, signal=0000, prev_apc=0024,
timeout=1ce1d98c58f8b8a (-0.0117520),
result={APC_VIRTUAL_PROTECT,status=0,addr=7bc81000,size=00001000,prot=20},
handles={} )
0009: *wakeup* signaled=0
0029: select() = PENDING { timeout=1ce1d98c58f8b8a (-0.0117520),
call={APC_NONE}, apc_handle=0000 }
0009: get_apc_result( handle=00c8 )
0009: get_apc_result() = 0 {
result={APC_VIRTUAL_PROTECT,status=0,addr=7bc81000,size=00001000,prot=20} }
0009: write_process_memory( handle=00d0, addr=7bc812b2,
data={b8,89,e5,57,56,ba,28,f2,68,00,ff,e2} )
0029: *signal* signal=19
0009: write_process_memory() = 0
0009:trace:virtual:NtProtectVirtualMemory 0xd0 0x7bc812b2 0000000c 00000020
0009: queue_apc( handle=00d0,
call={APC_VIRTUAL_PROTECT,addr=7bc812b2,size=0000000c,prot=20} )
0029: *wakeup* signaled=192
0009: queue_apc() = 0 { handle=00c8, self=0 }
0029: select( flags=4, cookie=7ffdb29c, signal=0000, prev_apc=0000,
timeout=1ce1d98c58f8b8a (-0.0118720), result={}, handles={} )
0029: select() = USER_APC { timeout=1ce1d98c58f8b8a (-0.0118720),
call={APC_VIRTUAL_PROTECT,addr=7bc812b2,size=0000000c,prot=20}, apc_handle=0024
}
0029:trace:virtual:NtProtectVirtualMemory 0xffffffff 0x7bc812b2 0000000c
00000020
0009: select( flags=4, cookie=0032f2ac, signal=0000, prev_apc=0000,
timeout=infinite, result={}, handles={00c8} )
0029:trace:virtual:VIRTUAL_SetProt 0x7bc81000-0x7bc81fff c-r-x
0009: select() = PENDING { timeout=infinite, call={APC_NONE}, apc_handle=0000 }
0029:trace:virtual:VIRTUAL_DumpView View: 0x7bc10000 - 0x7bce3fff (system)
0029:trace:virtual:VIRTUAL_DumpView       0x7bc10000 - 0x7bc10fff c-r--
0029:trace:virtual:VIRTUAL_DumpView       0x7bc11000 - 0x7bcc7fff c-r-x
0029:trace:virtual:VIRTUAL_DumpView       0x7bcc8000 - 0x7bce3fff c-rw-
0029: select( flags=4, cookie=7ffdb29c, signal=0000, prev_apc=0024,
timeout=1ce1d98c58f8b8a (-0.0119190),
result={APC_VIRTUAL_PROTECT,status=0,addr=7bc81000,size=00001000,prot=8},
handles={} )
0009: *wakeup* signaled=0
0029: select() = PENDING { timeout=1ce1d98c58f8b8a (-0.0119190),
call={APC_NONE}, apc_handle=0000 }
0009: get_apc_result( handle=00c8 )
0009: get_apc_result() = 0 {
result={APC_VIRTUAL_PROTECT,status=0,addr=7bc81000,size=00001000,prot=8} }
--- snip ---

The child API entry is read and analyzed for patchable opcode sequences (parent
process looks for specific opcodes).
A short opcode sequence is inserted into API entry prolog code by changing page
protection with VirtualProtectEx() to PAGE_WRITECOPY, calling
WriteProcessMemory() and changing protection back to PAGE_EXECUTE_READ.

Original:

--- snip ---
7BC43C84    push ebp
7BC43C85    mov ebp,esp
7BC43C87    push edi
7BC43C88    push esi
7BC43C89    push ebx
...
--- snip ---

Becomes:

--- snip ---
7BC43C84    mov eax,5657E589
7BC43C89    mov edx,3D8128
7BC43C8E    jmp edx
7BC43C90    add [eax],eax
...
--- snip ---

The original chunk (prolog) is written to another place in child process (along
with app own code chunk).

Now the failing patch sequence -> "NtCreateSection"

Like the previous case, the original entry opcodes are read and saved in
another place in child process:

--- snip ---
0009: read_process_memory( handle=00d0, addr=7bc94f6e )
0029: *signal* signal=19
0009: read_process_memory() = 0 {
data={55,89,e5,53,83,e4,f0,81,ec,b0,00,00,00,e8,f7,92} }
0009: read_process_memory( handle=00d0, addr=7bc94f6e )
0029: *signal* signal=19
0009: read_process_memory() = 0 {
data={55,89,e5,53,83,e4,f0,81,ec,b0,00,00,00,e8,f7,92} }
0009: write_process_memory( handle=00d0, addr=0068f250,
data={55,89,e5,53,83,e4,f0,81,ec,b0,00,00,00,e8,f7,92,00,00,00,00,00,00,00,00,83,ec,08,52,8b,54,24,0c,89,54,24,08,c7,44,24,0c,50,f2,68,00,c7,44,24,04,d0,43,40,00,5a,c3}
)
0029: *signal* signal=19
0009: write_process_memory() = 0 
--- snip ---

The target page protection bits are set for patch sequence (process write), by
calling VirtualProtectEx() with PAGE_WRITECOPY.

--- snip ---
0009:trace:virtual:NtProtectVirtualMemory 0xd0 0x7bc94f6e 0000000c 00000008
0009: queue_apc( handle=00d0,
call={APC_VIRTUAL_PROTECT,addr=7bc94f6e,size=0000000c,prot=8} )
0029: *wakeup* signaled=192
0009: queue_apc() = 0 { handle=00c8, self=0 }
0029: select( flags=4, cookie=7ffdb29c, signal=0000, prev_apc=0000,
timeout=1ce1d98c58f8b8a (-0.0121440), result={}, handles={} )
0029: select() = USER_APC { timeout=1ce1d98c58f8b8a (-0.0121440),
call={APC_VIRTUAL_PROTECT,addr=7bc94f6e,size=0000000c,prot=8}, apc_handle=0024
}
0029:trace:virtual:NtProtectVirtualMemory 0xffffffff 0x7bc94f6e 0000000c
00000008
0009: select( flags=4, cookie=0032f2ac, signal=0000, prev_apc=0000,
timeout=infinite, result={}, handles={00c8} )
0029:trace:virtual:VIRTUAL_SetProt 0x7bc94000-0x7bc94fff c-rW-
0009: select() = PENDING { timeout=infinite, call={APC_NONE}, apc_handle=0000 }
0029:trace:virtual:VIRTUAL_DumpView View: 0x7bc10000 - 0x7bce3fff (system)
0029:trace:virtual:VIRTUAL_DumpView       0x7bc10000 - 0x7bc10fff c-r--
0029:trace:virtual:VIRTUAL_DumpView       0x7bc11000 - 0x7bc93fff c-r-x
0029:trace:virtual:VIRTUAL_DumpView       0x7bc94000 - 0x7bc94fff c-rW-
0029:trace:virtual:VIRTUAL_DumpView       0x7bc95000 - 0x7bcc7fff c-r-x
0029:trace:virtual:VIRTUAL_DumpView       0x7bcc8000 - 0x7bce3fff c-rw- 
0029:err:seh:setup_exception_record nested exception on signal stack in thread
0029 eip 7bc942f2 esp 7ffdaec0 stack 0x242000-0x340000
0029: *killed* exit_code=0
0028: *process killed*
000c: *process killed* 
*boom*
--- snip ---

To carry out VirtualProtectEx(), Wine queues an APC to the child process.
NtProtectVirtualMemory() is called on arbitrary thread in the child process.

The problem: the code of executing NtProtectVirtualMemory() API call lives in
the same page that needs it's page protection changed to allow the app to patch
NtCreateSection() API entry.
If you look closely at the virtual view dump after protection change:

--- snip ---
0029:trace:virtual:VIRTUAL_DumpView       0x7bc94000 - 0x7bc94fff c-rW-
--- snip ---

The code of VIRTUAL_SetProt() lies in a different page hence you still see the
debug channel dump (+virtual) before the crash.
When the call returns to NtProtectVirtualMemory(), a segv is raised because the
page is no longer "execute".

Regards

-- 
Configure bugmail: http://bugs.winehq.org/userprefs.cgi?tab=email
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