[보이지 않는 Linux 위협과 대응 1부] Syslogk는 어떻게 자신을 숨기는가
Linux 시스템에 침투한 악성코드가 프로세스 목록, 파일 목록, 네트워크 연결 정보에서도 확인되지 않는다면 어떻게 찾아낼 수 있을까? Syslogk는 Linux 커널 영역에서 동작하며 프로세스와 파일, 네트워크 통신 그리고 자신까지 숨기는 루트킷이다. 이번 글에서는 AhnLab Security Intelligence Center(ASEC)가 분석한 Syslogk의 주요 은폐 기법과 동작 방식을 살펴본다.

Linux 커널에 숨는 루트킷, Syslogk
악성코드와 침해 흔적을 숨기기 위해 Linux 커널을 변조하는 기법은 오래전부터 사용돼 왔다. Syslogk 역시 이와 같은 방식으로 동작하는 루트킷 중 하나로, 악성 파일뿐만 아니라 프로세스와 TCP 통신, 파일과 디렉터리를 은폐하며 자신이 Linux 커널 모듈(Linux Kernel Module, LKM)로 동작하는 사실도 숨긴다.
Linux는 시스템 정보를 확인하기 위해 시스템 관리 도구를 사용할 때 커널 내부를 직접 확인하는 것이 아니라 사용자가 명령어를 실행하면 커널이 제공하는 정보를 받아 결과를 화면에 표시한다. 예를 들어 ps, top, pstree는 /proc의 프로세스 정보를 이용해 실행 중인 프로세스를 보여 주고, netstat은 TCP 소켓 정보를 바탕으로 네트워크 연결 상태를 표시한다. ls는 디렉터리 엔트리를 읽어 파일과 디렉터리 목록을 보여 주며, lsmod는 현재 동작 중인 모듈 정보를 표시한다.
Syslogk는 이러한 정보 반환 과정에 개입해 정상 함수나 Callback 함수의 처리 흐름을 가로채 공격자가 정한 조건에 해당하는 항목만 제외하고, 나머지 정보는 정상적으로 반환해 자신의 존재와 관련 흔적을 숨긴다. 이 과정에서 프로세스와 TCP 통신 정보는 인라인 후킹(Inline Hooking)으로 숨기고, 특정 파일과 디렉터리는 VFS(Virtual File System) 테이블 후킹을 통해 목록에 나타나지 않도록 한다. 또한 커널 모듈 목록에서도 자신의 정보를 제거해 루트킷 자체의 존재까지 감춘다. 결과적으로 조회 명령은 정상적으로 동작하고 다른 정보도 그대로 표시되기 때문에, 사용자는 일부 항목이 의도적으로 누락됐다는 사실을 알아차리기 어렵다.
인라인 후킹을 이용한 프로세스 및 TCP 통신 은폐
인라인 후킹은 함수의 시작 부분을 변조해 해당 함수가 호출되었을 때 원래 코드 대신 공격자가 정의한 함수가 먼저 실행되도록 만드는 기법이다. 이를 통해 공격자는 정상 함수의 처리 과정에 개입해 특정 정보를 제외한 후 정상 함수를 호출해 나머지 정상 동작을 이어 나갈 수 있게 한다.
Syslogk는 인라인 후킹을 적용하기 위해 먼저 변조할 커널 함수의 주소를 확인한다. 커널 심볼과 주소를 확인할 수 있는 /proc/kallsyms를 이용해 대상 함수의 메모리 위치를 찾고, 확인한 주소를 미리 정의한 구조체의 hookFuncAddress에 저장한다.

[그림 1] /proc/kallsyms를 이용해 후킹 대상 커널 API 함수의 주소를 확인하는 코드
Syslogk는 대상 커널 함수가 위치한 메모리 영역을 수정하기 위해 CR0 레지스터의 Write Protect 비트를 0으로 변경해 쓰기 보호를 해제하고, 해당 메모리 페이지의 PTE(Page Table Entry)에 쓰기 권한을 설정한다. 이를 통해 보호된 커널 함수의 코드 영역을 수정할 수 있는 상태를 만든다.

