ROP缓释机制

ROP攻击

1.ROP概述

  • rop是Return Oriented Programming(面向返回的编程)的缩写;
  • 根据函数调用规则,当刚跳转到其它函数去执行时,从被调用者的视角看:栈顶是返回地址,紧接着是自己的参数(X86架构下);
  • 然后,被调用者会对栈空间进行一系列操作,保存寄存器和存储临时变量,但在即将退出时会清理自己消耗的栈空间,以使其回到自己被调用前的栈空间,保持栈平衡
  • 最后,被调用者以ret指令结尾,ret指令将栈顶地址传递到IP寄存器,随后代码也就跳转到之前栈顶存放的返回地址处
  • rop链即基于以上这个简单的原理,在代码空间中寻找以ret结尾的代码片段或函数(代码片段称为Rop gadgets),组合可以实现拓展可写栈空间、写入内存、shell等功能,依靠ret将代码执行权紧握在自己的手里;

ROP 攻击一般得满足如下条件

  • 程序存在溢出,并且可以控制返回地址
  • 可以找到满足条件的 gadgets 以及相应 gadgets 的地址

2.常见的ROP攻击手段

2.1 ret2text

​ ret2text 即控制程序执行程序本身已有的的代码 (.text节)。控制执行程序已有的代码的时候也可以控制程序执行好几段不相邻的程序已有的代码 (也就是 gadgets),即ROP。

2.2 ret2shellcode

​ ret2shellcode,即控制程序执行 shellcode 代码。shellcode 指的是用于完成某个功能的汇编代码,常见的功能主要是启动恶意代码。一般来说,shellcode需要我们自己填充一些可执行的代码

2.3 ret2syscall

​ ret2syscall,即控制程序执行系统调用

2.4 ret2libc

​ ret2libc 即控制函数执行 libc 中的函数,具体可见绕过DEP

ROP缓释机制

漏洞利用攻击的特点

​ 当排除在进程环境中可以完全执行攻击(如XSS攻击)的情况时,攻击者必须使用ROP攻击技术,并且与其他进程或者内核进行交互,这种交互包括创建进程、打开和写入文件等。

基于这种特点,可以定义一个关键函数的概念:关键函数是攻击者通过调用,能达到不再需要ROP技术转而直接进行攻击的那些函数。比如:

  • CreateProcess :调用之后,攻击者可以创建另一个进程,然后来直接执行恶意行为。
  • VirtualProtect、VirtualAlloc、LoadLibrary:调用之后,攻击者可以绕过系统自身的漏洞利用缓解机制,不要需要ROP这种复杂且有诸多限制的技术,从而轻松执行恶意代码。
  • OpenFile、WriteFile:调用之后,攻击者可以打开并写入任意文件

ROP缓释机制会在这些关键函数时执行检查,通过以下几点,来确定ROP攻击是否发生

  • 关键函数是如何被调用的?
  • 关键函数执行后会发生什么?
  • 系统当前状态是否与正常程序执行时一致?
  • 执行关键函数是否会危害系统的安全性?

1.Load library checks

​ 某些反病毒软件未配置为在通过UNC路径访问时扫描或阻止从SMB或WebDAV执行恶意二进制文件。因此攻击者可以通过UNC路径简单地映射包含后门,黑客工具等恶意的共享文件,并执行恶意软件。

​ EMET使用Load library checks监视所有对加载库 API 的调用,并防止从 UNC 路径加载库(即\\evilsite\bad.dll)。如果已知某个程序从 UNC 路径或远程服务器合法加载 dll,则可以禁用此选项。这种缓解可用于 32 位和 64 位进程

1.1 UNC路径

​ UNC 代表通用(或一致、统一)命名约定,是一种用于访问计算机网络上的文件夹和文件的语法。语法如下:

\\<computer name>\<shared directory>\

​ 后跟任意数量的目录,并以目录或文件名结尾。例如:

\\pondermatic\public\studyarea.gdb
\\omnipotent\shared_stuff\wednesday\tools

