რა არის Buffer Overflow ?

Aleksi Kistauri
ka_GE
Published in
6 min readJun 2, 2019

--

buffer

რა არის buffer-ი?

ეს არის გაშვებული პროგრამის მეხსიერების ადგილი, რომელიც წინასწარ არის რეზერვირებული და გამოიყენება დროებითი მონაცემების შესანახად. buffer-ი წარმოიქმნება პროგრამის გაშვებისას, ინახება RAM-ში ხოლო ნადგურდება პროგრამის გათიშვის დროს.

მაგალითად, გვაქვს პროგრამა, რომელიც მომხარებელს ეკითხება სახელს, ინახავს მას ცვალდში სახელად ‘username’ და აბრუნებს — ‘Hello username’.

ჩავშალოთ ჩვენი პროგრამა

int main() — განსაზღვრავს ჩვენ მთავარ ფუნქციას

char username[20] — ეს ის ადგილია სადაც განვსაზღვრავთ ცვლადის სახელს, მაგრამ ამ ხაზში ყველაზე მნიშვნელოვანია char …. [20] ეს არის ის ადგილი სადაც განვსაზღვრავთ buffer-ს ჩვენი ცვლადისთვის, ჩვენ განვუსაზღვრეთ მას ზომა, რომელიც 20 ის ტოლი უნდა იყოს.

დანარჩენი კოდი იღებს მომხარებლის შემოტანილ მნიშვნელობას და პრინტავს მას.

printf(“name : “);
scanf(“%s”, username);

printf(“Hello %s\n”, username);

რა შედეგს მივიღებთ თუ ჩვენ პროგრამას დავაკომპილირებთ და გავუშვებთ? ვიღებთ იმ აუთფუთს რასაც ველოდით, არა?

ახლა სანამ buffer overflow-ზე გადავალთ გავიგოთ თუ როგორ მუშაობს აპლიკაციის მეხსიერება.

აპლიკაციის მეხსიერება, stack-ი და მეხსიერების მისამართები

როგორ გამოიყურება აპლიკაციის მეხსიერება და რა არის stack?

stack ში გვაქვს მეხსიერების buffer-ი რომელიც გამოიყენება იმ ფუნქციების და ლოკალური ცვლადების შესანახად, რომლებიც პროგრამას ჭირდება მუშაობისთვის. სადემონსტრაციოდ ვნახოთ ფოტო:

თავდაპირველად ჩვენ გვაქვს კოდი რომელიც არის ჩვენი პროგრამის სორს კოდი, ის მოიცავს ჩვენი პროგრამის მთავარ ინსტრუქციებს.

შემდეგ ჩვენ გვაქვს buffer-ი რომელშიც შენახულია გლობალური ცვლადები.

ამის მერე ჩვენ გვაქვს stack, რომელიც როგორც ავღნიშნეთ ჩვენთვის ყველაზე მნიშვნელოვანია რადგან სწორედ აქ ხდება buffer overflow. ის არის ადგილი სადაც შენახულია ლოკალური ცვლადები და ფუნქციები.

ბოლოს Heap-ი, ის არის დინამიური მეხსიერების ალოკაცია.

ახლა უკვე ვიცით თუ როგორ გამოიყურება აპლიკაციის მეხსიერება და რა არის stack-ი, მაგრამ რა არის მეხსიერების მისამართი?

ჩვეულებრივ, როდესაც პროგრამა კომპილირდება და ეშვება, მის თითოეულ ინსტრუქციას ენიჭება ადგილი მეხსიერებაში, სწორედ ეს არის მეხსიერების მისამართი, როგორც წესი ის წარმოდგენილია hexadecimal ფორმატში.

თუ დავშლით ჩვენ პროგრამას და შევხედავთ, ვიპოვით თითოეულ მეხსიერების მისამართს, რომელიც ჩვენი პროგრამის მაგალითზე ასე გამოიყურება:

რატომ ხდება Buffer overflow?

ჩვენ უკვე ვიცით რა არის buffer-ი, ალბათ უკვე მიხდვი რატომ და როდის ხდება buffer overflow.

ის თავს იჩენს მაშინ, როდესაც მეხსიერებაში ვცდილობთ წინასწარ რეზერვირებული ადგილის გამოყენებას, მაგრამ ვაწვდით მონაცემების იმ რაოდენობაზე მეტს, რაც მისთვის რეზერვირებული იყო . ამ შემთხვევაში მონაცემები, რომელსაც მივაწვდით ინახება იმ მეხსიერების გარეთ სადაც უნდა შენახულიყო, რაც იწვევს უამრავ პრობლემას, პროგრამა იქრაშება.

