Pwnable.kr 의 세번째 문제를 풀어보자.
이번 문제는 Buffer Overflow 에 대한 문제이다.
Buffer Overflow 란, 데이터를 버퍼에 저장할 때, 데이터가 지정된 범위 바깥에 저장되는 것을 의미하고,
벗어난 데이터는 인접 메모리를 덮어쓰게 된다.
nc 로 접속하면 bof 파일이 바로 실행되고,
그 전에 wget 으로 bof 파일과 bof.c 소스파일을 다운받아보았다.
bof.c 소스파일은 main 함수와 func 함수로 이루어져 있다.
main 함수에서는 func 함수를 호출하는 거 이외에 별다른 코드가 없고,
0xdeadbeef 라는 값을 인자로 넘기면서 func 함수를 호출하고 있다.
func 함수에서는 0xdeadbeef 라는 값을 int 형 변수 key 에다가 넣고,
overflowme 라는 32byte 크기의 char 배열을 선언한다.
그리고 gets 함수로 overflowme 배열에 값을 입력받은 후, key 변수의 값과 0xcafebabe 라는 값을 비교하여
같으면 system 함수를 호출해 shell 을 실행시키고 있다.
여기서 문제를 해결하기 위해서 주목해야 되는 부분은 gets 함수이다.
gets 함수는 입력 크기에 제한이 없기 때문에 취약하다.
위 코드에서 배열을 32byte 크기 만큼 선언하고 gets 함수를 이용해 배열에 값을 입력받는데,
만약 사용자가 32byte 가 넘는 값을 입력한다면, 메모리의 다른 부분에 덮어씌워지는
Buffer Overflow 가 발생할 수 있다.
그럼 이제 고민해야 할 부분은 문제를 해결하기 위해서는 if 문의 조건을 만족시키는 것이다.
key 변수의 값과 0xcafebabe 를 비교해서 같아야 하는데,
key 변수에는 main 함수에서 파라미터로 넘겨준 0xdeadbeef 라는 값이 들어가 있고,
이것을 변경할 수 있는 부분은 코드에 보이지 않는다.
그렇다면 우리는 Buffer Overflow 를 이용해 key 변수 부분의 값을 덮어씌워야 한다는 결론에 도달한다.
위 코드를 기반으로 메모리 구조를 간단하게 그려보면 이런 식으로 될 것이다.
이 그림이 이해되지 않는다면 메모리 구조, 스택 프레임, 레지스터에 대해 간단히 공부해보자.
우리는 gets 함수를 통해 overflowme 배열에 데이터를 삽입한다.
이때 우리가 바꾸고 싶은 부분은 key 변수이고, 데이터를 몇 byte 삽입해야 key 변수 부분을
덮어씌울 수 있는 지만 찾으면 된다.
단순히 스택 구조로 생각하면 byte 를 유추할 수 있지만 dummy byte 가 삽입되어 있을 수 있기 때문에,
gdb를 이용해 확실하게 주소값을 찾아보자.
func 함수 부분을 disassemble 해보았다.
소스 코드와 비교해서 확인해보면,
첫 번째 call 은 printf() 이고, 두 번째 call 은 gets() 이다.
두 번째 call 밑 라인에 있는 cmp 는 key 와 0xcafebabe 를 비교하는 구문이다.
우리가 눈여겨 봐야 할 부분은 여기이다.
해석해보자면, printf() 함수 호출 이후
gets() 함수가 호출되기 이전에 ebp 에서 0x2c 만큼 뺀 부분을 eax 에 저장하고 있다.
이는 곧 overflowme 배열의 주소가 ebp-0x2c 라는 것이다.
또한 cmp 하는 부분에서 ebp+0x8 부분의 값과 0xcafebabe 를 비교하고 있다.
이는 ebp 에서 0x8만큼 +한 부분에 key 변수가 위치하고 있음을 나타낸다.
최종적인 구조는 이렇게 될 것이다.
overflowme 변수와 key 변수 사이의 크기는 ebp 를 기준으로 -0x2c, +0x8 만큼 차이가 나므로,
52byte 만큼 값을 입력하면 overflow 가 일어나 값이 덮어씌워지고 key 변수 부분에도 원하는 값을 덮어씌울 수 있다.
페이로드를 작성해보면
flag 를 획득할 수 있다 !