XP3引擎逆向探究

2024-05-11 606 0

前言

在参考项目源码的基础上对XP3引擎进行深入逆向分析

XP3逆向初试

在exe的plugin目录下看到.tpm文件
die看看确实是一个dll 改了个后缀罢了...
而且在导出表部分发现是以xp3dec.tpm的name

一样的对exe执行exe设好三个条件断点

CreateFile

文件读取:  {utf16@[esp+4]}

ReadFile

读取文件: 句柄:{[esp+4]}  缓冲:{[esp+8]}  字节数:{[esp+C]}

SetFilePointer

设置文件指针: 句柄:{[esp+4]}  偏移量:{[[esp+C]]}{[esp+8]}  基准:{[esp+10]}

这里要改为utf8才能正常显示

然而这题由于是调用的dll来解密 不像万华镜是直接引擎解密 所以这样下断点分析很难看出端倪
那就从.tpm入手 string找到关键字符串
void ::TVSetXP3ArchiveExtractionFilter(tTVPXP3ArchiveExtractionF
交叉引用找到V2Link部分 正是dll的导出函数
XP3引擎逆向探究插图
逐个跟踪 跟踪到这个push offset loc_10001000
点过去发现IDA并没有完全识别

text:10001000 loc_10001000:                           ; DATA XREF: V2Link:loc_10001078↓o
.text:10001000                 push    esi
.text:10001001                 mov     esi, [esp+8]
.text:10001005                 cmp     dword ptr [esi], 18h
.text:10001008                 jz      short loc_1000102C
.text:1000100A                 mov     eax, dword_1000C2CC
.text:1000100F                 test    eax, eax
.text:10001011                 jnz     short loc_10001025
.text:10001013                 push    offset aVoidTvpthrowex ; "void ::TVPThrowExceptionMessage(const t"...
.text:10001018                 call    sub_100010C0
.text:1000101D                 add     esp, 4
.text:10001020                 mov     dword_1000C2CC, eax
.text:10001025
.text:10001025 loc_10001025:                           ; CODE XREF: .text:10001011↑j
.text:10001025                 push    offset aIncompatibleTt ; "Incompatible tTVPXP3ExtractionFilterInf"...
.text:1000102A                 call    eax ; dword_1000C2CC
.text:1000102C
.text:1000102C loc_1000102C:                           ; CODE XREF: .text:10001008↑j
.text:1000102C                 xor     eax, eax
.text:1000102E                 cmp     [esi+10h], eax
.text:10001031                 jbe     short loc_1000104B
.text:10001033
.text:10001033 loc_10001033:                           ; CODE XREF: .text:10001049↓j
.text:10001033                 mov     ecx, [esi+0Ch]
.text:10001036                 mov     dl, [ecx+eax]
.text:10001039                 xor     dl, [esi+14h]
.text:1000103C                 add     ecx, eax
.text:1000103E                 xor     dl, 0CDh
.text:10001041                 add     eax, 1
.text:10001044                 mov     [ecx], dl
.text:10001046                 cmp     eax, [esi+10h]
.text:10001049                 jb      short loc_10001033
.text:1000104B
.text:1000104B loc_1000104B:                           ; CODE XREF: .text:10001031↑j
.text:1000104B                 pop     esi
.text:1000104C                 retn    4
.text:1000104C ; ---------------------------------------------------------------------------
.text:1000104F                 align 10h
.text:10001050 ; Exported entry   1. V2Link

XP3引擎逆向探究插图1

/*
		tTVPXP3ExtractionFilterInfo のメンバは以下の通り
		* SizeOfSelf        : 自分自身の構造体のサイズ
		* Offset            : "Buffer" メンバが指し示すデータが、
		*                   : アーカイブに格納されているそのファイルの先頭からの
		*                   : どのオフセット位置からのものか、を表す
		* Buffer            : データ本体
		* BufferSize        : "Buffer" メンバの指し示すデータのサイズ(バイト単位)
		* FileHash          : ファイルの暗号化解除状態でのファイル内容の32bitハッシュ
	*/

逆向经验知道esi经常来传结构体 汇编已经很清晰了:

[esi+0xC]: 数组偏移 DWORD
[esi+0x10]:size DWORD
[esi+0x14]:xor的key值 DWORD

至于0~0xC的部分是什么还不是很清楚
看官方结构体的意思应该是

[esi+0x0]: 结构体大小 DWORD
[esi+0x4]: ? QWORD

但这个QWORD后面动调看每次都是0?
总:

[esi+0x0]: 结构体大小 DWORD
[esi+0x4]: ? QWORD
[esi+0xC]: 数组偏移 DWORD
[esi+0x10]:size DWORD
[esi+0x14]:xor的key值 DWORD

x32dbg在10001000下个断点跟踪 看内存布局
18 00 00 00 00 00 00 00 00 00 00 00 3A FD 19 00
02 00 00 00 D4 88 EA AC 88 00 00 00 02 00 00 00

对应着看就很清晰了
现在关注的点就是程序是在哪一步读取的数据
硬件断点定位到前面关注过的一处memcpy
XP3引擎逆向探究插图2
但各个内存数据对应关系不是很直接
data.xp3读的不是readfile读的 readfile读的与memcpy的有偏移
猜测应该是还有个结构体成员等来读取偏移
这个偏移的计算有点复杂

mov eax [ebx+0x38]
mov edx [ebx+0x40]
add eax [edx+0x8]
=>
[ebx+0x38]+[[ebx+0x40]+0x8]

间接调用找到对应内存发现并不是完全的对应关系 也就是在加密/解密和源数据之间还有没考虑到的地方 - 压缩与解压缩

而以前接触的引擎都没有压缩与解压缩的步骤 这就使得了解XP3文件结构尤为关键

XP3引擎研究

XP3文件格式

基本思路&&工具介绍

整体思路:

  1. 合理利用已有开源工具

  2. 用引擎不同打包方式得到文件进行对比分析

掌握的情报:

  1. XP3支持包含文件和目录

  2. XP3支持存储文件名

  3. XP3支持针对文件的压缩

  4. XP3支持基于文件的加密

  5. XP3支持对索引的压缩

工具:

  1. 010 Editor

  2. GARbro Github开源项目

  3. krkr官方xp3打包器 在KiriKiri2开发包下

  4. krkr-xp3 一个开源的XP3解包打包器 方便用来研究XP3文件格式 跟一些elf_parser很类似的功能

GARbro

XP3引擎逆向探究插图3

官方release:
XP3引擎逆向探究插图4

实验

XP3压缩&加密技术

GARbro的zlib
XP3引擎逆向探究插图5

krkrxp3的zlib
XP3引擎逆向探究插图6

这里可以看到zlib压缩等级为9

用GARbro和官方封包exe分别打包一次
GARbro选择XP3引擎逆向探究插图7
可以发现GARbro的Version2跟官方是匹配的
XP3引擎逆向探究插图8

接下来用bandizip进行不同压缩算法的尝试 找到xp3采用的压缩类型

经过尝试 发现gz的deflate模式开最大压缩比能够与xp3匹配
XP3引擎逆向探究插图9

确定了压缩类型 我们接下来就需要横向对比来找类似的索引结构
多选几种类型的文件GARbro进行压缩
XP3引擎逆向探究插图10)

