Hook免杀实战: 去除杀软的三环钩子

Hook的概念

什么是Hook

Hook(也被称为“挂钩子”)是一种程序设计模式,它提供了一种方式去截获(或者“挂钩子”在)系统级别或者应用级别的函数调用、消息、事件等。通过使用Hook,开发者可以在不修改源程序的情况下,改变或者扩展操作系统、应用程序、驱动程序的功能

Hook的分类

Hook通常分为两种形式,分别是修改函数代码和修改函数地址

修改函数代码

  • Inline Hook: Inline Hook即内联Hook,是指直接修改目标函数的代码,通常是将目标函数的前几个字节修改为跳转指令,使得执行流跳转到我们自己的代码中。此类Hook需要备份被修改的代码,以便在执行完我们的代码后能正确地返回

修改函数地址

  • IAT HOOK: IAT(Import Address Table)是进程加载动态链接库时用到的一个表,存储了DLL函数的地址。IAT Hook就是修改这个表中的函数地址,将其指向我们的代码,从而实现Hook。
  • SSDT HOOK: SSDT(System Service Descriptor Table)是Windows系统中用于保存系统服务例程地址的表。SSDT Hook就是修改这个表中的函数地址,实现Hook。
  • IDT HOOK: IDT(Interrupt Descriptor Table)是用于保存中断处理函数地址的表。IDT Hook就是修改这个表中的函数地址,实现Hook。
  • EAT HOOK: EAT(Export Address Table)是动态链接库对外提供服务的函数地址表,EAT Hook就是修改这个表中的函数地址,实现Hook。
  • IRP HOOK: IRP(I/O Request Packet)是Windows系统中用于描述I/O请求的数据结构。IRP Hook是通过修改驱动程序的IRP处理函数的指针,将其指向我们的代码,从而实现Hook。

InlineHook

什么是InlineHook

Inline Hook,又称为超级Hook,是一种强大而又灵活的Hook技术。

Inline Hook的主要思想就是直接修改目标函数的代码,通常是在目标函数的开头插入一个跳转指令(jmp)。这个跳转指令会将程序的执行流跳转到我们自定义的函数中。

在我们的自定义函数中,我们可以执行任意的代码,然后再跳回目标函数的剩余部分。这样,我们就可以在不改变目标函数原有逻辑的基础上,添加自己的功能

一个简单的Hook实例

首先用C语言创建一个简单的加法程序

#include<Windows.h>
#include<cstdio>int ADD(int a,int b) {return a + b;
}int main() {int a, b;scanf("%d,%d",&a, &b);printf("结果是:%d", ADD(a, b));
}

将程序拖入OD中,并且找到Add函数的call的地址并在此处下个断点

image-20230614220510557

OD运行程序输入数据,这里我们输入1,2

image-20230614220602636

F7进入Add函数的call,通过右下角的堆栈窗口可以查看上述输入的实参(1,2),也就是说,如果将这个堆栈里的参数值给修改了,那么就可以修改函数的返回值

image-20230614220700796

首先在函数头部地址添加一个JMP汇编指令,跳转的地址是任意的,只要这个地址周围没有指令,这里以跳转到401045为例

image-20230614220901386

红色部分的汇编指令就是我们自行创建的,以此来实现修改堆栈中的函数参数值

这里也要注意平衡堆栈,原先的call是有push ebpmov ebp,esp指令的,由于添加了jmp指令导致它们被覆盖,所以这里要添上

最后的jmp指令再跳转回到401003

image-20230614221037045

最后程序运行结果为9,这是因为我们把参数从原先的1和2, 修改成了4和5

image-20230614221139586

代码实现思路

这里我们会介绍两种实现 Inline Hook 的方法:

  1. 使用 jmp 指令:这种方法通过插入一条 jmp 指令到目标函数的开始,使得函数在调用时直接跳转到我们自定义的函数。这里需要计算 jmp 后面的地址,因为 jmp 指令的目标地址是相对于下一条指令的位置计算的。计算公式为:jmp 后面的地址 = 目的地址 - 源地址 - 5。其中,5 是 jmp 指令的字节数。
  2. 使用 mov eax, addressjmp eax 指令:这种方法首先把自定义函数的地址加载到 eax 寄存器,然后使用 jmp eax 跳转到该地址。这样,当目标函数被调用时,它会直接跳转到我们自定义的函数。

