实现屏蔽 Ctrl + Alt + Del 、Ctrl + Shift + Esc 等热键(一)

news2024/11/19 9:31:23

前面几篇我们都讲解了很多有关 winlogon 挂钩的事情。拦截系统热键的非驱动方式是比较复杂的。本节就复现《禁止Ctrl+Alt+Del、Win+L等任意系统热键》一文中的方法四,实现拦截 Ctrl + Alt + Del 等热键。其实通过 heiheiabcd 给出的方法从 WMsgKMessageHandler 入手并不是最简单的方式。其他方法比如:还可以从 RPC 调用入手,有一个只要修改 RPC Asnyc 过程中 Invoke 函数派发时的参数即可完成的操作,而派发过程可以通过特殊大小的内存初始化(memset 函数)来监听,并不需要复杂的定位机制,不过分析时花了一点功夫。

屏蔽热键系列将只谈 WMsgKMessageHandler 入手的方法,Hook NDR-RPC 的系列(前半部分排表)将只从 RPC 角度去分析,并且都考虑到稳定性和绕过系统程序默认开启的控制流防护和缓冲区防护。

系列文章:

  1. 屏蔽系统热键/关机(挂钩 Winlogon 调用 键盘钩子)​​​​​​
  2. 基于 Ncalrpc 协议 NDR64 线路接口的 Hook 实现系统热键屏蔽(一)
  3. Hook 实现系统热键屏蔽(二)[暂未发布]
  4. Windows 拦截系统睡眠
  5. 屏蔽 Ctrl + Alt + Del 、Ctrl + Shift + Esc 等热键(一)
  6. 屏蔽 Ctrl + Alt + Del 、Ctrl + Shift + Esc 等热键(二)[暂未发布]

一、原理概述

winlogon 进程通过 SignalManagerWaitForSignal 函数循环等待系统快捷键。最终通过 WMsgKMessageHandler 回调函数来实现 RPC 消息的处理。

该函数的声明应该是这样的:

int __fastcall WMsgKMessageHandler(
    unsigned int         uMsgCSessionKey,
    unsigned int         uMsgWLGenericKey,
    PRPC_ASYNC_STATE     pAsync,
    int *                pReserved
)

第一个参数 uMsgCSessionKey 控制会话有关(CSession)的回调消息,这不是我们需要关注的。

第二个参数 uMsgWLGenericKey 控制注册调用(WLGeneric)的回调消息,其中包含了对快捷键处理有关的函数。

这两个参数可以理解为窗口过程的 uMsg 参数,通过 switch...case 对消息进行处理。对于快捷键处理,大体上可以简化为如下伪代码过程:

