Tải bản đầy đủ (.docx) (63 trang)

Kĩ thuật khai thác lỗ hổng bảo mật trong nhân hệ điều hành linux

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.72 MB, 63 trang )

TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI
VIỆN CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THÔNG
──────── * ───────

ĐỒ ÁN

TỐT NGHIỆP ĐẠI HỌC
NGÀNH KĨ THUẬT MÁY TÍNH

TÊN ĐỀ TÀI
Nghiên cứu các kĩ thuật khai thác lỗ hổng bảo
mật trong nhân hệ điều hành Linux

Sinh viên thực hiện : Phạm Văn Khánh
Lớp KSTN - CNTT – K55
Giáo viên hướng dẫn: ThS Lương Ánh Hoàng

HÀ NỘI 12-2015
PHIẾU GIAO NHIỆM VỤ ĐỒ ÁN TỐT NGHIỆP


1. Thông tin về sinh viên
Họ và tên sinh viên: Phạm Văn Khánh
Điện thoại liên lạc: 01642642065
Email:
Lớp: KSTN-CNTT-K55
Hệ đào tạo: Kĩ sư
Đồ án tốt nghiệp được thực hiện tại: Hà Nội
Thời gian làm ĐATN: Từ ngày 15/08/2015 đến 18/12/2015
2. Mục đích nội dung của ĐATN
Tìm hiểu, nghiên cứu và thực hành các kĩ thuật khai thác lỗ hổng bảo mật trong Linux


Kernel.
3. Các nhiệm vụ cụ thể của ĐATN





Tìm hiểu Linux Kernel
Tìm hiểu các lỗ hổng bảo mật trong Linux Kernel
Nghiên cứu các kĩ thuật khai thác lỗ hổng trong Linux Kernel
Thực hành viết mã khai thác một lỗ hổng điển hình minh họa các kĩ thuật tìm hiểu
và nghiên cứu được

4. Lời cam đoan của sinh viên:
Tôi – Phạm Văn Khánh - cam kết ĐATN là công trình nghiên cứu của bản thân tôi dưới sự
hướng dẫn của Ths Lương Ánh Hoàng.
Các kết quả nêu trong ĐATN là trung thực, không phải là sao chép toàn văn của bất kỳ
công trình nào khác.
Hà Nội, ngày 18 tháng 12 năm 2015
Tác giả ĐATN
Phạm Văn Khánh

5. Xác nhận của giáo viên hướng dẫn về mức độ hoàn thành của ĐATN và cho phép bảo
vệ:
………………………………………………………………………………………………
………………………………………………………………………………………………
………………………………………………………………………………………………
………………………………………………………………………………………………
……………………………..
Hà Nội, ngày 18 tháng 12 năm 2015

Giáo viên hướng dẫn
Ths Lương Ánh Hoàng

TÓM TẮT NỘI DUNG ĐỒ ÁN TỐT NGHIỆP
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 2


Đồ án thực hiện tìm hiểu, nghiên cứu, thực hành các kĩ thuật khai thác lỗ hổng bảo mật
trong Linux Kernel. Đồ án gồm chia thành các phần tường ứng với các giai đoạn làm đồ
án: Phần 1 tìm hiểu các thành phần trong Linux Kernel như quản lý bộ nhớ, quản lý tiến
trình, tương tác với bộ vi xử lý; Phần 2 tìm hiểu các lỗ hổng bảo mật phổ biến trong Linux
Kernel, nắm rõ nguyên nhân gốc và các trường hợp dễ dẫn tới lỗ hổng; Phần 3 nghiên cứu,
tổng hợp các kĩ thuật khai thác đã biết trên thế giới; Phần 4 thực hành các kĩ thuật nghiên
cứu được để viết mã khai thác lỗ hổng, thực hành tấn công trên hệ thống lab; Phần 5 đưa ra
các biện pháp phòng chống mà lâu nay các quản trị viên hệ thống chưa hiểu rõ và lắm
vững.

MỤC LỤC
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55


Trang 3


Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 4


DANH MỤC HÌNH ẢNH

Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 5


VÀ TỪ VIẾT TẮT
 Linux: hệ điều hành mã nguồn được cung cấp miễn phí dưới dạng các bản phân
phối khác nhau. Thành phần chính của Linux là Linux Kernel.
 Linux Kernel: Thành phần chính của hệ điều hành tương tác với phần cứng, thực
hiện các chức năng quản lý bộ nhớ, quản lý tiến trình, quản lý vào ra thiết bị ngoại
vi.
 Debug: Quá trình chạy từng lệnh trong một chương trình, kiểm tra bộ nhớ, thanh

ghi, trạng thái của chương trình
 Debugger: Công cụ cho phép thực hiện debug một chương trình. Trình debugger
phổ nhất trên linux là gdb.
 Remote debug: Thực hiện debug từ xa. Kĩ thuật này được sử dụng khi cần debug
kernel máy khác hoặc không được truy cập trực tiếp vào máy khác.
 Little-endian: một định dạng lưu trữ dữ liệu trong đó các byte ít quan trọng nhất
được lưu ở địa chỉ cao nhất. Kiến trúc Intel sử dụng kiểu lưu trữ này.
 Page frame: Không gian địa chỉ vật lí được chia thành các khối kích thước cố định
để quản lý. Mỗi khối này được gọi là một page frame. Kích thước mỗi page frame
thường là 4KB.
 Page: Không gian địa chỉ ảo được chia thành các khối kích thước cố định để quản
lý. Mỗi khối này được gọi là một page. Kích thước page thường là 4KB.
 Page table: Bảng ánh xạ page vào page frame. Bảng ánh xạ cho phép tìm địa chỉ
vật lí trên RAM khi biết địa chỉ ảo của một vùng nhớ nào đó.
 Userland: Vùng nhớ dành cho các tiến trình thông thường, thường 3GB từ địa chỉ
0x00000000 đến 0xC0000000.
 Kernelland: Vùng nhớ dành cho kernel, thường 1GB từ địa chỉ 0xC0000000 đến
0xFFFFFFFF đối với Linux cho Intel x86.
 User Mode: Trạng thái của CPU, các lệnh sẽ bị giới hạn không được phép truy cập
không gian địa chỉ dành cho kernel. Thỉnh thoảng user mode cũng chỉ trạng thái
tiến trình đang chạy trong user path.
 Kernel Mode: Trạng thái của CPU, các lệnh không bị giới hạn. Thỉnh thoảng
