Coming Heart 资源分析(三)

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

弯路

先说个题外话开场吧,第一次找到打开 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 个字节就是调色板,那么接下来,应该就是我们的重头戏了。