switch(uMsgWLGenericKey) {
case Ctrl+Alt+Del:
{
    // 打开桌面安全选项();
}
break;
case Win+L:
{
    // 锁定桌面();
}
break;
case Ctrl+Shift+Esc:
{
    // 打开任务管理器();
}
break;
case Win+P:
{
    // 切换窗口();
}
break;
default:
{
    // do something...
}

当 uMsgCSessionKey == 0x404 时,才处理 WLGeneric 消息:

如果能够挂钩该函数并且修改函数的参数使得:当 uMsgCSessionKey == 0x404 时,uMsgWLGenericKey 的值变为比较大的一个数值,这样理论上就可以绕过调用。但是猜测会导致 CFG 检测不通过,似乎这个函数通过间接调用完成,按照道理应该会有控制流防护。

heiheiabcd 给出的方法是直接修改"case  ID"将每个 ID 改为很大的值。这一点我将在第二篇分去分析,本篇从定位该函数入手。

二、定位 WMsgKMessageXX 函数

这个函数由于开放窗口很大,函数调用非常复杂。所以选取特征码时候比较麻烦,我找了很久,目前确定的一种可行的方案是定位特殊指令法。这一段是对全局变量的解引用,和获取指针引用,使用了特殊的寄存器传递数据。并且 WMsgKMessageHandler 回调函数多次使用该方法使用 ETW 记录事件日志。

所以,考虑是否可以用该特征作为特征码,提取的特征数组如下:

{ 0x48u, 0x8Bu, 0x0Du, 0, 0, 0, 0, 0x49u, 0x3Bu, 0xCCu, 0x74u , 0, 0x44, 0x84, 0x79, 0x1C , 0x74 }

其中,0 表示通配符。

使用我在这篇文章:“程序特征码识别定位方法”,提出的方法即可完成搜索操作,这里以暴力搜索(winlogon.exe 文件版本 10.0.22621.3085)为例,搜索结果如下:

可以发现结果不止一处,不用担心,经过核对,匹配项均位于 WMsgKMessageHandler 回调函数的代码中。并且第一个匹配项位于函数入口点附近,这一点看 IDA 的伪代码/反汇编就可以看出:

所以,只需要匹配第一次搜索结果即可,代码如下:

#include <stdio.h>
#include <windows.h>
#include <vector>
#include <Psapi.h>
#include <time.h>

inline int BFTracePatternInModule(
    LPCWSTR moduleName, 
    PBYTE pattern, 
    SIZE_T patternSize, 
    DWORD dwRepeat, 
    DWORD dwSelect = 1
)
{
    if (pattern == 0 || moduleName == 0 || patternSize == 0 || dwRepeat <= 0)
    {
        return 0;
    }

    HMODULE hModule = LoadLibraryW(moduleName);
    if (hModule == nullptr) {
        printf("Failed to load module: %ws.\n", moduleName);
        return 0;
    }

    MODULEINFO moduleInfo;
    if (!GetModuleInformation(GetCurrentProcess(), hModule, &moduleInfo, sizeof(moduleInfo))) {
        printf("Failed to get module information.\n");
        FreeLibrary(hModule);
        return 0;
    }

    std::vector<uint64_t> vcMachList;
    BYTE* moduleBase = reinterpret_cast<BYTE*>(hModule);
    SIZE_T moduleSize = moduleInfo.SizeOfImage;

    printf("模块基址:0x%I64X.\n", reinterpret_cast<uint64_t>(hModule));
    printf("模块大小:%I64d Bytes.\n", moduleSize);


    if (moduleSize == 0)
    {
        printf("Failed to get module information.\n");
        FreeLibrary(hModule);
        return 0;
    }

    uint64_t thisMatch = 0;
    DWORD SelectCase = (dwSelect < 256) && dwSelect ? dwSelect: 256; // 最大结果记录次数
    SIZE_T MatchLimit = patternSize * dwRepeat - 1;  // 连续重复匹配次数限制
    int cwStart = clock();

    if (dwRepeat == 1)
    {
        for (SIZE_T i = 0; i < moduleSize; i++)
        {
            thisMatch = 0;
            SIZE_T j = 0;

            for (j; j < patternSize - 1; j++)
            {
                if (moduleBase[i + j] != pattern[j] && pattern[j] != 0u)
                {
                    break;
                }
            }

            if (j == patternSize - 1)
            {
                if (moduleBase[i + j] == pattern[j] || pattern[j] == 0u)
                {
                    thisMatch = i;
                    SelectCase--;
                    vcMachList.push_back(thisMatch);
                    if(!SelectCase) break;
                }
            }
        }
    }
    else {
        for (SIZE_T i = 0; i < moduleSize; i++)
        {
            thisMatch = 0;
            SIZE_T j = 0;

            for (j; j < MatchLimit; j++)
            {
                if (moduleBase[i + j] != pattern[j % patternSize] && pattern[j % patternSize] != 0u)
                {
                    break;
                }
            }

            if (j == MatchLimit)
            {
                if (moduleBase[i + MatchLimit] == pattern[patternSize - 1] || pattern[patternSize - 1] == 0u)
                {
                    thisMatch = i;
                    SelectCase--;
                    vcMachList.push_back(thisMatch);
                    if (!SelectCase) break;
                }
            }
        }
    }

    int cwEnd = clock();
    
    for (SIZE_T i = 0; i < vcMachList.size(); i++)
    {
        printf("匹配到模式字符串位于偏移: [0x%I64X] 处,动态地址:[0x%I64X]。\n", 
            vcMachList[i], reinterpret_cast<uint64_t>(moduleBase) + vcMachList[i]);
    }

    if (vcMachList.size() == 0)
    {
        printf("No Found.\n");
    }

    FreeLibrary(hModule);
    return cwEnd - cwStart;
}


int main() {
    // 暴力算法
    const wchar_t* moduleName = L"winlogon.exe";
    BYTE   pattern[] =
    { 0x48u, 0x8Bu, 0x0Du, 0, 0, 0, 0, 0x49u, 
      0x3Bu, 0xCCu, 0x74u , 0, 0x44, 0x84,
      0x79, 0x1C , 0x74 };// ETW Trace 特征码
    SIZE_T patternSize = 17; 
    DWORD dwRepeat = 1, dwSelect = 1; // 匹配第一次完整匹配,不重复匹配
    int TimeCost = 0;
    TimeCost = BFTracePatternInModule(moduleName, 
        pattern, patternSize, dwRepeat, dwSelect);
    printf("算法耗时:%d ms.\n", TimeCost);
    return 0;
}

测试结果如图,耗时在微秒级别,当然可以用我那篇文章里面给出来的更好的匹配算法的代码:

随后,我们只需要向上搜索 0xCCCCCCCC 或者 0x90909090 的 Hot Patch 片段,来确定函数入口点。

但是,比较麻烦的就是 Win 11 上和之前版本还有些不同,位点之后插入了一段不明作用的数值,应该也是属于 HotPatch 里面的,有大神指点不?(已解决:见补充更新部分)

我只能想到再通过入口特征进一步定位了:入口的 mov rsp --> 48 89 特征。 一定有更好的方法。

简单编写的测试代码如下:

#include <stdio.h>
#include <windows.h>
#include <vector>
#include <Psapi.h>
#include <time.h>

inline int BFTracePatternInModule(
    LPCWSTR moduleName, 
    PBYTE pattern, 
    SIZE_T patternSize, 
    DWORD dwRepeat, 
    DWORD dwSelect = 1
)
{
    if (pattern == 0 || moduleName == 0 || patternSize == 0 || dwRepeat <= 0)
    {
        return 0;
    }

    HMODULE hModule = LoadLibraryW(moduleName);
    if (hModule == nullptr) {
        printf("Failed to load module: %ws.\n", moduleName);
        return 0;
    }

    MODULEINFO moduleInfo;
    if (!GetModuleInformation(GetCurrentProcess(), hModule, &moduleInfo, sizeof(moduleInfo))) {
        printf("Failed to get module information.\n");
        FreeLibrary(hModule);
        return 0;
    }

    std::vector<uint64_t> vcMachList;
    BYTE* moduleBase = reinterpret_cast<BYTE*>(hModule);
    SIZE_T moduleSize = moduleInfo.SizeOfImage;

    printf("模块基址:0x%I64X.\n", reinterpret_cast<uint64_t>(hModule));
    printf("模块大小:%I64d Bytes.\n", moduleSize);


    if (moduleSize == 0)
    {
        printf("Failed to get module information.\n");
        FreeLibrary(hModule);
        return 0;
    }

    uint64_t thisMatch = 0;
    DWORD SelectCase = (dwSelect < 256) && dwSelect ? dwSelect: 256; // 最大结果记录次数
    SIZE_T MatchLimit = patternSize * dwRepeat - 1;  // 连续重复匹配次数限制
    int cwStart = clock();

    if (dwRepeat == 1)
    {
        for (SIZE_T i = 0; i < moduleSize; i++)
        {
            thisMatch = 0;
            SIZE_T j = 0;

            for (j; j < patternSize - 1; j++)
            {
                if (moduleBase[i + j] != pattern[j] && pattern[j] != 0u)
                {
                    break;
                }
            }

            if (j == patternSize - 1)
            {
                if (moduleBase[i + j] == pattern[j] || pattern[j] == 0u)
                {
                    thisMatch = i;
                    SelectCase--;
                    vcMachList.push_back(thisMatch);
                    if(!SelectCase) break;
                }
            }
        }
    }
    else {
        for (SIZE_T i = 0; i < moduleSize; i++)
        {
            thisMatch = 0;
            SIZE_T j = 0;

            for (j; j < MatchLimit; j++)
            {
                if (moduleBase[i + j] != pattern[j % patternSize] && pattern[j % patternSize] != 0u)
                {
                    break;
                }
            }

            if (j == MatchLimit)
            {
                if (moduleBase[i + MatchLimit] == pattern[patternSize - 1] || pattern[patternSize - 1] == 0u)
                {
                    thisMatch = i;
                    SelectCase--;
                    vcMachList.push_back(thisMatch);
                    if (!SelectCase) break;
                }
            }
        }
    }

    /*
    * 增加:向上搜索 HotPatch 代码段
    * 
    */
    uint64_t uintPostn = NULL; // 存储偏移量

    for (SIZE_T j = vcMachList[0] - 1; j > vcMachList[0] - 1000; j--)
    {
        if (moduleBase[j] == 0xCC
            && moduleBase[j - 1] == 0xCC
            && moduleBase[j - 2] == 0xCC
            && moduleBase[j - 3] == 0xCC   // HotPatch 特征
            )
        {
            for (j; j < vcMachList[0]; j++)   // 入口点特征
            {
                if (moduleBase[j] == 0x48 && moduleBase[j + 1] == 0x89)
                {
                    uintPostn = j;  // 如果找到
                    break;
                }
            }
            break;
        }

        if (moduleBase[j] == 0x90
            && moduleBase[j - 1] == 0x90
            && moduleBase[j - 2] == 0x90
            && moduleBase[j - 3] == 0x90
            )
        {
            for (j; j < vcMachList[0]; j++)
            {
                if (moduleBase[j] == 0x48 && moduleBase[j + 1] == 0x89)
                {
                    uintPostn = j;  // 如果找到
                    break;
                }
            }
            break;
        }
    }

    if (uintPostn)
    {
        printf("匹配到函数入口点位于偏移: [0x%I64X] 处,动态地址:[0x%I64X]。\n",
            uintPostn, reinterpret_cast<uint64_t>(moduleBase) + uintPostn);
    }

    for (SIZE_T i = 1; i < vcMachList.size(); i++)
    {
        uintPostn = NULL; // 归零
        for (SIZE_T j = vcMachList[i] - 1; j > vcMachList[i - 1] - 1; j--)
        {
            if (moduleBase[j] == 0xCC
                && moduleBase[j - 1] == 0xCC
                && moduleBase[j - 2] == 0xCC
                && moduleBase[j - 3] == 0xCC
                )
            {
                for (j; j < vcMachList[i]; j++)   // 入口点特征
                {
                    if (moduleBase[j] == 0x48 && moduleBase[j + 1] == 0x89)
                    {
                        uintPostn = j;  // 如果找到
                        break;
                    }
                }
                break;
            }

            if (moduleBase[j] == 0x90
                && moduleBase[j - 1] == 0x90
                && moduleBase[j - 2] == 0x90
                && moduleBase[j - 3] == 0x90
                )
            {
                for (j; j < vcMachList[i]; j++)   // 入口点特征
                {
                    if (moduleBase[j] == 0x48 && moduleBase[j + 1] == 0x89)
                    {
                        uintPostn = j;  // 如果找到
                        break;
                    }
                }
                break;
            }
        }

        if (uintPostn)
        {
            printf("匹配到函数入口点位于偏移: [0x%I64X] 处,动态地址:[0x%I64X]。\n",
                uintPostn, reinterpret_cast<uint64_t>(moduleBase) + uintPostn);
        }

    }

    int cwEnd = clock();

    //for (SIZE_T i = 0; i < vcMachList.size(); i++)
    //{
        //printf("匹配到模式字符串位于偏移: [0x%I64X] 处,动态地址:[0x%I64X]。\n", 
            //vcMachList[i], reinterpret_cast<uint64_t>(moduleBase) + vcMachList[i]);
    //}

    if (vcMachList.size() == 0)
    {
        printf("No Found.\n");
    }
    
    FreeLibrary(hModule);
    return cwEnd - cwStart;
}


int main() {
    // 暴力算法
    const wchar_t* moduleName = L"winlogon.exe";
    BYTE   pattern[] =
    { 0x48u, 0x8Bu, 0x0Du, 0, 0, 0, 0, 0x49u, 
      0x3Bu, 0xCCu, 0x74u , 0, 0x44, 0x84,
      0x79, 0x1C , 0x74 };// ETW Trace 特征码
    SIZE_T patternSize = 17; 
    DWORD dwRepeat = 1, dwSelect = 1; // 匹配第一次完整匹配,不重复匹配
    int TimeCost = 0;
    TimeCost = BFTracePatternInModule(moduleName, 
        pattern, patternSize, dwRepeat, dwSelect);
    printf("算法耗时:%d ms.\n", TimeCost);
    return 0;
}

运行结果如图: 

和 IDA 比对:

结果正确。

定位到了之后我们使用 Hook 就方便了,方法将在整理好后于下一篇继续讨论。

补充更新

后来发现 CC (INT3 软件断点)后面的数值是什么了,因为 winlogon 包含异常处理函数表,每一个内部函数都有异常处理信息,这很好用。

在目标函数之前,IDA 通过交叉引用(XREF)解析出了上一个函数的异常处理表地址

如下所示:

根据提供的 .pdata 节段中的数据,00007FF750263E28 是一个指向 RUNTIME_FUNCTION 结构的虚拟地址。RUNTIME_FUNCTION 结构通常用于异常处理和函数调用的信息,它包含了一系列函数范围和异常处理相关的信息。

在这个结构中,字段的解释如下:

00 03 96 20: 表示函数的开始地址相对于模块基址的偏移量,即函数的 RVA(相对虚拟地址)。
00 03 9A 54: 表示函数的结束地址相对于模块基址的偏移量,即函数的结束 RVA。
F6 F0: 表示异常处理信息的相对虚拟地址。
这些偏移量和地址都是相对于模块基址而言的,因此需要加上模块的基址才能得到实际的虚拟地址。通常情况下,IDA 可以通过分析二进制文件的导入表和段表等信息来确定模块的基址,并结合这些偏移量来计算实际的虚拟地址。

在这个特定的结构中,RUNTIME_FUNCTION 结构的字段通常与异常处理相关,其中包括函数的范围和异常处理的相关信息。这些信息在程序执行时由操作系统的异常处理机制使用,用于确定如何处理函数内部的异常。

根据正文第一个代码 CCCCCCCC 定位到的地址第一个 CC 的地址就是上一个函数的函数结束地址。通过遍历查找 pdata 就可以找到 WMsgKMessageHandler 的开始和结束位置,这样不管 WMsgKMessageHandler 入口是不是 mov rsp 啥的都可以准确定位了。

遍历 winlogon 模块的 pdata 段的代码如下:

#include <iostream>
#include <iostream>
#include <Windows.h>

int main() {
    const WCHAR filename[] = L"C:\\Windows\\System32\\winlogon.exe"; // winlogon 文件路径

    HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to open file" << std::endl;
        return 1;
    }

    HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (hMapping == NULL) {
        CloseHandle(hFile);
        std::cerr << "Failed to create file mapping" << std::endl;
        return 1;
    }

    LPVOID baseAddress = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
    if (baseAddress == NULL) {
        CloseHandle(hMapping);
        CloseHandle(hFile);
        std::cerr << "Failed to map view of file" << std::endl;
        return 1;
    }

    PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(baseAddress);
    if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
        UnmapViewOfFile(baseAddress);
        CloseHandle(hMapping);
        CloseHandle(hFile);
        std::cerr << "Not a valid DOS executable" << std::endl;
        return 1;
    }

    PIMAGE_NT_HEADERS ntHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<BYTE*>(baseAddress) + dosHeader->e_lfanew);
    if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) {
        UnmapViewOfFile(baseAddress);
        CloseHandle(hMapping);
        CloseHandle(hFile);
        std::cerr << "Not a valid NT executable" << std::endl;
        return 1;
    }

    PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);
    for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; ++i) {
        if (strcmp(reinterpret_cast<char*>(sectionHeader[i].Name), ".pdata") == 0) {
            DWORD pdataVirtualAddress = sectionHeader[i].VirtualAddress;
            DWORD pdataSize = sectionHeader[i].SizeOfRawData;

            DWORD pdataOffset = pdataVirtualAddress - sectionHeader[i].VirtualAddress + sectionHeader[i].PointerToRawData;
            PIMAGE_RUNTIME_FUNCTION_ENTRY pdata = reinterpret_cast<PIMAGE_RUNTIME_FUNCTION_ENTRY>(reinterpret_cast<BYTE*>(baseAddress) + pdataOffset);

            DWORD numEntries = pdataSize / sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY);
            for (DWORD j = 0; j < numEntries; ++j) {
                std::cout << "Function " << j << ": Start RVA: 0x" << std::hex << pdata[j].BeginAddress
                    << ", End RVA: 0x" << std::hex << pdata[j].EndAddress
                    << ", Unwind Info RVA: 0x" << std::hex << pdata[j].UnwindInfoAddress << std::endl;
            }

            break; // 找到 .pdata 节段后退出循环
        }
    }

    UnmapViewOfFile(baseAddress);
    CloseHandle(hMapping);
    CloseHandle(hFile);

    return 0;
}

