본문 바로가기

CAT-Security/미분류

PLT . GOT 정리

PLT

  PLT는 "Procedure Linkage Table"의 약자 입니다. 말 그대로 프로시져들을 연결해 주는 테이블 이라는 것입니다. 이 때 중요한 것은 프로그램을 어떻게 구현 했느냐에 따라서 약간의 메커니즘이 달라진 다는 것입니다. 무슨 말이냐 하면 PLT는 프로시져를 연결하지만 외부 프로시져를 대상으로 연결을 합니다. 즉 프로그램코드를 짰는데 모두 직접 같은 화일 안에서 구현한 프로시져들이라면 이것들은 따로 PLT가 필요없이 직접적으로 호출이 가능합니다. 그런데 이런 프로시져가 아닌 다른 라이브러리에 위치해 있는 프로시져를 호출 할 때에는 PLT를 사용해서 그 프로시져를 호출하게 됩니다. 이것은 심볼해석과도 연관이 있습니다. 연관이

 

GOT

  GOT는 "Global Offset Table"입니다. 이 테이블은 프로시져들의 주소를 가지고 있습니다. 이 테이블의 중요성은 바로 PLT가 참조하는 테이블 이라는 것입니다. PLT가 어떤 외부 프로시져를 호출할 때 이 GOT를 참조해서 해당 주소로 점프하게 됩니다.

 

관계도

  PLT와 GOT의 간단한 관계도 입니다.

 

[출처] PLT & GOT|작성자 해바라기 ObjC


pltgot_int_com.png



ROT1 프로그램의 got와 plt의 영역 이다.
got는 0x0804961c 부터 0x24 만큼의 영역이 할당 되어 있고
plt는 0x080482dc 부터 0x60 만큼의 영역이 할당 되어있는것을 확인 할 수 있다.

 



plt영역과 got영역을 살펴 보았다. 둘의 상관관계가 어떻게 되어있을까?


plt영역에서  jmp *주소 로 하는곳의 값들을 살펴보면은 바로...!!

 
각 jup 하는곳 바로 아래의 주소를 가르키고 있다.
즉 jmp를 해서 got부분으로 가더라도, got부분은 plt부분의 jmp다음 부분을 가르키고 있는것 이다.
 
현재가 맨 위에 Before Symebol Resolved 부분으로 볼수 있다. 자세히 본다면 elf부분에서
[함수+6] push값 바로 밑에 jmp    0x80482dc <_init+24> 하나씩 있는것을 볼수 있다.
바로 맨 위에 부분으로 돌아가는것이다.  이때 부터  _dl_rentime_resolve  함수가 실행 된다.
_dl_rentime_resolve 가 push 된 값을 인수로 취해서 내부적으로 실제 함수 주소를 GOT에 저장하고
그 다음 jmp가 가르키는 부분에 실제 함수 주소가 저장되는 것 이다.
다시 같은 함수를 호출 한다면, 이미 과정을 겪었기에 바로 실제함수 주소로 jmp 된다. 
 

이렇게 돌아가서 다시 함수가 실행되면은, got부분에 그 함수의 실제주소가 들어가게 되는것 이다.
그래서 처음에 한 plt -> got -> plt로 돌아오는일 없이 바로
plt -> got -> 함수 실행으로 실행될수 있게 되는것 이다. 


 [함수 +6] 부분을 가르키고 있던 got 영역의 값들이 전부 실제 함수 주소로 바뀐것을 볼수 있다.

정리 하자면 PLT는 Procedure Linkable Table이란 녀석인데, 
프로그램이 호출하는 모든 함수들을 나열 하는 테이블이다.
GOT는 Global Offset Table이라고 하며, 프로그램 실행 훟 libc.so 내의
실제 함수 주소가 저장 되는 곳이다.

가장 처음 공유라이브러리 내의 함수가 호출 되었을때 동적 링커는 먼저 PLT를 본다.
PLT는 실제 호출될 함수를 나타내는 값을 _dl_rentime_resolve 함수의 인자로 넘기고
_dl_rentime_resolve 함수는 전달된 인자 값을 사용하여 호출된 함수의 실제 주소를
구한후 GOT에 저장 한뒤 호출된 함수로 점프 한다.
이후 동일 한 함수가 다시 호출 하게 되면 동적 링커는
GOT에 저장 되어 있는 호출된 함수의 실제 주소로 바로 점프하게 되는것 이다.

첫번째 실행
printf함수 호출 -> plt -> push 주소를 포인터로 하는 주소로 이동 -> dl_rentime_resolve 
-> got 저장후 실제 주소로 점프

두번째 실행
printf함수 호출(두번째) -> plt -> got에 이미 바로 printf함수 주소가 있으니 바로 jmp!!




GOT . PLT 개념을 위한 문제이다.
printf를 2번 실행하고(got 영역에  printf 실제 함수 주소를 넣은뒤 한번더 printf를 실행한다.)
printf뒤에 인자로 /bin/sh를 가지고있어서, printf 부분을 system으로 바꿔준다면은 쉘이 떨어지는 프로그램이다.