​ 计算机名称的前面始终使用双反斜线 (\)。在 UNC 中,计算机名称又称为主机名称。对于 UNC 路径,存在以下几条规则:

  • UNC 路径不能包含盘符(如 D:)。
  • 不能导航到共享目录的上级目录。
  • 用于文档和工具的存储相对路径名选项对 UNC 路径不起作用。

​ 在 Windows 中,您可以共享某个文件夹,以便局域网上的其他用户能够对其进行访问。在 ArcCatalog 或 Windows 资源管理器中,右键单击文件夹,单击共享和安全,然后按照打开的对话框上的说明进行操作即可

1.2 DOS路径和UNC路径的转换

  1. 在Windows上除了“\” 还可以把“/”作为路径使用。例如:

    C:\Windows///\system32\calc.exe与C:\Windows\system32\calc.exe相同

    与此对应的UNC路径: \\127.0.0.1\C$\windows\system32\calc.exe

  2. 127.0.0.1也可以使用localhost代替。例如:

    C:\Windows\System32\spool\drivers\x64\4

    对应的UNC路径:\\localhost\C$\Windows\System32\spool\drivers\x64\printers\x64\4

  3. 对于注册表

    • 若binPath= c:\,注册表项 ImagePath= ??\c:\

    • 若binPath = \\vmware-host\Shared Folders\MyDriver\x64\Debug\sample.sys

      注册表项 ImagePath= ??\UNC\vmware-hots\Shared Folders\MyDriver\x64\Debug\sample.sys

1.3 Load library checks的实现

  1. 对所有加载模块的函数进行Inline Hook

    {"kernel32.LoadLibraryA", 1, 0x3D4CE},
    {"kernel32.LoadLibraryW", 1, 0x3D4CE},
    {"kernel32.LoadLibraryExA", 3, 0x3D4CE},
    {"kernel32.LoadLibraryExW", 3, 0x3D4CE},
    {"kernelbase.LoadLibraryExA", 3, 0x3D4CF},
    {"kernelbase.LoadLibraryExW", 3, 0x3D4CF},
    {"kernel32.LoadPackagedLibrary", 2, 0x14CE},
    {"ntdll.LdrLoadDll", 4, 0x14CF}
    
  2. 当程序调用LoadLibrary系列函数加载模块时,跳转至DetourFunction中,判断传入的模块名是否是UNC路径格式

    BOOL IsUNCPath(LPCSTR pszPath) {
    	int nPathLen = 0;
    	LPCSTR tempPath = NULL;
    	if (pszPath == NULL) {
    		return FALSE;
    	}
    	if ((nPathLen = strlen(pszPath)) < 5) {
    		return FALSE;
    	}
    	if (pszPath[0] != '\\') {
    		return FALSE;
    	}
    	if (pszPath[1] != '\\' && pszPath[1] != '/' && pszPath[1] != '?') {
    		return FALSE;
    	}
    	if (pszPath[2] != '.' && pszPath[2] != '?') {
    		return TRUE;
    	}
    	if (pszPath[strspn(pszPath, ".?/\\")] != 0) {
    		tempPath = &pszPath[strspn(pszPath, ".?/\\")];
    	}
    	if (tempPath) {
    		if (strncmp("unc", tempPath, 3) == 0) {
    			wchar_t wcUncBehind = *(tempPath + 3);
    			if (wcUncBehind != 0) {
    				if (wcUncBehind != '.') {
    
    				}
    				else {
    					wcUncBehind = *(tempPath + 3 + 1);
    					if (wcUncBehind == 0) {
    						return FALSE;
    					}
    				}
    
    				if (wcUncBehind == '\\') {
    					return TRUE;
    				}
    				else {
    					if (wcUncBehind == '/') {
    						return TRUE;
    					}
    				}
    			}
    		}
    	}
    	return FALSE;
    }
    
  3. 如果是UNC路径格式,进行拦截,直接终止进程

Load library checks的代码如下

