본문 바로가기
System/Linux

GOT overwrite - GOT를 PLT로 덮는다??

by bbolmin 2012. 9. 24.

 

GOT, PLT에 대한 설명 - PLT(Procedure Linkage Table), GOT(Global Offset Table)

 

 

- 그럼 본론으로 ....

 

 

 

보통 GOT overwite를 하면 got주소를 plt주소로 덮어서 공격한다.

ex) printf의 got를 system의 plt로 덮는다!

 

 

예전에 GOT overwirte를 할 때도 생각했었는데 got를 got로 덮거나 plt를 got나 plt로 덮어도 되나 생각해봤었다.

... 생각만 해보고 실습은 해보지 않았었다. 그래서 오늘 실습해본 내용을 적어보려고 한다. ()

 

 

이게 실습하면서 은근히 헷갈렸다. 그래서 아래 2가지를 확실히 기억하면 좀 더 이해하기 쉬워진다.

 

 

GOT에는 주소가 저장되어 있다. (plt+6의 주소 or 실제 함수의 주소)

PLT에는 코드가 저장되어 있다. (GOT로 점프하는 코드!)

 

ex)

(gdb) x/3i 0x8048390
   0x8048390 <puts@plt>:        jmp    *0x804a004
   0x8048396 <puts@plt+6>:      push   $0x8
   0x804839b <puts@plt+11>:     jmp    0x8048370


(gdb) x/x 0x804a004
0x804a004 <puts@got.plt>:       0x08048396

 

 

 

자 이제 가능한 경우를 보면 (이해하기 쉽게 printf함수를 system함수로 GOT overwrite한다고 하자.)

 

1. printf의 got를 system의 plt로 덮는다. //(또는 system함수의 주소로 덮음 - 일반적인 방법)

2. printf의 got를 system의 got로 덮는다.

3. printf의 plt를 system의 plt로 덮는다.

4. printf의 plt를 system의 got로 덮는다.

 

 

먼저 1) printf의 got를 system의 plt로 덮는다.

 

(gdb) x/3i 0x8048390
   0x8048390 <puts@plt>:        jmp    *0x804a004
   0x8048396 <puts@plt+6>:      push   $0x8
   0x804839b <puts@plt+11>:     jmp    0x8048370


(gdb) x/x 0x804a004
0x804a004 <puts@got.plt>:       0x08048396

 

printf의 plt, got가 위와 같다고 보면 빨간부분이 system의 plt로 덮어지는 것이다. 그러면 <puts@plt>:  jmp    *0x804a004 부분에 의해 system함수의 plt코드가 실행되므로 마치 call system을 한 것과 같은 모양이다.

 

printf의 got를 덮어서 overwrite를 성공시키는 방법은 아래 3가지로 정리할 수 있을 것이다.

 

1. system의 plt의 주소

2. system의 plt+6의 주소

3. system함수의 주소

 

 

 

그럼 이제 나머지 경우 대해 생각해보자.

 

2. printf의 got를 system의 got로 덮는다.

3. printf의 plt를 system의 plt로 덮는다.

4. printf의 plt를 system의 got로 덮는다.

 

 

 

3가지 경우가 있지만 아래 3가지만 생각한다면 쉽게 이해 될 것이다.

 

- GOT에는 주소가 저장되어 있다. (plt+6의 주소 or 실제 함수의 주소)

- PLT에는 코드가 저장되어 있다. (GOT로 점프하는 코드!)

- 흐름에 맞게 GOT에는 원래 GOT에 있던값이, PLT에는 원래 PLT에 있던 값이 있어야 한다. (반드시 그런건 아니지만, 흐름상 그렇다.)

 

 

 

먼저 2번의 경우를 보자.

 

got를 got로 덮는다?


got에는 plt+6의 주소 또는 실제 함수의 주소가 있다. 따라서 got를 참조하여 코드를 실행하는 부분으로 간다. 실행할 코드를 가지고 있는 주소를 가져야할 got가 단순히 주소만 가지고 있는 got로 다시 덮는다면

[0x8048390 <puts@plt>:        jmp    *0x804a004] 부분에서 에서 정상적인 실행이 되지 않는다.

 

 

 

이제 3,4번의 경우를 보자.

 

printf의 plt를 덮는다?., plt는 got주소를 참조한다. 하지만 plt=&got가 아니라 plt에는 코드가 저장되어 있다는 것을 기억해야 한다. 그럼 plt 코드의 got주소를 부분만 overwrite하면 공격이 가능할 것 같기도하다. 아래를 보자 

 

(gdb) x/3i 0x8048390  // x/3i로 코드를 봤을 때
   0x8048390 <puts@plt>:        jmp    *0x804a004
   0x8048396 <puts@plt+6>:      push   $0x8
   0x804839b <puts@plt+11>:     jmp    0x8048370


(gdb) x/2x 0x8048390 // x/2x로 헥스값으로 봤을 때 0x0804a004가 저장되어있다.
0x8048390 <puts@plt>:   0xa00425ff      0x08680804


(gdb) x/2x 0x8048390+2
0x8048392 <puts@plt+2>: 0x0804a004      0x00000868

 

printf의 plt를 덮어서 흐름을 바꾸고 싶다면 점프되는 주소값을 system의 got주소로 하면 될 것이다.

즉 주소값은 plt+2부분에 저장되어 있으므로 printf의 plt+2를 system의 got로 덮으면 된다.

 

간단하게 테스트해봤다.

테스트 소스. (특정 주소에 overwrite하는 프로그램이다. - printf("id")가 system("id")가 되도록 해보면)

 

#include <stdio.h>

void test()
{
    system("");
}

void main()
{
    int *ptr;
    int input;

    printf("A의 주소값에 B를 넣는다.\n");

    printf("input A : ");
    scanf("%x", &ptr);

    printf("input B : ");
    scanf("%x", &input);
    *ptr = input;


    printf("id\n");
}

 

이걸로 printf의 plt+2를 system의 got로 덮어보니... 아쉽게도 결과는 세그먼트 폴트가 떴다.

 

 

(실습환경은 우분투 11버전이다.)

 

위 그림과 같이 plt영역은 got와 다르게 READONLY라고 되어있다. 따라서 overwrite하면 세그먼트 폴트가 일어나는 것 같다. 이 것이 PLT overwrite가 없는 이유인가 보다. ㅋ 

 

 

 

이제 정리해보면 먼저 READONLY인 PLT를 덮는 3,4번의 경우는 쉽게 포기.

 

 

1. printf의 got를 system의 plt로 덮는다.

2. printf의 got를 system의 got로 덮는다.

 

2번의 경우에는 got가 원래 가지고 있는 값이 무엇인지 생각해보면 왜 안되는지 쉽게 답이 나온다.

 

마지막으로 정리해보면 got에 덮을만한 값은

 

1. plt의 주소

2. plt+6의 주소

3. 코드의 주소 [ex)system함수의 실제 코드 주소, 쉘 코드가 있는 주소 등 ..]

이 정도 인 것 같다.