Banyak orang mungkin sudah tahu secara langsung jenis binatang apa ini -
Ghidra ("Hydra") dan apa yang dimakan program tersebut secara langsung, meskipun alat ini tersedia untuk umum baru-baru ini - pada bulan Maret tahun ini. Saya tidak akan mengganggu pembaca dengan deskripsi Hydra, fungsinya, dll. Mereka yang berada dalam topik, saya yakin, telah mempelajari semua ini sendiri, dan mereka yang belum dalam topik - mereka dapat melakukannya kapan saja, karena sekarang mudah untuk menemukan informasi terperinci di Internet. Ngomong-ngomong, salah satu aspek Hydra (pengembangan plugin untuk itu) telah
dibahas di Habré (artikel bagus!) Saya hanya akan memberikan tautan utama:
Jadi, Hydra adalah
disassembler interaktif dan platform bebas dekompiler bebas dengan struktur modular, dengan dukungan untuk hampir semua arsitektur CPU utama dan antarmuka grafis yang fleksibel untuk bekerja dengan kode yang dibongkar, memori, kode yang dipulihkan (didekompilasi), simbol debugging, dan banyak lagi .
Ayo coba hancurkan sesuatu dengan Hydra ini!
Langkah 1. Temukan dan pelajari celahnya
Sebagai "korban" kami menemukan program "crackme" sederhana. Saya baru saja pergi ke
crackmes.one , yang ditunjukkan dalam pencarian tingkat kesulitan = 2-3 ("sederhana" dan "sedang"), bahasa sumber program = "C / C ++" dan platform = "Multiplatform", seperti pada tangkapan layar di bawah:

Pencarian menghasilkan 2 hasil (berwarna hijau di bawah). Retakan pertama ternyata 16-bit dan tidak dimulai pada Win10 64-bit saya, tetapi yang kedua (
level_2 oleh seveb ) muncul. Anda dapat mengunduhnya dari
tautan ini .
Unduh dan buka kemasannya; Kata sandi untuk arsip, sebagaimana ditunjukkan di situs, adalah
crackmes.de . Dalam arsip kami menemukan dua direktori yang sesuai dengan Linux dan Windows. Di komputer saya, saya pergi ke direktori Windows dan saya bertemu di dalamnya satu-satunya "executable" -
level_2.exe . Ayo lari dan lihat apa yang dia inginkan:

Sepertinya gelandangan! Saat startup, program tidak menampilkan apa pun. Kami mencoba untuk menjalankannya lagi, memberikannya string yang berubah-ubah sebagai parameter (tiba-tiba, apakah itu menunggu kunci?) - dan sekali lagi tidak ada ... Tapi jangan putus asa. Mari kita asumsikan bahwa kita juga harus mencari tahu parameter peluncuran sebagai tugas! Sudah waktunya untuk mengungkap "pisau Swiss" kami - Hydra.
Langkah 2. Membuat proyek di Hydra dan analisis pendahuluan
Misalkan Anda sudah menginstal Hydra. Jika belum, maka semuanya sederhana.
Instal Ghidra1) instal
JDK versi 11 atau lebih tinggi (saya punya
12 )
2) unduh Hydra (misalnya,
dari sini ) dan instal (pada saat penulisan, versi terbaru Hydra adalah 9.0.2, saya punya 9.0.1)
Kami meluncurkan Hydra dan dalam Project Manager yang terbuka segera membuat proyek baru; Saya memberinya nama
crackme3 (mis., Proyek crackme dan crackme2 telah dibuat untuk saya). Proyek ini, pada kenyataannya, adalah direktori file, Anda dapat menambahkan file apa saja untuk dipelajari (exe, dll, dll.). Kami akan segera menambahkan level_2.exe kami (
File | Impor atau hanya kunci
I ):

Kami melihat bahwa, sebelum mengimpor, Hydra mengidentifikasi dukun eksperimental kami sebagai PE 32-bit (dapat dieksekusi portabel) untuk OS Win32 dan platform x86. Setelah impor, kami menunggu lebih banyak informasi:

