Building a Small Rust eBPF EDR 4: File Telemetry and Persistence Detection

이번 글에서는 rand-guard의 file telemetry를 다룬다. EDR에서 파일 이벤트는 persistence, credential access, configuration tampering 같은 행위를 이해하는 데 중요한 신호다. 하지만 모든 파일 접근을 같은 방식으로 수집하면 noise가 많고, path 처리도 복잡해진다.

rand-guard는 file telemetry를 넓게 확장하기보다 persistence-sensitive path를 관찰하는 작은 범위에서 시작한다.

현재 수집하는 file event

기본 설정은 다음과 같다.

[file]
enabled = true
hooks = ["openat", "openat2", "write", "rename", "renameat2", "unlink", "unlinkat"]
watch_paths = ["/etc", "/usr/bin", "/bin"]
watch_patterns = ["*.service"]
exclude_paths = ["/var/log", "/tmp", "/proc", "/sys"]

코드에는 open, write, rename, unlink 계열 event가 나뉘어 있다.

  • crates/ebpf/src/file_open.rs
  • crates/ebpf/src/file_write.rs
  • crates/ebpf/src/file_rename.rs
  • crates/ebpf/src/file_unlink.rs

userspace에서는 crates/user/src/normalize/file.rs가 raw file event를 normalized event로 바꾼다. normalized event에는 process table에서 얻은 ppid, comm, exe_path 같은 context가 함께 들어간다.

왜 watch와 exclude가 필요한가

파일 이벤트는 양이 많다. 시스템은 계속 파일을 열고 쓰고 지운다. 모든 이벤트를 그대로 출력하면 사람이 보기 어렵고, demo나 test도 불안정해진다.

그래서 rand-guard는 설정에서 관심 경로와 제외 경로를 나눈다.

  • watch_paths는 관심 있는 prefix다.
  • watch_patterns*.service 같은 단순 pattern이다.
  • exclude_paths/tmp, /proc, /sys처럼 noise가 크거나 의미가 다른 경로다.

eBPF 쪽에는 FILE_FILTER map도 있다. userspace가 prefix filter를 넣으면 eBPF가 일부 이벤트를 kernel side에서 줄일 수 있다. 다만 pattern이 설정되어 있거나 prefix가 너무 많으면 kernel filter는 비활성화되고 userspace filtering에 맡긴다. 복잡한 pattern matching은 verifier-friendly한 eBPF 코드보다 userspace가 더 적합하다.

path를 완벽하게 복원하지 않는다

file telemetry에서 가장 어려운 부분 중 하나는 path다. syscall argument로 들어온 filename은 항상 absolute path가 아니다. relative path일 수도 있고, dirfd와 mount namespace에 따라 실제 의미가 달라질 수도 있다.

현재 rand-guard는 이 문제를 완전히 해결하려 하지 않는다. eBPF에서 bounded string read로 syscall argument나 제한된 path 정보를 읽고, userspace에서 가능한 범위 안에서 필터링하고 출력한다.

이것은 의도적인 MVP 선택이다. path reconstruction을 완벽하게 하려면 더 많은 kernel/userspace 상태, namespace 처리, fd tracking이 필요하다. 지금 단계에서는 persistence-sensitive path visibility를 만들고, 그 한계를 문서화하는 것이 더 중요하다.

persistence-sensitive detection

config.example.toml에는 built-in persistence detection 설정이 있다.

[[detections.persistence]]
name = "systemd_service_modified"
paths = ["/etc/systemd/system/", "/usr/lib/systemd/system/", "/run/systemd/system/"]
patterns = ["*.service"]
operations = ["file_open", "file_write", "file_rename", "file_unlink"]

[[detections.persistence]]
name = "cron_modified"
paths = ["/etc/cron.d/", "/etc/cron.daily/", "/etc/crontab"]
operations = ["file_open", "file_write", "file_rename", "file_unlink"]

이 detection은 crates/user/src/detections.rscheck_persistence에서 평가된다. rule은 operation, path prefix, pattern을 비교한다. systemd service 파일이나 cron 관련 경로가 수정되면 source file event에 detection metadata가 붙을 수 있다.

{"event_type":"file_write","resolved_path":"/etc/systemd/system/demo.service","alert":true,"detection_type":"systemd_service_modified"}

여기서 중요한 점은 이 detection이 공격을 확정하지 않는다는 것이다. system administrator가 정상적으로 service file을 수정해도 같은 이벤트가 발생한다. 이 로그는 “조사할 가치가 있는 persistence-sensitive file activity”를 표시하는 signal이다.

source event annotation과 alert record

rand-guard에는 두 종류의 탐지 표현이 있다.

첫 번째는 source event annotation이다. persistence detection처럼 source event 자체에 alertdetection_type이 붙는다.

두 번째는 generic [[rules]] match로 생성되는 별도 alert record다.

{"event_type":"alert","rule_id":"FILE-001","rule_type":"file","source_event_type":"file_write","path":"/etc/shadow"}

downstream consumer 입장에서는 별도의 event_type = "alert" record가 더 안정적인 탐지 stream이 될 수 있다. 반면 source event annotation은 원본 telemetry를 볼 때 바로 detection context를 확인할 수 있다는 장점이 있다.

demo scenario

docs에는 안전한 local demo가 정리되어 있다. agent가 실행 중일 때 temporary service file을 만들고 바로 지운다.

sudo sh -c 'printf "# rand-guard demo\n" > /etc/rand-guard-demo.service'
sudo rm -f /etc/rand-guard-demo.service

systemd persistence detection을 보려면 다음처럼 systemd service directory 아래에 temporary file을 만들 수 있다.

sudo sh -c 'printf "# rand-guard persistence demo\n" > /etc/systemd/system/rand-guard-demo.service'
sudo rm -f /etc/systemd/system/rand-guard-demo.service

이 demo는 real persistence-sensitive directory에 파일을 만들었다가 지운다. production host나 민감한 시스템에서 실행하면 안 된다. lab 또는 development machine에서만 수행해야 한다.

false positive 관점

file persistence detection은 false positive가 자연스럽게 생긴다. package install, service deployment, administrator maintenance, configuration management tool이 모두 systemd나 cron path를 수정할 수 있다.

따라서 이 detection은 block이나 quarantine 같은 response action과 연결되어서는 안 된다. 현재 rand-guard도 자동 response를 구현하지 않는다. output은 investigation signal로 남긴다.

4편 정리

이번 글에서는 file telemetry를 살펴봤다.

  • open, write, rename, unlink 계열 syscall event를 수집한다.
  • watch/exclude 설정으로 관심 경로를 줄인다.
  • eBPF prefix filter와 userspace filtering을 함께 사용한다.
  • systemd service와 cron path를 persistence-sensitive detection으로 다룬다.
  • path reconstruction과 success 판단은 현재 완전하지 않다.

다음 글에서는 network telemetry를 다룬다. 왜 network collection을 기본 비활성화로 두었는지, 현재 connect, bind, listen에서 어디까지 정보를 얻는지 살펴본다.