36.网络游戏逆向分析与漏洞攻防-游戏网络通信数据解析-数据解码器的实现

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!

如果看不懂、不知道现在做的什么,那就跟着做完看效果

内容参考于:易道云信息技术研究院VIP课

上一个内容:35.登录成功数据包内容分析

码云地址(master 分支):https://gitee.com/dye_your_fingers/titan

码云版本号:9474c79b07d63defaf0ca32e3cd3df21f8625505

代码下载地址,在 titan 目录下,文件名为:titan-数据解码器的实现.zip

链接:https://pan.baidu.com/s/1W-JpUcGOWbSJmMdmtMzYZg

提取码:q9n5

--来自百度网盘超级会员V4的分享

HOOK引擎,文件名为:黑兔sdk升级版.zip

链接:https://pan.baidu.com/s/1IB-Zs6hi3yU8LC2f-8hIEw

提取码:78h8

--来自百度网盘超级会员V4的分享

以 34.登录数据包的监视与模拟 它的代码为基础进行修改

首先添加一个类:

然后把所有角色都删了,看看没有角色登录之后的数据包,如下图没有角色的登录成功数据包

效果图:本次实现了,解读数据解析约定的数据包

新加 EnCode.h文件:用于数据解析约定数据包解析,一次只能解析一个

#pragma onceclass EnCode
{
private:char un[2]{};// 这个是为了内存对齐
public:char index = 0;// op + un[3]是四字节,op与buffer是6字节,如果自动内存对齐op与buffer可能不会挨在一起char op = 0;union{char dataPool[0x8];int lenth;char byte;short stval;int val;float fval;double dbval;long long lval = 0;};char* pointer = 0;
public:EnCode(char*& buff, unsigned int& _len, char ExIndex = 0);~EnCode();
};

新加 EnCode.cpp文件:用于数据解析约定数据包解析,一次只能解析一个

#include "pch.h"
#include "EnCode.h"
#include "extern_all.h"/*buff是数据包_len暂时没用ExIndex是解析方式,因为分析的时候发现6有时是char类型有时是char*类型看懂此方法需要分析手动分析一次数据包(数据解析约定的数据包)然后带着数据包去看这个函数
*/
EnCode::EnCode(char*& buff, unsigned int& _len,char ExIndex)
{index = ExIndex;op = buff[0];int len = data_desc[index][op].lenth;/*比如 07 0A 00 00 00 34 00 33 00 39 00 39 00 00 00buff + 1 的结果是 0A 00 00 00 34 00 33 00 39 00 39 00 00 00len是 07 所代表的类型的长度,也就是0A 00 00 00 这个东西memcpy(dataPool, buff + 1, len);也就是把 0A 00 00 00 复制到 dataPool里然后从下方的if ((op == 0x7))得到它的字符串*/memcpy(dataPool, buff + 1, len);buff = buff + 1 + len;// 下一个结构if (index == 0) {if ((op == 0x7)) {/*dataPool 与 lenth是一个联合体,联合体的特性就是所有变量共用一个内存,所以 dataPool 的值就是lenth的值从上方的注释得知现在dataPool的值是0A 00 00 00然后通过 lenth去读 0A 00 00 00结果就是十进制的 10然后创建一个10字节的空间给pointer然后在通过 memcpy(pointer, buff, lenth); 把字符串赋值给pointer*/pointer = new char[lenth];memcpy(pointer, buff, lenth);buff = buff + lenth;// 指向下一个字符串}}if (index == 1) {if ((op == 0x06)||(op == 0x7)) {pointer = new char[lenth];memcpy(pointer, buff, lenth);buff = buff + lenth;}}
}EnCode::~EnCode()
{if(pointer)delete[] pointer;
}

GameWinSock.cpp文件的修改:新加 OnloginOk函数,修改了 Init函数

