Kami memecahkan masalah Reverser Terbaik dengan PHDays 9

Halo

Nama saya Marat Gayanov, saya ingin berbagi dengan Anda solusi saya untuk masalah dari kontes Best Reverser , untuk menunjukkan cara membuat keygen untuk kasus ini.

gambar

Deskripsi


Dalam kompetisi ini, peserta diberikan permainan ROM untuk Sega Mega Drive ( best_reverser_phd9_rom_v4.bin ).

Tugas: untuk mengambil kunci seperti itu yang bersama-sama dengan alamat email peserta akan diakui valid.

Jadi solusinya ...

Alat-alatnya



Memeriksa Panjang Kunci


Program tidak menerima setiap kunci: Anda harus mengisi seluruh bidang, ini adalah 16 karakter. Jika kuncinya lebih pendek, Anda akan melihat pesan: โ€œPanjangnya salah! Coba lagi ... ".

Mari kita coba temukan baris ini di program, yang mana kita akan menggunakan pencarian biner (Alt-B). Apa yang akan kita temukan?

Kami tidak hanya akan menemukan ini, tetapi juga jalur layanan lain di sekitarnya: โ€œKunci salah! Coba lagi ... "dan" KAMU ADALAH REVERSER TERBAIK! ".

gambar

gambar

Saya menetapkan WRONG_LENGTH_MSG , WRONG_LENGTH_MSG , dan WRONG_KEY_MSG untuk kenyamanan.

Istirahat membaca alamat 0x0000FDFA - cari tahu siapa yang bekerja dengan pesan "Panjangnya salah! Coba lagi ... ". Dan jalankan debugger (itu akan berhenti beberapa kali sebelum kunci dapat dimasukkan, cukup tekan F9 di setiap perhentian). Masukkan email Anda, kunci ABCD .

Debugger mengarah ke 0x00006FF0 tst.b (a1)+ :

gambar

Tidak ada yang menarik di blok itu sendiri. Jauh lebih menarik siapa yang mentransfer kendali di sini. Kami melihat tumpukan panggilan:

gambar

Klik dan dapatkan di sini - ke instruksi 0x00001D2A jsr (sub_6FC0).l :

gambar

Kami melihat bahwa semua pesan yang mungkin ditemukan di satu tempat. Tetapi mari kita cari tahu di mana kontrol ditransfer ke dalam blok WRONG_KEY_LEN_CASE_1D1C . Kami tidak akan mengatur jeda, cukup gerakkan kursor di atas panah ke blok. Pemanggil terletak di 0x000017DE loc_17DE (yang akan saya ganti namanya menjadi CHECK_KEY_LEN ):

gambar

0x000017EC cmpi.b 0x20 (a0, d0.l) alamat 0x000017EC cmpi.b 0x20 (a0, d0.l) (instruksi dalam konteks ini terlihat untuk melihat apakah ada karakter kosong di akhir array karakter kunci), restart, masukkan kembali surat dan kunci ABCD . Debugger berhenti dan menunjukkan bahwa kunci yang dimasukkan terletak di alamat 0x00FF01C7 (disimpan pada saat itu dalam register a0 ):

gambar

Ini adalah temuan yang bagus, melalui itu kami akan mengambil semuanya. Tapi pertama-tama, tandai byte kunci untuk kenyamanan:

gambar

Menggulir ke atas dari tempat ini, kita melihat bahwa surat disimpan di sebelah tombol:

gambar

Kami menyelam lebih dalam dan lebih dalam, dan inilah saatnya untuk menemukan kriteria untuk kebenaran kunci. Sebaliknya, bagian pertama dari kunci.

Kriteria untuk kebenaran bagian pertama dari kunci


Perhitungan awal


Adalah logis untuk mengasumsikan bahwa segera setelah memeriksa panjang, operasi lain dengan kunci akan mengikuti. Pertimbangkan blok segera setelah cek:

gambar

Blok ini sedang menjalani pekerjaan pendahuluan. Fungsi get_hash_2b (dalam aslinya adalah sub_1526 ) dipanggil dua kali. Pertama, alamat byte pertama dari kunci ditransmisikan ke sana (register a0 berisi alamat KEY_BYTE_0 ), yang kedua - kelima ( KEY_BYTE_4 ).

