看雪论坛
发新帖
8

[技术专题]软件漏洞分析入门_4_初级栈溢出C_修改程序流程

failwest 2007-12-15 01:01 28906
第4讲  初级栈溢出C

To be the apostrophe which changed “Impossible” into “I’m possible”
—— failwest

没有星星的夜里,我用知识吸引你

上节课没有操练滴东西,不少蠢蠢欲动的同学肯定已经坐不住了。悟空,不要猴急,下面的两堂课都是实践课,用来在实践中深入体会上节课中的知识,并且很有趣味性哦

        信息安全技术是一个对技术性要求极高的领域,除了扎实的计算机理论基础外、更重要的是优秀的动手实践能力。在我看来,不懂二进制就无从谈起安全技术。

        缓冲区溢出的概念我若干年前已经了然于胸,不就是淹个返回地址把CPU指到缓冲区的shellcode去么。然而当我开始动手实践的时候,才发现实际中的情况远远比原理复杂。

        国内近年来对网络安全的重视程度正在逐渐增加,许多高校相继成立了“信息安全学院”或者设立“网络安全专业”。科班出身的学生往往具有扎实的理论基础,他们通晓密码学知识、知道PKI体系架构,但要谈到如何真刀实枪的分析病毒样本、如何拿掉PE上复杂的保护壳、如何在二进制文件中定位漏洞、如何对软件实施有效的攻击测试……能够做到的人并不多。

        虽然每年有大量的网络安全技术人才从高校涌入人力市场,真正能够满足用人单位需求的却聊聊无几。捧着书本去做应急响应和风险评估是滥竽充数的作法,社会需要的是能够为客户切实解决安全风险的技术精英,而不是满腹教条的阔论者。

        我所知道的很多资深安全专家都并非科班出身,他们有的学医、有的学文、有的根本没有学历和文凭,但他们却技术精湛,充满自信。

        这个行业属于有兴趣、够执着的人,属于为了梦想能够不懈努力的意志坚定者。如果你是这样的人,请跟着我把这个系列的所有实验全部完成,之后你会发现眼中的软件,程序,语言,计算机都与以前看到的有所不同——因为以前使用肉眼来看问题,我会教你用心和调试器以及手指来重新体验它们。

首先简单复习上节课的内容:

高级语言经过编译后,最终函数调用通过为其开辟栈帧来实现

开辟栈帧的动作是编译器加进去的,高级语言程序员不用在意

函数栈帧中首先是函数的局部变量,局部变量后面存放着函数返回地址

当前被调用的子函数返回时,会从它的栈帧底部取出返回地址,并跳转到那个位置(母函数中)继续执行母函数

我们这节课的思路是,让溢出数组的数据跃过authenticated,一直淹没到返回地址,把这个地址从main函数中分支判断的地方直接改到密码验证通过的分支!

这样当verify_password函数返回时,就会返回到错误的指令区去执行(密码验证通过的地方)

由于用键盘输入字符的ASCII表示范围有限,很多值如0x11,0x12等符号无法直接用键盘输入,所以我们把用于实验的代码在第二讲的基础上稍加改动,将程序的输入由键盘改为从文件中读取字符串。

#include <stdio.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
        int authenticated;
        char buffer[8];
        authenticated=strcmp(password,PASSWORD);
        strcpy(buffer,password);//over flowed here!       
        return authenticated;
}
main()
{
        int valid_flag=0;
        char password[1024];
        FILE * fp;
        if(!(fp=fopen("password.txt","rw+")))
        {
                exit(0);
        }
        fscanf(fp,"%s",password);
        valid_flag = verify_password(password);
        if(valid_flag)
        {
                printf("incorrect password!\n");
        }
        else
        {
                printf("Congratulation! You have passed the verification!\n");
        }
        fclose(fp);
}

        程序的基本逻辑和第二讲中的代码大体相同,只是现在将从同目录下的password.txt文件中读取字符串而不是用键盘输入。我们可以用十六进制的编辑器把我们想写入的但不能直接键入的ASCII字符写进这个password.txt文件。
       
        用VC6.0将上述代码编译链接。我这里使用默认编译选项,BUILD成debug版本。鉴于有些同学反映自己的用的是VS2003和VS2005,我好人做到底,把我build出来的PE一并在附件中双手奉上——没话说了吧!不许不学,不许学不会,不许说难,不许不做实验!呵呵。

