“金山杯2007逆向分析挑战赛”第一阶段第二题

  • 时间:
  • 浏览:0

  IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER.DataDirectory[2].Size = sizeof ( IMAGE_IMPORT_DESCRIPTOR ) * 4;

  在本题求解过程中,我严重依赖于我另一三个小写的一三个小展示 PE 文件格式的应用应用守护进程,此应用守护进程最近经过我的调整和改进,它的优点是将会此应用守护进程基于扩展 TreeView 控件,如可让帮助快速理解 PE 文件头的特性,其效果见以下截图:

  导入表的示意特性如下图所示(图中展示的是一三个小 Thunk 数组并行状况,如可让 FirstThunk 也是字符串指针的大多数状况,图中的字符串未必趋于稳定整齐的矩形格子之内,这所以为了图形外观,应该强调的是哪此字符串的长度是不固定的,长度有长有短,所以它们在空间中的分布是参差不齐的):

  每个元素的 OriginalFirstTrunk 和 FirstTrunk 是一三个小指针,指向了一三个小 并行的指针数组,通常状况下(即没有 在链接时事先绑定)这种 个数组的内容是相同的(即一三个小数组的所有元素的值相同),在静态 PE 文件中,都指向相同的长度不固定的函数名称字符串(将会是被导入函数的 Ordinal)。

  这种 步也相对比较简单,导入表趋于稳定 .rdata 中(趋于稳定中部)。在此事先,还要了解导入表的特性,导入表是一三个小由多个 IMAGE_IMPORT_DESCRIPTOR 元素组成的数组,以 NULL 元素(内容完正是 0 )标识结尾(IMAGE_IMPORT_DESCRIPTOR 的数据特性定义参见 winnt.h)。每个元素由 5 个 DWORD 组成,其中倒数第三个小 DWORD 是 Name 字段(字符串指针),它的值是一三个小 RVA(即相对于 ImageBase 的偏移),指向了 Dll 名字(ASCII)字符串(该字符串同样趋于稳定 .rdata 中)。

  VA = FA + 30h;

  OriginalFirstTrunkFirstTrunk 指向的这种 个指针数组趋于稳定 .rdata 的不同位置,其中 FirstTrunk 指向的数组趋于稳定 .rdata 的起始位置(稍后可可不里里能就看这所以 IAT),OriginalFirstTrunk 指向的数组趋于稳定稍微靠后的位置。一三个小 Trunk 在 PE 文件中的值都指向相同的 IMAGE_IMPORT_BY_NAME (由 Hint 和 函数名称字符串 组成的数据特性)。IAT 所在的页面将在加载时被临时设定为可写,绑定事先再恢复为只读。有关这累积的细节请参考我的博客文章:《读取PE文件的导入表》。

  (1)选取 AddressOfEntryPoint 的地址。

  题目描述:

  另一三个小入口点地址就选取好了。

  IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER.DataDirectory[2].VirtualAddress = 0x7618;

  Code 2.1 由 .text 提供的 WinMain 函数的汇编代码:

  要计算 IAT 的大小,还要遍历导入表,找到导入的所有 Dll 的 FirstTrunk 的最后一三个小元素的位置,一起还要考虑到结尾还还要一三个小 NULL 指针作为事先现在现在开始标志,所以:

  本题目中所有的 Trunk 的最大地址(RVA)是 0x7124(从 USER32.dll 导入的 DispatchMessageA),可得:

  (2)选取 DataDirectory [1] 导入表的地址和大小:

  当然很显然中间的工作并都是一步到位完成的,下面简要介绍中间的工作是如可完成的:

  可可不里里能先用 VC (我采用 VS305)创建一三个小 Windows 窗口应用守护进程(它将提供某些主要样本,所以称这种 应用守护进程为样本应用守护进程),把应用守护进程写的尽将会和题目中的应用守护进程类似于,如可让编译,即首先得到了一三个小 PE 文件头的原型,再次基础上进行修改,也所以根据题目给出的 section,适当调整 PE 文件头中的还要修改的字段。

  对于 PE 文件头的 IMAGE_OPTINAL_HEADER.CheckSum,Windows 看起来完正忽略这种 字段的值,所以这种 字段可可不里里能不不管。

  FA: 0x000077AC: "KERNEL32.dll";  (这里使用的是文件地址 FA,将会说是 RVA)

  (1)上加 .rsrc section (菜单资源)

  Code 1.1 将一三个小 Section 拼接成 PE 文件的 C++ 代码:

  经过以上修改,可可不里里能通过 CreateNewPe 函数,生成一三个小可可不里里能执行的 PE 文件了。题目的前半累积要求此时完成。接下来考虑后半累积要求,为应用守护进程上加菜单和相关的命令防止函数。

  现在我希望知道,在文件头中把入口地址设置到 __tmainCRTStartup 函数即可,文件头要求的是 RVA,如可让在代码中设置入口点:

  DataDirectory[12].VirualAddress = 0x7000; // RVA (Relative to ImageBase )

  其中,.rsrc 是还要在稍后插入的资源 section,将在稍后讲解。

  中间的函数将会是最终版本的函数,它将会完成了以下工作:

  关于此应用守护进程的更多信息,请参见我的博客文章:《[VC6] 图像文件格式数据查看》。

  0x00401527: __tmainCRTStartup,是 PE 文件的实际入口点。

  上加资源,同样通过在样本应用守护进程中实现。在样本应用守护进程中,上加题目要求一样的资源(只保留菜单,删除所有某些种类资源,另一三个小可可不里里能使 .rsrc 最小,仅占用 30h 大小),如可让可可不里里能从样本应用守护进程中拷贝 .rsrc 段,追加到大伙将会得到的 PE 文件的尾部。一起调整 PE 文件头中的相关字段。

  (二)上加菜单 和 防止函数。

  BmpFileView 的可执行文件的下载链接(不敢说它是最好的,但作为帮助学习PE文件格式的辅助工具而强烈推荐):

  目录:第13篇 论坛活动 \ 金山杯307逆向分析挑战赛 \ 第一阶段 \ 第二题 \ 题目 \ [第一阶段 第二题]

  (一)拼接可执行文件:

  关于导入表和 IAT 的在内存空间中的位置布局,请参考本文的补充讨论(2)。

  0x004012D5: WndProc, 当前的窗口过程(稍后将会被子类化)

  1. 将 _text, _rdata, _data 合并成一三个小 EXE 文件,重建一三个小 PE 头,某些关键参数,如 EntryPoint,ImportTable 的 RVA,请另一方分析文件获得。合并成功后,应用守护进程即可运行。

  2. 请在第1步获得的EXE文件基础上,增加菜单。具体见图:

  (3)选取 DataDirectory [12],IAT的地址和大小:

  上图表示了 pediy2_new.exe 的实际导入表,共导入了 3 个 DLL,每个导入 DLL 是导入表中的一三个小元素,在这种 数组中的每个元素大小为 20 Bytes,将会引用了 3 个 DLL,则这种 数组一共为 (3 + 1) * 20 = 30 Bytes (最后有一三个小 null terminator element)。下面是单个元素 descriptor 大小:

  众所周知,窗口的菜单通常是在注册窗口类时指定的。如可让为了上加菜单,在 IDA 中观察 WinMain 函数的代码:

  这里还要很重注意的是,.data 的虚拟内存尺寸,还要要比文件尺寸(RawDataSize)更大某些,关于这种 点我还暂时没有 给出完正的解释,有待于在将来做进一步研究。将会把 .data 的 VirtualSize 设置为和 RawDataSize 一样大(300h),则应用守护进程无法运行,会弹出一三个小消息框提示这都是一三个小有效的 Win32 应用守护进程。所以这种 步我也是反复尝试不是某些字段的问题,纠结了半天才发现另一三个小问题卡在这种 地方。

  

  注意:将会 .data 节在加载到虚拟内存中时被扩大了 30h,所以趋于稳定最后的 .rsrc 的文件地址(FA)和虚拟地址(VA)将会偏差 30h。即:

  FA: 0x00007624: AC 77 00 00

  0x004011EC: WinMain,高级语言编程时的应用守护进程入口点。

  (4)从样本应用守护进程 pediy02.exe 中插入资源 (.rsrc) section,并选取 DataDirectory[2]: resource Table (资源表)的地址和尺寸。

  明确了以上问题,现在可可不里里能把这种 个 section 和文件头链接成一三个小新的 PE 文件了,把样本应用守护进程 pediy02.exe 和一三个小 section 文件放入同一三个小目录下,通过一三个小辅助的 Console 项目(pediy02_helper 项目)来完成哪此工作,生成的新的 PE 文件名为 pediy02_new.exe,使用的辅助函数如下(为了简单明了起见,代码中并没有 插入繁琐的检测性代码,类似于申请的缓冲区大小,将会根据还要,在编码时被静态的选取了):

  补充说明:在没有 经过事先绑定时,OriginalFirstTrunkFirstTrunk 指向的数组内容在加载事先都指向 .rdata 中的某些长度不固定的 Ascii 编码的字符串,在加载时 FirstTrunk 指向的数组被系统绑定成映射到本应用应用守护进程的 DLL 的实际函数地址(如可让该数组称为 IAT),所以哪此元素称为 Trunk (由于其身份的可变性,哪此元素在加载后其身份趋于稳定了变化),将会指向的是数组头部,所以称之为 First(IMAGE_IMPORT_DESCRIPTOR.(Original)FirstTrunk 表示某个 DLL 被本模块导入的首个函数的 Trunk 的位置,中间还有更多的函数 Trunk,以 NULL 表征事先现在现在开始)。OriginalFirstTrunk 在加载后保持不变(所以称为 Original),所以最少存储着导入函数名称的一份副本。在模块被加载后,可可不里里能通过 OriginalFirstTrunk 数组了解到该模块导入了哪此函数(名称),通过 FirstTrunk 数组的内容可了解到导入函数的运行时虚拟地址。导入函数的实际地址是在加载时绑定的(无法在编译时选取),编译器将会为每个 dll 函数调用生成一三个小很小的函数体,称为 j_XXX, 该函数体负责 jmp 到 FirstTrunk 数组中的元素给出的运行时函数地址,也可可不里里能直接调用 IAT 元素内容指向的 VA 地址。

  http://files.cnblogs.com/hoodlum1930/BmpFileView_V2_Bin.zip

  注:题目来自于以下链接地址:http://www.pediy.com/kssd/

  (3)选取 DataDirectory[12]: Import Address Table (绑定导入函数地址表)的地址和尺寸。

  

  了解了导入表特性,就可可不里里能变快找到导入表的位置了,首先在 .rdata 中查找 DLL 名称字符串,可可不里里能找到如下的字符串:

  有关如可遍历导入表的更多内容,请参考我的博客文章(在此就不再完正叙述了):《读取PE文件的导入表》。

  观察题目给出的一三个小 section 文件,可可不里里能给出这种 个 section 的基本信息如下:

  IAT.Size = max ( 所有 DLL 的 FirstTrunk 数组元素所在的地址(RVA) ) - IAT.VirtualAddress (RVA) + 8 。

  IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER.AddressOfEntryPoint = 0x1527;

  

  DataDirectory[12].Size = 0x012C;

  0x004059C4: sub_4059C4,基本等价于 MessageBoxA,很重要,称它为 ___crtMessageBoxA

  

  己知是一三个小 PE 格式 EXE 文件,其一三个小(section)区块的数据文件依次如下:(详见附件)

 

  _text,_rdata,_data

  该工作相对简单容易,先把 EntryPoint 设置为 .text (代码段)的起始地址:0x30,如可让生成文件后,加载到 IDA 中分析代码段的内容,就可可不里里能很容易的找到以下函数的地址(以下地址为 VA,即上加了 ImageBase 后的地址):

  3. 执行菜单 Help / About 弹出如下图所示的 MessageBox 窗口:

  首先下载题目的附件,附件中将会有一三个小文件,分别是 PE 文件的一三个小 section,可可不里里能就看一三个小 section 文件将会按照 0x30 大小对齐。另一三个小大伙只还要把这种 个文件依次连接在一起,接在一三个小正确的 PE 文件头中间就可可不里里能了。

  这里所以一三个小 IMAGE_IMPORT_DESCRIPTOR 元素,把该地址减去 3 个 DWORD 值,即得到该元素的起始地址为 0x00007618。将会导入表元素内容非常有特点,很容易就可可不里里能判断导入表的两端边界,如可让可可不里里能变快选取导入表的起始地址(RVA)和 Size 如下:

  IAT 的地址比较简单,它所以所有 DLL 的 FirstTrunk 字段的最小值,通常所以 .rdata 的起始位置(哪此常量字符串趋于稳定 IAT 和 ImportTable 的中间),也所以 0x7000 (可可不里里能就看这里是从 Gdi32.dll 的导入的第一三个小函数 DeleteObject)。

  (2)选取 DataDirectory[1]: ImportTable (导入表)的地址和尺寸。

  未必应用应用守护进程可可不里里能通过序号导入函数,并具有极高效率,如可让另一三个小会由于看没有 导入函数的名字,对应用守护进程和系统的维护造成障碍。所以除非成本太高(类似于 MFC 类库的导出函数不多,且面向对象的 C++ 函数名称也很长,所以 MFC 类库的函数以 Ordinal 法律法律依据 被导入),按名称导入是普遍做法,显然按名称导入,还要线性搜索模块的导出函数表,这就会消耗一定的加载时间成本。为了提高应用守护进程加载时效率,应用应用守护进程可可不里里能通过 “事先 Rebase” (将应用守护进程还要导入的模块自身建议的 ImageBase 进行精心调整,从而防止在加载时重定向) 和 “事先绑定” 提高应用守护进程在客户运行环境的加载效率,系统通过时间戳判定绑定信息不是有效,将会时间戳不一致,将会趋于稳定重定向,系统则还要再次进行加载时绑定。

  找到互近指向该位置的指针,即在互近的文件内容中搜索 "AC 77 00 00" 片段,可可不里里能找到文件地址:

  题目分析和解答:

  (1)选取入口点地址:

  sizeof ( IMAGE_IMPORT_DESCRIPTOR ) = sizeof ( DWORD ) * 5 = 20 Bytes;