一道游戏题,题目描述
//GLHF
Good Luck Have Fun的缩写,给的附件不知道是啥,DIE查信息也只显示Binary,但是拖进010editor里面可以看到有文件头NES
搜索一下发现是游戏文件,也就是游戏逆向了,介绍如下
NES文件是任天堂红白机(Nintendo Entertainment System,简称NES或FC)游戏的ROM文件格式,通常以.nes为扩展名。这类文件包含了游戏的程序代码、图像数据、音效以及其他运行所需的资源,可通过模拟器在电脑或现代设备上运行。
也就是红白机运行的文件。用IDA打不开。找了一下资料可以用Fceux模拟器运行,并且该模拟器还自带一些分析调试的功能。
运行了后发现是一个吃豆豆游戏,但是会被敌人追逐,关卡越到后面,地图越大,要吃的豆豆也越多,敌人数量也会增加,总共只有三条命,但是玩一下发现难度不是很大,只有5关,玩完之后就可以得到flag的第一部分如图
flag的第三部分也很好获取,用该模拟器打开Debug->PPU Viewer即可看到,如图
PPU Viewer是一个用于调试和可视化 NES PPU(图像处理单元)内部数据的工具。通过 PPU Viewer,开发者或爱好者可以实时查看 PPU 的显存(VRAM)、调色板、图块(Tiles)和精灵(Sprites)等关键图形数据,帮助分析游戏画面渲染机制或调试图形问题。
那最难获取的就是flag的第二部分了,获取第二部分flag方法有很多种,以下几篇文章讲了有关的NES的图像技术
可以总结得到以下的信息
- 1.游戏中一帧图片是 256×240 个像素,16×15个 block 和32×30个 tile,所以一个tile 是 8×8 个像素,一个block是2×2个tile。
- 2.tile 用 2bit 来表示一个像素,先把每个像素的低位保存一边,之后保存高位的。
- 3.CHR是存储图形数据的部分,用于呈现游戏中的精灵(角色、敌人等)和背景图案,CHR 最多只能有 256 个,每个tile可以被一个CHR填充,所以一个CHR为16字节。
- 4.NES的PPU通过 Nametable 控制背景层,每个Nametable对应一个 1024字节的内存区域(1024字节 = 960字节 tiles + 64字节属性表)。
- 5.一帧画面由每个tile取CHR中的一个元素组成,用索引来取,相当于CHR[index],索引从左到右,从上往下依次增大,全部索引可以组成一个Nametable。
这里先说说比较Misc的解题方法,虾饺皇的一位师傅是直接每次提取附件的1024字节当作nam文件,然后导入NES Screen Tool,估计是看完相关文章后猜测游戏里面的画面可能都会将索引数据存在二进制文件当中,而第二部分flag一定是会在游戏画面当中的,无论是结算画面还是啥画面,所以这样操作。
当然NES文件肯定不是说将每帧画面的Nametable数据都存储的,因为那样的话数据会非常大,在以前的时代显然不可能,不够猜测这样的做法可行也许是因为组成flag字符串的tile是不变的。
写个Python脚本提取
def split_binary_file(source_file, start_byte=0):
chunk_size = 1024 # 每个分块的大小
chunk_number = 1 # 分块计数器
try:
with open(source_file, 'rb') as f:
f.seek(start_byte) # 移动到指定的起始字节
while True:
# 读取分块数据
chunk_data = f.read(chunk_size)
if not chunk_data:
break # 数据读取完毕时退出循环
# 生成分块文件名
output_file = f"{chunk_number}.nam"
# 写入分块文件
with open(output_file, 'wb') as chunk_file:
chunk_file.write(chunk_data)
print(f"已创建分块文件: {output_file}")
chunk_number += 1
print(f"\n分割完成,共生成 {chunk_number - 1} 个文件")
except FileNotFoundError:
print(f"错误:文件 '{source_file}' 未找到")
except Exception as e:
print(f"发生未知错误: {str(e)}")
if __name__ == "__main__":
source = r"C:\Users\Daki\Desktop\chase_0c724b7c48763fd667d493ea48cce8c6.nes"
start_byte = 64 # 可以在这里指定起始字节
split_binary_file(source, start_byte)
然后再一个个将nam文件导入来查看有没有想要的flag图形
脚本运行后即可从13.nam中得到第二部分flag
但是这里存在一个问题,我们知道Nametable的1024字节=960字节 tiles + 64字节属性表,如果我们需要的flag索引数据提取出来之后刚好在64字节属性表的地方,那就根本无法看到flag了。这题文件如果从头开始提取就会很巧存在这样的情况,理论上解决的方法就是进行两次提取,一次从头开始提取,一次从二进制文件第64个字节处开始提取即可。
所以这题的flag就是TPCTF{D0_Y0U_L1KE_PLAY1N9_6@M3S_ON_Y0UR_N3S?}
那么这里我们可以知道确实二进制文件有部分游戏画面的CHR元素编号,我们其实就可以直接在CHR里面去找,一个CHR有两个Pattern Table,该NES文件两个Table相同,而flag第三部分明显是出题人故意放在CHR那里的。
flag给出都是FLAG PT.x IS,我们可以在CHR界面找FLAG四个图形的编号,分别是26 2C 21 27
确实存在两处,分别是flag的第一部分和第二部分,都是出现在游戏界面当中的,提取出来导入NES Screen Tool即可。