要PE的点这里: stack_overflow_ret.rar

在与PE文件同目录下建立password.txt并写入测试用的密码之后,就可以用OllyDbg加载调试了。

停~~~啥是OllyDbg,开玩笑,在这里问啥是Ollydbg分明是不给看雪老大的面子么!如果没有这个调试器的话,去工具版找吧,帖子附件要挂出个OD的话会给被人鄙视的。

        在开始动手之前,我们先理理思路,看看要达到实验目的我们都需要做哪些工作。

        要摸清楚栈中的状况,如函数地址距离缓冲区的偏移量,到底第几个字节能淹到返回地址等。这虽然可以通过分析代码得到,但我还是推荐从动态调试中获得这些信息。
        要得到程序中密码验证通过的指令地址,以便程序直接跳去这个分支执行
        要在password.txt文件的相应偏移处填上这个地址

        这样verify_password函数返回后就会直接跳转到验证通过的正确分支去执行了。

        首先用OllyDbg加载得到的可执行PE文件如图:


图1       

        阅读上图中显示的反汇编代码,可以知道通过验证的程序分支的指令地址为0x00401122。

简单解释一下这段汇编与C语言的对应关系,其实凭着OD给出的注释,就算你没学过汇编语言,读懂也应该没啥问题。

0x00401102处的函数调用就是verify_password函数,之后在0x0040110A处将EAX中的函数返回值取出 ,在0x0040110D处与0比较,然后决定跳转到提示验证错误的分支或提示验证通过的分支。提示验证通过的分支从0x00401122处的参数压栈开始。

啥?用OllyDbg加载后找不到verify_password函数的位置?这个嘛,我这里只说一次啊。

OllyDbg在默认情况下将程序中断在PE装载器开始处,而不是main函数的开始。如果您有兴趣的话可以按F8单步跟踪一下看看在main函数被运行之前,装载器都做了哪些准备工作。一般情况下main函数位于GetCommandLineA函数调用后不远处,并且有明显的特征:在调用之前有3次连续的压栈操作,因为系统要给main传入默认的argc、argv等参数。找到main函数调用后,按F7单步跟入就可以看到真正的代码了。

我相信你,你一定行的,找到了吗?什么?还找不到?好吧,按ctr+g后面输入截图中的地址0x00401102,这回看到了吧。建议你按F2下个断点记住这个位置,别一会儿又在PE里边迷路了。

这步完成后,您应该对这个PE的主要代码有了一个把握了。这才牙长一点指令啊,真正的漏洞要对付的是软件,那个难缠~~~好,不泼冷水了

如果我们把返回地址覆盖成这个地址,那么在0x00401102 处的函数调用返回后,程序将跳转到验证通过的分支,而不是进入0x00401107处分支判断代码。这个过程如下图所示:

       

图2

        通过动态调试,发现栈帧中的变量分布情况基本没变。这样我们就可以按照如下方法构造password.txt中的数据:

        仍然出于字节对齐、容易辨认的目的,我们将“4321”作为一个输入单元。

        buffer[8]共需要2个这样的单元
        第3个输入单元将authenticated覆盖
        第4个输入单元将前栈帧EBP值覆盖
        第5个输入单元将返回地址覆盖

        为了把第5个输入单元的ASCII码值0x34333231修改成验证通过分支的指令地址0x00401122,我们采取如下方式借助16进制编辑工具UltraEdit来完成(0x40,0x11等ASCII码对应的符号很难用键盘输入)。
       
        步骤1:创建一个名为password.txt的文件,并用记事本打开,在其中写入5个“4321”后保存到与实验程序同名的目录下:


图3

步骤2:保存后用UltraEdit_32重新打开,如图:


图4

啥?问啥是UltraEdit?去工具版找吧,多的不得了,这里是看雪!

步骤3:将UltraEdit_32切换到16进制编辑模式,如图:


图5

步骤写到这个份上了,您不会还跟不上吧。

        步骤4:将最后四个字节修改成新的返回地址,注意这里是按照“内存数据”排列的,由于“大顶机”的缘故,为了让最终的“数值数据”为0x00401122,我们需要逆序输入这四个字节。如图:


图6

步骤5:这时我们可以切换回文本模式,最后这四个字节对应的字符显示为乱码:


图7

最终的password.txt我也给你附上。

要txt的点这里:   password.txt

将password.txt保存后,用OllyDbg加载程序并调试,可以看到最终的栈状态如下表所示:

局部变量名        内存地址        偏移3处的值        偏移2处的值        偏移1处的值        偏移0处的值
buffer[0~3]        0x0012FB14        0x31 (‘1’)        0x32 (‘2’)        0x33 (‘3’)        0x34 (‘4’)
buffer[4~7]        0x0012FB18        0x31 (‘1’)        0x32 (‘2’)        0x33 (‘3’)        0x34 (‘4’)
authenticated
(被覆盖前)        0x0012FB1C        0x00        0x00        0x00        0x01
authenticated
(被覆盖后)        0x0012FB1C        0x31 (‘1’)        0x32 (‘2’)        0x33 (‘3’)        0x34 (‘4’)
前栈帧EBP
(被覆盖前)        0x0012FB20        0x00        0x12        0xFF        0x80
前栈帧EBP
(被覆盖后)        0x0012FB20        0x31 (‘1’)        0x32 (‘2’)        0x33 (‘3’)        0x34 (‘4’)
返回地址
(被覆盖前)        0x0012FB24        0x00        0x40        0x11        0x07
返回地址
(被覆盖后)        0x0012FB24        0x00        0x40        0x11        0x22

程序执行状态如图:



        由于栈内EBP等被覆盖为无效值,使得程序在退出时堆栈无法平衡,导致崩溃。虽然如此,我们已经成功的淹没了返回地址,并让处理器如我们设想的那样,在函数返回时直接跳转到了提示验证通过的分支。

同学们,你们成功了么?

最后再总结一下这个实验的内容:
通过Ollydbg调试PE文件确定密码验证成功的分支的指令所处的内存地址为0x00401122
通过调试确定buffer数组距离栈帧中函数返回地址的偏移量
在password.txt相应的偏移处准确的写入0x00401122,当password.txt被读入后会同样准确的把verify_password函数的返回地址从分支判断处修改到0x00401122(密码正确分支)
函数返回时,笨笨的返回到密码正确的地方
程序继续执行,但由于栈被破坏,不再平衡,故出错

试想一下,如果我们在buffer[]中填入一些可执行的机器码,然后用溢出的数据把返回地址指向buffer[],那么函数返回后这些代码是不是就会执行了?

答案是肯定的,下一讲我将用类似的叙述方式,同样手把手的和您一起完成这段机器代码的编写,并把它们准确的布置在password.txt中,这样原本用来读取密码文件的程序读了这样一个精心构造的“黑文件”之后,就会做出一些“出格”的事情了。

明天见,顺便说一下,我一般会在凌晨1点左右发文,想坐沙发的同学注意了呦。
上传的附件:
本主题帖已收到 0 次赞赏,累计¥0.00
最新回复 (90)
1
likunkun 2007-12-15 01:12
2
沙发,沙发
强帖,强帖
学习,学习
黯魂 2007-12-15 01:38
3
板凳!!OH YEAH~~~~
25
sudami 2007-12-15 07:10
4
学习啊~~~
5
szdbg 2007-12-15 08:10
5
坐在前排听课...
8
kanxue 2007-12-15 10:26
6
凌晨还在忙,failwest要注意休息。

大家一起来进步
coollineme 2007-12-15 11:13
7
支持……学习中!failwest请注意休息。
11
火影 2007-12-15 12:03
8
缺少头文件:#include <stdlib.h>
12
yijun8354 2007-12-15 12:04
9
牛人都喜欢半夜动手~~~
wzmooo 2007-12-15 12:06
10
还好在第一排 差点迟到 哈哈
heimei 2007-12-15 12:09
11
哈哈,文章越来越精彩!
Good!
8
Bughoho 2007-12-15 12:11
12
八戒,我来晚了。
8
failwest 2007-12-15 13:06
13
[缺少头文件:#include <stdlib.h>]
什么都不缺啊,为什么要用stdlib.h呢,所有的代码都经过我调试的,如果按我说的指导来做的话,应该不会出问题的

另外,请顺利完成实验的同学在跟贴中举个手或者吱~~~一声,这样可以互相鼓励一起进步:)
8
Bughoho 2007-12-15 13:08
14
吱
8
failwest 2007-12-15 13:12
15
呵呵,希望后面几讲的跟贴能够吱~~吱~~~~~~吱~~~~~~~的吱声一片啊,哈哈

连续吱上两周,基本上就可以入门了
wyfe 2007-12-15 13:33
16
越吱越精彩
wdbxm 2007-12-15 14:37
17
还有#include <string.h>
1
NWMonster 2007-12-15 16:39
18
好啊,学习学习
mentalless 2007-12-15 17:16
19
注册终于到一天时间了,可以发贴了...第一次来这个论坛刚好赶上大哥发完入门A, 这文章对我这种一点基础都没有的人太适合了! 多谢!就是有个疑问...这图片为什么都是链接,必须在新窗口中打开啊...有点不方便.....还有提问就直接跟贴吗?能给回答吗,昨天看的有2个问题,当时发不了贴,现在想了半天也没想起来当时的问题是啥...
8
kanxue 2007-12-15 17:19
20
必须要登陆才能看到图片,本来也想让游客可看图片,可VBB不支持这个,改程序没成功,比较复杂。

如果登陆还是显示链接,那这个可能是论坛的bug,以后升级看看能不能解决。
8
HSQ 2007-12-15 18:41
21
今天的讲课有些晚,我现在才接着学
3
dttom 2007-12-15 20:46
22
--------------------Configuration: YC2 - Win32 Debug--------------------
Compiling...
YC2.CPP
E:\VC\YC2\YC2.CPP(9) : error C2065: 'strcmp' : undeclared identifier
E:\VC\YC2\YC2.CPP(10) : error C2065: 'strcpy' : undeclared identifier
E:\VC\YC2\YC2.CPP(20) : error C2065: 'exit' : undeclared identifier
E:\VC\YC2\YC2.CPP(33) : warning C4508: 'main' : function should return a value; 'void' return type assumed
执行 cl.exe 时出错.

YC2.exe - 1 error(s), 0 warning(s)
直接copy code 编译后出现的错误。
加入#include <stdlib.h>  #include <string.h> 编译通过,上一个例子也是加入#include <string.h>后才通过不知道什么原因?
3
炉子 2007-12-15 21:13
23
我是加上#include <windows.h>然后给main加上一个return 0;
这些都不是主要的事情  就别问了嘛。 hoho

顶一下
ssarg 2007-12-15 21:41
24
不错,接着听下去
8
HSQ 2007-12-15 21:49
25
代码都给你了,自己简单的修改一下都不会,晕
下面的用VC6.0 SP6编译通过的,那去吧.

#include <stdio.h>
#include <windows.h>

#define PASSWORD "1234567"

int verify_password (char *);

void main()
{
	int valid_flag=0;
	char password[1024];
	FILE * fp;
	if(!(fp=fopen("password.txt","rw+")))
	{
		exit(0);
	}
	fscanf(fp,"%s",password);
	valid_flag = verify_password(password);
	if(valid_flag)
	{
		printf("incorrect password!\n");
	}
	else
	{
		printf("Congratulation! You have passed the verification!\n");
	}
	fclose(fp);
}