看自己压缩的小文件看不出什么 我们横向对比一下大型的XP3文件
大致发现都是以
00 00 00 00 00 00 00 78 DA 73 CB CC 49作为分隔

对应的结构
XP3引擎逆向探究插图11

官方release的在中文系统不那么兼容 采用GARbro封包看(封包当前这个md文件)
三个压缩选项都不选择
XP3引擎逆向探究插图12

就能看到尾部的File``info``segm``adlr结构了

既然有压缩 那么很容易想到文件是否存在校验和
进行实验对比
建立两个md: 第一个写CRC or not 第二个写 CRC or not!
对比
XP3引擎逆向探究插图13

已经能看出一些迹象了
比如第一个文件内容长度 10=0A 第二个11=0B
再换成长度一样的 把第二个文件内容换为 CRC or nos

XP3引擎逆向探究插图14
然而实验发现即使更改最后DWORD也不影响提取 所以应该是不存在校验解包过程的

接下来我们更改文件名不改文件内容实验
XP3引擎逆向探究插图15

更改文件路径(多层文件夹)实验:
我将test1放入multifolder1-multifolder2 文件路径下
选择保持目录结构
XP3引擎逆向探究插图16

可以发现路径变化会影响某些结构值的变化
XP3引擎逆向探究插图17