kernel mode cũng chỉ trạng thái tiến trình đang chạy trong kernel path (xử lý ngắt
hoặc gọi hàm hệ thống)
 User Path: Mã lệnh đang thực thi nằm ở userland khi đó CPU đang chạy mã lệnh
của tiến trình bên phía user.
 Kernel Path: Mã lệnh đang thực thi nằm ở kernelland khi đó CPU đang chạy mã
lệnh của kernel (gọi hàm hệ thống, xử lý ngắt, lập lịch)
 System Call: Lời gọi hệ thống cho phép tiến trình tương tác với kernel. Bản chất
system call chỉ là một hàm bình thường, khi gọi CPU sẽ được chuyển sang kernel

mode.
 Interrupt: Tín hiệu được gửi đến CPU khi có một sự kiện nào đó xảy ra, ví dụ
người dùng gõ phím, di chuột, gói tin mạng đến card mạng.
 Interrupt Handler: Hàm xử lý ngắt được gọi khi một ngắt nào đó gửi đến CPU và
CPU quyết định xử lý ngắt đó.
 Vector ngắt (Interrupt vector): Vùng nhớ chứa thông tin về một ngắt cụ thể: địa
chỉ hàm xử lý ngắt, quyền để gọi ngắt,..
 IDT ( interrupt descriptor table): Bảng lưu trữ thông tin tất cả vector ngắt được sử
dụng trong hệ thống. Bản chất IDT sẽ là một mảng lưu trữ trong kernelland, mỗi
phần từ mảng lưu trữ một vector ngắt .
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 6


 Lỗ hổng: Là một loại lỗi phần mềm cho phép kẻ tấn công có thể thay đổi được
luồng thực thi của phần mềm theo ý của kẻ tấn công. Lỗi thông thường (bug)
thường chỉ làm phần mềm hoạt động sai.
 Shellcode: một đoạn mã lệnh nhỏ được đặt trên vùng nhớ nào đó. Mục tiêu khai
thác lỗ hổng thường là để chuyển hướng thực thi đến shellcode.
 Payload: một đoạn dữ liệu được gửi tới các phần mềm để khai thác lỗ hổng nào đó,
trong payload thường chứa shellcode.
 Local Privilege Escalation: Lỗ hổng phần mềm trong đó kẻ tấn công đã chiếm
được tài khoản thông thường và muốn chiếm được quyền của tài khoản cao hơn,
nhiều quyền hơn. Mục tiêu thường là tài khoản root đối với Linux hoặc tài khoản
Administrator đối với Windows.

 Dereference: Một con trỏ trong ngôn ngữ C lưu trữ địa chỉ của vùng nhớ.
Dereference là quá trình lấy địa chỉ vùng nhớ và truy cập tới giá trị vùng nhớ đó.
 NULL Pointer Dereference: Lỗ hổng phần mềm trong đó một con trỏ NULL
được giải tham chiếu, tức là truy cập tới vùng nhớ địa chỉ 0x0.
 Buffer: một vùng nhớ được cấp phát dùng để lưu trữ dữ liệu, buffer được xác định
bằng địa chỉ byte đầu tiên và kích thước.
 Buffer Overflow: Lỗ hổng phần mềm gây ra khi sao chép dữ liệu vào buffer. Kích
thước dữ liệu lớn hơn kích thước buffer dẫn tới tràn ra khỏi buffer, khi đè luôn các
vùng nhớ nằm sau buffer.
 Stack Frame: Một vùng nhớ trên stack được cấp phát khi gọi một hàm con. Vùng
nhớ này bao gồm tham số truyền vào hàm, địa chỉ trả về khi hàm kết thúc, địa chỉ
stack frame của hàm cha, biến cục bộ dùng trong hàm.
 Return Address: Địa chỉ trả về của một hàm lưu trên stack. Khi hàm kết thúc gọi
lệnh ret, nó sẽ pop địa chỉ trả về trên stack và chuyển hướng EIP vào địa chỉ này.
Hàm cha sẽ tiếp tục chạy từ vị trí gọi hàm con.
 Saved Frame Pointer: Địa chỉ stack frame của hàm cha được lưu trên stack frame
của hàm con. Địa chỉ hiện tại của hàm con lưu trong thanh ghi EBP. Địa chỉ này
dùng để khôi phục lại EBP khi hàm con thực hiện xong.
 Stack Overflow: Thuộc loại lỗ hổng buffer overflow khi mà buffer được cấp phát
trên stack.
 Heap Overflow: Thuộc loại lỗ hổng buffer overflow khi mà buffer được cấp phát
động trên heap thông qua các hàm malloc (userland) hoặc kalloc (kernelland).
 Infoleak: Một loại lỗ hổng trong đó dữ liệu quan trọng bị đọc mất. Chẳng hạn một
vùng nhớ kernelland bị đọc bởi tiến trình userland mặc dù tiến trình này không có
quyền truy cập vùng địa chỉ kernel.
 Executable: Trạng thái để đánh dấu một vùng nhớ có quyền thực thi.

MỞ ĐẦU
Ngày nay an toàn thông tin là một vấn đề hết sức quan trọng. Các hệ thống
công nghệ thông tin luôn tiềm ẩn nhiều nguy cơ mất an toàn đặc biệt các lỗ hổng

bảo mật. Nghiên cứu về lỗ hổng bảo mật giúp ta hiểu rõ cách thức hacker lợi dụng
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 7


các lỗ hổng để tấn công hệ thống từ đó xây dựng được các biện pháp phòng chống,
nâng cao mức độ an toàn cho hệ thống. Từ trước đến nay, các lỗ hổng bảo mật ở lớp
ứng dụng đã từng được biết đến và nghiên cứu khá nhiều ở Việt Nam chẳng hạn lỗ
hổng SQL Injection tấn công website, lỗ hổng trên Microsoft Office cho phép cài
đặt virus lên máy tính người dùng,… Tuy nhiên một lớp lỗ hổng ở sâu trong nhân
của hệ điều hành lại chưa thực sự được quan tâm. Nguyên nhân chính do nhân hệ
điều hành là một thành phần phức tạp của hệ thống, khó để tìm hiều và đào sâu
nghiên cứu, đòi hỏi nắm vững nhiều kiến thức căn bản về hệ điều hành, vi xử lý
máy tính.
Sau thời gian học tập tại trường, được sự chỉ bảo hướng dẫn nhiệt tình của
thầy cô giáo trong viện Công nghệ thông tin và truyền thông, trường đại học Bách
Khoa Hà Nội, em đã kết thúc khoá học và đã tích luỹ được vốn kiến thức nhất định.
Được sự đồng ý của nhà trường và thầy cô giáo trong viện em được giao đề tài tốt
nghiệp: “Nghiên cứu các kĩ thuật khai thác lỗ hổng bảo mật trong nhân hệ điều
hành Linux”.
Đồ án tốt nghiệp của em bố cục gồm sáu chương:








Chương 1: Đặt vấn đề và định hướng giải pháp
Chương 2: Giới thiệu Linux Kernel
Chương 3: Giới thiệu các lỗ hổng trong Linux Kernel
Chương 4: Kĩ thuật khai thác lỗ hổng trong Linux Kernel
Chương 5: Thực hiện khai thác một lỗ hổng điển hình trong Linux Kernel
Chương 6: Các biện pháp phòng chống

Bằng sự cố gắng nỗ lực của bản thân và đặc biệt là sự giúp đỡ tận tình, chu
đáo của thầy giáo Ths Lương Ánh Hoàng, em đã hoàn thành đồ án đúng thời hạn.
Do thời gian làm đồ án có hạn và trình độ còn nhiều hạn chế nên không thể tránh
khỏi những thiếu sót. Em rất mong nhận được sự đóng góp ý kiến của các thầy cô
cũng như là của các bạn sinh viên để đồ án này hoàn thiện hơn nữa. Em xin chân
thành cảm ơn thầy giáo Ths Lương Ánh Hoàng, các thầy cô giáo trong viện Công
nghệ thông tin và truyền thông, trường Đại học Bách khoa Hà nội, các anh chị và
các bạn tập thể phòng An ninh ứng dụng, Trung tâm An ninh mạng Viettel đã tạo
điều kiện và giúp đỡ em trong thời gian qua.
Hà nội, ngày 18/12/2015
Phạm Văn Khánh

1 Đặt vấn đề và định hướng giải pháp
Đặt vấn đề:
Linux Kernel được sử dụng rất phổ biến trên các máy chủ, thiết bị di động, thiết bị
nhúng. Thống kê năm 2015 khoảng 1 tỷ 400 triệu điện thoại Android chạy Linux,
Sinh viên Phạm Văn Khánh

SHSV: 20101715


Lớp: KSTN-CNTT-K55

Trang 8


35.9% số lượng máy chủ trong số 1 triệu máy chủ khảo sát chạy hệ điều hành
Linux, 98.8% siêu máy tính trong số 500 siêu máy tính chạy Linux (nguồn
Wikipedia). Tuy nhiên nhận thức về các lỗ hổng bảo mật cho chúng lại chưa thực
sự được quan tâm. Sở dĩ như vậy vì kiến thức về lỗ hổng bảo mật và cách khai thác
các lỗ hổng này còn rất kém đặc biệt đối với các quản trị viên hệ thống, chuyên viên
bảo mật ở Việt Nam. Qua kinh nghiệm thực tế, em phát hiện ra rằng các máy chủ
Linux ở Việt Nam sử dụng các phiên bản Kernel rất thấp, tồn tại nhiều lỗ hổng bảo
mật dễ dàng bị hacker khai thác tấn công leo thang lên tài khoản root; các thiết bị
Android của người dùng hầu hết không được cập nhật các phiên bản mới thường
xuyên. Từ đó em nhận thấy để nâng cao nhận thức về bảo mật đối với Linux Kernel
đồng thời nắm rõ các biện pháp phòng chống thì trước tiên cần phải hiểu rõ cơ chế
hoạt động của các lỗ hổng, chứng minh được sự nguy hiểm của các lỗ hổng này
thông qua việc tìm hiểu kĩ thuật khai thác và viết mã tấn công. Để làm được điều
này, đề tài em thực hiện sẽ tập trung giải quyết cho bài toán gốc sau: “Làm thế nào
chiếm được quyền root khi đã có quyền người dùng bình thường trên hệ thống
Linux”.

Nhiệm vụ của đề tài:
 Nghiên cứu các kĩ thuật khai thác lỗ hổng bảo mật trên Linux Kernel: tổng

hợp các kĩ thuật được phát triển trên thế giới.
 Thực hành viết mã khai thác một lỗ hổng điển hình: chọn một lỗ hổng hay,
điển hình đã được công bố trên Linux Kernel và viết mã khai thác bằng ngôn
ngữ C cho lỗ hổng đó. Dựng môi trường lab để thực hiện tấn công thử
nghiệm.

 Đưa ra các biện pháp phòng chống lỗ hổng bảo mật trong Linux Kernel.

Hướng giải quyết:
Đề tài này thiên hướng nghiên cứu, lý thuyết. Do đó, hướng giải quyết của em bắt
đầu từ tìm hiểu, nghiên cứu kĩ kiến thức lý thuyết. Ba phần kiến thức lý thuyết
chính cần đào sâu tìm hiểu trong đề tài là kiến thức về Linux Kernel, kiến thức về lỗ
hổng phần mềm và kiến thức về các kĩ thuật khai thác lỗ hổng phần mềm sử dụng
cho Linux Kernel. Các kiến thức lý thuyết này sẽ được áp dụng trong phần thực
hành viết mã khai thác thông qua các công cụ lập trình và các công cụ debug trên
môi trường lab mô phỏng hệ thống máy chủ Linux trong thực tế.

Các công cụ được lựa chọn:
 Phần mềm tạo máy ảo Vmware 10 có hỗ trợ module cho phép debug kernel

máy ảo.
 Bản phân phối Ubuntu 12.04.0 chạy Linux Kernel phiên bản 3.2.0-23generic.
 Trình biên dịch gcc và trình debug gdb.
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 9


 Ngôn ngữ lập trình C.

Môi trường lab thực hiện: Hai máy ảo Ubuntu, một máy ảo chạy gdb để
thực hiện remote debug kernel máy ảo còn lại, từ đó cho phép chạy từng lệnh hợp

ngữ trong kernel, kiểm tra các vùng nhớ, các biến, trạng thái các thanh ghi.

Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 10


