Protostar Heap 3

Alat dan Bahan

  • Fail: heap3
  • Kompiler: GCC
  • Sistem operasi: Protostar ISO

Mengatur Lingkungan Pekerjaan

  1. Source Code
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

void winner()
{
  printf("that wasn't too bad now, was it? @ %d\n", time(NULL));
}

int main(int argc, char **argv)
{
  char *a, *b, *c;

  a = malloc(32);
  b = malloc(32);
  c = malloc(32);

  strcpy(a, argv[1]);
  strcpy(b, argv[2]);
  strcpy(c, argv[3]);

  free(c);
  free(b);
  free(a);

  printf("dynamite failed?\n");
}

Identifikasi Kelemahan

Doug Lea Malloc dlmalloc adalah implementasi malloc yang tidak dilindungi oleh unlink. Artikel lain menjelaskan bagaimana caranya melakukan eksploitasi Heap Overflow. Sedangkan artikel lain menjelaskan bagaimana cara kerja malloc.

Tak dapat disangkal, menemukan heap overflow lebih menantang dibanding stack overflow. Karena lebih susah mengakali double-linked list agar bisa mendapatkan flow of execution dibanding mencoba overwrite EIP pada stack frame. Sebelum dimulai, sebaiknya baca Vudo Malloc dan Exploiting the Wilderness.

