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

目录

一、利用Hardware Breakpoints Detection

二、PatchingDetection - CodeChecksumCalculation 补丁检测,代码检验和

三、block input 封锁键盘、鼠标输入

四、使用EnableWindow 禁用窗口

五、利用ThreadHideFromDebugger

六、使用Disabling Breakpoints 禁用硬件断点

七、OllyDbg:OutputDebugString()Format String Bug

八、TLS Callbacks


引言

        在程序反调试的技术领域,存在着多种多样的检测与防范手段。接下来将介绍其他反调试手段下篇,从硬件断点检测到TLS回调等一系列方法,这些方法利用不同原理来识别调试器或干扰调试过程,涉及对调试寄存器、代码校验和、输入封锁、窗口禁用、线程信息设置以及利用软件漏洞等多方面操作,为程序安全防护提供了丰富策略 。 

一、利用Hardware Breakpoints Detection

        硬件断点的设置是通过对名为`Dr0`到`Dr7`的调试寄存器进行操作来达成的。其中,`Dr0 - Dr3` 最多能存储 4 个断点的地址,`Dr6` 是一个标志位,它的作用是表明哪个断点被触发了,`Dr7` 里包含了控制这 4 个硬件断点的标志,比如可以启用、禁用断点,或者让断点在进行读/写操作时中断。

        因为调试寄存器在`Ring3`级别下无法被访问,所以要检测硬件断点的话,就得执行一小段代码。可以借助`CONTEXT`结构,这个结构里存有调试寄存器的值,通过传递给异常处理例程的`ContextRecord`参数就能访问它。

关键代码示例:

static bool isDebuggedHBP = 0;
LONG WINAPI TopUnhandledExceptionFilterHBP(struct _EXCEPTION_POINTERS *ExceptionInfo
) {__asm pushadAfxMessageBox("回调函数被调用");ExceptionInfo->ContextRecord->Eip = NewEip;if (0!= ExceptionInfo->ContextRecord->Dr0 || 0!= ExceptionInfo->ContextRecord->Dr1 ||0!= ExceptionInfo->ContextRecord->Dr2 || 0!= ExceptionInfo->ContextRecord->Dr3) {isDebuggedHBP = 1; //检测有无硬件断点}ExceptionInfo->ContextRecord->Dr0 = 0; //禁用硬件断点,置0ExceptionInfo->ContextRecord->Dr1 = 0;ExceptionInfo->ContextRecord->Dr2 = 0;ExceptionInfo->ContextRecord->Dr3 = 0;ExceptionInfo->ContextRecord->Dr6 = 0;ExceptionInfo->ContextRecord->Dr7 = 0;ExceptionInfo->ContextRecord->Eip = NewEip; //转移到安全位置__asm popadreturn EXCEPTION_CONTINUE_EXECUTION;
}void CDetectODDlg::OnHardwarebreakpoint() {//TODO:Add your control notification handler code herelpSetUnhandledExceptionFilter = (pSetUnhandledExceptionFilter)GetProcAddress(LoadLibrary("kernel32.dll"), "SetUnhandledExceptionFilter");lpOldHandler = (DWORD)lpSetUnhandledExceptionFilter(TopUnhandledExceptionFilterHBP);__asm {mov NewEip, offset safe //方式二,更简单int 3mov isDebuggedHBP, 1 //调试时可能也不会触发异常去检测硬件断点safe:}if (1 == isDebuggedHBP) {AfxMessageBox("发现OD");}else {AfxMessageBox("没有OD");}
}

二、PatchingDetection - CodeChecksumCalculation 补丁检测,代码检验和

        补丁检测技术能够判断壳的代码有没有被改动,也可以识别是否设置了软件断点。这种检测是通过代码校验完成的,校验计算会用到从简单到复杂的校验和/哈希算法。

        实例:要是修改了被保护的代码,就需要对`CHECKSUM`进行修改,可以用`OD`等工具找出这个值。
        注意:测试时,要在被保护的代码段设置`F2`断点,或者修改字节。

关键示例代码:

BOOL CheckSum() {BOOL bFoundOD;bFoundOD = FALSE;DWORD CHECK_SUM = 5555; //正确校验值DWORD dwAddr;dwAddr = (DWORD)CheckSum;__asm {;检测代码开始mov esi, dwAddrchecksum_loop:mov ecx, 100xor eax, eaxmovzx ebx, byte ptr [esi]add eax, ebxrol eax, 1inc esiloop checksum_loopcmp eax, CHECK_SUMjz ODNotFoundmov bFoundOD, 1ODNotFound:}return bFoundOD;
}void CDetectODDlg::OnChecksum() {//TODO:Add your control notification handler code hereif (CheckSum()) {AfxMessageBox("发现OD");}else {AfxMessageBox("没有OD");}
}

三、block input 封锁键盘、鼠标输入

        `user32!BlockInput()` 这个 API 的作用是阻断键盘和鼠标的输入。

        常见的情况是,逆向分析人员会在 `GetProcAddress()` 函数里设置断点,接着运行脱壳代码,直到程序在该断点处中断。然而,在跳过一段无意义的垃圾代码后,壳程序会调用 `BlockInput()` 函数。当 `GetProcAddress()` 处的断点触发时,逆向分析人员会突然发现无法操控调试器,却不清楚发生了什么状况。

        示例:`BlockInput()` 函数有个参数 `fBlockIt`,当它的值为 `true` 时,键盘和鼠标的事件会被阻断;当值为 `false` 时,键盘和鼠标的事件阻断状态会被解除。

;Block input
push TRUE
call [BlockInput]
;...Unpacking code...
;Unblock input
push FALSE
call [BlockInput]

 

void CDetectODDlg::OnBlockInput() //#include "Winable.h"
{//TODO:Add your control notification handler code hereCString str = "利用我定位";DWORD dwNoUse;DWORD dwNoUse2;::BlockInput(TRUE);dwNoUse = 2;dwNoUse2 = 3;dwNoUse = dwNoUse2;::BlockInput(FALSE);
}

应对办法:
        (1) 最简单的做法是给 `BlockInput()` 打补丁,让它直接返回结果。
        (2) 也可以同时按下 `CTRL+ALT+DELETE` 键,手动解除输入阻断。

四、使用EnableWindow 禁用窗口

        有一种操作和`BlockInput`很像,都是先把窗口禁用,之后再解禁。要是在资源管理器中直接双击运行相关程序,那么当前的资源管理器窗口就会被禁用。要是在`OD`(OllyDbg)中运行,那`OD`窗口就会被禁用。不过在`MFC`环境里,这种操作对`OD`好像不起作用。 

关键示例代码:

void CDetectODDlg::OnEnableWindow() {//TODO:Add your control notification handler code hereCString str = "利用我定位";CWnd *wnd;wnd = GetForegroundWindow();wnd->EnableWindow(FALSE);DWORD dwNoUse;DWORD dwNoUse2;dwNoUse = 2;dwNoUse2 = 3;dwNoUse = dwNoUse2;wnd->EnableWindow(TRUE);
}

五、利用ThreadHideFromDebugger

        `ntdll!NtSetInformationThread()` 这个函数是用来设置线程相关信息的。要是把 `ThreadInformationClass` 参数设置成 `ThreadHideFromDebugger(11H)`,就能让线程不产生调试事件。

        `ntdll!NtSetInformationThread` 函数的参数列表如下,其中 `ThreadHandle` 一般设置为当前线程的句柄 `(0xFFFFFFFE)`。

NTSTATUS NTAPI NtSetInformationThread(IN HANDLE ThreadHandle,IN THREAD_INFORMATION_CLASS ThreadInformaitonClass,IN PVOID ThreadInformation,IN ULONG ThreadInformationLength
);

        在`ThreadHideFromDebugger`的内部机制里,它会对内核结构`ETHREAD`中的`HideThreadFromDebugger`成员进行设置。只要完成了对这个成员的设置,原本负责向调试器发送事件的关键内核函数`_DbgkpSendApiMessage()`,就不会再被调用了。 

invoke GetCurrentThread
invoke NtSetInformationThread, eax, 11H, NULL, NULL

应对方法:
(1) 在 `ntdll!NtSetInformationThread()` 函数处设置断点,当程序执行到断点停下后,操控 `EIP` 寄存器,避免该 API 调用进入内核。
(2) `Olly Advanced` 插件有针对这个 API 打补丁的功能。打好补丁后,若 `ThreadInformaitonClass` 参数为 `HideThreadFromDebugger`,该 API 就不会再深入内核执行,只是简单返回。

关键代码示例:

typedef enum _THREADINFOCLASS {ThreadBasicInformation, //0YNThreadTimes, //1 YNThreadPriority, //2N YThreadBasePriority, //3NYThreadAffinityMask, //4N YThreadImpersonationToken, //5NYThreadDescriptorTableEntry, //6YNThreadEnableAlignmentFaultFixup, //7N YThreadEventPair, //8N YThreadQuerySetWin32StartAddress, //9Y YThreadZeroTlsCell, //10 N YThreadPerformanceCount, //11 YNThreadAmILastThread, //12 Y NThreadIdealProcessor, //13 N YThreadPriorityBoost, //14 YYThreadSetTlsArrayAddress, //15NYThreadIsIoPending, //16 Y NThreadHideFromDebugger //17 N Y
}THREAD_INFO_CLASS;typedef NTSTATUS(NTAPI *ZwSetInformationThread)(HANDLE ThreadHandle,IN THREAD_INFO_CLASS ThreadInformaitonClass,IN PVOID ThreadInformation,IN ULONG ThreadInformationLength
);void CDetectODDlg::OnZwSetInformationThread(CString str = "利用我定位"
) {HANDLE hwnd;HMODULE hModule;hwnd = GetCurrentThread();hModule = LoadLibrary("ntdll.dll");ZwSetInformationThread myFunc;myFunc = (ZwSetInformationThread)GetProcAddress(hModule, "ZwSetInformationThread");myFunc(hwnd, ThreadHideFromDebugger, NULL, NULL);
}

六、使用Disabling Breakpoints 禁用硬件断点

        完成相关操作之后,在`OD`(OllyDbg)里查看硬件断点,虽然界面上显示硬件断点还在,但实际上它们已经没法正常发挥作用了。这里用到了`CONTEXT`结构,这个结构可以通过异常处理获取到。当异常处理结束后,相关数据会自动写回到`CONTEXT`结构中。有关这方面更详细的内容,可以参考“Hardware Breakpoints Detection”部分 。

七、OllyDbg:OutputDebugString()Format String Bug

        `OutputDebugString`函数的功能是给调试器发送一个格式化的字符串,在`Ollydbg`中,这些信息会显示在界面底端。不过,`OllyDbg`存在一个格式化字符串溢出漏洞,这个漏洞很严重。程度轻的话,会让`OllyDbg`崩溃;严重的话,甚至能让攻击者执行任意代码。

        之所以会出现这个漏洞,是因为`Ollydbg`在处理传递给`kernel32!OutputDebugString()`的字符串参数时,过滤不够严格。它仅仅检查参数长度,最多接受255个字节,却不对参数的具体内容做检查,这就引发了缓冲区溢出问题。

        拿`printf`函数来举例,当使用`%d`这类格式化符号时,所有参数都压入栈后才调用`printf`函数。而`printf`函数没办法检测参数是否正确,只是按照顺序从栈中读取数据当作参数来用,这种情况就有可能破坏堆栈,导致栈中的信息泄露。

        示例:下面这个简单的例子,会让`OllyDbg`出现违规访问异常,或者毫无征兆地直接终止运行。 

szFormatStr db '%s%s',0
push offset szFormatStr
call OutputDebugString

对策:补丁 `kernel32!OutputDebugStringA()` 入口使之直接返回。

void CDetectODDlg::OnOutputDebugString() {//TODO:Add your control notification handler code here::OutputDebugString("%s%s%s");
}

八、TLS Callbacks

        用 `Thread Local Storage(TLS)` 回调函数,能在程序实际入口点之前就执行反调试代码,这就是 `OD` 一载入程序就退出的原因(也就是所谓的 `Anti - OD`)。

        线程本地存储器能把数据和特定执行线程关联起来。在一个进程里,每个线程访问同一个线程局部存储时,拿到的都是和自身线程绑定的数据块,相互独立。要动态绑定(运行时)线程特定数据,可以通过 `TLS API`(像 `TlsAlloc`、`TlsGetValue`、`TlsSetValue` 和 `TlsFree` 这些)来实现。除了现有的 API 方式,`Win32` 和 `Visual C++` 编译器现在也支持静态绑定(加载时)基于线程的数据。要是用 `_declspec(thread)` 声明 `TLS` 变量,编译器会把它们放到一个叫 `.tls` 的区块里。应用程序加载到内存时,系统会在可执行文件里找 `.tls` 区块,然后动态分配一块足够大的内存来存放 `TLS` 变量。同时,系统会把一个指向这块已分配内存的指针放到 `TLS` 数组里,这个数组由 `FS:[2CH]` 指向。

        在数据目录表中,第 9 索引的 `IMAGE_DIRECTORY_ENTRY_TLS` 条目的 `VirtualAddress` 指向 `TLS` 数据。要是这个值不为零,这里就是一个 `IMAGE_TLS_DIRECTORY` 结构,具体如下:

IMAGE_TLS_DIRECTORY32 STRUCStartAddressOfRawData DWORD? ;内存起始地址,用于初始化新线程的TLSEndAddressOfRawData DWORD? ;内存终止地址AddressOfIndex DWORD? ;运行库使用该索引来定位线程局部数据AddressOfCallBacks DWORD? ;PIMAGE_TLS_CALLBACK函数指针数组的地址SizeOfZeroFill DWORD? ;用0填充TLS变量区域的大小Characteristics DWORD? ;保留,目前为0
IMAGE_TLS_DIRECTORY32 ENDS

        `AddressOfCallBacks`所指向的是在主线程以及其他线程建立和退出时会被调用的回调函数。只要有线程创建或者销毁,存储在这个列表里的每一个函数都会被调用。一般情况下,很多程序中是不存在回调函数的,所以这个列表通常是空的。 `TLS`数据的初始化以及`TLS`回调函数的调用,都是在程序入口点之前就执行的,这意味着`TLS`是程序最先开始运行的部分。在程序要退出的时候,`TLS`回调函数还会再被执行一次。相关的回调函数具体情况如下:

TLS_CALLBACK proto Dllhandle:LPVOID,Reason:DWORD,Reserved:LPVOID

具体的参数情况如下:
- `Dllhandle`:它代表的是模块的句柄。
- `Reason` 有以下几种取值:
  - `DLL_PROCESS_ATTACH` 取值为 1 ,表示当一个新进程启动并被加载时的情况。
  - `DLL_THREAD_ATTACH` 取值为 2 ,意思是当一个新线程启动并被加载时的状态。
  - `DLL_THREAD_DETACH` 取值为 3 ,指的是一个新线程终止时的状态。
  - `DLL_PROCESS_DETACH` 取值为 0 ,表示终止一个新进程被加载的情况。
- `Reserverd`:这个参数是用来保留的,通常将其设置为 0 。

        `IMAGE_TLS_DIRECTORY` 结构里的地址属于虚拟地址,并非相对虚拟地址(`RVA`)。所以,要是可执行文件不是从基地址开始装入的,那么这些地址就会通过基址重定位来进行修正。并且,`IMAGE_TLS_DIRECTORY` 本身并不在 `.TLS` 区块内,而是位于 `.rdata` 区块中。

        想要识别 `TLS` 回调,可以借助像 `pedump` 这样的 `PE` 文件分析工具。要是可执行文件中存在 `TLS` 条目,那么相应的数据条目就会被显示出来。

Data        directory
EXPORT      rva:00000000 size:00000000
IMPORT      rva:00061000 size:000000E0
:::
TLS         rva:000610E0 size:00000018
:::
IAT         rva:00000000 size:00000000
DELAY_IMPORT rva:00000000 size:00000000
COM_DESCRPTR rva:00000000 size:00000000
unused      rva:00000000 size:00000000

        随后便会展示出`TLS`条目的真实内容。其中,`AddressOfCallBacks`成员所指向的,是一个以`null`作为结尾标识的回调函数数组。 

TLS        directory:
StartAddressOfRawData:                                  00000000
EndAddressOfRawData:                                  00000000
AddressOf     Index:                                        004610F8
AddressOfCallBacks:                 004610FC
SizeOfZeroFill:                                            00000000
Characteristics:                                            00000000

        在这个示例里,相对虚拟地址(RVA) `0x4610fc` 指向回调函数指针(`0x490f43` 和 `0x44654e`)。

        默认情况下,`OllyDbg` 加载程序时会在入口点暂停。你需要对 `OllyDbg` 进行配置,让它在 `TLS` 回调被调用之前,在实际的加载器(`loader`)处中断。

        具体操作是通过“选项 -> 调试选项 -> 事件 -> 第一次中断于 -> 系统断点”,将其设置为在 `ntdll.dll` 内的实际加载器代码处中断。完成这样的设置后,`OllyDbg` 会在执行 `TLS` 回调的 `ntdll!LdrpRunInitializeRoutines()` 之前,在 `ntdll!_LdrpInitializeProcess()` 处中断。此时,你就能够在回调例程中设置断点并进行跟踪。比如,在内存映像的 `.text` 代码段设置内存访问断点,就可以让程序在 `TLS` 回调函数处中断。

 关键示例代码:

.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib.data?
dwTLS_Index dd?
OPTION DOTNAME
;定义一个TLS节
.tls SEGMENT
TLS_Start LABEL DWORDdd 0100h dup("slt.")
TLS_End LABEL DWORD
.tls ENDS
OPTION NODOTNAME.data
TLS_CallBackStart dd TlsCallBack0
TLS_CallBackEnd dd 0
szTitle db "Hello TLS",0
szInTls db "我在TLS里",0
szInNormal db "我在正常代码内",0
szClassName db "ollydbg" ;OD类名;这里需要注意的是,必须要将此结构声明为PUBLIC,用于让连接器连接到指定的位置,
;其次结构名必须为_tls_used这是微软的一个规定。编译器引入的位置名称也如此
PUBLIC _tls_used
_tls_used IMAGE_TLS_DIRECTORY <TLS_Start,TLS_End,dwTLS_Index,TLS_CallBackStart,0,?.code
;*************************************** **********************
;;TLS的回调函数
TlsCallBack0 proc Dllhandle:LPVOID,dwReason:DWORD,IpvReserved:LPVOIDmov eax,dwReason ;判断dwReason发生的条件cmp eax,DLL_PROCESS_ATTACH ;在进行加载时被调用jnz ExitTlsCallBack0invoke FindWindow,addr szClassName,NULL ;通过类名进行检测.if eax ;找到invoke SendMessage,eax,WM_CLOSE,NULL,NULL.endifinvoke MessageBox,NULL,addr szInTls,addr szTitle,MB_OKmov dword ptr[TLS_Start],0xor eax,eaxinc eax
ExitTlsCallBack0:ret
TlsCallBack0 ENDP
;*********************************
Start:invoke MessageBox,NULL,addr szInNormal,addr szTitle,MB_OKinvoke ExitProcess,1
end Start

VC++6.0

        在VC里使用TLS回调,总会出现一些问题,主要有以下几种情况:
        1. VC6不支持TLS回调。
        2. VS2005存在一种情况,Debug版能正常使用TLS回调,但Release版不行。
        3. VS2005还有另一种情况,Release版使用TLS回调正常,Debug版却不正常。

        VC6不支持TLS回调,是因为VC6自带的TLSSUP.OBJ有问题。它已经把回调表的第一项定义为0,而0代表回调表结束,所以我们添加的函数都不会被调用。

        关于上述第2个问题,我没碰到过,倒是遇到了第3个问题。我对这个问题做了研究,发现问题出在Link过程中。节.CRTSXLA和.CRT$XLB合并时,按道理应该按照字母顺序无间隙合并,但在DEBUG版的输出里并非如此。虽然顺序是对的,但出现了很大的间隙,这些间隙被填为0,这就相当于在我们的回调表前加了好几个0,导致回调表提前结束,这可能是个BUG。对于第2种情况,我没遇到过,不确定是不是同样的原因,如果是的话,我觉得应该是LINK工具的BUG。

        针对这些问题,我一开始想使用VS2008的tlssup.obj,但它和VC6不兼容,修改起来很麻烦。后来我想到,或许可以自己创建一个tlssup.obj。基于这个想法,我编写了自己的tlssup。目前测试结果表明,它能兼容VC6、VS2005和VS2008。具体操作步骤如下:
        1. 建立一个控制台工程。
        2. 创建tlssup.c文件,代码如下。
        3. 把这个文件添加到工程中。
        4. 英文版操作:右键点击tlssup.c文件,选择Setting->C/C++->Gategory->Precomliled Headers->Not using precompiled headers。中文版操作:右键点击tlssup.c文件,选择设置->C/C++->预编译的头文件->不使用预补偿页眉,然后点击确定。

tlssup.c文件代码:

#include <windows.h>
#include <winnt.h>int _tls_index = 0;
#pragma data_seg(".tls")
int _tls_start = 0;
#pragma data_seg(".tls$ZZZ")
int _tls_end = 0;
#pragma data_seg(".CRT$XLA")
int xl_a = 0;
#pragma data_seg(".CRT$XLZ")
int xl_z = 0;
#pragma data_seg(".rdata$T")
extern PIMAGE_TLS_CALLBACK my_tls_callbacktbl[];
IMAGE_TLS_DIRECTORY32 _tls_used = {(DWORD)&_tls_start,(DWORD)&_tls_end,(DWORD)&_tls_index,(DWORD)my_tls_callbacktbl,0,0
};

        然后,我们在其它CPP文件中定义my_tls_callbacktbl如下即可:

extern "C" PIMAGE_TLS_CALLBACK my_tls_callbacktbl[] = {my_tls_callback1,0};
//可以有多个回调,但一定要在最后加一个空项,否则很可能出错。
当然下面一行也不能少:
#pragma comment(linker,"/INCLUDE: _tls_used")

工程cpp文件代码:

//TLS_CallBack_test.cpp:Defines the entry point for the console application.
#include <windows.h>
#include <winnt.h>
//下面这行告诉链接器在PE文件中要创建TLS目录  
#pragma comment(linker,"/INCLUDE: _tls_used")void NTAPI my_tls_callback1(PVOID h,DWORD reason,PVOID pv) {//仅在进程初始化创建主线程时执行的代码if(reason == DLL_PROCESS_ATTACH) {MessageBox(NULL,"hi,this is tls callback","title",MB_OK);}return;
}
#pragma data seg(".CRT$XLB")
extern "C" PIMAGE_TLS_CALLBACK my_tls_callbacktbl[] = {my_tls_callback1,0};
#pragma data seg()int main(void) {MessageBox(NULL,"hi,this is main()","title",MB_OK);return 0;
}