这两个值存储的就是路径的长度
前面文件内容后面不同的 98 8C 可能是后面文件的长度?

总结一下 目前为止 初步分析出了:

  1. XP3对多层文件夹和路径都有对应处理

  2. 存在校验和 但是更改不会影响提取

  3. xp3文件的路径部分,即文件结构和目录信息是通过尾部index中的info结构来记录的

选择加密方法前后对比
XP3引擎逆向探究插图18

只对内容进行了加密 而且采用的都是简单xor...

下面结合已有的分析来看看GARbro对于xp3文件结构的处理

定位到ArcXP3.cs467行的Create函数
由函数名以及下面的功能可以知道这应该就是XP3的打包函数

XP3引擎逆向探究插图19

下面取出了结构

不压缩不加密的对应:
XP3引擎逆向探究插图20

下面我们加密看看变化
XP3引擎逆向探究插图21

但实际压缩测试却并没有对应上?
压缩与不压缩:
XP3引擎逆向探究插图22

不同文件的压缩:
能发现一个类似标记的共有点
XP3引擎逆向探究插图23

还不是很明了 继续看源码

分析从105行开始的TryOpen函数
从151行开始的一个大循环
代码都很清晰 注意一个点 像ReadInt32这种读了4Bytes后文件指针是往后移了的

while (-1 != header.PeekChar())
{
    uint entry_signature = header.ReadUInt32();
    long entry_size = header.ReadInt64();
    if (entry_size < 0)
        return null;
    dir_offset += 12 + entry_size;
    if (0x656C6946 == entry_signature) // File
    {
        var entry = new Xp3Entry();
        while (entry_size > 0)
        {
            uint section = header.ReadUInt32();
            long section_size = header.ReadInt64();
            entry_size -= 12;
            if (section_size > entry_size)
            {
                if (section != 0x6f666e69) // info
                    break;
                section_size = entry_size;
            }
            entry_size -= section_size;
            long next_section_pos = header.BaseStream.Position + section_size;
            switch (section)
            {
            case 0x6f666e69: // "info"
                if (entry.Size != 0 || !string.IsNullOrEmpty (entry.Name))
                {
                    goto NextEntry; // ambiguous entry, ignore
                }
                entry.IsEncrypted = 0 != header.ReadUInt32();
                long file_size = header.ReadInt64();
                long packed_size = header.ReadInt64();
                if (file_size >= uint.MaxValue || packed_size > uint.MaxValue || packed_size > file.MaxOffset)
                {
                    goto NextEntry;
                }
                entry.IsPacked     = file_size != packed_size;
                entry.Size         = (uint)packed_size;
                entry.UnpackedSize = (uint)file_size;

                if (entry.IsEncrypted || ForceEncryptionQuery)
                    entry.Cipher = crypt_algorithm.Value;
                else
                    entry.Cipher = NoCryptAlgorithm;

                var name = entry.Cipher.ReadName (header);
                if (null == name)
                {
                    goto NextEntry;
                }
                if (entry.Cipher.ObfuscatedIndex && ObfuscatedPathRe.IsMatch (name))
                {
                    goto NextEntry;
                }
                if (filename_map.Count > 0)
                    name = filename_map.Get (entry.Hash, name);
                if (name.Length > 0x100)
                {
                    goto NextEntry;
                }
                entry.Name = name;
                entry.IsEncrypted = !(entry.Cipher is NoCrypt)
                    && !(entry.Cipher.StartupTjsNotEncrypted && "startup.tjs" == name);
                break;
            case 0x6d676573: // "segm"
                int segment_count = (int)(section_size / 0x1c);
                if (segment_count > 0)
                {
                    for (int i = 0; i < segment_count; ++i)
                    {
                        bool compressed  = 0 != header.ReadInt32();
                        long segment_offset = base_offset+header.ReadInt64();
                        long segment_size   = header.ReadInt64();
                        long segment_packed_size = header.ReadInt64();
                        if (segment_offset > file.MaxOffset || segment_packed_size > file.MaxOffset)
                        {
                            goto NextEntry;
                        }
                        var segment = new Xp3Segment {
                            IsCompressed = compressed,
                            Offset       = segment_offset,
                            Size         = (uint)segment_size,
                            PackedSize   = (uint)segment_packed_size
                        };
                        entry.Segments.Add (segment);
                    }
                    entry.Offset = entry.Segments.First().Offset;
                }
                break;
            case 0x726c6461: // "adlr"
                if (4 == section_size)
                    entry.Hash = header.ReadUInt32();
                break;

            default: // unknown section
                break;
            }
            header.BaseStream.Position = next_section_pos;
        }

    ...
    }
    ...
}

