상세 컨텐츠

본문 제목

0x01 - x64 Basic BOF

Linux System_BOF/x64

by 킹갓해커다똥 2020. 6. 14. 23:20

본문

이번 글은 64비트 환경에서의 BOF를 다룬다.

나는 64비트뿐만 아니라 32비트 환경에서의 Pwnable 경험도 없기 때문에 나와 같이 백지상태에서 Pwnable을 시작하려는 사람들을 위해 버퍼오버플로우 공격에 대한 원리부터 관련 지식들을 차근차근 다루도록 하겠다!

 

버퍼가 무엇인지 잘 모르는 사람들을 위해!

버퍼의 개념을 이해하기 위해 단순히 값을 입력하고 출력해주는 간단한 프로그램을 준비했다. 문자형 변수 strgets로 사용자의 입력을 받아 값을 저장하고, printf를 이용하여 str에 저장된 문자열을 사용자에게 출력해주는 프로그램이다.

 

그럼 프로그램을 실행시켜보자! 입력한 ‘qqqqqqqqqq’ 가 그대로 출력되는 것을 볼 수 있다.

 

우리가 프로그램을 실행시키면서 사용한 gets, prinft 함수는 C언어에서는 표준 입출력 함수라고 한다. 이 표준 입출력 함수를 사용할 때에는 입력할 값 또는 출력할 값을 일시적으로 저장할 수 있는 임시 메모리 공간이 필요한데 이때 할당해주는 공간을 버퍼라고 한다.

 

 

버퍼를 알았으니 버퍼오버플로우 이해를 위한 준비가 끝났을까?.. 노우노우.. 버퍼오버플로우를 이해하기 위해 메모리 구조도 알아야한다. (알아야할게 왜 이렇게 많아 ^~^..)메모리는 아래와 같은 구조로 정의할 수 있다.

코드 영역엔 실행파일을 구성하는 명령어들이 저장되고, 이 영역에 저장된 명령어를 시스템이 읽어 하나씩 처리한다. 데이터 영역에는 프로그램 내에서 선언된 전역변수가 들어간다. 스택 영역은 위 프로그램에서 선언한 str과 같이 우리가 프로그램에서 사용하는 지역변수들이 저장된다. 스택에선 PUSH로 데이터를 저장하고 POP으론 데이터를 읽어들이는데. 이런 방식을 LIFO(Last-In First-Out)방식이라고 말한다. (그러니까 우리가 선언한 지역변수의 값에 접근하기 위해선 스택영역을 참조해 PUSHPOP연산을 한다는 것이다!)

 

스택을 더 자세히 보면 아래와 같다. 지금까지 버퍼가 뭔지메모리 구조가 뭔지 설명한 것이 바로 이것을 위해 존재했던 것이다.. 우리가 앞에서 말한 버퍼는 이 스택에 생성된다. 버퍼는 데이터를 저장하기 위해 필요한 일정한 공간을 할당받는데, 이 공간의 크기보다 큰 데이터를 쓰게되면 버퍼의 공간이 넘치게되고, RET(Return Address) 영역, 즉 다음에 복귀하기 위해 참조할 주소가 들어있는 공간을 침범하게 되며 프로그램에 에러가 발생되는 것이다.

 

 

위에서 살펴본 입출력 프로그램을 다시한번 실행해보자. 할당 받은 버퍼의 크기보다 더 많은 문자를 입력하자 크래쉬가 발생되면서 Segmentation falut가 출력됐다. Segmentation falut는 메모리에 잘못 접근했을 때 출력되는 메시지이다. 한마디로 할당받은 메모리 공간이 아닌 다른 영역을 침범했다악!!! 라는 것! RET 영역을 침범할 수 있다는 것은 RET 영역에 우리가 실행하고 싶은 어떤 명령어의 주소를 넣을 수 있다는 것이고, 버퍼에 우리가 원하는 코드를 넣어 RET이 버퍼의 주소를 향하게 만들면?!  

 

예제

위에서 살펴본 프로그램을 이용해 버퍼오버플로우를 실습해보겠다! 해당 프로그램은 BOF가 발생되는 조건을 가지고 있는 취약한 프로그램이다. 프로그램을 보면 buf 문자형 변수는 200이라는 공간을 할당받았고, 그 값을 gets로부터 입력 받는다. gets 함수는 키보드로부터 문자열을 입력받아 사용자가 엔터를 치기전까지 입력한 값들을 주어진 메모리 주소에 저장하는 역할을 한다. gets 함수에는 큰 문제점이 있는데 엔터를 치기 전까지는 얼마든지 많은 값을 입력할 수 있다는 것이다.

buf라는 변수에 200이라는 공간이 할당되어 있지만 gets300이 할당됐는지 400이 할당됐는지 신경안쓰고, 사용자가 입력한 곧이 곧대로 받아드리기 때문에 할당받은 공간 이상의 영역을 침범할 수가 있는 것이다!!!!

 

