吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 19384|回复: 143
收起左侧

[原创] Galgame汉化中的逆向(四)_IDA静态分析psv游戏

    [复制链接]
小木曾雪菜 发表于 2020-11-27 18:18
本帖最后由 小木曾雪菜 于 2020-11-27 18:19 编辑

Galgame汉化中的逆向(四) IDA静态分析psv游戏

本帖在论坛和我的博客同时发布
上期链接: Galgame汉化中的逆向(五):Switch平台下的Unity后端il2cpp分析

0x0 前言

前三篇都在谈pc汉化中的基本方法,这篇来说说主机汉化的操作。由于主机的特殊性,用户权限比较低,一般很难做到动态调试(大多是只有比较完善的模拟器才能动态调试),甚至连编写自制软件都不能用类似于gdb的调试,往往都是插入log来看输出。而且因为主机游戏往往没有加壳和混淆什么的(本身主机系统加密防破解就很变态了,游戏就没必要再搞防护了),因此一般都是进行静态分析。静态分析最重要的是定位,通常是通过特征字节来定位(如游戏opcode,文件magic等),或者根据代码量、大的switch结构、相关字符串(别抱太大希望,字符串很难找到refer)。这篇来谈谈如何用ida静态分析主机游戏,在魔改算法不容易看出来的情况下,如何将ida的伪代码翻译成可执行的代码。本次以psv游戏arm汇编为例。

BG_NAMING

0x1 文件结构分析

此游戏有一堆*.ARC *.BIN 文件,直接用GARBRO打开,可以看到文件名但是无法打开每个文件,观察可知是用了叫做gss的游戏引擎。简单分析可知 *.bin为索引文件。以SCRDATA.BIN SCRDATA.ARC为例,文件头如下所示。

psv_scrbin_scrarc

这里看着SCRDATA.BIN SCRDATA.ARC规律很明显,两者结合(根据SCRDATA.BIN 中的索引来打印SCRDATA.ARC的每项头信息)简单打印一下,可以得到如下的数据:

b'LSDARC V.100' 325

0 AUTOEXEC 0 0x0 0x1a6 0x800

1 DEBUG 0 0x800 0x143 0x800

2 INIT 1 NH 0x1000 0x1612 0x1000

3 PRG_BG 1 NH 0x2000 0x14c6 0x1000

4 PRG_EFFECT 1 NH 0x3000 0xc0d 0x1000

5 PRG_NAME 1 ND 0x4000 0x689 0x800

6 PRG_ROUTE 1 NH 0x4800 0x2e18 0x2800

7 PRG_STAND 1 NH 0x7000 0x31e0 0x2800

8 RELOAD 1 ND 0x9800 0x349 0x800

9 SELECT2 0 0xa000 0x102 0x800

10 COM001 1 WD 0xa800 0x3f1 0x800

11 COM002 1 NH 0xb000 0x130d 0x1800

12 COM003 0 0xc800 0xa23 0x1000

13 COM004 1 NH 0xd800 0x3e80 0x3800

14 COM005 1 NH 0x11000 0x1fb0 0x2000

15 COM006 1 NH 0x13000 0x4973 0x4000

16 COM007 1 NH 0x17000 0x1003 0x1000

17 COM008 1 NH 0x18000 0x3e5a 0x3800

18 COM009 1 NH 0x1b800 0x194f 0x1800

19 COM010 1 NH 0x1d000 0x2ad2 0x2800

...

根据对照很容易分析出SCRDATA.BIN数据结构,文件magic为12字节,之后4字节为index数量。然后就是index_entry了。

magic[12] //"LSDARC V.100"

count 4

index[]

|IsPacked 4 //导入不用打包,IsPacked调成0即可

|Offset 4

|UnpackedSize 4

|Size 4 //0x800的倍数

|Name 00

同理可得SCRDATA.ARC的数据结构。

//unpacked

53 43 52 20 32 2E 30 30  //SCR 2.00

//packed

fileblock[]

|magic[4] //4C 53 44 1A, LSD\x1A

|enc_method  1 //enc_method, B W S

|pack_method 1  //pack_method, D R H W

|unpacked_size 4 //may be different than the value in BIN index, often 4 byte asign

0x2 解包算法定位

上面文件数据结构分析,我们发现有是否压缩的flag和压缩后和解压的体积,再加上来查看*.ARC文件,内部比较紧凑,因此判定是压缩或者加密了。我们观察得到pack_method字段有"D,R,H,W"这四个很醒目的标识符,这里可以作为突破口。必须用ida6.8安装VitaLoaderIDA载入eboot.bin,  search immediate搜索"H"等标志位即可找到。

同时我们注意到GARBRO源码中已经有了框架分析了一部分,但是最难的一个函数UnpackH仍是throw new NotImplementedException();我们需要自己分析一下补全。

unpack_flag

