Reverse Engineering
Platformmal kapcsolatos kérdések és a platform relevanciája reverse engineeringben Felhasználók száma
Szomorú helyzet de ez a piac állapota
Első “programunk”
Az Assembly kód számunkra releváns része
Mi történik a háttérben? – 1 Többnyire valamilyen szövegszerkesztő vagy IDE segítségével elkészítjük a forráskódot majd lefordítjuk Forráskód (src/ “source code”): Source code is any collection of computer instructions (possibly with comments) written using some human-readable computer language, usually as text.
Mi történik a háttérben? – 2 A forráskódból a fordító program (natív nyelveknél) assemblyt készít Ez még mindig szöveges formátum, olvasható, struktúrált
Mi történik a háttérben? – 3 Az asm kódból assembler segítségével object kód készül Az object kód az asm utasításoknak megfelelő bytecode szintű parancsokból áll. Ez függ a célplatform architektúrájától és ezáltal utasításkészletétől is.
Mi történik a háttérben? – 4 A különálló object kódok összelinkelése után megkapjuk a végleges binárist (exe, dll, sys, elf, so, ko, …) Dinamikus vs Statikus linkelés Ezt már az OS értelmezni, futtatni tudja
Mi történik a háttérben? – 5 Az OS application loadere a gépi kód betöltésekor számos dolgot hajt végre: betöltés memóriába relokáció dependency module-ok betöltése …
Ezek után fut a programunk
A bináris tartalma (Windows PE) Gépi kód (pl.: x86, amd64, arm, 8085, ppc, Atmel AVR assembly) A gépi kód nem elég Metadata az OS számára Importok, exportok Globális változóknak szánt előre inicializált memóriaterületek Relocation Tables Egyéb resource-ok
Reverse Engineering Reverse engineering is the processes of extracting knowledge or design information from anything man-made and re-producing it or reproducing anything based on the extracted information. The process often involves disassembling something (a mechanical device, electronic component, computer program, or biological, chemical, or organic matter) and analyzing its components and workings in detail.
Reverse Engineering, mégis mire jó? Programok közötti együttműködés biztosítása (“Interoperability”) Létező program más rendszerbe integrálása Hiányos, vagy hiányzó dokumentáció pótlása Program analízis (pl. a kód hatékonyságára, vagy a fordító működésére) Biztonsági ellenőrzések, hibák javítása, pénz megspórólása Víruskeresés, software modernizáció “Military or commercial espionage” -> Learning about an enemy's or competitor's latest research by stealing or capturing a prototype and dismantling it. It may result in development of similar product, or better countermeasures for it.
Disassembler A disassembler is a computer program that translates machine language into assembly language—the inverse operation to that of an assembler. Ebbe a kategóriába tartoznak a következő programok például: IDA Pro, X64, OllyDbg, ObjDump, Visual Studio
Decompiler A decompiler is a computer program that takes as input an executable file, and attempts to create a high level, compilable source file that does the same thing. It is therefore the opposite of a compiler, which takes source files and makes an executable. Pl.: Snowman, Hex-Rays decompiler (IDA), Hopper
Debugger A debugger or debugging tool is a computer program that is used to test and debug other programs (the "target" program). Pl.: gdb, X64, IDA Pro, OllyDbg, WinDbg, Visual Studio
Patch-elés Egy program módosítása bytecode szinten, amivel valamilyen változást érünk el a működésében. Idejét tekintve lehet: Runtime: Futás közben módosítjuk a program gépi kódját a memóriában Pre modified: A program binárisát módosítjuk a háttértáron, majd a módosított változatot futtatjuk Célja: Crackelés, cheatelés, anti-debug kiütése, újrafordítás nélküli hibajavítás és számos más dolog (Legyetek kreatívak!)
RE Managed környezetben A managed assembly sokkal magasabb szintű, ezért a decompilation is sokkal egyszerűbb. A manage-elt bináris tartalmazza a függvények neveit és signatúráit, valamint a definiált struktúrák és osztályok leírását. Ezek az információk natív környezetben nem állnak rendelkezésre, extra kutatás árán lehet azokat rekonstruktálni. Természetesen ez az információ lehet, hogy nem áll rendelkezésre teljes egészében obfuscált programok esetében. .NET .NET Java Python
disassembler decompiler decompiler decompiler
ildasm ILSpy, .NET Reflector jad, jd-gui depyc, uncompyle2
Hogyan kezdjek hozzá?
Ami kelleni fog: végtelen türelem. A programok, amikről már esett szó:
Gdb (“GNU Debugger): Egy rugalmas, ingyenes, open source, C-ben írt debugger. Multiplatform (Több Unix szerű rendszer (Linux, yay!) és windows is (duh)) https://www.gnu.org/software/gdb/ X64: General purpose userspace debugger for Windows (duh); Open source és relatíve erős eszköz. http://x64dbg.com/ OllyDbg: General purpose userspace debugger for Windows (duh); Freeware http://www.ollydbg.de/ IDA Pro: a starter verzió ára 590 USD, ; a teljes változatai ilyen eladom a házam árkategóriában mozognak ; Freeware-ként elérhető a v5.0 lebutított változata, jelenleg v6.9-nél tartunk; multiplatform; https://www.hex-rays.com/ peda: Python Exploit Development Assistance; gdb plugin https://github.com/longld/peda
Gyakori vezérlési szerkezetek #include
void main() { int i; scanf_s("%d", &i); if (i != 0) { printf( "Hello CoreKor! THEN\n"); } }
Gyakori vezérlési szerkezetek #include void main() { int i; scanf_s("%d", &i); if (i != 0) { printf( "Hello CoreKor! THEN\n"); } }
Gyakori vezérlési szerkezetek #include void main() { int i; scanf_s("%d", &i); if (i != 0) { printf( "Hello CoreKor! THEN\n"); } }
Gyakori vezérlési szerkezetek #include void main() { int i; scanf_s("%d", &i); if (i != 0) { printf( "Hello CoreKor! THEN\n"); } }
Gyakori vezérlési szerkezetek #include void main() { int i; scanf_s("%d", &i); if (i != 0) { printf("Hello CoreKor! THEN\n"); } else { printf("Hello CoreKor! ELSE\n"); } }
Gyakori vezérlési szerkezetek #include void main() { int i; scanf_s("%d", &i); if (i != 0) { printf("Hello CoreKor! THEN\n"); } else { printf("Hello CoreKor! ELSE\n"); } }
Gyakori vezérlési szerkezetek #include void main() { int i; scanf_s("%d", &i); if (i != 0) { printf("Hello CoreKor! THEN\n"); } else { printf("Hello CoreKor! ELSE\n"); } }
Gyakori vezérlési szerkezetek #include void main() { int i; scanf_s("%d", &i); if (i != 0) { printf("Hello CoreKor! THEN\n"); } else { printf("Hello CoreKor! ELSE\n"); } }
Gyakori vezérlési szerkezetek #include void main() { for (int i = 0; i < 20; i++) { printf("Hello CoreKor! %d\n", i); } }
Gyakori vezérlési szerkezetek #include void main() { for (int i = 0; i < 20; i++) { printf("Hello CoreKor! %d\n", i); } }
Gyakori vezérlési szerkezetek #include void main() { for (int i = 0; i < 20; i++) { printf("Hello CoreKor! %d\n", i); } }
Gyakori vezérlési szerkezetek #include void main() { for (int i = 0; i < 20; i++) { printf("Hello CoreKor! %d\n", i); } }
Gyakori vezérlési szerkezetek #include void main() { int i; scanf_s("%d", &i); switch (i) { case 1: printf("Hello break; case 2: printf("Hello break; case 3: printf("Hello break; case 4: printf("Hello break; } }
CoreKor! 1\n"); CoreKor! 2\n"); CoreKor! 3\n"); CoreKor! 4\n");
Gyakori vezérlési szerkezetek #include void main() { int i; scanf_s("%d", &i); switch (i) { case 1: printf("Hello break; case 2: printf("Hello break; case 3: printf("Hello break; case 4: printf("Hello break; } }
CoreKor! 1\n"); CoreKor! 2\n"); CoreKor! 3\n"); CoreKor! 4\n");
Gyakori vezérlési szerkezetek #include void main() { int i; scanf_s("%d", &i); switch (i) { case 1: printf("Hello break; case 2: printf("Hello break; case 3: printf("Hello break; case 4: printf("Hello break; } }
CoreKor! 1\n"); CoreKor! 2\n"); CoreKor! 3\n"); CoreKor! 4\n");
Hívási konvenciók – x86 Egy függvény meghívásakor többféle módon használhatjuk a stack-et és adhatunk át paramétereket. A szokványos módokat nevezzük hívási konvencióknak. Pl.: caller clean-up: cdecl, syscall, ... callee clean-up: stdcall, ... thiscall, …
Hívási konvenciók – x86 cdecl Valószínűleg, ez a leggyakoribb amivel találkozni fogtok. A paraméterek átadása a stack-en történik (jobbról balra (azaz az eredeti sorrendhez képest fordítva)) A hívó szabadítja fel a stack-et
Hívási konvenciók – x86 cdecl
Hívási konvenciók – x86 cdecl
cdecl hívás
Paraméterek pusholása fordított sorrendben történik: cdeclfv(3,4) fog hívódni Stack felszabadítása
Hívási konvenciók – x86 cdecl Pareméterek és lokális változók
Ismét egy cdecl hívás Egyszerű return
Hívási konvenciók – x86 stdcall Az API hívásoknál használatos főleg (WinAPI pl) A hívott függvény szabadítja fel a stack-et A paramétereket ugyanúgy a stack-en kell átadnunk Dinamikus argumentum számú fv-eknél (Vararg) nem használható (pl.: printf)
Hívási konvenciók – x86 stdcall
Nincs add esp,8 → nem szabadítja fel a stack-et, mert belül fog megtörténni
Hívási konvenciók – x86 stdcall A különbség a függvényen belül: retn 8 → 8 byte-os felszabadítás a stack-re
Hívási konvenciók – x86 thiscall Objektumok függvényeinél alkalmazzák általában (C++ default calling convention, ha nincs dinamikus argumentum szám (vararg)) a paramétereket stack-en adjuk át this pointert pedig ECX registerben adjuk át A hívott fél szabadítja fel a stack-et (Gyakorlatilag stdcall + this pointer ecx-ben)
Tömbök Az alábbi kódrészlet látható a control flow gráfon: int arr[100]; // feltöltve for(int i = 0; i < 100; ++i) printf(“%d “, arr[i]);
Tömbök Optimalizálás Tömb megcímzése esivel (esi * 4, mert egy integer 4byte-on tárolódik X86 architektúrán Index növelése Kilépési feltétel ellenőrzése
Struktúrák / Objektumok Fieldek, VTable-k, függvények
Struktúrák / Objektumok – mezők struct Vector { int x, y, z; }; Vector* v = ...; int addr = (int)v; 12 byte ...
v->x
addr
v->y
addr+4
v->z
addr+8
...
addr+12
Struktúrák / Objektumok – mezők #include struct Vector { int x, y, z; }; void main() { Vector* v = new Vector(); scanf_s("%d%d%d", &v->x, &v->y, &v->z); v->x += 2; v->y *= 3; v->z /= 5; printf("%d %d %d", v->x, v->y, v->z); delete v; }
Struktúrák / Objektumok – mezők Function prologue 12 byte memória foglalása Vector *v = new Vector();
Ha sikerült a memóriafoglalás, akkor kinullázza a memóriaterületet
Struktúrák / Objektumok – mezők EBX tartalmazza a Vector példány kezdőcímét scanf_s("%d%d%d", &v->x, &v->y, &v->z); v->x += 2; v->y *= 3; v->z /= 5; printf("%d %d %d", v->x, v->y, v->z); delete v; Epilógus
Struktúrák / Objektumok – vtable // c++
// c void* Person_vtable[] = { (void*)Person_foo, (void*)Person_bar };
struct Person { int age; virtual int foo(); virtual void bar(); };
struct Person { void** vtable; // = Person_vtable; int age; };
Person* p = ...; p->bar();
Person* p = ...; (*(void (*)(Person*))(p->vtable[1]))(p);
// x86 asm
mov mov add call
ecx, ... eax, [ecx] eax, 4 eax
Struktúrák / Objektumok – vtable Osztályonként Példányonként Person példány void* vtable[] int age ... ...
Person vtable foo()
Futtatható memóriaterület függvény törzsek void Person::foo()
bar() ... ...
void Person::bar()