GameCube游戏中NES模拟器的逆向工程

图片

在寻找激活Animal Crossing剩下的开发人员菜单的方法(包括NES模拟器的游戏选择菜单)时,我发现了一个有趣的功能,该功能存在于原始游戏中并且一直处于激活状态,但任天堂从未使用过。

除了游戏内NES / Famicom游戏外,您还可以从存储卡中下载新的NES游戏。

我还设法找到了一种使用此ROM引导加载程序将代码和数据修补到游戏中的方法,该方法使您可以通过存储卡执行代码。

简介-NES控制台对象


可以从Animal Crossing获得的普通NES游戏是NES控制台形式的单独家具,上面放着一个弹药筒。

将这个对象放置在您的房屋中并与之互动之后,您就可以运行这个唯一的游戏。 下图显示了Excitebike和Golf。


还有一个常见的NES控制台对象,其中没有内置游戏。 它可以从Redd购买,有时可以通过随机事件获得,例如,通过在城市布告栏上读取控制台被埋在城市中的随机点来获得。


该对象看起来像是没有墨盒的NES控制台。


该对象的问题在于,它被认为无法播放。 每次与他互动时,您只会看到一条消息,说您没有游戏软件。


原来,该对象实际上是在尝试扫描存储卡中包含NES ROM映像的特殊设计文件! 用于运行嵌入式游戏的NES模拟器似乎是GameCube的完整标准NES模拟器,并且能够启动大多数游戏。

在演示这些功能之前,我将说明对它们进行反向工程的过程。

搜索存储卡上的ROM引导加载程序


我们正在寻找开发者菜单


最初,我想找到一个代码来激活各种开发人员菜单,例如NES模拟器的地图选择菜单或游戏选择菜单。 借助森林地图选择”菜单,您可以轻松地立即加载游戏的不同位置,该菜单非常容易找到-我只是寻找了显示在屏幕顶部的“森林地图选择”行(可以在互联网上的不同视频和屏幕截图中看到) )

在“ FOREST MAP SELECT”中,有对select_print_wait函数的数据交叉引用,这导致了很多具有select_*前缀的其他函数,包括select_init函数。 事实证明它们是控制地图选择菜单的功能。

select_init函数导致另一个有趣的函数,称为game_get_next_game_dlftbl 。 此功能将所有其他菜单和您可以运行的“场景”链接在一起:带有Nintendo徽标的屏幕,主屏幕,卡选择菜单,NES仿真器菜单(Famicom)等。 它从主要游戏过程的开始开始,找到它应该运行哪个场景初始化函数,并在名为game_dlftbls的表数据结构中找到它的条目。 该表包含指向各种场景的处理功能的链接,以及一些其他数据。


仔细研究该函数的第一个块后,发现它加载了“下一个游戏init”函数,然后开始将其与一系列众所周知的init函数进行比较:

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


他正在寻找的功能指针之一是famicom_emu_init ,它负责运行NES / Famicom仿真器。 在Dolphin调试器game_get_next_game_init结果game_get_next_game_init famicom_emu_initselect_init ,我能够显示特殊菜单。 下一步是确定在程序执行期间如何以常规方式设置这些指针。 game_get_next_game_init函数唯一要做的就是0xC第一个参数的偏移量0xC值加载到game_get_next_game_dlftbl

跟踪在各种数据结构中设置的这些值有点无聊,因此我将直接介绍其核心。 我发现的最重要的事情是:

  • 当游戏以常规方式启动时,它将执行以下操作序列:
    • first_game_init
    • second_game_init
    • trademark_init
    • play_init
  • player_select_init将下一个初始化设置为select_init 。 该屏幕应允许您在选择一张牌后立即选择一个球员,但看来它不能正常工作。

我还发现了一个无名函数,该函数定义了模拟器的init函数,但没有找到任何将init函数设置为播放器或地图选择的init值的函数。

