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
- .函数不包含缓冲区
- 函数使用无保护的关键字标记
- 函数在第一个语句中包含内嵌汇编代码
- 缓冲区不是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,那么就可以随便泄露了