코딩/Python

살아있는 호스트 IP 스캔하기

비니화이팅 2018. 2. 13. 18:39

같은 네트워크 대역에 살아있는 호스트의 IP를 스캔하는 소스를 작성해 보려고 한다.

 

찾아보니 방법은 아래와 같이 나왔다.

1. ping을 이용하는 방법(ICMP Echo Reply를 보내오는 호스트가 살아있는 것으로 판별)

2. 특정 포트로 메시지를 보내는 방법(Port Unreachable를 보내오는 호스트가 살아있는 것으로 판별)

 

이중에서 나는 ping을 이용하는 방법을 선택했다.

ping을 이용하는 방법에도 찾아보니 여러가지가 있었다.

1. pyping 모듈의 ping을 이용

2. os모듈의 system을 이용

3. ICMP Echo Request 패킷을 직접 만들기

나는 이중에서 ICMP Echo Request 패킷을 직접 만들어 보기로 결정했다.

 

일단 참고하기 위해서 wireshark로 ping패킷을 잡아봤다.

request와 reply쌍에 대한 seq값이 동일한 걸 확인할 수 있다. 

즉, seq값을 통해 request에 대한 응답임을 알 수 있다.

 

reqeust 헤더는 다음과 같다. Type이 8, Code가 0이다.

 

reply헤더는 다음과 같다. Type이 0, Code가 0이다.

 

Type

(1 byte)

Code

(1 byte)

Checksum

(2 byte)

Identifier

(2 byte)

 Sequence 

(2 byte)

 

ICMP Checksum이 틀리면 reply가 돌아오지 않는다. 따라서 Checksum을 구해줘야 하는데 방법은 아래와 같다.


ex ) ICMP Header -> b'\x08\x00\x00\x00\x01\x00\x01\x00'

 

1. 두 자리씩 끊어준다. (길이가 짝수가 아니면 뒤에 00을 추가한다.)

08 00    00 00    01 00    01 00

 

2. 1번에서 끊어 놓은 두 자리씩 더해준다. (4바이트 이상의 올림수가 발생한 경우에는 올림수를 더하고 없앤다.)

08 00 + 00 00 + 01 00 + 01 00 = 0A 00 

 

2. 위의 결과를 2진수로 바꿔 준다.

0000 1010 0000 0000

 

3. 위의 결과에 1의 보수를 적용한다.

1111 0101 1111 1111 = F5FF

 

이제 원리를 알았으니 코드를 작성해본다.

 

<ICMP Echo Request>

같은 네트워크 대역의 모든 주소로 ping을 보낸다.

존재하지 않는 IP면 실제로 ping이 나가지는 않는다. (MAC주소를 모르기 때문)

실제로 돌려보니 방화벽때문에 reply가 돌아오지 않는 경우가 많았다.

from socket import *
from struct import pack, unpack
from netaddr import IPNetwork

def getChksum(header):
    checksum=0
    if (len(header) % 2) == 1:
        header += b'\x00'
    header=unpack(str(len(header)//2)+'H', header)
    for x in header:
        checksum+=x
    checksum += (checksum >> 16)
    checksum = (checksum & 0xFFFF) ^ 0xFFFF
    return checksum

def makePacket():
    type = 8
    code = 0
    checksum = 0
    id = 1
    seq = 1
    data = "Hello".encode()
    header = pack("bbHHh", type, code, checksum, id, seq)
    checksum = getChksum(header + data)
    header = pack("bbHHh", type, code, checksum, id, seq)
    return header + data

def ping(host):
    sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)
    packet = makePacket()
    for dst in IPNetwork(host + '/24'):
        print('IP -> %s ' % dst)
        sock.sendto(packet, (str(dst), 0))

def main():
    host = gethostbyname(gethostname())
    print('Start pinging at [%s]' % host)
    ping(host)

if __name__ == '__main__':
    main()
​

 

<ICMP Echo Reply>

ICMP Echo Reply를 받아서  Alive host의 리스트를 출력한다.

from socket import *
import os
from struct import pack, unpack

def getIpHeader(data):
    ipHeader = unpack('!BBHHHBBH4s4s', data[:20])
    return ipHeader

def getIpHeaderLen(ipHeader):
    ipHeaderLen=(ipHeader[0]&0x0F)*4
    return ipHeaderLen

def printIp(ipData):
    ipData=unpack('BBBB', ipData)
    ipAddr=''
    count=0
    for x in ipData:
        count+=1
        ipAddr+=str(x)
        if(count!=4):
            ipAddr+='.'
    return ipAddr

def findAliveHost(myIp):
    sock=socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)
    sock.bind((myIp, 0))
    sock.setsockopt(IPPROTO_IP, IP_HDRINCL, 1)
    sock.ioctl(SIO_RCVALL, RCVALL_ON)

    while(True) :
        data = sock.recvfrom(65565)
        ipHeader=getIpHeader(data[0])
        if ipHeader[6]==1 :
            ipHeaderLen=getIpHeaderLen(ipHeader)
            icmpType=data[0][20]
            if icmpType==0 :
                print('Alive Host : %s' %printIp(ipHeader[8]))

def main():
    print('Start finding alive host')
    findAliveHost(gethostbyname(gethostname()))

if __name__ == '__main__':
    main()