0. 개요

  이 문서에서는 리눅스 안티바이러스에 대한 내용을 정리하기로 하겠다. 사실 그나마 흔한 윈도우 기반 안티바이러스에 대해서도 제대로 알지 못하지만 공부하며 찾아본다는 의미로 이 문서를 만들고자 한다. 리눅스도 윈도우와 마찬가지로 안티바이러스와 관련된 대부분의 자료들이 사용자 모드에서 실행되는 on-demand 형태의 시그니처 기반 스캐닝 위주로 존재한다. 여기서는 실제 상용 리눅스 안티바이러스 제품에서 사용될만한 기술들을 최대한 찾아보며 정리하고자 한다.

  먼저 파일 스캐닝과 관련된 내용을 정리하겠다. 앞에서 언급하였듯이 on-demand 형태의 사용자 모드 시그니처 기반 스캐닝은 윈도우와 비슷하기도 하고 이미 자료가 많이 있기 때문에 언급하지 않기로 하고 바로 실시간 파일 스캐닝 기법을 다루기로 한다. 다행히도 리눅스에서는 최근 fanotify라는 메커니즘이 제공되어 파일 실행 및 종료, 수정 등의 이벤트에 대한 알림 및 승인, 거부도 간단한 방식으로 가능해졌다. 윈도우의 경우에는 파일 시스템 미니필터 드라이버가 제공되서 필터 관리자를 통한 메커니즘이 제공된다.

  이후에는 프로세스 생성 감시 메커니즘을 다룬다. 윈도우의 경우에는 PsSetCreateProcessNotifyRoutineEx() 등의 함수들이 제공되어 프로세스나 스레드 생성 시에 콜백 루틴을 등록해줄 수 있으며 생성되는 프로세스의 정보 획득 및 생성의 승인 또는 거부를 선택할 수 있다. 관련 내용을 리눅스에서 찾아보았는데 아직 윈도우에서 제공되는 형태와 같은 메커니즘이 제공되어 있지 않다. 그래서 관련 사항들을 최대한 찾아본 결과 리눅스 보안 모듈 (LSM : Linux Security Module)을 이용한 방식이 가장 적합한 것으로 보인다. 다른 몇몇 방법들이 있긴 하지만 프로세스 생성 이전에 통지받고 동시에 생성을 거부할 수도 있는 방법은 이 방법밖에는 없어보인다.

  마지막으로 자가 보호 방식에 대해서 설명하고자 했지만 적절한 방식을 찾지 못하고 위에서 언급한 LSM을 이용한 방식을 소개한다. 구체적으로 grsecurity의 소스 코드를 분석하여 자가 보호를 제공하는 방식을 공부한다. 이 외에도 실제 안티바이러스 소프트웨어에서 사용되는 방식은 더 많이 존재하고 리눅스 자체적으로 제공하는 것이 한계가 있기 때문에 대부분 직접 구현되어 있을 것이다. 더 자세한 설명 그리고 추가적인 사항을 공부하면서 꾸준히 이 문서를 업데이트할 것이다.





