상세 컨텐츠

본문 제목

0x02 - x64 RTL(Return to libc)

Linux System_BOF/x64

by 킹갓해커다똥 2020. 7. 28. 21:09

본문

x64 RTL(Return to Library)

이번 문서에서는 64비트 환경에서의 RTL을 다루도록 하겠슴다. RTL 이라는 기법은 NX bit(DEP)라는 메모리 보호 기법이 적용됐을때 이를 우회하기 위한 공격입니다. NX bit는 이전 문서에서 다뤘던 방식 같이 스택영역에 쉘 코드를 삽입하고 이 쉘 코드가 실행되는 방법이 불가능하게 스택 상에 실행 권한을 제거하여 프로세스 명령어나 데이터 저장을 위한 메모리 영역을 분리 시키는 기술임다.

그럼 RTL은 뭐냐? Return Address 영역을 공유 라이브러리의 함수 주소로 변경해 우리가 원하는 함수를 공유라이브러리에서 호출하여 system(), execve() 같은 함수를 가져와 실행시키는 공격방법임다. RTL을 이용하면 NX bit가 걸려있어도 공유라이브러리에서 내가 원하는 함수를 가져와서 쓰면 되니까 문제 없겠쥬?

 

x86 RTL과 x64 RTL의 차이

오늘 우리가 다룰것은 64비트 환경의 RTL이기 때문에 기존에 공부하던 32비트 환경의 RTL과는 살짝 다를겁니다. 32비트 환경에서의 RTL과 64비트의 RTL이 어떻게 다른지 한번 봅봅시다!

-Calling Convention

함수 호출 규약은 cdecl, stdcall, fastcall, thiscall 이 있슴다. 이중에서 cdecl과 fastcall만 다루겠습니다. cdecl과 fastcall 는 인자 전달하는 방식이 쬐금 다릅니다. 두가지 함수 호출 규약의 차이를 보기 위해 아래 예제코드를 작성했슴다. main이  라는 함수에서 test 함수로 1,2,3,4 라는 인자를 전달하여 printf로 전달받은 인자를 출력하는 코드입니다.

 

 

위 코드를 32비트로 컴파일하여 확인해봅디다. 32비트는 기본적으로 cdecl 방식을 사용함다. 중간에 push로 스택에 인자 0x4, 0x3, 0x2, 0x1의 인자를 집어 넣는 것을 확인할 수 있죠..? 즉 32비트는 함수에 인자를 넣을때 스택에 오른쪽에서 왼쪽 순서로 인자를 전달합니다.

 

이번엔 64비트로 컴파일하여 확인해봅시당. 64비트는 기본적으로 fastcall 방식을 사용함다. *main+24 위치에 브레이크포인트를 걸고 실행시켜 레지스터 상태를 확인해봅시당

 

gdb에서 i r(info registers) 명령어로 레지스터 정보를 확인할 수 있습니닷. 64비트에서 fastcall 방식은 함수 호출 시 rdi, rsi, rdx, rcx, r8, r9 와 같이 총 6개의 레지스터를 순서대로 사용해서 인자를 전달함돠(인자가 7개 이상인 경우 스택을 이용해 전달함) 가장 첫번째 인자인 0x1은 rdi를 사용하고, 0x2는 rsi, 0x3은 rdx, 0x4는 rcx 인것을 확인할 수 있쥬?

 

함수 호출 규약에서 32비트는 스택기반 64비트의 레지스터 기반으로 인자 전달 방식이 다르듯 RET에 넣는 값도 달라지기때문에 예를들어 system 함수에 "/bin/sh"을 호출한다고 가정했을때 32비트 RTL에서는 아래와 같이 페이로드가 구성되지만

RET (system) dummy "/bin/sh"

 

64비트 RTL에서는 인자가 있는 함수일 경우 RET에 인자전달을 위한 가젯을 넣어준 후에, 첫번째 인자인 "/bin/sh" 문자열을 넣은뒤 system 함수를 호출합니닷

RET(pop rdi;  ret;) "/bin/sh" system

 

x64 RTL 예제

자자 이제 실전에 들어가야쥬. 아래는 취약하게 짜여진 프로그램입니다. 보시면 buf라는 변수에서 버퍼 크기를 30만큼 할당해줬는데 read함수에서는 buf 공간보다 큰 100바이트를 쓸라고하쥬? 딱봐도 취약해보이네유

 

컴파일전 ASLR을 끌겁니다. ASLR off 시 root권한이 필요합니다.

root@ubuntu # echo 0 > /proc/sys/kernel/randomize_va_space

 

컴파일은 아래와 같이 할겁니다. 우리는... 아.. 아니 나는..... 시스템 해킹 초짜니까 클린한 상태에서 NX bit만 활성화해줄거유.. 컴파일에 사용한 옵션은 아래와 같슴다.

  • -m64 : 64비트로 컴파일
  • -fno-stack-protector : CANARY off
  • -no-pie : PIE off
  • -mpreferred-stack-boundary=4 : Stack boundary off
  • -z norelro : RELRO off

 

gdb로 컴파일한 바이너리를 열어서 checksec 명령어로 적용된 보호 기법을 확인해보면 NX bit만 잘 적용된것을 확인할 수 있습니다

 