[그림 2] 메모리 쓰기 권한을 설정하고 대상 API 함수의 프롤로그를 변조하는 코드
이와 같이 인라인 후킹이 적용되면 대상 함수가 호출될 때 Syslogk가 지정한 함수가 먼저 실행되고, 이 과정에서 공격자가 숨기려는 정보만 조회 결과에서 제외할 수 있다. Syslogk는 이러한 방식으로 proc_root_readdir를 후킹해 특정 프로세스를 숨기고, tcp4_seq_show를 후킹해 특정 TCP 소켓 정보를 조회 결과에서 제외한다.
proc_root_readdir 후킹을 통한 프로세스 은폐
proc_root_readdir 함수는 /proc 디렉터리의 프로세스 항목을 확인하면서, 각 항목을 처리하도록 미리 등록된 Callback 함수를 호출한다. Callback 함수는 전달받은 항목을 프로세스 목록에 포함하는 역할을 하며, ps, top, pstree와 같은 유틸리티는 이 과정을 통해 제공되는 정보를 바탕으로 실행 중인 프로세스 목록을 보여 준다.
Syslogk는 proc_root_readdir를 후킹해 이 과정에 개입한다. 먼저 기존 Callback 함수의 주소를 bk_proc_filldir에 저장한 뒤, 공격자가 정의한 nw_proc_filldir로 교체한다. 이후 원본 proc_root_readdir 함수가 실행되더라도 /proc에서 확인한 프로세스 항목은 정상 Callback 함수로 바로 전달되지 않고 nw_proc_filldir를 먼저 거치게 된다.

[그림 3] 정상 Callback 함수의 주소를 백업하고 공격자가 정의한 Callback 함수가 실행되도록 변경하는 코드
nw_proc_filldir는 이렇게 전달된 각 프로세스 항목을 확인해 은폐 여부를 결정한다. 프로세스 이름이 was_sys_relay와 일치하면 해당 항목을 기존 Callback 함수로 넘기지 않아 프로세스 목록에 표시되지 않도록 한다.
Syslogk는 was_sys_relay 프로세스 자체뿐 아니라 PID와 상위 프로세스 ID를 확인해 was_sys_relay를 부모 또는 조부모로 둔 프로세스도 함께 숨긴다. PID가 2보다 작은 항목은 제외 대상에서 벗어나며, 은폐 조건에 해당하지 않는 프로세스는 기존 Callback 함수를 통해 정상적으로 목록에 표시된다.

[그림 4] 특정 프로세스와 관련 프로세스를 프로세스 목록에서 은폐하는 코드
결과적으로 was_sys_relay와 관련 프로세스는 실제로 계속 실행되지만, /proc의 프로세스 정보를 조회하는 과정에서 해당 항목이 제외되어 ps, top, pstree 등의 명령어로 확인한 프로세스 목록에 나타나지 않게 된다.
tcp4_seq_show 후킹을 통한 TCP 통신 은폐
Syslogk는 프로세스뿐 아니라 TCP 통신 정보도 은폐하며 이를 위해 /proc/net/tcp의 TCP 소켓 정보를 출력하는 tcp4_seq_show 함수를 후킹한다. tcp4_seq_show는 하나의 소켓 정보를 처리할 때마다 호출되며, 첫 번째 인자로 전달되는 seq_file 구조체에 각 소켓의 출력 정보를 기록한다. netstat과 같은 유틸리티는 이렇게 기록된 정보를 바탕으로 TCP 연결 목록을 보여 준다.
후킹된 tcp4_seq_show가 호출되면 Syslogk는 먼저 원본 tcp4_seq_show를 실행해 해당 소켓의 정보를 seq_file에 정상적으로 기록한다. 이후 숨기려는 포트 번호를 문자열로 변환한 뒤, 기록된 소켓 정보에 해당 문자열이 포함돼 있는지 확인한다. 조건이 일치하면 해당 소켓 정보가 기록된 부분을 출력 버퍼에서 제거해 TCP 연결 목록에 나타나지 않도록 한다.