运行结果如下:

计算结果一致,把这个功能整合到搜索代码中即可。

总结

本文就复现《禁止Ctrl+Alt+Del、Win+L等任意系统热键》一文中的方法四,为了实现拦截 Ctrl + Alt + Del 等热键,首先讨论了如何定位 WMsgKMessageHandler 这个关键函数。测试代码检测过 Win 8/10/11 x64 的部分版本系统,其他版本可能存在命中失败的现象。


发布于:2024.01.28,更新于:2024.01.28

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

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

相关文章

数字图像处理(实践篇)三十二 OpenCV-Python比较两张图片的差异

目录 一 方案 二 实践 ​通过计算两张图像像素值的均方误差(MSE)来比较两张图像。差异大的两张图片具有较大的均方差值,相反,相似的图片间则具有较小的均方差值。需要注意的是。待比较的两张图像要具有相同的高度、宽度和通道数。 一 方案 ①导入依赖库 import cv2 import…

【项目管理】CMMI-管理性能与度量

管理性能与度量 (Managing Performance and Measurement, MPM)的目的在于开发和维持度量能力来管理开发过程性能&#xff0c;以实现公司业务目标&#xff0c;更直接来说&#xff0c;将管理和改进工作集中在成本、进度表和质量性能上&#xff0c;最大限度地提高业务投资回报。 1…

