第86章

    当然Oracle RDBMS 可执行文件肯定拥有某种调试信息,或者带有符号信息(或者类似信息)的映射文件。

    WindowsNT Oracle RDBMS 的符号信息包含在具有.SYM扩展名的文件中,具有专门的格式。(平文文件挺好的,但是需要额外解析,因此获取速度更慢)

    咱们来看看能不能理解它的格式。我选了最短的orawtc8.sym文件,来自于Oracle 8.1.7 版本orawtc8.dll 文件。

    下面是Hiew加载后的效果:

    通过与其他.SYN文件的对比,我们可以快速发现“OSYM”总是头(和尾),因此这可能就是文件的标志。

    同时也可以看出,文件格式是:OSYM+一些二进制数据+以0为界定符的字符串+OSYM。这些字符串显然是函数和全局变量名。

    我标记了OSYM标志和字符串:

    第86章 - 图1

    咱们来看看。我在Hiew中标记了整个字符串块(除了末尾的OSYM),然后把它放进单独的文件中。然后我运行UNIX的strings和wc工具分析字符串

    1. 66

    有66个文本字符串,请记住这个数字。

    可以这么说,常规情况下,数量值会被存储在一个单独的二进制文件中。的确也是如此,我们可以在文件开头找到这个66数字(0x42),就在OSYM这个标志右边:

    当然,这里的0x42不是一个字节,更像是一个32比特的值,小端,后面至少跟着3个0字节。

    我检查了其他.SYM文件,证实了我的猜想:OSYM符号后面的32位值总表示文件字符串的数量。

    这对于所有的二进制文件来说几乎是通用的:文件头包含标志和文件其他信息。

    现在我们来进一步调查二进制块是什么。再次使用Hiew,我把从块头8个字节(32位计数值后面)开始一直到字符串块结尾的内容放入单独的文件中。

    在Hiew中看看这个二进制块:

    有一个明显的规律。

    我用红线划分了这个块:

    第86章 - 图2

    Hiew,就像其他的十六进制编辑器一样,每行显示16个字节。所以规律很容易看出来:每行有4个32位的值。

    这个规律容易看出来的原因是其中的一些值(地址0x104之前)总是具有0x1000xxxx的格式,以0x10和0字节开始。其他值(从地址0x108开始)都是0x0000xxxx的格式,总是以两个0字节开始。

    我们把这个块当作32位值的数组dump出来:

    1. $ od -v -t x4 binary_block
    2. 0000000 10001000 10001080 100010f0 10001150
    3. 0000020 10001160 100011c0 100011d0 10001370
    4. 0000040 10001540 10001550 10001560 10001580
    5. 0000060 100015a0 100015a6 100015ac 100015b2
    6. 0000100 100015b8 100015be 100015c4 100015ca
    7. 0000120 100015d0 100015e0 100016b0 10001760
    8. 0000140 10001766 1000176c 10001780 100017b0
    9. 0000160 100017d0 100017e0 10001810 10001816
    10. 0000200 10002000 10002004 10002008 1000200c
    11. 0000220 10002010 10002014 10002018 1000201c
    12. 0000240 10002020 10002024 10002028 1000202c
    13. 0000260 10002030 10002034 10002038 1000203c
    14. 0000300 10002040 10002044 10002048 1000204c
    15. 0000320 10002050 100020d0 100020e4 100020f8
    16. 0000340 1000210c 10002120 10003000 10003004
    17. 0000360 10003008 1000300c 10003098 1000309c
    18. 0000400 100030a0 100030a4 00000000 00000008
    19. 0000420 00000012 0000001b 00000025 0000002e
    20. 0000440 00000038 00000040 00000048 00000051
    21. 0000460 0000005a 00000064 0000006e 0000007a
    22. 0000520 000000b6 000000c0 000000d2 000000e2
    23. 0000560 00000121 0000012a 00000132 0000013a
    24. 0000600 00000146 00000153 00000170 00000186
    25. 0000620 000001a9 000001c1 000001de 000001ed
    26. 0000640 000001fb 00000207 0000021b 0000022a
    27. 0000660 0000023d 0000024e 00000269 00000277
    28. 0000700 00000287 00000297 000002b6 000002ca
    29. 0000720 000002dc 000002f0 00000304 00000321
    30. 0000740 0000033e 0000035d 0000037a 00000395
    31. 0000760 000003ae 000003b6 000003be 000003c6
    32. 0001000 000003ce 000003dc 000003e9 000003f8
    33. 0001020

    这里有132个值,也就是66*2。或许每一个符号有两个32位的值,或者有两个数组呢?咱们接着看。

    从0x1000开始的值可能是地址。毕竟这是dll的.SYM文件,win32 DLL默认的基址是0x10000000,代码通常从0x10001000开始。

    哇,“ax_reg”字符串看起来很熟悉。它的确是字符串块的第一个字符串。所以函数名是“ax_reg”。

    第二个函数是:

    1. .text:60351080 sub_60351080 proc near
    2. .text:60351080
    3. .text:60351080 arg_0 = dword ptr 8
    4. .text:60351080 arg_4 = dword ptr 0Ch
    5. .text:60351080
    6. .text:60351080 push ebp
    7. .text:60351081 mov ebp, esp
    8. .text:60351083 mov eax, dword_60353018
    9. .text:60351088 cmp eax, 0FFFFFFFFh
    10. .text:6035108B jnz short loc_603510CF
    11. .text:6035108D mov ecx, hModule
    12. .text:60351095 cmp ecx, 0FFFFFFFFh
    13. .text:6035109D jnz short loc_603510B1
    14. .text:6035109F call sub_603510F0
    15. .text:603510A4 mov ecx, eax
    16. .text:603510A6 mov eax, dword_60353018
    17. .text:603510AB mov hModule, ecx
    18. .text:603510B1
    19. .text:603510B1 loc_603510B1: ; CODE XREF: sub_60351080+1D
    20. .text:603510B1 test ecx, ecx
    21. .text:603510B3 jbe short loc_603510CF
    22. .text:603510B5 push offset aAx_unreg ; "ax_unreg"
    23. .text:603510BA push ecx ; hModule
    24. .text:603510BB call ds:GetProcAddress
    25. ...

    “ax_unreg”字符串也是字符串块的第二个字符串!第二个函数的开始地址是0x60351080,二进制块的第二个值是10001080.因此就是这个地址,但对于DLL加上了默认基地址

    现在可以快速的检查然后确定数组开始的66个值(也就是数组前一半)只是DLL中的函数地址,包括一些标签等等。那么另一半是什么呢?剩余的66个值都是以0x0000开始的,看上去范围是[0…0x3FB8]。并且他们看上去不像位域:序列的数量在增长。最后一个十六进制数字看上去是随机的。因此不像是地址(如果它是4字节,8字节,16字节则可除尽)

    我们问问自己吧:Oracle RDBMS的开发者还会在文件中保存什么呢?随便猜猜:可能是文本字符串(函数名)的地址。可以迅速验证这一点,是的,每个数字代表的就是字符串在这个块中第一个字符的位置。

    就是这样,完成了!

    我写了一个工具将这些.SYM文件转换到IDA脚本中,然后我可以加载.IDC脚本,设置函数名:

    下面是它工作的一个例子:

    1. #include <idc.idc>
    2. static main() {
    3. MakeName(0x60351000, "_ax_reg");
    4. MakeName(0x60351080, "_ax_unreg");
    5. MakeName(0x603510F0, "_loaddll");
    6. MakeName(0x60351150, "_wtcsrin0");
    7. MakeName(0x60351160, "_wtcsrin");
    8. MakeName(0x603511C0, "_wtcsrfre");
    9. MakeName(0x603511D0, "_wtclkm");
    10. ... }

    我使用的例子可以在这里找到:

    咱们来试试64位的Oracle RDBMS。相应的,地址应该为64位,对么?

    8字节的规律看上去更加明显了:

    是的,所有的表含有64位的元素,甚至是字符串的偏移。现在标志也变成了OSYMAM64,猜测是用于区分目标平台的。