﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-feixuwu-随笔分类-逆向工程</title><link>http://www.cppblog.com/feixuwu/category/14247.html</link><description /><language>zh-cn</language><lastBuildDate>Sat, 17 Jul 2010 07:46:39 GMT</lastBuildDate><pubDate>Sat, 17 Jul 2010 07:46:39 GMT</pubDate><ttl>60</ttl><item><title>剑3资源格式分析(仅用于学习和技术研究)（二）</title><link>http://www.cppblog.com/feixuwu/archive/2010/07/16/120581.html</link><dc:creator>feixuwu</dc:creator><author>feixuwu</author><pubDate>Fri, 16 Jul 2010 12:47:00 GMT</pubDate><guid>http://www.cppblog.com/feixuwu/archive/2010/07/16/120581.html</guid><wfw:comment>http://www.cppblog.com/feixuwu/comments/120581.html</wfw:comment><comments>http://www.cppblog.com/feixuwu/archive/2010/07/16/120581.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://www.cppblog.com/feixuwu/comments/commentRss/120581.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/feixuwu/services/trackbacks/120581.html</trackback:ping><description><![CDATA[&nbsp;&nbsp; 继续未完成的内容，声明<span style="font-weight: bold;">本文仅用于学习研究，不提供解压工具和实际代码</span>。<br>由于时间仓促，剑3资源格式分析(仅用于学习和技术研究)（一）
大部分只是贴出了分析的结果，并没有详细的分析过程，比如：如何知道那是一个pak文件处理对象，<br>如何根据虚表偏移获取实际函数地址等等，这就需要读者对c++对象在内存中的layout有一定基础。<br>开始正文了～～，先整理下前面的分析结果：<br>1、剑3是通过package.ini 来管理pak文件的，最多可配置key从 0-32（0x20）的32个文件。<br>2、每个pak文件都用一个独立对象来管理，所有的pak对象指针存储在一个数组里（这个后面会用到）。<br>3、pak文件格式：[pak标记(Uint32)] + [文件数目(Uint32)]+[索引数据偏移(Uint32)]+未知内容。另外，每个文件的索引数据是16个字节。<br><br>
<h1>一、路径名哈希</h1>
&nbsp; 剑3的pak的内部文件是通过hash值来查找的，这样有利于加快查询速度。这就需要有一个函数通过传入 路径名返回hash值。<br>这个函数居然是导出的。。。g_FileNameHash&nbsp;
这个函数代码比较少，可以逆向出来用C重写，也可以直接使用引擎函数(LoadLibrary，GetProceAddress来使用)。<br>熟悉剑侠系列的朋友会发现，这个函数从新剑侠情缘（可能历史更久）开始就没有变过（确实没必要变），具体细节就不逐个分析了，我是写了一个单独的命令行工具<br>来测试的。<br><br>
<h1>二、查询过程</h1>
查询函数在sub_10010E00
里，也就是(0x10010E00)的位置，我是通过简单分析g_IsFileExist 得知这个函数功能的。下面<br>来分析这个函数过程：<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/find.PNG" width="885" height="685"><br>从前文可知，pak文件对象是存储在一个数组的这个数组是类似 KPakFile* m_szPakFile[0x21];<br>前面0x20个存储的都是KPakFile对象指针，最后一个存储的是数组长度。<br>这个搜索结构比较简单，就是遍历所有的KPakFile对象，逐个查询，找到了就返回。想知道具体怎么查询的吗，<br>接下来要看sub_100108B0了。<br><br>这个函数稍微有点长，分几个部分来分析吧：<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/find2.PNG" width="862" height="593"><br>首先，验证下Hash值是否是0，如果是0，肯定是错了：）<br>然后接着开始根据这个hash值进行查找了，经过分析，我发现这个函数其实是一个二分查找，代码贴出来如下 sub_10010320
：<br><br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/find31.PNG" width="879" height="525"><br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/find32.PNG" width="836" height="524"><br>从上面的代码还是比较容易可以知道，每个文件的16个索引数据中，前4个字节是hash值，这个函数返回的是这个文件是pak包的第几个。<br><br>接着前面的sub_100108B0
来看吧<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/findresult.PNG" width="569" height="210"><br>这一段是保存查询到的数据到对象里。分析到这里，我只知道16个索引数据前4个字节是hash值，那么剩下的12个字节呢，<br>剩下的数据基本可以确定是：文件偏移、文件长度。我是个懒人，接下来的分析我是通过在fseek、fread下断点来得到的，为什么不是在SetFilePointer和ReadFile呢，<br>这是根据前面的分析得到的，因为pak文件管理对象使用的是C标准库函数。<br>根据fread和fseek的结果，可以得到如下结果：<br>索引数据构成是:<br>[哈希数值(Uint32)] + [文件偏移(Uint32)]+[未知数据(Uint32)] + 2(文件长度)+2（未知数据）。<br>剩下的，就是看看单独内部文件的解压方式了，<br>在fread的缓冲区上设置内存断点，就可以找到解压函数了：<br>sub_10018020<br>这个函数不算太长，一开始我也想逆向成C语言，后来看到如此多的分支就放弃了，转而用了一个偷懒的办法解决了：<br>从汇编代码可知这个函数的原型：<br>typedef int (*PUNPACK_FUN)(void* psrcData, int nSrcLen, void* pDstData, int* pDstLen);
<br>直接加载剑3的dll，设置函数地址：<br>PUNPACK_FUN pEngineUnpack = (PUNPACK_FUN)((unsigned int)hEngineModule + 0x18020);
<br>hEngineModule
是引擎dll的基址，大家看到了吧，dll的函数即使不导出，我们也是可以调用的：）<br><br>
<h1>三、尾声</h1>
到这里，已经可以写出一个pak文件的解压包了，但是，我们还是没有还原真实的文件名，<br>下面是我解压的script.pak的文件的部分内容：<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/script.PNG" width="496" height="207"><br>&nbsp;终于看到大侠们的签名了。当然，对着一堆hash值为名字的文件，阅读起来确实很困难，<br>那么有办法还原真实的文件名吗，办法还是有一些的，可以通过各种办法改写g_OpenFileInPak记录参数名，来获取游戏中用到的pak内部文件名，相信这难不倒各位了。<br><br> <img src ="http://www.cppblog.com/feixuwu/aggbug/120581.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/feixuwu/" target="_blank">feixuwu</a> 2010-07-16 20:47 <a href="http://www.cppblog.com/feixuwu/archive/2010/07/16/120581.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>剑3资源格式分析(仅用于学习和技术研究)（一）</title><link>http://www.cppblog.com/feixuwu/archive/2010/07/15/120478.html</link><dc:creator>feixuwu</dc:creator><author>feixuwu</author><pubDate>Thu, 15 Jul 2010 13:07:00 GMT</pubDate><guid>http://www.cppblog.com/feixuwu/archive/2010/07/15/120478.html</guid><wfw:comment>http://www.cppblog.com/feixuwu/comments/120478.html</wfw:comment><comments>http://www.cppblog.com/feixuwu/archive/2010/07/15/120478.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/feixuwu/comments/commentRss/120478.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/feixuwu/services/trackbacks/120478.html</trackback:ping><description><![CDATA[&nbsp;&nbsp; 这几天在玩剑三，突然兴趣来了，想要分析剑3的资源打包格式。在资源分析和逆向方面原来偶尔也干过，<br>不过总体来说还是处于菜鸟阶段，这篇文章希望和其他有兴趣的兄弟分享下这几天的经历，仅仅作为技术研究。<br>
<h1><br><span style="color: #180000;"></span></h1>
<h1><span style="color: #180000;">一、安全保护</span></h1>
&nbsp; 一般来说，很少有游戏的资源格式可以直接通过分析资源文件本身得到答案，大部分难免要静态逆向、动态调试。<br>无论是静态逆向还是动态调试，首先需要知道当前exe和dll的保护情况，用peid查看，发现只有gameupdater.exe 用upx加壳了。不太明白金山为什么对客户端没有加壳。<br>其实我并不关心gameupdater.exe 是否加壳，毕竟要动态分析的目标是JX3Client.exe
，要动态调试JX3Client.exe，首先要解决启动参数问题。<br><br>
<h1 style="color: #180000;">二、启动参数</h1>
&nbsp; 如果直接启动JX3Client.exe，JX3Client.exe会直接退出，并启动gameuodater.exe，然后通过gameupdater.exe启动JX3Client.exe。<br>这种启动方式会影响动态调试，所以首先我需要找出JX3Client.exe的启动参数。打开IDA逆向，转到启动处，汇编代码如下：<br><img src="file:///C:/DOCUME%7E1/feixuwu/LOCALS%7E1/Temp/moz-screenshot.png" alt=""><img src="file:///C:/DOCUME%7E1/feixuwu/LOCALS%7E1/Temp/moz-screenshot-1.png" alt="">
<meta http-equiv="Content-Type" content="text/html; charset=" utf-8="">
<meta name="ProgId" content="Word.Document">
<meta name="Generator" content="Microsoft Word 11">
<meta name="Originator" content="Microsoft Word 11">
<link rel="File-List" href="file:///C:%5CDOCUME%7E1%5Cfeixuwu%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">
<link rel="Edit-Time-Data" href="file:///C:%5CDOCUME%7E1%5Cfeixuwu%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_editdata.mso"><!--[if !mso]>
<style>
v\:* {behavior:url(#default#VML);}
o\:* {behavior:url(#default#VML);}
w\:* {behavior:url(#default#VML);}
.shape {behavior:url(#default#VML);}
</style>
<![endif]--><!--[if gte mso 9]><xml>
Normal
0
7.8 磅
0
2
false
false
false
MicrosoftInternetExplorer4
</xml><![endif]--><!--[if gte mso 9]><xml>
</xml><![endif]--><style>
<!--
/* Font Definitions */
@font-face
{font-family:宋体;
panose-1:2 1 6 0 3 1 1 1 1 1;
mso-font-alt:SimSun;
mso-font-charset:134;
mso-generic-font-family:auto;
mso-font-pitch:variable;
mso-font-signature:3 135135232 16 0 262145 0;}
@font-face
{font-family:"\@宋体";
panose-1:2 1 6 0 3 1 1 1 1 1;
mso-font-charset:134;
mso-generic-font-family:auto;
mso-font-pitch:variable;
mso-font-signature:3 135135232 16 0 262145 0;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{mso-style-parent:"";
margin:0cm;
margin-bottom:.0001pt;
text-align:justify;
text-justify:inter-ideograph;
mso-pagination:none;
font-size:10.5pt;
mso-bidi-font-size:12.0pt;
font-family:"Times New Roman";
mso-fareast-font-family:宋体;
mso-font-kerning:1.0pt;}
/* Page Definitions */
@page
{mso-page-border-surround-header:no;
mso-page-border-surround-footer:no;}
@page Section1
{size:612.0pt 792.0pt;
margin:72.0pt 90.0pt 72.0pt 90.0pt;
mso-header-margin:36.0pt;
mso-footer-margin:36.0pt;
mso-paper-source:0;}
div.Section1
{page:Section1;}
-->
</style><!--[if gte mso 10]>
<style>
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.0pt;
font-family:"Times New Roman";
mso-fareast-font-family:"Times New Roman";
mso-ansi-language:#0400;
mso-fareast-language:#0400;
mso-bidi-language:#0400;}
</style>
<![endif]--><!--[if gte vml 1]>
<![endif]--><!--[if !vml]-->start proc near<br>call&nbsp;&nbsp;&nbsp; ___security_init_cookie<br>jmp&nbsp;&nbsp;&nbsp;&nbsp; ___tmainCRTStartup<br>start endp<br>这是一个典型的VC程序入口，在___tmainCRTStartup
里，crt会初始化全局变量、静态变量，然后进入main,我们需要做的是直接找到main,<br>跟进去，会发现IDA已经帮我们找到WinMain了，直接跟进去，<br>关键代码在WinMain的入口处：<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/winmain.PNG" width="852" height="627"><br>从这个代码片段可以知道，WinMain开始就比较了命令行参数是否是"DOTNOTSTARTGAMEBYJX3CLIENT.EXE
",如果不是，<br>则转到启动更新程序了。这个好办，我们写一个run.bat，内容只有一行：<br>JX3Client.exe DOTNOTSTARTGAMEBYJX3CLIENT.EXE
<br>运行，果然，直接看到加载界面了。<br><br>
<h1>三、PAK文件管理</h1>
&nbsp; 在剑3里，PAK目录下有很多PAK文件，剑3是通过package.ini 来加载和管理pak内部文件的。<br>这个文件内容如下：<br>[SO3Client]<br>10=data_5.pak<br>1=ui.pak<br>0=update_1.pak<br>3=maps.pak<br>2=settings.pak<br>5=scripts.pak<br>4=represent.pak<br>7=data_2.pak<br>6=data_1.pak<br>9=data_4.pak<br>Path=.\pak<br>8=data_3.pak<br>基本上PAK目录下所有的PAK文件都列出来了，其实剑3的资源文件打包方式基本上和新剑侠情缘类似（细节还是有比较大的差别）。<br>打开ollyDbg，带参数启动JX3Client.exe，在CreateFile设置断点，可以发现，package.ini
的读取和处理是在<br>Engine_Lua5.dll
的g_LoadPackageFiles
函数，熟悉新剑侠情缘资源管理方式的同学大概会猜到这个函数是做什么的，先看看函数内容吧，这个函数比较长<br>只能逐步的分析了，首先是打开ini文件<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/openini.PNG"><br>使用g_OpenIniFile打开前面提到的ini文件，如果打开失败，自然直接返回了。<br>打开成功后，循环读取ini配置的文件，读取的section是SO3Client 读取的key是0到0x20。<br><br>loc_1001119A:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; int<br>push&nbsp;&nbsp;&nbsp; 0Ah<br>lea&nbsp;&nbsp;&nbsp;&nbsp; ecx, [esp+1A0h+var_178]<br>push&nbsp;&nbsp;&nbsp; ecx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; char *<br>push&nbsp;&nbsp;&nbsp; ebx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; int<br>call&nbsp;&nbsp;&nbsp; ds:_itoa&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; 这是根据数字生成key的代码<br>mov&nbsp;&nbsp;&nbsp;&nbsp; edx, [ebp+0]<br>mov&nbsp;&nbsp;&nbsp;&nbsp; edx, [edx+24h]<br>add&nbsp;&nbsp;&nbsp;&nbsp; esp, 0Ch<br>push&nbsp;&nbsp;&nbsp; 40h<br>lea&nbsp;&nbsp;&nbsp;&nbsp; eax, [esp+1A0h+var_168]<br>push&nbsp;&nbsp;&nbsp; eax<br>mov&nbsp;&nbsp;&nbsp;&nbsp; eax, [esp+1A4h+var_184]<br>push&nbsp;&nbsp;&nbsp; offset unk_10035B8C<br>lea&nbsp;&nbsp;&nbsp;&nbsp; ecx, [esp+1A8h+var_178]<br>push&nbsp;&nbsp;&nbsp; ecx<br>push&nbsp;&nbsp;&nbsp; eax<br>mov&nbsp;&nbsp;&nbsp;&nbsp; ecx, ebp<br>call&nbsp;&nbsp;&nbsp; edx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; 读取INI内容　readString(section, key)<br>test&nbsp;&nbsp;&nbsp; eax, eax<br>jz&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; loc_1001127A<br><br>
这段是通过readString("SO3Client", key)来获取pak文件名, key就是"0"~"32"的字符串，也就是最多能配置32个Pak文件。<br>获得了pak文件名后，下面就是打开和保存pak文件的索引数据了。<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/pakconstruct.PNG" width="843" height="264"><br>后面的注释是我分析的时候加上的，IDA这个功能不错！<br>首先new一个0x20字节的空间用来存储pak对象（我自己命名的类），接着调用构造函数，创建pak对象。<br>创建对象后，要用这个Pak对象打开对应的pak文件了，这是我们下面的代码：<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/openpak.PNG" width="492" height="321"><br>首先通过<br>mov&nbsp;&nbsp;&nbsp;&nbsp; [edi+edx*4], eax
<br>将对象保存，然后，调用这个类的成员函数打开pak文件，具体代码在sub_10010ca0。<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/openPakImp1.PNG" width="964" height="617"><br>这段代码的意思很明白了，打开文件，读取0x20的文件头，<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/openPakImp2.PNG" width="849" height="608"><br><br>这里做的是验证文件格式，和一些必要的验证。<br><img alt="" src="http://www.cppblog.com/images/cppblog_com/feixuwu/openPakImp3.PNG" width="652" height="491"><br>这段是读取pak内部文件数目，读取索引数据，以备后面查询使用。<br>到此为止，所有pak文件的管理对象都已经加载和设置完毕了。<br>以上内容看起来很顺理成章，但是实际上凝聚了无数的失败和重试。<br>后面是pak内部文件的查找和读取了。
<br>剩下的内容明天贴了～～～<br><br><br>    <img src ="http://www.cppblog.com/feixuwu/aggbug/120478.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/feixuwu/" target="_blank">feixuwu</a> 2010-07-15 21:07 <a href="http://www.cppblog.com/feixuwu/archive/2010/07/15/120478.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>