#include "pch.h"
#include "GameWinSock.h"
#include "extern_all.h"
#include "NetClass.h"
#include "EnCode.h"typedef bool(* DealProc)(char*&, unsigned&);DealProc SendDealProc[0x100];
DealProc RecvDealProc[0x100];GameWinSock::PROC GameWinSock::_OnConnect{};
GameWinSock::PROC GameWinSock::_OnSend{};
GameWinSock::PROC GameWinSock::_OnRecv{};bool DeafaultDeal(char*&, unsigned&) { return true; }// 登录数据包的处理
bool Onlogin(char *& buff, unsigned& len) { /* 修改账号密码len = sizeof(DATA_LOGIN) + 1;buff = new char[len];DATA_LOGIN data;PDATALOGIN _data = &data;buff[0] = 0x2;CStringA _id = "";// 补充账号CStringA _pass = "";// 补充密码memcpy(_data->Id, _id.GetBuffer(), _id.GetLength());memcpy(_data->Pass, _pass.GetBuffer(), _pass.GetLength());memcpy(buff + 1, _data, len - 1);*//* 监控登录数据PDATALOGIN _data = (PDATALOGIN)buff;CStringA _id = _data->Id;_data = (PDATALOGIN)(buff + _data->lenId - 0x10);CStringA _pass = _data->Pass;CStringA _tmp;// 请求登录 账号[% s]密码[% s] 这个内容别人在逆向的时候就会看到// 所以这种东西需要自己搞个编码来代替它_tmp.Format("请求登录 账号[%s]密码[%s]", _id, _pass);
#ifdef  Anlyanly->SendData(TTYPE::I_DIS, 1, _tmp.GetBuffer(), _tmp.GetAllocLength());
#endif*//*返回false,游戏无法发送数据包原因看调用此此函数的位置 OnSend 函数(if (SendDealProc[buff[0]]((buff + 1), len - 1)))*/return true;
}bool Onloginfailed(char*&buff, unsigned& len) { int* code = (int*)&buff[1];Client->loginfailed(code[0]);return true; 
}bool OnloginOk(char*& buff, unsigned& len) {CStringA txt;CStringA tmp;CString utmp;PDATALOGINOK _p = (PDATALOGINOK)&buff[1];if (_p->RoleCount > 0) {char* buffStart = buff + 1 + sizeof(DATA_LOGIN_OK);char* buffEnd = buff + len;while (buffStart < buffEnd) {/*由于 EnCode 的入参带了& 所以可以在EnCode里直接对buffStart或len进行操作也就是说只要入参带了&在函数里的操作会影响它原本的值比如 char*a = 0;aaa(a)aaa(char*&AA){AA = new char[10]; // 它就会把a的值改成new char[10]}*/EnCode* _coder = new EnCode(buffStart, len);tmp.Format("[%s]",data_desc[_coder->index][_coder->op].name);txt = txt + tmp;if (_coder->op == 0x7) {utmp = (wchar_t*)_coder->pointer;tmp = utmp;txt = txt + tmp;}if (_coder->op == 0x3) {tmp.Format("[%f]", _coder->fval);txt = txt + tmp;}else {tmp.Format("[%f]", _coder->val);txt = txt + tmp;}}#ifdef  Anly// 长度24的原因,它是宽字节要,一个文字要2个字节,一共是10个文字加上结尾的0是11个// 所以 11 乘以2,然后再加2 anly->SendData(TTYPE::I_DIS, 0, txt.GetBuffer(), txt.GetAllocLength() + 1);
#endif}return true;
}// 这个函数拦截了游戏的连接
bool GameWinSock::OnConnect(char* ip, unsigned port)
{
#ifdef  Anly// 长度24的原因,它是宽字节要,一个文字要2个字节,一共是10个文字加上结尾的0是11个// 所以 11 乘以2,然后再加2 anly->SendData(TTYPE::I_LOG, 0, L"服务器正在连接。。。", 24);
#endif// this是ecx,HOOK的点已经有ecx了WinSock = this;bool b = (this->*_OnConnect)(ip, port);// 下方注释的代码时为了防止多次注入,导致虚函数地址不恢复问题导致死循环,通过一次性HOOK也能解决/*unsigned* vtable = (unsigned*)this;vtable = (unsigned*)vtable[0];union {unsigned value;bool(GameWinSock::* _proc)(char*, unsigned);} vproc;vproc._proc = _OnConnect;DWORD oldPro, backProc;VirtualProtect(vtable, 0x10x00, PAGE_EXECUTE_READWRITE, &oldPro);vtable[0x34 / 4] = vproc.value;VirtualProtect(vtable, 0x10x00, oldPro, &backProc);*/return b;
}bool GameWinSock::OnSend(char* buff, unsigned len)
{/*这里就可以监控游戏发送的数据了*/#ifdef  Anlyanly->SendData(TTYPE::I_SEND, buff[0], buff, len);
#endif/*数据包的头只有一字节所以它的取值范围就是0x0-0xFF*/if (SendDealProc[buff[0]]((buff), len)) {// 执行失败不让游戏发送数据包return (this->*_OnSend)(buff, len);}else {// 发送失败屏蔽消息return true;// 屏蔽消息}}bool GameWinSock::OnRecving(char* buff, unsigned len)
{// MessageBoxA(0, "11111111111111", "0", MB_OK);/*监控游戏接收的数据包*/
#ifdef  Anlyanly->SendData(TTYPE::I_RECV, buff[0], buff, len);
#endifreturn RecvDealProc[buff[0]](buff, len);
}bool GameWinSock::OnRecv(char* buff, unsigned len)
{
//#ifdef  Anly
//	anly->SendData(1, buff, len);
//#endifreturn (this->*_OnRecv)(buff, len);
}void GameWinSock::Init()
{for (int i = 0; i < 0x100; i++) {SendDealProc[i] = &DeafaultDeal;RecvDealProc[i] = &DeafaultDeal;}// 注册登录数据包处理函数// SendDealProc[I_LOGIN] = &Onlogin;// 注册数据登录失败数据包处理函数RecvDealProc[S_LOGINFAIL] = &Onloginfailed;RecvDealProc[S_LOGINOK] = &OnloginOk;
}

GameProc.cpp文件的修改:修改了 Init函数

#include "pch.h"
#include "GameProc.h"
#include "extern_all.h"// typedef bool(GameWinSock::* U)(char*, unsigned);bool _OnRecv(HOOKREFS2) {unsigned* _esp = (unsigned*)_ESP;_EAX = WinSock->RecvPoint;WinSock->OnRecving((char*)_esp[1], _esp[2]);return true;
}bool _OnConnect(HOOKREFS2) {/*根据虚函数表做HOOK的操作截取 ecx 获取 winsock 的值(指针)*/unsigned* vtable = (unsigned*)_EDX;//WinSock = (GameWinSock *)_ECX;/*联合体的特点是共用一个内存由于 GameWinSock::OnConnect 的 OnConnect函数是 GameWinSock类的成员函数直接 vtable[0x34 / 4] = (unsigned)&GameWinSock::OnConnect; 这样写语法不通过所以使用联合体,让语法通过*/union {unsigned value;bool(GameWinSock::* _proc)(char*, unsigned);} vproc;DWORD oldPro, backProc;VirtualProtect(vtable, 0x100, PAGE_EXECUTE_READWRITE, &oldPro);/*vproc._proc = &GameWinSock::OnConnect;  这一句是把我们自己写的调用connect函数的地址的出来*/ vproc._proc = &GameWinSock::OnConnect; /*InitClassProc函数里做的是给指针赋值的操作InitClassProc(&GameWinSock::_OnConnect, vtable[0x34/4]);这一句的意思是把GameWinSock类里的_OnConnect变量的值赋值成vtable[0x34/4],这个 vtable[0x34/4] 是虚表里的函数vtable[0x34/4]是游戏中调用connect函数的函数地址,经过之前的分析调用connect是先调用了虚表中的一个函数,然后从这个函数中调用了connect函数*/InitClassProc(&GameWinSock::_OnConnect, vtable[0x34/4]);vtable[0x34 / 4] = vproc.value;vproc._proc = &GameWinSock::OnSend;InitClassProc(&GameWinSock::_OnSend, vtable[0x3C / 4]);vtable[0x3C / 4] = vproc.value;VirtualProtect(vtable, 0x100, oldPro, &backProc);return true;
}GameProc::GameProc()
{hooker = new htd::hook::htdHook2();Init();InitInterface();
}void GameProc::LoadBase()
{LoadLibraryA("fxnet2.dll");
}void GameProc::Init()
{
#ifdef  anlyanly = new CAnly();
#endif/*这里的 WinSock 是0没有创建对象,但是还是能调用Init函数这是因为Init函数里面没有用到this,没用到就不会报错*/WinSock->Init(); Client = new NetClient();init_datadesc();
}void GameProc::InitInterface()
{LoadBase();// MessageBoxA(0, "1", "1", MB_OK);// 只会HOOK一次,一次性的HOOKhooker->SetHook((LPVOID)0x10617046, 0x1, _OnConnect, 0, true);/*第一个参数是HOOK的位置第二个参数是HOOK的位置的汇编代码的长度(用于保证执行的汇编代码完整)第三个参数是HOOK之后当游戏执行到第一个参数的位置的时候跳转的位置*/hooker->SetHook((LPVOID)0x10618480, 0x1, _OnRecv);/*在这里绑定游戏处理数据包函数(0x10618480函数)然后0x10618480函数在上面一行代码已经进行了HOOK所以在调用_OnRecv函数指针时,它就会进入我们HOOK*/InitClassProc(&GameWinSock::_OnRecv, 0x10618480);
}

extern_all.cpp文件的修改:新加 data_desc变量初始化、init_datadesc函数

/*此文件是用来存放全局变量、全局函数(通用函数)
*/
#include "pch.h"
#include "extern_all.h"
GameWinSock* WinSock = nullptr;
GameProc* PGameProc = nullptr;
NetClient* Client = nullptr;
#ifdef Anly
CAnly* anly = nullptr;
#endifDATA_DESC data_desc[2][9]{};void init_datadesc() {data_desc[0][0] = { "none", 0 };data_desc[0][1] = { "short", 2 };data_desc[0][2] = { "int", 4 };data_desc[0][3] = { "int64", 8 };data_desc[0][4] = { "float", 4 };data_desc[0][5] = { "int64", 8 };data_desc[0][6] = { "char", 1 };data_desc[0][7] = { "wchar_t*", 4 };data_desc[0][8] = { "int64", 8 };data_desc[1][0] = { "none", 0 };data_desc[1][1] = { "short", 2 };data_desc[1][2] = { "int", 4 };data_desc[1][3] = { "int64", 8 };data_desc[1][4] = { "float", 4 };data_desc[1][5] = { "int64", 8 };data_desc[1][6] = { "char*", 4 };data_desc[1][7] = { "wchar_t*", 4 };data_desc[1][8] = { "int64", 8 };
}void InitClassProc(LPVOID proc_addr, unsigned value)
{unsigned* writer = (unsigned*)proc_addr;writer[0] = value;
}

