起因是优化代码性能,注意到这个函数,搜了一下发现是微软弃用的函数,说是有线程安全问题。经过一系列操作发现,处理大文件时这个函数会导致耗时变长,于是就研究一下这个函数。
首先看函数开头:
mov edi, edi
push ebp
mov ebp, esp
push 0FFFFFFFEh
push offset stru_77991A20
push offset FindResourceExA_SEH
mov eax, large fs:0
push eax
sub esp, 10h
push ebx
push esi
push edi
mov eax, ___security_cookie
xor [ebp+ms_exc.registration.ScopeTable], eax
xor eax, ebp
push eax
lea eax, [ebp+ms_exc.registration]
mov large fs:0, eax
mov [ebp+ms_exc.old_esp], esp
mov esi, dword_779B0708
mov ecx, [ebp+ucb]
test ecx, ecx
先看push offset stru_77991A20
这条指令,在IsBadReadPtr
函数入口处被压入栈中。stru_77991A20
为SEH的scope table
结构,它保存了当前函数中__try
块相匹配的__except
或__finally
的地址值。
stru_77991A20
被保存在.rdata
:
.rdata:77991A20 stru_77991A20 dd 0FFFFFFFEh ; GSCookieOffset
.rdata:77991A20 ; DATA XREF: IsBadReadPtr+7↑o
.rdata:77991A20 dd 0 ; GSCookieXOROffset ; SEH scope table for function 6B810100
.rdata:77991A20 dd 0FFFFFFD0h ; EHCookieOffset
.rdata:77991A20 dd 0 ; EHCookieXOROffset
.rdata:77991A20 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel
.rdata:77991A20 dd offset loc_77925269 ; ScopeRecord.FilterFunc
.rdata:77991A20 dd offset loc_7792526F ; ScopeRecord.HandlerFunc
C结构如下
struct _EH4_SCOPETABLE {
DWORD GSCookieOffset;
DWORD GSCookieXOROffset;
DWORD EHCookieOffset;
DWORD EHCookieXOROffset;
_EH4_SCOPETABLE_RECORD ScopeRecord[1];
};
struct _EH4_SCOPETABLE_RECORD {
DWORD EnclosingLevel;
long (*FilterFunc)();
union {
void (*HandlerAddress)();
void (*FinallyFunc)();
};
};
其中FilterFunc
与FinallyFunc
就是我们自定义的__except
或__finally
函数的地址。
接着把异常处理添加到当前线程的栈中:
push offset FindResourceExA_SEH
mov eax, large fs:0
push eax
...
lea eax, [ebp+ms_exc.registration]
mov large fs:0, eax
关于结构化异常处理的补充:
1.TIB
结构,又称线程信息块,是保存线程基本信息的数据结构,它位于TEB
的头部。TEB
是操作系统为了保存每个线程的私有数据创建的,每个线程都有自己的TEB
。
TIB
结构如下:
typedef struct _NT_TIB{
struct _EXCEPTION_REGISTRATION_RECORD *Exceptionlist;//指向异常处理链表
PVOID StackBase;//当前进程所使用的栈的栈底
PVOID StackLimit;//当前进程所使用的栈的栈顶
PVOID SubSystemTib;
union {
PVOID FiberData;
ULONG Version;
};
PVOID ArbitraryUserPointer;
struct _NT_TIB *Self;//指向TIB结构自身
} NT_TIB;
在这个结构中与异常处理有关的第一个成员:指向_EXCEPTION_REGISTRATION_RECORD
结构的Exceptionlist
指针
2.EXCEPTION_REGISTRATION_RECORD
结构
该结构主要用于描述线程异常处理过程的地址,多个该结构的链表描述了多个线程异常处理过程的嵌套层次关系
结构如下:
typedef struct _EXCEPTION_REGISTRATION_RECORD{
struct _EXCEPTION_REGISTRATION_RECORD *Next;//指向下一个结构的指针
PEXCEPTION_ROUTINE Handler;//当前异常处理回调函数的地址
}EXCEPTION_REGISTRATION_RECORD;
fs寄存器指向TEB结构,所以上面lea eax,[ebp+ms_exc.registration]
和mov large fs:0, eax
指令也就是在栈中插入一个SEH异常处理结构体到TIB顶部, __except_handler4就是添加的系统默认异常处理回调函数,当发生异常时会首先执行它。
跟进stru_77991A20.ScopeRecord.FilterFunc()
函数地址loc_77925269
,可以看到:
; START OF FUNCTION CHUNK FOR IsBadReadPtr
loc_77925269:
mov eax, 1
retn
按照意思还原成C代码:
long WINAPI FilterFunc(DWORD dwExceptionCode)
{
return (dwExceptionCode == STATUS_ACCESS_VIOLATION)
? EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_SEARCH;
}
之后根据IDA伪代码看检查内存可读的原理:
BOOL __stdcall IsBadReadPtr(const void *lp, UINT_PTR ucb)
{
char begin_ptr; // t1
char *begin_ptr_alignment; // edx
int v4; // eax
char v6; // t1
unsigned int end_ptr; // [esp+14h] [ebp-1Ch]
int end_ptr_alignment; // [esp+14h] [ebp-1Ch]
if ( !ucb )
return 0;
if ( lp )
{
end_ptr = (unsigned int)lp + ucb - 1;
if ( end_ptr >= (unsigned int)lp )
{
begin_ptr = *(_BYTE *)lp;
// 对0x1000按位取反,然后位与运算,得到去余数后的整数地址作为起始地址标识符
begin_ptr_alignment = (char *)((unsigned int)lp & -dword_779B0708);
v4 = -dword_779B0708 & end_ptr;
// 对0x1000按位取反,然后位与运算,得到去余数后的整数地址作为末尾地址标识符
end_ptr_alignment = -dword_779B0708 & end_ptr;
while ( begin_ptr_alignment != (char *)v4 )
{
begin_ptr_alignment += dword_779B0708;
v6 = *begin_ptr_alignment;
v4 = end_ptr_alignment;
}
return 0;
}
}
return 1;
}
可见首尾地址按照0x1000字节做对其,并且是每次跨0x1000地址读取一个字节内容,检查完之后返回false
,产生异常的话就会由FilterFunc
接管,函数判断是读内存异常,就会返回true
,因此优化后的完整代码应是:
long WINAPI FilterFunc(DWORD dwExceptionCode)
{
return (dwExceptionCode == STATUS_ACCESS_VIOLATION)
? EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_SEARCH;
}
BOOL __stdcall IsBadReadPtr_win32(const void* lp, UINT_PTR ucb) {
char begin_ptr; // t1
char tmp_byte; // t1
unsigned int end_ptr; // [esp+14h] [ebp-1Ch]
void* iterator_ptr_alignment;
void* end_ptr_alignment = 0;
if (!ucb) return 0;
__try {
if (lp) {
end_ptr = (unsigned int)lp + ucb - 1;
if (end_ptr >= (unsigned int)lp) {
begin_ptr = *(BYTE*)lp;
iterator_ptr_alignment =
(char*)((unsigned int)lp &
-0x1000); // 对0x1000按位取反,然后位与运算,得到去余数后的整数地址作为起始地址标识符
end_ptr_alignment =
(void*)(-0x1000 &
end_ptr); // 对0x1000按位取反,然后位与运算,得到去余数后的整数地址作为末尾地址标识符
while (iterator_ptr_alignment != (char*)end_ptr_alignment) {
iterator_ptr_alignment =
(void*)((int)iterator_ptr_alignment + 0x1000);
tmp_byte = *(BYTE*)iterator_ptr_alignment;
}
}
}
} __except (FilterFunc(GetExceptionCode())) {
return TRUE;
}
return FALSE;
}