Buffer Overflow의 이해 (1)

옥영진·2021년 5월 6일
0

BOF

목록 보기
3/4

버퍼(Buffer) : 시스템이 연산 작업을 하는데에 있어 필요한 데이터를 일시적으로 저장하는 공간. 대부분의 프로그램에서는 버퍼를 stack에 생성한다.

buffer overflow는 생성된 버퍼에 버퍼의 크기보다 큰 데이터가 저장될 때 발생한다. stack에서 버퍼 위에는 base pointer, 그 위에는 return address가 저장되어 있다. return address는 현재 함수의 base pointer 위에 있으므로 그 위치가 변하지 않는다. 따라서 버퍼의 크기보다 큰 데이터가 버퍼에 저장되려고 하면 그 위에 있는 base pointer, return address, 그 위의 데이터마저도 바뀌게 될 것이다.

buffer overflow 공격은 공격자가 메모리상의 임의의 위치에다가 공격 코드를 저장하고, return address가 저장되어 있는 지점에 공격 코드 시작 주소를 집어 넣음으로써 EIP에 공격자의 코드가 있는 곳의 주소를 저장시켜서 공격하는 방법이다.

이전 글에서 예시로 든 simple.c 프로그램에서 function() 함수 내에 buffer1[15], buffer2[10] 두 개의 버퍼가 있고 40 바이트가 할당되었다. 이 버퍼에 아래와 같은 코드로 데이터를 쓰려고 한다.

strcpy(buffer2, receive_from_client);

strcpy 함수는 길이 체크를 해주지 않기 때문에 receive_from_client 안에 있는 데이터에서 NULL(\0)을 만날 때까지 버퍼에 데이터를 복사한다.


위 그림과 같이 공격 코드를 구성하려고 한다. 공격자가 전송하는 데이터는 receive_from_client에 저장되어 버퍼에 복사될 것이다. 그리고 strcpy가 호출되어 receive_from_client가 buffer2에 복사된다면 아래와 같이 매칭이 될 것이다.


strcpy가 호출된 후 stack은 아래와 같은 구조가 될 것이다.


stack을 자세히 살펴보면 원래 구성했던 데이터와 순서에 차이가 있음을 알 수 있다.

Byte order

데이터가 저장되는 순서가 바뀐 이유는 바이트 저장 순서 때문이다. 현존하는 시스템에서는 두 가지 바이트 저장 순서가 있는데, 빅 엔디안, 리틀 엔디안이 그것이다.

  • 빅 엔디안(Big Endian) : 낮은 메모리 주소에 데이터의 높은 바이트(MSB)부터 저장하는 방식.
  • 리틀 엔디안(Little Endian) : 낮은 메모리 주소에 데이터의 낮은 바이트(LSB)부터 저장하는 방식.

0x12345678 이라는 32비트 크기의 정수를 저장하려고 할 때, 바이트 저장 순서에 따라 다음과 같이 저장된다. (출처 : http://www.tcpschool.com/c/c_refer_endian)


이러한 byte order 문제 때문에 return address 값을 넣을 때는 byte order를 고려해야 한다.


위 그림에서 보여주는 공격 코드는 execve(“/bin/sh”,...) 으로, 쉘을 띄우는 것이다. 공격 코드 시작 주소가 0xBFFFFA60 이라고 할 때 return address는 위와 같이 될 것이고, 함수가 리턴될 때 return address 값인 0xBFFFFA60이 EIP에 저장되면서 0xBFFFFA60에 있는 명령을 수행할 것이므로 execve(“/bin/sh”,...)가 수행된다. 이게 바로 buffer overflow를 이용한 공격 방법이다.

공격 시 발생할 수 있는 문제

위 예시에서는 stack 안에 공격 코드가 return address 위에 24 바이트 공간에 들어있다. 하지만 return address 위 버퍼 공간의 크기가 공격 코드가 들어가기에 충분하지 않다면 다른 공간을 찾아야 한다. 위 stack에서 사용할 수 있는 공간은 90909090... 이 들어있는 function() 함수가 사용한 공간이다. 이 공간은 40 바이트이고, 추가로 main() 함수의 base pointer가 들어 있는 4 바이트까지 총 44 바이트이다.
이 공간을 활용하기 위해서는 return address가 EIP에 들어간 다음에 40 바이트 stack 공간의 명령을 수행할 수 있도록 해 주어야 한다. 하지만 해당 명령어가 있는 주소를 정확히 알아내기는어려운 일이기 때문에 간접적으로 그곳으로 명령 수행 지점을 변경해주는 방법을 사용한다.


위 그림은 ESP 값을 이용하여 명령 수행 지점을 지정해주는 방법을 보여준다.

쉘 코드가 return address 아래에 있는데, return address는 똑같다. return address가 stack에서 POP되어 EIP에 들어가게 되면, stack pointer는 1 word 위로 이동해서 ESP는 return address가 있던 자리 위를 가리키게 된다. EIP는 0xbffffa60을 가리키고 있을 테니 그 곳에 있는 명령을 수행할 것이고, 이 명령은 다음과 같다.

  • ESP가 가리키는 지점을 쉘 코드가 있는 위치를 가리키도록 48byte를 빼기.
  • jmp %esp 명령을 수행하여 EIP에 ESP가 가리키는 지점의 주소 넣기.

이 명령들을 쉘 코드로 변환하면 8 바이트의 공간만 있으면 충분하다.

이렇게 return address 이후의 버퍼 공간이 부족할 경우 return address 이전의 버퍼 공간을 활용할 수 있지만, 만약 이 공간도 부족하다면 return address 부분만을 제외한 위아래 모든 공간을 활용하도록 코딩하거나 그것도 안 된다면 또 다른 공간을 찾는 작업을 해야 한다.

profile
안녕하세요 함께 공부합시다

0개의 댓글