조금 헷갈렸던 포너블 문제
포너블은 항상 어려운것 같다.
문제설명에서 best input 을 찾으라고 한다.
문제파일로 input 이라는 이름의 바이너리 파일이 하나 주어진다.
file 명령어로 확인해보면
ELF 64-bit 파일인것을 확인할 수 있다.
ELF 은 리눅스에서 실행가능한 파일이다.
파일을 실행시켜보면
Input : 이라는 문구가 출력되면서 사용자 입력을 기다린다.
hello 라고 입력해봤는데, 프로그램이 종료되거나 하지 않고 계속 대기중이었다.
IDA 라는 디스어셈블러를 이용해 input 파일을 열어보았다.
main 함수를 먼저 살펴봤는데
Input : 이라는 출력이 나온 뒤에
getchar() 함수를 이용해 사용자의 입력을 받고 있다.
반복문이 0에서 1089 까지 돌아가면서 사용자의 입력을 v7 이라는 배열에 저장하고 있다.
위쪽을 보면 v7 배열은 rbp-0x430 에 위치해 있으며
크기는 1008 인것을 볼 수 있다.
배열의 크기보다 반복문으로 입력받는 크기가 더 크니 버퍼 오버플로우가 발생한다.
반복문 바로 위쪽을 보면 execve('/bin/sh') 함수가 있는게 보이는데,
v7 배열을 오버플로우 시켜 ret 주소를 조작해 저 위치로 가게끔 만들어야 하는듯 하다.
저 위치의 주소를 확인해보면
0x401253 이다. 이 주소를 잘 기억해야 한다.
이번엔 반복문의 주소를 찾아준다.
왼쪽 끝에있는 화살표, 그리고 맨 첫줄에 있는 1089 라는 숫자를 보면
저기가 반복문 위치인 것을 알 수 있다.
반복문의 시작은 0x401283 이다.
이제부터는 peda 를 실행시켜 분석해준다.
(peda 설치법은 여기 참고: https://hackingstudypad.tistory.com/242)
아까 찾은 반복문 시작주소에 브레이크포인트를 걸어준다.
b *0x401283 이라고 입력하면 브레이크포인트가 걸린다.
r 을 입력해 프로그램을 실행시켜 주고
ni 를 입력해 getchar 함수가 실행되는 부분까지 이동해준다.
Input : 출력이 나오면 aaaaaaaa 를 입력해준다.
aaaaaaaa 를 입력하면 한글자씩 v7 배열에 집어넣는 작업을 한다.
반복문이 계속 돌면서 0x401283 으로 점프하는데
입력이 다끝나면 0x401283 으로 더이상 점프하지 않는다.
더이상 점프하지 않을때 스택을 살펴본다.
v7배열의 위치를 확인하기 위해 x/16xg $rbp-0x430 라고 입력해준다.
스택의 시작 주소가 0x7fffffffdb30 인것을 확인했고,
아까 입력했던 a(0x61) 가 8개 잘 들어가 있는것이 보인다.
bt 명령어를 이용해 스택프레임을 살펴본다.
지금은 함수 호출이 끝나면 __libc_start_main 으로 ret 하게 되는데,
스택 어딘가에 _libc_start_main 의 주소인 0x00007ffff7a7e81d 가 저장되어 있을것이다.
저 부분을 덮어써서 execve('/bin/sh') 위치로 이동하면 된다.
find 0x00007ffff7a7e81d 으로 스택에서 저장되어있는 위치를 찾을 수 있다.
현재 0x7fffffffdf68 위치에 저장되어있다.
ret 주소가 저장된 위치에서 스택의 시작 주소를 빼면
0x438 이 나오는데 이건 10진수로 1080 이 된다.
1080 만큼 데이터를 채우고, 그 뒤에 ret 주소를 붙여주면 되는것이다.
from pwn import *
p = process('./input')
ret = p64(0x401253) #execve address
payload = b'a' * 1080 + ret
p.sendline(payload)
p.interactive()
코드는 이렇게 pwntool을 이용해 간단하게 짤 수 있다.
그런데 이거 실행시켜보면 execve('/bin/sh') 가 실행이 안된다.
무엇이 문제인가 찾아보니
반복문에서 사용하는 이 j 변수 때문이었다.
j 변수의 위치를 보면 rbp - 0x18 인것을 알 수 있는데
x/10xg $rpb-0x18 로 해당 위치를 보면 8이 들어가 있는게 보인다.
아까 입력했던 a가 8개였기 때문에 지금 8이 들어가 있는것이다.
주소를 보면 0x7fffffffdf48 인데 크기를 비교해보면
0x7fffffffdb30(스택 시작) < 0x7fffffffdf48(j 변수) < 0x7fffffffdf68(ret 주소)
이렇게 정리할 수 있다.
그러니까 아까처럼 무작정 a를 1080개 집어넣으면
스택 시작부터 a를 채워넣다가, j 변수를 덮어 써 버려서
반복문이 진행하다가 갑자기 갈 길을 잃는 것이다.
j 변수 부분에는 적절한 j 변수를 값을 넣어줘야 한다.
여기가 좀 머리아픈데
j변수와 스택 시작 사이의 차이를 계산해보면 0x418 이 나온다.
0x418 은 10진수로 계산하면 1048 이 된다.
일단 a를 1048개 입력을 하면 j 변수 위치에 16진수로 418이 입력되어 있을것이다.
0x7fffffffdf48(j 변수) / 0x7fffffffdf68(ret 주소) 둘의 주소 차이를 보면 알 수 있듯이 0x20 이다.
j변수를 조작해서 418 다음에 419 로 가는게 아니라
0x20을 뛰어넘어 바로 ret 주소 시작으로 가게끔 해아한다.
주의할 점은 반복문이 ++j 이기 때문에
숫자를 하나 작게 해서 써줘야 한다는 것이다.
a 를 1048 개 쓰고 그다음 0x37(0x18 + 0x20 - 1) 을 넣어주면
j 변수의 값이 418에서 바로 437 로 바뀔 것이고, ++j 가 되어서 438 위치부터 다시 입력을 받을 것이다.
438 위치에는 바뀐 ret 주소가 들어가게 되고,
정상적으로 리턴하는 대신 execve('/bin/sh') 로 리턴하게되어 쉘이 떨어지게 된다.
from pwn import *
p = process('./input')
ret = p64(0x401253) #execve address
payload = b'a' * 1048 + b'\x37' + ret
p.sendline(payload)
p.interactive()
아까 코드에서 조금만 바꿔주면 된다.
a 를 1048개 넣어주고, 그다음 \x37, 0x401253를 순서대로 이어붙여주면 된다.
코드를 실행시키면
쉘이 떨어지고 os 명령어(id) 가 정상적으로 실행되는것이 보인다.
'CTF > 포너블' 카테고리의 다른 글
[2020CCE] Simple Pwn - 포너블 / 버퍼오버플로우 / Pwndbg / Pwntool / IDA (60) | 2022.11.08 |
---|---|
[2020CCE] Simple Cmdshell / Simple Patch - 포너블 / IDA (66) | 2022.10.24 |
[SharkyCTF] Give Away 1 - 포너블 / RTL / IDA / Peda / Pwntool (46) | 2022.08.24 |
[SharkyCTF] GIVE AWAY 0 - 포너블 / 버퍼오버플로우 / PEDA / IDA / Pwntool (50) | 2022.08.19 |
[ImaginaryCTF] ret2win - 포너블 / 버퍼오버플로우 (40) | 2022.07.22 |