자 그럼 컴파일을 해서 바이너리 실행파일을 생성하자. 최신 리눅스 환경에는 여러가지 메모리 보호기법이 적용되어 있기 때문에 보호기법을 해제하는 옵션을 붙여서 컴파일을 진행한다. (기초를 다루는거기 때문에 찡긋^^)

 

gdb로 생성된 실행파일을 열어 main 함수를 보자. sub rsp, 0xd0을 보면 스택의 공간을0xd0만큼 확장한다는 뜻인데 0xd0 값을 구해보면

 

십진수로 208이 나오는 것을 알 수 있다. 이게 바로 버퍼의 크기다. 그럼 208바이트의 이상의 값만 채우면 RET를 덮을 수 있을까? 위에서 살펴본 스택의 구조에서 SFP를 지나야 RET를 덮을 수 있다. x86에선 SFPRET영역이 4Byte지만 x64환경에서는 기본적으로 SFPRET 영역이 8byte로 이루어져 있다. 그럼 208+8=216바이트 이상의 값을 넣어야 RET 영역을 침범할 수 있는 것이다.

 

 

RET 까지의 거리는 구했으니 이제 버퍼의 주소를 찾아보자. gets 함수가 실행된 후인 *main+31 위치에 브레이크 포인트를 걸어서 입력한 것이 들어가는 위치를 통해 버퍼의 주소를 확인해보겠다.

 

프로그램 실행 후 A 문자를 400개를 집어넣는다.

 

rsp를 확인해보면 0x7ffffffffe3e0 위치부터 0x41(A)값이 채워진 것을 확인할 수 있다. 이곳이 버퍼의 시작주소이다.

 

그럼 pwntools를 이용해 익스플로잇 코드를 짜자. payload에는 실행시킬 쉘코드와 0x90(NOP) 값을 216바이트만큼 넣고 RET 영역에는 쉘코드가 위치하는 버퍼의 주소를 넣었다.

 

얼랍숑? 쉘은 떴는데.. Got EOF while sending in interactive 메시지가 뜨면서 자꾸 종료된다?.. 먼가 잘못된것이 분명해! 찾아보니 gdb분석에서의 버퍼의 시작 주소와 실제 실행 시에 버퍼의 시작 주소가 차이가 날 수 있다고 한다. 버퍼의 시작 주소를 알기 위해 코드를 조금 수정해보자.

 

버퍼의 시작 주소를 출력하도록 수정해서 실제 실행 시에 버퍼의 주소를 확인해보자

 

버퍼의 시작 주소 0x7fffffffe420를 확인했다.

RET 영역에 들어갈 주소를 수정하고 다시 실행해보자.

 

쉘이 제대로 실행된다

 

 

HackCTF x64 Buffer Overflow 문제

BOF를 배웠으니 이정도 문제는 껌아니겠는가 하하하 150점짜리 문제다. 쉘코드를 넣어서 실행시키진 않지만 RET 영역을 변조시키는 것은 똑같다!

 

프로그램을 다운받아서 IDA로 열어보자. 함수 목록에 가장 처음 실행되는 main 함수와 왠지 모르게 의심스러운 CallMeMaybe 함수가 있다.

 

 

main 함수를 분석해보자. 문자형 s 변수는 scanf를 통해 해당 변수에 문자열을 입력받는다. scanf 를 통해 입력 받기 때문에 입력받은 문자열의 길이 체크를 하지 않아 BOF에 취약하다.

 

callMeMaybe 함수를 보면 execve 함수를 이용해 쉘을 호출해준다. main 함수에서 return address의 주소를 callMeMaybe()의 주소로 변조하면 쉘을 실행시킬 수 있을 거 같다.

 

서칭하다가 알게된 방법인데 peda에선 패턴을 생성해 생성된 패턴을 기준으로 값들의 위치를 파악해 RET 까지의 길이를 구할 수 있다. 이 방법을 이용해400바이트의 패턴을 생성시키는 명령어를 실행해보자

 

생성된 패턴을 실행 프로그램에 대입해보자

 

 

대입한 패턴의 값을 확인하고

 

해당 값으로 RET 까지의 거리가 280인것을 확인했다.

 

 

RET 영역을 callMeMaybe의 시작주소로 덮어야하니까 callMeMaybe 주소 0x0000000000400606을 확인하고

 

익스플로잇 코드를 작성!

 

코드를 실행시키면 쉘이 잘 실행되고 flag를 얻을 수가 있다!

'Linux System_BOF > x64' 카테고리의 다른 글

0x02 - x64 RTL(Return to libc)  (3) 2020.07.28
0x00 - 64bit System Hacking  (0) 2020.05.28

관련글 더보기