[그림 5] 특정 포트가 포함된 TCP 소켓 정보를 조회 결과에서 제거하는 코드
이때 실제 TCP 연결이 사라지는 것은 아니며, 조회 결과에서 해당 연결 정보만 제외된다. 소켓이 유지되고 통신이 계속되지만 일반적인 TCP 조회 결과에는 해당 엔트리가 나타나지 않는 것이다. 특정 포트 조건에 해당하는 정보만 제외하기 때문에 다른 연결은 정상적으로 표시된다.
VFS 테이블 후킹을 통한 파일 및 디렉터리 은폐
Syslogk는 파일과 디렉터리를 숨길 때 VFS(Virtual File System) 테이블 후킹을 사용한다. VFS는 서로 다른 파일 시스템을 Linux에서 공통된 방식으로 처리하기 위한 계층으로 파일 시스템의 여러 동작을 담당하는 함수의 포인터가 저장돼 있다.
Syslogk는 VFS 테이블에 있는 연산 가운데 readdir에 후킹을 적용한다. 대상 경로의 파일 연산 구조체를 얻은 뒤 기존 readdir 포인터를 보관하고 공격자가 정의한 함수의 주소로 교체한다. 이후 디렉터리의 내용을 조회하면 정상 readdir보다 공격자 함수가 먼저 실행된다.

[그림 6] VFS 테이블을 후킹해 readdir 함수의 주소를 공격자가 정의한 함수로 변경하는 코드
변조된 readdir의 처리 구조는 프로세스를 숨길 때와 유사하다. 공격자 함수는 기존의 정상 filldir Callback 함수 주소를 백업하고, 원본 readdir를 호출할 때 자신이 정의한 nw_root_filldir Callback 함수를 전달한다. 디렉터리 항목은 원본 함수가 읽지만, 각 파일과 디렉터리 엔트리는 nw_root_filldir를 거쳐 결과에 포함될지 결정된다.

[그림 7] 정상 Callback 함수를 백업하고 공격자가 정의한 Callback 함수가 실행되도록 설정하는 코드
nw_root_filldir는 파일 또는 디렉터리 이름에 was-patch 문자열이 포함돼 있는지 검사한다. 이름이 조건에 맞으면 정상 Callback 함수로 전달하지 않아 해당 엔트리가 목록에 들어가지 않게 한다. 반대로 was-patch가 포함되지 않은 항목은 정상 Callback 함수로 전달하기 때문에 그대로 표시된다.