对照010分析
可以看到File后面的70 就是info开始的整个结构的size
XP3引擎逆向探究插图24

后面进行的就是各个section的switch check 还允许自定义section
当然 GARbro的代码跟实际krkr引擎执行的代码肯定还是有区别 先大致分析后面再调试对比

节区之间的关系已经清楚了 剩下的就是XP3 header是怎么索引到后面结构的?

进行多层次的xp3打包

层次结构:
-folder1
-folder2
-1.jpg
-2.png
-3.rtf
-folder3
-1.zip
-2.php
-22.rar

为了方便对比 这里再创建一个相同目录结构但是文件内容为空的进行对比

对比发现其实是将文件的所有内容都移到了开头
XP3引擎逆向探究插图25

现在还剩的就是两个中间部分的数据值:
XP3header-> ? -> Data... -> ? -> Index&Section

对比一下可以发现这个值是文件内容过后的区域(index&ampsection)的offset
XP3引擎逆向探究插图26

XP3引擎逆向探究插图27

这下第一个间隙就清楚了: 一个int64 存放后续偏移地址

接下来再来看 XP3文件索引区域
会发现并没有对齐 结合前面压缩时发现有一个字节位 的0->1 猜测第一个字节就是标记是否压缩
那么接下来这个就对应的后面索引结构的大小(前面已经分析过了)
XP3引擎逆向探究插图28

那对于压缩的情况呢?
可以发现很有趣的点
XP3引擎逆向探究插图29

这就全部联系起来了 然后后面紧跟的是标识头
对比压缩加密发现标识头并没有变化
XP3引擎逆向探究插图30
唯一区别在于加密的话是padding对齐了的
尝试换版本1压缩
XP3引擎逆向探究插图31

结尾的DWORD不一样 猜测可能是key/version ?

至此 XP3文件的结构已经大致明了了

接下来就是研究XP3封包中的压缩以及加密技术

先再来回顾一下索引的结构:

这里直接借图了(彩图更好看)
XP3引擎逆向探究插图32

有四个字段跟大小有关:

  • entry.UnpackedSize

  • entry.Size

  • segment.Size

  • segment.PackedSize

但这里bandizip压缩的和GARbro的对不上....
先记录下大致思路:
GARbro压缩与bandizip gz deflate最大档位压缩对比发现重合
比较区别 发现添加了adlr 校验和

好吧发现原因了 不知道为什么GARbro将index整个一起压缩了
根据前面的结构提取出对应压缩的数据
XP3引擎逆向探究插图33

python解压缩(zlib)得到
XP3引擎逆向探究插图34
然后替换回010
发现能吻合
XP3引擎逆向探究插图35

这里再解决这个部分最后一个疑问:
先加密后压缩还是先压缩后加密

理论分析:
我们看一个未加密的可视化
XP3引擎逆向探究插图36

再看加密的:
XP3引擎逆向探究插图37

可以发现加密后数据较随机 而压缩是越规律压缩效果越好
所以理论上 先压缩后加密更好
那krkr呢?
我们做一个实验
建一个256个FF的文件 然后GARbro勾选压缩和加密 再分步骤对比
这里对比可以用计算Adle32校验和的方式

实验发现竟然是先加密再压缩...
然而压缩效果还很好...
从逆向角度能分析出一个点:
加密采用的是单字节的加密方式 - 大概率为简单异或
只有这样才不会改变文件内容的数据规律...

XP3引擎再逆向

接下来就正式进入逆向部分了: 对krkr引擎进行分析

