1 of 28

Inżynieria wsteczna oraz znajdowanie �i wykorzystywanie luk w aplikacjach natywnych na x86 / x86_64�Wprowadzenie

Dominik Czarnota

22.03.2017

1

2 of 28

Inżynieria wsteczna (ang. reverse engineering) — proces badania produktu, w celu ustalenia jak on dokładnie działa.

Zastosowania:

  • Analiza złośliwego oprogramowania
  • Modyfikacja aplikacji bez dostępu do źródeł
  • Analiza protokołów
  • Odzyskiwanie kodu źródłowego

2

Inżynieria wsteczna

3 of 28

Asemblery – rodzina języków programowania niskiego poziomu, których polecenia odpowiadają rozkazom procesora.

3

Asembler

$ objdump -d a.out -M intel | egrep "<main>" -A 5

0000000000400536 <main>:�400536: 55 push rbp�400537: 48 89 e5 mov rbp, rsp�40053a: bf d4 05 40 00 mov edi, 0x4005d4�40053f: e8 cc fe ff ff call 400410 <puts@plt>�400544: b8 00 00 00 00 mov eax, 0x0

operandy

Adresy instrukcji

Program “Hello world”

mnemoniki

Kod maszynowy �(zakodowane mnemoniki + operandy)

4 of 28

Rejestry – komórki pamięci o niewielkich rozmiarach umieszczone wewnątrz procesora. Służą do przechowywania tymczasowych wyników obliczeń, adresów w pamięci itp.

Większość procesorów przeprowadza działania wyłącznie korzystając z wewnętrznych rejestrów.

Niektóre rodzaje:

  • Ogólnego przeznaczenia – danych oraz adresowe
  • Segmentowe
  • Specjalnego przeznaczenia
  • Koprocesora

4

Rejestry procesora

5 of 28

5

Rejestry x86

Rejestry danych

Rejestry adresowe

Rejestry segmentowe

Rejestry specjalnego przeznaczenia

Źródło obrazka: wikipedia.pl

Dla systemów 64-bitowych:

rax, rbx, rcx, edx, rbp, rip itd...

6 of 28

Zawiera stan procesora

6

Rejestr EFLAGS

Wynik operacji (ze znakiem; ang. signed) nie zmieścił się rejestrze

Wynik ostatniej operacji był ujemny (MSB ustawiony)

Ostatnia operacja równa zero

Operacja powoduje przeniesienie (dodawanie) lub pożyczenie (odejmowanie)

Źródło obrazka: wikipedia.pl

7 of 28

7

Asembler x86 — dwie główne składnie

AT & T

Intel

Kolejność parametrów

Pierwsze źródło, potem cel

mov $5, %eax

Pierwsze cel, potem źródło

mov eax, 5

Wielkość parametrów

Mnemoniki zakończone znakiem określającym rozmiar (qword, long (dword), word, byte)

addl $0xbeef, (%ebx)

Rozmiar jawnie podany �(QWORD, DWORD, WORD, BYTE)

add DWORD PTR [ebx], 0xbeef

Prefix

Rejestry prefiksowane znakiem %, wartości znakiem $.

Asembler sam wykrywa typ symbolu.

Adresacja

Wyrażenia matematyczne zapisane w składni DISP(BASE, INDEX, SCALE):

movl mem_loc(%ebx, %ecx, 4), %eax

Wyrażenia arytmetyczne w nawiasach kwadratowych:�mov DWORD PTR eax,[ebx+ecx*4 + mem_loc]

8 of 28

8

Podstawowe instrukcje — przykłady

mov eax, 5

Skopiowanie wartości 5 do rejestru eax

lea eax, [ebp-0x20]

Skopiowanie wartości ebp-0x20 do rejestru eax.�Można to zapisać w ten sposób:

sub ebp, 0x20

mov eax, ebp

add ebp, 0x20

mov eax, [ebx + ecx*4 + 0x4]

eax = *(ebx + ecx*4 + 0x4)

lea eax, [ebx + ecx*4 + 0x4]

eax = ebx + ecx*4 + 0x4

push eax

Odłożenie wartości rejestru eax na stos

pop eax

Ściągnięcie wartości ze stosu do rejestru eax

jmp eax

Skok pod adres znajdujący się w rejestrze eax �(eip = eax)

cmp eax, 0xe

Porównanie wartości w eax z liczbą 14, ustawia flagi w rejestrze EFLAGS

jz 4006e6

Skok pod adres 0x4006e6, jeżeli ZF jest ustawiona

9 of 28

9

Wywoływanie funkcji

Pamięć

0x0

call foo

ret

foo:

push rbp

mov rbp, rsp

sub rsp, 0x4

leave

ret

RSP

RIP

push return_address� jmp foo

RBP

Odkładamy “wskaźnik powrotu” (saved_rip) na stos

ramka stosu funkcji main

0xFFFFFFFFFFFFFFFF

10 of 28

10

Wywoływanie funkcji

Pamięć

0x0

RBP

RSP

RIP

push return_addressjmp foo

call foo

ret

foo:

push rbp

mov rbp, rsp

sub rsp, 0x4

leave

ret

saved_rip

ramka stosu funkcji main

0xFFFFFFFFFFFFFFFF

Skaczemy do funkcji foo

11 of 28

11

Wywoływanie funkcji

Pamięć

0x0

RBP

RIP

push return_address� jmp foo

call foo

ret

foo:

push rbp

mov rbp, rsp

sub rsp, 0x4

leave

ret

Prolog funkcji:�odkładamy rbp funkcji main na stos

RSP

saved_rip

ramka stosu funkcji main

0xFFFFFFFFFFFFFFFF

12 of 28

12

Wywoływanie funkcji

Pamięć

0x0

