For nå ser de mest effektive tilnærmingene i praksis ut til å følge prinsippet om symbolsk utførelse. Denne teknikken, opprinnelig utviklet for automatisk å bygge opp et sett med testtilfeller basert på en gitt kildekode, har nylig blitt brukt i binær analyse for å oppdage og (delvis) gjenopprette CFG i det analyserte binære programmet.
Mesteparten av tiden, hvis du vil håndtere flere forsamlingsspråk, må du bruke en mellomrepresentasjon for programmene. For nå har det vært mange av disse generiske modellene, og ingen fikk virkelig overlegenhet over de andre. Likevel ser de fleste populærene ut til å være den mellomliggende representasjonen av LLVM (mange verktøy bruker den), den nest mest populære ser ut til å være VEX, den mellomliggende representasjonen som brukes i Valgrind (som brukt i Angr). Den fra QEMU eller språket RREIL kan også være et alternativ, men de brukes mindre ofte. Men de fleste prosjektene kommer med sin egen mellomrepresentasjon.
Så, det du trenger for å bygge opp et CFG-gjenopprettingsprogram basert på symbolsk utførelse, er følgende moduler:
-
Loader : Denne modulen har ansvaret for å ta ditt binære program (det kan være en kjørbar eller et bibliotek) og simulere arbeidet til den opprinnelige lasteren for å bygge et realistisk minnebilde av hva du får etter at lasteprosessen er oppnådd.
-
Dekoder : Denne modulen har ansvaret for å oversette den innfødte forsamlingen som ble funnet mens den symbolsk utførte programmet til din egen mellomrepresentasjon. Det er en ganske lang (og smertefull) kode å implementere! Så vær forberedt på å lide mens du gjør dette.
Symbolisk utførelsesmotor : Vanligvis basert på en SMT-løser som bruker logikken QF_AUFBV (Quantifier Free / Arrays / Uninterpreted Function / Bitvectors), kan den symbolske utførelsesmotoren virkelig være en ytelsesflaskehals hvis du koder det naivt fordi gjenoppretting av CFG vil bruke det mye. Her er det virkelig viktig å ha en god formelforenklingsmodul (eller kutting).
Bortsett fra disse tre modulene, kan du også forbedre verktøyene dine ved å legge til mer avansert analyse og begynne å kode et abstrakt tolkningsrammeverk som kan legges til på toppen av den mellomliggende representasjonen din, bare for å få sjansen til å avdekke noen deler av CFG som ikke kan oppdages bare gjennom kraften til SMT-løsere.
Også ytelse er virkelig nøkkelen til å gjøre verktøyene dine virkelig brukbare. Så, å kunne fange det nøyaktige omfanget av en variabel eller muligheten til å oppdage en funksjon eller en modul / et objekt i binærkoden, hjelper mye å redusere størrelsen på koden du må vurdere på en gang.
Nå kan jeg gi deg mange tips og artikler om dette emnet, men jeg mangler litt tid. Jeg kan komme tilbake for å fullføre skrivingen av en omfattende liste senere, men den generelle ideen har forhåpentligvis blitt gitt ovenfor. Håper dette kan hjelpe deg.