同时,我们在定位处上下翻翻,还能看到huffmanrunleng的字符串,因此压缩算法很可能是基于这两种算法的魔改。

huffman_decode

0x3 解包算法伪代码分析

下面以UnpackH函数为例(其他函数UnpackD, UnpackW等也同理),来谈谈如何分析。伪代码层面上的分析一般有下列方法:

  • 猜每个变量的含义并改名(字符串缓冲区,当前位置指针等比较容易识别)

  • 修改到合适数据类型,

    有些是数组和结构的,添加结构体看着舒服一些。

    还有一些情况,地址显示的是负号,我们需要手动invert signed

  • 合并相同变量(注意不能乱合并,尤其是临时赋值的)

用上面方法初步分析整理,可得到如下的伪代码。


// UnpackH

int __fastcall sub_81043752(_BYTE *buf_packed, _BYTE *output, int unpacked_size)

{

  _BYTE *buf; // r10@1

  _BYTE *cur_addr; // r6@1

  int v7; // t1@1

  int v8; // r2@1

  int i1; // r8@1

  int v10; // r1@2

  signed int cur_char1; // r5@2

  int v12; // r0@4

  int v13; // r7@7

  int v14; // lr@7

  unsigned int v15; // r7@8

  int v16; // r10@9

  int v17; // r0@10

  int v18; // r6@6

  signed int v19; // r7@6

  int i2; // r5@12

  _BYTE *buf3_addr; // lr@13

  int v22; // r4@15

  int v23; // r0@18

  int v24; // r12@19

  __int64 v25; // r0@19

  int cur_char2; // r9@19

  unsigned int v27; // r5@19

  int v28; // lr@20

  int v29; // lr@23

  char v30; // lr@28

  _BYTE *v32; // [sp+0h] [bp-50h]@2

  signed __int64 v33; // [sp+8h] [bp-48h]@18

  int next; // [sp+10h] [bp-40h]@1

  int v35; // [sp+14h] [bp-3Ch]@18

  _BYTE *cur_output; // [sp+1Ch] [bp-34h]@1

  int first_char; // [sp+28h] [bp-28h]@1

  buf = (_BYTE *)&unk_811C6780;

  cur_output = output;

  memset(0x811C6780, 0, 0x2000);                // buf1

  memset(0x811C8784, 0, 0x800);                 // buf2

  memset(0x811C8F84, 0, 0x800);                 // buf3

  v7 = *buf_packed;

  cur_addr = buf_packed + 2;

  v8 = 0;

  first_char = v7;

  i1 = 0;

  next = 2;

  do

  {

    v10 = next;

    cur_char1 = *cur_addr;

    v32 = cur_addr + 1;

    ++next;

    if ( *cur_addr )

    {

      if ( cur_char1 >= 8 )

      {

        v13 = cur_addr[1];

        v14 = cur_addr[2];

        if ( cur_char1 >= 0xD )

        {

          v16 = (unsigned __int8)v13 + (v14 << 8);

          if ( cur_char1 < 0x10 )

          {

            *(_BYTE *)(8 * i1 + 0x811C8F88) = cur_char1;// buf3 + 4

            *(_DWORD *)(8 * i1 + 0x811C8F84) = v16;// buf3

            *(_BYTE *)(8 * i1 + 0x811C8F88) = v8;// bu3 + 4

            v32 = cur_addr + 3;

            next = v10 + 3;

            i1 = (unsigned __int8)(i1 + 1);

          }

          else

          {

            v17 = cur_addr[3];

            *(_BYTE *)(8 * i1 + 0x811C8F88) = cur_char1;// buf3+4

            v32 = cur_addr + 4;

            *(_DWORD *)(8 * i1 + 0x811C8F84) = v16 + (v17 << 16);// buf3

            *(_BYTE *)(8 * i1 + 0x811C8F89) = v8;// buf3+5

            next = v10 + 4;

            i1 = (unsigned __int8)(i1 + 1);

          }

        }

        else

        {

          v15 = v13 & 0xFFFF00FF | ((unsigned __int8)v14 << 8);

          buf[2 * (unsigned __int16)v15] = v8;

          buf[2 * (unsigned __int16)v15 + 1] = cur_char1;

          v32 = cur_addr + 3;

          next = v10 + 3;

        }

      }

      else

      {

        v32 = cur_addr + 2;

        next = v10 + 2;

        v12 = cur_addr[1];

        buf[2 * v12] = v8;

        buf[2 * v12 + 1] = cur_char1;

      }

    }

    buf = (_BYTE *)&unk_811C6780;

    cur_addr = v32;

    ++v8;

  }

  while ( v8 != 0x100 );

  v18 = 0;

  v19 = 13;

  do

  {

    i2 = 0;

    *(_BYTE *)(v19 + 0x811C9784) = v18;         // buf4, buf2+0x1000

    if ( i1 )

    {

      buf3_addr = (_BYTE *)&dword_811C8F84;

      do

      {

        if ( buf3_addr[4] == v19 )

        {

          v22 = *((_DWORD *)buf3_addr + 1);

          *(_DWORD *)(8 * v18 + 0x811C8784) = *(_DWORD *)buf3_addr;// buf2

          *(_DWORD *)(8 * v18 + 0x811C8788) = v22;// buf2+4

          v18 = (unsigned __int8)(v18 + 1);

        }

        ++i2;

        buf3_addr += 8;

      }

      while ( i2 != i1 );

    }

    v19 = (unsigned __int16)(v19 + 1);

  }

  while ( v19 != 24 );

  unk_811C979C = v18;

  v35 = 0;

  v33 = 0i64;

  qword_811C97A8 = 0i64;

  v23 = unpacked_size;

  if ( !unpacked_size )

    return v35;

  while ( 1 )

  {

    v24 = next + ((unsigned int)(v33 & 0x1F) >> 3) + ((v33 >> 3) & 0xFFFFFFFC);

    LOBYTE(v23) = buf_packed[v24 + 3];

    v25 = (signed int)(((buf_packed[v24] | (buf_packed[v24 + 1] << 8)) & 0xFF00FFFF | (buf_packed[v24 + 2] << 16)) & 0xFFFFFF | (v23 << 24));

    cur_char2 = first_char;

    v27 = sub_8105E56C(v25, HIDWORD(v25), (v33 & 0x1F) - (v33 & 0x18));

    if ( first_char != 0xD )

    {

      while ( 1 )

      {

        v28 = v27 & *(_DWORD *)(8 * cur_char2 + 0x8107F828);// const1

        dword_811C8780 = 2 * v28 + 0x811C6780;  // buf1

        if ( cur_char2 == *(_BYTE *)(2 * v28 + 0x811C6781) )// buf1+1

          break;

        cur_char2 = (unsigned __int16)(cur_char2 + 1);

        if ( (unsigned __int16)cur_char2 == 0xD )

          goto LABEL_22;

      }

      v30 = *(_BYTE *)(2 * v28 + 0x811C6780);   // buf1

      goto LABEL_29;

    }

LABEL_22:

    if ( cur_char2 == 0x18 )

      break;

    while ( 1 )

    {

      v29 = *(_BYTE *)(cur_char2 + 0x811C9784); // buf4

      if ( v29 != *(_BYTE *)(cur_char2 + 0x811C9785) )// buf4+1

        break;

LABEL_26:

      if ( ++cur_char2 == 0x18 )

        goto LABEL_33;

    }

    while ( *(_DWORD *)(8 * v29 + 0x811C8784) != (v27 & *(_DWORD *)(8 * cur_char2 + 0x8107F828)) )// buf2, const1

    {

      v29 = (unsigned __int16)(v29 + 1);

      if ( (unsigned __int16)v29 == *(_BYTE *)(cur_char2 + 0x811C9785) )// buf4+1

        goto LABEL_26;

    }

    v30 = *(_BYTE *)(8 * v29 + 0x811C8789);     // buf2+5

LABEL_29:

    *cur_output = v30;

    qword_811C97A8 = v33 + (unsigned __int16)cur_char2;

    v23 = v35 + 1;

    ++cur_output;

    v33 += (unsigned __int16)cur_char2;

    v35 = v23;

    if ( v23 == unpacked_size )

      return v35;

  }

LABEL_33:

  sub_8104332C();                               // None

  return 0;

}

