Diese Präsentation wurde erfolgreich gemeldet.
Die SlideShare-Präsentation wird heruntergeladen. ×

Základy reverse engineeringu a assembleru / KAREL LEJSKA, MILAN BARTOŠ [DEFENDIO]

Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Nächste SlideShare
MicroPython IoT vlaxa
MicroPython IoT vlaxa
Wird geladen in …3
×

Hier ansehen

1 von 116 Anzeige

Základy reverse engineeringu a assembleru / KAREL LEJSKA, MILAN BARTOŠ [DEFENDIO]

Herunterladen, um offline zu lesen

Přijďte se naučit základy, o které se můžete opřít. Pustíme se do crackmes od RubberDucka a probereme nějakou teorii okolo. Workshop volně vychází ze seriálu Nebojte se reverzního inženýrství. Je potřeba alespoň Windows 7, nejlíp 64bitový. Stačí, když poběží ve virtuálce. Stáhněte si zdrojáky a binárky. Budeme používat OllyDbg 2.01 a x64dbg.

Přijďte se naučit základy, o které se můžete opřít. Pustíme se do crackmes od RubberDucka a probereme nějakou teorii okolo. Workshop volně vychází ze seriálu Nebojte se reverzního inženýrství. Je potřeba alespoň Windows 7, nejlíp 64bitový. Stačí, když poběží ve virtuálce. Stáhněte si zdrojáky a binárky. Budeme používat OllyDbg 2.01 a x64dbg.

Anzeige
Anzeige

Weitere Verwandte Inhalte

Andere mochten auch (11)

Anzeige

Weitere von Security Session (20)

Aktuellste (19)

Anzeige

