├── datagram_ex.png ├── datagram_fmt.png ├── README.md ├── sipsas.py ├── sipsac.py └── test-tool ├── run_test.sh └── sipsa_test.c /datagram_ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0ki/SIPSA/HEAD/datagram_ex.png -------------------------------------------------------------------------------- /datagram_fmt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0ki/SIPSA/HEAD/datagram_fmt.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SIPSA 2 | ====== 3 | Source IP spoofing for anonymization over UDP 4 | 5 | Source IP spoofing anonymization over UDP (SIPSA) is a proposal for a protocol that in many network environments would allow two hosts on the network to hide both their source and destination addresses, while still being able to communicate information. 6 | 7 | 8 | This is a proof of concept tool that implements the SIPSA protocol. Use in production environments is highly discouraged. Do not depend on it for confidentiality, anonimity or deniablity. Do not expect the tool to preserve the integrity of your data or provide any reasonable level of availability for that matter. 9 | 10 | You are welcome to fork and experiment! 11 | 12 | * sipsas.py - IPv4 receiver 13 | * sipsac.py - IPv4 sender 14 | * test-tool - A tool used to test BCP38 limitations 15 | -------------------------------------------------------------------------------- /sipsas.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # SIPSAs4 - Source IP spoofing for anonymization over UDP (IPv4 receiver) 3 | # (C) Kirils Solovjovs, 2016; Modified BSD licence 4 | 5 | # WARNING WARNING WARNING 6 | # 7 | # This is a proof of concept tool. Do not depend on it for confidentiality, 8 | # anonimity or deniablity. Do not expect the tool to preserve the integrity 9 | # of your data or provide any reasonable level of availability for that matter. 10 | # This is a proof of concept tool. 11 | # 12 | # WARNING WARNING WARNING 13 | 14 | # This demo supports IPv4 only, but there is no reason why this shouldn't work for IPv6 15 | 16 | # Please make sure that all other UDP ports on your systems are FILTERED (not REJECTED) 17 | 18 | 19 | # Shared key used to perform AES encryption on SIPSA metadata *only* 20 | key="YOUR SHARED KEY HERE" 21 | 22 | import socket,re,sys 23 | from Crypto.Cipher import AES 24 | from hashlib import md5 25 | from socket import inet_ntoa 26 | 27 | key=md5(key).digest() #always exactly 32 bytes 28 | 29 | UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 30 | listen_addr = ("",51654) #listen on all interfaces, port 51654 31 | UDPSock.bind(listen_addr) 32 | while True: 33 | payload,[ip,port] = UDPSock.recvfrom(65535) #larger than actually 34 | print "Got a datagram from "+ip+".", 35 | if port!=51654: 36 | sys.stderr.write("Wrong source port ("+str(port)+").") 37 | continue 38 | if len(payload)<5+3+16: 39 | sys.stderr.write("Datagram too short to be SIPSA.") 40 | continue 41 | if not re.match("SIPSA",payload): #does the payload begin with "SIPSA"? 42 | sys.stderr.write("Not a SIPSA packet.") 43 | continue 44 | if payload[5:7]!="\x00\x04": 45 | sys.stderr.write("SIPSA version mismatch.") 46 | continue 47 | 48 | try: 49 | lenIndicator=ord(payload[7]) #how long is the metadata (in blocks of 16) 50 | rawdata=payload[8:8+lenIndicator*16] 51 | data=payload[8+lenIndicator*16:] 52 | iv=rawdata[:16] 53 | crypto=rawdata[16:] 54 | cipher = AES.new(key,AES.MODE_CBC,iv) 55 | metadata = cipher.decrypt(crypto) 56 | realSrcIP=inet_ntoa(metadata[0:4]) # real src 57 | realDstIP=metadata[4:8] # real dst 58 | srcIPlst=[] 59 | dstIPlst=[] 60 | i=8 61 | while metadata[i]!="\xff": #marker that indicates end of source list 62 | srcIPlst.append(metadata[i:i+4]) 63 | i = i + 4 64 | i = i + 1 65 | while metadata[i]!="\xff": #marker that indicates end of dest list 66 | dstIPlst.append(metadata[i:i+4]) 67 | i = i + 4 68 | 69 | except: 70 | sys.stderr.write("Packet damaged. (maybe the key is mismatched?)\n") 71 | continue 72 | if ip==realSrcIP: 73 | realSrcIP=realSrcIP+"(ip match)" 74 | # I suggest adding a randomized ~100ms delay before sending the reply 75 | # reply only once per each packet, not each copy. 76 | # if realSrcIP is anonymized, do this for each unique "iv" and/or "crypto" 77 | # send_sipsa(realDstIP,IP()/TCP(),key,srcIPlst,dstIPlst) # send the reply 78 | print "IP", realSrcIP, "sent us this:", data 79 | else: 80 | print 81 | 82 | -------------------------------------------------------------------------------- /sipsac.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # SIPSAc4 - Source IP spoofing for anonymization over UDP (IPv4 sender) 3 | # (C) Kirils Solovjovs, 2016; Modified BSD licence 4 | 5 | # WARNING WARNING WARNING 6 | # 7 | # This is a proof of concept tool. Do not depend on it for confidentiality, 8 | # anonimity or deniablity. Do not expect the tool to preserve the integrity 9 | # of your data or provide any reasonable level of availability for that matter. 10 | # This is a proof of concept tool. 11 | # 12 | # WARNING WARNING WARNING 13 | 14 | # This demo supports IPv4 only, but there is no reason why this shouldn't work for IPv6 15 | 16 | 17 | # How many sources and destinations to create? Total pairs = srcR x dstR 18 | srcR=5 19 | dstR=3 20 | 21 | # Shared key used to perform AES encryption on SIPSA metadata *only* 22 | key="YOUR SHARED KEY HERE" 23 | 24 | 25 | import os,sys 26 | 27 | if not os.geteuid() == 0: 28 | sys.exit("ERR: Peril awaits you, seeing you need to be root to craft packets. Quitting.") 29 | 30 | from scapy.all import * 31 | import random 32 | from struct import unpack 33 | from socket import AF_INET, inet_pton, inet_aton 34 | from Crypto.Cipher import AES 35 | from Crypto import Random 36 | from hashlib import md5 37 | 38 | 39 | key=md5(key).digest() #always exactly 32 bytes 40 | 41 | 42 | # privateIP(): is IPv4 address private or loopback? 43 | # function from http://stackoverflow.com/questions/691045 , author: jackdoe 44 | def privateIP(ip): 45 | f = unpack('!I',inet_pton(AF_INET,ip))[0] 46 | private = ( 47 | [ 2130706432, 4278190080 ], # 127.0.0.0, 255.0.0.0 http://tools.ietf.org/html/rfc3330 48 | [ 3232235520, 4294901760 ], # 192.168.0.0, 255.255.0.0 http://tools.ietf.org/html/rfc1918 49 | [ 2886729728, 4293918720 ], # 172.16.0.0, 255.240.0.0 http://tools.ietf.org/html/rfc1918 50 | [ 167772160, 4278190080 ], # 10.0.0.0, 255.0.0.0 http://tools.ietf.org/html/rfc1918 51 | ) 52 | for net in private: 53 | if (f & net[1]) == net[0]: 54 | return True 55 | return False 56 | 57 | 58 | # genIPs(): generate a list pseudo-random IPs 59 | def genIPs(base,count): 60 | baseOct=base.split('.') 61 | avoid3=baseOct[3] 62 | if (count<2): 63 | sys.exit("ERR: You should always generate at least two IPs.") 64 | lst=[] 65 | lst.append(base) # inclose original IP 66 | while avoid3==baseOct[3]: #include 67 | baseOct[3]=str(random.randrange(1,255)) # 1-254 just to be safe 68 | lst.append(".".join(baseOct)) #same subnet 69 | 70 | 71 | # WARNING! Please DO this if your base IP is in private space *and* NATted: 72 | # Swap comments on the next two lines to disable/enable full hiding of original source IP 73 | # lst=[] 74 | count=count-2 75 | 76 | 77 | while count>0: 78 | baseOct[0]=str(random.randrange(1,240)) # 1 - 239 , no multicast 79 | baseOct[1]=str(random.randrange(0,256)) # 0 - 255 80 | baseOct[2]=str(random.randrange(0,256)) # 0 - 255 81 | baseOct[3]=str(random.randrange(1,255)) # 1 - 254 , yes, it's crude 82 | if privateIP(".".join(baseOct)): # ignore private IPs 83 | continue 84 | count=count-1 85 | lst.append(".".join(baseOct)) 86 | avoid3=baseOct[3] 87 | while avoid3==baseOct[3] and count>0: #include 88 | baseOct[3]=str(random.randrange(1,255)) # 1-254 just to be safe 89 | if count>0: 90 | lst.append(".".join(baseOct)) #same subnet 91 | count=count-1 92 | return lst 93 | 94 | 95 | # send_sipsa(): send a SIPSA packet 96 | def send_sipsa(dstA,data,key="",pSrcIPlst=None,pDstIPlst=None): 97 | random.seed(dstA+realSrcIP+key+"."+str(srcR)+"."+str(dstR)) # always the same set of IP addresses 98 | if pSrcIPlst: 99 | srcIPlst=pSrcIPlst 100 | else: 101 | srcIPlst=genIPs(realSrcIP,srcR) 102 | random.shuffle(srcIPlst) 103 | if pDstIPlst: 104 | dstIPlst=pDstIPlst 105 | else: 106 | dstIPlst=genIPs(dstA,dstR) 107 | random.shuffle(dstIPlst) 108 | 109 | # WARNING! This crypto is likely not secure. Do not use in production! 110 | iv=Random.new().read(16) 111 | 112 | cipher=AES.new(key,AES.MODE_CBC,iv) 113 | metadata=inet_aton(realSrcIP)+inet_aton(dstA)+"".join([inet_aton(e) for e in srcIPlst])+"\xff"+"".join([inet_aton(e) for e in dstIPlst])+"\xff" 114 | 115 | # we can also hide the real IP from the server. it will still work: 116 | # metadata="\x00"*8+"".join([inet_aton(e) for e in srcIPlst])+"\xff"+"".join([inet_aton(e) for e in dstIPlst])+"\xff" 117 | 118 | crypto=iv+cipher.encrypt(metadata.ljust(len(metadata) + 16 - len(metadata) % 16,"\x00")) 119 | lenIndicator=len(crypto)/16 120 | if lenIndicator>255: 121 | sys.exit("ERR: Too many sources and/or dests.") # size field is currently limited to 1 byte 122 | 123 | payload="SIPSA\x00\x04"+chr(lenIndicator)+crypto+str(data) #Layer5 payload 124 | 125 | # send from and to all the addresses 126 | for srcIP in srcIPlst: 127 | for dstIP in dstIPlst: 128 | packet = Ether()/IP(src=srcIP,dst=dstIP)/UDP(sport=51654,dport=51654)/payload 129 | sendp(packet,iface=routingIface, verbose=0) 130 | 131 | print "Sent",len(srcIPlst)*len(dstIPlst),"packets." 132 | 133 | 134 | routes=[] 135 | for routeentry in conf.route.routes: 136 | if routeentry[0]==0: 137 | routes.append([routeentry[4],routeentry[3]]) 138 | 139 | if len(routes)<1: 140 | sys.exit("ERR: No IPv4 routes found.") 141 | 142 | realSrcIP=routes[0][0] 143 | routingIface=routes[0][1] 144 | 145 | if len(routes)>1: 146 | sys.stderr.write("WARN: Multiple IPv4 routes found.\n") 147 | 148 | print "Choosing "+routingIface+". "+realSrcIP+" is our real source IP." 149 | 150 | if privateIP(realSrcIP): 151 | sys.exit("ERR: This PoC will not work behind NAT. Remove this warning (in code), if you are testing the tool on internal network.") 152 | 153 | # Test: 154 | send_sipsa("203.0.113.3",IP(src="8.8.8.8",dst="8.8.4.4")/TCP(sport=1234,dport=2000)/"Tunneled Layer 5 data",key) 155 | -------------------------------------------------------------------------------- /test-tool/run_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #(C) Kirils Solovjovs, 2018. SIPSA project 3 | # 4 | # If you have any trouble running this or any concerns, 5 | # or if you would like your name listed in the research paper, 6 | # please send the any comments to research@kirils.org 7 | # Please include full output of this script where appropriate. 8 | 9 | set -e 10 | 11 | [ ! -r sipsa_test.c ] && echo source code not found in currect directory && exit 1 12 | 13 | echo This is SIPSA research tool version 20180106. 14 | echo 15 | echo This tool will send 150-200 small packets to sipsa.kirils.org. 16 | echo Various IP address data will be collected. 17 | echo Expected runtime is less than 10 seconds, usually 1 second. 18 | echo Root access will be required to craft some of the packets. 19 | echo 20 | echo Press RETURN if you agree, or ^C to abort. 21 | read 22 | 23 | 24 | echo -n Verifying that all required tools are installed... 25 | 26 | for tool in gcc cat grep cut head tail sed ip nc; do #sudo is excluded on purpose. the user can always run this as root. 27 | [ -z "$(which $tool | cat)" ] && echo && echo $tool is missing && exit 13 28 | echo -n $tool... 29 | done 30 | echo ALL GOOD. 31 | 32 | echo -n Compiling test code... 33 | gcc sipsa_test.c -o ./sipsa_test 34 | echo done 35 | 36 | echo -n Checking if test code runs on this machine... 37 | [ -z "$(./sipsa_test |grep ^Usage)" ] && echo no && exit 10 38 | echo yes 39 | 40 | echo -n Detecting main network interface... 41 | iface="$(cat /proc/net/route |grep -E 00000000.....+00000000|cut -d $'\t' -f 1|head -1)" 42 | [ -z "$iface" ] && echo failed && exit 2 43 | echo $iface 44 | 45 | echo -n Getting real IP address... 46 | rip="$(( sed -E 's/$/\r/' | nc 85.254.196.147 80 | tail -1 | grep '^[0-9]' | cat) << EOF 47 | GET /detect_ip/ HTTP/1.0 48 | Host: sipsa.kirils.org 49 | 50 | EOF)" 51 | 52 | [ -z "$rip" ] && echo failed && exit 4 53 | echo $rip 54 | 55 | echo -n Getting IP address of the machine... 56 | lip="$(ip addr show dev $iface | sed -E 's/^ +//'|grep ^inet\ | cut -d \ -f 2|grep ^$rip/ | cat)" 57 | [ -z "$lip" ] && lip="$(ip addr show dev $iface | sed -E 's/^ +//'|grep ^inet\ | cut -d \ -f 2|head -1)" 58 | [ -z "$lip" ] && echo failed && exit 3 59 | echo $lip 60 | 61 | netmask="$(echo $lip | cut -d / -f 2)" 62 | lip="$(echo $lip | cut -d / -f 1)" 63 | 64 | echo -n Calculating network address... 65 | IFS=. read -r i1 i2 i3 i4 <<< "$lip" 66 | IFS=. read -r m0 m1 m2 m3 m4 <<< $(for a in $(seq 1 32); do if [ $(((a - 1) % 8)) -eq 0 ]; then echo -n .; fi; if [ $a -le $netmask ]; then echo -n 1; else echo -n 0; fi; done) 67 | na=$(printf "%d.%d.%d.%d" "$((i1 & (2#$m1)))" "$((i2 & (2#$m2)))" "$((i3 & (2#$m3)))" "$((i4 & (2#$m4)))") 68 | echo $na 69 | 70 | echo -n Getting broadcast address... 71 | bc="$(ip addr show dev $iface | sed -E 's/^ +//'|grep ^inet\ | cut -d \ -f 4|head -1)" 72 | [ -z "$bc" ] && echo failed && exit 3 73 | echo $bc 74 | 75 | echo -n Getting gateway address... 76 | gw="$(ip route get 85.254.196.147 | sed -E 's/ +/ /g;s/ $//'|grep "$iface" | head -1 |cut -d \ -f 3| grep '^[0-9]' | cat)" 77 | [ -z "$gw" ] && echo failed && exit 5 78 | echo $gw 79 | 80 | failmode="" 81 | [ "$rip" != "$lip" ] && echo Real IP does not match the IP set on the interface. \ 82 | && echo This test is useless behind NAT, but proceeding anyway. In the name of SCIENCE\! \ 83 | && failmode="-nat" 84 | 85 | 86 | 87 | [ "$UID" != "0" ] && sudo="sudo " || sudo="" 88 | $sudo echo 89 | echo -n Running the tests as root... 90 | 91 | 92 | iplst="" 93 | 94 | echo -n 1... 95 | $sudo ./sipsa_test "$iface" "$lip" "$rip-$lip-native$failmode" 96 | $sudo ./sipsa_test "$iface" "$lip" "$rip-$lip-native$failmode" 97 | iplst="$iplst;$lip" 98 | 99 | echo -n 2... 100 | $sudo ./sipsa_test "$iface" "$gw" "$rip-$gw-gw$failmode" 101 | $sudo ./sipsa_test "$iface" "$gw" "$rip-$gw-gw$failmode" 102 | iplst="$iplst;$gw" 103 | 104 | echo -n 3... 105 | $sudo ./sipsa_test "$iface" "$rip" "$rip-$rip-real$failmode" 106 | $sudo ./sipsa_test "$iface" "$rip" "$rip-$rip-real$failmode" 107 | iplst="$iplst;$rip" 108 | 109 | echo -n 4... 110 | $sudo ./sipsa_test "$iface" "$na" "$rip-$na-lan0$failmode" 111 | $sudo ./sipsa_test "$iface" "$na" "$rip-$na-lan0$failmode" 112 | iplst="$iplst;$na" 113 | 114 | echo -n 5... 115 | $sudo ./sipsa_test "$iface" "$bc" "$rip-$bc-lanFF$failmode" 116 | $sudo ./sipsa_test "$iface" "$bc" "$rip-$bc-lanFF$failmode" 117 | iplst="$iplst;$bc" 118 | 119 | 120 | for ((i=1;i<4;i++)); do 121 | echo -n 6/$i... 122 | tmpip="$(echo "$lip" |cut -d \. -f 1-3).$(($RANDOM % 256))" 123 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-lanRND1$failmode" 124 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-lanRND1$failmode" 125 | iplst="$iplst;$tmpip" 126 | done 127 | 128 | 129 | for ((i=4;i<8;i++)); do 130 | echo -n 6/$i... 131 | tmpip="$(echo "$lip" |cut -d \. -f 1-2).$(($RANDOM % 256)).$(($RANDOM % 256))" 132 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-lanRND2$failmode" 133 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-lanRND2$failmode" 134 | iplst="$iplst;$tmpip" 135 | done 136 | 137 | 138 | for ((i=8;i<=10;i++)); do 139 | echo -n 6/$i... 140 | tmpip="$(echo "$lip" |cut -d \. -f 1).$(($RANDOM % 256)).$(($RANDOM % 256)).$(($RANDOM % 256))" 141 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-lanRND3$failmode" 142 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-lanRND3$failmode" 143 | iplst="$iplst;$tmpip" 144 | done 145 | 146 | 147 | for ((i=1;i<=10;i++)); do #class A 148 | echo -n 7/$i... 149 | tmpip="$(($RANDOM % 126+1)).$(($RANDOM % 256)).$(($RANDOM % 256)).$(($RANDOM % 256))" 150 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-A$i$failmode" 151 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-A$i$failmode" 152 | iplst="$iplst;$tmpip" 153 | done 154 | 155 | for ((i=1;i<=10;i++)); do #class B 156 | echo -n 8/$i... 157 | tmpip="$(($RANDOM % 40+128)).$(($RANDOM % 256)).$(($RANDOM % 256)).$(($RANDOM % 256))" 158 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-B$i$failmode" 159 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-B$i$failmode" 160 | iplst="$iplst;$tmpip" 161 | done 162 | 163 | for ((i=1;i<=10;i++)); do #class C 164 | echo -n 9/$i... 165 | tmpip="$(($RANDOM % 30+193)).$(($RANDOM % 256)).$(($RANDOM % 256)).$(($RANDOM % 256))" 166 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-C$i$failmode" 167 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-C$i$failmode" 168 | iplst="$iplst;$tmpip" 169 | done 170 | 171 | for ((i=1;i<=10;i++)); do #class D 172 | echo -n 10/$i... 173 | tmpip="$(($RANDOM % 16+224)).$(($RANDOM % 256)).$(($RANDOM % 256)).$(($RANDOM % 256))" 174 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-D$i$failmode" 175 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-D$i$failmode" 176 | iplst="$iplst;$tmpip" 177 | done 178 | 179 | for ((i=1;i<=10;i++)); do #class E 180 | echo -n 11/$i... 181 | tmpip="$(($RANDOM % 14+240)).$(($RANDOM % 256)).$(($RANDOM % 256)).$(($RANDOM % 256))" 182 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-E$i$failmode" 183 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-E$i$failmode" 184 | iplst="$iplst;$tmpip" 185 | done 186 | 187 | #private A 188 | echo -n 12/1... 189 | tmpip="10.$(($RANDOM % 256)).$(($RANDOM % 256)).$(($RANDOM % 256))" 190 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-Ap$failmode" 191 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-Ap$failmode" 192 | iplst="$iplst;$tmpip" 193 | 194 | #private B 195 | echo -n 12/2... 196 | tmpip="172.20.$(($RANDOM % 256)).$(($RANDOM % 256))" 197 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-Bp$failmode" 198 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-Bp$failmode" 199 | iplst="$iplst;$tmpip" 200 | 201 | #private C 202 | echo -n 12/3... 203 | tmpip="192.168.$(($RANDOM % 256)).$(($RANDOM % 256))" 204 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-Cp$failmode" 205 | $sudo ./sipsa_test "$iface" "$tmpip" "$rip-$tmpip-Cp$failmode" 206 | iplst="$iplst;$tmpip" 207 | 208 | echo done. 209 | $sudo ./sipsa_test "$iface" "$lip" "REPORT:20180106:$rip-$iface$failmode$iplst" 210 | 211 | echo 212 | echo Thank you for contributing to SIPSA research. 213 | echo Go to http://sipsa.kirils.org/ to view your results. 214 | echo 215 | 216 | 217 | -------------------------------------------------------------------------------- /test-tool/sipsa_test.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2011-2015 P.D. Buchan (pdbuchan@yahoo.com) 2 | Copyright (C) 2018 Kirils Solovjovs (research@kirils.org) 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include // close() 21 | #include // strcpy, memset(), and memcpy() 22 | 23 | #include // struct addrinfo 24 | #include // needed for socket(), uint8_t, uint16_t, uint32_t 25 | #include // needed for socket() 26 | #include // IPPROTO_RAW, IPPROTO_IP, IPPROTO_UDP, INET_ADDRSTRLEN 27 | #include // struct ip and IP_MAXPACKET (which is 65535) 28 | #include // struct udphdr 29 | #include // inet_pton() and inet_ntop() 30 | #include // macro ioctl is defined 31 | #include // defines values for argument "request" of ioctl. 32 | #include // struct ifreq 33 | 34 | #include // errno, perror() 35 | 36 | // Define some constants. 37 | #define IP4_HDRLEN 20 // IPv4 header length 38 | #define UDP_HDRLEN 8 // UDP header length, excludes data 39 | 40 | // Function prototypes 41 | uint16_t checksum (uint16_t *, int); 42 | uint16_t udp4_checksum (struct ip, struct udphdr, uint8_t *, int); 43 | char *allocate_strmem (int); 44 | uint8_t *allocate_ustrmem (int); 45 | int *allocate_intmem (int); 46 | 47 | int 48 | main (int argc, char **argv) 49 | { 50 | int status, datalen, sd, *ip_flags; 51 | const int on = 1; 52 | char *interface, *target, *src_ip, *dst_ip; 53 | struct ip iphdr; 54 | struct udphdr udphdr; 55 | uint8_t *data, *packet; 56 | struct addrinfo hints, *res; 57 | struct sockaddr_in *ipv4, sin; 58 | struct ifreq ifr; 59 | void *tmp; 60 | 61 | if( argc != 4 ) { 62 | printf("Usage %s \n\n",argv[0]); 63 | exit(1); 64 | } 65 | 66 | // Allocate memory for various arrays. 67 | data = allocate_ustrmem (IP_MAXPACKET); 68 | packet = allocate_ustrmem (IP_MAXPACKET); 69 | interface = allocate_strmem (strlen(argv[1])); 70 | target = allocate_strmem (40); 71 | src_ip = allocate_strmem (INET_ADDRSTRLEN); 72 | dst_ip = allocate_strmem (INET_ADDRSTRLEN); 73 | ip_flags = allocate_intmem (4); 74 | 75 | // Interface to send packet through. 76 | strcpy (interface, argv[1]); 77 | 78 | // Submit request for a socket descriptor to look up interface. 79 | if ((sd = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) { 80 | perror ("socket() failed to get socket descriptor for using ioctl() "); 81 | exit (EXIT_FAILURE); 82 | } 83 | 84 | // Use ioctl() to look up interface index which we will use to 85 | // bind socket descriptor sd to specified interface with setsockopt() since 86 | // none of the other arguments of sendto() specify which interface to use. 87 | memset (&ifr, 0, sizeof (ifr)); 88 | snprintf (ifr.ifr_name, sizeof (ifr.ifr_name), "%s", interface); 89 | if (ioctl (sd, SIOCGIFINDEX, &ifr) < 0) { 90 | perror ("ioctl() failed to find interface "); 91 | return (EXIT_FAILURE); 92 | } 93 | close (sd); 94 | //printf ("Index for interface %s is %i\n", interface, ifr.ifr_ifindex); 95 | 96 | // Source IPv4 address: you need to fill this out 97 | strcpy (src_ip, argv[2]); 98 | 99 | // Destination URL or IPv4 address: you need to fill this out 100 | strcpy (target, "85.254.196.147"); 101 | 102 | // Fill out hints for getaddrinfo(). 103 | memset (&hints, 0, sizeof (struct addrinfo)); 104 | hints.ai_family = AF_INET; 105 | hints.ai_socktype = SOCK_STREAM; 106 | hints.ai_flags = hints.ai_flags | AI_CANONNAME; 107 | 108 | // Resolve target using getaddrinfo(). 109 | if ((status = getaddrinfo (target, NULL, &hints, &res)) != 0) { 110 | fprintf (stderr, "getaddrinfo() failed: %s\n", gai_strerror (status)); 111 | exit (EXIT_FAILURE); 112 | } 113 | ipv4 = (struct sockaddr_in *) res->ai_addr; 114 | tmp = &(ipv4->sin_addr); 115 | if (inet_ntop (AF_INET, tmp, dst_ip, INET_ADDRSTRLEN) == NULL) { 116 | status = errno; 117 | fprintf (stderr, "inet_ntop() failed.\nError message: %s", strerror (status)); 118 | exit (EXIT_FAILURE); 119 | } 120 | freeaddrinfo (res); 121 | 122 | // UDP data 123 | datalen = strlen(argv[3]); 124 | strcpy (data, argv[3]); 125 | 126 | // IPv4 header 127 | 128 | // IPv4 header length (4 bits): Number of 32-bit words in header = 5 129 | iphdr.ip_hl = IP4_HDRLEN / sizeof (uint32_t); 130 | 131 | // Internet Protocol version (4 bits): IPv4 132 | iphdr.ip_v = 4; 133 | 134 | // Type of service (8 bits) 135 | iphdr.ip_tos = 0; 136 | 137 | // Total length of datagram (16 bits): IP header + UDP header + datalen 138 | iphdr.ip_len = htons (IP4_HDRLEN + UDP_HDRLEN + datalen); 139 | 140 | // ID sequence number (16 bits): unused, since single datagram 141 | iphdr.ip_id = htons (0); 142 | 143 | // Flags, and Fragmentation offset (3, 13 bits): 0 since single datagram 144 | 145 | // Zero (1 bit) 146 | ip_flags[0] = 0; 147 | 148 | // Do not fragment flag (1 bit) 149 | ip_flags[1] = 0; 150 | 151 | // More fragments following flag (1 bit) 152 | ip_flags[2] = 0; 153 | 154 | // Fragmentation offset (13 bits) 155 | ip_flags[3] = 0; 156 | 157 | iphdr.ip_off = htons ((ip_flags[0] << 15) 158 | + (ip_flags[1] << 14) 159 | + (ip_flags[2] << 13) 160 | + ip_flags[3]); 161 | 162 | // Time-to-Live (8 bits): default to maximum value 163 | iphdr.ip_ttl = 255; 164 | 165 | // Transport layer protocol (8 bits): 17 for UDP 166 | iphdr.ip_p = IPPROTO_UDP; 167 | 168 | // Source IPv4 address (32 bits) 169 | if ((status = inet_pton (AF_INET, src_ip, &(iphdr.ip_src))) != 1) { 170 | fprintf (stderr, "inet_pton() failed.\nError message: %s", strerror (status)); 171 | exit (EXIT_FAILURE); 172 | } 173 | 174 | // Destination IPv4 address (32 bits) 175 | if ((status = inet_pton (AF_INET, dst_ip, &(iphdr.ip_dst))) != 1) { 176 | fprintf (stderr, "inet_pton() failed.\nError message: %s", strerror (status)); 177 | exit (EXIT_FAILURE); 178 | } 179 | 180 | // IPv4 header checksum (16 bits): set to 0 when calculating checksum 181 | iphdr.ip_sum = 0; 182 | iphdr.ip_sum = checksum ((uint16_t *) &iphdr, IP4_HDRLEN); 183 | 184 | // UDP header 185 | 186 | // Source port number (16 bits): pick a number 187 | udphdr.source = htons (6790); 188 | 189 | // Destination port number (16 bits): pick a number 190 | udphdr.dest = htons (6790); 191 | 192 | // Length of UDP datagram (16 bits): UDP header + UDP data 193 | udphdr.len = htons (UDP_HDRLEN + datalen); 194 | 195 | // UDP checksum (16 bits) 196 | udphdr.check = udp4_checksum (iphdr, udphdr, data, datalen); 197 | 198 | // Prepare packet. 199 | 200 | // First part is an IPv4 header. 201 | memcpy (packet, &iphdr, IP4_HDRLEN * sizeof (uint8_t)); 202 | 203 | // Next part of packet is upper layer protocol header. 204 | memcpy ((packet + IP4_HDRLEN), &udphdr, UDP_HDRLEN * sizeof (uint8_t)); 205 | 206 | // Finally, add the UDP data. 207 | memcpy (packet + IP4_HDRLEN + UDP_HDRLEN, data, datalen * sizeof (uint8_t)); 208 | 209 | // The kernel is going to prepare layer 2 information (ethernet frame header) for us. 210 | // For that, we need to specify a destination for the kernel in order for it 211 | // to decide where to send the raw datagram. We fill in a struct in_addr with 212 | // the desired destination IP address, and pass this structure to the sendto() function. 213 | memset (&sin, 0, sizeof (struct sockaddr_in)); 214 | sin.sin_family = AF_INET; 215 | sin.sin_addr.s_addr = iphdr.ip_dst.s_addr; 216 | 217 | // Submit request for a raw socket descriptor. 218 | if ((sd = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) { 219 | perror ("socket() failed "); 220 | exit (EXIT_FAILURE); 221 | } 222 | 223 | // Set flag so socket expects us to provide IPv4 header. 224 | if (setsockopt (sd, IPPROTO_IP, IP_HDRINCL, &on, sizeof (on)) < 0) { 225 | perror ("setsockopt() failed to set IP_HDRINCL "); 226 | exit (EXIT_FAILURE); 227 | } 228 | 229 | // Bind socket to interface index. 230 | if (setsockopt (sd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof (ifr)) < 0) { 231 | perror ("setsockopt() failed to bind to interface "); 232 | exit (EXIT_FAILURE); 233 | } 234 | 235 | // Send packet. 236 | if (sendto (sd, packet, IP4_HDRLEN + UDP_HDRLEN + datalen, 0, (struct sockaddr *) &sin, sizeof (struct sockaddr)) < 0) { 237 | perror ("sendto() failed "); 238 | exit (EXIT_FAILURE); 239 | } 240 | 241 | // Close socket descriptor. 242 | close (sd); 243 | 244 | // Free allocated memory. 245 | free (data); 246 | free (packet); 247 | free (interface); 248 | free (target); 249 | free (src_ip); 250 | free (dst_ip); 251 | free (ip_flags); 252 | 253 | return (EXIT_SUCCESS); 254 | } 255 | 256 | // Computing the internet checksum (RFC 1071). 257 | // Note that the internet checksum does not preclude collisions. 258 | uint16_t 259 | checksum (uint16_t *addr, int len) 260 | { 261 | int count = len; 262 | register uint32_t sum = 0; 263 | uint16_t answer = 0; 264 | 265 | // Sum up 2-byte values until none or only one byte left. 266 | while (count > 1) { 267 | sum += *(addr++); 268 | count -= 2; 269 | } 270 | 271 | // Add left-over byte, if any. 272 | if (count > 0) { 273 | sum += *(uint8_t *) addr; 274 | } 275 | 276 | // Fold 32-bit sum into 16 bits; we lose information by doing this, 277 | // increasing the chances of a collision. 278 | // sum = (lower 16 bits) + (upper 16 bits shifted right 16 bits) 279 | while (sum >> 16) { 280 | sum = (sum & 0xffff) + (sum >> 16); 281 | } 282 | 283 | // Checksum is one's compliment of sum. 284 | answer = ~sum; 285 | 286 | return (answer); 287 | } 288 | 289 | // Build IPv4 UDP pseudo-header and call checksum function. 290 | uint16_t 291 | udp4_checksum (struct ip iphdr, struct udphdr udphdr, uint8_t *payload, int payloadlen) 292 | { 293 | char buf[IP_MAXPACKET]; 294 | char *ptr; 295 | int chksumlen = 0; 296 | int i; 297 | 298 | ptr = &buf[0]; // ptr points to beginning of buffer buf 299 | 300 | // Copy source IP address into buf (32 bits) 301 | memcpy (ptr, &iphdr.ip_src.s_addr, sizeof (iphdr.ip_src.s_addr)); 302 | ptr += sizeof (iphdr.ip_src.s_addr); 303 | chksumlen += sizeof (iphdr.ip_src.s_addr); 304 | 305 | // Copy destination IP address into buf (32 bits) 306 | memcpy (ptr, &iphdr.ip_dst.s_addr, sizeof (iphdr.ip_dst.s_addr)); 307 | ptr += sizeof (iphdr.ip_dst.s_addr); 308 | chksumlen += sizeof (iphdr.ip_dst.s_addr); 309 | 310 | // Copy zero field to buf (8 bits) 311 | *ptr = 0; ptr++; 312 | chksumlen += 1; 313 | 314 | // Copy transport layer protocol to buf (8 bits) 315 | memcpy (ptr, &iphdr.ip_p, sizeof (iphdr.ip_p)); 316 | ptr += sizeof (iphdr.ip_p); 317 | chksumlen += sizeof (iphdr.ip_p); 318 | 319 | // Copy UDP length to buf (16 bits) 320 | memcpy (ptr, &udphdr.len, sizeof (udphdr.len)); 321 | ptr += sizeof (udphdr.len); 322 | chksumlen += sizeof (udphdr.len); 323 | 324 | // Copy UDP source port to buf (16 bits) 325 | memcpy (ptr, &udphdr.source, sizeof (udphdr.source)); 326 | ptr += sizeof (udphdr.source); 327 | chksumlen += sizeof (udphdr.source); 328 | 329 | // Copy UDP destination port to buf (16 bits) 330 | memcpy (ptr, &udphdr.dest, sizeof (udphdr.dest)); 331 | ptr += sizeof (udphdr.dest); 332 | chksumlen += sizeof (udphdr.dest); 333 | 334 | // Copy UDP length again to buf (16 bits) 335 | memcpy (ptr, &udphdr.len, sizeof (udphdr.len)); 336 | ptr += sizeof (udphdr.len); 337 | chksumlen += sizeof (udphdr.len); 338 | 339 | // Copy UDP checksum to buf (16 bits) 340 | // Zero, since we don't know it yet 341 | *ptr = 0; ptr++; 342 | *ptr = 0; ptr++; 343 | chksumlen += 2; 344 | 345 | // Copy payload to buf 346 | memcpy (ptr, payload, payloadlen); 347 | ptr += payloadlen; 348 | chksumlen += payloadlen; 349 | 350 | // Pad to the next 16-bit boundary 351 | for (i=0; i