0x4 解包算法伪代码复原为可执行代码

下面,我们就需要将伪代码来转换成可执行代码了,由于GARBRO有了一部分解包代码,我们就在此基础上完善了,而且c#代码也和c很相似,就把ida的伪代码转换成c#代码。这个操作其实难度不是很大,但是需要非常细心,错一处最后结果都可能有问题,而且很不好调试。一般情况下看着解包后的数据有规律且可读,通常情况下就可以认为是正确了,但是遇到特殊情况需要用金手指插件去dump内存,然后去逐字节比对。至于为什么不直接dump解包,因为dump过程非常繁琐,游戏不是把所有资源都加载的,主机上的hook非常麻烦。因此还是很有必要来分析静态解包方法的。

将偏移地址转换为数组

以下为伪代码和c#代码,如将0x811c6780这个地址命名为buf1,所有这个基地址的偏移也可用数组表示。

*(_DWORD *)(8 * v29 + 0x811C8784) != (v27 & *(_DWORD *)(8 * cur_char2 + 0x8107F828)) 转换为BitConverter.ToUInt32(buf, (int)off2 + 8 * v29) != (v27 & dword_8107F828[2 * cur_char2])

注意指针加减*((dword *)addr + 1), 其实是addr+4的地址里的dword值。

同时要特别注意高级语言的数据类型,数据类型不对可能会把后面数组里的内容覆盖了。


  buf = (_BYTE *)&unk_811C6780;

  cur_output = output;

  memset(0x811C6780, 0, 0x2000);                // buf1

  memset(0x811C8784, 0, 0x800);                 // buf2

  memset(0x811C8F84, 0, 0x800);                 // buf3

  v7 = *buf_packed;

  cur_addr = buf_packed + 2;

  v8 = 0;

  first_char = v7;

  i1 = 0;

  next = 2;