1. 실시간 파일 스캐닝

  파일 스캐닝 기법에는 on-demand 스캐닝과 on-access 스캐닝이 존재한다. on-demand 스캐닝은 사용자가 검사 버튼을 누르거나 주기적으로 검사 스케줄을 정했을 때 수행되는 스캐닝이다. 일반적으로 인터넷에 퍼져 있는 안티바이러스 소스 코드들을 보면 대부분 시그니처 기반에 이러한 on-demand 스캐닝 방식이다. 이 방식은 전체 또는 지정된 범위에 존재하는 파일들을 모두 검사하는 방식으로서 특별한 지식이 필요하거나 난이도가 높거나 하지는 않으며 관련 자료들도 이미 충분히 존재한다. 물론 대부분 윈도우 플랫폼에서의 자료겠지만 리눅스 플랫폼에서도 충분히 존재한다고 생각한다.

  여기서는 on-access 스캐닝을 다룰 것이다. 이 방식은 실시간 검사라고도 불리는데 파일이 여러 이벤트를 통해 생성되었을 때 실시간으로 검사하는 것이다. 예를들어서 취약점을 이용해 악성코드 바이너리를 다운로드시킨다거나 드로퍼가 악성코드를 드롭시켜서 악성코드 바이너리가 생성되었을 때 이 파일을 검사한다. 당연히 이러한 방식을 사용하기 위해서는 파일 생성 이벤트가 발생하였을 때 알려주는 메커니즘이 필요할 것이다. 이 항목에서는 리눅스에서의 관련 메커니즘과 함께 on-access 스캐닝 기법을 다루기로 한다.

  먼저 그나마 조금 더 대중적인 윈도우의 메커니즘을 먼저 예시로서 설명한 후에 리눅스로 나아가기로 하겠다. 윈도우도 앞에서 설명한 on-demand 스캐닝 및 on-access 스캐닝 기법이 나뉜다는 것은 당연한 이야기일 것이다. 윈도우의 경우에는 on-access 스캐닝을 위하여 즉 안티바이러스 소프트웨어를 위하여 제공되는 메커니즘이 존재한다. 먼저 윈도우에서는 과거 루트킷처럼 (파일 생성 등과 관련된) 관련 함수들을 후킹하는 드라이버를 만들어 on-access 스캐닝을 수행하였다. 하지만 최근에는 커널의 구성 요소인 필터 관리자에 의해 관리되는 파일 시스템 미니 필터 드라이버를 개발하여 이러한 기능을 제공할 수 있다. 파일 시스템 미니 필터 드라이버에서는 여러 제공되는 함수들을 통해 파일 생성 등의 이벤트를 통지받고 또한 특정한 행위를 수행할 수 있다. 물론 안티바이러스의 경우에는 이 특정한 행위란 생성되는 파일에 대한 검사일 것이다.

  리눅스의 경우에는 비슷한 메커니즘이 존재한다. 과거 dnotify부터 inotify를 거쳐 현재는 fanotify가 존재한다. 여기서는 dnotify는 제외하고 inotify부터 설명해보도록 하겠다.


1.1 inotify

  inotify는 리눅스 커널에서 제공되는 기능으로서 디렉토리 및 파일을 감시할 수 있다. 참고로 디렉토리를 감시할 경우에는 디렉토리 자체 및 내부의 파일이 변경되는 이벤트에 대한 정보도 받을 수 있다. 이 메커니즘은 리눅스 커널에서 CONFIG_INOTIFY와 CONFIG_INOTIFY_USER를 설정해야 사용할 수 있는 커널 컴포넌트이다. 참고로 특정 디렉토리를 감시 대상에 추가한 경우에 내부에 존재하는 디렉토리까지 감시 대상에 추가되지는 않는다. 즉 recursive하지 않기 때문에 디렉토리 내부에 새로운 디렉토리가 생성되거나 삭제되는 경우를 위한 추가적인 메커니즘이 필요하다.

  다음으로는 inotify를 사용하는 방식 및 구성에 대해서 알아보겠다. 아주 간단한 예제를 통해 설명해 보자면 가장 먼저 inotify_init()을 호출하는데 이 함수는 inotify 인스턴스를 생성하는 역할을 하며 우리는 이 함수에서 반환된 file 디스크립터를 통해 생성된 inotify 인스턴스에 접근한다. 이후 감시할 대상의 경로 및 감시할 이벤트를 인자로 넣고 inotify_add_watch()를 통해 watch list에 추가한다(반대로 삭제하는 함수는 inotify_rm_watch()이다). 이 함수는 유니크한 watch 디스크립터를 반환하는데 이것은 뒤에서 사용될 것이다. 어쨌든 이후 반복문을 사용하여 주기적으로 read()를 통해 file 디스크립터를 읽는다. read()로 inotify_event 구조체를 읽을 수 있고 여기에는 여러 정보들이 들어있다.

  앞에서 inotify_add_watch()의 인자로 감시할 이벤트를 넣는다고 언급하였다. 파일과 관련된 이벤트는 여러 종류가 있지만 많이 사용되는 이벤트들은 다음과 같다. 파일이 read() 등의 함수를 통해 접근된 경우에는 IN_ACCESS 이벤트가, 감시 대상 디렉토리 내에 파일이나 디렉토리가 생성된 경우에는 IN_CREATE, 삭제된 경우에는 IN_DELETE, 감시 대상 파일 또는 디렉토리 자체가 삭제된 경우에는 IN_DELETE_SELF 이벤트가 발생하며 파일이 수정된 경우에는 IN_MODIFY, 파일이 오픈된 경우에는 IN_OPEN 이벤트가 발생한다. IN_ATTRIB 이벤트는 파일의 권한, 소유자, UID, GID 등의 정보가 수정되었을 때 IN_MOVE_SELF는 대상의 이름이 변경되었을 때 (즉 이동하였을 때) 발생한다. 마지막으로 IN_MOVED_FROM과 IN_MOVED_TO 이벤트는 감시 대상 디렉토리 내부의 파일이나 디렉토리 이름이 변경(이동)되었을 때 발생하는데 원본 파일에서는 IN_MOVED_FROM이 발생할 것이고 변경된 이름의(이동 후의) 파일에서는 IN_MOVED_TO가 발생할 것이다.

  위에서는 inotify_add_watch()를 통해 감시할 이벤트를 넣어주었는데 이후 read()를 통해 얻은, 즉 특정 이벤트가 발생한 경우에 얻는 이벤트도 동일하다. read()를 통해 얻는 데이터는 inotify_event라는 구조체인데 이 구조체에는 앞에서 설명했듯이 발생한 이벤트 및 이벤트가 발생한 watch 디스크립터 등의 값이 존재한다. watch 디스크립터를 굳이 알려주는 이유는 동일한 file 디스크립터를 통해 여러 대상을 감시할 때 필요하기 때문이다.


