jittery crackme writeup: unmask jit engine (part 0x1)

Bismillahirrahmaanirrahiim, dengan menyebut nama Allah yang Maha pengasih lagi Maha penyayang.

Kali ini saya akan menulis writeup salah satu crackme yang saya ambil dari crackmes.one. Awalnya iseng2 aja sih, itung2 sekalian ngasah skill saya juga di RE.

Jadi crackme yang saya kerjain deskripsinya begini:

Another x86_64 linux crackme, written in C and (a huge amount of) assembler. While it does not have any anti-debugging features, it is a ridiculously complex pile of self-modifying code, taking the ideas of my previous crackme (hell86) to another level of difficulty and hopefully adding something unique and interesting on top of it. The program prompts for an input and then tells you if it's correct or wrong. It does not take input from the command line. Like before, patching the binary is not a valid solution. Good luck! I hope you'll find this one just as interesting as my previous crackme. SHA256: 57aa4a74af67ad9a6f33ae92f26deff3877ae648b4688c87df20c9ce53d2a723 jittery

source : https://crackmes.one/crackme/5c44f1bb33c5d475210bc634
binary : https://crackmes.one/static/crackme/5c44f1bb33c5d475210bc634.zip (pass: crackmes.one)

Challenge ini diset sebagai level very hard (level 5) oleh pembuatnya, jadi saya udah expect kalo ngerjain ini bakal sulit dan lama. Disini saya akan nulis prosesnya gimana nyelesain ini dan harapannya untuk pembaca mungkin bisa terinspirasi atau bisa dapetin ide dari cara2 saya nyelesain ini atau jadi makin tertarik untuk ngerjain crackme yang lebih kompleks lagi, karena sejujurnya ini adalah challenge very hard pertama yang saya solve di crackmes.one (and i’m happy with that).

Oke langsung aja, ini adalah binary ELF 64 bit stripped bernama jittery dengan ukuran 32K (ukurannya kecil, jadi saya asumsiin kode didalamnya gak banyak dan akan lebih cepat dianalisa). Ketika dijalankan, (seperti biasa) program ini menerima inputan berupa password dan selanjutnya akan mengoutputkan password yang diinputkan benar atau tidak, tujuan kita adalah mendapatkan inputan password apa yang valid bagi program.

  % ./jittery
[jittery crackme] Good luck!


STRANGE PROCESSORS, INC. CENTRAL VAULT SYSTEM
=============================================

"We exist to make the world a weirder place." (tm)

To access the confidential information stored in this vault,
please provide the company's main password, which hasn't
been changed since 1984.

Password:PPPPPPPPPPPPPPPPPPPPPPPPPPP
WRONG!

# Finding main function

Load programnya di ghidra, decompile bagian entry point untuk mendapatkan fungsi mainnya.

void entry(undefined8 uParm1,undefined8 uParm2,undefined8 uParm3)

{
  undefined8 in_stack_00000000;
  undefined auStack8 [8];
  
  __libc_start_main(FUN_00100d10,in_stack_00000000,&stack0x00000008,FUN_001036b0,FUN_00103720,uParm3
                    ,auStack8);
  do {
                    /* WARNING: Do nothing block with infinite loop */
  } while( true );
}

Dari kode diatas kita mendapatkan fungsi main, fungsi main adalah argumen pertama yang digunakan pada pemanggil __libc_start_main. So, FUN_00100d10 itu adalah fungsi main, bisa langsung kita rename namanya ke main. Selanjutnya langsung kita decompile pada fungsi main.

undefined8 main(void)

{
  long lVar1;
  
  lVar1 = FUN_00103460(&DAT_00305020,0x400,&DAT_00307020,2);
  if (lVar1 != 0) {
    __printf_chk(1,
                 "Whoops, an internal error occurred. This is a bug, please let ttlhacker know.Error code: %lu\n"
                 ,lVar1);
  }
  return 0;
}

Di fungsi main hanya terdapat satu pemanggilan saja yaitu ke fungsi FUN_00103460.

# start_prog function

undefined8 FUN_00103460(undefined8 *puParm1,long lParm2,undefined8 uParm3,undefined8 uParm4)

