ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

栈溢出——邻接变量

2021-08-16 16:35:19  阅读:321  来源: 互联网

标签:authenticated 变量 0x7A 0x00 buffer 偏移 邻接 password 溢出


一般而言,局部变量在栈中的分布是相邻的(但也可能出于编译优化等需要而出现例外)。如果这些局部变量中有数组之类的缓冲区,并且程序确实没有防护数组越界,那么越界的数组元素就有可能破坏栈中相邻变量的值,甚至破坏栈帧中所保存的EBP的值、返回地址等重要数据。

0x00 源码

又是一份密码验证功能的C程序代码:

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


#define PASSWD "qazwsxe"


int verify_password(char* password) {
	int authenticated;
	char buffer[8];
	authenticated = strcmp(password, PASSWD);
	strcpy(buffer, password);
	return authenticated;
}


void main() {
	int valid_flag;
	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;
		}
	}
}

程序的“初衷”是验证密码“qazwsxe”,如果输入错误将循环等待下次输入,只有输入正确了才能跳出循环。

程序使用TDM-GCC 9.2.0 32-bit Debug进行编译;务必避开Visual Studio系列编译的GS选项,这个选项会规避栈溢出。

0x01 分析

函数verify_password()新增了局部变量char buffer[8],注意是紧邻着变量authenticated的,这个声明位置很重要。另外,在字符串比较后有函数strcpy(buffer, password),没有发挥实现验证功能的作用,非常违和,仅仅是为了构成栈溢出漏洞。

当程序执行到调用函数verify_password()时,其栈帧状态如下所示:

char buffer[0~3](ASCII编码的输入前4位)
char buffer[4~7](ASCII编码的输入第5到8位,期望第8位为字符串截断符)
int authenticated
返回地址
上一个栈帧的EBP
形参:password
……

authenticated是int类型,在内存中占用一个DWORD,即4个字节。如果能让buffer数组越界,buffer[8]、buffer[9]、buffer[10]、buffer[11]将写入相邻变量authenticated中。

authenticated承接的是函数strcmp()的返回值,之后会返回给main函数作为密码验证成功与否的标志变量。按照strcmp()的设计,authenticated为0表示验证成功;任何非0值都表示不成功。

如果输入超过了7个字符(本来buffer第8位是用来存放字符串截断符NULL的,截断符必须占用1个字节),则越界字符的ASCII码会修改掉authenticated的值:恰好为0,则程序流程就会发生改变。

0x02 调试

先通过IDA获得函数strcpy()的VA:

使用x96dbg打开程序,在这个VA处下断点:

输入错误密码“zzzzzzz”,按照ASCII编码,有字符串的序关系“zzzzzzz”>“qazwsxe”,则strcmp()函数应当返回1,即预计authenticated会被赋值1。实际情况符合预期(断点后进行一次单步步过):

0x7A是小写字母“z”的ASCII编码,下方的0x00000001正是authenticated的值。栈帧数据分布情况一目了然。

局部变量名 内存地址 偏移3处的值 偏移2处的值 偏移1处的值 偏移0处的值
buffer[0~3] 0x0065FA94 0x7A('z') 0x7A('z') 0x7A('z') 0x7A('z')
buffer[4~7] 0x0065FA98 NULL 0x7A('z') 0x7A('z') 0x7A('z')
authenticated 0x0065FA9C 0x00 0x00 0x00 0x01

注意:实际上,变量authenticated在内存中存储为0x01000000(小端存储),由调试器自行反转显示以便阅读。所以偏移量(相对左边给出的内存地址的距离)从左至右依次为3、2、1、0。

0x03 溢出

下面尝试输入超过7个字符,看看越界数据能否写入authenticated的数据区。输入“zzzzzzzzyxw”(“w”、“x”、“y”、“z”的ASCII码依次递增1):

与预期一致,从第9个字符开始的数据被依次写入authenticated的数据区,即authenticated的值变为了0x00777879

此时的栈帧数据分布情况为:

局部变量名 内存地址 偏移3处的值 偏移2处的值 偏移1处的值 偏移0处的值
buffer[0~3] 0x0065FA94 0x7A('z') 0x7A('z') 0x7A('z') 0x7A('z')
buffer[4~7] 0x0065FA98 0x7A('z') 0x7A('z') 0x7A('z') 0x7A('z')
authenticated(被覆盖前) 0x0065FA9C 0x00 0x00 0x00 0x01
authenticated(被覆盖后) 0x0065FA9C NULL 0x77('w') 0x78('x') 0x79('y')

现已知溢出数据确实能够覆写authenticated,只需要使authenticated为0就能通过程序验证了。

输入8个字符时,第9位的截断符NULL(0x00)将会被写入内存0x0065FA9C,即下一个双字的低位字节,恰好将authenticated从0x00000001变为0x00000000

此时栈帧数据为:

局部变量名 内存地址 偏移3处的值 偏移2处的值 偏移1处的值 偏移0处的值
buffer[0~3] 0x0065FA94 0x7A('z') 0x7A('z') 0x7A('z') 0x7A('z')
buffer[4~7] 0x0065FA98 0x7A('z') 0x7A('z') 0x7A('z') 0x7A('z')
authenticated(被覆盖前) 0x0065FA9C 0x00 0x00 0x00 0x01
authenticated(被覆盖后) 0x0065FA9C 0x00 0x00 0x00 0x00(NULL)

结论就是非常简单,输入8位字符即可,让截断符去覆盖0x01

0x04 补充

并不是所有的8位字符串都可以绕过验证。

如果输入的字符串按照ASCII编码的序关系小于正确密码“qazwsxe”,如“aaaaaaa”<“qazwsxe”,strcmp()函数会返回-1,此时authenticated存储着补码形式的-1,即0xFFFFFFFF。这样NULL覆写后也只能变成0xFFFFFF00

标签:authenticated,变量,0x7A,0x00,buffer,偏移,邻接,password,溢出
来源: https://www.cnblogs.com/4thrun/p/15148463.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有