BOOL LoadLib(UNION_HOOKEDFUNCINFO::PLOADLIB_INFO pLoadLibInfo, PHOOKINFO pHookInfo) {
	char *pszDllName_alloc = NULL;
	char *pszDllName = NULL;
	
	//当所加载模块只作为数据文件或资源文件时不进行拦截
	if (pLoadLibInfo->dwFlags != TYPE_LIBLOAD ||
		(pLoadLibInfo->dwIsExVersion == TRUE && pLoadLibInfo->dwFlags & LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_AS_DATAFILE))
	{
		return TRUE;
	}
	//如果是宽字节版本
	if (pLoadLibInfo->dwIsWideVersion == TRUE) {

		int nDLLnameLen = wcslen((const wchar_t *)pLoadLibInfo->dwFileNamePtr);
		pszDllName_alloc = new char[wcslen((const wchar_t *)pLoadLibInfo->dwFileNamePtr) + 1];
		WideCharToMultiByte(CP_ACP, 0, (LPCWCH)pLoadLibInfo->dwFileNamePtr, -1, pszDllName_alloc, nDLLnameLen, NULL, NULL);
		pszDllName = pszDllName_alloc;
	}
	else
	{
		pszDllName = (char*)pLoadLibInfo->dwFileNamePtr;
	}
	if (pszDllName) {

		BOOL bRet = IsUNCPath(pszDllName);
		if (bRet) {
			if (GetFileAttributes((LPCTSTR)pLoadLibInfo->dwFileNamePtr) != INVALID_FILE_ATTRIBUTES) {
				ErrorReport();
				return 1;
			}
		}
	}
	return 0;
}

2.Memory protection checks

​ 阻止掉将位于栈中的内存空间标记为可执行的操作。用VirtualProtect来使堆栈可执行是利用ROP攻击的常用方法之一:Corelan ROPdb

2.1 VirtualProtect系列函数

  1. VirtualProtect

    BOOL VirtualProtect(
      [in]  LPVOID lpAddress,
      [in]  SIZE_T dwSize,
      [in]  DWORD  flNewProtect,
      [out] PDWORD lpflOldProtect
    );
    
  2. VirtualProtectEx

    BOOL VirtualProtectEx(
      [in]  HANDLE hProcess,
      [in]  LPVOID lpAddress,
      [in]  SIZE_T dwSize,
      [in]  DWORD  flNewProtect,
      [out] PDWORD lpflOldProtect
    );
    
  3. NtProtectVirtualMemory

    NTSTATUS
    NtProtectVirtualMemory(
      IN  HANDLE ProcessHandle,
      IN OUT PVOID *BaseAddress,
      IN OUT PULONG RegionSize,
      IN  ULONG NewProtect,
      OUT PULONG OldProtect
      );
    

2.2 Memory protection checks实现

  1. 对可以修改内存页面保护属性的函数进行InlineHook:

    {"kernel32.VirtualProtect", 4, 0X14F2},
    {"kernel32.VirtualProtectEx", 5, 0X14F2},
    {"kernelbase.VirtualProtect", 4, 0X14F3},
    {"kernelbase.VirtualProtectEx", 5, 0X14F3},
    {"ntdll.NtProtectVirtualMemory", 5, 0X14F3}
    
  2. 当程序调用VirtualProtect系列函数试图改变内存页面属性时,跳转至DetourFunction中进行判断,检验传入参数,如果发现flwNewProtect中设置了可执行标记位,那么则进一步对lpAddress进行检验,如果发现[lpAddress,lpaddress+dwSize]这段内存恰好在当前线程的栈地址范围内,则认为检测到ROP,终止进程

    BOOL MemProt(UNION_HOOKEDFUNCINFO::PMEMPROT_INFO pMemProtStruct) {
    
    	if (pMemProtStruct->dwType != 2) {
    		return TRUE;
    	}
    
    	if (!(pMemProtStruct->dwNewProtect & 0xF0)) {
    		return TRUE;
    	}
    
    	if (pMemProtStruct->dwProcess) {
    		if (GetCurrentProcessId() != GetProcessId((HANDLE)pMemProtStruct->dwProcess)) {
    			return TRUE;
    		}
    	}
    
    	DWORD dwAddr = pMemProtStruct->dwAddress;
    	DWORD teb = (DWORD)NtCurrentTeb();
    	DWORD dwStackLimit = *(DWORD*)(teb + 8);
    	DWORD dwStackBase = *(DWORD*)(teb + 4);
    	if (((dwAddr + pMemProtStruct->dwSize + 0xFFF) & 0xFFFFF000) <= dwStackLimit
    		|| (dwAddr & 0xFFFFF000) >= dwStackBase)
    	{
    		return TRUE;
    	}
    
    	return FALSE;
    }
    