Saya menamai fungsi seperti ini karena menganggapnya seperti hash 2-byte. Ini adalah nama yang paling bisa dimengerti yang saya ambil.

Saya tidak akan mempertimbangkan fungsi itu sendiri, tetapi saya akan segera menulisnya dengan python. Dia melakukan hal-hal sederhana, tetapi deskripsinya dengan tangkapan layar akan memakan banyak ruang.

Hal paling penting untuk dikatakan tentang itu: alamat input diberikan ke input, dan pekerjaan sedang dilakukan pada 4 byte dari alamat ini. Artinya, mereka mengirim byte pertama kunci ke input, dan fungsinya akan bekerja dengan 1,2,3,4. Diposting kelima, fungsi ini bekerja dengan 5,6,7,8. Dengan kata lain, di blok ini ada perhitungan selama paruh pertama kunci. Hasilnya ditulis ke register d0 .

Jadi fungsi get_hash_2b :

 # key_4s -    def get_hash_2b(key_4s): #    def transform(b): # numbers -. if b <= 0x39: r = b - 0x30 # Letter case and @ else: # @ABCDEF if b <= 0x46: r = b - 0x37 else: # WXYZ if b >= 0x57: r = b - 0x57 # GHIJKLMNOPQRSTUV else: r = 0xff - (0x57 - b) + 1 # a9+b return r #    key_4b = bytearray(key_4s, encoding="ascii") #     codes = [transform(b) for b in key_4b] #      part0 = (codes[0] & 0xff) << 0xc part1 = (codes[1] << 0x8) & 0xf00 part2 = (codes[2] << 0x4) & 0xf0 hash_2b = (part0 | part1) & 0xffff hash_2b = (hash_2b | part2) & 0xffff hash_2b = (hash_2b | (codes[3] & 0xf)) return hash_2b 

Segera tulis fungsi decoding hash:

 #    4-  def decode_hash_4s(hash_2b): #    def transform(b): if b <= 0x9: return b + 0x30 if b <= 0xF: return b + 0x37 if b >= 0x0: return b + 0x57 return b - 0xa9 #         b0 = transform(hash_2b >> 12) b1 = transform((hash_2b & 0xfff) >> 8) b2 = transform((hash_2b & 0xff) >> 4) b3 = transform(hash_2b & 0xf) #  key_4s = [chr(b0), chr(b1), chr(b2), chr(b3)] key_4s = "".join(key_4s) return key_4s 

Saya tidak membuat fungsi decoding yang lebih baik, dan itu tidak sepenuhnya benar. Karena itu, saya akan memeriksanya seperti ini (tidak sekarang, tetapi nanti):

 key_4s == decode_hash_4s(get_hash_2b(key_4s)) 

Periksa pengoperasian get_hash_2b . Kami tertarik pada status register d0 setelah fungsi dieksekusi. Kami 0x000017FE break pada 0x000017FE , 0x00001808 , kunci yang kami masukkan ABCDEFGHIJKLMNOP .

gambar

gambar

Nilai 0xABCD , 0xEF01 dimasukkan dalam register d0 . Dan apa yang akan diberikan get_hash_2b ?

 >>> first_hash = get_hash_2b("ABCD") >>> hex(first_hash) 0xabcd >>> second_hash = get_hash_2b("EFGH") >>> hex(second_hash) 0xef01 

Verifikasi berlalu.

Kemudian xor eor.w d0, d5 diproduksi, hasilnya dimasukkan dalam d5 :

 >>> hex(0xabcd ^ 0xef01) 0x44cc 

gambar

Memperoleh hash 0x44CC adalah 0x44CC dan terdiri dari perhitungan awal. Selanjutnya, semuanya menjadi lebih rumit.

Kemana perginya hash