자!!!!!!!!!!!!!! 본격적으로 RTL을 시작해봅시다! vuln 함수 실행전인 *vuln+0 위치에 브레이크 포인트를 걸어줍니다.

 

요시요시!!! push rbp가 실행되기 전까지 프로그램이 돌아갔다!

 

레지스터 정보를 확인해봅시다. 스택의 꼭대기인 rsp! 즉 return address인 0x7fffffffde98를 확인할 수 있슴다!

 

자 이제 buf 변수의 주소를 확인해야 합니당. *vuln+67 위치에 브레이크 포인트를 걸고 실행해봅시다. (실행 전 *vuln+0 삭제해주세요)

 

요시요시!!! read 함수 호출전까지 프로그램이 돌아갔다!!!

 

레지스터 정보를 확인해봅시당. read 함수의 두번째 인자가 buf 변수이니 두번째 인자를 전달할때 사용하는 레지스터 rsi를 확인하면 buf 변수의 주소를 확인할 수 있슴다.

 

return address에서 buf 변수의 주소를 빼면 RET까지의 거리가 나오는데..! 아~ 56개 이상의 문자를 입력하면  RET을 덮을 수 있겠구나 ㅎㅎ

 

RET까지의 거리를 구했으니 쉘을 실행시키기 위해 필요한 system 함수와 "/bin/sh" 문자열의 위치를 알아야 합디다. 이때 브레이크 포인트가 걸려진 상태에서 실행해야 주소를 확인할 수 있습니다. 저는.. 처음에 브레이크 포인트가 안걸리면 왜 주소값이 안나오는거야?.. 했는데 이유를 알고보니 하하하!

C언어는 컴파일러 언어라 Python 같은 인터프리터 언어 같이 한줄한줄 명령어를 실행하는 것이 아니라 명령어들을 몽땅 모아놓고 한번에 실행하는 방식이기 때문에 프로그램 실행 전이나 이미 실행되고 끝나버린 후에는 주소를 확인할 수 없숩니다 ^^..

 

gdb에서 p 명령어로 system 함수의 주소 0x7ffff7a523a0를 확인했고, find 명령어로 "/bin/sh"의 주소 0x7ffff7b99e17를 확인했슴다. 여기서! 라이브러리는 함수들의 모음인데 이 "/bin/sh" 라는 문자열을 어떻게 라이브러리에서 가져오냐 하시는 분들이 있을텐데.. system 함수는 내부에서 /bin/sh -c를 사용해 인자로 전달된 프로그램을 실행시킵니다. (이해가 안가시면 지금 당장 /bin/sh -c ls 한번 쳐보세요 ㅎㅎ) 때문에 라이브러리안에도 이 문자열이 존재하는거죳!  

 

system 함수의 주소와 "/bin/sh"의 위치를 확인했으니 인자전달을 위해 필요한 가젯의 위치를 찾아보겠슴다. 저희는 "/bin/sh"이라는 인자 하나만 전달하면 되니까! 가젯도 pop rdi; ret; 만 있으면 됩니다. 위치가 0x00400613로 확인됩니닷. 

(rp-lin-x64는 가젯을 찾을때 사용합니다. 여기서 다운로드:https://github.com/0vercl0k/rp/downloads하심 돼요 ㅎㅎ)

 

0vercl0k/rp

rp++ is a full-cpp written tool that aims to find ROP sequences in PE/Elf/Mach-O x86/x64 binaries. It is open-source and has been tested on several OS: Debian / Windows 8.1 / Mac OSX Lion (10.7.3)....

github.com

 

자 이제 필요한 주소들은 다 구했으니 익스플로잇 코드를 짜봅시다 ㅎㅎ 별거 없습니다 껄껄.. 구한 주소들을 이어붙여 페이로드를 만들고 실행시키기만 하면됩니다. 단! dummy+gadget+"/bin/sh"+system의 순서는 지키셔야됩니닷! 익스플로잇을 실행해봅시다.

 

쫘잔.. 쉘을 따냈습니다.

 

마치며..

이번 글을 포스팅하기까지 꽤 오랜시간이 걸렸습니다.. (이제부터 징징대기 스타트! ->) 하다보니 잘못 이해한 개념도 있었고, 덜 이해한 개념도 있었슴다.. 다른분들이 써놓은 64비트 RTL 문서를 따라하면서 이해하는 방식을 택했는데,  대부분 이정도면 다 알겠지하고 설명을 생략하고 포스팅 한 부분이 많아.. 내가 어떤 개념이 부족한지도 모르는 상황이 생겨버렸습니다 ㅠㅠ 엉엉.. C언어도 몇줄 안되는 코드지만.. 코드를 잘못짠지도 모르고 바이너리 분석하다가 시간 날려버린 것도 몇개 있네요 ^~^..  

하고나니 뿌듯함은 있지만 64비트 ROP 포스팅이 두려워집니당 ^^.. 장난아니고.. 진짜로여.. 암튼 지긋지긋한 RTL 끝

 

 

'Linux System_BOF > x64' 카테고리의 다른 글

0x01 - x64 Basic BOF  (1) 2020.06.14
0x00 - 64bit System Hacking  (0) 2020.05.28

관련글 더보기