1.2 fanotify

  앞에서는 간략하게 inotify를 살펴보았다. 이것을 통해 우리는 파일에 관련 이벤트가 발생한 경우 이를 통지해 주는 메커니즘을 사용할 수 있다는 것을 알게되었다. inotify는 많은 이벤트들에 대한 모니터링이 가능하지만 단점도 존재하는 것이 recursive한 모니터링이 불가능했고 또한 이벤트가 발생하였다는 통지 이외에는 제공하는 기능이 없었다. inotify와 비교하여 장단점이 존재하는 fanotify를 살펴보겠다.

  fanotify도 inotify와 비슷한 부분이 많다. 이 메커니즘도 리눅스 커널에서 CONFIG_FANOTIFY를 설정해야 사용할 수 있는 구성 요소이다. 또한 뒤에서 나오겠지만 권한과 관련된 처리가 필요할 때가 있는데 이것은 CONFIG_FANOTIFY_ACCESS_PERMISSIONS를 설정해야 한다.

  이번에는 fanotify를 사용하는 방식을 알아보겠다. 이것도 비슷하게 fanotify_init()을 통해 fanotify group을 초기화하고 이 함수는 이 인스턴스와 연관된 file 디스크립터를 반환한다. 이 file 디스크립터는 inotify에서 inotify_add_watch(), inotify_rm_watch()와 비슷한 역할을 하는 fanotify_mark()에서 사용된다. 이 함수는 이것 외에도 flags, mask, 경로명 등의 인자를 받는데, 경로명은 당연히 감시 대상의 경로명을 의미할 것이고 flags는 FAN_MARK_ADD, FAN_MARK_REMOVE, FAN_MARK_FLUSH가 있다. FAN_MARK_ADD는 감시 대상에 대해 감시할 이벤트들을 추가한다는 것이고 FAN_MARK_REMOVE는 삭제, FAN_MARK_FLUSH는 전체 삭제를 의미한다. 이 3가지 중 적어도 하나는 필수이며 OR ( | )를 통해 다른 flag들이 추가될 수 있다. FAN_MARK_IGNORED_MASK는 mask에 설정된 이벤트들을 감시가 아닌 무시할 때 사용된다. 또한 FAN_MARK_MOUNT는 경로명에 지정된 마운트 포인트를 설정한다. 즉 마운트 포인트인 경로명 내의 서브디렉토리들까지 감시에 추가해주는 recursive한 모니터링 메커니즘을 제공한다.

  마지막으로 mask는 inotify와 마찬가지로 앞에서 언급한 감시할 이벤트들이다. FAN_ACCESS는 접근 즉 read된 경우에 발생하며 FAN_MODIFY는 수정 즉 write된 경우에 발생한다. FAN_OPEN 및 FAN_CLOSE는 각각 파일 또는 디렉토리가 open, close되었을 때 발생한다. FAN_OPEN_PERM과 FAN_ACCESS_PERM 이벤트는 파일 및 디렉토리를 각각 open 또는 read할 경우에 권한이 요구될 때 발생한다. 정확히 말하자면 파일이 열리기 전에는 FAN_OPEN_PERM, 닫힌 후에는 FAN_CLOSE_WRITE 이벤트가 발생한다.

  이제 모니터링이 제공되고 감시 대상 파일이나 디렉토리에서 이벤트가 발생할 것이다. 이것도 inotify처럼 read()를 통해 읽는데 정확히는 fanotify_event_metadata 구조체가 생성된다. 이 구조체에는 발생한 이벤트들 외에도 open file descriptor, 이벤트를 발생시킨 프로세스의 pid 등 여러 정보가 존재한다. 

  발생하는 이벤트는 두 종료가 있는데 Notification 이벤트와 Permission 이벤트가 그것이다. Notification 이벤트들은 inotify의 경우처럼 정보를 제공해 주며 Permission 이벤트들은 파일 접근에 관한 권한을 결정해 줘야 한다. 이 이벤트들은 앞에서 설명한 mask와 거의 같다고 보면 된다. 특이사항으로는 Permission 이벤트들 즉 FAN_ACCESS_PERM, FAN_OPEN_PERM 이벤트는 반드시 write()를 통해 응답을 보내야 한다는 것이다. fanotify_response() 구조체를 생성하여 fanotify file 디스크립터에 write()를 통해 응답을 보낸다. 응답은 이 구조체의 response 멤버에 FAN_ALLOW 또는 FAN_DENY 값을 설정하는 것이다.


