实验2:栈溢出

1. 实验目标

  • 通过对程序输入的密码的长度、内容等修改用Ollydbg来验证缓冲区溢出的发生。

  • 完成淹没相邻变量改变程序流程实验。

  • 完成淹没返回地址改变程序流程实验

  • 信安思考题:

            在不修改源代码的情况下,修改StackOverrun程序的流程,通过淹没返回地址,用jmp esp的方式,让其调用bar函数并输出结果。

  • 双培思考题:

            在不修改源代码的情况下,修改OverFlow_EXE程序的流程,通过弧注入的方式,不植入可执行代码,让其调用MessageBoxA函数(函数地址:0x77E23D68)弹出对话框(对话框显示bupt),之后调用ExitProcess函数(函数地址:0x77E7B0BB)退出进程。

2. 实验要求

  • 详述修改过程
  • 实验结果需要截图证明
  • 绘制修改原理的图示

3. 测试步骤与结果

源代码

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
#include <stdio.h>
#include <string.h>
#define PASSWORD "1234567"

int verify_password (char *password){
int authenticated;
char buffer[8];// add local buff
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);//over flowed here!
return authenticated;
}

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;
}
}
}

3.1 Ollydbg验证缓冲区溢出

        将可执行文件拖入Ollydbg中进行调试,找到strcpy()函数设置断点,运行输入错误的密码123456。

        步过到strcpy()函数下一行时,程序已将字符串复制到buf数组地址,可以看到返回值为FFFFFFFF即为-1的补码,说明密码对比不正确。

同样的操作输入正确的密码1234567进行查看。

        可以看到其返回值为0,说明密码对比正确。但是输入过长字符串时程序并没有设置限制,而是继续在栈中写入,从而导致缓冲区溢出,如输入1111111111。

可以看到其存储早已超出buf申请的8字符长度,可说明其存在缓冲区溢出,栈结构如下。

3.2 淹没相邻变量改变程序流

        当输入长于存储空间的字符串12345678时,因为没有保护机制所以会将其输入内容溢出到高地址上,此处即为溢出更改anthenticated的值。

        可以看到anthenticated的值被字符串溢出更改为0,即不输入正确的密码也可判定为密码正确。

相邻变量溢出覆盖的栈结构如下。

3.3 淹没返回地址改变程序流

        如果输入超长字母,就可以将栈中的返回地址也覆盖更改,从而就能实现控制程序流的效果,输入123456789123456789123456789。

        可以看到返回地址已经被覆盖,因此如果将返回地址覆盖成有意义的地址,即可截获程序流。

        此时需要填充8位buf[8] + 4位anthenticated存储空间 + 4位EBP回调存储空间 + 想要其返回的地址位置,此处想要直接跳转到congratulations处,即0x00401116

        因此输入123412341234123416114000即可达成目的,不过其地址都是以16进制存入,所以需要通过010Editor编译其对应的16进制,写入字符并按ctrl+h即可。

右侧内容作为密码输入即可成功淹没返回地址改变程序流。

返回地址溢出被覆盖的栈结构如下。

4. 测试结论

        可以看出栈溢出对程序运行的安全性有很大的威胁,如果不对栈进行保护,只要系统中存在栈溢出漏洞,即可令攻击者通过非正当方法获取操作权限。因此,需要对栈中保存数据的可执行权限做严格的管理,并且系统要能判断栈溢出从而采取相应的保护措施。

5. 思考题

信安

        在不修改源代码的情况下,修改StackOverrun程序的流程,通过淹没返回地址,用jmp esp的方式,让其调用bar函数并输出结果。

源代码(仅作参考,主要根据老师给的StackOverrun.exe)

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
/*
StackOverrun.c
This program shows an example of how a stack-based
buffer overrun can be used to execute arbitrary code. Its
objective is to find an input string that executes the function bar.
*/

#include <stdio.h>
#include <string.h>

void foo(const char* input){
char buf[10];

//What? No extra arguments supplied to printf?
//It's a cheap trick to view the stack 8-)
//We'll see this trick again when we look at format strings.
printf("My stack looks like:\n%p\n%p\n%p\n%p\n%p\n% p\n%p\n%p\n%p\n%p\n\n");

//Pass the user input straight to secure code public enemy #1.
strcpy(buf, input);
printf("%s\n", buf);

printf("Now the stack looks like:\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n\n");
}

void bar(void){
printf("Augh! I've been hacked!\n");
}

int main(int argc, char* argv[]){
//Blatant cheating to make life easier on myself
printf("Address of foo = %p\n", foo);
printf("Address of bar = %p\n", bar);

foo(argv[1]);
return 0;
}
  1. 运行StackOverrun.exe。发现程序打印了foo函数和bar函数的地址位置,并且调用了foo函数,输出了栈顶向下的40字节的数据。

  2. 将StackOverrun.exe拖入Ollydbg中分析。在菜单栏选择调试->参数,并输入参数11223344,并重新开始。

  3. 通过查找输出字符串定位到主函数位置并设置断点,调试观察调用foo函数前的栈空间情况。

            可以看到EBP指向了0x0012FFC0,ESP指向了0x0012FF70,由此栈顶向下依次为004060C4,00401060(bar函数地址),004060DC,00401000(foo函数地址)。

  4. 继续调试进入foo函数。

            观察此时栈空间,可以看到EBP未改变,ESP因SUB ESP,0C改变为0x0012FF60,此时的0x0012FF6C存储的是main函数地址。

            如下图,继续运行至strcpy函数处,可以看到输入的参数11223344也在栈空间中,所以可以通过输入12个填充字符和jmp esp指令地址使栈溢出覆盖之前返回main函数的地址为jmp esp,并让程序将栈空间的数据当做代码运行,从而调用bar函数。

  5. 右键->Overflow Return Address->ASCII overflow returns->Search JMP/CALL ESP来查找jmp esp指令。

    待下方搜索进度条满后,对其弹出提示进行确认后,点击菜单栏的L按键查看日志。

    本次选用77F8948B处的JMP ESP指令。

  6. 构造shellcode。

    需要将77F8948B利用UltraRdit转换成字符为嫈鴚

            输入参数12345678912嫈鴚运行调试,可以看到foo函数结束后跳转到了JMP ESP指令处,可以看到此时ESP为0012FF70,由此需要把该地址指令改为调用bar函数。

            继续运行调试,可以看到跳到了0012FF70处。双击0012FF70处指令,更改为CALL 00401060就可以获取到其机器码为E8EB102D00,不过查看后一定要改回原来的语句。

    将机器码转为字符桦-。由此可以构造参数为123456789012嫈鴚桦-

  7. 输入shellcode进行测试。

双培

        在不修改源代码的情况下,修改OverFlow_EXE程序的流程,通过弧注入的方式,不植入可执行代码,让其调用MessageBoxA函数(函数地址:0x77E23D68)弹出对话框(对话框显示bupt),之后调用ExitProcess函数(函数地址:0x77E7B0BB)退出进程。

源代码

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
#include <stdio.h>
#include <windows.h>
#include <string>
#define PASSWORD "1234567"
int verify_password (char *password){
int authenticated;
char buffer[44];
authenticated=strcmp(password,PASSWORD);
// strcpy(buffer,password);//over flowed here!
memcpy(buffer,password,150);//over flowed here!
return authenticated;
}
main(){
int valid_flag=0;
char password[1024];
FILE * fp;
LoadLibrary("user32.dll");//prepare for messagebox
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进行读取,所以需要在程序目录下创建文件并在其中写入1234567作为密码。将程序拖入Ollydbg中,在memcpy()函数处设置断点,将程序运行到断点。

        可以看到其保存返回地址的地址为0012FB24,向下步过可以看到字符存储地址为0012FAF0

        两个地址之间的距离为13*4,其为需要填充的长度,通过题目可知messagebox地址为0x77E23D68,Exit地址为0x77E7B0BB,输出参数为bupt,由此可以通过010Editor编写password.txt文件,在其中输入payload。

弧注入的栈结构如下。

根据栈结构编写的payload。

更改password中的文本后,运行程序即可看到窗口弹出,从而证明程序流被劫持。