【第45节】windows程序的其他反调试手段上篇

目录

引言

一、通过窗口类名和窗口名判断

二、检测调试器进程

三、父进程是否是Explorer

四、RDTSC/GetTickCount时间敏感程序段

五、StartupInfo结构的使用

六、使用BeingDebugged字段

七、 PEB.NtGlobalFlag,Heap.HeapFlags,Heap.ForceFlags

八、DebugPort:CheckRemoteDebuggerPresent()/NtQueryInformationProcess()


引言

        在Windows程序的开发与安全领域中,反调试技术是保障程序安全、防止被恶意调试分析的重要手段。上一节我们已经了解了一些相关的反调试方法,而这还远远不够。接下来我们将进一步探索更多有效的反调试技巧,篇幅分上中下三篇来讲述,该节是第一篇。

一、通过窗口类名和窗口名判断

        FindWindow函数:使用`FindWindow`函数,能依据特定的类名或者窗口名去查找对应的窗口。
        EnumWindow函数:调用`EnumWindow`函数后,系统会逐个遍历所有的顶级窗口,每遍历到一个窗口,就会调用一次回调函数。在这个回调函数里,先用`GetWindowText`函数获取窗口的标题,接着利用`strstr`函数(该函数区分大小写,与之对应的`StrStrI`函数不区分大小写)等,在窗口标题中查找是否存在“ollydbg”这样的字符串。`StrStr`函数的作用是返回`str2`首次在`str1`中出现的位置,要是没找到,就会返回`NULL`。
        GetForeGroundWindow函数:`GetForeGroundWindow`函数会返回桌面上当前处于激活状态的窗口。当程序处于被调试状态时,调用这个函数能够获取到“ollydbg”窗口的句柄,得到句柄后,就能向这个窗口发送`WM_CLOSE`消息,从而将其关闭。

关键示例代码:

 // FindWindow相关代码void CDetectODDlg::OnWndcls() {HWND hWnd;if (hWnd = ::FindWindow("ollyDbg", NULL)) {MessageBox("   发现OD");::SendMessage(hWnd, WM_CLOSE, NULL, NULL);}else {MessageBox("   没发现OD");}}// EnumWindow相关代码// 包含头文件:#include "Shlwapi.h"BOOL CALLBACK EnumWindowsProc(//handle to parent windowLPARAM lParam //application - defined value) {char ch[100];CString str = "ollydbg";if (IsWindowVisible(hwnd)) {::GetWindowText(hwnd, ch, 100);//AfxMessageBox(ch);if (::StrStrI(ch, str)) {AfxMessageBox("发现OD");return FALSE;}}return TRUE;}void CDetectODDlg::OnEnumwindow() {EnumWindows(EnumWindowsProc, NULL);AfxMessageBox("枚举窗口结束,未提示发现OD,则没有OD");}

二、检测调试器进程

        为了防范逆向分析人员通过修改调试器可执行文件名来规避检测,可采取以下方法:先对进程列表进行枚举操作,在这个过程中仔细查看是否有诸如“OLLYDBG.EXE”“windbg.exe”这类常见调试器进程存在。

        同时,借助`kernel32!ReadProcessMemory()`函数读取各个进程的内存数据,在读取到的数据里查找像“OLLYDBG”这样与调试器相关的字符串。一旦发现相关进程或字符串,就可以判定可能存在调试行为。 

关键示例代码:

 // 需要头文件:#include"tlhelp32.h"void CDetectODDlg::OnEnumProcess() {HANDLE hwnd;PROCESSENTRY32 tp32; //结构体CString str = "OLLYDBG.EXE";BOOL bFindOD = FALSE;hwnd = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);if (INVALID_HANDLE_VALUE!= hwnd) {Process32First(hwnd, &tp32);do {if (0 == lstrcmp(str, tp32.szExeFile)) {AfxMessageBox("发现OD");bFindOD = TRUE;break;}} while (Process32Next(hwnd, &tp32));if (!bFindOD)AfxMessageBox("没有OD");}CloseHandle(hwnd);}

