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

news2025/4/16 9:33:14

目录

引言

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

二、检测调试器进程

三、父进程是否是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 window
       LPARAM 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");
           else
               AfxMessageBox("发现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 here
       DWORD 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;           // 0000
       PSTR lpReserved;       // 0004
       PSTR lpDesktop;        // 0008
       PSTR lpTitle;           // 000D
       DWORD dwX;          // 0010
       DWORD dwY;          // 0014
       DWORD dwXSize;       // 0018
       DWORD dwYSize;       // 001D
       DWORD dwXCountChars; // 0020
       DWORD dwYCountChars; // 0024
       DWORD dwFillAttribute;  // 0028
       DWORD dwFlags;        // 002D
       WORD wShowWindow;   // 0030
       WORD cbReserved2;      // 0034
       PBYTE lpReserved2;      // 0038
       HANDLE hStdInput;      // 003D
       HANDLE hStd0utput;     // 0040
       HANDLE 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");
       else
           AfxMessageBox("没有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");
       else
           MessageBox("没有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.NtGlobalFlag
                   if (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.Flags
                               if (2!= flag) {
                                   bFoundOD = TRUE;
                                   if (ReadProcessMemory(hProcess, (LPCVOID)(AddrBase + 0x10), &flag, 2, &bytesrw) && bytesrw == 2) {
                                       //PEB.ProcessHeap.Forceflags
                                       if (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 here
    HANDLE 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.coloradmin.cn/o/2335862.html

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

相关文章

Linux常见指令介绍上(入门级)

1. ls指令 功能:显示出当前目录下的所有子目录与文件。 PS:注意显示的是当前文件下的子目录与文件。 以下这些是可以和ls配套使用的一些选项 -a 列出目录下的所有文件,包括以 . 开头的隐含文件。 -d 将目录象文件一样显示,而不是显示其下…

【版本控制】git命令使用大全

大家好,我是jstart千语。今天来总结一下git的使用命令,上文会先将git命令都列出来,便于快速寻找,然后还会对部分常用命令图文讲解,适合新手,让你快速地理解。最后还会总结在idea中使用git。如果有缺失的&am…

手阳明大肠经之合谷穴

个人主页:云纳星辰怀自在 座右铭:“所谓坚持,就是觉得还有希望!” 又名:1.《针灸甲乙经》:虎口。 所属经络:手阳明大肠经 定位 在手背,第1、2掌骨间,当第2掌骨桡侧的中点…

【VUE3】练习项目——大事件后台管理

目录 0 前言 1 准备工作 1.1 安装pnpm 1.2 创建vue项目 1.3 Eslint & Prettier的配置 1.4 husky 提交代码检查 1.5 目录调整 1.6 VueRouter4 1.6.1 基础配置 1.6.2 路由跳转 1.7 引入 Element Plus 组件库 1.8 Pinia 1.8.1 优化 1.9 封装请求工具 1.9.1 安…

万字长篇————C语言指针学习汇总

经过一段时间的学习,我们已经接触到了C语言的很多知识了。不过目前我们接下来我们要接触C语言中一个最大的“门槛”:指针。 什么是指针? 在介绍指针之前,我们首先要明白变量与地址之间的关系。 举一个生活中的案例:一…

实验一 HDFS的Shell操作

一、实验目的 熟悉HDFS Shell的语法格式,完成对HDFS上文件和目录的一系列操作 二、实验要求 2.1 练习dfs常用的子命令 ① -ls ② -du ③ -mv ④ -cp ⑤ -rm ⑥ -put ⑦ -cat ⑧ -help ⑨ -mkdir ⑩ -get 2.2通过Shell脚本定时采集数据到HDFS 三、实…

复变函数摘记3

复变函数摘记3 5. 留数5.1 可去奇点、极点、本性奇点5.2 零点与极点的关系5.3 在无穷远点处的情形5.4 留数 5. 留数 \quad 如果函数 f ( z ) f(z) f(z) 在 z 0 z_0 z0​ 及 z 0 z_0 z0​ 的邻域内处处可导,那么称 f ( z ) f(z) f(z) 在点 z 0 z_0 z0​ 处解析。…

【行测】判断推理:类比推理 and 定义判断

> 作者:დ旧言~ > 座右铭:读不在三更五鼓,功只怕一曝十寒。 > 目标:掌握 类比推理 and 定义判断 基本题型,并能运用到例题中。 > 毒鸡汤:有些事情,总是不明白,所以我不会…

Java使用WebSocket视频拆帧进度处理与拆帧图片推送,结合Apipost进行调试

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>Configuration public class WebSocketConfig {/*** 启动 WebSocket 服务器*/Beanpublic ServerEndpointE…

计算斐波那契数列

1.对于斐波那契数列的理解&#xff1a; (1)常规 该数列由兔子繁衍的想法产生&#xff0c;故又“兔子数列” 其数值为&#xff1a;1、1、2、3、5、8、13、21、34 从数学定义上走&#xff1a;F(0)1&#xff0c;F(1)1, F(n)F(n - 1)F(n - 2)&#xff08;n ≥ 2&#xff0c;n ∈…

JAVA如何操作文件?(超级详细)

目录 一、认识文件和相关知识 1.认识文件 2.⽬录 3.⽂件路径&#xff08;Path&#xff09; 4.文本文件和二进制文件的区分 二、File类操作文件 1.构造方法 2.方法 2.1 方法表 2.2 get相关的方法和构造方法 2.2.1 “.” 和 “..” 2.3 is相关的方法 2.4 删除相关…

(2)VTK C++开发示例 --- 绘制多面锥体

文章目录 1. 概述2. CMake链接VTK3. main.cpp文件4. 演示效果 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;VTK开发 &#x1f448; 1. 概述 VTK C开发示例程序&#xff1b; 使用C 和VTK绘制一个多面锥体。 环境说明系统ubuntu22.04、windows11cmake3.22、3.2…

(2025-04-12)向老主机箱中安装新买的显卡及固态硬盘

目录 1 引言2 显卡及其驱动的安装3 固态硬盘的安装及C盘扩容3.1 固态硬盘正确连接到主板上后&#xff0c;操作系统上面仍然不显示对应盘符怎么办&#xff1f;3.2 如何对C盘扩容&#xff1f;3.3 新问题&#xff1a;原有D盘程序不能运行 4 总结 1 引言 今天安装昨天买的新固态硬…

rk3588 驱动开发(一)字符设备开发

3.字符设备驱动开发 3.1 什么是字符设备驱动 字符设备&#xff1a;就是一个个字节&#xff0c;按照字节流进行读写操作的设备&#xff0c;读写是按照先后顺序的。 举例子&#xff1a;IIC 按键 LED SPI LCD 等 Linux 应用程序调用驱动程序流程&#xff1a; Linux中驱动加载成功…

AbMole| 体内相互作用筛选揭示肝脏对癌症转移的限制

癌症转移&#xff0c;作为导致患者死亡的主要原因之一&#xff0c;其复杂机制一直是医学界研究的热点。肝脏&#xff0c;作为癌症转移的常见靶器官&#xff0c;其微环境对癌症细胞的生长和转移具有重要影响。然而&#xff0c;肝脏如何限制癌症转移的具体机制尚不完全清楚。 来…

STM32移植文件系统FATFS——片外SPI FLASH

一、电路连接 主控芯片选型为&#xff1a;STM32F407ZGT6&#xff0c;SPI FLASH选型为&#xff1a;W25Q256JV。 采用了两片32MB的片外SPI FLASH&#xff0c;电路如图所示。 SPI FLASH与主控芯片的连接方式如表所示。 STM32F407GT6W25Q256JVPB3SPI1_SCKPB4SPI1_MISOPB5SPI1_MOSI…

2025年第十六届蓝桥杯省赛JavaB组真题回顾

第16届蓝桥杯省赛已经结束了&#xff0c;第一次参加也是坐牢了4个小时&#xff0c;现在还是来总结一下吧&#xff08;先声明以下的解法&#xff0c;大家可以当作一种思路来看&#xff0c;解法不一定是正解&#xff0c;只是给大家提供一种能够正常想到的思路吧&#xff09; 试题…

Android 接口定义语言 (AIDL)

目录 1. 本地进程调用(同一进程内)2. 远程进程调用(跨进程)3 `oneway` 关键字用于修改远程调用的行为Android 接口定义语言 (AIDL) 与其他 IDL 类似: 你可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。 在 Android 上,一个进…

c# 数据结构 链表篇 有关双向链表的一切

本人能力有限,如有不足还请斧正 目录 0.双向链表的好处 1.双向链表的分类 2.不带头节点的标准双向链表 节点类:有头有尾 链表类:也可以有头有尾 也可以只有头 增 头插 尾插 删 查 改 遍历 全部代码 3.循环双向链表 节点类 链表类 增 头插 尾插 删 查 遍历…

660 中值定理

文章目录 前言168169170总结 前言 background music: 《代替》 张叶蕾 660 上面没有专门的中值定理章节&#xff0c;我蒙了。不过应该可以找一下。就是证明题&#xff0c;标志性应该还行。找一下。然后做一下。660 的题质量应该还是非常高的。但是积分中值定理&#xff0c;还有…