Di sini, selain kedalaman bit yang disebutkan di atas, kita mungkin masih tertarik dengan
urutan endianness , yang dalam kasus kami adalah
Little (dari byte rendah ke tinggi), yang diharapkan untuk platform Intel 86.
Dengan analisis pendahuluan, kita selesai.
Langkah 3. Lakukan analisis otomatis
Saatnya memulai analisis otomatis penuh dari program di Hydra. Ini dilakukan dengan mengklik dua kali pada file yang sesuai (level_2.exe). Memiliki struktur modular, Hydra menyediakan semua fungsi dasarnya dengan sistem plug-in yang dapat ditambahkan / dinonaktifkan atau dikembangkan secara mandiri. Sama halnya dengan analisis - setiap plugin bertanggung jawab untuk jenis analisisnya. Karena itu, pertama, kita dihadapkan dengan jendela ini di mana Anda dapat memilih jenis analisis yang menarik:
Jendela Pengaturan Analisis Untuk tujuan kami, masuk akal untuk meninggalkan pengaturan default dan menjalankan analisis. Analisis itu sendiri dilakukan dengan sangat cepat (saya butuh sekitar 7 detik), meskipun pengguna di forum mengeluh bahwa untuk proyek-proyek besar, Hydra kehilangan kecepatan untuk
IDA Pro . Ini mungkin benar, tetapi untuk file kecil perbedaan ini tidak signifikan.
Jadi, analisisnya lengkap. Hasilnya ditampilkan di jendela Code Browser:

Jendela ini adalah yang utama untuk bekerja di Hydra, jadi Anda harus mempelajarinya lebih hati-hati.
Ikhtisar Kode Antarmuka BrowserPengaturan antarmuka default membagi jendela menjadi tiga bagian.
Di bagian
tengah adalah jendela utama - daftar disassembler, yang kurang lebih mirip dengan "saudara" di IDA, OllyDbg, dll. Secara default, kolom dalam daftar ini adalah (dari kiri ke kanan): alamat memori, opcode perintah, perintah ASM, parameter perintah ASM, referensi silang (jika berlaku). Secara alami, tampilan dapat diubah dengan mengklik tombol dalam bentuk dinding bata di bilah alat jendela ini. Sejujurnya, saya belum pernah melihat konfigurasi yang fleksibel dari output disassembler di mana pun, itu sangat nyaman.
Di bagian
kiri dari 3 panel:
- Bagian dari program (klik pada mouse untuk menelusuri bagian-bagian)
- Pohon karakter (impor, ekspor, fungsi, header, dll.)
- Ketik pohon variabel yang digunakan
Bagi kami, jendela paling berguna di sini adalah pohon simbol, yang memungkinkan Anda untuk dengan cepat menemukan, misalnya fungsi dengan namanya dan pergi ke alamat yang sesuai.
Di sebelah
kanan adalah daftar kode yang didekompilasi (dalam kasus kami, dalam C).
Selain jendela default, di menu
Window Anda dapat memilih dan menempatkan puluhan jendela lain dan menampilkan di mana saja di browser. Untuk kenyamanan, saya menambahkan jendela Bytes dan jendela dengan Function Graph ke tengah, dan ke kanan, variabel string (Strings) dan tabel fungsi (Functions). Jendela ini sekarang tersedia di tab terpisah. Juga, setiap jendela dapat dilepas dan dibuat "mengambang", menempatkan dan mengubah ukurannya sesuai kebijaksanaan Anda - ini juga merupakan solusi yang sangat bijaksana, menurut saya,.
Langkah 4. Mempelajari algoritma program - fungsi utama ()
Baiklah, mari kita lanjutkan ke analisis langsung dari program crack kita. Dalam kebanyakan kasus, Anda harus mulai dengan mencari titik masuk program, yaitu Fungsi utama yang dipanggil saat itu dimulai. Mengetahui bahwa crack kami ditulis dalam C / C ++, kami menduga bahwa nama fungsi utama akan menjadi
main () atau sesuatu seperti itu :) Dikatakan dan dilakukan. Masukkan "main" di filter Tree of Symbols (di panel kiri) dan lihat fungsi
_main () di bagian
Functions . Pergi ke sana dengan klik mouse.
Tinjauan umum fungsi utama () dan penggantian nama fungsi tidak jelas
Dalam daftar disassembler, bagian kode yang sesuai segera ditampilkan, dan di sebelah kanan kita melihat kode-C yang didekompilasi dari fungsi ini. Perlu diperhatikan satu lagi fitur sinkronisasi Hydra-selection yang nyaman: ketika mouse memilih serangkaian perintah ASM, bagian kode yang sesuai dalam dekompiler disorot dan sebaliknya. Selain itu, jika jendela melihat memori terbuka, alokasi disinkronkan dengan memori. Seperti yang mereka katakan, semua cerdik itu sederhana!
Segera, saya perhatikan fitur penting bekerja di Hydra (sebagai lawan dari, katakanlah, bekerja di IDA).
Pekerjaan di Hydra terutama difokuskan pada analisis kode yang didekompilasi . Untuk alasan ini, pencipta Hydra (kita ingat - kita berbicara tentang mata-mata dari NSA :)) menaruh perhatian besar pada kualitas dekompilasi dan kenyamanan bekerja dengan kode. Secara khusus, seseorang dapat dengan mudah mendefinisikan fungsi, variabel, dan bagian memori dengan hanya mengklik dua kali pada kode. Juga, variabel dan fungsi apa pun dapat segera diganti namanya, yang sangat nyaman, karena nama default tidak memiliki makna dan dapat membingungkan. Seperti yang akan Anda lihat nanti, kita akan sering menggunakan mekanisme ini.
Jadi, inilah fungsi
utama () , yang Hydra “membedah” sebagai berikut:
Daftar utama ()int __cdecl _main(int _Argc,char **_Argv,char **_Env) { bool bVar1; int iVar2; char *_Dest; size_t sVar3; FILE *_File; char **ppcVar4; int local_18; ___main(); if (_Argc == 3) { bVar1 = false; _Dest = (char *)_text(0x100,1); local_18 = 0; while (local_18 < 3) { if (bVar1) { _text(_Dest,0,0x100); _text(_Dest,_Argv[local_18],0x100); break; } sVar3 = _text(_Argv[local_18]); if (((sVar3 == 2) && (((int)*_Argv[local_18] & 0x7fffffffU) == 0x2d)) && (((int)_Argv[local_18][1] & 0x7fffffffU) == 0x66)) { bVar1 = true; } local_18 = local_18 + 1; } if ((bVar1) && (*_Dest != 0)) { _File = _text(_Dest,"rb"); if (_File == (FILE *)0x0) { _text("Failed to open file"); return 1; } ppcVar4 = _construct_key(_File); if (ppcVar4 == (char **)0x0) { _text("Nope."); _free_key((void **)0x0); } else { _text("%s%s%s%s\n",*ppcVar4 + 0x10d,*ppcVar4 + 0x219,*ppcVar4 + 0x325,*ppcVar4 + 0x431); _free_key(ppcVar4); } _text(_File); } _text(_Dest); iVar2 = 0; } else { iVar2 = 1; } return iVar2; }
Tampaknya semuanya tampak normal - definisi variabel, tipe-C standar, kondisi, loop, pemanggilan fungsi. Tetapi melihat lebih dekat pada kode, kita perhatikan bahwa untuk beberapa alasan nama-nama dari beberapa fungsi tidak didefinisikan dan diganti oleh pseudo-
function _text () (di jendela decompiler -
.text () ). Mari kita mulai dengan mendefinisikan apa fungsi-fungsi ini.
Mengklik dua kali pada badan panggilan pertama
_Dest = (char *)_text(0x100,1);
kita melihat bahwa ini hanyalah fungsi pembungkus di sekitar fungsi
calloc () standar, yang digunakan untuk mengalokasikan memori untuk data. Jadi mari kita ganti nama fungsi ini menjadi
calloc2 () . Mengatur kursor pada header fungsi, panggil menu konteks dan pilih
Ubah nama fungsi (tombol panas -
L ) dan masukkan nama baru di bidang yang terbuka:

Kami melihat bahwa fungsi itu segera diganti namanya. Kita kembali ke badan
() utama ( tombol
Kembali di bilah alat atau
Alt + <- ) dan kita melihat bahwa di sini alih-alih
_text () calloc2 () yang misterius sudah berdiri. Hebat!
Kami melakukan hal yang sama dengan semua fungsi pembungkus lainnya: kita masuk ke definisi mereka satu per satu, melihat apa yang mereka lakukan, mengganti nama (saya menambahkan indeks 2 ke nama standar fungsi-C) dan kembali ke fungsi utama.
Kami memahami kode fungsi utama ()
Oke, kami menemukan beberapa fungsi aneh. Kami mulai mempelajari kode fungsi utama. Melewati deklarasi variabel, kita melihat bahwa fungsi mengembalikan nilai variabel iVar2, yaitu nol (tanda keberhasilan fungsi) hanya jika kondisi yang ditentukan oleh string terpenuhi
if (_Argc == 3) { ... }
_Argc adalah jumlah parameter baris perintah (argumen) yang diteruskan ke
main () . Yaitu, program kami “makan” 2 argumen (argumen pertama, kita ingat, selalu merupakan jalur ke file yang dapat dieksekusi).
Oke, mari kita lanjutkan. Di sini kita membuat C-string (
char array) 256 karakter:
char *_Dest; _Dest = (char *)calloc2(0x100,1);
Selanjutnya kita memiliki loop 3 iterasi. Di dalamnya, pertama-tama kita memeriksa apakah flag
bVar1 diatur, dan jika demikian, salin argumen baris perintah berikut (string) ke
_Dest :
while (i < 3) { if (bVar1) { memset2(_Dest,0,0x100); strncpy2(_Dest,_Argv[i],0x100); break; } ... }
Bendera ini disetel saat menguraikan argumen berikut:
n_strlen = strlen2(_Argv[i]); if (((n_strlen == 2) && (((int)*_Argv[i] & 0x7fffffffU) == 0x2d)) && (((int)_Argv[i][1] & 0x7fffffffU) == 0x66)) { bVar1 = true; }
Baris pertama menghitung panjang argumen ini. Lebih lanjut, kondisi memeriksa bahwa panjang argumen harus 2, karakter kedua dari belakang == "-" dan karakter terakhir == "f". Perhatikan bagaimana dekompiler "menerjemahkan" ekstraksi karakter dari string menggunakan topeng byte.
Nilai angka desimal, dan pada saat yang sama karakter ASCII yang sesuai, dapat dimata-matai dengan menahan kursor di atas heksadesimal heksadesimal yang sesuai. Pemetaan ASCII tidak selalu berhasil (?), Jadi saya sarankan melihat tabel ASCII di Internet. Anda juga dapat secara langsung dalam Hydra mengonversi skalar dari sistem bilangan apa pun ke sistem bilangan lainnya (melalui menu konteks -> Konversi ), dalam hal ini nomor ini akan ditampilkan di mana-mana dalam sistem bilangan yang dipilih (dalam disassembler dan dalam dekompiler); tetapi secara pribadi, saya lebih suka meninggalkan hexes dalam kode untuk harmoni kerja, karena alamat memori, offset, dll. heks ditetapkan di mana-mana.
Setelah loop muncul kode ini:
if ((bVar1) && (*_Dest != 0)) { _File = fopen2(_Dest,"rb"); if (_File == (FILE *)0x0) { perror2("Failed to open file"); return 1; } ... }
Di sini saya langsung menambahkan komentar. Kami memeriksa kebenaran argumen ("-f path_to_file") dan membuka file yang sesuai (argumen ke-2 berlalu, yang kami salin ke _Dest). File akan dibaca dalam format biner, seperti yang ditunjukkan oleh parameter "rb" pada fungsi
fopen () . Jika pembacaan gagal (misalnya, file tidak tersedia), pesan kesalahan ditampilkan dalam aliran stderror dan program keluar dengan kode 1.
Berikutnya adalah yang paling menarik:
ppcVar3 = _construct_key(_File); if (ppcVar3 == (char **)0x0) { puts2("Nope."); _free_key((void **)0x0); } else { printf2("%s%s%s%s\n",*ppcVar3 + 0x10d,*ppcVar3 + 0x219,*ppcVar3 + 0x325,*ppcVar3 + 0x431); _free_key(ppcVar3); } fclose2(_File);
Deskriptor file terbuka (
_File ) diteruskan ke fungsi
_construct_key () , yang, jelas, melakukan verifikasi kunci yang dicari. Fungsi ini mengembalikan array byte dua dimensi (
char ** ), yang disimpan dalam variabel
ppcVar3 . Jika array kosong, "Tidak" singkat ditampilkan pada konsol (yaitu, menurut pendapat kami, "Tidak!") Dan memori dibebaskan. Kalau tidak (jika array tidak kosong), kunci yang tampaknya benar ditampilkan dan memori juga dibebaskan. Di akhir fungsi, deskriptor file ditutup, memori dibebaskan, dan nilai
iVar2 dikembalikan .
Jadi, sekarang kami menyadari bahwa kami membutuhkan:
1) membuat file biner dengan kunci yang benar;
2) melewati jalannya di celah setelah argumen "-f"Di bagian
kedua artikel, kami akan menganalisis fungsi
_construct_key () , yang, seperti yang kami
ketahui , bertanggung jawab untuk memeriksa kunci yang diinginkan dalam file.