在这一点上,我意识到我在IDA中加载函数名称的方式还有另一个愚蠢的问题:由于用于在调试符号文件中剪切行的正则表达式,我错过了所有以大写字母开头的函数名称。 famicom_emu_init设置的功能看起来像场景之间的过渡,当然称为Game_play_fbdemo_wipe_proc

Game_play_fbdemo_wipe_proc处理场景之间的过渡,例如屏幕Game_play_fbdemo_wipe_proc和停电。

在某些条件下,屏幕转换是从通常的游戏进行到模拟器的显示。 设置仿真器init功能的是他。

处理控制台对象


实际上,用于NES控制台的家具对象的处理程序会使屏幕过渡处理程序切换到仿真器。 当玩家与其中一个控制台进行交互时,将aMR_FamicomEmuCommonMove

调用该函数时, r6包含与famicom.arc NES游戏文件的名称中的数字相对应的索引值:

  • 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是专有文件存档格式。)

r6不等于零时,它将在aMR_RequestStartEmu调用中传递。 在这种情况下,将触发向仿真器的转换。


但是,如果r6为零,则将调用aMR_RequestStartEmu_MemoryC函数。 将调试器中的值设置为0,我收到消息“我没有任何软件”。 我没有立即记住,我需要检查NES控制台对象以确保它重置了r6值,但事实证明,零索引用于不带盒带的控制台对象。

尽管aMR_RequestStartEmu仅将索引值存储在某种数据结构中, aMR_RequestStartEmu_MemoryC执行更复杂的操作...


第三个代码块调用aMR_GetCardFamicomCount并检查结果是否为非零,否则将跳过功能图左侧的大多数有趣内容。

aMR_GetCardFamicomCount调用famicom_get_disksystem_titles ,然后调用memcard_game_list ,这里的一切都变得非常有趣。

memcard_game_list挂载存储卡并开始在文件写入周期中移动,并检入某些值。 通过跟踪调试器中的功能,我可以了解到它正在将值与存储卡上的每个文件进行比较。


该功能根据检查多行的结果来决定是否下载文件。 首先,它检查“ GAFE”和“ 01”行的存在,它们是游戏和公司的标识符。 01代表Nintendo,GAFE代表Animal Crossing。 我认为它代表GameCube Animal Forest English。

然后,她检查“ DobutsunomoriP_F_”和“ SAVE”行。 在这种情况下,第一行应匹配,但第二行不匹配。 事实证明,“ DobutsunomoriP_F_SAVE”是存储用于NES的嵌入式游戏的数据的文件的名称。 因此,除该文件外的所有文件都将加载前缀“ DobutsunomoriP_F_”。

使用Dolphin调试器跳过与“ SAVE”的字符串比较,并使游戏技巧相信“ SAVE”文件可以安全下载,在使用NES控制台后,我得到了以下菜单:


我回答“是”,并尝试将保存文件作为游戏加载,此后,我首先看到了内置的游戏崩溃屏幕:


太好了! 现在,我知道她实际上是在尝试从存储卡下载游戏,现在我可以开始分析保存文件的格式,看看是否可以下载真正的ROM。

我要做的第一件事是尝试找到从存储卡文件中读取游戏名称的位置。 搜索消息“您想播放<name>?”中出现的“ FEFSC”行,我发现从文件0x642读取的偏移量。 我复制了保存文件,将文件名更改为“ DobutsunomoriP_F_TEST”,将偏移量为0x642的字节更改为“ TESTING”,然后导入更改后的保存,然后在菜单中出现了我需要的名称。

在以这种格式添加更多文件之后,菜单中出现了更多选项:


下载ROM


如果aMR_GetCardFamicomCount返回非零值,则在堆上分配内存, famicom_get_disksystem_titles直接调用famicom_get_disksystem_titles ,然后在数据结构中指定一堆随机偏移量。 我开始研究famicom函数的列表,而不是解密将从何处读取这些值。

原来,我需要famicom_rom_load 。 它可以从存储卡或游戏内部资源控制ROM的加载。