Kami tidak bisa melangkah lebih jauh jika kami tidak tahu bagaimana program bekerja dengan hash. Tentunya itu bergerak dari d5 ke memori, karena Register berguna di tempat lain. Kita dapat menemukan peristiwa semacam itu melalui jejak (menonton d5 ), tetapi tidak manual, tetapi otomatis. Script berikut akan membantu:

 #include <idc.idc> static main() { auto d5_val; auto i; for(;;) { StepOver(); GetDebuggerEvent(WFNE_SUSP, -1); d5_val = GetRegValue("d5"); //    d5 if (d5_val != 0xFFFF44CC){ break; } } } 

Biarkan saya mengingatkan Anda bahwa kita sekarang pada istirahat terakhir 0x00001808 eor.w d0, d5 . Rekatkan skrip ( Shift-F2 ), klik Run

Script akan berhenti pada instruksi 0x00001C94 move.b (a0, a1.l), d5 , tetapi pada saat ini d5 telah dihapus. Namun, kita melihat bahwa nilai dari d5 digerakkan oleh instruksi 0x00001C56 move.w d5,a6 : ditulis ke dalam memori di alamat 0x00FF0D46 (2 byte).

Ingat: hash disimpan di 0x00FF0D46 .

gambar

Kami menangkap instruksi yang dibaca dari 0x00FF0D46-0x00FF0D47 (kami menetapkan jeda untuk membaca). Tertangkap 4 blok:

gambargambargambargambar

Bagaimana cara memilih yang benar / benar?

Kembali ke awal:

gambar

Blok ini menentukan apakah program akan menuju ke LOSER_CASE atau ke WINNER_CASE :

gambar

Kita melihat bahwa dalam register d1 harus nol untuk menang.

Di mana nol ditetapkan? Cukup gulir ke atas:

gambar

Jika loc_1EEC terpenuhi di blok loc_1EEC :

 *(a6 + 0x24) == *(a6 + 0x22) 

maka kita mendapatkan nol di d5 .

Jika kita menghentikan instruksi 0x00001F16 beq.w loc_20EA , kita akan melihat bahwa a6 + 0x24 = 0x00FF0D6A dan nilai 0x4840 disimpan di sana. Dan dalam a6 + 0x22 = 0x00FF0D68 disimpan.

Jika kita memasukkan kunci berbeda, email, kita akan melihat bahwa 0xCB4C - . Paruh pertama kunci hanya akan diterima jika di 0x00FF0D6A juga akan ada 0xCB4C . Ini adalah kriteria untuk ketepatan bagian pertama dari kunci.

Kami mencari tahu blok mana yang ditulis dalam 0x00FF0D6A - 0x00FF0D6A istirahat pada catatan, masukkan surat dan kunci lagi.

Dan kita akan menemukan blok loc_EAC ini (sebenarnya ada 3, tetapi dua yang pertama hanya nol 0x00FF0D6A ):

gambar

Blok ini milik fungsi sub_E3E .

Melalui tumpukan panggilan, kami mengetahui bahwa fungsi sub_E3E dipanggil di blok loc_1F94 , loc_203E :

gambargambar

Ingat kami menemukan 4 blok sebelumnya? loc_1F94 kami lihat di sana - ini adalah awal dari algoritma pemrosesan kunci utama.

Loc_1 loop penting pertama


Fakta bahwa loc_1F94 adalah sebuah siklus terlihat dari kode: ia dieksekusi d4 kali (lihat instruksi 0x00001FBA d4,loc_1F94 ):

gambar

Apa yang harus dicari:

  1. Ada fungsi sub_5EC .
  2. Instruksi 0x00001FB4 jsr (a0) memanggil fungsi sub_E3E (ini dapat dilihat dengan jejak sederhana).

Apa yang terjadi di sini:

  1. Fungsi sub_5EC menulis hasil eksekusi ke register d0 (ini dibahas pada bagian terpisah di bawah).
  2. Byte pada address sp+0x33 ( 0x00FFFF79 , debugger memberitahu kita) disimpan dalam register d1 , itu sama dengan byte kedua dari alamat hash utama ( 0x00FF0D47 ). Ini mudah dibuktikan jika Anda memecahkan rekor pada 0x00FFFF79 : ini akan bekerja pada instruksi 0x00001F94 move.b 1(a2), 0x2F(sp) . Register a2 pada saat ini menyimpan alamat 0x00FF0D46 - alamat hash, yaitu, 0x1(a2) = 0x00FF0D46 + 1 - alamat byte kedua dari hash.
  3. Register d0 ditulis d0^d1 .
  4. Hasil xor'a yang dihasilkan diberikan ke fungsi sub_E3E , perilaku yang tergantung pada perhitungan sebelumnya (ditampilkan di bawah).
  5. Ulangi