这两种方法各有优劣。使用 jmp 指令的方法简洁直观,但对源地址和目的地址的位置有一定的限制,如果目标和源地址之间的距离过大,可能导致 jmp 指令无法正确跳转。而使用 mov eax, addressjmp eax 的方法则没有这个问题,但需要更多的指令,可能会覆盖掉目标函数的更多代码

代码实现

1.借助eax间接jmp

#include <windows.h> 
#include <stdio.h>    
#include <iostream>   
#include <tchar.h>    
BYTE NewCode[7] = { 0xB8, 0x0, 0x0, 0x0, 0x0, 0xFF ,0xE0 };  // 新代码,用于Hook
BYTE OldCode[7] = { 0 };                                     // 旧代码,用于保存被Hook函数的原始字节
FARPROC MessageBoxAddress;                                   // MessageBox函数的地址// 自定义的MessageBoxA函数
int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{printf("MessageBoxA 已经被Hook\n");  // 打印信息// 在调用原始函数之前,恢复原始代码WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)MessageBoxAddress, (void*)OldCode, 7, NULL);// 调用原始的MessageBoxA函数int ret = MessageBoxA(NULL, "Hello World", "Title", MB_OK);// 在调用原始函数之后,再次将Hook代码写入WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)MessageBoxAddress, (void*)NewCode, 7, NULL);return ret;
}void InlineHook()
{HMODULE hModule_User32 = LoadLibrary("user32.dll");  //加载user32.dll模块MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxA");  //获取MessageBoxA函数的地址printf("MessageBoxA Addr is %x\n", MessageBoxAddress);printf("MyMessageBoxA Addr is %x\n", MyMessageBoxA);// 读取MessageBoxA函数的前7个字节,并保存在OldCode数组中if (ReadProcessMemory(INVALID_HANDLE_VALUE, MessageBoxAddress, OldCode, 7, NULL) == 0){printf("ReadProcessMemory error\n");return;}printf("OldCode is %x%x%x%x%x%x%x\n", OldCode[0], OldCode[1], OldCode[2], OldCode[3], OldCode[4], OldCode[5], OldCode[6]);DWORD JmpAddress = (DWORD)MyMessageBoxA - (DWORD)MessageBoxAddress - 5;  // 获取自定义的MessageBoxA函数的地址memcpy(&NewCode[1], &JmpAddress, 4);  // 将地址写入到NewCode的第二个字节开始的位置DWORD dwOldProtect = 0;  // 用于保存原始页保护printf("NewBytes is %x%x%x%x%x\n", NewCode[0], NewCode[1], NewCode[2], NewCode[3], NewCode[4], NewCode[5], NewCode[6]);// 使用VirtualProtect函数改变MessageBoxA函数所在页的保护属性,使其可读可写可执行。VirtualProtect(MessageBoxAddress, 7, PAGE_EXECUTE_READWRITE,&dwOldProtect);// 使用WriteProcessMemory函数将我们的Hook代码写入到MessageBoxA函数的开头。WriteProcessMemory(INVALID_HANDLE_VALUE, MessageBoxAddress, NewCode, 7,NULL);// 使用VirtualProtect函数恢复MessageBoxA函数所在页的保护属性。VirtualProtect(MessageBoxAddress, 7, dwOldProtect, &dwOldProtect);
}void main()
{	InlineHook();MessageBoxA(NULL, "Hello World", "Title", MB_OK);
}

该代码首先将自定义函数MyMessageBoxA的地址加载到eax寄存器,然后通过jmp eax指令跳转到MyMessageBoxAOldCode用于保存原始的MessageBoxA函数的前7个字节

然后通过WriteProcessMemory函数恢复原始的MessageBoxA函数的前7个字节,接着调用原始的MessageBoxA函数,最后再次通过WriteProcessMemory函数将NewCode写回到MessageBoxA函数的开头,以确保下次调用MessageBoxA时仍然会被Hook

InlineHook函数中,程序首先获取MessageBoxA函数的地址,然后读取该地址的前7个字节并保存在OldCode数组中,接着计算出MyMessageBoxA函数的地址与MessageBoxA函数的地址之间的偏移量并保存在NewCode数组的第二个字节开始的位置,然后通过VirtualProtect函数修改MessageBoxA函数所在页的保护属性使其可读可写可执行,最后通过WriteProcessMemory函数将NewCode写入到MessageBoxA函数的开头

