LAB 2: Buffer Overflows - Bùi Đức Thắng | Báo cáo bài thực hành môn An toàn thông tin Trường đại học sư phạm kỹ thuật TP. Hồ Chí Minh

Lỗi tràn bộ đệm thông thường xảy ra khi một bộ đệm đã được cấp phát một không gian lưu trữ cụ thể và có nhiều dữ liệu được sao chép vào nó hơn nó có thể xử lý. Các bước khai thác lỗ hỏng Buffer overflow : 1. Tìm sự hiện diện và vị trí của lỗ hổng tràn bộ đệm; 2. Ghi thêm dữ liệu vào bộ đệm nhiều hơn nó có thể xử lý; 3. Ghi đè địa chỉ trả về của một hàm; 4. Thay đổi luồng thực thi sang mã hacker. Tài liệu giúp bạn tham khảo, ôn tập và đạt kết quả cao. Mời bạn đọc đón xem!

BỘ GIÁO DỤC VÀ ĐÀO TẠO
TRƯỜNG ĐẠI HỌC SƯ PHẠM KỸ THUẬT TPHCM
KHOA: CÔNG NGHỆ THÔNG TIN
------oOo-----
BÁO CÁO
LAB 2: BUFFER OVERFLOWS
GVHD: THS.HUỲNH NGUYÊN CHÍNH
SVTH: BÙI ĐỨC THẮNG
MÃ SINH VIÊN: 22110422
LỚP: INSE330380_23_2_03
HỌC KỲ: 2
TPHCM, Tháng 2 Năm 2024
Lab2. Buffer Overflows
Lỗi tràn bộ đệm thông thường xảy ra khi một bộ đệm đã được cấp phát một
không gian lưu trữ cụ thể và có nhiều dữ liệu được sao chép vào nó hơn nó có thể xử
lý.
Các bước khai thác lỗ hỏng Buffer overflow :
1. Tìm sự hiện diện và vị trí của lỗ hổng tràn bộ đệm
2. Ghi thêm dữ liệu vào bộ đệm nhiều hơn nó có thể xử lý
3. Ghi đè địa chỉ trả về của một hàm
4. Thay đổi luồng thực thi sang mã hacker
Bước 0. Chuẩn bị công cụ và tài liệu liên quan
- Ubuntu 16.04 (32-bit)
- Source code: stack.c, exploit.c
Code được cung cấp bởi giảng viên
Bước 1. Tắt địa chỉ Randomization
$ sudo sysctl –w kernel.randomize_va_space=0
Để có thể tìm được địa chỉ của mã độc dễ hơn vì file được lưu ở một địa chỉ cố
định không bị lưu ngẫu nhiên gây có tìm kiếm. Giúp thực hiện bài lab này dễ hơn
Bước 2. Xác định địa chỉ trả về thích hợp để chèn đoạn code thực thi
2.1. Tạo file debug stack_dbg (bỏ qua StackGuard và cho phép stack có
thể executable)
$gcc –z execstack –fno-stack-protector –g –o stack_dbg stack.c
StackGuard: kỹ thuật bảo vệ trong lập trình để tránh tấn công Buffer overflow
nên ta cần phải bỏ qua rd để thực hiện đc bài Lab.StackGua
Cho phép stack có thể executable là cho phép mã thực thi trong vùng nhớ đó
có thể được thực thi trực tiếp từ một trình biên dịch hoặc trình thông dịch mà không
cần sao chép vào một vùng nhớ thực thi khác trước.
2.2. Chúng ta tạo một file “badfile”, được thực hiện bằng lệnh:
$touch badfile
2.3. Tiến hành vào chương trình debug cho file stack.c, bằng cách khởi
động gdb và set file cần debug ngay.
$ gdb stack_dbg
2.4. Thực hiện đặt breakpoint tại hàm bof (tên hàm trong file stack.c):
2.5. Thực hiện dòng lệnh:
(gdb)b bof
Xem địa chỉ dòng lệnh bof và thuộc dòng thứ mấy trong file stack.
2.6. Tiến hành kích hoạt chương trình chạy đến breakpoint:
(gdb)run
Lấy ra địa chỉ của thanh ghi ebp
(gdb)p $ebp
$1 = (void *) 0xbfffeb28
Ta xác định được địa chỉ thanh ghi ebp = (void *) 0xbfffeb38
EBP thanh ghi con trỏ cơ sở (Base Pointer), thường dùng để tham chiếu đến
các biến tham số sử dụng trong chương trình con.
Từ kết quả $1 ta thấy địa chỉ của chương trình con bof trong file stack có địa
chỉ là 0xbfffeb28
Tiếp theo tiến hành xác định địa chỉ biến buffer:
(gdb)p &buffer
$2 = (char (*) [24]) 0xbfffeb08
Ta xác định được địa chỉ biến buffer = (char (*)[24]) 0xbfffeb08
Tính khoảng cách từ ebp – buffer:
(gdb) p/d 0xbffffeb38 0xbffffeb18
$3 = 32
Từ các dữ liệu trên ta có thể tính được địa chỉ trả về của hàm bof:
Return address = ebp + (32 + 4) = ebp + 36
(Ta cộng thêm 4 vì ta bỏ qua 4 byte (32 bit) vùng Return address)
Bước 3. Chỉnh sửa file exploit.c
Thay đổi địa chỉ trả về thành:
*((long *) (buffer + )) = + ;36 0xbfffeb28 0x80
Dùng hàm memcpy để copy sizeof(shellcode) kí tự từ mảng shellcode đến địa
chỉ (buffer + sizeof(buffer) – sizeof(shellcode)) phía sau của vùng Buffer:
memcpy(buffer + sizeof(buffer) – sizeof(shellcode), shellcode,
sizeof(shellcode));
Bước 4. Thực thi:
Tiến hành tạo liên kết giữa /bin/zsh và /bin/sh:
$ sudo ln -sf /bin/zsh /bin/sh
Tạo một liên kết mềm (symbolic link) từ /bin/zsh đến /bin/sh trên hệ thống. Khi
thực hiện lệnh này với quyền quản trị (sudo), nó sẽ ghi đè lên bất kỳ tập tin hoặc liên
kết hiện có có tên /bin/sh.
Tiếp đến, compile file stack.c và cho phép stack executable đồng thời bỏ qua
StackGuard và thay đổi giá chị của biến DBUF_SIZE trong file stack.c thành 100.
$ gcc -DBUF_SIZE= -o stack -z execstack -fno-stack-protector stack.c100
Đặt quyền sỡ hữu file stack.c sang cho root:
$ sudo chown root stack
Thay đổi file stack thành chương trình đặc quyền:
$ sudo chmod 4755 stack
Kiểm tra xem chương trình đã có đặc quyền cho file stack câu lệnh: bằng
$ls -l stack
Để thực hiện tiếp chương trình từ bước hai sử dụng lệnh ”quit” để thoát chương
trình và sau đó thực hiện tiếp theo.
Compile chương trình exploit.c (chương trình khai thác lỗ hỏng)
$ gcc -o exploit exploit.c
Thực thi chương trình exploit
$./exploit
Sau khi thực thi chương trình, chương trình sẽ tạo ra “badfile”
$./stack
Khởi động cuộc tấn công bằng cách chạy chương trình dễ bị tấn công. Chương
trình dễ bị tấn công ở đây là chương trình stack
Kết quả là “#” tức là ta đang có quyền root trong máy nạn nhân. Có thể điều
khiển máy bạn nhân theo ý muốn.
Đến bước này thì phần khai thác lỗ hỏng Buffer overflow đã thành công.
Chúng ta đã thành công xâm nhập vào máy nạn nhân.
Bước 5. Defeating Address Randomization (ASLR)
Tiến hành bật lại Address Randomization
$sudo sysctl –w kernel.randomize_va_space=2
Trong ASLR, khi sử d ng phần randomize_va_space. Các giá trị sau được hỗ
trợ:
0 - Không ngẫu nhiên. Mọi thứ đều tĩnh.
1 - Ngẫu nhiên bảo thủ. Thư viện được chia sẻ stack, mmap(), VDSO và heap ,
là ngẫu nhiên.
2 - Hoàn toàn ngẫu nhiên. Ngoài các phần tử được liệt kê ở điểm trước, bộ nhớ
được quản lý thông qua brk() cũng được ngẫu nhiên hóa.
Vậy nên khi ta đặt giá trị = 2 thì điểm đến của các vị trí ô nhớ sẽ bị ngẫu nhiên
không thể đoán trước được nên việc đoán mù ra giá trị là hoàn toàn bất khả thi
Bước 6. Bật lại tùy chọn StackGuard Protection
Tiến hành compile chương trình stack mà không có tùy chọn -fno-stack-
protector
Thực thi chương trình bị ngăn chặn.
StackGuard là một kỹ thuật bảo vệ trong lập trình máy tính, nhằm ngăn chặn
các cuộc tấn công thực hiện thông qua việc tận dụng lỗ hổng tràn bộ nhớ (buffer
overflow). Kỹ thuật này thường được áp dụng vào việc bảo vệ bộ nhớ stack trong quá
trình thực thi chương trình. Mặc định khi compile chương trình đã được bật kỹ thuật
này.
Khi một chương trình được biên dịch với StackGuard, nó sẽ tự động chèn các
giá trị canary (gọi là "guard") vào giữa các biến cục bộ và các khung stack. Canaries
này sẽ là các giá trị được chọn ngẫu nhiên mỗi khi chương trình được thực thi. Khi
chương trình kết thúc hoặc một hàm kết thúc, giá trị canary sẽ được kiểm tra xem nó
đã bị thay đổi không. Nếu canary đã bị thay đổi, điều này cho thấy rằng có một cuộc
tấn công buffer overflow đã xảy ra và chương trình có thể chấm dứt thực thi hoặc
thực hiện các hành động bảo vệ khác.
StackGuard là một trong những kỹ thuật đầu tiên được phát triển để ngăn chặn
các cuộc tấn công buffer overflow và vẫn được sử dụng trong một số hệ thống và môi
trường lập trình. Tuy nhiên, các kỹ thuật bảo vệ khác như Address Space Layout
Randomization (ASLR) và các biến thể của StackGuard đã được phát triển để đối phó
với các hình thức tấn công mới và phức tạp hơn.
Step 7. Bật tùy chọn Non-executable Stack Protection
Compile chương trình với tùy chọn noexecstack
$ gcc -o stack -fno-stack-protector -z noexecstack stack.c
Tấn công thất bại
Executestack là một chương trình đặt, xóa hoặc truy vấn cờ stack có thể thực
thi của các tệp nhị phân ELF và các thư viện được chia sẻ. Trước đây, Linux đã cho
phép thực thi các lệnh trên stack và có rất nhiều mã nhị phân và thư viện được chia sẻ
giả định hành vi này. Hơn nữa, mã nhúng GCC cho ví dụ: các hàm lồng nhau yêu cầu
stack thực thi trên nhiều kiến trúc. Để tránh phá vỡ các tệp nhị phân và các thư viện
được chia sẻ cần stack thực thi, các tệp nhị phân ELF và các thư viện được chia sẻ
hiện có thể được đánh dấu là yêu cầu stack thực thi hoặc không yêu cầu stack.
Người dùng có thể ghi đè điều này tại code assembly (thông qua tùy chọn trình
hợp dịch --execstack hoặc --noexecstack), tại thời điểm liên kết (thông qua -z thực thi
hoặc -z tùy chọn trình liên kết noexecstack) và sử dụng công cụ thực thi cũng trên thư
viện đã chia sẻ hoặc nhị phân trình liên kết . Công cụ này đặc biệt hữu ích cho các thư
viện được chia sẻ của bên thứ ba, nơi được biết rằng chúng không cần ngăn xếp thực
thi hoặc thử nghiệm chứng minh điều đó
| 1/10

