workminer之dht通信部分

workminer是通过SSH爆破传播的挖矿木马,感染后会释放xmrig挖矿程序利用主机的CPU挖取北方门罗币。该样本能够执行特定的指令,指令保存在一个配置文件config中,config文件类似于xml文件,里面有要执行的指令和参数,样本中内嵌了一个config,无攻击指令。该样本通过使用自己扩展的DHT协议来更新config,有两种方式,一是访问8个引导节点(样本内嵌,公开的)加入p2p网络,通过来寻找具有特定前缀id的同伙,来更新config,二是会随机扫描其它IP的UDP端口(IP是随机生成的,端口是一个范围内的随机端口),若扫中其它受害主机,受害主机会返回config,用这种方式来更新config。
workminer属于Mozi僵尸网络组织,主要攻击Iot设备,作者已于2021年被中国警方逮捕,该僵尸网络目前在互联网上还持续活跃。本文主要介绍workminer的dht通信部分,workminer使用go和C语言混合编译,C语言主要用于与同伙节点通信,拉取和更新config。

基础知识一:Linux系统调用

为了识别样本中的socket相关函数,我们需要了解Linux系统调用,尤其是socket相关函数调用的基础知识。
linux系统中使用int 80来调用linux系统api,linux中调用socket相关函数,是通用系统调用sys_socketcall的实现的,该接口的系统调用号为102,函数定义为

#include <sys/socket.h>
int socketcall(int subcall, unsigned long *args);
调用的时候,寄存器eax保存调用号,ebx为第1个参数,ecx为第2个参数,通过eax和ebx就能知道是哪个api
ebx int  subcall
ecx unsigned long *
eax 102
int 0x80

第一个参数subcall要调用的api号,在 /usr/include/linux/net.h中定义,使用find命令找到这个文件

# cat /usr/include/linux/net.h | grep SYS                     
#define SYS_SOCKET      1               /* sys_socket(2)                */
#define SYS_BIND        2               /* sys_bind(2)                  */
#define SYS_CONNECT     3               /* sys_connect(2)               */
#define SYS_LISTEN      4               /* sys_listen(2)                */
#define SYS_ACCEPT      5               /* sys_accept(2)                */
#define SYS_GETSOCKNAME 6               /* sys_getsockname(2)           */
#define SYS_GETPEERNAME 7               /* sys_getpeername(2)           */
#define SYS_SOCKETPAIR  8               /* sys_socketpair(2)            */
#define SYS_SEND        9               /* sys_send(2)                  */
#define SYS_RECV        10              /* sys_recv(2)                  */
#define SYS_SENDTO      11              /* sys_sendto(2)                */
#define SYS_RECVFROM    12              /* sys_recvfrom(2)              */
#define SYS_SHUTDOWN    13              /* sys_shutdown(2)              */
#define SYS_SETSOCKOPT  14              /* sys_setsockopt(2)            */
#define SYS_GETSOCKOPT  15              /* sys_getsockopt(2)            */
#define SYS_SENDMSG     16              /* sys_sendmsg(2)               */
#define SYS_RECVMSG     17              /* sys_recvmsg(2)               */
#define SYS_ACCEPT4     18              /* sys_accept4(2)               */
#define SYS_RECVMMSG    19              /* sys_recvmmsg(2)              */
#define SYS_SENDMMSG    20              /* sys_sendmmsg(2)              */

DHT协议基础

DHT协议是一种BT协议,使用udp来通信,通信内容使用bencode编码。可参考bt协议详解 DHT篇(下) - 蓝猫163 - 博客园 (cnblogs.com)。

DHT协议有以下几种报文

ping

最基础的请求就是ping。这时KPRC协议中的“q”=“ping”。Ping请求包含一个参数id,它是一个20字节的字符串包含了发送者网络字节序的nodeID。对应的ping回复也包含一个参数id,包含了回复者的nodeID。
示例

ping请求={"t":"aa", "y":"q","q":"ping", "a":{"id":"abcdefghij0123456789"}}  B编码=d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe 
回复={"t":"aa", "y":"r", "r":{"id":"mnopqrstuvwxyz123456"}} 
B编码=d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re

find_node

Findnode被用来查找给定ID的node的联系信息。这时KPRC协议中的q=“find_node”。find_node请求包含2个参数,第一个参数是id,包含了请求node的nodeID。第二个参数是target,包含了请求者正在查找的node的nodeID。当一个node接收到了find_node的请求,他应该给出对应的回复,回复中包含2个关键字id和nodes,nodes是一个字符串类型,包含了被请求节点的路由表中最接近目标node的K(8)个最接近的nodes的联系信息
示例