1.3 정리

  여기서는 inotify와 fanotify를 비교하면서 정리하겠다. 둘을 살펴보면서 확인한 공통점들이 많기 때문에 기본적인 정보는 생략하고 차이 위주로 설명하겠다. 먼저 recursive한 모니터링을 제공한다는 점과 이벤트 통보 이외에도 접근 통제 즉, 접근의 허용 여부를 결정할 수 있다는 점에서 fanotify의 장점이 크다는 것을 알 수 있다. 이 접근 통제는 특히 안티바이러스에서 유용하게 사용될 수 있는 기능이다. 또한 fanotify_event_metadata 구조체를 보면 이벤트를 발생시킨 프로세스의 PID 및 open file descriptor가 제공된다. open fd를 통해 감시 대상 파일의 내용에도 접근할 수 있다.

  하지만 fanotify에서는 제공하지 않는 inotify만의 장점도 존재하는데 fanotify에서는 inotify보다 제한된 이벤트들을 감시할 수 있다. 즉 inotify에서는 지원됬었던 create, delete, move(rename) 이벤트에 대한 감시는 지원되지 않는다. 





2. 프로세스 생성 감시

  앞에서도 설명하였듯이 리눅스에서는 프로세스 생성 및 종료 등 프로세스의 상태와 관련된 모니터링 메커니즘이 존재하지 않는다. inotify를 통해 /proc을 모니터링하는 것은 불가능하고 proc connector 방식도 fork나 exec 같은 프로세스 이벤트를 알려주기만 할 뿐이지 이벤트 실행 이전에 통지함과 동시에 생성을 거부할 수 있는 메커니즘은 아니다. 그래서 결국 리눅스 보안 모듈을 이용한 방식을 선택하고 설명하고자 한다. 하지만 이 방식이 안티바이러스에서 실제로 사용될 수 있는지 즉 오버헤드를 감당할 수 있는지에 대해서는 모르겠다. 물론 다른 여러 방식으로 사용되고 있기 때문에 확실한 자료 없이 정리하기로 한다.


