EVA2格式资源包逆向解析:从原理到Python解包实战

📅 2026/8/4 2:56:09
EVA2格式资源包逆向解析:从原理到Python解包实战
最近在逆向分析一些移动应用资源时遇到了不少厂商自定义的加密包格式其中EVA2格式就是比较典型的一种。这类格式通常用于保护游戏或应用的资源文件直接解压会得到一堆乱码。网上资料零散工具也五花八门踩了不少坑。本文将系统梳理EVA2格式的解包流程从原理分析到工具实战提供一套完整的解决方案。无论你是安全研究员、游戏爱好者还是对移动应用资源逆向感兴趣的开发者都能从零开始一步步学会如何拆解这类加密资源包。1. EVA2 格式背景与核心概念在移动应用特别是游戏开发中为了优化加载速度、保护知识产权和防止资源被轻易窃取开发者会将图片、音频、配置文件等资源打包成一个单一的文件。EVA2就是其中一种自定义的打包格式。它并非一个公开的标准而是由特定引擎或公司内部定义的容器格式通常包含文件索引表和经过简单加密或混淆的原始数据块。1.1 什么是资源包与解包简单来说资源包就像一个压缩的行李箱。开发者把成百上千个散乱的文件衣服、鞋子、洗漱用品按照一定规则整理、压缩甚至上锁最后塞进一个行李箱.eva2,.ab,.bundle等文件里。应用运行时再从这个“行李箱”里按需取出文件使用。解包就是打开这个行李箱的过程。我们需要找到“行李箱”的锁加密算法和内部物品的清单文件索引然后把里面的东西原封不动地拿出来。对于EVA2格式这个过程通常涉及解析其特定的文件头结构、解密数据块以及根据索引重建原始文件。1.2 常见的相关格式在搜索和学习过程中你可能会遇到其他类似的格式了解它们的区别有助于定位正确的工具和方法OZIP: 通常是 OPPO 手机系统更新的包格式有特定的签名和加密方式需要专门的ozip解包工具。AssetBundle (.ab): Unity 引擎使用的资源打包格式有公开的文档和丰富的解包工具如 AssetStudio。一般压缩包 (Zip, RAR): 使用标准算法可用常见软件解压。EVA2: 属于自定义格式需要针对其特定结构进行解析。它可能源自某个特定的游戏引擎或中间件。核心区别EVA2的关键在于其非公开的、自定义的文件结构。通用解压工具对它无效必须使用能识别其结构或能暴力破解其简单加密的专用工具。1.3 为什么需要解包 EVA2学习与研究分析游戏的美术资源、音频设计或配置文件结构用于学习或非商业的 MOD 制作。本地化与修改提取文本资源进行翻译或替换部分贴图、音效需注意版权和法律风险。安全评估作为安全研究人员分析应用内资源是否存在潜在风险。数据恢复当官方不再支持某个应用但你想提取其中的个人数据或珍贵资源时。重要声明解包行为可能违反软件的用户许可协议EULA或侵犯知识产权。本文内容仅限技术学习和安全研究之用请务必在合法合规的前提下使用相关技术不得用于任何商业侵权或破坏性目的。2. 环境准备与工具说明工欲善其事必先利其器。解包EVA2文件通常不依赖复杂的 IDE但需要准备好命令行环境和合适的工具。以下环境基于 Windows 10/11 系统但原理同样适用于 macOS 和 Linux。2.1 基础环境准备操作系统Windows 10/11, macOS, 或 Linux 发行版如 Ubuntu。Python 环境许多解包脚本由 Python 编写。请确保安装 Python 3.6 或更高版本。访问 python.org 下载安装包。安装时务必勾选 “Add Python to PATH”。安装后在命令行输入python --version或python3 --version验证。文本编辑器/十六进制编辑器用于查看文件结构和修改脚本。推荐 VS Code、Sublime Text编辑脚本。推荐 HxD、010 Editor分析二进制结构。2.2 核心解包工具介绍由于EVA2并非单一标准不存在一个“官方”解包工具。我们需要根据文件的具体特征选择或调整合适的工具。以下是几种常见的思路和对应的工具通用逆向工具010 Editor功能强大的十六进制编辑器支持通过自定义模板Template来解析特定文件格式。如果你能找到或编写出EVA2的模板就能直观地看到其内部结构。CyberChef一个网页端的“数字瑞士军刀”内置了大量编码、解码、解密、哈希操作。可以尝试用它对文件块进行常见的 XOR、AES 等解密操作。专用解包脚本 这是最常见的方式。开发者或逆向社区在分析特定游戏或应用后会编写 Python 或 C# 脚本来解包其资源。你需要根据你的EVA2文件来源如游戏名称去 GitHub、论坛等地方搜索。搜索关键词“游戏名” extract,“游戏名” unpack,eva2 unpacker。基于已知工具修改 如果找不到完全匹配的工具可以寻找解包类似格式如 Unity AssetBundle的工具源码通过对比分析修改其解析逻辑以适应EVA2格式。这需要一定的逆向工程基础。本文演示将基于一个假设的 Python 解包脚本因为这种方式最灵活也最能体现解包原理。我们将创建一个模拟的EVA2文件并编写解包脚本。2.3 创建测试工作区在开始前创建一个干净的目录来存放所有文件避免混乱。mkdir eva2_unpack_tutorial cd eva2_unpack_tutorial3. EVA2 文件格式原理拆解要编写解包器首先得了解“行李箱”的结构。虽然不同的EVA2实现有差异但其核心思想大同小异。下面我们以一个典型的简化结构为例进行讲解。3.1 典型 EVA2 文件结构一个EVA2文件通常由三部分组成文件头Header包含魔数Magic Number、版本号、文件表偏移量、加密标志等元信息。魔数是识别文件类型的关键例如可能是EVA2的 ASCII 码或特定的十六进制值。文件索引表File Table/File Index相当于“行李箱的清单”。它记录了包内每个文件的文件名有时是哈希值或ID文件在包内的数据偏移量从哪里开始文件大小可能的压缩或加密标志文件数据区Data Blocks所有被打包文件的原始数据连续存储在这里。数据可能被压缩如 LZ4、zlib或使用简单加密如 XOR、AES处理过。---------------------------- | 文件头 (Header) | | - 魔数 (e.g., EVA2) | | - 版本号 | | - 文件表偏移量 | | - 文件表条目数 | | - 加密密钥/标志 | ---------------------------- | 文件索引表 (File Table) | | [条目1] 文件名偏移量大小| | [条目2] 文件名偏移量大小| | ... | ---------------------------- | 文件数据区 (Data) | | 文件1的原始数据 | | 文件2的原始数据 | | ... | ----------------------------3.2 关键概念偏移量与大小偏移量Offset从文件开头0字节位置到目标数据起始位置的字节数。大小Size目标数据占用的总字节数。解包时我们根据索引表中的偏移量和大小就能在数据区精准地“切割”出每个文件的原始数据。3.3 常见的简单加密方式EVA2使用的加密通常不会非常复杂常见的有XOR 异或加密使用一个密钥Key或密钥流与原始数据逐字节进行异或操作。异或加密的特点是用相同的密钥再执行一次异或操作就能解密。公式密文 明文 ^ 密钥明文 密文 ^ 密钥字节流加减法给每个字节加上或减去一个固定值。简单的块加密如自定义的置换较少见。识别加密如果解压出来的文件开头是已知的文件签名如 PNG 的89 50 4E 47 ZIP 的50 4B但后面是乱码很可能只是用了简单的 XOR 或字节变换。4. 实战编写一个简易 EVA2 解包脚本现在我们将动手实践。为了演示我们先创建一个模拟的、结构清晰的EVA2文件然后编写 Python 脚本来解包它。这个过程将完整还原解包的所有步骤。4.1 创建模拟的 EVA2 资源包我们首先创建两个简单的资源文件然后将它们“打包”成自定义格式。1. 创建资源文件在eva2_unpack_tutorial目录下创建两个文件test_image.png(这里我们用一个文本文件模拟)config.json# 模拟一个PNG文件头和一些数据 echo -ne \x89PNG\r\n\x1a\nThis is fake PNG data. test_image.png # 创建一个简单的配置文件 echo {language: zh-CN, volume: 80} config.json2. 编写“打包”脚本 (create_mock_eva2.py)这个脚本模拟了游戏引擎的打包过程生成一个具有我们自定义结构的.eva2文件。#!/usr/bin/env python3 # create_mock_eva2.py import struct import os def create_mock_eva2(): files_to_pack [ {path: test_image.png, name: images/icon.png}, {path: config.json, name: data/settings.json} ] # 1. 准备文件数据 file_entries [] all_data bytearray() current_offset 0 for f in files_to_pack: with open(f[path], rb) as fp: data fp.read() # 模拟一个简单的XOR加密每个字节与0xAA异或 encrypted_data bytes([b ^ 0xAA for b in data]) file_entries.append({ name: f[name], original_size: len(data), encrypted_size: len(encrypted_data), offset: current_offset, data: encrypted_data }) all_data.extend(encrypted_data) current_offset len(encrypted_data) # 2. 构建文件头 # 魔数 EVA2 的ASCII码4字节 magic bEVA2 # 版本号假设为12字节无符号整数 version 1 # 文件表条目数2字节无符号整数 entry_count len(file_entries) # 文件表相对于文件开始的偏移量4字节无符号整数 # 头大小 魔数(4) 版本(2) 条目数(2) 保留(4) 12字节 header_size 12 file_table_offset header_size # 3. 构建文件索引表二进制格式 file_table_data bytearray() for entry in file_entries: # 文件名以null结尾的字符串假设最大255字节 name_bytes entry[name].encode(utf-8)[:255] file_table_data.extend(name_bytes) file_table_data.append(0) # null终止符 # 偏移量4字节无符号整数 file_table_data.extend(struct.pack(I, entry[offset])) # 加密后大小4字节无符号整数 file_table_data.extend(struct.pack(I, entry[encrypted_size])) # 原始大小4字节无符号整数 file_table_data.extend(struct.pack(I, entry[original_size])) # 加密类型1字节 (0None, 1XOR)这里用1 file_table_data.append(1) # 保留字节3字节 file_table_data.extend(b\x00\x00\x00) # 4. 计算数据区偏移量 data_offset file_table_offset len(file_table_data) # 5. 写入最终文件 with open(mock_pack.eva2, wb) as f: # 写入文件头 f.write(magic) # 4字节 f.write(struct.pack(H, version)) # 2字节 f.write(struct.pack(H, entry_count)) # 2字节 f.write(struct.pack(I, file_table_offset)) # 4字节 # 写入文件索引表 f.write(file_table_data) # 写入文件数据区 f.write(all_data) print(f[] Created mock EVA2 file: mock_pack.eva2) print(f[] Header size: {header_size}) print(f[] File table offset: {file_table_offset}, size: {len(file_table_data)}) print(f[] Data offset: {data_offset}) if __name__ __main__: create_mock_eva2()运行这个脚本生成mock_pack.eva2文件。python create_mock_eva2.py4.2 编写 EVA2 解包脚本现在我们来编写解包脚本 (unpack_eva2.py)。这个脚本需要完成读取并验证文件头。定位并解析文件索引表。根据索引表读取加密的数据块。解密数据。将解密后的数据写入到输出文件保持目录结构。#!/usr/bin/env python3 # unpack_eva2.py import struct import os import sys def unpack_eva2(file_path, output_diroutput): if not os.path.exists(file_path): print(f[-] File not found: {file_path}) return with open(file_path, rb) as f: # 1. 解析文件头 magic f.read(4) if magic ! bEVA2: print(f[-] Not a valid EVA2 file. Magic: {magic}) return print(f[] Magic number OK: {magic}) version struct.unpack(H, f.read(2))[0] # 小端序2字节无符号整数 entry_count struct.unpack(H, f.read(2))[0] file_table_offset struct.unpack(I, f.read(4))[0] # 小端序4字节无符号整数 print(f[] Version: {version}, File entries: {entry_count}) print(f[] File table offset: 0x{file_table_offset:X}) # 2. 跳转到文件索引表并解析 f.seek(file_table_offset) file_entries [] for i in range(entry_count): # 读取以null结尾的文件名 name_bytes bytearray() while True: byte f.read(1) if byte b\x00: break name_bytes.extend(byte) name name_bytes.decode(utf-8, errorsignore) # 读取元数据 data_offset struct.unpack(I, f.read(4))[0] encrypted_size struct.unpack(I, f.read(4))[0] original_size struct.unpack(I, f.read(4))[0] encrypt_type struct.unpack(B, f.read(1))[0] f.read(3) # 跳过3字节保留字段 file_entries.append({ name: name, data_offset: data_offset, encrypted_size: encrypted_size, original_size: original_size, encrypt_type: encrypt_type }) print(f [-] Entry {i}: {name}, Offset: 0x{data_offset:X}, Size: {encrypted_size}/{original_size}, Encrypt: {encrypt_type}) # 3. 创建输出目录 os.makedirs(output_dir, exist_okTrue) # 4. 提取并解密文件 for entry in file_entries: f.seek(entry[data_offset]) encrypted_data f.read(entry[encrypted_size]) # 解密逻辑 if entry[encrypt_type] 1: # XOR 加密 # 使用密钥 0xAA 进行异或解密 key 0xAA decrypted_data bytes([b ^ key for b in encrypted_data]) else: # 无加密 decrypted_data encrypted_data # 检查解密后大小是否匹配原始大小可选 if len(decrypted_data) ! entry[original_size]: print(f[!] Warning: Size mismatch for {entry[name]}. Decrypted: {len(decrypted_data)}, Expected: {entry[original_size]}) # 5. 写入输出文件保持路径 output_path os.path.join(output_dir, entry[name]) os.makedirs(os.path.dirname(output_path), exist_okTrue) with open(output_path, wb) as out_f: out_f.write(decrypted_data) print(f[] Extracted: {output_path}) print(f\n[] Unpack completed. Files saved to {output_dir}.) if __name__ __main__: if len(sys.argv) 2: print(Usage: python unpack_eva2.py path_to_.eva2_file [output_directory]) sys.exit(1) pack_file sys.argv[1] out_dir sys.argv[2] if len(sys.argv) 2 else output unpack_eva2(pack_file, out_dir)4.3 运行解包脚本现在用我们编写的解包器来处理刚才创建的mock_pack.eva2文件。python unpack_eva2.py mock_pack.eva2如果一切正常你将在终端看到类似下面的输出并且在当前目录下生成一个output文件夹里面包含了解包后的文件。[] Magic number OK: bEVA2 [] Version: 1, File entries: 2 [] File table offset: 0xC [-] Entry 0: images/icon.png, Offset: 0x11C, Size: 31/31, Encrypt: 1 [-] Entry 1: data/settings.json, Offset: 0x13B, Size: 30/30, Encrypt: 1 [] Extracted: output/images/icon.png [] Extracted: output/data/settings.json [] Unpack completed. Files saved to output.4.4 验证解包结果进入output目录查看文件内容是否还原。# 查看图片文件实际是文本模拟的 cat output/images/icon.png # 应输出原始的 PNG 头和数据而不是乱码‰PNG...This is fake PNG data. # 查看配置文件 cat output/data/settings.json # 应输出{language: zh-CN, volume: 80}如果icon.png显示的是乱码说明解密步骤可能不对。在我们的例子中因为加密和解密都使用了 XOR 0xAA所以能正确还原。5. 常见问题与排查思路在实际解包第三方EVA2文件时绝不会像我们的示例这么顺利。以下是你会遇到的一些典型问题及解决思路。问题现象可能原因排查与解决思路脚本报错Magic number mismatch1. 文件不是EVA2格式。2. 魔数不是EVA2而是其他值如EVA1,PK。3. 文件已损坏。1. 用十六进制编辑器如 HxD打开文件查看前4-8个字节是什么。2. 根据看到的魔数搜索对应的解包工具如PK是 ZIP。3. 确认文件来源和完整性。解包出来的文件全是乱码/无法打开1. 数据区使用了不同的加密算法或密钥。2. 文件索引表解析错误偏移量/大小算错。3. 数据可能被压缩了需要先解压。1.分析加密用十六进制编辑器查看解包出的文件开头。如果是已知格式如 PNG, JPEG对比标准文件头看字节差异是否有规律如每个字节都固定 /- 一个值或 XOR 了一个固定值。尝试用 CyberChef 进行 XOR Brute Force 或尝试常见密钥。2.检查解析逻辑核对脚本中struct.unpack的格式字符串I小端序I大端序是否正确。计算偏移量时是否加上了文件头、索引表的大小。3.检查压缩尝试用zlib,lz4等库对数据块进行解压。脚本解析文件索引表时崩溃1. 索引表的结构与脚本假设的不同字段顺序、大小、有无额外字段。2. 文件名存储方式不同长度前缀 vs 定长 vs 哈希。3. 存在对齐填充Padding。1.逆向分析使用 010 Editor 或手动分析。找到文件头后根据其指示的“文件表偏移量”跳过去仔细观察二进制布局。记录每个字段的字节数。2.动态调试在脚本中多打印中间状态比如读取的原始字节帮助定位崩溃点。3.寻找现成模板在 010 Editor 官网或社区搜索是否有对应游戏或引擎的模板。只能解包部分文件1. 索引表中有特殊条目如目录标记、空文件未处理。2. 加密方式不是统一的不同文件可能用了不同密钥或算法。3. 文件大小或偏移量计算有误导致后续文件定位错误。1. 检查脚本是否处理了所有索引条目打印出所有条目的信息进行分析。2. 检查索引表条目中是否有标志位指示加密类型或压缩类型。3. 验证偏移量计算下一个文件的偏移量 当前偏移量 当前文件大小检查是否一致。找不到对应的解包工具该EVA2格式非常冷门或为新版本。1.通用方法尝试使用file命令或TrID工具分析文件真实类型。2.搜索技巧用游戏名称、公司名称、引擎名称加上file format,reverse engineering,resource archive等关键词在 GitHub、Xentax 论坛、ZenHAX 社区搜索。3.自己动手按照本文的步骤使用十六进制编辑器进行手动分析逐步弄清其结构。6. 进阶分析与工程化建议当你掌握了基础解包后可以朝着更深入、更工程化的方向发展。6.1 逆向分析真实 EVA2 文件面对一个未知的EVA2文件系统化的分析步骤如下初步侦察使用file命令Linux/macOS或通过十六进制编辑器查看文件头。搜索文件中是否包含可读字符串如文件名、路径、UnityFS、CRI等这能提示其来源引擎。定位关键结构寻找可能的魔数。常见的如EVA2、UnityWebData、PK(Zip) 等。在文件末尾附近寻找线索有时索引表放在最后。寻找重复出现的模式可能是文件大小或偏移量的数字通常以4字节一组。假设与验证假设一个文件头大小如 12、16、32 字节尝试将其后的数据解析为[文件名][偏移量][大小]...的结构。编写一个小脚本尝试用不同的偏移量去读取假设的“文件数据”看看能否提取出有意义的文件头如PNG、JFIF。解密测试如果提取的数据是乱码假设是 XOR 加密。将乱码数据的前几个字节与已知文件格式的标准头进行 XOR看看能否得到一个稳定的密钥字节。例如假设是 PNG 文件标准头是89 50 4E 47。如果提取的数据头是23 FA E4 ED那么可能的 XOR 密钥就是89^23AA,50^FAAA,4E^E4AA,47^EDAA。密钥0xAA的可能性就很大。6.2 编写健壮的解包工具一个用于实际项目的解包工具应该考虑更多# 健壮性增强示例 class Eva2Unpacker: def __init__(self, file_path): self.file_path file_path self.endian # 默认小端序可自动检测 self.verbose True def detect_endian(self, magic): # 简单检测如果魔数是 2AVE (EVA2的大端序)则使用大端序 if magic b2AVE: return return def unpack(self, output_dir, decrypt_callbackNone): # decrypt_callback: 用户自定义的解密函数用于处理未知加密 try: with open(self.file_path, rb) as f: # ... 解析逻辑 ... for entry in file_entries: data self._read_data(f, entry) if decrypt_callback: data decrypt_callback(data, entry) else: data self._default_decrypt(data, entry) self._write_file(data, entry, output_dir) except Exception as e: print(f[-] Unpack failed: {e}) # 记录日志 import traceback traceback.print_exc() def _default_decrypt(self, data, entry): # 实现默认的几种解密算法尝试 algorithms [self._decrypt_xor, self._decrypt_add] for algo in algorithms: result algo(data, entry) if self._looks_valid(result): # 简单的有效性检查 return result return data # 无法解密返回原始数据 def _looks_valid(self, data): # 非常简单的启发式检查数据是否包含大量可打印ASCII或常见文件头 if len(data) 4: return False # 检查是否为常见文件魔数 common_magics [b\x89PNG, b\xff\xd8\xff, bPK\x03\x04, bRIFF] for magic in common_magics: if data.startswith(magic): return True return False6.3 安全与法律注意事项合法性仅对您拥有合法权利或已获授权的软件进行逆向工程和分析。许多国家的法律允许为互操作性、安全研究等目的进行有限的逆向工程但界限模糊务必谨慎。道德性不要利用解包资源进行作弊、盗版或损害原开发者利益的行为。社区内的 MOD 和汉化通常建立在尊重原作的基础上。风险从不明来源下载的“解包工具”可能包含恶意软件。尽量从信誉良好的开源平台如 GitHub获取工具或自己编写。生产环境本文所述技术主要用于分析。切勿在线上生产环境中随意解包或修改核心资源文件这可能导致应用崩溃或产生安全漏洞。7. 总结通过本文的梳理我们完成了对EVA2这类自定义资源包解包技术的系统性学习。从理解其作为资源容器的基本概念到剖析典型的文件头、索引表、数据区三层结构从动手创建模拟包、编写 Python 解包脚本进行实战再到探讨逆向真实文件的方法和排查各种常见问题。核心要点回顾原理是关键解包的本质是理解并解析二进制文件格式。掌握文件头、偏移量、索引表这些核心概念比记住某个特定工具的命令更重要。工具是辅助十六进制编辑器如 010 Editor、二进制分析工具和编程语言Python是你的主要武器。通用工具如 CyberChef在试探性解密时非常有用。思路要清晰遇到未知格式按照“识别魔数 - 定位结构 - 解析索引 - 提取数据 - 尝试解密”的流程进行。实践出真知最好的学习方式就是像我们一样从一个简单的模拟案例开始逐步增加复杂性。对于希望进一步深入的同学可以尝试分析一个真实的、来自某个小游戏的EVA2文件。为 010 Editor 编写一个解析EVA2格式的模板.bt 文件。研究更复杂的加密方式如 AES 或自定义的流加密。将解包工具图形化方便非技术人员使用。技术探索的乐趣在于抽丝剥茧将未知变为已知。希望这篇教程能为你打开二进制文件分析的大门。如果在实践中遇到了新的问题欢迎在社区分享和讨论通常你会发现你遇到的坑早已有人填过。