find_node请求={"t":"aa", "y":"q","q":"find_node", "a":{"id":"abcdefghij0123456789","target":"mnopqrstuvwxyz123456"}}
B编码=d1:ad2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe
回复={"t":"aa", "y":"r", "r":{"id":"0123456789abcdefghij", "nodes":"def456..."}}
B编码=d1:rd2:id20:0123456789abcdefghij5:nodes9:def456...e1:t2:aa1:y1:re

get_peers

这个请求用来表明发出announce_peer请求的node,正在某个端口下载torrent文件。announce_peer包含4个参数。第一个参数是id,包含了请求node的nodeID;第二个参数是info_hash,包含了torrent文件的infohash;第三个参数是port包含了整型的端口号,表明peer在哪个端口下载;第四个参数数是token,这是在之前的get_peers请求中收到的回复中包含的。收到announce_peer请求的node必须检查这个token与之前我们回复给这个节点get_peers的token是否相同。如果相同,那么被请求的节点将记录发送announce_peer节点的IP和请求中包含的port端口号在peer联系信息中对应的infohash下。
这个请求报文本样本没有
其响应报文的处理同find_node报文

announce_peer

这个请求用来表明发出announce_peer请求的node,正在某个端口下载torrent文件。announce_peer包含4个参数。第一个参数是id,包含了请求node的nodeID;第二个参数是info_hash,包含了torrent文件的infohash;第三个参数是port包含了整型的端口号,表明peer在哪个端口下载;第四个参数数是token,这是在之前的get_peers请求中收到的回复中包含的。收到announce_peer请求的node必须检查这个token与之前我们回复给这个节点get_peers的token是否相同。如果相同,那么被请求的节点将记录发送announce_peer节点的IP和请求中包含的port端口号在peer联系信息中对应的infohash下。
这个请求报文本样本没有
不支持这个报文

workminer的DHT通信部分

workminer通过自己改良过的DHT协议来请求和config,下面来介绍workminer的dht通信过程。
首先会生成一个特殊的version,则workminer节点发出的dht报文中都会有这个version字段。version字段共9个字节,前5个字节为1:v43:(31 3A 76 34 3A),后面4个字节中根据下面的规则生成的。
第1个字节是随机产生的,第2个字节是硬编码的0x42或由config文件中[ver]字段指定,第3个字节和第4个字节是通过自定义的校验算法生成的。

下面是抓取的ping包,通过这个特殊的标记,wokerminer在处理接收到的包时就能够识别出哪些包是正常的dht包,哪些包中由同伙发出来的,对于正常的dht协议的请求和响应包,走正常的处理流程,对于同伙的包(ping和find_node),就会交换config。

上面这个计算校验和的算法是识别同伙的关键,如下代码,若结果为0表示是同伙的包,若不是0则是正常的dht数据包。

// 根据version字段来判断是同伙发来的包
int  GenCheckSum_82AAB68(unsigned __int16 a1, char *buf_a2, unsigned int buflen_a3)
{char j; // [esp+5h] [ebp-Bh]unsigned int i; // [esp+Ch] [ebp-4h]for ( i = 0; i < buflen_a3; ++i ){a1 ^= (unsigned __int8)buf_a2[i] << 8;for ( j = 8; j; --j ){if ( (a1 & 0x8000) != 0 )a1 = (2 * a1) ^ 0x8005;elsea1 *= 2;}}return a1;}
int main(){char version[] = { 0x31,0x3a ,0x76 ,0x34 ,0x3a ,0x26 ,0x42 ,0x53 ,0x77};int checksum = GenCheckSum_82AAB68(0,version,9);if(checksum == 0){puts("Mozi pkt");}else{puts("Normal dht pkt");}return 0;
}

从下列中随机选择一个端口,若端口为0,则取random()%64510+1024,监听这个udp端口。

port_list[28] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,8080,8000,1900,1434,1027,6881,4000
30301,5353,11211,8082,8081,8083,5060]

为这UDP端口添加iptables规则。