3.Caller checks

​ 阻止掉直接通过一个RET指令来调用的判定/关键函数的执行,确保当到达一个关键函数时,它是通过 CALL 指令而不是 RET 指令到达的

3.1 常见的call指令格式

指令 二进制形式
CALL rel32 E8 xx xx xx xx
CALL dword ptr [EAX] FF 10
CALL dword ptr [ECX] FF 11
CALL dword ptr [EDX] FF 12
CALL dword ptr [EBX] FF 13
CALL dword ptr [REG*SCALE+BASE] FF 14 xx
CALL dword ptr [abs32] FF 15 xx xx xx xx
CALL dword ptr [ESI] FF 16
CALL dword ptr [EDI] FF 17
CALL dword ptr [EAX+xx] FF 50 xx
CALL dword ptr [ECX+xx] FF 51 xx
CALL dword ptr [EDX+xx] FF 52 xx
CALL dword ptr [EBX+xx] FF 53 xx
CALL dword ptr [REG*SCALE+BASE+off8] FF 54 xx xx
CALL dword ptr [EBP+xx] FF 55 xx
CALL dword ptr [ESI+xx] FF 56 xx
CALL dword ptr [EDI+xx] FF 57 xx
CALL dword ptr [EAX+xxxxxxxx] FF 90 xx xx xx xx
CALL dword ptr [ECX+xxxxxxxx] FF 91 xx xx xx xx
CALL dword ptr [EDX+xxxxxxxx] FF 92 xx xx xx xx
CALL dword ptr [EBX+xxxxxxxx] FF 93 xx xx xx xx
CALL dword ptr [REG*SCALE+BASE+off32] FF 94 xx xx xx xx xx
CALL dword ptr [EBP+xxxxxxxx] FF 95 xx xx xx xx
CALL dword ptr [ESI+xxxxxxxx] FF 96 xx xx xx xx
CALL dword ptr [EDI+xxxxxxxx] FF 97 xx xx xx xx
CALL EAX FF D0
CALL ECX FF D1
CALL EDX FF D2
CALL EBX FF D3
CALL ESP FF D4
CALL EBP FF D5
CALL ESI FF D6
CALL EDI FF D7
CALL FAR seg16:abs32 9A xx xx xx xx xx xx

