Pandaのなかみ
tainted_instrプラグイン: 指定されたテイントデータにアクセスした命令をダンプするプラグイン。 テイント解析用のtaint2,コールスタック復元用のcallstack_isntrを使っている。
(tainted_instr.cpp)
|c| bool init_plugin(void self) { panda_require("taint2"); [] assert(init_taint2_api()); panda_require("callstack_instr"); [] assert (init_callstack_instr_api()); PPP_REG_CB("taint2", on_taint_change, taint_change); [] // this tells taint system to enable extra instrumentation // so it can tell when the taint state changes taint2_track_taint_state(); return true; } ||< [],[]でそれぞれtaint2,callstack_instrプラグインをincludeしている。 [*]ではtaint2プラグインのon_taint_changeコールバック関数はtaint_chage関数で実装している事を指示している。
このon_taint_changeコールバックはtaint2内のtaint_state_changed関数から呼ばれる。
(taint2.cpp)
|c| void taint_state_changed(FastShad *fast_shad, uint64_t shad_addr, uint64_t size) { Addr addr; if (fast_shad == shadow->llv) { addr = make_laddr(shad_addr / MAXREGSIZE, shad_addr % MAXREGSIZE); } else if (fast_shad == shadow->ram) { addr = make_maddr(shad_addr); } else if (fast_shad == shadow->grv) { addr = make_greg(shad_addr / sizeof(target_ulong), shad_addr % sizeof(target_ulong)); } else if (fast_shad == shadow->gsv) { addr.typ = GSPEC; addr.val.gs = shad_addr; addr.off = 0; addr.flag = (AddrFlag)0; } else if (fast_shad == shadow->ret) { addr.typ = RET; addr.val.ret = 0; addr.off = shad_addr; addr.flag = (AddrFlag)0; } else return;
PPP_RUN_CB(on_taint_change, addr, size); [*]
} ||<
[*]でon_taint_chageに登録された全てのコールバック関数を呼ぶ。このtaint_state_changedはテイントの伝搬時にshadow領域間でコピーが発生する際に呼ばれる。
(taint2/fast_shad.h)
|c| static inline void copy(FastShad *shad_dest, uint64_t dest, FastShad shad_src, uint64_t src, uint64_t size) { ... bool change = false; if (track_taint_state && (shad_dest->range_tainted(dest, size) || shad_src->range_tainted(src, size))) [] change = true;
memcpy(shad_dest->get_td_p(dest), shad_src->get_td_p(src), size * sizeof(TaintData));
if (change) taint_state_changed(shad_dest, dest, size); [*]
} ||< copyはsrcらdestへのテイント伝搬を行う関数。 []でどちらか一方の領域ががテイントされている時それは伝搬が発生したという事でchange=trueとなり[]で上記のtaint_state_chagedが呼ばれる。 このFastShad::copy関数はtaint_copy関数から呼ばれる。
(taint2/taint_ops.cpp)
|c| void taint_copy( FastShad *shad_dest, uint64_t dest, FastShad *shad_src, uint64_t src, uint64_t size, llvm::Instruction *I) { taint_log("copy: %s[%lx+%lx] <- %s[%lx] (", shad_dest->name(), dest, size, shad_src->name(), src);
if (dest + size >= shad_dest->get_size() || src + size >= shad_src->get_size()) {
taint_log("Ignoring IO\n");
return;
}
FastShad::copy(shad_dest, dest, shad_src, src, size); [*]
if (I) update_cb(shad_dest, dest, shad_src, src, size, I);
} ||<
(taint2/llvm_taint_lib.cpp)
|c| bool PandaTaintFunctionPass::doInitialization(Module &M) { PTV.copyF = M.getFunction("taint_copy"); } ||< PTVはPandaTaintVisitorクラス(taint2/llvm_taint_lib.h)のインスタンス。これはLLVM IRを実行する際に適時呼び出すVisitor。llvm::InstVisitorクラスを継承していて、このクラスのメンバ関数をimplementする事でこれらinstrument時にコールバックが呼ばれる仕組み。
つまりまとめるとLLVM IRでメモリコピー命令が発生すると、コピー先のdestのアドレスがコールバック関数に渡されて呼ばれる。
その他の命令はpanda/plugins/taint2/llvm_taint_lib.cppでmov,cmpなど各命令をInstVisitorでフックしてレジスタやメモリなどのオペランド間のテイント処理を実装している。
(UAF検知)useafterfreeプラグイン
(useafterfree.cpp)
|c| bool init_plugin(void *self) { #if defined(TARGET_I386) && TARGET_LONG_SIZE == 8 PPP_REG_CB("callstack_instr", on_ret, process_ret);
panda_enable_memcb();
panda_cb pcb;
pcb.virt_mem_write = virt_mem_write;
pcb.virt_mem_read = virt_mem_read;
pcb.before_block_exec = before_block_exec;
panda_arg_list *args = panda_get_args("useafterfree");
// Addresses for alloc/free/realloc
alloc_guest_addr = panda_parse_ulong(args, "alloc", 0x7787209D);
free_guest_addr = panda_parse_ulong(args, "free", 0x77871F31);
realloc_guest_addr = panda_parse_ulong(args, "realloc", 0x77877E54);
// CR3 to watch.
right_cr3 = panda_parse_ulong(args, "cr3", 0x3F98B320);
// Size of words on target OS.
word_size = panda_parse_uint64(args, "word", 4);
#endif
return true;
}
virt_mem_*は仮想アドレスへのr/w時のコールバック関数、before_block_execがブロック実行前に設定する。またプラグインはalloc/free/reallocのアドレス、監視するプロセスのcr3,ゲストOSのワードのバイト数を引数としてとる。
(useafterfree.cpp)
|c| int before_block_exec(CPUState *env, TranslationBlock *tb) { if (!is_right_proc(env)) return 0;
target_ulong cr3 = env->cr[3];
// Clear queue of potential bad reads.
while (bad_read_queue[cr3].size() > 0) { [*]
read_info& ri = bad_read_queue[cr3].front();
if (get_word(env, ri.loc) == ri.val) { // Still invalid.
printf("READING INVALID POINTER %lx @ %lx!! PC %lx\n", ri.val, ri.loc, ri.pc);
}
bad_read_queue[cr3].pop();
}
// Clear queue of potential dangling pointers.
while (invalid_queue[cr3].size() > 0) { [*]
target_ulong loc = invalid_queue[cr3].front();
if (invalid_ptrs[cr3].count(loc) == 0 || !alloc_now[cr3].contains(loc)) {
// Pointer has been overwritten or deallocated; not dangling.
invalid_queue[cr3].pop();
continue;
}
if (rr_get_guest_instr_count() - invalid_ptrs[cr3][loc] <= safety_window) {
// Inside safety window still.
break;
}
// Outside safety window and pointer is still dangling. Report.
printf("POINTER RETENTION to %lx @ %lx!\n", get_word(env, loc), loc);
invalid_queue[cr3].pop();
}
if (tb->pc == free_guest_addr) { // free
...
} else if (tb->pc == alloc_guest_addr) { // alloc
//printf("found alloc!\n");
alloc_info info;
info.retaddr = get_stack(env, 0);
info.heap = get_stack(env, 1);
info.size = get_stack(env, 3);
alloc_stacks[cr3].push(info);
//debug = 100;
} else if (tb->pc == realloc_guest_addr) { // realloc
...
}
return 0;
} ||< bad_read_queueは invalid_queueはすでにallocされた領域
仮想アドレスへのr/wコールバック関数は両方共virt_mem_accessのラッパー
(useafterfree.cpp)
|c| static int virt_mem_access(CPUState *env, target_ulong pc, target_ulong addr, target_ulong size, void *buf, int is_write) { if (!is_right_proc(env)) return 0;
target_ulong cr3 = env->cr[3];
if (size >= word_size && is_write) { // The addresses we're overwriting don't contain ptrs anymore.
target_ulong begin = addr, end = addr + size;
auto end_it = valid_ptrs[cr3].lower_bound(end);
for (auto it = valid_ptrs[cr3].lower_bound(begin); it != end_it;
it = valid_ptrs[cr3].erase(it)) {
// it->second is the value of a ptr. it->first is its location.
if (alloc_now[cr3].contains(it->second)) {
if (ptrprint) printf("Erasing pointer to %lx @ %lx.\n", it->second, it->first);
alloc_now[cr3][it->second].valid_ptrs.erase(it->first);
}
}
auto end_it2 = invalid_ptrs[cr3].lower_bound(end);
for (auto it = invalid_ptrs[cr3].lower_bound(begin); it != end_it2;
it = invalid_ptrs[cr3].erase(it)) {
if (ptrprint) printf("Erasing invalid pointer @ %lx.\n", it->first);
}
}
if (!inside_memop(cr3) && pc >> 20 != alloc_guest_addr >> 20) { // hack.
if (alloc_ever[cr3].contains(addr)
&& !alloc_now[cr3].contains(addr)) {
printf("USE AFTER FREE %s @ {%lx, %lx}! PC %lx\n",
is_write ? "WRITE" : "READ", cr3, addr, pc); [*]
return 0;
}
if (size == word_size) {
target_ulong loc = addr; // Pointer location
// Pointer value; should be address inside valid range
target_ulong val = *(uint32_t *)buf;
// Might be writing a pointer. Track.
if (is_write) {
if (alloc_now[cr3].contains(val)) { // actually creating pointer.
if (ptrprint) printf("Creating pointer to %lx @ %lx.\n", val, loc);
alloc_now[cr3][val].valid_ptrs.insert(loc);
try { valid_ptrs[cr3][loc] = val; } catch (int e) {}
} else if (alloc_ever[cr3].contains(val)) {
// Oops! We wrote an invalid pointer.
if (ptrprint) printf("Writing invalid pointer to %lx @ %lx.\n", val, loc);
invalid_ptrs[cr3][loc] = rr_get_guest_instr_count();
}
} else if (env->regs[R_ESP] != loc) { // Reading a pointer. Ignore stack reads.
// Leave safety window.
if (invalid_ptrs[cr3].count(loc) > 0 &&
rr_get_guest_instr_count() - invalid_ptrs[cr3][loc] > safety_window &&
val != 0) {
bad_read_queue[cr3].push(read_info(pc, loc, val));
}
}
}
}
return 0;
} ||<
[]はメモリ操作が(alloc/free)内ではなく、かつallocの上位20bitとも一致しない時(?) []では以前に該当アドレスのメモリをallocした後freeしており、それをここで使用としているためUAFになる。
kvalgrind 変数が初期化されているか <=> 変数のメモリ領域(スタック or bss or...)に以前にdefinedなレジスタからの書き込みがあった