在“从存储卡启动”块中,最重要的是它调用
memcard_game_load 。 她再次将文件装载到存储卡上,读取并解析。 这是最重要的文件格式选项显而易见的地方。

校验和值


上传文件后发生的第一件事是校验和计算。 calcSum函数calcSum ,这是一种非常简单的算法,用于对存储卡中数据中所有字节的值求和。 结果的后八位必须为零。 也就是说,要通过此检查,您需要对源文件中所有字节的值求和,计算需要添加的值,以使后八位变为零,然后将此值分配给文件中的校验和字节。

如果验证失败,则您将收到一条消息,提示无法正确读取存储卡,并且什么也没有发生。 在调试过程中,我要做的就是跳过此检查。

复制ROM


memcard_game_load结束时,发生了另一件事。 在它和校验和之间还有一些更有趣的代码块,但是没有一个导致跳过该行为的执行的分支。


如果从存储卡读取的某个16位整数值不等于零,则调用一个函数来检查缓冲区中的压缩头。 它通过查看Yay0或Yaz0缓冲区的开头来检查专有的Nintendo压缩格式。 如果找到这些行之一,则将调用unpack函数。 否则,将执行简单的复制复制功能。 无论如何,此后将nesinfo_data_size名为nesinfo_data_size的变量。

这里的另一个上下文提示是,嵌入式NES游戏的ROM文件使用Yaz0压缩,并且该文件的标头中存在此行。

观察检查为零的值并将缓冲区传递给压缩检查功能后,我迅速在存储卡上的文件中找到了正在读取游戏的位置。 对文件中从偏移量0x640复制的32字节缓冲区的一部分执行零检查,这很可能是ROM标头。 此功能还检查文件的其他部分,并且在其中找到游戏名称(从标头的第三个字节开始)。

在我发现的代码执行路径中,ROM缓冲区位于此32字节标题缓冲区之后。


此信息足以尝试创建有效的ROM文件。 我只是取出了另一个Animal Crossing保存文件之一,并在十六进制编辑器中对其进行了编辑,以用DobutsunomoriP_F_TEST替换文件名,并清除了我要粘贴数据的所有区域。

为了进行测试,我使用了游戏中已经存在的Pinball游戏ROM,并将其内容插入了32字节的标题之后。 设置断点而不是计算校验和值,而是只跳过calcSum ,还观察其他检查的结果,这些结果可能导致跳转到跳过ROM启动过程的分支。

最后,我通过Dolphin存储卡管理器导入了新文件,重新启动了游戏,并尝试启动控制台。



奏效了! 有一些与Dolphin参数有关的小图形错误,这些错误影响了NES模拟器使用的图形模式,但总体而言,游戏表现还不错。 (在较新版本的Dolphin中,默认情况下应该可以使用。)

为了确保其他游戏也能开始,我尝试编写其他不在游戏中的ROM。 Battletoads开始了,但在启动屏幕文字显示后停止工作(经过进一步的设置,我设法使其可以播放)。 另一方面,《洛克人》表现出色:


为了学习如何生成无需调试器干预即可加载的新ROM文件,我不得不开始编写代码并更好地理解文件格式解析。

外部ROM文件格式


解析文件的最重要部分发生在memcard_game_load 。 此功能中的代码解析块有六个主要部分:

  • 校验和
  • 保存文件名
  • ROM文件头
  • 未知缓冲区已复制,未经任何处理
  • 文本注释,图标和横幅加载程序(用于创建新的保存文件)
  • ROM引导程序


校验和


保存文件中所有字节值之和的低八位必须为零。 这是生成必要的校验和字节的简单Python代码:

 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 

可能有一个特殊的位置存储校验和字节,但是将其添加到保存文件末尾的空白处效果很好。

档案名称


同样,保存文件名应以“ DobutsunomoriP_F_”开头,并以不包含“ SAVE”的结尾。 此文件名被复制了两次,在一种情况下,字母“ F”被替换为“ S”。 这将是NES游戏(“ DobutsunomoriP_S_NAME”)的保存文件的名称。