2 Giới thiệu Linux Kernel
2.1 Tổng quan về Linux và Linux Kernel
Linux là hệ điều hành mã nguồn mở, được phát triển dựa trên hệ điều hành UNIX.
Linux gồm có một nhân kernel (mã cốt lõi quản lý các tài nguyên phần cứng và
phần mềm) và một bộ sưu tập các ứng dụng của người dùng (chẳng hạn như các thư
viện, các trình quản lý cửa sổ và các ứng dụng) (hình 1.1).
Nhân Linux (Linux Kernel) là trung tâm điều khiển của hệ điều hành Linux
chứa các mã nguồn điều khiển hoạt động toàn bộ của hệ thống, tương tác với vi xử
lý, bộ nhớ, quản lý các tiến trình ứng dụng, quản lý vào ra. Nhân Linux được lập
trình bằng ngôn ngữ C và được Linus Torvalds phát triển từ năm 1991.

Hình 1.1 Kiến trúc tổng quan hệ điều hành Linux

Phạm vi đồ án sẽ tập trung vào Linux Kernel trên nền tảng Intel x86 và
x86_64 vì đây là bộ vi xử lý được dùng phổ biến nhất trong các hệ thống server,
hơn nữa cũng là bộ vi xử lý phổ biến nhất trên các dòng máy tính PC, laptop.

2.2 Nhắc lại kiến thức nền tảng về vi xử lý
-


Bộ vi xử lý (CPU)

Bộ vi xử lý đọc lệnh từ bộ nhớ và thực hiện các lệnh này một cách liên tục,
không nghỉ. Lệnh sắp được thực thi được quyết định bởi con trỏ lệnh (instruction
pointer). Con trỏ lệnh là một thanh ghi của CPU, có nhiệm vụ lưu trữ địa chỉ của
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 11


lệnh kế tiếp trên bộ nhớ. Sau khi CPU thực hiện xong lệnh hiện tại, CPU sẽ thực
hiện tiếp lệnh tại vị trí do con trỏ lệnh chỉ tới.
-

Thanh ghi

Thanh ghi là một dạng bộ nhớ tốc độ cao, nằm ngay bên trong CPU. Thông
thường, thanh ghi sẽ có độ dài bằng với độ dài của cấu trúc CPU. Các nhóm thanh
ghi chính:
 Thanh ghi chung là những thanh ghi được CPU sử dụng như bộ nhớ

-

siêu tốc trong các công việc tính toán, đặt biến tạm, hay giữ giá trị tham
số. Các thanh ghi này thường có vai trò như nhau. Chúng ta hay gặp bốn

thanh ghi chính là EAX, EBX, ECX, và EDX.
 Thanh ghi xử lý chuỗi là các thanh ghi chuyên dùng trong việc xử lý
chuỗi ví dụ như sao chép chuỗi, tính độ dài chuỗi. Hai thanh ghi thường
gặp gồm có EDI, và ESI.
 Thanh ghi ngăn xếp là các thanh ghi được sử dụng trong việc quản lý
cấu trúc bộ nhớ ngăn xếp. Hai thanh ghi chính là EBP và ESP.
 Thanh ghi đặc biệt là những thanh ghi có nhiệm vụ đặc biệt, thường
không thể được gán giá trị một cách trực tiếp. Chúng ta thường gặp các
thanh ghi như EIP và EFLAGS. EIP chính là con trỏ lệnh chúng ta đã
biết. EFLAGS là thanh ghi chứa các cờ (mỗi cờ một bit) như cờ dấu (sign
flag), cờ nhớ (carry flag), cờ không (zero flag). Các cờ này được thay đổi
như là một hiệu ứng phụ của các lệnh chính. Ví dụ như khi thực hiện lệnh
lấy hiệu của 0 và 1 thì cờ nhớ và cờ dấu sẽ được bật. Chúng ta dùng giá
trị của các cờ này để thực hiện các lệnh nhảy có điều kiện ví dụ như nhảy
nếu cờ không được bật, nhảy nếu cờ nhớ không bật.
 Thanh ghi phân vùng là các thanh ghi góp phần vào việc đánh địa chỉ
bộ nhớ. Chúng ta hay gặp những thanh ghi DS, ES, CS. Trong những thế
hệ 16 bit, các thanh ghi chỉ có thể định địa chỉ trong phạm vi từ 0 đến 2 16
− 1. Để vượt qua giới hạn này, các thanh ghi phân vùng được sử dụng để
hỗ trợ việc đánh địa chỉ bộ nhớ, mở rộng nó lên 2 20 địa chỉ ô nhớ. Cho
đến thế hệ 32 bit thì hệ điều hành hiện đại đã không cần dùng đến các
thanh ghi phân vùng này trong việc định vị bộ nhớ nữa vì một thanh ghi
thông thường đã có thể định vị được tới 232.
Địa chỉ bộ nhớ

Thanh ghi là bộ nhớ siêu tốc nhưng đáng tiếc dung lượng của chúng quá ít nên
chúng không phải là bộ nhớ chính. Bộ nhớ chính mà chúng ta nói đến là RAM với
dung lượng thường thấy đến 1 hoặc 2 GB. RAM là viết tắt của Random Access
Memory (bộ nhớ truy cập ngẫu nhiên). Đặt tên như vậy vì để truy xuất vào bộ nhớ
thì ta cần truyền địa chỉ ô nhớ trước khi truy cập nó, và tốc độ truy xuất vào địa chỉ

nào cũng là như nhau. Vì thế việc xác định địa chỉ ô nhớ là quan trọng.
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 12


-

Định địa chỉ ô nhớ

Đến thế hệ 32 bit, các hệ điều hành đã chuyển sang dùng địa chỉ tuyến tính
(linear addressing) thay cho địa chỉ phân vùng. Cách đánh địa chỉ tuyến tính làm
đơn giản hóa việc truy xuất bộ nhớ. Khi ta nói đến địa chỉ bộ nhớ, chúng ta đang
nói đến địa chỉ tuyến tính của RAM. Địa chỉ tuyến tính này không nhất thiết là địa
chỉ thật của ô nhớ trong RAM mà sẽ phải được hệ điều hành ánh xạ lại. Công việc
ánh xạ địa chỉ bộ nhớ được thực hiện qua phần quản lý bộ nhớ ảo (virtual memory
management) của hệ điều hành. Kiểu đánh địa chỉ tuyến tính ảo như vậy cho phép
hệ điều hành mở rộng bộ nhớ thật có bằng cách sử dụng thêm phân vùng trao đổi
(swap partition). Chúng ta thường thấy máy tính chỉ có 1 GB RAM nhưng địa chỉ
bộ nhớ có thể có giá trị 0xBFFFF6E4 tức là khoảng hơn 3 GB. Trong 3 GB này,
ngoài dữ liệu còn có các mã lệnh của chương trình.
-

