from socket import *
import os
import struct
def parsing(host):
# raw socket 생성 및 bind
if os.name == "nt":
sock_protocol = IPPROTO_IP
else:
sock_protocol = IPPROTO_ICMP
sock = socket(AF_INET, SOCK_RAW, sock_protocol)
sock.bind((host, 0))
# socket 옵션
sock.setsockopt(IPPROTO_IP, IP_HDRINCL, 1)
# promiscuous mode 켜기
if os.name == "nt":
sock.ioctl(SIO_RCVALL, RCVALL_ON)
packet_number = 0
try:
while True:
packet_number += 1
data = sock.recvfrom(65535)
ip_headers, ip_payloads = parse_ip_header(data[0])
print(f"{packet_number} th packet\n")
#parse_ip_header 함수에 패킷을 byte형태로 넣어 주면 IP 헤더 20bytes의 ip_headers와 나머지 데이터인 ip_payloads를 튜플 형태로 반환 받는다.
#ip_headers[0]은 1byte(8bits)이며 bit연산자(>>)를 이용해 왼쪽 4bits에 해당하는 값이 이 IP Version이 된다.
#IP헤더의 길이는 ip_headers[0]에서 오른쪽 4bits에 해당하는 값을 구하고 워드 단위인 4를 곱한다.
#즉, 오른쪽 shift연산을 한 이후의 값이 IP Version이고, 이 값의 워드 단위인 4를 곱하면 IP헤더의 길이이다.
print(f"version: ", ip_headers[0] >> 4)
#5를 출력하는데 단위가 워드(4bytes)이므로 20bytes가 된다.
#IP Flags와 Fragment Offset은 bit형태로 출력했으며 예제에서 출력된 '010'은 Do not Fragment만 설정된다.
#Protocl은 상위 계층에 담는 ICMP(1), TCP(6), UDP(17)을 출력한다.
#inet_ntoa()함수는 byte형을 우리가 읽을 수 있는 IP 주소 체계로 보여준다.
print("Header Length: ", ip_headers[0] & 0x0F)
print("Type of Service: ", ip_headers[1])
print("Total Length: ", ip_headers[2])
print("IdentificiationL ", ip_headers[3])
print("IP Flags, Fragment OffsetL ", flags_and_offset(ip_headers[4]))
print("Time To Live: ", ip_headers[5])
print("Protocol: ", ip_headers[6])
print("Header Checksum: ", ip_headers[7])
print("Source AddressL ", ip_headers[8])
print("Destination Address: ", inet_ntoa(ip_headers[9]))
print("=" * 50)
except KeyboardInterrupt: # Ctrl-C key input
if os.name == "nt":
sock.ioctl(SIO_RCVALL, RCVALL_OFF)
sock.close()
#패킷을 바이트 형태로 받아 헤더와 나머지 부분을 반환한다. 이때 Struct모듈로 byte를 편하게 다룰 수 있다.
#Unpack의 첫번쨰 인자(!'BBHHHBBH4s4s')에 해당하는 알파벳 형식에 따라 앞에서부터 byte를 끊어 튜플 형태로 반환한다.
#두 번째 인자는 byte를 받는다. !는 네트워크 byte순서를 의미한다.
def parse_ip_header(ip_header):
ip_headers = struct.unpack("!BBHHHBBH4s4s", ip_header[:20])
ip_payloads = ip_header[20:]
return ip_headers, ip_payloads
#flags_and_offset() 함수에서는 숫자를 byte 형태로 변환시킨 후 bit형태로 다시 출력한다.
#.to_bytes(2, buteorder-"big")함수는 정수를 나타내는 byte 배열을 돌려준다.
#2bytes길이의 Big Endian 방식을 의미한다. 네트워크 패킷은 앞에서부터 바이트를 순서대로 읽는 Big Endian방식을 사용한다.
#1byte는 8bits이므로 zfill()함수를 이용해 자릿수를 맞춘다.
def flags_and_offset(int_num):
byte_num = int_num.to_bytes(2, byteorder="big")
x = bytearray(byte_num)
flags_and_flagment_offset = bin(x[0])[2:].zfill(8) + bin(x[1])[2:].zfill(8)
return (flags_and_flagment_offset[:3], flags_and_flagment_offset[3:])
if __name__ == "__main__":
host = "192.168.50.56"
print(f"Listening at [{host}]")
parsing(host)
[출력결과]
'웹해킹 > 파이썬해킹' 카테고리의 다른 글
TCP/UDP (2) | 2024.10.03 |
---|---|
Ping Sweep 스캐너 구현 (0) | 2024.10.03 |
은닉 채널을 이용한 파일 전송 (0) | 2024.09.20 |
ICMP(Internet Control Message Protocol) (1) | 2024.09.19 |