# 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], ®s, &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;
}
```