Introducere în Angr

  • 3 minute de citit
  • 3 exemple de cod
  • 1 imagine

Disponibil și în English

Mereu mi-am dorit să experimentez cu un framework de analiză a binarelor, dar de cele mai multe ori m-a descurajat cât de greu era de instalat și de folosit. Recent m-am gândit să încerc angr și acum vreau să îmi împărtășesc experiența! Vă voi prezenta două scripturi care rezolvă două provocări. Dacă doriți să aprofundați și să învățați Angr, ar trebui să vizitați documentația oficială.

angr este un framework Python pentru analiza binarelor. Combină analiza simbolică statică și dinamică („concolică”), ceea ce îl face potrivit pentru o varietate de sarcini.

Pentru mine, cea mai ușoară cale de a instala Angr și de a-l face să funcționeze din prima încercare a fost să descarc Kali Linux, să îl instalez în VirtualBox (asigurați-vă că aveți cel puțin 12 GB spațiu pe disc) și să execut: pip install angr.
De aici puteți configura mediul de dezvoltare Python în Kali după cum doriți.

Pentru prima provocare avem următorul cod sursă:

//written by bla#include <stdio.h>#include <string.h>#include <unistd.h>int main(int argc, char **argv){        int count = atoi(argv[1]);        int buf[10];        if(count >= 10 )                 return 1;        //printf("%lx\n", (size_t)(count * sizeof(int)));        memcpy(buf, argv[2], count * sizeof(int));        if(count == 0x574f4c46) {        printf("WIN!\n");                //execl("/bin/sh", "sh" ,NULL);    } else                printf("Not today son\n");        return 0;}

Sursa provocării: level-7

Scopul este să găsim două argumente pe care să le dăm programului astfel încât buf să depășească și să suprascrie count, iar programul să afișeze WIN. Putem încerca să rezolvăm asta prin încercare și eroare, prin depanare, prin calcul sau putem lăsa Angr să o rezolve în locul nostru cu următorul script Python.

import angrimport claripydef resolve_win(state):    # dacă octeții „WIN” se găsesc în stdout, returnează true    return  b"WIN" in state.posix.dumps(1)if __name__ == '__main__':    print("starting.")    # Declarăm proiectul și încărcăm binarul    proj = angr.Project('./lab-13/0-tutorial/level07')    # Creăm un bitvector simbolic de 88 de biți numit „sym_arg” pentru primul argument    arg1 = claripy.BVS('sym_arg', 8 * 11)  # maximum 11 * 8 bits    arg2 = claripy.BVS('sym_arg', 8 * 44)  # maximum 44 * 8 bits    # Construim un entry_state care primește cele două argumente    st = proj.factory.entry_state(args=['./level07', arg1, arg2])    # Modificarea st.libc.max_strtol_len îi spune reprezentării simbolice atoi/strtol să    # rezolve șiruri de cel mult 11 octeți (valoarea implicită este 10)    st.libc.max_strtol_len = 11    # Acum vom crea ceea ce în terminologia angr se numește un simulation manager.    # https://docs.angr.io/core-concepts/pathgroups    pg = proj.factory.simgr(st)    # Se poate citi astfel: explorează în căutarea unui drum p pentru care starea curentă    # p.state conține șirul „WIN” în ieșirea standard (p.state.posix.dumps(1),    # unde 1 este descriptorul de fișier pentru stdout).    pg.explore(find=resolve_win)    print("solution found")    s = pg.found[0]    print(s.posix.dumps(1)) # dump stdout    # Afișăm și evaluăm primul argument    print("Arg1: ", s.solver.eval(arg1, cast_to=bytes))    # Afișăm și evaluăm al doilea argument    print("Arg2: ", s.solver.eval(arg2, cast_to=bytes))

Rularea scriptului ne va oferi soluția pentru acest binar. Dacă binarul s-ar schimba puțin (valoarea count), tot putem rula scriptul și obținem o soluție.


Următoarea provocare este mai ușoară, binarul se numește multiple-styles și poate fi descărcat de aici:

Uitându-ne la ieșirea dezasamblării acestuia:

dezasamblarea multiple-styles Putem vedea că programul face următoarele lucruri:

  1. Apelează read, care citește „parola” de la stdin într-un buffer.
  2. Încarcă șirul „myvnvsuowsxs}ynk” într-un buffer.
  3. Parcurge buffer-ul octet cu octet, adaugă 10 la fiecare 00400a27 add dword [rbp-0x54 {var_5c_2} {var_5c_1}], 0xa și îl compară cu șirul încărcat anterior.
  4. Dacă se potrivesc, sare la 0x00400a6c și afișează „you got it!”

În acest moment am putea căuta pe Google un decriptor online pentru cifrul Cezar, să lipim șirul încărcat și să îl decriptăm cu un decalaj de -10, dar vom lăsa angr să „descifreze” parola în locul nostru.

import angrimport claripyif __name__ == '__main__':    print("starting")    proj = angr.Project("./multiple-styles", auto_load_libs=False)    # Creăm un bitvector simbolic de 160 de biți numit „password”    password = claripy.BVS('password', 20*8)    # Construim un blank_state cu adresa lui main și transmitem password către stdin    st = proj.factory.blank_state(addr=0x004009ae, stdin=password)    # Creăm un simulation manager    pg = proj.factory.simulation_manager(st)    # Îi spunem lui angr să caute 0x00400a6c, adresa de început a blocului verde    # care afișează „you got it!”, și să evite adresa 0x00400a40    pg.explore(find=(0x00400a6c), avoid=(0x00400a40))    print("solution found")    # Luăm soluția.    s = pg.found[0]    # Putem afișa conținutul lui stdin - 0:    print("Flag: ", s.posix.dumps(0))    # Putem obține și parola din bitvectorul nostru simbolic    print("Pass: ", s.solver.eval(password, cast_to=bytes))

Am folosit versiunea 8.19.7.25 a Angr pentru a scrie scripturile. Consultați documentația oficială a Angr dacă doriți să aflați mai multe!

Vă mulțumesc că ați citit! 😀

Referințe:

  • (provocarea 1)
  • (provocarea 2)