Python Scapy随心所欲研究TCP协议栈


Posted in Python onNovember 20, 2018

1. 前言

如果只需要研究Linux的tcp协议栈行为,只需要使用packetdrill就能够满足我的所有需求。packetdrill才是让我随心所欲地撩tcp协议栈。packetdrill的简单使用手册。

然而悲剧的是,除了要研究Linux的TCP协议栈行为,还需要研究Windows的tcp协议栈的行为,Windows不开源,感觉里面应该有挺多未知的坑。

为了能够重现Windows的tcp协议栈的一些网络行为,这里使用python的scapy进行包构造撩撩Windows的tcp协议栈。scapy在tcp数据报文注入会有一点的时延,这个工具在要求时延严格的场景无法使用(还是packetdrill好用,?澹?U攵阅壳坝龅降某【埃?闱磕苡茫?僭蛞丫?9吡?ython,上手起来比较容易。

2. 基本语法

  • 安装scapy

在Centos 7.2中直接使用yum install 来安装。

yum install scapy.noarch
  • help 能解决大部分问题
[root@localhost ~]# scapy
INFO: Can't import python gnuplot wrapper . Won't be able to plot.
INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().
WARNING: No route found for IPv6 destination :: (no default route?)
Welcome to Scapy (2.2.0)
>>> help(send)

在大部分时候,如果看到不明白的地方,请用help。其次是官方的参考手册

  • 基本语法

ip/tcp/http数据包操纵

>>> IP()
<IP |>
>>>> IP()/TCP()
<IP frag=0 proto=tcp |<TCP |>>
>>>> IP(proto=55)/TCP()
<IP frag=0 proto=55 |<TCP >> 
>>>> Ether()/IP()/TCP()
<Ether type=IPv4 |<IP frag=0 proto=tcp |<TCP |>>>
>>>> IP()/TCP()/"GET /HTTP/1.0\r\n\r\n"   数据部分可以直接使用字符串
<IP frag=0 proto=tcp |<TCP |<Raw load='GET /HTTP/1.0\r\n\r\n' |>>> 
>>>> Ether()/IP()/UDP()
<Ether type=IPv4 |<IP frag=0 proto=udp |<UDP |>>>
>>>> Ether()/IP()/IP()/UDP()
<Ether type=IPv4 |<IP frag=0 proto=ipencap |<IP frag=0 proto=udp |<UDP |>>>>
>>> str(IP())
'E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01'
>>> IP(_)
<IP version=4L ihl=5L tos=0x0 len=20 id=1 flags= frag=0L ttl=64 proto=hopopt 
chksum=0x7ce7 src=127.0.0.1 dst=127.0.0.1 |>
>>> a=Ether()/IP(dst="www.baidu.com")/TCP()/"GET /index.html HTTP/1.0 \n\n"
>>> hexdump(a)
0000  00 03 0F 19 6A 49 08 00 27 FE D8 12 08 00 45 00  ....jI..'.....E.
0010  00 43 00 01 00 00 40 06 70 78 C0 A8 73 C6 B4 61  .C....@.px..s..a
0020  21 6C 00 14 00 50 00 00 00 00 00 00 00 00 50 02  !l...P........P.
0030  20 00 B3 75 00 00 47 45 54 20 2F 69 6E 64 65 78  ..u..GET /index
0040  2E 68 74 6D 6C 20 48 54 54 50 2F 31 2E 30 20 0A  .html HTTP/1.0 .
0050  0A                         .
>>> b=str(a)
>>> b
"\x00\x03\x0f\x19jI\x08\x00'\xfe\xd8\x12\x08\x00E\x00\x00C\x00\x01\x00\x00@\x06px
\xc0\xa8s\xc6\xb4a!l\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xb3u
\x00\x00GET /index.html HTTP/1.0 \n\n"

1.数据包发送

数据包的发送主要包括以下函数send/sendp/sr/sr1/srp 主要区别是:

send函数工作在第三层

send(IP(dst="192.168.115.188")/ICMP())

sendp函数工作在第二层,你可以选择网卡和协议

sendp(Ether()/IP(dst="192.168.115.188",ttl=(1,4)),iface="eth0")

fuzz函数的作用:可以更改一些默认的不可以被计算的值(比如校验和checksums),更改的值是随机的,但是类型是符合字段的值的。

send(IP(dst="www.baidu.com")/UDP()/NTP(version=4),loop=2) #未使用fuzz()
send(IP(dst=" www.baidu.com")/fuzz(UDP()/NTP(version=4)),loop=2) #使用fuzz()

SR()函数用来来发送数据包和接收响应。该函数返回有回应的数据包和没有回应的数据包;该函数也算得上是scapy的核心了,他会返回两个列表数据,一个是answer list 另一个是unanswered

>>> sr(IP(dst="192.168.115.1")/TCP(dport=[21,22,23]))
Begin emission:
Finished to send 3 packets.
***
Received 3 packets, got 3 answers, remaining 0 packets
Results: TCP:3 UDP:0 ICMP:0 Other:0>, Unanswered: TCP:0 UDP:0 ICMP:0 Other:0
>>> ans,unans=_  这也是scapy的核心了
>>> ans.show()
0000 IP / TCP 192.168.115.198:ftp_data > 192.168.115.1:ftp S ==> IP / TCP 192.168.115.1:ftp > 192.168.115.198:ftp_data RA / Padding
0001 IP / TCP 192.168.115.198:ftp_data > 192.168.115.1:ssh S ==> IP / TCP 192.168.115.1:ssh > 192.168.115.198:ftp_data RA / Padding
0002 IP / TCP 192.168.115.198:ftp_data > 192.168.115.1:telnet S ==> IP / TCP 192.168.115.1:telnet > 192.168.115.198:ftp_data SA / Padding 
>>>sr(IP(dst="192.168.115.1")/TCP(dport=[21,22,23]),inter=0.5,retry=-2,timeout=1) 网络环境不好时,也可以追加inter retry timeout等附加信息,

函数sr1()是sr()一个变种,只返回应答发送的分组(或分组集)。这两个函数发送的数据包必须是第3层数据包(IP,ARP等)。而函数SRP()位于第2层(以太网,802.3,等)。

>>> p=sr1(IP(dst="192.168.115.188")/ICMP()/"test")
Begin emission:
.....Finished to send 1 packets.
.*
Received 7 packets, got 1 answers, remaining 0 packets
>>> p
<IP version=4L ihl=5L tos=0x0 len=32 id=26000 flags= frag=0L ttl=128 proto=icmp chksum=0x6c79 src=192.168.115.188 dst=192.168.115.198 options=[] |<ICMP type=echo-reply code=0 chksum=0x1826 id=0x0 seq=0x0 |<Raw load='test' |<Padding load='\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |>>>>
>>> p.show()
###[ IP ]###
 version= 4L
 ihl= 5L
 tos= 0x0
 len= 32
 id= 26000
 flags= 
 frag= 0L
 ttl= 128
 proto= icmp
 chksum= 0x6c79
 src= 192.168.115.188
 dst= 192.168.115.198
 \options\
###[ ICMP ]###
   type= echo-reply
   code= 0
   chksum= 0x1826
   id= 0x0
   seq= 0x0
###[ Raw ]###
    load= 'test'
###[ Padding ]###
      load= '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

1.数据包sniff

a=sniff(count=1,filter="tcp and host 192.168.1.1 and port 80")

使用sniff主要是用于数据包的接收,根据filter设定的条件,将符合条件的数据包接收回来。

3. 场景构造

scapy的缺点是,他只负责构造包,是单向的。不像packetdrill这么完美,packetdrill 不但可以构造包,还能实现系统调用构造不同的场景,还能帮你检查协议栈发出的数据包是否符合预期。撩协tcp协议栈的过程不外乎两端,一端使用系统调用模拟协议栈行为,另外一端则是我们构造的包。常见场景主要是:服务器场景、客户端场景。

  • 服务器场景:

服务器场景使用系统调用(即用户态程序),而客户端则是scapy构造的包。

在这里构造一个简单的三次握手后向服务器端发送数据。为了防止Linux客户端rst。

iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 192.168.56.1 -j DROP
#!/usr/local/bin/python
from scapy.all import *
# VARIABLES
src = sys.argv[1]
dst = sys.argv[2]
sport = random.randint(1024,65535)
dport = int(sys.argv[3])
# SYN
ip=IP(src=src,dst=dst)
SYN=TCP(sport=sport,dport=dport,flags='S',seq=1000)
SYNACK=sr1(ip/SYN)
# ACK
ACK=TCP(sport=sport, dport=dport, flags='A', seq=SYNACK.ack, ack=SYNACK.seq + 1)
send(ip/ACK)

在这里可以安装一个nginx来验证。

  • 客户端场景:

客户端场景使用系统调用(即用户态程序),而服务器端则是scapy构造包。

在这里使用scapy构造一个简单的http服务器。为了防止协议栈发送RST,需要对iptables进行设置。

iptables -A OUTPUT -p tcp --tcp-flags RST RST --sport 80 -j DROP
#!/usr/bin/python
from scapy.all import *
# Interacts with a client by going through the three-way handshake.
# Shuts down the connection immediately after the connection has been established.
# Akaljed Dec 2010, http://www.akaljed.wordpress.com
# Wait for client to connect.
a=sniff(count=1,filter="tcp and host 192.168.1.1 and port 80")
# some variables for later use.
ValueOfPort=a[0].sport
SeqNr=a[0].seq
AckNr=a[0].seq+1
# Generating the IP layer:
ip=IP(src="192.168.1.1", dst="192.168.1.2")
# Generating TCP layer:
TCP_SYNACK=TCP(sport=80, dport=ValueOfPort, flags="SA", seq=SeqNr, ack=AckNr, options=[('MSS', 1460)])
#send SYNACK to remote host AND receive ACK.
ANSWER=sr1(ip/TCP_SYNACK)
# Capture next TCP packets with dport 80. (contains http GET request)
GEThttp = sniff(filter="tcp and port 80",count=1,prn=lambda x:x.sprintf("{IP:%IP.src%: %TCP.dport%}"))
AckNr=AckNr+len(GEThttp[0].load)
SeqNr=a[0].seq+1
# Print the GET request
# (Sanity check: size of data should be greater than 1.)
if len(GEThttp[0].load)>1: print GEThttp[0].load
# Generate custom http file content.
html1="HTTP/1.1 200 OK\x0d\x0aDate: Wed, 29 Sep 2010 20:19:05 GMT\x0d\x0aServer: Testserver\x0d\x0aConnection: Keep-Alive\x0d\x0aContent-Type: text/html; charset=UTF-8\x0d\x0aContent-Length: 291\x0d\x0a\x0d\x0a<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\"><html><head><title>Testserver</title></head><body bgcolor=\"black\" text=\"white\" link=\"blue\" vlink=\"purple\" alink=\"red\"><p><font face=\"Courier\" color=\"blue\">-Welcome to test server-------------------------------</font></p></body></html>"
# Generate TCP data
data1=TCP(sport=80, dport=ValueOfPort, flags="PA", seq=SeqNr, ack=AckNr, options=[('MSS', 1460)])
# Construct whole network packet, send it and fetch the returning ack.
ackdata1=sr1(ip/data1/html1)
# Store new sequence number.
SeqNr=ackdata1.ack
# Generate RST-ACK packet
Bye=TCP(sport=80, dport=ValueOfPort, flags="FA", seq=SeqNr, ack=AckNr, options=[('MSS', 1460)])
send(ip/Bye)
# The End

这个服务器只需要使用wget或者curl就可以实现验证了。

4. 参考资料

http://www.secdev.org/projects/scapy/doc/usage.html#starting-scapy

https://akaljed.wordpress.com/2010/12/12/scapy-as-webserver/

http://lost-and-found-narihiro.blogspot.com/2012/12/scapy-simple-web-server-with-scapy.html

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。如果你想了解更多相关内容请查看下面相关链接

Python 相关文章推荐
Python实现字典依据value排序
Feb 24 Python
用Pygal绘制直方图代码示例
Dec 07 Python
朴素贝叶斯Python实例及解析
Nov 19 Python
Python txt文件加入字典并查询的方法
Jan 15 Python
django admin组件使用方法详解
Jul 19 Python
Pytorch反向求导更新网络参数的方法
Aug 17 Python
Python拆分大型CSV文件代码实例
Oct 07 Python
Python标准库itertools的使用方法
Jan 17 Python
详解django使用include无法跳转的解决方法
Mar 19 Python
Python基于traceback模块获取异常信息
Jul 23 Python
Python猫眼电影最近上映的电影票房信息
Sep 18 Python
pandas 实现将NaN转换为None
May 14 Python
python版飞机大战代码分享
Nov 20 #Python
pygame实现雷电游戏雏形开发
Nov 20 #Python
pygame游戏之旅 游戏中添加显示文字
Nov 20 #Python
pygame游戏之旅 添加键盘按键的方法
Nov 20 #Python
pygame游戏之旅 载入小车图片、更新窗口
Nov 20 #Python
一文带你了解Python中的字符串是什么
Nov 20 #Python
pygame游戏之旅 创建游戏窗口界面
Nov 20 #Python
You might like
php从给定url获取文件扩展名的方法
2015/03/14 PHP
PHP实现动态创建XML文档的方法
2018/03/30 PHP
PHP实现的简单组词算法示例
2018/04/10 PHP
Laravel框架路由设置与使用示例
2018/06/12 PHP
JavaScript表达式:URL 协议介绍
2013/03/10 Javascript
基于JavaScript实现 获取鼠标点击位置坐标的方法
2013/04/12 Javascript
解析jQuery的三种bind/One/Live事件绑定使用方法
2013/12/30 Javascript
中止javascript执行的方法
2014/02/14 Javascript
javascript ajax的5种状态介绍
2014/08/18 Javascript
web前端设计师们常用的jQuery特效插件汇总
2014/12/07 Javascript
javascript结合Canvas 实现简易的圆形时钟
2015/03/11 Javascript
jQuery实现调整表格单列顺序完整实例
2016/06/20 Javascript
JAVA Web实时消息后台服务器推送技术---GoEasy
2016/11/04 Javascript
js判断密码强度的方法
2020/03/18 Javascript
Vue-CLI 3 scp2自动部署项目至服务器的方法
2020/07/24 Javascript
jQuery zTree如何改变指定节点文本样式
2020/10/16 jQuery
[06:24]DOTA2 2015国际邀请赛中国区预选赛第二日TOP10
2015/05/27 DOTA
[01:01:18]VP vs NIP 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
Python切片用法实例教程
2014/09/08 Python
win10环境下python3.5安装步骤图文教程
2017/02/03 Python
Python基于生成器迭代实现的八皇后问题示例
2018/05/23 Python
python里dict变成list实例方法
2019/06/26 Python
使用卷积神经网络(CNN)做人脸识别的示例代码
2020/03/27 Python
TensorFlow低版本代码自动升级为1.0版本
2021/02/20 Python
浅谈Python xlwings 读取Excel文件的正确姿势
2021/02/26 Python
5分钟弄清楚html5的drag and drop(小结)
2019/04/10 HTML / CSS
复古斯堪的纳维亚儿童服装:Baby go Retro
2017/09/09 全球购物
现代绅士日常奢侈品:Todd Snyder
2019/12/13 全球购物
Bonprix法国:时尚、鞋子、家居
2020/12/29 全球购物
全球最受追捧的运动服品牌领先数字目的地:Stylerunner
2020/11/25 全球购物
轻化专业学生实习自我鉴定
2013/09/20 职场文书
毕业生学校推荐信范文
2014/05/21 职场文书
集结号观后感
2015/06/08 职场文书
信息技术教研组工作总结
2015/08/13 职场文书
新课程改革心得体会
2016/01/22 职场文书
redis三种高可用方式部署的实现
2021/05/11 Redis