反调试之RtlAddVectoredExceptionHandler
2022-11-13 10:53:17 Author: OnionSec(查看原文) 阅读量:28 收藏

在Guloader加载器代码的开头,第一层shellcode解密完成后,接着会对比DJB2算法得到的哈希来获取特定的API函数。

如下对比特定hash来导入API函数:

F0D2CE31 == ntdll_12.LdrLoadDll

0C79894C == ntdll_12.ZwGetContextThread

E2C26F81 == ntdll_12.RtlAddVectoredExceptionHandler

Guloader加载器使用ZwGetContextThread是用来实现反调试,对于RtlAddVectoredExceptionHandler函数则是一方面用于反调试,另一方面也用于干扰动静态分析。Guloader下载器在最近几年公开的分析报告中并未提及到RtlAddVectoredExceptionHandler函数反调试干扰分析的细节,因此这里简单记录一下。

通过动态调试发现会注册向量异常处理程序(VEH)作用是反调试,输入的参数为1表明一旦发生异常则被第一个处理。

函数定义如下:

PVOID AddVectoredExceptionHandler(

  ULONG                       First,

  PVECTORED_EXCEPTION_HANDLER Handler

);

参数说明:

First:应调用处理程序的顺序。如果参数不为零,则处理程序是第一个被调用的处理程序。如果参数为零,则处理程序是最后一个被调用的处理程序。

Handler:指向要调用的处理程序的指针。有关详细信息,请参阅VectoredHandler。

返回值:如果函数成功,则返回值是异常处理程序的句柄。如果函数失败,则返回值为NULL。

通过查询MSDN找到如下VectoredHandler结构体信息,后续会利用这些结构体来理解shellcode实现干扰分析的原理。

PVECTORED_EXCEPTION_HANDLER PvectoredExceptionHandler;

LONG PvectoredExceptionHandler(

  [in] _EXCEPTION_POINTERS *ExceptionInfo

)

{...}

参数

[in] ExceptionInfo

指向接收异常记录的EXCEPTION_POINTERS结构的指针

EXCEPTION_POINTERS结构体如下:

typedef struct _EXCEPTION_POINTERS {

  PEXCEPTION_RECORD ExceptionRecord;

  PCONTEXT          ContextRecord;

} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

成员

ExceptionRecord

指向 包含与机器无关的异常描述的EXCEPTION_RECORD结构的指针。

ContextRecord

指向 CONTEXT结构的指针,该结构包含异常时处理器状态的特定于处理器的描述。

EXCEPTION_RECORD结构体如下:

typedef struct _EXCEPTION_RECORD {

  DWORD                    ExceptionCode;

  DWORD                    ExceptionFlags;

  struct _EXCEPTION_RECORD *ExceptionRecord;

  PVOID                    ExceptionAddress;

  DWORD                    NumberParameters;

  ULONG_PTR                ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];

} EXCEPTION_RECORD;

在32位环境中,CONTEXT结构体定义如下。

#define MAXIMUM_SUPPORTED_EXTENSION     512

typedef struct _CONTEXT {

    //

    // The flags values within this flag control the contents of

    // a CONTEXT record.

    //

    // If the context record is used as an input parameter, then

    // for each portion of the context record controlled by a flag

    // whose value is set, it is assumed that that portion of the

    // context record contains valid context. If the context record

    // is being used to modify a threads context, then only that

    // portion of the threads context will be modified.

    //

    // If the context record is used as an IN OUT parameter to capture

    // the context of a thread, then only those portions of the thread's

    // context corresponding to set flags will be returned.

    //

    // The context record is never used as an OUT only parameter.

    //

    DWORD ContextFlags;

    //

    // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is

    // set in ContextFlags.  Note that CONTEXT_DEBUG_REGISTERS is NOT

    // included in CONTEXT_FULL.

    //

    DWORD   Dr0;

    DWORD   Dr1;

    DWORD   Dr2;

    DWORD   Dr3;

    DWORD   Dr6;

    DWORD   Dr7;

    //

    // This section is specified/returned if the

    // ContextFlags word contians the flag CONTEXT_FLOATING_POINT.

    //

    FLOATING_SAVE_AREA FloatSave;

    //

    // This section is specified/returned if the

    // ContextFlags word contians the flag CONTEXT_SEGMENTS.

    //

    DWORD   SegGs;

    DWORD   SegFs;

    DWORD   SegEs;

    DWORD   SegDs;

    //

    // This section is specified/returned if the

    // ContextFlags word contians the flag CONTEXT_INTEGER.

    //

    DWORD   Edi;

    DWORD   Esi;

    DWORD   Ebx;

    DWORD   Edx;

    DWORD   Ecx;

    DWORD   Eax;

    //

    // This section is specified/returned if the

    // ContextFlags word contians the flag CONTEXT_CONTROL.

    //

    DWORD   Ebp;

    DWORD   Eip;

    DWORD   SegCs;              // MUST BE SANITIZED

    DWORD   EFlags;             // MUST BE SANITIZED

    DWORD   Esp;

    DWORD   SegSs;

    //

    // This section is specified/returned if the ContextFlags word

    // contains the flag CONTEXT_EXTENDED_REGISTERS.

    // The format and contexts are processor specific

    //

    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

} CONTEXT;

地址1637C3作为异常处理函数入口(也就是PVECTORED_EXCEPTION_HANDLER Handler函数),如下。

0x80000003属于BreakPoint异常,不用做修改,shellcode执行逻辑会进行修改EIP值作跳转。

