SEH (Structured Exception Handling) Security Changes in XPSP2 and 2003 SP1

【AI智能体】Langchain 上下文记忆使用实战操作详解 阅读详情

from eeye

If you're reading this, hopefully you already know why abusing Structured Exception Handling, or SEH, is extremely interesting for Windows exploit writers. As a quick summary, SEH can provide a useful and portable way to precisely locate stack-based shellcode, and can also be used to evade Microsoft's stack protection by having the OS dispatch control to a bogus exception handler, bypassing the stack cookie check. In fact, causing an exception before the function returns is probably the only way to avoid the stack cookie check, which makes the security of SEH an area of great importance.

So, this whole thing began when I started cataloguing all of the new exploitation-related security features that Microsoft have added to their recent operating systems. All those high level details, covering much more than just SEH, can be found in an eEye whitepaper(1).

After describing those new protection methods at a conference recently, a Microsoft employee told me privately that the new Structured Exception Handling (SEH) protection was not nearly as weak as I had described. That interested me, since my primary research for that part of the presentation was a paper by David Litchfield(2), written in 2003, nothing having been published since. It appeared that some changes had been made in XPSP2 and 2003 SP1 which had not been made public. Obviously, it was time to go digging!

The best overall reference work on Structured Exception Handling was written by Matt Pietrek, back in 1997(3). If you're not already familiar with low-level SEH operation, go read that paper now and come back, it will really help.

