标签:ascii https enc 汇编 实验目的 意图 pass sci 退出
上节实验介绍的改写邻接变量的方法是很有用的,但这种漏洞利用对代码环境的要求相对比较苛刻。更通用、更强大的攻击通过缓冲区溢出改写的目标往往不是某一个变量,而是瞄准栈帧最下方的 EBP 和函数返回地址等栈帧状态值。
回顾上节实验中输入 7 个‘q’程序正常运行时的栈状态,栈帧数据如表所示。
局部变量名 | 内存地址 | 偏移3处的值 | 偏移2处的值 | 偏移1处的值 | 偏移0处的值 |
---|---|---|---|---|---|
buffer | 0x0012fb18 | 0x71(‘q‘) | 0x71(‘q‘) | 0x71(‘q‘) | 0x71(‘q‘) |
0x0012fb1c | NULL | 0x71(‘q‘) | 0x71(‘q‘) | 0x71(‘q‘) | |
authenticated | 0x0012fb20 | 0x00 | 0x00 | 0x00 | 0x01 |
前栈帧 EBP | 0x0012fb24 | 0x00 | 0x12 | 0xFF | 0x80 |
返回地址 | 0x0012fb28 | 0x00 | 0x40 | 0x10 | 0xEB |
OD中的动态调试,如下图:
如果继续增加输入的字符,那么超出 buffer[8]边界的字符将依次淹没 authenticated、前栈帧 EBP、返回地址。也就是说,控制好字符串的长度就可以让字符串中相应位置字符的 ASCII 码覆盖掉这些栈帧状态值。
按照上面对栈帧的分析,不难得出下面的结论。
在vc6.0,这里用 19 个字符作为输入,看看淹没返回地址会对程序产生什么影响。出于双字对齐的目的,我们输入的字符串按照“4321”为一个单元进行组织,最后输入的字符串为“4321432143214321432”,运行情况如图所示。
说明,栈溢出导致程序崩溃。
用 OllyDbg 加载程序,在字符串复制函数调用结束后观察栈状态,如图 2.3.2 所示。
实际的内存状况和我们分析的结论一致,此时的栈状态如表 2-3-2 所示。
局部变量名 | 内存地址 | 偏移3处的值 | 偏移2字节 | 偏移1字节 | 偏移0字节 |
---|---|---|---|---|---|
buffer[0~3] | 0x0012fb18 | 0x31(‘1‘) | 0x32(‘2‘) | 0x33(‘3‘) | 0x34(‘4‘) |
buffer[4~7] | 0x0012fb1c | 0x31(‘1‘) | 0x32(‘2‘) | 0x33(‘3‘) | 0x34(‘4‘) |
authenticated(被覆盖前) | 0x0012fb20 | 0x00 | 0x00 | 0x00 | 0x01 |
authenticated(被覆盖后) | 0x0012fb20 | 0x31(‘1‘) | 0x32(‘2‘) | 0x33(‘3‘) | 0x34(‘4‘) |
前栈帧 EBP (被覆盖前) | 0x0012fb24 | 0x00 | 0x12 | 0xFF | 0x80 |
前栈帧 EBP (被覆盖后) | 0x0012fb24 | 0x31(‘1‘) | 0x32(‘2‘) | 0x33(‘3‘) | 0x34(‘4‘) |
返回地址(被覆盖前) | 0x0012fb28 | 0x00 | 0x40 | 0x10 | 0xEB |
返回地址(被覆盖后) | 0x0012fb28 | 0x00(NULL) | 0x32(‘2‘) | 0x33(‘3‘) | 0x34(‘4‘) |
前面已经说过,返回地址用于在当前函数返回时重定向程序的代码。在函数返回的“retn” 指令执行时,栈顶元素恰好是这个返回地址。“retn”指令会把这个返回地址弹入 EIP 寄存器,之后跳转到这个地址去执行。
在这个例子中,返回地址本来是 0x004010EB,对应的是 main 函数代码区的指令,如图 2.3.3 所示。
现在我们已经把这个地址用字符的 ASCII 码覆盖成了 0x00323334,函数返回时的状态如 图 2.3.4 所示。(依然是在地址00401059处设置断点,使用f8快捷键调试)
使用f8快捷键动态调试,到地址0040106f处停止:
接着使用快捷键f7步入,如图
由于 0x00323334 是一个无效的指令地址,所以处理器在取指的时候发生了错误使程序崩 溃。但如果这里我们给出一个有效的指令地址,就可以让处理器跳转到任意指令区去执行(比如直接跳转到程序验证通过的部分),也就是说,我们可以通过淹没返回地址而控制程序的执行流程。以上就是通过淹没栈帧状态值控制程序流程的原理,也是本节实验要做的事。
用键盘输入字符的 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("C:\\Documents and Settings\\Administrator\\桌面\\project\\test\\Debug\\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 文件。
同1.4 Crack小实验(以后若无特殊声明,均以此环境为准)
如果完全采用实验本文的实验环境,将精确地重现指导中所有的细节,否则需要根据具体情况重新调试。
用 VC6.0 将上述代码编译链接(使用默认编译选项,Build 成 debug 版本),在与 PE 文件 同目录下建立 password.txt 并写入测试用的密码“1234567”,测试程序是否正确。
之后,就可以用 OllyDbg 加载调试了。
开始动手之前,我们先理清思路,看看要达到实验目的我们都需要做哪些工作。
这样 verify_password 函数返回后就会直接跳转到验证通过的正确分支去执行了。
首先用 OllyDbg 加载得到可执行 PE 文件(第1步在password.txt写入正确密码,然后编译执行,产生PE文件,即test.exe文件),如图 2.3.5 所示。
阅读图 2.3.5 中显示的反汇编代码,可以知道通过验证的程序分支的指令地址为 0x00401102。
0x004010E2 处的函数调用就是 verify_password 函数,之后在 0x004010EA 处将 EAX 中的函数返回值取出,在 0x004010ED 处与 0 比较,然后决定跳转到提示验证错误的分支或提示验证通过的分支。
提示验证通过的分支从 0x00401102 处的参数压栈开始。如果我们把返回地址覆盖成这个 地址,那么在 0x004010E2 处的函数调用返回后,程序将跳转到验证通过的分支,而不是进入 0x004010E7 处分支判断代码。这个过程如图 2.3.6 所示。
仍然出于字节对齐、容易辨认的目的,我们将“4321”作为一个输入单元。 buffer[8]共需要两个这样的单元。
第 3 个输入单元将 authenticated 覆盖;第 4 个输入单元将前栈帧 EBP 值覆盖;第 5 个输入单元将返回地址覆盖。
为了把第 5 个输入单元的 ASCII 码值 0x34333231 修改成验证通过分支的指令地址
0x00401102,我们将借助十六进制编辑工具 UltraEdit 来完成(0x40、0x11 等 ASCII 码对应的符号很难用键盘输入)。
步骤 1:将上面创建的 password.txt 文件用记事本打开,在其中写入 5 个“4321” 后保存。如图 2.3.7 所示。
步骤 2:保存后用 UltraEdit_32 重新打开,如图 2.3.8 所示。
步骤 3:将 UltraEdit_32 切换到十六进制编辑模式,如图 2.3.9 所示。
步骤 4:将最后 4 个字节修改成新的返回地址,注意这里是按照“内存数据”排列的,由于“大顶机”的缘故,为了让最终的“数值数据”为 0x00401102,我们需要逆序输入这 4 个字节,如图 2.3.10 所示。
步骤 5:这时我们可以切换回文本模式,最后这 4 个字节对应的字符显示为乱码,如图 2.3.11 所示。
将 password.txt 保存后,用 OllyDbg 加载程序并调试,首先可以看到成功绕过密码验证:
我们再回头看一下最终的栈状态:authenticated被覆盖为 0x0040106C;EBP被覆盖为0x31323334,返回地址被覆盖后为0x00401102(正好为验证成功的地址)
可以看到最终的栈状态如表 2-3-4 所示。
局部变量名 | 内存地址 | 偏移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 | 0x00 | 0x40 | 0x10 | 0x6C |
前栈帧 EBP (被覆盖前) | 0x0012fb20 | 0x00 | 0x12 | 0xFF | 0x80 |
前栈帧 EBP (被覆盖后) | 0x0012fb20 | 0x31(‘1‘) | 0x32(‘2‘) | 0x33(‘3‘) | 0x34(‘4‘) |
返回地址(被覆盖前) | 0x0012fb24 | 0x00 | 0x40 | 0x10 | 0xE7 |
返回地址(被覆盖后) | 0x0012fb24 | 0x00 | 0x40 | 0x11 | 0x02 |
VC6.0中,重新编译执行。程序执行状态如图 2.3.12 所示。
由于栈内 EBP 等被覆盖为无效值,使得程序在退出时堆栈无法平衡,导致崩溃。虽然如 此,我们已经成功地淹没了返回地址,并让处理器如我们设想的那样,在函数返回时直接跳转到了提示验证通过的分支。
http://www.cnblogs.com/0831j/p/9219081.html
https://github.com/walkerfuz/writeups/blob/master/books/0day_security_second_edition.md
标签:ascii https enc 汇编 实验目的 意图 pass sci 退出
原文地址:https://www.cnblogs.com/narisu/p/9372839.html