2.直接jmp

#include <windows.h>
#include <stdio.h>
#include <iostream>BYTE JmpOriginal[5] = { 0xE9, 0, 0, 0, 0 };  // 用于跳转到MyMessageBoxA的指令,0xE9代表JMP指令
BYTE OldCode[5] = { 0 };                      // 存储原始MessageBoxA的前5个字节
FARPROC MessageBoxAddress;                    // MessageBoxA的函数地址
void* Trampoline;                             // 桥接函数地址// 自定义的MessageBoxA函数
int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{printf("MessageBoxA 已经被Hook\n");  // 打印被Hook的信息// 使用桥接函数调用原始的MessageBoxA,这里需要类型转换int ret = ((int (WINAPI*)(HWND, LPCTSTR, LPCTSTR, UINT))Trampoline)(hWnd, lpText, lpCaption, uType);return ret;
}void InlineHook()
{HMODULE hModule_User32 = LoadLibraryA("user32.dll");  // 加载user32.dll模块MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxA");  // 获取MessageBoxA的函数地址DWORD JmpAddress = (DWORD)MyMessageBoxA - (DWORD)MessageBoxAddress - 5;  // 计算跳转到MyMessageBoxA的地址memcpy(&JmpOriginal[1], &JmpAddress, 4);  // 将跳转地址复制到JmpOriginal的第二个字节ReadProcessMemory(GetCurrentProcess(), MessageBoxAddress, OldCode, 5, NULL);  // 读取并保存MessageBoxA的前5个字节// 分配10个字节的内存空间作为桥接函数Trampoline = VirtualAlloc(NULL, 10, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);memcpy(Trampoline, OldCode, 5);  // 复制MessageBoxA的前5个字节到桥接函数// 计算并写入桥接函数的跳回地址DWORD jmpBackAddr = (DWORD)MessageBoxAddress + 5 - (DWORD)Trampoline - 5;memcpy((void*)((DWORD)Trampoline + 5), &JmpOriginal[0], 5);memcpy((void*)((DWORD)Trampoline + 6), &jmpBackAddr, 4);DWORD dwOldProtect;// 修改MessageBoxA的前5个字节的页属性,使其可读可写可执行VirtualProtect(MessageBoxAddress, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);// 替换MessageBoxA的前5个字节为跳转到MyMessageBoxA的指令WriteProcessMemory(GetCurrentProcess(), MessageBoxAddress, &JmpOriginal[0], 5, NULL);// 恢复MessageBoxA的前5个字节的原始页属性VirtualProtect(MessageBoxAddress, 5, dwOldProtect, &dwOldProtect);
}void main()
{InlineHook();  // 实施Inline HookMessageBoxA(NULL, "Hello World", "Title", MB_OK);  // 调用MessageBoxA函数
}

与上述代码不同的是, 这里需计算跳转到我们自定义的MyMessageBoxA函数的跳转指令。除此之外,我们还在虚拟内存分配了一块区域用来存放被覆盖的原始代码,并在其后面添加一个跳转指令,使其能够返回被Hook的函数,这样做的好处是可以避免每次调用hook函数时都需要恢复和再次修改被Hook的函数代码

3.使用detours库

在windows 10操作系统中由于ASLR(地址随机化)的缘故,手工实现InLine比较麻烦,这里使用微软的一个轻量级的开源库Detours,

Detours 是一个由 Microsoft Research 开发的库,用于钩取和修改 Windows API 调用和其他函数调用。你可以从其 GitHub 仓库下载它:https://github.com/microsoft/Detours

这里我使用vcpkg来按照Detours库,运行如下命令,至于如何安装和使用vcpkg可以看这篇文章:https://blog.csdn.net/xf555er/article/details/130465197

vcpkg install detours

以下是具体的实现代码:

#include<Windows.h>
#include<stdio.h>
#include <detours/detours.h>// 声明一个函数指针OldMessageBoxA,指向MessageBoxA函数
static int (WINAPI* OldMesssageBoxA)
(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType) = MessageBoxA;// 自定义函数MyFunction0,当MessageBoxA被调用时,会跳转到这个函数
int WINAPI MyFunction0(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{// 在这个函数中,调用原来的MessageBoxA函数,显示特定的信息return OldMesssageBoxA(NULL, "Hook Success!", "Warming", MB_OKCANCEL);
}int main() {// 开始一次新的Detour操作DetourTransactionBegin();// 告诉Detour这个线程将被影响DetourUpdateThread(GetCurrentThread());// 将OldMessageBoxA函数指针替换为MyFunction0,也就是说当MessageBoxA被调用时,跳转到MyFunction0DetourAttach(&(PVOID&)OldMesssageBoxA, MyFunction0);// 如果需要移除Hook,可以调用DetourDetach// DetourDetach(&(PVOID&)OldMesssageBoxA, MyFunction0);// 提交Detour操作DetourTransactionCommit();// 调用MessageBoxA,但实际上会跳转到MyFunction0MessageBoxA(0, 0, 0, 0);return 0;
}

定义一个函数指针 OldMessageBoxA,这个函数指针指向了MessageBoxA函数,同时定义了一个新的函数MyFunction0

DetourTransactionBegin来开始一个新的Detour操作,然后使用DetourUpdateThread来更新当前线程的状态,使得Detour操作能影响到当前线程

DetourAttach函数是Detour操作的核心,这个函数会将OldMessageBoxA的调用重定向到MyFunction0,也就是说,当其他代码尝试调用MessageBoxA时,实际上会调用MyFunction0

调用DetourTransactionCommit来提交Detour操作,使得之前的重定向生效。这时候,任何尝试调用MessageBoxA的代码,实际上都会调用MyFunction0

注意,如果想要取消Detour操作,只需要使用DetourDetach函数,将OldMessageBoxAMyFunction0之间的重定向取消即可

编写Dll

代码实现

如下是通过detour库实现inlineHook的动态链接库代码:

#include<Windows.h>
#include<stdio.h>
#include <detours/detours.h>// 声明一个函数指针OldMessageBoxA,指向MessageBoxA函数
static int (WINAPI* OldMesssageBoxA)
(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType) = MessageBoxA;// 自定义函数MyFunction0,当MessageBoxA被调用时,会跳转到这个函数
int WINAPI MyFunction0(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{// 在这个函数中,调用原来的MessageBoxA函数,显示特定的信息return OldMesssageBoxA(NULL, "Hook Success!", "Warming", MB_OKCANCEL);
}DWORD WINAPI ThreadProc(LPVOID lpParameter)
{   // 开始一次新的Detour操作DetourTransactionBegin();// 告诉Detour这个线程将被影响DetourUpdateThread(GetCurrentThread());// 将OldMessageBoxA函数指针替换为MyFunction0,也就是说当MessageBoxA被调用时,跳转到MyFunction0DetourAttach(&(PVOID&)OldMesssageBoxA, MyFunction0);// 如果需要移除Hook,可以调用DetourDetach// DetourDetach(&(PVOID&)OldMesssageBoxA, MyFunction0);// 提交Detour操作DetourTransactionCommit();// 调用MessageBoxA,但实际上会跳转到MyFunction0//MessageBoxA(0, 0, 0, 0);return 0;
};BOOL APIENTRY DllMain(HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved
)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH: {HANDLE hThread = CreateThread(NULL, 0, ThreadProc, (LPVOID)NULL, 0, NULL);break;}case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;
}

如下是用来测试InlineHook_dll的可执行程序代码, 在没被Hook的情况下会弹框提示"HelloWorld", 被Hook后会弹框提示"Hook Success!"

#include <Windows.h>
#include <stdio.h>int main() {LoadLibraryA("InlineHook_dll.dll");getchar();MessageBoxA(NULL, "HelloWorld", "窗口标题", NULL);return 0;
}

运行测试

执行HookTest.exe, 按回车后出现弹框, 提示"Hook Success!"

动画

参考文章

  • https://idiotc4t.com/persistence/detous-inline-hook#inline-hook-jian-jie

去除Ntdll的Hook

简介

