逆向实战 | PvZ资源包逆向

植物大战僵尸资源解密分析

分析的时候定位main.pak字符串,具体的分析流程有点多,有空的时候整理好贴上。

主要流程

  1. 对main.pak使用win32api进行大小等的判断,如果不存在就准备从资源中获取
  2. 通过一个主要的函数结构体对main.pak的文件头部分进行读取,在读取的过程中每个字节会xor 0xf7,然后做检测。
  3. 继续通过win32api对main.pak进行读取

主要结构

struct PAK{
    DWORD magic = 0xbac04ac0;     // 文件指纹
    DWORD align = 0;             // 对齐?
    file_header file_header_table[x];          // 若干个file_header结构体
    BYTE mark = 0x80;            // 标志文件头描述结构体部分结束
    FILE_CONTENT f[x];         // 文件内容若干,和文件头一一对应
}


// 文件头描述结构体
struct file_header{
    BYTE mark;         // 0x0代表文件 0x80文件头部分结束
    BYTE file_name_length;     // 文件名长度
    char* file_name;           // 文件名
    DWORD file_size;          // 文件大小
    FILETIME file_time;       // 文件时间结构体,应该是8bytes
}

解压方式

  1. 先对main.pak逐字节xor 0xf7
  2. 然后逐个读取文件头信息保存
  3. 最后创建相应目录,释放文件

解压脚本如下:

import struct
import os
fname = "main.pak"
outfile = "main.pak.xor"
file_path = ".\\extract\\"

def fmt(b): # 测试用16进制输出bytes数组
	for i in b:
		tmp = str(hex(i))[2:]
		while len(tmp) < 2:
			tmp = '0' + tmp
		print(tmp,end=' ')
	print()

class Pak:
	def __init__(self, pak):
		self.pak = pak          # pak文件bytes数组
		self.pointer = 0        # index指针
	def walk(self, length):
		b = self.pak[self.pointer: self.pointer+length]
		self.pointer += length
		return b
		
class Header:
	def __init__(self, fname, fsize):
		self.fname = fname
		self.fsize = fsize
#==============================================================================
# 对文件进行预处理

'''
with open(fname, 'rb') as f:
	tmp = f.read()

with open(outfile, 'wb') as f:
	for i in tmp:
		f.write((i^0xf7).to_bytes(1, 'little'))	
	
print('output file ok!')
'''

# 在0x1A9D0附近达到文件头结尾
# 开始释放处理文件

print('start to process file...')
with open(outfile, 'rb') as f:
	pak = f.read()

header = []         # header列表
magic = b'\xC0\x4A\xC0\xBA\x00\x00\x00\x00'
pak = Pak(pak)      # 创建Pak对象

if pak.walk(8) == magic:
	print(" > magic check ok!")
	
while 1:
	mark = pak.walk(1)
	if mark != b'\x00':          # 0x00 代表有文件 0x80表示没有
		fmt(mark)
		break
	print(" > get a file: ",end='')
	fname_len = pak.walk(1)[0]
	fname = pak.walk(fname_len)        # 文件名
	print(fname)
	fsize = struct.unpack("<I", pak.walk(4))[0]              # 文件长度
	print("    file size = " + str(fsize))
	header.append(Header(fname,fsize))                    # 添加到列表
	pak.walk(8)   # 时间戳
	
print("total: "+str(len(header))+ " files")
if not os.path.exists(file_path):         # 不存在文件释放路径, 则创建
	os.mkdir(file_path)
	print(' > create dir ok!')
else:
	print(" > file_path dir exist!")

for i in range(len(header)):
	h = header[i]             # 取出当前文件头信息
	sfname = h.fname.decode()
	p = sfname.split('\\')
	_p = file_path
	for i in range(len(p)-1):       # 递归创建路径
		_p += p[i]
		_p += '\\'
		# print(_p)
		if not os.path.exists(_p):         # 不存在文件释放路径, 则创建
			os.mkdir(_p)
			print('  > create dir {} ok!'.format(_p))
		else:
			print("  > dir exist!")

# 开始输出文件
for i in range(len(header)):
	h = header[i]
	fname = file_path + h.fname.decode()
	fsize = h.fsize
	print(" > excract: "+ fname)
	with open(fname, 'wb') as f:
		tmp = pak.walk(fsize)
		f.write(tmp)
	print("    ok!")

打包的脚本也写了一个:

import struct
import os
fname = "main.pak.xor"
outfile = "main.pak"
pack_dir = ".\\extract"

class Header:
	def __init__(self, fname, fsize):
		self.fname = fname
		self.fsize = fsize
		
		
def get_file(_dir):
	_file = []
	try:
		tmp = os.listdir(_dir)
		print("遍历文件夹"+_dir)
		for i in tmp:
			i = _dir +'\\'+ i
			if os.path.isdir(i):
				_file += get_file(i)
			else:
				_file.append(i)
	except:
		pass
	return _file
	
