劫持__security_check_cookie
本章探索一种劫持__security_check_cookie函数的可能性方法。
__security_cookie 是微软 C++ 编译器 (MSVC) 用于缓冲区溢出保护(也称为堆栈保护或 GS 保护)的一种安全机制,旨在防止软件漏洞被利用导致恶意代码执行。以下是其原理的简要说明:
1. 作用
__security_cookie 是一个在程序运行时生成的随机值(cookie),存储在进程的内存中,用于检测堆栈缓冲区溢出攻击。它主要用于确保函数返回地址和其他关键数据在函数执行期间未被非法修改。
2. 工作原理
-
初始化
-
在程序启动时(通常在模块加载或主函数入口处),运行时库会调用一个初始化函数(例如 __security_init_cookie),生成一个随机值作为 __security_cookie。
-
这个值通常基于系统时间、进程 ID、线程 ID 等信息,通过加密算法生成,具有较高的随机性。
-
__security_cookie 存储在全局变量中,通常位于 .data 段。
-
-
堆栈保护
-
在函数序言(prologue)中,编译器会在堆栈上插入 __security_cookie 的副本(称为“堆栈 cookie”或“canary”),通常位于局部变量和返回地址之间。
-
例如,堆栈布局可能如下:
高地址 | 返回地址 | | 保存的帧指针 | | 堆栈 cookie | | 局部变量 | 低地址
-
-
检查
-
在函数返回前(函数结尾/epilogue),编译器插入代码,比较堆栈上的 cookie 与全局 __security_cookie 的值是否一致。
-
如果一致,说明堆栈未被篡改,函数正常返回。
-
如果不一致,说明可能发生了缓冲区溢出(例如,局部变量溢出覆盖了 cookie),程序会调用 __security_check_failure,通常触发异常或终止程序。
-
3. 实现细节
-
生成随机性:__security_cookie 的值在每次程序运行时都会重新生成,增加攻击者猜测的难度。
-
函数选择:并非所有函数都会插入 cookie 检查。编译器根据函数特性(例如,是否分配了缓冲区、是否使用了危险函数如 strcpy)决定是否启用保护。
-
异常处理:在 Windows 的结构化异常处理 (SEH) 中,__security_cookie 还用于验证异常处理链的完整性,防止攻击者通过伪造异常处理程序来执行恶意代码。
4. 局限性
-
不完全防护:只保护堆栈上的缓冲区溢出,无法防止堆溢出、格式化字符串漏洞或其他攻击。
-
性能开销:插入和检查 cookie 会带来少量性能开销,尽管现代编译器对此优化较好。
-
绕过可能性:如果攻击者能泄露 __security_cookie 的值(例如通过信息泄露漏洞),可能绕过保护。
5. 相关选项
-
在 MSVC 中,堆栈保护通过 /GS 编译选项启用(默认开启)。可以通过 /GS- 禁用,但不推荐用于生产环境。
-
开发者可以使用 __declspec(safebuffers) 控制特定函数是否启用 cookie 检查。
6. 实际应用
-
__security_cookie 是 Windows 平台广泛使用的安全特性,常见于 C/C++ 开发的桌面应用、驱动程序和服务端软件。
-
它是现代软件安全防御体系的一部分,通常与其他机制(如 ASLR、DEP)结合使用。
7. Hijacking
没有栈溢出,__security_check_cookie多此一举,把玩一下!
// 裸体函数(函数开头没有堆栈操作)
//
void _declspec(naked) __stdcall hijack_func()
{// 暂存下一条指令地址__asm {push eax}MessageBoxA(NULL, "Hijack __security_check_cookie!", "HiJack!!!", MB_OK | MB_ICONWARNING);// 取出下一条指令地址,并跳转过去__asm {pop eaxjmp eax}
}int hijack_security_check_cookie()
{// 为了使函数在编译时具有__security_check_cookie堆栈检测,需要在函数里有数组等操作unsigned char buff[16] = {0};strncpy_s((char *)buff, 8, "Hijack!!!", 7);// 获取EIP地址unsigned long eip;__asm {push eaxcall next
next:pop eaxmov eip, eaxpop eax}/* 搜索__security_check_cookie函数__security_check_cookie:003D118A 3B 0D 04 B0 3D 00 cmp ecx,dword ptr [___security_cookie (3DB004h)] 003D1190 75 02 jne failure (3D1194h) 003D1192 F3 C3 rep ret failure:003D1194 E9 39 02 00 00 jmp __report_gsfailure (3D13D2h) ......*/unsigned char *cookie_check = (unsigned char *)eip;while ( cookie_check[0] != 0x3B || cookie_check[1] != 0x0D || cookie_check[8] != 0xF3 || cookie_check[9] != 0xC3) cookie_check++;unsigned long oldprotect = 0;unsigned long bytes = 8;unsigned char rop[] = {0x58, // pop eax; 弹出__security_check_cookie函数返回后的下一条指令地址0x68, 0x90, 0x90, 0x90, 0x90 // push address; 压入新的下一条指令地址};VirtualProtect(cookie_check, bytes, PAGE_EXECUTE_READWRITE, &oldprotect);memset(cookie_check, 0x90, bytes);memcpy(cookie_check, rop, 6);*((unsigned long *)&cookie_check[2]) = (unsigned long)hijack_func; // 替换新的下一条指令地址VirtualProtect(cookie_check, bytes, oldprotect, &oldprotect);return 0;
}int hijack_test()
{hijack_security_check_cookie();hijack_again();return 0;
}
8. 运行结果
程序运行后,依次弹出2次MessageBox