본문 바로가기

3. Pwnable (포너블)/2) 개념 정리

[2020.11.07] 4너블4ever - 달고나 문서 44p~57p

Null의 제거

 

문제점 발견 : char 형 배열 (문자열) 에서는 0의 값을 만나면 그것을 문자열의 끝으로 인식

0x00 뒤에 어떤 값이 있더라도 그 이후는 무시

따라서 \x00인 기계어 코드가 생기지 않도록 만들어야 한다. ( 대표적으로 mov, $0xb, %eax)


다음과 같이 NULL을 제거한 쉘을 실행시키는 어셈블리 코드를 작성하였다.

 

이제 이것을 문자열화 시키는 것만이 남았는데, char 형 배열에 16진수 형태의 바이너리 데이터를 전달할 것이다. 그러기 위해서는 \x90형식으로 바꿔주어야 한다.


기계어 코드를 추출하고, 이를 문자열 배열에

넣기위해 다시가공하면















다른 방법

쉘 코드를 저장할 변수를 int형으로 만들어주면 된다.

*유의할점

little endian순서로 정렬, int형이므로 4byte단위로 만들어 줘야 함.

 

but int형 배열을 사용하면 objump를 이용하여 얻은 기계어 코드를 little endian 방식으로 재정렬 해줘야 해서 귀찮다.

그리고 대부분의 buffer overflow 공격 방법이 문자열형 데이터 처리의 실수를 이용.

그러므로 char형으로 생성하는 것이 더 편함.

 

seteuid()함수를 이용하여 프로그램 소유자의 권한을 얻어오기 위해서는 쉘 코드에 setreuid()가 하는 기계어 코드를 추가해 줘야 함.

 

setreuid()함수의 기계어 코드와 어셈블리어 코드

 

exit(0) : 공격자가 overflow공격을 수행하고 난 뒤 프로그램의 정상적인 종료를 위해서 사용

exit(0)의 기계어 코드는

“x31\xc0\xb0\x01\xcd\x80” 이다.

Buffer Overflow 공격

 

버퍼오버플로 취약점이 있는 vul.c 코드를 살펴보면

이 프로그램은 1024바이트의 버퍼 공간에 쉘 코드와 NOP로 채우고, 4바이트는 main 함수의 base pointer이므로 역시 NOP로 채우고, 다음 만나는 4바이트가 return address이므로 이곳에 쉘 코드가 있는 곳의 address를 넣어주면 쉘 코드를 실행시킬 수 있다.

즉, 이 쉘 코드가 있는 곳의 address를 찾는 것이 가장 큰 문제이다.

 

- 고전적인 방법

가장 고전적인 방법은 쉘 코드가 있는 곳의 address를 추측하는 것이다.

그래서 몇 번의 시행착오를 거치면서 쉘이 떨어질 때까지 계속 공격을 시도한다. 이 때 쉘 코드가 실행되는 확률을 높이기 위해, buffer를 채우기 위해 NOP를 사용하는데 보통 NOP는 0x90 값을 많이 쓴다.

 

NOP(No Operation): 아무런 실행을 하지 않는다는 것. NOP의 역할은 기계어 코드가 다른 코드와 섞이지 않게 하는 것

 

버퍼오버플로 공격에서 NOP는 쉘 코드가 있는 곳까지 아무런 수행을 하지 않고 흘러 들어가게 만드는 목적으로 사용된다. 즉 CPU는 NOP를 만나면 유효한 명령이 있는 쉘 코드의 시작점이 나올 때까지 한 바이트씩 EIP를 이동시키게 되는 것.

 

따라서 고전적인 방법에서는 쉘 코드 앞을 NOP로 채우고 return address를 NOP로 채워져 있는 영역 어딘가의 주소로 바꾸면 operation의 흐름은 NOP를 타고 쉘 코드가 있는 곳까지 흘러 들어갈 수 있게 되는 것.





- 환경변수를 이용하는 방법

*nic 계열의 쉘에서 환경변수는 포인터로 참조된다. 환경변수는 응용프로그램에서 참조하여 사용할 수 있기 때문에 putenv(), getenv()같은 API 함수들도 많이 사용된다. 이 특성을 이용해 공격자는 환경변수를 하나 만들고 이 환경 변수에다 쉘 코드를 넣은 다음에 취약한 프로그램에 환경변수의 address를 return address에 넣어줌으로써 쉘 코드를 실행하게 할 수 있다. 이는 overflow 되는 버퍼의 크기가 쉘 코드가 들어갈 만큼 넉넉하지 못할 때 매우 유용하다.