ROM头


32字节标头的直接副本被加载到内存中。 此标头中的某些值用于确定如何处理后续部分。 基本上,这些是一些16位大小的值和打包的参数位。

如果您一直跟踪由标头复制的指针到函数的开头并找到其参数的位置,则下面函数的签名将显示它实际上具有MemcardGameHeader_t*类型。

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

未知缓冲区


检查标题中的16位大小值。 如果不等于零,则将相应的字节数直接从文件缓冲区复制到分配的内存的新块中。 这会将数据指针移到文件缓冲区中,以便可以从下一部分继续进行复制。

横幅,图标和评论


在标头中检查另一个大小值,如果它不等于零,则调用文件压缩检查功能。 如有必要,将启动解包算法,然后SetupExternCommentImage

此功能执行三件事:“注释”,横幅图像和图标。 对于它们中的每一个,ROM头中都有一个代码,显示如何处理它们。 有以下选项:

  1. 使用默认值
  2. 从ROM文件中的标题/图标/注释部分复制
  3. 从备用缓冲区复制

代码的默认值导致从磁盘上的资源加载图标或横幅,并且为保存文件的名称和注释(文件的文本描述)分配了值“ Animal Crossing”和“ NES Cassette Save Data”。 看起来是这样的:


代码的第二个值只是简单地从ROM文件中复制游戏名称(“动物穿越”的替代方法),然后尝试在文件注释中找到字符串“] ROM”,并将其替换为“] SAVE”。 显然,任天堂要发布的文件应该采用“游戏名称[NES] ROM”之类的格式。

对于图标和横幅,代码尝试确定图像格式,获取与此格式对应的固定大小值,然后复制图像。

在最后一个代码值处,文件名和描述被复制而缓冲区中没有更改,并且图标和横幅也从备用缓冲区中加载。

只读存储器


如果您仔细查看memcard_game_load复制ROM的屏幕截图,您会看到检查为零的16位值向左移动了4位(乘以16),然后在未检测到压缩的情况下用作memcpy函数的大小。 这是标题中存在的另一个大小值。

如果大小不等于零,则检查ROM数据是否压缩,然后复制。

未知的缓冲区和错误搜索


尽管下载新的ROM很好奇,但对我而言,关于此ROM加载器的最有趣的事情是,实际上,这是游戏中接收可变大小用户输入并将其复制到不同存储位置的唯一部分。 几乎所有其他内容都使用恒定大小的缓冲区。 诸如名称和字母文本之类的东西在长度上可能看起来有所不同,但实际上,空白处只是用空格填充。 不经常使用以零结尾的字符串,以避免常见的内存损坏错误,例如,将strcpy与太小而无法将字符串复制到其中的缓冲区一起使用。

我对是否有可能基于保存文件发现游戏漏洞很有兴趣,这似乎是最好的选择。

除了未知的缓冲区和ROM数据外,上述大多数ROM文件操作都使用恒定大小的副本。 不幸的是,处理该缓冲区的代码分配的空间恰好等于复制它所需的空间,因此没有溢出,并且设置非常大的ROM文件大小不是很有用。

但是我仍然想知道该缓冲区发生了什么,该缓冲区未经任何处理就被复制了。

NES信息标签处理程序


我回到了famicom_rom_load 。 从存储卡或磁盘加载ROM后,将调用以下几个功能:

  • nesinfo_tag_process1
  • nesinfo_tag_process2
  • nesinfo_tag_process3

跟踪了复制未知缓冲区的位置之后,我确保此任务由这些功能执行。 他们从对nesinfo_next_tag的调用开始,该调用执行一个简单的算法:

  • 检查指定的指针是否nesinfo_tags_end指针nesinfo_tags_end 。 如果它小于nesinfo_tags_endnesinfo_tags_end为零,则它将检查指针头中是否存在字符串“ END”。

    • 如果到达“ END”,或者指针已经上升到nesinfo_tags_end或之上,则该函数返回null。
    • 否则,将指针的偏移量0x3处的字节添加到4并添加到当前指针,然后返回该值。