extern_all.h文件的修改:新加 DATA_DESC结构体、data_desc变量、init_datadesc函数

/*此文件是用来存放全局变量、全局函数(通用函数)
*/
#pragma once
#include "GameWinSock.h"
#include "GameProc.h"
#include "CAnly.h"
#include "NetClient.h"typedef struct DATA_DESC {char* name;short lenth;
}*PDATADESC;extern 	GameWinSock* WinSock;
extern GameProc* PGameProc;
extern NetClient* Client;
extern DATA_DESC data_desc[2][9];
extern void InitClassProc(LPVOID proc_addr, unsigned value);
extern void init_datadesc();
#ifdef Anly
extern CAnly* anly;
#endif

NetClient.h文件的修改:写了一个 loginok函数,这个loginok函数暂无用

#pragma once
#include "NetClass.h"
class NetClient // 监视客户端每一个操作
{
public:/*模拟登陆的方法Id是账号Pass是密码它要基于发送的方法实现,因为我们没有连接socket的操作*/bool login(const char* Id, const char*Pass);
public:// 登陆失败,参数是错误码void loginfailed(int code);// void loginok();
};

NetClass.h文件的修改:

#pragma once
/*数据包还原结构体要注意内存对齐,如果数据不满4字节,它字段会补齐比如结构体里有一个char变量,它是1字节,在内存里它可能会为了内存对齐让它变成4字节,所以这要注意
*/
// 登录数据
typedef struct DATA_LOGIN {int op = 0x0300;char buff[0x10]{};int lenId = 0x10;/*这个是登录的账号,它可能会变成0x20或更长,现在默认让它0x10读的时候以长度为准就好了*/char Id[0x10]{};int lenPass = 0x10;/*这个是登录的密码,它可能会变成0x20或更长,现在默认让它0x10读的时候以长度为准就好了*/char Pass[0x10]{};int lenCode = 0x10;char Code[0x10]{};int eop = 0x01;
}*PDATALOGIN;
typedef struct DATA_LOGIN_OK {int un[8] = { 0, 0, 0x76B, 0x0C, 0x1E,0, 0, 0 };int index = 0;int RoleCount = 0;
}*PDATALOGINOK;

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

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

