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)
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:
- Gunakan nilai default
- Salin dari bagian banner / ikon / komentar dalam file ROM
- 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 memcpy
berdasarkan 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 memcpy
dapat 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_process1
sebenarnya 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 malloc
digunakan 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 free
untuk blok tumpukan yang terlibat.Namun, implementasi yang digunakan di sini malloc
memeriksa pola byte tertentu ( 0x7373
) di awal blok berikutnya dan sebelumnya yang akan dimanipulasi ketika dipanggilfree
. Jika dia tidak menemukan byte ini, maka dia memanggil OSPanic
dan 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 0x73730000
disimpan 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 0x800C3180
buffer 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_process1
juga 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 memcpy
telah 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_process2
tidak pernah memulai, karena mereka hanya bekerja ketika penunjuk adalah nesinfo_rom_start
nol. 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_start
untuk 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_start
menggunakan banyak tanda PAT.Namun, ada dua lagi pemeriksaan nilai kode ...- Jika kode berada di antara
0x80
dan 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 0x80000000
hingga 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. 0x00
Apakah ukuran buffer datanya: nol.PAT \x08 \xA0 \x04 \x6F\x9C \x00\x00\x00\x7D
: Patch 0x80206F9C
di 0x0000007D
.
0x08
Apakah ukuran buffer label.0xA0
ketika ditambahkan 0x7F80
menjadi 0x8020
, yaitu, 16 bit atas dari alamat tujuan.0x04
Apakah 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 malloc
untuk kemungkinan menggunakan exploit dengan heap overflow, saya belajar bahwa fungsi implementasi malloc
dapat dinonaktifkan secara dinamis menggunakan struktur data yang disebut my_malloc
. my_malloc
memuat pointer ke implementasi saat ini malloc
atau free
dari tempat statis di memori, dan kemudian memanggil fungsi ini, meneruskan semua argumen yang diteruskan ke my_malloc
.Emulator NES aktif digunakanmy_malloc
untuk 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_malloc
memuat pointer dari memori dan membuat transisi ke sana, saya dapat mengubah proses eksekusi program hanya dengan menimpa pointer sehingga menunjuk ke fungsi saat ini malloc
atau 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 AnimalCrossing biasa di GameCube sungguhan.