Scapy 의 마지막으로 세번째 이야기를 소개해본다. 이번에는 Scapy 를 이용해 나만의 프로그램을
만드는 방법이다. 앞서 소개한 것과 같이 Scapy 는 파이썬기반이어서, 파이썬에서도 Scapy 모듈을 로드하여
내가 원하는 형태로 패킷을 쉽게 다룰 수가 있다.
모든 패킷 도구들이 내가 원하는 입맛에 맞게 맞춰져 있지는 않다. 이럴때 Scapy 는 간단하게 패킷을 쉽게
다룰 수 있는 방법을 제공해 준다. 이에 몇 가지 사용 예제를 소개해 본다. 예제를 보는 것 만으로도
쉽게 이해가 될 것이며, 이 예제를 기반으로 원하는 프로그램으로 확장하는 것은 그리 어렵지 않을 것이다.
여기 외에, 인터넷을 찾아보면 많은 예제파일들이 있으므로 여기서는 '아 ~ 이현형태로 이용가능하구나' 하는 것만을 인지해 두면 될것 같다.
[관련글]
- 2010/10/25 Rigel Scapy 의 다양한 기능을 익혀보자 - 두번째
- 2010/10/20 Rigel 강력한 패킷 조작 프로그램 Scapy 를 소개한다 - 첫번째
1. 간단한 ICMP 패킷 보내보기
첫 시작으로 간단하게 ICMP 패킷 보내는 것을 해 보자. 우선, Scapy 모듈을 사용하기 위해서는
모듈을 불러들여야 한다.
from scapy.all import sr1, IP, ICMP
위와 같이 하여 sr1, IP, ICMP 만을 로드하였다. 물론 import * 로 하여 전체 관련된 것을 모두 로드할 수 도 있다. import sys 는 실행할때 인자로 받아들일 값을 사용하기 위해 sys 모듈을 로드하였고,
packet 변수에 IP()/ICMP() 와 같이 하여 IP 와 ICMP 헤더를 넣어주었다. 이때 IP 헤더에는 인자로 받은 값을 넘겨주어 목적지 주소를 지정한 것이다. show() 는 각 구성 형태를 세부적으로 보여주며, sr1 은 패킷 한개를 발송하도록 정의한 것이다.
#!/usr/local/bin/python
# PacketInside.com - Scapy Example
import sys
from scapy.all import sr1,IP,ICMP
packet=IP(dst=sys.argv[1])/ICMP()
print "+-------- Sending Packet INFO"
packet.show()
result = sr1(packet)
if result:
print "+-------- Receiving Packet INFO"
result.show()
발송하기 전에 패킷 구성 형태를 packet.show() 를 통해 한번 보여주고, 패킷 발송후에 성공했으면
전달 받은 패킷 데이터를 또 보여주는 간단한 구조다. 다음과 같이 실행해 본다.
# ./icmp.py google.com
WARNING: No route found for IPv6 destination :: (no default route?)
+-------- Sending Packet INFO
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = icmp
chksum = None
src = 192.168.0.240
dst = Net('google.com')
\options \
###[ ICMP ]###
type = echo-request
code = 0
chksum = None
id = 0x0
seq = 0x0
Begin emission:
.Finished to send 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets
+-------- Receiving Packet INFO
###[ IP ]###
version = 4L
ihl = 5L
tos = 0x0
len = 46
id = 56007
flags =
frag = 0L
ttl = 49
proto = icmp
chksum = 0x6d5f
src = 74.125.53.147
dst = 192.168.0.240
\options \
###[ ICMP ]###
type = echo-reply
code = 0
chksum = 0x0
id = 0x0
seq = 0x0
###[ Raw ]###
load = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
google.com 으로 ICMP 패킷을 보내고 전달받은 결과를 보여준다. 와이어샤크로 해당 패킷을 덤프해 보면
아래와 같이 제대로 패킷이 전송된 것을 알 수 있다.
만약 위 packet 변수에 아래와 같은 값을 입력하면 어떨까?
packet=IP(dst=sys.argv[1])/TCP(sport=4321,dport=80)/Raw(load="GET /index.html HTTP/1.0 \n\n")
의미는 간단해 보인다. TCP 출발지 포트를 4321, 목적지 포트를 80 으로 하고 페이로드에는 /index.html 을 요청하는 것을 집어 넣은 것이다. 하지만 이렇게 바꾸고 실행하면 오류가 발생한다. Scapy 모듈을 로드시에 IP,ICMP 만 했기 때문이다. 모듈 로드를 아래와 같이 해 주어야 한다.
from scapy.all import sr1,IP,TCP,Raw
2. ARP 패킷 모니터링
네트워크 인터페이스에서 ARP 트래픽만을 모니터링 하는 코드를 보자. 앞서 소개한 Scapy 에서 패킷 덤프를 할때 sniff 를 사용 한다고 했다. sniff() 를 통해 패킷 덤프를 하되, filter 를 통해 arp 프로토콜로만 한정을 한다.
#!/usr/local/bin/python
from scapy.all import *
def arp_monitor_callback(pkt):
if ARP in pkt and pkt[ARP].op in (1,2):
return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%")
sniff(prn=arp_monitor_callback, filter="arp", store=0)
arp_monitor_callback 이라는 함수를 정의하고 넘어온 pkt 에서 ARP 가 있으면 하드웨어 맥 주소와 출발지 주소를 sprintf 로 리턴해 주어 출력하도록 한 것이다. ARP 이외도 ICMP, TCP 등 여러분들이 원하는 형태로 지정만 하면 출력이 가능해 지는 것이다. 위 코드의 실행결과는 아래와 같다:
# ./arp.py
WARNING: No route found for IPv6 destination :: (no default route?)
00:e0:4d:41:fc:XX 192.168.0.221
00:01:36:2e:0b:XX 192.168.0.200
00:01:36:2e:0b:XX 192.168.0.200
00:e0:4d:41:fc:XX 192.168.0.221
3. 패킷 추출정보 파일로 저장하기
지정한 파일을 읽어들이고 Payload 부분만 저장해 보자. 이때 출발지 주소가 192.168.0.240 인 IP 로만 한정을 한다. 각 세부 동작내용은 아래 주석을 참고하자
#!/usr/local/bin/python
from scapy.all import *
src="192.168.0.240" # 출발지 IP 정의
pcap_file = "test3.pcap" # 읽어들일 PCAP 파일 정의
pcap = rdpcap(pcap_file) # rdpcap() 을 통해 pcap_file 을 읽어들임
data = "" # data 변수 초기화
for packet in pcap: # 읽어들인 pcap 파일을 루프돌면서 처리
ilayer = packet.getlayer("IP") # getlayer 를 통해 IP 레이어를 ilayer 에 저장
if ilayer.src != src: # 출발지가 192.168.0.240 이 아닌 경우 Skip
continue
tlayer = packet.getlayer("TCP") # TCP 레이어를 tlayer 에 저장
if packet.getlayer("Raw"): # Raw 영역이 존재하면, payload 를 data 에 추가
data += str(tlayer.payload)
f = open("raw_data.dat", 'w') # raw_data.dat 를 오픈하고 기록한다.
f.write(data)
f.close()
실행을 해 보면 raw_data.dat 가 만들어지고, 이 파일에는 Payload 만 저장된 것을 확인할 수 있다.
# hexdump -C raw_data.dat | more
00000000 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a |GET / HTTP/1.1..|
00000010 48 6f 73 74 3a 20 67 6f 6f 67 6c 65 2e 63 6f 6d |Host: google.com|
00000020 0d 0a 55 73 65 72 2d 41 67 65 6e 74 3a 20 4d 6f |..User-Agent: Mo|
00000030 7a 69 6c 6c 61 2f 35 2e 30 20 28 58 31 31 3b 20 |zilla/5.0 (X11; |
00000040 55 3b 20 4c 69 6e 75 78 20 69 36 38 36 3b 20 65 |U; Linux i686; e|
00000050 6e 3b 20 72 76 3a 31 2e 39 2e 30 2e 31 39 29 20 |n; rv:1.9.0.19) |
00000060 47 65 63 6b 6f 2f 32 30 30 38 30 35 32 38 20 45 |Gecko/20080528 E|
00000070 70 69 70 68 61 6e 79 2f 32 2e 32 32 0d 0a 41 63 |piphany/2.22..Ac|
00000080 63 65 70 74 3a 20 74 65 78 74 2f 68 74 6d 6c 2c |cept: text/html,|
00000090 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 68 74 6d |application/xhtm|
000000a0 6c 2b 78 6d 6c 2c 61 70 70 6c 69 63 61 74 69 6f |l+xml,applicatio|
000000b0 6e 2f 78 6d 6c 3b 71 3d 30 2e 39 2c 2a 2f 2a 3b |n/xml;q=0.9,*/*;|
000000c0 71 3d 30 2e 38 0d 0a 41 63 63 65 70 74 2d 4c 61 |q=0.8..Accept-La|
...(생략)
즉, 필요한 부분만 얻어내서 파일로 만들 수도 있다. 패킷파일의 모든 부분을 출력시키거나 쉽게 저장할 수도 있으며, getlayer() 를 통해 원하는 레이어 영역으로 쉽게 접근할 수 있다.
4. HTTP 패킷을 처리하는 프로그램 형태를 만들어 보자.
조금 더 프로그램 형태를 갖춰서 HTTP 정보를 얻어내는 코드를 살펴보도록 하겠다. 여기 예에서는 기본적인 부분에 대해서 설명하고 있는 것이므로, 응용만 하면 원하는 형태로 쉽게 핸들링이 가능해진다. 이런식으로 사용할 수 있다는 것을 보여주기 위해 작성된 것이므로, 여러분들이 다양하게 응용해 보기 바란다.
#!/usr/local/bin/python
# PacketInside.com - Examples
import sys
from scapy.all import *
CNT = 10 # CNT 에 10 을 지정
def run(target):
try:
pkt = rdpcap(target, count=CNT) # 패킷파일을 오픈할때 10 개 까지만 읽어들인다.
except MemoryError: # 패킷파일을 오픈하다 메모리 에러가 발생하는 경우 Exception 처리
print "Sorry - Memory Error"
sys.exit() # 메모리 에러를 출력하고 프로그램 종료
numPkt = len(pkt) # pkt 의 길이를 numPkt 에 저장
print "Analyzing : " + target
print "Total Packets: %d\n" % numPkt # 전체 패킷의 Count 를 출력, rdpcap() 에서 count 를 사용하지 않을시는 전체 패킷 건수를 표시하나 카운트를 10으로 제한했으므로 항상 10이 됨
for packet in pkt:
layer = packet.payload
while layer: # 각 레이어 별로 루프 돌면서 처리
layerName = layer.name # 레이어 이름을 layerName 에 저장
if layerName == "IP": # 만약 레이어 이름이 IP 인 경우 다음 내용 수행
print "[IP Layer]\nSRC: %s, DST: %s" % (layer.src, layer.dst)
# 출발지 와 목적지 주소를 출력 함
if layerName == "TCP": # 레이어가 TCP 인 경우
print "[TCP Layer]\nSPORT: %s, DPORT: %s" % (layer.sport, layer.dport)
if layerName == "Raw": # 레이어가 Raw 인 경우 ( Payload 가 됨)
result = processHTTP(layer.load) # Raw 가 있는 경우 processHTTP 를 호출함
if result != "Error": # 넘어온 결과가 Error 인 경우 에러 결과를 출력
print result[0] + " " + result[1]
layer = layer.payload
def processHTTP(data):
str_method = ""
str_uri = ""
# 정규표현식을 통해 넘어온 데이터에서 METHOD, URI, HTTP 버전 정보등으로 구분함
h = re.search("(?P<method>(^GET|^POST|^PUT|^DELETE)) (?P<uri>.+) (?P<version>.+)", data)
if not h: return "Error" # 정규표현식에 해당하는 데이터가 없는 경우 Error 를 리턴해줌
# method 로 정의된 부준은 str_method 에 저장
if h.group("method"): str_method = h.group("method")
# URI 데이터는 str_uri 에 저장
if h.group("uri"): str_uri = h.group("uri")
return str_method,str_uri # method 와 uri 를 리턴해 줌
# call run
run("tt.pcap") # run 함수를 호출, 이때 읽어들일 pcap 파일을 같이 전달
실행된 결과는 아래 화면과 같다 :
패킷을 분석할 데이터가 많은 경우에는 클래스와 함수를 적절히 사용하여 프로그램을 작성하면, 더욱 효과적으로 사용할 수 있을 것이다.
여기서는 소개하는데 한계가 있기 때문에, 이 정도 선에서 마무리 하고자 한다. 기본적인 것들은 다 언급하였기 때문에 파이썬에 익숙한 분들이라면 어렵지 않게 패킷분석 프로그램을 만들 수가 있다. 물론, 분석 대상의 범위에 따라서 달라지겠지만 Scapy 모듈만을 이용해서 뚝딱 원하는 형태로 패킷을 다룰 수 있으므로, 유용한 도구임에는 틀림없다.
세번째 소개 글은 프로그램 예제를 만들고 검증하다 보니, 여러분들한테 소개하기까지 늦어졌다. 요 근래, 필자를 괴롭히는 귀차니즘이 한 몫하기도 했다. :-)
Scapy 를 이용하려는 분들에게 조금이라도 도움이 되길 바라며 Scapy 소개를 끝마친다.
From Rigel.
안녕하세요
답글삭제wireshark에서 패킷을 스니핑하고 pcap파일로 만들었습니다.
이것을 open하려고 하는데 scapy에서 rdpcap()이나 open으로 열려고 해도 열리지 않습니다.
여는 방법에 대해서 안내해주실 수 있나요?
어떤 에러메시지가 발생하나요? 만들어진 pcap 파일이 제대로 된 패킷파일인지 다시 한번 오픈 또는 검증해 보시고요. 이외 다른 환경적 요인들은 없는지도 보세요. 퍼미션 또는 파일 포맷형태를요. 기본 저장될때 pcap-ng 로 저장되었다면 한번 pcap 으로 다시 저장해서 로드해 보시기 바랍니다.
삭제pcap 파일은 어디서 얻을수있나요?
답글삭제