三、父进程是否是Explorer

- 原理说明:一般来说,当我们通过双击的方式来运行程序时,这个程序进程的父进程通常是`explorer.exe`。要是情况并非如此,那就很有可能意味着该程序正处于被调试的状态。
   - 具体实现步骤:
     1. 可以通过`TEB(TEB.ClientId)`,也能使用`GetCurrentProcessId()`函数,来获取当前进程的PID(进程标识符)。
     2. 运用`Process32First/Next()`函数,能够获取到所有进程的列表。在这个列表里,要留意`explorer.exe`对应的PID,它可从`PROCESSENTRY32.szExeFile`中获取,同时还要关注当前进程的父进程PID,这个可通过`PROCESSENTRY32.th32ParentProcessID`来获取。另外,`Explorer`进程的ID还可以依据桌面窗口的类和名称来得到。
     3. 要是父进程的PID和`explorer.exe`、`cmd.exe`、`Services.exe`的PID都不相同,那么这个目标进程极有可能正在被调试。
   - 应对策略:`Olly Advanced`采用的办法是让`Process32Next()`函数一直返回`fail`,这样进程枚举就无法正常进行,PID检查也就会被跳过。实现这一操作,是通过对`kernel32!Process32NextW()`的入口代码打补丁(把`EAX`值设置为0后直接返回)来达成的。

   // (1)通过桌面类和名称获得Explorer的PID源码DWORD ExplorerID;::GetWindowThreadProcessId(::FindWindow("Progman", NULL), &ExplorerID);// (2)通过进程列表快照获得Explorer的PID源码void CDetectODDlg::OnExplorer() {HANDLE hwnd;PROCESSENTRY32 tp32; //结构体CString str = "Explorer.EXE";DWORD ExplorerID;DWORD SelfID;DWORD SelfParentID;SelfID =GetCurrentProcessId();::GetWindowThreadProcessId(::FindWindow("Progman", NULL), &ExplorerID);hwnd = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);if (INVALID_HANDLE_VALUE!= hwnd) {Process32First(hwnd, &tp32);do {if (0 == lstrcmp(str, tp32.szExeFile)) {//ExplorerID=tp32.th32ProcessID;//AfxMessageBox("aaa");}if (SelfID == tp32.th32ProcessID) {SelfParentID = tp32.th32ParentProcessID;}} while (Process32Next(hwnd, &tp32));str.Format("本进程:%d父进程:%d Explorer进程:%d", SelfID, SelfParentID, ExplorerID);MessageBox(str);if (ExplorerID == SelfParentID)AfxMessageBox("没有OD");elseAfxMessageBox("发现OD");CloseHandle(hwnd);}}

四、RDTSC/GetTickCount时间敏感程序段

原理阐述

        在进程正常运行时,CPU循环相对稳定。然而,一旦进程处于被调试状态,调试器的事件处理代码以及步过指令等操作会占用CPU资源,这就使得进程中相邻指令执行所耗费的时间大幅增加。因此,若发现相邻指令间耗时远超正常水平,基本可以判定该进程正被调试。

RDTSC操作说明

        `RDTSC`指令的功能是将计算机自启动以来CPU的运行周期数存储到`EDX:EAX`中,其中`EDX`存储高位数据,`EAX`存储低位数据。但要注意,如果`CR4`中的`TSD(time stamp disabled)`标志被置位,那么在`ring3`级别运行`rdtsc`指令会引发异常,因为这属于特权指令。为解决这个问题,需要进入`ring0`级别,将该标志置位。接着,对`OD`(OllyDbg调试器)的`WaitForDebugEvent`进行`Hook`操作,以此拦截异常事件。当捕获到异常代码属于特权指令时,读取异常处的操作码(`opcode`)进行检查。若确定是`rdtsc`指令,将指令指针(`eip`)值增加2,并通过`SetThreadContext`函数进行相关设置,此时`edx:eax`的返回值可由开发者自主决定。

