首页
社区
课程
招聘
[原创]看雪论坛.腾讯公司2008软件安全技术竞赛第一题分析过程
2008-10-4 13:41 12574

[原创]看雪论坛.腾讯公司2008软件安全技术竞赛第一题分析过程

2008-10-4 13:41
12574
【文章标题】: 看雪论坛.腾讯公司2008软件安全技术竞赛第一题分析过程 
【文章作者】: chimney
【作者邮箱】: chimney88@163.com
【软件大小】: 3K
【使用工具】: LoadPE PEID UE32 Stu_PE
【操作平台】: WINXP+SP2
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
  1.整体分析
       题目要求是:
    1.为DLL文件新增输出表,同时增加输出函数OpenUrlA;
    2.OpenUrlA函数功能是调用IE浏览器打开http://bbs.pediy.com,打开后无其他操作;
    3.不得改变pediy.dll的ImageBase的值400000h;
       程序要求的是在原有的dll中导出一个函数,函数的功能是调用IE浏览器打开看雪论坛,打开后没 

       有其他操作,而且注明了下不得改变原有dll的ImageBase的值400000h。先用Stud_PE对pediy.dll 

       进行分析:
  
  No  | 名称      | VSize      | VOffset    | RSize      | ROffset    | Charact.   
  01  | .text     | 000000CA   | 00001000   | 00000200   | 00000400   | 60000020   
  02  | .rdata    | 000000AA   | 00002000   | 00000200   | 00000600   | 40000040   
  03  | .data     | 00000054   | 00003000   | 00000200   | 00000800   | C0000040   
  04  | .reloc    | 0000004C   | 00004000   | 00000200   | 00000A00   | 42000040   
  
  dll中有四个节,分别是代码节,只读数据节,数据节和重定位表节。还有在dll中输入了三个函数:
  USER32.dll
    MessageBoxA  ord:446 rva: 0000200C
    
  KERNEL32.dll
    GetProcessHeap  ord:320 rva: 00002000
    HeapAlloc  ord:409 rva: 00002004
  在dll中没有发现导出函数。基本的情况就是这么多。分析完程序后,得出我们要做的有以下几点:
      a.要导出函数,我们必须先要在程序中构造输出表,导出OpenUrlA函数。
      b.要调用IE打开网址,我们一般用的API有:
       HINSTANCE ShellExecuteA(HWND hwnd,
       LPCTSTR lpOperation,
       LPCTSTR lpFile,
       LPCTSTR lpParameters,
       LPCTSTR lpDirectory,
       INT nShowCmd
       );
       和
       BOOL ShellExecuteExA(LPSHELLEXECUTEINFO lpExecInfo);
       这两个函数的详细用法大家可以参考MSDN.这里我们选哪个函数好呢?在这里我开始选择了
      ShellExecuteA后来又改成了ShellExecuteExA。为什么?因为他的参数只有一个而ShellExecuteA
      有六个,在本题要求OpenUrlA函数尽量下,试问压一个参数与压六个参数入栈哪个用的代码少点呢 

      ?那么我在这里为什么要提ShellExecuteA函数呢?大家可以想以下,我在这里卖个关子后面在说.  

    这两个函数都在shell32.dll中,程序中没有引入shell32.dll。那我们要增加这个输入函数。
      c.构造参数,完成具体的OpenUrlA函数。
      d.因为要在dll中增加代码而且dll的基址又设为了400000H不能更改,所以一定得考虑数据重定
      位的问题。具体在我们这题有哪些要重定位呢?在下面详细的介绍中会说到。
  
  有了上面的整体分析,下面来一项项的来完成题目。
  
  2.首先我们来完成输出表的添加,因为dll中没有任何输出函数,所以也就没有输出表,我们就要考
   虑把输出表放在什么地方。这里可以增加一个节来存放,我没有这么做。我把他放到了.rdata节中
   了。.rdata中只放了输入表,后面还有很多的空闲空间可以用.
  
  DataDirector(数据目录表)的第一个成员指向输出表。该指针具体位置是在PE文件头的78h偏移处。
  数据目录表的的结构为:
  IMAGE_DATA_DIRECTORY  STRUCT
       VirtualAddress   DWORD  ?      ;数据块的起始RVA
       Size        DWORD  ?         ;数据块的长度
  IMAGE_DATA_DIRECTORY  ENDS
  
  pediy.dll的PE文件头起始位置是D0h,输出表指针就是在整个文件的D0h+78h=148h处,用十六进制
  工具看了下,发现了8个字节都是零,因为此dll中根本就没有导出表的缘故。我们来修改他。
  在.rdata中选择块空地方来放导出表,我选择的是偏移为700h的地方,转换为RVA为 2100h,输出
  表大小为32h。所以数据目录的这项修改为:
  00 21  00 00 28 00 00 00
  
  下面构造输出表,输出表的结构为:
  
  typedef struct _IMAGE_EXPORT_DIRECTOR{
  
  ULONG Characteristics            ;未使用,总是0  
  ULONG TimeDateStamp         ;文件生成时间  
  USHORT MajorVersion        ;主版本号,0      
  USHORT MinorVersion          ;次版本号,0
  ULONG  Name         ;模块真实名称
  ULONG  Base         ;基数,加上序数就是函数地址数组的索引
  ULONG  NumberOfFunctions      ;AddressOfFunctions阵列中元素的个数
  ULONG  NumberOfNames         ;AddressOfNames 阵列中元素的个数
  PULONG *AddressOfFunctions           ;指向函数地址数组
  PULONG  *AddressOfNames            ;函数名字的指针地址
  PUSHORT *AddressOfNameOrdinals   ;指向输出序列号数组
  
  }IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;
  
  给该结构赋值为:
  00000700h: 00 00 00 00 00 00 00 00 00 00 00 00 32 21 00 00 ; ............2!..
  00000710h: 00 00 40 00 01 00 00 00 01 00 00 00 28 21 00 00 ; ..@.........(!..
  00000720h: 2C 21 00 00 30 21 00 00 E0 10 00 00 3C 21 00 00 ; ,!..0!..?..<!..
  00000730h: 00 00 70 65 64 69 79 2E 64 6C 6C 00 4F 70 65 6E ; ..pediy.dll.Open
  00000740h: 55 72 6C 41 00 00 00 00 00 00 00 00 00 00 00 00 ; UrlA............
  
  AddressOfFunctions是一个指向的存放导出函数RVA地址的数组指针,有几个函数导出,数组就有几
  项。这里只要导出一个函数OpenUrlA。所以就只有一项。所以2128h(RVA)=728(RAW)存放的就是
  OpenUrlA的地址(RVA)。这里我选取的OpenUrlA函数地址是在.text节中,偏移为4E0h(RAW)=10E0h(RVA)


  AddressOfNames:是一个指向存放函数名字符串地址的数组的指针。
  AddressOfNameOrdinals:是一个指向输出序号数组的指针。
  
  3.修改输入表结构。
    PE文件头的可选映像头(IMAGE_OPTIONAL_HEADER32)中有一项DataDirector(数据目录表),该数组的第

二成员指向输出表。输入表以一个IMAGE_IMPORT_DESCRIPTOR(简称IID)数组开始。每个被PE文件隐式地链

接进来的DLL都有一个IID。在这个数组中,没有字段指出该结构数组的项数,但它的最后一个单元是NULL

,可以由此计算出该数组的项数。例如,pediy.dll从两个DLL文件引入函数,就存在两个IID结构来描述

这些DLL文件。IID结构如下:
  
  IMAGE_IMPORT_DESCRIPTOR struct
  union{
     DWORD Characteristics;       ;00h
     DWORD OriginalFirstThunk;    ;
     };
  TimeDateStamp    DWORD          ;04h
  ForwarderChain   DWORD          ;08h
  Name             DWORD          ;0Ch
  FirstThunk       DWORD          ;10h
  IMAGE_IMPORT_DESCRIPTOR ends;
  pediy.dll的输入表地址是 614h(RAW)=2014(RVA)。
  
  00000610h: 00 00 00 00 5C 20 00 00 00 00 00 00 00 00 00 00 ; ....\ ..........
  00000620h: 72 20 00 00 0C 20 00 00 50 20 00 00 00 00 00 00 ; r ... ..P ......
  00000630h: 00 00 00 00 9C 20 00 00 00 20 00 00 00 00 00 00 ; ....?... ......
  00000640h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
  
  我们要增加shell32.dll的引用,就要增加一个IID结构。如果在原有的输入表后面增加,势必对后面的
  数据进行修改增加工作量,我们将原有的输入表复制到786h(RAW)=2186h(RVA).修改后的输入表如下:
  
  00000760h: 53 48 45 4C 4C 33 32 2E 44 4C 4C 00 00 00 53 68 ; SHELL32.DLL...Sh
  00000770h: 65 6C 6C 45 78 65 63 75 74 65 45 78 41 00 6C 21 ; ellExecuteExA.l!
  00000780h: 00 00 00 00 00 00 5C 20 00 00 00 00 00 00 00 00 ; ......\ ........
  00000790h: 00 00 72 20 00 00 0C 20 00 00 50 20 00 00 00 00 ; ..r ... ..P ....
  000007a0h: 00 00 00 00 00 00 9C 20 00 00 00 20 00 00 7E 21 ; ......?... ..~!
  000007b0h: 00 00 00 00 00 00 00 00 00 00 60 21 00 00 7E 21 ; ..........`!..~!
  
  最后记住改数据目录中,指向输入表项的地址,在偏移地址为150h处,改为:
  
  00000150h: 86 21 00 00 3C 00 00 00                         ; ?..<...
  
  在这里可以用LoadPE添加一个新的IID结构,不过用LoadPE会产生个新节。这里有个取巧的方法,就是
  用LoadPE添加完后,再复制新的导入表结构放到.RDADA节中的空隙中。然后只要修改数据目录中导入
  表的位置和最后一个IID就可以了。调用ShellExecuteExA代码为:CALL [基址+ThunkRVA]即 
  CALL [0400000+217E]。
  
  4.编写OpenUrlA代码。
  
  ShellExecuteExA(LPSHELLEXECUTEINFO lpExecInfo);
  转化为汇编最简单的为
  
  push lpExecInfo
  call ShellExecutexA
  ret
  
  下面来在数据段中构造参数,LPSHELLEXECUTEINFO结构如下;
  typedef struct _SHELLEXECUTEINFO {
      DWORD cbSize;
      ULONG fMask;
      HWND hwnd;
      LPCTSTR lpVerb;
      LPCTSTR lpFile;
      LPCTSTR lpParameters;
      LPCTSTR lpDirectory;
      int nShow;
      HINSTANCE hInstApp;
      LPVOID lpIDList;
      LPCTSTR lpClass;
      HKEY hkeyClass;
      DWORD dwHotKey;
      union {
          HANDLE hIcon;
          HANDLE hMonitor;
       } DUMMYUNIONNAME;
     HANDLE hProcess;
  } SHELLEXECUTEINFO, *LPSHELLEXECUTEINFO
  
  这里只关心几个值,其余的用零填充就可以了。这个结构体应先在数据段中构造出来,选择的构造该结

构体偏移地址为860H.
  cbSize=0000003C;
  fMask=00000040;
  lpFile=004030C0("C:\\Program Files\\Internet Explorer\\iexplore.exe")
  注:在这里你可能发现了问题,如果我系统不在D盘,那调用不就失败了吗?不错,这就是我们上面要介

绍ShellExecyteA函数
  的缘故。ShellExecute(0,0,"bbs.pediy.com",0,0,SW_SHOW)就可以打开网址了,不需要给定程序地址

就行了。但为了函数足够
  小,我还是选择了ShellExecuteExA了。
  lpParameters=00403100("http://bbs.pediy.com")。
  nShow=5;
  其他值都可以用0填充。 lpFile,lpParameters都要自己构造出来。构造后结构体如下:
  
  00000860h: 3C 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 ; <...@...........
  00000870h: C0 30 40 00 00 31 40 00 00 00 00 00 05 00 00 00 ; ?@..1@.........
  00000880h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
  00000890h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
  000008a0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
  000008b0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
  000008c0h: 43 3A 5C 5C 50 72 6F 67 72 61 6D 20 46 69 6C 65 ; C:\\Program File
  000008d0h: 73 5C 5C 49 6E 74 65 72 6E 65 74 20 45 78 70 6C ; s\\Internet Expl
  000008e0h: 6F 72 65 72 5C 5C 69 65 78 70 6C 6F 72 65 2E 65 ; orer\\iexplore.e
  000008f0h: 78 65 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; xe..............
  00000900h: 68 74 74 70 3A 2F 2F 62 62 73 2E 70 65 64 69 79 ; http://bbs.pediy
  00000910h: 2E 63 6F 6D 00 00 00 00 00 00 00 00 00 00 00 00 ; .com............
  
  完成OpenUrlA。
  函数的起始偏移地址位4E0H,
  
    PUSH          68 60 30 40 00     ;bbs.pediy.com
    CALL          FF 15 7E 21 40 00  ;ShellExecuteExA 地址
    ret        c3 
  
  注意这里还要考虑到重定位的问题,重定位有4处: lpFile,lpParameters这两个地址要重定位,在数

据段中。还有两处在我们增加的代码中,一个是push lpExecInfo的lpExecInfo须重定位,第二是call 

ShellExecutexA中的ShellExecutexA也须重定位。
  
  
  5.重定位数据。
   上面介绍了有四处重要重定位。DataDirector(数据目录)的第五个成员指向重定位表,该指针具体位

置是在PE文件头A0h偏移处,重定位表是由许多段串接成,每一段存放这4KB大小的重定位信息。他们以一

个IMAGE_BASE_RELOCATION结构开始,格式如下:

  typedef struct _IMAGE_BASE_RELOCATION{
     DWORD   VirtualAddress;        ;重定位数据开始RVA地址
     DWORD   SizeOfBlock;           ;这一块的大小
     WORD    TypeOffset[];          ;重定位项数
  }IMAGE_BASE_RELOCATION
  
  VirtualAddress:是这一组重定位数据的开始RVA地址。各重定位项的地址加上这个值才是该重定位项完

整的RVA地址。
  SizeOfBlock:是这一结构的大小。
  TypeOffset:是一个数组。数组每项大小为两个字节,共16位。它又分为高4位与低12位,高4位代表重

定位类型,TypeOffset高4位一般为0003;低12位是重定位地址,它与VirtualAddress相加即是指向PE映

像中需要修改的地址数据的指针。
  举个例子:
      我这里来重定位lpExecInfo的地址。他在代码段中4e2h中用到。4e2h(raw)=10e2(RVA)

  000004e0h: 68 60 30 40 00 FF 15 7E 21 40 00 C3 00 00 00 00 ; h`0@..~!@.?...
  
  看看程序中的重定位表:
  00000a00h: 00 10 00 00 20 00 00 00 03 30 08 30 10 30 2F 30 ; .... ....0.0.0/0
  00000a10h: 34 30 83 30 8E 30 96 30 9F 30 BE 30 C5 30 00 00 ; 40??????..
  00000a20h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
  现在已经有了一个重定位表项,他的VirtualAddress=1000h正好是在代码段中,这里我们就不需要在构

造个IMAGE_BASE_RELOCATION直接改这个就行了。SizeOfBlock=20h(有12个重定位数据,(20h-

08h)/0x2=12),数数 后面的TypeOffset只有11个,还可以放下去一个。填什么呢?高4位为003h,低12位

为10e2h-1000h=0e2h,所以整个 数就是30E2h
  
  00000a00h: 00 10 00 00 20 00 00 00 03 30 08 30 10 30 2F 30 ; .... ....0.0.0/0
  00000a10h: 34 30 83 30 8E 30 96 30 9F 30 BE 30 C5 30 E2 30 ; 40??????..
  00000a20h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
  
  ShellExecutexA的地址也是在代码段中用到,和lpExecInfo差不多,差别在与如果要增加他就要修改

SizeOfBlock的大小。
  
  数据段中的重定位和上面的有些区别,需要增加个IMAGE_BASE_RELOCATION,其他差不多。修改的重定

位表如下:
  
  00000a00h: 00 10 00 00 22 00 00 00 03 30 08 30 10 30 2F 30 ; ...."....0.0.0/0
  00000a10h: 34 30 83 30 8E 30 96 30 9F 30 BE 30 C5 30 E1 30 ; 40???????
  00000a20h: E7 30 00 30 00 00 0C 00 00 00 70 30 74 30 00 00 ; ?.0......p0t0..
  00000a30h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
  
  最后别忘了修改数据目录中重定位表项的大小:
  00000170h: 00 40 00 00 2E 00 00 00 00 00 00 00 00 00 00 00 ; .@..............
  
  6.完成测试程序。这里用VC来完成。见附件。
  
  

  
      
  
--------------------------------------------------------------------------------
【经验总结】
     文件修改所有的区段。因为把导出表和增加的导入函数表放到了.rdata段的空隙中,所以程序的大

小没有发生变化。增加的导出函数为12个字节。
     完成这个题目后,发觉自己又进一步增加了对PE结构的了解。本题涉及到了对导入表,导出表,重

定位表的结构知识,感觉到出题的花了心思的。
     其实改造过程<<加密与解密>>中有很详细的描述。第一次写这种文章,写的不是很清楚,大家如有

疑问可参考<<加密与解密 >>或和我讨论。写给新手看的,老鸟嘛就飘过吧,欢迎板砖!!
  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                                       2008年10月04日 13:13:06

[培训]内核驱动高级班,冲击BAT一流互联网大厂工 作,每周日13:00-18:00直播授课

上传的附件:
收藏
点赞7
打赏
分享
最新回复 (17)
雪    币: 207
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
lunglungyu 1 2008-10-4 13:48
2
0
看着很舒服。
雪    币: 64
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
songyibug 2008-10-4 13:50
3
0
000008c0h: 43 3A 5C 5C 50 72 6F 67 72 61 6D 20 46 69 6C 65 ; C:\\Program File
  000008d0h: 73 5C 5C 49 6E 74 65 72 6E 65 74 20 45 78 70 6C ; s\\Internet Expl
  000008e0h: 6F 72 65 72 5C 5C 69 65 78 70 6C 6F 72 65 2E 65 ; orer\\iexplore.e
  000008f0h: 78 65 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; xe..............
  00000900h: 68 74 74 70 3A 2F 2F 62 62 73 2E 70 65 64 69 79 ; http://bbs.pediy
  00000910h: 2E 63 6F 6D 00 00 00 00 00 00 00 00 00 00 00 00 ; .com............
参数不通用...用explorer.exe http://bbs.pediy.com或cmd start命令
我第一次提交就是用这个参数~~结果说不通用..提交了第二次~
雪    币: 268
活跃值: (95)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
chimney 3 2008-10-4 13:56
4
0
第一次用的是ShellExecuteA但是要压六个参数入栈,太大了,给否了!
雪    币: 64
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
songyibug 2008-10-4 13:59
5
0
如果是D:\Program Files\Internet Explorer\IEXPLORE.EXE..那就不通用了..
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ALT用户 2008-10-4 13:59
6
0
厉害啊!!!
雪    币: 207
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
lunglungyu 1 2008-10-4 14:05
7
0
用ShellExecuteA我做了19
雪    币: 243
活跃值: (11)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
edisonH 3 2008-10-4 14:10
8
0
mov eax, lpExecInfo
push eax
call [eax+xx]
retn
可以再减少2字节
雪    币: 268
活跃值: (95)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
chimney 3 2008-10-4 14:12
9
0
       
        dec  eax         48
        PUSH 05         6A 05
        PUSH EAX       50
        PUSH EAX       50
        PUSH              68 38 30 40 00                 ;www.pediy.com
        PUSH EAX      50
        PUSH EAX      50
        CALL              FF 15 1C 50 40 00        ;ShellExecute 地址       
        ret              c3

    这样可达到19个,不知道还能不能在少了?
雪    币: 2134
活跃值: (14)
能力值: (RANK:170 )
在线值:
发帖
回帖
粉丝
Aker 4 2008-10-4 14:13
10
0
说的很详细,支持下:)
雪    币: 709
活跃值: (2265)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
sudami 25 2008-10-4 14:17
11
0
很详细,谢谢分享,学习之~~~
雪    币: 296
活跃值: (89)
能力值: ( LV15,RANK:340 )
在线值:
发帖
回帖
粉丝
木桩 8 2008-10-4 14:47
12
0
之前手动添加引入函数失败N次,无奈拿出LoadPE加了个节。现在看了LZ的文章,终于明白该怎么偷懒了 ,还有移位这种做法啊!
又学到一招,万分感谢!
雪    币: 231
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qdk 2008-10-4 14:50
13
0
我是现在拷贝的dll里面用lordPE添加导入函数,然后把添加的节里面的内容拷到
要修改的dll里面。

对照着文档说明修改几处地址就可以了
雪    币: 107
活跃值: (1437)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
frozenrain 2008-10-4 15:25
14
0
牛人就是牛人 真精彩 又学到了不少
雪    币: 220
活跃值: (50)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
manbug 2008-10-4 22:16
15
0
分析得很详细。学习
雪    币: 381
活跃值: (130)
能力值: ( LV13,RANK:330 )
在线值:
发帖
回帖
粉丝
HSQ 8 2008-10-4 22:46
16
0
很详细,学习
雪    币: 251
活跃值: (25)
能力值: ( LV9,RANK:290 )
在线值:
发帖
回帖
粉丝
newjueqi 7 2008-10-15 19:21
17
0
牛人,学习了
雪    币: 218
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lmbi 2008-10-22 23:47
18
0
学习了,谢谢
游客
登录 | 注册 方可回帖
返回