{
  void *__addr;
  undefined8 uVar1;
  void *pvVar2;
  void *pvVar3;
  size_t __len;
  long in_FS_OFFSET;
  void *local_58;
  undefined8 local_50;
  undefined8 local_48;
  long local_40;
  
  __len = lParm2 * 0x40;
  local_40 = *(long *)(in_FS_OFFSET + 0x28);
  __addr = mmap((void *)0x0,__len,7,0x22,-1,0);
  if (__addr == (void *)0xffffffffffffffff) {
    uVar1 = 2;
  }
  else {
    if (lParm2 != 0) {
      pvVar2 = __addr;
      do {
        uVar1 = *puParm1;
        pvVar3 = (void *)((long)pvVar2 + 0x40);
        puParm1 = puParm1 + 1;
        *(undefined8 *)((long)pvVar2 + 0x38) = uVar1;
        FUN_001033b0(pvVar2);
        pvVar2 = pvVar3;
      } while (pvVar3 != (void *)(__len + (long)__addr));
    }
    local_58 = __addr;
    local_50 = uParm3;
    local_48 = uParm4;
    uVar1 = FUN_00103390(&local_58,(long)__addr + 0x40);
    munmap(__addr,__len);
  }
  if (local_40 == *(long *)(in_FS_OFFSET + 0x28)) {
    return uVar1;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

Dilihat dari polanya, pada awalnya fungsi ini akan menginisialisasi sebuah data terlebih dahulu, bisa ditebak dari pemanggilan fungsi mmap (http://man7.org/linux/man-pages/man2/mmap.2.html) dan dilanjutkan dengan pengisian memory yang dihasilkan mmap (dilihat dari kode blok while-loop). Selanjutnya setelah inisialisasi data, eksekusi akan dialihkan sepenuhnya ke fungsi FUN_00103390. Sementara pada fungsi FUN_001033b0 (yang dipanggil di dalam kode blok while-loop), fungsi ini menginisialisasi data juga, tetapi pada awalnya saya gak tau itu data apa dan apa tujuannya, hasil decompilenya seperti ini.

void FUN_001033b0(undefined8 *puParm1)

{
  long lVar1;
  long in_FS_OFFSET;
  
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  *(undefined8 *)((long)puParm1 + 0x2c) = 0xcccccccccccccccc;
  *puParm1 = 0x103210b848;
  *(undefined4 *)((long)puParm1 + 0x34) = 0xcccccccc;
  *(undefined4 *)(puParm1 + 1) = 0xd0ff0000;
  *(undefined4 *)((long)puParm1 + 0xc) = 0xcccccccc;
  *(undefined4 *)(puParm1 + 2) = 0xcccccccc;
  *(undefined4 *)((long)puParm1 + 0x14) = 0xcccccccc;
  *(undefined4 *)(puParm1 + 3) = 0xcccccccc;
  *(undefined4 *)((long)puParm1 + 0x1c) = 0xcccccccc;
  *(undefined4 *)(puParm1 + 4) = 0xcccccccc;
  *(undefined4 *)((long)puParm1 + 0x24) = 0xcccccccc;
  *(undefined4 *)(puParm1 + 5) = 0xcccccccc;
  if (lVar1 == *(long *)(in_FS_OFFSET + 0x28)) {
    return;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

Fungsi diatas cukup aneh, karena saya gak tau tujuan sebenernya apa. Oke, itu kita simpen dulu. Kita balik lagi ke fungsi FUN_00103460.

Agar tidak membingungkan saya mengganti nama fungsi FUN_00103460 menjadi start_prog karena asumsi saya fungsi ini merupakan fungsi utama yang mengendalikan fungsi program selama berjalan (sebenernya bebas sih mau dikasih nama apa, biar ga bingung aja kalo saya tulis sesuai ghidranya yaitu FUN_<alamat_fungsi> nanti bisa ketuker dengan fungsi lain yang saya sebut juga di tulisan ini). Dan juga saya mengganti nama fungsi FUN_001033b0 menjadi setup_data, walaupun saya belum tau sebenernya fungsi ini tujuannya apa, tapi sepertinya fungsi ini cuman melakukan inisialisasi data, menurut saya nama itu cocok.

Balik lagi ke fungsi start_prog, setelah while-loop terdapat pemanggilan fungsi ke FUN_00103390.

    local_58 = __addr;
    local_50 = uParm3;
    local_48 = uParm4;
    uVar1 = FUN_00103390(&local_58,(long)__addr + 0x40);
    munmap(__addr,__len);

Sepertinya fungsi FUN_00103390 adalah fungsi yang terakhir dipanggil di fungsi start_prog, itu artinya fungsi FUN_00103390 adalah fungsi inti dimana kode dieksekusi. Saya ganti fungsi tersebut dengan do_start.

Fungsi do_start mempunyai kode assembly seperti ini.

        00103390  53            PUSH       RBX
        00103391  e8 9a fe      CALL       FUN_00103230                                     undefined FUN_00103230(void)
                   ff ff
        00103396  48 89 fb      MOV        RBX,param_1
        00103399  ff d6         CALL       param_2
        0010339b  e8 b0 fe      CALL       FUN_00103250                                     undefined FUN_00103250(void)
                   ff ff
        001033a0  48 89 d8      MOV        RAX,RBX
        001033a3  5b            POP        RBX
        001033a4  c3            RET

Di fungsi ini terdapat 3 pemanggilan fungsi ke FUN_00103230, selanjutnya pemanggilan fungsi pada alamat yang disimpan pada param2. Sebelumnya kita sudah membaca bahwa param2 berisi sebuah address yg dihasilkan mmap yg dilakukan pada fungsi start_prog, kita flashback sedikit, kodenya seperti ini.

undefined8 start_prog(undefined8 *puParm1,long lParm2,undefined8 uParm3,undefined8 uParm4)

{
  void *__addr;
  ....
  __len = lParm2 * 0x40;
  local_40 = *(long *)(in_FS_OFFSET + 0x28);
  __addr = mmap((void *)0x0,__len,7,0x22,-1,0)
  if (__addr == (void *)0xffffffffffffffff) {
    uVar1 = 2;
  }
  else {
    ....
    uVar1 = do_start(&local_58,(long)__addr + 0x40);
  }
  ....
}

Yep, param_2 itu berisi __addr + 0x40, __addr adalah alamat memory yang dihasilkan dari fungsi mmap, bisa kita simpulkan karena __addr + 0x40 akan dieksekusi maka __addr + 0x40 harus berisi instruksi assembly yang valid. Kita akan melakukan checking terlebih dahulu untuk memastikan apakan __addr + 0x40 itu merupakan instruksi assembly yang valid sesuai asumsi kita atau tidak. Saya menggunakan GDB dan melakukan breakpoint pada saat sebelum fungsi do_start dipanggil.

Breakpoint 1, 0x000055555555750a in ?? ()
gef➤  x/i $pc
=> 0x55555555750a:      call   0x555555557390
gef➤  x/8i $rsi
   0x7ffff7fe7040:      movabs rax,0x555555557210
   0x7ffff7fe704a:      call   rax
   0x7ffff7fe704c:      int3
   0x7ffff7fe704d:      int3
   0x7ffff7fe704e:      int3
   0x7ffff7fe704f:      int3
   0x7ffff7fe7050:      int3
   0x7ffff7fe7051:      int3

Dan ya, param2 yang terdapat di register rsi berisi alamat dari instruksi2 assembly yang valid yang akan dipanggil pada fungsi do_start.

Sekarang kita tarik ulur ke belakang, siapa yang mengisi instruksi2 assembly tersebut. Kita perhatikan seksama kode pada blok while-loop di fungsi start_prog ini. Sebelumnya saya juga mengubah beberapa variable agar lebih mudah dibaca.

undefined8 start_prog(undefined8 *param_1,long param_2,void *param_3,long param_4)

{
  void *mmap_addr;
  undefined8 uVar1;
  void *pmmap_addr;
  void *next_pmmap_addr;
  size_t __len;
  long in_FS_OFFSET;
  void *local_58;
  undefined8 local_50;
  undefined8 local_48;
  long local_40;
  
  __len = lParm2 * 0x40;
  local_40 = *(long *)(in_FS_OFFSET + 0x28);
  mmap_addr = mmap((void *)0x0,__len,7,0x22,-1,0);
  if (mmap_addr == (void *)0xffffffffffffffff) {
    uVar1 = 2;
  }
  else {
    if (param_2 != 0) {
      pmmap_addr = mmap_addr;
      do {
        uVar2 = *param_1;
        next_pmmap_addr = (void *)((long)pmmap_addr + 0x40);
        param_1 = param_1 + 1;
        *(undefined8 *)((long)pmmap_addr + 0x38) = uVar2;
        setup_data((undefined8 *)pmmap_addr);
        pmmap_addr = next_pmmap_addr;
      } while (next_pmmap_addr != (void *)(__len + (long)mmap_addr));
    }
   ....
}

Code speaks alot. Blok while-loop diatas akan dieksekusi selama variable next_pmmap_addr bukan sama dengan mmap_addr + __len. Dan di setiap loop terdapat penambahan next_pmmap_addr dengan 0x40. Dilihat dari kodenya juga param_1 seharusnya merupakan sebuah array of 8 bytes atau array of long. Disetiap loopnya pmmap_addr (yang berasal dari next_pmmap_addr) pada index ke 0x38 akan diisi uVar1 (yang berasal dari elemen param_1 di setiap iterasinya. Selanjutnya dilakukan pemanggil pada fungsi setup_data dengan parameternya yaitu pmmap_addr. Fungsi setup_data mempunyai kode seperti berikut.

void setup_data(undefined8 *puParm1)

{
  long lVar1;
  long in_FS_OFFSET;
  
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  *(undefined8 *)((long)puParm1 + 0x2c) = 0xcccccccccccccccc;
  *puParm1 = 0x103210b848;
  *(undefined4 *)((long)puParm1 + 0x34) = 0xcccccccc;
  *(undefined4 *)(puParm1 + 1) = 0xd0ff0000;
  *(undefined4 *)((long)puParm1 + 0xc) = 0xcccccccc;
  *(undefined4 *)(puParm1 + 2) = 0xcccccccc;
  *(undefined4 *)((long)puParm1 + 0x14) = 0xcccccccc;
  *(undefined4 *)(puParm1 + 3) = 0xcccccccc;
  *(undefined4 *)((long)puParm1 + 0x1c) = 0xcccccccc;
  *(undefined4 *)(puParm1 + 4) = 0xcccccccc;
  *(undefined4 *)((long)puParm1 + 0x24) = 0xcccccccc;
  *(undefined4 *)(puParm1 + 5) = 0xcccccccc;
  if (lVar1 == *(long *)(in_FS_OFFSET + 0x28)) {
    return;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

Dilihat kodenya cukup ribet, tapi itu akan mengisi pmmap_addr dengan sebuah data. Dilihat dari bytenya banyak byte yang bernilai 0xcc, yang saya tau itu merupakan nilai opcode dari instruksi int 3, instruksi itu juga terdapat pada hasil dump instruksi pada saat sebelum fungsi do_start dipanggil yang sudah saya lakukan tadi.

Breakpoint 1, 0x000055555555750a in ?? ()
gef➤  x/i $pc
=> 0x55555555750a:      call   0x555555557390
gef➤  x/8i $rsi
   0x7ffff7fe7040:      movabs rax,0x555555557210
   0x7ffff7fe704a:      call   rax
   0x7ffff7fe704c:      int3
   0x7ffff7fe704d:      int3
   0x7ffff7fe704e:      int3
   0x7ffff7fe704f:      int3
   0x7ffff7fe7050:      int3
   0x7ffff7fe7051:      int3

Mungkin saja, instruksi2 tersebut diisi oleh fungsi setup_data, kita akan melakukan pengecekan lagi untuk memastikannya. Saya melakukan breakpoint saat sebelum fungsi setup_data dipanggil, dan mendump isi dari pmmap_addr sebelum dan sesudah fungsi setup_data dieksekusi.

gef➤  r
Starting program: ~/ctf/crackme/one/jittery/jittery

Breakpoint 5, 0x00005555555574e2 in ?? ()
gef➤  x/3i $pc
=> 0x5555555574e2:      call   0x5555555573b0
   0x5555555574e7:      cmp    rbx,rbp
   0x5555555574ea:      jne    0x5555555574d0
gef➤  x/8i $rdi
   0x7ffff7fe7000:      add    BYTE PTR [rax],al
   0x7ffff7fe7002:      add    BYTE PTR [rax],al
   0x7ffff7fe7004:      add    BYTE PTR [rax],al
   0x7ffff7fe7006:      add    BYTE PTR [rax],al
   0x7ffff7fe7008:      add    BYTE PTR [rax],al
   0x7ffff7fe700a:      add    BYTE PTR [rax],al
   0x7ffff7fe700c:      add    BYTE PTR [rax],al
   0x7ffff7fe700e:      add    BYTE PTR [rax],al
gef➤  ni
0x00005555555574e7 in ?? ()
gef➤  x/8i $rdi
   0x7ffff7fe7000:      movabs rax,0x555555557210
   0x7ffff7fe700a:      call   rax
   0x7ffff7fe700c:      int3
   0x7ffff7fe700d:      int3
   0x7ffff7fe700e:      int3
   0x7ffff7fe700f:      int3
   0x7ffff7fe7010:      int3
   0x7ffff7fe7011:      int3

Oke, asumsi kita benar, bahwa yang mengisi instruksi2 pada mmap_addr itu adalah fungsi setup_data.

Sekarang kita balik ke bagian fungsi do_start untuk dipahami.

        00103390  53            PUSH       RBX
        00103391  e8 9a fe      CALL       FUN_00103230                                     undefined FUN_00103230(void)
                   ff ff
        00103396  48 89 fb      MOV        RBX,param_1
        00103399  ff d6         CALL       param_2
        0010339b  e8 b0 fe      CALL       FUN_00103250                                     undefined FUN_00103250(void)
                   ff ff
        001033a0  48 89 d8      MOV        RAX,RBX
        001033a3  5b            POP        RBX
        001033a4  c3            RET

Terdapat pemanggilan ke fungsi FUN_00103230, dan dilanjutkan pemanggilan ke alamat yg dimiliki oleh param_2, seperti yang tadi dijelaskan param_2 akan berisi instruksi berasal dari mmap_addr yang instruksi2nya diisi fungsi setup_data. Sementara itu fungsi FUN_00103230 memiliki kode seperti berikut.

                           **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined __stdcall FUN_00103230(void)
             undefined         AL:1           <RETURN>
                             FUN_00103230                                    XREF[5]:     call_case_syscall:00102170(c), 
                                                                                          vmvm:00103214(c), 
                                                                                          do_start:00103391(c), 00103944, 
                                                                                          00103f68(*)  
        00103230  59            POP        RCX
        00103231  41 50         PUSH       R8
        00103233  41 51         PUSH       R9
        00103235  41 52         PUSH       R10
        00103237  41 53         PUSH       R11
        00103239  41 54         PUSH       R12
        0010323b  41 55         PUSH       R13
        0010323d  41 56         PUSH       R14
        0010323f  41 57         PUSH       R15
        00103241  ff e1         JMP        RCX

Fungsi tersebut mempush hampir semua nilai register setelah itu melakukan lompatan pada alamat yg dimiliki register RCX, RCX sendiri kalo kita liat berasal dari instruksi POP RCX, instruksi tersebut pastinya akan me-load return address ke register RCX, itu artinya instruksi POP RCX; JMP RCX disini akan equivalen dengan instruksi RET. Okay, tidak ada masalah disini. Saya ubah fungsi ini dengan nama save_regs karena fungsi ini akan menyimpan semua nilai yang ada di register ke stack.

Setelah instruksi JMP RCX eksekusi akan balik lagi ke fungsi do_start dan mengeksekusi instruksi setelah instruksi CALL save_regs.

        00103390  53            PUSH       RBX
        00103391  e8 9a fe      CALL       save_regs                                        undefined save_regs(void)
                   ff ff
        00103396  48 89 fb      MOV        RBX,param_1
        00103399  ff d6         CALL       param_2
        0010339b  e8 b0 fe      CALL       FUN_00103250                                     undefined FUN_00103250(void)
                   ff ff
        001033a0  48 89 d8      MOV        RAX,RBX
        001033a3  5b            POP        RBX
        001033a4  c3            RET

Sebelum kita menganalisa intruksi2 yang ada di param_2 saya akan melihat fungsi FUN_00103250, karena kodenya simple, kodenya seperti ini.

        00103250  59            POP        RCX
        00103251  41 5f         POP        R15
        00103253  41 5e         POP        R14
        00103255  41 5d         POP        R13
        00103257  41 5c         POP        R12
        00103259  41 5b         POP        R11
        0010325b  41 5a         POP        R10
        0010325d  41 59         POP        R9
        0010325f  41 58         POP        R8
        00103261  ff e1         JMP        RCX

Hampir mirip dengan fungsi save_regs, terdapat instruksi POP RCX; JMP RCX yang equivalen dengan instruksi RET, selebihnya terdapat intruksi POP hampir kesemua register. Saya menamai fungsi ini dengan load_regs karena fungsi ini akan meload nilai2 yang ada di stack ke hampir semua register.

Balik lagi ke fungsi do_start sekarang kita analisa pada bagian pemanggilan fungsi ke alamat yg dimiliki param_2

        00103390  53            PUSH       RBX
        00103391  e8 9a fe      CALL       save_regs                                        undefined save_regs(void)
                   ff ff
        00103396  48 89 fb      MOV        RBX,param_1
        00103399  ff d6         CALL       param_2
        0010339b  e8 b0 fe      CALL       load_regs                                     undefined load_regs(void)
                   ff ff
        001033a0  48 89 d8      MOV        RAX,RBX
        001033a3  5b            POP        RBX
        001033a4  c3            RET

Langsung kita break pada saat instruksi call param_2 di gdb, dan kita analisa instruksi yang ada di param_2.

gef➤  b* $_pie()+0x3399
Breakpoint 6 at 0x555555557399
gef➤  r
Starting program: ~/ctf/crackme/one/jittery/jittery

Breakpoint 6, 0x0000555555557399 in ?? ()
gef➤  x/3i $pc
=> 0x555555557399:      call   rsi
   0x55555555739b:      call   0x555555557250
   0x5555555573a0:      mov    rax,rbx
gef➤  x/8i $rsi
   0x7ffff7fe7040:      movabs rax,0x555555557210
   0x7ffff7fe704a:      call   rax
   0x7ffff7fe704c:      int3
   0x7ffff7fe704d:      int3
   0x7ffff7fe704e:      int3
   0x7ffff7fe704f:      int3
   0x7ffff7fe7050:      int3
   0x7ffff7fe7051:      int3

instruksi yang ada di param_2 atau register rsi berisi instruksi2 yang memanggil fungsi pada alamat 0x555555557210 atau 0x0103210 kalau di ghidra (karena programnya PIE, base address yang dimuat selalu beda jadi alamat yg ada di gdb dan di ghidra juga beda). Jika kita lihat di ghidra, alamat 0x0103210 atau fungsi FUN_00103210 berisi kode seperti ini.

                            **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined FUN_00103210()
             undefined         AL:1           <RETURN>
                             FUN_00103210                                    XREF[4]:     setup_data:001033e9(*), 
                                                                                          setup_data:001033f0(*), 0010393c, 
                                                                                          00103f54(*)  
        00103210  48 89 df      MOV        RDI,RBX
        00103213  5e            POP        RSI
        00103214  e8 17 00      CALL       save_regs                                        undefined save_regs()
                   00 00
        00103219  e8 a2 00      CALL       FUN_001032c0                                     undefined FUN_001032c0()
                   00 00
        0010321e  e8 2d 00      CALL       load_regs                                        undefined load_regs()
                   00 00
        00103223  ff e0         JMP        RAX

Di awal terdapat instruksi mov rdi, rbx, jika kita mundur ke belakang rbx telah diisi oleh param_1 pada fungsi do_start (scroll lagi keatas kalau kelupaan). Selanjutnya terdapat instruksi pop rsi, karena bagian kode ini dieksekusi karena instruksi call maka bisa dipastikan nantinya rsi akan berisi return address atau address dimana setelah instruksi call berasal. Selanjutnya terdapat pemanggilan fungsi save_regs yang berguna menyimpan semua nilai register ke stack (yang sudah kita bahas sebelumnya juga), dilanjutkan dengan pemanggilan ke fungsi FUN_001032c0, setelah itu dilakukan pemanggilan ke fungsi load_regs (yang sudah kita bahas juga sebelumnya). Setelah itu terdapat instruksi JMP RAX, yang ini belum kita ketahui RAX akan berisi apa. Sebelum kita melakukan analisa ke fungsi FUN_001032c0, kita akan melakukan breakpoint pada instruksi jmp rax diatas, karena saya cukup penasaran apa yang dimiliki RAX pada saat itu.

gef➤  b* $_pie()+0x3223
Breakpoint 7 at 0x555555557223
gef➤  c
Continuing.

Breakpoint 7, 0x0000555555557223 in ?? ()
gef➤  x/3i $pc
=> 0x555555557223:      jmp    rax
   0x555555557225:      ud2
   0x555555557227:      nop    WORD PTR [rax+rax*1+0x0]
gef➤  x/8i $rax
   0x7ffff7fe7040:      movabs r13,0x400
   0x7ffff7fe704a:      movabs rax,0x7ffff7fe7080
   0x7ffff7fe7054:      jmp    rax
   0x7ffff7fe7056:      int3
   0x7ffff7fe7057:      int3
   0x7ffff7fe7058:      int3
   0x7ffff7fe7059:      int3
   0x7ffff7fe705a:      int3

Hmm, wait.. sepertinya ada yang aneh, kalo kalian lihat, eksekusi program kembali lagi ke alamat 0x7ffff7fe7040, tetapi dengan instruksi yang berbeda, kalo kita lihat kebelakang saat instruksi call param_2, param_2 berisi address 0x7ffff7fe7040 juga tetapi instruksi2nya hanya berisi pemanggilan ke fungsi FUN_00103210 saja, tetapi pada saat saya break di jmp rax, rax berisi 0x7ffff7fe7040, tetapi dengan instruksi2 yang berbeda. Perubahan itu bisa kalian lihat seksama kalo flashback ke belakang (scroll keatas deh) atau lihat result dari perintah2 gdb yang saya berikan.

gef➤  b* $_pie()+0x3399
Breakpoint 6 at 0x555555557399
gef➤  r
Starting program: /home/n0psledbyte/ctf/crackme/one/jittery/jittery

Breakpoint 6, 0x0000555555557399 in ?? ()
gef➤  x/3i $pc # telah break saat call param_2
=> 0x555555557399:      call   rsi
   0x55555555739b:      call   0x555555557250
   0x5555555573a0:      mov    rax,rbx
gef➤  x/8i $rsi
   0x7ffff7fe7040:      movabs rax,0x555555557210
   0x7ffff7fe704a:      call   rax
   0x7ffff7fe704c:      int3
   0x7ffff7fe704d:      int3
   0x7ffff7fe704e:      int3
   0x7ffff7fe704f:      int3
   0x7ffff7fe7050:      int3
   0x7ffff7fe7051:      int3
gef➤  b* $_pie()+0x3223
Breakpoint 7 at 0x555555557223
gef➤  c
Continuing.

Breakpoint 7, 0x0000555555557223 in ?? ()
gef➤  x/3i $pc
=> 0x555555557223:      jmp    rax
   0x555555557225:      ud2
   0x555555557227:      nop    WORD PTR [rax+rax*1+0x0]
gef➤  x/8i $rax
   0x7ffff7fe7040:      movabs r13,0x400
   0x7ffff7fe704a:      movabs rax,0x7ffff7fe7080
   0x7ffff7fe7054:      jmp    rax
   0x7ffff7fe7056:      int3
   0x7ffff7fe7057:      int3
   0x7ffff7fe7058:      int3
   0x7ffff7fe7059:      int3
   0x7ffff7fe705a:      int3

Cukup aneh, tadinya 0x7ffff7fe7040 berisi instruksi yang memanggil ke fungsi lain pada program, dan dibagian lain pada program juga melakukan lompatan ke alamat 0x7ffff7fe7040, tetapi alamat 0x7ffff7fe7040 sudah berisi instruksi yang berbeda dari aslinya. Oke, satu keanehan ini akan kita simpan dulu.

Balik lagi ke fungsi FUN_00103210 ada instruksi yang belum kita analisa.

**************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined FUN_00103210()
             undefined         AL:1           <RETURN>
                             FUN_00103210                                    XREF[4]:     setup_data:001033e9(*), 
                                                                                          setup_data:001033f0(*), 0010393c, 
                                                                                          00103f54(*)  
        00103210  48 89 df      MOV        RDI,RBX
        00103213  5e            POP        RSI
        00103214  e8 17 00      CALL       save_regs                                        undefined save_regs()
                   00 00
        00103219  e8 a2 00      CALL       FUN_001032c0                                     undefined FUN_001032c0()
                   00 00
        0010321e  e8 2d 00      CALL       load_regs                                        undefined load_regs()
                   00 00
        00103223  ff e0         JMP        RAX

Terdapat pemanggilan ke fungsi FUN_001032c0, fungsi tersebut jika didecompile memiliki kode seperti ini.

undefined8 * FUN_001032c0(long *plParm1,long lParm2)

{
  int iVar1;
  uint uVar2;
  long lVar3;
  code *pcVar4;
  ulong uVar5;
  long in_FS_OFFSET;
  undefined8 *local_38;
  undefined2 local_2a;
  undefined6 uStack40;
  undefined2 uStack34;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  if (*(byte *)(lParm2 + 0x2c) < 0x29) {
    pcVar4 = (code *)(lParm2 + -0xc);
    uVar5 = (long)((long)pcVar4 - *plParm1) >> 6;
    local_38 = (undefined8 *)pcVar4;
    iVar1 = (*(code *)(&PTR_FUN_00304c40)[(ulong)*(byte *)(lParm2 + 0x2c)])
                      (plParm1,lParm2 + 0x2c,&local_38,uVar5 & 0xffffffff);
    if (iVar1 == 0) {
      uVar2 = FUN_00103270(plParm1,uVar5 & 0xffffffff);
      local_2a = 0xb848;
      lVar3 = (ulong)uVar2 * 0x40 + *plParm1;
      uStack40 = (undefined6)lVar3;
      uStack34 = (undefined2)((ulong)lVar3 >> 0x30);
      *local_38 = CONCAT62(uStack40,0xb848);
      *(undefined2 *)(local_38 + 1) = uStack34;
      *(undefined2 *)((long)local_38 + 10) = 0xe0ff;
      goto LAB_00103315;
    }
  }
  pcVar4 = FUN_00103200;
LAB_00103315:
  if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
    return (undefined8 *)pcVar4;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

Sebelum kita menganalisa lebih dalam, saya ingin memberitahukan suatu hal yang belum saya beritahu (lol). Dari awal saya menganalisa kodenya dari awal entry poin sampai bagian yg “cukup dalam” ini, mengapa saya ga mencoba langsung mencari fungsi pengecekan passwordnya saja tanpa harus memahami keseluruhan program? mungkin kalian bertanya. Well, step2 yang sudah saya lakukan diatas sebenarnya step yang benar yang saya lakukan untuk mensolve crackme ini.

Biasanya, mungkin kalian melakukan xrefs pada sebuah string yang kalian ketahui dari sebuah asumsi bahwa string tersebut digunakan dalam proses pengecekan password.

Misalnya, agar langsung mendapatkan fungsi pengecekan passwordnya saja kita langsung mensearch string “Password:” dan melakukan xrefs untuk mencari penggunaan string tersebut pada kode program, karena kita berasumsi bahwa di kode program terdapat statement seperti printf("Password:"). Jika kita mengetahui dimana bagian kode yang melakukan sesuatu seperti printf("Password:") biasanya setelah instruksi itu pasti dibawah kodenya terdapat fungsi yang melakukan pembacaan input (menggunakan fgets misalnya) setelah itu terdapat sebuah proses yg melakukan pengecekan input tersebut. Setelah mengetahui dimana bagian kode yang melakukan pengecekan password kita bisa langsung mereverse algoritmanya untuk mendapatkan password yang valid.

Atau kita juga dapat melakukan xrefs terhadap fungsi2 yang digunakan untuk menerima input. Misalnya kita ingin mencari tahu dimana penggunaan fungsi read (untuk menerima input) digunakan. Pada awalnya kita berasumsi juga setelah fungsi read dipanggil, kode setelahnya pasti akan melakukan pengecekan sebuah password.

Tetapi tidak dengan binary ini, binary ini cukup unik. Terlihat dari strings sepertinya tidak ada string yang menarik untuk dijadikan xrefs dan mendapatkan kode yang kita inginkan (dalam hal ini kode yang kita cari dalam melakukan fungsi pengecekan password).

  % strings jittery
/lib64/ld-linux-x86-64.so.2
H_a[
libc.so.6
fflush
__printf_chk
_IO_putc
__stack_chk_fail
stdin
mmap
stdout
aligned_alloc
munmap
__cxa_finalize
_IO_getc
__libc_start_main
free
GLIBC_2.16
GLIBC_2.3.4
GLIBC_2.4
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
=ab
5Zb
=Ab
u/UH
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
(HcN
dH34%(
(HcN
dH34%(
(HcN
dH34%(
(HcN
dH34%(
(HcN
dH34%(
(HcN
dH34%(
8HcN
D$(1
|$      A
L$%f
L$"f
t$(dH34%(
(HcN
dH3<%(
(Hc~
dH3<%(
dH3<%(
(Hc~
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
dH34%(
D$(1
L$%f
L$ H
|$(dH3<%(
dH3<%(
dH3<%(
dH3<%(
[]A\
(HcN
dH34%(
(HcN
dH34%(
YAPAQARASATAUAVAW
YA_A^A]A\A[AZAYAX
ATUSH
F,<(w.H
L+'H
 []A\
AWE1
D$(1
t$@H
L$(dH3
8[]A\A]A^A_
:HcG<
[]A\
AWAVI
AUATL
[]A\A]A^A_
Whoops, an internal error occurred. This is a bug, please let ttlhacker know. Error code: %lu
;*3$"
GCC: (Ubuntu 8.2.0-1ubuntu2~18.04) 8.2.0
.shstrtab
.interp
.note.ABI-tag
.note.gnu.build-id
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.plt.got
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.data.rel.ro
.dynamic
.data
.bss
.comment

Binary ini melakukan output dan input perkarakter menggunakan fungsi putc dan getc, bisa dilihat output dari ltrace ini.

  % ltrace ./jittery
mmap(0, 0x10000, 7, 34)                                                                                                 = 0x7fc701731000
aligned_alloc(8, 8192, 0x7ffe1e0673d0, 0x7ffe1e0673d0)                                                                  = 0x5637309e9260
_IO_putc('[', 0x7fc70150e760)                                                                                           = 91
fflush(0x7fc70150e760[)                                                                                                  = 0
_IO_putc('j', 0x7fc70150e760)                                                                                           = 106
fflush(0x7fc70150e760j)                                                                                                  = 0
_IO_putc('i', 0x7fc70150e760)                                                                                           = 105
fflush(0x7fc70150e760i)                                                                                                  = 0
_IO_putc('t', 0x7fc70150e760)                                                                                           = 116
fflush(0x7fc70150e760t)                                                                                                  = 0
_IO_putc('t', 0x7fc70150e760)                                                                                           = 116
fflush(0x7fc70150e760t)                                                                                                  = 0
_IO_putc('e', 0x7fc70150e760)                                                                                           = 101
fflush(0x7fc70150e760e)                                                                                                  = 0
_IO_putc('r', 0x7fc70150e760)                                                                                           = 114
fflush(0x7fc70150e760r)                                                                                                  = 0
_IO_putc('y', 0x7fc70150e760)                                                                                           = 121
....
aligned_alloc(8, 1024, 0x7ffe1e0673d0, 0x7ffe1e0673d0)                                                                  = 0x5637309eb680
_IO_getc(0x7fc70150da00abcdef
)                                                                                                = 'a'
_IO_getc(0x7fc70150da00)                                                                                                = 'b'
_IO_getc(0x7fc70150da00)                                                                                                = 'c'
_IO_getc(0x7fc70150da00)                                                                                                = 'd'
_IO_getc(0x7fc70150da00)                                                                                                = 'e'
_IO_getc(0x7fc70150da00)                                                                                                = 'f'
_IO_getc(0x7fc70150da00)                                                                                                = '\n'
_IO_putc('W', 0x7fc70150e760)                                                                                           = 87
fflush(0x7fc70150e760W)                                                                                                  = 0
_IO_putc('R', 0x7fc70150e760)                                                                                           = 82
fflush(0x7fc70150e760R)                                                                                                  = 0
_IO_putc('O', 0x7fc70150e760)                                                                                           = 79
fflush(0x7fc70150e760O)                                                                                                  = 0
_IO_putc('N', 0x7fc70150e760)                                                                                           = 78
fflush(0x7fc70150e760N)                                                                                                  = 0
_IO_putc('G', 0x7fc70150e760)                                                                                           = 71
fflush(0x7fc70150e760G)                                                                                                  = 0
_IO_putc('!', 0x7fc70150e760)                                                                                           = 33
fflush(0x7fc70150e760!)                                                                                                  = 0
_IO_putc('\n', 0x7fc70150e760
)                                                                                          = 10
fflush(0x7fc70150e760)                                                                                                  = 0
free(0x5637309eb680)                                                                                                    = <void>
free(0x5637309e9260)                                                                                                    = <void>
munmap(0x7fc701731000, 0x10000, 0x7fc70173200b, 0x56372fa513a0)                                                         = 0
+++ exited (status 0) +++

Disini saya berasumsi, “sepertinya” ada semacam proses enkripsi perkarakter yg dilakukan pada program, oleh karena itu ia mengoutputkannya dengan putc satu persatu.

Kali ini, asumsi saya salah, setelah saya analisa, fungsi putc hanya memiliki satu xrefs (getc juga cuman punya satu xrefs btw) yaitu saya temukan pada fungsi FUN_00103680 yang memiliki kode seperti berikut.

ulong FUN_00103680(undefined8 uParm1,ulong uParm2,undefined8 uParm3,long *plParm4)

{
  int iVar1;
  uint uVar2;
  ulong uVar3;
  
  switch(uParm1) {
  case 0:
    _IO_putc((uint)uParm2 & 0xff,stdout); // putc dipanggil
    fflush((FILE *)stdout);
    return 0;
  case 1:
    iVar1 = _IO_getc(stdin);              // getc juga dipanggil
    return (long)iVar1;
  case 2:
    uVar3 = aligned_alloc(8,uParm2 * 8);  // aligned alloc? hmm
    return uVar3 >> 3;
  case 3:
    free((void *)(uParm2 << 3));          // free juga ada
    return 0;
  case 4:
    return (long)*(int *)(uParm2 * 0x40 + *plParm4 + 0x3c);
  case 5:
    uVar2 = FUN_00103270(plParm4,uParm2 & 0xffffffff);
    return (ulong)uVar2;
  case 6:
    __printf_chk(1,"%ld",uParm2);       // printf?
    return 0;
  case 7:
    return *(undefined8 *)(uParm2 * 0x40 + *plParm4 + 0x38);
  case 8:
    setup_data(*plParm4 + uParm2 * 0x40);
    *(undefined8 *)(*plParm4 + 0x38 + uParm2 * 0x40) = uParm3;
    return 0;
  default:
    return 0;
  }
}

Saya xrefs lagi pada fungsi FUN_00103680 diatas dan saya menemukan fungsi yang memanggil fungsi tersebut yaitu pada fungsi `FUN_00102170 yang memiliki kode berikut.

void FUN_00102170(undefined8 param_1,ulong param_2,undefined8 param_3,long *param_4)

{
  undefined8 extraout_RDX;
  long *unaff_RBX;
  
  save_regs();
  FUN_00103680(param_1,param_2,extraout_RDX,unaff_RBX);
  load_regs();
  return;
}

Kalo saya xrefs lagi fungsi diatas, terdapat fungsi lain yang menggunakan fungsi ini, tetapi ia memperlakukan fungsi ini sebagai data (bukan memanggilnya bahkan bukan juga sebagai data function pointer), ini terdapat pada fungsi FUN_001021a0.

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined FUN_001021a0()
...
        00102272  48 8d 3d      LEA        RDI,[FUN_00102170]
                   f7 fe ff ff
        00102279  48 8d 48 03   LEA        RCX,[RAX + 0x3]
        0010227d  48 89 0a      MOV        qword ptr [RDX],RCX
        00102280  48 89 7c      MOV        qword ptr [RSP + local_25[2]],RDI=>FUN_00102170
                   24 15
...

Sekarang saya menggunakan cara lain. Untuk mengetahui, siapa yang memanggil fungsi FUN_00102170. Disini saya menggunakan debugger, break pada fungsi itu, dan melihat backtracenya, untuk mengetahui fungsi FUN_00102170 dipanggil dari mana.

─────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x555555556165                  sub    bl, al
   0x555555556167                  call   0x555555554c80 <__stack_chk_fail@plt>
   0x55555555616c                  nop    DWORD PTR [rax+0x0]
 → 0x555555556170                  call   0x555555557230
   ↳  0x555555557230                  pop    rcx
      0x555555557231                  push   r8
      0x555555557233                  push   r9
      0x555555557235                  push   r10
      0x555555557237                  push   r11
      0x555555557239                  push   r12
─────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
0x555555557230 (
   $rdi = 0x0000000000000002,
   $rsi = 0x0000000000000400,
   $rdx = 0x0000000000000400,
   $rcx = 0x0000555555557223 →  jmp rax
)
─────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "jittery", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x555555556170 → call 0x555555557230
[#1] 0x7ffff7fe709c → mov r14, rax
[#2] 0x55555555739b → call 0x555555557250
[#3] 0x55555575b020 → or al, BYTE PTR [rax]
[#4] 0x55555575b020 → or al, BYTE PTR [rax]
────────────────────────────────────────────────────────────────────────────────────────────────────

Breakpoint 10, 0x0000555555556170 in ?? ()
gef➤  x/8i 0x7ffff7fe709c
   0x7ffff7fe709c:      mov    r14,rax
   0x7ffff7fe709f:      movabs rax,0x7ffff7fe7100
   0x7ffff7fe70a9:      jmp    rax
   0x7ffff7fe70ab:      int3
   0x7ffff7fe70ac:      int3
   0x7ffff7fe70ad:      int3
   0x7ffff7fe70ae:      int3
   0x7ffff7fe70af:      int3
gef➤  x/10i 0x7ffff7fe7080
   0x7ffff7fe7080:      movabs rdi,0x2
   0x7ffff7fe708a:      mov    rsi,r13
   0x7ffff7fe708d:      mov    rdx,r13
   0x7ffff7fe7090:      movabs rax,0x555555556170
   0x7ffff7fe709a:      call   rax
   0x7ffff7fe709c:      mov    r14,rax
   0x7ffff7fe709f:      movabs rax,0x7ffff7fe7100
   0x7ffff7fe70a9:      jmp    rax
   0x7ffff7fe70ab:      int3
   0x7ffff7fe70ac:      int3

hmm, ternyata ada yang memanggil fungsi FUN_00102170 (atau 0x555555556170 pada gdb, sekali lagi, base address pada ghidra dan gdb berbeda btw) pada alamat 0x7ffff7fe709a. Dilihat alamat memorynya, memory tersebut adalah memory yang dialokasikan dari fungsi mmap yang dipanggil sebelumnya.

undefined8 start_prog(undefined8 *param_1,long param_2,undefined8 param_3,undefined8 param_4)

{
   __len = param_2 * 0x40;
  local_40 = *(long *)(in_FS_OFFSET + 0x28);
  mmap_addr = mmap((void *)0x0,__len,7,0x22,-1,0);
  ....
}

dan mmap menghasilkan ruang memory pada alamat 0x00007ffff7fe7000 sampai 0x00007ffff7ff7000 dengan permission rwx, ini bisa kita liat di memory mappingnya. Lalu kita set juga 0x00007ffff7fe7000 sebagai variable mmap_addr di gdb untuk memudahkan pemahaman dan penulisan.

gef➤  vmmap
Start              End                Offset             Perm Path
....
0x00007ffff7fe7000 0x00007ffff7ff7000 0x0000000000000000 rwx
....
gef➤  set $mmap_addr=0x00007ffff7fe7000

Bisa dipastikan, terdapat sebuah bagian program yang mengisi instruksi2 assembly untuk dieksekusi. Dan juga sepertinya kode program utama, mulai dari output, input dan pengecekan password terdapat pada memory yang telah dialokasikan mmap tadi, tetapi kita masih belum tau siapa yang mengisi instruksi2 assembly tersebut.

gef➤  x/10i 0x7ffff7fe7080
   0x7ffff7fe7080:      movabs rdi,0x2
   0x7ffff7fe708a:      mov    rsi,r13
   0x7ffff7fe708d:      mov    rdx,r13
   0x7ffff7fe7090:      movabs rax,0x555555556170
   0x7ffff7fe709a:      call   rax
   0x7ffff7fe709c:      mov    r14,rax
   0x7ffff7fe709f:      movabs rax,0x7ffff7fe7100
   0x7ffff7fe70a9:      jmp    rax
   0x7ffff7fe70ab:      int3
   0x7ffff7fe70ac:      int3
// Kalau dalam disassembly alamatnya saya mundur sedikit (menjadi 0x7ffff7fe7070 misalnya)
// terdapat byte2 yang bukan bagian dari instruksi assembly sehingga merusak disassemblynya
// dan karena ini saya juga meyakini, bahwa blok instruksi pada bagian cuman di mulai pada alamat 0x7ffff7fe7080 sampai 0x7ffff7fe70a9 saja
gef➤  x/20i 0x7ffff7fe7070
   0x7ffff7fe7070:      int3
   0x7ffff7fe7071:      int3
   0x7ffff7fe7072:      int3
   0x7ffff7fe7073:      int3
   0x7ffff7fe7074:      int3
   0x7ffff7fe7075:      int3
   0x7ffff7fe7076:      int3
   0x7ffff7fe7077:      int3
   0x7ffff7fe7078:      sbb    eax,0x5
   0x7ffff7fe707d:      add    al,0x0
   0x7ffff7fe707f:      add    BYTE PTR [rax-0x41],cl
   0x7ffff7fe7082:      add    al,BYTE PTR [rax]
   0x7ffff7fe7084:      add    BYTE PTR [rax],al
   0x7ffff7fe7086:      add    BYTE PTR [rax],al
   0x7ffff7fe7088:      add    BYTE PTR [rax],al
   0x7ffff7fe708a:      mov    rsi,r13
   0x7ffff7fe708d:      mov    rdx,r13
   0x7ffff7fe7090:      movabs rax,0x555555556170
   0x7ffff7fe709a:      call   rax
   0x7ffff7fe709c:      mov    r14,rax

Sebelumnya kita tahu, bahwa fungsi setup_data yang dipanggil pada fungsi start_prog melakukan pengisian data pada mmap_addr dengan opcode2 assembly yang nantinya akan dieksekusi.

undefined8 start_prog(undefined8 *param_1,long param_2,undefined8 param_3,undefined8 param_4)

{
      ....
      pmmap_addr = mmap_addr;
      do {
        uVar1 = *param_1;
        next_pmmap_addr = (void *)((long)pmmap_addr + 0x40);
        param_1 = param_1 + 1;
        *(undefined8 *)((long)pmmap_addr + 0x38) = uVar1;
        setup_data((undefined8 *)pmmap_addr); // [1]
        pmmap_addr = next_pmmap_addr;
      } while (next_pmmap_addr != (void *)(__len + (long)mmap_addr));
      ....
}

Di dalam while loop tersebut, setup_data selalu dipanggil dengan parameter pmmap_addr yang bertambah sebanyak 0x40 disetiap loopnya. Kalau kita break setelah while-loop selesai, kita dapat melihat, disetiap kelipatan 0x40, terdapat instruksi2 assembly yang dihasilkan oleh setup data).

gef➤  x/8i $mmap_addr+0x40*0
   0x7ffff7fe7000:      movabs rax,0x555555557210
   0x7ffff7fe700a:      call   rax
   0x7ffff7fe700c:      int3
   0x7ffff7fe700d:      int3
   0x7ffff7fe700e:      int3
   0x7ffff7fe700f:      int3
   0x7ffff7fe7010:      int3
   0x7ffff7fe7011:      int3
gef➤  x/8i $mmap_addr+0x40*1
   0x7ffff7fe7040:      movabs rax,0x555555557210
   0x7ffff7fe704a:      call   rax
   0x7ffff7fe704c:      int3
   0x7ffff7fe704d:      int3
   0x7ffff7fe704e:      int3
   0x7ffff7fe704f:      int3
   0x7ffff7fe7050:      int3
   0x7ffff7fe7051:      int3
gef➤  x/8i $mmap_addr+0x40*2
   0x7ffff7fe7080:      movabs rax,0x555555557210
   0x7ffff7fe708a:      call   rax
   0x7ffff7fe708c:      int3
   0x7ffff7fe708d:      int3
   0x7ffff7fe708e:      int3
   0x7ffff7fe708f:      int3
   0x7ffff7fe7090:      int3
   0x7ffff7fe7091:      int3
gef➤  x/8i $mmap_addr+0x40*3
   0x7ffff7fe70c0:      movabs rax,0x555555557210
   0x7ffff7fe70ca:      call   rax
   0x7ffff7fe70cc:      int3
   0x7ffff7fe70cd:      int3
   0x7ffff7fe70ce:      int3
   0x7ffff7fe70cf:      int3
   0x7ffff7fe70d0:      int3
   0x7ffff7fe70d1:      int3
gef➤  // and so on

Wait, kita perhatikan bagian ini. Perintah di atas dan di bawah ini lakukan saat setelah blok while-loop dilakukan.

gef➤  x/8i $mmap_addr+0x40*2
   0x7ffff7fe7080:      movabs rax,0x555555557210
   0x7ffff7fe708a:      call   rax
   0x7ffff7fe708c:      int3
   0x7ffff7fe708d:      int3
   0x7ffff7fe708e:      int3
   0x7ffff7fe708f:      int3
   0x7ffff7fe7090:      int3
   0x7ffff7fe7091:      int3

Lihat instruksi diatas pada alamat 0x7ffff7fe7080 (alamat ini telah saya bahas juga sebelumnya, kalau kalian ingat). Instruksi diatas awalnya hanya seperti itu, tetapi setelah dilakukan pemrosesan yang kita belum tau seperti apa itu, instruksi2 yang berada pada alamat 0x7ffff7fe7080 akan berubah menjadi seperti ini.

gef➤  x/10i 0x7ffff7fe7080
   0x7ffff7fe7080:      movabs rdi,0x2
   0x7ffff7fe708a:      mov    rsi,r13
   0x7ffff7fe708d:      mov    rdx,r13
   0x7ffff7fe7090:      movabs rax,0x555555556170
   0x7ffff7fe709a:      call   rax
   0x7ffff7fe709c:      mov    r14,rax
   0x7ffff7fe709f:      movabs rax,0x7ffff7fe7100
   0x7ffff7fe70a9:      jmp    rax
   0x7ffff7fe70ab:      int3

Kalau kalian ingat (untuk memastikan kalian tidak lupa, karena panjangnya tulisan ini), instruksi diatas diambil pada saat saya mencari kode dibagian mana fungsi FUN_00102170 (atau alamat 0x555555556170 di gdb) dipanggil, dan saya menemukan instruksi2 diatas.

Dibawah ini saya melakukan hardware-breakpoint pada alamat 0x7ffff7fe7080. Kenapa dengan hbreak? karena kalau dengan breakpoint biasa, instruksi int 3 yang di-write pada alamat 0x7ffff7fe7080 akan terhapus karena ada bagian program lain yang me-write ke alamat 0x7ffff7fe7080 juga, sehingga breakpoint biasa menjadi tidak bisa.

gef➤  hbreak *0x7ffff7fe7080
Hardware assisted breakpoint 12 at 0x7ffff7fe7080
gef➤  r
Starting program: /home/n0psledbyte/ctf/crackme/one/jittery/jittery

Breakpoint 12, 0x00007ffff7fe7080 in ?? ()
gef➤  x/3i $pc
=> 0x7ffff7fe7080:      movabs rax,0x555555557210
   0x7ffff7fe708a:      call   rax
   0x7ffff7fe708c:      int3
gef➤  c
Continuing.

Breakpoint 12, 0x00007ffff7fe7080 in ?? ()
gef➤  x/8i $pc
=> 0x7ffff7fe7080:      movabs rdi,0x2
   0x7ffff7fe708a:      mov    rsi,r13
   0x7ffff7fe708d:      mov    rdx,r13
   0x7ffff7fe7090:      movabs rax,0x555555556170
   0x7ffff7fe709a:      call   rax
   0x7ffff7fe709c:      mov    r14,rax
   0x7ffff7fe709f:      movabs rax,0x7ffff7fe7100
   0x7ffff7fe70a9:      jmp    rax

Kalau saya lakukan hbreak, ternyata instruksi pada alamat 0x7ffff7fe7080 akan dieksekusi sebanyak 2 kali, tetapi dengan instruksi yang berbeda (bisa dilihat diatas). Sepertinya ada sesuatu yang mengubah instruksi2 tersebut, dan ada sesuatu juga yang mengarahkan eksekusi ke alamat 0x7ffff7fe7080 lagi.

Untuk melihat siapa yang mengubah nilai2 yang berada pada alamat 0x7ffff7fe7080 (selain setup_data), kita dapat melakukan watchpoint, yaitu melakukan breakpoint pada saat sebuah alamat yang kita berikan nilainya. Misalnya kita ingin melakukan breakpoint ketika 0x7ffff7fe7080 nilainya berubah, dengan begitu kita dapat mengetahui bagian program mana yang melakukan perubahan nilai tersebut.

gef➤  x/i 0x7ffff7fe7080
   0x7ffff7fe7080:      movabs rax,0x555555557210
gef➤  watch *0x7ffff7fe7080
Hardware watchpoint 15: *0x7ffff7fe7080
gef➤  c
Continuing.

Hardware watchpoint 15: *0x7ffff7fe7080

Old value = 0x7210b848
New value = 0x2bf48
0x00005555555561e3 in ?? ()
gef➤  x/i 0x7ffff7fe7080
   0x7ffff7fe7080:      movabs rdi,0x2
gef➤  x/8i $pc
=> 0x5555555561e3:      movzx  ecx,WORD PTR [rsp+0x11]
   0x5555555561e8:      mov    BYTE PTR [rsp+0x23],0x89
   0x5555555561ed:      mov    WORD PTR [rax+0x8],cx
   0x5555555561f1:      mov    rdi,QWORD PTR [rdx]
   0x5555555561f4:      mov    WORD PTR [rsp+0x13],r8w
   0x5555555561fa:      lea    rax,[rdi+0xa]
   0x5555555561fe:      mov    QWORD PTR [rdx],rax
   0x555555556201:      movzx  eax,BYTE PTR [rsi+0x2]

Lihat diatas, saya menemukan bagian kode mana yang melakukan perubahan nilai pada alamat 0x7ffff7fe7080. Terdapat alamat instruksi sebelum 0x5555555561e3 yang melakukan write ke 0x7ffff7fe7080, kalau kita buka di ghidra, kode diatas adalah bagian dari fungsi FUN_001021a0, kodenya seperti ini.

undefined8 FUN_001021a0(undefined8 param_1,long param_2,long *param_3)

{
  long lVar1;
  undefined8 *puVar2;
  long lVar3;
  byte bVar4;
  uint uVar5;
  long in_FS_OFFSET;
  int6 iStack45;
  short sStack39;
  ushort local_19;
  ushort local_16;
  ushort local_13;
  
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  puVar2 = (undefined8 *)*param_3;
  iStack45 = (int6)*(int *)(param_2 + 4);
  sStack39 = (short)(*(int *)(param_2 + 4) >> 0x1f);
  *puVar2 = CONCAT62(iStack45,0xbf48);
  *(short *)(puVar2 + 1) = sStack39;
  lVar3 = *param_3;
  *param_3 = lVar3 + 10;
  uVar5 = (uint)*(byte *)(param_2 + 2) + 8;
  *(byte *)(lVar3 + 0xc) = (byte)((uVar5 & 7) << 3) | 0xc6;
  local_13 = CONCAT11(0x89,(char)((uVar5 >> 3 & 1) << 2)) | 0x48;
  *(ushort *)(lVar3 + 10) = local_13;
  lVar3 = *param_3;
  *param_3 = lVar3 + 3;
  uVar5 = (uint)*(byte *)(param_2 + 3) + 8;
  *(byte *)(lVar3 + 5) = (byte)((uVar5 & 7) << 3) | 0xc2;
  local_16 = CONCAT11(0x89,(char)((uVar5 >> 3 & 1) << 2)) | 0x48;
  *(ushort *)(lVar3 + 3) = local_16;
  lVar3 = *param_3;
  *param_3 = lVar3 + 3;
  *(undefined8 *)(lVar3 + 3) = 0x102170b848;
  *(undefined4 *)(lVar3 + 0xb) = 0xd0ff0000;
  lVar3 = *param_3;
  *param_3 = lVar3 + 0xc;
  bVar4 = *(char *)(param_2 + 1) + 8;
  *(byte *)(lVar3 + 0xe) = bVar4 & 7 | 0xc0;
  local_19 = CONCAT11(0x89,bVar4 >> 3) & 0xff01 | 0x48;
  *(ushort *)(lVar3 + 0xc) = local_19;
  *param_3 = *param_3 + 3;
  if (lVar1 == *(long *)(in_FS_OFFSET + 0x28)) {
    return 0;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

Keliatannya cukup ribet, tetapi yang kita pasti tahu bahwa kode ini mengisi atau menghasilkan (op)code assembly, dalam bagian ini dia mengisi ke alamat 0x7ffff7fe7080.

Balik lagi ke gdb, kita melakukan backtrace untuk mengetahui siapa yang memanggil fungsi FUN_001021a0 ini.

gef➤  x/8i $pc
=> 0x5555555561e3:      movzx  ecx,WORD PTR [rsp+0x11]
   0x5555555561e8:      mov    BYTE PTR [rsp+0x23],0x89
   0x5555555561ed:      mov    WORD PTR [rax+0x8],cx
   0x5555555561f1:      mov    rdi,QWORD PTR [rdx]
   0x5555555561f4:      mov    WORD PTR [rsp+0x13],r8w
   0x5555555561fa:      lea    rax,[rdi+0xa]
   0x5555555561fe:      mov    QWORD PTR [rdx],rax
   0x555555556201:      movzx  eax,BYTE PTR [rsi+0x2]
gef➤  bt 2
#0  0x00005555555561e3 in ?? ()
#1  0x000055555555730a in ?? ()
(More stack frames follow...)
gef➤  x/8i 0x000055555555730a
   0x55555555730a:      test   eax,eax
   0x55555555730c:      je     0x555555557338
   0x55555555730e:      lea    rbx,[rip+0xfffffffffffffeeb]        # 0x555555557200
   0x555555557315:      mov    rcx,QWORD PTR [rsp+0x18]
   0x55555555731a:      xor    rcx,QWORD PTR fs:0x28
   0x555555557323:      mov    rax,rbx
   0x555555557326:      jne    0x555555557380
   0x555555557328:      add    rsp,0x20

Dilihat backtrace, nanti program akan kembali ke alamat 0x000055555555730a, dan pasti sebelum alamat tersebut ada instruksi yang memanggil fungsi FUN_001021a0. Jika ditelusuri alamat 0x000055555555730a terlihat di ghidra, bahwa ini masuk ke fungsi FUN_001032c0, kodenya seperti ini.

undefined8 * FUN_001032c0(long *param_1,long param_2)

{
  int iVar1;
  long lVar2;
  code *pcVar3;
  ulong uVar4;
  long in_FS_OFFSET;
  undefined8 *local_38;
  undefined2 local_2a;
  undefined6 uStack40;
  undefined2 uStack34;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  if (*(byte *)(param_2 + 0x2c) < 0x29) {
    pcVar3 = (code *)(param_2 + -0xc);
    uVar4 = (long)((long)pcVar3 - *param_1) >> 6;
    local_38 = (undefined8 *)pcVar3;
    iVar1 = (*(code *)(&PTR_FUN_00304c40)[(ulong)*(byte *)(param_2 + 0x2c)])
                      (param_1,param_2 + 0x2c,&local_38,uVar4 & 0xffffffff);
    if (iVar1 == 0) {
      uVar4 = FUN_00103270((long)param_1,(uint)uVar4);
      local_2a = 0xb848;
      lVar2 = (uVar4 & 0xffffffff) * 0x40 + *param_1;
      uStack40 = (undefined6)lVar2;
      uStack34 = (undefined2)((ulong)lVar2 >> 0x30);
      *local_38 = CONCAT62(uStack40,0xb848);
      *(undefined2 *)(local_38 + 1) = uStack34;
      *(undefined2 *)((long)local_38 + 10) = 0xe0ff;
      goto LAB_00103315;
    }
  }
  pcVar3 = FUN_00103200;
LAB_00103315:
  if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
    return (undefined8 *)pcVar3;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

Kalau kalian ingat (atau coba scroll keatas) saya pernah menyebut fungsi ini (FUN_001032c0). Sepertinya sedikit demi sedikit, kita bisa menemukan hubungannya. Dilihat dari kodenya, fungsi FUN_001021a0 akan dipanggil melalui table yang berada pada PTR_FUN_00304c40.

undefined8 * FUN_001032c0(long *param_1,long param_2)

{
  ....
  if (*(byte *)(param_2 + 0x2c) < 0x29) {
    pcVar3 = (code *)(param_2 + -0xc);
    uVar4 = (long)((long)pcVar3 - *param_1) >> 6;
    local_38 = (undefined8 *)pcVar3;
    iVar1 = (*(code *)(&PTR_FUN_00304c40)[(ulong)*(byte *)(param_2 + 0x2c)])
                      (param_1,param_2 + 0x2c,&local_38,uVar4 & 0xffffffff);

Terlihat sebuah data digunakan sebagai index untuk mendapatkan fungsi dari table. *(byte*)(param_2 + 0x2c) digunakan sebagai index, dilihat dari fungsi if bahwa *(byte*)(param_2 + 0x2c) harus kurang dari 0x29, sehingga bisa dipastikan bahwa panjang table yaitu 0x29 juga.

                             PTR_ARRAY_00304c40                              XREF[1]:     FUN_001032c0:001032e4(*)  
        00304c40  90 21 10      addr[41]
                   00 00 00 
                   00 00 a0 
           00304c40 90 21 10 00 00  addr      FUN_00102190            [0]                               XREF[1]:     FUN_001032c0:001032e4(*)  
                    00 00 00
           00304c48 a0 21 10 00 00  addr      FUN_001021a0            [1]
                    00 00 00
           00304c50 00 23 10 00 00  addr      FUN_00102300            [2]
                    00 00 00
           00304c58 50 0e 10 00 00  addr      FUN_00100e50            [3]
                    00 00 00
           00304c60 40 0f 10 00 00  addr      FUN_00100f40            [4]
                    00 00 00
           00304c68 30 10 10 00 00  addr      FUN_00101030            [5]
                    00 00 00
           00304c70 10 11 10 00 00  addr      FUN_00101110            [6]
                    00 00 00
           00304c78 e0 11 10 00 00  addr      FUN_001011e0            [7]
                    00 00 00
           00304c80 d0 12 10 00 00  addr      FUN_001012d0            [8]
                    00 00 00
           00304c88 c0 13 10 00 00  addr      FUN_001013c0            [9]
                    00 00 00
           00304c90 b0 14 10 00 00  addr      FUN_001014b0            [10]
                    00 00 00
           00304c98 c0 15 10 00 00  addr      FUN_001015c0            [11]
                    00 00 00
           00304ca0 d0 16 10 00 00  addr      FUN_001016d0            [12]
                    00 00 00
           00304ca8 e0 17 10 00 00  addr      FUN_001017e0            [13]
                    00 00 00
           00304cb0 f0 18 10 00 00  addr      FUN_001018f0            [14]
                    00 00 00
           00304cb8 00 1a 10 00 00  addr      FUN_00101a00            [15]
                    00 00 00
           00304cc0 10 1b 10 00 00  addr      FUN_00101b10            [16]
                    00 00 00
           00304cc8 20 1c 10 00 00  addr      FUN_00101c20            [17]
                    00 00 00
           00304cd0 30 1d 10 00 00  addr      FUN_00101d30            [18]
                    00 00 00
           00304cd8 40 1e 10 00 00  addr      FUN_00101e40            [19]
                    00 00 00
           00304ce0 50 1f 10 00 00  addr      FUN_00101f50            [20]
                    00 00 00
           00304ce8 60 20 10 00 00  addr      FUN_00102060            [21]
                    00 00 00
           00304cf0 20 30 10 00 00  addr      FUN_00103020            [22]
                    00 00 00
           00304cf8 10 31 10 00 00  addr      FUN_00103110            [23]
                    00 00 00
           00304d00 60 2c 10 00 00  addr      FUN_00102c60            [24]
                    00 00 00
           00304d08 e0 2c 10 00 00  addr      FUN_00102ce0            [25]
                    00 00 00
           00304d10 00 2e 10 00 00  addr      FUN_00102e00            [26]
                    00 00 00
           00304d18 c0 2e 10 00 00  addr      FUN_00102ec0            [27]
                    00 00 00
           00304d20 80 2f 10 00 00  addr      FUN_00102f80            [28]
                    00 00 00
           00304d28 80 23 10 00 00  addr      FUN_00102380            [29]
                    00 00 00
           00304d30 00 24 10 00 00  addr      FUN_00102400            [30]
                    00 00 00
           00304d38 70 24 10 00 00  addr      FUN_00102470            [31]
                    00 00 00
           00304d40 f0 24 10 00 00  addr      FUN_001024f0            [32]
                    00 00 00
           00304d48 f0 25 10 00 00  addr      FUN_001025f0            [33]
                    00 00 00
           00304d50 f0 26 10 00 00  addr      FUN_001026f0            [34]
                    00 00 00
           00304d58 f0 27 10 00 00  addr      FUN_001027f0            [35]
                    00 00 00
           00304d60 f0 28 10 00 00  addr      FUN_001028f0            [36]
                    00 00 00
           00304d68 f0 29 10 00 00  addr      FUN_001029f0            [37]
                    00 00 00
           00304d70 c0 2a 10 00 00  addr      FUN_00102ac0            [38]
                    00 00 00
           00304d78 90 2b 10 00 00  addr      FUN_00102b90            [39]
                    00 00 00
           00304d80 70 23 10 00 00  addr      FUN_00102370            [40]
                    00 00 00

Diatas adalah isi dari table, berisi fungsi2 yang akan dipanggil sesuai index yang berasal dari *(byte*)(param_2 + 0x2c) pada fungsi FUN_001032c0. Untuk mengetahui param_2 itu apa, sebenarnya ini sudah saya jelaskan sebelumnya, jadi saya tidak memperjelas lagi (silakan baca ulang keatas bila lupa). Fungsi FUN_001032c0 di panggil dari fungsi FUN_00103210 yang memiliki kode berikut.

00103210  48 89 df      MOV        RDI,RBX
00103213  5e            POP        RSI
00103214  e8 17 00      CALL       save_regs                                        undefined save_regs()
           00 00
00103219  e8 a2 00      CALL       FUN_001032c0                                     undefined FUN_001032c0()
           00 00
0010321e  e8 2d 00      CALL       load_regs                                        undefined load_regs()
           00 00
00103223  ff e0         JMP        RAX

Dilihat, param_1 atau register RDI, didapat dari register RBX. Sementara param_2 atau register RSI didapat dari nilai yang ada di stack. Kita backtrace lagi fungsi ini, (sebelumnya sudah saya jelaskan juga diatas) fungsi ini dipanggil dari kode2 assembly yang berada di memory yang dialokasikan mmap sebelumnya.

gef➤  x/8i $mmap_addr+0x40
   0x7ffff7fe7040:      movabs rax,0x555555557210
   0x7ffff7fe704a:      call   rax
   0x7ffff7fe704c:      int3
   0x7ffff7fe704d:      int3
   0x7ffff7fe704e:      int3
   0x7ffff7fe704f:      int3
   0x7ffff7fe7050:      int3
   0x7ffff7fe7051:      int3

Dilihat, instruksi2 diatas memanggil alamat 0x555555557210 atau jika di ghidra ini merupakan alamat fungsi FUN_00103210. Jika pop rsi di eksekusi pada fungsi FUN_00103210 maka bisa dipastikan, rsi akan berisi return address atau alamat 0x7ffff7fe704c. Untuk rbx kita harus melakukan backtrace, instruksi diatas kita tahu (sudah saya bahas sebelumnya) itu dipanggil dari fungsi do_start.

        00103390  53            PUSH       RBX
        00103391  e8 9a fe      CALL       save_regs                                        undefined save_regs(void)
                   ff ff
        00103396  48 89 fb      MOV        RBX,param_1
        00103399  ff d6         CALL       param_2
        0010339b  e8 b0 fe      CALL       load_regs                                        undefined load_regs(void)
                   ff ff
        001033a0  48 89 d8      MOV        RAX,RBX
        001033a3  5b            POP        RBX
        001033a4  c3            RET

rbx berasal dari param_1, param_1 adalah parameter ketika fungsi do_start dipanggil, kita bisa melihat fungsi do_start dipanggil pada fungsi start_prog.

undefined8 start_prog(undefined8 *param_1,long param_2,undefined8 param_3,undefined8 param_4)

{
    ....
    local_58 = mmap_addr;
    local_50 = param_3;
    local_48 = param_4;
    uVar1 = do_start(&local_58,(undefined *)((long)mmap_addr + 0x40));
    ....
}

param_1 adalah &local_58 yang berisi mmap_addr juga, dilihat diatas sebelum fungsi do_start terdapat param_3 yang diisi ke local_50, dan param_4 yg diisi ke local_48. Kalau dilihat dari keseluruhan fungsi start_prog, variable param_3 dan param_4 hanya dipakai pada bagian kode itu saja, dan ini tidak mungkin, seharusnya tidak seperti itu, karena untuk apa ada param_3 dan param_4 kalau tidak digunakan. Lalu saya berasumsi sebenarnya disana ada sebuah struct yang elemennya berisi local_58, local_50, dan local_48. dan struktur itu akan dikirim sebagai param_1 pada fungsi do_start. Disini saya membuat sebuah struktur baru dengan nama unk_struct yg membernya berisi.

struct unk_struct {
    void* base;
    void* ptr;
    long c;
};

Type yang saya beri sesuai dengan parameter yang diberikan ke fungsi do_start melalui fungsi main, yakni param_3 sebagai pointer, param_4 sebagai long integer, sementara kita tahu mmap_addr yang nantinya diisi ke unk_struct.base itu merupakan pointer juga, oleh karena itu saya berikan type void*. Jika sudah diset strukturnya di ghidra, hasil decompilenya akan seperti ini.

    local_58.base = mmap_addr;
    local_58.ptr = param_3;
    local_58.c = param_4;
    uVar2 = do_start(&local_58,(undefined *)((long)mmap_addr + 0x40));

Balik lagi ke fungsi FUN_001032c0 kita sudah tahu param_1 yang dikirim adalah unk_struct yang dibuat di fungsi start_prog, dan param_2 adalah return address, dalam contohnya kode yang dieksekusi terlebih dahulu berada di mmap_addr + 0x40, lalu memanggil FUN_00103210 melalui instruksi berikut.

   0x7ffff7fe7040:      movabs rax,0x555555557210
   0x7ffff7fe704a:      call   rax
   0x7ffff7fe704c:      int3

pada fungsi FUN_00103210 fungsi pop rsi yang meload return address ke rsi dalam hal ini adalah ini 0x7ffff7fe704c, lalu rsi dipakai sebagai param_2 ke fungsi FUN_001032c0, dari sini kita bisa pastikan nantinya param_2 akan berisi 0x7ffff7fe704c.

undefined8 * FUN_001032c0(long *param_1,long param_2)

{
  ....
  if (*(byte *)(param_2 + 0x2c) < 0x29) {
    pcVar3 = (code *)(param_2 + -0xc);
    uVar4 = (long)((long)pcVar3 - *param_1) >> 6;
    local_38 = (undefined8 *)pcVar3;
    iVar1 = (*(code *)(&PTR_FUN_00304c40)[(ulong)*(byte *)(param_2 + 0x2c)])
                      (param_1,param_2 + 0x2c,&local_38,uVar4 & 0xffffffff);

Balik lagi ke kode diatas, dimana kita sudah mengetahui param_1 berisi unk_struct yang diisi di start_prog, param_2 akan berisi 0x7ffff7fe704c atau mmap_addr+0x40*0x1+0xc (saya tulis seperti ini untuk memudahkan pengaksesan, karena kita tahu bahwa mmap_addr berisi blok2 data yang masing2 panjangnya 0x40 byte, dan 0x1 disana nanti menunjukkan indexnya, dan dimulai dari 0x1 karena pada pemanggilan fungsi do_start param_2 berisi mmap_addr+0x40, yang nantinya akan dipanggil sebagai kode (call param_2)).

Jadi, param_2 merupakan mmap_addr+0x40*0x1+0xc, jika kita subtitusi ke statement ini *(byte *)(param_2 + 0x2c) akan berubah menjadi *(byte*)(mmap_addr+0x40*0x1+0x38). Kalu di dump di gdb, hasilnya seperti ini.

gef➤  x/gx $mmap_addr+0x40*1+0x38
0x7ffff7fe7078: 0x000004000000051d

Jika kita ubah indexnya, tiap index mempunyai nilai yang berbeda (untuk offset 0x38).

gef➤  x/gx $mmap_addr+0x40*2+0x38
0x7ffff7fe70b8: 0x0000000205050601
gef➤  x/gx $mmap_addr+0x40*3+0x38
0x7ffff7fe70f8: 0x0000006500000028
gef➤  x/gx $mmap_addr+0x40*4+0x38
0x7ffff7fe7138: 0x000000810006001a
gef➤  x/gx $mmap_addr+0x40*5+0x38
0x7ffff7fe7178: 0x0000002100000028

Offset 0x38 pada setiap mmap_addr diisi melalui blok while-loop di fungsi start_prog.

undefined8 start_prog(undefined8 *param_1,long param_2,void *param_3,long param_4)

{
    ....
    if (param_2 != 0) {
      pmmap_addr = mmap_addr;
      do {
        uVar2 = *param_1;
        next_pmmap_addr = (void *)((long)pmmap_addr + 0x40);
        param_1 = param_1 + 1;
        *(undefined8 *)((long)pmmap_addr + 0x38) = uVar2; // [1]
        setup_data((undefined8 *)pmmap_addr);
        pmmap_addr = next_pmmap_addr;
      } while (next_pmmap_addr != (void *)(__len + (long)mmap_addr));
    }
    ....
}

Dilihat diatas, setiap mmap_addr pada offset 0x38 diisi oleh uVar2, dan uVar2 berasal dari setiap elemen param_1. param_1 pada fungsi start_prog sudah saya bahas juga sebelumnya, dan saya asumsikan itu adalah array of long. param_1 berasal dari &DAT_00305020 (berdasarkan pemanggilan fungsi start_prog di fungsi main). Jika kita dump di gdb, isi dari &DAT_00305020 ini sama dengan nilai2 tiap blok data di mmap_addr di offset 0x38.

gef➤  x/6gx $_pie()+0x0205020
0x555555759020: 0x0000000000000000      0x000004000000051d
0x555555759030: 0x0000000205050601      0x0000006500000028
0x555555759040: 0x000000810006001a      0x0000002100000028
gef➤  x/gx $mmap_addr+0x40*0+0x38
0x7ffff7fe7038: 0x0000000000000000
gef➤  x/gx $mmap_addr+0x40*1+0x38
0x7ffff7fe7078: 0x000004000000051d
gef➤  x/gx $mmap_addr+0x40*2+0x38
0x7ffff7fe70b8: 0x0000000205050601
gef➤  x/gx $mmap_addr+0x40*3+0x38
0x7ffff7fe70f8: 0x0000006500000028
gef➤  x/gx $mmap_addr+0x40*4+0x38
0x7ffff7fe7138: 0x000000810006001a
gef➤  x/gx $mmap_addr+0x40*5+0x38
0x7ffff7fe7178: 0x0000002100000028

Oke, kita sudah menganalisis semakin dalam, dan sepertinya sudah bisa sedikit menyimpulkan.

  1. mmap_addr dialokasikan, dan diisi tiap blok datanya dengan panjang masing-masin per 0x40 byte
  2. Tiap blok data mmap_addr, diisi oleh setup_data yang nantinya menghasilkan instruksi movabs rax,0x555555557210; call rax; int 3; int 3; int 3; ....
  3. Tiap blok data mmap_addr, pada offset 0x38 diisi sebuah 8 byte data berasal dari tiap elemen dari array &DAT_00305020
  4. Blok data mmap_addr pada index 1 dieksekusi, singkatnya instruksinya akan memanggil FUN_001032c0
  5. Pada FUN_001032c0 akan memanggil sebuah function table dengan index byte pertama dari offset 0x38 blok data itu sendiri.
  6. Setelah function table itu dipanggil, blok data itu terganti nilainya dengan instruksi2 assembly baru
  7. Lalu blok data itu akan dieksekusi lagi dengan instruksi2 assembly yang baru tersebut.
  8. Setelah itu proses berlanjut lagi ke step 4, tetapi dengan index blok data yang berbeda, karena sebenernya di akhir instruksi2 assembly baru itu terdapat jump ke blok data mmap_addr yang lain. Contohnya pada instruksi baru index blok data ke-satu.
gef➤  x/5i $mmap_addr+0x40*1
   0x7ffff7fe7040:      movabs r13,0x400
   0x7ffff7fe704a:      movabs rax,0x7ffff7fe7080
   0x7ffff7fe7054:      jmp    rax
   0x7ffff7fe7056:      int3
   0x7ffff7fe7057:      int3
gef➤  x/3i $mmap_addr+0x40*2
   0x7ffff7fe7080:      movabs rax,0x555555557210
   0x7ffff7fe708a:      call   rax
   0x7ffff7fe708c:      int3

Lihat diatas, di akhir instruksi pada mmap_addr pada index ke-1, ia akan loncat ke 0x7ffff7fe7080 atau mmap_addr+0x40*2 atau mmap_addr pada index ke-2, untuk melanjutkan eksekusinya lagi.

Step2 diatas akan terus dilakukan selama program berjalan. Kalau kalian memperhatikan step2 diatas, secara garis besar sistem dari program ini adalah mentranslate sebuah data atau bytecode menjadi instruksi2 assembly yang akan dieksekusi. bytecode berasal dari tiap2 blok data mmap_addr di offset 0x38, yang mana data tersebut juga berasal dari &DAT_00305020, lalu tiap blok data diisi dengan instruksi assembly sesuai bytecodenya masing2, dan setelah itu blok data juga akan dieksekusi.

Disini kita bisa membuat struktur baru untuk blok data mmap_addr, disini saya namakan struct blok_mmap dengan size 0x40 byte.

struct blok_mmap {
    byte[56] asm_inst;
    byte[8] bytecode;
};

blok_mmap berisi asm_inst dan bytecode, nantinya asm_inst akan disi oleh instruksi2 assembly sesuai bytecodenya. Dan juga nantinya, mmap_addr akan kita set datanya menjadi array of blok_mmap.

Sistem in dinamakan JIT (Just-in-time) engine, didalamnya terdapat jit compiler yang mengubah bytecode menjadi instruksi2 assembly yang akan dieksekusi. Bedanya dengan VM, VM langsung melakukan perintah berdasarkan bytecodenya dan tidak mentranslasi bytecode menjadi instruksi assembly.

Disini saya menamai komponen penting untuk JIT engine pada binary ini, ada beberapa hal seperti berikut:

  1. Pertama jit_entry, jit_entry bisa kita lihat pada fungsi start_prog, fungsi ini melakukan penginisialisasian blok_mmap (yang dibutuhkan dalam eksekusi di jit), dan fungsi ini juga yang mengalihkan dari context program asli ke context jit enginenya
  2. Kedua, yaitu jit_dispatcher, fungsi ini bisa kita lihat di fungsi FUN_001032c0 (fungsi ini juga saya ubah namanya menjadi jit_dispatcher) fungsi ini melakukan fetch-translate-execute, yaitu mengambil bytecode, lalu mengubah bytecode menjadi instruksi assembly, lalu mengalihkan eksekusi ke instruksi assembly (yang telah ditranslasi) tersebut.
  3. Ketiga, yaitu jit_handler_table, ini merupakan table yang berisi fungsi-fungsi untuk menerjemahkan bytecode ke instruksi assembly. Tabel ini bisa kita lihat di PTR_FUN_00304c40 (nantinya akan saya ubah juga namanya menjadi jit_handler_table). Byte pertama dari bytecode digunakan sebagai index untuk mengakses table ini. Masing2 fungsi pada table mempunyai fungsi tersendiri, masing2 fungsi akan mentranslate satu tipe instruksi bytecode.

Sampai saat ini kita sudah memahami gambaran besar dari fungsi2 dari program ini, yang mana seperti yang sudah jelaskan, sistem dari program ini seperti jit engine, yang mengubah suatu bytecode menjadi instruksi assembly yang akan dieksekusi. Saya akan melanjutkan tulisan ini di part berikutnya. Di part selanjutnya saya akan menjelaskan memahami bytecode2 nya dan juga saya menulis custom disassembler untuk bytecode tersebut dan ada lagi hal2 yang tidak terduga lainnya dalam memahami fungsi2 bytecodenya. Saya ucapkan terima kasih untuk pembuat crackme yang sangat unik ini dan juga bagi para pembaca yang telah membaca tulisan ini.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s