Membalikkan rekayasa emulator NES dalam game untuk GameCube

gambar

Saat mencari cara untuk mengaktifkan menu pengembang yang tersisa di Animal Crossing, termasuk menu pemilihan gim untuk emulator NES, saya menemukan fungsi menarik yang ada di gim asli dan terus-menerus aktif, tetapi tidak pernah digunakan oleh Nintendo.

Selain game NES / Famicom dalam game, Anda dapat mengunduh game NES baru dari kartu memori.

Saya juga berhasil menemukan cara untuk menggunakan bootloader ROM ini untuk menambal kode dan data saya ke dalam permainan, yang memungkinkan Anda untuk mengeksekusi kode melalui kartu memori.

Pendahuluan - Objek Konsol NES


Gim NES biasa, yang dapat diperoleh dari Animal Crossing, adalah bagian-bagian furnitur yang terpisah dalam bentuk konsol NES dengan kartrid yang diletakkan di atasnya.

Setelah menemukan objek ini di rumah Anda dan berinteraksi dengannya, Anda dapat menjalankan satu-satunya permainan ini. Gambar di bawah ini menunjukkan Excitebike dan Golf.


Ada juga objek Konsol NES yang umum di mana tidak ada game bawaan. Ini dapat dibeli dari Redd, dan kadang-kadang diperoleh melalui peristiwa acak, misalnya, dengan membaca di papan pengumuman kota bahwa konsol dimakamkan di titik acak di kota.


Objek ini terlihat seperti konsol NES di mana tidak ada kartrid.


Masalah dengan objek ini adalah ia dianggap tidak dapat dimainkan. Setiap kali Anda berinteraksi dengannya, Anda hanya melihat pesan yang mengatakan bahwa Anda tidak memiliki perangkat lunak game.


Ternyata objek ini sebenarnya mencoba memindai kartu memori untuk file yang dirancang khusus berisi gambar ROM untuk NES! Emulator NES yang digunakan untuk menjalankan game tertanam tampaknya merupakan emulator NES standar penuh untuk GameCube, dan mampu meluncurkan sebagian besar game.

Sebelum menunjukkan fitur-fitur ini, saya akan menjelaskan proses rekayasa balik mereka.

Cari ROM bootloader pada kartu memori


Kami mencari menu pengembang


Awalnya, saya ingin menemukan kode yang mengaktifkan berbagai menu pengembang, seperti menu pemilihan peta atau menu pilihan permainan untuk emulator NES. Menu Forest Map Select , berkat Anda dapat dengan mudah memuat lokasi yang berbeda dari permainan, cukup mudah ditemukan - Saya hanya mencari garis FOREST MAP SELECT yang muncul di bagian atas layar (dapat dilihat di berbagai video dan tangkapan layar di Internet) )

Dalam “FOREST MAP SELECT” ada referensi silang data ke fungsi select_print_wait , yang mengarah ke sekelompok fungsi lain yang juga memiliki awalan select_* , termasuk fungsi select_init . Mereka ternyata adalah fungsi yang mengontrol menu pemilihan peta.

Fungsi select_init mengarah ke fungsi menarik lainnya yang disebut game_get_next_game_dlftbl . Fungsi ini menghubungkan semua menu dan "adegan" lain yang dapat Anda jalankan: layar dengan logo Nintendo, layar utama, menu pilihan kartu, menu emulator NES (Famicom), dan sebagainya. Itu dimulai pada awal prosedur permainan utama, menemukan fungsi inisialisasi adegan mana yang harus dijalankan, dan menemukan entri dalam struktur data tabel yang disebut game_dlftbls . Tabel ini berisi tautan ke fungsi pemrosesan berbagai adegan, serta beberapa data lainnya.


Sebuah penelitian yang cermat terhadap blok pertama dari fungsi menunjukkan bahwa ia memuat fungsi "next game init", dan kemudian mulai membandingkannya dengan serangkaian fungsi init yang terkenal:

  • first_game_init
  • select_init
  • play_init
  • second_game_init
  • trademark_init
  • player_select_init
  • save_menu_init
  • famicom_emu_init
  • prenmi_init


Salah satu fungsi pointer yang dia cari adalah famicom_emu_init , yang bertanggung jawab untuk menjalankan emulator NES / Famicom. game_get_next_game_init hasil game_get_next_game_init ke famicom_emu_init atau famicom_emu_init di debugger Dolphin, saya dapat menampilkan menu khusus. Langkah selanjutnya adalah menentukan bagaimana pointer ini diatur dengan cara normal selama eksekusi program. Satu-satunya hal yang fungsi game_get_next_game_init adalah memuat nilai pada offset 0xC argumen pertama ke game_get_next_game_dlftbl .

Melacak nilai-nilai ini yang diatur dalam berbagai struktur data sedikit membosankan, jadi saya akan langsung ke inti. Hal terpenting yang saya temukan:

  • Ketika permainan dimulai dengan cara yang biasa, ia melakukan urutan tindakan berikut:
    • first_game_init
    • second_game_init
    • trademark_init
    • play_init
  • player_select_init menetapkan init berikutnya untuk select_init . Layar ini seharusnya memungkinkan Anda untuk memilih pemain segera setelah memilih kartu, tetapi tampaknya itu tidak berfungsi dengan benar.