Berapa kali siklus ini berjalan?

Temukan ini. Jalankan skrip berikut:

 #include <idc.idc> static main() { auto pc_val, d4_val, counter=0; while(pc_val != 0x00001F16) { StepOver(); GetDebuggerEvent(WFNE_SUSP, -1); pc_val = GetRegValue("pc"); if (pc_val == 0x00001F92){ counter++; d4_val = GetRegValue("d4"); print(d4_val); } } print(counter); } 

0x00001F92 subq.l 0x1,d4 - di sini ditentukan apa yang akan terjadi di d4 segera sebelum loop:

gambar

Kami berurusan dengan fungsi sub_5EC.

sub_5EC


Sepotong kode yang signifikan:

gambar

di mana 0x2c(a2) selalu 0x00FF1D74 .
Bagian ini dapat ditulis ulang seperti ini di pseudo-code:

 d0 = a2 + 0x2C *(a2+0x2C) = *(a2+0x2C) + 1 #*(0x00FF1D74) = *(0x00FF1D74) + 1 result = *(d0) & 0xFF 

Yaitu, 4 byte dari 0x00FF1D74 adalah alamatnya, karena mereka diperlakukan seperti pointer.

Bagaimana cara menulis ulang fungsi sub_5EC dengan python?

  1. Atau membuat memori dump dan bekerja dengannya.
  2. Atau cukup tuliskan semua nilai yang dikembalikan.

Metode kedua saya lebih suka, tetapi bagaimana jika, dengan data otorisasi yang berbeda, nilai yang dikembalikan berbeda? Lihat ini.

Script akan membantu dalam hal ini:

 #include <idc.idc> static main() { auto pc_val=0, d0_val; while(pc_val != 0x00001F16){ pc_val = GetRegValue("pc"); if (pc_val == 0x00001F9C) StepInto(); else StepOver(); GetDebuggerEvent(WFNE_SUSP, -1); if (pc_val == 0x00000674){ d0_val = GetRegValue("d0") & 0xFF; print(d0_val); } } } 

Saya baru saja membandingkan keluaran ke konsol dengan kunci berbeda, surat.

Menjalankan skrip beberapa kali dengan tombol yang berbeda, kita akan melihat bahwa fungsi sub_5EC selalu mengembalikan nilai berikutnya dari array:

 def sub_5EC_gen(): dump = [0x92, 0x8A, 0xDC, 0xDC, 0x94, 0x3B, 0xE4, 0xE4, 0xFC, 0xB3, 0xDC, 0xEE, 0xF4, 0xB4, 0xDC, 0xDE, 0xFE, 0x68, 0x4A, 0xBD, 0x91, 0xD5, 0x0A, 0x27, 0xED, 0xFF, 0xC2, 0xA5, 0xD6, 0xBF, 0xDE, 0xFA, 0xA6, 0x72, 0xBF, 0x1A, 0xF6, 0xFA, 0xE4, 0xE7, 0xFA, 0xF7, 0xF6, 0xD6, 0x91, 0xB4, 0xB4, 0xB5, 0xB4, 0xF4, 0xA4, 0xF4, 0xF4, 0xB7, 0xF6, 0x09, 0x20, 0xB7, 0x86, 0xF6, 0xE6, 0xF4, 0xE4, 0xC6, 0xFE, 0xF6, 0x9D, 0x11, 0xD4, 0xFF, 0xB5, 0x68, 0x4A, 0xB8, 0xD4, 0xF7, 0xAE, 0xFF, 0x1C, 0xB7, 0x4C, 0xBF, 0xAD, 0x72, 0x4B, 0xBF, 0xAA, 0x3D, 0xB5, 0x7D, 0xB5, 0x3D, 0xB9, 0x7D, 0xD9, 0x7D, 0xB1, 0x13, 0xE1, 0xE1, 0x02, 0x15, 0xB3, 0xA3, 0xB3, 0x88, 0x9E, 0x2C, 0xB0, 0x8F] l = len(dump) offset = 0 while offset < l: yield dump[offset] offset += 1 