GetTickCount方法

        获取当前系统时间,其原理是:当程序被调试时,调试器的事件处理代码、单步执行等操作会占用 CPU 时间,导致代码执行的时间变长。通过记录两段代码执行前后的系统时间(以毫秒为单位),计算时间差,若这个时间差超过了正常情况下的预期值(这里设定为 100 毫秒),就认为程序可能正在被调试,即检测到了调试器。

关键代码示例:

 void CDetectODDlg::OnGetTickCount() {//TOD0:Add your control notification handler code hereDWORD dTimel;DWORD dTime2;dTimel =GetTickCount();GetCurrentProcessId();GetCurrentProcessId();GetCurrentProcessId();GetCurrentProcessId();dTime2 =GetTickCount();if (dTime2 - dTimel>100) {AfxMessageBox("发现OD");}else {AfxMessageBox("没有OD");}}

五、StartupInfo结构的使用

        原理说明:在Windows操作系统里,当`explorer.exe`创建进程时,它会将`STARTUPINFO`结构里的值设置为0 。然而,要是进程不是由`explorer.exe`创建的,那么在创建过程中就会忽略`STARTUPINFO`结构中的值,这就意味着该结构里的值不会是0 。基于这个特性,我们能够以此来判断是否有像OD这样的调试器正在调试程序。 

关键示例代码:

   // 结构体定义typedef struct _STARTUPINFO {DWORD cb;           // 0000PSTR lpReserved;       // 0004PSTR lpDesktop;        // 0008PSTR lpTitle;           // 000DDWORD dwX;          // 0010DWORD dwY;          // 0014DWORD dwXSize;       // 0018DWORD dwYSize;       // 001DDWORD dwXCountChars; // 0020DWORD dwYCountChars; // 0024DWORD dwFillAttribute;  // 0028DWORD dwFlags;        // 002DWORD wShowWindow;   // 0030WORD cbReserved2;      // 0034PBYTE lpReserved2;      // 0038HANDLE hStdInput;      // 003DHANDLE hStd0utput;     // 0040HANDLE hStdError;       // 0044} STARTUPINFO, * LPSTARTUPINFO;void CDetectODDlg::OnGetStartupInfo() {STARTUPINFO info;GetStartupInfo(&info);if (info.dwX!= 0 || info.dwY!= 0|| info.dwXCountChars!= 0 || info.dwYCountChars!= 0|| info.dwFillAttribute!= 0 || info.dwXSize!= 0 || info.dwYSize!= 0)AfxMessageBox("发现OD");elseAfxMessageBox("没有OD");}

六、使用BeingDebugged字段

原理阐述

        在Windows系统环境下,存在一个名为`kernel32!IsDebuggerPresent()`的API函数,它的作用是通过检测进程环境块(PEB)里的`BeingDebugged`标志,来判断当前进程是否正被用户模式的调试器调试。每个进程都有自己的PEB结构,一般来说,获取PEB地址得借助线程环境块(TEB)。具体来说,`Fs:[0]`这个地址指向的是当前线程的TEB结构。在TEB结构里,偏移量为0的位置是线程信息块结构TIB 。而在TIB结构中,偏移18H的地方有个`self`字段,它其实是TIB的反身指针,这个指针又指向TIB(同时也是PEB)的起始地址。另外,在TEB偏移30H处,有一个指针指向PEB结构。在PEB结构里,偏移2H的位置就是`BeingDebugged`字段,其数据类型为`Uchar` 。

检测方式介绍

        1. 调用函数间接读取:可以调用`IsDebuggerPresent`函数,该函数会间接地读取`BeingDebugged`字段的值,以此判断进程是否被调试。
        2. 直接地址读取:也能通过获取的地址,直接去读取`BeingDebugged`字段,进而判断进程的调试状态 。

应对策略说明

        1. 手动修改标志值:在数据窗口中,通过按下`Ctrl + G`组合键,输入`fs:[30]`,就可以查看PEB数据。找到`PEB.BeingDebugged`标志后,将其值设置为0 。
        2. 使用Ollyscript命令:利用`Ollyscript`中的“`dbh`”命令,能够对`PEB.BeingDebugged`这个标志进行补丁操作 。

关键代码示例:

   void CDetectODDlg::OnIsdebuggerpresent() {if (IsDebuggerPresent())MessageBox("发现OD");elseMessageBox("没有OD");}

七、 PEB.NtGlobalFlag,Heap.HeapFlags,Heap.ForceFlags

        PEB.NtGlobalFlag情况:一般情况下,程序没被调试时,PEB里有个成员叫`NtGlobalFlag`(偏移量是0x68),它的值是0。要是进程正在被调试,这个值通常会变成0x70,这代表下面这些标志被设置了:
        - `FLG_HEAP_ENABLE_TAIL_CHECK(0X10)`
        - `FLG_HEAP_ENABLE_FREE_CHECK(0X20)`
        - `FLG_HEAP_VALIDATE_PARAMETERS(0X40)`
        这些标志是在`ntdll!LdrpInitializeExecutionOptions()`函数里进行设置的。要注意,`PEB.NtGlobalFlag`的默认值可以通过`gflags.exe`工具来修改,也能在注册表的`HKLM\Software\Microsoft\Windows Nt\CurrentVersion\Image File Execution Options`位置创建条目进行修改。下面是相关的汇编代码:

mov eax,fs:[30h]
mov eax,[eax+68h]
and eax,70h

        堆标志情况:因为设置了`NtGlobalFlag`标志,堆也会开启几个标志,这个变化能在`ntdll!Rt1CreateHeap()`函数里观察到。正常情况下,系统给进程创建第一个堆时,会把`Flags`设为2(也就是`HEAP_GROWABLE`),把`ForceFlags`设为0。要是进程正在被调试,这两个标志通常会分别设为50000062(具体数值取决于`NtGlobalFlag`)和0x40000060(这个值等于`Flags AND 0x6001007D`)。下面是相关的汇编代码:

assume fs:nothing
mov ebx,fs:[30h]     ;ebx指向PEB
mov eax,[ebx+18h]   ;PEB.ProcessHeap
cmp dword ptr [eax+0ch],2    ;PEB.ProcessHeap.Flags
jne debugger_found
cmp dword ptr [eax+10h],0          ;PEB.ProcessHeap.ForceFlags
jne debugger_found

        这些标志位的变化都是由`BeingDebugged`引发的。系统在创建进程时,会将`BeingDebugged`设为`TRUE`。之后,`NtGlobalFlag`会依据这个标记,设置如`FLGVALIDATEPARAMETERS`等标记。在为进程创建堆时,受`NtGlobalFlag`影响,堆的`Flags`会被设置一些标记。这些标记随后会被填入`ProcessHeap`的`Flags`和`ForceFlags`中。同时,堆里会被填入很多类似“BAADFOOD”的内容(也就是`HeapMagic`,也能用于检测调试情况)。若要一次性解决这些状态相关问题,可查看《加密解密》413页。 

关键示例代码:

 typedef NTSTATUS(_stdcall *ZwQueryInformationProcess)(HANDLE ProcessHandle,PROCESSINFOCLASS ProcessInformationClass,PVOID ProcessInformation,ULONG ProcessInformationLength,PULONG ReturnLength);//定义函数指针void CDetectODDlg::OnPebFlags() {//定义函数指针变量ZwQueryInformationProcess MyZwQueryInformationProcess;HANDLE hProcess = NULL;PROCESS_BASIC_INFORMATION pbi = {0};LLONG peb = 0;LLONG cnt = 0;LLONG PebBase = 0;LLONG AddrBase;BOOL blFoundOD = FALSE;WORD flag;DWORD dwFlag;DWORD bytesrw;DWORD Processld = GetCurrentProcessId();hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, Processld);if (hProcess!= NULL) {//函数指针变量赋值MyZwQueryInformationProcess=(ZwQueryInformationProcess)GetProcAddress(LoadLibrary("ntdll.dll"),"ZwQueryInformationProcess");//函数指针变量调用if (MyZwQueryInformationProcess(hProcess,ProcessBasicInformation,&pbi,sizeof(PROCESS_BASIC_INFORMATION),&cnt) == 0) {PebBase = (ULONG)pbi.PebBaseAddress;  //获取PEB地址AddrBase = PebBase;//读内存地址if (ReadProcessMemory(hProcess, (LPCVOID)(PebBase + 0x68), &flag, 2, &bytesrw) && bytesrw == 2) {//PEB.NtGlobalFlagif (0x70 == flag) {bFoundOD = TRUE;if (ReadProcessMemory(hProcess, (LPCVOID)(PebBase + 0x18), &dwFlag, 4, &bytesrw) && bytesrw == 4) {AddrBase = dwFlag;if (ReadProcessMemory(hProcess, (LPCVOID)(AddrBase + 0x0c), &flag, 2, &bytesrw) && bytesrw == 2) {//PEB.ProcessHeap.Flagsif (2!= flag) {bFoundOD = TRUE;if (ReadProcessMemory(hProcess, (LPCVOID)(AddrBase + 0x10), &flag, 2, &bytesrw) && bytesrw == 2) {//PEB.ProcessHeap.Forceflagsif (0!= flag) {bFoundOD = TRUE;}}}}}}if (bFoundOD == FALSE)AfxMessageBox("没有OD");else {AfxMessageBox("发现OD");}}CloseHandle(hProcess);}}}

八、DebugPort:CheckRemoteDebuggerPresent()/NtQueryInformationProcess()

        `Kernel32!CheckRemoteDebuggerPresent()` 这个函数的作用是判断有没有调试器附加到某个进程上。它的函数原型如下: 

BOOL CheckRemoteDebuggerPresent(HANDLE hProcess,PBOOL pbDebuggerPresent
);

        `Kernel32!CheckRemoteDebuggerPresent()` 函数需要 2 个参数。第一个参数是进程句柄,第二个参数是指向 `boolean` 变量的指针。要是进程正在被调试,这个变量就会是 `TRUE`。这个 API 实际上是调用了 `ntdll!NtQuery InformationProcess()` 来完成检测。

        下面定义函数指针:

typedef BOOL(WINAPI*CHECK_REMOTE_DEBUGGER_PRESENT)(HANDLE, PBOOL);

        检测函数的实现:

void CDetectODDlg::OnCheckremotedebuggerpresent() {HANDLE hProcess;HINSTANCE hModule;BOOL bDebuggerPresent = FALSE;CHECK_REMOTE_DEBUGGER_PRESENT CheckRemoteDebuggerPresent; // 建立函数指针变量hModule = GetModuleHandleA("Kernel32"); // 地址要从模块中动态获得CheckRemoteDebuggerPresent = (CHECK_REMOTE_DEBUGGER_PRESENT)GetProcAddress(hModule, "CheckRemoteDebuggerPresent"); // 获取地址hProcess = GetCurrentProcess();CheckRemoteDebuggerPresent(hProcess, &bDebuggerPresent); // 调用if (bDebuggerPresent == TRUE) {AfxMessageBox("发现OD");}else {AfxMessageBox("没有OD");}
}

        `ntdll!NtQueryInformationProcess()` 函数有 5 个参数。要是想检测调试器是否存在,得把 `ProcessInformationclass` 参数设置成 `ProcessDebugPort(7)`。`NtQueryInformationProcess()` 会去获取内核结构 `EPROCESS` 里的 `DebugPort` 成员,这个成员其实是系统和调试器进行通信时用的端口句柄。要是 `DebugPort` 成员的值不是 0,就说明进程正在被用户模式的调试器调试。这种情况下,`ProcessInformation` 会被设为 `0xFFFFFFFF`;要是没有被调试,`ProcessInformation` 就会被设为 0。

        `ZwQueryInformationProcess` 函数的原型如下:

ZwQueryInformationProcess(IN HANDLE ProcessHandle,IN PROCESSINFOCLASS ProcessInformationClass,OUT PVOID ProcessInformation,IN ULONG ProcessInformationLength,OUT PULONG ReturnLength OPTIONAL
);

        定义函数指针如下:

typedef NTSTATUS(__stdcall *ZW_QUERY_INFORMATION_PROCESS)(HANDLE ProcessHandle,PROCESSINFOCLASS ProcessInformationClass, // 该参数也需要上面声明的数据结构PVOID ProcessInformation,ULONG ProcessInformationLength,PULONG ReturnLength
);

        检测函数的实现:

void CDetectODDlg::OnZwqueryinfomationprocess() {//TODO: Add your control notification handler code hereHANDLE hProcess;HINSTANCE hModule;DWORD dwResult;ZW_QUERY_INFORMATION_PROCESS MyFunc;hModule = GetModuleHandle("ntdll.dll");MyFunc = (ZW_QUERY_INFORMATION_PROCESS)GetProcAddress(hModule, "ZwQueryInformationProcess");hProcess = GetCurrentProcess();MyFunc(hProcess, ProcessDebugPort, &dwResult, 4, NULL);if (dwResult != 0) {AfxMessageBox("发现OD");}else {AfxMessageBox("没有OD");}
}

 

 

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

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

相关文章

Golang|select

文章目录 多路监听超时控制 多路监听 如果selcet外面没有for循环,则只会监听一次,要实现一直监听的话要加for循环但是如果要设置退出条件的话,break语句只会退出这个select而不会退出for循环 select也可以有default,用于不用等cha…

无人机的群体协同与集群控制技术要点!

一、技术要点 通信技术 高效可靠的通信链路:无人机集群需要稳定、低延迟的通信网络,以实现实时数据传输和指令交互。通信方式包括无线自组织网络(Ad Hoc)、蜂窝网络、卫星通信等,需根据任务场景选择合适的通信技术。…

新手小白如何给个人电脑安装Deepseek?

准备工作:Ollama安装包、Chatbox安装包 一、安装Ollama 官网下载: 在 Windows 上下载 Ollama:https://ollama.com/download/windows 下载较慢,大家可以自行搜索资源下载,直接双击安装即可。 安装完毕后,…

Redis之RedLock算法以及底层原理

自研redis分布式锁存在的问题以及面试切入点 lock加锁关键逻辑 unlock解锁的关键逻辑 使用Redis的分布式锁 之前手写的redis分布式锁有什么缺点?? Redis之父的RedLock算法 Redis也提供了Redlock算法,用来实现基于多个实例的分布式锁。…

【控制学】控制学分类

【控制学】控制学分类 文章目录 [TOC](文章目录) 前言一、工程控制论1. 经典控制理论2. 现代控制理论 二、生物控制论三、经济控制论总结 前言 控制学是物理、数学与工程的桥梁 提示:以下是本篇文章正文内容,下面案例可供参考 一、工程控制论 1. 经典…

Android 15 中 ApnPreferenceController 的 onStart 和 onStop 调用失效

背景 AOSP对APN入口(Access Point Name)实现中,overried了 onStart 和 onStop ,但实际执行中根本不会进入这两个接口的逻辑。 Q:MobileNetworkSettings (APN入口Preference所在的界面Fragement承载,TAG是NetworkSettings)的生命周期和ApnPreference 有什么关系? Not…

React 在组件间共享状态

在组件间共享状态 有时候,你希望两个组件的状态始终同步更改。要实现这一点,可以将相关 state 从这两个组件上移除,并把 state 放到它们的公共父级,再通过 props 将 state 传递给这两个组件。这被称为“状态提升”,这…

阶段性使用总结-通义灵码

序言 前段时间用通义灵码,参加了下数字中国闽江流域的比赛。https://www.dcic-china.com/competitions/10173 最后成绩一般般,106名,大概有2000多人参加这题目,估计有一堆小号。 按照下面这个思路建模的,迭代了大概15…

游戏引擎学习第228天

对上次的内容进行回顾,并为今天的开发环节做铺垫。 目前大部分功能我们已经完成了,唯一剩下的是一个我们知道存在但目前不会实际触发的 bug。这个 bug 的本质是在某些线程仍然访问一个已经被销毁的游戏模式(mode)之后的状态&…

游戏测试入门知识

高内聚指的是一个模块或组件内部的功能应该紧密相关。这意味着模块内的所有元素都应该致力于实现同一个目标或功能,并且该模块应当尽可能独立完成这一任务。 低耦合则是指不同模块之间的依赖程度较低,即一个模块的变化对其它模块造成的影响尽可能小。理…

L1-2 种钻石

题目 2019年10月29日,中央电视台专题报道,中国科学院在培育钻石领域,取得科技突破。科学家们用金刚石的籽晶片作为种子,利用甲烷气体在能量作用下形成碳的等离子体,慢慢地沉积到钻石种子上,一周“种”出了一…

基于开源技术生态的社群运营温度化策略研究——以“开源链动2+1模式AI智能名片S2B2C商城小程序源码”融合应用为例

摘要 在社交媒体与电商深度融合的背景下,社群运营的“温度化”成为企业构建用户忠诚度的核心命题。本文以康夏社群运营案例为切入点,结合“开源链动21模式AI智能名片S2B2C商城小程序源码”技术架构,分析其通过开源技术实现情感联结与商业价值…

编程技能:调试01,调试介绍

专栏导航 本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏,故划分为两个专栏导航。读者可以自行选择前往哪个专栏。 (一)WIn32 专栏导航 上一篇:编程基础:位运算07,右移 回到目录 下一…

从零开始学A2A二 : A2A 协议的技术架构与实现

A2A 协议的技术架构与实现 学习目标 技术架构掌握 深入理解 A2A 协议的分层架构设计掌握各层次的功能和职责理解协议的工作原理和数据流 实现能力培养 能够搭建基本的 A2A 服务端掌握客户端开发方法实现智能体间的有效通信 架构设计理解 理解与 MCP 的本质区别掌握多智能体协…

UE5滚轮控制目标臂长度调整相机距离

UE5通过鼠标滚轮来控制摄像机目标臂长度 , 调整相机距离 看图就行,不多说,照着连就完事了

python的strip()函数用法; 字符串切片操作

python的strip()函数用法 目录 python的strip()函数用法代码整体功能概述代码详细解释1. `answer["output_text"]`2. `.strip()`3. `final_answer = ...`字符串切片操作:answer[start_index + len("Helpful Answer:"):].strip()整体功能概述代码详细解释1…

云服务模式全知道:IaaS、PaaS、SaaS与DaaS深度解析

云服务模式详解:IaaS、PaaS、SaaS与DaaS 在当今数字化快速发展的时代,云计算已经成为企业和开发者不可或缺的一部分。它提供了灵活的资源和服务,使得用户可以根据自己的需求选择最合适的解决方案。本文将详细介绍四种主要的云服务模式&#…

AIDL 语言简介

目录 软件包类型注释导入AIDL 的后端AIDL 语言大致上基于 Java 语言。AIDL 文件不仅定义了接口本身,还会定义这个接口中用到的数据类型和常量。 软件包 每个 AIDL 文件都以一个可选软件包开头,该软件包与各个后端中的软件包名称相对应。软件包声明如下所示: package my.pac…

PINN:用深度学习PyTorch求解微分方程

神经网络技术已在计算机视觉与自然语言处理等多个领域实现了突破性进展。然而在微分方程求解领域,传统神经网络因其依赖大规模标记数据集的特性而表现出明显局限性。物理信息神经网络(Physics-Informed Neural Networks, PINN)通过将物理定律直接整合到学习过程中&a…

程序化广告行业(89/89):广告创意审核的关键要点与实践应用

程序化广告行业(89/89):广告创意审核的关键要点与实践应用 在程序化广告这个充满机遇与挑战的领域,持续学习和知识共享是我们不断进步的动力。一直以来,我都希望能和大家一同深入探索这个行业,今天让我们聚…