포스트

eBPF로 BPFDoor 탐지하고 차단하기

eBPF로 BPFDoor 탐지하고 차단하기

들어가며

BPFDoor는 BPF(Berkeley Packet Filter)를 악용하는 리눅스 백도어입니다.

우리나라에서는 2025년 4월 발생한 대형 통신사 유심 정보 유출 사고에 사용된 해킹 도구로 알려져 있습니다.

항목내용
피해 규모2,696만 건 유심 정보 유출
감염 서버28대 (악성코드 33종 발견)
잠복 기간2022년 6월 ~ 2025년 4월 (약 3년)


이 글에서는 네트워크 레벨에 집중하여 BPFDoor의 매직 패킷 동작을 분석하고, eBPF/XDP로 탐지 및 차단하는 방법을 다룹니다.

(BPFDoor는 BPF 필터 외에도 위장 프로세스, 자가 복제 후 삭제 등 다양한 탐지 우회 기법을 사용합니다.)

💡 Note
이 글에서 다루는 BPFDoor-Defender 전체 소스 코드는 GitHub - kimmap/ebpf-bpfdoor-defender에서 확인할 수 있습니다.


1. BPFDoor 동작 원리

1.1 전체 아키텍처

BPFDoor의 동작은 크게 두 단계로 나뉩니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1단계: 잠복
BPFDoor 프로세스
    ├── RAW 소켓 생성 (PF_PACKET, SOCK_RAW)
    ├── BPF 필터 부착 (SO_ATTACH_FILTER)
    └── 매직 패킷 대기 (무한 루프)

            │
            │ 매직 패킷 수신
            ▼

2단계: 활성화
    │
    ├── 공격자 <─── 리버스 쉘 연결 ─── Target
    │
    └── 또는: 공격자 ──> iptables 조작 ──> 바인드 쉘

리버스 쉘 원리 (dup2):

1
2
3
4
5
// 소켓 fd를 표준 입출력으로 복제
dup2(sockfd, STDIN_FILENO);   // 소켓 → 표준 입력
dup2(sockfd, STDOUT_FILENO);  // 소켓 → 표준 출력
dup2(sockfd, STDERR_FILENO);  // 소켓 → 표준 에러
execl("/bin/sh", "sh", NULL); // 쉘 실행 → 모든 입출력이 공격자에게 전달

1.2 왜 탐지가 어려운가?

일반적인 백도어와 BPFDoor의 차이점:

항목일반 백도어BPFDoor
리스닝 포트열림 (예: 4444)없음
netstat 탐지가능불가능
방화벽 우회어려움쉬움
트래픽 특성지속적 연결단발성 트리거

BPFDoor는 RAW 소켓을 사용하여 모든 IP 패킷의 복사본을 수신합니다. 커널 수준에서 BPF 필터가 매직 패킷만 골라내므로, 유저스페이스에서는 관련 포트가 전혀 보이지 않습니다.

2. 매직 패킷 구조 분석

2.1 패킷 캡처

tcpdump로 실제 BPFDoor 매직 패킷을 캡처해 보았습니다:

1
2
3
4
5
07:24:10.913005 IP 192.168.57.101.47857 > 192.168.57.100.29269: UDP, length 24
    0x0000:  4500 0034 719a 4000 4011 d504 c0a8 3965  E..4q.@.@.....9e
    0x0010:  c0a8 3964 baf1 7255 0020 1ca0 7255 0000  ..9d..rU....rU..
    0x0020:  ffff ffff 2329 6a75 7374 666f 7266 756e  ....#)justforfun
    0x0030:  0000 0000                                ....

2.2 패킷 구조 분석

IP 헤더 (20 bytes)

1
2
3
4
5
6
4500 0034 719a 4000 4011 d504 c0a8 3965 c0a8 3964
││││ ││││                     │││││││││ │││││││││
││││ ││││                     │││││││││ └───────┴─ Dst: 192.168.57.100
││││ ││││                     └───────┴─────────── Src: 192.168.57.101
││││ └┴┴┴──────────────────────────────────────── Total Length: 52
└┴┴┴───────────────────────────────────────────── IPv4, IHL=5

UDP 헤더 (8 bytes)

1
2
3
4
5
baf1 7255 0020 1ca0
││││ ││││ ││││
││││ ││││ └┴┴┴─── Length: 32 bytes
││││ └┴┴┴──────── Dst Port: 29269 (0x7255) ★ 트리거 포트
└┴┴┴───────────── Src Port: 47857 (임의)

매직 패킷 페이로드 (24 bytes)

BPFDoor가 정의한 magic_packet 구조체:

1
2
3
4
5
6
struct magic_packet {
    unsigned int    flag;     // 4 bytes - 매직 플래그
    in_addr_t       ip;       // 4 bytes - 콜백 IP
    unsigned short  port;     // 2 bytes - 콜백 포트
    char            pass[14]; // 14 bytes - 패스워드
} __attribute__ ((packed));

실제 데이터 매핑:

1
2
3
4
5
6
7255 0000 ffff ffff 2329 6a75 7374 666f 7266 756e 0000 0000
│││││││││ │││││││││ ││││ │││││││││││││││││││││││││││││││││
│││││││││ │││││││││ ││││ └───────────────────────────────┴─ pass: "justforfun"
│││││││││ │││││││││ └┴┴┴───────────────────────────────────  port: 9001
│││││││││ └───────┴────────────────────────────────────────  ip: 0xFFFFFFFF (src 사용)
└───────┴──────────────────────────────────────────────────  flag: 0x72550000 ★

2.3 핵심 탐지 시그니처

필드설명
UDP Dst Port29269 (0x7255)트리거 포트
Payload[0:4]0x72550000매직 플래그
Payload[4:8]0xFFFFFFFF콜백 IP (패킷 src 사용)
Payload[8:10]가변콜백 포트
Payload[10:24]가변인증 패스워드

3. BPF 필터 코드 분석

BPFDoor는 classic BPF(cBPF) 필터를 사용하여 매직 패킷만 선별합니다.

3.1 필터 코드 원본

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct sock_filter bpf_code[] = {
    { 0x28, 0, 0, 0x0000000c },   // [0]  A = EtherType
    { 0x15, 0, 27, 0x00000800 },  // [1]  if (A != IP) goto REJECT
    { 0x30, 0, 0, 0x00000017 },   // [2]  A = IP.Protocol
    { 0x15, 0, 5, 0x00000011 },   // [3]  if (A != UDP) goto ICMP_CHECK
    { 0x28, 0, 0, 0x00000014 },   // [4]  A = IP.FragOffset
    { 0x45, 23, 0, 0x00001fff },  // [5]  if (fragmented) goto REJECT
    { 0xb1, 0, 0, 0x0000000e },   // [6]  X = IP.HeaderLen
    { 0x48, 0, 0, 0x00000016 },   // [7]  A = Payload[0:2]
    { 0x15, 19, 20, 0x00007255 }, // [8]  if (A == 0x7255) ACCEPT else REJECT
    // ... ICMP, TCP 체크 생략 ...
    { 0x6, 0, 0, 0x0000ffff },    // [28] return ACCEPT
    { 0x6, 0, 0, 0x00000000 },    // [29] return REJECT
};

3.2 핵심 로직 해석

1
2
3
4
5
6
7
8
9
10
11
12
13
14
EtherType 체크
    │
    ├── != 0x0800 (IP) ──> REJECT
    │
    └── == 0x0800 (IP)
            │
            ▼
        Protocol 체크
            │
            ├── UDP(17)  ──> Fragment? ──> Payload[0:2] == 0x7255? ──> ACCEPT
            │
            ├── ICMP(1)  ──> Fragment? ──> Payload[0:2] == 0x7255? ──> ACCEPT
            │
            └── TCP(6)   ──> Fragment? ──> Payload[0:2] == 0x5293? ──> ACCEPT

3.3 프로토콜별 매직 값

프로토콜매직 값위치
UDP0x7255페이로드 첫 2바이트
ICMP0x7255데이터 첫 2바이트
TCP0x5293페이로드 첫 2바이트

4. 실제 공격/방어 테스트

4.1 테스트 환경

1
2
3
4
5
┌─────────────────┐                                    ┌───────────────────┐
│    Attacker     │  ────── UDP Magic Packet ──────>   │     Target        │
│ 192.168.57.101  │  <───── Reverse Shell ─────────    │ 192.168.57.100    │
│                 │                                    │ (bpfdoor_defender)│
└─────────────────┘                                    └───────────────────┘
  • VM 2대: VirtualBox + Vagrant
  • OS: Rocky Linux 9.4
  • Kernel: 5.14.0-427.13.1.el9_4.x86_64

4.2 Case 1: 방어 시스템 없음 (공격 성공)

Defender가 없는 상태에서 공격을 수행하면 리버스 쉘이 성공적으로 연결됩니다.

[Target] BPFDoor 실행:

1
2
3
$ git clone https://github.com/gwillgues/BPFDoor.git
$ gcc -o bpfdoor bpfdoor.c
$ ./bpfdoor --init

[Attacker] 공격 실행:

💡 Note
공격 프로그램(bpfdoor_attacker)은 테스트 목적으로 별도 작성하였으며, 악용 위험이 있어 공개하지 않습니다.


1
2
3
4
5
6
7
8
9
10
$ ./bpfdoor_attacker --target-ip 192.168.57.100
[INFO] Executing Reverse Shell Attack against 192.168.57.100
[INFO] Listening for reverse shell on local port: 9001
[INFO] UDP Magic packet sent (src_port:0 -> 192.168.57.100:29269) with password 'justforfun'
[INFO] Reverse shell connection accepted from 192.168.57.100:50418
[INFO] Read and discarded initial handshake: "3458"
[INFO] Starting interactive shell I/O (RC4 encrypted)...

[root@target /]$ whoami
root

[Target] tcpdump로 매직 패킷 확인:

1
2
3
4
5
6
$ tcpdump -i eth1 -nn 'udp and port 29269' -X
08:02:46.822257 IP 192.168.57.101.41864 > 192.168.57.100.29269: UDP, length 24
    0x0000:  4500 0034 5b34 4000 4011 eb6a c0a8 3965  E..4[4@.@..j..9e
    0x0010:  c0a8 3964 a388 7255 0020 3409 7255 0000  ..9d..rU..4.rU..
    0x0020:  ffff ffff 2329 6a75 7374 666f 7266 756e  ....#)justforfun
    0x0030:  0000 0000                                ....

결과: 매직 패킷 하나로 root 권한의 암호화된 리버스 쉘 획득


4.3 Case 2: Defender 활성화 (공격 차단)

💡 Note
BPFDoor-Defender 전체 소스 코드는 GitHub - kimmap/ebpf-bpfdoor-defender에서 확인할 수 있습니다.


Step 1: eBPF 프로그램 컴파일

1
2
3
4
$ clang -O2 -g -target bpf -c bpfdoor_defender.bpf.c -o bpfdoor_defender.bpf.o

$ file bpfdoor_defender.bpf.o
bpfdoor_defender.bpf.o: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), with debug_info, not stripped

Step 2: eBPF 바이트코드 확인 (선택)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ llvm-objdump -S bpfdoor_defender.bpf.o

Disassembly of section xdp:

0000000000000000 <bpfdoor_defender>:
;     if (bpf_ntohs(eth->h_proto) == ETH_P_IP);
       7:       56 02 29 00 08 00 00 00 if w2 != 0x8 goto +0x29
;     return (iph->protocol == IPPROTO_UDP);
      18:       56 02 1e 00 11 00 00 00 if w2 != 0x11 goto +0x1e
;     return (bpf_ntohs(*magic) == BPFDOOR_MAGIC);
      43:       56 01 05 00 72 55 00 00 if w1 != 0x5572 goto +0x5  # 0x7255 매직!
;     bpf_printk("BPFDOOR: Magic packet detected! DROP");
      47:       85 00 00 00 06 00 00 00 call 0x6
      48:       b4 00 00 00 01 00 00 00 w0 = 0x1  # XDP_DROP

Step 3: XDP 프로그램 Attach

1
2
3
4
5
6
7
8
9
10
11
12
13
# attach 전 - bpfdoor_defender 없음
$ bpftool prog list | grep xdp
(없음)

# XDP attach
$ ip link set dev eth1 xdp obj bpfdoor_defender.bpf.o sec xdp

# attach 후 - id 49로 로드됨
$ bpftool prog list | grep -A3 "xdp.*bpfdoor"
49: xdp  name bpfdoor_defender  tag 6c59633e1a31ba4a  gpl
        loaded_at 2026-02-03T08:10:02+0900  uid 0
        xlated 400B  jited 263B  memlock 4096B  map_ids 13
        btf_id 77

Step 4: 프로그램 상세 정보

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ bpftool prog show id 49 --pretty
{
    "id": 49,
    "type": "xdp",
    "name": "bpfdoor_defender",
    "tag": "6c59633e1a31ba4a",
    "gpl_compatible": true,
    "jited": true,
    "bytes_xlated": 400,
    "bytes_jited": 263
}

$ bpftool net list
xdp:
eth1(3) generic id 49

Step 5: 공격 시도 및 탐지

[Attacker]

1
2
3
4
$ ./bpfdoor_attacker --target-ip 192.168.57.100
[INFO] UDP Magic packet sent (src_port:0 -> 192.168.57.100:29269)
[INFO] Waiting for reverse shell connection or timeout...
# 연결 타임아웃 - 패킷이 DROP됨

[Target] 탐지 로그:

1
2
$ cat /sys/kernel/debug/tracing/trace_pipe
<idle>-0  [000] ..s2.  1885.217886: bpf_trace_printk: BPFDOOR: Magic packet detected! DROP

[Target] tcpdump - XDP에서 DROP되어 패킷 안 보임:

1
2
3
$ tcpdump -i eth1 -nn 'udp and port 29269'
listening on eth1, link-type EN10MB (Ethernet), snapshot length 262144 bytes
# (패킷 없음 - XDP에서 커널 스택 도달 전 DROP)

4.4 결과 비교

항목Defender OFFDefender ON
Magic Packet 도달✅ 커널 통과❌ XDP에서 DROP
리버스 쉘 연결✅ 성공❌ 타임아웃
tcpdump 탐지✅ 패킷 보임❌ 패킷 안 보임
공격 소요 시간~5msN/A (차단)

XDP의 강점: 커널 네트워크 스택에 도달하기 전에 DROP하므로, tcpdump로도 패킷을 볼 수 없습니다.

5. 결론 및 추가 탐지 방안

5.1 구현 결과

eBPF를 활용하여 BPFDoor의 매직 패킷을 커널 수준에서 탐지하고 차단하는 시스템을 구현했습니다.

항목결과
UDP 매직 패킷 탐지
실시간 패킷 DROP
이벤트 로깅
통계 수집

5.2 마무리

BPFDoor는 BPF 기술을 교묘하게 악용한 백도어입니다.

하지만 같은 기술의 발전형인 eBPF를 사용하면 커널 수준에서 효과적으로 탐지하고 차단할 수 있습니다.


참고 자료

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.