某些xp3引擎是会调用plugins目录下的.tpm文件

tpm后缀实则是dll
两个导出函数
XP3引擎逆向探究插图38

IDA分析:
V2Link
XP3引擎逆向探究插图39

XP3引擎逆向探究插图40

就是

v3[result] ^= 0xCD^(*(_BYTE *)(a1 + 20))

官方源码:
XP3引擎逆向探究插图41

可以发现厂家在原基础上作了简单的key值修改

ida动调跟一根这个tpm

调用:
XP3引擎逆向探究插图42

XP3引擎逆向探究插图43

在引擎的sub_5ECD70也能找到对xp3结构的识别

_DWORD *__fastcall sub_5ECD70(int a1, _DWORD *a2, char a3, int a4)
{
  char v4; // bl
  _DWORD *v5; // eax
  unsigned int v6; // edi
  _DWORD *v7; // eax
  _DWORD *v8; // esi
  int v9; // eax
  _DWORD *result; // eax
  int v11; // eax
  _DWORD *v12; // [esp-10h] [ebp-4004Ch]
  int v13; // [esp-Ch] [ebp-40048h] BYREF
  char v14[262144]; // [esp+0h] [ebp-4003Ch] BYREF
  char Buf2[15]; // [esp+40000h] [ebp-3Ch] BYREF
  char v16; // [esp+4000Fh] [ebp-2Dh]
  _DWORD *v17; // [esp+40010h] [ebp-2Ch]
  int (__cdecl ***v18)(_DWORD, _DWORD, _DWORD, _DWORD); // [esp+40014h] [ebp-28h]
  _EXCEPTION_REGISTRATION_RECORD *v19; // [esp+40018h] [ebp-24h]
  void *v20; // [esp+4001Ch] [ebp-20h]
  void *v21; // [esp+40020h] [ebp-1Ch]
  int *v22; // [esp+40024h] [ebp-18h]
  __int16 v23; // [esp+40028h] [ebp-14h]
  int v24; // [esp+40034h] [ebp-8h]

  v16 = a3;
  v17 = a2;
  v18 = (int (__cdecl ***)(_DWORD, _DWORD, _DWORD, _DWORD))a1;
  v21 = &unk_6ED0A8;
  v22 = &ampv13;
  v20 = &_ExceptionHandler;
  v19 = NtCurrentTeb()->NtTib.ExceptionList;
  v24 = 1;
  v23 = 8;
  sub_438664((int (__cdecl ***)(_DWORD, _DWORD, _DWORD, _DWORD))a1, 0i64);
  if ( byte_6ED095 )
  {
    byte_6ED095 = 0;
    memcpy(&unk_7B2C9C, &unk_6ED088, 8u);
    memcpy(&unk_7B2CA4, &unk_6ED091, 3u);
  }
  Buf2[0] = 0;
  sub_4386A4((int)v18, (int)Buf2, 11);
  if ( Buf2[0] == 'M' && Buf2[1] == 'Z' )
  {
    v4 = 0;
    v5 = v17;
    *v17 = 16;
    v5[1] = 0;
    sub_438664(v18, 16i64);
    while ( 1 )
    {
      v7 = (_DWORD *)((int (__cdecl *)(int (__cdecl ***)(_DWORD, _DWORD, _DWORD, _DWORD), char *, int))(*v18)[1])(
                       v18,
                       v14,
                       0x40000);
      v8 = v7;
      if ( !v7 )
        break;
      v6 = 0;
      v23 = 8;
      while ( 1 )
      {
        v7 = (_DWORD *)memcmp(&unk_7B2C9C, &v14[v6], 0xBu);
        if ( !v7 )
          break;
        v6 += 16;
        if ( (unsigned int)v8 <= v6 )
          goto LABEL_10;
      }
      v7 = (_DWORD *)v6;
      v4 = 1;
      *(_QWORD *)v17 += v6;
LABEL_10:
      if ( v4 )
        break;
      *(_QWORD *)v17 += 0x40000i64;
    }
    if ( v4 )
      goto LABEL_32;
    if ( v16 )
    {
      if ( dword_7C1FF8 )
        v9 = dword_7C1FF8;
      else
        v9 = dword_7C1FF4;
      v7 = (_DWORD *)sub_669CA0(v9, &a4);
      goto LABEL_32;
    }
    --v24;
    if ( a4 )
      sub_408920((_DWORD *)a4);
    v23 = 8;
    result = 0;
  }
  else
  {
    if ( !memcmp(&unk_7B2C9C, Buf2, 0xBu) )     // XP3
    {
      v7 = v17;
      *v17 = 0;
      v7[1] = 0;
LABEL_32:
      LOBYTE(v7) = 1;
      v12 = v7;
      --v24;
      if ( a4 )
        sub_408920((_DWORD *)a4);
      return v12;
    }
    if ( v16 )
    {
      if ( dword_7C2004 )
        v11 = dword_7C2004;
      else
        v11 = dword_7C2000;
      sub_669CA0(v11, &a4);
    }
    --v24;
    if ( a4 )
      sub_408920((_DWORD *)a4);
    result = 0;
  }
  return result;
}

