Reversing Arkavidia CTF

UwU

Solves : 28 (sebelum scoreboard freeze)

Di sini diberikan binary ELF 64 bit. Binary ini menerima tiga inputan string. Bisa diliat dari baris kode di fungsi main ini.

__isoc99_scanf("%s %s %s",flag1,flag2,flag3);

Setelah itu tiap inputan diolah tiap karakternya dengan suatu algoritma.

  olah1 = xor0x40(flag1);
  olah2 = (char *)add0x20(flag2);
  olah3 = min0x30(flag3);

Fungsi xor 0x40 akan men-xor tiap byte string yang ada di parameternya dengan 0x40. Fungsi 0x20 akan menambahkan tiap byte string yang ada di parameternya dengan 0x20. Fungsi min0x30 akan mengurangi tiap byte string yang ada di parameternya dengan 0x30.

Setelah itu variable 0lah1, olah2, dan olah3 dibandingkan dengan sesuatu.

  k = 0;
  while (k< 7) {
    if (olah1[k] != local_1bf[k]) {
      puts("wrong one syre");
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
    k = k + 1;
  }
  i = 0;
  while (i < 0xb) {
    if (olah2[i] != local_16e[i]) {
      puts("wrong one syre");
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
    i = i + 1;
  }
  while (j < 0xb) {
    if (olah3[j] != local_163[j]) {
      puts("wrong one syre");
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
    j = j + 1;
  }

Sekarang kita ambil variable local_1bf, local_16e, dan local_163 dan dilakukan reverse terhadap algoritma yang digunakan. Misalnya local_16e tiap bytenya dibandingkan dengan olah3, olah3 berasalah dari tiap byte yang dikurang 0x30 (di fungsi min0x30 tadi), itu berarti tiap byte di variable local_16e harus kita tambahkan dengan 0x30 agar mendapatkan inputan yang tepat. Agar meyakinkan di sini saya mengambil nilai dari variable local_1bf, local_16e dan local_163 lewat debugger. Setelah ditelusuri local_1bf berada di offset -0x1b7, local_16e berada di offset -0x166, dan local_163 berada di offset -0x15b, offset-offset tersebut akan relative pada rbp di dalam fungsi main. Sekarang kita extract nilai-nilainya lewat gdb.

Setelah itu, kita masukkan ke dalam list python dan lakukan pembalikan algoritma seperti yang saya jelaskan sebelumnya.

Jalankan script di atas dan didapatkan flag: Arkav6{uwU_e4zy_Cr4ck_m3_uwU}

old school

Solves: 0 (sebelum scoreboard difreeze)

Challenge ini sebenernya saya solve di menit-menit terakhir sebelum kompetisi berakhir. Kita diberikan sebuah program executebale 16-bit. Saya belum menemukan decompile untuk 16-bit, jadi saya mendecompilenya secara manual.

Di awal entrypoint program memanggil suatu fungsi, fungsi ini saya berinama main.

Pada awalnya program membandingkan sesuatu dengan 0x23, sesuatu itu berasal dari register eax yang dihasilkan dari pemanggilan fungsi sebelumnya. asumsi pada awalnya itu merupakan kondisi untuk mengecek panjang string, dan fungsi itu saya berinama get_input. Dan berarti var_40 akan berisi inputan kita.

Di sini terlihat blok if-else bergantung dari return value dari fungsi sub_1023c, jika true program akan ke blok loca_10478 dan mempush address 0x60f, setelah ditelusuri 0x60f berisi string “Yayy” itu berarti dia akan memprint sesuatu, jadi pemanggilan fungsi setelah push saya berinama fungsi print, begitu juga di blok ketika false, instruksi yang mempush address 0x608, address tersebut berisi string “Nope”. Kita harus mencari tau bagaimana agar return value dari sub_1023c itu bernilai true.

Ingat, inputan kita berada di variable var_40. Jika blok pada loc_1042c ditranslate menjadi pseudo c akan menjadi seperti ini:

sub_1035d(var_40, var_b8);
if(sub_1023c(var_b8, (void*)(0x5c0))
    print("Yayy");
else
    print("Nope");

Saya menganalisa terlebih dahulu fungsi sub_1035d, flow graphnya seperti ini.

Terdapat fungsi seperti while loop selama variable var_A bukan 0, disetiap loopnya var_A didapat dari string dengan index dari var_8, sementara var_8 akan diincrement setiap loopnya, itu artinya var_a akan berisi setiap karakter pada string str1 (str1 adalah parameter pertama dari fungsi tersebut). Jadi kurang lebih kodenya seperti ini.

Setelah itu pada blok local_10384 terdapat operasi-operasi di dalam while loop, saya mentranslate kode assembly tersebut ke pseudo c secara manual, hasil akhir translasi fungsi tersebut kodenya seperti ini.

void sub_1035d(char* str1, uint16_t* dest) {
    var_a = str1[0];
    var_8 = 0;
    var_9 = 0;
    while(var_a) {
        dest[var_8] = sub_102e1(var_a + var_9);
        var_8 = var_a;
        var_8 = var_8 + 1;
        var_a = str[var_8];
    }
}

Sementara fungsi sub_102e1 mempunyai flow graph seperti ini.

Terlihat seperti loop lagi, dengan counter yang berada pada variable var_8, pada blok loc_1034d dicek dengan 7, bisa kita simpulkan loop ini akan berulang sebanyak 8 kali. Setelah itu kode di dalam loop pada blok loc_1030c, bagian ini juga saya mentranslate kode assembly menjadi pseudo c secara manual. Hasil dari translasi fungsi sub_102e1 menjadi seperti ini.

uint16_t sub102e1(uint8_t n) {
	uint16_t var_14 = n;
	uint16_t var_2 = n << 4;
	uint16_t var_8 = 0;
	for(var_8; var_8 <= 7; var_8++) {
		var_2 = var_2 + 0x7762;
		var_2 ^= var_8 << 1;
		var_2 ^= 0x8000 >> var_8;
		var_8 += 1;
	}
	return var_2;
}

Sekarang kita balik lagi ke kode yang berada di fungsi main.

sub_1035d(var_40, var_b8);
if(sub_1023c(var_b8, (void*)(0x5c0))
    print("Yayy");
else
    print("Nope");

Kita sudah menganalisa fungsi sub_1035d, setelah kita harus melihat fungsi sub_1023c, flow graphnya seperti ini.

Di sini saya tidak mentranslate ke pseudo c nya, tapi terlihat dari graph iya membandingkan setiap elemen pada variable arg_2 dan arg_6 (itu merupakan variable argumen pertama dan kedua pada fungsi itu), jadi variable arg_2 dan arg_6 merupakan sebuah variable array unsigned integer 16 bit, mereka dibandingkan jika semua nilainya sama maka akan menghasilkan true.

Kita gabungkan terlebih dahulu kode yang telah kita translate.

uint16_t sub102e1(uint8_t n) {
	uint16_t var_14 = n;
	uint16_t var_2 = n << 4;
	uint16_t var_8 = 0;
	for(var_8; var_8 <= 7; var_8++) {
		var_2 = var_2 + 0x7762;
		var_2 ^= var_8 << 1;
		var_2 ^= 0x8000 >> var_8;
		var_8 += 1;
	}
	return var_2;
}
void sub_1035d(char* str1, uint16_t* dest) {
    var_a = str1[0];
    var_8 = 0;
    var_9 = 0;
    while(var_a) {
        dest[var_8] = sub_102e1(var_a + var_9);
        var_8 = var_a;
        var_8 = var_8 + 1;
        var_a = str[var_8];
    }
}
void main() {
    .....
    sub_1035d(var_40, var_b8);
    if(sub_1023c(var_b8, (void*)(0x5c0))
        print("Yayy");
    else
        print("Nope");
    .....
}

Jadi, untuk mendapatkan inputan yang tepat, kita harus mereverse algoritma sub_1035d agar ketika inputan kita masuk ke fungsi sub_1035d menghasilkan array 16 bit, tiap2 elemennya harus sama dengan array 16 bit yang berada pada dialamat 0x5c0. Berikut kode solver saya yang mereverse algoritma diatas untuk mendapatkan inputan yang tepat.

from ctypes import *

def sub102e1(n):
    var_2 = n << 4
    for i in range(8):
        var_2 = var_2 + 0x7762
        var_2 ^= (1 << i)
        var_2 ^= 32768 >> i;
    return c_ushort(var_2).value

tbl = dict()
for i in range(256):
    tbl[sub102e1(i)] = i

def rev_ff(dst):
    src = ""
    n = len(dst);
    bc = 0
    for i in range(0,70,2):
        nn = ord(dst[i+1]) << 8
        nn |= ord(dst[i])
        tmp = chr(tbl[nn] - bc)
        src += tmp
        bc = ord(tmp)
    return src

# Diambil dari 0x5c0
flags = "6F E9 CF F9 AF 06 BF 07 8F 07 BF E9 6F  E6 1F 07 8F EB DF EA AF 07 FF 07 FF 02 3F 02 3F  07 6F 04 2F E6 EF E6 EF E6 2F E6 8F EA 5F EB 3F  04 CF EA 3F ED CF EA DF 07 0F 04 DF 06 DF EA 8F  EB EF 07 9F EA 6F EB 5F 02".replace(" ", "").decode("hex")
print(rev_ff(flags))

Jalankan script diatas, dan flag didapatkan: Arkav6{c4n_you_r3v3r5e_D0s_pr0gr4m}

1001 Malam

Solves: 1 (sebelum scoreboard di-freeze)

Diberikan sebuah file bernama chall.dll, file ini merupakan file dll 64 bit. Setelah dicek melalui strings, kode dll ini ternyata dibuat dari delphi.

$ strings chall.dll | grep Delphi                                                                                                                       SOFTWARE\Borland\Delphi\RTL                                                                                                                                                                     Software\Borland\Delphi\Locales                                                                                                                                                                 Delphi Picture                                                                                                                                                                                  Delphi Component                                                                                                                                                                                Delphi%.8X 

Kita dapat meloadnya melalui PE Explorer, dan kita dapat melihat method2 pada program delphi tersebut.

Bisa dilihat dari methodnya, terdapat Form artinya program ini adalah program GUI. Ternyata kita dapat me-run program ini dengan rundll.

Untuk mencari algoritma pengecekannya terdapat pada fungsi TForm1.ButtonCheckClick yaitu pada alamat 0x477b0c, kita juga dapat mendecompile fungsi itu di ghidra.


void FUN_00477b0c(int param_1,undefined4 param_2)

{
  int *piVar1;
  int iVar2;
  undefined4 uVar3;
  undefined4 *in_FS_OFFSET;
  undefined4 uStack28;
  undefined *puStack24;
  undefined *puStack20;
  undefined4 local_10;
  uint local_c;
  int local_8;
  
  puStack20 = &stack0xfffffffc;
  local_c = 0;
  puStack24 = &DAT_00477b88;
  uStack28 = *in_FS_OFFSET;
  *(undefined4 **)in_FS_OFFSET = &uStack28;
  local_10 = param_2;
  local_8 = param_1;
  FUN_00444f04(*(int *)(param_1 + 0x2fc),(int *)&local_c);
  iVar2 = check_00469060(local_c);
  if (iVar2 == 0) {
    FUN_004277d4("Nope!");
  }
  else {
    uVar3 = FUN_00477a34(local_c);
    piVar1 = *(int **)(*(int *)(local_8 + 0x300) + 0x168);
    (**(code **)(*piVar1 + 8))(piVar1,uVar3);
  }
  *in_FS_OFFSET = uStack28;
  puStack20 = &LAB_00477b8f;
  puStack24 = (undefined *)0x477b87;
  FUN_00403ed4((int *)&local_c);
  return;
}

Lihat snippet kode berikut dari kode di atas.

  FUN_00444f04(*(int *)(param_1 + 0x2fc),(int *)&local_c);
  iVar2 = check_00469060(local_c);
  if (iVar2 == 0) {
    FUN_004277d4("Nope!");
  }

Jika iVar2 itu False maka akan ditampilkan “Nope” ini seperti behaviour program yang kita lihat jika kita run melalui rundll sebelumnya. Untuk meyakinkan saya melakukan breakpoint pada saat kode ingin memanggil fungsi check. Saya merun programnya dengan rundll dan menattachnya di x32dbg. Jangan lupa sebelum breakpoint sesuaikan base address chall.dll-nya.

Bisa kita lihat sebelum fungsi check dipanggil, string yang kita inputkan ditaruh diargumen pertama pada fungsi itu. Ternyata ada keanehan dari fungsi check ini, fungsi check yang saya lihat di ghidra berbeda dengan saya lihat debugger, sepertinya program mengubah byte2 awal pada fungsi check ini pada saat runtime.

fungsi check di ghidra
fungsi check pada saat runtime

Jika kita lakukan step-step sampai tidak ada lagi instruksi jump kita diarahkan pada fungsi (yang sepertinya) adalah fungsi check yang asli.

Kita juga dapat mendecompile fungsi check yang asli tersebut, saya berinama fungsi tersebut sebagai realcheck.


void realcheck(uint param_1)

{
  undefined4 *in_FS_OFFSET;
  bool bVar1;
  undefined4 uStack60;
  undefined *puStack56;
  undefined *puStack52;
  undefined4 *local_30;
  uint local_2c [4];
  int local_1c;
  uint *local_18;
  int local_14;
  int local_10;
  int local_c;
  uint local_8;
  
  puStack52 = &stack0xfffffffc;
  local_30 = (undefined4 *)0x0;
  local_18 = (uint *)0x0;
  puStack56 = &DAT_0046adc4;
  uStack60 = *in_FS_OFFSET;
  *(undefined4 **)in_FS_OFFSET = &uStack60;
  local_1c = -1;
  local_8 = param_1;
  local_14 = strlen(param_1);
  local_10 = 1;
  if (local_14 != 0xe) {
    local_1c = 0;
  }
  while ((local_1c != 0 && (bVar1 = local_10 == local_14, local_10 <= local_14))) {
    FUN_004040bc((int *)&local_30,local_8 & 0xffffff00 | (uint)*(byte *)((local_8 - 1) + local_10));
    FUN_00467a78(local_30,local_2c);
    FUN_00467a94((byte *)local_2c,(int *)&local_18);
    FUN_004042e0(local_18,(uint *)(&PTR_s_f5b83d3f4bd3c29cfc2f499f883a7ae9_0047ad10)[local_10]);
    if (!bVar1) {
      local_1c = 0;
    }
    local_10 = local_10 + 1;
  }
  local_c = local_1c;
  *in_FS_OFFSET = uStack60;
  puStack52 = &LAB_0046adcb;
  puStack56 = (undefined *)0x46adbb;
  FUN_00403ed4((int *)&local_30);
  puStack56 = (undefined *)0x46adc3;
  FUN_00403ed4((int *)&local_18);
  return;
}

Kita dapat dengan mudah mencari panjang string yang harus dimasukkan dengan melihat kode ini.

  local_14 = strlen(param_1);
  local_10 = 1;
  if (local_14 != 0xe) {
    local_1c = 0;
  }

Lalu terdapat blok while loop.

  while ((local_1c != 0 && (bVar1 = local_10 == local_14, local_10 <= local_14))) {
    FUN_004040bc((int *)&local_30,local_8 & 0xffffff00 | (uint)*(byte *)((local_8 - 1) + local_10));
    FUN_00467a78(local_30,local_2c);
    FUN_00467a94((byte *)local_2c,(int *)&local_18);
    FUN_004042e0(local_18,(uint *)(&PTR_s_f5b83d3f4bd3c29cfc2f499f883a7ae9_0047ad10)[local_10]);
    if (!bVar1) {
      local_1c = 0;
    }
    local_10 = local_10 + 1;
  }

Terdapat pemanggilan ke empat fungsi, setelah ditelusuri ada banyak kode2 yang sulit dipahami dan membutuhkan waktu yang lama memahaminya. Jadi saya melihat argumen2 dan return value dari fungsi itu untuk melihat polanya. Agar masuk ke blok while loop kita lakukan inputan ulang dengan panjang 0xe.

Disini saya melakukan breakpoint di dalam fungsi realcheck tersebut dengan menginputkan “ABCDEFGHIJKLMN”

Dipemanggilan fungsi terakhir sebelum perbandingan, terdapat dua buah string hex. Bisa dilihat dari register eax dan edx. Edx didapat dari sebuah variable global, sementara variable eax sepertinya didapat dari inputan kita. Karena fungsi2 sebelumnya cukup kompleks dipahami, asumsi saya karena melihat string hex dan algoritma kompleks tersebut itu merupakan sebuah algoritma kriptografi. Lalu saya mencoba mempastekan string hex eax dan edx tersebut ke md5 crack online didapatkan, string hex eax merupakan hasil md5 dari string “A” dan string hex yg ada di edx merupakan hasil md5 dari string “l”. Dengan melihat flow algoritmanya sekali lagi, setiap karakter pada inputan akan di-md5 dan dibandingkan dengan setiap string md5 pada alamat 0x272ad10 atau 0x0047ad10 jika di ghidra.

Pada 0x0047ad10 berisi string-string hex yang akan dibandingkan dengan md5 dari setiap karakter yang kita inputkan.

Kita tinggal mengumpulkan string2 hex md5 itu dan mencracknya, dan gabungkan karakter2nya.

2db95e8e1a9267b7a1188556b2013b33 l
518ed29525738cebdac49c49e60ea9d3 @
6cff047854f19ac2aa52aac51bf3af4a &
02129bb861061d1a052c592e2dc6b383 X
fbade9e36a3f36d3d676c1b808451dd7 z
a5f3c6a11b03839d46af9fb43c97c188 K
eccbc87e4b5ce2fe28308fd9f2a7baf3 3
eccbc87e4b5ce2fe28308fd9f2a7baf3 3
f623e75af30e62bbd73d6df5b50bb7b5 D
3a3ea00cfc35332cedf6e5e9a32e94da E
8d9c307cb7f3c4a32822a51922d1ceaa N
865c0c0b4ab0e063e5caa3387c1a8741 i
9d5ed678fe57bcca610140957afab571 B
800618943025315f869e4e1f09471012 F
l@&XzK33DENiBF

Didapatkan string l@&XzK33DENiBF. Kita bisa menginputkannya ke program. Hasilnya seperti ini.

Sepertinya inputan kita sudah benar, dan potongan flag terlihat pada gambar, tetapi karena form terlalu kecil sehingga flag tidak dapat terlihat semuanya. Untuk memperbaikinya saya mengubah ukuran form dan image menjadi lebih besar. Saya mengubahnya dengan Resource Hacker.

Disini saya menguhba ClientWidth menjadi 1000 dan Timage.Width menjadi 930, Save dengan nama chall.dll (saya tidak tau kenapa dengan mensave dengan nama yang lain dll tidak dapat dijalankan). Jalankan chall.dll yang telah diubah dengan rundll dan inputkan string yang telah kita dapatkan tadi yaitu ” l@&XzK33DENiBF “, dan flag akan muncul di form.

One thought on “Reversing Arkavidia CTF

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