본문 바로가기

System Programming/CSAPP Lab

[Buffer Lab] Level 0, Stack Corruption 이용하여 함수의 return address 조작하기

Level 0의 목표

bufbomb 프로그램이 실행되면 test() 함수가 호출된다. 이 함수 내에서 다시 getbuf() 함수를 호출하는데, 만약 getbuf() 함수가 정상적으로 종료되면, test() 함수의 if-else 문을 들어와서 이전에 그냥 실행시켰을 때처럼 메시지를 출력하고 프로그램이 종료된다.

 

여기서 원하는 것은 getbuf() 함수가 return할 때, smoke() 함수가 실행되도록 하는 것이다.

 

getbuf() 함수의 Assembly Code

gdb의 disas 명령어로 getbuf 함수의 assembly code를 얻을 수 있다.

일단, getbuf 함수를 호출하는 test() 함수의 15줄에 breakpoint를 걸고 실행시킨다.

 

Return Address는 스택에 저장되어 있다

알아내야 할 것 중 하나는 getbuf 함수의 return address가 어디에 저장되어 있는지 이다. 호출 직전인 test() 함수의 15줄과 호출 직후인 getbuf() 함수의 0줄에서 각각 스택 포인터 esp 레지스터의 값을 보면,

 

0x55683c78 -> 0x55683c74

 

로 값이 변경되었음을 알 수 있다. x86 ISA에서 call 명령어가 어떻게 구현되는지 자세히 모르는 상태이지만, 어쨌든 다음 명령어의 PC 값을 레지스터에든 스택에든 저장을 해둘 것이다. 매뉴얼에도 힌트로 제시되어 있듯이, 스택을 확인해보면 현재 esp 레지스터에 들어있는 값이 곧 getbuf() 함수가 return할 때 돌아갈 주소(PC) 값이다.

이제 어떻게 exploit string과 stack corruption이 연결되어서 프로그램의 흐름을 바꿀 수 있는지를 알아내야 한다.

 

input string도 스택에 저장된다

이제 input string이 스택에 어떻게 저장되는지를 확인하기 위해, get 함수 직전과 직후에 각각 breakpoint를 걸어두고, 임의로 abcdefg 라는 string을 입력하였다.

 

get 함수 내부를 뜯어보지 않더라도, input string이 스택에 저장될 것이라는 것은 추측할 수 있다. get 함수가 종료된 직후 스택 포인터가 가리키는 주소를 byte 단위로 50개 출력해보면 아래와 같다.

a의 ascii 값이 0x61인데, 이 값들은 스택 포인터+16부터 차례로 저장되어 있는 것을 알 수 있다. 뒤에 return address가 저장되어 있다는 사실로부터, 조금 더 뒤의 값까지 찍어보면,

역시 마지막 4 byte에는 return address인 0x08048dbe 가 들어있다.

 

Stack Corruption with Long Input String

그러면 여기서 드는 의문은 만약 input string이 44개보다 더 길게 들어오면 스택에 저장된 값들이 어떻게 변할까 이다. 이를 확인하기 위해 길이가 48인 문자열을 입력하였고, 편의상 8개씩 반복되는 문자열로 하였다.

 

그리고, 아까처럼 스택 포인터 + 16의 주소를 찍어보면 아래와 같이 stack corruption이 일어나는 것을 볼 수 있었다.

스택을 그림으로 표현해보면 아래와 같다.

Segmentation Error

이제 이 프로그램을 마저 실행시켜보면, 아래와 같이 segmentation error가 난다.

여기서 주목할 점은 마지막에 jump한 address가 우리가 입력했던 문자열의 hex 값이라는 것인데, 여기서는 이 주소가 임의의 값이므로 아무 것도 참조하지 않는 것이다.

 

smoke() 함수로 jump하도록 input string 입력

이제, 이 address가 smoke() 함수의 PC 값이 되도록 하면 된다.

 

disas smoke

 

명령어를 통해 smoke 함수의 시작 주소 값이 0x08048c18 임을 알 수 있다. 이제 이를 input string의 45~48번째 값으로 주면 된다.

 

exploit.txt 파일을 아래와 같이 작성한다. (사실 44번째 이전의 값들은 여기서 크게 상관이 없다.)

이 파일을 hex2raw 프로그램을 통해 변환하고, output 파일을 확인해보면 print로 확인하기는 힘들다. 왜나하면 이 값들은 모두 대응되는 ascii 문자가 알파벳이나 숫자 같은 character가 아니기 때문이다.

이제 이렇게 변환된 파일을 이용해 input string을 입력하면 level 0을 해결할 수 있다.