After an exception, the kernel returns control to userland at KiUserExceptionDispatcher, and the basic call structure from there is like this (this is from Pietrek's article):

   KiUserExceptionDispatcher()
      RtlDispatchException()
         RtlpExecuteHandlerForException()
            ExecuteHandler()

Assuming that Litchfield wrote his paper when Windows 2003 had been recently released, it seemed logical to compare that code with the code I was interested in, in Windows XPSP2.

KiUserExceptionDispatcher really just hands things off to RtlDispatchException, so there is nothing to see there. RtlDispatchException is where things start to get interesting. The first thing to note is that there is some code that wasn't there in Windows 2000 which prevents the OS from dispatching to an exception handler on the stack. That code is there in Windows 2003 SP0, and appeared in Windows XP sometime before Service Pack 2. The next thing to note as a difference from Windows 2000 is a whole new function – RtlIsValidHandler – that is called before RtlpExecuteHandlerForException. That function is present on 2003 SP0 and XPSP2, and it seems likely that a lot of the new protection is implemented in there.

A First Glance

Without even starting to reverse anything yet, let's take a quick look at the function graphs of RtlIsValidHandler from 2003 SP0 and XPSP2.



OK, it certainly looks like they changed a few things! For now, then, I'm going to assume that Litchfield's analysis of 2003 was correct at the time, and focus on the new code in XPSP2. By the way, in case you're wondering, RtlIsValidHandler() is identical in XPSP2 and 2003SP1.

After a few minutes of inspection, the pseudocode for RtlIsValidHandler on 2003 SP0 looks like this:

if (&handler is in a module with an SEH registration structure) {
if (&handler is registered) {
return TRUE;
else
return FALSE;
}
}
// otherwise...
unless (RtlLookupFunctionTable returned -1) {
//--- TODO, add some security here! For now just allow it
return TRUE;
}
return FALSE;


By the way, when RtlIsValidHandler returns FALSE that will cause RtlDispatchException to return DISPOSITION_DISMISS, which should in turn raise STATUS_NONCONTINUABLE_EXCEPTION, and bail out to the UEF (after doing whatever else it does).

Digging Deeper

Now let's take a closer look at the new code.

Things start off the same, with an attempt to retrieve the SEH information for the handler address. If there is an SEH registration structure, things proceed more or less the same as before. We reach the following assembly, which is reproduced below, as a small reverse engineering puzzle. What is going on here?

Register Start States:
EDI - SEHandlerCount
EDX - 0
EAX - Pointer to SEHandlerTable

.text:7C9378F3 CheckRegisteredHandlers:
.text:7C9378F3 lea ecx, [edi+edx]
.text:7C9378F6 sar ecx, 1
.text:7C9378F8 mov ebx, [eax+ecx*4]
.text:7C9378FB cmp esi, ebx
.text:7C9378FD jb loc_7C92AA34
.text:7C937903 ja loc_7C92E1D8

.text:7C937909 HandlerValid:
.text:7C937909
.text:7C937909 mov al, 1 ; return true
[...]
.text:7C937916 leave
.text:7C937917 retn 4

.text:7C92AA34 loc_7C92AA34:
.text:7C92AA34 lea edi, [ecx-1]
.text:7C92AA37
.text:7C92AA37 loc_7C92AA37:
.text:7C92AA37 cmp edi, edx
.text:7C92AA39 jl HandlerInvalid
.text:7C92AA3F jmp CheckRegisteredHandlers

.text:7C92E1D8 loc_7C92E1D8:
.text:7C92E1D8 lea edx, [ecx+1]
.text:7C92E1DB jmp loc_7C92AA37


The key to understanding this snippet is recognizing the "sar ecx, 1" instruction (Shift Arithmetic Right) as the division of a variable by two, which is then used as an index into the handler table "mov ebx, [eax+ecx*4]". This snippet is a binary search, and it's a much faster way to search an ordered table of values than simply iterating through them. Here's what that section looks like when converted into C:

i = 0;
j = SEHCount;

do {
k = (i + j) / 2;

if (HandlerAddress < SEHTable[k]) {
j = k - 1;
}
else if (HandlerAddress > SEHTable[k]) {
i = k + 1;
}
else { // found handler within SEH table
return TRUE;
}
}
while (i <= j);
//--- If not found...
RtlInvalidHandlerDetected(HandlerAddress, SEHTable, SEHCount);


Eventually we will be able to convert the whole of RtlIsValidHandler to C, to make it easier to follow.

So, that covers the cases where the handler is in a loaded module with an SEH registration structure – no more jumping to known DLL offsets to find a pop,pop,ret. At least not in DLLs that have registered exception handlers...

Now let's look at what happens when that's not the case. The first thing we see is a call to NtQueryVirtualMemory followed by a couple of interesting tests. Documented function calls are a big help when reverse engineering. Because we know the calling convention of NtQueryVirtualMemory, we can start to rename the internal function variables, which makes things much easier to understand.

When calling NtQueryVirtualMemory, one of the parameters is the address of a structure which will be filled out with the results of the call. In the example below, the code specifies that the results should be a MEMORY_BASIC_INFORMATION structure. This is a 28 byte structure, which is based at EBP-0x34, and so it finishes at EBP-0x18. Since we know the structure definition, we can go ahead and name the structure members in that range to match. Here is what it looks like when all that is done. Unfortunately, it's not always this easy.

.text:7C937B80           lea	eax, [ebp+ResultLength]
.text:7C937B83 push eax
.text:7C937B84 push 1Ch
; sizeof(MBI), 1C == 28 bytes
.text:7C937B86 lea eax, [ebp+MBI]
.text:7C937B89 push eax
; &struct (to hold the result)
.text:7C937B8A push ebx
; EBX = 0 - MEMORY_BASIC_INFORMATION
.text:7C937B8B push esi
; Handler Address
.text:7C937B8C push 0FFFFFFFFh
; -1 - CurrentProcessId
.text:7C937B8E call _NtQueryVirtualMemory@24
.text:7C937B93 test eax, eax
.text:7C937B95 jl HandlerValid
.text:7C937B9B test [ebp+MBI.Protect], 0F0h
.text:7C937B9F jz loc_7C94EA34
.text:7C937BA5 cmp [ebp+MBI.Type], 1000000h
.text:7C937BAC jnz HandlerValid


The first thing that jumps out is this:

.text:7C937B9B           test	[ebp+MBI.Protect], 0F0h 
.text:7C937B9F jz loc_7C94EA34


The test instruction performs a bitwise AND and sets the status flags accordingly. After looking up the Memory Protection Constants, we can see that 0F0 is a mask which will cause this test to be non-zero anytime the page is PAGE_EXECUTE. In other words, if the page containing our handler is NOT executable, we jump somewhere. Interesting, we'll check that out in a minute.

Next, we see this:

.text:7C937BA5           cmp	[ebp+MBI.Type], 1000000h 
.text:7C937BAC jnz HandlerValid


That type is defined as MEM_IMAGE – the page regions which contain loaded modules and the executable image itself will all be of this type. So, if the page containing our handler is executable but NOT within a loaded module then it's valid. This is presumably to cater for runtime code generation or some similar thing.

So, this is big news! None of these checks were present in Windows 2003 SP0. One of the attacks against stack protection in Windows 2003 SP0, described by David Litchfield in his paper, involved locating suitable trampoline code inside a memory-mapped file, or perhaps causing suitable code to fill the heap and dispatching to a heap location. In those circumstances, though, the page would not normally be executable, and so the check at loc_7C94EA34 will come into play.

Let's take a look at it.

.text:7C94EA34 loc_7C94EA34:             
.text:7C94EA34 push ebx
.text:7C94EA35 push 4
.text:7C94EA37 lea eax, [ebp+mystery]
.text:7C94EA3A push eax
.text:7C94EA3B push 22h
.text:7C94EA3D push 0FFFFFFFFh
.text:7C94EA3F mov [ebp+mystery], ebx
.text:7C94EA42 call _ZwQueryInformationProcess@20
.text:7C94EA47 test eax, eax
.text:7C94EA49 jl short loc_7C94EA55
.text:7C94EA4B test byte ptr [ebp+mystery], 10h
.text:7C94EA4F jnz HandlerValid
.text:7C94EA55 push 0FFFFFFFEh
.text:7C94EA57 push 0FFFFFFFEh
.text:7C94EA59 push esi
.text:7C94EA5A call _RtlInvalidHandlerDetected@12


The calling convention for ZwQueryInformationProcess (which is just a wrapper for NtQueryInformationProcess) is well known, but we still aren't able to identify our mystery variable. Why not? The second parameter is a PROCESS_INFORMATION_CLASS, which tells NtQueryInformationProcess what kind of information to return. Unfortunately, class 0x22 is not documented anywhere, so we really have no idea what this byte test against 0x10 is trying to determine. However, we can see that if that bit is set then the handler is considered valid, even though the page is not executable. Otherwise, we call RtlInvalidHandlerDetected(HandlerAddress, -2, -2).

First, a quick inspection of RtlInvalidHandlerDetected is in order, since we saw it called before with the SEH registration table and the registered SEH count as parameters instead of -2, -2. The results are a little unusual, and can be flippantly represented by the following pseudocode:

if(-2 == SEHTable && -2 == SEHCount) {
CallKernel32UnhandledExceptionFilter([...],c0000005,[...]);
// Shut down immediately, STATUS_ACCESS_VIOLATION
else
do_nothing()
// Fix later, free donuts in the kitchen...
}
return;


OK, so if this mystery check fails then the rest of the SEH machinery will be bypassed and the UEF will be called immediately. It's time to jump into the kernel and work out what this mystery PROCESS_INFORMATION_CLASS is.

Into Ring 0

On opening up NTOSKRNL.EXE in IDA, something horrible happened to me – the symbols didn't work. That's bad, since we don't get names or call-hints for non-exported functions. For now, we won't worry about it. NtQueryInformationProcess is easy enough, even without symbols. All the major functionality is based around a large switch statement, and tracking down case 0x22 is quickly done. Once there, there is a call to a subroutine that IDA calls sub_54D0A6, which is fairly simple:

PAGE:0054D0AB           mov	eax, large fs:124h
PAGE:0054D0B1 mov eax, [eax+44h]
PAGE:0054D0B4 mov cl, [eax+6Bh]
PAGE:0054D0B7 mov eax, [ebp+arg_0]
PAGE:0054D0BA and dword ptr [eax], 0
PAGE:0054D0BD xor edx, edx
PAGE:0054D0BF inc edx
PAGE:0054D0C0 test cl, dl ; case 1
PAGE:0054D0C2 jz short loc_54D0C6
PAGE:0054D0C4 mov [eax], edx
PAGE:0054D0C6
PAGE:0054D0C6 loc_54D0C6:
PAGE:0054D0C6 test cl, 2 ; case 2
PAGE:0054D0C9 jz short loc_54D0CE
PAGE:0054D0CB or dword ptr [eax], 2
[...] [...]
PAGE:0054D0DE test cl, 10h ; case 0x10
PAGE:0054D0E1 jz short loc_54D0E6
PAGE:0054D0E3 or dword ptr [eax], 10h
[...] [...]
PAGE:0054D0EE xor eax, eax
PAGE:0054D0F0 pop ebp
PAGE:0054D0F1 retn 4


So, we can see that the single parameter is a pointer to a bitfield, which is being modified according to the flags located in some kernel structure that has been loaded in CL (the bottom byte of ECX). Google quickly comes to the rescue. fs:124h is a shortcut to the kernel ETHREAD structure for the current thread. Probably the best way to get instant documentation on this kind of structure is to use the WinDbg kernel debugger. In this case it tells us this (output trimmed):

kd> .load kdex2x86 (for some kernel debugging extensions)
kd> !ethread 81893020
struct _ETHREAD (sizeof=584)
+000 struct _KTHREAD Tcb
+000 struct _DISPATCHER_HEADER Header
+000 byte Type = 03
+001 byte Absolute = 00
+002 byte Size = 1b
+003 byte Inserted = 00
+004 int32 SignalState = 00000000
+008 struct _LIST_ENTRY WaitListHead
+008 struct _LIST_ENTRY *Flink = 81893028
+00c struct _LIST_ENTRY *Blink = 81893028
[...]
+044 struct _KPROCESS *Process = 81893060
+048 byte KernelApcInProgress = 00
+049 byte KernelApcPending = 00
+04a byte UserApcPending = 00
[...]


So, offset 44h is a pointer to the KPROCESS structure for the current process. Next, offset 0x6b of that structure is moved into the bottom byte of ECX, so let’s see what WinDbg tells us about _KPROCESS (output trimmed).

kd> dt nt!_KPROCESS 81893060 -r
+0x000 Header : _DISPATCHER_HEADER
+0x000 Type : 0x3 ''
+0x001 Absolute : 0 ''
+0x002 Size : 0x1b ''
+0x003 Inserted : 0 ''
+0x004 SignalState : 0
[...]
+0x018 DirectoryTableBase : [2] 0xf8be000
+0x020 LdtDescriptor : _KGDTENTRY
+0x000 LimitLow : 0
+0x002 BaseLow : 0
+0x004 HighWord : __unnamed
+0x000 Bytes : __unnamed
+0x000 Bits : __unnamed
[...]
+0x05c Affinity : 1
+0x060 StackCount : 1
+0x062 BasePriority : 8 ''
+0x063 ThreadQuantum : 18 ''
[...]
+0x06b Flags : _KEXECUTE_OPTIONS
+0x000 ExecuteDisable : 0y0
+0x000 ExecuteEnable : 0y0
+0x000 DisableThunkEmulation : 0y0
+0x000 Permanent : 0y0
+0x000 ExecuteDispatchEnable : 0y0
+0x000 ImageDispatchEnable : 0y0
+0x000 Spare : 0y00
+0x06b ExecuteOptions : 0 ''


There we go! Flag 0x10 is ExecuteDispatchEnable. The XPSP2 system this was taken from uses the default DEP setting, which is OptIn (look in boot.ini for /NoExecute=OptIn). Just as a test, I changed it to AlwaysOff, and got the following:

+0x06b Flags            	: _KEXECUTE_OPTIONS
+0x000 ExecuteDisable : 0y0
+0x000 ExecuteEnable : 0y1
+0x000 DisableThunkEmulation : 0y0
+0x000 Permanent : 0y1
+0x000 ExecuteDispatchEnable : 0y1
+0x000 ImageDispatchEnable : 0y1
+0x000 Spare : 0y00
+0x06b ExecuteOptions : 0x3a ':'


Just to be doubly sure, this is a handy trick. By turning on "function offset addressing" in IDA, we can disassemble NtQueryVirtualMemory in WinDbg (using u <address> or uf <function>) and match up the offsets – we find that sub_54D0A6 is really called _MmGetExecutionOptions.

So, now things are becoming clearer. If DEP is enabled, RtlIsValidHandler will check the execution permissions on the page, and raise a STATUS_ACCESS_VIOLATION if an attempt is made to dispatch to a handler in NX memory – whether the CPU supports NX at a hardware level or not. That's an important change!

A Few Loose Ends

Looking back at the Windows 2003 pseudocode, we notice that the special check for a return value of -1 from RtlLookupFunctionTable is still present in the SP2 code. All that is left now is to find out what would cause this -1 return value, and we'll have the complete picture. The answer to this question lies not within RtlLookupFunctionTable itself, but in RtlCaptureImageExceptionValues. RtlCaptureImageExceptionValues is designed to return a pointer to the SEH Registration Table, if one exists, as well as the number of registered handlers. It is called by RtlLookupFunction table, which first has to find the base of the page region containing the handler.

Although we completely reversed RtlCaptureImageExceptionValues (looking for anomalies), here is a much faster way. Locate and name the code location that sets the -1 return values, then look for possible code paths leading to that location using the IDA function graph (F12). It turns out that there are only two ways to get there. This is the first, right at the start of the function:

.text:7C9379CE           push	 [ebp+ImageBase]
.text:7C9379D1 call _RtlImageNtHeader@4
.text:7C9379D6 test byte ptr [eax+5Fh], 4
.text:7C9379DA jnz returnminusone


So it looks like that's a check against the DllCharacteristics field in the IMAGE_OPTIONAL_HEADER:

0:000> dt _IMAGE_NT_HEADERS -r
+0x000 Signature : Uint4B
+0x004 FileHeader : _IMAGE_FILE_HEADER
[...]
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
[...]
+0x046 DllCharacteristics : Uint2B
+0x048 SizeOfStackReserve : Uint4B
+0x04c SizeOfStackCommit : Uint4B


So, if DllCharacteristics has 0x04 set, we return -1 immediately. Sadly, there seems to be no modern documentation on that field anywhere, so it will have to remain a minor mystery. One possible meaning for this check is that it lets you mark DLLs as "no handlers here, go away", but that's just a guess.

The other way to cause a return of -1 is this:

.text:7C937BD0 NoSEHTable:  ;There is no SEH Registration table
.text:7C937BD0
.text:7C937BD0 lea eax, [ebp+ImageBase]
.text:7C937BD3 push eax
.text:7C937BD4 push 0Eh ; COM_DESCRIPTOR
.text:7C937BD6 push 1
.text:7C937BD8 push [ebp+ImageBase]
.text:7C937BDB call _RtlImageDirectoryEntryToData@16
.text:7C937BE0 test eax, eax
.text:7C937BE2 jnz ManagedCodeCheck

.text:7C937BE8 returnzero:
.text:7C937BE8 mov eax, [ebp+HandlerTable]
.text:7C937BEB and dword ptr [eax], 0
.text:7C937BEE mov eax, [ebp+HandlerCount]
.text:7C937BF1 and dword ptr [eax], 0
.text:7C937BF4 jmp locret_7C937A37

.text:7C94F6C0 ManagedCodeCheck:
.text:7C94F6C0 test byte ptr [eax+10h], 1 ; flags
.text:7C94F6C4 jnz short returnminusone
.text:7C94F6C6 jmp returnzero


RtlImageDirectoryEntryToData returns information from the IMAGE_DIRECTORY entries linked to the PE Header. In this case, we’re looking at a flag that has something to do with the COM_DESCRIPTOR entry (0xe). A little sleuthing turns up the fact that IMAGE_DIRECTORY_COM_DESCRIPTOR uses the header structure COR20_HEADER. COR20 ... Common Object Runtime 2.0? Hey, COM+ 2.0 was the early name for the .NET framework, so it’s very likely that this check is something to do with .NET Managed Code. If the code is Managed then the checking can be much more strict about modules that have missing SEH Registration Tables. Probably this is why it has a special return value, to let RtlIsValidHandler know that it was more than just a generic failure to find the SEH table – that probably happens all the time with 3rd party applications that use custom .DLLs.

In fact, looking up the flag in corhdr.h and then some more detective work finally turns up the following definition. Bingo.

COMIMAGE_FLAGS_ILONLY (0x00000001) The Image contains IL Code Only, with no embedded native unmanaged code, except the startup stub. Because Common Language Runtime - aware Operating Systems (such as Windows XP) ignore the startup stub, for all practical purposes the file can be considered Pure-IL.[...]



_RtlIsValidHandler(PVOID handler)

So, with all that out of the way, we are finally ready to convert the new RtlIsValidHandler to pseudocode. This is done for clarity, actually based on an exact C conversion that was produced during the course of the investigation.

if (SEHTable != NULL && SEHCount != 0) {
if (SEHTable == -1 && SEHCount == -1) {
// Managed Code but no SEH Registration table
// or IMAGE_LOAD_CONFIG.DllCharacteristics == 4
return FALSE;
}
if (&handler is registered) {
return TRUE;
else
return FALSE;
}
}
// otherwise...
if (&handler is on an NX page) {
if (DEP is turned on) {
bail(STATUS_ACCESS_VIOLATION);
else
return TRUE;
}
}
if (&handler is on a page mapped MEM_IMAGE) {
// normally only true for executable modules
if (SEHTable == NULL && SEHCount == 0) {
return TRUE;
// probably an old or 3rd party DLL
// without SEH registrations
}
return FALSE // we should have caught this before
// so something is wrong.
}
// Handler is on a eXecutable page, but not in module space
// Allow it for compatibility.
return TRUE;


There are one or two little quirks, like this little snippet that happens just before the NX check:

.text:7C937B8E           call	_NtQueryVirtualMemory@24
.text:7C937B93 test eax, eax
.text:7C937B95 jl HandlerValid


Which is going to mark a handler as valid if NtQueryVirtualMemory fails for some reason – a more secure approach would be to mark the handler as invalid, which is “default deny”. Also, note that the default is to allow any handlers in non-module space which are on eXecutable pages – it would probably be more secure to deny this by default, and let applications which need to generate exception handlers at runtime override the default setting during compilation.

What about __except_handler3?

The next attack that springs to mind, if we can't just jump to our own code by using the handler pointer, is to attack the exception handling process itself. For Visual C++ compiled apps (which obviously covers all the Microsoft system components) most of the SEH work is performed by __except_handler3. In fact, the exception handler pointer in every EXCEPTION_REGISTRATION on the stack normally points to __except_handler3, which then sorts things out by using additional information that is specific to the Visual C++ implementation of SEH.

That extra information is stored in an array of scopetable entries, and the current trylevel is used as an index to the array. The scopetable array is, unfortunately, not stored on the stack, so we can't attack it directly. Here is the general structure of the scopetable array entries:

typedef struct _SCOPETABLE
{
DWORD previousTryLevel;
DWORD lpfnFilter
DWORD lpfnHandler
} SCOPETABLE, *PSCOPETABLE;


Again, I strongly recommend that you read this next part after understanding Pietrek's SEH article. During the handling process, __except_handler3 will ask each handler that has an EXCEPTION_REGISTRATION structure on the stack whether or not it is prepared to handle the exception. This is done by calling the handler's filter function which expected to return EXCEPTION_EXECUTE_HANDLER if it agrees to handle the exception or EXCEPTION_CONTINUE_SEARCH if it won't. In pseudocode that call to the filter function looks like this (again, taken from Pietrek's article):