files = get_file(pack_dir)
print("total:" + str(len(files)) + " files")

header = []
for i in range(len(files)):
	header.append(Header((files[i].replace(pack_dir,''))[1:], os.path.getsize(files[i])))   # 录入文件头信息
	
print(' > start to pack!')
output = open('main.pak.xor', 'wb')
magic = b'\xC0\x4A\xC0\xBA\x00\x00\x00\x00'

output.write(magic)      # 写入文件头
for i in range(len(files)):
	output.write(b'\x00')   # 表示有文件
	output.write(struct.pack("<B", len(header[i].fname)))          # 文件名长度
	output.write(header[i].fname.encode())         # 文件名
	output.write(struct.pack("<I", header[i].fsize))       # 文件大小
	output.write(bytes.fromhex('8543CFCC0EFCCB01'))        # 时间戳 应该不影响
	
output.write(b'\x80')    # 写结束标志
# 开始输出文件
for i in range(len(files)):
	print(' > pack file: '+ files[i])
	with open(files[i], 'rb') as f:
		tmp = f.read()
	output.write(tmp)
		
output.close()
print(' > ok!')


with open(fname, 'rb') as f:
	tmp = f.read()

with open(outfile, 'wb') as f:
	for i in tmp:
		f.write((i^0xf7).to_bytes(1, 'little'))	
	
print('output file ok!')

==================================
2024/6/1更新
代码中解压文件读写的部分效率较低,在写书的时候使用bytearray重新了异或的片段,如下:

import struct
import os
fname = "main.pak"
outfile = "main.pak.xor"
file_path = ".\\extract\\"

def fmt(b): # 测试用16进制输出bytes数组
	for i in b:
		tmp = str(hex(i))[2:]
		while len(tmp) < 2:
			tmp = '0' + tmp
		print(tmp,end=' ')
	print()

class Pak:
	def __init__(self, pak):
		self.pak = pak          # pak文件bytes数组
		self.pointer = 0        # index指针
	def walk(self, length):
		b = self.pak[self.pointer: self.pointer+length]
		self.pointer += length
		return b
		
class Header:
	def __init__(self, fname, fsize):
		self.fname = fname
		self.fsize = fsize

#==============================================================================
# 对文件进行预处理
with open(fname, 'rb') as f:
	tmp = f.read()

tmp = bytearray(tmp)
for i in range(len(tmp)):
	tmp[i] ^= 0xf7

with open(outfile, 'wb') as f:
	f.write(bytes(tmp))	
	
print('output file ok!')
# 在0x1A9D0附近达到文件头结尾
# 开始释放处理文件

print('start to process file...')
with open(outfile, 'rb') as f:
	pak = f.read()

header = []         # header列表
magic = b'\xC0\x4A\xC0\xBA\x00\x00\x00\x00'
pak = Pak(pak)      # 创建Pak对象

if pak.walk(8) == magic:
	print(" > magic check ok!")
	
while 1:
	mark = pak.walk(1)
	if mark != b'\x00':          # 0x00 代表有文件 0x80表示没有
		fmt(mark)
		break
	print(" > get a file: ",end='')
	fname_len = pak.walk(1)[0]
	fname = pak.walk(fname_len)        # 文件名
	print(fname)
	fsize = struct.unpack("<I", pak.walk(4))[0]              # 文件长度
	print("    file size = " + str(fsize))
	header.append(Header(fname,fsize))                    # 添加到列表
	pak.walk(8)   # 时间戳
	
print("total: "+str(len(header))+ " files")
if not os.path.exists(file_path):         # 不存在文件释放路径, 则创建
	os.mkdir(file_path)
	print(' > create dir ok!')
else:
	print(" > file_path dir exist!")

for i in range(len(header)):
	h = header[i]             # 取出当前文件头信息
	sfname = h.fname.decode()
	p = sfname.split('\\')
	_p = file_path
	for i in range(len(p)-1):       # 递归创建路径
		_p += p[i]
		_p += '\\'
		# print(_p)
		if not os.path.exists(_p):         # 不存在文件释放路径, 则创建
			os.mkdir(_p)
			print('  > create dir {} ok!'.format(_p))
		else:
			print("  > dir exist!")

# 开始输出文件
for i in range(len(header)):
	h = header[i]
	fname = file_path + h.fname.decode()
	fsize = h.fsize
	print(" > excract: "+ fname)
	with open(fname, 'wb') as f:
		tmp = pak.walk(fsize)
		f.write(tmp)
	print("    ok!")

posted @ 2021-09-13 23:17  Mz1  阅读(509)  评论(0编辑  收藏  举报