vscode copilot怎么去掉提示代码(ghost text or incline completion)

原因&#xff1a;最近在刷题&#xff0c;被这个提示烦死了&#xff0c;记录一下怎么关掉&#xff0c;防止将来需要开启找不到了XD. 1.直接ctrlshiftp召唤设置 2.输入preferences: open usr settings找到如图第一个 3.去掉这个方框的勾选 ps直接在extension里disable不行呢 不…

OpenHarmony—仅允许在表达式中使用typeof运算符

规则&#xff1a;arkts-no-type-query 级别&#xff1a;错误 ArkTS仅支持在表达式中使用typeof运算符&#xff0c;不允许使用typeof作为类型。 TypeScript let n1 42; let s1 foo; console.log(typeof n1); // number console.log(typeof s1); // string let n2: typeof …

Arduino Uno R3通过ESP-01S连接网络

一、材料准备 Arduino Uno R3开发板 1 USB串口通信数据线&#xff08;Uno开发板使用&#xff09; 1 ESP8266-01S Wi-Fi模块 1 ESP8266固件烧录下载器&#xff08;烧录固件使用&#xff09; 1 WiFi无线收发转接板&#xff08;适用于ESP-01S、ESP-01&#xff09; 杜邦线…

iOS 面试 Swift基础题