使用异或的方式解密了两个公钥,异或使用密钥为
4E 66 5A 8F 80 C8 AC 23 8D AC 47 06 D5 4F 6F 7E
将内嵌在样本的config提取出来,共624字节,前524字节为加密的config内容,后96字节为文件的签名。使用第1个公钥来对加密的config验证签名。若验证签名通过,则使用go语言来解析解密后的config,执行config中的指令。若config中含有[ver]标签,使用ver标签的值来重新计算数据包version字段。
公钥为

第一个公钥 用于验证config前528字节的完整性
02 d5 d5 e7 41 ee dc c8 10 6d 2f 48 0d 04 12 21 
27 39 c7 45 0d 2a d1 40 72 01 d1 8b cd c4 16 65 
76 57 c1 9d e9 bb 05 0d 3b cf 6e 70 79 60 f1 ea 
ef第二个公钥 用于验证config前428字节的完整性
02 c0 a1 43 78 53 be 3c c4 c8 0a 29 e9 58 bf c6
a7 1b 7e ab 72 15 1d 64 64 98 95 c4 6a 48 c3 2d
6c 39 82 1d 7e 25 f3 80 44 f7 2d 10 6b cb 2f 09 
c6解密后的config
[ss]ssh[/ss][cpu].x[/cpu][hp]88888888[/hp]

构造自身nodeid,nodeid有20个字节,有以下几种可能
一 1/1000的可能 使用字符串888888d1:ad2:id20:作为前缀,即38 38 38 38 38 38 64 31 3A 61 64 32 3A 69 64 32 30 3A xx xx,后两个字节为随机值。
二 49/1000的可能,使用888888作为前缀,即38 38 38 38 38 38 xx xx xx xx xx xx xx xx xx xx xx xx xx xx,后面12个字节为随机值
三 950/1000的可能,使用随机生成的nodeid.

接着使用上面的构造的selfNodeId,向内置的8个引导节点发送ping请求。TransactionId使用6E 70 00 00

router.bittorrent.com:6881 
bttracker.debian.org:6881
router.utorrent.com:6881
dht.transmissionbt.com:6881
212.129.33.59:6881
82.221.103.244:6881
130.239.18.159:6881
87.98.162.88:6881


使用wireshark抓包可以看到包的内容

这个包的含义如下,

请求参数(request arguments)
a:{id:38383838383864313a6164323a696432303a9550}
请求类型(request type)
q:ping
会话Id(Transaction Id)
t:706e0000
版本(Version)
v:1942d177
消息类型(Message Type)q表示请求
y:q

设置一个全局的队列,用来存储的节点信息,这个队列结构如下所示。

struct NODE
{char node_id[20];//节点的node_idchar addr[128];//地址信息 可存储Ipv4和ipv6的sockaddr结构int addrlen;//sockaddr结构的长度
};
struct NODE_LIST
{int head;//队列头int end;//队列尾NODE nodelist[64];//队列,队列大小为64
};

下面进入一个死循环,不断的向外发包和处理响应包。
每隔899秒,向内嵌的8个公共节点发送ping请求。
从config中取出[nd]标签的值,这是一个以,分割的列表,如url1,ip,url2,...,向其中的每个地址发送ping请求,使用端口6881,会话id使用6E 70 00 00。(内嵌的config中没有nd标签,不会执行)

每隔299秒,若node_list不为空的话,从其中随机取一个node,发送find_node请求。
find_node消息中的target_node_id使用以下的规则构造,有4种可能。
一是 targetid完全使用随机值,即20个随机的字节
二是 将 123888d1:ad2:id20: 作为target_nodeid的前18个字节,后2个字节随机
三是 以888888作为target_nodeid的前6个字节,后14个字节随机
四是 以888888d1:ad2:id20:作为target_nodeid的前18个字节,后2个字节随机
会话id为6E 66 00 00

构造一段随机长度的buf,长度为从[3,97]中取一个随机数,将第二个字节为0x2e,使用前面的算法计算校验值,将2个字节的校验值添加到buf末尾。

['8080','8000', '1900', '1434', '1027', '6881', '4000', '30301', '5353', '11211', '8082', '8081', '8083', '5060']中随机取一个端口,构造一个随机的IP地址,将上面构造好的数据发出去。执行100次。
当同伙收到这个报文后,会将加密的config发回来,这里将这个报文定义为GET_CONF报文。

每隔599秒,向node_list中所有节点发送一次find_node请求,target_nodeid有两种情况,会话id为6E660000.
一是 以888888d1:ad2:id20:为前缀,后面为随机值
二是 以888888前缀后面为随机值