[...]
search_for_handler:

if ( pRegistrationFrame->trylevel != TRYLEVEL_NONE )
{
if ( pRegistrationFrame->scopetable[trylevel].lpfnFilter )
{
// Save this frame EBP
PUSH EBP

// Switch to original EBP
EBP = &pRegistrationFrame->_ebp

// Call the filter function
// Maybe we can own this?
filterFuncRet = scopetable[trylevel].lpfnFilter();

// Restore handler frame EBP
POP EBP
[...]


Looking again at Litchfield's article on 2003 SP0, he mentions abusing an "existing registered handler" at 0x77F45A34. Although it is not mentioned, that handler is none other than __except_handler3 in NTDLL.DLL, and this is the relevant disassembly representing the code above.

[...]
.text:77F45A3F mov ebx,[ebp+arg_4] // &pRegistrationFrame
[...]
.text:77F45A61 mov esi,[ebx+0Ch] // trylevel
.text:77F45A64 mov edi,[ebx+8] // scopetable
[...]
.text:77F45A75 lea ecx,[esi+esi*2]
.text:77F45A78 mov eax,[edi+ecx*4+4]
// scopetable[trylevel].lpfnFilter() -> EAX
[...]
.text:77F45A81 push ebp // save EBP
.text:77F45A82 lea ebp,[ebx+10h] // original frame EBP
[...]
.text:77F45A8F call eax


So, given that we own everything on the stack, including &pRegistrationFrame, we can obviously use this to take control. The general method would be to supply bogus values for the scopetable pointer and trylevel, so that scopetable[trylevel].lpfnFilter() points to our payload, which __except_handler3 will call for us. Another idea would be to preserve the original frame EBP at EBX+16h (we control the stack at that location) and replace the filter function with the location of non-stack-based trampoline code like CALL EBP, PUSH EBP // RET, etc. This approach would have much the same effect, but might be useful for evading certain protection mechanisms.

Unfortunately for attackers, they fixed this area too.

The disassembly above was NTDLL.DLL from Windows 2003 SP0. In 2003 SP1 a new function has been added, called __ValidateEH3RN. This new function is pretty huge, there wasn't the time or the need to completely reverse engineer it. However here are some highlights that jumped out:

  1. A check to ensure that the scopetable array is not on the stack and that it is 4-byte aligned.
  2. A sanity check on the array, made by walking the array from scopetable[0] to scopetable[trylevel]. This would make it very difficult to use the real scopetable array with a bogus trylevel to reference an lpfnFilter address which is out of array bounds.
  3. Nested handlers are also sanity checked in step 2, above, which effectively means that any existing code to be used as a fake scopetable entry would need to have previousTryLevel set to -1, ie 0xFFFFFFFF preceding the payload address.
  4. An NtQueryVirtualMemory check on the scopetable against MEM_IMAGE and READONLY.
  5. A lot of other code, which is probably some kind of check against the lpfnFilter pointer itself.

This puts paid to the obvious attempts to abuse __except_handler3, although a complete analysis may yet turn up an exploitable scenario. For the time being, none of the "simple" attacks seem possible.

The Bottom Line

Overall, the new RtlIsValidHandler is a vast improvement over the old one, and closes the most egregious holes, so it seems very unlikely that control can be directly dispatched to a payload using "raw" exception handling. It may still be possible to find suitable trampoline code in a 3rd party DLL that does not have an SEH registration structure, but this would depend heavily on the individual application being attacked.

Attacking the Visual C++ SEH implementation via __except_handler3 is similarly unlikely, thanks to the new C++ runtime library function, __ValidateEH3RN. The SEH implementations of other compilers may still be vulnerable, however.

There will always be a few corner cases, 3rd party applications that opt-out of DEP and application specific errors, of course. The good news, though, is that in conjunction with the stack cookie implementation this new protection seems to eliminate every currently known, application-generic attack templates for Windows stack overflows - which is good news.

At the time of writing, virtually none of this new functionality had been publicized. Hopefully, bringing the full extent of this new protection to light will encourage organizations to accelerate their shift to Windows XP SP2 or Windows 2003 SP1, even on platforms without hardware NX support.

References

  1. B. Nagy, Generic Anti-Exploitation Technology for Windows, 2005, available online at http://www.eeye.com/html/research/whitepapers/index.html
  2. D. Litchfield, Defeating the Stack Based Buffer Overflow Prevention Mechanism of Microsoft Windows 2003 Server, 2003, http://www.nextgenss.com/papers/defeating-w2k3-stack-protection.pdf
  3. M. Pietrek, A Crash Course on the Depths of Win32™ Structured Exception Handling, in Microsoft Systems Journal, January 1997, online at http://www.microsoft.com/msj/0197/exception/exception.aspx


Acknowledgements
Derek Soeder and his Bit-Fu helped in countless ways, including some of the C conversion. I was spurred on by the efforts of a blogger in China called "nop" who reversed some of the same areas of code, but didn't get right to the bottom of it. Thank you to Robert Ross who gave me a great suggestion as well.

Source: Ben Nagy, Senior Security Engineer, eEye Digital Security


Ask Research

Q: Is silently fixing security issues made available during regular software updates common practice amongst software vendors?

A: Yes. Although recent speaking engagements by members of the eEye Research Team have focused on Microsoft patches, it has been observed that most software vendors will fix security issues during regular updates. The list of corrections published by the vendor will not mention the security fixes, so only by dissecting the actions of the patch can one tell that something was changed. Look for the slides from the eEye Research presentation "Skeletons in Microsoft's Closet: Silently Fixed Vulnerabilities" on the eEye Research Portal (research.eeye.com) in the near future.


Q: We noticed that in your D-Link security advisory that you listed all the patched models of D-Link routers, but D-Link support does not have all the patches available. What gives?

A: When eEye releases an advisory, we are dependent on the vendors informing us of their patches being released to the public. In this case, on the date we released the advisory, all of the listed models did in fact have a patch. If those patches have been removed for whatever reason, not all vendors will notify the author of the advisory. In this case, we suggest that you contact D-Link support for clarification on what patches are available and when patches for certain models will be re-released.

Have a question you would like answered? Send it to vice@eeye.com, and win an eEye t-shirt if we select your question for an upcoming newsletter.
 

matlab 三维绘图 抛光,瓷砖抛光过程建模与仿真 :瓷砖抛 光过程 建模与仿真朱 成 就(广 东工业大学 机 电工程 学院, 广东广州 )摘要 :瓷砖的抛磨影响 因素众 多 ,实际磨削加工时容易出现漏抛 、返抛现象 。对陶瓷抛光机磨头进行运动学分析 ,并基于不 同粒度号 的磨 粒在一定压力下的加工深度模型 ,探讨抛光机磨 头转 速 、横梁摆动频率 、传送速度对瓷砖抛光加工表 面均匀性 的影响,然 后结合 磨削机理使 用 对陶瓷抛光过程进行仿真 ... 阅读详情

相关推荐

WhatsApp网页版扫码分析

扫码登录本质上是WhatsApp的多设备登录,设备之间可以独立运行,那么当我们首次通过WhatsApp扫码登录二维码时,到底发生了那些事情?交换了那些数据?

SocialMarketing的博客 5231

SEH,DEP, Compiler,FS:[0], LOAD_CONFIG and PE format

SEH,DEP, Compiler,FS:[0], LOAD_CONFIG and PE format原贴地址:http://spaces.msn.com/eparg/blog/cns!59BFC22C0E7E1A76!712.entry原贴时间:2006-04-16原贴作者:epargRecently I am planning some user mode troubleshoot

eparg的专栏 2947

【小白教程】本地部署VLM-R1模型(使用Docker)

通过Docker本地部署VLM-R1大模型,详细教程。

m0_69855053的博客 2944

微软技术日报 11-09:.NET 8 与 9 明日同时停止支持,注册表编辑器出了 WinUI 3 版

今天这四条消息的共同点是收口。.NET 8 和 9 的支持期到点,注册表编辑器的新界面只放在仓库里给人看,GitHub 把一类危险触发器按住默认,微软在安全报告里把防线从地址黑名单挪到行为。收口并不等于关门。.NET 10 还在支持期里,被按住的触发器可以用策略按文件放回来,实验室里的样例随时可能变成产品。区别在于这些动作要有人签字,不再是默认开着。如果今天只做一件事,去看一眼构建机上的 .NET 版本。明天之后,8 和 9 的漏洞补丁不会再来了。

海盗Sharp的博客 390

免疫治疗新思路:用COMPASS算法挖掘单细胞数据中的代谢靶点(含差异分析技巧)

本文介绍了如何利用COMPASS算法从单细胞转录组数据中系统推断免疫细胞的代谢活性,为肿瘤免疫治疗提供新的靶点发现思路。文章详细阐述了COMPASS结合流平衡分析的核心原理,并提供了从差异分析到可成药性评估的完整转化路径,旨在帮助研究者精准定位代谢弱点,开发新型免疫代谢疗法。

weixin_29275257的博客 368

opencv 常用函数总结

本文是一份OpenCV图像处理的常用函数总结,详细介绍了图像的读入、显示、保存、像素处理、图像属性获取、基础操作、阈值处理、图像平滑、形态学转换以及直方图处理等关键技术。文章通过代码示例和参数说明,帮助读者快速掌握OpenCV在图像处理领域的应用。

weixin_44063529的博客 1600

AI驱动数据处理的安全边界:行业现状、风险权衡与本地化实践路径

本文基于第三方数据,分析AI用于数据处理的安全议题。大模型应用加速普及,但伴随数据出境、影子AI、幻觉与成本失控等风险。行业共识指向“脚本复用”路径:将分析逻辑本地化固化,实现数据可控、结果可复现、成本可降。产品如数以轻舟提供本地处理、多模型接入与一次付费永久使用方案。建议企业从数据落点、可控性、可复现性与合规性四维度评估选型,推动AI在安全边界内释放价值。

数据矿工-数据化运营博客 329

利用腾讯云轻量服务器快速搭建网站CDN

腾讯云的轻量最近在主机和站长中间可谓是占尽来风头,优秀的线路,低廉的价格使得越来越多的人开始使用腾讯云的轻量级服务器来部署应用。由于腾讯云轻量服务器的海外服务器电信走的是CN2GIA线路带宽30m挺适合做CDN的,我就记录一下如何利用腾讯云香港的轻量服务器搭建一个CDN。 购买服务器 首先选购服务器,最近腾讯云在做活动,力度很大国内备案用户可以选用国内的服务器最低128元/年,未备案的可以选用香港或者新加坡的服务器。 购买链接 服务器系统选Debian、Ubuntu、centos都可以 一键脚

demo_top的博客 2566

微软技术日报 11-08:Foundry 与 Entra 各挂一个 10.0 分漏洞,Foundry 接 fal 五款视频模型

这一周的动作可以分成两类。一类在收。Copilot 从电视上退场,搜索里的 AI 改成可选,云端漏洞只发通知不收补丁。共同点是承认铺得太开,代价比预期高。另一类在放。Foundry 一次摆上五个视频模型,容器版应用网关一个入口挂 64 个 MCP 服务器。共同点是把能力做成可组合的积木,让使用者按需挑。两个方向背后是同一个判断。把能力塞到每一个界面上,短期看覆盖率高,长期要解释为什么删不掉。新的做法是先给一个能关的开关,再谈覆盖。对每天写代码的人来说,今天有两样现成的东西可以试。

海盗Sharp的博客 419

保姆级教程:用MS建完分子模型,如何一键转成LAMMPS能用的data文件?

本文详细介绍了如何将Materials Studio(MS)构建的分子模型转换为LAMMPS可用的data文件的全流程。重点讲解了力场选择、环境配置、转换命令参数解析及常见问题排查,帮助初学者快速掌握MS与LAMMPS之间的数据转换技巧,特别适合计算材料学领域的研究人员。

weixin_42665255的博客 472

新手必看:C#调用YOLO模型完成图片/视频目标检测(上位机版)

新手学习C#调用YOLO模型,核心是先把基础流程跑通,再逐步优化。图片检测是基础,视频检测就是在图片的基础上加循环和线程,而上位机的实际项目,就是在这个基础上适配工业硬件和业务需求。本文的代码都是新手能直接复制运行的,如果你运行时遇到问题,先检查前期准备是否到位,再看避坑指南。记住,新手别追求一步到位,先实现功能,再慢慢优化性能和界面。等你能熟练调用YOLO模型后,再去研究模型训练、量化优化等进阶内容,会轻松很多。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 988

微软技术周报 11-02~11-09:VS Code 混装 Claude 与 Codex、EF Core 11 静默改行为、.NET 11 倒计时

11 月 6 日,Power Platform 通过消息中心通知管理员,Copilot Studio、Power Automate 和 Power Apps 里所有跑在 Anthropic Claude、xAI Grok 以及旧版 GPT 上的 prompt,12 月 2 日自动迁移到 GPT-6 系列,档位价格不变,模型选择收窄到一条产品线。效果是这样的:让 Claude 评审一个 Copilot chat 写出的方案,或者把同样的任务交给 Codex,两边并排跑在同一个会话里。更要紧的是下周的时间线。

海盗Sharp的博客 992

rk3568 点亮双LVDS 屏

承接文章《rk3568点亮LCD(lvds)》中仅仅只能使用亮单LVDS;经过厂商的努力,陆续发布新补丁。目前支持双lvds显示,缺憾是需要自己额外移植相关补丁。受限于通信频率和VOP编码能力,支持最大分辨仍然是1920x1080、60fps。

weixin_35723192的博客 3008

MAI-Cyber-1 深度解析:微软首个网络安全专用模型

微软没有去做"更大的安全大模型",而是用一颗 137B 总参、仅 5B 激活的稀疏 MoE 专用模型,配上 90/10 的"小模型铺量、大模型兜底"路由,在 CyberGym 基准上拿到 95.95% 的同时把成本砍掉一半——这标志着 AI 安全从"通用模型加提示词"进入了"专用模型加系统工程"的时代。

Simple_Pure的博客 327

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

微软输入法标点乱跳解决方案

微软拼音输入法常因“中文输入时使用英文标点”开关被意外开启,导致标点自动切换为英文且快捷键失效。本文提供一键批处理脚本,通过修改注册表(HKCU\Software\Microsoft\InputMethod\Settings\CHS)强制锁定中文标点,覆盖多个相关键值,确保设置持久生效。相比手动操作,该方案可抵御更新、同步等导致的配置重置,提升稳定性。执行后需重启输入法或系统以使更改生效,建议提前备份注册表。

谙弆悕博士的技术博客! 370

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

微软2026年11月补丁星期二解析:1094个 CVE、Outlook 零日 CVE-2026-124194 与修复优先级

微软11月补丁星期二修复1094个 CVE,唯一在野利用的 Outlook 零日 CVE-2026-124194 只评为“中等”,修复随 Office 更新下发、不在 Windows 累积更新里。本文整理漏洞分布、严重级漏洞分类处置与三档修复优先级。

SplashtopLoki的博客 217

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

微软 BioEmu2 实测:AI 设计的抗体,能否通过动态模拟提前筛选?

微软 BioEmu2 实测:AI 设计的抗体,能否通过动态模拟提前筛选?**2026 年 10 月 26 日,展示了一个 AI 抗体候选的 10 纳秒传播动画。**但这个案例留下的最大问题,仍是实验室门前那道选择题:一批看起来合理的设计,究竟该把哪几个送去测试?Ford 正在参与 Adaptyv 与 Anthropic 的蛋白设计竞赛,也在开发抗体语言模型。**但他在原文中坦言,生成候选容易,挑出值得测试的候选依然困难。**本文解读他的公开实测,并对照微软文档核查证据边界,未独立运行模型。

weixin_71777199的博客 217

【第 008 讲】在终端中运行 Python 程序:交互模式与脚本模式 | 绝对路径与相对路径 | 以模块方式运行 | 常见报错的识别与排查

本文系统讲解在终端中运行 Python 程序的方式:从交互模式与脚本模式讲起,梳理绝对路径与相对路径的运行写法及 cd、dir 等命令的用法;进而讲解以模块方式运行的特点与字节码缓存的产生;最后通过路径、拼写、标点三类典型报错,帮助读者掌握读懂报错、定位并修复错误的排查思路。

大模型全栈工程师 · Thanks_ks 797

企业微信 × AI 对接第六步:模板卡片交互闭环——按钮回调、ResponseCode 状态机、卡片更新(附跑通代码)

AI 生成的审批建议,让主管在卡片上直接点"同意/驳回",比跳系统快一个数量级——这是企业微信模板卡片消息的交互闭环:发送时带 task_id 和按钮 key,用户点击后企微回调 EventKey/TaskId/ResponseCode,你拿 ResponseCode(72 小时有效、只能用一次)调 update_template_card 把卡片原地改成"已处理"。这篇把五种卡片类型、回调字段、更新接口一次讲透,附一个带完整防御(重复核销/超时/未知 key 拒绝)的状态机代码,本地实测跑通四种场景。

weixin_45264536的博客 373

情感交互机器人融资,四川具身科技的「爱湫」登场

当前人形机器人的主流战场是工业、仓储、巡检这些「干活」的场景,而「爱湫」把重心放在了「陪伴」上——通过面部投影和情绪识别,让机器人和人之间多一层情感层面的交互。情感交互机器人的市场,其实一直存在,只是过去被「能干活」的主流叙事盖住了。它主推的产品「爱湫」,是一款主打情感交互的人形机器人,搭载 3D 投影面部技术,情感交互机器人的逻辑,是面向养老陪伴、文旅服务、展厅导览这类「不要求重体力、但要求互动感」的场景。真正的考验不在发布会,而在它能不能在真实的养老和陪伴场景里,被用户用起来、留下来。

jushenAGI的博客 294

微软 365 全球掉线复盘,Purview 数据治理退役,C++ 动态调试转正

把今天这几条放在一起,会看到一个共同的题目,系统的自动判断出错时,人还能不能兜住。故障那条是自动判断误判。健康检查本来的职责是把坏机器摘出去,这次它把还能用的机器也摘了,剩下的机器承接双倍压力,再被摘掉,容量一路缩减。微软的补救方向很说明问题,一边验证一套替代的健康检查方式,一边准备自动检测并重启无响应服务器的机制,等于在原来的判断链路上加第二道保险。脚本那条是校验从宽容变严格。旧文件在宽松的校验下一直能用,规则一收紧,一批历史遗留的编码问题同时浮出水面,而表象是「签名没了」。

海盗Sharp的博客 301

微软技术日报 2026-11-07:Copilot 本机跑 27B 模型、Front Door 默认后量子 TLS、Beta 转 26H2

四条消息挤在同一天,方向各不相同:Foundry Local 把 27B 的编码模型压进了 24GB 显存并接进 GitHub Copilot;Azure Front Door 把入站 TLS 1.3 的混合后量子密钥交换设成了默认;Windows Insider 一次放出五个构建,Beta 通道正式转到 26H2;Power Platform 通知管理员,12 月 2 日提示模型换血,Claude 与 Grok 一起退出。四条放在一起看,有两条在改默认值,两条在改节奏。

海盗Sharp的博客 515

微软Copilot生态深度解析:从办公助手到超级应用的整合趋势与实战技巧

在人工智能技术飞速发展的今天,微软Copilot已成为连接用户与AI生产力的核心桥梁。虽然“copilot-analysis”这一特定仓库可能指向具体的分析脚本或文档,但从更宏观的视角来看,它代表了当前对微软Copilot生态系统的深度探索。Copilot不仅仅是一个聊天机器人,它已经深度嵌入到Excel、Word、GitHub等核心生产力工具中,甚至正在向“超级应用”演进。

wulechun的博客 167

全球化筑基,AI 赋能新体验,openKylin 3.0 SP1 正式发布

本次版本更新以国际化为底座,围绕国际化体验、AI 融合、硬件生态、系统质量四大方向完成全面升级:新增阿拉伯语、朝鲜语、傣语等多语言支持;持续强化重点平台与关键外设适配能力,完善飞腾 D3000M、进迭时空 V100 等平台图形显示、网卡驱动、高性能计算等能力,进一步拓展 openKylin 在多架构、多设备场景下的兼容边界。围绕系统稳定性、硬件兼容性与用户体验持续迭代,累计完成 200 余项缺陷修复与细节优化,覆盖桌面交互、图形显示、设备管理等核心场景,推动系统更加稳定、流畅、易用。

openKylin的博客 200
上一篇: KernelMode Callback UserMode
下一篇: [Please don't DEP me, Sir] 0x00 The First Peer Inside DEP (Data Execution Prevention)
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值