3.2 Caller checks实现

  1. 对所有关键函数进行InlineHook:

    {"kernel32.LoadLibraryA", 1, 0x3D4CE},
    {"kernel32.LoadLibraryW", 1, 0x3D4CE},
    {"kernel32.LoadLibraryExA", 3, 0x3D4CE},
    {"kernel32.LoadLibraryExW", 3, 0x3D4CE},
    {"kernelbase.LoadLibraryExA", 3, 0x3D4CF},
    {"kernelbase.LoadLibraryExW", 3, 0x3D4CF},
    {"kernel32.LoadPackagedLibrary", 2, 0x14CE},
    {"ntdll.LdrLoadDll", 4, 0x14CF},
    {"kernel32.VirtualAlloc", 4, 0X14C2},
    {"kernel32.VirtualAllocEx", 5, 0X14C2},
    {"kernelbase.VirtualAlloc", 4, 0X14C3},
    {"kernelbase.VirtualAllocEx", 5, 0X14C3},
    {"ntdll.NtAllocateVirtualMemory", 6, 0X14C3},
    {"kernel32.VirtualProtect", 4, 0X14F2},
    {"kernel32.VirtualProtectEx", 5, 0X14F2},
    {"kernelbase.VirtualProtect", 4, 0X14F3},
    {"kernelbase.VirtualProtectEx", 5, 0X14F3},
    {"ntdll.NtProtectVirtualMemory", 5, 0X14F3},
    {"kernel32.HeapCreate", 3, 0X14C2},
    {"kernelbase.HeapCreate", 3, 0X14C3},
    {"ntdll.RtlCreateHeap", 6, 0X14C3},
    {"kernel32.CreateProcessA", 10, 0X14C2},
    {"kernel32.CreateProcessW", 10, 0X14C2},
    {"kernel32.CreateProcessInternalA", 12, 0X14C2},
    {"kernel32.CreateProcessInternalW", 12, 0X14C2},
    {"ntdll.NtCreateUserProcess", 11, 0x14C3},
    {"ntdll.NtCreateProcess", 8, 0X14C3},
    {"ntdll.NtCreateProcessEx", 9, 0X14C3},
    {"kernel32.CreateRemoteThread", 7, 0X14C2},
    {"kernel32.CreateRemoteThreadEx", 8, 0X14C2},
    {"kernelbase.CreateRemoteThreadEx", 8, 0X14C3},
    {"ntdll.NtCreateThreadEx", 11, 0X14C3},
    {"kernel32.WriteProcessMemory", 5, 0X14C2},
    {"kernelbase.WriteProcessMemory", 5, 0X14C3},
    {"ntdll.NtWriteVirtualMemory", 5, 0X14C3},
    {"kernel32.WinExec", 2, 0x14C2},
    {"kernel32.CreateFileA", 7, 0x14C2},
    {"kernel32.CreateFileW", 7, 0x14C2},
    {"kernelbase.CreateFileW", 7, 0x14C3},
    {"ntdll.NtCreateFile", 11, 0x14C3},
    {"kernel32.CreateFileMappingA", 6, 0x14C2},
    {"kernel32.CreateFileMappingW", 6, 0X14C2},
    {"kernelbase.CreateFileMappingW", 6, 0X14C3},
    {"kernelbase.CreateFileMappingNumaW", 7, 0X14C3},
    {"ntdll.NtCreateSection", 7, 0X14C3},
    {"kernel32.MapViewOfFile", 5, 0X14C2},
    {"kernel32.MapViewOfFileEx", 6, 0X14C2},
    {"kernelbase.MapViewOfFile", 5, 0X14C3},
    {"kernelbase.MapViewOfFileEx", 6, 0X14C3},
    {"kernel32.MapViewOfFileFromApp", 4, 0X14C3}
    
  2. 当程序调用关键函数时,跳转至DetourFunction中进行拦截,对关键函数的返回地址进行验证,如果返回地址的前一条指令是call指令,而且目标跳转地址是关键函数的地址,那么则通过验证。反之,如果返回地址的前一条指令不是call,或者目标跳转地址不是关键函数的地址,都认为检测到ROP

    //所有可能的call指令的长度
    DWORD g_CallInstructionLen[CALL_TYPE] = { 6, 5, 2, 3, 7, 4 };
    
    BOOL Caller(PHOOKINFO pHookInfo) {
    	for (int i = 0; i < CALL_TYPE; i++) {
    		DWORD dwCallLen = g_CallInstructionLen[i];
    		DWORD dwDisAsmAddr = pHookInfo->dwRetAddr + dwCallLen;
    		UINT uCodeSize = 0;
    		char szASM[0x100] = { 0 };
    		Decode2Asm((PBYTE)dwDisAsmAddr, szASM, &uCodeSize, (UINT)dwDisAsmAddr);
    
    		if (strstr(szASM, "call") != NULL) {
    			if (uCodeSize == i) {
    				return TRUE;
    			}
    		}
    	}
    	//指令不是call
    	ErrorReport();
    	return FALSE;
    }
    

4.Simulate execution flow

​ 通过观察可以发现,大多数ROP gadgets是以RETN结尾的短序列指令,那么可以尝试在关键函数返回后模拟少量指令的执行,由此可以在不依赖帧指针的情况下检查出调用关键函数后会发生什么。