0x804830c <printf>: jmp    *0x8049630
printf가 jmp 하는 포인터주소이다. 이때 포인터주소의 개념을 잘 이해 해야 한다. 
0x8049630의 주소로 jmp 하는것이 아니라, 0x8049630이 가르키고 있는곳의 4바이트를 가져와서 읽는것이다.
처음에 
0x8049630가 가리키고 있는것은 바로 밑에 [printf+6]의 부분이다.
하지만 한번 printf함수가 실행이 되고, got에 printf의 실제 실행 주소가 저장된다면은 바로 printf가 실행된다.
두번째 printf 가 실행될때, system 함수를 가르키게 한다면..? 한번 해보자! 




main에 브레이크를 한뒤 프로그램을 실행 시키면 라이브러리에 있는 함수들이 할당되게 된다.
그 뒤 system 함수의 주소를 알아 낸뒤 jmp 하는부분을 시스템의 함수로 바꿔치기 했다.
결국엔 printf 대신 system 함수가 실행되고 인자값으로 /bin/sh를 받아서 온전히 실행되는걸 확인 할수 있다.



다른풀이 방법
gdb안에서 주소값을 바꿔주는 간단한 방법으로 쉘을 띄었다
그렇다면 쉘 상에서, 프로그램을 정상적으로 실행시키면서 쉘을 띄우는 방법은 없을까?
이 방법에서는 return into lib의 기법이 필요하다.

우선 기본적으로 함수의 주소값, 즉 got안에 들어있는 printf를 가르키고있는 포인터주소
system을 가르키는 포인터주소로 바꿔주는것이 핵심이다. 이것에는 몇가지 제한사항이 있다.
1. 주소값을 바꿔야 한다. -> memcpy 함수 사용
2. memcpy 함수를 이용해야 한다. -> return into lib 기법 사용
3. 프로그램을 끝내지 않아야 한다. -> memcpy의 리턴값을 함수의 윗부분을 가르키게 한다.
4. system을 가르키고있는 포인터주소를 찾아야 한다. -> while문으로 구현
5. 공격!!


1. 주소값을 바꿔야 한다. -> memcpy 함수 사용


우선 memcpy함수의 주소값을 구한다.


2. memcpy 함수를 이용해야 한다. -> return into lib 기법 사용
return into lib 기법을 이용하여, ret 위치에 memcpy의 주소를 넣어주어서 memcpy의 함수실행을 한다. 
memcpy 함수는 총 3개의 인자를 받는 함수이며, 두번쨰인자값이 가르키는 곳의 세번째 인자값만큼의
bite값을 읽어와서  첫번째 인자의 복사해 넣는 함수이다.

즉 printf 함수를 가르키고있는 포인터주소를 첫번째 인자에, system함수주소를 가르키고있는 포인터주소를 두번째 인자에 , 그리고 세번째에 4만큼 읽어서 복사하게 한다면은 printf를 가르키고있던 포인터주소가
system을 가르키게 될것이다.



3. 프로그램을 끝내지 않아야 한다. -> memcpy의 리턴값을 함수의 윗부분을 가르키게 한다.
return into lib의 기법을 적용시켜서 
printf와 system을 가르키는 주소를 바꾼다고 하더라도
바꾼뒤에 바로 프로그램이 끝나버리기때문에 쉘을 띄울수가 없어진다.
그래서 memcpy 함수의 return 주소값을 다시 프로그램의 '어느지점'을 가르키게 해야 한다.

여러번 해본 결과 프로그램 시작지점을 가르키든, 중간지점을 가르키든 문제가 될것은 없었다.
핵심은 /bin/sh 전의 지점을 가르키게 해야 한다는 것이다.
그래야지만 printf와 system을 가르키는부분을 바꿨을때 /bin/sh를 인자로 받아서
쉘을 띄울수 있기 때문이다.

 
4. system을 가르키고있는 포인터주소를 찾아야 한다. -> while문으로 구현  
system을 가르키고있는 포인터 주소를 가르킨다라는것은 memcpy함수의 특성 때문이다.
memcpy가 포인터주소를 인자값으로 받기때문에, system과 printf의 주소를 인자값으로 넘기면
그 자체를 포인터주소라고 판단해서 되려 엉뚱한 값을 넘기게 되는것이다.


메모리를 0x42000000부터 찾은 이유는 공유 라이브러리이기 떄문이다.
우리가 사용할수 있는 메모리에서 공유라이브러리가 가장 덩치가 크고, 분명 push system 이런식으로
라이브러리 안에서 사용할것이기 떄문에, system 함수 주소를 가르키는 포인터주소를 찾을수 있을것이다.
mcmcmp로 찾았다.
비슷한 예로 시스템 함수주소부터 /bin/sh 라는 문자열을 찾는 방식과 비슷했다.


payload 는 다음과 같다.


ret 부분에 memcpy를 넣고, 뒤에 memcpy의 return값으로 /bin/sh의 주소를 넣어 준다.
그리고 첫번째 인자값으로 printf를 가르키는 포인터주소를 넣어주고, 두번째 인자값으로 system을 가르키는 포인터주소를 넣어준다. 그리고 세번째 인자값으로 4를 넣어 준다.



쉘을 띄울수 있었다.








'CAT-Security > 미분류' 카테고리의 다른 글

cogegate 300문제 풀이(ida)  (0) 2012.06.09
정리 잘 되어 있음!!  (0) 2012.04.06
blind sql injection 기초 및 실습  (1) 2012.03.13
blind sql injdection  (0) 2012.03.11
[Hack-Me] View Sourcecode 2(100)  (0) 2012.03.02