2.1 LSM

  리눅스 보안 모듈(LSM)이라고 하면 일반적인 모듈이라고 생각할 수 있는데 사실 설명이 필요한 부분이다. 과거 리눅스라는 운영체제에서 보안을 구현할 수 있는 방식으로서 선택 사항은 두 가지가 있었다. 하나는 커널 수정 방식이며 다른 하나는 LKM(Loadable Kernel Module) 즉 탑재 가능한 커널 모듈 방식이었다. 보안을 커널에 집적 구현하는 것과 모듈에 구현하는 것은 각각의 장단점이 명확했다. 리눅스 보안 모듈이라고 하면 LKM 형태로 구현된 보안 모듈이라고 생각하기 쉽지만 사실은 조금 다른 개념이다. 

  정확히 설명하자면 LSM은 여러 종류의 보안 정책 모듈들을 위하여 일반화된 framework를 제공하는 방식이다. 즉 리눅스 커널에서는 framework만을 제공하는 방식으로서 커널에 인터페이스를 만들고 이 인터페이스를 통해 써드 파티 보안 모듈들의 접근 제어 메커니즘과 커널을 연결시킨다. 많이 들어봤겠지만 SELinux, AppArmor, Smack, TOMOYO Linux 등이 공식적으로 리눅스 커널에 받아들여진 보안 모듈들이다.

  조금 더 자세히 보자면 리눅스 커널의 소스 코드 중에서 security.c가 LSM을 구현한 것이라고 보면 된다. 우리는 LSM에서 구현된 인터페이스를 통해 보안 정책 모듈을 만들 수 있다. 실질적으로 LSM은 커널이 내부 객체에 접근을 시도하기 직전에 Hook을 걸어 보안 모듈로 우회시킨다. SELinux나 AppArmor 등의 보안 모듈들은 원하는 부분에 콜백을 등록하여 보안 정책을 구현한다.


