c++

在ShellCode里面使用异常处理(Win32位平台) 

上次发帖介绍了一下64位Shellcode异常处理的(链接:https://bbs.kanxue.com/thread-277595-1.htm),有朋友发消息说想看一下32位的,所以抽空做了一点尝试。

Win32的异常处理最经典的是Matt Pietrek写的文章,这是一篇翻译的:https://www.cnblogs.com/sunkang/archive/2011/04/29/2038817.html

32位的异常挂接比较简单,下面的代码是从上文复制过来的:

DWORD  scratch;
 
EXCEPTION_DISPOSITION
__cdecl
_except_handler(
    struct _EXCEPTION_RECORD *ExceptionRecord,
    void * EstablisherFrame,
    struct _CONTEXT *ContextRecord,
    void * DispatcherContext )
{
    unsigned i;
 
    // Indicate that we made it to our exception handler
    printf( "Hello from an exception handler\n" );
 
    // Change EAX in the context record so that it points to someplace
    // where we can successfully write
    ContextRecord->Eax = (DWORD)&scratch;
 
    // Tell the OS to restart the faulting instruction
    return ExceptionContinueExecution;
}
 
 
 
int main()
{
    DWORD handler = (DWORD)_except_handler; 
    __asm
    { 
        // 创建 EXCEPTION_REGISTRATION 结构:
        push handler  // handler函数的地址
        push FS:[0]   // 前一个handler函数的地址
        mov FS:[0],ESP    // 装入新的EXECEPTION_REGISTRATION结构
    } 
    __asm
    {
        mov eax,0         // EAX清零
        mov [eax], 1     // 写EAX指向的内存从而故意引发一个错误
    } 
    printf( "After writing!\n" ); 
    __asm
    { 
        // 移去我们的 EXECEPTION_REGISTRATION 结构记录
        mov eax,[ESP]      // 获取前一个结构
        mov FS:[0], EAX  // 装入前一个结构
        add esp, 8         // 将 EXECEPTION_REGISTRATION 弹出堆栈
    } 
    return 0; 
}

原理其实就是将自己的异常处理函数挂接到TEB的异常列表里面。

但Win32如果想在ShellCode里面使用自己的异常处理,其实还是很困难的,原因在于Safe SEH。比如说上面的代码,如果使用VS2017编译为release版本,默认情况下,异常回调根本不会触发。所以比如说你将ShellCode注入到记事本,异常函数根本不会触发,记事本闪崩。网上也有一些解决方案,比如说使用向量异常接管,但这也失去了SEH的意义。详细介绍可以参考这篇文章:https://hackmag.com/uncategorized/exceptions-for-hardcore-users/ ,代码下载:

EXCEPTION_DISPOSITION
__cdecl
_except_handler(
    struct _EXCEPTION_RECORD *ExceptionRecord,
    void * EstablisherFrame,
    struct _CONTEXT *ContextRecord,
    void * DispatcherContext )
{
         
       if (ExceptionRecord->ExceptionFlags&&( cUnwinding || cUnwindingForExit))
        return  EXCEPTION_CONTINUE_SEARCH;//如果处于展开阶段,则不处理。
         
      ExceptionRecord->ExceptionFlags ||= cUnwinding;
    __asm
    {
        push    ebp
        mov     ebp, esp
         
        push    ebx              
        push    esi              
        push    edi
         
         
         
           //RtlUnwind(EstablisherFrame,pReturnAddress,ExceptionRecord,NULL);//展开
     
     
        push    dword ptr [retVal]
        push    dword ptr [ExceptionRecord]
        push    offset _returnAddress
        push    dword ptr [pEstablisherFrame]
 
        mov     eax, [RtlUnwind]
        call    eax
      
        
        pop     edi
        pop     esi
        pop     ebx
    }
 
_returnAddress:
          asm
          {
          MOV     EBP,[EDI].TExcFrame.hEBP//恢复发生异常前保存的EBP
          MOV     EBX,[EDI].TExcFrame.desc//这个就是异常处理函数跳转前的地址,内容是:jmp _except_handler
          ADD     EBX,TExcDesc.instructions//加上5个字节,就等于Filter的地址了
          JMP     EBX //跳到异常处理后的地址继续执行
              }
      
}

32位异常,不能在代码里面使用try…except语句了,一旦使用,编译器就会在编译的时候加入挂接函数了。必须像上面那样自己挂接。这个是跟64位最大不同的地方。另外,还有一个地方需要提一下的,就是异常处理的时候,展开时会调用到RtlUnwind函数,这个函数是会清除除了EBP和ESP外的所有寄存器的,所以一定要先保存。

例如(伪代码):

顺便提一下64位的入门资料,因为有朋友问过。

64位的异常可以参考这篇文章:https://pmeerw.net/blog/programming/RtlAddFunctionTable.html,代码如下:

// sample code to demonstrate Windows RtlAddFunctionTable API
// (c) 2019 Peter Meerwald-Stadler, pmeerw@pmeerw.net
// 64-bit only, compile with: cl rtlft.c /link /fixed
 
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
 
#include "windows.h"
 
typedef uint8_t UBYTE;
typedef uint16_t USHORT;
 
typedef union _UNWIND_CODE {
    struct {
        UBYTE CodeOffset;
        UBYTE UnwindOp : 4;
        UBYTE OpInfo   : 4;
    };
    USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
 
typedef struct _UNWIND_INFO {
    UBYTE Version       : 3;
    UBYTE Flags         : 5;
    UBYTE SizeOfProlog;
    UBYTE CountOfCodes;
    UBYTE FrameRegister : 4;
    UBYTE FrameOffset   : 4;
    UNWIND_CODE UnwindCode[1];
/*  UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
 * OPTIONAL ULONG ExceptionHandler;
 * OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;
 
typedef struct {
    uint8_t code[0x1000];
    RUNTIME_FUNCTION function_table[1];
    UNWIND_INFO unwind_info[1];
} DYNSECTION;
 
static EXCEPTION_DISPOSITION handler(PEXCEPTION_RECORD ExceptionRecord, ULONG64 EstablisherFrame, PCONTEXT ContextRecord, PDISPATCHER_CONTEXT DispatcherContext) {
    printf("handler!\n");
    ContextRecord->Rip += 3;
    return ExceptionContinueExecution;
}
 
int main() {
    int ret;
    RUNTIME_FUNCTION *q;
    DYNSECTION *dynsection = VirtualAlloc(NULL, 0x2000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
     
    uint8_t *code = dynsection->code;
    size_t p = 0;
    code[p++] = 0xb8; // mov rax, 42
    code[p++] = 0x2a;
    code[p++] = 0x00;
    code[p++] = 0x00;
    code[p++] = 0x00;
    code[p++] = 0xc6; // mov byte [rax], 0  -- raises exception!
    code[p++] = 0x00;
    code[p++] = 0x00;
    code[p++] = 0xc3; // ret
 
    size_t trampoline = p;
    code[p++] = 0x48; // mov rax,
    code[p++] = 0xb8;
    size_t patch_handler_address = p;
    code[p++] = 0x00; // address to handler patched here
    code[p++] = 0x00;
    code[p++] = 0x00;
    code[p++] = 0x00;
    code[p++] = 0x00;
    code[p++] = 0x00;
    code[p++] = 0x00;
    code[p++] = 0x00;
    code[p++] = 0xff; // jmp rax
    code[p++] = 0xe0;
     
    DWORD64 dyn_base = 0;
    q = RtlLookupFunctionEntry((DWORD64) code, &dyn_base, NULL);
    printf("lookup 'code' %p %llx\n", q, dyn_base); // no function table entry
 
    DWORD64 image_base = 0;
    q = RtlLookupFunctionEntry((DWORD64) main, &image_base, NULL);
    printf("lookup 'main' %p %llx\n", q, image_base); // there is a function table entry
 
    dyn_base = (DWORD64) dynsection;
    UNWIND_INFO *unwind_info = dynsection->unwind_info;
    unwind_info[0].Version = 1;
    unwind_info[0].Flags = UNW_FLAG_EHANDLER;
    unwind_info[0].SizeOfProlog = 0;
    unwind_info[0].CountOfCodes = 0;
    unwind_info[0].FrameRegister = 0;
    unwind_info[0].FrameOffset = 0;
    *(DWORD *) &unwind_info[0].UnwindCode = (DWORD64) &code[trampoline] - dyn_base;
 
    RUNTIME_FUNCTION *function_table = dynsection->function_table;
    function_table[0].BeginAddress = (DWORD64) &code[0] - dyn_base; // set RVA of dynamic code start
    function_table[0].EndAddress = (DWORD64) &code[trampoline] - dyn_base; // RVA of dynamic code end
    function_table[0].UnwindInfoAddress = (DWORD64) unwind_info - dyn_base; // RVA of unwind info
 
    *(DWORD64 *) &code[patch_handler_address] = (DWORD64) handler; // VA of handler
 
    printf("main VA %016llx\n", (DWORD64) main);   
    printf("code VA %016llx\n", (DWORD64) code);
    printf("function table VA %016llx\n", (DWORD64) function_table);
    printf("unwind info VA %016llx\n", (DWORD64) unwind_info);
    printf("handler VA %016llx\n", (DWORD64) handler);
    printf("RUNTIME_FUNCTION begin RVA %08x, end RVA %08x, unwind RVA %08x\n",
        function_table[0].BeginAddress, function_table[0].EndAddress,
        function_table[0].UnwindInfoAddress);
    printf("UNWIND_INFO handler RVA %08x\n", *(DWORD *) &unwind_info[0].UnwindCode);
     
    if (!RtlAddFunctionTable(function_table, 1, dyn_base)) {
        printf("RtlAddFunctionTable() failed, exit.\n");
        exit(EXIT_FAILURE);
    }
 
    q = RtlLookupFunctionEntry((DWORD64) code, &dyn_base, NULL);
    printf("lookup 'code' %p %llx\n", q, dyn_base); // should return address of function table entry
 
    uint64_t (*call)() = (uint64_t (*)()) code;
    uint64_t result = (*call)();
    printf("result = %llx\n", result);  
 
    if (!RtlDeleteFunctionTable(function_table)){
        printf("RtlDeleteFunctionTable() failed, exit.\n");
        exit(EXIT_FAILURE);
    }
 
    return EXIT_SUCCESS;
}

从这个代码入手,有一个初步的认识后,自己写一个会产生异常的函数,用它来替换上面例子的异常代码和异常表,就可以调试自己的异常处理函数了。全部OK后,全部集成到ShellCode里面即可。

AI相关的一切

留言

您的邮箱地址不会被公开。 必填项已用 * 标注