galgame吧 关注:1,646,516贴子:22,939,534

【摸鱼】老游戏《最果てのイマ》引擎的一点分析

只看楼主收藏回复



IP属地:广东1楼2020-02-12 10:48回复
    首先从sub_40BFF0这个函数入手。

    可以看到有一个格式字符串,这个是拼接文件名用的。
    得到文件名之后进入了Gaf004::HashFileName函数,这个函数接受一个字符串参数和一个缓冲区参数。

    看看这个函数长啥样。

    嗯……看不懂,溜了溜了。
    回到sub_40BFF0,已知把文件路径传入Hash_FileName会得到两个返回值。
    一个是hash_1,里面是上面那个谜之函数计算出来的字节,一个是hash_1里字节的数量。

    接着进入了LoadFileAndIndex函数,把文件路径和hash_1传了进去。

    这里直接用CreateFileA打开了封包文件。
    接着把文件句柄和hash_1传入sub_40C150函数。

    进入sub_40C150后接着把文件句柄和hash_1传入Gaf004Loader__LoadFromHFile函数。
    继续跟进。

    这里发现它用SetFilePointer取得当前读写位置,因为文件是刚刚打开的,所以它一定会返回0。
    具体作用先不管,接着往下看。
    把文件句柄存到了类中。
    接着把hash_1传入Gaf004Loader::Load函数。跟进。

    首先用SetFilePointer设置读写位置,并检查错误。
    然后用ReadFile读入了4字节和2字节,用于检查文件类型。

    6个字节是固定不变的。
    检查完文件类型,接着把hash_1传入Gaf004Loader::Load2函数,继续跟进。


    IP属地:广东2楼2020-02-12 10:52
    回复
      请问up是怎么逆向出源码的,还是直接有现成源码


      IP属地:广东3楼2020-02-12 10:55
      收起回复
        继续跟进Gaf004Loader::Load2。

        读入64字节,读入4字节。分别对应:

        记住这个字符串。
        4字节是一个整数,是封包里的文件数量。
        接下来到了一个叫Gaf004Loader::4AF490的函数。
        这个函数输入hash_1,输出了两个整数。这两个整数分别是两个数据块的字节数。

        读取第一个数据块的所有字节,并计算CRC。
        接着又是一个循环。

        这个循环读取了entry_count个整数,作为偏移表。
        表里的每个整数对应着每个资源在封包中的位置。
        接着还有一个循环。

        这个循环读取了第二个数据块,计算CRC。
        读完这个数据块之后,继续读入2字节。

        这个2个字节是CRC校检的最后两个字节。
        如果CRC函数返回0,则校检成功,否则失败,说明数据损坏或者被篡改。
        接着把刚才读到的那个64字节的字符串存到类里。
        最后把hash_1传入一个叫Gaf004Loader::Hash_2的函数。

        嗯,看不懂,过。

        可以看到这个函数根据hash_1生成了新的数据,记作hash_2。
        最后函数开始逐层返回。回到sub_40C150函数。(调用LoadFromHFile那个)
        至此可以判断,封包的文件头应该是读完了。
        我们来整理一下文件头的结构。


        IP属地:广东4楼2020-02-12 10:55
        回复


          IP属地:广东来自Android客户端5楼2020-02-12 11:28
          回复
            Gaf004Loader__LoadFromHFile执行之后,封包就已经载入完成了,但是还没有开始读取数据。

            接着用Gaf004::Verify函数来验证封包特征。
            跟进去瞧一眼。

            没错,就是上面那个64字节的字符串啦。
            如果验证通过,接下来就会调用Gaf004::Hash_1,输入这个字符串,生成一个新的数据,记作hash_3。
            然后用SetSignature把hash_3存进类里。
            存完hash_3之后,就开始读取封包里的文件了。
            ReadByIndex这个函数用来读取指定索引(或者说ID)的文件。
            跟进去看看。

            首先检查了一下索引是否正确。
            接着把索引存进了reading_index。
            接着进入Read_Entry函数。

            进来之后发现一个SeekToIndex函数,把reading_index传进去。

            首先它计算了一个pos变量。
            这个pos实际上就是索引表(offset_table)中某一项的位置。

            接着它会去offset_table中对应的索引的位置读取4个字节。
            接着解密这4个字节。
            此处解密数据就用到了hash_2,还记得吗?这个算法后面会补充说明。
            解密完成后,这4个字节就是一个整数了。
            这个整数就是要对应索引的文件在封包中的位置。
            接着调用SetFilePointer把读写位置移动到该位置处。
            返回Read_Entry函数。

            当程序把读写位置移动到目标位置之后,就开始读取文件数据了。
            进入Read_DSET函数。

            首先调用SetFilePointer取得当前读写位置,用来解密数据。
            读入4字节是标识符。
            读入4字节是项目数量。
            读入2字节是无用数据。
            一共读入了4+4+2=10字节数据。
            这些数据要用刚才的解密算法来解密。
            但是用到的密码表却不同。
            由于密码表是上一层传进来的,所以回去看一眼。

            它用的是hash_3,还记得吗?


            IP属地:广东6楼2020-02-12 11:49
            回复
              Read_DSET函数返回了一个整数,记作Count。
              接下来循环Count次,读取DSET中的,每个项目。

              Read_ITEM类似Read_DSET,但它返回两个整数。
              一个是ITEM的类型,一个是ITEM的长度(大小)。
              ITEM的类型有好几种,这里先看看PICT。

              读入了4字节,用途暂时未知。
              这里看到它只是把Item的数据存到了某个结构体里。
              说明目前暂时还不会读取真正的文件数据。
              根据上面读取数据的代码,可以整理出Dset的数据结构。

              根据上面的循环可以确定,Dset中可以包含多个子结构。例如Pict和Ftag。

              从ReadEntry里出来。

              接着进入ReadEntry1。

              移动到刚才读到的Item的位置。
              然后读入length(Item的数据长度)。并解密。

              再往下看已经开始读取实际资源数据了。
              所以可以判断Item头部结构之后紧接着就是资源数据了。


              IP属地:广东7楼2020-02-12 11:53
              回复
                通过解密Entry数据,可以分析出一些信息。


                IP属地:广东9楼2020-02-12 12:00
                回复
                  最后总结一下Gaf004封包结构。
                  为了加载封包,必须用到三个密码表。
                  密码表:hash_1(通过封包文件名生成)
                  密码表:hash_2(通过hash_1生成)
                  密码表:hash_3(通过文件头部的字符串生成)
                  很显然,为了读取文件头,必须知道两个block(数据块)的大小。
                  这两个数据块的大小通过 Gaf004Loader::4AF490 这个函数,输入hash_1密码表计算出来。
                  实际上这两个数据块就是填充用的。无实际作用。
                  读入offset_table(偏移表)之后,偏移表需要用hash_2密码表来解密。
                  有了偏移表之后就可以定位到想要读取的文件了。
                  Gaf004中主要包含DSET结构。
                  这个结构通常包含两个子结构。
                  第一个子结构是文件内容(例如PICT),第二个子结构是FTAG(文件名)。
                  还有不得不提的是,文件头还有CRC校检。
                  文件头最后还有2字节就是校检蚂。


                  IP属地:广东11楼2020-02-12 12:14
                  回复
                    写代码测试。

                    解包完成。


                    IP属地:广东12楼2020-02-12 12:20
                    回复

                      加密算法就是简单的查表异或而已。
                      有意思的是三个Hash函数。(上图我写为Sign签名函数了)
                      其中两个用于生成密码表,一个用于生成两个整数。
                      这几个函数代码真的估计就是随便写的。
                      反正能生成一堆意义不明的数据就对了。
                      我也是直接扒的代码,没必要去研究它的算法。

                      然后说说封包。
                      实际上,加密所需的材料就两个,一个是封包文件名,一个是文件头部的字符串。
                      按照结构构建封包即可。
                      至于CRC校检,直接扒函数就行了。
                      封包中那些无用的字节随机生成或者直接填0即可。
                      (比方说block_0和block_1)
                      文件头所有数据过CRC之后,把CRC的值存到文件头尾部即可。
                      然后写出所有构建好的Dset结构。就完成了封包。


                      IP属地:广东13楼2020-02-12 12:29
                      回复
                        是大佬,我死了


                        IP属地:四川来自Android客户端14楼2020-02-12 12:30
                        回复
                          还有就是一开始提到的那个保存文件头位置的操作,那个其实是用来实现嵌套封包的。
                          对的你没看错,这个封包里还可以嵌入封包。


                          IP属地:广东来自iPhone客户端15楼2020-02-12 12:37
                          回复
                            Sdlwsl


                            IP属地:重庆来自Android客户端16楼2020-02-12 13:18
                            回复


                              IP属地:广东来自iPhone客户端17楼2020-02-12 15:19
                              回复