nes逆向

https://blog.csdn.net/fogota/article/details/6767758

背景

一、如何找到Mapper=0的NES游戏?

你自然有不少的NES文件,同时也知道上哪去下载这些文件,你可以多下载一些你觉得简单的游戏。我下面介绍查看mapper值的方法。

我已经找到几个Mapper=0的游戏:超级玛丽、坦克大战

方法有两个

方法(1) VirtuaNES

用VirtuaNES 打开NES文件,点菜单“文件-ROM信息”就可以查看mapper值了。

方法(2) NesHeader

用网友“疾风の迅雷”写的一个软件NesHeader.exe,可以批量读取整个文件夹上所有NES文件的ROM信息。

**

找到Mapper0的NES游戏之后,我们来确定机器码的运行首地址。

Mapper0的nes只有一段机器码段,这就好办了。其首地址有可能是

$8000 或 $C000

先不要问我为什么有两个可能性,我先来教你判断。

二、怎样判断Mapper0的NES游戏的机器码的首地址。

第一步:我提供两个办法。

先看NES文件的大小,

NES文件

另一个办法:用上面提到的NesHeader.exe,查看PROM大小,

第二步:如果nes大小=24K则要查看游戏程序的运行地址区域。

用Fcdebug打开ROM,点菜单DEBUG,打开“指令显示/控制”窗口。按“暂停”,查看(运行)地址。(这时看到的是正在运行任一条指令)

本办法是针对Mapper0的NES而设计的,对于别的Mapper值则完全不适用。

钻牛角的人要跑来问“别的Mapper是如何找到首地址?”

我在文章的后面解答一下吧。

**

接下来要将机器码从NES文件上分离出来。

你要知道,NES文件是由文件头(Head)、程序ROM(即PROM)、图形ROM(即VROM)组成。在ROM里面,连接的顺也是Head-PROM-VROM。其中Head固定占16字节;PROM占[n x 16K],n是一个倍数;VROM占[m x 8K],m是一个倍数。

朋友们看出来了,这个PROM的大小和VROM的大小在ROM信息中出现过。

PROM一般是16K为1页(1 bank),也有8K,4K为单位的。因不同的Mapper值而异。(NES文件中统统以16K为单位记录)

我们专门说Mapper0,这个号是不切页的,所以可以看成只有1页,也就是说:

注:机器码首地址,指机器码运行时在寄存器中的首地址,不是NES文件上的地址。

NES大小

PROM大小

机器码首地址

机器码末地址

机器码长度

40K

在上面的述中揭示了一些东西。要是看不明白也没关系,下面可以照着做。

三、如何才能将机器码从NES文件上分离出来?

我提供两个办法。上面用到的地址,我都算准了。

方法(1)DUMP法:

用FCdebug(NES_debug)运行ROM,点菜单DEBUG,打开“指令显示/控制”窗口。按“暂停”,在窗口的最底行,找到“内存”,在边上的两个编辑框里,填入首地址和末地址,建议末地址统一填FFFF,其中$号省略。然后按“DUMP下来”,保存成mem文件。

FCdebug是VirtuaNES的一个改版,加入了debug功能。

方法(2)HEX编辑法:

用HEX类编辑软件,打开ROM。例如Hex Workshop。

将这个地址以下的部分(包括这个分界点)删除或剪切走。留下的部分另存为mem文件。

**

终于到了反汇编软件上场了。

有人会问:“这个mem是什么文件?”

这个是内存文件,其实就是机器码,里面没有别的东西了。这个后缀名不重要,因为都没有关联软件。你叫他bin文件或别的都行。我只为统一叫法。

四、如何将mem文件显示成汇编代码呢?

我用这个

看图标,这是我用的“反汇编器”。它可以对多种CPU进行反汇编。

(1)打开它,先要选CPU,在窗口右上角选6502。

(2)用这个反汇编软件,打开mem文件。

(3)然后点选项“BIN文件”,在开始地址,填上“首地址”,