[그림 8] 파일 또는 디렉터리명에 ‘was-patch’ 문자열이 포함된 경우 해당 항목을 은폐하는 코드
따라서 관리자가 ls로 디렉터리를 확인하면 이름에 was-patch가 포함된 항목이 실제로 존재하더라도 목록에 나타나지 않는다. 다만 Syslogk가 파일을 삭제하거나 해당 경로에 대한 직접 접근을 차단한 것은 아니기 때문에 숨은 디렉터리의 정확한 경로를 알고 있다면 cd로 직접 접근할 수 있고, 그 안의 하위 파일도 열람할 수 있다.
이처럼 Syslogk는 파일이나 디렉터리 자체를 제거하는 것이 아니라 readdir가 반환하는 목록에서 특정 항목만 제외한다. 나머지 항목은 정상적으로 표시되고 명령어도 오류 없이 실행되기 때문에, 사용자는 일부 파일이나 디렉터리가 숨겨졌다는 사실을 알아차리기 어렵다.
커널 모듈 은폐를 통한 Syslogk 자체 은닉
Syslogk는 LKM(Loadable Kernel Module) 형태로 Linux 커널 영역에서 동작한다. LKM은 실행 중인 Linux 커널에 동적으로 로드되어 커널 기능을 확장하는 모듈로, 장치 드라이버와 같은 정상 기능에도 사용된다. Syslogk는 이러한 LKM 형태로 동작하면서 프로세스와 TCP 통신, 파일을 숨기고, 자신이 커널 모듈로 동작하고 있다는 사실까지 은폐한다.
Linux 커널은 로드된 모듈을 이중 연결 리스트로 관리한다. 각 커널 모듈의 노드는 앞뒤 노드와 연결돼 있으며, lsmod 명령은 이 목록을 바탕으로 현재 동작 중인 커널 모듈을 보여 준다. Syslogk는 이 모듈 목록에서 자신의 노드를 제거해 lsmod 결과에 나타나지 않도록 한다. 이 작업을 수행하기 전에 mod_list 변수에 자신의 모듈 노드 위치가 저장돼 있는지 확인해 현재 은폐 상태를 판단한다.
아직 숨지 않은 상태라면 Syslogk는 자신의 현재 모듈 노드 위치를 mod_list에 백업한 뒤 커널 모듈 목록을 구성하는 이중 연결 리스트에서 해당 노드를 분리한다. 이 과정에서 모듈 자체가 중지되는 것은 아니기 때문에 Syslogk는 커널에서 계속 동작하지만 lsmod 결과에는 나타나지 않는다.
Syslogk는 lsmod뿐 아니라 sysfs의 /sys/module에서도 자신의 정보를 숨긴다. Linux는 커널 모듈 정보를 kobject 형태로 sysfs에 등록하는데, Syslogk는 자신의 kobject를 제거해 /sys/module에서 해당 모듈이 보이지 않도록 한다. 이 과정에서 state_in_sysfs는 해당 객체가 sysfs에 등록돼 있는지를 나타내는 상태값으로 사용된다. kobject가 제거되면 이 값이 0으로 바뀌는데, Syslogk는 이를 다시 1로 설정해 등록 상태인 것처럼 유지한다.

[그림 9] 커널 모듈 목록과 /sys/module에서 Syslogk 루트킷 자체를 은폐하는 코드
이러한 조작은 Syslogk 자체를 종료하거나 제거하는 것이 아니라, 관리자가 확인하는 커널 모듈 정보에서 자신의 존재를 감추는 방식이다. Syslogk는 커널에서 계속 동작하면서 은폐 기능을 수행할 수 있다.
은폐된 위협을 확인하고 대응하는 방법
Syslogk와 같은 루트킷은 운영체제의 정보 조회 과정에 개입해 특정 프로세스와 파일, 통신 정보를 결과에서 숨길 수 있다. 이 때문에 ps, ls, netstat과 같은 일반적인 Linux 명령어만으로는 은폐된 프로세스와 파일, 통신 정보 등을 확인하기 어렵다.
따라서 루트킷 감염이 의심되는 경우에는 먼저 커널 영역에서 정보를 숨기는 모듈이 존재하는지 확인해야 한다. 은폐된 커널 모듈을 탐지하고 은폐 상태를 해제하면 기존 조회 결과에서는 보이지 않았던 프로세스와 파일 등의 정보도 다시 확인할 수 있다.
이후에는 확인된 프로세스와 파일을 검사해 악성 여부를 확인하고, 루트킷과 관련된 커널 모듈과 악성 파일을 제거해야 한다. 즉, 은폐형 루트킷에 대응하려면 단순히 현재 보이는 항목을 검사하는 데서 끝나는 것이 아니라, 은폐 여부를 확인하고 숨겨진 대상을 다시 드러낸 뒤 진단과 치료를 진행하는 과정이 필요하다.
[보이지 않는 Linux 위협과 대응 2부] 은닉 루트킷 탐지부터 치료까지
AhnLab V3 Net for Linux Server를 이용해 은폐된 커널 모듈을 탐지하고 은폐 상태를 해제한 뒤, 관련 파일을 진단하고 치료하는 실제 대응 과정을 확인할 수 있다.
- AhnLab