基本逻辑跟GARbro是一样的 分MZ和普通xp3文件进行不同的解析

官方解密调用:

tjs_uint TJS_INTF_METHOD tTVPXP3ArchiveStream::Read(void *buffer, tjs_uint read_size)
{
	EnsureSegment();

	tjs_uint write_size = 0;
	while(read_size)
	{
		while(SegmentRemain == 0)
		{
			// must go next segment
			if(!OpenNextSegment()) // open next segment
				return write_size; // could not read more
		}

		tjs_uint one_size =
			read_size > SegmentRemain ? (tjs_uint)SegmentRemain : read_size;

		if(CurSegment->IsCompressed)
		{
			// compressed segment; read from uncompressed data in memory
			memcpy((tjs_uint8*)buffer + write_size,
				SegmentData->GetData() + (tjs_uint)SegmentPos, one_size);
		}
		else
		{
			// read directly from stream
			Stream->ReadBuffer((tjs_uint8*)buffer + write_size, one_size);
		}

		// execute filter (for encryption method)
		if(TVPXP3ArchiveExtractionFilter)
		{
			tTVPXP3ExtractionFilterInfo info(CurPos, (tjs_uint8*)buffer + write_size,
				one_size, Owner->GetFileHash(StorageIndex));
			TVPXP3ArchiveExtractionFilter
				( (tTVPXP3ExtractionFilterInfo*) &info );
		}

		// adjust members
		SegmentPos += one_size;
		CurPos += one_size;
		SegmentRemain -= one_size;
		read_size -= one_size;
		write_size += one_size;
	}

	return write_size;
}

采用循环读取数据 每次读取一个块来进行xor加密
可以发现这种如此简单的加解密完全不需要另起一个dll 可以隐藏在引擎代码流中

现在选取另一款xp3引擎进行逆向探究
这款引擎就没有解密dll了

可以静态翻代码翻到一些类似加解密的
XP3引擎逆向探究插图44

还有这个设置的函数表
XP3引擎逆向探究插图45

其中好多类似的函数
XP3引擎逆向探究插图46

x32dbg下条件断点来看
XP3引擎逆向探究插图47
XP3引擎逆向探究插图48
XP3引擎逆向探究插图49

在CreateFileW断到data.xp3时停下 返回用户领空

XP3引擎逆向探究插图50

注意到是循环读的 (两次CreateFileW之间就可能有解密?)

