버퍼 오버플로 공격이 너무 흔하고 수많은 취약점을 발견하면서 운영체제도 자체적으로 대책을 세우기 시작했다.
1. 안전한 함수 사용
버퍼 오버플로 공격에 대응하는 가장 쉽고 효과적이면서 확실한 방법은 버퍼 오버플로에 취약한 함수를 사용하지 않는 것이다.
일반적으로 알려진 버퍼 오버플로 공격에 취약한 함수는 다음과 같다.
- strcpy(char *dest, const char *src)
- strcat(char *dest, const char *src)
- getwd(char *buf)
- gets(char *)
- fscanf(FILE *stream, const char *format)
- scanf(const char *format,...)
- realpath(char *path, char resloved_path [])
- sprintf(char *str, const char *format)
기본적으로 버퍼 오버플로에 취약한 함수는 사용하지 않는 것이 좋다. 모두 사용하지 않고 프로그램을 작성하려면 무척 번거롭고 프로그램 효율이 떨어지기 때문에 실제로는 많이 쓴다고 한다.
취약한 함수를 안전한 함수로 바꿔 사용한 예를 보자
이는 strcpy함수 대신 strncpy을 사용한 예이다.
이는 gets대신 fgets를 사용한 예이다.
이는 scanf대신 fscanf를 사용한 예이다.
2. 실행 가능 공간 보호
프로그램은 메모리의 텍스트 세그먼트 부분에 오브젝트 파일 형태로 저장하고 실행한다.
이 부분은 보통 읽기만 가능하고 쓰기가 가능하지 않은데, 프로그램은 실행할 때 추가적으로 필요한 일종의 임시 코드 등을 저장하는 데이터 공간은 스택이나 힙에 저장된다. 실행 가능 공간 보호는 스택이나 힙에 이렇게 저장된 데이터 공간에서 쓰기만 가능하게 하고 실행은 할 수 없게 된 것이다.
현재 칼리 리눅스에서 메모리에 관한 여러 사항을 확인해 보자.
cat /proc/self/maps명령어를 통해 확인해보자.
stack이라고 쓰인 곳에 메모리 범위가 쓰여있다.
그리고 rw-p라고 되어있는데 이는 r(read), w(write), x(execution), p(private) 임을 나타낸다.
주의 깊게 봐야 할 것은 x권한이다.
이것이 설정이 되어있다면 쉘 코드를 스택에 올려놓고 실행할 수 있는 것이다.
지금 현재는 Non-Executable Stack을 적용한 모습으로 스택에 올려 수행하는 버퍼 오버플로 공격은 성공할 수 없다.
Non-Executable Stack에 대한 해커 대응책은 rtl(=return to libc)이었다.
rtl은 스택에 있는 ret주소를 실행 가능한 임의의 주소로 돌려 원하는 함수를 수행하게 하는 기법이다.
메모리에 적재된 공유 라이브러리는 스택에 존재하는 것이 아니므로 Non-Executable Stack을 우회하는 것이 가능하다.
libc 영역에서 셸을 실행시킬 수 있는 함수에는 system, exeve, execl 등이 있다.
- rtl공격
system함수를 사용하여 rtl공격을 수행해보려고 한다.
이전에 사용하였던 bugfile.c 를 보자.
이를 컴파일하고 SETUID를 한다.
gdb로 disas s 해보았을 때이다
rbp와 rsp의 차이는 0x20이다.
system과 exit의 주소를 확인한다.
현재 system과 exit의 주소를 확인했고 /bin/sh의 문자열이 필요하다.
우리는 system 함수 내부에 존재하는 "/bin/sh"문자열의 위치 값을 얻어 그 주소를 직접 사용할 것이다.
/bin/sh 문자열은 0x622 f7 ffc 0x 732 f6369 0x00000068이다.
해당 데이터가 있는 주소를 찾으면 된다. 현재 주소가 동적으로 바뀌기 때문에 외부에서. c파일로 찾기가 힘들다.
따라서 gdb로 메모리를 덤프 해가며 찾아야 하는데 이게 x 명령어를 output파일로 저장하고 싶은데 그게 쉽지가 않다.
만약에 "/bin/sh"의 주소를 찾았다면
스택
/bin/sh의 주소
exit함수의 주소
system함수의 주소
ebp
와 같은 형태로 넣어두어야 한다.
따라서 프로그램을 실행할 때 20 바이트 + ebp(4바이트) + system함수의 주소 in little endian으로 인자를 주면 실행이 된다.
3. 스택 가드
Non-Executable Stack을 우회하는 기술을 발표한 후 스택 가드라는 버퍼 오버플로 방어 기술도 생겼다.
이는 컴파일러가 프로그램의 함수를 호출할 때 ret 앞에 canary 값을 주입하고, 종료할 때 canary값을 변조했는지 여부를 확인하여
버퍼 오버플로 공격을 탐지한다.
즉 이전에 실행했던 ret를 덮어 씌우는 공격을 탐지하게 된다. 하지만 이를 아는 공격자는 canary값을 미리 예측하고 버퍼 오버플로 공격 문자열을 삽입할 수 있다.
스택 가드는 다음 기술을 이용한다.
- Random canary : 프로그램을 실행할 때마다 canary값을 바꾸어 이전에 실행하여 얻은 canary값을 다시 사용하지 못하게 한다.
- Null canary : 공격자가 버퍼 오버플로 공격을 시도할 때 null 문자열은 해당 값의 종료를 의미한다. 즉, 이 방법은 null은 절대로 넣을 수 없다는 것을 이용하여 canary에 문자열을 포함시킨다.
- Terminator canary : 대부분의 문자열 함수 동작은 null에서 끝나지만 null에서 끝나지 않는 몇몇 함수의 종료 값은 canary값을로 사용한다.
리눅스에서 대부분 카나리 값은 % fs:~ 에서 가져온다. 이 canary값을 %rax에 저장한다.
그리고 마지막에 저장해놓은 이 값과 %fs:~ 의 값을 비교하여 판단한다.
4. 스택 실드
스택 실드도 gcc 컴파일러 확장으로 개발했으며, ret 보호가 주목적이다. 스택 실드는 함수를 호출할 때는 ret를 Global Ret Stack이라는 특수 스택에 저장한다.
함수를 종료할 때는 Global Ret Stack에 저장된 ret 값과 스택의 ret 값을 비교하여 일치하지 않으면 프로그램은 종료된다.
5. ASLR
ASLR(=Address Space Layout Randomization)은 메모리 공격을 방어하려고 주소 공간 배치를 난수 화하는 기법이다.
즉 , 스택, 힙, 라이브러리 등 데이터 영역 주소 등을 난수 화하여 프로세스의 주소 공간에 배치하는 것으로 리눅스 커널 2.6.12 이후부터 적용했다.
실제로 cat/proc/self/maps 명령을 연속하여 실행해보면 메모리 주소가 계속 바뀌는 것을 확인할 수 있다.
즉, ASLR을 적용하여 메모리에 존재하는 주소를 지속적으로 변화시킴으로써 공격자가 특정 주소에 버퍼 오버플로 공격하는 것을 불가능하게 한다.
'Security_lab' 카테고리의 다른 글
백도어 (0) | 2021.07.30 |
---|---|
포맷 스트링 공격 (0) | 2021.07.29 |
gdb를 통한 heap buffer overflow (0) | 2021.07.27 |
gdb를 통한 Stack buffer overflow (0) | 2021.07.27 |
레이스 컨디션 실습 (0) | 2021.07.25 |