System Hacking

PLT와 GOT

jir4vvit 2020. 9. 22. 08:56

참고 : dreamhack.io
환경 : ubuntu 16.04.7

 

PLT와 GOT를 처음 접한 곳은 아래 문제이다.

jiravvit.tistory.com/entry/dreamhack-pwnable-basicexploitation002-%ED%92%80%EC%9D%B4

 

[dreamhack : pwnable] basic_exploitation_002 풀이

 

jiravvit.tistory.com

이때 PLT와 GOT를 아래와 같이 설명하였었다.

 

PLT는 코드이고 GOT는 주소값이 저장된 공간이다.

함수를 호출하면 PLT 코드가 실행이 되고 PLT 코드는 GOT에 적힌 주소로 이동한다.

 

실습과 함께 더 자세히 알아보자.

 


PLT(Procedure Linkage Table)

외부 라이브러리 함수를 사용할 수 있도록 주소를 연결해주는 역할을 하는 테이블

 

GOT(Global Offset Table)

PLT에서 호출하는 resolve 함수를 통해 구한 라이브러리 함수의 절대 주소가 저장되어 있는 테이블 

 

 

 

ASLR이 적용되어 있는 환경에는 바이너리가 실행될 때마다 라이브러릴가 매핑되는 메모리의 주소가 변함.

PLT와 GOT 영역이 존재하는 이유는 동적으로 라이브러리를 링크하여 실행되는 바이너리의 경우, 바이너리가 실행되기 전까지 라이브러리 함수의 주소를 알 수 없기 때문임.

 

 

//gcc -o example4 example4.c -fno-stack-protector -mpreferred-stack-boundary=2 -m32

#include <stdio.h>

int main(void){

  char buf[32] = {};
  
  puts("Hello World!");
  puts("Hello ASLR!");
  
  scanf("%s", buf);
  
  return 0;
  
}

 

PLT 영역으로 이동

라이브러리가 메모리에 매핑된 후 라이브러리 함수가 호출되면,, 해당 함수의 PLT와 GOT영역에 접근함으로써 함수의 주소를 찾고 실행시킴

 

라이브러리 함수 실행 -> 함수가 바로 실행되는 것이 아닌 PLT 영역으로 jmp -> GOT 영역으로 이동

 


위의 example4의 put영역의 PLT를 call 하는 부분에 break를 걸고 메모리를 살펴보자.

 

break *0x8048320

continue

 

resolve 함수로 jmp

0x804a008 주소에 저장되어 있는 0xf7fee000 함수로 점프하는 것을 볼 수 있다. 이는 PLT 영역에서 호출하는 resolve 함수이다. 이 함수를 통해 puts 함수가 찐으로 저장되어 있는 절대주소를 구할 수 있다.

 

disassemble 0xf7fee000

마지막 ret 부분까지 가서 esp 레지스터를 확인해보자.

break *0xf7fee01b

continue

 

 

0xf7fee000 함수, 즉 resolve 함수는 호출된 라이브러리 함수의 주소를 알아내는 함수라는 것을 눈으로 확인하였다.

 


특정 함수의 PLT를 호출하면 함수의 실제 주소를 호출하는 것과 같은 효과를 나타낸다.

 

PLT의 주소는 고정되어 있기 때문에 서버에 ASLR 보호기법이 적용되어 있어도 PLT로 점프하면 공격이 가능하다.

 

//gcc -o example4 example4.c -fno-stack-protector -mpreferred-stack-boundary=2 -m32

#include <stdio.h>

int main(void){

  char buf[32] = {};
  
  puts("Hello World!");
  puts("Hello ASLR!");
  
  scanf("%s", buf);
  
  return 0;
  
}

위의 example4 예시에서 스택 버퍼 오버플로우 취약점을 이용해 리턴 주소를 puts@plt+6(0x8048326)으로 바꾸고, 첫 번째 인자는 "ASLR!" 문자열의 주소로 바꾸어 보겠다.

 

바꿀 리턴 주소
'Hello ASLR!' 문자열이 저장된 주소

0x804854d 위치에 저장된 값은 아스키코드 값이다. 한 바이트씩 짤라서 해석을 해보면 

lleH

SA o

0!RL

가 된다.

 

ASLR! 문자열은 0x804854d로부터 6바이트 떨어진 곳에 위치한다.

0x804854d + 6 = 0x8048553

 

따라서 ASLR! 문자열이 저장된 주소는 0x8048553 이다.

 

메모리

메모리 구조는 위와같이 생겼으며, 스택 버퍼 오버플로우 취약점을 이용해 ASLR! 을 출력하는 것이 목표이다. 

(매개변수는 저렇게 ret보다 먼저 쌓인다는 것을 잊지 말자..)

 

 

위를 바탕으로 페이로드를 작성해 실행시켜 보자.

 

ASLR! 문자열이 정상적으로 출력되었지만, puts 함수가 실행된 후 리턴할 주소는 0x42424242('BBBB') 이기 떄문에 세그멘테이선 오류가 발생하여 프로그램이 비정상 종료되는 것을 확인할 수 있다.

 

 

함수가 호출될 때 GOT에 저장된 주소로 점프하기 때문에 GOT에 저장된 값을 바꾸면 원하는 주소로 점프할 수 있다. 

 


example4 바이너리의 main 함수에 브레이크 포인트를 걸고 실행한 후 puts 함수의 GOT인 0x804a00c 메모리의 값을 0xdeadbeef 로 바꾸어보자.

 

참고로 0xdeadbeef는 죽은 쇠고기...로.. 우리가 16값으로 눈에 잘 보이는 값을 표현하는데에 사용되는데 보통 디버깅할 때 많이 사용된다고 한다.

puts 함수의 GOT

set *0x804a00c = 0xdeadbeef

 

프로그램을 이어서 실행하면 puts가 호출될 때 puts@got에 저장된 값으로 점프해 eip의 값이 0xdeadbeef가 된 것을 확인할 수 있다.

 

 

 


 

PLT에 존재하는 함수들, 즉 프로그램에서 한 번 이상 사용하는 라이브러리 함수들은 고정된 주소를 통해 호출할 수 있다는 것을 알게 되었다. 

 

하지만.. 이 바이너리, example4 바이너리 한정, 최종 목표인 셸을 획득하는 데 필요한 함수들(system함수 혹은 exec 계열 함수)을 사용하지 않기 때문에 여기의 ASLR 환경에서는 직접적으로 해당 함수를 호출할 수 없다...

 

 

 

 

 

아 그리고 뭔가.. 메모리에서 스택은 아래서부터 쌓이는데, 값은 위에서.. 즉, 낮은주소부터 채워지는 느낌이다.