一、Swift 存储属性和计算属性比较&#xff1a; 存储型属性:用于存储一个常量或者变量 计算型属性: 计算性属性不直接存储值,而是用 get / set 来取值 和 赋值,可以操作其他属性的变化. 计算属性可以用于类、结构体和枚举&#xff0c;存储属性只能用于类和结构体。存储属性可…

跟着cherno手搓游戏引擎【13】着色器(shader)

创建着色器类&#xff1a; shader.h:初始化、绑定和解绑方法&#xff1a; #pragma once #include <string> namespace YOTO {class Shader {public:Shader(const std::string& vertexSrc, const std::string& fragmentSrc);~Shader();void Bind()const;void Un…

总线协议:基于RS-485的Modbus协议(1):物理层实现

0 工具准备 Modbus协议规范&#xff08;中文&#xff09; 1 基于RS-485的Modbus协议的物理层实现 Modbus协议的物理层实现可以通过RS-485、RS-232、RS-422来实现&#xff0c;不过通常都是用RS-485作为Modbus协议的物理层实现。有关RS-485、RS-232、RS-422的区别如下&#xff1…

MySQL的SQL MODE

目录 举例&#xff1a; --常见SQL mode --mysql8 sql_mode 官方文档 https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html --查看全局的SQL MODE select global.sql_mode; --查看当前会话的SQL MODE select session.sql_mode; --运行时修改全局的SQL mode set gl…

