123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264(**************************************************************************)(* This file is part of BINSEC. *)(* *)(* Copyright (C) 2016-2026 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* you can redistribute it and/or modify it under the terms of the GNU *)(* Lesser General Public License as published by the Free Software *)(* Foundation, version 2.1. *)(* *)(* It is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU Lesser General Public License for more details. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file licenses/LGPLv2.1). *)(* *)(**************************************************************************)openGhidra_optionstypekind=|Fallthrough|Branch|Call|ReturnofVirtual_address.t|Presumed(* Main module : the graph representing the CFG *)moduleG=Graph.Imperative.Digraph.ConcreteBidirectionalLabeled(structtypet=Virtual_address.tletcompare=Virtual_address.compareletequal=Virtual_address.equallethash=Hashtbl.hashend)(structtypet=kindletdefault=Fallthroughletcompare=compareend)includeGletlink_return=letrecwalkcfgterminatorscallsuccvisited=function|[]->()|addr::todo->letvisited=Virtual_address.Set.addaddrvisitedinwalkcfgterminatorscallsuccvisited(fold_succ_e(fun(_,kind,addr)todo->ifkind==Call||Virtual_address.Set.memaddrvisitedthentodoelseifVirtual_address.Set.memaddrterminatorsthen(add_edge_ecfg(E.createaddr(Returncall)succ);todo)elseaddr::todo)cfgaddrtodo)infuncfgterminatorssuccaddr->iter_succ_e(function|_,Call,dest->walkcfgterminatorsaddrsuccVirtual_address.Set.empty[dest]|_->())cfgaddrletparse_cache~path=letic=open_inpathinletcfg=create()inletmnc=Virtual_address.Htbl.create0inletlexbuf=Lexing.from_channelicintryletcalls,terminators=List.fold_left(fun(calls,terminators)(addr,size,mnemonic,kind,succs)->Virtual_address.Htbl.addmncaddrmnemonic;match(kind,succs)with|"CALL_TERMINATOR",[dest]->(* CALL_TERMINATOR is used when a function tail calls another one,
so it is in fact an UNCONDITIONAL_JUMP *)add_edge_ecfg(E.createaddrBranchdest);(calls,terminators)|"COMPUTED_CALL",_->letsucc=Virtual_address.add_intsizeaddrinadd_edge_ecfg(E.createaddrPresumedsucc);List.iter(funsucc->add_edge_ecfg(E.createaddrCallsucc))succs;((addr,succ)::calls,terminators)|"COMPUTED_CALL_TERMINATOR",_->(* COMPUTED_CALL_TERMINATOR is used when a function tail calls
another one, so it is in fact a COMPUTED_JUMP *)List.iter(funsucc->add_edge_ecfg(E.createaddrBranchsucc))succs;(calls,terminators)|"COMPUTED_JUMP",_->List.iter(funsucc->add_edge_ecfg(E.createaddrBranchsucc))succs;(calls,terminators)|"CONDITIONAL_JUMP",[dest]->add_edge_ecfg(E.createaddrFallthrough(Virtual_address.add_intsizeaddr));add_edge_ecfg(E.createaddrBranchdest);(calls,terminators)|"FALL_THROUGH",[]->add_edge_ecfg(E.createaddrFallthrough(Virtual_address.add_intsizeaddr));(calls,terminators)|"TERMINATOR",[]->(calls,Virtual_address.Set.addaddrterminators)|"UNCONDITIONAL_CALL",[dest]->letsucc=Virtual_address.add_intsizeaddrinadd_edge_ecfg(E.createaddrPresumedsucc);add_edge_ecfg(E.createaddrCalldest);((addr,succ)::calls,terminators)|"UNCONDITIONAL_JUMP",[dest]->add_edge_ecfg(E.createaddrBranchdest);(calls,terminators)(* Skip jumps to external addresses *)|"UNCONDITIONAL_JUMP",[]->(calls,terminators)|_->Errors.not_yet_implementedkind)([],Virtual_address.Set.empty)(Parser_ghidra.instructionsLexer_ghidra.tokenlexbuf)inclose_inic;List.iter(fun(addr,succ)->link_returncfgterminatorssuccaddr)calls;(cfg,mnc)withParser_ghidra.Error->close_inic;letp=Lexing.lexeme_start_plexbufinLogger.fatal"lexeme was %s at line %d"(Lexing.lexemelexbuf)p.Lexing.pos_lnumletrun_ghidra?(temp_dir="/dev/shm")?cache~runnerexe=letid=Filename.temp_file~temp_dir""""inletbase=Filename.basenameidinletdir=base^".d"andlen=String.lengthbaseinletinclude_path=List.find(funpath->Sys.file_exists(path^"/ghidra_export.java"))Cli.Sites.utilsinletcommand=Format.sprintf"%s %s %s -import %s -scriptPath %s -postScript ghidra_export.java \
-scriptlog %s -deleteProject"runnertemp_dirdirexeinclude_pathidinLogger.debug"%s"command;ifSys.commandcommand<>0thenLogger.fatal"can not run ghidra";letreaddir=Sys.readdirtemp_dirinletlog=matchcachewithNone->id^".log"|Somep->pinletoc=open_out_binloginBasic_types.String.Set.iter(funf->letic=open_in(Printf.sprintf"%s/%s"temp_dirf)inletn=in_channel_lengthicinoutput_stringoc@@really_input_stringicn;close_inic)@@Array.fold_left(funsf->ifString.lengthf>len&&String.(equalbase(subf0len))thenBasic_types.String.Set.addfselses)Basic_types.String.Set.emptyreaddir;letic=open_inidinletn=in_channel_lengthicinoutput_stringoc@@really_input_stringicn;close_inic;close_outoc;letr=parse_cache~path:loginletcommand=Format.sprintf"rm %s*"idinifSys.commandcommand<>0thenLogger.fatal"can not remove tempory files";rmoduleL=Graph.Leaderlist.Make(G)letis_conditional_jumpgv=matchG.succ_egvwith|[(_,Branch,_);(_,Fallthrough,_)]|[(_,Fallthrough,_);(_,Branch,_)]->true|_->falseletpretty_dotppf(g,m)=letleaders=L.leader_listsg(Loader_utils.entry_point(Kernel_functions.get_img()))inletlabels=Virtual_address.Htbl.create(nb_vertexg)inletg'=G.create~size:(nb_vertexg)()inList.iter(funbb->letv=List.hdbbinVirtual_address.Htbl.addlabelsv(Format.asprintf"%a"(Format.pp_print_list~pp_sep:(fun_()->())(funppfv->Format.fprintfppf"%a: %s\\l"Virtual_address.ppv(tryVirtual_address.Htbl.findmvwithNot_found->"extern")))bb);lete=List.fold_left(fun_e->e)vbbinG.iter_succ_e(fun(_,k,d)->G.add_edge_eg'(v,k,d))ge)leaders;letmoduleD=Graph.Graphviz.Dot(structincludeGletgraph_attributes_=[`Orientation`Portrait;`Fontname"Courier"]letdefault_vertex_attributes_=[`Shape`Box]letvertex_namev=Format.asprintf"\"%a\""Virtual_address.ppvletvertex_attributesv=[`Label(Virtual_address.Htbl.findlabelsv)]letget_subgraph_=Noneletdefault_edge_attributes_=[]letedge_attributes=function|s,Fallthrough,_whenis_conditional_jumpg's->[`Headport`N;`Tailport`S;`Taillabel"f";`Fontcolor0xff5733;`Color0xff5733;]|_,Fallthrough,_->[`Headport`N;`Tailport`S]|s,Branch,dwhens=d&&is_conditional_jumpg's->[`Constraintfalse;`Dir`Back;`Headlabel"t";`Fontcolor0x25ac1e;`Color0x25ac1e;]|s,Branch,dwhens=d->[`Constraintfalse;`Dir`Back]|s,Branch,_whenis_conditional_jumpg's->[`Constraintfalse;`Taillabel"t";`Fontcolor0x25ac1e;`Color0x25ac1e;]|_,Branch,_->[`Constraintfalse]|_,Call,_->[`Color0x1e3aac]|_,Return_,_->[`Style`Invis;`Constraintfalse]|_,Presumed,_->[`Style`Dotted;`Headport`N;`Tailport`S]end)inD.fprint_graphppfg'letimport()=match(Cache.get_opt(),Runner.get_opt())with|Somepath,None->parse_cache~path|cache,Somerunner->run_ghidra?cache~runner@@Kernel_options.ExecFile.get()|_,_->Logger.fatal"missing either cache or analyzeHeadless script"let()=Cli.Boot.enlist~name:"Ghidra"~f:(fun()->ifis_enabled()thenLogger.result"%a"pretty_dot(import()))