Coming Heart 资源分析(三)

书接上文,我们已经打开了 chtitle.hhp,那么是时候看看程序是如何读取这个文件的了。

目录

  1. 环境准备
  2. Cutter、TDW、CreateFile
  3. 按位读取、调色板
  4. 图像读取(一)
  5. 图像读取(二)
  6. 脚本读取

弯路

先说个题外话开场吧,第一次找到打开 chtitle 时,可能是喜悦冲昏了头脑, 我觉得读取文件的地方一定在不远的下面,于是开始 F7 一步一步跟了下去, 一下午的时间很快过去,在几进几出关键区后,我觉得这不是个办法, 可能 windows 程序的复杂程度超过了我的想象。不过还好, 我想到了 api 应该会有读取文件的地方,从那边定位应该会比较快。这就有了 ReadFile

Windows API

BOOL ReadFile(
  [in]                HANDLE       hFile,
  [out]               LPVOID       lpBuffer,
  [in]                DWORD        nNumberOfBytesToRead,
  [out, optional]     LPDWORD      lpNumberOfBytesRead,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

ReadFile 把文件 handle 和缓冲区地址(lpBuffer)告诉内核, 告诉它我们要读取哪个文件,并把文件内容放到哪个缓冲区。

Cutter

我们一样可以在 cutter 的导入表中,找到 ReadFile,从而去定位它的调用位置:

Untitled

有两个位置,但是距离很近,我们就先看第一个好了:

Untitled

cutter 很贴心地向我们指明了缓存区的位置保存在这行的 edx 中:

Untitled

记下这行的位置 40bd43h ,后面我们需要验证缓冲区中保存的内容。

TDW

我们接着 第二部分 来讲,这时我们中断在打开 ctitle.hhp 时:

Untitled

这时我们使用 Ctrl-g,前往 40bd43h 下断点:

Untitled

F9 继续程序,很快就会来到刚刚我们下的断点:

Untitled

edx 的值是 6aec84h,这个就是缓冲区的地址,注意 bd47 行 edx 的值会有变化, 所以我们要及时观察 edx 的值,不能停在 bd4bcall 之前。 我们把内存窗口调整到显示缓冲区的内容,然后让程序执行到 40bd5b 行, 也就是 ReadFile 执行完成。这时我们就可以去检查缓冲区的内容:

Untitled

我们来对比一下实际文件的内容:

Untitled

缓冲区确实加载了这段内容,接下来我们要做的就是不断地 F7 来寻找缓冲区的内容被搬到哪里去, 顺带一提,也许 TDW 有读取内存时中断的功能,但是我没试出来,所以只好自己看了, 不过还好这个还算好找:

Untitled

我们注意到 ecx 的值就是缓冲区的地址,把值放入 al,然后地址自增 1, 这一定就是读取的位置了。看来这里值得我们仔细研究一下,我们回到 cutter, cutter 可以很贴心地帮我们找到整块函数:

;-- section..text:
fcn.00401000 (int32_t arg_14h);
; arg int32_t arg_14h @ esp+0x24
0x00401000      push    ebx        ; [00] -r-x section size 73728 named .text
0x00401001      push    esi
0x00401002      push    edi
0x00401003      mov     esi, ecx
0x00401005      push    ebp
0x00401006      xor     edi, edi
0x00401008      mov     ebx, dword [arg_14h]
0x0040100c      cmp     ebx, edi
0x0040100e      jle     0x401089
0x00401010      cmp     dword [esi + 0x200c], 0
0x00401017      jne     0x401069
0x00401019      cmp     dword [esi + 0x2008], 0
0x00401020      jne     0x401044
0x00401022      lea     ebp, [esi + 4]
0x00401025      mov     eax, dword [esi]
0x00401027      push    eax
0x00401028      push    0x2000     ; int32_t arg_14h
0x0040102d      push    1          ; 1 ; int32_t arg_18h_2
0x0040102f      push    ebp        ; int32_t arg_18h
0x00401030      call    fcn.00408c40
0x00401035      add     esp, 0x10
0x00401038      mov     dword [esi + 0x2008], eax
0x0040103e      mov     dword [esi + 0x2004], ebp
0x00401044      mov     ecx, dword [esi + 0x2004]
0x0040104a      dec     dword [esi + 0x2008]
0x00401050      mov     al, byte [ecx]
0x00401052      inc     ecx
0x00401053      mov     byte [esi + 0x2010], al
0x00401059      mov     dword [esi + 0x200c], 8
0x00401063      mov     dword [esi + 0x2004], ecx
0x00401069      add     edi, edi
0x0040106b      mov     al, byte [esi + 0x2010]
0x00401071      test    al, 0x80   ; 128
0x00401073      je      0x401078
0x00401075      or      edi, 1
0x00401078      add     al, al
0x0040107a      dec     dword [esi + 0x200c]
0x00401080      dec     ebx
0x00401081      mov     byte [esi + 0x2010], al
0x00401087      jne     0x401010
0x00401089      cmp     dword [esi + 0x200c], 0
0x00401090      jne     0x4010e2
0x00401092      cmp     dword [esi + 0x2008], 0
0x00401099      jne     0x4010bd
0x0040109b      lea     ebx, [esi + 4]
0x0040109e      mov     eax, dword [esi]
0x004010a0      push    eax
0x004010a1      push    0x2000     ; int32_t arg_14h
0x004010a6      push    1          ; 1 ; int32_t arg_18h_2
0x004010a8      push    ebx        ; int32_t arg_18h
0x004010a9      call    fcn.00408c40
0x004010ae      add     esp, 0x10
0x004010b1      mov     dword [esi + 0x2008], eax
0x004010b7      mov     dword [esi + 0x2004], ebx
0x004010bd      mov     eax, dword [esi + 0x2004]
0x004010c3      dec     dword [esi + 0x2008]
0x004010c9      mov     cl, byte [eax]
0x004010cb      inc     eax
0x004010cc      mov     byte [esi + 0x2010], cl
0x004010d2      mov     dword [esi + 0x200c], 8
0x004010dc      mov     dword [esi + 0x2004], eax
0x004010e2      mov     eax, edi
0x004010e4      pop     ebp
0x004010e5      pop     edi
0x004010e6      pop     esi
0x004010e7      pop     ebx
0x004010e8      ret     4

但是还是很难理解,经过不停的 F7 之后,我对这个 al 最终如何处理的还是一头雾水, 而且一直看到 40107a401080 像倒计时一样一直在 876543210 这样变化, 像倒计时一样。不过我觉得我们可以参照以前的经验, 看看哪里引用了这个 fcn.00401000 (int32_t arg_14h)

Untitled

很多地方用到,我们看第一个好了:

0x00401136      push 8             ; 8 ; int32_t arg_14h
0x00401138      mov byte [ebx - 4], al
0x0040113b      mov ecx, dword [esi]
0x0040113d      call fcn.00401000
0x00401142      mov dword [var_10h_2], eax
0x00401146      mov dword [var_10h], 0
0x0040114e      fild qword [esp + 0x10]
0x00401152      fmul qword [esp + 0x18]
0x00401156      call flirt.ftol
0x0040115b      push 8             ; 8 ; int32_t arg_14h
0x0040115d      mov byte [ebx - 3], al
0x00401160      mov ecx, dword [esi]
0x00401162      call fcn.00401000

每次调用前都 push 了一个 8 进去,如果你是一个对汇编很熟悉的人, 可能已经意识到什么,不过对于我这种汇编新手,这还意味不了什么, 不过 cutter 还有一个贴心功能,显示反编译后的代码,方便我这种人理解逻辑, 切换反编译器的 tab 在窗口右下侧:

Untitled

我们点击反编译器,cutter 会自动显示当前函数反编译后的代码, 我们可以看到这样一个循环:

edi = 0x100;
...
do {
        ecx = *(esi);
        ebx++;
        eax = fcn_00401000 (8);
        ebx++;
        ...
        ebx++;
        ebx++;
        ...
        *((ebx - 4)) = al;
        ecx = *(esi);
        eax = fcn_00401000 (8);
        ...
        *((ebx - 3)) = al;
        ecx = *(esi);
        eax = fcn_00401000 (8);
        ...
        edi--;
        *((ebx - 2)) = al;
        *((ebx - 1)) = 0;
    } while (edi != 0);

我省略了一些代码中的内容,这个代码虽然乱七八糟, 不过我们可以很快根据结构推测出 fcn_00401000 的功能:按位读取,所以三次调用, 参数都是 8,表示读取一个 byte。那么每次循环读取 3 个 byte,我们也很好推测: 这是在读取调色板的 rgb 数值,而且由于循环次数就是 2561, 所以这个是调色板基本实锤。事实上如果我们仔细观察 chtitle.hhp 的前 768 个字节, 确实也很像是调色板:

Untitled

总结

其实我个人觉得,这种按 byte 读取数据的功能,确实可以套用读取 8 位数据的逻辑, 但其实没什么必要,直接实现就好。不过这不是我们的重点, 我们也没有细看按位读取的逻辑,只是我的个人看法。 既然我们知道了 fcn_00401000 是按位从缓冲区读取内容的函数, 并且确认文件前 768 个字节就是调色板,那么接下来,应该就是我们的重头戏了。