CTF/포너블

[IJCTF] Input Checker - 포너블 / 버퍼오버플로우 / IDA / Peda / Pwntool

SecurityMan 2022. 9. 1. 11:00

 

조금 헷갈렸던 포너블 문제

 

포너블은 항상 어려운것 같다.

 

문제설명에서 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) 가 정상적으로 실행되는것이 보인다.

반응형