安全矩阵

 找回密码
 立即注册
搜索
查看: 2942|回复: 0

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

[复制链接]

141

主题

153

帖子

517

积分

高级会员

Rank: 4

积分
517
发表于 2022-3-1 22:22:29 | 显示全部楼层 |阅读模式
栈溢出原理与实践之读书笔记[color=rgba(0, 0, 0, 0.3)]瑞皇 [url=]看雪学苑[/url] [color=rgba(0, 0, 0, 0.3)]2022-03-01 17:59

本文为看雪论坛优秀文章
看雪论坛作者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'


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-24 04:48 , Processed in 0.015962 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表