MFC里
1. tlssup.c文件同样设置
2. 代码

#pragma comment(linker,"/INCLUDE:tls_used")
/*这是PIMAGE_TLS_CALLBACK()函数的原型,其中第一个和第三个参数保留,第二个参数
决定函数在那种情况下*/
void NTAPI my_tls_callback1(PVOID h,DWORD reason,PVOID pv) {if(reason == DLL_PROCESS_ATTACH) {MessageBox(NULL,"hi,this is tls callback","title",MB_OK);}return;
}
#pragma data_seg(".CRT$XLB")
extern "C" PIMAGE_TLS_CALLBACK my_tls_callbacktbl[] = {my_tls_callback1,0};
#pragma data_seg()

 

 

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

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

相关文章

【笔记ing】AI大模型-03深度学习基础理论

神经网络&#xff1a;A neural network is a network or circuit of neurons,or in a modern sense,an artificial neural network,composed of artificial neurons or nodes.神经网络是神经元的网络或回路&#xff0c;或者在现在意义上来说&#xff0c;是一个由人工神经元或节…

基于Djiango实现中药材数据分析与可视化系统

中药材数据分析与可视化系统 项目截图 登录 注册 首页 药材Top20 药材价格 产地占比 历史价格 新闻资讯 后台管理 一、项目概述 中药材数据分析与可视化系统是一个基于Django框架开发的专业Web应用&#xff0c;致力于对各类中药材数据进行全面、系统的采集、分析和可视化展示…