相关文章

Embedding模型提升效果的方法之一:Whitening和pooling

0. 前言 Embedding模型的主流框架基本上分为三类——基于bert结构的&#xff0c;基于GPT结构的和基于T5结构的&#xff0c;当然这些结构都是Transformer的变形。对于Embedding模型&#xff0c;使用bert结构目前看是最好的。有篇论文论文对基于bert的Embedding模型和基于GPT的E…

集合(下)Map集合的使用

文章目录 前言一、Map接口二、Map接口的实现类 1.HashMap类2.TreeMap类总结 前言 Map集合没有继承Collection接口&#xff0c;不能像List集合和Set集合那样直接使用Collection接口的方法。Map集合其自身通过以key到value的映射关系实现的集合&#xff0c;也有相应的许多方法。类…

CUMT linux操作系统课程设计 任务2

先说题目: 调试Linux内核的启动过程&#xff0c;并在Linux 0.11内核进入保护模式之前添加提示信息 //这里吐槽一下&#xff0c;学校发的文档让你用断点去查看运行根本无法操作&#xff0c;报错如下&#xff1a; 所以别管这个报错&#xff0c;先跟着我来 第一题&#xff0c;调试…

各城市宗族文化姓氏占比数据

各城市宗族文化姓氏占比数据 1、指标&#xff1a;省份代码、所属省份、城市代码、所属城市、第1大姓氏、第2大姓氏、第3大姓氏、宗族文化强度 2、方法说明&#xff1a; 根据2005年全国1%的人口调查数据计算。其中第1大姓氏第一大姓人口数/总人口数&#xff0c;宗族文化强度(…

