查看原文
其他

栈溢出原理与实践之读书笔记

瑞皇 看雪学苑 2022-07-01


本文为看雪论坛优秀‍‍‍文章
看雪论坛作者ID:瑞皇


新的一年想把0day漏洞安全这本书读完,会一步一步踏踏实实的学完,和大家共享笔记。


环境我想用vs2019运行,老的编译器很多人不用了,渐渐的会被淘汰。用新的编译器可以锻炼下编译选项的功底。

我的笔记是配合书写的,理论部分会少用笔墨,着重在实验上。


1


基础知识


这一部分书上讲解的足够了,如果暂时看不懂,多看看就好。功夫不负有心人,迟早学会的事情。


2


栈溢出原理与实践



2.1 系统栈的工作原理

这一部分书上讲解的足够了,如果暂时看不懂,多看看就好。功夫不负有心人,迟早学会的事情。


2.2 修改邻接变量

运行环境:

VS2019 X86 Debug


运行设置:

属性->c/c++->常规->sdl检查关闭
属性->c/c++->代码运行->基本运行时检查->关闭【堆栈帧 (/RTCs)】
然后运行代码,输入qqqqqqqq即,可完成简单的溢出覆盖。
#include <stdio.h>#include <stdlib.h>#include <string.h>#define PASSWORD "1234567"int verify_password(char* password){ int authenticated; char buffer[8];// add local buffto be overflowed authenticated = strcmp(password, PASSWORD); strcpy(buffer, password);//over flowed here! return authenticated;}void main(){ int valid_flag = 0; char password[1024]; while (1) { printf("please input password: "); scanf("%s", password); valid_flag = verify_password(password); if (valid_flag) { printf("incorrect password!\n\n"); } else { printf("Congratulation! You have passed the verification!\n"); break; } }}

实验情况:SDL关闭和头文件的增加为了让程序代码跑起来。


堆栈帧 (/RTCs)的关闭,则是为了让程序不在运行时检查程序。

