前言 子函数在运行过程中,由于存在危险函数,在数据移动过程中,突破了函数栈空间的边界,破坏了母函数栈空间中的内容,导致程序运行状态异常
分析环境和工具 Windows XP Professional、VC 6.0、IDA、OllyICE
StackOverflow程序结构
Main函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 .text:00401090 .text:00401090 ; =============== S U B R O U T I N E ======================================= .text:00401090 .text:00401090 ; Attributes: bp-based frame .text:00401090 .text:00401090 ; int __cdecl main(int argc, const char **argv, const char **envp) .text:00401090 _main proc near ; CODE XREF: _main_0j .text:00401090 .text:00401090 var_444 = byte ptr -444h .text:00401090 Str1 = byte ptr -404h .text:00401090 var_4 = dword ptr -4 .text:00401090 argc = dword ptr 8 .text:00401090 argv = dword ptr 0Ch .text:00401090 envp = dword ptr 10h .text:00401090 .text:00401090 push ebp .text:00401091 mov ebp, esp .text:00401093 sub esp, 444h .text:00401099 push ebx .text:0040109A push esi .text:0040109B push edi .text:0040109C lea edi, [ebp+var_444] .text:004010A2 mov ecx, 111h .text:004010A7 mov eax, 0CCCCCCCCh .text:004010AC rep stosd .text:004010AE mov [ebp+var_4], 0 .text:004010B5 .text:004010B5 loc_4010B5: ; CODE XREF: _main:loc_401115j .text:004010B5 mov eax, 1 .text:004010BA test eax, eax .text:004010BC jz short loc_401117 .text:004010BE push offset Format ; "please input password: " .text:004010C3 call _printf .text:004010C8 add esp, 4 .text:004010CB lea ecx, [ebp+Str1] .text:004010D1 push ecx .text:004010D2 push offset aS ; "%s" .text:004010D7 call _scanf .text:004010DC add esp, 8 .text:004010DF lea edx, [ebp+Str1] .text:004010E5 push edx ; Str1 .text:004010E6 call j_?verify_password@@YAHPAD@Z ; verify_password(char *) .text:004010EB add esp, 4 .text:004010EE mov [ebp+var_4], eax .text:004010F1 cmp [ebp+var_4], 0 .text:004010F5 jz short loc_401106 .text:004010F7 push offset aIncorrectPassw ; "incorrect password!\n\n" .text:004010FC call _printf .text:00401101 add esp, 4 .text:00401104 jmp short loc_401115 .text:00401106 ; --------------------------------------------------------------------------- .text:00401106 .text:00401106 loc_401106: ; CODE XREF: _main+65j .text:00401106 push offset aCongratulation ; "congratulations! you have passed the ve"... .text:0040110B call _printf .text:00401110 add esp, 4 .text:00401113 jmp short loc_401117 .text:00401115 ; --------------------------------------------------------------------------- .text:00401115 .text:00401115 loc_401115: ; CODE XREF: _main+74j .text:00401115 jmp short loc_4010B5 .text:00401117 ; --------------------------------------------------------------------------- .text:00401117 .text:00401117 loc_401117: ; CODE XREF: _main+2Cj .text:00401117 ; _main+83j .text:00401117 push offset Command ; "pause" .text:0040111C call _system .text:00401121 add esp, 4 .text:00401124 pop edi .text:00401125 pop esi .text:00401126 pop ebx .text:00401127 add esp, 444h .text:0040112D cmp ebp, esp .text:0040112F call __chkesp .text:00401134 mov esp, ebp .text:00401136 pop ebp .text:00401137 retn .text:00401137 _main endp .text:00401137 .text:00401137 ; ---------------------------------------------------------------------------
上图所示,为main函数创建函数栈空间。
1 mov dword ptr [ebp-4], 0
创建局部变量,并置为零
上图部分,开始进入循环
1 2 3 mov eax, 1 test eax, eax jz short loc_401117
eax为1,始终无法跳出循环,可判断是while(1)循环
1 2 3 push 00427090 call printf add esp, 4
调用printf,并输出数据段存储的数据 “please input password: ”
1 2 3 4 5 6 7 8 lea ecx, dword ptr [ebp-404] push ecx push offset aS ; "%s" call _scanf add esp, 8 lea edx, dword ptr [ebp-404] push edx call 00401005
调用scanf,接收输入的数据,将接收的数据存入 esp+8 的位置,接着将 ebp-404 中所存数据存入 edx 中,并传给接下来调用的子函数 verify_password ,ebp-404 中存的是地址,所以传给子函数的也是地址
接下来就进入子函数verify_password
从子函数会到主函数,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 add esp, 4 mov dword ptr [ebp-4], eax cmp dword ptr [ebp-4], 0 je short 00401106 push 00427070 ; /format = "incorrect password!",LF,LF,"" call printf ; \printf add esp, 4 jmp short 00401115 push 00427030 ; /format = "congratulations! you have passed the verification",LF,LF,"" call printf ; \printf add esp, 4 jmp short 00401117 jmp short 004010B5 push 00427028 ; /command = "pause" call system ; \system add esp, 4 pop edi pop esi pop ebx add esp, 444 cmp ebp, esp call _chkesp mov esp, ebp pop ebp retn
esp加4
将从子函数返回的值——eax中存的数据,存到ebp-4中,再将ebp-4中的值与0作比较。
若结果等于零,跳转至00401106的位置,对应“push 00427030”,从而调用printf函数,输出”congratulations! you have passed the verification”,esp加4,接着跳转至0040117,对应“push 00427028”,接着调用system函数,执行“pause”指令,终结循环,接着销毁函数栈,退出main函数。
若结果不等于零,继续,调用函数printf,输出“incorrect password!”,esp加4,跳转至00401115,对应“jmp short 004010B5”,又回到循环开始,继续循环。
verify_password函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 .text:00401020 .text:00401020 ; =============== S U B R O U T I N E ======================================= .text:00401020 .text:00401020 ; Attributes: bp-based frame .text:00401020 .text:00401020 ; int __cdecl verify_password(char *Str1) .text:00401020 ?verify_password@@YAHPAD@Z proc near ; CODE XREF: verify_password(char *)j .text:00401020 .text:00401020 var_4C = byte ptr -4Ch .text:00401020 Dest = byte ptr -0Ch .text:00401020 var_4 = dword ptr -4 .text:00401020 Str1 = dword ptr 8 .text:00401020 .text:00401020 push ebp .text:00401021 mov ebp, esp .text:00401023 sub esp, 4Ch .text:00401026 push ebx .text:00401027 push esi .text:00401028 push edi .text:00401029 lea edi, [ebp+var_4C] .text:0040102C mov ecx, 13h .text:00401031 mov eax, 0CCCCCCCCh .text:00401036 rep stosd .text:00401038 push offset Str2 ; "1234567" .text:0040103D mov eax, [ebp+Str1] .text:00401040 push eax ; Str1 .text:00401041 call _strcmp .text:00401046 add esp, 8 .text:00401049 mov [ebp+var_4], eax .text:0040104C mov ecx, [ebp+Str1] .text:0040104F push ecx ; Source .text:00401050 lea edx, [ebp+Dest] .text:00401053 push edx ; Dest .text:00401054 call _strcpy .text:00401059 add esp, 8 .text:0040105C mov eax, [ebp+var_4] .text:0040105F pop edi .text:00401060 pop esi .text:00401061 pop ebx .text:00401062 add esp, 4Ch .text:00401065 cmp ebp, esp .text:00401067 call __chkesp .text:0040106C mov esp, ebp .text:0040106E pop ebp .text:0040106F retn .text:0040106F ?verify_password@@YAHPAD@Z endp .text:0040106F .text:0040106F ; ---------------------------------------------------------------------------
创建子函数的函数栈空间
1 2 3 4 5 6 push 0042701C ; /s2 = "1234567" mov eax, dword ptr [ebp+8] ; | push eax ; |s1 call strcmp ; \strcmp add esp, 8 mov dword ptr [ebp-4], eax
将0042701C 中存的数据压入,再将ebp+8的位置存的数据通过eax压入,再调用strcmp函数对两者进行比较,将比较结果存入eax中,由于我输入的数据是123456,在与1234567比较后,eax中所存的值为FFFFFFFF,若我输入的值为1254,则eax中所存的值为00000001,若我输入的数据为1234567,则eax中所存数据为00000000。
将esp加8
将比较结果从eax中取出,存入ebp-4的位置,也就是子函数verify_password的栈底
1 2 3 4 5 6 mov ecx, dword ptr [ebp+8] push ecx ; /src lea edx, dword ptr [ebp-C] ; | push edx ; |dest call strcpy ; \strcpy add esp, 8
将ebp+8中存的数据存入ecx,再将ebp-C(ebp-8,C->Char)中的值存入edx,然后调用strcpy函数,将拷贝的值存入esp+8的位置
1 2 3 4 5 6 7 8 9 10 mov eax, dword ptr [ebp-4] pop edi pop esi pop ebx add esp, 4C cmp ebp, esp call _chkesp mov esp, ebp pop ebp retn
将ebp-4中所存结果存入eax中(这是用来保存子函数需要返回给主函数的值),然后进行一系列操作销毁函数栈,并执行retn,返回到主函数
栈溢出分析与利用 该程序产生栈溢出的主要原因在于,子函数中采用了strcmp函数,在执行子函数时,会向函数栈的栈底先后存入整型变量authentication的值和char型数组buffer的值,这两个数据存放位置相邻,若通过strcpy函数存入buffer的字符串长度恰好等于buffer数组的长度,由于字符串最后一位还有截断字符,那么,阶段字符会将authentication的值覆盖,这将导致程序功能发生错误,比如原本程序输入“1234567”时才会显示成功,而现在我们只需要在保证authentication的值为00000001的情况下输入任意长度为8的一串数字就能得到显示成功的结果,效果如下图所示:
我们继续研究,就这个程序来说,我们有没有可能,利用栈溢出漏洞,做点其他事,比如弹出个cmd啥的,这一点由于个人能力有限,便向能力强的同学请教,得到以下利用方式:
其中“^\”是ctrl+\,“^Q”是ctrl+Q,最后一个符号是@
在OllyICE中分析过程,可以看到,在执行完strcmp函数后,子函数verify_password栈底附近的变化,如下图所示
可以看到,上述一串字符,覆盖至0012FB28的位置,导致该位置存的地址发生改变,改变之前,该地址指向主函数中执行完子函数后的位置,改变后的地址指向主函数中执行system的位置,也就是0040111C,从而执行了系统命令,达到弹出cmd的效果。