(4)确定提勾“立即反编译”,点“确定”就OK

(5)保存反汇编的asm文档,结束。

**

汇编代码是反出来了。可是问题来了:“从何看起?”

针对NES的结构,机器码是有一个入口的,这个入口的地址写在一个固定的地方。我们叫“指针”。共有3个指针,都放在一起,分别指向NMI、RESET、IRQ|BRK。其中RESET就是主程序的入口,NMI是中断的入口,IRQ|BRK是另一种中断入口。

五、如何找到程序的入口?

将asm文件(用记事本)打开,滚动条向下拉,拉到底。看到FFFA到FFFF的数据,抄下来。

记a,b 是一组指针,实际地址为ba,即a为低位,b为高位。如此类推得3组指针

NMI = ba

RESET = dc

IRQ|BRK = fe

NMI = $8035

RESET = $8000

IRQ|BRK = $805C

六、注意事项

反汇编的结果不是全都是代码,其中有部分可能会是数据,也有可能整页都是图型(CHR)数据。反汇编器总是优先将能够反成代码的译成代码。于是你会发现在DB旁边出现没有作用的代码,有时发现代码去读一个指令,其实是在读指针或数据。

另外一些软件生成的ROM会加入一些没机会运行的代码。这可能是优化不完全。

七、局部反汇编,只做第三步和第四步就行。其中第三步用dump法。具体,多接触就会了解。

疑难解答:

Mapper是什么?

Mapper是一个编号,用于区分不同电路结构的卡带。NES模拟器拿一个ROM当作是一个卡带来模拟,那么卡带的电路也要区分才能模拟的。卡带不是标准统一的,当时不同的NES游戏开发商都开发出自己独有的保密的卡带;还发展出多个不同的升级产品,可以令游戏性能更理想;同时对游戏程序有保密作用。

为什么Mapper0的ROM机器码首地址有可能是$8000或$C000?

Mapper0就是表示卡带电路没有任何特别,卡带的寄存器是直接接到CPU总线上的。也就是有可能

(1) 16K的寄存器接到$8000-$BFFF的地址上并镜像到$C000-FFFF。

(2) 32K的寄存器接到$8000-$FFFF的地址上。

在(1)情况下,$8000-$BFFF和$C000-FFFF的机器码是完全一样的。主要是看程序的作者,将代码(基于).org $8000还是.org $C000进行编译的。

我本来想在“判断首地址”的方法用程序入口的办法,但这个不准确,因为在(1)情况下我可以恶作剧的将程序入口改成对应镜像的位置,程序一样是正常运行的。

Mapper0以外的ROM是如何找到首地址?

只有Mapper0不切页,换句话说,Mapper0是静态将寄存器接入总线。其余的Mapper都是动态的将寄存器接入总线。也就是说,是通过程序控制接入某一块寄存器接入总线上某地址段,而原接入的块(页)断开。

答案就是:你要先知道最早是默认接入的寄存器页,将之按mapper0的方法反汇编。然后从程序入口,一行一行看代码,看调入了哪一页,然后计算这一页的位置,将它分离出来。再反汇编。如此直到掌握全部页的动向。

FCEUX使用

debug ppu viewer

右键可以更改颜色

https://fceux.com/web/help/PPUViewer.html

介绍

NES 架构包括一个 6502 CPU 以及一个称为 PPU(图片处理单元)的自定义视频控制器。PPU 的视频内存与主 CPU 内存分开,可以通过特殊端口进行读/写(请参阅 PPU 内存)。

PPU 查看器将仅显示当前 PPU 内存的内容。它不会以任何方式更改游戏数据。

使用 PPU 查看器

在扫描线上显示

此选项使其显示屏幕绘制该特定扫描线时 PPU 的外观。它对于像 SMB 这样的游戏很有用,这些游戏会在帧中交换模式表(例如,用于状态栏内容)。