如果不关闭堆栈帧 (/RTCs),原本buffer[8]会因为检查多出8字节。然后程序报错,我原本以为时字符对齐的问题,检查后发现不是。是运行时检查的问题。


 
关闭堆栈帧 (/RTCs)的汇编代码:
int verify_password(char* password){004015F0 push ebp 004015F1 mov ebp,esp 004015F3 sub esp,50h 004015F6 mov eax,dword ptr [__security_cookie (0407004h)] 004015FB xor eax,ebp 004015FD mov dword ptr [ebp-4],eax 00401600 push ebx 00401601 push esi 00401602 push edi 00401603 mov ecx,offset _06E17EB3_Test@cpp (0409008h) 00401608 call @__CheckForDebuggerJustMyCode@4 (0401285h) int authenticated; char buffer[8];// add local buffto be overflowed authenticated = strcmp(password, PASSWORD);0040160D push offset string "1234567" (0405B30h) 00401612 mov eax,dword ptr [password] 00401615 push eax 00401616 call _strcmp (040103Ch) 0040161B add esp,8 0040161E mov dword ptr [authenticated],eax strcpy(buffer, password);//over flowed here!00401621 mov eax,dword ptr [password] 00401624 push eax 00401625 lea ecx,[buffer] 00401628 push ecx 00401629 call _strcpy (040119Ah) 0040162E add esp,8 return authenticated;00401631 mov eax,dword ptr [authenticated] }00401634 pop edi 00401635 pop esi 00401636 pop ebx 00401637 mov ecx,dword ptr [ebp-4] 0040163A xor ecx,ebp 0040163C call @__security_check_cookie@4 (040111Dh) 00401641 mov esp,ebp 00401643 pop ebp 00401644 ret

开启堆栈帧 (/RTCs)的汇编代码:
int verify_password(char* password){00E317A0 push ebp 00E317A1 mov ebp,esp 00E317A3 sub esp,0E0h 00E317A9 push ebx 00E317AA push esi 00E317AB push edi 00E317AC lea edi,[ebp-20h] 00E317AF mov ecx,8 00E317B4 mov eax,0CCCCCCCCh 00E317B9 rep stos dword ptr es:[edi] 00E317BB mov eax,dword ptr [__security_cookie (0E3A004h)] 00E317C0 xor eax,ebp 00E317C2 mov dword ptr [ebp-4],eax 00E317C5 mov ecx,offset _06E17EB3_Test@cpp (0E3C008h) 00E317CA call @__CheckForDebuggerJustMyCode@4 (0E3132Fh) int authenticated; char buffer[8];// add local buffto be overflowed authenticated = strcmp(password, PASSWORD);00E317CF push offset string "1234567" (0E37B30h) 00E317D4 mov eax,dword ptr [password] 00E317D7 push eax 00E317D8 call _strcmp (0E31046h) 00E317DD add esp,8 00E317E0 mov dword ptr [authenticated],eax strcpy(buffer, password);//over flowed here!00E317E3 mov eax,dword ptr [password] 00E317E6 push eax 00E317E7 lea ecx,[buffer] 00E317EA push ecx 00E317EB call _strcpy (0E31212h) 00E317F0 add esp,8 return authenticated;00E317F3 mov eax,dword ptr [authenticated] }00E317F6 push edx 00E317F7 mov ecx,ebp 00E317F9 push eax 00E317FA lea edx,ds:[0E31828h] 00E31800 call @_RTC_CheckStackVars@8 (0E311EFh) 00E31805 pop eax 00E31806 pop edx 00E31807 pop edi 00E31808 pop esi 00E31809 pop ebx 00E3180A mov ecx,dword ptr [ebp-4] 00E3180D xor ecx,ebp 00E3180F call @__security_check_cookie@4 (0E31154h) 00E31814 add esp,0E0h 00E3181A cmp ebp,esp 00E3181C call __RTC_CheckEsp (0E31253h) 00E31821 mov esp,ebp 00E31823 pop ebp 00E31824 ret 00E31825 nop dword ptr [eax] 00E31828 add dword ptr [eax],eax 00E3182A add byte ptr [eax],al 00E3182C xor byte ptr [eax],bl 00E3182E jecxz __$EncStackInitStart+84h (0E31830h) 00E31830 in al,0FFh 00E31832 ?? ??????}00E31833 dec dword ptr [eax] 00E31835 add byte ptr [eax],al 00E31837 add byte ptr [eax+ebx],bh 00E3183A jecxz __$EncStackInitStart+90h (0E3183Ch) 00E3183C bound esi,qword ptr [ebp+66h] 00E3183F jb 00001843

通过代码的比较,我们可以发现,函数使用@_RTC_CheckStackVars@8进行检测,跟进去发现该函数容纳了两个_RTC_CheckStackVars.

当检测不是0CCCCCCCCh的时候,会报错,并进入_RTC_StackFailure函数。
006E1DD4 mov ecx,dword ptr [ebx+4] 006E1DD7 mov eax,dword ptr [frame] 006E1DDA mov edx,dword ptr [ecx+edi] 006E1DDD cmp dword ptr [edx+eax-4],0CCCCCCCCh 006E1DE5 jne _RTC_CheckStackVars+39h (06E1DF9h) 006E1DE7 mov eax,dword ptr [ecx+edi+4] 006E1DEB add eax,edx 006E1DED mov edx,dword ptr [frame] 006E1DF0 cmp dword ptr [eax+edx],0CCCCCCCCh 006E1DF7 je _RTC_CheckStackVars+49h (06E1E09h) 006E1DF9 push dword ptr [ecx+edi+8] 006E1DFD mov eax,dword ptr [ebp+4] 006E1E00 push eax 006E1E01 call _RTC_StackFailure (06E1352h)


2.3 控制程序的执行流程

运行环境:VS2019 X86 Debug


运行设置:

属性->c/c++->常规->sdl检查关闭
属性->c/c++->代码运行->基本运行时检查->关闭【堆栈帧 (/RTCs)】


实验情况:

修改上述配置后,发现无法正常运行,单步调试发现fopen断点出现问题。搜索查询后发现有些编译器不支持rw+的格式。这里我们将rw+修改为r+即可正确运行。r和r+的区别是r+拥有写权限。我大胆猜测,rw+不支持的原因是功能设计上的重复。

在这里我们运行还会遇到一个问题:

我们需要把栈保护天使GS关闭。
属性->c/c++->代码运行->安全检查->关闭【禁用安全检查 (/GS-)】
这个时候代码就会报书上期望我们出现的错误,返回值出现错误。理论方面书上说的已经很全面,这里就简单画个图。

最终结果如下图,符合书上预期:


2.4 向进程中植入代码

运行环境:VS2019 X86 Debug


运行设置:

属性->c/c++->常规->sdl检查关闭
属性->c/c++->代码运行->基本运行时检查->关闭【堆栈帧 (/RTCs)】
属性->c/c++->代码运行->安全检查->关闭【禁用安全检查 (/GS-)】


实验情况:

创建一个文件,password.txt,构造shellcode。这里按照书上就好,然后看下面的步骤。

配置需要进行修改,因为要静态获取buffer地址,aslr随机基址要关闭,因为要在数据区执行代码,数据执行保护(DEP)也要关闭。
属性->链接器->高级->随机基址->否
属性->链接器->高级->数据执行保护(DEP)->否
 
password.txt,有两个地方需要修改,根据书中描述。
一个是Messagebox的地址,在下图中会讲解如何寻找。
一个是buffer的地址,下文中也会详细介绍细节。

我们在程序中添加messagebox,这样我们的代码就会调用User32.dll,我们使用dependency可以获取该模块函数地址。
根据书中方法,计算正确值。
>>> hex(0x69E00000+0x83670)'0x69e83670'

但是可以看到,程序并不能正确运行,这问题常出在现在的windows操作系统里,优先基址常常不是实际加载地址,在PE格式中DLL会给出一个优先加载地址,当程序并未占用该地址时,优先按照该基址进行计算。占用的话,会重新申请空间。

问题发生了,如何解决,这里使用Ollydbg查看模块地址。

 
下面是Ollydbg的正确解决方案

步骤1:点击E字母


步骤2:找到User32,此时使用该模块地址计算,可以获得正确地址,成功弹出messagebox

如果不想算,就按一下User32,快捷键ctrl+n,找到messageboxA一样可以。
>>> hex(0x75cc0000+0x83670)'0x75d43670'




 


看雪ID:瑞皇

https://bbs.pediy.com/user-home-848111.htm

*本文由看雪论坛 瑞皇 原创,转载请注明来自看雪社区



# 往期推荐

1.符号执行挖掘开源库命令注入

2.CVE-2021-4034 pkexec本地提权漏洞复现与原理分析

3.Microsoft Windows提权漏洞CVE-2013-3660 x86、x64双平台分析

4.Flutter应用逆向分析相关讨论

5.XX弹幕投票助手分析

6.什么是runC?






球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存