下面是处理接收到的数据包的逻辑
首先使用select和recvfrom接收udp的包,将dht相关的包中关键字段解析出来,如下图所示。对dht数据包进行解析,对其中的关键的字段提取,并返回数据包的类型,根据这个类型值进入不同的处理逻辑,提取出来的字段dht字段有

version: 可判断是否为同伙的包
transctionid :可判断ping和find_node的响应
nodeid :对方的nodeid
targetid: find_node请求的目标id
nodes :find_node响应的nodes列表


数据的类型有以下几种

enum DHTPktType : int
{DHTPktType_OTHER = -1,//其它DHTPktType_ERROR = 0,//报错信息DHTPktType_RESPONSE,//响应包 包括ping、find_node的响应DHTPktType_PING,// ping请求DHTPktType_FIND_NODE,// find请求DHTPktType_GET_PEERS,//get_peers请求DHTPktType_ANNOUNCE_PEER,//annouce_peer 请求DHTPktType_MOZI_CNF_REQ,//get_conf请求 即上面的GET_CONF报文DHTPktType_MOZI_ENCODING_CNF //接收到对方发来加密的config
};

下面介绍这几种报文的处理逻辑

ping、find_node的响应

若会话id为6E 70 00 00,说明是本机发出去的ping请求的响应包,将对方的nodeid、ip、port添加进入全局队列。
若会话id为6E 66 00 00,说明是本机发出来的find_node请求的响应,将对方的信息加入队列,检查version字段。若不是Mozi节点发来的包,将其中的nodes字段中8个节点加入队列。若是Mozi节点的包,且nodes大小为624,这其实是加密的config,处理这个config。

ping请求

若收到别的节点发来的ping请求,将对方信息加入队列,进行正常的回复。

find_node、get_peers的请求

这两类请求的处理逻辑相同。若是非同伙的包,按照正常的dht协议来处理,从队列中取8个节点信息发给对方。
若是同伙发来的请求,有4/5的概率,会将自身加密的config填充进nodes字段发送给对方。

announce_peer请求

这类请求不支撑,发送一个error信息。

GET_CONF请求

这不是DHT请求,就是前面随机构造的数据包,数据包长度<=99字节,最后两个字段是校验值,当节点收到此请求,会将自身的config发送给对方,大小为624字节。

收到对方的config

若不是DHT报文,且大于99字节,这是别的节点发来的加密的config,处理这个config.

config的处理

对同伙获取的config大小为624字段,其中前528字段是config1,后面96字段是签名信息,使用第1个公钥验证其完整性,
对于config1,取其前428为config2,428到524的96字段为签名信息,使用第2个公钥签证其完整性。
然后对config2进行解密,向对方节点发送一个ping请求,将config文件中的[cpu]、[ss]、[sv]三个标签的值,提取hp字段,这是的nodeid的前缀,提取ver字段,重新计算verison。

cpu 不知道含义
ss 不知道含义
sv 不知道含义
hp nodeid的前缀
ver 这是一个byte值,作为version字段的第二个字节

最后调用go函数main_deal_conf来处理config,提取出下列标签,进而执行不同的操作。

slan 这是个开关变量,作用未知
swan 这是个开关变量,未知
spl 这是一个url,用于下载更新的bash脚本
sdf 格式为"url|filename",会从url中下载文件保存为/tmp/+filename
sud 其中含有一个url,用于更新本地的病毒母体
ssh 其中含有一个url,用于下载bash脚本来执行
sdr 用于下载文件执行
srn 用于执行命令
scount 指向用于回连的url,默认为`http://ia.51.la/go1`,受害者会周期性的访问这个地址,用于便于攻击者知道受害者的信息

IOC

如何快速识别workminer的流量,可以根据下面的特征

正常的dht报文的version字段符合文中的校验算法
nodeid以88888、88888888、888888d1:ad2:id20:开头
会话id中为6E 70 00 00或6E 66 00 00
udp的载荷小于99,符合文中的校验算法
upd的载荷大小为624
有对外随机的udp扫描行为,目标端口为['8080','8000', '1900', '1434', '1027', '6881', '4000', '30301', '5353', '11211', '8082', '8081', '8083', '5060']访问url
http://ia.51.la/go1连接下列地址udp
router.bittorrent.com:6881 
bttracker.debian.org:6881
router.utorrent.com:6881
dht.transmissionbt.com:6881
212.129.33.59:6881
82.221.103.244:6881
130.239.18.159:6881
87.98.162.88:6881访问门罗币矿池
xmr.crypto-pool.fr:6666扫描TCP以下端口
2222
3389
22222
443
55554
9000
2223
9090
8888
8022
6000
9999
2323
2002
7777
2022
666
444
5555
222
26
2382
830
4118
23
50000流量中含有
SSH-2.0-Go