შესადარებლად მოვიყვან ფინჯანისა და წყლის მაგალითს. წარმოვიდგნოთ ჩვენი პროგრამა როგორც ფინჯანი, ხოლო წყალი როგორც მონაცემები, რომელსაც პროგრამას ვაწვდით.

რახდება მაშინ როდისეც ფინჯანში ვასხავთ იმაზე მეტ წყალს, ვიდრე მაშ შეუძლია დაიტიოს? წყალი გადმოდის ფინჯანიდან და ისხმევა სხვა ადგილას.

სადემონსტრაციოდ დავუბრუნდეთ პირველ მაგალითს.

მოდი დავამატოთ ახალი ხაზი რომელიც იმ შემთხვევაში თუ პროგრამა არ დაიქრაშება გამოიტანს შეტყობინებას ‘Works’.

ახლა პროგრამა კითხავს მომხარებელს სახელს, და როგორც აქამდე უბრუნებდა, ისევ ისე დაუბრუნებს ‘Hello username’ და მიამატებს ახალ შეტყობინებას ‘Works’.

buffer-ი რომელიც username-ს ინახავს, როგორც ჩვენ განვსაზღვრეთ უნდა იყოს ზომაში 20 ბაიტი, ანუ სიმბოლოების ზომა მაქსიმუმ უნდა იყოს 20-ის ტოლი.

ყველაფერი იდეალურად მუშაობს მანამ სანამ შეყვანილი username-ს ზომა არ იქნება 20-ზე მეტი.
როდესაც ის მეტი გახდება, პროგრამა, როგორც ვახსენეთ, დაიქრაშება.

თავდიპირველად გავუშვათ პროგრამა ნორმალური, 20-ზე ნაკლები ზომის username-ით:

სავსებით ნორმალური შედეგი მივიღეთ, მოდი ახლა 20-ზე გრძელი მნიშვნელობა ვცადოთ და ვნახოთ რა მოხდება:

Bazinga! ჩვენი პროგრამა დაიქრაშა და მივიღეთ segmentation fault ერორი. ეს მოხდა იმიტომ, რომ პროგრამა ელოდა 20 სიმბოლოს, ხოლო გადაეცა მასზე მეტი, რამაც გამოიწვია buffer overflow და მივიღეთ segmentation fault-ი.

რატომ არის Buffer overflow საშიში?

ალბათ გაგიჩნდებოდა კითხვა როგორ შეიძლება მისი გამოყენებით რაიმე დავაზიანო?

Buffer Overflow - განსაკუთრბით საშიშია მაშინ როდესაც მოწყვლადი პროგრამა გაშვებულია როგორც რუთი. ხოლო როდესაც ის მოწყვლადია Buffer Overflow — ს მიმართ, შემტევს შეუძლია მისი საშუალებით მონაცამებს გადააწეროს საკუთარი payload-ი რომელიც სისტემაში გაუშვებს root shell-ს.

Protostar Stack0

ახლა გავაკეთოთ პრაკტიკული მაგალითი

ჩასაწერი ლინკი https://www.vulnhub.com/entry/exploit-exercises-protostar-v2,32/

კოდიდან ვიგებთ რომ პროგრამაში გვაქვს ცვლადი ‘buffer’ რომელიც განსაზღვრულია რომ იყოს 64 სიმბოლოსგან შედმგარი. ასევე გვაქვს სხვა ცვლადი სახელად ‘modifed’ რომელიც 0 ის ტოლია. ხოლო gets(buffer) გვაძლევს საშუალებას შემოვიტანოთ მნიშვნელობა და შევინახოთ ცვლადში — buffer. შედმეგია if statement, რომელიც ამოწმებს modified-ის მნიშვნელობას და თუ არ უდრის 0-ს აბრუნებს ‘you have changed the ‘modified’ variable\n’ შესაბამისად ჩვენი მისია შევცალოთ მისი მნიშვნელობა.

ჩვენ უკვე ვიცით, რომ buffer არის 64 სიმბოლოსგან შემდგარი, 65 სიმბოლო უკვე შეცვლის modified — ის მნიშვნელობას.

64 სიმბოლოს შემთხვევაში კოდი ჩვეულებრივ ეშვება:

ls4cfk@ls4cfk:~$ (python -c "print 'a'*64") | ./stack0
Try again?
ls4cfk@ls4cfk:~$

ხოლო 65 ის შემთხვევაში, modified ცვლადს ეწერება ზედ მნიშვნელობა, იცვლება და ჩვენი if statement იც სრულდება.

ls4cfk@ls4cfk:~$ (python -c "print 'a'*65") | ./stack0
you have changed the 'modified' variable
ls4cfk@ls4cfk:~$

მოდი ცოტა გავართულოთ

განვიხილოთ კიბერ ოლიმპიადის, ექსპლოიტ დეველოპმენტის, ამოცანა — Klasika, რომლის ნახვაც Cyber-lab.tech — ზე შეგიძლიათ.