int verify_password (char *password)
{
	int authenticated;
	char buffer[8];
	authenticated=strcmp(password,PASSWORD);
	strcpy(buffer,password);   //over flowed here!  
	return authenticated;
}
mentalless 2007-12-15 22:08
26
还是不行,清楚cookie再登陆还是没图片只有链接,你能看到图片吗?
mentalless 2007-12-15 22:50
27
大哥能不能给个OllyDbg的入门教程,从这节课开始像我这样的没基础的就跟不上了。。。不知道怎么分析出来的各个函数的地址什么的,前面讲得都非常基础都能听懂。。。这里没基础是应该看什么教程?调试还是什么?谢谢!
还有,这节课的数组还是8个元素啊,没看到44个。。。是怎么回事?
mentalless 2007-12-15 23:05
28
不好意思,刚找到入门文章。。。对我的匆忙提问表示抱歉。。
http://bbs.pediy.com/showthread.php?t=31840
先去学习了。。。
66
KuNgBiM 2007-12-16 04:19
29
iupek 2007-12-16 11:52
30
好喜欢你的文章
5
szdbg 2007-12-16 13:58
31
我一直在没有星星的夜里摸索,如今,终于看到了星光......
3
dttom 2007-12-16 14:15
32
对不起,我没看清楚,在我机器上用了示例的password文件,一直不能正确运行。用ollydby 一调试发现地址有问题,重新做一个password通过测试。
阿南 2007-12-16 15:49
33
看了这个 收获很大,早出2个月就更好了 当时在看The_Shellcoder's_Handbook
可惜里面基础部分和中级部分都是在LINUX上实现的 想装LINUX 可惜硬盘容量不够...挣扎很久
才放弃的.....终于有在WINDOWS上的 基础教程了....特地还原了机器 恢复了删除的VC6

顺便问一下 为什么我把第4个单元 改成正确的EBP 结果 没有溢出?
看到你上面说 因为堆栈不平衡 程序崩溃 所以我试着改了第4个单元
重载以后 发现 第5个单元没有覆盖函数返回的地址...
怎么回事?而且改了以后 TXT里 全部变成乱码
7
壹只老虎 2007-12-16 18:38
34
坐第5排学习!~~~
黑色猎鹰 2007-12-16 18:49
35
明天等着做沙发??
顺便问下,为什么:
仍然出于字节对齐、容易辨认的目的,我们将“4321”作为一个输入单元。

  buffer[8]共需要2个这样的单元
  第3个输入单元将authenticated覆盖
  第4个输入单元将前栈帧EBP值覆盖
  第5个输入单元将返回地址覆盖
这个是怎么得到的啊???是根据程序默认的处理还是用OD调试的
好象本文没说怎么个"通过动态调试,发现栈帧中的变量分布情况基本没变。这样我们就可以按照如下方法构造password.txt中的数据:"
5
天高飞扬 2007-12-16 19:55
36
来晚了,继续学习
吱-吱-吱
asd 2007-12-17 14:02
37
为什么楼主的OD可以识别 fopen这类函数呢
我的无论哪个版本都不行呀
leejingang 2007-12-17 16:00
38
看出来了,你在出书。。什么时间,我第一个买
jinghui 2007-12-18 16:31
39

请问显示反汇编后显示函数名而不是显示函数编号的OD插件是什么?谢谢大侠能帮助我,有这个插件的话不妨上传一个
lmcc 2007-12-19 03:18
40
学习完成! 还比较容易理解啊,遇到一个问题呀
verify_password函数的位置 ,不用跳转怎么找这个位置啊``,要是自己去寻找漏洞的话 一定要会找 这个地方啊,作者大大讲明白点点嘛``不是很清楚怎么进去这个栈栈
glts 2007-12-19 04:35
41
坐后面也得学习啊
8
failwest 2007-12-19 10:15
42
1:检查你写入的正确EBP的字节序,大顶机需要按DWORD字节逆序
2:检查填入字节的值,fscan函数会对一些敏感字节值(文件EOF)做出其他响应,如0x00等,前面有一层的朋友说过这个问题。这会password.txt中的字符串被截断。
3:当然乱码了,我们是按2进制形式填入的机器代码,肯定很多不在ASCII范围内的。
4:只恢复EBP是不够的,还有ESP,还有要让EIP返回到原先的位置。最后EAX这些寄存器也可能会引起错误。要修复所有相关寄存器才能恢复到原先的流程上去,这虽然困难,但却是可以做到的,取决于shellcode的质量和你的汇编编程技巧以及对系统的了解程度~~~~其实还有SEH退出等方法可以用