一文700字从0到1教你实现Jmeter分布式压力测试!

之前写过用jmeter做接口测试的文章&#xff0c;本篇我们继续介绍下用jmeter做分布式压力测试的例子。 用jmeter做压力测试&#xff0c;如果只用一台机器&#xff0c;有鉴于线程数的限制和一台机器的性能&#xff0c;可能无法满足压力测试的实际需求&#xff0c;解决这个问题&a…

力扣--并查集1631.最小体力消耗路径

这题将图论和并查集联系起来。把数组每个位置看成图中的一个节点。 这段代码的主要思路是&#xff1a; 遍历地图中的每个节点&#xff0c;将每个节点与其相邻的下方节点和右方节点之间的边加入到边集合中&#xff08;因为从上到下和从下到上他们高度绝对值一样的&#xff0c;…

OpenHarmony之媒体组件模块简介

源码 本文基于OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;3.2 Release源码foundation目录下的player_framework&#xff0c;在OpenHarmony 2.0 Release版本当中&#xff0c;这个模块的名字叫媒体组件模块&#xff0c;为了方便理解我们在本文中仍旧延…

VR全景展示:传统制造业如何保持竞争优势?

在结束不久的两会上&#xff0c;数字化经济和创新技术再度成为了热门话题。我国制造产业链完备&#xff0c;但是目前依旧面临着市场需求不足、成本传导压力加大等因素影响&#xff0c;那么传统制造业该如何保持竞争优势呢&#xff1f; 在制造行业中&#xff0c;VR全景展示的应用…

Query2doc——Query改写

大模型LLM最近一年比较火&#xff0c;但是可能由于数据量较大&#xff0c;存在一些矛盾的数据或者质量差的数据&#xff0c;就会导致大模型存在幻视情况&#xff0c;即存在严重不符合事实的情况。随着之而来&#xff0c;RAG&#xff08;Retrieval Augmented Generation&#xf…

计算机组成原理(超详解!!) 第三节 运算器(浮点加减乘)

1.浮点加法、减法运算 操作过程 1.操作数检查 如果能够判断有一个操作数为0&#xff0c;则没必要再进行后续一系列操作&#xff0c;以节省运算时间。 2.完成浮点加减运算的操作 (1) 比较阶码大小并完成对阶 使二数阶码相同&#xff08;即小数点位置对齐&#xff09;…

windows@浏览器主页被篡改劫持@360篡改主页@广告和弹窗设置@极速版

