当前位置: 首页 > 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;下面我们…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar&#xff1a;依赖注入与仓储模式实践 在 C# 的应用开发中&#xff0c;数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护&#xff0c;许多开发者会选择成熟的 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;SqlSugar 就是其中备受…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

Web 架构之 CDN 加速原理与落地实践

文章目录 一、思维导图二、正文内容&#xff08;一&#xff09;CDN 基础概念1. 定义2. 组成部分 &#xff08;二&#xff09;CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 &#xff08;三&#xff09;CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 &#xf…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...