逆向 AnimeGANv3 程序
使用声明
本文仅供学习交流使用,请勿用于商业用途或违法用途,否则后果自负。本文作者不对因此产生的任何后果负责。本文作者不对本文的内容负责,请自行参考。
软件样本
AnimeGANv3 早期还没有发布模型文件,但提供了加密文件,但目前已经开源,所以本文仅用于学习。
加密文件可以在 Hugging Face 上获取,下载 6b4330d(当前最新)中的:
AnimeGANv3_bin_encryption.so
AnimeGANv3_src_encryption.so
这个程序似乎是 Pyinstaller 打包的,我们可以对发布的程序进行解包。
使用 pyinstxtractor
工具进行解包,得到版本 3.7,使用 Python 3.7 版本的 pyinstxtractor
进行解包,得到的解包文件夹,AnimeGANv3.exe_extracted
。
使用 uncompyle6
反编译 PYZ-00.pyz_extracted/pb_and_ONNX_model/test_by_onnxZIP.pyc
,得到源代码。
进而得到密码:
djfdawieurFKJAKSJFKK12338913*^&^&*SJFAKJFMSAKFLLKJKLJASFKJ34892
测试最小可执行版本程序 test_by_onnxZIP.py
,修改后代码如下:
import os
import time
from glob import glob
import cv2
import numpy as np
import onnxruntime as ort
pic_form = ['.jpeg', '.jpg', '.png', '.JPEG', '.JPG', '.PNG']
def to_32s(x: int):
if x < 256:
return 256
return x - x % 32
def check_folder(path: str):
if not os.path.exists(path):
os.makedirs(path)
return path
def process_image(img: np.ndarray, x32=True):
h, w = img.shape[:2]
if x32:
img = cv2.resize(img, (to_32s(w), to_32s(h)), interpolation=cv2.INTER_AREA)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) / 127.5 - 1.0
return img, [w, h]
def load_test_data(path: str):
img = cv2.imread(path)
img, wh = process_image(img)
img = np.asarray(np.expand_dims(img, 0))
return img, wh
def save_image(img: np.ndarray, save_path: str, wh: list[int]):
img = (img.squeeze() + 1.0) / 2 * 255
img = img.astype(np.uint8)
img = cv2.resize(img, (wh[0], wh[1]))
cv2.imwrite(save_path, cv2.cvtColor(img, cv2.COLOR_RGB2BGR))
return img
def Convert(input_imgs_path: str, output_path: str, onnx_file: str):
result_dir = check_folder(output_path)
test_files = glob('{}/*.*'.format(input_imgs_path))
test_files = [x for x in test_files if os.path.splitext(x)[-1] in pic_form]
with open(onnx_file, 'rb') as fp:
onnx = fp.read()
session = ort.InferenceSession(
onnx, providers=['CPUExecutionProvider']
)
x = session.get_inputs()[0].name
y = session.get_outputs()[0].name
begin = time.time()
for i, sample_file in enumerate(test_files):
t = time.time()
sample_image, wh = load_test_data(sample_file)
image_path = os.path.join(
result_dir, '{0}'.format(os.path.basename(sample_file)))
fake_img = session.run(None, {x: sample_image})
save_image(fake_img[0], image_path, wh)
print(f"Processing image: {i}, " + sample_file, ' size:',
(wh[0], wh[1]), f" time: {time.time() - t:.3f} s")
end = time.time()
if len(test_files) > 0:
print(f"Average time per image : {(end - begin) / len(test_files)} s")
else:
print(
f"There is no image with the format in {('.jpeg', '.jpg', '.png')} ")
return True
if __name__ == '__main__':
model_name = './animeganv3_H64_model0.onnx'
input_path = input('input > ')
output_path = input('output > ')
Convert(input_path, output_path, model_name)
逆向 AnimeGANv3 链接库
使用 IDA 反编译 AnimeGANv3_src_encryption.so
,可以看到很多 __pyx
开头的函数,可以得出这是 Cython 编写的程序,破解难度较高。
从入口函数和上面破解的 test_by_onnxZIP.py
来看,作者使用了相同的函数接口,可以断言其内容也大致相同。
猜测此时加密库中保存着二进制格式的 .onnx
文件,寻找其位置。通过文件大小和命名,它一定在 AnimeGANv3_bin_encryption.so
中。
IDA 反编译 AnimeGANv3_bin_encryption.so
,发现数据段居多,只有几个函数。
发现大量数据段都是文本格式的,很明显是 Base64 编码文本。从数据段 0x4220 ~ 0x1e15f4c
都是文本,截取获得二进制流。
from base64 import b64decode
with open('AnimeGANv3_bin_encryption.so', 'rb') as f:
data = f.read()
# 取文件段
content = data[0x4220:0x1e15f4c]
# 首先确定一下这是一个文件还是多个文件的编码,检查其 '\0' 个数
content.count(b'\0')
# 得到 92,说明有多个文件,将多个分隔符去掉
last_length = len(content)
while True:
content = content.replace(b'\0\0', b'\0')
curr_length = len(content)
if curr_length == last_length:
break
last_length = curr_length
fs = content.split(b'\0')
len(fs)
# 得到 6 个文件分片,符合预期
# 保存这几个文件
for i, stream in enumerate(fs):
with open('model{}.onnx'.format(i + 1), 'rb') as f:
f.write(b64decode(stream))
最后我们查看这些文件到底是不是 .onnx
文件,查看文件的前几个字节:
0x08 0x04 0x12 0x07 t f 2 o n n x
确实符合 .onnx
文件的开头,使用 Netron 随便打开一个。
发现属性有 描述:converted from AnimeGANv3_PortraitSketch_25.pb
,确定这个模型的名字了,修改这几个文件的名字即可。
AnimeGANv3_Arcane_46.onnx
AnimeGANv3_Hayao_36.onnx
AnimeGANv3_PortraitSketch_25.onnx
AnimeGANv3_Shinkai_40.onnx
AnimeGANv3_usa_118.onnx
还有一个文件没名字,看上去是输入任意图像,输出三个灰度图,但不知道干嘛的,把上面破解出来的测试文件改一下代码,因为输入的维度顺序改变了。
def load_test_data(path: str):
img = cv2.imread(path)
img, wh = process_image(img)
img = img.transpose((2, 0, 1))
img = np.asarray(np.expand_dims(img, 0))
return img, wh
跑了一下,生成了三个灰度图,没看出来什么规律,怀疑是和人脸什么的有关。
读取流中的内容
读取文件流中的图片,并转换为 OpenCV 的一种安全做法,支持性好:
import io
import cv2
import numpy as np
from PIL import Image
byte_stream = io.BytesIO(...)
pimg = Image.open(byte_stream)
if len(pimg.getbands()) != 3:
pimg = pimg.convert('RGB')
img = cv2.cvtColor(np.asarray(pimg), cv2.COLOR_RGB2BGR)
然后放入 ONNXRuntime 中进行推理。
如果要使用 GPU,遵循 对照表 进行安装依赖。