首页
社区
课程
招聘
[原创]在ShellCode里面使用异常处理(Win32位平台)
2023-7-13 19:14 14251

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

2023-7-13 19:14
14251


上次发帖介绍了一下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/ ,代码下载:

https://github.com/Teq2/SEH-Over-VEH。当然,如果你是自己的Loader,那么可以通过修改编译选项来解决,但也失去了Shellcode的意义---因为写成Dll然后内存加载更好。



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




例如(伪代码):

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 //跳到异常处理后的地址继续执行
              }
     
}

顺便提一下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里面即可。




[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
点赞7
打赏
分享
最新回复 (5)
雪    币: 456
活跃值: (917)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cjshpzh 2023-7-13 19:54
2
0
感谢分享,做个记号
雪    币: 149
活跃值: (2023)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
saloyun 2023-7-13 19:59
3
0
写得非常好,那么,arm64的异常是不是在路上了??
雪    币: 30092
活跃值: (2047)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
bestbird 2023-7-13 21:33
4
0
saloyun 写得非常好,那么,arm64的异常是不是在路上了??[em_71]
只懂Win:)
雪    币: 149
活跃值: (2023)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
saloyun 2023-7-14 16:25
5
0
bestbird 只懂Win:)
windows的arm64也出来很多年了呀,大佬写文分析一下吧
雪    币: 30092
活跃值: (2047)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
bestbird 2023-7-14 17:01
6
0
saloyun windows的arm64也出来很多年了呀,大佬写文分析一下吧[em_1]

找不到arm64的机器,因为我只有一台电脑。不过你可以试试附件里面的win64目录下的TEST。因为我看了一下Delphi的system.pas,里面有编译定义:

  {$IFDEF CPUX86}
        {$DEFINE ARITH_PUREPASCAL_EXT80}
  {$ENDIF CPUX86}
  {$IFDEF CPUX64}
    {$IFDEF MSWINDOWS}
        {$DEFINE ARITH_PUREPASCAL_EXT64}
    {$ENDIF MSWINDOWS}
    {$IFDEF LINUX}
        {$DEFINE ARITH_USE_LIBM}
    {$ENDIF LINUX}
  {$ENDIF CPUX64}
  {$IFDEF CPUARM}
        {$DEFINE ARITH_USE_LIBM}
  {$ENDIF CPUARM}

还有一个是初始化的函数:

procedure _InitializeControlWord;
begin
{$IF defined(IOS) and defined(CPUX86)} // iOS Simulator
  TestSSE := $3; // All Intel Mac supports SSE2.
  FPUExceptionMaskBits := (Default8087CW and $003F) or ((DefaultMXCSR and $1F80) shr 7) or $8000;
{$ELSEIF defined(CPUX86) and defined(ASSEMBLER)} // Win32 or OSX32
  TestSSE := GetBriefSSEType;
  DefaultMXCSR := GetMXCSR and $FFC0;  // Remove flag bits;
{$ELSEIF defined(CPUX64) and defined(MSWINDOWS)} // Win64
  TestSSE := $3; // SSE & SSE2 are available on X64
  Default8087CW := Get8087CW and $1F3F; // Remove reserved bits.
{$ELSEIF defined(CPUX64) and defined(ARITH_USE_LIBM)} // Linux64
  TestSSE := $3; // SSE & SSE2 are available on X64
  FPExceptionMaskBits := DefaultFPEnv;
{$ENDIF}
end;

而异常处理函数里面,只是分为32位和64位的,其中32位是纯汇编的。所以你可以试试附件,唯一有可能不一样的地方是里面使用了一条指令:

LDMXCSR DefaultMXCSR。Delphi本身支持一套代码编译出Win、linux、Andriod、IOS,但对于Win,只有Win32和Win64可以选择,没有ARM64的选项,所以要么它本身就支撑(包括异常),要么连编译的64 bit EXE都跑不起来。不过我隐约记得,64位的异常表结构,好像是分CPU的。

游客
登录 | 注册 方可回帖
返回