这个属于比较高级的问题,会在后面的讲座中逐渐加入。我还是主张一步一个脚印的学习方式,虽然可以看到很多鸟儿已经跃跃欲试,准备振翅高飞了:)
21
大菜一号 2007-12-19 14:28
43
举手,提问了
大概和楼主的系统不一样,地址有少许出入。就是要构造的返回地址0x401122和我的0x40111a,
对于楼主构造的
3433323134333231343332313433323122114000

fscanf总是能获取到完整的一行
而我的0x40111a
343332313433323134333231343332311a114000

总是只获取到返回地址前的dword,就是第四个"4321",导致后面构造的返回地址没能覆盖掉原来的

并且"4321432143214321"最后的一个NULL,覆盖了原返回地址0x4010ff,所以就返回到0x401000了,

附上我的txt
上传的附件:
21
大菜一号 2007-12-19 14:45
44
下面多发了一贴```汗
21
大菜一号 2007-12-19 14:45
45
原来0x1a模拟文件结束

我想其他办法吧
21
大菜一号 2007-12-19 16:01
46
问题解决

分享一下
---------------
因为我的系统中,该程序的密码正确提示分支地址是0x40111a,而0x1a模拟文件结束,当fscanf从文件获得一行文件时,遇到0x1a则停止。
所以不能直接覆盖返回地址( 1a 11 40 00 )
buffer[8] (8字节)+authenticated变量(4字节)+原栈帧(4字节)+返回地址(4字节)
=20字节

减去返回地址必须的四个字节,我们还有16字节的利用空间

构造一个返回地址,让程序返回我们的buffer,在buffer及以后的十六字节中让程序返回正确分支0x40111a

条件是,
因为fscanf,所以不能直接出现0x1a;
因为strcpy,所以不能直接出现0x00空中止;
控制在16字节以内。

下面是我构造的反汇编代码:
0013FB7C    33C0            xor     eax, eax
0013FB7E    B0 40           mov     al, 40
0013FB80    C1E0 10         shl     eax, 10
0013FB83    66:B8 2211      mov     ax, 1122
0013FB87    2C 08           sub     al, 8
0013FB89    50              push    eax
0013FB8A    C3              retn
0013FB8B    90              nop


上面加上一个nop后是十六字节,算出0x40111a,再后面四字节的返回地址
0013FB7C 33 C0 B0 40 C1 E0 10 66 B8 22 11 2C 08 50 C3 90 3腊@拎f?,P脨 0013FB8C 7C FB 13 00 |?..


返回ebp還是有問題,我放棄
在楼主大侠面前献丑了
⒉⒌ 2007-12-22 11:50
47
study  ...
LULU 2007-12-22 12:57
48
http://bbs.pediy.com/attachment.php?attachmentid=10373&d=1197651319
请问显示反汇编后显示函数名而不是显示函数编号的OD插件是什么?谢谢大侠能帮助我,有这个插件的话不妨上传一个

我也有39楼的问题,我在导入库中加了一些库都没成功,哪位大侠帮忙吱一声
cnqdhi 2007-12-23 10:30
49
听课了 还好不是很晚 o(∩_∩)o...
MaxLucifer 2007-12-28 13:48
50
我已经来晚了n天了,不过还是很认真的学习。
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 微信公众号:ikanxue
Time: 0.016, SQL: 11 / 京ICP备10040895号-17