Ping 좀 보내본 사람이라면 아마 ICMP 패킷에 대해 알고 있을 것이다.
ICMP 프로토콜의 구조는 다음과 같은데, 핵심은 Type과 Code 로 내가 수행할 행동을 정의 하는 것이다. nmap 에서는 이런 프로토콜들의 다이어그램을 제공한다.
여기서 내가 Type:8, Code:0 으로 ICMP 메세지를 보내면, 이걸 받은 상대는 Type:0, Code:0 으로 응답을 해주게 되고, 이것이 Ping 명령의 실체인 것이다. 덧붙여 tracert 명령같은 경로 추적 기능은 IP헤더의 TTL을 일부러 작게 설정해 경로상에 놓인 장비들이 TTL을 보고 패킷을 드롭 시킬때 ICMP의 TTL Exceed 메세지를 보내는 매커니즘을 이용하게 된다. (Type:11, Code:0)
긴 말 않고 한번 코드를 살펴보자.
import socket
import struct
import sys
import ipaddress
import time
import threading
PingResponse = {}
Running:bool
def checksum(data) -> int:
s = 0; n = len(data) % 2
for i in range(0, len(data)-n, 2):
s+=int.from_bytes(data[i:i+2], byteorder='big')
if n : s+=int.from_bytes(data[i:i+1], byteorder='big') # 잔여 바이트 처리
while (s >> 16):
s = (s & 0xFFFF) + (s >> 16)
s = ~s & 0xFFFF
return s
def SendICMP(sock:socket.socket, ip:str):
type = 8; code = 0; csum = 0; icmpid = 0; seq = 0
TmpData = struct.pack("!BBHHH", type, code, csum, icmpid, seq)
csum = checksum(TmpData)
RealData = struct.pack("!BBHHH", type, code, csum, icmpid, seq)
sock.sendto(RealData, (ip, 0))
def isPrefix(Mask:int) -> bool:
if Mask<0 or Mask>32 : return False
return True
def Prefix2Range(Mask:int) -> int:
"""
넷마스크를 범위로 바꾼다.\n
ex : 22 -> 1024
ex : 24 -> 256
"""
if isPrefix(Mask)==False : return 0
inverse = 32 - Mask
return 1<<inverse
def PrintUsage():
"""
인자를 잘못 넣었을때 출력할 도움말
"""
print("wrong command.\n"
"usage : pingtest <ip[/prefix]>\n"
"ex : pingtest 8.8.8.8\n"
"ex : pingtest 192.168.0.0/24\n")
def PingListenThread():
sock = socket.socket(socket.AF_INET,socket.SOCK_RAW, socket.IPPROTO_ICMP)
sock.bind(("0.0.0.0", 0))
while True:
data, address = sock.recvfrom(1500)
if address[0] in PingResponse :
PingResponse[address[0]] = True
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) #ICMP 로우소켓 만들기
"""
if len(sys.argv) != 2: PrintUsage(); exit()
try:
address:str = sys.argv[1]
except:
PrintUsage()
exit()
"""
address = "172.30.1.0/24".split("/") # <= input 으로 입력 받아도 되고, 위 코드 주석 해제해서 인자로 받아도 된다.
if len(address)==2 :
ip, prefixStr = address
prefix:int = int(prefixStr)
else :
ip = address[0]; prefix:int=32
HostRange:int = Prefix2Range(prefix) # 이 대역 안에 총 몇개의 IP 가 있을 수 있는지?
if not HostRange: PrintUsage(); exit()
mask:int = 0xffffffff & ~((1 <<(32 - prefix)) - 1) # 네트워크ID 마스크
RawIP = int(ipaddress.ip_address(ip))
NetworkID = mask & RawIP
for HostID in range(HostRange):
ip = socket.inet_ntoa(int.to_bytes(NetworkID+HostID, 4, "big")) # 네트워크 ID와 호스트ID를 합치면 IP가 됨
PingResponse[ip] = False # 응답 받을 목록 초기화
Listener = threading.Thread(target=PingListenThread, daemon=True) #핑 응답 받을 스레드 생성
Listener.start()
for HostID in range(HostRange):
ip = socket.inet_ntoa(int.to_bytes(NetworkID+HostID, 4, "big")) # 네트워크 ID와 호스트ID를 합치면 IP가 됨
SendICMP(sock, ip) # Ping 전송
time.sleep(3) # 응답 3초 대기 ~~
for tmp in PingResponse :
tmp:dict
if PingResponse[tmp]==True: print(tmp)
main()
quit()
|
cs |
문제점 # 1
결과적으로 보면, 되긴 된다. 현재 코드에서는 IP주소와 대역을 하드코딩 하였지만, 코드의 주석에 쓰여진대로 약간의 수정을 거치면 명령 행 인자로 입력받아 손쉽게 사용 할 수 있다.
문제는 이 코드를 윈도우에서 실행하면 여러가지 애로사항이 꽃핀다는 것이다. 이 코드는 ICMP 를 직접 만들어 전송 하게 되는데 이러한 데이터를 전송 하려면 Raw 소켓을 생성해야 한다.
자... 윈도우와 Raw 소켓? 지난 포스팅 했던 글이 생각난다. https://nitwit.tistory.com/18
Raw 소켓은 윈도우에서 상상도 못할 방법으로 제약이 걸린다. 가령, Raw 소켓을 열어, ICMP 요청을 의미하는 Type:8, Code:0 셋팅으로 데이터를 보내면 관리자권한이 없어도 정상적으로 패킷이 나간다. 그런데 ICMP 응답을 의미하는 Type:0, Code:0 을 보내려 하면 이때는 관리자권한이 아닌 이상 권한 관련 오류를 내게 된다.
윈도우에서 VSCode 로 ICMP 필드를 원하는 대로 조작해서 테스트 해보고싶다면, VSCode를 관리자 권한으로 실행 해야한다.
문제점 # 2
윈도우는 악성코드의 감염 및 확산을 방지 하기 위해 운영체제 레벨에서 매우 깐깐한 트래픽 관리를 한다. 이 코드를 그대로 실행 하게 되면, 자기 자신의 IP 주소밖에 보이지 않는다. 과연 왜 그런것일까?
윈도우 기본 방화벽은 커널이 받은 데이터를 응용프로그램으로 넘겨주기 전에 깐깐한 필터링을 한다. 실제로 와이어샤크를 켜서 보면 ICMP Reply 패킷을 잘 온 것을 확인할 수 있지만, 이 프로그램에서는 받지 못한 것 처럼 멀뚱히 아무 동작도 하지 않는다. 따라서 이 방화벽을 전부 내려줘야 제대로 받아 볼 수 있다.
이 소스의 ICMP 리스너 스레드 코드를 한번 살펴보자.
통상, 소켓이라 함은 0번 포트에 bind 시키면 시스템에 흘러다니는 모든 패킷을 전부 볼 수 있다. (이더넷 프레임이나 802.11 은 제외하고). 하지만 파이썬은 이렇게 소켓을 열어도 방화벽을 내리기 전 까진 모든 데이터를 전부 받아오지 않는다. 사실 이러한 현상은 C/C++ 로 짰을때는 전혀 발생하지 않는다. 방화벽을 내리거나 시스템의 옵션을 건들지 않아도 모든 트래픽을 받아올 수 있어야 정상적인 상황이다. (이 현상은 좀 더 연구가 필요 해 보인다.)
결론 : 윈도우가 윈도우 했다.
'뻘짓' 카테고리의 다른 글
Direct2D C++ UI 라이브러리 "Foxhop.lib" (1) | 2023.06.06 |
---|---|
유니코드 - UTF8 변환 코드 (0) | 2022.07.04 |
디스어셈블 라이브러리 "Capstone" 빌드 및 사용법 (2) | 2022.04.28 |
Windows에서 FFmpeg 빌드하기 (빌드된 파일 첨부) (1) | 2022.02.08 |
[짧은글] 블루투스 페어링 : sspmode 0 (0) | 2021.12.10 |