저번 글에서 SF1을 풀이할 때 RTL에서 왜 4byte를 주는지 잘 몰랐는데,
이제 확실히 이해하면서 글을 적어보고자 합니다.
이 글을 통해서가 아니더라도 RTL에서 인자와 4byte를 띄운다는 것을 이해한다는건 이후 있을 ROP 개념에서도 중요하고 어셈블리를 이해했느냐에 대한 척도가 되는 것 같습니다.
그러면 이제 그 이유에 대해 이야기 해보도록 하겠습니다.
우선 RTL부터 얘기해보도록 하죠.
RTL
RTL은 Return To Libc, Return to Library라고도 부르는 기법입니다.
해당 해킹 기법은 보호기법인 NX bit와 ASLR을 (함께) 다루면서 (또는 NX bit만으로) 배우게 됩니다.
NX bit 보호기법은 코드영역 외에 실행 권한을 주지 않아 보통 넣는 쉘코드를 실행하지 못하게 하는 기법입니다.
ASLR은 스택, 힙, 라이브러리 등의 영역 주소를 랜덤하게 설정하여 exploit을 방해하는 기법이라고 생각 할 수 있습니다.
NX bit로 인해 실행권한이 주어지지 않을 때, 바이너리의 코드 영역과 라이브러리의 코드 영역에는 실행 권한이 존재합니다. RTL은 여기서 라이브러리의 코드 부분을 이용하는 공격입니다.
RTL을 이해하기 위해서는 PLT, GOT, 라이브러리의 개념이 필요합니다.(제 생각에는)
음..
그냥 원래는 ret를 쉘코드나 /bin/sh를 실행하는 함수 주소로 넣었는데 이제 쉘코드를 실행할 권한도 없고 ..~
늘 쉘을 실행시키는 함수가 있는 것도 아니라서 우리가 system같은 함수를 라이브러리에서 가져다 쓴다고 생각하면 되겠습니다.
라이브러리와 링킹
앞서 라이브러리를 언급했는데 라이브러리가 무엇이냐? 하면 컴파일 과정을 이해하면 좋습니다.
리버싱에서도 dll같은 개념을 공부할 것이므로 미리미리 다 알아두면 나중에 좋을 것 같습니다.
전처리는 C 프로그램이 컴파일 되기 직전에 전처리기에 의해 처음으로 수정되는 과정입니다. 전처리기를 위한 명령문을 지시자(directive)라고 하는데, 보통 익숙한 #include가 이에 해당합니다.
컴파일은 C언어를 다른 언어로 바꾸는 과정인데 더 low level 언어인 어셈블리로 변경합니다.
어셈블리는 기계어를 오브젝트 파일(PE, ELF등)으로 변경하는 과정이고 완전한 기계어로 변경됩니다.
링킹은 라이브러리를 오브젝트 파일에 연결(link)하는 과정입니다.
우리가 printf 함수를 사용한다고 할 때, 이미 stdio.h에 있지 않느냐? 할 수 있는데 거기에는 함수 원형이 있고 라이브러리에는 함수의 내용이 존재한다고 합니다.
라이브러리에는 정적 라이브러리(Static Link Librar)와 동적 라이브러리(Dynamic Link Library)가 존재합니다.
윈도우에서 정적 라이브러리는 (*.lib), 동적 라이브러리는 (*.dll)의 확장자를 가지고
리눅스에서는 정적 라이브러리가 (*.a), 동적 라이브러리가 (*.so)의 확장자를 가집니다.
그래서 우리가 볼 libc의 확장자가 libc~~.so 일 것입니다.
정적 라이브러리는 실행 파일이 라이브러리를 복사해서 가지고 있어 실행할 때 라이브러리가 필요없습니다.
동적 라이브러리는 반대로 해당 내용의 주소만 가지고 있다가 필요할 때 그 주소로 가서 내용을 가져옵니다.
이런 특징으로
정적 라이브러리는 실행파일의 크기가 크고 쓸데없는 함수들까지 링킹해서 자원을 낭비하는 대신에 이식성이 좋고 안정적입니다.
동적 라이브러리는 그와 반대로 효율적으로 사용할 수 있습니다. gcc의 디폴트 옵션일만큼 이 방식을 주로 사용합니다.
실제로 gcc를 통해 빌드(컴파일+링킹) 해서 확인해보면
#include<stdio.h>
int main(void)
{
puts("Hello, world!");
return 0;
이런 간단한 프로그램에서
용량이 약 100배정도 차이가 나고
info func를 통해서도 차이가 매우 큽니다.
offset
앞서 RTL에서 system같은 함수를 라이브러리에서 가져다 쓴다고 했는데, 그러면 라이브러리에서 우리가 찾는 함수의 주소가 어디 있는지 알아야 합니다.
ASLR로 인해 라이브러리의 주소는 랜덤화되고 그러면 함수의 주소도 랜덤화되고.. 못 찾을거 같지만 다 찾는데엔 방법이 있습니다.
라이브러리의 주소가 랜덤화된다 해도 라이브러리의 base로부터의 간격, 즉 offset은 일정합니다.
어떤 함수의 offset이 0x10이라고 하면 libc_base로부터 +0x10에 해당 함수가 위치하는 것이죠.
또한 ASLR은 페이지 단위로 랜덤화되기 때문에(?) <-이건 잘 모르겠음
어쨌든, 하나의 함수 주소, 단서를 알면(이건 LEAK해야겠죠?..) 뒤의 (12bit?) 그니까 16진수로 뒤의 3개 (0x12345678에서 678)로 offset을 이용하여 libc_base를 역으로 구할 수 있고 그로부터 정해진 offset들로 원하는 함수를 사용할 수 있습니다.
libc database에서 검색하면 됩니다.(https://libc.blukat.me/)
앞서 실습한 dynamic으로 확인해보면 (gdb에서 자체적으로 aslr을 꺼버려서 gdb에서 set disable-randomization off를 입력함)
dynamic에서는 puts 함수만 사용했으므로.
puts 함수의 실제 주소는 0xf7e43d90이고 vmmap <func name>으로 확인했을 때 libc_base로부터 + 0x67d90입니다.
해당 프로그램을 다시 실행해보면 ASLR로 주소가 바뀔 것이고
예상대로 0xf7d4bd90으로 바뀌었고 vmmap으로 확인한 libc_base로부터의 offset은 0x67d90으로 그대로입니다.
이번 글에서는 RTL의 개념과 라이브러리에 대해서 알아보았고
다음 글에서 더욱 자세히 라이브러리에서 어떻게 함수를 가져오는지, plt와 got의 개념을 다루도록 하겠습니다.
ps.
gdb aslr 없애는 기능 끄기
(gdb) set disable-randomization off
gdb aslr 없애는 기능 켜기
(gdb) set disable-randomization on
켰는지 껐는지 보기
(gdb) show disable-randomization