Saya juga menemukan satu fungsi tanpa nama yang mendefinisikan fungsi init emulator, tetapi saya tidak menemukan apa pun yang mengatur fungsi init ke nilai init dari pemain atau pilihan peta.

Pada titik ini, saya menyadari bahwa saya memiliki masalah bodoh lain dengan cara saya memuat nama fungsi di IDA: karena ekspresi reguler yang digunakan untuk memotong garis dalam file simbol debug, saya melewatkan semua nama fungsi mulai dengan huruf besar . Fungsi yang diatur oleh famicom_emu_init tampak seperti transisi antar adegan, dan, tentu saja, disebut Game_play_fbdemo_wipe_proc .

Game_play_fbdemo_wipe_proc menangani transisi antar adegan, seperti screen Game_play_fbdemo_wipe_proc dan blackout.

Dalam kondisi tertentu, transisi layar dilakukan dari gameplay biasa ke tampilan emulator. Dialah yang mengatur fungsi init emulator.

Menangani Objek Konsol


Sebenarnya, penangan objek furnitur untuk konsol NES membuat pengalih transisi layar beralih ke emulator. Ketika seorang pemain berinteraksi dengan salah satu konsol, aMR_FamicomEmuCommonMove .

Saat memanggil fungsi, r6 berisi nilai indeks yang sesuai dengan angka dalam nama file game NES di famicom.arc :

  • 01_nes_cluclu3.bin.szs
  • 02_usa_balloon.nes.szs
  • 03_nes_donkey1_3.bin.szs
  • 04_usa_jr_math.nes.szs
  • 05_pinball_1.nes.szs
  • 06_nes_tennis3.bin.szs
  • 07_usa_golf.nes.szs
  • 08_punch_wh.nes.szs
  • 09_usa_baseball_1.nes.szs
  • 10_cluclu_1.qd.szs
  • 11_usa_donkey3.nes.szs
  • 12_donkeyjr_1.nes.szs
  • 13_soccer.nes.szs
  • 14_exbike.nes.szs
  • 15_usa_wario.nes.szs
  • 16_usa_icecl.nes.szs
  • 17_nes_mario1_2.bin.szs
  • 18_smario_0.nes.szs
  • 19_usa_zelda1_1.nes.szs

( .arc adalah format arsip file berpemilik.)

Ketika r6 tidak sama dengan nol, itu diteruskan dalam panggilan aMR_RequestStartEmu . Dalam hal ini, transisi ke emulator dipicu.


Namun, jika r6 adalah nol, maka fungsi aMR_RequestStartEmu_MemoryC dipanggil aMR_RequestStartEmu_MemoryC . Menetapkan nilai dalam debugger ke 0, saya menerima pesan "Saya tidak memiliki perangkat lunak apa pun." Saya tidak segera ingat bahwa saya perlu memeriksa objek Konsol NES untuk memastikannya menyetel ulang nilai r6 , tetapi ternyata indeks nol digunakan untuk objek konsol tanpa kartrid.

Meskipun aMR_RequestStartEmu hanya menyimpan nilai indeks dalam beberapa jenis struktur data, aMR_RequestStartEmu_MemoryC melakukan operasi yang jauh lebih kompleks ...


Blok kode ketiga ini memanggil aMR_GetCardFamicomCount dan memeriksa hasil yang tidak nol, jika tidak, melewatkan sebagian besar hal menarik di sisi kiri grafik fungsi.

aMR_GetCardFamicomCount memanggil famicom_get_disksystem_titles , yang kemudian memanggil memcard_game_list , dan di sini semuanya menjadi sangat menarik.

memcard_game_list memasang kartu memori dan mulai berkeliling dalam siklus tulis file, memeriksa masing-masing dari beberapa nilai. Dengan melacak fungsi dalam debugger, saya dapat memahami bahwa itu membandingkan nilai dengan masing-masing file saya pada kartu memori.


Fungsi memutuskan apakah akan mengunduh file atau tidak, tergantung pada hasil pemeriksaan beberapa baris. Pertama, ia memeriksa keberadaan garis "GAFE" dan "01", yang merupakan pengidentifikasi permainan dan perusahaan. 01 adalah singkatan dari Nintendo, GAFE adalah singkatan dari Animal Crossing. Saya pikir itu singkatan dari GameCube Animal Forest English.

Dia kemudian memeriksa baris "DobutsunomoriP_F_" dan "SAVE". Dalam hal ini, baris pertama harus cocok, tetapi bukan yang kedua. Ternyata "DobutsunomoriP_F_SAVE" adalah nama file yang menyimpan data game yang disematkan untuk NES. Oleh karena itu, semua file kecuali yang ini akan dimuat dengan awalan "DobutsunomoriP_F_".

Menggunakan debugger Dolphin untuk melewati perbandingan string dengan "SIMPAN" dan membuat trik permainan untuk percaya bahwa file "SIMPAN" saya dapat diunduh dengan aman, saya mendapatkan menu ini setelah menggunakan konsol NES:


Saya menjawab "Ya" dan mencoba memuat file save sebagai game, setelah itu saya pertama kali melihat layar crash game bawaan:


Hebat! Sekarang saya tahu bahwa dia sebenarnya mencoba mengunduh permainan dari kartu memori, dan saya dapat mulai menganalisis format untuk menyimpan file untuk melihat apakah ROM yang sebenarnya dapat diunduh.

Hal pertama yang saya coba lakukan adalah mencoba mencari di mana nama permainan dibaca dari file kartu memori. Mencari baris "FEFSC" yang ada di pesan "Apakah Anda ingin memutar <nama>?", Saya menemukan offset di mana ia dibaca dari file: 0x642 . Saya menyalin file simpan, mengubah nama file menjadi “DobutsunomoriP_F_TEST”, mengubah byte pada offset 0x642 menjadi “TESTING” dan mengimpor save yang diubah, setelah itu nama yang saya butuhkan muncul di menu.

Setelah menambahkan beberapa file lagi dalam format ini, beberapa opsi muncul di menu:


Unduh ROM


Jika aMR_GetCardFamicomCount dikembalikan bukan nol, maka memori dialokasikan pada heap, famicom_get_disksystem_titles secara langsung dipanggil famicom_get_disksystem_titles , setelah itu sekelompok offset acak ditentukan dalam struktur data. Alih-alih menguraikan di mana nilai-nilai ini akan dibaca, saya mulai mempelajari daftar fungsi famicom .

Ternyata saya membutuhkan famicom_rom_load . Ini mengontrol pemuatan ROM, baik dari kartu memori, atau dari sumber daya internal permainan.


Yang paling penting di blok "boot from memory card" ini adalah ia memanggil
memcard_game_load . Dia me-mount file pada kartu memori lagi, membacanya dan mem-parsing. Di sinilah opsi format file yang paling penting menjadi jelas.

Nilai checksum


Hal pertama yang terjadi setelah file diunggah adalah perhitungan checksum. Fungsi calcSum , yang merupakan algoritma yang sangat sederhana yang menjumlahkan nilai semua byte dalam data dari kartu memori. Delapan bit terbawah dari hasilnya harus nol. Artinya, untuk lulus pemeriksaan ini, Anda perlu menjumlahkan nilai semua byte dalam file sumber, menghitung nilai yang perlu ditambahkan sehingga delapan bit terbawah menjadi nol, dan kemudian menetapkan nilai ini ke byte checksum dalam file.

Jika verifikasi gagal, maka Anda menerima pesan tentang ketidakmungkinan membaca dengan benar kartu memori, dan tidak ada yang terjadi. Selama debugging, yang harus saya lakukan adalah melewati pemeriksaan ini.

Salin ROM


Menjelang akhir memcard_game_load , hal menarik lainnya terjadi. Ada beberapa blok kode yang lebih menarik antara itu dan checksum, tetapi tidak ada satupun yang mengarah ke percabangan yang mengabaikan pelaksanaan perilaku ini.


Jika nilai integer 16-bit tertentu yang dibaca dari kartu memori tidak sama dengan nol, maka sebuah fungsi dipanggil yang memeriksa header kompresi di buffer. Itu memeriksa untuk format kompresi Nintendo eksklusif dengan melihat awal Yay0 atau buffer Yaz0. Jika salah satu dari baris ini ditemukan, fungsi unpack disebut. Jika tidak, fungsi salin salinan sederhana dilakukan. Bagaimanapun, setelah itu, variabel yang disebut nesinfo_data_size .

Petunjuk lain dari konteks di sini adalah bahwa file ROM untuk permainan NES tertanam menggunakan kompresi Yaz0, dan baris ini ada di header file mereka.

Setelah mengamati nilai yang diperiksa nol, dan buffer dialihkan ke fungsi pemeriksaan kompresi, saya segera mengetahui dari mana game itu dibaca dari dalam file pada kartu memori. Pemeriksaan nol dilakukan untuk bagian buffer 32-byte yang disalin dari offset 0x640 dalam file, yang kemungkinan besar adalah header ROM. Fungsi ini juga memeriksa bagian lain dari file tersebut, dan di dalamnya ada nama game tersebut (dimulai dengan byte ketiga dari header).

Di jalur eksekusi kode yang saya temukan, buffer ROM terletak tepat setelah buffer header 32-byte ini.


Informasi ini cukup untuk mencoba membuat file ROM yang berfungsi. Saya hanya mengambil salah satu file Animal Crossing lainnya dan mengeditnya di hex editor untuk mengganti nama file dengan DobutsunomoriP_F_TEST dan menghapus semua area di mana saya ingin menempelkan data.

Untuk uji coba, saya menggunakan Pinball game ROM, yang sudah ada dalam game, dan memasukkan isinya setelah header 32-byte. Alih-alih menghitung nilai checksum, saya mengatur breakpoint sehingga saya hanya melewatkan calcSum dan juga mengamati hasil pemeriksaan lain yang mungkin mengarah ke cabang yang melewatkan proses boot ROM.