​ 此方法仅模拟改变堆栈的指令(PUSH、POP、ADD ESP, #、 SUB ESP, #、RETN ),并跟踪这些指令对堆栈指针值的修改,其它指令只是简单的跳过。一旦遇到RETN指令,将检查返回地址是否可执行以及返回地址处的指令是否在调用指令之前。

​ 通过这种方式,不仅可以检查当前关键函数的返回地址,而且如果当前的关键函数是从RETN结束的ROP gadgets中调用的,那么还可以检查到后续的返回地址。当模拟到配置文件中指定的指令数或者遇到改变执行流的指令(RETN除外)时,模拟执行停止。

4.1 Simulate execution flow实现

  1. 定义一个数据结构,用于表示模拟的上下文环境

    typedef struct _SIMULATE_CONTEXT
    {
    	DWORD UNKONWN;
    	DWORD EBP;
    	DWORD ESP;
    	DWORD EIP;
    }SIMULATE_CONTEXT;
    
  2. 定义一个枚举类型,用于表示指令的控制流类型

    typedef enum _EXE_FLOW_TYPE
    {
     EXE_FLOW_ERROR  = 0;
     EXE_FLOW_BRANCH = 1;    //call或者jmp跳转指令
     EXE_FLOW_SEQ     = 2;   //顺序执行指令
     EXE_FLOW_RETURN = 3;    //返回指令
    }
    
  3. 定义一个枚举类型,用于表示指令的类型

    typedef enum _INSTRUCTION_TYPE
    {
    	ERROR_INS = 0,
    	CALL_INS,                  //CALL指令
    	JMP_INS,
    	MOV_ESP_EBP_INS,
    	PUSH_INS,                  //PUSH指令
    	POP_INS,                   //一般的POP指令
    	POP_ESP_INS,               //POP ESP指令
    	POP_EBP_INS,               //POP EBP指令
    	ADD_ESP_INS,               //ADD ESP,XXX指令
    	ADD_EBP_INS,               //ADD EBP,XXX指令
    	SUB_ESP_INS,               //SUB ESP,XXX指令
    	SUB_EBP_INS,               //SUB EBP,XXX指令
    	LEAVE_INS,                 //LEAVE指令
    	RETURN_INS,                //RETURN指令
    }INSTURCTION_TYPE;
    
  4. 处理每一条指令的过程代码如下:

    EXE_FLOW_TYPE SimulateExeFlow(SIMULATE_CONTEXT *SimulateContext)
    {
    	EXE_FLOW_TYPE FlowType = EXE_FLOW_SEQ;
    
    	DWORD dwDisAsmAddr = SimulateContext->EIP;
    	UINT uCodeSize = 0;
    
    	char szASM[0x100] = { 0 };
    	Decode2Asm((PBYTE)dwDisAsmAddr, szASM, &uCodeSize, (UINT)dwDisAsmAddr);
    	INSTURCTION_TYPE InstructionType = GetInstructionType(szASM);
    	
    	switch (InstructionType)
    	{
    	case ERROR_INS:
    		//如果解码失败
    		FlowType = EXE_FLOW_ERROR;
    		break;
    	case CALL_INS:
    	case JMP_INS:
    		FlowType = EXE_FLOW_BRANCH;
    		break;
    	case MOV_ESP_EBP_INS:
    		SimulateContext->ESP = SimulateContext->EBP;
    		break;
    	case PUSH_INS:
    		SimulateContext->ESP = SimulateContext->ESP - 4;
    		break;
    	case POP_INS :
    		SimulateContext->ESP = SimulateContext->ESP + 4;
    		break;
    	case POP_EBP_INS :
    		SimulateContext->EBP = SimulateContext->ESP;
    		SimulateContext->ESP = SimulateContext->ESP + 4;
    		break;
    	case POP_ESP_INS :
    		SimulateContext->ESP = SimulateContext->ESP;
    		SimulateContext->ESP = SimulateContext->ESP + 4;    //此处值得商榷,按说POP ESP之后,无须再对新的 
    															//ESP进行调整但EMET的代码里确实是真么做的-_-!!
    		break;
    	case LEAVE_INS :
    		SimulateContext->ESP = SimulateContext->EBP;
    		SimulateContext->EBP = SimulateContext->ESP;
    		SimulateContext->ESP = SimulateContext->ESP + 4;
    		break;
    	case RETURN_INS :
    		FlowType = EXE_FLOW_RETURN;
    		dwDisAsmAddr = SimulateContext->ESP;
    		SimulateContext->ESP = SimulateContext->ESP + 4;
    		break;
    	}
    	//根据指令类型,修改EIP
    	if (EXE_FLOW_SEQ == FlowType)
    	{
    		//如果顺序执行,那么下一条指令地址,为当前EIP加上指令长度
    		SimulateContext->EIP = SimulateContext->EIP + uCodeSize;
    	}
    	else if (EXE_FLOW_RETURN == FlowType)
    	{
    		//如果是返回指令,那么EIP更新为返回地址
    		SimulateContext->EIP = dwDisAsmAddr;
    	}
    	return FlowType;
    }
    

    重点逻辑代码如下:

  5. 规定模拟执行15条指令,进入循环,处理每一条指令,若指令为Ret类型,检查返回地址是否可执行以及返回地址处的指令是否在调用指令之前

    BOOL SimExecFlow(PHOOKINFO pHookInfo) {
    	SIMULATE_CONTEXT *SimulateContext;
    	SimulateContext->EBP = pHookInfo->dwRetAddr;
    	SimulateContext->EIP = pHookInfo->dwRetAddr;
    	SimulateContext->ESP = pHookInfo->dwArgAddr + pHookInfo->dwArgc * 4;
    
    	for (int i = 0; i < 15; i++)
    	{
    		EXE_FLOW_TYPE FlowType = SimulateExeFlow(SimulateContext);
    		if (EXE_FLOW_ERROR == FlowType || EXE_FLOW_BRANCH == FlowType)
    		{
    			//如果模拟执行的是跳转指令,或者函数SimulateExeFlow函数执行错误,则返回TRUE
    			return TRUE;
    		}
    		else if (EXE_FLOW_RETURN == FlowType)
    		{
    			//如果模拟执行的是ret/ret n指令,则需要对返回地址的有效性进行检验,这里主要是判断
    			//返回地址的前一条指令是否为call指令
    			//在这里还可以判断该地址是否可以执行
    			if (FALSE == Caller(SimulateContext->EIP) || FALSE == IsExecutableAddress((LPVOID)SimulateContext->EIP))
    			{
    				return FALSE;
    			}
    		}
    	}
    	return TRUE;
    }
    

5.Stack pivot

​ 对当前ESP中保存的栈顶指针进行检验。如果发现ESP指向的地址,不在当前线程的栈地址范围内,就认为检测到ROP

5.1 Stack pivot实现

  1. 对所有关键函数进行InlineHook:

    {"kernel32.LoadLibraryA", 1, 0x3D4CE},
    {"kernel32.LoadLibraryW", 1, 0x3D4CE},
    {"kernel32.LoadLibraryExA", 3, 0x3D4CE},
    {"kernel32.LoadLibraryExW", 3, 0x3D4CE},
    {"kernelbase.LoadLibraryExA", 3, 0x3D4CF},
    {"kernelbase.LoadLibraryExW", 3, 0x3D4CF},
    {"kernel32.LoadPackagedLibrary", 2, 0x14CE},
    {"ntdll.LdrLoadDll", 4, 0x14CF},
    {"kernel32.VirtualAlloc", 4, 0X14C2},
    {"kernel32.VirtualAllocEx", 5, 0X14C2},
    {"kernelbase.VirtualAlloc", 4, 0X14C3},
    {"kernelbase.VirtualAllocEx", 5, 0X14C3},
    {"ntdll.NtAllocateVirtualMemory", 6, 0X14C3},
    {"kernel32.VirtualProtect", 4, 0X14F2},
    {"kernel32.VirtualProtectEx", 5, 0X14F2},
    {"kernelbase.VirtualProtect", 4, 0X14F3},
    {"kernelbase.VirtualProtectEx", 5, 0X14F3},
    {"ntdll.NtProtectVirtualMemory", 5, 0X14F3},
    {"kernel32.HeapCreate", 3, 0X14C2},
    {"kernelbase.HeapCreate", 3, 0X14C3},
    {"ntdll.RtlCreateHeap", 6, 0X14C3},
    {"kernel32.CreateProcessA", 10, 0X14C2},
    {"kernel32.CreateProcessW", 10, 0X14C2},
    {"kernel32.CreateProcessInternalA", 12, 0X14C2},
    {"kernel32.CreateProcessInternalW", 12, 0X14C2},
    {"ntdll.NtCreateUserProcess", 11, 0x14C3},
    {"ntdll.NtCreateProcess", 8, 0X14C3},
    {"ntdll.NtCreateProcessEx", 9, 0X14C3},
    {"kernel32.CreateRemoteThread", 7, 0X14C2},
    {"kernel32.CreateRemoteThreadEx", 8, 0X14C2},
    {"kernelbase.CreateRemoteThreadEx", 8, 0X14C3},
    {"ntdll.NtCreateThreadEx", 11, 0X14C3},
    {"kernel32.WriteProcessMemory", 5, 0X14C2},
    {"kernelbase.WriteProcessMemory", 5, 0X14C3},
    {"ntdll.NtWriteVirtualMemory", 5, 0X14C3},
    {"kernel32.WinExec", 2, 0x14C2},
    {"kernel32.CreateFileA", 7, 0x14C2},
    {"kernel32.CreateFileW", 7, 0x14C2},
    {"kernelbase.CreateFileW", 7, 0x14C3},
    {"ntdll.NtCreateFile", 11, 0x14C3},
    {"kernel32.CreateFileMappingA", 6, 0x14C2},
    {"kernel32.CreateFileMappingW", 6, 0X14C2},
    {"kernelbase.CreateFileMappingW", 6, 0X14C3},
    {"kernelbase.CreateFileMappingNumaW", 7, 0X14C3},
    {"ntdll.NtCreateSection", 7, 0X14C3},
    {"kernel32.MapViewOfFile", 5, 0X14C2},
    {"kernel32.MapViewOfFileEx", 6, 0X14C2},
    {"kernelbase.MapViewOfFile", 5, 0X14C3},
    {"kernelbase.MapViewOfFileEx", 6, 0X14C3},
    {"kernel32.MapViewOfFileFromApp", 4, 0X14C3}
    
  2. 当程序调用关键函数时,跳转至DetourFunction中进行拦截,对关键函数的ESP中保存的栈顶指针进行检验。如果发现ESP指向的地址,不在当前线程的栈地址范围内,就认为检测到ROP

    BOOL StackPivot(DWORD dwRetAddr, DWORD dwOriginalAPIAddr, PHOOKINFO hookInfo) {
    	PNT_TIB pTib = (PNT_TIB)NtCurrentTeb();
    	DWORD dwStackLimit = (DWORD)pTib->StackLimit;
    	DWORD dwStackBase = (DWORD)pTib->StackBase;
    
    	if (hookInfo->dwArgAddr > dwStackBase || hookInfo->dwArgAddr < dwStackLimit) {
    		ErrorReport();
    	}
    
    	return 0;
    }
    

参考

博客

中级ROP - CTF Wiki (ctf-wiki.org)

​ [翻译]返回导向编程(ROP)检测技术ROPGuard-编程技术-看雪论坛-安全社区|安全招聘|bbs.pediy.com

(74条消息) 浅析EMET 3.5中的ROP Mitigation_usertony的博客-CSDN博客

github

GitHub - ivanfratric/ropguard: Runtime Prevention of Return-Oriented Programming Attacks

posted @ 2024-03-03 00:04  修竹Kirakira  阅读(22)  评论(0编辑  收藏  举报