好久没发帖了,不知不觉又到了除夕,祝大家新年快乐~
by devseed, 本贴论坛和我的博客同时发布
本贴代码开源详见我的github: GalgameReverse, ReverseUtil。
上篇链接:Galgame汉化中的逆向(六):动态汉化分析_以MAJIROv3引擎为例
上节 Galgame汉化中的逆向(六):动态汉化分析_以MAJIROv3引擎为例,我们介绍了动态汉化。动态汉化不用分析封包结构,不用分析opcode
,看上去很方便,但是动态汉化解决同步问题会很麻烦,比如说改完文本后backlog文本仍是日文、返回主界面再载入文本没有变动等问题。动态汉化也有可能出现莫名其妙的崩溃bug,且这些bug不容易被调试。
针对动态汉化的上述缺点,本节我们将介绍一种这种半动态汉化
的方案。与上节的方法不同,本节不进行文本级替换,而是文件级别的替换。即去hook
相关函数,动态将解密后的缓冲区替换为我们汉化后的文件。适合于那种封包与加密特别麻烦或复杂的游戏。
本文将以azsystem
为例,来分析:
和上节相同,第一步先分析文件,无论静态分析算法还是动态dump缓冲区,先把文件提取出来。
由于方法差不多,这里不再详细展开了。
这个游戏封包为.arc
文件,用文件长度哈希值来作为加密密钥,里面有若干个.asb
脚本文件。IDA里面直接搜.asb
字符串就能找到相关函数了,读取脚本文件函数如下:
简单分析后,我们可以得到asb
的文件头结构、校验文本函数、解压函数以下结论,具体如下:
提取只需要hooksub_40AB65
,frida代码如下:
用其他工具如arc unpack
可以得到arc
封包的文件名,把文件名录入frida脚本,即可dump出全部asb
脚本。
结合上面文件分析,我们可以在004311E1| E8 7F99FDFF| call lamune.40AB65| decompress
进行inlinehook
,在此直接加载我们已经解密并汉化的asb
文件。解密的缓冲区是前面new
出来的,我们还需要修改缓冲区大小。另外还要nop
掉缓冲区crc
校验的函数。
上节我们用了detours
,这期我们来手动inlinehook
,步骤如下:
在需要hook
的位置用5字节call(E9)
或 jmp(E8)
进行相对跳转到我们的函数上,
机器码为E8 XXXXXXXX
, E9 XXXXXXXX
。
XXXXXXXX
为相对于下一条指令的偏移,即targetva - (va + 5)
执行完后hook
的函数后,结尾手动修复一下被我们修改5字节破坏的代码,跳转到下个指令处。
动态替换解密后的缓冲区脚本代码如下:
上面代码中load_rawasb
即为我们读取对应解密文件的代码,这里为了减少零碎文件,我采取了从zip
文件中读取的方法。
此处不再赘述,详见我的github。
导入中文文本后,经测试发现一大堆半角乱码。
这是因为有sjis
首字节字符编码范围检测,不在sjis
范围内的字符将被解析为单字节文本。
与其他游戏不同,此游戏不是用cmp ax, 0x81
等指令来检测sjis
字符,而且位置过多过于分散,修改起来很麻烦。
这部分定位我们可以在TextOutA
下断点,往上慢慢找,可以看到下图位置:
这里非常巧妙,用一条c^0x20 + 0x5f > 0x3B
就可以判断是否为sjis首字符了,具体分析如下:
修改方法也很简单,把上面xor
和add
用nop
patch,编码检测改为cmp dl, 0x80
即可。
修改完后,虽然文本框正确了,但我们发现backlog
中文本还有乱码。
这时候就要在搜索其他地方的检测字符函数了,可以试着搜cmp al|bl|cl|dl, 0x3b
,逐个下断点,启动backlog
看哪里断下。
以0nana.asb
为例,这个opcode是对齐的,很工整,如下图:
总结起来就是optype 4, oplengh 4, payload n
结构,超长文本只需要修正一下oplengh
和jmp
相关的指令就行了,如下:
将测试文本导入后,我们可以完成超长文本的汉化测试了~
这个游戏是通过Windows compatible DC
进行绘图的,我们可以在CreateDIBinfo
下断点,然后一层层往上跟,找到在缓冲区填充像素的函数,之后bitblt
到帧缓冲位置。这里有个麻烦事,这游戏有很多虚函数通过虚表来寻址,如v3=(*(**v7+12))(*v7, v5, v10,a3
这种。静态跟起来很费劲,可以尝试动态来看虚表。由于跟踪过于繁琐了,具体流程从略了,callback
和具体调用流程如下:
上面我们来讲了一下定位方法,和整体加载流程。在这节我们来分析一下cpb
文件如何读取和加载渲染到屏幕上的。
cpb
中像素是分通道存储的,数据结构如下:
在渲染图片之前,游戏引擎先进行DC
的初始化。
这部分是读取cpb
到内存里,并检验文件头等信息
加载后,会根据通道数不同调用不同的解压缩函数。
这个游戏有多个cpb
解压函数,对应着不同通道数的文件,这里以32位图为例分析。
注意这里vv1 = (*(*obja + 0xC))(obja)
中的vv1
值为prepare dc
中的v5 = CreateDIBSection(0, &pbmi, 0, this + 0x28, 0, 0)
此句的DIB缓冲区。
我们可以替换decompress_channel_40AA38
后的缓冲区为汉化后的图片,然后让游戏引擎帮我们复制到DIB
缓冲区内。
解压各通道算法,看起来有点像lzss
改版?
最后再通过bitblt
到屏幕帧缓存中,至此整个游戏图片渲染分析完毕。
为了搞明白这个游戏游戏引擎图像如何渲染的,我把很多的虚函数都跟了一遍。
其实汉化图片只需要逆向到如何解压cpb
文件那里就足够了。这个游戏麻烦地方在于不同通道对应的不同处理函数,要依次来hook
替换缓冲区。另外在读取文件适合要记录一些文件名,用于缓冲区动态替换我们汉化的图片。
以24位图片代码替换为例,代码如下:
这里采取的是png
格式存储的汉化图片,为了方便用了stb进行加载。
加载后遇到渲染bug,我们把对应缓冲区dump出来放到ct2中进行查看,确定原因。
这里发现原来是stbi_load_from_memory
函数对于tga
格式有些问题,换成png
格式最后参数为0,问题解决。
至此,图片汉化问题全部解决。
这个游戏我逆向了一周多把引擎的加载方式搞明白了,之后又测试导入翻译断断续续修复bug一个月,基本上汉化完美了。这里有个坑,通关后没法打开gallary
。这是官方的bug,下载了升级补丁可以修复。但是之前给我的文件是初版游戏,我说基于这个版本分析的。还得把旧版搬到新版上,非常麻烦。这个故事告诉我们,以后汉化要第一时间检查更新补丁。
整体来讲,这游戏有三大难点。难点之一在封包上,有加密和校验非常麻烦,因此我们采取了动态替换解密后的缓冲区;其二,图像缓冲区不好找,里面有大量虚函数,需要一点点跟;其三,sjis
字符检测过于分散,需要手动一个个调整,而且也是用非主流方式判断的。因此,我认为此游戏比较适合半动态汉化
。这种基于文件的替换方式可以免去复杂的封包,同时相比文本层面上的全动态汉化,可以更方便调试,少引发一些文本同步之类的问题。
另外我用stb加载图片,这里遇到了问题,xp上运行会崩溃。
调试定位在了mov eax, large fs:2Ch
上,这是因为这个库用了__declspec(thread)
,在win xp
上LoadLibrary
遇到tls
就会崩,定义宏#define STBI_NO_THREAD_LOCALS
即可解决。
然后进行了若干测试,我这个汉化兼容补丁性还不错~ win xp
, win7
, win8
, win10
甚至连linux wine
,exagear
都测试了,可以说是全平台兼容了~ 完结撒花~
int
__thiscall sub_43112A(_DWORD
*
this, char
*
script_name)
{
char
*
raw_data;
/
/
edi
int
v4;
/
/
eax
unsigned
int
v5;
/
/
ecx
_DWORD
*
v7[
4
];
/
/
[esp
+
8h
] [ebp
-
34h
] BYREF
int
v8;
/
/
[esp
+
18h
] [ebp
-
24h
] BYREF
unsigned
int
compressed_size;
/
/
[esp
+
1Ch
] [ebp
-
20h
]
unsigned
int
raw_size;
/
/
[esp
+
20h
] [ebp
-
1Ch
]
int
v11;
/
/
[esp
+
24h
] [ebp
-
18h
]
int
(__thiscall
*
*
v12)(void
*
, char);
/
/
[esp
+
28h
] [ebp
-
14h
]
char
*
compressed_data;
/
/
[esp
+
2Ch
] [ebp
-
10h
]
int
v14;
/
/
[esp
+
38h
] [ebp
-
4h
]
v7[
0
]
=
off_460A6C;
sub_40BD95(v7);
v14
=
1
;
v12
=
&off_462CDC;
v11
=
0
;
sub_430FC9((
int
)this);
if
( fopen_40C102(v7, script_name,
0x80000000
) !
=
1
)
{
logprintf_407C41(
"CScript::Create"
, byte_4679CC, script_name);
goto LABEL_13;
}
readfile_40C03E(v7, (char
*
)&v8,
0xC
);
if
( v8
=
=
0x1A425341
)
/
/
asb\x1a
{
compressed_data
=
(char
*
)operator new(compressed_size);
raw_data
=
(char
*
)operator new(raw_size);
readfile_40C03E(v7, compressed_data, compressed_size);
if
( sub_430F6A(compressed_data, compressed_size, raw_size) )
{
v4
=
decompress_40AB65(compressed_data, compressed_size, raw_data, raw_size);
/
/
decompress
v5
=
raw_size;
if
( v4
=
=
raw_size )
{
this[
4
]
=
0
;
this[
1
]
=
raw_data;
this[
2
]
=
v5;
this[
3
]
=
raw_data;
this[
5
]
=
raw_data;
v11
=
1
;
LABEL_10:
if
( compressed_data )
j__free(compressed_data);
goto LABEL_13;
}
logprintf_407C41(
"CScript::Create"
, byte_467A38, script_name);
}
else
{
logprintf_407C41(
"CScript::Create"
, byte_467A0C, script_name);
/
/
error
}
if
( raw_data )
j__free(raw_data);
goto LABEL_10;
}
LABEL_13:
v14
=
-
1
;
v12
=
&off_462CDC;
v7[
0
]
=
off_460A6C;
sub_40BFDD(v7);
return
v11;
}
int
__thiscall sub_43112A(_DWORD
*
this, char
*
script_name)
{
char
*
raw_data;
/
/
edi
int
v4;
/
/
eax
unsigned
int
v5;
/
/
ecx
_DWORD
*
v7[
4
];
/
/
[esp
+
8h
] [ebp
-
34h
] BYREF
int
v8;
/
/
[esp
+
18h
] [ebp
-
24h
] BYREF
unsigned
int
compressed_size;
/
/
[esp
+
1Ch
] [ebp
-
20h
]
unsigned
int
raw_size;
/
/
[esp
+
20h
] [ebp
-
1Ch
]
int
v11;
/
/
[esp
+
24h
] [ebp
-
18h
]
int
(__thiscall
*
*
v12)(void
*
, char);
/
/
[esp
+
28h
] [ebp
-
14h
]
char
*
compressed_data;
/
/
[esp
+
2Ch
] [ebp
-
10h
]
int
v14;
/
/
[esp
+
38h
] [ebp
-
4h
]
v7[
0
]
=
off_460A6C;
sub_40BD95(v7);
v14
=
1
;
v12
=
&off_462CDC;
v11
=
0
;
sub_430FC9((
int
)this);
if
( fopen_40C102(v7, script_name,
0x80000000
) !
=
1
)
{
logprintf_407C41(
"CScript::Create"
, byte_4679CC, script_name);
goto LABEL_13;
}
readfile_40C03E(v7, (char
*
)&v8,
0xC
);
if
( v8
=
=
0x1A425341
)
/
/
asb\x1a
{
compressed_data
=
(char
*
)operator new(compressed_size);
raw_data
=
(char
*
)operator new(raw_size);
readfile_40C03E(v7, compressed_data, compressed_size);
if
( sub_430F6A(compressed_data, compressed_size, raw_size) )
{
v4
=
decompress_40AB65(compressed_data, compressed_size, raw_data, raw_size);
/
/
decompress
v5
=
raw_size;
if
( v4
=
=
raw_size )
{
this[
4
]
=
0
;
this[
1
]
=
raw_data;
this[
2
]
=
v5;
this[
3
]
=
raw_data;
this[
5
]
=
raw_data;
v11
=
1
;
LABEL_10:
if
( compressed_data )
j__free(compressed_data);
goto LABEL_13;
}
logprintf_407C41(
"CScript::Create"
, byte_467A38, script_name);
}
else
{
logprintf_407C41(
"CScript::Create"
, byte_467A0C, script_name);
/
/
error
}
if
( raw_data )
j__free(raw_data);
goto LABEL_10;
}
LABEL_13:
v14
=
-
1
;
v12
=
&off_462CDC;
v7[
0
]
=
off_460A6C;
sub_40BFDD(v7);
return
v11;
}
typedef struct {
s8 magic[
4
];
/
*
"ASB"
*
/
u32 comprlen;
u32 uncomprlen;
u32 unknown;
} asb_header_t;
typedef struct {
s8 magic[
4
];
/
*
"ASB\x1a"
通过此magic来定位
*
/
u32 comprlen;
u32 uncomprlen;
} asb1a_header_t;
/
/
CScript.constructor, 这里不再自己构造了,在游戏调用的时候记录下this指针
void
*
__thiscall sub_43277F(_DWORD
*
this)
/
/
check_valid
BOOL
__stdcall sub_430F6A(char
*
compressed_data,
int
compressed_size,
int
raw_size)
/
/
decompress
sub_40AB65(char
*
compressed_data,
int
compressed_len, char
*
raw_data,
int
raw_len)
0043112A
| B8
9EE54500
| mov eax,lamune.
45E59E
|load_script(char
*
name)
004311D4
| FF75 E4 | push dword ptr ss:[ebp
-
1C
] | raw_len
004311D7
|
8D4D
EC | lea ecx,dword ptr ss:[ebp
-
14
]
004311DA
|
57
| push edi | raw_data
004311DB
| FF75 E0 | push dword ptr ss:[ebp
-
20
] | compressed_len
004311DE
| FF75 F0 | push dword ptr ss:[ebp
-
10
] | compressed_data
004311E1
| E8
7F99FDFF
| call lamune.
40AB65
| decompress
typedef struct {
s8 magic[
4
];
/
*
"ASB"
*
/
u32 comprlen;
u32 uncomprlen;
u32 unknown;
} asb_header_t;
typedef struct {
s8 magic[
4
];
/
*
"ASB\x1a"
通过此magic来定位
*
/
u32 comprlen;
u32 uncomprlen;
} asb1a_header_t;
/
/
CScript.constructor, 这里不再自己构造了,在游戏调用的时候记录下this指针
void
*
__thiscall sub_43277F(_DWORD
*
this)
/
/
check_valid
BOOL
__stdcall sub_430F6A(char
*
compressed_data,
int
compressed_size,
int
raw_size)
/
/
decompress
sub_40AB65(char
*
compressed_data,
int
compressed_len, char
*
raw_data,
int
raw_len)
0043112A
| B8
9EE54500
| mov eax,lamune.
45E59E
|load_script(char
*
name)
004311D4
| FF75 E4 | push dword ptr ss:[ebp
-
1C
] | raw_len
004311D7
|
8D4D
EC | lea ecx,dword ptr ss:[ebp
-
14
]
004311DA
|
57
| push edi | raw_data
004311DB
| FF75 E0 | push dword ptr ss:[ebp
-
20
] | compressed_len
004311DE
| FF75 F0 | push dword ptr ss:[ebp
-
10
] | compressed_data
004311E1
| E8
7F99FDFF
| call lamune.
40AB65
| decompress
/
*
for
lamune.exe v1.
0
open
the game to title, then
frida
-
l lamune_hook.js
-
n lamune.exe
next
go to the prologue to dump
all
asbs
*
/
function install_decompress_hook(outdir
=
'./dump'
)
{
/
/
hook decompress function to dump
const addr_decompress
=
ptr(
0x40AB65
);
var raw_asbname
=
"";
var raw_asbdata
=
ptr(
0
);
var raw_asbsize
=
0
;
Interceptor.attach(addr_decompress, {
onEnter: function(args)
{
raw_asbdata
=
ptr(args[
2
]);
raw_asbsize
=
args[
3
].toUInt32();
raw_asbname
=
ptr(this.context.ebp).add(
8
).
readPointer().readAnsiString();
},
onLeave: function(retval)
{
/
/
var asbname
=
asbname_buf.readAnsiString();
var asbname
=
raw_asbname;
console.log(asbname,
", raw_asbdata addr at"
, raw_asbdata,
", raw_asbsize "
, raw_asbsize)
try
{
var fp
=
new
File
(outdir
+
"/"
+
asbname,
'wb'
);
fp.write(raw_asbdata.readByteArray(raw_asbsize));
fp.close();
}
catch(e)
{
console.log(
"file error!"
, e);
}
}
})
}
function dump_asbs(names, outdir
=
"./dump"
)
{
const addr_loadscript
=
ptr(
0x43112A
);
const load_script
=
new NativeFunction(addr_loadscript,
'void'
, [
'pointer'
,
"pointer"
],
'thiscall'
);
console.log(
"load_script at:"
, load_script)
/
/
use this to store c
+
+
context
var pthis
=
ptr(
0
)
Interceptor.attach(addr_loadscript, {
onEnter: function(args)
{
pthis
=
ptr(this.context.ecx)
}
})
install_decompress_hook(outdir)
/
/
wait
for
c
+
+
context
while
(!pthis.toInt32())
{
Thread.sleep(
0.2
);
}
/
/
dump
all
scripts
var name_buf
=
Memory.alloc(
0x100
);
for
(var i
=
0
;i<names.length;i
+
+
)
{
console.log(
"try to dump"
, names[i],
", this="
,pthis);
name_buf.writeAnsiString(names[i]);
load_script(pthis, name_buf);
}
console.log(
"dump asbs finished!\n"
);
}
function dump_scenario()
{
var names_v103
=
[
"00suzuk.asb"
]
dump_asbs(names_v103)
}
/
*
for
lamune.exe v1.
0
open
the game to title, then
frida
-
l lamune_hook.js
-
n lamune.exe
next
go to the prologue to dump
all
asbs
*
/
function install_decompress_hook(outdir
=
'./dump'
)
{
/
/
hook decompress function to dump
const addr_decompress
=
ptr(
0x40AB65
);
var raw_asbname
=
"";
var raw_asbdata
=
ptr(
0
);
var raw_asbsize
=
0
;
Interceptor.attach(addr_decompress, {
onEnter: function(args)
{
raw_asbdata
=
ptr(args[
2
]);
raw_asbsize
=
args[
3
].toUInt32();
raw_asbname
=
ptr(this.context.ebp).add(
8
).
readPointer().readAnsiString();
},
onLeave: function(retval)
{
/
/
var asbname
=
asbname_buf.readAnsiString();
var asbname
=
raw_asbname;
console.log(asbname,
", raw_asbdata addr at"
, raw_asbdata,
", raw_asbsize "
, raw_asbsize)
try
{
var fp
=
new
File
(outdir
+
"/"
+
asbname,
'wb'
);
fp.write(raw_asbdata.readByteArray(raw_asbsize));
fp.close();
}
catch(e)
{
console.log(
"file error!"
, e);
}
}
})
}
function dump_asbs(names, outdir
=
"./dump"
)
{
const addr_loadscript
=
ptr(
0x43112A
);
const load_script
=
new NativeFunction(addr_loadscript,
'void'
, [
'pointer'
,
"pointer"
],
'thiscall'
);
console.log(
"load_script at:"
, load_script)
/
/
use this to store c
+
+
context
var pthis
=
ptr(
0
)
Interceptor.attach(addr_loadscript, {
onEnter: function(args)
{
pthis
=
ptr(this.context.ecx)
}
})
install_decompress_hook(outdir)
/
/
wait
for
c
+
+
context
while
(!pthis.toInt32())
{
Thread.sleep(
0.2
);
}
/
/
dump
all
scripts
var name_buf
=
Memory.alloc(
0x100
);
for
(var i
=
0
;i<names.length;i
+
+
)
{
console.log(
"try to dump"
, names[i],
", this="
,pthis);
name_buf.writeAnsiString(names[i]);
load_script(pthis, name_buf);
}
console.log(
"dump asbs finished!\n"
);
}
function dump_scenario()
{
var names_v103
=
[
"00suzuk.asb"
]
dump_asbs(names_v103)
}
/
*
for
hook new decompressed
buffer
0043119A
| FF75 E0 | push dword ptr ss:[ebp
-
20
]
0043119D
| E8 A1510000 | call lamune.
436343
| new
004311A2
| FF75 E4 | push dword ptr ss:[ebp
-
1C
] | [ebp
-
1c
] raw_size
004311A5
|
8945
F0 | mov dword ptr ss:[ebp
-
10
],eax
004311A8
| E8
96510000
| call lamune.
436343
| new raw_buf
*
/
const DWORD g_newrawbufi_4311A2
=
0x4311A2
;
const DWORD g_newrawbufo_4311A8
=
0x4311A8
;
/
*
for
hook decompress asb
.text:
004311D4
FF
75
E4 push [ebp
+
raw_size] ; raw_len
.text:
004311D7
8D
4D
EC lea ecx, [ebp
+
var_14]
.text:
004311DA
57
push edi ; raw_data
.text:
004311DB
FF
75
E0 push [ebp
+
compressed_size] ; compressed_len
.text:
004311DE
FF
75
F0 push [ebp
+
compressed_data] ; compressed_data
.text:
004311E1
E8
7F
99
FD FF call decompress_40AB65
*
/
const DWORD g_decompressasbi_4311E1
=
0x4311E1
;
const DWORD g_decompressasbo_40AB65
=
0x40AB65
;
/
/
inlinehook stubs
void __declspec(naked) newrawbuf_hook_4311A2()
{
__asm{
pushad;
xor eax, eax;
/
/
size_t __stdcall load_rawasb(char
*
name, PBYTE buf)
push eax;
push [ebp
+
8
];
call load_rawasb;
test eax, eax;
je newrawbuf_hook_end;
mov [ebp
-
0x1c
], eax;
/
/
change raw buf size
newrawbuf_hook_end:
popad;
/
/
fix origin code
push dword ptr [ebp
-
0x1c
];
mov dword ptr [ebp
-
0x10
], eax;
jmp dword ptr ds:[g_newrawbufo_4311A8];
}
}
void __declspec(naked) decompressasb_hook_4311E1()
{
/
/
sub_40AB65(char
*
compressed_data,
int
compressed_len, char
*
raw_data,
int
raw_len)
__asm {
push [esp
+
0xc
];
/
/
after push ret addr, above, raw_buf
push [ebp
+
0x8
];
/
/
asbname
call load_rawasb;
test eax, eax;
je decompress_origin;
ret
0x10
;
decompress_origin:
mov eax,
0x99E15CB4
;
/
/
this
is
the original corrent crc value
mov dword ptr ds:[
0x0047E718
], eax;
/
/
this
is
not
worked...
jmp dword ptr ds:[g_decompressasbo_40AB65];
}
}
/
/
hook install functions
void install_asbhook()
{
/
*
inlinehook check_valid
.text:
0040AB8A
6A
00
push
0
.text:
0040AB8C
8D
43
FC lea eax, [ebx
-
4
]
.text:
0040AB8F
50
push eax
.text:
0040AB90
8D
77
04
lea esi, [edi
+
4
]
.text:
0040AB93
56
push esi
.text:
0040AB94
E8
27
D9 FF FF call makecrc_4084C0
.text:
0040AB99
83
C4
0C
add esp,
0Ch
.text:
0040AB9C
39
07
cmp
[edi], eax
.text:
0040AB9E
75
64
jnz short loc_40AC04
*
/
BYTE nop2[
0x2
]
=
{
0x90
,
0x90
};
winhook_patchmemory((LPVOID)
0x4311d2
,
nop2, sizeof(nop2));
winhook_patchmemory((LPVOID)
0x40AB9E
,
nop2, sizeof(nop2));
/
/
inlinehook newrawdata
BYTE jmpE8buf[
0x5
]
=
{
0xE9
};
/
/
jmp relative
*
(DWORD
*
)(jmpE8buf
+
1
)
=
(DWORD)newrawbuf_hook_4311A2
-
((DWORD)g_newrawbufi_4311A2
+
sizeof(jmpE8buf));
winhook_patchmemory((LPVOID)g_newrawbufi_4311A2,
jmpE8buf, sizeof(jmpE8buf));
/
/
inlinehook decompress
BYTE callE9buf[
0x5
]
=
{
0xE8
};
/
/
call relative
*
(DWORD
*
)(callE9buf
+
1
)
=
(DWORD)decompressasb_hook_4311E1
-
((DWORD)g_decompressasbi_4311E1
+
sizeof(jmpE8buf));
winhook_patchmemory((LPVOID)g_decompressasbi_4311E1,
callE9buf, sizeof(callE9buf));
}
/
*
for
hook new decompressed
buffer
0043119A
| FF75 E0 | push dword ptr ss:[ebp
-
20
]
0043119D
| E8 A1510000 | call lamune.
436343
| new
004311A2
| FF75 E4 | push dword ptr ss:[ebp
-
1C
] | [ebp
-
1c
] raw_size
004311A5
|
8945
F0 | mov dword ptr ss:[ebp
-
10
],eax
004311A8
| E8
96510000
| call lamune.
436343
| new raw_buf
*
/
const DWORD g_newrawbufi_4311A2
=
0x4311A2
;
const DWORD g_newrawbufo_4311A8
=
0x4311A8
;
/
*
for
hook decompress asb
.text:
004311D4
FF
75
E4 push [ebp
+
raw_size] ; raw_len
.text:
004311D7
8D
4D
EC lea ecx, [ebp
+
var_14]
.text:
004311DA
57
push edi ; raw_data
.text:
004311DB
FF
75
E0 push [ebp
+
compressed_size] ; compressed_len
.text:
004311DE
FF
75
F0 push [ebp
+
compressed_data] ; compressed_data
.text:
004311E1
E8
7F
99
FD FF call decompress_40AB65
*
/
const DWORD g_decompressasbi_4311E1
=
0x4311E1
;
const DWORD g_decompressasbo_40AB65
=
0x40AB65
;
/
/
inlinehook stubs
void __declspec(naked) newrawbuf_hook_4311A2()
{
__asm{
pushad;
xor eax, eax;
/
/
size_t __stdcall load_rawasb(char
*
name, PBYTE buf)
push eax;
push [ebp
+
8
];
call load_rawasb;
test eax, eax;
je newrawbuf_hook_end;
mov [ebp
-
0x1c
], eax;
/
/
change raw buf size
newrawbuf_hook_end:
popad;
/
/
fix origin code
push dword ptr [ebp
-
0x1c
];
mov dword ptr [ebp
-
0x10
], eax;
jmp dword ptr ds:[g_newrawbufo_4311A8];
}
}
void __declspec(naked) decompressasb_hook_4311E1()
{
/
/
sub_40AB65(char
*
compressed_data,
int
compressed_len, char
*
raw_data,
int
raw_len)
__asm {
push [esp
+
0xc
];
/
/
after push ret addr, above, raw_buf
push [ebp
+
0x8
];
/
/
asbname
call load_rawasb;
test eax, eax;
je decompress_origin;
ret
0x10
;
decompress_origin:
mov eax,
0x99E15CB4
;
/
/
this
is
the original corrent crc value
mov dword ptr ds:[
0x0047E718
], eax;
/
/
this
is
not
worked...
jmp dword ptr ds:[g_decompressasbo_40AB65];
}
}
/
/
hook install functions
void install_asbhook()
{
/
*
inlinehook check_valid
.text:
0040AB8A
6A
00
push
0
.text:
0040AB8C
8D
43
FC lea eax, [ebx
-
4
]
.text:
0040AB8F
50
push eax
.text:
0040AB90
8D
77
04
lea esi, [edi
+
4
]
.text:
0040AB93
56
push esi
.text:
0040AB94
E8
27
D9 FF FF call makecrc_4084C0
.text:
0040AB99
83
C4
0C
add esp,
0Ch
.text:
0040AB9C
39
07
cmp
[edi], eax
.text:
0040AB9E
75
64
jnz short loc_40AC04
*
/
BYTE nop2[
0x2
]
=
{
0x90
,
0x90
};
winhook_patchmemory((LPVOID)
0x4311d2
,
nop2, sizeof(nop2));
winhook_patchmemory((LPVOID)
0x40AB9E
,
nop2, sizeof(nop2));
/
/
inlinehook newrawdata
BYTE jmpE8buf[
0x5
]
=
{
0xE9
};
/
/
jmp relative
*
(DWORD
*
)(jmpE8buf
+
1
)
=
(DWORD)newrawbuf_hook_4311A2
-
((DWORD)g_newrawbufi_4311A2
+
sizeof(jmpE8buf));
winhook_patchmemory((LPVOID)g_newrawbufi_4311A2,
jmpE8buf, sizeof(jmpE8buf));
/
/
inlinehook decompress
BYTE callE9buf[
0x5
]
=
{
0xE8
};
/
/
call relative
*
(DWORD
*
)(callE9buf
+
1
)
=
(DWORD)decompressasb_hook_4311E1
-
((DWORD)g_decompressasbi_4311E1
+
sizeof(jmpE8buf));
winhook_patchmemory((LPVOID)g_decompressasbi_4311E1,
callE9buf, sizeof(callE9buf));
}
.text:
004340F6
loc_4340F6:
.text:
004340F6
mov ecx, [ebp
+
74h
+
var_4]
.text:
004340F9
mov cl, [ecx]
.text:
004340FB
mov dl, cl
.text:
004340FD
xor dl,
20h
.text:
00434100
add dl,
5Fh
;
'_'
.text:
00434103
cmp
dl,
3Bh
;
';'
.text:
00434106
ja loc_434215
.text:
004340F6
loc_4340F6:
.text:
004340F6
mov ecx, [ebp
+
74h
+
var_4]
.text:
004340F9
mov cl, [ecx]
.text:
004340FB
mov dl, cl
.text:
004340FD
xor dl,
20h
.text:
00434100
add dl,
5Fh
;
'_'
.text:
00434103
cmp
dl,
3Bh
;
';'
.text:
00434106
ja loc_434215
/
/
from
the
file
start, there are several opcodes entries
optype
4
, oplengh
4
, payload n
[
26
|
27
00
00
00
], oplengh
4
, [
00
]
*
0x10
, optext
/
/
26
music,
27
text
[
0d
00
00
00
], oplengh
4
, [
00
]
*
4
, option_num
4
, [
00
]
*
8
, text1,
00
, text2 ...
/
/
option
[
0a
00
00
00
], [
18
00
00
00
] , addr
4
, [
00
]
*
4
, unknow1
4
, unknow2
4
/
/
jmp
[
0b
00
00
00
], [
1c
00
00
00
] , addr
4
, [
00
]
*
4
, unknow1
4
, unknow2
4
/
/
option jmp
00
00
00
00
FF FF FF FF FF FF FF FF
00
00
00
00
00
00
00
00
00
00
00
00
/
/
end with that
/
/
from
the
file
start, there are several opcodes entries
optype
4
, oplengh
4
, payload n
[
26
|
27
00
00
00
], oplengh
4
, [
00
]
*
0x10
, optext
/
/
26
music,
27
text
[
0d
00
00
00
], oplengh
4
, [
00
]
*
4
, option_num
4
, [
00
]
*
8
, text1,
00
, text2 ...
/
/
option
[
0a
00
00
00
], [
18
00
00
00
] , addr
4
, [
00
]
*
4
, unknow1
4
, unknow2
4
/
/
jmp
[
0b
00
00
00
], [
1c
00
00
00
] , addr
4
, [
00
]
*
4
, unknow1
4
, unknow2
4
/
/
option jmp
00
00
00
00
FF FF FF FF FF FF FF FF
00
00
00
00
00
00
00
00
00
00
00
00
/
/
end with that
0019FD80
0040EF75
50
return
to lamune.sub_40EE6F
+
106
from
??? User
/
/
CreateDIBinfo
0019FDD0
00401E77
0040EE6F
34
return
to lamune.sub_401D0F
+
168
from
lamune.sub_40 User
0019FE04
0040955D
24
return
to lamune.sub_40951B
+
42
from
??? User
0019FE28
0040686C
0040951B
24
return
to lamune.sub_406813
+
59
from
lamune.sub_409 User
0019FE4C
004383EA
00406813
A4
return
to lamune.EntryPoint
+
184
from
lamune.sub_40 User
0019FEF0
0043827E
0043F210
84
return
to lamune.EntryPoint
+
18
from
lamune.sub_43F User
DWORD __thiscall sub_42A199(
int
*
this)
/
/
neko_logo.cpb
| loadimg_419E03(off_473088,
"neko_logo.cpb"
, this
+
0x214
);
| readcpb_40C03E((_DWORD
*
*
)this
+
1
, cpb_header,
0x10
);
| (
*
(_DWORD
*
)
*
v7
+
4
))(
*
v7, cpb_header)
/
/
check magic cpb\x1a
| (
*
(v8
+
0x3C
))(v10[
4
], v10[
5
])
/
/
0041D3FB
, read full
| v3
=
(
*
(
*
*
v7
+
12
))(
*
v7, v5, v10,a3);
/
/
0041D453
, check depth
|
return
(
*
(
*
v6
+
0x10
))(v6, a2, a4);
/
/
0041E36F
,
0041ddb8
,decompress
|decompress2_40AA38(char
*
compressed_buf, size_t |compressed_size, char
*
raw_buf, size_t raw_len)
/
/
lzss?
| sub_40C9C1(DWORD
*
this,
int
a2,
int
a3,
int
*
a4, DWORD
*
a5)
|sub_4101EB(v9
+
2
, a2, a3, a4,
*
a5, a5[
1
], a5[
2
], a5[
3
],
0
);
/
/
bltalpha
|(
*
(this[
2
]
+
0x48
))(this
+
2
, a2, a3, a4,
*
a5, a5[
1
], a5[
2
], a5[
3
],
0xCC0020
);
/
/
004123E1
, to bitblt
0019FD80
0040EF75
50
return
to lamune.sub_40EE6F
+
106
from
??? User
/
/
CreateDIBinfo
0019FDD0
00401E77
0040EE6F
34
return
to lamune.sub_401D0F
+
168
from
lamune.sub_40 User
0019FE04
0040955D
24
return
to lamune.sub_40951B
+
42
from
??? User
0019FE28
0040686C
0040951B
24
return
to lamune.sub_406813
+
59
from
lamune.sub_409 User
0019FE4C
004383EA
00406813
A4
return
to lamune.EntryPoint
+
184
from
lamune.sub_40 User
0019FEF0
0043827E
0043F210
84
return
to lamune.EntryPoint
+
18
from
lamune.sub_43F User
DWORD __thiscall sub_42A199(
int
*
this)
/
/
neko_logo.cpb
| loadimg_419E03(off_473088,
"neko_logo.cpb"
, this
+
0x214
);
| readcpb_40C03E((_DWORD
*
*
)this
+
1
, cpb_header,
0x10
);
| (
*
(_DWORD
*
)
*
v7
+
4
))(
*
v7, cpb_header)
/
/
check magic cpb\x1a
| (
*
(v8
+
0x3C
))(v10[
4
], v10[
5
])
/
/
0041D3FB
, read full
| v3
=
(
*
(
*
*
v7
+
12
))(
*
v7, v5, v10,a3);
/
/
0041D453
, check depth
|
return
(
*
(
*
v6
+
0x10
))(v6, a2, a4);
/
/
0041E36F
,
0041ddb8
,decompress
|decompress2_40AA38(char
*
compressed_buf, size_t |compressed_size, char
*
raw_buf, size_t raw_len)
/
/
lzss?
| sub_40C9C1(DWORD
*
this,
int
a2,
int
a3,
int
*
a4, DWORD
*
a5)
|sub_4101EB(v9
+
2
, a2, a3, a4,
*
a5, a5[
1
], a5[
2
], a5[
3
],
0
);
/
/
bltalpha
|(
*
(this[
2
]
+
0x48
))(this
+
2
, a2, a3, a4,
*
a5, a5[
1
], a5[
2
], a5[
3
],
0xCC0020
);
/
/
004123E1
, to bitblt
00000000
cpb1a_header_t struc ; (sizeof
=
0x20
, mappedto_128)
00000000
; XREF: decompresscpb_41E36F
/
r
00000000
magic db
4
dup(?) ; string(C)
00000004
unknow1 db ?
00000005
color_depth db ?
00000006
unknow2 db ?
00000007
version db ?
00000008
width dw ? ; XREF: decompresscpb_41E36F
+
39
/
r
0000000A
height dw ? ; XREF: decompresscpb_41E36F
+
3E
/
r
0000000C
max_comprlen dd ? ; XREF: decompresscpb_41E36F
+
56
/
r
00000010
comprlen dd
4
dup(?); XREF: decompresscpb_41E36F
+
93
/
r
00000010
; decompresscpb_41E36F
+
B7
/
r ...
00000020
cpb1a_header_t ends
00000000
cpb1a_header_t struc ; (sizeof
=
0x20
, mappedto_128)
00000000
; XREF: decompresscpb_41E36F
/
r
00000000
magic db
4
dup(?) ; string(C)
00000004
unknow1 db ?
00000005
color_depth db ?
00000006
unknow2 db ?
00000007
version db ?
00000008
width dw ? ; XREF: decompresscpb_41E36F
+
39
/
r
0000000A
height dw ? ; XREF: decompresscpb_41E36F
+
3E
/
r
0000000C
max_comprlen dd ? ; XREF: decompresscpb_41E36F
+
56
/
r
00000010
comprlen dd
4
dup(?); XREF: decompresscpb_41E36F
+
93
/
r
00000010
; decompresscpb_41E36F
+
B7
/
r ...
00000020
cpb1a_header_t ends
void
*
__thiscall sub_40FDC2(void
*
*
this,
LONG
width,
int
height)
{
void
*
result;
/
/
eax
HBITMAP v5;
/
/
eax
HDC dc;
/
/
eax
int
(__thiscall
*
*
v7)(void
*
*
, _DWORD);
/
/
eax
BITMAPINFO pbmi;
/
/
[esp
+
8h
] [ebp
-
2Ch
] BYREF
if
( (void
*
)width
=
=
this[
41
] && (void
*
)height
=
=
this[
42
] )
return
(void
*
)(
*
((
int
(__thiscall
*
*
)(void
*
*
, _DWORD))
*
this
+
26
))(this,
0
);
(
*
((void (__thiscall
*
*
)(void
*
*
))
*
this
+
13
))(this);
if
( width >
0
&& height >
0
)
{
memset(&pbmi,
0
, sizeof(pbmi));
pbmi.bmiHeader.biHeight
=
-
height;
pbmi.bmiHeader.biSize
=
0x28
;
/
/
struct size
pbmi.bmiHeader.biWidth
=
width;
pbmi.bmiHeader.biPlanes
=
1
;
/
/
must be
1
pbmi.bmiHeader.biBitCount
=
32
;
/
/
rgba
pbmi.bmiHeader.biCompression
=
0
;
v5
=
CreateDIBSection(
0
, &pbmi,
0
, this
+
0x28
,
0
,
0
);
/
/
this
+
0x28
this[
37
]
=
v5;
if
( v5 )
{
dc
=
CreateCompatibleDC(
0
);
this[
0x27
]
=
dc;
if
( dc )
{
this[
0x26
]
=
SelectObject(dc, this[
0x25
]);
this[
0x29
]
=
(void
*
)width;
this[
0x2A
]
=
(void
*
)height;
this[
0x2E
]
=
(void
*
)(height
-
1
);
v7
=
(
int
(__thiscall
*
*
)(void
*
*
, _DWORD))
*
this;
this[
0x2B
]
=
0
;
this[
0x2C
]
=
0
;
this[
0x2D
]
=
(void
*
)(width
-
1
);
result
=
(void
*
)v7[
0x1A
](this,
0
);
/
/
0041295A
, FillRect
this[
0x52
]
=
result;
return
result;
}
}
(
*
((void (__thiscall
*
*
)(void
*
*
))
*
this
+
0xD
))(this);
}
return
0
;
}
void
*
__thiscall sub_40FDC2(void
*
*
this,
LONG
width,
int
height)
{
void
*
result;
/
/
eax
HBITMAP v5;
/
/
eax
HDC dc;
/
/
eax
int
(__thiscall
*
*
v7)(void
*
*
, _DWORD);
/
/
eax
BITMAPINFO pbmi;
/
/
[esp
+
8h
] [ebp
-
2Ch
] BYREF
if
( (void
*
)width
=
=
this[
41
] && (void
*
)height
=
=
this[
42
] )
return
(void
*
)(
*
((
int
(__thiscall
*
*
)(void
*
*
, _DWORD))
*
this
+
26
))(this,
0
);
(
*
((void (__thiscall
*
*
)(void
*
*
))
*
this
+
13
))(this);
if
( width >
0
&& height >
0
)
{
memset(&pbmi,
0
, sizeof(pbmi));
pbmi.bmiHeader.biHeight
=
-
height;
pbmi.bmiHeader.biSize
=
0x28
;
/
/
struct size
pbmi.bmiHeader.biWidth
=
width;
pbmi.bmiHeader.biPlanes
=
1
;
/
/
must be
1
pbmi.bmiHeader.biBitCount
=
32
;
/
/
rgba
pbmi.bmiHeader.biCompression
=
0
;
v5
=
CreateDIBSection(
0
, &pbmi,
0
, this
+
0x28
,
0
,
0
);
/
/
this
+
0x28
this[
37
]
=
v5;
if
( v5 )
{
dc
=
CreateCompatibleDC(
0
);
this[
0x27
]
=
dc;
if
( dc )
{
this[
0x26
]
=
SelectObject(dc, this[
0x25
]);
this[
0x29
]
=
(void
*
)width;
this[
0x2A
]
=
(void
*
)height;
this[
0x2E
]
=
(void
*
)(height
-
1
);
v7
=
(
int
(__thiscall
*
*
)(void
*
*
, _DWORD))
*
this;
this[
0x2B
]
=
0
;
this[
0x2C
]
=
0
;
this[
0x2D
]
=
(void
*
)(width
-
1
);
result
=
(void
*
)v7[
0x1A
](this,
0
);
/
/
0041295A
, FillRect
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!