개요
- clamav를 개발 환경에 맞춰 빌드하고 가져오는 것이 필요했다
- 환경은 기본적으로 openwrt buildroot를 이용한 크로스 컴파일이 지원되었다
- 그러나 clamav는 공유 라이브러리(so) 형식으로 종속 모듈들과 관리되어야 한다
- clamav는 1.x 이상 버전을 사용해야 한다
환경구성
- glibc 2.31
- openssl 3.0.13
방안
- docker를 사용해 cross compile을 시도했다
- glibc 2.31을 기본으로 사용하는 ubuntu 20.04 환경에서 clamav를 빌드했다
- 현재 clamav lts (1.0.6) 이 openssl 3.x를 지원하지 않아 최근 릴리즈 버전인 1.3.1을 사용했다
플로우
- ubuntu 20.04 docker 환경에서 clamav와 그 종속 모듈들을 빌드한다
- 빌드된 clamav와 종속 모듈들의 공유 라이브러리, clamav의 종속 헤더 파일을 각각 docker에서 압축하여 가져온다
사용 도커파일
FROM ubuntu:20.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install -y build-essential wget libssl-dev libmspack-dev libbz2-dev zlib1g-dev \
libcurl4-openssl-dev libncurses5-dev cmake curl git check python3 \
python3-pip libxml2-dev libpcre2-dev libjson-c-dev libmilter-dev \
libclamav-dev ca-certificates tzdata
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
ENV TZ=Asia/Seoul
RUN ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && dpkg-reconfigure --frontend noninteractive tzdata
RUN wget https://www.openssl.org/source/openssl-3.0.13.tar.gz && \
tar -xzf openssl-3.0.13.tar.gz && \
cd openssl-3.0.13 && \
./config --prefix=/usr/local --openssldir=/usr/local/openssl && \
make -j$(nproc) && \
make install && \
cd .. && rm -rf openssl-3.0.13*
ENV LD_LIBRARY_PATH=/usr/local/lib64:/usr/local/lib:/usr/local/libcurl/lib:$LD_LIBRARY_PATH
ENV PKG_CONFIG_PATH=/usr/local/libcurl/lib/pkgconfig:$PKG_CONFIG_PATH
RUN wget https://curl.se/download/curl-8.0.1.tar.gz && \
tar -xzf curl-8.0.1.tar.gz && \
cd curl-8.0.1 && \
LDFLAGS="-L/usr/local/lib64" CPPFLAGS="-I/usr/local/include" ./configure --with-ssl=/usr/local --prefix=/usr/local/libcurl --enable-shared --disable-static && \
make -j$(nproc) && \
make install && \
cd .. && rm -rf curl-8.0.1*
RUN wget https://www.clamav.net/downloads/production/clamav-1.3.1.tar.gz && \
tar -xzf clamav-1.3.1.tar.gz && \
cd clamav-1.3.1 && \
mkdir build && cd build && \
cmake .. -DOPENSSL_ROOT_DIR=/usr/local -DOPENSSL_INCLUDE_DIR=/usr/local/include -DOPENSSL_CRYPTO_LIBRARY=/usr/local/lib64/libcrypto.so -DOPENSSL_SSL_LIBRARY=/usr/local/lib64/libssl.so -DCURL_LIBRARY=/usr/local/libcurl/lib/libcurl.so -DCURL_INCLUDE_DIR=/usr/local/libcurl/include && \
make -j$(nproc) && \
make install && \
cd ../.. && rm -rf clamav-1.3.1*
RUN mkdir -p /tmp/clamav-libs/lib /tmp/clamav-libs/include /tmp/clamav-libs/include/openssl && \
cp /usr/local/lib/libclamav.so* /tmp/clamav-libs/lib/ && \
cp /usr/local/lib/libclammspack.so* /tmp/clamav-libs/lib/ && \
cp /usr/local/include/clamav.h /tmp/clamav-libs/include/ && \
cp /usr/local/include/clamav-types.h /tmp/clamav-libs/include/ && \
cp /usr/local/include/clamav-version.h /tmp/clamav-libs/include/ && \
cp /usr/local/include/openssl/* /tmp/clamav-libs/include/openssl/ && \
cp /usr/lib/x86_64-linux-gnu/libpcre2-8.so* /tmp/clamav-libs/lib/ && \
cp /usr/local/lib64/libcrypto.so* /tmp/clamav-libs/lib/ && \
cp /usr/lib/x86_64-linux-gnu/libbz2.so* /tmp/clamav-libs/lib/ && \
cp /usr/local/lib64/libssl.so* /tmp/clamav-libs/lib/ && \
cp /usr/lib/x86_64-linux-gnu/libz.so* /tmp/clamav-libs/lib/ && \
cp /usr/lib/x86_64-linux-gnu/libjson-c.so* /tmp/clamav-libs/lib/ && \
cp /usr/lib/x86_64-linux-gnu/libxml2.so* /tmp/clamav-libs/lib/ && \
cp /usr/lib/x86_64-linux-gnu/libicuuc.so* /tmp/clamav-libs/lib/ && \
cp /usr/lib/x86_64-linux-gnu/libicudata.so* /tmp/clamav-libs/lib/ && \
tar -czvf /tmp/clamav-libs.tar.gz -C /tmp clamav-libs
CMD ["clamscan", "--version"]
테스트 코드(c)
#include <stdio.h>
#include <stdlib.h>
#include <clamav.h>
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <file_to_scan>\n", argv[0]);
return 1;
}
const char *filename = argv[1];
const char *dbdir = "./clamav-db";
int ret;
unsigned long int scanned = 0;
const char *virname = NULL;
if ((ret = cl_init(CL_INIT_DEFAULT)) != CL_SUCCESS) {
fprintf(stderr, "cl_init() error: %s\n", cl_strerror(ret));
return 1;
}
struct cl_engine *engine = cl_engine_new();
if (!engine) {
fprintf(stderr, "cl_engine_new() error: %s\n", cl_strerror(CL_EMEM));
return 1;
}
unsigned int sigs = 0;
if ((ret = cl_load(dbdir, engine, &sigs, CL_DB_STDOPT)) != CL_SUCCESS) {
fprintf(stderr, "cl_load() error: %s\n", cl_strerror(ret));
cl_engine_free(engine);
return 1;
}
printf("Loaded %u virus signatures from %s.\n", sigs, dbdir);
if ((ret = cl_engine_compile(engine)) != CL_SUCCESS) {
fprintf(stderr, "cl_engine_compile() error: %s\n", cl_strerror(ret));
cl_engine_free(engine);
return 1;
}
struct cl_scan_options scan_options = {
.general = CL_SCAN_GENERAL_ALLMATCHES | CL_SCAN_GENERAL_HEURISTICS | CL_SCAN_GENERAL_UNPRIVILEGED,
.parse = CL_SCAN_PARSE_ARCHIVE | CL_SCAN_PARSE_ELF | CL_SCAN_PARSE_PDF | CL_SCAN_PARSE_MAIL,
.heuristic = CL_SCAN_HEURISTIC_BROKEN | CL_SCAN_HEURISTIC_MACROS,
.mail = CL_SCAN_MAIL_PARTIAL_MESSAGE,
.dev = 0
};
ret = cl_scanfile(filename, &virname, &scanned, engine, &scan_options);
if (ret == CL_VIRUS) {
printf("Virus found: %s\n", virname);
} else if (ret == CL_CLEAN) {
printf("No virus found in %s\n", filename);
} else {
fprintf(stderr, "cl_scanfile() error: %s\n", cl_strerror(ret));
}
cl_engine_free(engine);
return 0;
}
빌드
gcc -o clamscan_example clamscan_example.c -I./include -L./lib -lclamav -Wl,-rpath=./lib
테스트
- eicar에서 테스트 악성코드를 가져와서 테스트했다
wget -O eicar_com.zip "https://www.eicar.org/download/eicar_com-zip/?wpdmdl=8847&refresh=66c1ae90c82bb1723969168"
LD_LIBRARY_PATH=/tmp/workplace/clamav-libs/lib ./clamscan_example eicar_com.zip
- 결과
root@4dd1c9322fad:/tmp/workplace/clamav-libs
Loaded 8697521 virus signatures from ./clamav-db.
Virus found: Win.Test.EICAR_HDB-1
참조: 바이러스 데이터베이스