Heap digunakan untuk dynamic allocation dan setiap sistem operasi memiliki heap allocator. Jadi cara mengeksploitasi heap allocator bergantung pada implementasi heap allocator pada sistem ataupun program. Pada binary yang diberikan, allocator yang digunakan adalah dlmalloc (Doug Lea's Malloc). Pahami cara kerja dlmalloc terlebih dahulu agar lebih memahami masalah yang diberikan.

dlmalloc menganggap heap sebagai serangkaian potongan yang berbeda. Potongan terakhir pada bagian atas heap merupakan potongan spesial yang disebut Wilderness Chunk (WC). Alamat pada bagian akhir heap atau bagian atas WC disebut Program Break (PB). Heap dapat diperluas jika diperlukan dengan memanggil fungsi brk() dan sbrk() untuk menambah nilai PB. Setiap potongan bisa dialokasikan maupun dibebaskan. Potongan yang dialokasikan nampak pada bagan dibawah ini.

    chunk -> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             | prev_size: ukuran potongan sebelumnya, dalam bytes      |
             | (digunakan oleh dlmalloc jika potongan sebelumnya       |
             | dibebaskan)                                             |
             +---------------------------------------------------------+
             | size: ukuran potongan (jumlah bytes antara "chunk"      |
             | dan "nextchunk") dan 2 bits sebagai informasi status    |
      mem -> +---------------------------------------------------------+
             | fd: tidak digunakan oleh dlmalloc karena "chunk" sudah  |
             | dialokasikan (data pengguna berawal disini)             |
             + - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
             | bk: tidak digunakan oleh dlmalloc karena "chunk" sudah  |
             | dialokasikan (mungkin saja ada data pengguna disini     |
             + - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
             |                                                         .
             .                                                         .
             . data pengguna (bisa berukuran 0 bytes long)             .
             .                                                         .
             .                                                         |
nextchunk -> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
             | prev_size: tidak digunakan oleh dlmalloc karena "chunk" |
             | sudah dialokasikan (bisa berisi data pengguna, juga     |
             | untuk mengurangi pemborosan)     |
             +---------------------------------------------------------+

Dan free chunks terlihat seperti ini:

    chunk -> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             | prev_size: bisa saja berisi data pengguna (termasuk     |
             | sejak "chunk" dibebaskan, potongan sebelumnya perlu     |
             | dialokasikan)                                           |
             +---------------------------------------------------------+
             | size: ukuran potongan (jumlah bytes antara "chunk"      |
             | dan "nextchunk") dan 2 bits sebagai informasi status    |
      mem -> +---------------------------------------------------------+
             | fd: forward pointer untuk potongan selanjutnya pada     |
             | siklus doubly-linked list (bukan _physical_ chunk       |
             | selanjutnya)                                            |
             +---------------------------------------------------------+
             | bk: back pointer untuk potongan sebelumnya pada siklus  |
             | doubly-linked list (bukan _physical_ chunk sebelumnya)  |
             +---------------------------------------------------------+
             |                                                         .
             .                                                         .
             . ruang tak terpakai (bisa berjumlah 0 bytes long)                      .
             .                                                         .
             .                                                         |
nextchunk -> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             | prev_size: ukuran "chunk", dalam bytes (digunakan oleh  |
             | dlmalloc karena potongan sebelumnya sudah bebas         |
             +---------------------------------------------------------+

Tiap potongan terdiri dari ruang yang digunakan untuk menyimpan data pengguna dan ruang lainnya menyimpan 4 bytes untuk informasi metadata (prev_size, size, fd, bk). Mem adalah alamat yang dikembalikan ke pengguna saat fungsi malloc() dipanggil untuk mengalokasikan memori secara dinamis. Jika potongan dibebaskan maka dlmalloc menggunakan proses yang disebut binning untuk menyimpan potongan-potongan yang bebas pada doubly-linked list dan tiap potongan yang bebas pada pointer fd maupun bk akan diarahkan pada sebelum maupun sesudah potongan yang bebas pada doubly-linked list. Bins berada pada memori sebagai array of pointers. fd dan bk hanya digunakan jika potongan sudah bebas. Jika fd dan bk sudah dialokasikan maka data pengguna dapat digunakan pada pointer fd dan bk. Begitu juga ruang sebelumnya hanya digunakan jika potongan sebelumnya sudah bebas. Dengan kata lain, potongan sebelumnya dapat digunakan untuk menyimpan data.

Poin yang perlu diingat adalah bit terbawah pada size field menunjukkan apakah potongan sebelumnya sudah dialokasikan atau bebas. Jika nilainya 1, maka potongan sebelumnya sudah dialokasikan begitu juga sebaliknya. Sedangkan pada bit terbawah selanjutnya pada size field menunjukkan apakah potongan sudah dialokasikan via mmap.

Penting!

Saat potongan dibebaskan, jika dialokasikan via mmap, maka program akan memanggil fungsi munmap_chunk(), Dengan kata lain, program akan memanggil fungsi chunk_free(). Disamping fungsi chunk_free(), fungsi unlink juga dipanggil jika potongan sebelumnya dibebaskan untuk menonaktifkan potongan sebelumnya pada doubly-linked list dan digabungkan dengan potongan yang sedang dibebaskan! Catat juga jika ada dua potongan yang dibebaskan secara berurutan maka heap allocator akan menggabungkan kedua potongan ini menjadi potongan heap yang lebih besar untuk membantu defragmentation.

Lihat bagian penting fungsi chunk_free() dan macro unlink().

// chunk_free()
INTERNAL_SIZE_T hd = p->size;
 ...
 if (!hd & PREV_INUSE))     /* consolidate backward */    /* [A] */
 {
   prevsz = p->prev_size;
   p = chunk_at_offset(p, -(long)prevsz);                 /* [B] */
   sz += prevsz;

   if (p->fd == last_remainder(ar_ptr))
     islr = 1;
   else
     unlink(p, bck, fwd);
 }
// unlink()

#define unlink( P, BK, FD ) {            \
    BK = P->bk;                          \
    FD = P->fd;                          \
    FD->bk = BK;                         \
    BK->fd = FD;                         \
}

Berdasarkan source code vuln.c, program mengalokasikan 32 bytes dari memori untuk setiap buffer. Ada fungsi strcpy untuk 3 args yang diberikan, dan setiap buffer dibebaskan secara berurutan sebelum memanggil fungsi printf. Untuk memanggil fungsi winner maka yang harus dilakukan adalah menemukan heap overflow sebelum fungsi printf.

Untuk latihan ini, fungsi yang akan dieksploitasi adalah free. Buatlah potongan palsu didalam buffer B sehingga buffer B dibebaskan, akibatnya potongan sebelumnya menjadi bebas dan memanggil fungsi unlink() untuk menghapus potongan sebelumnya dari doubly-linked list. Mengapa program menghapus potongan palsu dari doubly-linked list? Ini terjadi karena saat potongan dibebaskan dan berlanjut pada potongan lain yang juga dibebaskan maka dua potongan tersebut digabung menjadi satu potongan yang lebih besar.

Program tidak akan memproses prev_size jika potongan sebelumnya sudah dialokasikan sehingga cara yang digunakan agar program menganggap potongan sudah bebas maka isi bit terbawah dengan \x00\x00\x00\x00. Selain dengan null-byte bisa diisi juga dengan nilai negatif, misal isi buffer B adalah 0xfffffffc (-4), ini perlu dilakukan karena size field dari potongan yang berurutan sebesar 4 bytes pada prev_size buffer B. Selain itu dlmalloc juga menghitung p pada kode chunk_at_offset(p, -(long)prevsz) yang melakukan inverse untuk mendapatkan lokasi potongan sebelumnya. Atur nilai prev_size menjadi -8 sehingga program akan menganggap bahwa potongan sebelumnya dimulai pada offset 8 bytes.

Dengan menggunakan fungsi unlink() maka atur p agar sama dengan nilai awal potongan palsu, BK diisi dengan NOP sled, dan FD diisi dengan fungsi puts() dikurangi 12 bytes. Sebelum membangun exploit ingat bahwa pointer BK ada pada 12 bytes awal dari potongan dan pointer FD ada pada 8 bytes awal dari awal potongan. Jadi tambahkan 8 byte data agar bisa mencapai pointer FD, misal dengan bytes 0xdeadbeef dua kali.

Global Offset Table (GOT) adalah tabel fungsi pointer didalam pemrosesan ruang alamat yang mengarahkan pada fungsi yang telah diimpor. Saat program dikompilasi, tiap entri pada GOT bernilai 0. Pada GLIBC library, tiap proses memiliki tabel GOT sendiri sehingga exploit yang digunakan dapat menimpa salah satu tabel GOT agar diarahkan ke fungsi yang diinginkan.

Tabel GOT bisa dilihat dengan perintah readelf -a heap3 | less

Fungsi yang akan ditimpa adalah puts, mengapa bukan fungsi printf saja? Karena fungsi printf memerlukan parameter tambahan sedangkan fungsi puts tidak perlu. Ingat bahwa perlu mengurangi 12 byte dari alamat yang ada pada tabel GOT karena fungsi unlink() pada kode FD-bk = BK terletak pada 12 byte offset awal pada potongan pertama sehingga nilai alamatnya menjadi 0x0804b128-0xc = 0x0804b11c.

Dimanakah NOP Sled perlu diletakkan pada program? Atur breakpoint pada b main dan amati *registerdengani r`.

gdb -q heap3
> b *0x0804889e
> r AAAA BBBB CCCC
> i r

Arahkan pointer ke alamat register EAX 0x0804c008, shellcode yang digunakan adalah push 0x08048864 dan ret. 0x08048864 adalah alamat fungsi winner() sehingga payload menjadi \x68\x64\x88\x04\x08\xc3.

Eksploitasi

Pada bagan dibawah ini akan dijelaskan sekilas exploit yang akan digunakan.

Buffer A = [NOP sled] + [shellcode] + [12 filler bytes] + [modified prev_size header of Buffer B (-8)] + [modified size header of Buffer B (-4)]

Buffer B = [8 filler bytes] + [address of GOT entry – 12 bytes] + [address of NOP sled]

Buffer C = [C]

Pada buffer A, tempatkan 14 byte NOP Sled diikuti dengan 6 byte shellcode, 12 byte filler data agar bisa mencapai akhir potongan pertama dan tambahan 8 byte data untuk melakukan heap overflow pada header buffer B. Tambahan 8 bytes berguna untuk mengambil alih informasi metada pada potongan kedua dengan mengubah nilai prev_size menjadi -8 (0xfffffff8) dan ukurannya menjadi -4 (0xfffffffc) sehingga dlmalloc akan memproses potongan palsu dengan fungsi unlink() serta alur program dapat diatur mengikuti NOP Sled dan shellcode.

./heap3 `python -c 'print "\x90"*14 + "\x68\x64\x88\x04\x08\xc3" + "A"*12 + "\xf8\xff\xff\xff" + "\xfc\xff\xff\xff"'` `python -c 'print "\xde\xad\xbe\xef"*2+"\x1c\xb1\x04\x08"+"\x08\xc0\x04\x08"'` C

Referensi

results matching ""

    No results matching ""