Akhirnya, saya mengimpor file baru melalui manajer kartu memori Dolphin, memulai kembali permainan, dan mencoba untuk meluncurkan konsol.



Berhasil! Ada beberapa bug grafis kecil yang terkait dengan parameter Dolphin, yang memengaruhi mode grafis yang digunakan oleh emulator NES, tetapi secara umum gim ini bekerja dengan baik. (Pada build Dolphin yang lebih baru, ini seharusnya berfungsi secara default.)

Untuk memastikan bahwa permainan lain juga mulai, saya mencoba menulis beberapa ROM lain yang tidak ada dalam permainan. Battletoads dimulai, tetapi berhenti bekerja setelah teks layar splash (setelah pengaturan lebih lanjut saya berhasil membuatnya dimainkan). Mega Man, di sisi lain, bekerja dengan sempurna:


Untuk mempelajari cara membuat file ROM baru yang dapat dimuat tanpa campur tangan para debugger, saya harus mulai menulis kode dan lebih memahami parsing format file.

Format File ROM Eksternal


Bagian terpenting dari penguraian file terjadi di memcard_game_load . Ada enam bagian utama blok parsing kode dalam fungsi ini:

  • Checksum
  • Simpan nama file
  • Header file ROM
  • Buffer tidak dikenal disalin tanpa pemrosesan apa pun
  • Komentar teks, ikon dan pemuat spanduk (untuk membuat file penyimpanan baru)
  • Bootloader ROM


Checksum


Delapan bit terbawah dari jumlah semua nilai byte dalam file save harus nol. Berikut ini adalah kode Python sederhana yang menghasilkan byte checksum yang diperlukan:

 checksum = 0 for byte_val in new_data_tmp: checksum += byte_val checksum = checksum % (2**32) # keep it 32 bit checkbyte = 256 - (checksum % 256) new_data_tmp[-1] = checkbyte 

Mungkin ada tempat khusus untuk menyimpan byte checksum, tetapi menambahkannya ke ruang kosong di akhir file save berfungsi dengan baik.

Nama file


Sekali lagi, nama file save harus dimulai dengan "DobutsunomoriP_F_" dan diakhiri dengan sesuatu yang tidak mengandung "SAVE". Nama file ini disalin beberapa kali, dan dalam satu kasus huruf "F" diganti dengan "S". Ini akan menjadi nama file penyimpanan untuk game NES ("DobutsunomoriP_S_NAME").

Header ROM


Salinan langsung header 32-byte dimuat ke dalam memori. Beberapa nilai dalam header ini digunakan untuk menentukan cara menangani bagian selanjutnya. Pada dasarnya, ini adalah beberapa nilai ukuran 16-bit dan bit parameter yang dikemas.

Jika Anda melacak pointer yang disalin oleh header sampai ke awal fungsi dan menemukan posisi argumennya, tanda tangan dari fungsi di bawah ini akan menunjukkan bahwa ia sebenarnya memiliki tipe MemcardGameHeader_t* .

 memcard_game_load(unsigned char *, int, unsigned char **, char *, char *, MemcardGameHeader_t *, unsigned char *, unsigned long, unsigned char *, unsigned long) 

Buffer tidak dikenal


Periksa nilai ukuran 16-bit dari header. Jika tidak sama dengan nol, maka jumlah byte yang sesuai secara langsung disalin dari buffer file ke blok baru memori yang dialokasikan. Ini memindahkan penunjuk data dalam buffer file sehingga penyalinan lebih lanjut dapat dilanjutkan dari bagian berikutnya.

Spanduk, ikon dan komentar


Nilai ukuran lain diperiksa di header, dan jika tidak sama dengan nol, fungsi pemeriksaan kompresi file dipanggil. Jika perlu, algoritma pembongkaran akan diluncurkan, setelah itu SetupExternCommentImage .

Fungsi ini melakukan tiga hal: "komentar", gambar spanduk dan ikon. Untuk masing-masing, ada kode di header ROM yang menunjukkan cara menanganinya. Ada beberapa opsi berikut:

  1. Gunakan nilai default
  2. Salin dari bagian banner / ikon / komentar dalam file ROM
  3. Salin dari buffer alternatif

Nilai default kode menyebabkan ikon atau spanduk dimuat dari sumber pada disk, dan nama file simpan dan komentar (deskripsi teks dari file) diberi nilai "Animal Crossing" dan "NES Cassette Save Data". Begini tampilannya:


Nilai kedua kode hanya menyalin nama permainan dari file ROM (alternatif untuk "Animal Crossing"), dan kemudian mencoba untuk menemukan string "] ROM" di komentar file dan menggantinya dengan "] SIMPAN". Rupanya, file yang ingin dirilis Nintendo seharusnya dalam format nama "Game Name [NES] ROM" atau yang serupa.

Untuk ikon dan spanduk, kode mencoba menentukan format gambar, mendapatkan nilai ukuran tetap yang sesuai dengan format ini, dan kemudian menyalin gambar.

Pada nilai kode terakhir, nama file dan deskripsi disalin tanpa perubahan dari buffer, dan ikon dan spanduk juga diambil dari buffer alternatif.

ROM


