-
-
[原创]看雪论坛.腾讯公司2008软件安全技术竞赛第一题分析过程
-
发表于:
2008-10-4 13:41
13168
-
[原创]看雪论坛.腾讯公司2008软件安全技术竞赛第一题分析过程
【文章标题】: 看雪论坛.腾讯公司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
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)