1) PLT (Procedure Linkage Table) :
외부 프로시저를 연결해주는 테이블이다. PLT를 통해 다른 라이브러리에 있는 프로시저를 호출해 사용할 수 있다.
2) GOT (Global Offset Table) :
PLT가 참조하는 테이블이다. 프로시저들의 주소가 들어 있다.
- 함수를 호출하면(PLT를 호출하면) GOT로 점프하는데 GOT에는 함수의 실제 주소가 쓰여있다.
- 첫 번째 호출이라면 GOT는 함수의 주소를 가지고 있지 않고 ‘어떤 과정’을 거쳐 주소를 알아낸다.
- 두 번째 호출부터는 첫 번째 호출 때 알아낸 주소로 바로 점프한다.
3) libc database 활용 :
▶ https://dreamhack.io/wargame/challenges/354 에 있는 rop 파일을 이용하였다.
(1) rop.c
(2) rop.c 컴파일 : gcc -o rop rop.c -fno-PIE -no-pie => rop 파일 생성
// Name: rop.c
// Compile: gcc -o rop rop.c -fno-PIE -no-pie
#include <stdio.h>
#include <unistd.h>
int main() {
char buf[0x30];
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
// Leak canary
puts("[1] Leak Canary");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
// Do ROP
puts("[2] Input ROP payload");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
return 0;
}
(3) readelf -d rop 결과 : 파일의 동적 섹션에 대한 내용을 출력한다.
- readelf 명령어 : http://korea.gnu.org/manual/release/binutils/binutils_15.html
(4) ldd rop 결과 : 지정한 프로그램 또는 파일의 라이브러리 의존성을 살펴볼 때 사용하는 명령어
(5) nm rop 결과 : 오브젝트 파일, 실행 파일 및 오브젝트 파일 라이브러리에 있는 기호에 대한 정보를 표시한다.
(6) 위의 readelf와 ldd 결과를 보고, rop에서는 libc.so.6 라이브러리를 사용한다고 생각하여 다음과 같이 readelf 명령어로 해당 라이브러리 파일에서 printf와 puts 함수를 찾았다.
puts의 주소 마지막 12비트는 e50이며, printf의 경우는 6f0이다. 마지막 12비트를 보는 이유는, libc_base(libc의 시작주소)의 하위 12비트가 0이기 때문이다.
이를 이용하여 실행 중인 특정 함수의 주소 마지막 12비트를 보면, 어떤 라이브러리를 사용하고 있는지 추측할 수 있는데 이 때 libc database를 활용할 수 있다.
- readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep "puts"
- readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep "printf"
(7) 두 가지 방법으로 바이너리가 어떤 라이브러리를 사용하는지 알 수 있다.
- 일단 gdb 실행 후 바이너리에서 사용하는 puts/printf 함수의 주소가 어디인지 조사한다. 위에서 언급했듯 첫 번째 호출이라면 GOT는 함수의 주소를 가지고 있지 않고 ‘어떤 과정’을 거쳐 주소를 알아내기 때문에 두 번째 호출에서 함수의 주소를 알아낼 수 있다.
gdb rop => start / c 명령으로 프로그램을 일단 한 번 실행한 후 주소를 조사하자.
이후 puts/printf 함수의 주소 맨 끝 12비트를 비교하면 (6)에서와 동일함을 알 수 있다.
- 첫 번째 방법 : https://libc.rip/ 이용
해당 사이트에서 함수 이름과 함수 주소 맨 끝 12비트를 입력하면 이에 맞는 libc 라이브러리를 찾아 준다.
단 없는 버전도 있는 것 같으니 주의.
- 두 번째 방법 : libc-database를 다운로드 받아 검색하기
https://github.com/niklasb/libc-database 에서 다운로드 후 설정하고 검색
(1) - (6)까지 보았듯이 실제 사용 라이브러리는 libc.so.6인데... 이 쪽이 좀 더 정확한가? 싶다.
$ ./find puts e50
ubuntu-glibc (libc6_2.35-0ubuntu3.8_amd64)
/usr/lib/x86_64-linux-gnu/libc.so.6 (local-b99cddb548877d514655da7ecac1348dd45e6eee)
$ ./find printf 6f0
rpm (libc-2.36-52.mga9.x86_64_2)
rpm (libc-2.36-53.mga9.x86_64_2)
rpm (libc-2.36-54.mga9.x86_64_2)
ubuntu-glibc (libc6_2.35-0ubuntu3.8_amd64)
ubuntu-old-glibc (libc6_2.3.6-0ubuntu20_i386_2)
ubuntu-old-glibc (libc6-amd64_2.19-10ubuntu2.3_i386)
ubuntu-old-eglibc (libc6-i386_2.12.1-0ubuntu10.4_amd64)
ubuntu-glibc (libc6-i386_2.38-1ubuntu6.3_amd64)
ubuntu-glibc (libc6-i386_2.38-1ubuntu6_amd64)
/usr/lib/x86_64-linux-gnu/libc.so.6 (local-b99cddb548877d514655da7ecac1348dd45e6eee)
(8) 이를 통해 libc_base를 구해 보자.
puts 함수를 예로 들면, (6)의 libc.so.6 라이브러리에서 puts 함수의 오프셋은 0x80e50이며, rop에서의 주소는 (7)에서 보면 0x7ffff7e0be50이다. rop에서의 함수 주소에 라이브러리에서의 오프셋을 빼면 rop 바이너리의 libc_base를 알 수 있다.
libc_base의 마지막 12비트는 000이므로 맞게 구했음을 알 수 있다.
0x7ffff7e0be50 - 0x80e50 = 0x7ffff7D8B000 (rop 바이너리에서의 libc_base 값)
(9) libc_base 값을 이용하여 libc 내 다른 함수들을 rop 바이너리에서 어떤 주소로 호출하는지 알 수 있다.
이를 이용하면, 원하는 libc 함수를 rop 바이너리에서 실행할 수도 있게 된다.
(ex) rop 바이너리에서 호출하는 read 함수 주소 구하기
(1) readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep "read" 명령어를 실행하면, read 함수의 오프셋이 0x1147d0임을 알 수 있다. 이를 아까 구한 libc_base와 더하면 rop 바이너리에서 호출하는 read 함수 주소가 나온다.
0x7ffff7D8B000 (rop 바이너리에서의 libc_base 값) + 0x1147d0 = 0x7ffff7e9f7d0
(2) 실제로 gdb rop을 하여 확인해보면 read 함수의 주소가 맞음을 확인할 수 있다.
▶ 참조
https://bpsecblog.wordpress.com/2016/03/07/about_got_plt_1/
https://bpsecblog.wordpress.com/2016/03/09/about_got_plt_2/
https://dreamhack.io/forum/qna/4937#answer-5269
https://kaspyx.tistory.com/86?category=939328
https://s1m0hya.tistory.com/20
'프로그래밍 > 해킹' 카테고리의 다른 글
웹 해킹 퀴즈 모음 (0) | 2024.08.30 |
---|---|
Format String Bug (0) | 2024.08.18 |
Ghidra 사용법 (0) | 2024.08.03 |
IDA 사용법 (0) | 2024.08.03 |
XSS Filtering Bypass 실습 문제 (0) | 2024.07.22 |