许多杀毒软件使用钩子(hook)技术来监视系统的API函数调用,并检测潜在的恶意代码行为。Ntdll.dll是Windows操作系统的一个核心动态链接库,其中包含许多系统级API函数。通过在Ntdll上设置钩子,杀毒软件可以拦截这些API函数的调用,并检查它们的参数和返回值,以便发现可能的恶意行为

下图是没有被杀软挂钩子的NtCreateThread函数

image-20230630171325746

下图是被BitDefender挂钩子的NtCreateThread函数,钩子是jmp 7FFAACAD0264

image-20230630171710947

代码思路

UNHOOKntdll()函数的主要目标是移除对ntdll.dll文件的任何hooks

1.获取被挂钩的ntdll信息

函数首先通过GetModuleHandleA("ntdll.dll")获取ntdll模块的句柄,这个句柄指向当前进程中已经加载的ntdll.dll

GetModuleInformation()被用于获取ntdll模块的信息,包括模块的基地址等。此处获取到的信息将被用于后续操作

2.打开新的ntdll并映射至进程空间

通过调用CreateFileA()打开系统目录下的原始ntdll.dll文件。这个文件没有被任何hook修改,所以可以作为一个“清洁”的源来恢复被hook修改的部分

使用CreateFileMapping()MapViewOfFile()函数将原始ntdll文件映射至当前进程的地址空间中

3.查找ntdll的text节区

遍历当前进程中加载的ntdll模块的所有section(节区)。在Windows PE文件格式(包括DLL和EXE)中,一个section是包含特定类型数据的一个内存区块,例如代码或者数据。特别地,.text section通常包含程序的代码

4.替换text节区的内容

通过调用VirtualProtect()改变它的内存保护属性,使其成为可读、可写、可执行。这样才能修改该内存区块的内容

通过memcpy()将原始ntdll文件中的.text section的内容复制到当前进程的ntdll模块的对应部分。这个操作实际上就是“恢复”了被hook修改过的代码,因为现在它被原始的、没有被hook的代码所替代

代码实现

#include <Windows.h>
#include <TlHelp32.h>
#include <iostream>
#include <winternl.h>
#include <psapi.h>// 智能句柄,自动管理系统资源
struct SmartHandle {HANDLE handle; // 系统句柄SmartHandle(HANDLE handle) : handle(handle) {} // 构造函数,接收系统句柄~SmartHandle() { // 析构函数if (handle != INVALID_HANDLE_VALUE) { // 判断句柄是否有效CloseHandle(handle); // 如果有效,关闭句柄}}
};DWORD UNHOOKntdll() {MODULEINFO mi = {}; // 定义模块信息结构体HMODULE ntdllModule = GetModuleHandleA("ntdll.dll"); // 获取ntdll模块的句柄GetModuleInformation(HANDLE(-1), ntdllModule, &mi, sizeof(mi)); // 获取模块信息,存放到miLPVOID ntdllBase = (LPVOID)mi.lpBaseOfDll; // 获取ntdll模块的基址// 使用智能句柄打开ntdll.dll文件SmartHandle ntdllFile(CreateFileA("c:\\windows\\system32\\ntdll.dll",GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL));// 创建文件映射,映射ntdll.dll文件SmartHandle ntdllMapping(CreateFileMapping(ntdllFile.handle, NULL, PAGE_READONLY |SEC_IMAGE, 0, 0, NULL));// 映射视图,得到映射地址LPVOID ntdllMappingAddress = MapViewOfFile(ntdllMapping.handle, FILE_MAP_READ, 0,0, 0);// 读取ntdll模块的DOS头PIMAGE_DOS_HEADER hookedDosHeader = (PIMAGE_DOS_HEADER)ntdllBase;// 读取ntdll模块的NT头PIMAGE_NT_HEADERS hookedNtHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)ntdllBase+ hookedDosHeader->e_lfanew);// 遍历所有节for (WORD i = 0; i < hookedNtHeader->FileHeader.NumberOfSections; i++) {// 读取每个节的头信息PIMAGE_SECTION_HEADER hookedSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(hookedNtHeader) +((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i));// 检查是否是.text节if (!strcmp((char*)hookedSectionHeader->Name, (char*)".text")) {DWORD oldProtection = 0; // 用来保存旧的保护属性try {// 修改内存保护属性为可读可写可执行BOOL isProtected = VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase +(DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtection);// 使用memcpy替换.text节的内容memcpy((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress),(LPVOID)((DWORD_PTR)ntdllMappingAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize);}catch (...) {// 如果在修改过程中发生异常,确保恢复内存的保护属性VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, oldProtection, &oldProtection);throw; // 再次抛出异常,让上层调用者知道发生了异常}// 确保我们恢复了内存的保护属性VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, oldProtection, &oldProtection);}}FreeLibrary(ntdllModule); // 释放模块return 0;
}int main() {getchar();UNHOOKntdll();return 0;
}

