// // Jonathan Salwan - Copyright (C) 2013-08 // // http://shell-storm.org // http://twitter.com/JonathanSalwan // // Note: Example 5 - http://shell-storm.org/blog/Taint-analysis-with-Pin/ // Detect the classical use after free vulnerability // #include "pin.H" #include #include #include #include #define LOCKED 1 #define UNLOCKED !LOCKED #define ALLOCATE 1 #define FREE !ALLOCATE #define TRICKS(){if (tryksOpen++ == 0)return;} #define checkLockTaint(){if(lockTaint)return;} static size_t lastSize; static unsigned int tryksOpen; struct mallocArea { UINT64 base; UINT64 size; BOOL status; }; UINT32 lockTaint = LOCKED; std::list addressTainted; std::list regsTainted; std::list mallocAreaList; KNOB KnobTaintFunc(KNOB_MODE_WRITEONCE, "pintool", "taintFunction", "main", "Taint specific function"); INT32 Usage() { std::cerr << "Ex 5" << std::endl; std::cerr << std::endl << KNOB_BASE::StringKnobSummary() << std::endl; return -1; } BOOL checkAlreadyRegTainted(REG reg) { list::iterator i; for(i = regsTainted.begin(); i != regsTainted.end(); i++){ if (*i == reg){ return true; } } return false; } VOID removeMemTainted(UINT64 addr) { addressTainted.remove(addr); std::cout << std::hex << "\t\t\t" << addr << " is now freed" << std::endl; } VOID addMemTainted(UINT64 addr) { addressTainted.push_back(addr); std::cout << std::hex << "\t\t\t" << addr << " is now tainted" << std::endl; } BOOL taintReg(REG reg) { if (checkAlreadyRegTainted(reg) == true){ std::cout << "\t\t\t" << REG_StringShort(reg) << " is already tainted" << std::endl; return false; } switch(reg){ case REG_RAX: regsTainted.push_front(REG_RAX); case REG_EAX: regsTainted.push_front(REG_EAX); case REG_AX: regsTainted.push_front(REG_AX); case REG_AH: regsTainted.push_front(REG_AH); case REG_AL: regsTainted.push_front(REG_AL); break; case REG_RBX: regsTainted.push_front(REG_RBX); case REG_EBX: regsTainted.push_front(REG_EBX); case REG_BX: regsTainted.push_front(REG_BX); case REG_BH: regsTainted.push_front(REG_BH); case REG_BL: regsTainted.push_front(REG_BL); break; case REG_RCX: regsTainted.push_front(REG_RCX); case REG_ECX: regsTainted.push_front(REG_ECX); case REG_CX: regsTainted.push_front(REG_CX); case REG_CH: regsTainted.push_front(REG_CH); case REG_CL: regsTainted.push_front(REG_CL); break; case REG_RDX: regsTainted.push_front(REG_RDX); case REG_EDX: regsTainted.push_front(REG_EDX); case REG_DX: regsTainted.push_front(REG_DX); case REG_DH: regsTainted.push_front(REG_DH); case REG_DL: regsTainted.push_front(REG_DL); break; case REG_RDI: regsTainted.push_front(REG_RDI); case REG_EDI: regsTainted.push_front(REG_EDI); case REG_DI: regsTainted.push_front(REG_DI); case REG_DIL: regsTainted.push_front(REG_DIL); break; case REG_RSI: regsTainted.push_front(REG_RSI); case REG_ESI: regsTainted.push_front(REG_ESI); case REG_SI: regsTainted.push_front(REG_SI); case REG_SIL: regsTainted.push_front(REG_SIL); break; default: std::cout << "\t\t\t" << REG_StringShort(reg) << " can't be tainted" << std::endl; return false; } std::cout << "\t\t\t" << REG_StringShort(reg) << " is now tainted" << std::endl; return true; } BOOL removeRegTainted(REG reg) { switch(reg){ case REG_RAX: regsTainted.remove(REG_RAX); case REG_EAX: regsTainted.remove(REG_EAX); case REG_AX: regsTainted.remove(REG_AX); case REG_AH: regsTainted.remove(REG_AH); case REG_AL: regsTainted.remove(REG_AL); break; case REG_RBX: regsTainted.remove(REG_RBX); case REG_EBX: regsTainted.remove(REG_EBX); case REG_BX: regsTainted.remove(REG_BX); case REG_BH: regsTainted.remove(REG_BH); case REG_BL: regsTainted.remove(REG_BL); break; case REG_RCX: regsTainted.remove(REG_RCX); case REG_ECX: regsTainted.remove(REG_ECX); case REG_CX: regsTainted.remove(REG_CX); case REG_CH: regsTainted.remove(REG_CH); case REG_CL: regsTainted.remove(REG_CL); break; case REG_RDX: regsTainted.remove(REG_RDX); case REG_EDX: regsTainted.remove(REG_EDX); case REG_DX: regsTainted.remove(REG_DX); case REG_DH: regsTainted.remove(REG_DH); case REG_DL: regsTainted.remove(REG_DL); break; case REG_RDI: regsTainted.remove(REG_RDI); case REG_EDI: regsTainted.remove(REG_EDI); case REG_DI: regsTainted.remove(REG_DI); case REG_DIL: regsTainted.remove(REG_DIL); break; case REG_RSI: regsTainted.remove(REG_RSI); case REG_ESI: regsTainted.remove(REG_ESI); case REG_SI: regsTainted.remove(REG_SI); case REG_SIL: regsTainted.remove(REG_SIL); break; default: return false; } std::cout << "\t\t\t" << REG_StringShort(reg) << " is now freed" << std::endl; return true; } VOID ReadMem(INS ins, UINT64 memOp, ADDRINT sp) { list::iterator i; list::iterator i2; UINT64 addr = memOp; REG reg_r; checkLockTaint(); if (INS_OperandCount(ins) != 2) return; for(i2 = mallocAreaList.begin(); i2 != mallocAreaList.end(); i2++){ if (addr >= i2->base && addr < (i2->base + i2->size) && i2->status == FREE){ std::cout << std::hex << "[UAF in " << addr << "]\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl; return; } } reg_r = INS_OperandReg(ins, 0); for(i = addressTainted.begin(); i != addressTainted.end(); i++){ if (addr == *i){ std::cout << std::hex << "[READ in " << addr << "]\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl; taintReg(reg_r); if (sp > addr && addr > 0x700000000000) std::cout << std::hex << "[UAF in " << addr << "]\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl; return; } } /* if mem != tained and reg == taint => free the reg */ if (checkAlreadyRegTainted(reg_r)){ std::cout << std::hex << "[READ in " << addr << "]\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl; removeRegTainted(reg_r); } } VOID WriteMem(INS ins, UINT64 memOp, ADDRINT sp) { list::iterator i; list::iterator i2; UINT64 addr = memOp; REG reg_r; checkLockTaint(); if (INS_OperandCount(ins) != 2) return; for(i2 = mallocAreaList.begin(); i2 != mallocAreaList.end(); i2++){ if (addr >= i2->base && addr < (i2->base + i2->size) && i2->status == FREE){ std::cout << std::hex << "[UAF in " << addr << "]\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl; return; } } reg_r = INS_OperandReg(ins, 1); for(i = addressTainted.begin(); i != addressTainted.end(); i++){ if (addr == *i){ std::cout << std::hex << "[WRITE in " << addr << "]\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl; if (!REG_valid(reg_r) || !checkAlreadyRegTainted(reg_r)) removeMemTainted(addr); if (sp > addr && addr > 0x700000000000) std::cout << std::hex << "[UAF in " << addr << "]\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl; return ; } } if (checkAlreadyRegTainted(reg_r)){ std::cout << std::hex << "[WRITE in " << addr << "]\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl; addMemTainted(addr); } } VOID spreadRegTaint(INS ins) { REG reg_r, reg_w; checkLockTaint(); if (INS_OperandCount(ins) != 2) return; reg_r = INS_RegR(ins, 0); reg_w = INS_RegW(ins, 0); if (REG_valid(reg_w)){ if (checkAlreadyRegTainted(reg_w) && (!REG_valid(reg_r) || !checkAlreadyRegTainted(reg_r))){ std::cout << "[SPREAD]\t\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl; std::cout << "\t\t\toutput: "<< REG_StringShort(reg_w) << " | input: " << (REG_valid(reg_r) ? REG_StringShort(reg_r) : "constant") << std::endl; removeRegTainted(reg_w); } else if (!checkAlreadyRegTainted(reg_w) && checkAlreadyRegTainted(reg_r)){ std::cout << "[SPREAD]\t\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl; std::cout << "\t\t\toutput: " << REG_StringShort(reg_w) << " | input: "<< REG_StringShort(reg_r) << std::endl; taintReg(reg_w); } } } VOID followData(INS ins) { REG reg; checkLockTaint(); reg = INS_RegR(ins, 0); if (!REG_valid(reg)) return; if (checkAlreadyRegTainted(reg)){ std::cout << "[FOLLOW]\t\t" << INS_Address(ins) << ": " << INS_Disassemble(ins) << std::endl; } } VOID Instruction(INS ins, VOID *v) { if (INS_OperandCount(ins) > 1 && INS_MemoryOperandIsRead(ins, 0) && INS_OperandIsReg(ins, 0)){ INS_InsertCall( ins, IPOINT_BEFORE, (AFUNPTR)ReadMem, IARG_PTR, ins, IARG_MEMORYOP_EA, 0, IARG_REG_VALUE, REG_STACK_PTR, IARG_END); } else if (INS_OperandCount(ins) > 1 && INS_MemoryOperandIsWritten(ins, 0)){ INS_InsertCall( ins, IPOINT_BEFORE, (AFUNPTR)WriteMem, IARG_PTR, ins, IARG_MEMORYOP_EA, 0, IARG_REG_VALUE, REG_STACK_PTR, IARG_END); } else if (INS_OperandCount(ins) > 1 && INS_OperandIsReg(ins, 0)){ INS_InsertCall( ins, IPOINT_BEFORE, (AFUNPTR)spreadRegTaint, IARG_PTR, ins, IARG_END); } if (INS_OperandCount(ins) > 1 && INS_OperandIsReg(ins, 0)){ INS_InsertCall( ins, IPOINT_BEFORE, (AFUNPTR)followData, IARG_PTR, ins, IARG_END); } } VOID Syscall_entry(THREADID thread_id, CONTEXT *ctx, SYSCALL_STANDARD std, void *v) { unsigned int i; UINT64 start, size; if (PIN_GetSyscallNumber(ctx, std) == __NR_read){ TRICKS(); /* tricks to ignore the first open */ start = static_cast((PIN_GetSyscallArgument(ctx, std, 1))); size = static_cast((PIN_GetSyscallArgument(ctx, std, 2))); for (i = 0; i < size; i++) addressTainted.push_back(start+i); std::cout << "[TAINT]\t\t\tbytes tainted from " << std::hex << "0x" << start << " to 0x" << start+size << " (via read)"<< std::endl; } } VOID callbackBeforeMalloc(ADDRINT size) { checkLockTaint(); lastSize = size; } VOID callbackBeforeFree(ADDRINT addr) { list::iterator i; checkLockTaint(); std::cout << "[INFO]\t\tfree(" << std::hex << addr << ")" << std::endl; for(i = mallocAreaList.begin(); i != mallocAreaList.end(); i++){ if (addr == i->base){ i->status = FREE; break; } } } VOID callbackAfterMalloc(ADDRINT ret) { list::iterator i; struct mallocArea elem; checkLockTaint(); std::cout << "[INFO]\t\tmalloc(" << lastSize << ") = " << std::hex << ret << std::endl; if (ret){ for(i = mallocAreaList.begin(); i != mallocAreaList.end(); i++){ if (ret == i->base){ i->status = ALLOCATE; i->size = lastSize; return; } } elem.base = ret; elem.size = lastSize; elem.status = ALLOCATE; mallocAreaList.push_front(elem); } } VOID startTaint(CHAR *name) { lockTaint = UNLOCKED; std::cout << "[INFO]\t\tThe " << name << " function is called. The taint analysis begins." << std::endl; } VOID stopTaint(CHAR *name) { lockTaint = LOCKED; std::cout << "[INFO]\t\tThe " << name << " function is over. The taint analysis stop." << std::endl; } VOID Image(IMG img, VOID *v) { const char *optFunc = KnobTaintFunc.Value().c_str(); RTN RtnFunc = RTN_FindByName(img, optFunc); RTN mallocRtn = RTN_FindByName(img, "malloc"); RTN freeRtn = RTN_FindByName(img, "free"); if (RTN_Valid(mallocRtn)){ RTN_Open(mallocRtn); RTN_InsertCall( mallocRtn, IPOINT_BEFORE, (AFUNPTR)callbackBeforeMalloc, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_END); RTN_InsertCall( mallocRtn, IPOINT_AFTER, (AFUNPTR)callbackAfterMalloc, IARG_FUNCRET_EXITPOINT_VALUE, IARG_END); RTN_Close(mallocRtn); } if (RTN_Valid(freeRtn)){ RTN_Open(freeRtn); RTN_InsertCall( freeRtn, IPOINT_BEFORE, (AFUNPTR)callbackBeforeFree, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_END); RTN_Close(freeRtn); } if (RTN_Valid(RtnFunc)){ RTN_Open(RtnFunc); RTN_InsertCall( RtnFunc, IPOINT_BEFORE, (AFUNPTR)startTaint, IARG_ADDRINT, optFunc, IARG_END); RTN_InsertCall( RtnFunc, IPOINT_AFTER, (AFUNPTR)stopTaint, IARG_ADDRINT, optFunc, IARG_END); RTN_Close(RtnFunc); } } int main(int argc, char *argv[]) { PIN_InitSymbols(); if(PIN_Init(argc, argv)){ return Usage(); } PIN_SetSyntaxIntel(); IMG_AddInstrumentFunction(Image, 0); //PIN_AddSyscallEntryFunction(Syscall_entry, 0); INS_AddInstrumentFunction(Instruction, 0); PIN_StartProgram(); return 0; }