第二次生成EXCEPTION_SINGLE_STEP异常(代码0x80000004),如果不做修改,shellcode会直接退出表明存在调试行为。

该shellcode注册的Handler函数地址基于shellcode代码开头偏移为137C3h,主要作用是引导后续的执行流,当执行到shellcode代码里内嵌的int 3(CC)指令时,发生BreakPoint异常,于是异常处理程序来到地址1637C3入口(也就是PVECTORED_EXCEPTION_HANDLER Handler函数),在这个函数中会修改计算得到异常结束后下一次执行最终的EIP值进行跳转执行。得到最终EIP值的实现算法为取shellcode内嵌的int 3(CC)指令的下一个地址的字节值A,将A与0xE5进行异或得到B,将B与当前int 3(CC)指令的当前地址进行累加得到C,将C赋值给_EXCEPTION_POINTERS->ContextRecord->Eip后,等异常处理完毕将恢复状态时,EIP就会恢复为上述保存的值,从而执行时发生跳转,shellcode利用RtlAddVectoredExceptionHandler注册的Handler函数地址实现的逻辑如下。

因为后续在调试shellcode的过程中大量的遇到int3中断导致的异常来干扰调试分析,因此有必要需要进行修复。首先需要经过逆向来理解shellcode利用int3中断跳转执行干扰分析的原理。

举个例子,目前提取的部分shellcode的代码数据如下,当执行流执行到000A2121地址会产生BreakPoint异常。

seg000:000A2121 CC

seg000:000A2122 ED

seg000:000A2123 BC

......

seg000:000A2129 51

之后经过对1637C3入口(也就是PVECTORED_EXCEPTION_HANDLER Handler函数)的逻辑进行逆向,得到伪代码实现如下:

例子1如下:

ED ^ E5 = 8h (CC的下一个地址的数据ED与0xE5进行异或得到8h

000A2121 + 8h = 000A2129(将发生中断的地址加上8h得到最终执行的地址,也就是EIP会被替换的值

000A2123 + offset = 000A2129(这里为了利用jmp指令进行二进制补丁,我们可以在发生中断的地址偏移+2处经过计算得到EB操作码后续的偏移值offset)

EB offset == EB 06(因为直接修改EIP的过程类似跳转执行,所以对其补丁的时候可以转换为jmp指令的操作码,只需在中断的位置修改两个字节数据即可,可以对shellcode进行补丁来适当修复)

例子2如下(原理如上):

F6 ^ E5 = 13

000A212A + 13 = 000A213D

(000A212A + 2) + offset = 000A213D

EB offset == EB 11

因此我们可以编写IDAPython脚本进行补丁修复或者直接对shellcode二进制数据进行修复,在修复之前我们需要获取到shellcode执行过程中出现int3指令的地址,这个算是一个难点,因为没有完全获取到所有int3中断时的地址,所以只能是部分修复,修复后对于后续的反调试与反虚拟机机制调试起来也就比较容易了。

目前想到的是可以利用ollydbg的条件记录断点功能,在获取中断地址的逻辑处设置断点,一旦命中就进行记录,之后在日志记录窗口获取到所有int3中断时所在的地址,将地址保存后就可以本地利用Python脚本对其进行二进制补丁。直接利用静态反汇编得到的结果来获取int3中断时的地址是可以的,但是无法覆盖大部分地址,因为静态反汇编会存在混乱。如果是要利用模拟执行框架来获取int3中断时的地址,感觉意义不大,因为内嵌int3中断(CC)很多,掺杂在反调试与反虚拟机机制中。

简单实现二进制补丁的脚本如下:

# -*- coding: UTF-8 -*-

def main():

    list_offset = []

    with open("od_bp_log.txt", 'r', encoding="utf-8") as f:  # from ollydbg disam

        diasm = f.readlines()

    for l in diasm:

        line = l.strip()

        if "int3" in line:

            print(line)

            print(type(line[2:8]))

            # offset = int(line[2:8], 16) - 0x110000

            print(line[-6:])

            offset = int(line[-6:], 16) - 0x110000

            list_offset.append(offset)

    with open("decode_shellcode_2", 'rb') as f:

        data = f.read()

        shellcode = bytearray(data)

    for n in list_offset:

        int3_next = shellcode[n+1]

        # value = int.from_bytes(int3_next, byteorder='litten', signed=False)

        print(int3_next)

        print(type(int3_next))

        jmp_value = (int3_next ^ 229) - 2

        print(str(jmp_value) + " " + str(type(jmp_value)))

        # code = jmp_value.encode('utf-8')

        # print(type(code))

        shellcode[n] = 0xEB

        shellcode[n + 1] = jmp_value

        # end_vaule = n + (int3_next ^ 229)

        # a = n + 2

        # for a in range(a, end_vaule):

        #     shellcode[a] = 0x90

    with open("decode_shellcode_3", 'wb') as fw:

        fw.write(shellcode)

if __name__ == '__main__':

    try:

        main()

    except Exception as e:

        print('Exception', e)


文章来源: http://mp.weixin.qq.com/s?__biz=MzUyMTUwMzI3Ng==&mid=2247484671&idx=1&sn=90bfc19f2401c45d7fd611b48805356e&chksm=f9db53bcceacdaaa8171555d8d44f701e2988a1aa2bb52de2404eac702e9a6b210ec8ed22264#rd
如有侵权请联系:admin#unsafe.sh