é«æ©è½ãã¤ããªãã¬ã¼ãµqiraã¯ã©ã®ããã«å®è£ ããã¦ããã®ã
1. qiraã¨ã¯
qiraã¨ã¯ä¸ççãªããã«ã¼ãGeorge Hotzæ° (ジョージ・ホッツ - Wikipedia)
ã«ãã£ã¦éçºãããé«æ©è½ãã¤ããªãã¬ã¼ãµã¼ã§ãããqiraã¨ããåã¯ï¼QEMU Interactive Runtime Analyserï¼ã®ç¥ã§ããã
GitHub - BinaryAnalysisPlatform/qira: QEMU Interactive Runtime Analyser
ç¥èªãè¦ãã°åãããuser mode QEMUã使ç¨ãããã¤ããªè§£æãã¼ã«ã§ãããELFãªã©ã®å®è¡å½¢å¼ãã¤ããªãå®éã«åä½ããã¦åå½ä»¤ã®ã¬ã¸ã¹ã¿ãã¡ã¢ãªã¸ã®æä½ãé次è¨é²ããã
ãããã®è¨é²ã¯web UIãéãã¦å¥½ããªå½ä»¤ä½ç½®ã«ã«ã¼ã½ã«ã移åãããã ãã§è¦ããã¨ãã§ãããã®æã®ã¬ã¸ã¹ã¿ãã¡ã¢ãªã®è¨é²ãåç¾ãããä»çµã¿ã«ãªã£ã¦ãããã½ããã¦ã§ã¢ã®ãããã°ãCTFã«ããããã¤ããªè§£æã«ããã¦é常ã«æç¨ã§ããã
qiraã¯Timeless Debuggingã¨ããææ³ãç¨ãã¦ãããããã¯å1å½ä»¤ãã¬ã¸ã¹ã¿,ã¡ã¢ãªã¸ã®1ã³ãããã¨ã¿ãªãã¦é次è¨é²ãã解æè
ã¯å¥½ããªå½ä»¤ä½ç½®ã«å¾ããcheckoutãã¦ãã®æã®ç¶æ
ãè¦ããã¨ãããã®ã ã
詳ããã¯éçºè
æ¬äººã§ããgeohotæ°ã«ããUSENIX Enigmaã§ã®è¬æ¼ãåç
§ããããã
USENIX Enigma 2016 - Timeless Debugging
www.youtube.com
gdbã«ãReverse Debuggingã®ãããªéå»ã®å®è¡è¨é²ã辿ããæ©è½ã¯ããããqiraã®Timeless Debuggingã¯ãã£ã¨å¯¾è©±çã§ã解æè ã®å´åãæ¸ãããããããªå·¥å¤«ãããã¦ããã
2. qiraã®å®è£
ãç¥ãæ義
qiraã¯å¤ãã®ã¢ã¼ããã¯ãã£åããã¤ããª(x86,arm,mips,ppc...etc)ã®è§£æã«å¯¾å¿ãã¦ãããããã¯QEMUã§ã¢ã¼ããã¯ãã£ã®å·®ãå¸åãã¦ããããã ãåºæ¬çã«ãã¬ã¼ãµã¼ã®ã³ã¢ãã¸ãã¯ã¯QEMUãæ¹é ããäºã§å®ç¾ããã¦ãããã®ã ããå®ã¯ä»ã«ã並è¡ãã¦radare2ãida sdk, capstoneãç¨ãã¦å¯¾è±¡ãã¤ããªããããããéç解æããããã°ã©ããã¥ã¼ãèªåã§ç¨æãããªã©è§£æãã¼ã«ã¨ãã¦é常ã«ææåãã§ãããã
ã§ã¯ãããã解æãã¼ã«ã®å®è£
ãç¥ãæ義ã¯ä½ãããªãä¸èº«ã¾ã§ç¥ãå¿
è¦ãããã®ããããã¯æ¢åã®ãã¬ã¼ã ã¯ã¼ã¯ãã¹ã¯ãªããã§ã¯å¯¾å¦ããããªããããé«åº¦ãªããã¯ãè¡ãæã«ãããã®ç¥è¦ãå¿
è¦ã«ãªãããã ã
æ¬è¨äºã¯ãããªæ©ä¼ãæ¥ã(ãããã¯æ¢ã«æ¥ã)人ã®ããã®æåã®ä¸æ©ã«ãªãã°å¹¸ãã§ããã
éã«ããããªäºããªãã¦ãä¸ã®ä¸ã«ã¯å
åè¯ãç©ããããã俺ã¯ããã使ã£ã¦æºè¶³ãã¦ããã¨ãã人ã«ã¨ã£ã¦ã¯ãã¾ãææ義ãªè¨äºããç¡ããããããªãã
ãã¦ã§ã¯ããããã¯æ¬é¡ã«æ»ã£ã¦qiraã®å®è£
ãè¦ã¦ããã
3. qiraå®è¡ããQEMUèµ·åã¾ã§
qiraã¯ä»¥ä¸ã®ã³ãã³ãã§å®è¡ã§ããã
$ qira ./a.out
qira [target-binary] ã§ãã¬ã¼ã¹å¯¾è±¡ã¨ãããã¤ããªãæå®ããã
ãã®qiraæ¬ä½ã¯ã·ã§ã«ã¹ã¯ãªããã«ãªã£ã¦ãã¦ä¸ããmiddleware/qira.pyãå¼ãã§ããã
(qira)
#!/bin/bash -e unamestr=$(uname) if [[ "$unamestr" == 'Linux' ]]; then DIR=$(dirname $(readlink -f $0)) elif [[ "$unamestr" == "Darwin" ]]; then cmd=$(which "$0") if [ -L "$cmd" ]; then cmd=$(readlink "$cmd") fi DIR=$(dirname "$cmd") else echo "Only Linux and Mac OS X are supported!" exit fi unset PYTHONPATH source $DIR/venv/bin/activate exec /usr/bin/env python2.7 $DIR/middleware/qira.py $* [*1]
[*1]ã§qira.pyã¹ã¯ãªãããå®è¡ãã¦ããã
qira.pyã«ã¯æ§ã ãªãªãã·ã§ã³ããããä¸ã§æå®ããã¿ã¼ã²ãããã¤ããªã¯binaryãªãã·ã§ã³ã«ãã¹ãè¨å®ãããã
(middleware/qira.py)
if __name__ == '__main__': # define arguments parser = argparse.ArgumentParser(description = 'Analyze binary. Like "qira /bin/ls /"') parser.add_argument('-s', "--server", help="bind on port 4000. like socat", action="store_true") parser.add_argument('-t', "--tracelibraries", help="trace into all libraries", action="store_true") parser.add_argument('binary', help="path to the binary") [*2] parser.add_argument('args', nargs='*', help="arguments to the binary") [*3] ... # creates the file symlink, program is constant through server run program = qira_program.Program(args.binary, args.args, qemu_args) [*4] # start the binary runner if args.server: qira_socat.start_bindserver(program, qira_config.SOCAT_PORT, -1, 1, True) else: print "**** running "+program.program program.execqira(shouldfork=not is_qira_running) [*5] if not is_qira_running: # start the http server qira_webserver.run_server(args, program) [*6]
ä¸è¨ã®ã³ã¼ããqiraã®ã¡ã¤ã³å¦çã
ã¾ã[*2],[*3]ã§ããããã¿ã¼ã²ãããã¤ããªã¨ãã¤ããªã«æ¸¡ãå¼æ°ããã¼ã¹ãã[*4]ã§ã¿ã¼ã²ãããã¤ããªã解æããããã®åæåãè¡ãã[*5]ã§å®éã«QEMUä¸ã§ã¿ã¼ã²ãããã¤ããªãåä½ããã¦ãããæå¾ã«[*6]ã§web UIã¨ããåãããããã®ãµã¼ãã¼ãèµ·åããã
ããããqiraã®ä¸é£ã®å¦çã§ãããããããã®å¦çã«ã¤ãã¦è©³ç´°ãè¦ã¦ããã
ã¾ã[*4]ã®Programã¯ã©ã¹ã®åæåã³ã¼ãã¯ä»¥ä¸ã®ããã«ãªã£ã¦ããã
(middleware/qira_program.py)
class Program: def __init__(self, prog, args=[], qemu_args=[]): # call which to match the behavior of strace and gdb self.program = which(prog) self.args = args self.proghash = sha1(open(self.program, "rb").read()).hexdigest() print "*** program is",self.program,"with hash",self.proghash # this is always initted, as it's the tag repo self.static = static2.Static(self.program) [*7] # bring this back if self.program != "/tmp/qira_binary": try: os.unlink("/tmp/qira_binary") except: pass try: os.symlink(os.path.realpath(self.program), "/tmp/qira_binary") [*8] except: pass # defaultargs for qira binary self.defaultargs = ["-strace", "-D", "/dev/null", "-d", "in_asm", "-singlestep"]+qemu_args [*9] if qira_config.TRACE_LIBRARIES: self.defaultargs.append("-tracelibraries") self.identify_program() [*10]
ã¾ãã¯[*7]ã®stati2.Staticã¯ã©ã¹ã®åæåã ããããã¯ã¿ã¼ã²ãããã¤ããªãéç解æããããã®åæåã§ããã2ç« ã§ãå°ãè¿°ã¹ãããã«qiraã¯ã¿ã¼ã²ãããã¤ããªãQEMUã§åä½ãããã ãã§ãªããåæã«ã¿ã¼ã²ãããã¤ããªã®éç解æãè¡ã£ã¦ããããã®ããã®åæåã§ããã(詳細ã¯å¾è¿°)
次ã«[*8]ã§ã¿ã¼ã²ãããã¤ããªã/tmp/qira_binaryã«symbolic linkãã¦ããã以å¾ã¿ã¼ã²ãããã¤ããªã¯/tmp/qira_binaryã¨ãã¦ã¢ã¯ã»ã¹ãããã
[*9]ã¯ä¸è¨[*5]ã®execqiraã§å®éã«QEMUãåä½ãããéãQEMUã«æ¸¡ãå¼æ°ãè¨å®ãã¦ãããç¹ã«æ³¨ç®ãã¹ããªã®ã¯"-d in_asm"ã¨"-singlestep"ã§ããããã®ãªãã·ã§ã³ãä¸ããçç±ã¯å¾ã»ã©èª¬æããã
[*10]ã®identify_programã§ã¿ã¼ã²ãããã¤ããªã®ãã©ã¼ããããã¢ã¼ããã¯ãã£ã®è§£æãè¡ã£ã¦ãããããã«ã¤ãã¦ã¯è©³ç´°ã¯çãããããããªã¼ã½ããã¯ã¹ãªãã¤ããªãã¼ã¹å¦çã ããã ã(èå³ã®ããæ¹ã¯middleware/qira_program.pyã®identify_programé¢æ°ãåç
§ãããã)
ã¡ãªã¿ã«ãã®identify_programé¢æ°ã¯[*7]ã§åæåããéç解æã¯ã©ã¹ã使ç¨ãã¦ããªãå®å
¨ã«ç¬ç«ãããã¤ããªãã¼ãµã¼ã§ããã
qiraã«ã¯è³ãæã«ç¬èªã®éç解æã®å®è£
ããããæ¢åã®static2.Staticã¯ã©ã¹ã¨æ··åããäºãããããåºæ¬çã«ã¯stati2.Staticã¯ã©ã¹ã¯web UIã§è¡¨ç¤ºãããã¹ã¿ãã¯ãã¬ã¼ã¹ãã°ã©ããã¥ã¼ãæ§ç¯ããããã«ä½¿ããã¦ãããä¸æ¹ã¢ã¼ããã¯ãã£ç¹å®ã®ããã®ãã¤ããªãã¼ã¹ã¨ãã£ãåºæ¬çãªéç解ææ©è½ã¯qiraãç¬èªã«å®è£
ãã¦ããã±ã¼ã¹ãå¤ãã
ã§ã¯[*5]ã¾ã§æ»ã£ã¦execqiraã§å®éã«QEMUä¸ã§ãã¤ããªãåä½ãããå¦çãè¦ã¦ã¿ããã
(qira_program.py)
def execqira(self, args=[], shouldfork=True): if qira_config.USE_PIN: # is "-injection child" good? eargs = [self.pinbinary, "-injection", "child", "-t", self.pintool, "--", self.program]+self.args else: eargs = [self.qirabinary]+self.defaultargs+args+[self.program]+self.args #print "***",' '.join(eargs) os.execvp(eargs[0], eargs) [*11]
[*11]ã§self.qirabinaryãå®è¡ãã¦ãããself.qira_binaryã¯x86_64ç°å¢ã®å ´åqira-x86_64ã¨ãããã¡ã¤ã«åã«ãªãããã®qira-[arch_name]ã¨ãããã¡ã¤ã«ã¯tracers/qemuã«ãã£ã¦ãããããåãã£ã¬ã¯ããªã®qemu-latest/[arch_name]-linux-user/qemu-*ã¸ã®ã·ã³ããªãã¯ãªã³ã¯ã«ãªã£ã¦ãããã¤ã¾ãqira_x86_64ãå®è¡ããã¨x86_64ç¨ã®ã¦ã¼ã¶ã¼ã¢ã¼ãQEMUãåä½ãéå§ããäºã«ãªãã
ããã§ã¿ã¼ã²ãããã¤ããªãQEMUã§åä½ãããæã¾ã§æ¥ãã
4. QEMUã«ãããã¬ã¼ã¹
ã§ã¯æ¬¡ã«ãã¤ããªããã¬ã¼ã¹ããããã«QEMUã¦ã¼ã¶ã¼ã¢ã¼ãã«å ãã¦ããç¬èªå®è£
é¨åãè¦ã¦ãããæ¬å®¶QEMUã¨ã®å·®åã¯ãããã¨ãã¦tracers/qemu/qemu.patchã«ããã
qiraã¯QEMUã®TCI(TCG Interprter)ã¨ããæ©è½ããã¾ãå©ç¨ããäºã§ãã¬ã¼ã¹ãè¡ã£ã¦ããã
ããããTCGã¨ã¯ä½ããç°¡åã«èª¬æããã(QEMUã®å®è£
ã«é¢ããããã«è©³ãã話
QEMUのなかみ(QEMU internals) part1 - るくすの日記 ~ Out_Of_Range ~
ãåç
§ãããã)
QEMUã¯ã¿ã¼ã²ãããã¤ããªãä¸æ¦TCG micro codeã¨ããç¬èªã®ä¸é表ç¾ã«å¤æããããããã¹ãå´ã®ã¢ã¼ããã¯ãã£ã®ãã·ã³èªã«å¤æããã¨ããäºãé次è¡ã£ã¦ãããããã«ããä»®ã«ã¿ã¼ã²ãããã¤ããªãARMãMIPS,PPCãªã©ã§ãã£ã¦ããã¹ã(x86)ç¨ã®å½ä»¤ã«å¤æããããå®è¡å¯è½ã ã
ãã¦tracers/qemu/qemu.patchãè¦ã¦ã¿ããããããã®875è¡ç®ããtcg_qemu_tb_exec(tci.c)é¢æ°ã¸ã®è¿½å å·®åãè¼ã£ã¦ããã
/* Interpret pseudo code in tb. */ uintptr_t tcg_qemu_tb_exec(CPUArchState *env, uint8_t *tb_ptr) { +#ifdef QIRA_TRACKING + CPUState *cpu = ENV_GET_CPU(env); + TranslationBlock *tb = cpu->current_tb; + //TaskState *ts = (TaskState *)cpu->opaque; + + if (unlikely(GLOBAL_QIRA_did_init == 0)) { + // get next id + if (GLOBAL_id == -1) { GLOBAL_id = get_next_id(); } + + // these are the base libraries we load + write_out_base(env, GLOBAL_id); + + init_QIRA(env, GLOBAL_id); + + // these three arguments (parent_id, start_clnum, id) must be passed into QIRA + // this now runs after init_QIRA + if (GLOBAL_parent_id != -1) { + run_QIRA_log(env, GLOBAL_parent_id, GLOBAL_start_clnum); + run_QIRA_mods(env, GLOBAL_id); + } + + return 0; + } ...
tcg_qemu_tb_execé¢æ°(tci.c)ã¯QEMUãTCG micro codeããããã¯ã¨ã³ã,ããªãã¡ãã¹ãã®ãã·ã³èªã«å¤æããç©ãå®éã«QEMUãå®è¡ããããã®é¢æ°ã§ããã
ãã®é¢æ°ã¯QEMUã®ãã«ãæã«configureã§--enable-tcg-interprterãæå¹ã«ãããå¦ãã§ä¸èº«ãå¤ããããããã®ãªãã·ã§ã³ãä»ããªãã£ãå ´åã¯ä¸è¨tci.cå
ã®tcg_qemu_tb_execã§ã¯ãªããå¤ããã«tcg/tcg.hå
ã§å®ç¾©ãããååã®ãã¯ããå¼ã°ããããã®ãã¯ãã¯çæãããã¹ãã³ã¼ãçã«ç´æ¥ã¸ã£ã³ããããããªé¢æ°ã«ãªã£ã¦ãã¦ä¸è¨tci.cã®tcg_qemu_tb_execã¨ã¯å
¨ãéãä¸èº«ã«ãªã£ã¦ããã(詳細ã¯QEMUã®ãªãã¿part2
QEMUのなかみ(QEMU internals) part2 - るくすの日記 ~ Out_Of_Range ~
ãåç
§)
qiraã¯--enable-tcg-interprterãæå¹ã«ããç¶æ
ã§QEMUããã«ãããããã«ãªã£ã¦ãããããã®ãªãã·ã§ã³ãæå¹ã«ããã¨QEMUã¯TCGã®ä¸é表ç¾ããã¹ãã®ãã·ã³èªã«å¤æããã®ã§ã¯ãªããç´æ¥ã¤ã³ã¿ããªã¿çã«ä¸é表ç¾ãå®è¡ããããã«ãªãããããTCI(TCG Interpreter)æ©è½ã§ããã
ãªãqiraã¯ããããTCIã使ã£ã¦ããã®ããããã¯ã¿ã¼ã²ãããã¤ããªãTCG microcodeã«ã©ã®ããã«å¤æãããããè¦ãã°åããã
ä¾ãã°ä»¥ä¸ã®ãããªãã·ã³èª(x86)ããã£ãã¨ããã
mov $0xffffffffc0084100,%rdi
ããã¯ä»¥ä¸ã®ãããªTCG micro codeã«å¤æãããã
movi_i64 tmp0,$0xffffffffc0084100 mov_i64 rdi,tmp0
ã¡ãªã¿ã«TCG micro codeã§ã¯mov_i64ã¯64bitã®ã¬ã¸ã¹ã¿é代å
¥ãmovi_i64ã¯64bitã®å³å¤ä»£å
¥ãæå³ããã(TCG micro codeã¯intelè¨æ³ãªã®ã§æ³¨æ)
ããã¦ãã®ä¸é表ç¾ã¯(TCIãç¡å¹ãªæ)以ä¸ã®ãããªãã¹ãã³ã¼ã(x86)ã«å¤æãããã
mov $0xffffffffc0084100,%rbp mov %rbp,0x38(%r14)
ããªãéç´æçãªã³ã¼ãã«å¤æããã¦ããããå®ã¯ç°¡åã§ããã
ã¾ãä¸ã®ä½ã®å触ããç¡ãã«ç»å ´ããrbpã¬ã¸ã¹ã¿ã¯ä¸é表ç¾ä¸ã®tmp0ãæå³ãããQEMUã§ã¯tmp0ã®ãããªä¸æã¬ã¸ã¹ã¿ã¯ããã®æ使ããã¦ããªãé©å½ãªãã¹ãã®ã¬ã¸ã¹ã¿ãå²ãå½ã¦ããããªå¤æãã¸ãã¯ã«ãªã£ã¦ããããããã¾ãã¾tmp0ã«rbpã¬ã¸ã¹ã¿ãå²ãå½ã¦ããã¦ããã ãã§ããããã¦ã§ã¯0x38(%r14)ã¯ä½ã表ãã®ããå®ã¯ãããã¿ã¼ã²ãããã¤ããªã®ãã·ã³èªã«ãã£ãrdiã¬ã¸ã¹ã¿ã§ãããã¤ã¾ãã¿ã¼ã²ãããã¤ããªä¸ã®ã¬ã¸ã¹ã¿ã¸ã®ã¢ã¯ã»ã¹ã¯å
¨ã¦ãã¹ãã§ã®ã¡ã¢ãªã¢ã¯ã»ã¹ã«å¤æããããã¡ãªã¿ã«ãã®r14ã¬ã¸ã¹ã¿ã¨ããã®ã¯åºå®ã§QEMUä¸ã®CPUArchStateã¨ããæ§é ä½ã®å
é ãæãã¦ãããã¤ã¾ã0x38(%r14)ã¨ã¯CPUArchStateæ§é ä½ã®ã¡ã³ãã¸ã®ã¢ã¯ã»ã¹ã«ãªã£ã¦ããã0x38ãªãã»ããã¯CPUArchState->regs[R_RDI]ãæå³ããã
ãããã¤ã¾ãQEMUã®æã¤ä»®æ³RDIã¬ã¸ã¹ã¿ã¸ã®ã¢ã¯ã»ã¹ã«ãªã£ã¦ããããã ã
ãã¦ãã§ã¯--enable-tcg-interprterãæå¹ã«ããæã¯ã©ã®ããã«ãªããã
ä¸ã§ãè¿°ã¹ãããã«TCIãæå¹ã ã¨ä¸é表ç¾ãç´æ¥å®è¡ãããããå®ã¯å°ãã ãéãã
ã¨ããã®ãTCIã®å ´åã¯ãTCG miro codeãæ´ã«å¤æãã¦TCG micro code(backend)ã«å¤æãã¦ããå®è¡ãããããã ã
ã¤ã¾ã--enable-tcg-interpreterããªãå ´å
arm -> (TCG) -> x86 ã®ç¨ã«å¤æãããã TCIã®å ´å
arm -> (TCG) -> TCG backend ã®ããã«å¤æãããã
ãã®TCG backendå½ä»¤ã¯TCG micro code(frontendã¨å¼ã¶)ã¨æ®ã©å¤ãããªãå½ä»¤ã ããã»ãã³ãã£ãã¯ãããå°ãåç ãããªã¤ã¡ã¼ã¸ã«ãªããä¾ãã°ä¸ã®
movi_i64 tmp0,$0xffffffffc0084100 mov_i64 rdi,tmp0
ã¨ããTCG micro code(frontend)ã¯
movi_i64 tmp0,$0xffffffffc0084100 ld_64 0x38(T_AREG0), tmp0
ã«å¤æããããå
ã»ã©ã®ãã¹ãã³ã¼ãã¨æ¯è¼ããã°åããã¨æãããT_AREG0ã¨ããã®ã¯ä¸ã®r14ã¬ã¸ã¹ã¿ã¨åããCPUArchStateæ§é ä½ãæãã¦ããTCGã®ã¬ã¸ã¹ã¿ã§ããã
TCIã¯TCG micro codeãç´æ¥å®è¡ããã¨ã¯è¨ã£ããããããã£ãã¬ã¸ã¹ã¿ã¢ã¯ã»ã¹ããã¹ãã¡ã¢ãªã¢ã¯ã»ã¹ã«å¤æãããªã©ã®ä½æ¥ãè¡ã£ã¦ããTCG micro code(backend)ãå®è¡ããããã ã
ãã¦ãã¤ã¾ãã¾ã¨ããã¨ã¿ã¼ã²ãããã¤ããªã«ãããã¬ã¸ã¹ã¿ã¸ã®ã¢ã¯ã»ã¹ãããã¦å¿è«ã¡ã¢ãªã¸ã®ã¢ã¯ã»ã¹ã¯å
¨ã¦ãã¹ããã·ã³ã§ã¯ã¡ã¢ãª(CPUArchStateãªã©)ã¸ã®ã¢ã¯ã»ã¹ã«å¤æããã¦ããäºãåãã£ãã(ç´°ãããã¨ãè¨ãã¨å®ã¯ã¿ã¼ã²ãããã¤ããªå
ã®ã¡ã¢ãªã¢ã¯ã»ã¹å½ä»¤ã¯ld_64å½ä»¤ã«å¤æãããã®ã§ã¯ç¡ããqemu_ld64å½ä»¤ã«ãªããããã¯ã¿ã¼ã²ãããã¤ããªå
ã§ã®ldãã対象ã¢ãã¬ã¹ã¯ã²ã¹ãã¢ãã¬ã¹ãæãããããã®ã¾ã¾ã®ã¢ãã¬ã¹ã§ãã¹ãã®ld_64å½ä»¤ã«ç´ãã¨ãããããªãããã£ã¦softmmuãªã©ã§å¤æããããããã«ä¸æ¦qemu_ld64å½ä»¤ãéãã®ã ãç´°ãã話ã¯ã¾ããã¤ã...)
qiraã¯ãã®QEMUã®TCIã®æ§è³ªã使ã£ã¦ã¿ã¼ã²ãããã¤ããªã®ã¬ã¸ã¹ã¿,ã¡ã¢ãªã¢ã¯ã»ã¹ã¤ãã³ããè¨é²ãã¦ãããã¤ã¾ãld_64ãld_32,st_64... ãªã©ã®TCG micro code(backend)ããããã¯ãã¦ããã°ã¬ã¸ã¹ã¿ã®å¤åãè¨é²ã§ãããã¡ãªã¿ã«ã¡ã¢ãªã¢ã¯ã»ã¹ã«é¢ãã¦ã¯qemu_ld64å½ä»¤ãªã©ãããã¯ããã°åããã(ããã«ã¤ãã¦ã¯ã¾ããã¤ã説æãã)
å®éã«tracers/qemu/qemu.patchãè¦ã¦ã¿ãã¨
(tracers/qemu/qemu.path: 1093è¡ç®)
case INDEX_op_ld_i64: t0 = *tb_ptr++; t1 = tci_read_r(&tb_ptr); t2 = tci_read_s32(&tb_ptr); + track_read(t1, t2, *(uint64_t *)(t1 + t2), 64); [*1] tci_write_reg64(t0, *(uint64_t *)(t1 + t2)); break;
ld_i64å½ä»¤ãTCIãå®è¡ããã¨ãã[*1]ã§è¿½å ããã¦ããtrack_readé¢æ°ã§ããã¯ãã¦ãªãã©ã³ãå¤ãè¨é²ãã¦ããã
(tracers/qemu/qemu.path: 576è¡ç®)
+void track_read(target_ulong base, target_ulong offset, target_ulong data, int size) { + QIRA_DEBUG("read: %x+%x:%d = %x\n", base, offset, size, data); + if ((int)offset < 0) return; + if (GLOBAL_logstate->is_filtered == 0) add_change(offset, data, size); [*2] +}
track_readé¢æ°ã¯base,offset,data,sizeãåãããbaseã¯ä½¿ã£ã¦ããªãã
[*2]ã®add_changeã§base以å¤ã®offset,data,sizeããã¼ã¿ãã¼ã¹ã«è¨é²ããã
ããã¯ã¤ã¾ããä¾ãã°ld_64 0x38(T_AREG0), tmp0ã¨ããå½ä»¤ããã£ãå ´åã"ã¢ãã¬ã¹0x38ã«dataãsizeãã¤ãæ¸ãè¾¼ã¾ãã"ã¨ããããã«DBã«è¨é²ãããã
é常0x38ãªã©ã¨ããã¡ã¢ãªã«ã¢ã¯ã»ã¹ããäºã¯ç¡ãããããã®ãããªé常ã«å°ããç¯å²ã®ã¢ãã¬ã¹ã¸ã®ã¢ã¯ã»ã¹ã¯å¾ã§qiraã®ããã³ãã¨ã³ãããDBãèªãæã«ãã¬ã¸ã¹ã¿ã¸ã®ã¢ã¯ã»ã¹è¨é²ã ã¨å¤æããããã«ãªã£ã¦ããã
ã¡ãªã¿ã«add_changeé¢æ°ã«ãã£ã¦è¨é²ããããã©ãã¯å±¥æ´ã®ãã©ã¼ãããã¯ä»¥ä¸ã®ãããªæ§é ä½ã§è¡¨ãããã
(tci.c)
// struct storing change data struct change { uint64_t address; uint64_t data; uint32_t changelist_number; uint32_t flags; }; #define IS_VALID 0x80000000 //æå¹ãªãã¼ã¿ã(ãããã°ç¨ï¼) #define IS_WRITE 0x40000000 //æ¸ã込㿠#define IS_MEM 0x20000000 //ã¡ã¢ãªã¢ã¯ã»ã¹ (ãªãä¸ä½bitã¯ãã¼ã¿ã®ãµã¤ãº) #define IS_START 0x10000000 //[address, address+data]ãå½ä»¤é åã¨ããäºãç¥ãããããã®ãã©ã° #define IS_SYSCALL 0x08000000 //ã·ã¹ãã ã³ã¼ã«éå§ #define SIZE_MASK 0xFF
5. Webãµã¼ãã¼ã®èµ·åã¨DBã®åæ
3ç« ã®[*6]ã®run_serverã§webãµã¼ãã¼ãèµ·åãã¦ãããæ¬æ§ã§ã¯ããããwebãµã¼ãã¼ã®åä½ã追ã£ã¦ãããã
(qira_webserver.py)
def run_server(largs, lprogram): # web static moved to external file import qira_webstatic qira_webstatic.init(lprogram) ... threading.Thread(target=mwpoller).start() [*1] try: socketio.run(app, host=qira_config.HOST, port=qira_config.WEB_PORT, log=open("/dev/null", "w")) [*2]
[*1]ã§ã¯mwpollerã¨ããé¢æ°ãå®è¡ããå¥ã®ã¹ã¬ãããèµ·åãã¦ãããmwpoller(qira_webserver.py)ã¯mwpollé¢æ°ã0.2ç§ãã¨ã«å¼ã¶ã ãã®å®è£
ã§ããã
ãã®mwpollé¢æ°ã¯ãã¬ã¼ã¹ä¸ã®ã¿ã¼ã²ãããã¤ããªã®å
¨ããã»ã¹(forkããã°åããã»ã¹ãå¢ããããããç£è¦ãã¦ãããã1ã¿ã¼ã²ãããã¤ããªã«å¯¾ãã¦è¤æ°ã®ããã»ã¹ãåå¨ããå¯è½æ§ããã)ã®DBããã§ãã¯ããå
容ãæ´æ°ããã¦ããã°ãããweb UIå´ã«åæ ããããããªå®è£
ã«ãªã£ã¦ããã
(qira_webserver.py)
def mwpoll(): # poll for new traces, call this every once in a while for i in os.listdir(qira_config.TRACE_FILE_BASE): if "_" in i: continue i = int(i) if i not in program.traces: program.add_trace(qira_config.TRACE_FILE_BASE+str(i), i) [*3] did_update = False # poll for updates on existing for tn in program.traces: if program.traces[tn].db.did_update(): [*4] t = program.traces[tn] t.read_strace_file() socketio.emit('strace', {'forknum': t.forknum, 'dat': t.strace}, namespace='/qira') did_update = True # trace specific stuff if program.traces[tn].needs_update: push_trace_update(tn) if did_update: program.read_asm_file() [*5] push_updates(False)
[*3]ã®add_traceã§ã¯åããã»ã¹ç¨ã®Traceã¹ã¬ããã追å ãã¦ããããã®Traceã¹ã¬ããã¯è§£æ対象ã®ãã¤ããª1ããã»ã¹ã®æ å ±ã管çããã¹ã¬ããã§ããã¤ããªã®éç解æããã¬ã¼ã¹DBã®ä½æãªã©ãè¡ãã詳ããè¦ã¦ãããã
(middleware/qira_program.py)
def add_trace(self, fn, i): self.traces[i] = Trace(fn, i, self, self.tregs[1], len(self.tregs[0]), self.tregs[2]) return self.traces[i] class Trace: def __init__(self, fn, forknum, program, r1, r2, r3): ... threading.Thread(target=self.analysis_thread).start() def analysis_thread(self): #print "*** started analysis_thread" while 1: time.sleep(0.2) # so this is done poorly, analysis can be incremental if self.maxclnum == None or self.db.get_maxclnum() != self.maxclnum: [*6] self.analysisready = False minclnum = self.db.get_minclnum() maxclnum = self.db.get_maxclnum() self.program.read_asm_file() [*7] self.flow = qira_analysis.get_instruction_flow(self, self.program, minclnum, maxclnum) [*8] self.dmap = qira_analysis.get_hacked_depth_map(self.flow, self.program) qira_analysis.analyse_calls(self) # hacky pin offset problem fix hpo = len(self.dmap)-(maxclnum-minclnum) if hpo == 2: self.dmap = self.dmap[1:] self.maxd = max(self.dmap) self.picture = qira_analysis.get_vtimeline_picture(self, minclnum, maxclnum) self.minclnum = minclnum self.maxclnum = maxclnum self.needs_update = True
Traceã¹ã¬ããã§å®è¡ãããanalysis_threadé¢æ°ã¯0.2ç§ãã¨ã«ã«ã¼ããåããQEMUã®ãã¬ã¼ãµãåãåºããDBããã§ãã¯ãããã¡ãªã¿ã«DBã®å®ä½ã¯/tmp/qira_logs/ã®ä¸ã«ããã(0,1ãªã©ã®é£çªã®ååã«ãªã£ã¦ãã)
ãã®DBããã§ãã¯ã[*6]ã§DBå
ã®ææ°ã¨ã³ããªã¼çªå·self.db.maxclnum()ã以åããæ´æ°ããã¦ããå ´åã¯ãDBãæ´æ°ãããã¨ã¿ãªãã¦ããã
ãã¦DBã®æ´æ°ã確èªããå¾ã¯[*7]ã®read_asm_fileã§ã§å®éã«QEMUä¸ã§å®è¡ãããã¿ã¼ã²ãããã¤ããªã®ã¢ã»ã³ããªãã¼ã¿ãèªã¿åºãããã®é¢æ°ã¯QEMUã-d in_asmãªãã·ã§ã³ã§åãåºããã¢ã»ã³ããªãã¡ã¤ã«(/tmp/qira_asm)ãèªã¿ã«è¡ãããã¼ã¹ããã
(qira_program.py)
def read_asm_file(self): if os.name == "nt": return ... for d in dat.split("\n"): thumb = False if len(d) == 0: continue # hacks try: if self.fb == 0x28: #thumb bit in front addr = int(d.split(" ")[0][1:].strip(":"), 16) else: addr = int(d.split(" ")[0].strip(":"), 16) except: continue if self.fb == 0x28: ... elif self.fb == 0xb7: # aarch64 inst = d[d.rfind(" ")+5:] else: inst = d[d.find(":")+2:] cnt += 1 # trigger disasm d = self.static[addr]['instruction'] [*9]
/tmp/qira_asmãã¡ã¤ã«ã¯ä»¥ä¸ã®ãããªããã¹ããã¡ã¤ã«ã«ãªã£ã¦ããã
0xffffffffc0082040: xchg %ax,%ax 0xffffffffc0082045: push %rbp 0xffffffffc0082046: mov $0xffffffffc0084100,%rdi 0xffffffffc008204d: mov %rsp,%rbp 0xffffffffc0082050: callq 0xffffffff810f8220 0xffffffffc0082055: xor %eax,%eax
read_asm_fileã§ãèªã¿åºãã/tmp/qira_asmã®ãã¼ã¿ããã¼ã¹ããããã ãããã§ãã¼ã¹ããã®ã¯0xffffffffc0082040:ãªã©ã®ã¢ãã¬ã¹ã ãã§ãå½ä»¤åã¯è¦ãªãã
[*9]ã®addrããã¼ã¹ãã¦å¾ãã¢ãã¬ã¹ã§ãããself.static[addr]['instruction']ã«ã¢ã¯ã»ã¹ããã¨__getitem__ã¡ã³ããå®è¡ãã該å½addrç®æã®ãã£ã¹ã¢ã»ã³ãã«ãè¡ããã¦æ ¼ç´ãããå®è£
ã«ãªã£ã¦ããã
self.static[addr]ã¯Tagsã¯ã©ã¹ã«ãªã£ã¦ããããããã¼ã¿ãåå¾ãããã¨ã¢ã¯ã»ã¹ããã¨Tagsã¯ã©ã¹ã®__getitem__ãå¼ã°ããã
(model.py)
class Tags: def __init__(self, static, address=None): self.backing = {} self.static = static self.address = address def __contains__(self, tag): return tag in self.backing def __getitem__(self, tag): if tag in self.backing: return self.backing[tag] else: # should reading the instruction tag trigger disasm? # and should dests be a seperate tag? if tag == "instruction": dat = self.static.memory(self.address, 0x10) [*10] # arch should probably come from the address with fallthrough self.backing['instruction'] = Instruction(dat, self.address, self.static[self.address]['arch']) [*11] self.backing['len'] = self.backing['instruction'].size() self.backing['type'] = 'instruction' return self.backing[tag] if tag == "crefs" or tag == "xrefs": # crefs has a default value of a new array self.backing[tag] = set() return self.backing[tag] if tag in self.static.global_tags: return self.static.global_tags[tag] return None
Tagsã®__getitem__ã§ã¯ã¾ã[*10]ã®self.static.memoryé¢æ°ã§äºãéç解æã¨ã³ã¸ã³(radare2ãªã©)ã§èªã¿è¾¼ãã§ãããã¿ã¼ã²ãããã¤ããªã®è©²å½ã¢ãã¬ã¹ãã0x10ãã¤ãèªã¿ã ãã¦ããã
(static/static2.py)
# return the memory at address:ln # replaces get_static_bytes # TODO: refactor this! def memory(self, address, ln): dat = [] for i in range(ln): ri = address+i # hack for "RuntimeError: dictionary changed size during iteration" for (ss, se) in self.base_memory.keys(): if ss <= ri and ri < se: try: dat.append(self.base_memory[(ss,se)][ri-ss]) [*12] break except: return ''.join(dat) return ''.join(dat)
base_memory[(start_addr, end_addr)]ã«[start_addr, end_addr]ã®ã¢ãã¬ã¹ç¯å²ã«ãããã¤ããªåãæ ¼ç´ããã¦ããã[*12]ã§å¼æ°ã§ä¸ãããã[address, address+len]ã®ç¯å²ã®ãã¤ããªãã¼ã¿ãbase_memoryããåãåºãã¦ããã
ããã§å¯¾è±¡ã¢ãã¬ã¹ããå½ä»¤åãåå¾ãããã¨ãã§ããã
åå¾ããå½ä»¤åã¯[*11]ã§Instructionã¯ã©ã¹ã¨ãã¦åæåããã
(static/model.py)
class Instruction(object): def __new__(cls, *args, **kwargs): if qira_config.WITH_BAP: try: return BapInsn(*args, **kwargs) except Exception as exn: print "bap failed", type(exn).__name__, exn return CsInsn(*args, **kwargs) else: return CsInsn(*args, **kwargs)
ãã®Instructionã¯ã©ã¹ã¯BAP(Binary Analysis Platform)ã®ä½¿ç¨ãã©ã°ããªã³ã«ãªã£ã¦ããå ´åã¯BapInsnã¯ã©ã¹ã«ãªããããã§ãªãå ´åã¯Capstoneã¨ãããã£ã¹ã¢ã»ã³ãã«ãã¬ã¼ã ã¯ã¼ã¯ã使ã£ãCsInsnã¯ã©ã¹ã«ãªããã©ã¡ããä¸ãããããã¤ããªãã¼ã¿ããã£ã¹ã¢ã»ã³ãã«ãã¦è¨é²ãã¦ããããã®ã¯ã©ã¹ã§ããã
ãã¦ã§ã¯analysis_threadã«æ»ã£ã¦ãæ®ãã¯analysis_threadé¢æ°[*8]ã®get_instruciton_flowãå§ãã¨ãããã¤ããªã®è§£æé¢æ°ã ãããããã¯ã°ã©ããã¥ã¼ãé¢æ°ã®ã³ã¼ã«ã¨ã¬ã¼ã¹ãä½æããããã«å¼ã³åºããã¦ããã
ããã§ã¯è©³ãã説æããªããèå³ã®ãã人ã¯å®è£
ãèªãã§ã¿ãã¨é¢ç½ããããããªãã
6. Web UIä¸ã¸ã®è¡¨ç¤º
5ç« ã§èª¬æããwebãµã¼ãã¼ã¯QEMUãã¬ã¼ãµã¼ã®åºåããDBãç£è¦ããå
é¨ãã¼ã¿ãé次æ´æ°ãã¦ããããã®æ´æ°ããããã¼ã¿ã¯Web UIå´ã«socket IOãéãã¦éãããã
qira_webserver.pyå
ã«ãã@socketio.onãWeb UIå´ããæå®ãããã¡ãã»ã¼ã¸ãåä¿¡ããå ´åã®åä½ã«ãªããä¾ãã°å½ä»¤åãã³ãã®UI(web/client/idump.js)ã¯'getinstructions'ã¨ãããªã¯ã¨ã¹ããsocketioãéãã¦ãµã¼ãã¼å´ã«éä¿¡ããããããåãããµã¼ãã¼å´ã®å¦çã¯qira_webserver.pyã«å®è£
ããã¦ããã
(qira_webserver.py)
@socketio.on('getinstructions', namespace='/qira') [*] @socket_method def getinstructions(forknum, clnum, clstart, clend): trace = program.traces[forknum] slce = qira_analysis.slice(trace, clnum) ret = [] top = [] clcurr = clnum-1 while len(top) != (clnum - clstart) and clcurr >= 0: rret = get_instruction(clcurr) [*1] if rret != None: top.append(rret) clcurr -= 1 clcurr = clnum while len(ret) != (clend - clnum) and clcurr <= clend: rret = get_instruction(clcurr) if rret != None: ret.append(rret) clcurr += 1 ret = top[::-1] + ret emit('instructions', ret)
[*1]ã§get_instructionãå¼ã³åºãã¦å¤æ´å±¥æ´clcurrçªå·ã«è©²å½ããå½ä»¤åãåå¾ãã¦ããã
(qira_webserver.py)
def get_instruction(i): rret = trace.db.fetch_changes_by_clnum(i, 1) [*2] if len(rret) == 0: return None else: rret = rret[0] instr = program.static[rret['address']]['instruction'] rret['instruction'] = instr.__str__(trace, i) #i == clnum ...
[*2]ã§ãµã¼ãã¼ãä¿åãã¦ããQEMUãã¬ã¼ãµã¼ã®DBããå½ä»¤ãèªã¿ã ãã¦ããã
UIã«ãããã¬ã¸ã¹ã¿ã®è¡¨ç¤ºãåãããã«'getregisters'ãªã¯ã¨ã¹ããsocket IOã§é£ã°ãã¦è¡ãããã®è¾ºãã¯qira_webserver.pyã«ç¶²ç¾ çã«è¨è¿°ããã¦ããããèå³ã®ãã人ã¯èªãã§ã¿ã¦ã»ããã
7. ãããã«
ãã¦ä»åqiraã®ä¸»è¦ãªå¦çã追ã£ããããã®ããã«å¤ãã®ãã¬ã¼ã ã¯ã¼ã¯ãã½ããã¦ã§ã¢ãçµã¿åããã¦åæçã«ãã¼ã¿ãæ´æ°ãããããªè¨è¨ã¯ãã»ãã¥ãªãã£ãã¼ã«ã ã¨é常ã«å¤ãã
ç¹ã«qiraã¯æ¢åã®QEMUãéç解æã¨ã³ã¸ã³(radare2, ida sdk)ãªã©ããã¾ãçµã¿åããã¦ãããæ¬è¨äºãqiraãå§ãã¨ããæ§ã
ãªè§£æãã¼ã«ããã¯ã®1æ©ã«ãªãã°å¹¸ãã§ããã