პირობაში ბევრი არაფერი არ გვაქვს მოცემული:

ისღა დაგვრჩენია გავუშვათ სერვერი

nmap ით მარტივად შეგვიძლია ვნახოთ თუ რომელი პორტებია ღია.

nmap ip-რომელსაც სერვერის გაშვების შემდეგ მიიღებთ

მოცემული გვაქვს ორი საინტერესო პორტი, 21 და 443.

ftp-ზე თუ შევიხედავთ ვნახავთ ჩვენთვის საინტერესო ფაილებს:

დროებით დავივიწყოთ სერვერი და ლოკალურად გაკეთება ვცადოთ.

ჩვენი პროგრამა ასე გამოიყურება:

ის იღებს არგუმენტად სახელს და ინახავს ცვლადში name.

ჩვენ ასევე გვაქვს მოცემული ფუნქცია — giveme_shell რომელიც სისტემაში უშვების bash-ს და გვაძლევს მასზე წვდომას. სისტემაზე წვდომის მოსაპოვებლად უნდა გამოვიძახოთ ჩვენი ფუნქცია — რისთვისაც საჭიროა პირველ რიგში ვიპოვოთ offset-ი, ანუ გვჭირდება არსებული buffer ის overflow , რადგან შევცვალოთ stack-ში არსებული return address-ი და დავაბრუნოთ ჩვენი ფუნქცია.

offset — ის საპოვნელი მარტივი გზაა exploit-pattern ის გამოყენება, გავხსნათ ჩვენი pwn , gdb-ით და გადავაწოდოთ exploit-pattern ით დაგენერირებული მნიშვნელობა:

gdb pwn

Aa0Aa0Aa1Aa1Aa2Aa2Aa3Aa3Aa4Aa4Aa5Aa5Aa6Aa6Aa7Aa7Aa8A

RBP -სა და RSP— ზე დაკვირვებით მარტივად შეგვიძლია გავიგოთ, რომ 6A-ს მერე იწყება გადაწერა, ანუ ჩვენი offset არის ჩვენი პატერნი 6A-ს ჩათვლით, რომლის სიგრძეც 40 — ია :

Aa0Aa0Aa1Aa1Aa2Aa2Aa3Aa3Aa4Aa4Aa5Aa5Aa6A

მაშ ასე, გვაქვს offset, ისღა დაგვრჩენია ვიპოვოთ giveme_shell ის მისამართი.

ფუნქციის მისამართიც გვაქვს:

0x0000000000400711

მოდი ახლა ვცადოთ მისი ექსპლოიტი ლოკალურად.

ამისთვის დაგვჭირდება python და მოდული struct რომლის საშუალებითაც მისამართს შესაბამის ფორმატში მივაწვდით. (რა თქმა უნდა, სხვა გზებიცაა. მე ყველაზე მეტად ეს მომწონს.)

ჩვენი offset უნდა შედგებოდეს 40 სიმბოლოსგან, არ აქვს მნიშვნელობა რა იქნება, მე 40 ცალი A ავიღე.

ჩვენი payload -ი ‘\n’-ის გარეშეც იმუშავებდა, უბრალოდ ასე უფრო კარგად ვნახავთ შედეგს.

მზადაა ჩვენი payload, ისღა დაგვრჩენია ჩვენ მოწყვლად პროგრამას მივაწოდოთ ის.

(./klasika_exploit.py; cat) | ./pwn

cat საშუალებას გვაძლევს ინტერაქტიულად გვქონდეს გაშვებული ჩვენი ექსპლოიტი, ანუ შეგვიძლია ნებისმიერი ბრძანების მიწოდება.

ჩვენმა ექსპლოიტმა იმუშავა ლოკალურად, ისღა დაგვრჩა იგივე ვქნათ ოღონ ამ შემთხვევაში ჩვენ სერვერზე, რომელსაც netcat ით ვუკავშირდებით.

(./klasika_exploit.py; cat) | nc 185.212.254.52 443

სულ ეს იყო,სერვერზე გავხდით root-ი და ავიღეთ სასურველი flag-ი.

იმედია მოგეწონათ და შეიძინეთ ცოდნა buffer overflow - ს შესახებ.

cyber-lab ზე უამრავი მსგავსი, ზოგიც უფრო საინტერესო ამოცანაა.

თუ გაქვთ სურვილი, რომ დარეგისტრირდეთ პლატფორმაზე, საჭიროა მიწეროთ Cyber-Lab.Tech ოფიციალურ გვერდზე. ან დაუკავშირდეთ ელ.ფოსტაზე: contact@cyber-lab.tech.

Have fun!

Follow me for future posts ✌️

--

--