Truy xuất bộ nhớ và tính kết thúc nhỏ (little-endian)

Như đã nói sơ qua, bộ vi xử lý cần xác định địa chỉ ô nhớ, và sẵn sàng nhận

dữ liệu từ hoặc truyền dữ liệu vào bộ nhớ. Do đó để kết nối CPU với bộ nhớ chúng
ta có hai đường truyền là đường truyền dữ liệu (data bus) và đường truyền địa chỉ
(address bus). Khi cần đọc dữ liệu từ bộ nhớ, CPU sẽ thông báo rằng địa chỉ ô nhớ
đã sẵn sàng trên đường truyền địa chỉ, và yêu cầu bộ nhớ truyền dữ liệu qua đường
truyền dữ liệu. Khi ghi vào thì CPU sẽ yêu cầu bộ nhớ lấy dữ liệu từ đường truyền
dữ liệu và ghi vào các ô nhớ. Các đường truyền dữ liệu và địa chỉ đều có độ rộng 32
bit cho nên mỗi lần truy cập vào bộ nhớ thì CPU sẽ truyền hoặc nhận cả 32 bit để
tối ưu việc sử dụng đường truyền. Điều này dẫn đến câu hỏi về kích thước các kiểu
dữ liệu nhỏ hơn 32 bit. Câu hỏi đầu tiên là làm sao để CPU nhận được 1 byte thay
vì 4 byte (32 bit) nếu mọi dữ liệu từ bộ nhớ truyền về CPU đều là 32 bit? Câu trả lời
là CPU nhận tất cả 4 byte từ bộ nhớ, nhưng sẽ chỉ xử lý 1 byte theo như yêu cầu của
chương trình. Việc này cũng giống như ta có một thùng hàng to nhưng bên
trong chỉ để một vật nhỏ. Câu hỏi thứ hai liên quan tới vị trí của 8 bit dữ liệu sẽ
được xử lý trong số 32 bit dữ liệu nhận được. Làm sao CPU biết lấy 8 bit nào? Các
nhà thiết kế vi xử lý Intel x86 32 bit đã quyết định tuân theo tính kết thúc nhỏ (little
endian). Kết thúc nhỏ là quy ước về trật tự và ý nghĩa các byte trong một kiểu trình
bày dữ liệu mà byte ở vị trí cuối (vị trí thấp nhất) có ý nghĩa nhỏ hơn byte ở vị trí kế
(Hình 1.2).

Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 13


Hình 1.2 Kiến trúc bộ nhớ little-endian
-


Tập lệnh và hợp ngữ

Tập lệnh là tất cả những lệnh mà CPU có thể thực hiện. Đây có thể được coi
như kho từ vựng của một máy tính. Các chương trình là những tác phẩm văn học;
chúng chọn lọc, kết nối các từ vựng riêng rẽ lại với nhau thành một thể thống nhất
diễn đạt một ý nghĩa riêng.
-

Các nhóm lệnh

Hợp ngữ có nhiều nhóm lệnh khác nhau. Chúng ta sẽ chỉ điểm qua các nhóm
và những lệnh sau:
 Nhóm lệnh gán là những lệnh dùng để gán giá trị vào ô nhớ, hoặc thanh ghi

ví dụ như LEA, MOV, SETZ.
 Nhóm lệnh số học là những lệnh dùng để tính toán biểu thức số học ví dụ
như INC, DEC, ADD, SUB, MUL, DIV.
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 14


 Nhóm lệnh luận lý là những lệnh dùng để tính toán biểu thức luận lý ví dụ






-

như AND, OR, XOR, NEG.
Nhóm lệnh so sánh là những lệnh dùng để so sánh giá trị của hai đối số và
thay đổi thanh ghi EFLAGS ví dụ như TEST, CMP.
Nhóm lệnh nhảy là những lệnh dùng để thay đổi luồng thực thi của CPU
bao gồm lệnh nhảy không điều kiện JMP, và các lệnh nhảy có điều kiện như
JNZ, JZ, JA, JB.
Nhóm lệnh ngăn xếp là những lệnh dùng để đẩy giá trị vào ngăn xếp, và lấy
giá trị từ ngăn xếp ra ví dụ như PUSH, POP, PUSHA, POPA.
Nhóm lệnh hàm là những lệnh dùng trong việc gọi hàm và trả kết quả từ
một hàm ví dụ như CALL và RET.
Cú pháp

Mỗi lệnh hợp ngữ có thể nhận 0, 1, 2, hoặc nhiều nhất là 3 đối số. Đa số các
trường hợp chúng ta sẽ gặp lệnh có hai đối số theo dạng tương tự như ADD dst, src.
Với dạng này, lệnh số học ADD sẽ được thực hiện với hai đối số dst và src, rồi kết
quả cuối cùng sẽ được lại trong dst, thể hiện công thức dst = dst + src. Tùy vào mỗi
lệnh riêng biệt mà dst và src có thể có các dạng khác nhau. Nhìn chung, chúng ta có
các dạng sau đây cho dst và src:
 Giá trị trực tiếp là một giá trị cụ thể như 0x6789ABCD. Ví dụ MOV EAX,

0x6789ABCD sẽ gán giá trị 0x6789ABCD vào thanh ghi EAX. Giá trị trực
tiếp không thể đóng vai trò của dst.
 Thanh ghi là các thanh ghi như EAX, EBX, ECX, EDX.
 Bộ nhớ là giá trị tại ô nhớ có địa chỉ được chỉ định. Để tránh nhầm lẫn với
giá trị trực tiếp, địa chỉ này được đặt trong hai ngoặc vuông. Ví dụ MOV