文章目录 360篡改浏览器主页方法1锁定浏览器主页 方法2注册表修改 360广告和弹窗360极速版 小结 360篡改浏览器主页 如果您使用360,且不想卸载它,那么当你启动360后,它可能会篡改你的浏览器(比如edge)的主页start page为360早期可能是通过修改快捷方式的target等属性,但是现在…

《剑指 Offer》专项突破版 - 面试题 93 : 最长斐波那契数列(C++ 实现)

题目链接&#xff1a;最长斐波那契数列 题目&#xff1a; 输入一个没有重复数字的单调递增的数组&#xff0c;数组中至少有 3 个数字&#xff0c;请问数组中最长的斐波那契数列的长度是多少&#xff1f;例如&#xff0c;如果输入的数组是 [1, 2, 3, 4, 5, 6, 7, 8]&#xff0…

C++模版(基础)

目录 C泛型编程思想 C模版 模版介绍 模版使用 函数模版 函数模版基础语法 函数模版原理 函数模版实例化 模版参数匹配规则 类模版 类模版基础语法 C泛型编程思想 泛型编程&#xff1a;编写与类型无关的通用代码&#xff0c;是代码复用的一种手段。 模板是泛型编程…

【前端Vue】Vue3+Pinia小兔鲜电商项目第3篇:静态结构搭建和分类实现,1. 整体结构创建【附代码文档】

Vue3ElementPlusPinia开发小兔鲜电商项目完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;认识Vue3&#xff0c;使用create-vue搭建Vue3项目1. Vue3组合式API体验,2. Vue3更多的优势,1. 认识create-vue,2. 使用create-vue创建项目,1. setup选项的写法和执行…

【数据结构与算法】java有向带权图最短路径算法-Dijkstra算法(通俗易懂)

目录 一、什么是Dijkstra算法二、算法基本步骤三、java代码四、拓展&#xff08;无向图的Dijkstra算法&#xff09; 一、什么是Dijkstra算法 Dijkstra算法的核心思想是通过逐步逼近的方式&#xff0c;找出从起点到图中其他所有节点的最短路径。算法的基本步骤如下&#xff1a;…

应用层协议 - HTTP

文章目录 目录 文章目录 前言 1 . 应用层概要 2. WWW 2.1 互联网的蓬勃发展 2.2 WWW基本概念 2.3 URI 3 . HTTP 3.1 工作过程 3.2 HTTP协议格式 3.3 HTTP请求 3.3.1 URL基本格式 3.3.2 认识方法 get方法 post方法 其他方法 3.3.2 认识请求报头 3.3.3 认识请…

MyBatis是纸老虎吗?(七)

在上篇文章中&#xff0c;我们对照手动编写jdbc的开发流程&#xff0c;对MyBatis进行了梳理。通过这次梳理我们发现了一些之前文章中从未见过的新知识&#xff0c;譬如BoundSql等。本节我想继续MyBatis这个主题&#xff0c;并探索一下MyBatis中的缓存机制。在正式开始梳理前&am…

如何解决kafka rebalance导致的暂时性不能消费数据问题

文章目录 背景思考答案排它故障转移共享 背景 之前在review同组其它业务的时候&#xff0c;发现竟然把kafka去掉了&#xff0c;问了下原因&#xff0c;有一个单独的服务&#xff0c;我们可以把它称为agent&#xff0c;就是这个服务是动态扩缩容的&#xff0c;会采集一些指标&a…

使用C++实现一个简单的日志功能

日志对于一些大一些的项目来说&#xff0c;可以在项目运行出现问题时更好的帮助 项目的维护人员快速的定位到问题出现的地方并且知道出现问题的原因&#xff0c; 并且日志也可以帮助程序员很好的进行项目的Debug&#xff0c;那么今天我就来实 现一个C编写的一个简单的日志功能。…

深度学习中常用计算距离的几种算法对比与python实现

前言 距离度量在许多机器学习算法中扮演着至关重要的角色&#xff0c;无论是监督学习还是无监督学习。选择适当的距离度量可以显著影响模型的性能。 在高维数据集中&#xff0c;欧几里得距离可能会受到所谓的“维度诅咒”的影响&#xff0c;因为随着维度的增加&#xff0c;数…