这告诉我们,从三个字母的名称,数据大小值和数据本身来看,存在某种标签格式。 结果是指向下一个标签的指针,因为跳过了当前标签( cur_ptr + 4跳过了三个字母的名称和一个字节,而size_byte跳过了数据)。

如果结果不为零,则标签处理功能将执行一系列字符串比较,以找出需要处理的标签。 nesinfo_tag_process1检查的某些标签名称nesinfo_tag_process1nesinfo_tag_process1 ,VNE,GID,GNO,BBR和QDS。


如果找到标签匹配项,则会执行一些处理程序代码。 一些处理程序除了在调试消息中显示标签外,什么也不做。 其他人则拥有更复杂的处理程序。 处理完标签后,该函数尝试获取下一个标签并继续处理。

幸运的是,检测到标签时会显示许多详细的调试消息。它们都是日文,因此必须首先从Shift-JIS进行解码并进行翻译。例如,有关QDS的消息可能显示为“正在加载磁盘保存区域”或“由于这是第一次运行,请创建磁盘保存区域”。 BBR的消息显示为“正在加载备用电池”或“由于这是第一次启动,因此我们执行清理”。

这两个代码还从其标签的数据部分加载了一些值,并使用它们来计算ROM数据中的偏移量,然后执行复制操作。显然,它们负责确定ROM存储器中与状态保存相关的部分。

还有一个带有调试消息的“ HSC”标签,说明正在处理点记录。她从标签数据以及原始得分记录值中获得了ROM偏移。这些标记可用于指示NES游戏在内存中的位置,用于存储高分,将来可能会保存和恢复它们。

这些标签创建了一个相当复杂的ROM元数据下载系统。而且,它们中的许多导致memcpy基于标签数据中传输的值的呼叫

找虫子


大多数导致内存操作的标签对于漏洞利用不是很有用,因为它们全部具有指定为16位整数的最大偏移量和大小值。这足以与16位NES地址空间一起使用,但不足以写入有用的目标值,例如指向函数的指针或在32位GameCube地址空间中的堆栈上的返回地址。

但是,在某些情况下,传输的大小偏移值memcpy可能会超过0xFFFF

QDS

QDS从其标签数据中加载24位偏移量,以及16位大小值。

这里的好处是,偏移量用于计算复制操作的目标地址。偏移量的基地址是下载数据的开头,复制源位于存储卡的ROM文件中,其大小由标签中的16位大小值设置。

24位值具有最大值0xFFFFFF,该最大值远远大于写入已加载ROM数据所需的最大值。但是,存在某些问题...

第一个问题是,尽管最大大小值相等0xFFFF,但它最初用于重置内存分区。如果大小值太高(不大0x1000),则将重置游戏代码中的“ QDS”标记。

问题就在这里,因为它nesinfo_tag_process1实际上被调用了两次。她第一次收到有关准备存储数据所需空间的一些信息。第一次运行时未完全处理QDS和BBR标签。第一次运行后,将为保存数据准备一个位置,然后再次调用该函数。这次,QDS和BBR标签已被完全处理,但是如果从内存中清除了标签名称字符串,则无法再次匹配标签!

可以通过设置较小的大小值来避免这种情况。另一个问题是偏移值只能在内存中向前移动,并且ROM NES数据位于堆中,非常接近可用内存的末尾。

在它们之后只有几个堆,并且其中没有一个堆具有特别有用的功能,例如明显的函数指针。

在正常情况下,您可以使用它来利用堆溢出,但是在malloc用于此堆的实现中,已在块中添加了很多字节的运行状况检查malloc。我们可以在后续堆块中覆盖指针值。如果没有运行状况检查,则当调用free涉及的堆块时,可以将其用于写入任意内存区域

