ping
VIP Members
-
19/06/2013
-
58
-
101 bài viết
Phân tích lỗi Dirty COW - CVE-2016-5195
Về Dirty Cow
Gần đây tôi có thấy nhiều thông tin về lỗ hổng này, được đánh giá là Serious, cho phép leo thang đặc quyền, ảnh hưởng đến nhiều distro Linux… Tôi cũng không hiểu sao một lỗ hổng sau khi được công bố thì có logo, website riêng, Twitter, Facebook Page thậm chí là có cả một cái shop bán đồ lưu niệm :| Vì tò mò nên cũng muốn tìm hiểu cơ chế hoạt động của vuln này, dù tôi không phải người chuyên research OS Kernel.
Dirty COW được gán mã lỗi CVE-2016-5195, là một cái bug Linux Kernel Race condition cho phép leo thang đặc quyền thông qua Local Exploit (nghĩa là kẻ tấn công phải vào được server nạn nhân trước với quyền Normal rồi dùng lỗi này để nâng lên quyền root). Dirty COW ảnh hưởng đến nhiều phiên bản Kernel(2.6.22–3.9) và hầu hết các distro Linux thông dụng, nhưng cách khai thác thì rất đơn giản và hiệu quả.
Người phát hiện ra lỗi này, Phil Oester, công bố sau khi một server anh này quản lý bị tấn công bằng chính Dirty COW. Có nghĩa là mã khai thác hiện có đã bị dùng để tấn công thực tế từ rất lâu rồi. Cha đẻ Linux, Linus Torvalds cũng đề cập trong bản vá lỗi rằng anh đã mường tượng lỗi này có thể xảy ra, và đã đưa ra bản vá vào năm 2005 nhưng nó đã không thực sự phát huy tác dụng.
Trong bài viết này, tôi sẽ cố gắng giải thích chi tiết cách lỗi này hoạt động. Và nếu có gì sai sót, hy vọng ai đó có thể góp ý thêm.
Chi tiết lỗi
Một cách phân tích bug hiệu quả là dựa vào mã khai thác đã có. Dưới đây là đoạn mã được cung cấp từ repository github “chính thức” của Dirty COW.
Mã khai thác này cho phép sửa nội dung một file đã được set permision Read Only, hoạt động được trên hầu hết các distro Linux trừ Red Hat Enterprise Linux 5 và 6 (lý do sẽ giải thích ở dưới). Cách dùng thì có thể thấy dễ hiểu thế này:
Theo luồng xử lý của chương trình, chúng ta có thể chia nhỏ các công việc mà nó thực hiện như sau.
1. Load file và map vào Virtual Address Space
Tại dòng 87, chương trình mở file input và đọc với chế độ Read Only. Tiếp đến lấy thông tin file ghi vào biến con trỏ st và lưu lại tên file vào biến name. Mọi thứ khá rõ ràng.
Ở dòng 101, nội dung file được map vào Virtual Address Space bằng hàm mmap()
mmap() không copy nội dung file được vào RAM, mà ánh xạ nội dung này vào vùng nhớ ảo của tiến trình đang chạy.
Hãy xem qua đặc tả hàm:
Tham số addr trong trường hợp này là NULL, nghĩa là kernel tự quyết định địa chỉ lưu trữ, độ dài vùng nhớ length bằng độ lớn của file st.st_size, chế độ truy cập prot vẫnđược để là Read Only PROT_READ vì thế vùng mapping này chỉ có thể được đọc, fd và offset tương ứng với file input và vị trí bắt đầu load file. Và tham sốquan trọng nhất ở đây, flags được gán bằng MAP_PRIVATE.
Theo như tài liệu của linux:
Copy-on-write là một khái niệm quan trọng của hệ điều hành, tư tưởng của nó là nếu một tiến trình đọc một tài nguyên dạng copy-on-write thì sẽ vẫn như đọc tài nguyên thường, nhưng nếu thực hiện thao tác ghi/sửa thì hệ điều hành sẽ tạo một bản sao của tài nguyên đó và cập nhật các nội dung mới vào đây, bản gốc không bị thay đổi.
Với cờ MAP_PRIVATE, bản mapping được xử lý riêng với tiến trình sở hữu nó. Các thay đổi về nội dung trên mapping không ảnh hưởng đến bản gốc và cũng không chịu sự tác động của bất kỳ tiến trình nào khác. Cụ thể hơn, nếu chúng ta sửa nội dung file input sau khi đã map vào Virtual Address Space thì file gốc không bị thay đổi.
Cụm Copy-on-write cũng chính là dạng đầy đủ của từ COW trong cái tên Dirty COW
2. Race Condition
Như đã nói ở phần đầu, Dirty COW là một lỗi Race Condition, và lỗi này được tạo ra bởi 2 dòng lệnh sau:
Về cơ bản thì Race Condition xảy ra khi mà có nhiều hơn một luồng thực thi cùng đọc/ghi vào tài nguyên dùng chung dẫn đến xung đột và những tình huống không đoán được trước. Trong trường hợp của Dirty COW, các luồng thực thi này là 2 thread madviseThread và procselfmemThread, còn tài nguyên dùng chung là bản mapping của file input. Ta sẽ đi cụ thể vào từng thread.
1. madviseThread()
Chú ý vào dòng 45, đây là lệnh quan trọng nhất của thread này. Còn vòng lặp for chỉ giúp cho Race được diễn ra.
madvise là một system call, nó đưa ra chỉ thị cho kernel biết phải làm gì với không gian nhớ bắt đầu từ addr và có độ dài length
Ở đây, chỉ thị là MADV_DONTNEED, nó yêu cầu kernel không sử dụng không gian nhớ này nữa và có thể giải phóng tài nguyên liên kết đến file input. Lúc này vùng nhớ trên trở thành dirty, đây là từ đầu tiên trong tên Dirty COW. Với vùng nhớ dirty, lại có thêm chỉ thị MADV_DONTNEED (do thực hiện vòng lặp), dữ liệu ghi vào sẽ bị “ thrown away”, còn dữ liệu đọc ra vẫn sẽ được lấy từ file gốc.
Tóm lại, mục đích thread này là ngăn cản việc sử dụng vùng mapping.
2. procselfmemThread()
Gần đây tôi có thấy nhiều thông tin về lỗ hổng này, được đánh giá là Serious, cho phép leo thang đặc quyền, ảnh hưởng đến nhiều distro Linux… Tôi cũng không hiểu sao một lỗ hổng sau khi được công bố thì có logo, website riêng, Twitter, Facebook Page thậm chí là có cả một cái shop bán đồ lưu niệm :| Vì tò mò nên cũng muốn tìm hiểu cơ chế hoạt động của vuln này, dù tôi không phải người chuyên research OS Kernel.
Dirty COW được gán mã lỗi CVE-2016-5195, là một cái bug Linux Kernel Race condition cho phép leo thang đặc quyền thông qua Local Exploit (nghĩa là kẻ tấn công phải vào được server nạn nhân trước với quyền Normal rồi dùng lỗi này để nâng lên quyền root). Dirty COW ảnh hưởng đến nhiều phiên bản Kernel(2.6.22–3.9) và hầu hết các distro Linux thông dụng, nhưng cách khai thác thì rất đơn giản và hiệu quả.
Người phát hiện ra lỗi này, Phil Oester, công bố sau khi một server anh này quản lý bị tấn công bằng chính Dirty COW. Có nghĩa là mã khai thác hiện có đã bị dùng để tấn công thực tế từ rất lâu rồi. Cha đẻ Linux, Linus Torvalds cũng đề cập trong bản vá lỗi rằng anh đã mường tượng lỗi này có thể xảy ra, và đã đưa ra bản vá vào năm 2005 nhưng nó đã không thực sự phát huy tác dụng.
Trong bài viết này, tôi sẽ cố gắng giải thích chi tiết cách lỗi này hoạt động. Và nếu có gì sai sót, hy vọng ai đó có thể góp ý thêm.
Chi tiết lỗi
Một cách phân tích bug hiệu quả là dựa vào mã khai thác đã có. Dưới đây là đoạn mã được cung cấp từ repository github “chính thức” của Dirty COW.
Mã khai thác này cho phép sửa nội dung một file đã được set permision Read Only, hoạt động được trên hầu hết các distro Linux trừ Red Hat Enterprise Linux 5 và 6 (lý do sẽ giải thích ở dưới). Cách dùng thì có thể thấy dễ hiểu thế này:
Mã:
$ sudo -s
# echo this is not a test > foo
# chmod 0404 foo
$ ls -lah foo-r — — -r — 1 root root 19 Oct 20 15:23 foo
$ cat foo this is not a test
$ ./dirtyc0w foo m00000000000000000
$ cat foo m00000000000000000
Theo luồng xử lý của chương trình, chúng ta có thể chia nhỏ các công việc mà nó thực hiện như sau.
1. Load file và map vào Virtual Address Space
Tại dòng 87, chương trình mở file input và đọc với chế độ Read Only. Tiếp đến lấy thông tin file ghi vào biến con trỏ st và lưu lại tên file vào biến name. Mọi thứ khá rõ ràng.
Mã:
87: f=open(argv[1],O_RDONLY);
88: fstat(f,&st);
89: name=argv[1];
Ở dòng 101, nội dung file được map vào Virtual Address Space bằng hàm mmap()
Mã:
101: map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0 );
mmap() không copy nội dung file được vào RAM, mà ánh xạ nội dung này vào vùng nhớ ảo của tiến trình đang chạy.
Hãy xem qua đặc tả hàm:
Mã:
[B]void *mmap(void *[/B][I]addr[/I][B], size_t [/B][I]length[/I][B], int [/B][I]prot[/I][B], int [/B][I]flags[/I][B], int [/B][I]fd[/I][B], off_t [/B][I]offset[/I][B]);[/B]
Tham số addr trong trường hợp này là NULL, nghĩa là kernel tự quyết định địa chỉ lưu trữ, độ dài vùng nhớ length bằng độ lớn của file st.st_size, chế độ truy cập prot vẫnđược để là Read Only PROT_READ vì thế vùng mapping này chỉ có thể được đọc, fd và offset tương ứng với file input và vị trí bắt đầu load file. Và tham sốquan trọng nhất ở đây, flags được gán bằng MAP_PRIVATE.
Theo như tài liệu của linux:
MAP_PRIVATE: Create a private copy-on-write mapping
Copy-on-write là một khái niệm quan trọng của hệ điều hành, tư tưởng của nó là nếu một tiến trình đọc một tài nguyên dạng copy-on-write thì sẽ vẫn như đọc tài nguyên thường, nhưng nếu thực hiện thao tác ghi/sửa thì hệ điều hành sẽ tạo một bản sao của tài nguyên đó và cập nhật các nội dung mới vào đây, bản gốc không bị thay đổi.
Với cờ MAP_PRIVATE, bản mapping được xử lý riêng với tiến trình sở hữu nó. Các thay đổi về nội dung trên mapping không ảnh hưởng đến bản gốc và cũng không chịu sự tác động của bất kỳ tiến trình nào khác. Cụ thể hơn, nếu chúng ta sửa nội dung file input sau khi đã map vào Virtual Address Space thì file gốc không bị thay đổi.
Cụm Copy-on-write cũng chính là dạng đầy đủ của từ COW trong cái tên Dirty COW
2. Race Condition
Như đã nói ở phần đầu, Dirty COW là một lỗi Race Condition, và lỗi này được tạo ra bởi 2 dòng lệnh sau:
Mã:
106: pthread_create(&pth1,NULL,madviseThread,argv[1]);
107: pthread_create(&pth2,NULL,procselfmemThread,argv[2]);
Về cơ bản thì Race Condition xảy ra khi mà có nhiều hơn một luồng thực thi cùng đọc/ghi vào tài nguyên dùng chung dẫn đến xung đột và những tình huống không đoán được trước. Trong trường hợp của Dirty COW, các luồng thực thi này là 2 thread madviseThread và procselfmemThread, còn tài nguyên dùng chung là bản mapping của file input. Ta sẽ đi cụ thể vào từng thread.
1. madviseThread()
Chú ý vào dòng 45, đây là lệnh quan trọng nhất của thread này. Còn vòng lặp for chỉ giúp cho Race được diễn ra.
Mã:
45: c+=madvise(map,100,MADV_DONTNEED);
madvise là một system call, nó đưa ra chỉ thị cho kernel biết phải làm gì với không gian nhớ bắt đầu từ addr và có độ dài length
Mã:
[B]int madvise(void *[/B][I]addr[/I][B], size_t [/B][I]length[/I][B], int [/B][I]advice[/I][B]);[/B]
Ở đây, chỉ thị là MADV_DONTNEED, nó yêu cầu kernel không sử dụng không gian nhớ này nữa và có thể giải phóng tài nguyên liên kết đến file input. Lúc này vùng nhớ trên trở thành dirty, đây là từ đầu tiên trong tên Dirty COW. Với vùng nhớ dirty, lại có thêm chỉ thị MADV_DONTNEED (do thực hiện vòng lặp), dữ liệu ghi vào sẽ bị “ thrown away”, còn dữ liệu đọc ra vẫn sẽ được lấy từ file gốc.
Tóm lại, mục đích thread này là ngăn cản việc sử dụng vùng mapping.
2. procselfmemThread()
Mã:
61: int f=open("/proc/self/mem",O_RDWR);
62: int i,c=0;
63: for(i=0;i
Chỉnh sửa lần cuối bởi người điều hành: