개요

파이썬에서 네트워크 패킷을 다룰 때 주로 사용하는 라이브러리인 Scapy의 사용법을 메모해둔다.

Scapy 개요

Scapy는 강력한 네트워크 패킷 분석 및 조작 툴이다. Scapy로 wireshark에서 하는 네트워크 패킷 분석작업을 할 수도 있고, Nmap이나 기타 네트워크 스캔 툴, 또는 공격툴과 같이 네트워크 패킷을 생성해서 원격 서버에 보내는 일도 할 수 있다.

Scapy는 ‘스케이피’로 발음한다.

슬래시 문법

Scapy에는 아주 특이한 문법이 있다. 처음봤을 때는 이게 뭔가 싶었다. IP레이어를 TCP레이어로 나누는 것과 같은 문법이다. 예를들어 다음과 같다.

from scapy.all import *

packet = IP(dst="1.2.3.4")/TCP(dport=80)

Scapy에서 “IP를 TCP로 나눈다”는 표현은 일반적으로 패킷을 레이어별로 분해하거나 구성한다는 의미로 해석된다.

위의 코드에서:

  • IP(…)는 IP 계층을 생성한다.
  • /TCP(…)는 TCP 계층을 IP 계층 위에 덧붙인다. 이때 / 연산자는 Scapy에서 상위 계층과 하위 계층을 연결하는 데 사용된다. 즉, 코드 IP()/TCP()는 IP 패킷 안에 TCP 세그먼트를 포함시키는 구조를 만든다.

네트워크 패킷 분석하기

pcap 파일 읽어들이기

tcpdump와 같은 툴로 패킷을 캡쳐한 pcap파일이 있다면 다음과 같이 패킷을 읽어들일 수 있다.

from scapy.all import rdpcap

pkts = rdpcap('xxx.pcap')
for pkt in pkts:
    # pkt 가 각각의 패킷이다. pkt을 분석하는 작업을 수행한다.

패킷의 프로토콜/포트 확인

from scapy.layers.dns import DNSRR
from scapy.layers.inet import UDP

# 패킷이 DNS응답 (DNSRR)  레이어를 가지고 있는지 체크 
if pkt.haslayer(DNSRR):
    # ...

# 패킷이 UDP 레이어의 포트번호가 53번인지 체크
if pkt.getlayer(UDP).sport == 53:

네트워크 패킷 보내기

패킷 보내기는 쉽다.

from scapy.all import send, sr1
from scapy.layers.inet import IP, TCP


ip_layer = IP(src='1.2.3.4', dst='2.3.4.5')
tcp_layer = TCP(sport=1024, dport=513)
pkt = ip_layer / tcp_layer
send(pkt)
    

네트워크 패킷을 보내고 결과 분석하기

네트워크 패킷을 보내고 결과를 분석하기 위해서는 sr1 함수를 사용한다. sr1()은 Scapy에서 패킷을 전송하고 응답을 하나만 기다리는 함수다. 주로 네트워크 스캐닝이나 테스트에 사용된다.

주요 파라메터는 다음과 같다.

파라메터 설명
packet 전송할 패킷
timeout 응답을 기다릴 최대 시간 (초)
verbose 출력 메시지 표시 여부 (0이면 출력 없음)
iface 사용할 네트워크 인터페이스 지정

샘플 코드는 다음과 같다.

# Ping 테스트 샘플코드
from scapy.all import sr1, sniff, conf
from scapy.layers.inet import IP, ICMP

response = sr1(IP(dst="1.1.1.1")/ICMP(), timeout=2, verbose=0)
if response:
    print("응답 도착:", response.summary())
else:
    print("응답 없음")

# TCP SYN 스캔 샘플코드
packet = IP(dst="scanme.nmap.org")/TCP(dport=80, flags="S")
response = sr1(packet, timeout=2, verbose=0)

if response and response.haslayer(TCP):
    if response[TCP].flags == "SA":
        print("포트 열림")
    elif response[TCP].flags == "RA":
        print("포트 닫힘")
else:
    print("응답 없음")

참고

  • Violent Python 책