当前位置: 首页 > news >正文

Dns被莫名篡改的逆向分析定位(笔记)

引言:最近发现用户的多台机器上出现了Dns被莫名修改的问题,从系统事件上看并未能正常确定到是那个具体软件所为,现在的需求就是确定和定位哪个软件具体所为。

解决思路:
  1. 首先到IPv4设置页面对Dns进行设置:
  2. 通过ProcExp确定了该窗口的宿主进程是Explorer.exe,通过ProcMon对Explorer进行监控,并未发现Explorer将静态Dns的地址写入注册表(后来发现其实Explorer是通过DllHost.exe来实现对注册表修改的,所以没监控到)。
  3. 通过对Explorer进行逆向分析发现Explorer实现比较复杂,后来通过网络发现修改Dns可以通过Netsh.exe这个程序来实现:
  4. 于是转到对Netsh.exe的逆向分析上来,经过仔细分析,发现Netsh.exe对dns的修改是通过netiohlp.dll的NhIpHandleSetDnsServer来实现的:

     
  5. 通过进一步定位发现是NhIpAddDeleteSetServer:
  6. 并发现会通过写入注册表来保存相关信息:

    并通过定位发现注册表地址是:
  7. 并且有重启Dnscahe服务等相关操作:
  8. 1)通过设置系统全局钩子来挂钩系统下所有进程然后挂钩SetRegvalue等api监控,该进程通过SetWindowHookEx来设置全局钩子(其实该挂钩方式不能挂钩没有消息循环的经常),通过inject-helper.exe进程来挂钩发现不能挂钩系统下的所有进程,而且新创建的进程也无法挂钩。
    2)通过设置KnowDlls注册表发现也无法正常挂钩所有进程。
    3)通过底层驱动挂钩,这个方法能监控到应用层的所有进程对注册表的操作,但为了回溯到目标进程,可能也需要加入对父子进程的回溯,这个相对麻烦一些。
  9. 笔者采用相对比较简单容易操作的方法。采用ProcMon来对注册表的监控:

    1)设置第一项筛选:operation is RegSetValue 操作

    2)设置第二项筛选:path is HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{33701f65-c437-47a4-9162-071bd72b3425}\NameServer (复制这个字符串修改UUID即可)

    最后的效果如下图:

    设置好后可以看到监控效果:

  10. 但是我们需要追踪最初始的设置Dns的进程,比如进程A调用了Netsh或DllHost等其他的三方进程来设置Dns,这个时候仅仅监控到Netsh或者DllHost等进程是没用的,需要对进程进行父进程的回溯,才知道源操作进程。
  11. 这个时候需要写一个ProcMon的插件,然后在ProcMon在监控到操作进程后能第一时间对父进程进行回溯。

    编写一个Dll插件,并通过窗口子类化方式来对ProcMon的ListView控件进行消息监控:
     
    static DWORD WINAPI DoWork(LPVOID param){swprintf_s(g_szProfilePath, L"%s\\record_%d.log", GetCurrentExePath().c_str(), GetCurrentProcessId());do {HWND hwndTaskManager = FindWindowW(L"PROCMON_WINDOW_CLASS", L"Process Monitor - Sysinternals: www.sysinternals.com");if (!hwndTaskManager){MessageBox(NULL, L"查找进程窗口失败", L"错误", NULL);break;}DWORD TaskManagerPID = 0;GetWindowThreadProcessId(hwndTaskManager, &TaskManagerPID);if (TaskManagerPID != GetCurrentProcessId()){MessageBox(NULL, L"TaskManagerPID != GetCurrentProcessId()", L"错误", NULL);break;}//EnumChildWindows(hwndTaskManager, _EnumChildProc, NULL);g_hListView = FindWindowExW(hwndTaskManager, NULL, L"SysListView32", L"");if (!g_hListView){MessageBox(NULL, L"获取listview句柄为空", L"错误", NULL);break;}fnOriginNetworkList = (WNDPROC)SetWindowLongPtrW(g_hListView,GWLP_WNDPROC,(LONG_PTR)NetworkListWndProc);} while (0);return 0;}
    // 这里是ListView控件的消息处理函数 
    LRESULT CALLBACK NetworkListWndProc(HWND hwnd,      // handle to windowUINT_PTR uMsg,      // message identifierWPARAM wParam,  // first message parameterLPARAM lParam   // second message parameter){if (uMsg == LVM_SETITEMTEXT || uMsg == LVM_SETITEMTEXTA || uMsg == 4211){ wchar_t szSection[50] = { 0 };wsprintf(szSection, L"%d_%d", g_iPrevItem, pItem->iItem);wchar_t szSubItem[50] = { 0 };wsprintf(szSubItem, L"%d", pItem->iSubItem);WritePrivateProfileStringW(szSection, szSubItem, pItem->pszText, g_szProfilePath);if (pItem->iSubItem == 3) // process ID{time_t tm = time(NULL);struct tm now;localtime_s(&now, &tm);wchar_t str[100] = { 0 };wcsftime(str, sizeof(str) / 2, L"%A %c", &now);WritePrivateProfileStringW(szSection, L"time", str, g_szProfilePath);DWORD pid = _wtoi(pItem->pszText);stuProcNode node = { _wtoi(pItem->pszText), szSection };PushProcNodeQueue(node);  // 这里不卡顿消息线程,把父进程回溯抛到另一个独立线程处理}}LRESULT rc = CallWindowProc(fnOriginNetworkList,hwnd,uMsg,wParam,lParam);return rc;}
    // 这里是父进程回溯操作
    static DWORD WINAPI  DoQueryProcess(LPVOID param){while (1){WaitForSingleObject(g_hQueryProcEvt, INFINITE); // 新的请求进入队列会触发事件std::vector<stuProcNode> ProcNodeList;GetProcNodeQueue(ProcNodeList);for (int i = 0; i < ProcNodeList.size(); i++){std::wstring strpPidNameList;DWORD pid = ProcNodeList[i].dwPid;while (1){  // 回溯一下父进程HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);if (hProcess == (HANDLE)-1 || hProcess == 0){break;}WCHAR szPath[MAX_PATH] = { 0 };GetProcessImageFileName(hProcess, szPath, MAX_PATH);strpPidNameList += L"  [";wchar_t buffer[20] = { 0 };_itow_s(pid, buffer, 20, 10);strpPidNameList += buffer;strpPidNameList += L"  ";strpPidNameList += wcsrchr(szPath, L'\\') ? wcsrchr(szPath, L'\\') + 1 : L"NULL";strpPidNameList += L"]  ";PROCESS_BASIC_INFORMATION pbi = { 0 };if (0 == NtQueryInformationProcess(hProcess, 0, &pbi, sizeof(pbi), NULL)){pid = pbi.InheritedFromUniqueProcessId;}else{break;}CloseHandle(hProcess);}WritePrivateProfileStringW(ProcNodeList[i].strSection.c_str(), L"PidNameINFO", strpPidNameList.c_str(), g_szProfilePath);}}return 0;}
  12. 代码写好了,这个时候通过CFF软件修改ProcMon的导入表,使其依赖我们的插件,这个时候当ProcMon启动的时候就会自动加载我们的插件了,相当于变相注入到了ProcMon进程。
    看看效果:

  13. 这样的监控程序就算写好了,可以交付运维去部署监控了,一旦监控到就会输出到日志文件,并把父子进程进行了回溯。
  14. 附录(全局钩子注入代码):
    BOOL create_inject_process(LPCWSTR inject_helper, LPCWSTR hook_dll, DWORD id, bool bRemoteThread)
    {BOOL success = FALSE;WCHAR sz_command_line[4096] = { 0 };PROCESS_INFORMATION pi = { 0 };STARTUPINFO si = { 0 };si.cb = sizeof(si);swprintf_s(sz_command_line, L"\"%s\" \"%s\" %s %lu",inject_helper,hook_dll,bRemoteThread ? L"0" : L"1",id);success = CreateProcessW(inject_helper, sz_command_line, NULL, NULL,false, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);if (success) {CloseHandle(pi.hThread);WaitForSingleObject(pi.hProcess, 300);CloseHandle(pi.hProcess);}else {GetLastError();}return success;
    }BOOL remote_thread_inject(LPCWSTR inject_helper, LPCWSTR hook_dll, DWORD process_pid)
    {return create_inject_process(inject_helper, hook_dll, process_pid, true);
    }BOOL UI_Message_inject(LPCWSTR inject_helper, LPCWSTR hook_dll, DWORD process_tid)
    {return create_inject_process(inject_helper, hook_dll, process_tid, false);
    }调用:
    #define SHELLPLUGINNAME64   L"PatchPlg64.dll"
    #define SHELLPLUGINNAME32   L"PatchPlg32.dll"
    #define INJECTHELPER64      L"inject-helper64.exe"
    #define INJECTHELPER32      L"inject-helper32.exe"
    void CHookMonitorDlg::OnBnClickedOk()
    {HWND hwnd = (HWND)0x6060241A; //FindWindowExW(NULL, NULL, L"TaskManagerWindow", L"");DWORD dwPid = 0;DWORD dwTid = GetWindowThreadProcessId(hwnd, &dwPid);BOOL b64BitProcess = IsProcessBit(dwPid);std::wstring strCurpath = GetCurrentExePath();std::wstring strInjectHelper = strCurpath + L"\\" + (b64BitProcess ? INJECTHELPER64 : INJECTHELPER32);std::wstring strInjectPlugin = strCurpath + L"\\" + (b64BitProcess ? SHELLPLUGINNAME64 : SHELLPLUGINNAME32);//remote_thread_inject(strInjectHelper.c_str(), strInjectPlugin.c_str(), dwPid);UI_Message_inject(strInjectHelper.c_str(), strInjectPlugin.c_str(), /*dwTid*/0);
    }
    

    插件代码:

    BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
    {switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:/* this prevents the library from being automatically unloaded* by the next FreeLibrary call */GetModuleFileNameW(hModule, name, MAX_PATH);LoadLibraryW(name);dll_inst =(HINSTANCE)hModule;init_dummy_window_thread();Hook();break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;
    }#define DEF_FLAGS (WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS)HWND dummy_window;
    static DWORD WINAPI dummy_window_thread(LPVOID* unused)
    {static const wchar_t dummy_window_class[] = L"temp_d3d_window_4033485";WNDCLASSW wc;MSG msg;OutputDebugStringA("lzlong dummy_window_thread");memset(&wc, 0, sizeof(wc));wc.style = CS_OWNDC;wc.hInstance = dll_inst;wc.lpfnWndProc = (WNDPROC)DefWindowProc;wc.lpszClassName = dummy_window_class;if (!RegisterClass(&wc)) {//hlog("Failed to create temp D3D window class: %lu",//    GetLastError());return 0;}dummy_window = CreateWindowExW(0, dummy_window_class, L"Temp Window",DEF_FLAGS, 0, 0, 1, 1, NULL, NULL,dll_inst, NULL);if (!dummy_window) {//hlog("Failed to create temp D3D window: %lu", GetLastError());return 0;}while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}(void)unused;return 0;
    }static inline void init_dummy_window_thread(void)
    {HANDLE thread =CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)dummy_window_thread, NULL, 0, NULL);if (!thread) {//hlog("Failed to create temp D3D window thread: %lu",//    GetLastError());return;}CloseHandle(thread);
    }extern "C" {__declspec(dllexport) LRESULT CALLBACKdummy_debug_proc(int code, WPARAM wparam, LPARAM lparam){static bool hooking = true;MSG* msg = (MSG*)lparam;if (hooking && msg->message == (WM_USER + 432)) {HMODULE user32 = GetModuleHandleW(L"USER32");BOOL(WINAPI * unhook_windows_hook_ex)(HHOOK) = NULL;unhook_windows_hook_ex = (BOOL(WINAPI*)(HHOOK))GetProcAddress(GetModuleHandleW(L"user32"), "UnhookWindowsHookEx");if (unhook_windows_hook_ex)unhook_windows_hook_ex((HHOOK)msg->lParam);hooking = false;}return CallNextHookEx(0, code, wparam, lparam);}
    }

    部分inject-helper的代码:

    
    typedef HHOOK (WINAPI *set_windows_hook_ex_t)(int, HOOKPROC, HINSTANCE, DWORD);#define RETRY_INTERVAL_MS      500
    #define TOTAL_RETRY_TIME_MS    4000
    #define RETRY_COUNT            (TOTAL_RETRY_TIME_MS / RETRY_INTERVAL_MS)int inject_library_safe_obf(DWORD thread_id, const wchar_t *dll,const char *set_windows_hook_ex_obf)
    {HMODULE user32 = GetModuleHandleW(L"USER32");set_windows_hook_ex_t set_windows_hook_ex;HMODULE lib = LoadLibraryW(dll);LPVOID proc;HHOOK hook;size_t i;if (!lib || !user32) {return INJECT_ERROR_UNLIKELY_FAIL;}#ifdef _WIN64proc = GetProcAddress(lib, "dummy_debug_proc");
    #elseproc = GetProcAddress(lib, "_dummy_debug_proc@12");
    #endifif (!proc) {return INJECT_ERROR_UNLIKELY_FAIL;}set_windows_hook_ex = (void*)GetProcAddress(user32, set_windows_hook_ex_obf);hook = set_windows_hook_ex(WH_GETMESSAGE, proc, lib, thread_id);if (!hook) {return GetLastError();}/* SetWindowsHookEx does not inject the library in to the target* process unless the event associated with it has occurred, so* repeatedly send the hook message to start the hook at small* intervals to signal to SetWindowsHookEx to process the message and* therefore inject the library in to the target process.  Repeating* this is mostly just a precaution. */for (i = 0; i < RETRY_COUNT; i++) {Sleep(RETRY_INTERVAL_MS);PostThreadMessage(thread_id, WM_USER + 432, 0, (LPARAM)hook);}return 0;
    }
    

相关文章:

Dns被莫名篡改的逆向分析定位(笔记)

引言&#xff1a;最近发现用户的多台机器上出现了Dns被莫名修改的问题&#xff0c;从系统事件上看并未能正常确定到是那个具体软件所为&#xff0c;现在的需求就是确定和定位哪个软件具体所为。 解决思路&#xff1a; 首先到IPv4设置页面对Dns进行设置&#xff1a;通过ProcExp…...

SpringBoot中整合ONLYOFFICE在线编辑

SpringBoot整合OnlyOffice SpringBoot整合OnlyOffice实现在线编辑1. 搭建私有的OnlyOffice的服务2. SpringBoot进行交互2.1 环境2.2 我们的流程2.3 接口规划2.3.1 获取编辑器配置的接口2.3.2 文件下载地址2.3.3 文件下载地址 3. 总结4. 注意4.1 你的项目的地址一定一定要和only…...

Python打字练习

代码解析 导入模块和定义单词列表 import tkinter as tk import randomsample_words ["apple", "banana", "cherry", "date", "fig", "grape", "kiwi", "lemon", "mango", &quo…...

Pytorch添加自定义算子之(10)-mmdeploy编译流程

整体参考 一、mmcv的编译安装 见上一篇 opencv的安装 $env:OpenCV_DIR = "D:\git_clone\opencv\build" # 我这里下载解压之后的地址 $env:path = "$env:OpenCV_DIR\x64\vc15\bin;" + $env:path $env:path = "D:\git_clone\opencv\build\OpenCVConf…...

大数据面试题之Flink(4)

Flink广播流 Flink实时topN 在实习中一般都怎么用Flink Savepoint知道是什么吗 为什么用Flink不用别的微批考虑过吗 解释一下啥叫背压 Flink分布式快照 Flink SQL解析过程 Flink on YARN模式 Flink如何保证数据不丢失 Flink广播流 Apache Flink 中的广播流&…...

C#实战|账号管理系统:通用登录窗体的实现。

哈喽,你好啊,我是雷工! 本节记录登录窗体的实现方法,比较有通用性,所有的项目登录窗体实现基本都是这个实现思路。 一通百通,以下为学习笔记。 01 登录窗体的逻辑 用户在登录窗输入账号和密码,如果输入账号和密码信息正确,点击【登录】按钮,则跳转显示主窗体,同时在固…...

php简单商城小程序系统源码

&#x1f6cd;️【简单商城小程序】&#x1f6cd;️ &#x1f680;一键开启&#xff0c;商城搭建新体验&#x1f680; 你还在为繁琐的商城搭建流程头疼吗&#xff1f;现在&#xff0c;有了简单商城系统小程序&#xff0c;一切变得轻松又快捷&#xff01;无需复杂的编程知识&a…...

NativeMemoryTracking查看java内存信息

默认该功能是禁用的&#xff0c;因为会损失5-10%的性能 开启命令 -XX:NativeMemoryTrackingdetail 打印命令 jcmd 45064 VM.native_memory summary scaleMB > NativeMemoryTracking.log 具体的日志信息 ➜ ~ ➜ ~ jcmd 45064 VM.native_memory summary scaleMB 45064…...

建智慧医院核心:智能导航系统的功能全析与实现效益

在数字化转型的浪潮中&#xff0c;智慧医院的建设是医疗行业数字化转型的关键步骤。随着医院规模的不断扩大和医疗设施的日益复杂&#xff0c;传统的静态不连续的导航方式已无法满足患者的需求。院内智能导航系统&#xff0c;作为医疗数字化转型的关键组成部分&#xff0c;正逐…...

数据库基础之:函数依赖

函数依赖在数据库设计中是非常关键的概念&#xff0c;用于描述关系数据库中数据项之间的相关性。下面我将通过几个例子来说明函数依赖的几种类型&#xff1a;完全函数依赖、部分函数依赖和传递函数依赖。 完全函数依赖 考虑一个关系模式 Student&#xff0c;包含属性 Student…...

Newport太阳光模拟器MSOL-UV-X使用说明手侧

Newport太阳光模拟器MSOL-UV-X使用说明手侧...

pandas读取CSV格式文件生成数据发生器iteration

背景 数据集标签为csv文件格式&#xff0c;有三个字段column_hander [‘id’, ‘boneage’, ‘male’]&#xff0c;需要自己定义数据集。文件较大&#xff0c;做一个数据发生器迭代更新数据集。 实现模板 在Pandas中&#xff0c;可以使用pandas.read_csv函数读取CSV文件&…...

SpringBoot 启动流程四

SpringBoot启动流程四 前面这个创建对象是初始化SpringApplication对象 是加载了SpringBoot程序的所有相关配置 我们接下来要将这个run方法 run过程是一个运行 初始化容器 我们看我们的运行结果是得到一个ConfigurableApplicationContext对象 package com.bigdata1421.star…...

实现桌面动态壁纸(二)

目录 前言 一、关于 WorkerW 工作区窗口 二、关于窗口关系 2.1 窗口以及窗口隶属关系 2.2 桌面管理层窗口组分简析 2.3 厘清两个概念的区别 2.4 关于设置父窗口 三、编写代码以供在 Vista 上实现 3.1 方法二&#xff1a;子类化并自绘窗口背景 四、初步分析桌面管理层…...

JavaEE——计算机工作原理

冯诺依曼体系&#xff08;VonNeumannArchitecture&#xff09; 现代计算机&#xff0c;大多遵守冯诺依曼体系结构 CPU中央处理器&#xff1a;进行算术运算与逻辑判断 存储器&#xff1a;分为外存和内存&#xff0c;用于存储数据&#xff08;使用二进制存储&#xff09; 输入…...

并发、多线程和HTTP连接之间有什么关系?

一、并发的概念 并发是系统同时处理多个任务或事件的能力。在计算中&#xff0c;这意味着系统能够在同一时间段内处理多个任务&#xff0c;而不是严格按照顺序一个接一个地执行它们。并发提高了系统的效率和资源利用率&#xff0c;从而更好地满足用户的需求。在现代应用程序中&…...

展开说说:Android服务之startService源码解析

通过上一篇文章我们掌握了Android四种的基本使用&#xff0c;本篇从源码层面总结一下startService的执行过程。 本文依然按着是什么&#xff1f;有什么&#xff1f;怎么用&#xff1f;啥原理&#xff1f;的步骤来分析。 1、是什么 上一篇总结了“Service是Android系统中的四…...

Java + MySQL 实现存储完整 Json

Java MySQL 实现存储完整 Json 一、应用场景二、数据库配置三、后端代码配置1、maven 依赖2、实体类3、Service 实现类4、xml 文件 四、测试1、新增接口2、查询接口3、数据表内容 一、应用场景 将前端传过来的 Json 完整存储到 MySQL 中&#xff0c;涉及技术栈为 Java、MyBat…...

解决刚申请下来的AWS EC2,无法用finalshell连接的问题

在AWS的命令页面创建一个root用户 切换到root 模式,输入密码 su root 不知道密码的可以使用一下命令来设置root用户的密码&#xff1a; su passwd root 再切换到root用户 su 修改配置文件 输入 vim /etc/ssh/sshd_config进入文件&#xff0c;键入’i’ &#xff0c;进行…...

如何在PD虚拟机中开启系统的嵌套虚拟化功能?pd虚拟机怎么用 Parallels Desktop 19 for Mac

PD虚拟机是一款可以在Mac电脑中运行Windows系统的应用软件。使用 Parallels Desktop for Mac 体验 macOS 和 Windows 的最优性能&#xff0c;解锁强大性能和无缝交互。 在ParallelsDesktop&#xff08;PD虚拟机&#xff09;中如何开启系统的嵌套虚拟化功能&#xff1f;下面我们…...

Nunchaku-flux-1-dev一键部署教程:Ubuntu20.04环境配置

Nunchaku-flux-1-dev一键部署教程&#xff1a;Ubuntu20.04环境配置 1. 开篇&#xff1a;为什么选择这个部署方案 如果你刚接触Linux环境下的模型部署&#xff0c;可能会觉得配置各种依赖和环境变量很头疼。Nunchaku-flux-1-dev作为一个功能强大的模型&#xff0c;其实在Ubunt…...

如何通过Nginx反向代理部署WeTTY:生产环境完整配置指南

如何通过Nginx反向代理部署WeTTY&#xff1a;生产环境完整配置指南 【免费下载链接】wetty Terminal in browser over http/https. (Ajaxterm/Anyterm alternative, but much better) 项目地址: https://gitcode.com/gh_mirrors/we/wetty WeTTY&#xff08;Web TTY&…...

LaTeX科技论文写作:如何优雅地呈现SenseVoice-Small模型实验数据

LaTeX科技论文写作&#xff1a;如何优雅地呈现SenseVoice-Small模型实验数据 写论文&#xff0c;尤其是技术论文&#xff0c;最头疼的往往不是实验本身&#xff0c;而是如何把那些复杂的模型结构、密密麻麻的数据和曲线图&#xff0c;清晰又专业地呈现在纸上。你辛辛苦苦跑出来…...

搜索引擎技巧

一.搜索行动框架第三步&#xff1a;抽取关键词、构造检索式在选择好搜索工具之后&#xff0c;紧接着就是抽取关键词、构造检索式了。检索式通常由三个要素组成。• 关键词&#xff1a;这个非常容易理解&#xff0c;我们常常在搜索过程中只会输入关键词。但很多时候&#xff0c;…...

昇腾算子开发知识地图

作者&#xff1a;昇腾实战派 背景 本博客旨在对社区发表的昇腾算子相关博客进行整理归类&#xff0c;方便用户导航使用&#xff1b;以下文章所用的机器均为昇腾相关设备。 Ascend C 基础理论 Ascend C基础 Ascend C算子开发详解&#xff1a;从原理到实战的深度剖析 深入A…...

lumenpnp校准–连接至 LumenPnP 并配置底部相机

总目录&#xff1a;https://www.xlzyw.top/archives/295 既然 OpenPnP 已安装并且 LumenPnP 配置文件已放置在隐藏的系统文件夹中&#xff0c;OpenPnP 可以使用一些基本的预配置设置启动了。下一步是连接您的 LumenPnP 并熟悉 OpenPnP 的用户界面。。 然后&#xff0c;我们将安…...

《荣耀出征:奇迹MU》安徽游昕官方正版下载:12区开服前瞻 全玩法解析与新手指南

《荣耀出征》奇迹mu手游是安徽游昕运营的手机游戏。由三天手游官网负责游戏攻略、资讯、礼包发放。2026年3月官方授权渠道&#xff08;官方最新&#xff09;游戏官方主站为985.yxnds.com&#xff0c;由安徽游昕运营&#xff0c;为游戏官方认证的信息与下载入口&#xff0c;可查…...

3分钟掌握Mermaid:用代码思维绘制专业图表的核心技巧

3分钟掌握Mermaid&#xff1a;用代码思维绘制专业图表的核心技巧 【免费下载链接】mermaid mermaid-js/mermaid: 是一个用于生成图表和流程图的 Markdown 渲染器&#xff0c;支持多种图表类型和丰富的样式。适合对 Markdown、图表和流程图以及想要使用 Markdown 绘制图表和流程…...

Unity游戏开发实战:用三阶贝塞尔曲线为你的角色设计一条丝滑的移动路径

Unity游戏开发实战&#xff1a;用三阶贝塞尔曲线为你的角色设计一条丝滑的移动路径 在游戏开发中&#xff0c;角色的移动路径设计往往决定了玩家的第一印象。想象一下&#xff0c;当你的主角从一个平台跳跃到另一个平台时&#xff0c;是希望看到机械的直线移动&#xff0c;还是…...

数据中心升级选卡指南:Intel X710 vs. Mellanox MCX4121A,10G网卡实战对比与避坑心得

数据中心网络升级实战&#xff1a;Intel X710与Mellanox MCX4121A深度评测与选型策略 当数据中心面临网络升级时&#xff0c;10G双端口网卡的选择往往成为关键决策点。作为基础设施的核心组件&#xff0c;网卡性能直接影响虚拟化效率、存储吞吐和业务连续性。本文将基于实际部署…...