运行测试

重载Ntdll后,可以发现杀软的钩子被去除掉了

image-20230630183555397

END

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

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

相关文章

基于Echarts的中国地图数据展示

概述 基于echarts的大数据中国地图展示&#xff0c;结合API定制&#xff0c;开发样式&#xff0c;监听鼠标事件&#xff0c;实现带参数路由跳转等自定义事件。 详细 一、概述 实际项目中大概率会遇到很多需要进行数据展示的地方&#xff0c;如折现图&#xff0c;柱状图等&…

每日一博 - MPP(Massively Parallel Processing,大规模并行处理)架构

文章目录 概述优点缺点小结 概述 MPP&#xff08;Massively Parallel Processing&#xff0c;大规模并行处理&#xff09;架构是一种常见的数据库系统架构&#xff0c;主要用于提高数据处理性能。它通过将多个单机数据库节点组成一个集群&#xff0c;实现数据的并行处理。 在 …

SAP SQL/CDS新功能货币汇率转换CURRENCY_CONVERSION( p1 = a1, p2 = a2, … )

1. 示例 PARAMETERS: p_waers TYPE mseg-waers OBLIGATORY DEFAULT USD.SELECT SUM( currency_conversion( amount a~hsl, "转换的金额source_currency b~isocd, "源货币target_currency p_waers, "目标货币exchange_rate_dat…

intelij idea 2023 创建java web项目

1.点击New Project 2.创建项目名称为helloweb &#xff0c;jdk版本这里使用8&#xff0c;更高版本也不影响工程创建 点击create 3.新建的工程是空的&#xff0c;点击File-> Project Structure 4.点击Modules 5.点击加号&#xff0c;然后键盘输入web可以搜索到web模块&…

阿里云容器镜像服务ACR(Alibaba Cloud Container Registry)推送镜像全过程及总结

前提&#xff1a;安装配置好docker&#xff0c;可参考我这篇 基于CentOS7安装配置docker与docker-compose。 一、设置访问凭证 1.1 容器镜像服务ACR 登录进入阿里云首页&#xff0c;点击 产品-容器-容器镜像服务ACR 点击管理控制台 1.2 进入控制台-点击实例列表 个人容器…

QT的工程文件认识

目录 1、QT介绍 2、QT的特点 3、QT模块 3.1基本模块 3.2扩展模块 4、QT工程创建 1.选择应用的窗体格式 2.设置工程的名称与路径 3.设置类名 4.选择编译器 5、QT 工程解析 xxx.pro 工程配置 xxx.h 头文件 main.cpp 主函数 xxx.cpp 文件 6、纯手工创建一个QT 工程…

uniapp 回退到指定页面 保存页面状态

uniapp 历史页面回退到指定页面。 getCurrentPages() 内容如下 let delta getCurrentPages().reverse().findIndex(item > item.route "pages/popularScience/daodi") if(delta-1){uni.navigateTo({url: /pages/popularScience/daodi,success: res > {},fa…

【C++奇遇记】内存模型

&#x1f3ac; 博客主页&#xff1a;博主链接 &#x1f3a5; 本文由 M malloc 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;LeetCode刷题集 数据库专栏 初阶数据结构 &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如…

HexoAssistant——博客上传助手(含源码)

文章目录 HexoAssistant——博客上传助手(含源码)1 前言2 效果演示3 源码地址4 总结 HexoAssistant——博客上传助手(含源码) 1 前言 旅行之余&#xff0c;用PyQt5写了一个博客上传的工具&#xff0c;旨在更加便捷地将本地文章上传Github博客。之前虽然配置过hexogithub的博客…

关于打包多模块SpringBoot项目并通过宝塔上传服务器

打包 —— 如何打包多模块项目&#xff0c;参考b站up主&#xff1a;[喜欢编程的代先生] 的视频 总结&#xff1a;1. 对着视频里看一下父模块和各个子模块pom.xml文件关于打包工具的依赖是否正确。2. 从最底层开始打包&#xff0c;逐层向上&#xff0c;最后再合并打包。 部署 …

【计算机网络篇】TCP协议

✅作者简介&#xff1a;大家好&#xff0c;我是小杨 &#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; TCP协议 1&#xff0c;TCP 简介 TCP&#xff08;Transmission Control Protocol&#xff09;是…

uniapp,使用canvas制作一个签名版

先看效果图 我把这个做成了页面&#xff0c;没有做成组件&#xff0c;因为之前我是配合uview-plus的popup弹出层使用的&#xff0c;这种组件好像是没有生命周期的&#xff0c;第一次打开弹出层可以正常写字&#xff0c;但是关闭之后再打开就不会显示绘制的线条了&#xff0c;还…

【C语言进阶(4)】指针和数组笔试题

文章目录 Ⅰ 一维数组Ⅱ 字符数组题型 1题型 2题型 3 Ⅲ 二维数组 数组名的意义 sizeof(数组名)&#xff0c;这里的数组名表示整个数组&#xff0c;计算的是整个数组的大小。&数组名&#xff0c;这里的数组名表示的是整个数组&#xff0c;取出的是整个数组的地址。除了上述…

2023年03月 C/C++(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;和数 给定一个正整数序列&#xff0c;判断其中有多少个数&#xff0c;等于数列中其他两个数的和。 比如&#xff0c;对于数列1 2 3 4, 这个问题的答案就是2, 因为3 2 1, 4 1 3。 时间限制&#xff1a;10000 内存限制&#xff1a;65536 输入 共两行&#x…

AMBA总线协议(6)——AHB(四):传输细节

一、前言 在之前的文章中&#xff0c;我们已经讲述了AHB传输中的两种情况&#xff0c;基本传输和猝发传输。我们进行一个简单的回顾&#xff0c;首先&#xff0c;开始一次传输之前主机需要向仲裁器申请获得总线的使用权限&#xff0c;然后主机给出地址和控制信号&#xff0c;根…

【JavaEE进阶】SpringMVC

文章目录 一. 简单认识SpringMVC1. 什么是SpringMVC?2. SpringMVC与MVC的关系 二. SpringMVC1. SpringMVC创建和连接2. SpringMVC的简单使用2.1 RequestMapping 注解介绍2.2 RequestMapping支持的请求类型2.3 GetMapping 和 PostMapping 3. 获取参数3.1 传递单个参数3.2 传递对…

【linux】2 Linux编译器-gcc/g++和Linux调试器-gdb

文章目录 一、Linux编译器-gcc/g使用1.1 背景知识1.2 gcc如何完成1.3 函数库1.4 gcc选项 二、linux调试器-gdb使用2.1 背景2.2 开始使用 总结 ヾ(๑╹◡╹)&#xff89;" 人总要为过去的懒惰而付出代价ヾ(๑╹◡╹)&#xff89;" 一、Linux编译器-gcc/g使用 1.1 背景…

/root/.ssh/config line 2: Bad protocol 2 host key algorithms ‘+ssh-rsa‘.

文章目录 1、问题2、查看openssh版本3、解决问题4、重新生成密钥5、查看是否可连接工蜂 1、问题 ssh访问工蜂报错&#xff1a; [rootlocalhost .ssh]# ssh -T gitgit.code.tencent.com /root/.ssh/config line 2: Bad protocol 2 host key algorithms ‘ssh-rsa’. 2、查看o…

升级Qt后VS项目不能使用

错误场景&#xff1a; 如果你的QT卸载了装了新版,那么VS你原来设置的项目就不能跑了. 问题 升级Qt后&#xff36;&#xff33;项目不能使用 跟我一起开始挽救自己的项目 升级Qt后&#xff36;&#xff33;项目不能使用 假如你从5.14.6 升级到 Qt6.2并删除了原来的QT 你在VS里…

Java网络编程(一)网络基础

概述 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统、网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递 网络分类 局域网(LAN) 局域网是一种在小区域内使用的,由多台计算机组成的网络,覆盖范围…