前言
2010年是街机的黄金时代,随着移动终端和家用游戏机普及,街机行业也逐渐走向没落,尽管国内出台了一些政策鼓励游戏游艺设备行业发展,但此行业在未来一直都不被资本看好。2020 疫情更是给电玩行业带来了很大的打击。
在以前,100元可能只能买50个币,现在100元可以买200个币。
我喜欢赛车游戏,湾岸Midnight、头文字D、Speed Driver是近几年电玩城最火的游戏,因为它有账号功能,具备社交属性。前段时间在破解极速5的玩家APP,才知道IGS (鈊象电子)就是我小时候玩的三国战记和西游的厂商,IGS反破解在整个行业都是顶尖的,竞速游戏的极速系列更是号称无人可破解。
上个月,我家旁边的大玩家电玩城倒闭了,我感觉迟早有一天极速系列游戏会消失,便想挑战破解极速系列游戏。
IGS 极速系列发布顺序如下:
Game | Device Model | Year |
---|---|---|
Speed Driver:Evolution Evolution | 2004 | |
Speed Driver 2 | E2000 | 2007 |
极速 3 | E3000, E3100 | 2010 |
极速 4 | E3000, E3100, S3000 | 2013 |
极速 5 | S3000 | 2019 |
我计划从E2000平台开始破解,难度肯定比PGM(PolyGame Master)更低,因为是基于PC开发的,不需要额外模拟声卡和显卡。硬件几乎没有物理保护,CPU指令集是x86,OS是Linux,没有反调试,也没有VMP,这不就是新手村吗。
Speed Driver系列在亚洲影响力媲美湾岸Midnight,头文字D,雷动G(都被破解了)。IGS的反破解是最成功的,在整个产品生命周期内都未被破解。
硬件分析
在 Arcade-docs 可以查询到每个设备的硬件信息和支持的游戏 凭借我擅长的淘电子垃圾能力,已经搞到一台E2000主机。国外有不少爱好者购买,所以价格涨上去了。
外部接口分析
正面有两个RS-485接口,并且CF卡可以从外部拆卸
背面接口,具有千禧年PC的接口特点
- 12V DC
- 2 x DB9 COM
- 4 x USB 2.0
- RJ45 LAN
- 3.5mm Audio Out
- 25pin + 30pin 的I/O口
主板分析
考虑到这个破解难度不高,其他贴了贴纸遮盖丝印的元件就不分析了,太麻烦
我买的这个主机安装了 Percussion Master 2008 游戏,但极速2也能运行
- 主板型号:I-JOIN E2000-V256 IH-02 (研华)
- A - CPU:Celeron M 370 (1.5 GHz)
- B - 北桥:未知
- C - GPU:NVIDIA GeForce 6200 (256 MB, GDDR2)
- D - 2 x DDR 333 256MB
- E - Chipset: Intel 852GME (ICH4-M)
- F - PCI9030,是GPIO芯片,可能用于游戏控制器的信号传输。
- G - I/O 控制器 有LPC接口
- H - A11 BIOS芯片 SST 49LF004B(PLCC32)
- I - IH-C02 ALTERA EPM3032ALC44-10N CPLD (PLCC44), 控制器,里面有ROM,用途不详
- J - IGS EV29LV640-90PCR 8MB EEPROM,DIP 48-Pin 封装,IGS定制的芯片。这个芯片专门贴了游戏名称的标签,说明BIOS ROM 也和游戏有关。可能会有跟V21芯片相关的内容。
- K - V21,IGS036E,也许是一颗FPGA或者ASIC,用于加密处理控制信号的输入输出
- L - 64K x 16 HIGH-SPEED CMOS STATIC RAM x3
- M - CF卡:ADATA 2GB (266X)
- N - IDE硬盘接口
我有很多种办法拿硬件的root shell,但我对游戏加载的过程更感兴趣,因此先做软件逆向分析吧。
I/O板分析
这几块板子又称控制板,应该是用于连接游戏控制器,比如方向盘、刹车油门、投币器之类的。
通过连接主板的25pin + 30pin 的I/O口 进行通信
价格可能比主机本体还贵
应该可以使用逻辑分析仪来捕获这些传感器信号,然后实现控制功能。但据说控制信号由V21芯片(ASIC)处理,可能会加密。我也不打算去仿制一个控制板。这种街机游戏没有地平线5好玩。现在电玩城也便宜,真想玩的话,就直接花钱去电玩城,或者买一套方向盘在家里玩。
文件系统分析
直接dump CF卡,文件大小2GB,file命令的回显如下:
DOS/MBR boot sector, LInux i386 boot LOader; partition 1 : ID=0x1, active, start-CHS (0x0,1,1), end-CHS (0x2,63,63), startsector 63, 12033 sectors; partition 2 : ID=0x83, start-CHS (0x3,0,1), end-CHS (0x22,63,63), startsector 12096, 129024 sectors; partition 3 : ID=0x83, start-CHS (0x62,0,1), end-CHS (0x399,63,63), startsector 395136, 3322368 sectors; partition 4 : ID=0x83, start-CHS (0x23,0,1), end-CHS (0x61,63,63), startsector 141120, 254016 sectors
启动过程,LILO作为MBR,直接安装在第一个扇区,用来引导Linux
+----------------------------------------+
| Master Boot Record Operating system |
|----------------------------------------|
| LILO ---------------> Linux |
| ---> other OS |
+----------------------------------------+
通过fdisk查看分区信息。是Linux操作系统,四个分区,并首尾连续,最后一个分区之后的内容,都是0x00,不存在隐藏分区
Disk ./percussion_master_2008.img: 1.77 GiB, 1903878144 bytes, 3718512 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x6e4a3fef
Device Boot Start End Sectors Size Id Type
./percussion_master_2008.img1 * 63 12095 12033 5.9M 1 FAT12
./percussion_master_2008.img2 12096 141119 129024 63M 83 Linux
./percussion_master_2008.img3 395136 3717503 3322368 1.6G 83 Linux
./percussion_master_2008.img4 141120 395135 254016 124M 83 Linux
E2000平台的分区有一个特点,第三分区和第四分区,顺序是反的。
第二分区和第四分区,7z无法完全提取,binwalk能识别出一些压缩区域,因此大概率是经过了魔改的文件系统。
每个分区的用途如下(最后我找到了分区2,4的原始文件系统类型):
Partition 1: Ext2, Bootloader, kernel
Partition 2: IGS CRAMFS, RootFS
Partition 3: Ext3, Log Data
Partition 4: IGS SquashFS, Game Data
Kernel 逆向
目前未找到rootfs和游戏程序,他们大概率位于第二分区和第四分区。 我认为逆向kernel可以少走不少弯路。不依赖内核,其实可以直接硬逆出文件系统的格式,但这样很无聊,而且最终写出来的extractor,肯定比不了开源的文件系统解压工具。 首先看第一个分区,有一个 loader 和 kernel。显示了内核版本,但是这里不一定是正确的版本。
SD2: LILO (LInux LOder)
SD2-OS-61P: Linux kernel x86 boot executable, bzImage, version 2.4.31-IGS_V0.5a (james@Code_Server.linnet.net.tw) #1 Thu Aug 30 19:06:24 CST 2007, RO-rootFS, root_dev 0X305, Normal VGA, setup size 512*6, syssize 0x3b88, jump 0x230 0xe800040000000000 instruction, protocol 2.3
LILO经过了IGS 定制开发,会显示,IGS Loader v2.0 Boot Menu 2007/04,可判断版本是lilo-22.8
这里没什么好分析的,直接开始提取vmlinux,内核文件是bzImage格式,需要解压
7z x ./SD2-OS-61P -okernel.decomped
接下来就是普通的内核逆向,第一步计算基址,基址有很多种确定方法,可以参考我其他文章。
这里多看两眼就猜出来了,0xC0100000
基址配置正确,IDA就可以自动识别出一些交叉引用,剩下的需要手动还原。IDA 9.0修改了一些API,又得重新写一下脚本。
import ida_bytes
import ida_segment
import ida_funcs
import ida_ida
def find_and_make_function(start_ea, end_ea, pattern_str):
image_base = idaapi.get_imagebase()
pattern = ida_bytes.compiled_binpat_vec_t()
err = ida_bytes.parse_binpat_str(pattern, image_base, pattern_str, 16)
if err:
return
current_ea = start_ea
found_count = 0
while current_ea < end_ea:
current_ea, index = ida_bytes.bin_search(
current_ea,
end_ea,
pattern,
ida_bytes.BIN_SEARCH_FORWARD | ida_bytes.BIN_SEARCH_NOSHOW)
if current_ea == ida_idaapi.BADADDR:
break
if current_ea % 4 != 0:
current_ea += 1
continue
if ida_bytes.get_flags(current_ea) & ida_bytes.FF_CODE:
if idc.get_func_flags(current_ea) != -1:
current_ea += 1
continue
seg = ida_segment.getseg(current_ea)
if not seg:
current_ea += 1
continue
if not ida_funcs.add_func(current_ea):
print("Create function failed at 0x{:X}".format(current_ea))
else:
found_count += 1
current_ea += 4
print("Search finished, {} functions created".format(found_count))
find_and_make_function(0xC0100000, 0xC0508000, "55 89 E5")
# 可以根据实际情况自己定义 function entry opcode
当然到这里还没有完全识别出全部交叉引用,在data段,还有一些字符串和offset还原的不是很好,但影响不大,实在是没有思路的话,就把这些内容还原,说不定会有新发现。
接下来就是找分析切入点,实在没啥技术含量,我有很多种方案,比如从文件系统特征入手之类的,但全写出来就像说“茴字有五种写法”一样搞笑。
可以确认内核版本是 2.4.31,并且IGS版本是v1.0,而不是bzImage显示的v0.5。
根据下列字符串,可以确定IGS魔改文件系统对应的原始文件系统版本
- rofs:cramfs
- shfs: squashfs 2.2
Linux 2.4.31 没有squashfs,直接找squashfs的patch,IGS魔改SHFS是基于squashfs2.2。 https://master.dl.sourceforge.net/project/squashfs/OldFiles/squashfs2.2r2.tar.gz
Linux 2.4没有支持kallsysm,因此需要手动还原符号。一些printf,memcpy,str之类的函数可以快速一眼看出来,但是我要分析系统启动过程,所以需要还原一些内核专用的符号。
这种老设备,在新的linux下没有办法交叉编译,我安装了一个CentOS 5.10的32位虚拟机,成功编译,提取出 System.map 和 vmlinux,这样就可以方便对比IGS魔改的部分。
对于这种找不到ramdisk或者rootfs的固件,第一步肯定是找到kernel里的sys_mount和boot parameters
root=/dev/hdc2 ro console=ttyS1,115200
sys_mount("/dev/hdc3", "/PM2008v2", "shfs", flag, data)
sys_mount("/dev/hdc4", "/PM2008v2/pm2_data", "ext3", flag, data)
这里可以确定,rootfs应该是第二分区,游戏数据在第三分区。
挂在ramdisk的逻辑,位于此处
prepare_namespace
->rd_load_disk
->rd_load_image
->identify_ramdisk_image
在此次可以确定IGS 支持 squashfs 和 igs rofs 格式的ramdisk。并且可以确定它们的magic。
分析过程就不记录了,他们把 FS 的header魔改了,分析起来有一些恶心🤢。
提取文件系统
我分析了 E2000 平台的三种自定义文件系统 header,内容如下。
IGS SHFS V1 Header
struct squashfs_super_block_22_v1 {
unsigned int s_magic; // 0xD4AA2682
unsigned int block_size_1:16;
unsigned int block_log:16;
unsigned int major_number;
unsigned int minor_number;
unsigned int inodes;
unsigned int bytes_used;
unsigned int uid_start;
unsigned int guid_start;
unsigned int inode_table_start;
unsigned int directory_table_start;
unsigned int flags:8;
unsigned int no_uids:8;
unsigned int no_guids:8;
int mkfs_time /* time of filesystem creation */;
squashfs_inode root_inode;
unsigned int block_size;
unsigned int fragments;
unsigned int fragment_table_start;
} __attribute__ ((packed));
IGS SHFS V2 Header
以 Percussion Master 2008 为例
struct squashfs_super_block_22_v2 {
unsigned int s_magic; // 0xD4AA2682
char igs_info[64]; // banner
unsigned int block_size_1:16;
unsigned int block_log:16;
unsigned int igs_fs_version_major; // 59 17 23 96
unsigned int igs_fs_version_minor; // E1 5D 70 00
unsigned int major_number; // 31 64 52 E5
unsigned int minor_number; // 92 2C 03 68
unsigned int inodes;
unsigned int bytes_used;
unsigned int uid_start;
unsigned int guid_start;
unsigned int inode_table_start;
unsigned int directory_table_start;
unsigned int flags:8;
unsigned int no_uids:8;
unsigned int no_guids:8;
int mkfs_time /* time of filesystem creation */;
squashfs_inode root_inode;
unsigned int block_size;
unsigned int fragments;
unsigned int fragment_table_start;
} __attribute__ ((packed));
IGS ROFS V1 Header
struct cramfs_inode {
unsigned int inode_magic;
unsigned int namelen:6, offset:26;
unsigned int size:24, gid:8;
unsigned int mode:16, uid:16;
};
struct cramfs_info {
unsigned int crc;
unsigned int edition;
unsigned int blocks;
unsigned int files;
};
struct igs_rofs_super_block {
unsigned int magic1; // 0x81006e6a
unsigned int future;
char igs_info[64];
unsigned int size;
unsigned int magic2; // 0xD4AA2682
unsigned int flags;
unsigned int padding;
cramfs_info fsid;
char name[64];
cramfs_inode root[2];
};
igs_rofs_super_block File;
将上述文件header,移植到cramfs-tools和squashfs-tools,就可以完美提取文件系统,并且还可以发现文件是否损坏。我已经将代码上传至github。https://github.com/gorgiaxx/igs-toolkits
这个版本提取难度不高,但下个版本E3000就用上加密了。
玩家 @novacosmic00 之前请我帮忙提取 GoGoBall 的文件,但他说他已经解密了其他游戏的文件。唯独 GoGoBall 解不了。用我修改的工具尝试提取,发现文件CRC不对,但还是能提取出大部分。
在我提取完固件之后,和玩家 @novacosmic00 沟通,才知道两年前有人研究过这个,并写了提取脚本。
https://github.com/batteryshark/igstools/tree/main/scripts
作者可能是根据文件系统内容,找出地址规律反推结构体的,这也是一种办法,但缺点是提取出来的文件可能比较混乱,而且无法检测CRC错误。
shadow-
shadow文件,默认都清空了root密码
root:x:13130:0:99999:7:::
test::13074:0:99999:7:::
这是原始的文件
root:$1$qAw/5vcb$x9rPCAwLMdRBQXwlq1zG70:13130:0:99999:7:::
test:$1$JjP1oLAJ$xilZIedv3S3jbs8oTZAad1:13074:0:99999:7:::
反破解
/PM2008v2/PM2008v2,符号去除了,并且根据字符串特征,也像是正常的loader
但实际上,如果在自己电脑运行,他会把你分区前512k写0。我当时居然运行了,还好我没给root,而且硬盘是NVME的。谁能想到这个18年前的游戏,还有这一手🙈。以前从来没接触过这个领域,据说街机的反破解有很多自杀式逻辑。
下一篇文章将分析游戏内部的保护机制