【AI飞】AutoIT入门七(实战):python操控autoit解决csf视频批量转换(有点难,AI都不会)

背景&#xff1a; 终极目标&#xff1a;通过python调用大模型&#xff0c;获得结果&#xff0c;然后根据返回信息&#xff0c;控制AutoIT操作电脑软件&#xff0c;执行具体工作。让AI更具有执行力。 已完成部分&#xff1a; 关于python调用大模型的&#xff0c;可以参考之前的…

leetcode 188. Best Time to Buy and Sell Stock IV

目录 题目描述 第一步&#xff0c;明确并理解dp数组及下标的含义 第二步&#xff0c;分析明确并理解递推公式 1.求dp[i][j].holding 2.求dp[i][j].sold 第三步&#xff0c;理解dp数组如何初始化 第四步&#xff0c;理解遍历顺序 代码 题目描述 这道题把第123题推广为一…

【笔记】【C++】【基础语法】作用域(scope)、持续时间(duration)和链接(linkage)

【笔记】【C】【基础语法】作用域&#xff08;scope&#xff09;、持续时间&#xff08;duration&#xff09;和链接&#xff08;linkage&#xff09; 最近正在复习学习C&#xff08;查漏补缺ing&#xff09;。记录一下学习所得。希望能将所学都整理成一系列的笔记和博客。优先…

