从零开始学习winpwn(2)--GS保护

GS保护机制

学习windows pwn第一个要认识的保护机制就是GS,他对应于linux下的canary保护。
他的用途就是,在old_ebp之前插入一段数据,并且再pop ebp之前检查该数据,如果对不上,说明发生了栈溢出,那么就结束执行

如何开启GS保护

进入vs2022,点击项目
在这里插入图片描述
选择属性,可以看到启用安全检查![
在这里插入图片描述
可以选择禁用检查在这里插入图片描述

关闭GS的栈结构

以下面这个简单的函数为例
(注意,由于编译优化的问题,可能生成的代码不完全相同,但大体上保持一致)
在这里插入图片描述
在这里插入图片描述
对应的一般函数的调用就是

push ebp
mov ebp,esp

pop ebp
ret

当调用函数的时候,首先会push参数,然后call,对应的栈结构
在这里插入图片描述
接着我们push ebp,所以完整的结构
在这里插入图片描述

开启GS的栈结构

首先明确一定
并不是对所有的函数都应用GS,以下情况不会应用GS

  1. .函数不包含缓冲区
  2. 函数使用无保护的关键字标记
  3. 函数在第一个语句中包含内嵌汇编代码
  4. 缓冲区不是8字节类型且大小不大于4个字节
    比如说我们上面的函数,开启GS之后,没有变化
    在这里插入图片描述
    但是引入#pragma strict_gs_check(on)可以对任意类型的函数添加Security Cookie。(但证明,像我们这里的backdoor还是不会受影响)
    那么我们直接使用微软官方的例子
#pragma strict_gs_check(on)
void** ReverseArray(void** pData,
	size_t cData)
{
	void* pReversed[20];

	for (size_t j = 0, i = cData; i; --i, ++j)
		pReversed[j] = pData[i];

	for (size_t i = 0; i < cData; ++i)
		pData[i] = pReversed[i];

	return pData;
}

在这里插入图片描述
在这里插入图片描述
这里首先做了一个sub esp,60h因为我们参数,gs保护都是用ebp来寻址,所以当作看不见
这里通过汇编看到了GS的算法
data段的securiy_cookie^now_ebp就是当前的gs值
同理结束的时候,也是把gs^now_ebp然后调用__security_check_cookie()
这个代码很简洁,就是比较一下,不一样就跳转到failure
在这里插入图片描述
这就是添加GS保护后的栈结构
在这里插入图片描述

如何判断程序有没有GS保护

本来打算说用winchecksec
但是事实证明,一点都不可靠
还是老老实实自己看,因为有些函数就算开了GS保护他也不一定有
最关键还是看反汇编
最开头位置有没有xor 寄存器,ebp(寄存器一定要是security cookie)push寄存器
但要注意,这个GS的值不一定在ebp-4,如果设置了seh处理,这个gs位于ebp-0x1c处

如何bypass GS

格式化字符串漏洞

如果可以看到printf(buf),buf可控
那么就可以利用格式化字符串漏洞,传入多个%p(数量要回控制)即可
···
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
char tmp[20];
scanf("%s",tmp);
printf(tmp);

return 0;

}
···
在这里插入图片描述

通过调试,找到printf调用时候的堆栈,可以看到最上面的就是我们printf的参数,也就是地址,所以说里面的每一项都是从参数后面的下一个地址开始算,也就是00affc38的内容对应于第一个%p
那么00affc58是ebp,所以需要泄漏到00affc54的位置,也就是需要8个%p
那么重新运行,由于堆栈随机化,可能地址不同
在这里插入图片描述
当执行完push ebp之后,esp就指向当前参数
在这里插入图片描述
可以看到这次gs是0073fed0位置的值,看能否泄露出来
刚好最后一个,这个时候只需要用字符串截取一下就可以了
在这里插入图片描述
然后栈溢出的时候,我们可以直接把原来gs的值填进去,然后跳转到后门的地址就好

复制\x00截断

这里我用了类似web方向的\x00截断
因为我们经常看到这样的复制代码

int copy(char* a, int len) {
	setbuf(stdout, 0);
	setbuf(stdin, 0);
	int i;
	for (i = 0; i < len; i++) {
		char tmp = getchar();
		if (tmp == '\n') {
			*(char*)(a + i) = '\0';
			break;
		}
		else {
			*(char *)(a + i )= tmp;
		}
	}
	return i;
}

这个代码就是复制,然后如果\n就结束并换成\0,表面上看上去没有漏洞
那我们实际看一下

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int copy(char* a, int len) {
	setbuf(stdout, 0);
	setbuf(stdin, 0);
	int i;
	for (i = 0; i < len; i++) {
		char tmp = getchar();
		if (tmp == '\n') {
			*(char*)(a + i) = '\0';
			break;
		}
		else {
			*(char*)(a + i) = tmp;
		}
	}
	return i;
}
int main()
{
	char tmp[20];
	copy(tmp, 20);
	printf("%s", tmp);

	return 0;
}

当输入20个0之后,可以看到,最后多个乱码,怎么回事呢,我们调试一下
在这里插入图片描述
当输入20个61之后,发现堆栈被填满了,但是没有00截断,所以一旦printf(*
%s")由于遇不见\x00,他会一直输出,把后面的数据全部输出了,直到遇见第一个\x00
在这里插入图片描述
这个可以利用winpwn的u32进行转化
大概代码如下

io.sendline("a"*19+"@")
io.recvuntil("@")
u32(io.recv(4))

既然知道了gs,那么就可以随便泄露了

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值