C는 high-level assembly이다 따라서..
- memory가 raw pointer에 노출된다. : 다른 언어가 레퍼런스 변수만으로 객체에 접근하는데 비해 C는 포인터를 이용하여 메모리 접근이 자유롭다.
- 배열의 경계 검사를 하지 않는다. (하지만 하드웨어도 하지 않는다.)
Buffer Overflow
아래의 코드를 예로 들어보자.
void read_req() {
char buf[128];
int i;
gets(buf);
}
컴파일은 memory layout을 어떻게 생성할까?
- 알다시피, C로 생성한 실행파일의 구조는 아래와 같다.
공격자는 해당 코드를 어떻게 이용할까?
- gets함수로 표준입력장치로 buf에 데이터를 받을때, 만약 buf의 사이즈가 128을 넘어가게되면 다른 데이터를 덮어버린다!
그러면 뭐가 문젠데?
- 공격자는 return address에 데이터를 쓸수있게 된다! 이는 공격자가 의도한 프로그램으로 jump가 가능하다! 공격자는 무엇이든 할수있게 된다.
공격자는 해당 코드를 이용해 무엇을 할 수 있을까?
- 만약 프로세스가 root나 관리자 권한으로 실행중인경우 시스템에서 무엇이든 할 수 있다.
- root권한으로 프로세스가 실행중이지 않더라도 스팸을 보내거나, 파일을 읽거나, 방화벽을 타지않고도 다른 서버를 공격할 수 있다.
왜 OS는 이런 공격을 알아차리고 막지 않는걸까.
- 서버가 IO나 IPC를 할때 OS는 웹서버에 의해서만 호출된다.
- 그외에 서버는 기본적으로 프로그램이 실행되도록 하며 하드웨어 페이지 테이블에 의존하여 프로세스들이 서로의 메모리 영역을 침범하지 않도록 예방하는 일을 한다.
- 하지만 페이지 테이블 보호는 버퍼가 overflow되서 return address영역까지 덮어쓴다 하더라도 모든 항목이 프로세스의 유효한 주소공간 내에 있기 때문에 이를 방지할 수 없다.
FIXING BUFFER OVERFLOWS
C에서 버그 예방하기
- buffer, string arrays등 버퍼의 사이즈 체크 신중히하기.
- 버퍼의 크기를 체크하는 표준 라이브러리 함수를 사용하기.(strcpy()대신 strncpy(), gets 대신fgets())
버그를 찾는데 도움이 되는 툴 사용하기
void foo(int *p) {
int offset; //주목
int *z = p + offset;
if(offset > 7){
bar(offset);
}
}
- 코드 정적분석도구를 이용하여 offset이 초기화 되지 않고 사용되었음을 알 수 있고, 버그를 방지할 수 있다.
- 무작위 입력을 제공하는 '퍼저'는 버그를 찾는데 효과적일수 있다.
- 그러나, 버그가 완전히 없다는것은 보장하기 어렵다.
- 부분분석도 유용하다. 예를들어 baggy bounds는 모든 메모리 에러를 찾지는 못하지만 많은 중요한것들을 감지한다.
memory-safe language(js, c#, python).
- raw memory pointer를 사용하지 않으면 메모리에러를 방지할수있다! 하지만 성능이 저하되겠지..(그리고 device driver처럼 C를 반드시 사용해야하는 분야도 있다.)
- garbage collection을 사용하는 언어를 사용하면 예방 가능!
canaries
- Idea: 호출하기전에 code ptr을 덮어쓰는것이 좋다.
StackGuard
- stack upon entry canary를 설정하고, return전에 canary value를 체크한다.
- 소스코드가 필요하다. 컴파일러가 canary checks를 넣는다.
- 까나리는 스택의 return address '앞에' 위치해야 한다. 그래야 return address를 덮어쓰는 overflow동작이 canary를 rewrite할것이다.
참고
'Today I learned' 카테고리의 다른 글
2020 10 16 (0) | 2020.10.16 |
---|---|
2020 10 14 (0) | 2020.10.14 |
2020 10 07 (0) | 2020.10.07 |
2020 10 05 (0) | 2020.10.05 |
2020 10 04 (0) | 2020.10.04 |
댓글