软件安全实验3:shellcode注入
软件安全第三次实验报告
实验目标
1.了解shellcode注入原理
2.理解给出的弹出对话框的汇编代码
3.通过淹没静态地址来实现shellcode的代码植入
4.通过跳板来实现shellcode的代码植入
5.尝试修改汇编语句的shellcode实现修改标题等简单操作(1~5目标记作实验一)
6.在不修改StackOverrun程序源代码的情况下,构造shellcode,通过JMP ESP的方式实现通过记事本打开shellcode.txt(可实验CreateProcessA或WinExec等API)。(记作实验二)
实验一步骤与结果
(一)创建可执行程序
1.进入虚拟机,打开shellcode实验文件夹下的overflow_exe的c文件。
2.读懂main.cpp文件。
(1)首先分析verify_password函数:
a)传入参数:char *password是一个名为password的字符数组。
b)函数中定义了一个整型变量authenticated和一个长度为44的字符数组buffer[]。
c)调用strcmp函数比较password和常量PASSWORD两个字符串,并将比较结果赋值给authenticated。
d)进行字符串比较后,调用strcpy函数将字符串password的内容复制到buffer数组中。
e)函数最后返回变量authenticated。
(2)接下来分析main函数:
a)首先定义了一个初始值为0的整型变量valid_flag和一个长度为1024的字符数组password[]。
b)接下来程序读取password.txt文件,将文件内容字符串赋值给password。
c)调用veriy_password函数,将password作为参数传入,返回值赋值给valid_flag。
d)若valid_flag不等于0,即strcmp结果不为0,说明password与正确密码PASSWORD不符,打印提示信息“incorrect password!\n\n”。
e)若valid_flag等于0,即strcmp结果为0,说明password与正确密码PASSWORD相同,打印提示信息“Congratulation! You have passed the verification!\n”。
(3)由以上分析可知,密码验证程序起到的作用是:正确密码为1234567。读取password.txt中字符串,若与正确密码相同,则通过验证;若与正确密码不同,则验证失败。
3.自己新建一个工程main3_project,并在工程中新建一个文件main3.cpp,将c文件的内容复制进去。
4.运行程序。
5.这个程序与之前的实验的程序有3处不同:
(1)增加头文件windows.h,以便能调用LoadLibrary函 数去装载user32.dll;
(2)Verify_password的buffer变量增加到44字节,以 便能够承载我们的shellcode;
(3)Main函数装载user32.dll以便能在植入代码中调用 MessageBox。
(二)寻找dll中函数地址
1.程序调通运行不报错后,运行dependency walker程序。点击桌面上的depends.exe进入程序,打开我们生成的main3_project.exe。
2.点击剖析→开始剖析,参数保持默认。
3.点击kernel32.dll,在右侧能找到exitprocess的函数入口点的偏移地址为0x0001B0BB。
4.用相同的步骤查出MessageBoxA的函数入口地址,此函数属于user32.dll库。由下图可知,MessageBoxA的函数入口的偏移地址为0x00033D68。
5.在下方查看到KERNEL32.DLL的实际基址为0x77E60000,USER32.DLL的实际基址为0x77DF0000。由此加上偏移地址,可知ExitProcess函数和MessageBoxA函数的实际地址分别为0x77E7BB0B、0x77E23D68。
(三)调试shellcode并查找overflow_exe中的shellcode起始地址
1.新建项目shellcode_project,新建文件shellcode_main,将shellcode实验下的shellcode文件内容复制到新文件中。
2.修改部分代码:可以修改bupt字符串,即弹出框的标题等。两个函数的入口必须修改成所计算出的地址。
3.运行程序,如下图所示成功弹出对话框。
4.用odllydbg打开shellcode的exe文件。
5.找到我们写的那些汇编语言。
6.复制选定我们需要的部分:从xor ebp,ebp开始,到call eax结束(调用exit process的那个call eax),右键保存到文件,我们就得到了操作码的文件opcode2.txt。
7.接下来要查找overflow_exe中的shellcode的起始地址。首先在main3_project文件夹中新建password.txt文件,在其中写上正确密码1234567。
8.用ollydbg打开overflow_exe的exe文件,在strcpy处添加断点(F2)。
9.开始执行到断点处,查看缓冲区信息,我们发现dest的提示,就是我们要注入的shellcode的起始地址0012FAF0。
(四)采用静态地址跳转的方法进行注入
1.用UltraEdit打开password.txt并切换成十六进制编辑的方式。
2.计算一下我们发现返回地址应该在44(buff)+ 4(authenticated)+4(EBP)=52的偏移后,即第53-56字节。
我们构造的txt文件格式应该是这样的:Shellcode + 0x90若干 + shellcode在缓冲区的起始地址(注意逆序填写)。
3.正确填写后保存到overflow_exe项目的debug目录下并运行exe程序,看是否弹出对话框,并产生错误提示。完成后可以将password.txt重命名为passworda.txt,因为之后还要新制作一个名为password的txt文件。
(五)利用跳板来进行shellcode注入
1.首先在main3_project项目的Debug文件夹下新建一个内容为“1234567”的password.txt文件。
2.用Ollydbg打开main3_project.exe,在strcpy函数上设置断点,运行至断点,然后右键选择overflow return address→ASCII overflow returns→search JMP/CALL ESP。
3.等待搜索完毕后点击日志查看(L图标的按钮)。
4.查看日志后,我们选择一条在user32中的JMP ESP指令,记录它的地址0x77E2E32A,用ultraedit打开password.txt,这次的文件结构为52字节填充物+4字节JMP ESP地址(逆序)+shellcode(可选+若干0x90)。
5.运行exe程序,观察结果。如下图所示,成功弹出弹窗显示“BUPTBUPT”。
6.修改password.txt在buptbupt处的操作码,我修改成AGPTAGPT。
7.重新运行exe程序,可以看到弹出窗口。
实验二步骤与结果
编写shellcode.cpp
为了通过记事本打开shellcode.txt,首先用汇编语言编写shellcode.cpp文件,在其中构建WinExec函数栈,达到运行shellcode.cpp即可通过记事本打开shellcode.txt的目的。
(一)设计shellcode.cpp中的函数栈
1.WinExec函数:WinExec主要运行exe文件,不能运行其他类型的文件,不用引用特别单元。
(1)函数原型:UINT WinExec(exePath,ShowCmd)
(2)函数参数:
①参数1-exePath:命令行参数。注意,要用pChar转化一下。
②参数2-ShowCmd:外部程序的运行方式。其取值如下:
SW_HIDE = 0; {隐藏, 并且任务栏也没有最小化图标}
SW_SHOWNORMAL = 1; {用最近的大小和位置显示, 激活}
SW_NORMAL = 1; {同 SW_SHOWNORMAL}
SW_SHOWMINIMIZED = 2; {最小化, 激活}
SW_SHOWMAXIMIZED = 3; {最大化, 激活}
SW_MAXIMIZE = 3; {同 SW_SHOWMAXIMIZED}
SW_SHOWNOACTIVATE = 4; {用最近的大小和位置显示, 不激活}
SW_SHOW = 5; {同 SW_SHOWNORMAL}
SW_MINIMIZE = 6; {最小化, 不激活}
SW_SHOWMINNOACTIVE = 7; {同 SW_MINIMIZE}
SW_SHOWNA = 8; {同 SW_SHOWNOACTIVATE}
SW_RESTORE = 9; {同 SW_SHOWNORMAL}
SW_SHOWDEFAULT = 10; {同 SW_SHOWNORMAL}
SW_MAX = 10; {同 SW_SHOWNORMAL}
(3)因此,想要用记事本打开shellcode.txt文件,shellcode.cpp程序应该以正常方式运行**WinExec(pChar(‘notepad.exe shellcode.txt’),SW_SHOWNORMAL)**
(4)根据以上分析,shellcode中构造的WinExec函数栈应该如下图所示:
WinExec函数栈
2.ExitProcess函数:为了使得程序在成功使用记事本打开shellcode.txt后可以自行结束shellcode.cpp程序而去执行原程序接下来的正常步骤,要在WinExec函数执行结束后调用ExitProcess函数结束程序。
3.据此,编写shellcode.cpp还需要知道:
①字符串str的十六进制表达;
②参数1(指向str的指针);
③WinExec的实际入口地址;
④ExitProcess的实际入口地址。
(二)查看字符串“notepad.exe shellcode.txt”的十六进制
(1)利用UltraEdit打开shellcode.cpp文件,输入notepad.exe shellcode.txt。
(2)点击上方按钮转成十六进制,即可得到此语句所对应的十六进制内容。
(三)得到指向字符串“notepad.exe shellcode.txt”的指针
由于在程序构造函数栈时,会首先向栈内压入str[24]=“notepad.exe shellcode.txt”字符串,因此使所需的参数1指针指向栈内字符串开头str[0]即可。
而利用栈指针的特点,在字符串入栈完成后,栈顶指针esp所指向的栈顶位置恰好就是str[0]的位置,因此在此时将esp赋值给参数1即为指向字符串str的指针。
利用以下语句:
mov eax,esp
push ebx
…
push eax
即可完成参数1的入栈。
(四)查找WinExec函数的入口地址
1.使用Depends.exe打开Stackoverrun的可运行程序(main3_project.exe),点击“剖析→开始剖析→确定”。
2.WinExec函数属于Kernel32.dll,找到WinExec函数可知,其相对入口地址为0x00018601。
3.在下方查看到Kernel32.dll的实际基址为0x77E60000。
4.由Kernel32.dll的实际基址与WinExec函数的相对入口地址相加可得,WinExec函数的实际入口地址为0x77E78601。
(五)查找ExitProcess函数的入口地址
1.ExitProcess函数属于Kernel32.dll,找到WinExec函数可知,其相对入口地址为0x0001B0BB。
2.由Kernel32.dll的实际基址与WinExec函数的相对入口地址相加可得,WinExec函数的实际入口地址为0x77E7B0BB。
(六)编写shellcode.cpp
1.程序开头增加头文件windows.h,接下来调用LoadLibrary函数去装载kernel32.dll。
2.在栈中首先压入之前得到的字符串”notepad.exe shellcode.txt”十六进制内容(倒序压入)。
3.在将参数字符串压入栈后,将压入字符串后的栈顶指针赋值给eax,即为字符串str的地址指针,即WinExec函数的参数1。
4.在压入WinExec函数的两个参数时,考虑到栈的先入后出特点,首先压入参数2(SW_SHOWNORMAL=5),再压入参数1(指向str的指针,现暂存于eax中)。
5.在参数都入栈完毕后,调用WinExec函数,执行**WinExec(pChar(‘notepad.exe shellcode.txt’),SW_SHOWNORMAL)**后,再调用ExitProcess函数结束shellcode.cpp程序。
6.由以上分析得,编写的shellcode.cpp代码如下:
1 |
|
7.将shellcode.txt文件复制到所生成的项目文件夹shell_project下。
8.VC6中点击上方运行按钮运行shellcode.cpp程序,如下图所示成功弹出使用记事本打开的shellcode.txt。
利用JMP ESP进行shellcode注入**
(一)获取shellcode的指令码
1.用ollydbg打开我们生成的shell_project.exe文件,找到我们编写的那些汇编语言。
2.复制选定我们需要的部分,右键保存到文件,得到shellcode.cpp操作码的文件opcode3.txt。
3.查看Debug文件夹下的opcode3.txt文件内容如下图所示。
(二)查找overflow_exe中的shellcode起始地址
1.用olldbg打开overflow_exe文件,在strcpy处添加断点。
2.开始执行到断点处,此时dest的提示就是要注入的shellcode的起始地址0x0012FAF0。
(三)查找JMP ESP指令地址
1.在strcpy函数上设置断点,运行至断点后右键选择overflow return address →ASCII overflow returns → search JMP/CALL ESP,等待搜索完毕后点击日志查看。
2.查看日志后,选择一条JMP ESP的指令地址,如下图所示选择的JMP ESP指令地址为0x77FB948B。
(四)编写password.txt
1.在StackOverrun程序的文件夹下新建一个password.txt文件。
2.password.txt的文件结构应为“若干字节填充物+4字节JMP ESP地址(逆序)+shellcode+若干90”。
(1)为了知道开头需要多少字节的填充物,首先填充8字节进行尝试,此时password.txt如下图所示。
(2)使用ollydbg打开StackOverrun.exe时,在参数处输入“(password.txt内容)”。
(3)点击两次运行后,由缓冲区提示信息可知,从0012FF68到0012FF70都需要填充,因此password.txt开头需要十二字节的填充物,将password.txt修改如下图二所示。
(4)以修改后的password.txt内容为参数再次用ollydbg打开StackOverrun.exe。
3.点击运行按钮运行程序,如下图所示程序可以成功用记事本打开shellcode.txt。
实验结论
本次实验围绕shellcode注入技术展开,涵盖了从原理理解到实际操作的完整流程。首先,通过了解shellcode注入的基本原理,我掌握了缓冲区溢出攻击中代码植入的基本机制。在此基础上,我深入分析了示例中用于弹出对话框的汇编代码,理解了shellcode如何通过机器指令调用系统API实现特定功能。
在实践环节,我分别采用淹没静态地址和利用跳板两种方法成功实现了shellcode的代码植入。通过淹没静态地址的方式,我学会了如何精确计算内存地址并覆盖返回地址;而通过跳板技术,我掌握了如何利用程序已有的指令片段实现执行流的重定向。这两种方法从不同角度展示了攻击者如何利用程序漏洞获取控制权。
为了进一步探索shellcode的定制化能力,我还尝试修改汇编语句,实现了对消息框标题等参数的自定义设置。这让我认识到shellcode不仅能够执行固定功能,还可以通过参数调整实现更灵活的攻击效果。
实验的最高阶挑战是在不修改StackOverrun程序源代码的前提下,构造特殊的shellcode,通过JMP ESP的方式实现通过记事本打开指定文件。这一环节综合运用了CreateProcessA等Windows API的调用技巧,展现了shellcode技术在现实攻击中的实际应用场景。
通过这一系列实验,我深刻认识到shellcode注入技术的危害性。这种攻击方式可以完全绕过正常的程序执行流程,实现任意代码执行,轻则导致恶作剧式的弹窗,重则可实现文件操作、数据窃取、病毒植入等恶意行为。这凸显了软件安全防护的极端重要性。
本次实验让我对软件安全课程的核心价值有了更深入的理解。安全不是事后补救,而是应该贯穿于软件开发生命周期的每个环节。从代码编写时的输入验证、缓冲区边界检查,到编译部署阶段的安全机制启用(如DEP、ASLR),都需要系统性的安全思维。只有真正理解攻击原理,才能设计出有效的防御方案。在数字化时代,软件安全已然成为信息系统可靠运行的基石。作为未来的软件开发者,我们不仅要掌握功能实现的技术,更要具备安全防护的意识和能力。本次实验不仅提升了我的技术实践水平,更培养了我对软件安全重要性的深刻认知,这将对我未来的专业发展产生持久而积极的影响。
实验报告持续更新中…



























