Yarn的安装及环境配置

### Yarn 安装教程及环境配置步骤 #### 1. 检查 Node.js 是否已安装 在安装 Yarn 前&#xff0c;需确认系统中已经安装了 Node.js。可以通过以下命令验证其是否存在并获取版本号&#xff1a; bash node -v 如果未安装&#xff0c;则需要先完成 Node.js 的安装。 --- #### 2…

day2-小白学习JAVA---java第一个程序

java第一个程序 1、新建一个文件&#xff0c;以.java为结尾2、用编辑器打开后写入代码&#xff08;本人写前端&#xff0c;所以用vscode&#xff0c;也可用其他&#xff09;3、编译文件4、运行文件5、HelloWorld代码解释6、文档注释 1、新建一个文件&#xff0c;以.java为结尾 …

docker部署springboot(eureka server)项目

打jar包 使用maven&#xff1a; <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>17</source><target>17&…

解读《人工智能指数报告 2025》:洞察 AI 发展新态势

美国斯坦福大学 “以人为本人工智能研究院”&#xff08;HAI&#xff09;近日发布的第八版《人工智能指数报告》&#xff08;AI Index Report 2025&#xff09;备受全球瞩目。自 2017 年首次发布以来&#xff0c;该报告一直为政策制定者、研究人员、企业高管和公众提供准确、严…

