SEH,DEP, Compiler,FS:[0], LOAD_CONFIG and PE format
原贴地址:
http://spaces.msn.com/eparg/blog/cns!59BFC22C0E7E1A76!712.entry
原贴时间:
2006-04-16
原贴作者:
eparg
Recently I am planning some user mode troubleshooting paper. In the exception section, I tried the following code to explain how SEH works in VS2005
BOOL EXCEPTION=0; //Exception flag.
class ExcepStackMsg //Create local variable of this class to record function info
{
char* pmsg;
public:
ExcepStackMsg(char *msg)
{
pmsg=(char*)malloc(strlen(msg)+1);
strcpy(pmsg,msg);
};
~ExcepStackMsg()
{
if(EXCEPTION)
printf("Exception happens: %s/n",pmsg);
free(pmsg);
};
};
EXCEPTION_DISPOSITION
__cdecl
_except_handler(
struct _EXCEPTION_RECORD *ExceptionRecord,
void * EstablisherFrame,
struct _CONTEXT *ContextRecord,
void * DispatcherContext )
{
EXCEPTION=1;
//printf("got it/n");
return ExceptionContinueSearch; //Allow C++ handler to run
}
void foo1()
{
//printf("in foo1/n");
//ExcepStackMsg e("foo1");
throw 1;
};
void foo2()
{
//printf("in foo2/n");
//ExcepStackMsg e("foo2");
foo1();
};
void foo3()
{
//printf("in foo3/n");
//ExcepStackMsg e("foo3");
foo2();
};
DWORD WINAPI ThreadProc( LPVOID lpParameter)
{
DWORD myESP;
void * test=_except_handler;
DWORD handler = (DWORD)_except_handler;
//_except_handler(0,0,0,0);
printf("1/n");
getchar();
__asm
{
mov myESP,ESP //Save Stack register pointers
push _except_handler // Address of handler function
// push test // Address of handler function
push FS:[0] // Address of previous handler
mov FS:[0],ESP // Install new EXECEPTION_REGISTRATION
// mov FS:[0],100
}
try
{
printf("2/n");
getchar();
foo3();
}
catch(...)
{
EXCEPTION=0;
printf("catch/n");
};
__asm
{
mov ESP,myESP //Restore Stack register pointers
}
return 0;
}
The problem is that, in debug mode, my _except_handler is injected and called fine. However, in release mode, the application exists silently. No AV, just inform me 2nd chance C++ exception. How could it happen?
I set bp on kernel32!RaiseException, and then use !exchain to exam, but the result is under my expectation. I have to trace the assembly code step by step.
At last, the cause is the ntdll!RtlIsValidHandler function. My _except_handler function is passed in to exam. In release mode, the return value is NULL. However, by dumping the paramer before calling RtlIsValidHanlder to check, everything is fine and the address of my _except_handler is exactly there. The only difference is that in debug mode, the handler is wrapped by a stub jmp; while in release mode, the naked function start address is passed in. Is there any restriction for the exception handler?
Checking the assembly code to find out the root cause is not the most effective way. Let me discuss with xwlan the expert tomorrow.
PS: I tried to turn off the DEP without help :(
======30 mins later=========
After idle around for a while without any other intresting things, I decied to trace on. I found the RtlIsValidHandler goes into RtlLookupFunctionTable. ok, let me try google, and then I get:
https://www.xfocus.net/bbs/index.php?act=ST&f=2&t=58881
U know what? This article is from:
http://nop.cnblogs.com/
The bloger is xwlan....
=========18 hours later=======
xwlan is OOF so I have to go on by myself.
After 2.5 hours, I found the problem is related to PE header. During the exception handler dispatch, the OS checks the PE header to find out if LOAD_CONFIG section is avaliable. I am not quite sure if the LOAD_CONFIG section is the exception handler table. If the section is avaliable, the handler must exist in the section. Otherwise, the handler must be in Read_Execute page.
The call stack is:
0012f5f8 7c832742 ntdll!RtlpImageDirectoryEntryToData32+0x44
0012f618 7c8155dc ntdll!RtlImageDirectoryEntryToData+0x57
0012f634 7c815638 ntdll!RtlCaptureImageExceptionValues+0x32
0012f670 7c813fe2 ntdll!RtlLookupFunctionTable+0xd0
0012f6c4 7c8140b3 ntdll!RtlIsValidHandler+0x24
0012f738 7c82ecc6 ntdll!RtlDispatchException+0x78
0012f738 77e55dea ntdll!KiUserExceptionDispatcher+0xe
0012fa90 10243990 kernel32!RaiseException+0x53
0012fad0 0041154c MSVCR80D!_CxxThrowException+0x50
The difference between debug version and release version is explict with windbg output and the PE tool from Matt Pietrek's PE tool in MSDN.
Release version
--------------------
0:000> dt NtHeaders -r -b
Local var @ 0x12fa6c Type _IMAGE_NT_HEADERS*
0x004000f8
+0x000 Signature : 0x4550
+0x004 FileHeader : _IMAGE_FILE_HEADER
+0x000 Machine : 0x14c
+0x002 NumberOfSections : 4
+0x004 TimeDateStamp : 0x4442f434
+0x008 PointerToSymbolTable : 0
+0x00c NumberOfSymbols : 0
+0x010 SizeOfOptionalHeader : 0xe0
+0x012 Characteristics : 0x103
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
+0x000 Magic : 0x10b
+0x002 MajorLinkerVersion : 0x8 ''
+0x003 MinorLinkerVersion : 0 ''
+0x004 SizeOfCode : 0xc00
+0x008 SizeOfInitializedData : 0xe00
+0x00c SizeOfUninitializedData : 0
+0x010 AddressOfEntryPoint : 0x1473
+0x014 BaseOfCode : 0x1000
+0x018 BaseOfData : 0x2000
+0x01c ImageBase : 0x400000
+0x020 SectionAlignment : 0x1000
+0x024 FileAlignment : 0x200
+0x028 MajorOperatingSystemVersion : 4
+0x02a MinorOperatingSystemVersion : 0
+0x02c MajorImageVersion : 0
+0x02e MinorImageVersion : 0
+0x030 MajorSubsystemVersion : 4
+0x032 MinorSubsystemVersion : 0
+0x034 Win32VersionValue : 0
+0x038 SizeOfImage : 0x5000
+0x03c SizeOfHeaders : 0x400
+0x040 CheckSum : 0x8862
+0x044 Subsystem : 3
+0x046 DllCharacteristics : 0
+0x048 SizeOfStackReserve : 0x100000
+0x04c SizeOfStackCommit : 0x1000
+0x050 SizeOfHeapReserve : 0x100000
+0x054 SizeOfHeapCommit : 0x1000
+0x058 LoaderFlags : 0
+0x05c NumberOfRvaAndSizes : 0x10
+0x060 DataDirectory :
[00] _IMAGE_DATA_DIRECTORY
+0x000 VirtualAddress : 0
+0x004 Size : 0
[01]
+0x000 VirtualAddress : 0x23a4
+0x004 Size : 0x3c
[02]
+0x000 VirtualAddress : 0x4000
+0x004 Size : 0x1ac
[03]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[04]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[05]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[06]
+0x000 VirtualAddress : 0x20e0
+0x004 Size : 0x1c
[07]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[08]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[09]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[10]
+0x000 VirtualAddress : 0x2148
+0x004 Size : 0x40
[11]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[12]
+0x000 VirtualAddress : 0x2000
+0x004 Size : 0xc4
[13]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[14]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[15]
+0x000 VirtualAddress : 0
+0x004 Size : 0
Dump of file RSE.EXE
File Header
Machine: 014C (I386)
Number of Sections: 0004
TimeDateStamp: 4442F434 -> Mon Apr 17 09:49:40 2006
PointerToSymbolTable: 00000000
NumberOfSymbols: 00000000
SizeOfOptionalHeader: 00E0
Characteristics: 0103
RELOCS_STRIPPED
EXECUTABLE_IMAGE
32BIT_MACHINE
Optional Header
Magic 010B
linker version 8.00
size of code C00
size of initialized data E00
size of uninitialized data 0
entrypoint RVA 1473
base of code 1000
base of data 2000
image base 400000
section align 1000
file align 200
required OS version 4.00
image version 0.00
subsystem version 4.00
Win32 Version 0
size of image 5000
size of headers 400
checksum 8862
Subsystem 0003 (Windows character)
DLL flags 0000
stack reserve size 100000
stack commit size 1000
heap reserve size 100000
heap commit size 1000
RVAs & sizes 10
Data Directory
EXPORT rva: 00000000 size: 00000000
IMPORT rva: 000023A4 size: 0000003C
RESOURCE rva: 00004000 size: 000001AC
EXCEPTION rva: 00000000 size: 00000000
SECURITY rva: 00000000 size: 00000000
BASERELOC rva: 00000000 size: 00000000
DEBUG rva: 000020E0 size: 0000001C
ARCHITECTURE rva: 00000000 size: 00000000
GLOBALPTR rva: 00000000 size: 00000000
TLS rva: 00000000 size: 00000000
LOAD_CONFIG rva: 00002148 size: 00000040
BOUND_IMPORT rva: 00000000 size: 00000000
IAT rva: 00002000 size: 000000C4
DELAY_IMPORT rva: 00000000 size: 00000000
COM_DESCRPTR rva: 00000000 size: 00000000
unused rva: 00000000 size: 00000000
Debug version
-------------------
0:000> dt NtHeaders -r -b
Local var @ 0x12f610 Type _IMAGE_NT_HEADERS*
0x004000f0
+0x000 Signature : 0x4550
+0x004 FileHeader : _IMAGE_FILE_HEADER
+0x000 Machine : 0x14c
+0x002 NumberOfSections : 6
+0x004 TimeDateStamp : 0x4442f2da
+0x008 PointerToSymbolTable : 0
+0x00c NumberOfSymbols : 0
+0x010 SizeOfOptionalHeader : 0xe0
+0x012 Characteristics : 0x103
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
+0x000 Magic : 0x10b
+0x002 MajorLinkerVersion : 0x8 ''
+0x003 MinorLinkerVersion : 0 ''
+0x004 SizeOfCode : 0x5000
+0x008 SizeOfInitializedData : 0x5000
+0x00c SizeOfUninitializedData : 0
+0x010 AddressOfEntryPoint : 0x1108c
+0x014 BaseOfCode : 0x1000
+0x018 BaseOfData : 0x1000
+0x01c ImageBase : 0x400000
+0x020 SectionAlignment : 0x1000
+0x024 FileAlignment : 0x1000
+0x028 MajorOperatingSystemVersion : 4
+0x02a MinorOperatingSystemVersion : 0
+0x02c MajorImageVersion : 0
+0x02e MinorImageVersion : 0
+0x030 MajorSubsystemVersion : 4
+0x032 MinorSubsystemVersion : 0
+0x034 Win32VersionValue : 0
+0x038 SizeOfImage : 0x1b000
+0x03c SizeOfHeaders : 0x1000
+0x040 CheckSum : 0
+0x044 Subsystem : 3
+0x046 DllCharacteristics : 0
+0x048 SizeOfStackReserve : 0x100000
+0x04c SizeOfStackCommit : 0x1000
+0x050 SizeOfHeapReserve : 0x100000
+0x054 SizeOfHeapCommit : 0x1000
+0x058 LoaderFlags : 0
+0x05c NumberOfRvaAndSizes : 0x10
+0x060 DataDirectory :
[00] _IMAGE_DATA_DIRECTORY
+0x000 VirtualAddress : 0
+0x004 Size : 0
[01]
+0x000 VirtualAddress : 0x19000
+0x004 Size : 0x3c
[02]
+0x000 VirtualAddress : 0x1a000
+0x004 Size : 0x459
[03]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[04]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[05]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[06]
+0x000 VirtualAddress : 0x16620
+0x004 Size : 0x1c
[07]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[08]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[09]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[10]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[11]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[12]
+0x000 VirtualAddress : 0x191cc
+0x004 Size : 0x190
[13]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[14]
+0x000 VirtualAddress : 0
+0x004 Size : 0
[15]
+0x000 VirtualAddress : 0
+0x004 Size : 0
Dump of file DBG.EXE
File Header
Machine: 014C (I386)
Number of Sections: 0006
TimeDateStamp: 4442F2DA -> Mon Apr 17 09:43:54 2006
PointerToSymbolTable: 00000000
NumberOfSymbols: 00000000
SizeOfOptionalHeader: 00E0
Characteristics: 0103
RELOCS_STRIPPED
EXECUTABLE_IMAGE
32BIT_MACHINE
Optional Header
Magic 010B
linker version 8.00
size of code 5000
size of initialized data 5000
size of uninitialized data 0
entrypoint RVA 1108C
base of code 1000
base of data 1000
image base 400000
section align 1000
file align 1000
required OS version 4.00
image version 0.00
subsystem version 4.00
Win32 Version 0
size of image 1B000
size of headers 1000
checksum 0
Subsystem 0003 (Windows character)
DLL flags 0000
stack reserve size 100000
stack commit size 1000
heap reserve size 100000
heap commit size 1000
RVAs & sizes 10
Data Directory
EXPORT rva: 00000000 size: 00000000
IMPORT rva: 00019000 size: 0000003C
RESOURCE rva: 0001A000 size: 00000459
EXCEPTION rva: 00000000 size: 00000000
SECURITY rva: 00000000 size: 00000000
BASERELOC rva: 00000000 size: 00000000
DEBUG rva: 00016620 size: 0000001C
ARCHITECTURE rva: 00000000 size: 00000000
GLOBALPTR rva: 00000000 size: 00000000
TLS rva: 00000000 size: 00000000
LOAD_CONFIG rva: 00000000 size: 00000000
BOUND_IMPORT rva: 00000000 size: 00000000
IAT rva: 000191CC size: 00000190
DELAY_IMPORT rva: 00000000 size: 00000000
COM_DESCRPTR rva: 00000000 size: 00000000
unused rva: 00000000 size: 00000000
However, I am not sure if it is related to DEP, and why we need to check LOAD_CONFIG. Also, if we compile the code in VC6, we may get different results. Any hint?
======
Move xwlan's comment here:
if load config is available, os assume the exception handlers can be looked up in the table, if load config is not available, the os is not sure about whether the handler is valid or not, so it will simply invoke the handler.
my guess is:
it actually relate with safe exception handlers. compiler/linker generated handlers, new MS C compiler will add security cookie to ensure the stack is ok when invoke exception handler, the corresponding RVA is added to the look up table. so manually embedded assembler seh handler won't appear in the table,
os will consider it's an invalid handler.
a workaround is to sweep the load config directory in PE image, dynamically programming...
a trick to fool the OS...
modify image file as your own exe name.
// =========================================================================
HMODULE h = GetModuleHandle(L"exchain.exe");
PUCHAR Base = (PUCHAR)h;
ULONG OldProtect;
VirtualProtect(Base, 0x1000, PAGE_READWRITE, &OldProtect);
IMAGE_NT_HEADERS *NtHeader = ImageNtHeader(Base);
NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress = 0;
NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size = 0;
VirtualProtect(Base, 0x1000, PAGE_READONLY, &OldProtect);
//
===========30 hours later==================
Thanks Mike Marcelais for the following explanation:
Basically, you're running afowl of /SafeSEH. /SafeSEH is incompatable with incremental linking, so it is probably disabled in debug builds.
What /SafeSEH is designed to do is prevent a buffer overrun (or other exploitable code) from overwriting the exception handler on the stack and point it at the hacker's code. The way they do this is at compile time, a list of all valid exception handlers is built up, and at runtime, before a handler is called, it is checked to see if it is on the list. [The loadconfig structure you find gives that list, and a bit in the header says whether the binary was built with /SafeSEH or not.]
If your ASM code was in an .asm file, then you'd use the .safeseh directive to tell the assembler (and hence, the linker), that this function is an exception handler function. I don't know any mechansim in inline asm that will achieve the same effect.
If you compile with warnings enabled, you get warning C4733 telling you that you're going to explode at runtime.