总结

workminer的作者使用了一种变形的dht协议来更新config,确认非常聪明,具有很好的隐蔽性。config 文件中有些标签的含义还是没有分析清楚。

参考资料

  • Linux系统调用 int 80h int 0x80_怎么寻找int80指令-CSDN博客
  • Linux system call table 정리(32bit, 64bit) (tistory.com)
  • 深度追踪Mozi僵尸网络:360安全大脑精准溯源,揪出幕后黑手_360社区
  • BitTorrent 分布式散列表(DHT)协议详解 | 寂静花园 (addesp.com)
  • bt协议详解 DHT篇(下) - 蓝猫163 - 博客园 (cnblogs.com)
  • P2P Botnet: Mozi分析报告 (360.com)
  • P2P僵尸网络深度追踪——Mozi(二)二叉树吃瓜记-安全客 - 安全资讯平台 (anquanke.com)
  • 『P2P僵尸网络漏洞研究——mozi』 netgear路由器漏洞复现-安全客 - 安全资讯平台 (anquanke.com)
  • 【转】ECDSA 签名验证原理及C语言实现_ecdsa 嵌入式c语言-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/320608.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Three.js纹理贴图

偏移 旋转 重复 纹理显示的清晰度 <template><div id"webgl"></div> </template><script setup> import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js;const scene new THREE…

数据库SQL语言实战(七)

前言 这次的有一点点难~~~~~我也写了好久 练习题 题目一 在学生表pub.student中统计名字&#xff08;姓名的第一位是姓氏&#xff0c;其余为名字&#xff0c;不考虑复姓&#xff09;的使用的频率&#xff0c;将统计结果放入表test5_01中 create table test5_01(First_name…

【notes2】并发,IO,内存

文章目录 1.线程/协程/异步&#xff1a;并发对应硬件资源是cpu&#xff0c;线程是操作系统如何利用cpu资源的一种抽象2.并发&#xff1a;cpu&#xff0c;线程2.1 可见性&#xff1a;volatile2.2 原子性&#xff08;读写原子&#xff09;&#xff1a;AtomicInteger/synchronized…

SparkSql介绍

概述 SparkSQL&#xff0c;顾名思义&#xff0c;就是Spark生态体系中的构建在SparkCore基础之上的一个基于SQL的计算模块。SparkSQL的前身不叫SparkSQL&#xff0c;而叫Shark&#xff0c;最开始的时候底层代码优化&#xff0c;sql的解析、执行引擎等等完全基于Hive&#xff0c…

React中的高阶组件的封装,高阶函数,HOC的含义及用法:

含义及作用: 高阶函数代码案例: 调用高阶组价:

软件测试与管理:黑盒测试-等价类划分法和 边界值分析法

知识思维导图&#xff1a; 例题1&#xff1a;日期检查功能的等价类划分 设有一个档案管理系统&#xff0c;要求用户输入以年月表示的日期。假设日期限定在1990年1月~2049年12月&#xff0c;并规定日期由6位数字字符组成&#xff0c;前4位表示年&#xff0c;后2位表示月。现用等…

计算机组成原理实验一 寄存器实验

目录 实验目的和要求 实验环境 实验内容与过程 连接线表 将8AH写入A寄存器 将6cH写入W寄存器 实验结果与分析 实验箱主要部件 将55H写入A寄存器 将66H写入W寄存器 按住STEP脉冲键实验现象? (实验箱中有什么变化) 放开STEP 键实验现象? (实验箱中有什么变化) 数据…

proxy代理面试题

1、动态属性值 const r1add[1][2][3]4//输出10 const r2add[10][20]30//输出60 const r3add[100][200][300]400//输出1000柯里化&#xff0c;有参考下文 https://blog.csdn.net/p1967914901/article/details/127621032 add 是对象&#xff0c;通过链式传入属性求和返回结果&a…

蛋白质/聚合物防污的机器学习(材料基因组计划)

