今日下午,群中一个小伙伴突然发了一个data.lua文件,并且问是什么编码,我下载一看,内容是加密的,但是有个头“FFSZ”,当时心想,这不是xxtea吗,有手就行啊,然后让小伙伴发来apk给我,遂有此贴。
目标apk包名:Y29tLmZ1bnBsdXMuZmFtaWx5ZmFybWNoaW5hLmh1YXdlaQ==
下载打开一看,是个农场类游戏,然后看一下lib目录,32和64位的都有,就拿arm64-v8a目录下手吧,里面就三个so文件,有一个最大的30多m,叫libgame.so,ok,不用想就知道今天的目标就是它了。
拖入ida,等加载(大文件ida加载是真的慢啊,不知道各位有没有加速的方式),然后搜索xxtea,ok有xxtea_decrypt字样,点进去,看一下参数,然后直接hook,hook代码如下:
[Python] 纯文本查看 复制代码
# -*- coding: utf-8 -*-
import codecs
import frida
import sys
import threading
str_host = '192.168.1.10:9999'
manager = frida.get_device_manager()
device = manager.add_remote_device(str_host)
pending = []
sessions = []
scripts = []
event = threading.Event()
jscode = """
function readStdString(str){
var isTiny = (str.readU8() & 1) == 0;
if(isTiny){
return str.add(1).readUtf8String();
}
return str.add(2*Process.pointerSize).readPointer().readUtf8String();
}
function inline_hook() {
while(1)
{
var so_addr = Module.findBaseAddress("libgame.so");
console.log("so_addr:", so_addr);
if (so_addr) {
var addr = Module.findExportByName("libgame.so", "xxtea_decrypt")
console.log("addr:", addr);
if(!addr)
{
continue;
}
Interceptor.attach(new NativePointer(addr), {
onEnter: function (args)
{
console.log("xxtea key:",readCString(args[2]));
},
onLeave: function (retval)
{
}
});
break;
}
}
}
inline_hook()
"""
pid = device.spawn(["com.funplus.familyfarmchina.huawei"])
session = device.attach(pid)
print(" Attach Application id:",pid)
device.resume(pid)
script = session.create_script(jscode)
print(' Running CTF')
script.load()
sys.stdin.read()
Hook成功,看到打印出ffs字样,嗯,这应该就是key了,下载一个xxtea解密工具,设置参数,解密:
image.png (47.05 KB, 下载次数: 0)
下载附件
2023-2-14 18:10 上传
如图,所有文件全部解密失败,此时我眉头一皱,心想事情并不简单,难道是key错了,还是根本就不是xxtea的加密方式,此时,回到ida,从xxtea_decrypt往上找
image.png (132.31 KB, 下载次数: 0)
下载附件
2023-2-14 18:11 上传
好的,我们找到了cocos2dx_lua_loader,然后看了一下流程,key设置成ffs,然后解密,好像是没错,但是和我们测试的结果不一致,所以整体看一下这个函数的流程,此函数如下:[C] 纯文本查看 复制代码__int64 __fastcall cocos2dx_lua_loader(__int64 a1)
{
const char *v2; // x20
__int64 v3; // x0
const char *v4; // x20
unsigned __int64 v5; // x21
__int64 v6; // x22
const char *v7; // x21
__int64 v8; // x0
const char *v9; // x8
const char *v10; // x23
__int64 v11; // x24
__int64 v12; // x0
const char *v13; // x24
__int64 first_of; // x0
cocos2d::CCFileUtils *v15; // x0
__int64 v16; // x0
const char *v17; // x1
unsigned __int8 *v18; // x0
unsigned __int8 *v19; // x20
__int64 v20; // x0
__int64 ProcessedBuffSize; // x21
__int64 ProcessedBuffData; // x1
const char *v23; // x3
const char *v24; // x21
char v25; // w22
const char *v26; // x23
__int64 v27; // x3
const char *v28; // x2
const char *v29; // x21
char v30; // w22
const char *v31; // x23
const char *v32; // x4
const char *v33; // x3
__int64 v34; // x0
__int64 v35; // x22
unsigned __int64 *v36; // x23
void *v37; // x21
unsigned __int64 v38; // x4
const char *v39; // x3
const char *v40; // x22
char v41; // w23
const char *v42; // x24
__int64 v43; // x3
const char *v44; // x2
const char *v45; // x22
char v46; // w23
const char *v47; // x24
const char *v48; // x4
const char *v49; // x3
__int64 v50; // x0
__int64 v51; // x22
int *v52; // x23
void *v53; // x21
unsigned __int64 v54; // x4
const char *v55; // x3
const char *v56; // x22
char v57; // w23
const char *v58; // x24
__int64 v59; // x3
const char *v60; // x2
const char *v61; // x22
char v62; // w23
const char *v63; // x24
const char *v64; // x4
const char *v65; // x3
const char *v66; // x21
unsigned __int64 v67; // x23
unsigned __int64 v68; // x0
const char *v69; // x3
const char *v70; // x21
char v71; // w22
const char *v72; // x23
__int64 v73; // x3
const char *v74; // x2
const char *v75; // x21
char v76; // w22
const char *v77; // x23
const char *v78; // x4
const char *v79; // x3
const char *v81; // x23
__int64 v82; // x0
const char *v83; // x21
unsigned __int64 v84; // x23
unsigned __int64 v85; // x0
const char *v86; // x21
unsigned __int64 v87; // x23
unsigned __int64 v88; // x0
const char *v89; // x21
unsigned __int64 v90; // x23
unsigned __int64 v91; // x0
const char *v92; // x21
unsigned __int64 v93; // x23
unsigned __int64 v94; // x0
const char *v95; // x21
unsigned __int64 v96; // x23
unsigned __int64 v97; // x0
const char *v98; // x21
unsigned __int64 v99; // x23
unsigned __int64 v100; // x0
const char *v101; // x21
unsigned __int64 v102; // x23
unsigned __int64 v103; // x0
const char *v104; // x21
unsigned __int64 v105; // x23
unsigned __int64 v106; // x0
const char *v107; // x21
char v108; // w22
const char *v109; // x23
__int64 v110; // x3
const char *v111; // x2
const char *v112; // x21
char v113; // w22
const char *v114; // x23
const char *v115; // x4
const char *v116; // x3
const char *v117; // x23
__int64 v118; // x0
const char *v119; // x23
__int64 v120; // x0
const char *v121; // x23
__int64 v122; // x0
const char *v123; // x23
__int64 v124; // x0
const char *v125; // x23
__int64 v126; // x0
const char *v127; // x23
__int64 v128; // x0
const char *v129; // x23
__int64 v130; // x0
const char *v131; // x23
__int64 v132; // x0
_BYTE v133[72]; // [xsp+0h] [xbp-510h] BYREF
__int64 v134[38]; // [xsp+48h] [xbp-4C8h] BYREF
__int128 v135; // [xsp+278h] [xbp-298h] BYREF
const char *v136; // [xsp+288h] [xbp-288h]
unsigned __int64 v137; // [xsp+4A8h] [xbp-68h] BYREF
__int128 v138; // [xsp+4B0h] [xbp-60h] BYREF
const char *v139; // [xsp+4C0h] [xbp-50h]
v2 = (const char *)luaL_checklstring(a1, 1LL, 0LL);
v138 = 0uLL;
v139 = 0LL;
v3 = std::char_traits::length(v2);
std::string::__init(&v138, v2, v3);
if ( (v138 & 1) != 0 )
v4 = v139;
else
v4 = (char *)&v138 + 1;
if ( (v138 & 1) != 0 )
v5 = *((_QWORD *)&v138 + 1);
else
v5 = (unsigned __int64)(unsigned __int8)v138 >> 1;
v6 = std::char_traits::length(".lua");
v7 = &v4[v5];
v8 = std::__find_end(
v4,
v7,
".lua",
&aLua[v6],
std::char_traits::eq);
if ( (v6 == 0 || v8 != (_QWORD)v7) && v8 - (_QWORD)v4 != -1 )
{
std::string::basic_string(&v135, &v138, 0LL, v8 - (_QWORD)v4, &v138);
LOBYTE(v134[0]) = 0;
if ( (v138 & 1) != 0 )
{
std::char_traits::assign(v139, v134);
*((_QWORD *)&v138 + 1) = 0LL;
}
else
{
std::char_traits::assign((char *)&v138 + 1, v134);
LOBYTE(v138) = 0;
}
std::string::reserve(&v138, 0LL);
v9 = v136;
v136 = 0LL;
v139 = v9;
v138 = v135;
v135 = 0uLL;
std::string::~string(&v135);
}
while ( 1 )
{
v10 = (v138 & 1) != 0 ? v139 : (char *)&v138 + 1;
v11 = (v138 & 1) != 0 ? *((_QWORD *)&v138 + 1) : (unsigned __int64)(unsigned __int8)v138 >> 1;
v12 = std::char_traits::length(".");
if ( !v12 )
break;
if ( !v11 )
break;
v13 = &v10[v11];
first_of = std::__find_first_of_ce(
v10,
v13,
".",
&aFerrorInFuncti_1677[v12 + 36],
std::char_traits::eq);
if ( (const char *)first_of == v13 || first_of - (_QWORD)v10 == -1 )
break;
std::string::replace((int)&v138, first_of - (_DWORD)v10, 1, "/");
}
v15 = (cocos2d::CCFileUtils *)std::string::append((int)&v138, ".lua");
v137 = 0LL;
v16 = cocos2d::CCFileUtils::sharedFileUtils(v15);
if ( (v138 & 1) != 0 )
v17 = v139;
else
v17 = (char *)&v138 + 1;
v18 = (unsigned __int8 *)(*(__int64 (__fastcall **)(__int64, const char *, const char *, unsigned __int64 *))(*(_QWORD *)v16 + 32LL))(
v16,
v17,
"rb",
&v137);
v19 = v18;
if ( v18 )
{
if ( v137 >= 4 )
{
switch ( *(_DWORD *)v18 )
{
case 0x43534646:
FunPlus::CDesDecryptor::CDesDecryptor((FunPlus::CDesDecryptor *)&v135);
memset(v134, 0, 24);
v20 = std::char_traits::length("FP_F_ENC");
std::string::__init(v134, "FP_F_ENC", v20);
FunPlus::CDesDecryptor::setKey(&v135, v134);
std::string::~string(v134);
FunPlus::CCryptor::setForceUse64Bit((FunPlus::CCryptor *)&v135, 1);
if ( (FunPlus::CDecryptor::processBuffer((FunPlus::CDecryptor *)&v135, v19 + 4, v137 - 4) & 1) != 0 )
{
ProcessedBuffSize = FunPlus::CCryptor::getProcessedBuffSize((FunPlus::CCryptor *)&v135);
ProcessedBuffData = FunPlus::CCryptor::getProcessedBuffData((FunPlus::CCryptor *)&v135);
if ( (v138 & 1) != 0 )
v23 = v139;
else
v23 = (char *)&v138 + 1;
if ( (unsigned int)luaL_loadbuffer(a1, ProcessedBuffData, ProcessedBuffSize, (__int64)v23) )
{
v24 = (const char *)lua_tolstring(a1, 1LL, 0LL);
v25 = v138;
v26 = v139;
v27 = lua_tolstring(a1, 0xFFFFFFFFLL, 0LL);
if ( (v25 & 1) != 0 )
v28 = v26;
else
v28 = (char *)&v138 + 1;
cocos2d::CCLog((cocos2d *)"error loading module %s from file %s :\n\t%s", v24, v28, v27);
v29 = (const char *)lua_tolstring(a1, 1LL, 0LL);
v30 = v138;
v31 = v139;
v32 = (const char *)lua_tolstring(a1, 0xFFFFFFFFLL, 0LL);
if ( (v30 & 1) != 0 )
v33 = v31;
else
v33 = (char *)&v138 + 1;
luaL_error(a1, "error loading module %s from file %s :\n\t%s", v29, v33, v32);
}
FunPlus::CDesDecryptor::~CDesDecryptor((FunPlus::CDesDecryptor *)&v135);
LABEL_99:
operator delete[](v19);
goto LABEL_100;
}
FunPlus::CDesDecryptor::~CDesDecryptor((FunPlus::CDesDecryptor *)&v135);
break;
case 0x5A534646:
FunPlus::CDesDecryptor::CDesDecryptor((FunPlus::CDesDecryptor *)v134);
v136 = 0LL;
v135 = 0uLL;
v34 = std::char_traits::length("FP_F_ENC");
std::string::__init(&v135, "FP_F_ENC", v34);
FunPlus::CDesDecryptor::setKey(v134, &v135);
std::string::~string(&v135);
FunPlus::CCryptor::setForceUse64Bit((FunPlus::CCryptor *)v134, 1);
if ( (FunPlus::CDecryptor::processBuffer((FunPlus::CDecryptor *)v134, v19 + 4, v137 - 4) & 1) != 0 )
{
v35 = FunPlus::CCryptor::getProcessedBuffSize((FunPlus::CCryptor *)v134);
v36 = (unsigned __int64 *)FunPlus::CCryptor::getProcessedBuffData((FunPlus::CCryptor *)v134);
*(_QWORD *)&v135 = *(int *)v36;
v37 = (void *)operator new[](v135);
if ( (FunPlus::CCompressUtil::UnCompress(
(FunPlus::CCompressUtil *)v37,
&v135,
v36 + 1,
(const void *)(((v35 > 32),
v38) & 1) != 0 )
{
if ( (v138 & 1) != 0 )
v39 = v139;
else
v39 = (char *)&v138 + 1;
if ( (unsigned int)luaL_loadbuffer(a1, (__int64)v37, v135, (__int64)v39) )
{
v40 = (const char *)lua_tolstring(a1, 1LL, 0LL);
v41 = v138;
v42 = v139;
v43 = lua_tolstring(a1, 0xFFFFFFFFLL, 0LL);
if ( (v41 & 1) != 0 )
v44 = v42;
else
v44 = (char *)&v138 + 1;
cocos2d::CCLog((cocos2d *)"error loading module %s from file %s :\n\t%s", v40, v44, v43);
v45 = (const char *)lua_tolstring(a1, 1LL, 0LL);
v46 = v138;
v47 = v139;
v48 = (const char *)lua_tolstring(a1, 0xFFFFFFFFLL, 0LL);
if ( (v46 & 1) != 0 )
v49 = v47;
else
v49 = (char *)&v138 + 1;
luaL_error(a1, "error loading module %s from file %s :\n\t%s", v45, v49, v48);
}
operator delete[](v37);
FunPlus::CDesDecryptor::~CDesDecryptor((FunPlus::CDesDecryptor *)v134);
goto LABEL_99;
}
operator delete[](v37);
}
FunPlus::CDesDecryptor::~CDesDecryptor((FunPlus::CDesDecryptor *)v134);
break;
case 0x457:
FunPlus::CXXTeaDecryptor::CXXTeaDecryptor((FunPlus::CXXTeaDecryptor *)v133);
v136 = 0LL;
v135 = 0uLL;
v50 = std::char_traits::length("ffs");
std::string::__init(&v135, "ffs", v50);
FunPlus::CXXTeaDecryptor::setKey(v133, &v135);
std::string::~string(&v135);
if ( (FunPlus::CXXTeaDecryptor::processBuffer((FunPlus::CXXTeaDecryptor *)v133, v19 + 4, v137 - 4) & 1) != 0 )
{
v51 = FunPlus::CCryptor::getProcessedBuffSize((FunPlus::CCryptor *)v133);
v52 = (int *)FunPlus::CCryptor::getProcessedBuffData((FunPlus::CCryptor *)v133);
*(_QWORD *)&v135 = *v52;
v53 = (void *)operator new[](v135);
if ( (FunPlus::CCompressUtil::UnCompress(
(FunPlus::CCompressUtil *)v53,
&v135,
(unsigned __int64 *)(v52 + 1),
(const void *)(((v51 > 32),
v54) & 1) != 0 )
{
if ( (v138 & 1) != 0 )
v55 = v139;
else
v55 = (char *)&v138 + 1;
if ( (unsigned int)luaL_loadbuffer(a1, (__int64)v53, v135, (__int64)v55) )
{
v56 = (const char *)lua_tolstring(a1, 1LL, 0LL);
v57 = v138;
v58 = v139;
v59 = lua_tolstring(a1, 0xFFFFFFFFLL, 0LL);
if ( (v57 & 1) != 0 )
v60 = v58;
else
v60 = (char *)&v138 + 1;
cocos2d::CCLog((cocos2d *)"error loading module %s from file %s :\n\t%s", v56, v60, v59);
v61 = (const char *)lua_tolstring(a1, 1LL, 0LL);
v62 = v138;
v63 = v139;
v64 = (const char *)lua_tolstring(a1, 0xFFFFFFFFLL, 0LL);
if ( (v62 & 1) != 0 )
v65 = v63;
else
v65 = (char *)&v138 + 1;
luaL_error(a1, "error loading module %s from file %s :\n\t%s", v61, v65, v64);
}
operator delete[](v53);
FunPlus::CXXTeaDecryptor::~CXXTeaDecryptor((FunPlus::CXXTeaDecryptor *)v133);
goto LABEL_99;
}
operator delete[](v53);
}
FunPlus::CXXTeaDecryptor::~CXXTeaDecryptor((FunPlus::CXXTeaDecryptor *)v133);
break;
}
}
if ( (v138 & 1) != 0 )
v66 = v139;
else
v66 = (char *)&v138 + 1;
if ( (v138 & 1) != 0 )
v67 = *((_QWORD *)&v138 + 1);
else
v67 = (unsigned __int64)(unsigned __int8)v138 >> 1;
v68 = std::char_traits::length("/");
if ( v67 >= v68 )
{
if ( !v68
|| (v81 = &v66[v67],
v82 = std::__search(
v66,
v81,
"/",
&asc_1A19AA4[v68 + 1],
std::char_traits::eq),
v81 != (const char *)v82)
&& v82 - (_QWORD)v66 != -1 )
{
v83 = (v138 & 1) != 0 ? v139 : (char *)&v138 + 1;
v84 = (v138 & 1) != 0 ? *((_QWORD *)&v138 + 1) : (unsigned __int64)(unsigned __int8)v138 >> 1;
v85 = std::char_traits::length("common/");
if ( v84 ::eq),
v117 == (const char *)v118)
|| v118 - (_QWORD)v83 == -1) )
{
v86 = (v138 & 1) != 0 ? v139 : (char *)&v138 + 1;
v87 = (v138 & 1) != 0 ? *((_QWORD *)&v138 + 1) : (unsigned __int64)(unsigned __int8)v138 >> 1;
v88 = std::char_traits::length("game_loading/");
if ( v87 ::eq),
v119 == (const char *)v120)
|| v120 - (_QWORD)v86 == -1) )
{
v89 = (v138 & 1) != 0 ? v139 : (char *)&v138 + 1;
v90 = (v138 & 1) != 0 ? *((_QWORD *)&v138 + 1) : (unsigned __int64)(unsigned __int8)v138 >> 1;
v91 = std::char_traits::length("version_logo");
if ( v90 ::eq),
v121 == (const char *)v122)
|| v122 - (_QWORD)v89 == -1) )
{
v92 = (v138 & 1) != 0 ? v139 : (char *)&v138 + 1;
v93 = (v138 & 1) != 0 ? *((_QWORD *)&v138 + 1) : (unsigned __int64)(unsigned __int8)v138 >> 1;
v94 = std::char_traits::length("fast_switch/");
if ( v93 ::eq),
v123 == (const char *)v124)
|| v124 - (_QWORD)v92 == -1) )
{
v95 = (v138 & 1) != 0 ? v139 : (char *)&v138 + 1;
v96 = (v138 & 1) != 0 ? *((_QWORD *)&v138 + 1) : (unsigned __int64)(unsigned __int8)v138 >> 1;
v97 = std::char_traits::length("ab_test/");
if ( v96 ::eq),
v125 == (const char *)v126)
|| v126 - (_QWORD)v95 == -1) )
{
v98 = (v138 & 1) != 0 ? v139 : (char *)&v138 + 1;
v99 = (v138 & 1) != 0 ? *((_QWORD *)&v138 + 1) : (unsigned __int64)(unsigned __int8)v138 >> 1;
v100 = std::char_traits::length("novice_guide/");
if ( v99 ::eq),
v127 == (const char *)v128)
|| v128 - (_QWORD)v98 == -1) )
{
v101 = (v138 & 1) != 0 ? v139 : (char *)&v138 + 1;
v102 = (v138 & 1) != 0 ? *((_QWORD *)&v138 + 1) : (unsigned __int64)(unsigned __int8)v138 >> 1;
v103 = std::char_traits::length("neighbor/");
if ( v102 ::eq),
v129 == (const char *)v130)
|| v130 - (_QWORD)v101 == -1) )
{
v104 = (v138 & 1) != 0 ? v139 : (char *)&v138 + 1;
v105 = (v138 & 1) != 0 ? *((_QWORD *)&v138 + 1) : (unsigned __int64)(unsigned __int8)v138 >> 1;
v106 = std::char_traits::length("spine-lua/");
if ( v105 ::eq),
v131 == (const char *)v132)
|| v132 - (_QWORD)v104 == -1) )
{
v107 = (const char *)lua_tolstring(a1, 1LL, 0LL);
v108 = v138;
v109 = v139;
v110 = lua_tolstring(a1, 0xFFFFFFFFLL, 0LL);
if ( (v108 & 1) != 0 )
v111 = v109;
else
v111 = (char *)&v138 + 1;
cocos2d::CCLog((cocos2d *)"error loading module %s from file %s :\n\t%s", v107, v111, v110);
v112 = (const char *)lua_tolstring(a1, 1LL, 0LL);
v113 = v138;
v114 = v139;
v115 = (const char *)lua_tolstring(a1, 0xFFFFFFFFLL, 0LL);
if ( (v113 & 1) != 0 )
v116 = v114;
else
v116 = (char *)&v138 + 1;
luaL_error(a1, "error loading module %s from file %s :\n\t%s", v112, v116, v115);
}
}
}
}
}
}
}
}
}
}
if ( (v138 & 1) != 0 )
v69 = v139;
else
v69 = (char *)&v138 + 1;
if ( (unsigned int)luaL_loadbuffer(a1, (__int64)v19, v137, (__int64)v69) )
{
v70 = (const char *)lua_tolstring(a1, 1LL, 0LL);
v71 = v138;
v72 = v139;
v73 = lua_tolstring(a1, 0xFFFFFFFFLL, 0LL);
if ( (v71 & 1) != 0 )
v74 = v72;
else
v74 = (char *)&v138 + 1;
cocos2d::CCLog((cocos2d *)"error loading module %s from file %s :\n\t%s", v70, v74, v73);
v75 = (const char *)lua_tolstring(a1, 1LL, 0LL);
v76 = v138;
v77 = v139;
v78 = (const char *)lua_tolstring(a1, 0xFFFFFFFFLL, 0LL);
if ( (v76 & 1) != 0 )
v79 = v77;
else
v79 = (char *)&v138 + 1;
luaL_error(a1, "error loading module %s from file %s :\n\t%s", v75, v79, v78);
}
goto LABEL_99;
}
if ( (v138 & 1) != 0 )
cocos2d::CCLog((cocos2d *)"can not get file data of %s", v139);
else
cocos2d::CCLog((cocos2d *)"can not get file data of %s", (const char *)&v138 + 1);
LABEL_100:
std::string::~string(&v138);
return 1LL;
}
通过分析,我们知道,他先是读入lua文件,然后判断文件头,根据不同的文件头走不同的流程,我们的文件头是FFSZ,走的应该是des解密的流程,des解密之后还有一个zlib的解压缩,ok按照这个思路开始写代码。 一开始写的时候,我去掉文件头直接des解密,但是得出来的结果一直不符合预期,所以我直接跳过了12个字节重新解密,别问我为什么跳过12个字节,我也是猜的,然后解密出的结果,第九个字节刚好是789c,标准的zlib头,这里再进行zlib解压,好家伙,直接出明文了,解密代码如下:[Python] 纯文本查看 复制代码import pyDes,base64,zlib
from pyDes import des, ECB
def des_descrypt(s,KEY):
secret_key = KEY
iv = secret_key
k = des(secret_key, ECB,IV=iv, pad=None)
de = k.decrypt(s)
return de
f=open('data.lua','rb').read()
f=f[12:]
ret = des_descrypt(f,b'FP_F_ENC')
new_data = zlib.decompress(ret[8:])
print(new_data)
open('data_decrypt.lua','wb').write(new_data)
看一下解密出来的文件,完美!
image.png (107.93 KB, 下载次数: 0)
下载附件
2023-2-14 18:12 上传
好了,至此此文件的解密完成,总结一下,平常我们遇到的都是xxtea的lua加密,但是这个lua的so里面虽然也有xxtea,但是用了des加密并且进行了压缩,所以遇到lua的游戏的时候不要慌,冷静分析,也是可以搞定的 最后附一下常见各种压缩算法的头特征:gzip 1f 8b 08lzma 6c 00zlib 78 9c