var buf = new Byte[0x10000];

Array.Clear(buf, 0, buf.Length);

const uint off2 = 0x2004;

const uint off3 = 0x2804;

const uint off4 = 0x3004; //0x811C9784

uint cur_addr = 2, pre_pos, next_pos = 2, cur_output_addr = 0;

byte outchar, i1 = 0, i2, v29, first_char = buf_packed[0];

int idx1 = 0, v28=0;

dump静态全局变量数组

我们注意到伪代码里有调用dword_8107F828,去ida里看看相应的地址,把截至到0的数据dump出来作为全局变量到c#里。

ida_array_8107F748


dword_8107F828:

00000000 00000000 00000001 00000000 00000003 00000000 00000007 00000000

0000000F 00000000 0000001F 00000000 0000003F 00000000 0000007F 00000000

000000FF 00000000 000001FF 00000000 000003FF 00000000 000007FF 00000000

00000FFF 00000000 00001FFF 00000000 00003FFF 00000000 00007FFF 00000000

0000FFFF 00000000 0001FFFF 00000000 0003FFFF 00000000 0007FFFF 00000000

000FFFFF 00000000 001FFFFF 00000000 003FFFFF 00000000 007FFFFF 00000000

00FFFFFF 00000000 00000000 00000000 00000000 00000000 00000007 0000000F

static readonly uint[] dword_8107F828 = { 

    0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000003, 0x00000000, 0x00000007, 0x00000000,

    0x0000000F, 0x00000000, 0x0000001F, 0x00000000, 0x0000003F, 0x00000000, 0x0000007F, 0x00000000,

    0x000000FF, 0x00000000, 0x000001FF, 0x00000000, 0x000003FF, 0x00000000, 0x000007FF, 0x00000000,

    0x00000FFF, 0x00000000, 0x00001FFF, 0x00000000, 0x00003FFF, 0x00000000, 0x00007FFF, 0x00000000,

    0x0000FFFF, 0x00000000, 0x0001FFFF, 0x00000000, 0x0003FFFF, 0x00000000, 0x0007FFFF, 0x00000000,

    0x000FFFFF, 0x00000000, 0x001FFFFF, 0x00000000, 0x003FFFFF, 0x00000000, 0x007FFFFF, 0x00000000,

    0x00FFFFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000007, 0x0000000F,

    0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF,

    0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, 0x0001FFFF, 0x0003FFFF, 0x000FFFFF, 0x00000000,

    0x06050403, 0x0A090807, 0x0E0D0C0B, 0x00000000, 0x00000001, 0x00000002, 0x00000004, 0x00000008,

    0x00000010, 0x00000020, 0x00000040, 0x00000080, 0x00000100, 0x00000200, 0x00000400, 0x00000800,

    0x00001000, 0x00002000, 0x00004000, 0x00000000, 0xFFFF0001, 0x00000000, 0x00000000, 0x00000001,

    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000001, 0x00000001,

        };

goto相关的转换

由于c#的goto不能跳入其他循环,遇到这种情况我们就需要把goto进入的代码都粘贴过来了。


LABEL_22:

    if ( cur_char2 == 0x18 )

      break;

    while ( 1 )

    {

      v29 = *(_BYTE *)(cur_char2 + 0x811C9784); // buf4

      if ( v29 != *(_BYTE *)(cur_char2 + 0x811C9785) )// buf4+1

        break;

LABEL_26:

      if ( ++cur_char2 == 0x18 )

        goto LABEL_33;

    }

    while ( *(_DWORD *)(8 * v29 + 0x811C8784) != (v27 & *(_DWORD *)(8 * cur_char2 + 0x8107F828)) )// buf2, const1

    {

      v29 = (unsigned __int16)(v29 + 1);

      if ( (unsigned __int16)v29 == *(_BYTE *)(cur_char2 + 0x811C9785) )// buf4+1

        goto LABEL_26;

    }

LABEL_26_2:

while (true)

{

    v29 = buf[off4 + cur_char2];

    if (v29 != buf[off4 + 1 + cur_char2])

        break;

    LABEL_26: //goto can not jump here

    if (++cur_char2 == 0x18)

    {

        return 0;

        //outchar = buf[off2 + 5 + 8 * v29]; //this seems a hack...

        //goto LABEL_29;                   

    }

}

while (BitConverter.ToUInt32(buf, (int)off2 + 8 * v29) !=

       (v27 & dword_8107F828[2 * cur_char2]))

