LinuxovΘ noviny | B°ezen 1998 | ||||||||
| |||||||||
P°eteΦenφ bufferu je jednou z nejΦast∞j╣φch bezpeΦnostnφch d∞r v programech UNIXov²ch systΘm∙. Programovacφ jazyky, kterΘ umo╛≥ujφ rekurzivnφ volßnφ podprogram∙ (podprogram je rekurzivnφ, jestli╛e jeho novß aktivace m∙╛e zaΦφt je╣t∞ p°ed tφm, ne╛ se ukonΦφ jeho p°edchozφ aktivace) musφ pro jejich volßnφ pou╛φvat n∞jakou dynamickou strukturu, kterß udr╛uje informace pot°ebnΘ k ·sp∞╣nΘmu nßvratu z podprogramu. K tomuto ·Φelu se pou╛φvß zßsobnφk. OblastiNejd°φve si musφme vysv∞tlit, jak je proces organizovßn v pam∞ti. Proces je rozd∞len na t°i hlavnφ oblasti: text, data a zßsobnφk.
![]() Obrßzek 6: Pam∞╗ p°i startu programu Prßce se zßsobnφkemZßsobnφkovß oblast je souvisl² blok pam∞ti obsahujφcφ data. Na vrchol zßsobnφku ukazuje (u procesor∙ Intel) registr SP. Dno zßsobnφku je na pevnΘ adrese. Procesor pou╛φvß pro manipulaci se zßsobnφkem dv∞ instrukce: PUSH pro uklßdßnφ a POP pro vybφrßnφ dat ze zßsobnφku. Zßsobnφk v zßvislosti na typu procesoru roste bu∩ sm∞rem k ni╛╣φm nebo k vy╣╣φm adresßm. U procesor∙ Intel, SPARC a MIPS roste zßsobnφk sm∞rem k ni╛╣φm adresßm.Zßsobnφk pou╛φvajφ programy k volßnφ sv²ch podprogram∙, p°edßvßnφ parametr∙ a uklßdßnφ lokßlnφch prom∞nn²ch. Na zßsobnφku jsou ulo╛eny ve form∞ tzv. aktivaΦnφho zßznamu AZ. P°i implementaci p°id∞lovßnφ pam∞ti b²vß jeden registr vyhrazen jako ukazatel na zaΦßtek aktußlnφho aktivaΦnφho zßznamu. Vzhledem k tomuto registru se pak poΦφtajφ adresy datov²ch objekt∙ umφst∞n²ch v aktivaΦnφm zßznamu. U procesor∙ Intel se pou╛φvß registr BP (Base Pointer). Napln∞nφ tohoto registru a p°id∞lenφ novΘho aktivaΦnφho zßznamu je souΦßstφ volacφ posloupnosti (prologu) podprogramu. Volacφ posloupnost je rozd∞lena mezi volajφcφ a volan² podprogram. Volajφcφ ulo╛φ do zßsobnφku parametry p°edßvanΘ podprogramu. Pak zavolß pomocφ instrukce CALL volan² podprogram. Nßvratovß adresa je ulo╛ena na zßsobnφk. Volan² podprogram na zßsobnφk ulo╛φ ukazatel na star² aktivaΦnφ zßznam (BP), pak do ukazatele BP ulo╛φ vrchol zßsobnφku a nakonec vyhradφ mφsto pro lokßlnφ prom∞nnΘ. Podprogram potom inicializuje lokßlnφ prom∞nnΘ a zaΦne provßd∞t svΘ t∞lo. Jeden typick² p°φklad je na v²pisu P°φklad example1.c.
V²pis 7: P°φklad example1.c Pomocφ gcc vygenerujeme assemblerov² k≤d:
$ gcc -S -o example1.s example1.c P°φslu╣n² k≤d pro volßnφ funkce f vypadß nßsledovn∞:
pushl $3 pushl $2 pushl $1 call f Program ulo╛φ t°i argumenty v po°adφ od poslednφho k prvnφmu na zßsobnφk a pak zavolß funkci f. Toto po°adφ uklßdßnφ parametr∙ na zßsobnφk umo╛≥uje snadnΘ volßnφ funkcφ s prom∞nliv²m poΦtem parametr∙ (funkce s v²pustkou - int funkce(...)). Instrukce call f ulo╛φ na zßsobnφk nßvratovou adresu (nßvrat je pak proveden instrukcφ RET). Volan² podprogram pak provede prolog:
/* ulo╛φ ukazatel na star² AZ do zßsobnφku */ pushl %ebp /* do BP ulo╛φ ukazatel na nov² AZ */ movl %esp,%ebp /* vyhrazenφ mφsta pro lokßlnφ prom∞nnΘ */ subl $20,%esp Ulo╛φ registr ukazujφcφ na stßvajφcφ aktivaΦnφ zßznam (ebp) a ulo╛φ do n∞j nov² ukazatel na prßv∞ vytvß°en² zßznam. Pak vytvo°φ mφsto pro lokßlnφ prom∞nnΘ. P°ekladaΦ zarovnßvß prom∞nnΘ na dΘlku slova (tzn. v na╣em p°φpad∞ 4B). Tak╛e bytov² buffer velikosti 5 byt∙ ve skuteΦnosti zabφrß 8 byt∙ a buffer2 zabφrß 12 byt∙. Proto je nutno od SP odeΦφst 20. Obsah zßsobnφku je znßzorn∞n na obrßzku Obsah zßsobnφku.
![]() Obrßzek 8: Obsah zßsobnφku Po provedenφ t∞la podprogramu je nutnΘ obnovit stav, kter² byl p°ed volßnφm podprogramu. Tento postup se naz²vß nßvratovß posloupnost (function epilog) a je op∞t rozd∞len mezi volan² a volajφcφ podprogram. Volan² podprogram odstranφ ze zßsobnφku lokßlnφ prom∞nnΘ a obnovφ ukazatel na p°edchozφ AZ. Potom pomocφ instrukce RET vrßtφ °φzenφ volajφcφmu podprogramu. Volajφcφ podprogram dokonΦφ nßvratovou posloupnost tφm, ╛e odstranφ ze zßsobnφku parametry p°edßvanΘ podprogramu. Nßvratovß posloupnost volanΘho podprogramu:
/* odstran∞nφ lokßlnφch prom∞nn²ch ze zßsobnφku */ movl %ebp,%esp /* obnovenφ ukazatele na AZ volajφcφho podprogramu */ popl %ebp /* nßvrat do volajφcφho podprogramu */ ret Nßvratovß posloupnost volajφcφho podprogramu:
/* odstran∞nφ p°edßvan²ch parametr∙ ze zßsobnφku */ addl $12,%esp P°eteΦenφ bufferuData se tedy do zßsobnφku vklßdajφ od vy╣╣φch adres k ni╛╣φm. V∞t╣ina operacφ se ov╣em provßdφ od ni╛╣φch adres k vy╣╣φm adresßm. Typick²m p°φkladem m∙╛e b²t kopφrovßnφ °et∞zc∙ (viz v²pis P°φklad example2.c).
V²pis 9: P°φklad example2.c Zde programßtor ud∞lal chybu, kdy╛ neo╣et°il stav, kdy je do prom∞nnΘ buffer ulo╛eno vφce dat ne╛ je jejφ velikost. To se mu ov╣em krut∞ vymstφ. Proto╛e je prom∞nnß buffer ulo╛ena na zßsobnφku, kter² roste od vy╣╣φch adres k ni╛╣φm, jsou p°epsßny v╣echny informace, kterΘ se nachßzejφ nad prom∞nnou buffer. Nane╣t∞stφ zde le╛φ takΘ nßvratovß adresa do volajφcφho podprogramu. P°i pokusu o nßvrat tedy s nejv∞t╣φ pravd∞podobnostφ dojde k poru╣enφ ochrany pam∞ti a k nßsilnΘmu ukonΦenφ procesu. Jak vypadß zßsobnφk p°ed a po volßnφ funkce strcpy() je na obrßzku Zßsobnφk.
![]() Obrßzek 10: Zßsobnφk Ale co s tφm? Zatφm to nevypadß na n∞jakou mo╛nost zneu╛itφ. Program se pokou╣el provΘst k≤d, kde ╛ßdn² k≤d nebyl a tak interpretovat v podstat∞ nßhodn² k≤d nebo sßhl do oblasti, ke kterΘ nem∞l p°φstup. Ale co se stane v p°φpad∞, kdy na danΘm mφst∞ skuteΦn∞ n∞jak² programov² k≤d bude? K≤d se jednodu╣e provede. Nejjednodu╣╣φ p°φpadZ°ejm∞ nejjednodu╣╣φ je spu╣t∞nφ shellu. Na nßvratovou adresu, kterou p°epsal p°φli╣ dlouh² °et∞zec umφstφme volßnφ jßdra execve pro spu╣t∞nφ shellu (/bin/sh). JedinΘ co musφme v∞d∞t je, jak takovΘ volßnφ vypadß (viz v²pis P°φklad example3.c).
V²pis 11: P°φklad example3.c
$ gcc -g -O example3.c -o example3 V²stup z gdb vypadß pro funkci main() nßsledovn∞:
(gdb) disas main Dump of assembler code for function main: 0x8048140 <main>: pushl %ebp 0x8048141 <main+1>: movl %esp,%ebp 0x8048143 <main+3>: pushl $0x0 0x8048145 <main+5>: pushl $0x0 0x8048147 <main+7>: pushl $0x8058828 0x804814c <main+12>: call 0x8048354 <execve> 0x8048151 <main+17>: xorl %eax,%eax 0x8048153 <main+19>: movl %ebp,%esp 0x8048155 <main+21>: popl %ebp 0x8048156 <main+22>: ret End of assembler dump. (gdb) Disassemblovan² v²stup z funkce execve():
0x8048354 <execve>: pushl %ebp 0x8048355 <execve+1>: movl %esp,%ebp 0x8048357 <execve+3>: pushl %ebx 0x8048358 <execve+4>: movl $0xb,%eax 0x804835d <execve+9>: movl 0x8(%ebp),%ebx 0x8048360 <execve+12>: movl 0xc(%ebp),%ecx 0x8048363 <execve+15>: movl 0x10(%ebp),%edx 0x8048366 <execve+18>: int $0x80 0x8048368 <execve+20>: movl %eax,%edx 0x804836a <execve+22>: testl %edx,%edx 0x804836c <execve+24>: jnl 0x804837e <execve+42> 0x804836e <execve+26>: negl %edx 0x8048370 <execve+28>: pushl %edx 0x8048371 <execve+29>: call 0x8050a44 <__normal_errno_location> 0x8048376 <execve+34>: popl %edx 0x8048377 <execve+35>: movl %edx,(%eax) 0x8048379 <execve+37>: movl $0xffffffff,%eax 0x804837e <execve+42>: popl %ebx 0x804837f <execve+43>: movl %ebp,%esp 0x8048381 <execve+45>: popl %ebp 0x8048382 <execve+46>: ret 0x8048383 <execve+47>: nop Nejd∙le╛it∞j╣φ Φinnostφ knihovnφ funkce execve je volßnφ jßdra vytvß°ejφcφ nov² proces. V Linuxu pro Intel se pro volßnφ jßdra pou╛φvß p°eru╣enφ int 80. ╚φslo funkce jßdra se p°edßvß v registru eax a p°φpadnΘ parametry pak v dal╣φch registrech. ╚φslo 0xb je prßv∞ Φφslo funkce v jßd°e, kterß p°epφ╣e stßvajφcφ k≤d nov²m k≤dem a spustφ jej. Nynφ by staΦilo pou╛φt tuto Φßst k≤du jako °et∞zec, kter²m p°eteΦeme buffer v nφ╛e uvedenΘm programu. Jsou zde ov╣em dva malΘ problΘmy. ╪et∞zec "/bin/sh" se do volßnφ execve() p°edßvß jako ukazatel na °et∞zec. Tento °et∞zec je umφst∞n v datovΘm segmentu. Proto╛e nem∙╛eme poΦφtat s tφm, ╛e program, kter² se sna╛φme napadnout bude mφt n∞kde v pam∞ti °et∞zec "/bin/sh" musφme si ho dodat sami. Druh² problΘm spoΦφvß v tom, ╛e k≤d obsahuje nulovΘ byty - chceme toti╛ pro p°eteΦenφ bufferu pou╛φt volßnφ strcpy(). ╪e╣enφ prvnφho problΘmu je nßsledujφcφ: °et∞zec "/bin/sh" umφstφme na konec na╣eho °et∞zce s k≤dem. Nynφ musφme ov╣em zjistit adresu tohoto °et∞zce. Pou╛ijeme k tomu sekvenci relativnφho skoku (jmp) a relativnφho volßnφ (call). Instrukci jmp umφstφme na zaΦßtek k≤du. Instrukci call na konec k≤du, t∞sn∞ p°ed °et∞zec "/bin/sh". Instrukce jmp provede relativnφ skok na instrukci call, kterß ulo╛φ do zßsobnφku nßvratovou adresu a provede skok na instrukci t∞sn∞ za jmp. Instrukce call ulo╛φ na zßsobnφk adresu nßsledujφcφ instrukce. Za call ov╣em nenφ instrukce, ale °et∞zec "/bin/sh". Tφmto pon∞kud komplikovan²m zp∙sobem jsme zφskali adresu °et∞zce "/bin/sh". Druh² problΘm vy°e╣φme pomocφ instrukce xor %eax,%eax, tak dostaneme do registru eax nulu bez uvedenφ nulovΘho bytu. Tak na v╣echna mφsta, kde by m∞la b²t nula umφstφme nulu a╛ v dob∞ b∞hu na╣eho k≤du.
V²pis 12: P°φklad example5.c
V²pis 13: P°φklad example4.c Z programu na v²pisu P°φklad example4.c pak zφskßme °et∞zec, kter² spolu s dal╣φmi Φßstmi pou╛ijeme p°i p°eteΦenφ bufferu. Program, kter² tento °et∞zec vytvo°φ je na v²pisu P°φklad example5.c. Tφmto zp∙sobem vygenerujeme °et∞zec, kter² bude na zaΦßtku obsahovat n∞kolik instrukcφ NOP (pro jistotu, nemusφ tam b²t), pak °et∞zec, kter² spou╣tφ program /bin/sh a nakonec adresu zaΦßtku °et∞zce, kterou zadßme jako parametr (touto Φßstφ p°epφ╣eme nßvratovou adresu). Nynφ ov╣em musφme zjistit, kde zaΦφnß prom∞nnß buffer na zßsobnφku. Asi nejjednodu╣╣φ zp∙sob je upravit program example2.c, aby nßm tuto adresu vytiskl (viz v²pis P°φklad example6.c).
V²pis 14: P°φklad example6.c FunkΦnost programu ov∞°φme nßsledovn∞:
$ gcc -o example5 example5.c $ gcc -o example6 example6.c $ ./example6 ^D Zßsobnφk: 0xbffffabc $ ./example5 0xbffffabc | ./example6 $ A nynφ na p∙vodnφm programu: $ ./example6 ^D Zßsobnφk: 0xbffffabc $ ./example5 0xbffffabc | ./example2 $ Pokud jsme postupovali sprßvn∞, tak se specißlnφm vstupem dosßhneme spu╣t∞nφ jinΘho programu (pokud to nenφ z°ejmΘ, zkuste nahradit °et∞zec "/bin/sh" °et∞zcem "/bin/ps"). P°edstavte si takovou chybu nap°φklad v programu finger (d°φve byl spou╣t∞n pod u╛ivatelem root). Zneu╛itφ?Mo╛nost zneu╛itφ takovΘto programßtorskΘ chyby obvykle zßvisφ na charakteristice danΘho programu. O zneu╛itφ se dß hovo°it zejmΘna v p°φpad∞ program∙ s prop∙jΦenφm identifikace vlastnφka (suid) nebo aplikacφ spu╣t∞n²ch s oprßvn∞nφm n∞koho jinΘho a Φtoucφ data od u╛ivatele (nap°. sφ╗ovΘ dΘmony). Asi nejznßm∞j╣φ p°φpad takovΘho druhu zneu╛itφ byl tzv. Morris∙v internetov² Φerv zneu╛φvajφcφ mimo jinΘ p°eteΦenφ bufferu p°i Φtenφ dat v dΘmonu finger(1).Obrana?SprßvnΘ programovacφ techniky :-). Zßm∞na funkcφ typu strcpy() za funkce strncpy(), gets() za fgets() a pod. Dal╣φmi mo╛nostmi jsou zejmΘna specißln∞ upravenΘ p°ekladaΦe nebo p°φmo patch do jßdra. O tom mo╛nß n∞kdy p°φ╣t∞...Pou╛itß literatura:
![]()
|