右键单击其中一个 PPU 面板将更改显示它的调色板,在图案调色板之间循环,然后是 sprite 调色板,然后是第九个固定的灰色调色板(如果所有调色板当前都是黑色的,则可用于检查 CHR)。

将鼠标光标放在图块上将显示图块地址。将光标移动到调色板颜色上将给出调色板地址。

Code/Data Logger 运行时,您还可以使用 “Mask unused graphics” 功能。或者,您只能遮罩已使用(绘制或以其他方式访问)的图块,并强调未使用的图块(例如,为了查找秘密 Sprite)。

注意:此功能仅适用于使用 CHR ROM 的游戏,因为代码/数据记录器仅记录对 CHR ROM 的访问。

debug cheat

先看ram search 确定变量

再cheat 可以导入随机数

例题

例题xctf chase

mapper为0

看到机器码从8000

ida用二进制打开 以6502的形式

flag01

可以直接ce开挂,

TPCTF{D0_Y0U_L1KE_

也可用FCEUX自带的cheat改动

flag02

法1

经过不断调试发现AB对应的值和场景有关,每个场景对应值固定且范围普遍在0-60之间,那么尝试爆破,调整AB的值,改内存时发现会有随机性,相同的值不保证每次的闪烁结果相同,这里是尝试到0x15时看到flag2:

法2

第二段有点misc的感觉,猜测也是在内存里面只不过不会加载到屏幕上,由于每段flag开头都会有THE FLAG PT我们搜索这几个字符的tile 34 28 25,一共发现两个其实一个是第一段flag,tile D2就是代表字符2 D1也就是字符1

这段是flag1

第二段是flag2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import binascii

data = "34282500262C21270030340ED200262F3200392F35002933000112A4000118302C2139D12ED93DD6202DD3333D00019ABDADAD6D000103B9ADAD6E000103B9ADAD6E0001030D01030001100001000F0F0F0F0F1C2C3C0F1222320F1424340F1132300F1C2C3C0F0927380F1121310F1132300F1121310F0727380F1323330F1132300F1525350F0527380F1323330F1132300F1929390F0B27380F1727370F1132300F1626360F0727380F1828380F0F29300F0F26300F0F24300F0F213000FF490008FF4A0000074B0008074C008000FF4D0108FF4E0100074F01080750018000FF4D0208FF4E0200074F02080750028000FF4D0308FF4E0300074F03080750038077F488F499F4AAF4B0E717F4E1E827F4ACEA37F4C8EC37F448EF37F49BF217F4280044280044280044280044204F10205010205110FF2C2536252C1A000027252D331A0000000F000000002C293625331A000304037A7B7C7D7C7D7E7F80819A9B867B8788898A8B8C8D7D807D909192939495967F94959C9500000000000058E5ADE40BE7FEE48D1C038E1D038D23038E240388B9FFFF8D2D0388B9FFFF8D2C038C2F0320FFFFA0FFD0E860000000000000"

data = binascii.a2b_hex(data)

table = {}
str_1 = ord("A")
for i in range(0x21,0x3B):
table[i] = chr(str_1)
str_1 += 1
table[0x20] = "@"
table[0] = " "
table[0x3d] = "_"
str_1 = ord("0")
for i in range(0xD0,0xDA):
table[i] = chr(str_1)
str_1 += 1
flag = ""

for i in range(len(data)):
for key,value in table.items():
if data[i] == key:
flag += value
print(flag)

THE FLAG PT2 FOR YOU IS PLAY1N9_6@M3S_ LBRDTRPLGXAQRPAQGXCSRPEUGXCSRPIYGXGWRPFVGXHXIPFPDPAP GWWWH H H H @@@LEVEL GEMS LIVES CDMLO@0

flag03

看ppu viewer

ON_Y0UR_N3S?}

FLAG:TPCTF{D0_Y0U_L1KE_PLAY1N9_6@M3S_ ON_Y0UR_N3S?}


nes逆向
http://example.com/2024/11/01/nes逆向/
Author
chaye
Posted on
November 1, 2024
Licensed under