Inżynieria wsteczna oraz znajdowanie �i wykorzystywanie luk w aplikacjach natywnych na x86 / x86_64�Wprowadzenie
Dominik Czarnota
22.03.2017
1
Inżynieria wsteczna (ang. reverse engineering) — proces badania produktu, w celu ustalenia jak on dokładnie działa.
Zastosowania:
2
Inżynieria wsteczna
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)
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:
4
Rejestry procesora
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...
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
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
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
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
Wywoływanie funkcji
Pamięć
0x0
RBP
RSP
RIP
push return_address� jmp 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
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
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
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
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
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
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
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
Źródła błędów
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
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
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
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]
Wykorzystanie -INT_MIN powoduje warning kompilacji:
23
Przepełnienie liczby całkowitej — ciekawostka
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
Przyczyny:
25
Przepełnienie bufora (ang. buffer overflow)
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
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
Dziękuję za uwagę
28