Jika Anda dengan hati-hati melihat tangkapan layar memcard_game_load copying ROM, Anda dapat melihat bahwa nilai 16-bit diperiksa untuk kesetaraan ke nol digeser ke kiri oleh 4 bit (dikalikan dengan 16), dan kemudian digunakan sebagai ukuran fungsi memcpy jika kompresi tidak terdeteksi. Ini adalah nilai ukuran lain yang ada di header.

Jika ukurannya tidak sama dengan nol, maka data ROM diperiksa untuk kompresi, dan kemudian disalin.

Pencarian buffer dan bug tidak dikenal


Walaupun mengunduh ROM baru cukup aneh, hal yang paling menarik tentang loader ROM ini bagi saya adalah bahwa sebenarnya ini adalah satu-satunya bagian dari permainan yang menerima input pengguna dengan ukuran variabel dan menyalinnya ke lokasi memori yang berbeda. Hampir semuanya menggunakan buffer ukuran konstan. Hal-hal seperti nama dan teks huruf mungkin tampak berbeda panjangnya, tetapi pada dasarnya ruang kosong hanya diisi dengan spasi. String zero-terminated jarang digunakan, menghindari bug kerusakan memori umum, seperti menggunakan strcpy dengan buffer yang terlalu kecil untuk menyalin string ke dalamnya.

Saya sangat tertarik dengan kemungkinan menemukan exploit dari gim tersebut berdasarkan pada menyimpan file, dan sepertinya ini adalah pilihan terbaik.

Sebagian besar operasi file ROM yang dijelaskan di atas menggunakan salinan ukuran konstan, dengan pengecualian buffer dan data ROM yang tidak diketahui. Sayangnya, kode yang memproses buffer ini mengalokasikan ruang yang sama persis dengan yang diperlukan untuk menyalinnya, sehingga tidak ada overflow, dan pengaturan ukuran file ROM yang sangat besar tidak terlalu berguna.

Tetapi saya masih ingin tahu apa yang terjadi pada buffer ini, yang disalin tanpa pemrosesan apa pun.

Penangan Label Informasi NES


Saya kembali ke famicom_rom_load . Setelah memuat ROM dari kartu memori atau disk, beberapa fungsi dipanggil:

  • nesinfo_tag_process1
  • nesinfo_tag_process2
  • nesinfo_tag_process3

Setelah melacak tempat penyangga yang tidak dikenal disalin, saya memastikan bahwa tugas ini dilakukan oleh fungsi-fungsi ini. Mereka mulai dengan panggilan ke nesinfo_next_tag , yang melakukan algoritma sederhana:

  • Cek apakah pointer yang ditentukan nesinfo_tags_end pointer di nesinfo_tags_end . Jika kurang dari nesinfo_tags_end atau nesinfo_tags_end adalah nol, maka ia memeriksa keberadaan string "END" di header pointer.

    • Jika "END" tercapai, atau penunjuk telah naik ke atau di atas nesinfo_tags_end , maka fungsi mengembalikan nol.
    • Jika tidak, byte pada offset 0x3 pointer ditambahkan ke 4 dan ke pointer saat ini, setelah itu nilai dikembalikan.

Ini memberitahu kita bahwa ada beberapa jenis format label dari nama tiga huruf, nilai ukuran data dan data itu sendiri. Hasilnya adalah penunjuk ke label berikutnya, karena label saat ini dilewati ( cur_ptr + 4 melompati nama tiga huruf dan satu byte, dan size_byte melewatkan data).

Jika hasilnya tidak nol, maka fungsi pemrosesan label melakukan serangkaian perbandingan string untuk mengetahui label mana yang perlu diproses. Beberapa nama label yang diperiksa di nesinfo_tag_process1 : VEQ, VNE, GID, GNO, BBR, dan QDS.


Jika kecocokan label ditemukan, beberapa kode handler dijalankan. Beberapa penangan tidak melakukan apa pun selain menampilkan label dalam pesan debug. Lainnya memiliki penangan yang lebih kompleks. Setelah memproses label, fungsi mencoba untuk mendapatkan label berikutnya dan melanjutkan pemrosesan.

Untungnya, ada banyak pesan debugging terperinci yang ditampilkan ketika tag terdeteksi.Mereka semua dalam bahasa Jepang, jadi pertama-tama mereka harus diterjemahkan dari Shift-JIS dan diterjemahkan. Misalnya, pesan untuk QDS mungkin bertuliskan "Memuat area penyimpanan disk" atau "Karena ini adalah proses pertama, buat area penyimpanan disk". Pesan untuk BBR bertuliskan "memuat cadangan baterai" atau "karena ini adalah awal pertama, kami melakukan pembersihan".

Kedua kode ini juga memuat beberapa nilai dari bagian data label mereka dan menggunakannya untuk menghitung offset dalam data ROM, setelah itu mereka melakukan operasi penyalinan. Jelas, mereka bertanggung jawab untuk menentukan bagian-bagian dalam memori ROM yang terkait dengan pelestarian negara.