2.2 LSM을 이용한 프로세스 생성 모니터링

  앞에서는 개념만 설명하였기에 예를들어 설명해보겠다. 다음 링크 [ https://github.com/skx/linux-security-modules ]의 개발자는 LSM을 이용한 간단한 보안 모듈을 만들었다. 이 중에서 whitelist라는 예제를 보면 실행 파일 중에서 특정한 속성을 가진 바이너리만 실행 가능하게 만들려고 한다. 이 경우 여러 설정들은 생략하고 security_add_hooks() 함수로 bprm_check_security에 hook을 설치한다. 참고로 리눅스 커널 버전 4.1부터 security_add_hooks()라는 함수가 사용되고 있으며 그 이전에는 register_security()를 이용한 방식을 사용하였다. 또한 bprm_check_security라는 hook은 자세히 살펴보면 (CONFIG_SECURITY 커널 컴파일 옵션을 사용해 LSM을 활성화시킨 경우에) exec()이 최종적으로 호출하는 함수인 security_bprm_check()에서 사용되는 hook이다. 즉 exec() 호출 시에 호출되는 hook에 security_add_hooks()로 콜백 함수를 등록하는 것이다. 이 콜백 함수는 당연히 바이너리의 속성을 살피고 실행 여부를 판단하는 루틴으로 이뤄어져 있다.

  예를 조금 더 들어서 fork()의 경우에는 걸 수 있는 함수가 security_task_create()이며 task_create에 hook을 걸어서 사용할 수 있다. 이 외에도 include/linux/lsm_hooks.h를 확인하면 여러 hook들을 찾을 수 있다. 여기서 찾은 hook을 통해 security/security.c에서 이 hook을 사용하는 함수를 찾는다. 이후 이 함수가 어디에서 사용되는지를 추론하여 여러가지 지원되는 hook들을 정리할 수 있다. 앞에서는 프로세스와 관련된 것만 예를 들었지만 파일 시스템, 네트워크 등 여러 종류의 hook을 위한 인터페이스가 제공된다.





3. 자가 보호

  안티바이러스 프로그램은 특성 상 자기 자신을 보호할 필요가 있다. 안티바이러스에 대한 공격으로는 프로세스 종료(리눅스에서는 kill) 외데도 메모리 변조(리눅스에서는 ptrace 등을 이용한) 같은 공격이 있을 수 있다. 윈도우에서는 ObRegisterCallbacks()를 제공하여 프로세스 및 스레드 객체에 대한 특정한 사전 / 사후 동작에 관한 통지를 받는 콜백 함수를 등록할 수 있다. 이를 통해 유저 모드에서 프로세스나 스레드에 접근하려고 하는 경우 이 콜백이 먼저 호출되어 이러한 접근을 차단할 수 있다.

  리눅스의 경우 이러한 함수가 제공되는지 찾아보았지만 역시 존재하지 않았다. 사실 방어 이전에 리눅스에서는 어떤 공격이 존재하는지에 대한 지식도 부족한 상태였기 때문에 자질구레한 방어 기법들을 일일이 정리할 능력이 되지 못한다. 그러던 차에 앞에서 정리했던 LSM을 이용한 방식을 알게되었다. LSM을 정리하면서 가장 유명한 보안 모듈로 SELinux를 이야기했는데 여기서는 grsecurity라는 보안 모듈(공식 커널에 포함되어 있다)을 설명하고자 한다. 사실 SELinux를 보면서 복잡하기도 하고 보안 정책과 관련된 내용이겠구나 하는 생각에 그다지 관심을 가지지 않았는데 grsecurity를 보면서 지금까지 다루어왔던 익숙한 보안 기법들을 제공해준다는 사실에 놀랐다. 여기서는 프로세스 보호에 관련된 내용만 정리하겠지만 grsecurity는 이 외에도 PaX(윈도우의 DEP 같은), ASLR 같은 수많은 보안 기능들을 제공한다.

  직접 어떤 방식으로 구동하는지에 대한 설명은 당연히 위에서 설명한 hook을 이용한 방식일 것이므로 생략하고 어떤 공격이 있을 수 있는지 위주로 정리하겠다. 물론 당연히 grsecurity에서도 제공되는 방어 기능들이다. 가장 먼저 안티바이러스를 종료시키는 공격이 있을텐데 grsecurity에서는 LSM을 이용하여 특정 프로세스에 대한 kill을 못하게 설정할 수 있다. 종료 외에도 리눅스의 경우 ptrace를 이용하여 프로세스를 가로챌 수 있는데 특정 프로세스에 관해 이것을 막아주는 기능도 존재하며 사용자 모드의 프로세스가 실행 중일 때 메모리를 할당하여 코드를 생성하는 기능 및 패치하는 공격에 대한 방어도 존재한다. 다음 링크들은 grsecurity에서 제공되는 보안 기능들을 정리한 사이트들이다. [ https://grsecurity.net/compare.php ] [ https://en.wikipedia.org/wiki/Grsecurity ]





4. 정리

  현대 상용 안티바이러스 제품들이 어떤 형식으로 구현되는지에 대한 지식이 많이 부족한 상태에서 이렇게 나름대로 정리했다는 문서를 만들어서 올리는 것이 검색하는 사람들에게 민폐가 될 수 있겠다는 걱정이 있지만 그래도 대충이라도 훑어보면서 검색할만한 키워드 하나 쯤은 제공할 수 있지 않을까 하는 마음에 정리한 문서를 공개한다. 

  사실 그나마 자료가 있는 윈도우 안티바이러스에 대해서도 찾기 힘들었지만 리눅스 플랫폼의 안티바이러스는 정말 자료를 찾기 힘들어 보인다. 물론 ClamAV라는 오픈 소스 안티바이러스가 존재하지만 여기서는 fanotify를 이용한 on-demand 파일 스캐닝과 관련된 자료밖에 없어서 나머지 내용들은 나름대로 추리해 가면서 찾아보았다.

  실제로 판매되는 적절한 기능을 갖춘 리눅스 플랫폼의 안티바이러스에 대한 정보가 많이 존재했으면 하는 바람이며 이것은 윈도우도 마찬가지이다. 물론 시장이 좁아서 더더욱 없을 것이지만 다른 프로그램들과 달리 안티바이러스 제품들은 특히나 제대로 된 정보를 찾기 힘든 편이다. 어쨌든 배움이 있을 때마다 추가하도록 하겠다.



'악성코드 분석' 카테고리의 다른 글

윈도우 권한과 UAC (User Access Control) 우회  (5) 2017.08.19
악성코드가 감염되기까지  (2) 2017.08.13
Windbg, Gdb 명령어 정리  (0) 2017.06.27
VC++ 옵션 정리  (0) 2017.06.03
다형성 바이러스  (4) 2017.05.16
Posted by SanseoLab

블로그 이미지
Malware Analyst
SanseoLab

태그목록

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.4
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

최근에 올라온 글

최근에 달린 댓글

글 보관함