-
-
[原创] Pintools 之 Shadow Stack 编写
-
2020-9-6 16:21 5579
-
Pintools之Shadow stack编写
前言
本文是对加州大学滨州分校cs260实验二的作答.shadow stack是一种防护机制,保护函数返回值地址被覆写.阅读本篇文章前要求对Intel Pin有一定的了解.
测试代码
// example01.c #include <stdio.h> #include <string.h> int IsPasswordOkay(void) { char Password[12]; gets(Password); if (!strcmp(Password, "goodpass")) return 1; return 0; } int main(void) { int PwStatus; puts("Enter password:"); PwStatus = IsPasswordOkay(); if (PwStatus == 0) { puts("Access denied"); return -1; } puts("Access granted"); return 0; }
版本一
思路
根据实验指导,初步的shadow stack 运行原理如下:
- 执行call指令时,将其下条指令地址push入shadow stack中
- 遇到ret指令时,取出esp指向的内存中值,与shadow stack的栈顶比较. 如果相等,则继续执行;如果不相等,则存在返回值地址的覆盖.
代码
// 版本一 #include <iostream> #include <fstream> #include <unistd.h> #include <cstdlib> #include <vector> #include "pin.H" using std::cerr; using std::ofstream; using std::ios; using std::string; using std::endl; using std::hex; using std::cout; std::vector<unsigned int> stack; VOID call_ins_call(ADDRINT ip, UINT32 size){ // 入栈 UINT32 next_eip = ip + size; stack.push_back(next_eip); } VOID ret_ins_call(CONTEXT * ctxt){ VOID * current_esp = (VOID *)PIN_GetContextReg(ctxt, REG_STACK_PTR); UINT32 current_eip = (UINT32)(*(int *)current_esp); if(stack.size() > 0){ // 出栈并比较 UINT32 original_ret_value = (UINT32)stack.back(); stack.pop_back(); if(original_ret_value != current_eip){ cout << "control flow jacking is happending" << endl; exit(1); } } else { cout << "stack is empty" << endl; } } VOID Trace(TRACE trace, VOID *v) { // Visit every basic block in the trace for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) { for(INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins)){ // 是call 指令 if(INS_IsCall(ins)){ UINT32 size = INS_Size(ins); INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)call_ins_call, IARG_INST_PTR, IARG_UINT32, size, IARG_END); } // 是 ret指令 if(INS_IsRet(ins)){ INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)ret_ins_call, IARG_CONTEXT, IARG_END); } } } } // This function is called when the application exits VOID Fini(INT32 code, VOID *v) { cout << "Over" << endl; } /* ===================================================================== */ /* Print Help Message */ /* ===================================================================== */ INT32 Usage() { cerr << "This tool is used to detect control flow jacking" << endl; return -1; } /* ===================================================================== */ /* Main */ /* ===================================================================== */ int main(int argc, char * argv[]) { // Initialize symbol process PIN_InitSymbols(); // Initialize pin if (PIN_Init(argc, argv)) { return Usage(); } // 注册Trace粒度回调函数 TRACE_AddInstrumentFunction(Trace, 0); // Register Fini to be called when the application exits PIN_AddFiniFunction(Fini, 0); // Start the program, never returns PIN_StartProgram(); return 0; }
测试
# 编译 example01 gcc -g -fno-stack-protector -z execstack -o example01 example01.c # 编译pin tools 之 shadow stack, 将上述代码替换source/tools/ManualExamples/inscount0.cpp中的内容 make # 测试 # 正常情况 path/pin -t ./inscount0.so -- ./example01 Enter password: 11111111 Access denied # 覆写情况 Enter password: 111111111111111111111111111111111111 control flow jacking is happending # core dump情况 Enter password: 11111111111111 Over Segmentation fault (core dumped) # 说明 # 只有覆盖了返回值地址才会被检测出来,如果长度不够,指挥core dump.
版本二
思路
版本一代码只适合检测单线程情况下的覆写,多线程情况下会更加复杂的入栈和出栈操作.
如: thread1 : call fun1, thread2 : call fun2. 分别将thread1和thread2的返回值地址ret_Address1和ret_Address2依次压入stack中。此时栈顶为ret_Address2,但若线程切换至thread1, 并执行ret指令,就会出现不匹配的情况,但此时并不是将返回值地址覆写的情况. 为此,我们需要为每个线程设置一个shadow stack, 其余操作和版本一相同,部分代码做了优化.
代码
#include <iostream> #include <fstream> #include <unistd.h> #include <cstdlib> #include <vector> #include <stack> #include "pin.H" using std::cerr; using std::ofstream; using std::ios; using std::string; using std::endl; using std::hex; using std::cout; using std::stack; static TLS_KEY tlsKey; VOID call_ins_call(ADDRINT ip, UINT32 size, THREADID tid){ stack<ADDRINT> *local_stack = static_cast<stack <ADDRINT> *> (PIN_GetThreadData(tlsKey, tid)); ADDRINT next_eip = ip + size; local_stack->push(next_eip); } VOID ret_ins_call(ADDRINT retAddr, THREADID tid, CONTEXT * ctxt){ stack<ADDRINT> *local_stack = static_cast <stack <ADDRINT> *> (PIN_GetThreadData(tlsKey, tid)); if(local_stack -> size() > 0){ ADDRINT original_ret_value = local_stack -> top(); local_stack -> pop(); if(original_ret_value != retAddr){ cout << "control flow jacking is happending" << endl; exit(1); } } else { cout << "stack is empty" << endl; } } VOID Trace(TRACE trace, VOID *v) { // Visit every basic block in the trace INS ins = BBL_InsTail(TRACE_BblTail(trace)); if(INS_IsCall(ins)){ UINT32 size = INS_Size(ins); INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)call_ins_call, IARG_INST_PTR, IARG_UINT32, size, IARG_THREAD_ID, IARG_END); } if(INS_IsRet(ins)){ INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)ret_ins_call, IARG_BRANCH_TARGET_ADDR, IARG_THREAD_ID, IARG_CONTEXT, IARG_END); } } VOID ThreadStart(THREADID tid, CONTEXT *ctxt, INT32 flags, VOID *v){ // 使用tls存储shadow stack. stack<ADDRINT> *local_stack = new stack<ADDRINT>(); PIN_SetThreadData(tlsKey, local_stack, tid); } VOID ThreadEnd(THREADID tid, const CONTEXT *ctxt, INT32 code, VOID *v){ // 删除 shadow stack stack<ADDRINT> *local_stack = static_cast <stack <ADDRINT> *> (PIN_GetThreadData(tlsKey, tid)); delete(local_stack); } // This function is called when the application exits VOID Fini(INT32 code, VOID *v) { cout << "Over" << endl; } /* ===================================================================== */ /* Print Help Message */ /* ===================================================================== */ INT32 Usage() { cerr << "This tool is used to detect control flow jacking" << endl; return -1; } /* ===================================================================== */ /* Main */ /* ===================================================================== */ int main(int argc, char * argv[]) { // Initialize symbol process PIN_InitSymbols(); // Initialize pin if (PIN_Init(argc, argv)) { return Usage(); } tlsKey = PIN_CreateThreadDataKey(NULL); TRACE_AddInstrumentFunction(Trace, 0); PIN_AddThreadStartFunction(ThreadStart, NULL); // 线程开始回调函数 PIN_AddThreadFiniFunction(ThreadEnd, NULL); // 线程结束回调函数 // Register Fini to be called when the application exits PIN_AddFiniFunction(Fini, 0); // Start the program, never returns PIN_StartProgram(); return 0; }
编译与测试
# 编译 # 生成example01 gcc -g -fno-stack-protector -z execstack -o example01 example01.c # 编译pin tools之shadow stack, 将上述代码替换source/tools/ManualExamples/inscount1.cpp中的内容 make # 测试 # 正常情况 path/pin -t ./inscount1.so -- ./example01 Enter password: 3333333333 Access denied Over # 覆写情况 Enter password: 3333333333333333333333333333333333333333 control flow jacking is happending # core dump情况 Enter password: 33333333333333 Over Segmentation fault (core dumped)
温馨提示
请使用最新的Intel Pin进行实验,否则会出现各种问题.
相关环境 os: ubuntu 20.04 Intel Pin: 3.16
相关链接
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-9-6 16:23
被baolongshou编辑
,原因:
赞赏
他的文章
看原图