Jadi sub_5EC siap.

Baris berikutnya adalah sub_E3E .

sub_E3E


Sepotong kode yang signifikan:

gambar

Dekripsi:

    ,   d2,     .  a2   0xFF0D46, a2 + 0x34 = 0xFF0D7A d0 = *(a2 + 0x34) *(a2 + 0x34) = *(a2 + 0x34) + 1   ,   a0    a0 = d0 *(a0) = d2    offset,     d2.  a2   0xFF0D46, a2 + 0x24 = 0xFF0D6A -  ,     (. )  0x00000000,     d0 = *(a2 + 0x24) d2 = d0 ^ d2 d2 = d2 & 0xFF d2 = d2 + d2  - 2    0x00011FC0 + d2,   ROM,   0x00011FC0 + d2  a0 = 0x00011FC0 d2 = *(a0 + d2)       8  d0 = d0 >> 8  d2 = d0 ^ d2     *(a2 + 0x24) = d2 

Fungsi sub_E3E turun ke langkah-langkah ini:

  1. Simpan argumen input ke array.
  2. Hitung offset offset.
  3. Tarik 2 byte di alamat 0x00011FC0 + offset (ROM).
  4. Hasil = ( >> 8) ^ (2 0x00011FC0 + offset) .

Bayangkan fungsi sub_E3E dalam formulir ini:

 def sub_E3E(prev_sub_E3E_result, d2, d2_storage): def calc_offset(): return 2 * ((prev_sub_E3E_result ^ d2) & 0xff) d2_storage.append(d2) offset = calc_offset() with open("dump_00011FC0", 'rb') as f: dump_00011FC0_4096b = f.read() some = dump_00011FC0_4096b[offset:offset + 2] some = int.from_bytes(some, byteorder="big") prev_sub_E3E_result = prev_sub_E3E_result >> 8 return prev_sub_E3E_result ^ some 

dump_00011FC0 hanyalah sebuah file tempat saya menyimpan 4096 byte dari [0x00011FC0:00011FC0+4096] .

Aktivitas sekitar 1FC4


Kami belum melihat alamat 0x00001FC4 , tetapi mudah ditemukan, karena blok berjalan hampir segera setelah siklus pertama.

gambar

Blok ini mengubah konten di alamat 0x00FF0D46 (register a2 ), dan di situlah hash kunci disimpan, jadi kami sekarang mempelajari blok ini. Mari kita lihat apa yang terjadi di sini.

  1. Kondisi yang menentukan apakah cabang kiri atau kanan dipilih adalah: ( ) & 0b1 != 0 . Yaitu, bit pertama dari hash diperiksa.
  2. Jika Anda melihat kedua cabang, Anda akan melihat:
    • Dalam kedua kasus, pergeseran ke kanan dengan 1 bit terjadi.
    • Di cabang kiri operasi hash dilakukan 0x8000 .
    • Dalam kedua kasus, nilai hash yang diproses ditulis ke alamat 0x00FF0D46 , yaitu, hash diganti dengan nilai baru.
    • Perhitungan lebih lanjut tidak kritis, karena, secara kasar, tidak ada operasi tulis di (a2) (tidak ada instruksi di mana operan kedua akan berada (a2) ).

Bayangkan sebuah blok seperti ini:

 def transform(hash_2b): new = hash_2b >> 1 if hash_2b & 0b1 != 0: new = new | 0x8000 return new 

Loop penting kedua adalah loc_203E


loc_203E - loop, karena 0x0000206C bne.s loc_203E .

gambar

Siklus ini menghitung hash dan di sini adalah fitur utamanya: jsr (a0) adalah panggilan ke fungsi sub_E3E yang telah kita periksa - ia bergantung pada hasil sebelumnya dari pekerjaannya sendiri dan pada beberapa masukan argumen (dilewatkan melalui register d2 atas, dan di sini melalui d0 )

