이번 글에서는 시스템이 메모리를 보호하는 방법중 하나인
ASLR과 NX를 다루도록 하겠습니다.
ASLR은 바이너리가 실행될때마다 스택, 힙, 공유 라이브러리 등을 임의의 주소에
할당하는 보호 기법 입니다.
NX는 실행에 사용되는 메모리 영역과 쓰기에 사용되는 메모리 영역을 분리하는
보호기법 입니다.
NX가 적용된 바이너리는 코드 영역외에는 실행권한이 없습니다.
NX가 적용되면 스택이나 데이터 영역에 실행권한이 사라지므로
Return to Shellcode와 같은 공격을 시도할 수 없습니다.
NX로 인해 공격자들은 직접 입력한 셸 코드를 실행하기 어려워졌지만
여전히 버퍼 오버플로우 취약점으로 반환 주소를 덮는것이 가능했습니다.
그래서 공격자들은 실행권한이 남아있는 라이브러리의 코드 영역으로
반환 주소를 덮는 공격 기법을 고안하였습니다.
C언어로 작성된 프로그램이 참조하는 libc에는 system, execve 등
프로세스의 실행과 관련된 함수들이 구현되어 있습니다.
공격자들은 libc의 함수들로 NX를 우회하고 셸을 획득하는 공격 기법을 개발하였고
이를 Return To Libc 혹은 Return To Library라고도 합니다.
다음은 스택 오버플로우 취약점이 존재하는 코드입니다.
위 코드에서는 문자열 "/bin/sh"의 주소를 알고
system 함수를 호출하고 있으므로 system 함수의 plt 주소값을 알 수 있습니다.
따라서 rdi 레지스터의 값을 "/bin/sh" 문자열의 주소로 설정할 수 있다면
system("/bin/sh") 를 실행할 수 있습니다.
이를 위해 리턴 가젯을 사용하겠습니다.
x64 시스템 환경에서는 반환 주소를 다음과 같이
리턴 가젯, 함수의 파라미터, 함수의 plt 순서로 덮으면
이어지는 ret으로 함수를 호출할 수 있습니다.
address of ("pop rdi; ret")
address of "/bin/sh"
address of "system" plt
x86 환경에서는 함수의 plt 주소, 리턴 가젯, 함수의 파라미터 순서로 반환주소를 덮습니다.
바이너리 파일의 리턴 가젯의 주소는 다음과 같이 확인할 수 있습니다.
"/bin/sh"의 주소는 다음과 같이 확인할 수 있습니다.
system 함수의 plt 주소는 pwndbg 또는 pwntools의 API를 활용하여
찾을 수 있습니다.
익스플로잇 코드를 작성하기 전 주의해야할 점이 한가지 있습니다.
system 함수로 rip가 이동할 때, 스택은 반드시 0x10 단위로 정렬되어야 한다는 점입니다.
이는 system 함수 내부에 있는 movaps 명령어 때문인데
movaps는 잘못 정렬된 ROP가 사용되었을때 호환성 문제를 일으켜
General Protection Exception을 발생시킵니다.
따라서 작성한 ROP 체인에 앞서서 ret 가젯을 사용하여 system 함수의 가젯을
8바이트 뒤로 미루어야 합니다.
따라서 전체 익스플로잇 코드는 다음과 같습니다.