EAX, [0x6789ABCD] sẽ gán giá trị 32 bit bắt đầu từ ô nhớ 0x6789ABCD
vào thanh ghi EAX.
- Ngăn xếp
Ngăn xếp là một vùng bộ nhớ được hệ điều hành cấp phát sẵn cho chương
trình khi nạp. Chương trình sẽ sử dụng vùng nhớ này để chứa các biến cục bộ (local
variable), và lưu lại quá trình gọi hàm, thực thi của chương trình. Ngăn xếp hoạt
động theo nguyên tắc vào sau ra trước (Last In, First Out). Thanh ghi ESP lưu giữ
vị trí đỉnh ngăn xếp, tức địa chỉ ô nhớ của đối tượng được đưa vào ngăn xếp sau
cùng, nên còn được gọi là con trỏ ngăn xếp (stack pointer). Thao tác đưa một đối
tượng vào ngăn xếp là lệnh PUSH. Thao tác lấy từ ngăn xếp ra là lệnh POP.

2.3 Quản lý bộ nhớ trong Linux Kernel
Linux quản lý bộ nhớ theo cơ chế bộ nhớ ảo, cụ thể là bộ nhớ ảo phân trang
(paging) theo 3 cấp, tùy vào CPU được dùng mà kích thước mỗi trang sẽ khác nhau.
Đối với intel x86, kích thước trang nhớ thường là 4KB. Như chúng ta đã biết, máy
tính có kích thước bộ nhớ vật lí cố định (bộ nhớ RAM) được sử dụng để lưu trữ dữ
liệu tạm thời khi hệ thống chạy. Không gian địa chỉ của bộ nhớ vật lí chạy từ 0 đến
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 15


RAM_SIZE – 1. Tuy nhiên, mỗi tiến trình khi chạy không truy xuất trực tiếp đến
không gian địa chỉ vật lí này mà sử dụng một không gian địa chỉ ảo, riêng độc lập
với nhau. Không gian địa chỉ ảo này thường lớn hơn không gian địa chỉ vật lí, phụ
thuộc vào kiến trúc. Đối với linux x86, không gian này chạy từ 0 đến 2 32-1. Thành

phần quản lý bộ nhớ ảo trong linux kernel sẽ chịu trách nhiệm ánh xạ không gian
địa chỉ ảo sang không gian địa chỉ vật lí. Để thực hiện điều này, Linux chia không
gian địa chỉ vật lí thành các khối kích thước cố định được gọi là khung trang (page
frame), và cũng chia không gian địa chỉ ảo thành các khối kích thước bằng nhau gọi
là các trang nhớ (page). Mỗi khi tiến trình cần sử dụng một trang nhớ trong không
gian ảo, hệ thống sẽ cấp phát khung trang nhớ trong bộ nhớ vật lí và ánh xạ tương
ứng cho nó. Việc ánh xạ này được thực hiện thông qua bảng phân trang (page table)
(hình 1.3).

Hình 1.3 Bảng phân trang bộ nhớ trong Linux

Khi tất cả khung trang nhớ được sử dụng hết, và một khung trang nhớ mới
được yêu cầu, hệ điều hành cụ thể là Linux Kernel sẽ lấy một khung trang hiện
đang không được sử dụng và copy nó lên trên đĩa tại phân vùng swap. Khung trang
nhớ vừa được giải phóng sẽ được cấp phát và sử dụng. Nếu khung trang nhớ ban
đầu được sử dụng lại, (giả sử tiến trình cần truy cập lại các biến lưu trên đó sau một
thời gian) hệ thống tương tự sẽ chọn một khung trang nhớ khác, và hoản đổi nó vào
vùng nhớ swap trêm ổ đĩa, sau đó copy dữ liệu cũ từ vùng swap ngược lại để tiến
trình sử dụng lại trang nhớ đó. Quá trình swap cho phép hệ thống tận dụng được
nhiều hơn các khung trang nhớ đặc biệt đối với các hệ thống có ít RAM.
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 16


Không gian địa chỉ ảo cho mỗi tiến trình là giống nhau. Linux sử dụng không

gian chung cho cả phần kernel và phần user. Không gian địa chỉ kernel bắt đầu từ
địa chỉ 0xC0000000 đến 0xFFFFFFFF, độ lớn là 1GB. Linux sử dụng chung một
không gian địa chỉ ảo cho cả kernel và user. Đặc điểm này đem lại nhiều lợi ích tới
quá trình khai thác lỗ hổng sau này (Phần 3 đồ án sẽ trình bày rõ hơn vấn đề này).
Hình vẽ dưới đây minh họa, không gian địa chỉ cho một tiến trình :

Hình 1.4 Không gian địa chỉ bộ nhớ trong Linux

Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 17


2.4 Quản lý tiến trình trong Linux Kernel
Tiến trình là đối tượng quan trọng được quản lý bởi kernel. Tiến trình sử dụng bộ
nhớ trong để lưu trữ mã chạy và dữ liệu, sử dụng CPU để đọc lệnh từ bộ nhớ và
thực thi lệnh đó. Mỗi CPU có nhiều trạng thái thực thi khác nhau, mỗi trạng thái
xác định quyền thực thi trong trạng thái đó. Ví dụ CPU Intel x86 có 4 trạng thái
khác nhau, xác định bởi 2 bit trong thanh ghi cr0. Tuy nhiên Linux Kernel chỉ sử
dụng hai trạng thái trong số đó để xác định trạng thái thực thi tiến trình: User Mode
và Kernel Mode (hình 1.5). Khi một tiến trình đang chạy trong User Mode, nó
không thể trực tiếp truy cập và sử dụng các cấu trúc dữ liệu kernel cụ thể là phần bộ
nhớ 1GB phía trên dành cho kernel hoặc các dịch vụ kernel cung cấp. Khi tiến trình
chuyển sang Kernel Mode, những giới hạn đó không còn. Mỗi CPU đều cung cấp
một số lệnh đặc biệt cho phép chuyển trạng thái từ User Mode sang Kernel Mode và
ngược lại. Một tiến trình thường chạy nhiều hơn trong User Mode, khi nó cần sử

dụng dịch vụ hoặc dữ liệu kernel cung cấp nó sẽ chạy lệnh đặc biệt chuyển sang
Kernel Mode. Khi phần kernel chạy xong, nó đẩy tiến trình trở lại User Mode. Việc
chuyển đổi giữa User Mode và Kernel Mode chính là cách giao tiếp giữa tiến trình
và kernel, được gọi là system call. System call là tập hợp các hàm để tương tác với
kernel. Mỗi system call bao gồm một số tham số để truyền yêu cầu cũng như dữ
liệu cho kernel xử lý.
Ngoài các tiến trình user thông thường, bản thân Linux Kernel luôn luôn chạy
một số tiến trình đặc biệt gọi là Kernel Threads, những tiến trình chạy các tác vụ
như lập lịch, xử lý ngắt,.. Những tiến trình này có đặc điểm sau:



Chúng chỉ chạy trong Kernel Mode trong không gian địa chỉ của kernel.
Chúng không tương tác với người dùng, do đó không cần thiết bị cuối
(terminals).
• Chúng được tạo khi hệ thống bắt đầu và tồn tại cho đến khi hệ thống tắt.

Hình 1.5 Giao tiếp giữa user mode và kernel mode
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 18


Ngoài system call, Linux còn sử dụng nhiều cách khác cho phép tiến trình
user có thể tương tác với Kernel:
 CPU đang chạy tiến trình sinh ra một ngoại lệ (exception) chẳng hạn CPU


đọc được một lệnh không hợp lệ, hoặc thực hiện phép chia cho 0, khi đó tiến
trình sẽ tạm dừng lại, kernel sẽ xử lý ngoại lệ đó thay cho tiến trình (Hình
1.6).
 Thiết bị ngoại vi (chuột, bàn phím,...) sinh ra một tín hiệu ngắt (interrupt)
cho CPU để thông báo một sự kiện nào đó xảy ra. Mỗi tín hiệu ngắt đó sẽ
được chuyển đến một tiến trình kernel gọi là interrupt handler xử lý. Tiến
trình đang chạy sẽ buộc phải tạm dừng lại để chuyển sang xử lý ngắt (Hình
1.6).
 Timer Interrupt: đây là loại ngắt đặc biệt, cứ sau một khoảng thời gian, timer
interrupt sẽ được kích hoạt, các tiến trình đang chạy dừng lại, chuyển thực
thi sang một tiến trình kernel đặc biệt là Scheduler, tiến chình này có nhiệm
vụ lập lịch cho các tiến trình user, giúp ta có một hệ thống đa nhiệm (hình
1.6).

Hình 1.6 Xử lý ngắt trong Linux Kernel

Mỗi tiến trình khi chạy được kernel quản lý bởi một cấu trúc dữ liệu gọi là
process descriptor chứa các thông tin trạng thái cho tiến trình đó. Khi tiến trình tạm
thời dừng lại (do lập lịch hoặc gọi system call hoặc xử lý ngắt, … ), nó sẽ lưu giá trị
các thanh ghi của CPU lên process descriptor để sau này có thể khôi phục lại. Các
thanh ghi bao gồm:






Bộ đếm chương trình (PC), con trỏ stack (SP)
Thanh ghi thông thường (EAX, EBX, ECX, EDX, …)

Thanh ghi xử lý dấu phẩy động
Thanh ghi trạng thái CPU (cr0, cr3, … )
Thanh ghi quản lý bộ nhớ

Khi tiến trình được khôi phục, kernel sẽ sử dụng các thông tin đã lưu trong
process descriptor để đặt lại giá trị các thanh ghi đặc biệt là bộ đếm chương trình
hay con trỏ lệnh, từ đó tiến trình có thể chạy lại lệnh cuối cùng từ thời điểm dừng.
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 19


Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 20


3 Giới thiệu lỗ hổng trong Linux Kernel
3.1 Giới thiệu chung về lỗ hổng
Lỗ hổng phần mềm là khái niệm chỉ các lỗi xảy ra trong phần mềm có thể giúp kẻ
tấn công thay đổi được luồng thực thi của phần mềm đó, từ đó thực hiện được các
hành động mà kẻ tấn công mong muốn. Ví dụ như người dùng sử dụng Microsoft

office để đọc file doc, nhưng Microsoft office có lỗ hổng, khiến cho khi người dùng
mở file doc do kẻ tấn công gửi đến, sẽ chạy một đoạn mã hoặc chương trình nào đó
của kẻ tấn công (hình 2.1). Lỗ hổng phần mềm bản chất cũng là một lỗi của phần
mềm (bug), nhưng khác với lỗi thông thường, thường khiến chương trình không
hoạt động, chạy sai chức năng, lỗ hổng phần mềm thường hướng đến việc điều
khiển, chiếm quyền thực thi chương trình đó, khiến chương trình đó chạy theo ý
muốn của kẻ tấn công.

Hình 2.7 Lỗ hổng trong Microsoft Word cho phép chạy chương trình calc.exe

Lỗ hổng phần mềm xảy ra do lập trình viên lập trình không đúng, thường do
việc xử lý dữ liệu đầu vào không chính xác. Ví dụ phần mềm Microsoft office, dữ
liệu đầu vào là file doc, phần mềm sẽ đọc file doc, đọc dữ liệu trong đó, xử lý dữ
liệu và hiển thị cho người dùng. Trình duyệt Chrome đọc mã html từ trang web,
phân tích mã đó và hiển thị trang web cho người dùng. Lỗ hổng phần mềm thường
xuất hiện trong các phần mềm lập trình bởi ngôn ngữ cấp thấp như C, Assembly khi
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 21


mà lập trình viên thường phải tự quản lý bộ nhớ, cấp phát, giải phóng, sử dụng con
trỏ để truy cập bộ nhớ.
Lỗ hổng phần mềm được chia thành nhiều loại: memory corruption như stack
overflow, heap overflow, lỗi format string, sử dụng con trỏ không đúng như useafter-free, double-free, không khởi tạo con trỏ, các lỗi logic như race condition.
Lịch sử lĩnh vực nghiên cứu lỗ hổng phần mềm bắt đầu từ những năm 1982, khi mà