【数据分享】1929-2023年全球站点的逐年平均气温数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;其中又以气温指标最为常用&#xff01;说到气温数据&#xff0c;最详细的气温数据是具体到气象监测站点的气温数据&#xff01;本次我们为大家带来的就是具体到气象监…

ChatGPT与文心一言:智能回复与语言准确性的较量

在当今数字化时代&#xff0c;随着人们对智能化技术的需求不断增长&#xff0c;智能回复工具也成为了日常生活中不可或缺的一部分。ChatGPT和文心一言作为两个备受瞩目的智能回复工具&#xff0c;在智能回复、语言准确性以及知识库丰富度等方面各有卓越之处。 本文将对这两者进…

每日一题——LeetCode1351.统计有序矩阵中的负数

方法一 暴力枚举&#xff1a; var countNegatives function(grid) {let count0for(let arr of grid){for(let num of arr){if(num<0){count}}}return count }; 消耗时间和内存情况&#xff1a; 方法二 二分法&#xff1a; var countNegatives function(grid) {const m …

Node.js的学习1

Node.js简介 浏览器是JavaScript的前端运行环境Node.js是JavaScript的后端运行环境Node.js中无法调用DOM和BOM等浏览器内置API 终端中的快捷键 使用向上箭头&#xff0c;可以快速定位到上一次执行的命令使用tab键&#xff0c;可以快速补全路径使用esc键&#xff0c;可以快速清…