但是,此处使用的实现会在下一个和上一个块的开头malloc检查特定的字节模式(0x7373),在调用该块时将对其进行操作free如果她没有找到这些字节,则她会呼叫OSPanic并冻结游戏。


无法影响这些字节在某些目标位置的存在,因此无法在此处写入。换句话说,如果不能在该位置附近记录任何东西,就不可能在该位置记录任何东西。可能有一些方法可以使值0x73730000直接在返回地址和该值所指向位置之前存储在堆栈中,我们希望将其写入目标地址(也将检查它是否是指向堆块的指针),但这很难在漏洞利用中实现和使用它。

nesinfo_update_highscore

关于QDS,BBR和HSC标签的另一个功能是this nesinfo_update_highscore。 QDS,BBR和OFS标记的大小(偏移量)用于计算记录的偏移量,HSC标记包括该位置的记录。对于NES模拟器处理的每个帧都执行此功能。

在这种情况下,即使对于QDS,每个标签的最大偏移值也等于0xFFFF。但是,在标签处理周期中,BBR和QDS标签的尺寸值实际上会累积。这意味着可以使用多个标记来计算几乎任何偏移值。限制是可以容纳在存储卡中文件中ROM标签的数据部分中的标签数,并且标签的最大大小也要限制0xFFFF

添加偏移量的基址是0x800C3180保存数据缓冲区。该地址比ROM数据低得多,这使我们在选择记录位置时拥有更大的自由度。例如,将堆栈中的返回地址重写为address将非常简单0x812F95DC

不幸的是,这也不起作用。事实证明,它nesinfo_tag_process1还会检查这些标签的偏移量的累积大小,并使用此大小来初始化空间:

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


使用我试图计算的偏移量值,导致了0x48D91EC(76,386,796)字节的内存被清除的事实,这就是游戏崩溃的原因。

PAT标志


我已经开始失去希望,因为所有这些发出不受保护的呼叫的标签memcpy都在我设法使用它们之前就失败了。我决定只记录每个标签的用途,然后逐步了解中的标签nesinfo_tag_process2

大多数标签处理程序nesinfo_tag_process2永远不会启动,因为它们仅在指针nesinfo_rom_start非零时才起作用。代码中没有任何内容为此指针分配非零值。它使用空值初始化,并且不再使用。当仅设置ROM时nesinfo_data_start,因此它看起来像死代码。

但是,有一个标签在非零时仍然可以使用nesinfo_rom_start:PAT。这是函数中最难的标签nesinfo_tag_process2


它还用作指针nesinfo_rom_start,但永远不会检查它是否为零。PAT标签读取其自己的标签数据缓冲区,处理计算偏移量的代码。将这些偏移量添加到指针nesinfo_rom_start以计算目标地址,然后将字节从修补程序缓冲区复制到此位置。这种复制是通过加载和保存字节来完成的,而不是使用指令完成的memcpy,因此我以前没有注意到它。

每个PAT标记数据缓冲区都有一个8位类型代码,一个8位补丁大小和一个16位偏移值,后跟补丁数据。

  • 如果代码为2,则将偏移值添加到偏移的当前总和中。
  • 如果代码为9,则将偏移量上移4位,并添加到偏移量的当前总和中。
  • 如果代码为3,则偏移总和将重置为0。

NES信息标签的最大大小为255,即最大的PAT补丁大小为251个字节。但是,可以使用多个PAT标记,即,您可以修补251个以上的字节,也可以修补非连续的空格。

只要我们有一系列带有代码2或代码9的PAT鞋底,目标指针的偏移就会继续累积。复制补丁数据时,会将其重置为零,但是如果使用零补丁大小,则可以避免这种情况。显然,可以使用带有nesinfo_rom_start许多PAT标记的空指针来计算任意偏移量

但是,还有两个检查代码值的方法...

  • 如果代码在0x80之间0xFF,则将其添加到中0x7F80,然后上移16位。然后将其添加到16位偏移值,并用作补丁的结束地址。