con sâu máy tính đầu tiên Morris được viết khai thác lỗ hổng của phần mềm finger.
Các kĩ thuật khai thác lỗ hổng phần mềm ngày càng phức tạp, các cơ chế bảo vệ
được xây dựng đồng thời các cơ chế bypass cũng được tìm ra.
Khi nhắc đến lỗ hổng phần mềm, chúng ta thường nghĩ đến lỗ hổng trên các
phần mềm người dùng phổ biến như Microsoft Office, Adobe Reader, Chrome,
Firefox. Tuy nhiên không chỉ có vậy, Linux Kernel bản chất cũng là một phần
mềm, nó là một phần mềm phức tạp được code bằng ngôn ngữ C và nhiều phần
code bằng assembly. Do đó, Linux Kernel tiềm ẩn nhiều lỗ hổng khác nhau. Thực tế
quá khứ đã cho thấy nhiều lỗ hổng đã từng được tìm ra và khai thác trên Linux
Kernel. Khi nhắc đến việc khai thác lỗ hổng trên kernel chúng ta thường sử dụng
thuật ngữ local privilege escalation – vượt quyền. Giả sử ta đã chiếm được quyền
tài khoản thông thường trên hệ thống (đơn giản như là tấn công ứng dụng web của
hệ thống), nhưng tài khoản thường có rất ít quyền, không thể đọc được file shadow,
không thể tắt được hệ thống, không thể tạo thêm được tài khoản mới, … Làm nào ta
có thể vượt được lên quyền cao hơn, cụ thể quyền root. Rõ ràng hệ thống hoạt động
bình thường không cho phép ta làm như vậy. Một cách phổ biến để chiếm được
quyền root là thực hiện chiềm quyền kernel, bởi vì mã lệnh trong kernel có thể làm
tùy ý (hình 2.2). Nếu trong kernel có một lỗ hổng nào đó, tài khoản bình thường
thực hiện khai thác lỗ hổng đó, và chạy được mã lệnh trong kernel thì đồng nghĩa
cũng chiếm được quyền cao nhất (Tham khảo phần quản lý tiến trình – chương 1).

Hình 2.8 Khai thác lỗ hổng trong Linux Kernel để chiếm quyền root
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 22



Cách thức hoạt động và mã nguồn của kernel tương đồng với các phần mềm
user thông thường do đó các lớp lỗ hổng thường gặp cũng tương tự nhau. Phần tiếp
theo sẽ trình bày các dạng lỗ hổng thường gặp trong Linux Kernel.

3.2 Lỗ hổng NULL Pointer Dereference
Đây là lớp lỗ hổng đã từng rất phổ biến cho Linux Kernel. Lỗ hổng này liên quan
đến việc sử dụng con trỏ không đúng. Như chúng ta đã biết, con trỏ được sử dụng
để lưu trữ địa chỉ của một biến khác trong bộ nhớ, mỗi lần con trỏ được giải tham
chiếu, giá trị được lưu tại địa chỉ mà con trỏ giữ sẽ được tham chiếu. Trong ngôn
ngữ lập trình C theo chuẩn ISO, một con trỏ tĩnh, không được khởi tạo giá trị ban
đầu sẽ có giá trị là NULL (0x0), nói cách khác địa chỉ mà nó chứa là địa chỉ 0x0.
NULL cũng là giá trị thường được trả về trong các hàm cấp phát bộ nhớ nếu có lỗi
xảy ra. Giả sử phần kernel giải tham chiếu một con trỏ NULL, nó sẽ thử sử dụng bộ
nhớ tại địa chỉ 0x0, điều này thường làm chết kernel, vì địa chỉ 0x0 thường không
được ánh xạ để sử dụng.

Hình 2.9 Không gian địa chỉ bộ nhớ userspace và kernelspace trong Linux

Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 23


Hình 2.10 Địa chỉ được cấp phát (mmap) ở địa chỉ 0x00000000


Hình 2.11 Mã khai thác (exploit payload) được đặt ở địa chỉ 0x00000000

Số lượng lỗ hổng NULL Pointer Dereference được tìm ra khá nhiều. Nhìn một
cách tổng quát, lỗi này nằm trong một lớp lỗi rộng hơn được biết đến với thuật ngữ
tiếng anh “uninitialized/nonvalidated/corrupted pointer dereference”. Lớp lỗi này
bao phủ tất cả các tình huống khi mà con trỏ được sử dụng nhưng nội dung của con
trỏ (địa chỉ nó chứa) đã bị sửa đổi, hoặc không được khởi tạo, hoặc không được
kiểm tra chính xác. Một con trỏ tĩnh không khởi tạo thì sẽ có giá trị NULL, tuy
nhiên một con trỏ cục bộ (biến cục bộ trong hàm) nếu không được khởi tạo thì nó sẽ
có giá trị ngẫu nhiên nào đó phụ thuộc vào khung stack của hàm tại thời điểm đó, vì
Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 24


biến cục bộ được lưu trên bộ nhớ stack. Bằng cách điều khiển stack ta có thể điều
khiển được giá trị con trỏ đó.
Ví dụ minh họa:

Hình 2.12 Hàm ptr_un_initialized không khởi tạo con trỏ p

Trong phần code trên, hàm main gọi hàm big_stack_usage() trước, hàm
big_stack_usage khởi tạo một chuỗi lớn trên stack và lấp đầy chuỗi này bằng các kí
tự A. Khi kết thúc hàm big_stack_usage, phía trên đỉnh stack sẽ chứa các kí tự A
này. Đến khi hàm ptr_un_initialized được gọi, hàm này sẽ sử dụng lại phần stack
của hàm trước, do đó con trỏ p mặc dù không được khởi tạo nhưng sẽ nhận giá trị

của phần stack được cấp cho nó, nghĩa là giá trị của nó hoàn toàn tiên đoán được.
Nếu chuỗi string “A” là do người dùng truyền vào, thì giá trị của con trỏ sẽ điều
khiển được. Tức là ta có thể điều khiển được p trỏ tới bất kì vị trí nào. Nếu p được
sử dụng như con trỏ hàm, thì luồng thực thi của chương trình hoàn toàn có thể điều
khiển được bằng cách cho p trỏ đến đoạn shellcode của ta.

3.3 Lỗ hổng Stack Buffer Overflow
Kernel stack tồn tại cho mỗi luồng/tiến trình khi chạy ở mức kernel. Mỗi một tiến
trình user-land chạy trên hệ thống có ít nhất hai stack: user-land stack và kernelland stack. Kernel stack được sử dụng bất kì khi nào tiến trình chuyển sang kernel
land (Thông qua việc xử lý ngắt hoặc gọi một system call).
Chức năng chung của kernel stack không khác nhiều so với chức năng của
user-land stack, nó có chung một số đặc điểm với user-land stack: hướng phát triển
của stack (theo hướng giảm dần, từ địa chỉ cao tới địa chỉ thấp), thanh ghi esp (hoặc
rsp cho x86_64) lưu địa chỉ đỉnh stack và cách một hàm sử dụng stack (lưu trữ biến
cục bộ, tham số được truyền vào hàm, địa chỉ trả về của một hàm,…) (hình 2.7).

Sinh viên Phạm Văn Khánh

SHSV: 20101715

Lớp: KSTN-CNTT-K55

Trang 25


×