OpenGauss 数据库介绍

OpenGauss 数据库介绍 OpenGauss 是华为基于 PostgreSQL 开发的企业级开源关系型数据库&#xff0c;现已成为开放原子开源基金会的项目。以下是 OpenGauss 的详细介绍&#xff1a; 一 核心特性 1.1 架构设计亮点 特性说明优势多核并行NUMA感知架构充分利用现代CPU多核性能行…

使用Trae CN分析项目架构

架构分析后的截图 A区是打开的项目、B区是源码区、C区是AI给出当前项目的架构分析结果。 如何用 Trae CN 快速学习 STM32 嵌入式项目架构 在嵌入式开发领域&#xff0c;快速理解现有项目的架构是一项关键技能。Trae CN 作为一款强大的分析工具&#xff0c;能帮助开发者高效剖…

MCP协议量子加密实践:基于QKD的下一代安全通信(2025深度解析版)

一、量子计算威胁的范式转移与MCP协议改造必要性 1.1 传统加密体系的崩塌时间表 根据IBM 2025年量子威胁评估报告&#xff0c;当量子计算机达到4000个逻辑量子比特时&#xff08;预计2028年实现&#xff09;&#xff0c;现有非对称加密体系将在72小时内被完全破解。工业物联网…

STM32单片机入门学习——第40节: [11-5] 硬件SPI读写W25Q64

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.04.18 STM32开发板学习——第一节&#xff1a; [1-1]课程简介第40节: [11-5] 硬件SPI读…

