IsBadReadPtr函数和异常处理

起因是优化代码性能,注意到这个函数,搜了一下发现是微软弃用的函数,说是有线程安全问题。经过一系列操作发现,处理大文件时这个函数会导致耗时变长,于是就研究一下这个函数。

首先看函数开头:

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)(); 
    };
};

其中FilterFuncFinallyFunc就是我们自定义的__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;
}

参考:https://b0ldfrev.gitbook.io/note/pwn/windowsseh-li-yong

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值