Základy reverse engineeringu a assembleru / KAREL LEJSKA, MILAN BARTOŠ [DEFENDIO]

  1. 1. RE WORKSHOP Karel Lejska Milan Bartoš security session 2016
  2. 2. Pojetí workshopu • Solidní základy pro reverzování • Windows: tradiční platforma pro reverzery • OllyDbg: nejoblíbenější debugger • x64dbg: částečná náhrada za nedokončený 64-bit OllyDbg, postupy bývají stejné • Slidy pro pozdější studium
  3. 3. Podklady • Nebojte se reverzního inženýrství I. • Nebojte se reverzního inženýrství II. • Nebojte se reverzního inženýrství III. • x86asm.net/links • ref.x86asm.net/coder32.html • ref.x86asm.net/coder64.html • Poděkování Lukáši „RubberDuck“ Čižmarovi • Security Cave
  4. 4. 1. část • Crackme #1 by RubberDuck • + opkódy instrukcí • + x64 • + x64 verze crackme #1
  5. 5. 1. workshop - nástroje • 32bitový debugger OllyDbg • OllyDbg 2.01 • 64bitový debugger x64dbg • x64dbg snapshot_2016-01-21_02-44
  6. 6. Crackme #1 by RubberDuck
  7. 7. OllyDbg – crackme1.exe
  8. 8. OllyDbg – nastavení eventů
  9. 9. OllyDbg – nastavení výjimek
  10. 10. crackme1.exe – 1. instrukce MOV • 00401000 B8 00000000 MOV EAX, 0 • 1. sloupec – virtuální adresa 00401000 • 2. sloupec – operační kód – opcode B8 00000000 • 3. sloupec – instrukce MOV EAX, 0 • OllyDbg disassembler: Intel syntax, MASM příchuť • OllyDbg AT&T syntaxe: MOVL $0, %EAX
  11. 11. 1. instrukce podrobně • B8 00000000 MOV EAX, 0 • Mnemonic MOV • Cílový 32bitový operand EAX • Zdrojový 32bitový operand 0x0 • Jednobajtový primární opkód 0xB8 • ref.x86asm.net/coder32.html#xB8 • Skupina opkódů B8+r MOV r16/32, imm16/32 • 32bitová konstanta 0x00000000 • Intel jí říká immediate value
  12. 12. IA-32 general-purpose registers • Osm 32bitových všeobecných registrů, kód 0-7 (3 bity) EAX ECX EDX EBX ESP EBP ESI EDI 0 1 2 3 4 5 6 7
  13. 13. General-purpose registers - příklady • EAX = 44332211 – 32 bitů, velikost DWORD • AX: 2211 – 16 bitů, velikost WORD • AL: 11 – dolních 8 bitů, velikost BYTE • AH: 22 – horních 8 bitů, velikost BYTE • EBP = 40302010 • BP: 2010 • Horní WORD nemá název a nejde ho adresovat • Pozn. 4 bitům (půlbajt) se říká nibble
  14. 14. Crackme1.exe – 2. instrukce CMP • 00401005 83F8 01 CMP EAX, 1 • Mnemonic CMP • Cílový 32bitový operand EAX • Zdrojový 32bitový operand 0x1 • Jednobajtový primární opkód 0x83 • ref.x86asm.net/coder32.html#x83 • Skupina opkódů 83 • ModR/M byte F8 = 11111000bin • zatím to víc neřešíme • 8bitová přímá (immediate) hodnota 0x01
  15. 15. Skupina opkódů 0x83 • Osm aritmetických a logických instrukcí • Operandy: r/m16/32, imm8 • Viz taky opkódy 80, 81
  16. 16. 2. instrukce podrobně • CMP: compare two operands • CMP nemění žádný operand • Operace CMP = operace SUB • SUB: subtract two operands • SUB EAX, 1: EAX = EAX - 1 • CMP nastavuje šest stavových příznaků (status flags): CF, PF, AF, ZF, SF, OF • Viz instrukční reference: • Příznaky jsou uloženy v registru EFlags
  17. 17. Registr EFlags, operace CMP • Šest stavových příznaků v bitech 0, 2, 4, 6, 7 a 11 • Hromada jiných, ty teď neřešíme • CMP nastaví ZF (Zero Flag), pokud mají operandy stejnou hodnotu • Stejně jako SUB nastaví ZF, pokud je výsledek odečítání nula
  18. 18. crackme1.exe – 3. instrukce JE • 00401008 74 0C JE SHORT 00401016 • Jednobajtový primární opkód 0x74 • ref.x86asm.net/coder32.html#x74 • Mnemonic JE: Jump if Equal • Mnemonic JZ: Jump if Zero • SHORT znamená skok v rozmezí -128 a +127 bajtů • Skupina opkódů 70-7F: 16 podmíněných skoků Jcc • 8-bit relative offset 0x0C • Relativní offset se přičítá k registru EIP
  19. 19. 3. instrukce JE a reg. EIP podrobně • EIP: Instruction Pointer Register • EIP obsahuje virtuální adresu následující instrukce, tzn. v době spouštění instrukce JE je EIP 40100A • Operace: if (ZF == 1) EIP = EIP + sign extended rel8 • Kalkulace návěští (label) skoku: adresa instrukce JE je 401008, je dlouhá 2 bajty: 401008 + 2 + 0C = 401016
  20. 20. crackme1.exe – 4. instrukce MOV • 0040100A B8 14304000 MOV EAX, OFFSET 00403014 • Viz 1. instrukce MOV • OFFSET: MASM příchuť, důsledek analýzy – OllyDbg rozeznal, že jde o adresu, ne o nahodilou hodnotu • Na adrese 403014 je řetězec "Please patch me! :("
  21. 21. crackme1.exe – 5. instrukce MOV • 0040100F B9 30000000 MOV ECX, 30 • Viz 1. instrukce MOV • Připomenutí: opkód B9 = B8+r, kód ECX je 1
  22. 22. crackme1.exe – 6. instrukce JMP • 00401014 EB 0A JMP SHORT 00401020 • ref.x86asm.net/coder32.html#xEB: JMP rel8 • Mnemonic JMP: Unconditional Jump • SHORT znamená skok v rozmezí -128 a +127 bajtů • 8-bit relative offset 0x0A • Relativní offset se přičítá k registru EIP • Kalkulace návěští skoku: 401014 + 2 + 0A = 401020
  23. 23. crackme1.exe – 9. instrukce PUSH • 00401020 51 PUSH ECX • ref.x86asm.net/coder32.html#x50: 50+r PUSH r16/32 • Skupina opkódů 50-57 • Uloží hodnotu operandu na zásobník ve dvou krocích: ESP = ESP – 4 *ESP = ECX • Zásobník je zatím prostě nějaká paměť, víc to neřešíme • OllyDbg: zásobník je vidět vpravo dole
  24. 24. Parametry WinAPI MessageBoxA() • Prototyp funkce z MSDN: int WINAPI MessageBox( _In_opt_ HWND hWnd, _In_opt_ LPCTSTR lpText, _In_opt_ LPCTSTR lpCaption, _In_ UINT uType );
  25. 25. Parametry WinAPI MessageBoxA() • Odpovídající instrukce PUSH 51 PUSH ECX ; uType 68 28304000 PUSH OFFSET 403028 ; lpCaption 50 PUSH EAX ; lpText 6A 00 PUSH 0 ; hWnd
  26. 26. Volací konvence stdcall • Konvence je vidět v prototypu: • int WINAPI MessageBox(); • WinDef.h (Windows SDKs): • #define WINAPI __stdcall • MSDN stdcall calling convention • Parametry jsou uložené zprava doleva • Zásobník čistí volaná funkce • Návratová hodnota je v registru EAX • Registry EAX, ECX a EDX může volaná funkce změnit, ostatní musí zachovat • stdcall používá většina WinAPI funkcí
  27. 27. Další formy instrukce PUSH • PUSH immediate value: buď 32bitová hodnota, nebo znaménkově rozšířený imm8 na 32 bitů 68 28304000 PUSH OFFSET 403028 6A 00 PUSH 0
  28. 28. crackme1.exe – 13. instrukce CALL • 00401029 E8 0E000000 CALL <jmp.&user32.MessageBoxA> • Trochu jinak: • CALL 0040103C • Jednobajtový primární opkód E8 CALL rel16/32 • Operace: PUSH EIP JMP 0040103C • Tento CALL volá MessageBoxA() nepřímo přes JMP umístěný na konci kódu, je to záležitost importování funkcí a assembleru MASM, toto teď neřešíme
  29. 29. Volání funkce: pár CALL - RET CALL func ... func: ... RET ; taky „RETN“ • Operace CALL: • ESP = ESP – 4 • *ESP = EIP • JMP func • Operace RET: • EIP = *ESP • ESP = ESP + 4
  30. 30. OllyDbg: základní akce • Step into (klávesa F7): dojde k přechodu na návěští CALL • Step over (klávesa F8): krokování pokračuje až po návratu z CALL • Run (klávesa F9): spustí debugovaný program, tzn. nekrokuje ho • Restart (Ctrl+F2): zabije aktuální session a načte debugovaný program znovu; dobrá věc, pokud krokováním dojdu donikam a potřebuju začít znovu
  31. 31. Disassembling MessageBoxA() 8BFF mov edi, edi 55 push ebp 8BEC mov ebp, esp 6A 00 push 0 FF75 14 push dword ptr [ebp+14] FF75 10 push dword ptr [ebp+10] FF75 0C push dword ptr [ebp+0C] FF75 08 push dword ptr [ebp+8] E8 A0FFFFFF call MessageBoxExA 5D pop ebp C2 1000 retn 10
  32. 32. MessageBoxA(): RETN 10 • C2 1000 RETN 10 • 0x10 = 16 • 16/4 = 4 DWORD parametry • Pseudooperace: ESP = ESP + 16 EIP = *ESP ESP = ESP + 4
  33. 33. crackme1.exe – 15. instrukce CALL • 00401030 E8 01000000 CALL <jmp.&kernel32.ExitProcess> • Trochu jinak: • CALL 00401036 • Viz předchozí instrukce CALL • Jde o ukončení procesu: funkce ExitProcess() v knihovně kernel32.dll • I když nikdy nedojde k návratu, stejně se použivá CALL a ne JMP
  34. 34. OllyDbg assembler • Assembluj MOV EAX, 5 na adrese 401000 (= EIP)
  35. 35. OllyDbg assembler • Např. nekonečná smyčka – instrukce MOV, SUB a JMP: 1. Assembluj MOV EAX, 5 na adrese 401000 (= EIP) 2. Assembluj SUB EAX, 1 na adrese 401005 3. Assembluj JMP 401005 na adrese 401008 • Změny se obarví červeně, super věc na učení se opkódů
  36. 36. Instrukce INT3; NOP; ADD [EAX], AL • CC INT3 • Výplň kódu (i dat) na místech, kam se nemá dostat řízení • Způsobí ladící výjimku; tu teď neřešíme • 90 NOP • No OPeration • Výplň pro zarovnání kódu • 0000 ADD [EAX], AL • Podezřelá instrukce • Inicializační hodnota při alokaci paměti, nejde o kód
  37. 37. Shrnutí pojmů (1) • Virtuální adresa • Opkód, primární opkód • Mnemonic instrukce • Operand instrukce • Intel syntaxe vs. AT&T syntaxe • Osm všeobecných registrů • Velikosti: DWORD, WORD, BYTE • Orientace v primárních opkódech • Registr EFlags, šest stavových příznaků, příznak ZF • Registr EIP, kalkulace návěští skoku • Volací konvence stdcall
  38. 38. Shrnutí pojmů (2) • Volání funkce: CALL – RET • Odstraňování parametrů ze zásobníku pomocí RET • OllyDbg assembler
  39. 39. Shrnutí instrukcí • MOV • CMP, SUB • JE, Jcc • JMP • PUSH • CALL • INT3 • NOP • ADD [EAX], AL
  40. 40. Problémy s označením architektury • Hromada zjednodušujících názvů pro komplikovanou architekturu: • Intel • IA-32 architecture • Intel64 architecture (nový „64-bit mode“) • IA-64 je úplně jiná (mrtvá) architektura • AMD64 architecture • x86 architecture, „legacy mode“ • 64-bit x86 architecture (nový „Long Mode“) • Microsoft • x86 • x64
  41. 41. Crackme #1 (x64) by RubberDuck
  42. 42. x64dbg – nastavení eventů
  43. 43. x64dbg – crackme1_x64.exe
  44. 44. crackme1_x64: 1. instrukce SUB • 000000013FC0100 48 83 EC 08 SUB RSP, 8 • Vypadá jako alokace 8 bajtů na zásobníku • Ve skutečnosti zarovnání zásobníku na 16 bajtů, vynucené volací konvencí (viz následující slajdy) • Komplikace při programování v assembleru • Jednobajtový primární operační znak 0x83 • ref.x86asm.net/coder64.html#x83 • Nová věc: prefix 48 REX.W • ref.x86asm.net/coder64.html#x48 • Nejčastější REX.W zvětší velikost operandu na 64 bitů • REX prefixy na x86 neexistují
  45. 45. 64-bit registers • Šestnáct 64bitových všeobecných registrů, kód 0-15 (4 bity) RAX RCX RDX RBX RSP RBP RSI RDI 0 1 2 3 4 5 6 7 R8 R9 R10 R11 R12 R13 R14 R15 8 9 10 11 12 13 14 15 • 64bitový instrukční ukazatel RIP
  46. 46. 64-bit general-purpose registers • RAX = 8877665544332211, velikost QWORD • EAX: 44332211 • AX: 2211 • AL: 11 • AH: 22 • RSP – ESP – SP – SPL • RBP – EBP – BP – BPL • RSI – ESI – SI – SIL • RDI – EDI – DI – DIL • R15 - R15D - R15W - R15B • Horní DWORD nejde adresovat a nemá název
  47. 47. crackme1_x64: 5. instrukce MOVABS • 000000013FC0100 48 B8 1430C03F01000000 MOVABS RAX, 13FC03014 • Jednobajtový primární opkód 0xB8 • Prefix 48 REX.W • Mnemonic MOVABS neexistuje, jde o MOV • lidová tvořivost, odpovídá to správnějšímu OFFSET v OllyDbg
  48. 48. crackme1_x64: volání MessageBoxA • Předávání parametrů je zhruba vidět: SUB RSP, 20 MOV RCX, 0 ; hWnd MOV RDX, RAX ; lpText MOVABS R8, 13FC03028 ; lpCaption MOV R9, RBX ; uType CALL <crackme1_x64.MessageBoxA> ADD RSP, 20 • První 4 parametry jdou vždycky do registrů RCX, RDX, R8 a R9 • Ale co ten SUB/ADD RSP, 20?
  49. 49. Volací konvence fastcall (1) • Pozor, nejde o x86 fastcall, i když je podobný • Ještě jednou, konvence bývá vidět v prototypu: • int WINAPI MessageBox(); • WinDef.h (Windows SDKs): #define WINAPI • tentokrát je to „prázdná“ hodnota, všechno je fastcall (s výjimkami) • MSDN x64 fastcall calling convention • První 4 parametry jdou vždycky do registrů RCX, RDX, R8 a R9, ostatní na zásobník • Zásobník čistí volaná funkce • Návratová hodnota je v registru RAX • Registry RAX, RCX, RDX, R8, R9, R10 a R11 může volaná funkce změnit, ostatní musí zachovat
  50. 50. Volací konvence fastcall (2) • První 4 parametry jsou v registrech, ale na zásobníku musí být tak jako tak alokovaný prostor, další jdou na zásobník jako u stdcall • Alokace a uvolnění 4 parametrů pomocí SUB/ADD RSP, 20 • 0x20 = 32 = 4*8
  51. 51. fastcall a zarovnání zásobníku • MSDN: The stack will always be maintained 16-byte aligned • Vstupní bod programu je vlastně vstupní bod funkce main() • Volání funkce zruší zarovnání na 16 bytů: 1. Před provedením CALL je zásobník zarovnaný na 16 bytů 2. CALL provede PUSH RIP (8 bytů) 3. Na vstupu do funkce je potřeba zásobník znovu zarovnat pomocí SUB RSP, 8
  52. 52. crackme1_x64: volání ExitProcess • ExitProcess() má jenom jeden parametr, ale na zásobníku je potřeba vždycky alokovat 4 parametry: SUB RSP, 20 ; 4*QWORD MOV ECX, 0 CALL crackme1_x64.RtlExitUserProcess
  53. 53. Shrnutí pojmů (x64) • 16 64bitových všeobecných registrů • 64bitová virtuální paměť, registr RIP • Velikost QWORD • Volací konvence fastcall • Zarovnání zásobníku • Prefixy REX
  54. 54. Úkoly 1. Crackněte crackme1 (x86 i x64) podle návodu v článku Nebojte se reverzního inženýrství I. 2. Crackněte crackme1 ještě jinak než podle návodu 3. Pokud v crackme1 změníte instrukci CMP na SUB, jaký vliv to bude mít na funkčnost programu? 4. Vytvořte přímo v OllyDbg assembleru co nejkratší kód, který způsobí vyčerpání paměti zásobníku
  55. 55. 2. část • Crackme #2 by RubberDuck • + opkódy instrukcí • + x64 verze crackme #2 • Stack, heap, virtual memory • Memory model, TEB (TIB) • Exception handling • Entry point, kód, data • Importy • Binární formát instrukce
  56. 56. Crackme #2 by RubberDuck
  57. 57. OllyDbg – crackme2.exe
  58. 58. crackme2.exe – 1. instrukce PUSH • 00401000 6A F5 PUSH -0B • ref.x86asm.net/coder32.html#x6A • 6A PUSH (sign extended) imm8 • imm8: 8bitová immediate value • Takže jinak (velikost položky na zásobníku je DWORD): • PUSH FFFFFFF5 • Je to první parametr volání WinAPI funkce GetStdHandle()
  59. 59. GetStdHandle() • MSDN: Retrieves a handle to the specified standard device (standard input, standard output, or standard error). HANDLE WINAPI GetStdHandle( _In_ DWORD nStdHandle ); • Potřebujeme návratovou hodnotu HANDLE • winnt.h (Microsoft SDKs): • typedef void *HANDLE; • Volací konvence stdcall: návratová hodnota je v EAX, ta je uložena pomocí následující instrukce na později
  60. 60. crackme2.exe – 3. instrukce MOV • 00401007 A3 95304000 MOV [403095], EAX • ref.x86asm.net/coder32.html#xA3 • A3 MOV moffs16/32, eAX • Opkódy A0-A3 • speciální forma MOV mezi AL/AX/EAX a pamětí • Registr eAX (tzn. AX nebo EAX): • accumulator, historicky optimalizovaný pro častý přístup • Spousta opkódů podporujících accumulator, umožňujících kratší kódování instrukcí (viz třeba 0x04, 0x05, 0x0C, ...) • Dědictví historie, dnes nemá podstatný význam • moffs - memory offset: konstatní hodnota adresuje paměť
  61. 61. crackme2.exe – 3. instrukce MOV • MOV [403095], EAX • 0x403095 = 4206741 = nezarovnaná paměť při ukládání DWORD hodnoty • Architektura x86 nevynucuje zarovnání paměti na určitou hodnotu • Pozor, architektura x64 typicky (dá se to vypnout na úrovni OS) vyžaduje zarovnání paměti na „přirozenou“ hodnotu, tzn. DWORD je potřeba uložit na adresu zarovnanou na 4 byty atd. • Připomenutí: 64-bit Windows navíc vynucuje zarovnání zásobníku na 16 bytů (ještě z jiných důvodů) • Přístup k zarovnané paměti je rychlejší
  62. 62. crackme2.exe – 10. a 11. instr. PUSH • 00401024 68 00304000 PUSH OFFSET 00403000 • 00401029 FF35 95304000 PUSH DWORD PTR [403095] • ref.x86asm.net/coder32.html#xFF • Skupina různých instrukcí • FF /6 PUSH r/m16/32 • ModR/M byte 0x35 (viz dál) • DWORD PTR: označení velikosti odkazované hodnoty
  63. 63. crackme2.exe – porovnání hesel • WriteConsoleA(): vypíše výzvu k zadání hesla • ReadConsoleA(): přečte zadané heslo • lstrcmp(): WinAPI funkce porovnávající řetězce, podobná funkci ze standardní C knihovny int WINAPI lstrcmp( _In_ LPCTSTR lpString1, _In_ LPCTSTR lpString2 ); • „If the strings are equal, the return value is zero.“
  64. 64. crackme2.exe – vyhodnocení hesla • 85C0 TEST EAX, EAX • Zvláštní případ na vyhodnocení nulové hodnoty v registru • Opkódy 84 a 85: TEST r/m, r • ModR/M byte C0 • TEST: logical compare • TEST nemění žádný operand • Operace TEST = operace AND • TEST nastavuje šest stavových příznaků, viz: • Smysl dávají tři: SF, ZF a PF • 0 AND 0 = 0 → nastaví se ZF
  65. 65. crackme1.exe – vyhodnocení CMP • crackme1: 83F8 01 CMP EAX, 1 74 0C JE SHORT 00401016 • crackme2: 85C0 TEST EAX, EAX 74 1E JZ SHORT 00401081 • TEST EAX, EAX má kratší opkód než CMP EAX, 0 • Zajímavost: OllyDbg disassembler vyhodnotil, že vhodnější je mnemonic JZ
  66. 66. Binární formát instrukce (opkód) • Teoreticky: • Formát instrukce je jenom jeden (ignorujeme VEX) • Instrukce se skládá z 6 částí • Primární opkód se vyskytuje vždycky, ostatní volitelně 1. Prefix(y) 2. Primární opkód: 1, 2 nebo 3 byty, volitelně +3 bity 3. ModR/M byte 4. SIB byte (zatím neřešíme) 5. Displacement / memory offset 6. Immediate
  67. 67. Crackme #2 (x64) by RubberDuck
  68. 68. x64dbg – crackme2_x64.exe
  69. 69. crackme2_x64: úvod • Zarovnání zásobníku na 16 bytů • 48 83 EC 08 sub rsp, 8 • Volání GetStdHandle, uložení 64bitové HANDLE • 48 83 EC 20 sub rsp, 20 • B9 F5 FF FF FF mov ecx, FFFFFFF5 • E8 3C 01 00 00 call <crackme2_x64.GetStdHandle> • 48 83 C4 20 add rsp, 20 • 48 89 05 7E 20 00 00 mov qword ptr [7FF60549309B], rax
  70. 70. crackme2_x64: RIP-rel addressing • 48 89 05 7E200000 MOV [7FF60549309B], RAX • Operand vypadá jako disp64, ale ten neexistuje • Jde o RIP-relative addressing: • ref.x86asm.net/coder64.html#modrm_byte_32_64 • Ve skutečnosti: • 48 89 05 7E200000 MOV [RIP+207E], RAX
  71. 71. Crackme2_x64: WriteConsoleA() • WriteConsoleA() má pět parametrů • Alokace jakoby 6 parametrů na zásobníku • 1 QWORD navíc je zase zarovnání na 16 bytů 48 83EC 30 sub rsp, 30 ; 48dec = 6*QWORD 48 8B0D 5A200000 mov rcx, [7FF60549309B] 48 BA ... movabs rdx, crackme2_x64.7FF605493000 41 B8 31 00 00 00 mov r8d, 31 49 B9 ... movabs r9, crackme2_x64.7FF605493093 48 C7 44 24 20000000 mov qword ptr [rsp+20], 0 E8 F1000000 call <crackme2_x64.WriteConsoleA> 48 83C4 30 add rsp, 30
  72. 72. x64 stack a volání WriteConsoleA() • Stack po SUB RSP, 30: RSP+28: zarovnání RSP+20: parameter lpReserved RSP+18: R9 home: parametr lpNumberOfCharsWritten RSP+10: R8 home: parametr nNumberOfCharsToWrite RSP+08: RDX home: parametr *lpBuffer RSP+00: RCX home: parametr hConsoleOutput
  73. 73. Stack: rychlá dočasná paměť • LIFO (Last In First Out) • Roste směrem k nižším adresám • rSP: Stack Pointer register; pod ním je všechno volatile • rSP ukazuje na platnou položku, ne pod • Alokace jednoho DWORD: rSP = rSP – 4 • Jazyk C: alokace automatic variables probíhá na stacku • Páry instrukcí • CALL – RET; PUSH – CALL – RET imm16 • PUSH – POP • INT – IRET (doručení přerušení a návrat z přerušení) • Každý thread má svůj stack
  74. 74. Stack frame – „rámec funkce“ • Problém: rSP se často mění • rBP: stack frame register push ebp ; EBP je nonvolatile (stdcall convention) mov ebp, esp sub esp, x ; alokace automatic variables ... ; konec prologu mov eax, [ebp+y] ; výběr parametru funkce mov [ebp-z], eax ; inicializace automatické proměnné ... ; začátek epilogu mov esp, ebp ; uvolnění proměnných – ukončení scope pop ebp ; obnova nonvolatile registru ret
  75. 75. Paměť stacku (1) • OllyDbg: menu View, Memory map
  76. 76. Paměť stacku (2) • Počáteční hodnota ESP při startu procesu: 1A0000 • Velikost alokovaná systémem: 3000 (12 KB, 3 stránky) • Hodnota ESP na entry pointu: 19FF84 • Tzn. nějaká inicializace a zavolání kódu programu zabralo 31 položek (DWORDs) na stacku • Za alokovaným zásobníkem na adrese 19B000 je guard page (několik) o velikosti 2000 (8 KB, 2 stránky) • Další guard pages jsou od adresy 95000 o velikosti B000 (44 KB) • Vyčerpání stacku nastane na adrese A3000 • Konkrétní hodnoty se liší podle aplikace a podle verze OS
  77. 77. Stack a guard page • Cílem je při inicializaci procesu alokovat jenom tolik stacku, aby stačil po celou dobu jeho života • Další paměť se alokuje dynamicky „na požádání“ čtením nebo zápisem pod alokovanou paměť • Exception zachytí memory manager, alokuje další stránku • Paměť zasobníku nemusí alokovat jenom zvláštní instrukce, ale i přímý přístup k rSP, např. SUB ESP, 100 • Alokace příliš velkých bloků paměti může obejít guard pages, je proto potřeba provádět stack probe nebo použít _alloca()
  78. 78. Velikost položky na 32-bit zásobníku • Vždycky DWORD • Zarovnání se (na Windows) neočekává • Příklady: • PUSH 11223344 • PUSH EAX • PUSH DWORD PTR [EAX] • Jde ale i WORD: • PUSH 1122 • PUSH AX • PUSH WORD PTR [EAX]
  79. 79. Velikost položky na 64-bit zásobníku • Vždycky QWORD • Pozor, Windows očekává zarovnání zásobníku na 16 bytů před voláním (CALL) funkce • Příklady: • PUSH 11223344 (jde jenom sign extended imm32) • PUSH RAX • PUSH QWORD PTR [RAX] • Paradox: nejde DWORD, ale WORD ano: • PUSH EAX • PUSH AX • PUSH 1122 • PUSH WORD PTR [RAX]
  80. 80. Heap („hromada“) • Vysokoúrovňový memory management • Heap má svůj HANDLE • Proces má víc heaps najednou • Windows loader vytvoří default Process heap • WinAPI: HeapAlloc (GlobalAlloc, LocalAlloc) • C runtime heap: malloc, operator new • Viz Sysinternals VMMap
  81. 81. Manipulace s virtuální pamětí • Nízkoúrovňový přístup přímo ke stránkám paměti • 4KB stránka • Rezervace virtuálních adres (reserve) • Alokace virtuálních adres (commit) • VirtualAlloc, VirtualQuery • VirtualProtect: dynamická generace a spuštění kódu • Virtuální paměť = nefyzická paměť
  82. 82. Přístupné virtuální adresy • Proces nemůže přistupovat ke všem existujícím adresám (neplatí pro drivery) • OllyDbg to dokáže částečně zobrazit • 32-bit Windows: • část prostoru musí zůstat systému • první 2 GB paměti (adresy 0x00000000 až 0x7FFFFFFF) • 4-gigabyte tuning (aka /3GB switch): prvním 3 GB paměti (0x00000000 až 0xBFFFFFFF) • 64-bit Windows: • 32-bit proces: vždy celé 4 GB, bez ohledu na další nastavení • 64-bit proces: teoreticky všechny 64-bit adresy, prakticky omezení systémem, např. Windows 7 - 192 GB, Windows 10 - 2 TB
  83. 83. Paměťový model (memory model) • Flat memory model • Všechno se řeší přes stránkování (paging) • Kontinuální (nesegmentovaný) virtuální paměťový prostor, tzn. všechny segmenty mají bázi 0, viz OllyDbg • Zjednodušeně: paměť začíná od adresy 0 až po nejvyšší hodnotu (danou systémem) • Reziduum segmentace: paměť TEB (TIB) • 32-bit Windows: adresace pomocí FS segmentu • SEH chain: FS:[0] • 64-bit Windows: GS segment; FS je nevyužitý
  84. 84. Thread Information Block (1) • aka Thread Environment Block • Nedokumentovaná struktura (Bingem jde vygooglit) • Všechny informace o threadu • 32-bit Windows • adresace pomocí segmentu FS • OllyDbg: FS 0053 32bit 3C0000(FFF) • 64-bit Windows • adresace pomocí segmentu GS • x64Dbg neumí zobrazit báze segmentů
  85. 85. Thread Information Block (2) • V OllyDbg Memory map „Data block of main thread“
  86. 86. Thread Information Block (3)
  87. 87. Structured Exception Handling (1) • Jenom 32-bit Windows • FS:[0] pointer na: EXCEPTION_REGISTRATION STRUCT prev DWORD ? ; předchozí handler handler DWORD ? ; aktuální handler EXCEPTION_REGISTRATION ENDS • OllyDbg: View – VEH/SEH chain
  88. 88. SEH (2) • Instalace vlastního handleru do řetězce handlerů: push pointer_to_exception_handler push dword ptr fs:[0] mov fs:[0], esp • Odinstalace: pop dword ptr fs:[0] add esp, 4
  89. 89. SEH: ukázka • OllyDbg assembler: • Instalace handleru na 0040101A • INT 3 vyvolá výjimku • JMP způsobí zacyklení
  90. 90. Table-based exception handling • 64bitový Windows nezná SEH • Table-based exception handling je popsaný v datech, neinstaluje se během spouštění kódu
  91. 91. Entry point, kód, importy, data • PE header je taky v paměti • Entry point určuje PE header • Kód, importy, data atd. načtené ze sekcí v souboru
  92. 92. Importy • „.rdata“ sekce: read-only initialized data • Knihovny, které program používá • OllyDbg: View – Executable modules • User-mode systémové knihovny načtené těsně pod hranicí 2 GB
  93. 93. Shrnutí instrukcí • MOV • CMP, SUB • TEST, AND • JE, Jcc • JMP • PUSH, POP • CALL, RET • Adresace paměti pomocí [moffs] a [disp32] • INT3 • NOP • ADD [EAX], AL
  94. 94. Domácí úkoly 1. Crackněte crackme2, jakobyste neznali heslo 3. Pokud nahradíme instrukci TEST za AND, jak se změní chování crackme2? 4. Jakou jinou logickou instrukci se stejnými operandy a stejným testováním výsledku bysme mohli k vyhodnocení použít?
  95. 95. Crackme3 • Jenom naživo, slidy nejsou
  96. 96. 3. část: C crackmes • Crackmes napsané v jazyce C • Jenom naživo, slidy nejsou
  97. 97. Dekompilace crackme2 (asm verze) void __cdecl start(UINT a1) { hConsoleOutput = GetStdHandle(0xFFFFFFF5); hConsoleInput = GetStdHandle(0xFFFFFFF6); WriteConsoleA(hConsoleOutput, aCrackme2ByRubb, 0x2Bu, &Ncw, 0); ReadConsoleA(hConsoleInput, String1, 0x14u, &NumberOfCharsRead, 0); if ( lstrcmpA(String1, String2) ) WriteConsoleA(hConsoleOutput, aNo_ThisIsnTGoo, 0x22u, & Ncw, 0); else WriteConsoleA(hConsoleOutput, aGoodJobCracker, 0x15u, & Ncw, 0); ExitProcess(a1); }
  98. 98. Dekompilace crackme3 (asm verze) --Ncr; if ( (signed int)--Ncr <= 3 || Ncr > 0xE ) WriteConsoleA(hConsoleOutput, aLoginMustHaveM, 0x39u, &Ncw, 0); else { WriteConsoleA(hConsoleOutput, aInsertPassword, 0x12u, &Ncw, 0); ReadConsoleA(hConsoleInput, String1, 0x64u, &Ncr, 0); for ( i = &unk_4030AB; *i; ++i ) { sub_40117C((unsigned __int8)*i, (int)&word_40318B); *v0 = word_40318B; } String1[sub_401170((int)String1) - 2] = 0; sub_4011AE((int)v0, v1, (int)String1); if ( lstrcmpA(String1, String2) ) WriteConsoleA(hConsoleOutput, aNo_ThisIsnTGoo, 0x22u, &Ncw, 0); else WriteConsoleA(hConsoleOutput, aGoodJobCracker, 0x15u, &Ncw, 0); }
  99. 99. Díky za pozornost! karel@defendio.net milan@defendio.net RubberDuck@security-portal.cz
  100. 100. Nedokončeno • Sorry, nestíhám • Formát Portable Executable • Oficiálně: PECOFF verze 8.3
  101. 101. Přídavek: formát instrukce podrobně • Pokud lidi neodejdou, poveselit je ještě popisem formátu instrukce
  102. 102. Jenom jednobajtový primární opkód • 90 NOP – No OPeration • CC INT3 • 50+r PUSH r • 5 horních bitů opkód (01010), 3 dolní bity kód registru • Příklady: • 01010 000 PUSH rAX • 01010 001 PUSH rCX • 01010 010 PUSH rDX • 01010 111 PUSH rDI
  103. 103. Registr zakódovaný v opkódu + imm • B8+r MOV r32/64, imm32/64 • BA 10203040 MOV EDX, 40302010 • Registrů na x64 je 16, tzn. jsou potřeba 4 bity pro kód • Přístup k novým registrům pomocí prefixu REX.B 41 BA 10203040 MOV R10D, 40302010 • Přístup k operandům r64, imm64 pomocí prefixu REX.W: 48 BA 1020304050607080 MOV RDX, 8070605040302010 • Kombinace - prefix REX.WB: 49 BA 1020304050607080 MOV R10, 8070605040302010
  104. 104. REX prefixy • Opkódy 40-4F • ref.x86asm.net/coder64.html#x40 • x64: 16 registrů, ale jednotlivých 8bitových částí je 20... • Opkód 40 - „plain“ REX prefix přepíná: AH CH DH BH bez jakéhokoliv REX prefixu SPL BPL SIL DIL s jakýmkoliv REX prefixem
  105. 105. ModR/M byte • 2 bity 7-6: pole Mod • 3 bity 5-3: kód registru nebo 3-bit opcode extension • 3 bity 2-0: kód registru nebo kód paměťového operandu
  106. 106. ModR/M byte: operandy reg, reg • 85C0 TEST EAX, EAX • Obecně: • 85 /r TEST r/m16/32, r16/32 • /r v manuálech značí přítomnost ModR/M byte • ModR/M C0: 11‘000‘000 • Mod 11: operand R/M je registr • Reg 000: EAX • R/M 000: EAX
  107. 107. ModR/M: ruční disassembling 0xC0 • 85C0 TEST EAX, EAX • ref.x86asm.net/coder32.html#modrm_byte_32
  108. 108. ModR/M: ruční diassembling 0x00 • 0000 ADD [EAX], AL • 00 /r ADD r/m8, r8
  109. 109. Reg/Opcode obsahuje 3-bit opkód • crackme1: • 83F8 01 CMP EAX, 1 • Obecně: • ref.x86asm.net/coder32.html#x83 • 83 /7 CMP r/m16/32, imm8 • /digit značí ModR/M byte s 3bitovou opcode extension • F8 = 11‘111‘000 • Mod 11: R/M kóduje registr • Reg/Opcode 111: opcode extension 7 • R/M 000: registr EAX
  110. 110. Mod 00
  111. 111. Operand disp32 • crackme2: • 00401029 FF35 95304000 PUSH DWORD PTR [403095] • ModR/M 35
  112. 112. Mod 01 a 10 • Mod 01: přidání disp8 • Mod 10: přidání disp32
  113. 113. ModR/M: nejenom GP registry • ModR/M kóduje i MMX, XMM, segment, control a debug registry, ty neřešíme
  114. 114. Motivace pro ruční disassembling • Chyby a lidová tvořivost v disassemblerech • Disassembler někdy není po ruce • Patchování, hlavně když není dost místa • Shellcode: „cení se každý ušetřený bajt, který neohrozí funkčnost shellcodu“ • Dis/assemblování z hlavy v hospodě
  115. 115. Shrnutí opkódů • A0-A3: zvláštní skupina opkódů, jsou další • rAX: operace s „accumulator“ často využívají vlastní kratší opkódy • Jenom jeden formát instrukce • Jednobajtový opkód (NOP) • Jednobajtový opkód + kód registru • Některé REX prefixy • Kompletní ModR/M byte • Displacement, memory offset • Immediate
  116. 116. Trocha historie • Původně se registr SP nedal pro adresaci paměti vůbec použít (8086) • Vyřešilo to přidání SIB byte později (80286)

×