tmxklab

RTL(Return-To-Libc) 원리 및 실습 본문

Security/01 System Hacking

RTL(Return-To-Libc) 원리 및 실습

tmxk4221 2020. 2. 16. 23:10

1. RTL(Return To Libc)

 

사용자가 작성한 코드에 없는 함수를 호출하고자 메모리에 이미 적재된 공유 라이브러리의 원하는 함수를 사용할 수 있는 기법이다. 또한, 리눅스의 메모리 보호 기법 중 NX bitDEP를 우회하여 공격이 가능하다.

 

1) DEP(Data Execution Prevention)
- 데이터 영역에서 코드가 실행되는 것을 막는 기법
- 공격자가 Heap 또는 Stack영역에 쉘 코드를 저장해서 실행하기 위해서 해당 영역에 실행권한이 있어야 한다. 따라서, DEP가 적용된 경우 실행권한이 없어 쉘 코드가 실행되지 않고 프로그램에 대한 예외처리 후 종료

2) NX bit(Never eXecute Bit)
- 메모리에서 데이터 영역을 실행하는 것을 방지해주는 CPU기능
- NX특성으로 지정된 모든 메모리 구역에서 데이터 저장만 가능하며 프로세서 명령어가 그곳에 상주하지 않음으로써 실행되지 않도록 만들어 준다. 
- 인텔에서는 XD(eXecute Disable)bit, AMD에서는 EVP(Enhanced Virus Protection), 윈도우에서는 DEP(Data Execution Prevention)이라고 부른다.

* 윈도우에서는 DEP라고 부르고 리눅스에서는 NX bit라고 불러서 결국 둘 다 동일한 말이다.

 


2. RTL 실습 및 원리

* 목표
바이너리에 원하는 함수가 없어도 공유 라이브러리에 있는 system함수를 호출하여 system("/bin/sh");를 실행해보도록 하자

 

* 예시 코드(rtl.c)

#include<stdio.h>
#include<unistd.h>

int main()
{
    char buf[128];
    read(0, buf, 256);
    return 0;
}

 

- 위 코드는 128bytes까지 담을 수 있는 buf배열에 read함수를 통해 256bytes까지 저장할 수 있으므로 bof취약점이 존재한다.
- 이제 bof취약점을 이용하여 system함수의 주소 값을 찾아 RET에 위치시키도록 해야 한다.
- 또한, system함수의 인자 값인 "/bin/sh"의 주소 값도 찾아서 공격 페이로드를 구성해야 한다.

 

2.1 공격 페이로드 구성

         [ buf(128bytes) ] + [ SFP(4bytes) ] + [ RET(4bytes) ] + [ dummy(4bytes) ] + [ 인자 값 1(4bytes) ]

 

- buf와 SFP에는 더미 값을 채워 넣어야 되며, RET에는 system함수의 주소 값을 채워 넣어야 한다.
- 또한, system()의 인자 값으로 "인자 값1" 위치에 "/bin/sh"의 주소 값을 채워 넣으면 된다.
( RET와 인자 값1 사이에 dummy를 채워 넣는 이유에 대해서는 밑에서 설명할 예정)

 

따라서, 다음과 같은 페이로드로 작성된다.

dummy(128bytes) + dummy(4bytes) + system()(4bytes) + dummy(4bytes) + "/bin/sh"(4bytes)

 

2.2 원리

이제 위 공격 페이로드로 구성했을 경우 스택 프레임이 어떻게 변하는지 그림으로 확인해보자

 

설명 1 : leave가 실행되어 EBP는 다른 곳으로 가버리고 EIP는 다음 RET를 가리키게 된다.

 

설명 2 : RET을 실행하면 ESP가 가리키는 system()의 주소 값이 EIP로 들어가 system()가 실행된다.

 

설명 3 : system()의 프롤로그가 진행되면서 스택에 system()의 SFP가 push되면서 ESP는 4bytes 감소

 

설명 4 : system()의 프롤로그가 끝난 후 EBP와 ESP는 같은 곳을 가리키게 되며 이때, system()의 인자인 /bin.sh는 EBP에서 8bytes 떨어진 곳에 참조된다.

 

설명 5 : system()의 에필로그가 진행되면서 leave실행 이후에 EBP는 다른 곳으로 가버리게 된다.

 

설명 6 : RET가 실행되면서 EIP에 dummy값이 들어가 그 곳으로 점프된다.

이 때, 우리는 system함수 한 번만 호출하는 것을 목표로 하므로 system함수가 실행된 이후에는 신경쓰지 않으므로 더미 값을 채워 넣는 것이다. 만약, RTL Chaining의 통해서 여러 개의 함수를 호출하고 싶을 때는 dummy값 대신에 가젯(Gadget)을 넣어 사용하면 됩니다.

따라서, 더미 값의 존재 이유는 다음 리턴 어드레스를 가리키기 위한 용도입니다.

 

2.3 RTL 실습

- 실습에 앞서 위 예시 코드(rtl.c)를 컴파일하여 rtl로 만들자
- 또한, 바이너리 파일에 리눅스 메모리 보호기법이 걸려있으므로 실습을 통해 확인해보자

 

1) rtl파일이 참조하는 라이브러리의 주소가 매번 바뀌는 것을 확인

이는 ASLR(Address Space Layout Randomization)기법이 걸려있으므로 실습의 편리함을 위해 이를 해제해준다.

 

2) ASLR 해제

 

3) ASLR 해제 결과

해제된 이후에는 주소 값이 매번 동일함을 알 수 있다.

 

4) gcc 컴파일 옵션에 추가로 다른 보안 옵션 해제

  • -mpreferred-stack-boundary=2 : 스택을 정렬하는 인스트럭션 제거
  • -fno-stack-protector : canary 제거

 

5) $checksec을 통해 메모리 보호기법 확인

 

6) gdb를 통해 main함수 확인

<main+11> : read() 호출 직전에 [ebp-0x80] 위치 값을 push하므로 ebp에서 128bytes떨어진 곳으로부터 buf배열이 시작하는 것을 알 수 있다.

 

7) rtl에 필요한 주소 값 찾기

  • system() : 0xb7e43da0
  • "/bin/sh" : 0xb7f64a0b

 

8) rtl 수행

 

Comments