└── pypwnat.py /pypwnat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Created by i@BlahGeek.com at 2014-01-07 4 | 5 | import socket 6 | import logging 7 | import time 8 | from ipaddr import IPAddress 9 | from threading import Thread 10 | from bitstring import Bits 11 | 12 | 13 | ICMP_PROTO = socket.getprotobyname('icmp') 14 | ICMP_ECHO_REQUEST_TYPE = 8 15 | ICMP_TIME_EXCEED_TYPE = 11 16 | SERVER_PORT = 2345 17 | CLIENT_PORT = 2345 18 | BUFSIZE = 4096 19 | NO_RESPONSE_IP = '59.66.1.1' 20 | UDP_HELLO_MSG = 'Hello from pypwnat' 21 | 22 | 23 | def checksum(data, checksum_offset=1): 24 | ''' calcualte checksum using one's complement sum of all 16-bit words, 25 | put the result in the `checksum_offset`th 16-bit word 26 | data and returned data is bitstring.Bits''' 27 | chunks = list(data.cut(16)) 28 | s = sum(map(lambda x: x.uint, chunks)) 29 | s = (s & 0xffff) + (s >> 16) 30 | chunks[checksum_offset] = ~ Bits(length=16, uint=s) 31 | return Bits(0).join(chunks) 32 | 33 | 34 | def make_ip_packet(src, dst, protocol, body, id=42, ttl=64): 35 | ip_header = Bits(hex='4500') # IP version and type of service and etc 36 | total_length = Bits(length=16, uint=20+body.length/8) # Total length 37 | # The BSD suite of platforms (excluding OpenBSD) 38 | # present the IP offset and length in host byte order. 39 | # as they say... It's a feature, not a BUG! 40 | total_length = Bits(length=16, uint=socket.htons(total_length.uint)) 41 | ip_header += total_length 42 | ip_header += Bits(length=16, uint=id) # identification 43 | ip_header += Bits(hex='0000') # flags, fragment offset 44 | ip_header += Bits(length=8, uint=ttl) # TTL 45 | ip_header += Bits(length=8, uint=protocol) 46 | ip_header += Bits(hex='0000') # checksum 47 | ip_header += Bits(length=32, uint=int(IPAddress(src))) 48 | ip_header += Bits(length=32, uint=int(IPAddress(dst))) 49 | return checksum(ip_header, 5) + body 50 | 51 | 52 | def make_icmp_packet(typ, code=0, body=None, id=42, seq=42): 53 | icmp_header = Bits(length=8, uint=typ) # type 54 | icmp_header += Bits(length=8, uint=code) # code 55 | icmp_header += Bits(hex='0000') # checksum 56 | icmp_header += Bits(length=16, uint=id) + Bits(length=16, uint=seq) 57 | icmp_header = checksum(icmp_header) 58 | return icmp_header if body is None else (icmp_header+body) 59 | 60 | 61 | def send_echo_request(sock, ip, seq=42, id=42): 62 | ''' a simple ping ''' 63 | logging.debug('Sending echo request with id=%d, seq=%d.' % (id, seq)) 64 | icmp_packet = make_icmp_packet(ICMP_ECHO_REQUEST_TYPE) 65 | ip_packet = make_ip_packet(0, ip, ICMP_PROTO, icmp_packet) 66 | sock.sendto(ip_packet.bytes, (ip, 1)) 67 | return ip_packet 68 | 69 | 70 | def send_time_exceed(sock, server_ip, seq=42, id=42): 71 | logging.debug('Sending time exceed message.') 72 | inner_icmp = make_icmp_packet(ICMP_ECHO_REQUEST_TYPE) 73 | inner_ip = make_ip_packet(server_ip, NO_RESPONSE_IP, ICMP_PROTO, inner_icmp) 74 | icmp_packet = make_icmp_packet(ICMP_TIME_EXCEED_TYPE, body=inner_ip) 75 | ip_packet = make_ip_packet(0, server_ip, ICMP_PROTO, icmp_packet) 76 | sock.sendto(ip_packet.bytes, (server_ip, 1)) 77 | return ip_packet 78 | 79 | 80 | def handle_icmp_response(response): 81 | logging.debug('Handling response in new thread.') 82 | response = Bits(bytes=response) 83 | source_ip = response[12*8:][:4*8] 84 | source_ip = IPAddress(source_ip.uint) 85 | response = response[20*8:] # ignore IP header 86 | typ = response[:8] 87 | if typ.uint != 11: 88 | logging.debug('Not time exceed packet, ignore.') 89 | return 90 | logging.info('Got response from %s' % source_ip.compressed) 91 | udpsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 92 | udpsock.bind(('0.0.0.0', SERVER_PORT)) 93 | udpsock.connect((source_ip.compressed, CLIENT_PORT)) 94 | udpsock.send(UDP_HELLO_MSG) 95 | 96 | 97 | def run_server(ping_interval=10.0): 98 | sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_PROTO) 99 | sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 100 | sock.settimeout(ping_interval) 101 | while True: 102 | send_echo_request(sock, NO_RESPONSE_IP) 103 | try: 104 | response = sock.recv(BUFSIZE) 105 | except socket.timeout: 106 | logging.debug('No ICMP response within %f seconds, continue.' % ping_interval) 107 | continue 108 | else: 109 | logging.debug('Got ICMP response!') 110 | th = Thread(target=handle_icmp_response, args=[response]) 111 | th.start() 112 | 113 | 114 | def run_client(server_ip): 115 | sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, ICMP_PROTO) 116 | sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 117 | udpsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 118 | udpsock.bind(('0.0.0.0', CLIENT_PORT)) 119 | udpsock.connect((server_ip, SERVER_PORT)) 120 | while True: 121 | logging.debug('Sending hello message via UDP.') 122 | udpsock.send(UDP_HELLO_MSG) 123 | send_time_exceed(sock, server_ip, NO_RESPONSE_IP) 124 | try: 125 | response = udpsock.recv(BUFSIZE) 126 | except socket.error: 127 | logging.debug('UDP message refused, continue') 128 | time.sleep(0.1) 129 | continue 130 | else: 131 | logging.info('Got UDP response!') 132 | print response 133 | break 134 | 135 | 136 | if __name__ == '__main__': 137 | logging.basicConfig(level=logging.DEBUG, 138 | format='[%(threadName)s] %(levelname)s : %(msg)s') 139 | import argparse 140 | parser = argparse.ArgumentParser() 141 | parser.add_argument('-s', '--server', action='store_true', help='Run as server') 142 | parser.add_argument('-c', '--client', type=str, help='Run as client, must provide IP of server') 143 | args = parser.parse_args() 144 | if args.server: 145 | run_server() 146 | else: 147 | run_client(args.client) 148 | --------------------------------------------------------------------------------