前面我们介绍在关闭/GS和DEP的情况下,如何利用栈溢出来实现exploit。下面我们会开启/GS,以及介绍突破/GS的常用手法。
我们先开启/GS,编译一个build,再试试原先的exploit,看看会有什么问题。
使用原先的msg.dat会导致程序crash,从下面的分析可以看出,WinDBG是可以给出栈溢出的信息的。具体原因是由于开启/GS选项以后,会在return之前,调用__security_check_cookie检测栈上的cookie是否被修改,如果发现被修改,就认为是栈溢出,抛异常。
CommandLine: C:\Users\Administrator\Desktop\stack_overflow\test_x86_with_gs_no_dep.exe msg.dat
Starting directory: C:\Users\Administrator\Desktop\stack_overflow
************* Symbol Path validation summary **************
Response Time (ms) Location
Deferred SRV*C:\Symbol\web_symbol*http://msdl.microsoft.com/download/symbols
Symbol search path is: SRV*C:\Symbol\web_symbol*http://msdl.microsoft.com/download/symbols
Executable search path is:
ModLoad: 013b0000 013b6000 test_x86_with_gs_no_dep.exe
ModLoad: 77cb0000 77e30000 ntdll.dll
ModLoad: 75a30000 75b40000 C:\Windows\syswow64\kernel32.dll
ModLoad: 76160000 761a6000 C:\Windows\syswow64\KERNELBASE.dll
ModLoad: 73590000 73666000 C:\Windows\SysWOW64\MSVCR110.dll
STATUS_STACK_BUFFER_OVERRUN encountered
(814.5c0): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=013b2108 ecx=75a801b8 edx=001af851 esi=00000000 edi=00000000
eip=75a7ff99 esp=001afa98 ebp=001afb14 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
kernel32!UnhandledExceptionFilter+0x5f:
75a7ff99 cc int 3
0:000> kn
# ChildEBP RetAddr
00 001afb14 736300f1 kernel32!UnhandledExceptionFilter+0x5f
01 001afb20 013b138e MSVCR110!__crtUnhandledException+0x14
02 001afb30 013b14a5 test_x86_with_gs_no_dep!__raise_securityfailure+0x1d
03 001afe60 013b10eb test_x86_with_gs_no_dep!__report_gsfailure+0xf7
04 001afe8c 0082e8fc test_x86_with_gs_no_dep!main+0xeb
0:000> ub 013b10eb
test_x86_with_gs_no_dep!main+0xd4 [z:\d_disk\workspace\vs_2012\test\stack_overflow.cpp @ 19]:
013b10d4 ff15a4203b01 call dword ptr [test_x86_with_gs_no_dep!_imp__printf (013b20a4)]
013b10da 8b4dfc mov ecx,dword ptr [ebp-4]
013b10dd 83c438 add esp,38h
013b10e0 33cd xor ecx,ebp
013b10e2 5b pop ebx
013b10e3 33c0 xor eax,eax
013b10e5 5e pop esi
013b10e6 e804000000 call test_x86_with_gs_no_dep!__security_check_cookie (013b10ef)
绕过Stack Cookie的方式也有很多,比较简单稳定的方式是覆写SEH hander。首先需要了解SEH的原理,网上资料很多,这里推荐参考《软件调试》第24章的内容。
下面直接从利用调试器看看SEH 是什么,以及如何存放的。
下面列出三种查看异常处理链的方式:
- 使用!exchain
0:000> !exchain
0032fd04: test_x86_no_dep!_except_handler4+0 (01291749)
CRT scope 0, filter: test_x86_no_dep!__tmainCRTStartup+115 (012912de)
func: test_x86_no_dep!__tmainCRTStartup+129 (012912f2)
0032fd50: ntdll!_except_handler4+0 (77d271d5)
CRT scope 0, filter: ntdll!__RtlUserThreadStart+2e (77d274b0)
func: ntdll!__RtlUserThreadStart+63 (77d290cb)
- 使用FS段寄存器
0:000> ?poi(fs:[0])
Evaluate expression: 3341572 = 0032fd04
- 使用TEB
0:000> !teb
TEB at 7efdd000
ExceptionList: 0032fd04
StackBase: 00330000
StackLimit: 0032e000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7efdd000
EnvironmentPointer: 00000000
ClientId: 00000390 . 00000204
RpcHandle: 00000000
Tls Storage: 7efdd02c
PEB Address: 7efde000
LastErrorValue: 0
LastStatusValue: c0000139
Count Owned Locks: 0
HardErrorMode: 0
上面使用!exchain列出的exception list,也可以使用纯手工的方式搜索出来。
0:000> dt _EXCEPTION_REGISTRATION_RECORD
test_x86_no_dep!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : Ptr32 _EXCEPTION_DISPOSITION
0:000> dt _EXCEPTION_REGISTRATION_RECORD 0032fd04
test_x86_no_dep!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0x0032fd50 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x01291749 _EXCEPTION_DISPOSITION test_x86_no_dep!_except_handler4+0
0:000> dt _EXCEPTION_REGISTRATION_RECORD 0x0032fd50
test_x86_no_dep!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x77d271d5 _EXCEPTION_DISPOSITION ntdll!_except_handler4+0
从栈地址的信息可以看出,SEH 是存放在栈上的。
0:000> r esp
esp=0032fcd8
在开始编写exploit之前,重新编译测试程序,这一次,将/SAFESEH option关闭。目的是为了简化exploit的难度,因为/SAFESEH是防止覆写SEH的有效手段,当然,这种手段也是可以被绕过的,以后,有时间会介绍。
为了方便说明SEH的问题,我们使用下面的这段code编译一个exe来演示:
#include <cstdio>
int main(int argc, char** argv) {
if (argc != 2) {
printf("Usage:\n test.exe file_path");
return -1;
}
char msg[32] = {0};
printf("Reading msg from file...\n");
FILE *f = fopen(argv[1], "rb");
if (!f) {
return -1;
}
fseek(f, 0L, SEEK_END);
long bytes = ftell(f);
fseek(f, 0L, SEEK_SET);
int pos = 0;
while (pos < bytes) {
int len = bytes - pos > 200 ? 200 : bytes - pos;
fread(msg + pos, 1, len, f);
pos += len;
}
fclose(f);
printf("%s\n", msg);
return 0;
}
使用10000个’a’填充msg.dat,看看有什么情况发生,结果程序crash,而且EIP被修改为0x61616161,看来我们是可以通过栈溢出控制EIP的。
(568.4e0): Access violation - code c0000005 (!!! second chance !!!)
eax=00000000 ebx=00000000 ecx=61616161 edx=7737b46d esi=00000000 edi=00000000
eip=61616161 esp=0017127c ebp=0017129c iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
61616161 ?? ???
为什么会出现这种crash?下面我们开始分析一下。
先看看这个函数的反汇编代码:
.text:00401000 ; =============== S U B R O U T I N E =======================================
.text:00401000
.text:00401000 ; Attributes: bp-based frame
.text:00401000
.text:00401000 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401000 _main proc near ; CODE XREF: __tmainCRTStartup+F8p
.text:00401000
.text:00401000 var_28 = dword ptr -28h
.text:00401000 msg = byte ptr -24h
.text:00401000 var_4 = dword ptr -4
.text:00401000 argc = dword ptr 8
.text:00401000 argv = dword ptr 0Ch
.text:00401000 envp = dword ptr 10h
.text:00401000
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 sub esp, 28h
.text:00401006 mov eax, ___security_cookie
.text:0040100B xor eax, ebp
.text:0040100D mov [ebp+var_4], eax
.text:00401010 cmp [ebp+argc], 2
.text:00401014 push esi
.text:00401015 mov esi, [ebp+argv]
.text:00401018 jz short loc_40103A
.text:0040101A push offset Format ; "Usage:\n test.exe file_path"
.text:0040101F call ds:__imp__printf
.text:00401025 add esp, 4
.text:00401028 or eax, 0FFFFFFFFh
.text:0040102B pop esi
.text:0040102C mov ecx, [ebp+var_4]
.text:0040102F xor ecx, ebp ; cookie
.text:00401031 call @__security_check_cookie@4 ; __security_check_cookie(x)
.text:00401036 mov esp, ebp
.text:00401038 pop ebp
.text:00401039 retn
.text:0040103A ; ---------------------------------------------------------------------------
.text:0040103A
.text:0040103A loc_40103A: ; CODE XREF: _main+18j
.text:0040103A xorps xmm0, xmm0
.text:0040103D push ebx
.text:0040103E push offset aReadingMsgFrom ; "Reading msg from file...\n"
.text:00401043 mov [ebp+msg], 0
.text:00401047 movq qword ptr [ebp+msg+1], xmm0
.text:0040104C movq qword ptr [ebp+msg+9], xmm0
.text:00401051 movq qword ptr [ebp+msg+11h], xmm0
.text:00401056 mov dword ptr [ebp+msg+19h], 0
.text:0040105D mov word ptr [ebp+msg+1Dh], 0
.text:00401063 mov [ebp+msg+1Fh], 0
.text:00401067 call ds:__imp__printf
.text:0040106D push offset Mode ; "rb"
.text:00401072 push dword ptr [esi+4] ; Filename
.text:00401075 call ds:__imp__fopen
.text:0040107B mov ebx, eax
.text:0040107D add esp, 0Ch
.text:00401080 test ebx, ebx
.text:00401082 jnz short loc_401097
.text:00401084 pop ebx
.text:00401085 or eax, 0FFFFFFFFh
.text:00401088 pop esi
.text:00401089 mov ecx, [ebp+var_4]
.text:0040108C xor ecx, ebp ; cookie
.text:0040108E call @__security_check_cookie@4 ; __security_check_cookie(x)
.text:00401093 mov esp, ebp
.text:00401095 pop ebp
.text:00401096 retn
.text:00401097 ; ---------------------------------------------------------------------------
.text:00401097
.text:00401097 loc_401097: ; CODE XREF: _main+82j
.text:00401097 mov esi, ds:__imp__fseek
.text:0040109D push edi
.text:0040109E push 2 ; Origin
.text:004010A0 push 0 ; Offset
.text:004010A2 push ebx ; File
.text:004010A3 call esi ; __imp__fseek
.text:004010A5 push ebx ; File
.text:004010A6 call ds:__imp__ftell
.text:004010AC push 0 ; Origin
.text:004010AE push 0 ; Offset
.text:004010B0 push ebx ; File
.text:004010B1 mov [ebp+var_28], eax
.text:004010B4 call esi ; __imp__fseek
.text:004010B6 mov eax, [ebp+var_28]
.text:004010B9 add esp, 1Ch
.text:004010BC xor edi, edi
.text: