profile image

L o a d i n g . . .

RTL(Return to Libc)

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

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

 

이번엔 x64에서 RTL을 실습해볼 것이다.

x86과 x64 차이는 쉽게 말해 x8632비트, x64 64비트인것으로 구분을 하면 된다.

그렇다면 이번에도 먼저 함수 호출 규약에 대해 공부해보자!

 

 

Calling Convention

System V AMD64 ABI

  • Solaris, Linux, FreeBSD, macOS 에서 "System V AMD64 ABI" 호출 규약을 사용하기 때문이다.
  • Unix, Unix 계열 운영체제의 표준이라고 할 수 있다.

 

Calling convention features

x86에서 많이 쓰이는 Cdecl 방식과 가장 큰 차이점은 함수 호출 시 인자를 스택이 아닌 레지스터에 저장한다는 점이다.

 

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

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

앞에 코드를 "System V AMD64 ABI" 형태의 assembly code로 변환하면 다음과 같다.

  • 4개의 인자 값을 mov명령어를 이용해 레지스터에 저장한다.
  • 함수 호출 후 반환된 값은 EAX레지스터에 저장되며, 해당 값을 ret 변수에 저장한다.
mov     rcx,d
mov     rdx,c
mov     rsi,b
mov     rdi,a
call    function
mov     ret,eax

 

즉, ret2libc 기법을 사용하기 위해서는 각 레지스터에 값을 저장 할 수 있어야 한다.

  • 다음과 같은 방법으로 레지스터에 값을 저장 할 수 있다.
    • Return Address 영역에 "pop rdi, ret" 코드가 저장된 주소 값을 저장한다.
    • Return Address 다음 영역에 해당 레지스터에 저장 할 인자 값을 저장한다.
    • 그 다음 영역에 호출 할 함수의 주소를 저장한다.
    • 이러한 방식은 ROP(Return-oriented programming) 라고 한다.

 

  • 즉, 다음과 같은 구조로 ret2libc를 사용할 수 있다.

※ 풀이 : RET에 (pop rdi, ret) 주소를 넣으므로써 그 명령어가 존재하는 주소로 이동함.

pop 명령어로 인해 현재 스택에 존재하는 값인 첫번째 인자 값을 레지스터에 저장함.

pop 명령어로 인해 esp 레지스터의 값이 증가해 호출할 함수의 주소를 가르키고 있을 때, ret로 인해 esp가 현재 가르키고 있는 함수 주소로 return 해 함수를 호출시킴.

 

 

ABOUT pop & ret

더보기

pop EAX = mov eax, [esp]

                add esp, 4

 

-> ESP 레지스터가 가리키고 있는 위치의 스택 공간에서 4Byte 만큼을 EAX에 복사

-> ESP 레지스터의 값에 4를 더함

 

 

ret = pop eip       

        jmp eip

-> esp가 가리키고 있는 주소값에 들어있는 값을 eip에 저장하고, esp = esp +4 한다.
-> eip가 가리키는 주소로 간다.

 

 

 

Overwriting the return address

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

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

다음과 같이 Build 한다.

lazenca0x0@ubuntu:~/Exploit/RTL$ gcc -fno-stack-protector -o ret2libc ret2libc.c -ldl

 

 

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

  • 0x400676 : vuln() 함수의 첫번째 명령어
  • 0x4006e0 : read() 함수 호출
  • 0x4006e7 : vuln() 함수의 RET 명령어 

 

Breakpoint 1

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

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

 

 

Breakpoint 2

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

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

 

 

Breakpoint 3

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

  • 0x7fffffffdda8 영역에 0x4141414141414141(AAAAAAAA)가 저장되었다.

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

 

 

 

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

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

 

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

 

offset 들을 몽땅 구해주었다!

 

gadget 주소도 구했다! 이제 필요한 것들은 모두 구했다.

 

 

Exploit

위의 논리대로 라젠카 블로그 없이 직접 exploit 코드를 작성해보았다!

 

 

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

 

 

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