工程对接大模型流式和非流式对话底层原理解析

文章目录 前言一、非流式输出设计二、stream流式输出设计三、手撸一个流式输出项目总结 前言 之前对接过OpenAi大模型的官方API&#xff0c;可以看到它有一个Stream参数&#xff0c;设置成true的时候就是流式的对话输出&#xff0c;现象就是一段一段的往外崩。 官方手册的地址…

qemu调试kernel启动(从第一行汇编开始)

一、背景 大部分qemu调试kernel 都是讲解从start_kernel开始设置断点&#xff0c;然后开启调试&#xff1b; 但是我们熟悉linux启动流程的伙伴肯定知道&#xff0c;在start_kernel之前还有一段汇编&#xff0c;包括初始化页表及mmu等操作&#xff0c; 这部分如何调试呢&#x…

AOP+Redisson 延时队列,实现缓存延时双删策略

一、缓存延时双删 关于缓存和数据库中的数据保持一致有很多种方案&#xff0c;但不管是单独在修改数据库之前&#xff0c;还是之后去删除缓存都会有一定的风险导致数据不一致。而延迟双删是一种相对简单并且收益比较高的实现最终一致性的方式&#xff0c;即在删除缓存之后&…

HarmonyOS --@state状态装饰器

在声明式UI中&#xff0c;是以状态驱动视图更新。 状态&#xff08;state&#xff09;&#xff1a;指驱动视图更新的数据&#xff08;被装饰器标记的变量&#xff09;。 试图&#xff08;view&#xff09;&#xff1a;基于UI描述渲染得到用户界面 State装饰器标记的变量必须初…

【华为 ICT HCIA eNSP 习题汇总】——题目集11

1、某公司的内网用户采用 NAT 技术的 NO-pat 方式访问互联网&#xff0c;若所有的公网地址均被使用&#xff0c;则后续上网的内网用户会&#xff08;&#xff09;。 A、挤掉前一个用户&#xff0c;强制进行 NAT 转换上网 B、将报文同步到其他 NAT 转换设备上进行 NAT 转换 C、自…

从零学习Linux操作系统 第二十部分 mariadb数据库的管理

一、对于数据库的基本介绍 1.什么是数据库 数据库就是个高级的表格软件 2.常见数据库 Mysql Oracle mongodb db2 sqlite sqlserver … 3.Mysql (SUN -----> Oracle) 4.mariadb (Mysql的一种&#xff09; 数据库中的常用名词 1.字段 &#xff1a;表格中的表头 2.表 &…

Qt QPlainTextEdit高亮显示当前行

Qt QPlainTextEdit高亮显示当前行 文章目录 Qt QPlainTextEdit高亮显示当前行摘要错误的代码正确的代码QTextEdit::ExtraSelection 关键字&#xff1a; Qt、 QPlainTextEdit、 QTextBlock、 ExtraSelection、 GPT 摘要 今天要在说一下GPT&#xff0c;当下如果你还不会用G…