首页
社区
课程
招聘
[原创] Pintools 之 Shadow Stack 编写
2020-9-6 16:21 5579

[原创] 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 运行原理如下:

    1. 执行call指令时,将其下条指令地址push入shadow stack中
    1. 遇到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

相关链接

wikipedia
cs260
intel pin


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-9-6 16:23 被baolongshou编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回