Ada juga tag "HSC" dengan pesan debug yang mengatakan bahwa ia sedang memproses catatan poin. Dia mendapatkan offset dalam ROM dari data tag-nya, serta nilai catatan skor asli. Tanda ini dapat digunakan untuk menunjukkan tempat di memori game NES untuk menyimpan skor tinggi, mungkin untuk menyimpan dan mengembalikannya di masa mendatang.

Tag ini membuat sistem pengunduhan metadata ROM yang cukup rumit. Selain itu, banyak dari mereka mengarah pada panggilan memcpyberdasarkan nilai yang dikirimkan dalam label data.

Perburuan serangga


Kebanyakan tag yang mengarah pada manipulasi memori tidak terlalu berguna untuk eksploitasi, karena semuanya memiliki nilai offset dan ukuran maksimum yang ditentukan sebagai bilangan bulat 16-bit. Ini cukup untuk bekerja dengan ruang alamat NES 16-bit, tetapi tidak cukup untuk menulis nilai target yang bermanfaat, seperti pointer ke fungsi atau mengembalikan alamat pada tumpukan di ruang alamat GameCube 32-bit.

Namun, ada beberapa kasus di mana nilai ukuran offset yang ditransmisikan memcpydapat melebihi 0xFFFF.

QDS

QDS memuat offset 24-bit dari data tag-nya, serta nilai ukuran 16-bit.

Hal yang baik di sini adalah bahwa offset digunakan untuk menghitung alamat tujuan dari operasi penyalinan. Alamat dasar offset adalah awal dari data yang diunduh, sumber salinan ada dalam file ROM kartu memori, dan ukurannya ditentukan oleh nilai ukuran 16-bit dari label.

Nilai 24-bit memiliki nilai maksimum 0xFFFFFF, yang jauh lebih dari apa yang diperlukan untuk menulis di luar data ROM yang dimuat. Namun, ada masalah tertentu ...

Yang pertama adalah bahwa meskipun nilai ukuran maksimum sama 0xFFFF, pada awalnya digunakan untuk mengatur ulang partisi memori. Jika nilai ukuran terlalu tinggi (tidak jauh lebih besar 0x1000), maka ini akan mengatur ulang tanda "QDS" dalam kode game.

Dan di situlah letak masalahnya, karena nesinfo_tag_process1sebenarnya disebut dua kali. Untuk pertama kalinya, dia menerima beberapa informasi tentang ruang yang dia butuhkan untuk mempersiapkan data yang disimpan. Tag QDS dan BBR tidak sepenuhnya diproses pada proses pertama. Setelah dijalankan pertama kali, sebuah tempat disiapkan untuk menyimpan data, dan fungsinya dipanggil lagi. Kali ini, tag QDS dan BBR sepenuhnya diproses, tetapi jika string nama tag dihapus dari memori, maka tidak mungkin untuk mencocokkan tag lagi!

Ini dapat dihindari dengan menetapkan nilai ukuran yang lebih kecil. Masalah lain adalah bahwa nilai offset hanya dapat bergerak maju dalam memori, dan data ROM NES terletak di tumpukan yang cukup dekat dengan akhir memori yang tersedia.

Setelah mereka hanya ada beberapa tumpukan, dan tidak satu pun dari mereka memiliki sesuatu yang sangat berguna, seperti pointer fungsi yang jelas.

Dalam kasus normal, Anda bisa menggunakan ini untuk mengeksploitasi heap overflow, tetapi dalam implementasi yang mallocdigunakan untuk heap ini, beberapa byte pemeriksaan kesehatan dalam blok telah ditambahkan malloc. Kita dapat menulis lebih dari nilai pointer di blok heap berikutnya. Tanpa pemeriksaan kesehatan, ini dapat digunakan untuk menulis ke area memori sewenang-wenang ketika dipanggil freeuntuk blok tumpukan yang terlibat.

Namun, implementasi yang digunakan di sini mallocmemeriksa pola byte tertentu ( 0x7373) di awal blok berikutnya dan sebelumnya yang akan dimanipulasi ketika dipanggilfree. Jika dia tidak menemukan byte ini, maka dia memanggil OSPanicdan gamenya membeku.


Tidak dapat memengaruhi keberadaan byte ini di beberapa lokasi target, tidak mungkin untuk menulis di sini. Dengan kata lain, tidak mungkin untuk merekam sesuatu di tempat yang sewenang-wenang tanpa bisa merekam sesuatu di dekat tempat ini. Mungkin ada beberapa cara untuk membuat nilai yang 0x73730000disimpan di tumpukan langsung di depan alamat kembali dan tempat yang merujuk nilai, yang ingin kita tulis ke alamat tujuan (itu juga akan diperiksa seolah-olah itu adalah pointer ke heap block), tetapi ini sulit untuk mencapai dan menggunakannya dalam eksploitasi.

nesinfo_update_highscore

Fungsi lain tentang tag QDS, BBR dan HSC adalah ini nesinfo_update_highscore. Ukuran tanda QDS, BBR, dan OFS (offset) digunakan untuk menghitung offset yang digunakan untuk merekam, dan tanda HSC mencakup perekaman di lokasi tersebut. Fungsi ini dilakukan untuk setiap frame yang diproses oleh emulator NES.