Model Context Protocol (MCP) 开放协议对医疗多模态数据整合的分析路径【附代码】

Model Context Protocol (MCP) 作为一种革命性的开放协议,正在重塑医疗领域多模态数据整合的方式。本文将深入分析MCP协议在医疗多模态数据整合中的具体路径、技术实现、应用场景及未来发展方向,揭示这一协议如何成为连接AI与医疗数据的关键桥梁。 MCP协议概述及其在医疗多模…

刀片服务器的散热构造方式

刀片服务器的散热构造是其高密度、高性能设计的核心挑战之一。其散热系统需在有限空间内高效处理多个刀片模块产生的集中热量,同时兼顾能耗、噪音和可靠性。以下从模块化架构、核心散热技术、典型方案对比、厂商差异及未来趋势等方面展开分析: 一、模块化散热架构 刀片服务器…

java 排序算法-快速排序

快速排序&#xff08;Quick Sort&#xff09;是一种高效的排序算法&#xff0c;它使用分治法&#xff08;Divide and Conquer&#xff09;策略来把一个序列分为较小和较大的两个子序列&#xff0c;然后递归地排序两个子序列。 快速排序算法的基本思想&#xff1a; 选择基准值&…

Linux工具学习之【vim】

&#x1f4d6;vim 基本用法 要想学会 vim 先要学会进入与退出它 &#x1f4c3;进入 vim 首先要保证自己的 Linux 中已经安装好了 vim &#xff08;云服务器大多数都是出厂就安装好了&#xff09;&#xff0c;如果没有安装&#xff0c;需要在 root 用户下通过指令 yum instal…

win11系统截图的几种方式

在 Windows 11 中&#xff0c;系统内置的截图功能已全面升级&#xff0c;不仅支持多种截图模式&#xff0c;还整合了录屏、OCR 文字识别和 AI 增强编辑等功能。以下是从基础操作到高阶技巧的完整指南&#xff1a; 一、快捷键截图&#xff08;效率首选&#xff09; 1. Win Sh…

写论文时降AIGC和降重的一些注意事项

‘ 写一些研究成果&#xff0c;英文不是很好&#xff0c;用有道翻译过来句子很简单&#xff0c;句型很单一。那么你会考虑用ai吗&#xff1f; 如果语句太正式&#xff0c;高级&#xff0c;会被误判成aigc &#xff0c;慎重选择ai润色。 有的话就算没有用ai生成&#xff0c;但…

Java学习手册:Java并发编程最佳实践

在Java并发编程中&#xff0c;遵循最佳实践可以显著提高程序的性能、可靠性和可维护性。本文将总结Java并发编程中的关键最佳实践&#xff0c;帮助开发者避免常见陷阱并编写高效的并发程序。 1. 选择合适的并发工具 Java提供了丰富的并发工具&#xff0c;选择合适的工具可以简…