# 13.4 code coverage ## `code_coverage.cc` ### header files ```cpp= #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include "../inc/loader.h" // from Ch.4 #include "triton_util.h" #include "disasm_util.h" #include <string> #include <map> #include <vector> #include <triton/api.hpp> #include <triton/x86Specifications.hpp> ``` ### `main()` 13.3の`main`関数とほぼ同じ。 ただし、9〜10行目、13行目、23行目、28〜31行目、35〜37行目、54〜57行目で変更あり。 変更箇所はスライドで扱う。 ```cpp= int main(int argc, char *argv[]) { Binary bin; triton::API api; triton::arch::registers_e ip; std::map<triton::arch::registers_e, uint64_t> regs; std::map<uint64_t, uint8_t> mem; std::vector<triton::arch::registers_e> symregs; std::vector<uint64_t> symmem; if(argc < 5) { printf("Usage: %s <binary> <sym-config> <entry> <branch-addr>\n", argv[0]); return 1; } std::string fname(argv[1]); if(load_binary(fname, &bin, Binary::BIN_TYPE_AUTO) < 0) return 1; if(set_triton_arch(bin, api, ip) < 0) return 1; api.enableMode(triton::modes::ALIGNED_MEMORY, true); if(parse_sym_config(argv[2], &regs, &mem, &symregs, &symmem) < 0) return 1; for(auto &kv: regs) { triton::arch::Register r = api.getRegister(kv.first); api.setConcreteRegisterValue(r, kv.second); } for(auto regid: symregs) { // Symbolize Registers triton::arch::Register r = api.getRegister(regid); api.convertRegisterToSymbolicVariable(r)->setComment(r.getName()); } for(auto &kv: mem) { api.setConcreteMemoryValue(kv.first, kv.second); } for(auto memaddr: symmem) { // Symbolize Memory api.convertMemoryToSymbolicVariable(triton::arch::MemoryAccess(memaddr, 1))->setComment(std::to_string(memaddr)); } uint64_t pc = strtoul(argv[3], NULL, 0); uint64_t branch_addr = strtoul(argv[4], NULL, 0); Section *sec = bin.get_text_section(); while(sec->contains(pc)) { char mnemonic[32], operands[200]; int len = disasm_one(sec, pc, mnemonic, operands); if(len <= 0) return 1; triton::arch::Instruction insn; insn.setOpcode(sec->bytes+(pc-sec->vma), len); insn.setAddress(pc); api.processing(insn); if(pc == branch_addr) { // find new user input to reach not explorered path. find_new_input(api, sec, branch_addr); break; } // update program counter pc = (uint64_t)api.getConcreteRegisterValue(api.getRegister(ip)); } unload_binary(&bin); return 0; } ``` ### `find_new_input()` ```cpp= static void find_new_input(triton::API &api, Section *sec, uint64_t branch_addr) { triton::ast::AstContext &ast = api.getAstContext(); // initialize constraint with tautology. triton::ast::AbstractNode *constraint_list = ast.equal(ast.bvtrue(), ast.bvtrue()); printf("evaluating branch 0x%jx:\n", branch_addr); const std::vector<triton::engines::symbolic::PathConstraint> &path_constraints = api.getPathConstraints(); for(auto &pc: path_constraints) { // for all path constraints if(!pc.isMultipleBranches()) continue; // ex. return for(auto &branch_constraint: pc.getBranchConstraints()) { bool flag = std::get<0>(branch_constraint); // whether used or not uint64_t src_addr = std::get<1>(branch_constraint); // branch src->dst uint64_t dst_addr = std::get<2>(branch_constraint); triton::ast::AbstractNode *constraint = std::get<3>(branch_constraint); if(src_addr != branch_addr) { /* this is not our target branch, so keep the existing "true" constraint */ if(flag) { // `flag` is True if `constraint` was used. constraint_list = ast.land(constraint_list, constraint); } } else { /* this is our target branch, compute new input */ printf("0x%jx -> 0x%jx (%staken)\n", src_addr, dst_addr, flag ? "" : "not "); if(!flag) { // `flag` is False if `constraint` was not used. printf("computing new input for 0x%jx -> 0x%jx\n", src_addr, dst_addr); constraint_list = ast.land(constraint_list, constraint); // add constraint for(auto &kv: api.getModel(constraint_list)) { // invoke Z3 printf("SymVar %u (%s) = 0x%jx\n", kv.first, api.getSymbolicVariableFromId(kv.first)->getComment().c_str(), (uint64_t)kv.second.getValue()); } } } } } } ``` ### `set_triton_arch()` 13.3で用いたものと全く同じ ```cpp= static int set_triton_arch(Binary &bin, triton::API &api, triton::arch::registers_e &ip) { if(bin.arch != Binary::BinaryArch::ARCH_X86) { fprintf(stderr, "Unsupported architecture\n"); return -1; } if(bin.bits == 32) { api.setArchitecture(triton::arch::ARCH_X86); ip = triton::arch::ID_REG_EIP; } else if(bin.bits == 64) { api.setArchitecture(triton::arch::ARCH_X86_64); ip = triton::arch::ID_REG_RIP; } else { fprintf(stderr, "Unsupported bit width for x86: %u bits\n", bin.bits); return -1; } return 0; } ``` ## `branch.c` Test program ```c= #include <stdio.h> #include <stdlib.h> void branch(int x, int y) { if(x < 5) { if(y == 10) printf("x < 5 && y == 10\n"); else printf("x < 5 && y != 10\n"); } else { printf("x >= 5\n"); } } int main(int argc, char *argv[]) { if(argc < 3) { printf("Usage: %s <x> <y>\n", argv[0]); return 1; } branch(strtol(argv[1], NULL, 0), strtol(argv[2], NULL, 0)); return 0; } ``` # 13.5 Exploiting Vulnerability スライドで適宜補足説 ## The vulernable program (ical.c) ### header files ```cpp= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <crypt.h> ``` ### `ical` struct ```cpp= void forward (char *hash); void reverse (char *hash); void hash (char *src, char *dst); static struct { void (*functions[2])(char *); char hash[5]; // 4-byte hash + NULL } icall; ``` ### `main` functions `ical` takes two command line arguments.(L.10) - `index` : decides which entry in `ical.functions` will be used - `string` : serves as input to generate the ***hash***, as you will see later in the slide. There's also third command line argument.(L.14~L.18) - `argv[3]` : password for secret admin area - Admin area provides root shell. ```cpp= int main(int argc, char *argv[]) { unsigned i; icall.functions[0] = forward; // initialize icall.funcitons icall.functions[1] = reverse; if(argc < 3) { printf("Usage: %s <index> <string>\n", argv[0]); return 1; } if(argc > 3 && !strcmp(crypt(argv[3], "$1$foobar"), "$1$foobar$Zd2XnPvN/dJVOseI5/5Cy1")) { /* secret admin area */ if(setgid(getegid())) perror("setgid"); if(setuid(geteuid())) perror("setuid"); execl("/bin/sh", "/bin/sh", (char*)NULL); } else { hash(argv[2], icall.hash); i = strtoul(argv[1], NULL, 0); printf("Calling %p\n", (void*)icall.functions[i]); icall.functions[i](icall.hash); // <--------- vulnerable ---------- } return 0; } ``` ### Functions ```cpp= void forward(char *hash) { int i; printf("forward: "); for(i = 0; i < 4; i++) { printf("%02x", hash[i]); } printf("\n"); } void reverse(char *hash) { int i; printf("reverse: "); for(i = 3; i >= 0; i--) { printf("%02x", hash[i]); } printf("\n"); } void hash(char *src, char *dst) { int i, j; for(i = 0; i < 4; i++) { dst[i] = 31 + (char)i; for(j = i; j < strlen(src); j += 4) { dst[i] ^= src[j] + (char)j; if(i > 1) dst[i] ^= dst[i-2]; } } dst[4] = '\0'; } ``` ## Exploit Tool(`exploit_callsite.py`) ### `main` - There's no way to pass arguments to concolic Triton tool. - alternative way 1. hardcode (we use this here) 2. pass through environment variable ```python= #!/usr/bin/env python2 ## -*- coding: utf-8 -*- # Assumptions: we've already used taint analysis to determine there's a # user-controllable indirect call site, now we're using # symbex to figure out how to control it import sys import triton import pintool taintedCallsite = 0x400bef # Found in a previous DTA pass target = 0x400b3b # Target to redirect callsite to # sybex state is maintained in global Triotn context Triton = pintool.getTritonContext() def main(): # initialize Triton Triton.setArchitecture(triton.ARCH.X86_64) Triton.enableMode(triton.MODE.ALIGNED_MEMORY, True) # setup start point(in case symbol is available) pintool.startAnalysisFromSymbol('main') # pintool.startAnalysisFromAddress('0xhogehoge') pintool.insertCall(symbolize_inputs, pintool.INSERT_POINT.ROUTINE_ENTRY, 'main') pintool.insertCall(hook_icall, pintool.INSERT_POINT.BEFORE) # before every inst pintool.runProgram() if __name__ == '__main__': main() ``` ### Triton Insert Points | Insert Point | Callback moment | Arguments | Callback arguments | | -------------- | --------------------------- | ------------ | ----------------------------- | | AFTER | After instruction executes | | Instruction object | | BEFORE | Before instruction executes | | Instruction object | | BEFORE_SYMPROC | Before symbolic processing | | Instruction object | | FINI | End of execution | | | | ROUTINE_ENTRY | Routine entry point | Routine name | Thread ID | | ROUTINE_EXIT | Routine exit | Routine name | Thread ID | | IMAGE_LOAD | New image loaded | | | | SIGNALS | Signal delivery | | Thread ID, signal ID | | SYSCALL_ENTRY | Before syscall | | Thread ID, syscall descriptor | | SYSCALL_EXIT | After syscall | | Thread ID, syscall descriptor | ### functions ```python= def symbolize_inputs(tid): """ tid : thread id """ rdi = pintool.getCurrentRegisterValue(Triton.registers.rdi) # argc rsi = pintool.getCurrentRegisterValue(Triton.registers.rsi) # argv # for each string in argv while rdi > 1: # read 8 bytes addr = pintool.getCurrentMemoryValue(rsi + ((rdi-1)*triton.CPUSIZE.QWORD), triton.CPUSIZE.QWORD) # symbolize the current argument string (including the terminating NULL) c = None s = '' while c != 0: # read 1 byte c = pintool.getCurrentMemoryValue(addr) s += chr(c) Triton.setConcreteMemoryValue(addr, c) # set for Triton's global context # symbolize and set comment Triton.convertMemoryToSymbolicVariable(triton.MemoryAccess(addr, triton.CPUSIZE.BYTE)).setComment('argv[%d][%d]' % (rdi-1, len(s)-1)) addr += 1 rdi -= 1 print 'Symbolized argument %d: %s' % (rdi, s) def exploit_icall(insn, op): regId = Triton.getSymbolicRegisterId(op) regExpr = Triton.unrollAst(Triton.getAstFromId(regId)) ast = Triton.getAstContext() exploitExpr = ast.equal(regExpr, ast.bv(target, triton.CPUSIZE.QWORD_BIT)) for k, v in Triton.getSymbolicVariables().iteritems(): if 'argv' in v.getComment(): # Argument characters must be printable argExpr = Triton.getAstFromId(k) argExpr = ast.land([ ast.bvuge(argExpr, ast.bv(32, triton.CPUSIZE.BYTE_BIT)), ast.bvule(argExpr, ast.bv(126, triton.CPUSIZE.BYTE_BIT)) ]) exploitExpr = ast.land([exploitExpr, argExpr]) print 'Getting model for %s -> 0x%x' % (insn, target) model = Triton.getModel(exploitExpr) for k, v in model.iteritems(): print '%s (%s)' % (v, Triton.getSymbolicVariableFromId(k).getComment()) def hook_icall(insn): if insn.isControlFlow() and insn.getAddress() == taintedCallsite: for op in insn.getOperands(): if op.getType() == triton.OPERAND.REG: print 'Found tainted indirect call site \'%s\'' % (insn) exploit_icall(insn, op) ``` ## Appendix How to generate valid license key which can be accepted by this program? ```c= #include <stdio.h> #include <string.h> int check_license(char *serial) { int i; unsigned char sum, xor, negxor; sum = xor = negxor = 0; for(i = 0; i < strlen(serial); i++) { sum += serial[i]; xor ^= serial[i]; negxor ^= ~serial[i]; } printf("sum=0x%02x xor=%02x negxor=%02x\n", sum, xor, negxor); return sum == xor == negxor; } int main(int argc, char *argv[]) { int i, alnum, ret; char *serial; if(argc < 2) { printf("Usage: %s <serial>\n", argv[0]); return 1; } serial = argv[1]; if(strlen(serial) != 8) { printf("Serial must be 8 characters\n"); return 1; } for(i = 0; i < strlen(serial); i++) { alnum = (serial[i] >= 'A' && serial[i] <= 'Z') || (serial[i] >= 'a' && serial[i] <= 'z') || (serial[i] >= '0' && serial[i] <= '9'); if(!alnum) { printf("Serial must be alphanumeric\n"); return 1; } } if((ret = check_license(serial))) { printf("License check passed\n"); } else { printf("License check failed\n"); } return ret; } ```