Nilai offset maksimum untuk setiap label dalam hal ini, bahkan untuk QDS, adalah sama 0xFFFF. Namun, selama siklus pemrosesan label, nilai dimensi dari label BBR dan QDS sebenarnya menumpuk . Ini berarti bahwa beberapa tanda dapat digunakan untuk menghitung hampir semua nilai ofset. Batasannya adalah jumlah label yang dapat ditampung di bagian data label ROM dalam file pada kartu memori, dan itu juga memiliki ukuran maksimum 0xFFFF.

Alamat dasar yang ditambahkan offset adalah 0x800C3180buffer data simpanan. Alamat ini jauh lebih rendah daripada data ROM, yang memberi kita lebih banyak kebebasan dalam memilih lokasi rekaman. Sebagai contoh, akan cukup sederhana untuk menulis ulang alamat pengirim di stack ke alamat tersebut 0x812F95DC.

Sayangnya, ini juga tidak berhasil. Ternyata itu nesinfo_tag_process1juga memeriksa ukuran akumulasi dari offset dari label ini, dan menggunakan ukuran ini untuk menginisialisasi ruang:

 bzero(nintendo_hi_0, ((offset_sum + 0xB) * 4) + 0x40) 


Dengan nilai offset yang saya coba hitung, ini mengarah pada fakta bahwa 0x48D91EC(76.386.796) byte memori telah dihapus , itulah sebabnya mengapa game tersebut mengalami crashed secara spektakuler.

Tanda PAT


Saya sudah mulai kehilangan harapan, karena semua tag yang membuat panggilan tanpa kondom memcpytelah gagal bahkan sebelum saya berhasil menggunakannya. Saya memutuskan untuk mendokumentasikan tujuan dari setiap tag, dan secara bertahap mencapai tag tersebut nesinfo_tag_process2.

Sebagian besar penangan label nesinfo_tag_process2tidak pernah memulai, karena mereka hanya bekerja ketika penunjuk adalah nesinfo_rom_startnol. Tidak ada dalam kode yang memberikan nilai bukan nol pada pointer ini. Ini diinisialisasi dengan nilai nol dan tidak pernah digunakan lagi. Saat memuat ROM hanya diatur nesinfo_data_start, jadi sepertinya kode mati.

Namun, ada satu label yang masih bisa berfungsi ketika bukan nol nesinfo_rom_start: PAT. Ini adalah label yang paling sulit dalam suatu fungsi nesinfo_tag_process2.


Ini juga digunakan sebagai pointer nesinfo_rom_start, tetapi tidak pernah memeriksanya nol. Tag PAT membaca buffer data tag-nya sendiri, memproses kode yang menghitung offset. Offset ini ditambahkan ke pointer nesinfo_rom_startuntuk menghitung alamat tujuan, dan kemudian byte disalin dari buffer patch ke lokasi ini. Penyalinan ini dilakukan dengan memuat dan menyimpan byte, tidak menggunakan instruksi memcpy, jadi saya belum menyadarinya sebelumnya.

Setiap buffer data tanda PAT memiliki kode jenis 8-bit, ukuran patch 8-bit, dan nilai offset 16-bit, diikuti oleh data patch.

  • Jika kodenya 2, maka nilai offset ditambahkan ke jumlah offset saat ini.
  • Jika kodenya 9, maka offset digeser ke atas sebanyak 4 bit dan ditambahkan ke jumlah offset saat ini.
  • Jika kodenya 3, maka jumlah offset diset ulang ke 0.

Ukuran maksimum label informasi NES adalah 255, mis. Ukuran patch PAT terbesar adalah 251 byte. Namun, beberapa tanda PAT dapat digunakan, yaitu, Anda dapat menambal lebih dari 251 byte, serta menambal ruang yang tidak bersebelahan.

Selama kita memiliki serangkaian sol PAT dengan kode 2 atau kode 9, offset dari pointer tujuan terus menumpuk. Saat menyalin data tambalan, ini diatur ulang ke nol, tetapi jika Anda menggunakan ukuran tambalan nol, ini bisa dihindari. Jelas bahwa ini dapat digunakan untuk menghitung beberapa offset acak dengan pointer nol nesinfo_rom_startmenggunakan banyak tanda PAT.

Namun, ada dua lagi pemeriksaan nilai kode ...

  • Jika kode berada di antara 0x80dan 0xFF, kemudian ditambahkan ke 0x7F80, dan kemudian bergeser ke atas 16 bit. Kemudian ditambahkan ke nilai offset 16-bit dan digunakan sebagai alamat akhir untuk tambalan.

Ini memungkinkan kami untuk menetapkan alamat tujuan untuk tambalan dalam rentang dari 0x80000000hingga 0x807FFFFF! Di sinilah sebagian besar kode Animal Crossing berada dalam memori. Ini berarti bahwa kita dapat menambal kode Animal Crossing itu sendiri menggunakan label metadata ROM dari file pada kartu memori.

Dengan bantuan patch loader kecil, Anda bahkan dapat dengan mudah mengunduh patch yang lebih besar dari kartu memori ke alamat apa pun.