前言&#xff1a;对于采用机器学习去研究聚合物的防污性能&#xff0c;以及或者其他性质。目前根据我的了解我认为最困难的点有三条&#xff1a; 其一&#xff1a;数据&#xff0c;对于将要训练的数据必须要有三点要求&#xff0c;1.数据要多&#xff0c;也就是大数据&#xff…

毕设:邮件分发系统

文章目录 前言一、登录1.邮箱登录2.账号登录 二、注册三、首页四、写邮件五、收邮件六、草稿箱七、垃圾箱八、已发送九、通讯录十、用户管理十一、邮件管理十二、登录日志总结 前言 分享一下邮件分发系统 一、登录 1.邮箱登录 2.账号登录 二、注册 三、首页 首页有邮件信息&…

【蓝桥杯备赛国赛】5-5

文章目录 求阶乘双子数 求阶乘 求阶乘 分析k的范围&#xff0c;10的18次方。这个数字很大 想要末尾有0的存在必须要2和5&#xff0c;但是通过分析2的数目应该是远远多于5的&#xff0c;所以只要5的数目够多即可。所以for循环的层次也是10的九次方以上&#xff0c;必然会超时&…

光端机(2)——光纤通信学习笔记九

学习笔记里面只关注基本原理和概念&#xff0c;复杂的公式和推导都没有涉及 光端机 光发射机 作用&#xff1a;实现电光转换。将来自电端机的电信号对光源发出的光波进行调制&#xff0c;然后将调制好的光信号耦合到光线中传输。 基本性能要求 1.合适的发光波长&#xff08;光…

GateWay检查接口耗时

添加gateway依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId> </dependency>创建一个LogTimeGateWayFilterFactory类&#xff0c;可以不是这个名字但是后面必须是x…

webstorm 常用插件

安装插件步骤&#xff1a; 打开软件&#xff0c;文件 -- 设置-- 插件 -- 输入插件名称 -- 安装 代码截图: code screenShots 先选中代码&#xff0c;按 ctrl shift alt a&#xff0c;就可截取选中的代码颜色注释: comments highlighter 对注释的文字改变颜色高亮成对符号: h…

设计模式Java实现-建造者模式

楔子 小七在2019年的时候&#xff0c;就想写一个关于设计模式的专栏&#xff0c;但是最终却半途而废了。粗略一想&#xff0c;如果做完一件事要100分钟&#xff0c;小七用3分钟热情做的事&#xff0c;最少也能完成10件事情了。所以这一次&#xff0c;一定要把他做完&#xff0…

【前端】HTML实现个人简历信息填写页面

文章目录 前言一、综合案例&#xff1a;个人简历信息填写页面 前言 这篇博客仅仅是对HTML的基本结构进行了一些说明&#xff0c;关于HTML的更多讲解以及CSS、Javascript部分的讲解可以关注一下下面的专栏&#xff0c;会持续更新的。 链接&#xff1a; Web前端学习专栏 下面我对…

【毕业设计】基于SSM的运动用品商城的设计与实现

1.项目介绍 在这个日益数字化和信息化的时代&#xff0c;随着人们购物习惯的转变&#xff0c;传统的实体商店已经无法满足人们日益增长的在线购物需求。因此&#xff0c;基于SSM&#xff08;Spring Spring MVC MyBatis&#xff09;框架的运动用品商城项目应运而生&#xff0…

LearnOpenGL(七)之摄像机

一、摄像机/观察空间 当我们讨论摄像机/观察空间(Camera/View Space)的时候&#xff0c;是在讨论以摄像机&#xff08;人&#xff09;的视角作为场景原点时场景中所有的顶点坐标&#xff1a;观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。要定义一个摄像机…

探索Kimi模型AI:革新人工智能的未来

探索Kimi模型AI&#xff1a;革新人工智能的未来 人工智能&#xff08;AI&#xff09;技术的发展已经取得了巨大的进步&#xff0c;为我们的生活带来了许多便利和创新。在这个充满活力和竞争的领域中&#xff0c;Kimi模型AI以其独特的设计和功能吸引了人们的注意。本文将深入探…

final关键词

基本介绍 final 中文意思是&#xff1a;最后的&#xff0c;最终的final可以修饰 类、属性、方法和局部变量何时会用到final&#xff1a; 1&#xff0c;当不希望类被继承时&#xff0c;可以用final修饰 2&#xff0c;当不希望父类的某个方法被子类覆盖/重写&#xff08;overrid…