文件读取: d:\reverse engineering\reverse实战\xp3逆向\不败世界与终焉之花\data.xp3
线程 90F8 已启动,入口: sweet_01kareseka.0063AAB8
追踪 41 步后成功结束!
设置文件指针: 句柄:2A8 偏移量:00 基准:0
读取文件: 句柄:2A8 缓冲:15FAEC 字节数:B
设置文件指针: 句柄:2A8 偏移量:0B 基准:0
读取文件: 句柄:2A8 缓冲:19FAF0 字节数:8
设置文件指针: 句柄:2A8 偏移量:017 基准:0
读取文件: 句柄:2A8 缓冲:19FB2F 字节数:1
读取文件: 句柄:2A8 缓冲:19FADC 字节数:8
读取文件: 句柄:2A8 缓冲:2FE3E00 字节数:0
读取文件: 句柄:2A8 缓冲:19FAF0 字节数:8
设置文件指针: 句柄:2A8 偏移量:02FBF9C11 基准:0
读取文件: 句柄:2A8 缓冲:19FB2F 字节数:1
读取文件: 句柄:2A8 缓冲:19FAB4 字节数:8
读取文件: 句柄:2A8 缓冲:19FAB0 字节数:8
读取文件: 句柄:2A8 缓冲:3100048 字节数:1D9FF
文件读取: d:\reverse engineering\reverse实战\xp3逆向\不败世界与终焉之花\data.xp3
设置文件指针: 句柄:2A8 偏移量:00 基准:0
读取文件: 句柄:2A8 缓冲:19F5F8 字节数:B
设置文件指针: 句柄:2A8 偏移量:0B 基准:0
读取文件: 句柄:2A8 缓冲:19F614 字节数:8
设置文件指针: 句柄:2A8 偏移量:017 基准:0
读取文件: 句柄:2A8 缓冲:19F6EB 字节数:1
读取文件: 句柄:2A8 缓冲:19F614 字节数:8
读取文件: 句柄:2A8 缓冲:A00200 字节数:0
读取文件: 句柄:2A8 缓冲:19F614 字节数:8
设置文件指针: 句柄:2A8 偏移量:02FBF9C11 基准:0
读取文件: 句柄:2A8 缓冲:19F6EB 字节数:1
读取文件: 句柄:2A8 缓冲:19F614 字节数:8
读取文件: 句柄:2A8 缓冲:19F614 字节数:8
读取文件: 句柄:2A8 缓冲:326A0A0 字节数:1D9FF

这两次竟然读取同样的数据???

注意到特征都类似 找对应内存看值
对应读取的
第一次 B: 58 50 33 0D 0A 20 0A 1A 8B 67 01 17
第二次 8: 00 00 00 00 00 00 00 01

把另外两个的条件断点都设置中断 对比内存分析
发现并不一样 文件指针是跟着移动了的
第二次读取的B
XP3引擎逆向探究插图51
XP3引擎逆向探究插图52

找到发生变化的分界点 IDA看看找到这里

int __fastcall sub_4DBB30(_BYTE *a1, int a2)
{
  int result; // eax
  int v3; // esi
  char v4; // bl
  int v5; // eax

  result = dword_7B8CA0;
  v3 = 0;
  if ( a2 > 0 )
  {
    v4 = byte_7B8CA4;
    do
    {
      v4 ^= a1[v3];
      v5 = result + 1;
      byte_7B8CA7[v5] ^= v4;
      result = v5 & 0xFFF;
      ++v3;
      dword_7B8CA0 = result;
    }
    while ( v3 < a2 );
    byte_7B8CA4 = v4;
  }
  dword_7B8CA0 = ((_WORD)result + (*a1 & 1)) & 0xFFF;
  return result;
}

发现跟前面的.tpm的算法十分类似 调试发现这里就是一个解密点
注意到传参时
sub_4DBB30(&a3, 4);
所以这是部分加解密 而不是整体都加解密的

总结

XP3引擎整体封包流程: 先加密再压缩
加密算法: 官方模板是利用(校验和)末字节逐比特异或 各厂商可以自行修改xor的key值
压缩算法: 与bandizip的gz deflate 3档位以及zlib库的level=9相当
XP3索引部分结构:
XP3引擎逆向探究插图53
对应GARbro源码解析:
XP3引擎逆向探究插图54

逆向解包流程: XP3头找index偏移 -> 对应各个结构 -> 提取data部分 -> zlib解压缩 -> 厂商加密算法逆向解密 -> 原数据文件

至此 XP3引擎逆向结束

参考文章

http://keepcreating.g2.xrea.com/DojinDOC/HowToCode.html
https://github.com/krkrz/krkr2/blob/master/kirikiri2/branches/2.32stable/kirikiri2/src/plugins/win32/


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

webpack打包站点,js文件名批量获取思路
加密对抗靶场enctypt——labs通关
【论文速读】| 注意力是实现基于大语言模型的代码漏洞定位的关键
蓝队技术——Sysmon识别检测宏病毒
内网渗透学习|powershell上线cs
LLM attack中的API调用安全问题及靶场实践

发布评论