0xFFFFFFFFFFFFFFFF

RBP

RSP

RIP

push return_address� jmp foo

call foo

ret

foo:

push rbp

mov rbp, rsp

sub rsp, 0x4

leave

ret

Prolog funkcji:�przypisujemy rbp = rsp

saved_rbp

saved_rip

ramka stosu funkcji main

13 of 28

13

Wywoływanie funkcji

Pamięć

0x0

0xFFFFFFFFFFFFFFFF

RBP, RSP

RIP

push return_address� jmp foo

call foo

ret

foo:

push rbp

mov rbp, rsp

sub rsp, 0x4

leave

ret

Prolog funkcji:�“alokujemy” pamięć na stosie

saved_rbp

saved_rip

ramka stosu funkcji main

14 of 28

14

Wywoływanie funkcji

Pamięć

0x0

0xFFFFFFFFFFFFFFFF

RIP

push return_address� jmp foo

call foo

ret

foo:

push rbp

mov rbp, rsp

sub rsp, 0x4

leave

ret

Epilog funkcji:�“przywracamy” poprzednią ramkę stosu

saved_rbp

saved_rip

ramka stosu funkcji main

RBP

RSP

mov rsp, rbp

pop rbp

15 of 28

15

Wywoływanie funkcji

Pamięć

0x0

0xFFFFFFFFFFFFFFFF

RIP

push return_address� jmp foo

call foo

ret

foo:

push rbp

mov rbp, rsp

sub rsp, 0x4

leave

ret

Epilog funkcji:�“przywracamy” poprzednią ramkę stosu

saved_rbp

saved_rip

ramka stosu funkcji main

RSP, RBP

mov rsp, rbp

pop rbp

16 of 28

16

Wywoływanie funkcji

Pamięć

0x0

0xFFFFFFFFFFFFFFFF

RIP

push return_address� jmp foo

call foo

ret

foo:

push rbp

mov rbp, rsp

sub rsp, 0x4

leave

ret

Wracamy z funkcji.

saved_rbp

saved_rip

ramka stosu funkcji main

mov rsp, rbp

pop rbp

RBP

RSP

17 of 28

17

Wywoływanie funkcji

Pamięć

0x0

0xFFFFFFFFFFFFFFFF

RIP

push return_address� jmp foo

call foo

ret

foo:

push rbp

mov rbp, rsp

sub rsp, 0x4

leave

ret

saved_rbp

saved_rip

ramka stosu funkcji main

mov rsp, rbp

pop rbp

RBP

RSP

18 of 28

  • Typy liczbowe
  • Przepełnienie bufora (ang. buffer overflow)
  • “Kopiuj-wklej”
  • Wyścig wątków (ang. data race)
  • Optymalizacje kompilatora

18

Źródła błędów

19 of 28

bool foo(double d) {�� if (d <= 34.0)return false; // d is too low

if (d > 34.0)return false; // d is too high

return true;

}

19

Liczby zmiennoprzecinkowe — IEEE754

20 of 28

In [1]: map(float, ['-0', '+0', '-inf', 'inf', 'nan'])

Out[1]: [-0.0, 0.0, -inf, inf, nan]

In [2]: nan = float('nan')

In [3]: nan > 34.0, nan < 34.0, nan == 34.0

Out[3]: (False, False, False)

In [4]: nan == nan, nan != nan

Out[4]: (False, True)

“What Every Computer Scientist Should Know About Floating-Point Arithmetic”, �David Goldberg

20

Liczby zmiennoprzecinkowe — IEEE754

21 of 28

bool bar(int n) {if (n < 0)� n = -n;�� n %= 3;�� if (n == 0 || n == 1 || n == 2)return false;

return true;

}

21

Liczby całkowite

22 of 28

dc@dc:/tmp$ cat a.c && gcc a.c -Wall -Wextra -Wpedantic && ./a.out

#include <stdio.h>#include <limits.h>#include <stdlib.h>��int main() {printf("%d %d\n", abs(INT_MIN), abs(INT_MIN+1));}

-2147483648 2147483647

22

abs(...) ma ten sam problem

zakres int32_t = [(-2)31, 231-1]

23 of 28

Wykorzystanie -INT_MIN powoduje warning kompilacji:

23

Przepełnienie liczby całkowitej — ciekawostka

24 of 28

W funkcji SSLVerifySignedServerKeyExchange:

if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)� goto fail;�if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)� goto fail;� goto fail;�if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)� goto fail;�

Impact: An attacker with a privileged network position may capture or modify data in sessions protected by SSL/TLS.

24

Błąd kopiuj/wklej — CVE-2014-1266

25 of 28

Przyczyny:

  • Używanie niebezpiecznych funkcji — gets, strcpy, sprintf — nie patrzących na rozmiar bufora
    • Bezpieczne odpowiedniki — strncpy, snprintf itd.
  • Błędy typu integer overflow
    • img.width * img.height
    • abs(INT_MIN)

25

Przepełnienie bufora (ang. buffer overflow)

26 of 28

26

Przepełnienie bufora (ang. buffer overflow)

26

Pamięć

0x0

0xFFFFFFFFFFFFFFFF

saved_rbp

saved_rip

ramka stosu funkcji main

RBP

RSP

Ramka stosu funkcji foo

Nadpisując RIP skaczemy w dowolne miejsce

27 of 28

bool isPasswordCorrect() {bool isPasswordCorrect = false;string Password("password");�� if(Password == getPasswordFromUser())� isPasswordCorrect = true;�� memset(Password, 0, sizeof(Password));�� return isPasswordCorrect;}

Źródło: Bug 8537 - bugzilla gcc - Optimizer Removes Code Necessary for Security

27

Optymalizacje kompilatora

28 of 28

Dziękuję za uwagę

28