ClamAV는 TCP socket을 통해 명령을 제어할 수 있다. File Socket을 통해 명령을 제어할 수 있으나 여기서는 다루지 않겠다.

  • 요청-응답 당 하나의 connection을 갖는다.
  • 명령앞에 ‘n’ prefix를 추가하는걸 권장 (특정 명령은 n이 무조건 필요하다.)
    • ex) PING 명령을 하는경우 nPING
      • echo "sPING" | nc localhost 3310

명령어

  • PING
    • 무조건 PONG이 반환되어야 한다.
  • VERSION
    • clamav의 버전 및 DB의 버전을 반환한다.
  • RELOAD
    • Signature Database 리로드
  • SHUTDOWN
    • ClamAV 데몬 종료
  • SCAN 파일/디렉토리
    • ClamAV가 설치된 서버의 파일이나 디렉토리(재귀적으로)를 검사한다.
    • 기본적으로 아카이브파일에 대한 검사를 허용함 (clamd.conf 비활성화 가능)
      • 압축 파일내 파일들을 검사하는 것
  • RAWSCAN 파일/디렉토리
  • CONTSCAN 파일/디렉토리
    • ClamAV가 설치된 서버의 파일이나 디렉토리(재귀적으로) 검사하며 바이러스가 감지되어도 검사를 중단하지 않음
    • 기본적으로 아카이브파일에 대한 검사를 허용함
  • MULTISCAN 파일/디렉토리
    • 파일의 경우 SCAN처럼 동작한다.
    • 디랙토리의 경우 멀티 스래드를 사용하여 검사한다.
  • ALLMATCHSCAN 파일/디렉토리
    • 감염된 파일에 대한 모든 시그니처를 반환한다.
    • infected.doc 라는 파일에 2개의 시그니처가 존재하면 아래와 같이 반환된다.
      • SCAN 의 경우
        • infected.doc: Trojan.Agent FOUND
      • ALLMATCHSCAN 의 경우

          infected.doc: Trojan.Agent FOUND
          infected.doc: Macro.Downloader FOUND
        
  • INSTREAM
    • 바이트스트림 기반 검사를 수행합니다.
  • FILDES
    • 검사시 파일 경로를 직접 호출하는게 아닌 파일 디스크립터(fd)를 통해 검사합니다.
    • 이미 열려있는 파일을 clam이 검사하려고 할 때 유용하게 사용됨
    • Unix 시스템에서만 동작
  • STATS
    • clamav의 상태를 리턴한다.
      • 스캔 큐(queue) 상태
      • 현재 큐에 쌓인 파일들 정보
      • clamd 프로세스의 메모리 사용량 등
  • IDSESSION, END
    • 일반적으로는 SCAN 이나 INSTREAM 같은 명령을 보낼 때마다 소켓을 열고 → 명령 보내고 → 응답 받고 → 닫음 패턴을 따릅니다.
    • 그런데 파일을 많이 검사해야 하면, 매번 소켓을 새로 여닫는 건 오버헤드가 커집니다.
    • 그래서 IDSESSION 을 쓰면 하나의 연결(socket)을 열어둔 채로 여러 명령을 순차적으로 보낼 수 있음.

코드 예제

1. SCAN을 통한 바이러스 파일 및 일반 파일 검사

코드

public static void main(String[] args) throws Exception {
    Socket virusTestSocket = new Socket( // clamav 소켓생성
            "<clamav-server-ip>", 3310 
    );

    var in = virusTestSocket.getInputStream();
    var out = virusTestSocket.getOutputStream();

    out.write("nSCAN /tmp/eicar.com.txt\n".getBytes()); // /tmp/eicar.com.txt 파일에 대한 검사 요청 명령
    out.flush(); // 명령 전달
    var virusResult = new String(in.readAllBytes()); // 응답

    Socket normalTestSocket = new Socket( // clamav 소켓생성
            "<clamav-server-ip>", 3310
    );

    in = normalTestSocket.getInputStream();
    out = normalTestSocket.getOutputStream();

    out.write("nSCAN /tmp/normal-txt\n".getBytes()); // /tmp/normal-txt 파파일에 대한 검사 요청 명령
    out.flush(); // 명령 전달
    var normalResult = new String(in.readAllBytes()); // 응답

    System.out.println("virus sample result : " + virusResult);
    System.out.println("normal sample result : " + normalResult);
}

출력

virus sample result : /tmp/eicar.com.txt: Win.Test.EICAR_HDB-1 FOUND

normal sample result : /tmp/normal-txt: OK

출력된 결과를 보면 normal sample result 이전 줄이 줄바꿈 되어있는데 이는 하나의 매시지가 끝나면 개행문자(\n) 을 통해 메세지의 끝을 구분하기 때문이다.

2. IDSESSION, END 을 통한 다중 SCAN

코드

public static void main(String[] args) throws Exception {
    Socket virusTestSocket = new Socket( // clamav 소켓생성
            "<clamav-server-ip>", 3310
    );

    var in = virusTestSocket.getInputStream();
    var out = virusTestSocket.getOutputStream();

    out.write("nIDSESSION\n".getBytes()); // 세션 시작
    // out.write("nPING\n".getBytes()); // PING 요청 (만약 오랬동안 세션을 열고 있으면 주기적인 PING 체크가 필요할 수 있다.
    out.write("nSCAN /tmp/eicar.com.txt\n".getBytes()); // /tmp/eicar.com.txt 파일에 대한 검사 요청 명령
    out.write("nSCAN /tmp/normal-txt\n".getBytes()); // //tmp/normal-txt 파일에 대한 검사 요청 명령
    out.write("nEND\n".getBytes()); // 세션 요청 끝
    out.flush(); // 명령 전달
    var result = new String(in.readAllBytes()); // 응답
    System.out.println(result);
}

출력

2: /tmp/normal-txt: OK
1: /tmp/eicar.com.txt: Win.Test.EICAR_HDB-1 FOUND

출력된 결과를 보면 명령을 보낸 순서와 다르게 출력되는걸 확인할 수 있다.

따라서 명령별 순차적인 ID를 부여하여 이를 응답에 매칭하는 처리가 필요하다.

CLI로 테스트 - nc

echo "nPING" | nc localhost 3310 # PING 요청
echo "nSCAN /tmp/eicar.com.txt" | nc localhost 3310 # scan 요청

Reference