这使我们能够在一个范围内从指定的目的地地址的补丁0x80000000最多0x807FFFFF这是大量动物穿越代码驻留在内存中的位置。这意味着我们可以使用存储卡中文件中的ROM元数据标签来对Animal Crossing代码本身进行修补。

借助小型补丁程序加载器,您甚至可以轻松地将较大的补丁程序从存储卡下载到任何地址。

作为快速检查,我创建了一个包含“ zuru mode 2”(游戏开发人员模式)的补丁,如我先前的文章中所述),当用户从游戏地图加载ROM时。事实证明,按键的作弊组合只能激活“ zuru模式1”模式,而该模式无法访问模式2所具有的功能,有了此补丁,由于有了存储卡,我们可以在真实硬件上完全访问开发者模式。


ROM引导时将处理补丁标记。


加载ROM后,您需要退出NES仿真器以查看结果。


有效!

补丁信息标签格式


运行此补丁的保存文件中的信息标记如下所示:

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:忽略开始标记。0x00是其数据缓冲区的大小:零。
  • PAT \x08 \xA0 \x04 \x6F\x9C \x00\x00\x00\x7D:修补程序0x80206F9C0x0000007D
    • 0x08 标签缓冲区的大小。
    • 0xA0加到时0x7F80变为0x8020,即目标地址的高16位。
    • 0x04补丁数据的大小(0x0000007D)。
    • 0x6F9C 是目标地址的低16位。
    • 0x0000007D 是补丁数据。
  • END \x00 :结束标记标记。

如果您想尝试创建修补程序或ROM保存文件,那么在https://github.com/jamchamb/ac-nesrom-save-generator上,我发布了一个非常简单的代码来生成文件。可以使用以下命令生成类似于上述补丁的补丁:

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

任意代码执行


由于有了这个标签,您可以在Animal Crossing中实现任意代码执行。

但这是最后一个障碍:对数据使用修补程序效果很好,但是在修补代码指令时会出现问题。

记录补丁程序后,游戏将继续按照原先的指示进行操作。这似乎是一个缓存问题,实际上是。如规范中所述 GameCube CPU具有指令缓存

为了了解如何清除缓存,我开始从GameCube SDK文档中研究与缓存相关的功能,并发现了ICInvalidateRange。此功能使在指定存储器地址处的缓存指令块无效,从而允许使用更新的代码执行修改后的指令存储器。

但是,如果没有运行原始代码的能力,我们仍然无法调用ICInvalidateRange。为了成功执行代码,我们还需要另一个技巧。

研究实现方案malloc以利用带有堆溢出漏洞的可能性,我了解到malloc可以使用名为的数据结构动态禁用实现功能my_mallocmy_malloc加载指向当前实现mallocfree内存中静态位置的指针,然后调用此函数,并将传递给的所有参数传递给my_malloc

NES模拟器积极使用my_malloc分配和释放与ROM相关的NES数据的内存,因此我确信它将与PAT标记同时启动几次。

由于它my_malloc从内存中加载了一个指针并对其进行了转换,因此我可以通过简单地覆盖指针使其指向当前函数malloc来更改程序执行过程free。工具缓存不会阻止这种情况的发生,因为不需要更改任何指令my_malloc

名为Cuyler的Dōbutsuno Mori e +风扇项目的开发人员在PowerPC汇编器中编写了这样的加载器,并在此视频中演示了如何将其用于注入新代码:https : //www.youtube.com/watch?v=BdxN7gP6WIc(Dōbutsuno Mori e +是GameCube上的最新Animal Crossing迭代,更新最多。仅在日本发行。)该补丁下载了一些代码,该代码允许玩家通过输入字母ID并按Z按钮来创建任何对象。


因此,您可以
在真实的GameCube上以常规版本的Animal Crossing 下载mod,作弊和自制软件

Source: https://habr.com/ru/post/zh-CN417117/


All Articles