{

    v29++;

    if (v29 == buf[off4 + 1 + cur_char2 ])

    {

        if (++cur_char2 == 0x18)

        {

            return 0;

        }

        goto LABEL_26_2;

    }

}

将所有的子函数也都按照上述方法复原

就是寻找所有的子函数调用,并将其逐个还原。有时候可能伪代码显示有点问题,要看看汇编。


v27 = sub_8105E56C(v25, HIDWORD(v25), (v33 & 0x1F) - (v33 & 0x18));

seg000:8105E56C sub_8105E56C                            ; CODE XREF: sub_81043752+22Ap

seg000:8105E56C                                         ; sub_8105E344+80p

seg000:8105E56C

seg000:8105E56C var_18          = -0x18

seg000:8105E56C var_10          = -0x10

seg000:8105E56C

seg000:8105E56C                 PUSH            {R4,R5,LR}

seg000:8105E56E                 SUB             SP, SP, #0xC

seg000:8105E570                 MOV             R5, #0x810604D0

seg000:8105E578                 LDR             R4, [R5] ; dword_810604D4

seg000:8105E57A                 STR             R4, [SP,#0x18+var_10]

seg000:8105E57C                 STRD.W          R2, R3, [SP]

seg000:8105E580                 LDR             R2, [SP,#0x18+var_18+4]

seg000:8105E582                 CBNZ            R2, loc_8105E5CE

seg000:8105E584                 LDR             R2, [SP,#0x18+var_18]

seg000:8105E586                 CMP             R2, #0x3F

seg000:8105E588                 BHI             loc_8105E5CE

seg000:8105E58A                 LDR             R2, [SP,#0x18+var_18]

seg000:8105E58C                 CBNZ            R2, loc_8105E590

seg000:8105E58E                 B               loc_8105E5D2

seg000:8105E590 ; ---------------------------------------------------------------------------

seg000:8105E590

seg000:8105E590 loc_8105E590                            ; CODE XREF: sub_8105E56C+20j

seg000:8105E590                 STRD.W          R0, R1, [SP]

seg000:8105E594                 CMP             R2, #0x1F

seg000:8105E596                 LDR             R3, [SP,#0x18+var_18]

seg000:8105E598                 LDR.W           R12, [SP,#0x18+var_18+4]

seg000:8105E59C                 BHI             loc_8105E5BA

seg000:8105E59E                 RSBS.W          R0, R2, #0x20

seg000:8105E5A2                 LSRS.W          R1, R3, R2

seg000:8105E5A6                 LSL.W           R0, R12, R0

seg000:8105E5AA                 LSR.W           R2, R12, R2

seg000:8105E5AE                 STR             R2, [SP,#0x18+var_18+4]

seg000:8105E5B0                 ORRS            R0, R1

seg000:8105E5B2                 STR             R0, [SP,#0x18+var_18]

seg000:8105E5B4                 LDRD.W          R0, R1, [SP]

seg000:8105E5B8                 B               loc_8105E5D2

seg000:8105E5BA ; ---------------------------------------------------------------------------

seg000:8105E5BA

seg000:8105E5BA loc_8105E5BA                            ; CODE XREF: sub_8105E56C+30j

seg000:8105E5BA                 MOVS            R0, #0

seg000:8105E5BC                 STR             R0, [SP,#0x18+var_18+4]

seg000:8105E5BE                 SUBS.W          R1, R2, #0x20

seg000:8105E5C2                 LSR.W           R0, R12, R1

seg000:8105E5C6                 STR             R0, [SP,#0x18+var_18]

seg000:8105E5C8                 LDRD.W          R0, R1, [SP]

seg000:8105E5CC                 B               loc_8105E5D2

seg000:8105E5CE ; ---------------------------------------------------------------------------

seg000:8105E5CE

seg000:8105E5CE loc_8105E5CE                            ; CODE XREF: sub_8105E56C+16j

seg000:8105E5CE                                         ; sub_8105E56C+1Cj

seg000:8105E5CE                 MOVS            R0, #0

seg000:8105E5D0                 MOVS            R1, #0

seg000:8105E5D2

seg000:8105E5D2 loc_8105E5D2                            ; CODE XREF: sub_8105E56C+22j

seg000:8105E5D2                                         ; sub_8105E56C+4Cj ...

seg000:8105E5D2                 LDR             R3, [SP,#0x18+var_10]

seg000:8105E5D4                 LDR             R2, [R5]

seg000:8105E5D6                 CMP             R2, R3

seg000:8105E5D8                 BNE             loc_8105E5DE

seg000:8105E5DA                 ADD             SP, SP, #0xC

seg000:8105E5DC                 POP             {R4,R5,PC}

seg000:8105E5DE ; ---------------------------------------------------------------------------

seg000:8105E5DE

seg000:8105E5DE loc_8105E5DE                            ; CODE XREF: sub_8105E56C+6Cj

seg000:8105E5DE                 BLX             __stack_chk_fail

seg000:8105E5DE ; End of function sub_8105E56C

uint v27 = sub_8105E56C((uint)v25, (uint)(v25 >> 32), (uint)((v33 & 0x1F) - (v33 & 0x18)));

uint sub_8105E56C(uint result, uint a2, uint a3)

{

    if (a3 > 63)

    {

        result = 0;

    }

    else if (a3 != 0)

    {

        if (a3 > 31)

            result = a2 >> (int)(a3 - 32);

        else

            result = (a2 << (int)(32 - a3)) | (result >> (int)a3);

        //insert the low a3 bit of the result into a2, then asign to result

    }

    return result;

}

提取同类项与整体结构优化

在运行结果正确的技术上,这时候可以进行整体的考虑了。即站在更高层来分析那些代码逻辑是相同的,把每部分相同功能的变量与代码提到外层,使得整体看着简洁。同时优化位运算等操作的可读性。此处UnpackH这个函数的整体还原代码如下:


int UnpackH(byte[] buf_packed, byte[] output, uint unpacked_size)

{

    var buf = new Byte[0x10000];

    Array.Clear(buf, 0, buf.Length);

    const uint off2 = 0x2004;

    const uint off3 = 0x2804;

    const uint off4 = 0x3004; //0x811C9784

    uint cur_addr = 2, pre_pos, next_pos = 2, cur_output_addr = 0;

    byte outchar, i1 = 0, i2, v29, first_char = buf_packed[0];

    int idx1 = 0, v28=0;

    do

    {

        pre_pos = next_pos;

        var cur_char1 = buf_packed[cur_addr];

        var next_addr = cur_addr + 1;

        next_pos++;

        if (cur_char1 != 0)

        {

            byte l1 = buf_packed[cur_addr + 1];

            byte h1 = buf_packed[cur_addr + 2];

            uint t1 = (ushort)(l1 + (h1 << 8));

            if (cur_char1 >= 8)

            {

                if (cur_char1 >= 0xD)

                {

                    uint d;

                    if (cur_char1 < 0x10) // 2 byte

                    {

                        d = t1;

                        next_addr = cur_addr + 3;

                        next_pos = pre_pos + 3;

                    }

                    else //3 byte

                    {

                        d = (uint)(t1 + (buf_packed[cur_addr + 3] << 16));

                        next_addr = cur_addr + 4;

                        next_pos = pre_pos + 4;

                    }

                    buf[off3 + 4 + 8 * i1] = cur_char1;

                    BitConverter.GetBytes((uint)(d)).CopyTo(buf, off3 + 8 * i1);

                    buf[off3 + 5 + 8 * i1] = (byte)idx1;

                    i1++;

                }

                else //2byte

                {

                    buf[2 * t1] = (byte)idx1;

                    buf[2 * t1 + 1] = cur_char1;

                    next_addr = cur_addr + 3;

                    next_pos = pre_pos + 3;

                }

            }

            else // 1byte

            {

                buf[2 * l1] = (byte)idx1;

                buf[2 * l1 + 1] = cur_char1;

                next_addr = cur_addr + 2;

                next_pos = pre_pos + 2;

            }

        }

        cur_addr = next_addr;

        ++idx1;

    } while (idx1 != 0x100);

    byte idx2 = 0;

    byte v19 = 0xD;

    do

    {

        i2 = 0;

        buf[off4 + v19] = (byte)idx2;

        if (i1 != 0)

        {

            int buf3_addr = 0x2804; //buf3_addr = (_BYTE *)&dword_811C8F84;

            do

            {

                if (buf[buf3_addr + 4] == v19)

                {

                    Array.Copy(buf, buf3_addr, buf, off2 + 8 * idx2, 4);

                    Array.Copy(buf, buf3_addr+4, buf, off2 + 4 + 8 * idx2, 4); 

                    idx2++;

                }

                ++i2;

                buf3_addr += 8;

            }

            while (i2 != i1);

        }

        v19++;

    } while (v19 != 0x18);

    buf[0x301c] = (byte)idx2; //unk_811C979C = idx2;

    Array.Clear(buf, 0x3028, 8);//qword_811C97A8 = 0i64;

    int v23 = (int)unpacked_size, v33=0, size_done = 0;

    while (true)

    {

        int v24 = (int)(next_pos + ((uint)(v33 & 0x1F) >> 3) + ((v33 >> 3) & 0xFFFFFFFC)); //this place, out of range

        ulong v25 = BitConverter.ToUInt32(buf_packed, v24);

        byte cur_char2 = first_char;

        uint v27 = sub_8105E56C((uint)v25, (uint)(v25 >> 32), (uint)((v33 & 0x1F) - (v33 & 0x18)));

        if (first_char != 0xD)

        {

            while (true)

            { 

                v28 = (int)(v27 & dword_8107F828[2 * cur_char2]);

                BitConverter.GetBytes((int)(2 * v28 + 0x811C6780)).CopyTo(buf, off2 - 4); //must pay attention to the type convert and length

                if (cur_char2 == buf[2 * v28 + 1])// buf1+1

                    break;

                cur_char2++;

                if (cur_char2 == 0xD)

                    goto LABEL_22;

            }

            outchar = buf[2 * v28];

            goto LABEL_29;

        }

        LABEL_22:

        if (cur_char2 == 0x18)

            break;

        LABEL_26_2:

        while (true)

        {

            v29 = buf[off4 + cur_char2];

            if (v29 != buf[off4 + 1 + cur_char2])

                break;

            LABEL_26: //goto can not jump here

            if (++cur_char2 == 0x18)

            {

                return 0;

                //outchar = buf[off2 + 5 + 8 * v29]; //this seems a hack...

                //goto LABEL_29;                   

            }

        }

        while (BitConverter.ToUInt32(buf, (int)off2 + 8 * v29) !=

               (v27 & dword_8107F828[2 * cur_char2]))

        {

            v29++;

            if (v29 == buf[off4 + 1 + cur_char2 ])

            {

                if (++cur_char2 == 0x18)

                {

                    return 0;

                }

                goto LABEL_26_2;

            }

        }

        outchar = buf[off2 + 5 + 8 * v29];

        LABEL_29:

        output[cur_output_addr] = (byte)outchar;

        BitConverter.GetBytes((Int64)v33 + (ushort)cur_char2).CopyTo(buf, 0x3028);

        v23 = size_done + 1;

        ++cur_output_addr;

        v33 += cur_char2 ;

        size_done = v23;

        if (v23 >= unpacked_size)

            return size_done;

    }

    return 0;

}

uint sub_8105E56C(uint result, uint a2, uint a3)

{

    if (a3 > 63)

    {

        result = 0;

    }

    else if (a3 != 0)

    {

        if (a3 > 31)

            result = a2 >> (int)(a3 - 32);

        else

            result = (a2 << (int)(32 - a3)) | (result >> (int)a3);

        //insert the low a3 bit of the result into a2, then asign to result

    }

    return result;

}

0X5 后记

根据这个游戏,我把GARBROgss引擎解包完善了,详见我的github同时我也pull request了,不过作者好像好久没维护了。

还原算法后测试一下,发现剧本提取没问题,文字也都出来了,同理图片封包格式也一样。

ida2csharp1

至于封包,我们可以走一个捷径,就是这个游戏有个IsPacked 这个flag。修改这个flag,我们可以不压缩和加密,直接把原来文件封包,就是要注意一下0x800等字节对齐与补零,索引重建等。

之后为了汉化文本超长,需要分析一下opcode。这个游戏根据猜测把opcode打印一下即可看出规律,分析出每条指令是干什么的。这游戏比较简单,放一下opcode打印代码和图了,不再详细分析了。当然还有字库问题,这个相对来说处理起来不难,这里不再赘述了。


def print_opcode(scrpath):

    with open(scrpath, 'rb') as fp:

        data = fp.read()

    magic = data[0:8]

    unk1, unk2 = struct.unpack("<II", data[8:0x10])

    print(magic, hex(unk1), hex(unk2))

    i = 0x10

    while i+2 < len(data):

        optype = data[i:i+2]

        oplen = data[i+2]

        opcodestr = ""

        texts = []

        j = i + 3

        while j < i + oplen:

            if data[j] == 0x3 and data[j+1] != 0x3:

                start = j+1 

                while data[j] != 0: j+=1

                texts.append(data[start:j].decode('sjis'))

            else: 

                opcodestr += "{:02X} ".format(data[j])

            j += 1

        print(hex(i), hex(int.from_bytes(optype, 'big')), opcodestr, " #".join(texts))

        i += oplen

opcode_option

另外此游戏psv和psp文件结构非常相似,一些内容也可以用psp模拟器测试提高效率。至于psp的mips汇编和如何动态调试,这些我打算之后出教程。

这些都分析完后,重新导入文本和图片,搞定!

psv_test

psp_importpic_test2

免费评分

参与人数 68吾爱币 +60 热心值 +62 收起 理由
lancher + 1 + 1 我很赞同!
WhaoC + 1 + 1 我很赞同!
821303407 + 1 + 1 我很赞同!
cpj1203 + 1 + 1 谢谢@Thanks!
Forehawk + 1 + 1 我很赞同!
远在咫尺丶 + 1 + 1 谢谢@Thanks!
wapjzxy + 1 太厉害了
NoooomoRe + 1 + 1 太猛啦
星野vv + 1 + 1 用心讨论,共获提升!
茗箩寰霁 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
又见蜉蝣 + 1 + 1 热心回复!
jrzhao + 1 + 1 用心讨论,共获提升!
luochunyan + 1 + 1 谢谢@Thanks!
lijkiiu + 1 我很赞同!
塞活 + 1 + 1 谢谢@Thanks!
LI266 + 1 + 1 热心回复!
绝对的孤独者 + 1 热心回复!
糯米团子 + 1 + 1 我很赞同!
借我一支黑兰州 + 1 + 1 谢谢@Thanks!
lookerJ + 1 + 1 用心讨论,共获提升!
一只会飞的猪 + 1 感谢您的宝贵建议,我们会努力争取做得更好!
文姐姐 + 1 我很赞同!
LoveMiku233 + 1 + 1 热心回复!
xiong_online + 1 + 1 用心讨论,共获提升!
丶峰宇 + 1 + 1 谢谢@Thanks!
CokkeizigenDAR + 1 + 1 谢谢@Thanks!
yeahr1ght + 1 + 1 谢谢@Thanks!
完美剿灭OVG + 1 + 1 真诚感谢分享
如T初 + 1 + 1 谢谢@Thanks!
siuhoapdou + 1 + 1 谢谢@Thanks!
丿吹雪灬 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
lantian21 + 1 我很赞同!
独行风云 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
2ci晨曦 + 1 热心回复!
JunMoXiao + 1 热心回复!
FlashArrow + 1 + 1 热心回复!
hej1981 + 1 + 1 谢谢@Thanks!
namefin + 1 + 1 用心讨论,共获提升!
asong + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
poisonbcat + 1 + 1 谢谢@Thanks!
darkspr + 1 我很赞同!
myyuri25 + 1 + 1 谢谢@Thanks!
misalla + 1 + 1 谢谢@Thanks!
gaosld + 1 + 1 热心回复!
scloud + 1 + 1 用心讨论,共获提升!
唯我独嘎 + 1 + 1 我很赞同!
xy820208 + 1 大佬牛批 雪碧
Reo_52 + 1 + 1 用心讨论,共获提升!
Schocolade + 1 + 1 用心讨论,共获提升!
schedule + 1 用心讨论,共获提升!
dmc420 + 1 + 1 谢谢 @Thanks!
榮君 + 1 + 1 我很赞同!
二酸化炭素 + 1 + 1 谢谢@Thanks!
指尖花凉亦成伤 + 1 + 1 谢谢@Thanks!
shuaibi_chen + 1 + 1 用心讨论,共获提升!
内瑟斯 + 1 + 1 谢谢@Thanks!
LangeHeris + 1 + 1 我很赞同!
腰围两尺99 + 1 + 1 用心讨论,共获提升!
pandora01 + 1 用心讨论,共获提升!
DaKxhq54zDH + 1 我很赞同!
jzbcbaba + 1 我很赞同!
WAlitudealiy + 1 谢谢@Thanks!
SweetRain + 1 我很赞同!
fengbolee + 1 + 1 用心讨论,共获提升!
Martil1o + 1 + 1 用心讨论,共获提升!
笙若 + 1 + 1 谢谢@Thanks!
Razuri + 1 + 1 大佬牛逼
风绕柳絮轻敲雪 + 3 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 小木曾雪菜 发表于 2020-12-5 20:56
namefin 发表于 2020-12-4 20:45
哇塞,之前玩小黄油遇到非汉化版还超抓狂,想着汉化不就是替换字幕么怎么还没人做,这才了解到,这么难,看 ...

是呀,汉化游戏要远比影视字幕难。要考虑很多东西,解包,封包,文本opcode解析,字库改造与扩容,尤其是冷门的游戏,没有现成的工具,都要从头开始分析
tiemoboy 发表于 2020-11-27 19:29
easthq 发表于 2020-11-27 18:23
牛啊,涉及了好多知识,我原来以为汉化只是简单的字幕文件替换,没想到这么复杂
yl17 发表于 2020-11-27 18:31
牛啊大佬
 楼主| 小木曾雪菜 发表于 2020-11-27 18:38
easthq 发表于 2020-11-27 18:23
牛啊,涉及了好多知识,我原来以为汉化只是简单的字幕文件替换,没想到这么复杂

汉化游戏和汉化影视字幕不同,里面涉及到很多东西。往往最难搞的就是字库了,尤其是遇到冷门主机游戏,没法动态调式,只能一点点的静态分析
芽衣 发表于 2020-11-27 19:11
黄油就靠你了

看来柚子社不怕没人汉化,草
gunxsword 发表于 2020-11-27 20:55
看不懂啊,顶起来就对了!!!
guangzisam 发表于 2020-11-27 21:04
我的IDA只能算菜鸟。目前还学不懂
九条可怜 发表于 2020-11-28 00:00
学会了,一点,哈哈哈
talon___ 发表于 2020-11-28 00:47
楼主厉害啊,膜拜
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-27 03:19

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表