profile image

L o a d i n g . . .

RTL(Return to Library)

RTL이란 Return address 영역에 공유 라이브러리 함수의 주소로 변경해, 해당 함수를 호출하는 방식이다.

  • 해당 기법을 이용해 NX bit(DEP)를 우회 할 수 있다.

RTL 이해하기 위해 잠깐 Calling Convention을 공부하고 가자!

 

Calling Convention

Cdecl(C declaration)

  • 해당 호출 규약(Calling Convention)은 인텔 x86 기반 시스템의 C/C ++ 에서 사용되는 경우가 많다.
  • 기본적으로 Linux kernel에서는 Cdecl 호출 규약(Calling Convention)을 사용한다.

 

Calling convention features

인자 전달 방법 Stack을 이용
인자 전달 순서 오른쪽에서 왼쪽의 순서대로 스택에 쌓인다.
함수의 반환 값 EAX
Stack 정리 호출한 함수가 호출된 함수의 stack 공간을 정리함

 

다음 코드는 4개의 인자를 전달 받고, 반환값은 ret변수에 저장한다.

int a,b,c,d;
int ret;
 
ret = function(a,b,c,d);

앞에 코드를 cdecl 형태의 assembly code로 변환하면 다음과 같다.

  • 4개의 인자 값을 push명령어를 이용해 stack에 저장한다.
  • 함수 호출 후 반환된 값은 EAX레지스터에 저장되며, 해당 값을 ret 변수에 저장한다.

 

push    d
push    c
push    b
push    a
call    function
mov     ret,eax

즉, Cdecl 방식의 경우 함수를 호출하기 전 먼저 인자를 스택에 넣는다. 그리고 함수를 호출하면 함수가 끝나고 돌아갈 주소인 Return address가 스택에 쌓이고 이전 ebp를 push 하면서 새로운 스택 프레임이 생성된다.

 

따라서 다음과 같은 ret2libc 기법 사용시 인자 값을 전달하기 위해서는 Return Address의 4byte 뒤에 인자 값을 전달해야 한다.

 


이제 실습을 해보도록 하자!

Return to Shellcode를 확인하기 위해 다음 코드를 사용한다.

 

  • main() 함수는 vuln() 함수를 호출한다.
  • vuln() 함수는 read() 함수를 이용해 사용자로 부터 100개의 문자를 입력받는다.
    • 여기에서 취약성이 발생한다. buf 변수의 크기는 50byte이기 때문에 Stack Overflow가 발생한다.
  • libc의 Base address를 얻기 위해 libc 영역에서 printf() 함수의 주소를 출력한다.

 

다음과 같이 build 한다.

 

 

 

Overwriting the return address

다음과 같이 Break point를 설정한다.

  • 0x0804851b : vuln() 함수의 첫번째 명령어
  • 0x08048586 : read() 함수 호출
  • 0x08048595 : vuln() 함수의 RET 명령어 

 

Breakpoint 1

다음과 같이 return address를 확인 할 수 있다.

  • ESP 레지스터가 가리키고 있는 최상위 Stack의 주소는 0xffffcf9c 이다.
  • 0xffffcf9c 영역에 Return address(0x080485ac)가 저장되어 있다.

 

Breakpoint 2

다음과 같이 buf 변수의 주소를 확인 할 수 있다.

  • buf변수의 위치는 0xffffcf5a 이며, Return address 위치와 66byte 떨어져 있다.
  • 즉, 사용자 입력 값으로 문자를 66개 이상 입력하면, Return address를 덮어쓸 수 있다.

 

Breakpoint 3

다음과 같이 Return address 값이 변경된 것을 확인 할 수 있다.

  • 0xffffd57c 영역에 0x41414141(AAAA)가 저장되어있다.

※ 정리 : bof를 일으켜 RET를 변경할 수 있다는 것까지 알았다. 그렇다면 RET를 system 함수의 주소로 바꿔 system 함수를 호출하고 그 인자값으로 "/bin/sh"을 줘서 쉘을 얻을 수 있을 것이다.

 

 

 

Find the Libc address of the system() function and "/bin/sh"

system() 함수는 다음과 같은 형태이다.

  • 인자 값으로 실행할 command의 경로를 문자열로 전달 받고 있다.
  • 즉, RTL을 기법을 사용해 shell을 실행하기 위해 "/bin/sh" 문자열을 전달해야 한다.
int system(const char *command)

 

 

다음과 같이 Libc 영역에서 System() 함수의 주소를 찾을 수 있다.

  • 0xf7e47680(printf function address in libc) - 0xf7dfe000(libc Start Address) = 0x49680(libc Base Address offset)
  • 0xf7e38db0(system function address in libc) - 0xf7dfe000(libc Start Address) = 0x3adb0(system function Address offset)

 

 

다음과 같이 "/bin/sh" 문자열을 찾을 수 있다.

  • 0xf7f59b0b("/bin/sh" string address in libc) - 0xf7dfe000(libc Start Address) = 0x15bb0b("/bin/sh" string Address offset)

 

 

Exploit

위의 논리대로 exploit 코드를 작성해보았다.

 

성공적으로 쉘을 획득하였다!!!!!!!

 

 

참고
https://lazenca.net/display/TEC/01.RTL%28Return+to+Libc%29+-+x86
복사했습니다!