Sebagai pemeriksaan cepat, saya membuat tambalan yang menyertakan "zuru mode 2" (mode pengembang game, dijelaskan dalam artikel saya sebelumnya) ketika pengguna memuat ROM dari peta game. Ternyata cheat combo dari tombol hanya mengaktifkan mode "zuru mode 1", yang tidak memiliki akses ke fungsi yang dimiliki Mode 2. Dengan tambalan ini, berkat kartu memori, kita bisa mendapatkan akses penuh ke mode pengembang di perangkat keras nyata.


Tanda patch akan diproses saat ROM melakukan booting.


Setelah memuat ROM, Anda harus keluar dari emulator NES untuk melihat hasilnya.


Itu berhasil!

Format Label Info Patch


Tanda informasi dalam file simpan yang menjalankan tambalan ini terlihat seperti ini:

000000 5a 5a 5a 00 50 41 54 08 a0 04 6f 9c 00 00 00 7d >ZZZ.PAT...o....}<
000010 45 4e 44 00 >END.<


  • ZZZ \x00: tanda mulai diabaikan. 0x00Apakah ukuran buffer datanya: nol.
  • PAT \x08 \xA0 \x04 \x6F\x9C \x00\x00\x00\x7D: Patch 0x80206F9Cdi 0x0000007D.
    • 0x08 Apakah ukuran buffer label.
    • 0xA0ketika ditambahkan 0x7F80menjadi 0x8020, yaitu, 16 bit atas dari alamat tujuan.
    • 0x04Apakah ukuran data tambalan ( 0x0000007D).
    • 0x6F9C Apakah 16 bit terbawah dari alamat tujuan.
    • 0x0000007D Apakah data tambalan.
  • END \x00 : tanda penanda akhir.

Jika Anda ingin bereksperimen sendiri dengan membuat patcher atau ROM menyimpan file, maka di https://github.com/jamchamb/ac-nesrom-save-generator, saya memposting kode yang sangat sederhana untuk membuat file. Patch seperti yang ditunjukkan di atas dapat dihasilkan dengan perintah berikut:

$ ./patcher.py Patcher /dev/null zuru_mode_2.gci -p 80206F9c 0000007D

Eksekusi kode sewenang-wenang


Berkat tag ini, Anda dapat mencapai eksekusi kode arbitrer di Animal Crossing.

Tapi inilah rintangan terakhir: menggunakan tambalan untuk data berfungsi dengan baik, tetapi masalah muncul ketika menambal instruksi kode.

Ketika tambalan direkam, gim terus mengikuti instruksi lama yang ada di tempatnya. Ini sepertinya masalah caching, dan sebenarnya itu. CPU GameCube memiliki cache instruksi, seperti dijelaskan dalam spesifikasi .

Untuk memahami bagaimana Anda bisa menghapus cache, saya mulai mempelajari fungsi-fungsi yang berhubungan dengan cache dari dokumentasi GameCube SDK, dan menemukan ICInvalidateRange. Fungsi ini membatalkan blok instruksi yang di-cache di alamat memori yang ditentukan, yang memungkinkan memori instruksi yang dimodifikasi dijalankan dengan kode yang diperbarui.

Namun, tanpa kemampuan untuk menjalankan kode asli, kami masih tidak dapat menelepon ICInvalidateRange. Agar eksekusi kode berhasil, kita perlu satu trik lagi.

Mempelajari implementasi mallocuntuk kemungkinan menggunakan exploit dengan heap overflow, saya belajar bahwa fungsi implementasi mallocdapat dinonaktifkan secara dinamis menggunakan struktur data yang disebut my_malloc. my_mallocmemuat pointer ke implementasi saat ini mallocatau freedari tempat statis di memori, dan kemudian memanggil fungsi ini, meneruskan semua argumen yang diteruskan ke my_malloc.

Emulator NES aktif digunakanmy_mallocuntuk mengalokasikan dan membebaskan memori untuk data NES terkait ROM, jadi saya yakin itu akan diluncurkan beberapa kali pada waktu yang sama dengan tanda PAT.

Karena my_mallocmemuat pointer dari memori dan membuat transisi ke sana, saya dapat mengubah proses eksekusi program hanya dengan menimpa pointer sehingga menunjuk ke fungsi saat ini mallocatau free. Caching alat tidak akan mencegah hal ini terjadi, karena tidak ada instruksi yang harus diubah my_malloc.

Pengembang proyek penggemar Dōbutsu no Mori e +, bernama Cuyler, menulis pemuat seperti itu di assembler PowerPC dan menunjukkan penggunaannya untuk menyuntikkan kode baru dalam video ini: https://www.youtube.com/watch?v=BdxN7gP6WIc. (Dōbutsu no Mori e + adalah iterasi Animal Crossing terakhir di GameCube, yang memiliki pembaruan terbanyak. Dirilis hanya di Jepang.) Patch mengunduh beberapa kode yang memungkinkan pemain untuk membuat objek dengan memasukkan ID mereka dengan huruf dan menekan tombol Z.


Berkat ini, Anda dapat mengunduh mod, cheat dan homebrew dalam salinan Animal
Crossing biasa di GameCube sungguhan.

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


All Articles