Mari cari tahu apa yang diteruskan padanya melalui register d0 .

Kami telah bertemu dengan konstruksi 0x34(a2) - fungsi sub_E3E menyimpan argumen yang diteruskan di sana. Ini berarti bahwa argumen yang diteruskan sebelumnya digunakan dalam loop ini. Tapi tidak semua.

Dekripsi bagian kode:

   2    a2+0x1C move.w 0x1C(a2), d0  neg.l d0   a0       sub_E3E movea.l 0x34(a2), a0 ,  d0  2    a0-d0(   d0 ) move.b (a0, d0.l), d0 

Intinya adalah tindakan sederhana: pada setiap iterasi, ambil d0 argumen yang tersimpan dari akhir array. Artinya, jika 4 disimpan dalam d0 , maka kita mengambil elemen keempat dari akhir.

Jika demikian, apa tepatnya yang d0 ? Di sini saya melakukannya tanpa skrip, tetapi cukup menuliskannya, membuat istirahat di awal blok ini. Inilah mereka: 0x04, 0x04, 0x04, 0x1C, 0x1A, 0x1A, 0x06, 0x42, 0x02 .

Sekarang kita memiliki segalanya untuk menulis fungsi perhitungan hash kunci lengkap.

Fungsi perhitungan hash penuh


 def finish_hash(hash_2b): #    def transform(hash_2b): new = hash_2b >> 1 if hash_2b & 0b1 != 0: new = new | 0x8000 return new main_cycle_counter = [17, 2, 2, 3, 4, 38, 10, 30, 4] second_cycle_counter = [2, 2, 2, 2, 2, 4, 2, 4, 28] counters = list(zip(main_cycle_counter, second_cycle_counter)) d2_storage = [] storage_offsets = [0x04, 0x04, 0x04, 0x1C, 0x1A, 0x1A, 0x06, 0x42, 0x02] prev_sub_E3E_result = 0x0000 sub_5EC = sub_5EC_gen() for i in range(9): c = counters[i] for _ in range(c[0]): d0 = next(sub_5EC) d1 = hash_2b & 0xff d2 = d0 ^ d1 curr_sub_E3E_result = sub_E3E(prev_sub_E3E_result, d2, d2_storage) prev_sub_E3E_result = curr_sub_E3E_result storage_offset = storage_offsets.pop(0) for _ in range(c[1]): d2 = d2_storage[-storage_offset] curr_sub_E3E_result = sub_E3E(prev_sub_E3E_result, d2, d2_storage) prev_sub_E3E_result = curr_sub_E3E_result hash_2b = transform(hash_2b) return curr_sub_E3E_result 

Pemeriksaan Kesehatan


  1. Dalam debugger, kami menetapkan break ke alamat 0x0000180A move.l 0x1000,(sp) (segera setelah perhitungan hash).
  2. Istirahat ke alamat 0x00001F16 beq.w loc_20EA (perbandingan hash akhir dengan 0xCB4C konstan).
  3. Di program, masukkan kunci ABCDEFGHIJKLMNOP , tekan Enter .
  4. Debugger berhenti di 0x0000180A , dan kami melihat bahwa nilai 0xFFFF44CC 0x44CC register d5 , 0x44CC adalah hash pertama.
  5. Kami memulai debugger lebih lanjut.
  6. Kami berhenti di 0x00001F16 dan melihat bahwa pada 0x00FF0D6A terletak 0x4840 - hash terakhir
    gambar
  7. Sekarang periksa fungsi finish_hash (hash_2b) kami:
     >>> r = finish_hash(0x44CC) >>> print(hex(r)) 0x4840 

Kami mencari kunci yang tepat 1


Kunci yang benar adalah kunci ini yang hash terakhirnya adalah 0xCB4C (ditemukan di atas). Maka pertanyaannya: apa yang harus menjadi hash pertama untuk final menjadi 0xCB4C ?

Sekarang mudah untuk mengetahuinya:

 def find_CB4C(): result = [] for hash_2b in range(0xFFFF+1): final_hash = finish_hash(hash_2b) if final_hash == 0xCB4C: result.append(hash_2b) return result >>> r = find_CB4C() >>> print(r) 