Preview text:

BỘ GIÁO DỤC VÀ ĐÀO TẠO
TRƯỜNG ĐẠI HỌC SƯ PHẠM KỸ THUẬT TPHCM
KHOA: CÔNG NGHỆ THÔNG TIN ------oOo----- BÁO CÁO LAB 2: BUFFER OVERFLOWS
GVHD: THS.HUỲNH NGUYÊN CHÍNH SVTH: BÙI ĐỨC THẮNG MÃ SINH VIÊN: 22110422 LỚP: INSE330380_23_2_03 HỌC KỲ: 2 TPHCM, Tháng 2 Năm 2024 Lab2. Buffer Overflows
Lỗi tràn bộ đệm thông thường xảy ra khi một bộ đệm đã được cấp phát một
không gian lưu trữ cụ thể và có nhiều dữ liệu được sao chép vào nó hơn nó có thể xử lý.
Các bước khai thác lỗ hỏng Buffer overflow :
1. Tìm sự hiện diện và vị trí của lỗ hổng tràn bộ đệm
2. Ghi thêm dữ liệu vào bộ đệm nhiều hơn nó có thể xử lý
3. Ghi đè địa chỉ trả về của một hàm
4. Thay đổi luồng thực thi sang mã hacker
Bước 0. Chuẩn bị công cụ và tài liệu liên quan
- Ubuntu 16.04 (32-bit)
- Source code: stack.c, exploit.c
Code được cung cấp bởi giảng viên
Bước 1. Tắt địa chỉ Randomization
$ sudo sysctl –w kernel.randomize_va_space=0
Để có thể tìm được địa chỉ của mã độc dễ hơn vì file được lưu ở một địa chỉ cố
định không bị lưu ngẫu nhiên gây có tìm kiếm. Giúp thực hiện bài lab này dễ hơn
Bước 2. Xác định địa chỉ trả về thích hợp để chèn đoạn code thực thi
2.1. Tạo file debug stack_dbg (bỏ qua StackGuard và cho phép stack có thể executable)
$gcc –z execstack –fno-stack-protector –g –o stack_dbg stack.c
StackGuard: kỹ thuật bảo vệ trong lập trình để tránh tấn công Buffer overflow
nên ta cần phải bỏ qua StackGuard để thực hiện đc bài Lab.
Cho phép stack có thể executable là cho phép mã thực thi trong vùng nhớ đó
có thể được thực thi trực tiếp từ một trình biên dịch hoặc trình thông dịch mà không
cần sao chép vào một vùng nhớ thực thi khác trước.
2.2. Chúng ta tạo một file “badfile”, được thực hiện bằng lệnh: $touch badfile
2.3. Tiến hành vào chương trình debug cho file stack.c, bằng cách khởi
động gdb và set file cần debug ngay. $ gdb stack_dbg
2.4. Thực hiện đặt breakpoint tại hàm bof (tên hàm trong file stack.c):
2.5. Thực hiện dòng lệnh: (gdb)b bof
Xem địa chỉ dòng lệnh bof và thuộc dòng thứ mấy trong file stack.
2.6. Tiến hành kích hoạt chương trình chạy đến breakpoint: (gdb)run
Lấy ra địa chỉ của thanh ghi ebp (gdb)p $ebp $1 = (void *) 0xbfffeb28
Ta xác định được địa chỉ thanh ghi ebp = (void *) 0xbfffeb38
EBP thanh ghi con trỏ cơ sở (Base Pointer), thường dùng để tham chiếu đến
các biến tham số sử dụng trong chương trình con.
Từ kết quả $1 ta thấy địa chỉ của chương trình con bof trong file stack có địa chỉ là 0xbfffeb28
Tiếp theo tiến hành xác định địa chỉ biến buffer: (gdb)p &buffer
$2 = (char (*) [24]) 0xbfffeb08
Ta xác định được địa chỉ biến buffer = (char (*)[24]) 0xbfffeb08
Tính khoảng cách từ ebp – buffer:
(gdb) p/d 0xbffffeb38 – 0xbffffeb18 $3 = 32
Từ các dữ liệu trên ta có thể tính được địa chỉ trả về của hàm bof:
Return address = ebp + (32 + 4) = ebp + 36
(Ta cộng thêm 4 vì ta bỏ qua 4 byte (32 bit) vùng Return address)
Bước 3. Chỉnh sửa file exploit.c
Thay đổi địa chỉ trả về thành: *((long *) (buffer + )) = 36 0xbfffeb28 + 0x80;
Dùng hàm memcpy để copy sizeof(shellcode) kí tự từ mảng shellcode đến địa
chỉ (buffer + sizeof(buffer) – sizeof(shellcode)) phía sau của vùng Buffer:
memcpy(buffer + sizeof(buffer) – sizeof(shellcode), shellcode, sizeof(shellcode)); Bước 4. Thực thi:
Tiến hành tạo liên kết giữa /bin/zsh và /bin/sh:
$ sudo ln -sf /bin/zsh /bin/sh
Tạo một liên kết mềm (symbolic link) từ /bin/zsh đến /bin/sh trên hệ thống. Khi
thực hiện lệnh này với quyền quản trị (sudo), nó sẽ ghi đè lên bất kỳ tập tin hoặc liên
kết hiện có có tên /bin/sh.
Tiếp đến, compile file stack.c và cho phép stack executable đồng thời bỏ qua
StackGuard và thay đổi giá chị của biến DBUF_SIZE trong file stack.c thành 100. $ gcc -DBUF_SIZE= -o stack -z execstack 100
-fno-stack-protector stack.c
Đặt quyền sỡ hữu file stack.c sang cho root: $ sudo chown root stack
Thay đổi file stack thành chương trình đặc quyền: $ sudo chmod 4755 stack
Kiểm tra xem chương trình đã có đặc quyền cho file stack bằng câu lệnh: $ls -l stack
Để thực hiện tiếp chương trình từ bước hai sử dụng lệnh ”quit” để thoát chương
trình và sau đó thực hiện tiếp theo.
Compile chương trình exploit.c (chương trình khai thác lỗ hỏng) $ gcc -o exploit exploit.c
Thực thi chương trình exploit $./exploit
Sau khi thực thi chương trình, chương trình sẽ tạo ra “badfile” $./stack
Khởi động cuộc tấn công bằng cách chạy chương trình dễ bị tấn công. Chương
trình dễ bị tấn công ở đây là chương trình stack
Kết quả là “#” tức là ta đang có quyền root trong máy nạn nhân. Có thể điều
khiển máy bạn nhân theo ý muốn.
Đến bước này thì phần khai thác lỗ hỏng Buffer overflow đã thành công.
Chúng ta đã thành công xâm nhập vào máy nạn nhân.
Bước 5. Defeating Address Randomization (ASLR)
Tiến hành bật lại Address Randomization
$sudo sysctl –w kernel.randomize_va_space=2
Trong ASLR, khi sử dụng phần randomize_va_space. Các giá trị sau được hỗ trợ:
0 - Không ngẫu nhiên. Mọi thứ đều tĩnh.
1 - Ngẫu nhiên bảo thủ. Thư viện được chia sẻ, stack, mmap(), VDSO và heap là ngẫu nhiên.
2 - Hoàn toàn ngẫu nhiên. Ngoài các phần tử được liệt kê ở điểm trước, bộ nhớ
được quản lý thông qua brk() cũng được ngẫu nhiên hóa.
Vậy nên khi ta đặt giá trị = 2 thì điểm đến của các vị trí ô nhớ sẽ bị ngẫu nhiên
không thể đoán trước được nên việc đoán mù ra giá trị là hoàn toàn bất khả thi
Bước 6. Bật lại tùy chọn StackGuard Protection
Tiến hành compile chương trình stack mà không có tùy chọn -fno-stack- protector
Thực thi chương trình bị ngăn chặn.
StackGuard là một kỹ thuật bảo vệ trong lập trình máy tính, nhằm ngăn chặn
các cuộc tấn công thực hiện thông qua việc tận dụng lỗ hổng tràn bộ nhớ (buffer
overflow). Kỹ thuật này thường được áp dụng vào việc bảo vệ bộ nhớ stack trong quá
trình thực thi chương trình. Mặc định khi compile chương trình đã được bật kỹ thuật này.
Khi một chương trình được biên dịch với StackGuard, nó sẽ tự động chèn các
giá trị canary (gọi là "guard") vào giữa các biến cục bộ và các khung stack. Canaries
này sẽ là các giá trị được chọn ngẫu nhiên mỗi khi chương trình được thực thi. Khi
chương trình kết thúc hoặc một hàm kết thúc, giá trị canary sẽ được kiểm tra xem nó
đã bị thay đổi không. Nếu canary đã bị thay đổi, điều này cho thấy rằng có một cuộc
tấn công buffer overflow đã xảy ra và chương trình có thể chấm dứt thực thi hoặc
thực hiện các hành động bảo vệ khác.
StackGuard là một trong những kỹ thuật đầu tiên được phát triển để ngăn chặn
các cuộc tấn công buffer overflow và vẫn được sử dụng trong một số hệ thống và môi
trường lập trình. Tuy nhiên, các kỹ thuật bảo vệ khác như Address Space Layout
Randomization (ASLR) và các biến thể của StackGuard đã được phát triển để đối phó
với các hình thức tấn công mới và phức tạp hơn.
Step 7. Bật tùy chọn Non-executable Stack Protection
Compile chương trình với tùy chọn noexecstack
$ gcc -o stack -fno-stack-protector -z noexecstack stack.c Tấn công thất bại
Executestack là một chương trình đặt, xóa hoặc truy vấn cờ stack có thể thực
thi của các tệp nhị phân ELF và các thư viện được chia sẻ. Trước đây, Linux đã cho
phép thực thi các lệnh trên stack và có rất nhiều mã nhị phân và thư viện được chia sẻ
giả định hành vi này. Hơn nữa, mã nhúng GCC cho ví dụ: các hàm lồng nhau yêu cầu
stack thực thi trên nhiều kiến trúc. Để tránh phá vỡ các tệp nhị phân và các thư viện
được chia sẻ cần stack thực thi, các tệp nhị phân ELF và các thư viện được chia sẻ
hiện có thể được đánh dấu là yêu cầu stack thực thi hoặc không yêu cầu stack.
Người dùng có thể ghi đè điều này tại code assembly (thông qua tùy chọn trình
hợp dịch --execstack hoặc --noexecstack), tại thời điểm liên kết (thông qua -z thực thi
hoặc -z tùy chọn trình liên kết noexecstack) và sử dụng công cụ thực thi cũng trên thư
viện đã chia sẻ hoặc nhị phân trình liên kết . Công cụ này đặc biệt hữu ích cho các thư
viện được chia sẻ của bên thứ ba, nơi được biết rằng chúng không cần ngăn xếp thực
thi hoặc thử nghiệm chứng minh điều đó