Output dari program menunjukkan bahwa hanya ada satu opsi: hash pertama harus 0xFEDC .

Karakter apa yang kita butuhkan sehingga hash pertama mereka adalah 0xFEDC ?

Karena 0xFEDC = __4_ ^ __4_ , Anda hanya perlu menemukan __4_ , karena __4_ = __4_ ^ 0xFEDC . Dan kemudian memecahkan kode kedua hash.

Algoritma adalah sebagai berikut:

 def get_first_half(): from collections import deque from random import randint def get_pairs(): pairs = [] for i in range(0xFFFF + 1): pair = (i, i ^ 0xFEDC) pairs.append(pair) pairs = deque(pairs) pairs.rotate(randint(0, 0xFFFF)) return list(pairs) pairs = get_pairs() for pair in pairs: key_4s_0 = decode_hash_4s(pair[0]) key_4s_1 = decode_hash_4s(pair[1]) hash_2b_0 = get_hash_2b(key_4s_0) hash_2b_1 = get_hash_2b(key_4s_1) if hash_2b_0 == pair[0] and hash_2b_1 == pair[1]: return key_4s_0, key_4s_1 

Banyak pilihan, pilih apa saja.

Kami mencari kunci yang tepat 2


Bagian pertama dari kunci sudah siap, bagaimana dengan yang kedua?

Ini adalah bagian yang paling mudah.

Sepotong kode yang bertanggung jawab terletak di 0x00FF2012 , saya mendapatkannya dengan penelusuran manual, dimulai dengan alamat 0x00001F16 beg.w loc_20EA (validasi pada paruh pertama kunci). Dalam register a0 adalah alamat email, loc_FF2012 adalah sebuah siklus, karena bne.s loc_FF2012 . Ini dieksekusi selama ada *(a0+d0) (byte surat berikutnya).

Dan jsr (a3) memanggil fungsi get_hash_2b sudah dikenal, yang sekarang bekerja dengan bagian kedua kunci.

gambar

Mari kita buat kode lebih jelas:

 while(d1 != 0x20){    d2++ d1 = d1 & 0xFF     d3 = d3 + d1 d0 = 0 d0 = d2    d1 = *(a0+d0) } d0 = get_hash_2b(key_byte_8) d3 = d0^d3 d0 = get_hash_2b(key_byte_12) d2 = d2 - 1 d2 = d2 << 8 d2 = d0^d2 if (d2 == d3) success_branch 

Dalam register d2 - ( -1) << 8 . Di d3 , jumlah byte karakter surat.

Kriteria kebenaran adalah sebagai berikut: __ ^ d2 == ___2 ^ d3 .

Kami menulis fungsi pemilihan bagian kedua kunci:

 def get_second_half(email): from collections import deque from random import randint def get_koeff(): k1 = sum([ord(c) for c in email]) k2 = (len(email) - 1) << 8 return k1, k2 def get_pairs(k1, k2): pairs = [] for a in range(0xFFFF + 1): pair = (a, (a ^ k1) ^ k2) pairs.append(pair) pairs = deque(pairs) pairs.rotate(randint(0, 0xFFFF)) return list(pairs) k1, k2 = get_koeff() pairs = get_pairs(k1, k2) for pair in pairs: key_4s_0 = decode_hash_4s(pair[0]) key_4s_1 = decode_hash_4s(pair[1]) hash_2b_0 = get_hash_2b(key_4s_0) hash_2b_1 = get_hash_2b(key_4s_1) if hash_2b_0 == pair[0] and hash_2b_1 == pair[1]: return key_4s_0, key_4s_1 


Keygen


Surat harus dalam batas.

 def keygen(email): first_half = get_first_half() second_half = get_second_half(email) return "".join(first_half) + "".join(second_half) >>> email = "M.GAYANOV@GMAIL.COM" >>> print(keygen(email)) 2A4FD493BA32AD75 

gambar

Terima kasih atas perhatian anda! Semua kode tersedia di sini .

Source: https://habr.com/ru/post/id453194/


All Articles