【Windows】实现窗口子类化(基于远程线程注入)

目录

前言

原理解释

完整项目

相关文献


文章出处链接:[https://blog.csdn.net/qq_59075481/article/details/140334106]

前言

众所周知,DLL 注入有多种用途,如热修补、日志记录、子类化等。本文重点介绍使用 DLL 注入对窗口进行子类化。子类化是通过更改窗口过程来重新定义窗口的行为。要更改窗口过程,窗口过程应驻留在创建窗口的进程中。我计划通过注入子类化模块在不了解创建窗口的进程的情况下替换窗口行为。本文涉及三个模块。

  1. GUITestProcess:这是创建窗口的目标进程。称为“GUITestProcess”,DLL(“SubClassModule”)被注入其中。这是一个简单的 Win32 应用程序,它有一个窗口,每当按下鼠标左键时,就会绘制一个圆圈。
  2. SubClassModule:这是一个 DLL,它具有新的窗口过程,可以挂接到被注入者的窗口。
  3. Injector:这是实际将注入物注入被注入者的进程。这是一个简单的控制台应用程序。

原理解释

窗口子类化有两种实现方式:一种是 SetWindowLongPtr 指定 GWLP_WNDPROC 参数,替换窗口过程函数,另外一种是使用 ComCtl32.dll (至少 6.0 版本)导出的 SetWindowSubclass 来设置子类化窗口过程(另外几个函数分别是:GetWindowSubclass、RemoveWindowSubclass 和 DefSubclassProc),后者提供更多扩展特性。本文以最基础的 SetWindowLongPtr 子类化进行讲解。

首先是一个桌面窗口程序实例代码:

// GUITestProcess.cpp : 定义应用程序的入口点。
//#include "framework.h"
#include "GUITestProcess.h"
#include <windowsx.h>#define MAX_LOADSTRING 100// Global Variables:
HINSTANCE hInst;								// current instance
TCHAR szTitle[MAX_LOADSTRING];					// The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];			// the main window class name// Forward declarations of functions included in this code module:
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR    lpCmdLine,int       nCmdShow)
{UNREFERENCED_PARAMETER(hPrevInstance);UNREFERENCED_PARAMETER(lpCmdLine);// TODO: Place code here.MSG msg;HACCEL hAccelTable;// Initialize global stringsLoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);LoadString(hInstance, IDC_INJECTEE, szWindowClass, MAX_LOADSTRING);MyRegisterClass(hInstance);// Perform application initialization:if (!InitInstance(hInstance, nCmdShow)){return FALSE;}hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_INJECTEE));// Main message loop:while (GetMessage(&msg, NULL, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg);DispatchMessage(&msg);}}return (int)msg.wParam;
}//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
//  COMMENTS:
//
//    This function and its usage are only necessary if you want this code
//    to be compatible with Win32 systems prior to the 'RegisterClassEx'
//    function that was added to Windows 95. It is important to call this function
//    so that the application will get 'well formed' small icons associated
//    with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{WNDCLASSEX wcex;wcex.cbSize = sizeof(WNDCLASSEX);wcex.style = CS_HREDRAW | CS_VREDRAW;wcex.lpfnWndProc = WndProc;wcex.cbClsExtra = 0;wcex.cbWndExtra = 0;wcex.hInstance = hInstance;wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_INJECTEE));wcex.hCursor = LoadCursor(NULL, IDC_ARROW);wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);wcex.lpszMenuName = MAKEINTRESOURCE(IDC_INJECTEE);wcex.lpszClassName = szWindowClass;wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));return RegisterClassEx(&wcex);
}//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{HWND hWnd;hInst = hInstance; // Store instance handle in our global variablehWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);if (!hWnd){return FALSE;}ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);return TRUE;
}//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND	- process the application menu
//  WM_PAINT	- Paint the main window
//  WM_DESTROY	- post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{int wmId, wmEvent;PAINTSTRUCT ps;HDC hdc;switch (message){case WM_LBUTTONDOWN:{int x = GET_X_LPARAM(lParam);int y = GET_Y_LPARAM(lParam);HDC hDC = ::GetDC(hWnd);::Ellipse(hDC, x, y, (x + 50), (y + 50));break;}case WM_COMMAND:wmId = LOWORD(wParam);wmEvent = HIWORD(wParam);// Parse the menu selections:switch (wmId){case IDM_ABOUT:DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);break;case IDM_EXIT:DestroyWindow(hWnd);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}break;case WM_PAINT:hdc = BeginPaint(hWnd, &ps);// TODO: Add any drawing code here...EndPaint(hWnd, &ps);break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;
}// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{UNREFERENCED_PARAMETER(lParam);switch (message){case WM_INITDIALOG:return (INT_PTR)TRUE;case WM_COMMAND:if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL){EndDialog(hDlg, LOWORD(wParam));return (INT_PTR)TRUE;}break;}return (INT_PTR)FALSE;
}

让我们看一下注入的源代码。众所周知,注入是一个简单的 DLL,当 DllMain 被调用时DLL_PROCESS_ATTACH,我正在进行 Hack。

//
case DLL_PROCESS_ATTACH:
{//Find the window of the Injectee using its titleHWND hwnd = ::FindWindow(NULL,TEXT("GUITestProcess") );//If the window found, then change it's window procif( hwnd ){oldWindowProc = ::SetWindowLongPtr( hwnd, GWLP_WNDPROC, (LONG_PTR) HackingWndProc );}break;
}

新的窗口过程看起来像这样,这个窗口过程将替换原窗口的左键单击和右键单击操作:

LRESULT CALLBACK HackingWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch( message ) {case WM_LBUTTONDOWN:{int x = GET_X_LPARAM(lParam);int y = GET_Y_LPARAM(lParam);HDC hDC = ::GetDC( hWnd );//::Rectangle(  hDC,x,y,(x+50),(y+50) );DrawText( hDC,x,y);break;}case WM_RBUTTONDOWN:{int x = GET_X_LPARAM(lParam);int y = GET_Y_LPARAM(lParam);HDC hDC = ::GetDC( hWnd );::Ellipse( hDC,x,y,(x+50),(y+50));break;}case WM_DESTROY:{PostQuitMessage(0);break;}default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;
}

接下来我们看一下 Injector 的源代码。

这段代码很简单,首先我们要知道要注入到目标进程(injectee)中的 DLL(SubClassModule)的名字。

这个DLL名称应该是目标进程知道的。因此,它必须写入目标进程的地址空间中。

  • 使用窗口标题找出 GUITestProcess 的窗口。
  • 接下来使用 创建窗口的进程的句柄 GetWindowThreadProcessId。
  • 使用句柄打开注入者的进程。
  • 通过命令行的参数获取要注入的 DLL 的路径。
  • 然后在被注入者的地址空间中分配内存以写入要注入的 DLL 的名称。
  • 获取 LoadLibrary 函数的地址。这是将在注入对象的地址空间中创建的线程要调用的方法。
  • 然后调用 NtCreateThreadEx 在被注入者的地址空间内创建一个线程,这个线程将调用 LoadLibrary 函数来注入 DLL。
  • 当线程调用 LoadLibrary 方法加载 DLL 时,DllMain 被调用的原因为 DLL_PROCESS_ATTACH。参阅注入的代码以查看 DllMain 调用时会发生什么。
  • 等待远程线程执行完毕后再调用该 VirtualFree 方法,否则当远程线程查找 DLL 的名称时,保存 DLL 名称的地址块的名称已被注入器进程释放,这可能会导致崩溃!

注入器的命令行参数:注入器程序路径   Dll 文件路径。

注入器主要代码: 

#include <iostream>
#include <windows.h>
#include <vector>
#include <tlhelp32.h>
#include <shlwapi.h>#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "advapi32.lib")#define INJECTEE_NAME TEXT("GUITestProcess")BOOL ProcessHasLoadDll(DWORD pid,const TCHAR* dll
);BOOL ZwCreateThreadExInjectDll(DWORD dwProcessId,const wchar_t* pszDllFileName
);int wmain(int argc, wchar_t* argv[])
{SetConsoleTitleW(L"SubClassWindowsInjector v.1.0");if (argc != 2){std::cerr << "错误:参数不合法!" << std::endl;std::cin.get();return -1;}const size_t dllpthlen =wcslen(argv[1]) * sizeof(wchar_t);if (dllpthlen < 1){std::cerr << "错误:文件路径错误!" << std::endl;std::cin.get();return -1;}wchar_t* dllpth = new wchar_t[dllpthlen];wcscpy_s(dllpth, dllpthlen, argv[1]);BOOL dllextflag = PathFileExistsW(dllpth);if (FALSE == dllextflag){std::cerr << "错误:文件不存在或者无法访问!" << std::endl;std::cin.get();return -1;}if (PathGetDriveNumberW(dllpth) == -1){TCHAR szPath[_MAX_PATH] = { 0 };TCHAR szDrive[_MAX_DRIVE] = { 0 };TCHAR szDir[_MAX_DIR] = { 0 };TCHAR szFname[_MAX_FNAME] = { 0 };TCHAR szExt[_MAX_EXT] = { 0 };TCHAR CurBinPath[MAX_PATH] = { 0 };GetModuleFileNameW(NULL, szPath, sizeof(szPath) / sizeof(TCHAR));ZeroMemory(CurBinPath, sizeof(CurBinPath));_wsplitpath_s(szPath, szDrive, szDir, szFname, szExt);wsprintf(CurBinPath, L"%s%s", szDrive, szDir);wcscat_s(CurBinPath, dllpth);dllextflag = PathFileExistsW(CurBinPath);if (FALSE == dllextflag){std::cerr << "错误:文件不存在或者无法访问!" << std::endl;std::cin.get();return -1;}dllpth = CurBinPath;}DWORD dwProcessID = 0;//Find the main window of the InjecteeHWND hwnd = ::FindWindow(NULL, INJECTEE_NAME);//Get the process hanlde of injecteeGetWindowThreadProcessId(hwnd, &dwProcessID);if (dwProcessID == 0) {std::cerr << "错误:找不到目标窗口!"<< std::endl;return 0;}if (ProcessHasLoadDll(dwProcessID, dllpth) != NULL){std::cerr << "警告:PID 为 " << dwProcessID << " 的进程已经包含目标 DLL。" << std::endl;return 1;}if (ZwCreateThreadExInjectDll(dwProcessID, dllpth) == FALSE){std::cerr << "错误:注入 PID 为 " << dwProcessID << " 的进程时失败。" << std::endl;std::cerr << "原因:" << GetLastError() << std::endl;return 1;}std::cout << "操作已经完成。" << std::endl;std::cin.get();return 0;
}BOOL ProcessHasLoadDll(DWORD pid, const TCHAR* dll) {/** 参数为TH32CS_SNAPMODULE 或 TH32CS_SNAPMODULE32时,如果函数失败并返回ERROR_BAD_LENGTH,则重试该函数直至成功* 进程创建未初始化完成时,CreateToolhelp32Snapshot会返回error 299,但其它情况下不会*/HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);while (INVALID_HANDLE_VALUE == hSnapshot) {DWORD dwError = GetLastError();if (dwError == ERROR_BAD_LENGTH) {hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);continue;}else {printf("CreateToolhelp32Snapshot failed: %d\ncurrentProcessId: %d \t targetProcessId:%d\n",dwError, GetCurrentProcessId(), pid);return FALSE;}}MODULEENTRY32W mi{};mi.dwSize = sizeof(MODULEENTRY32W); //第一次使用必须初始化成员BOOL bRet = Module32FirstW(hSnapshot, &mi);while (bRet) {// mi.szModule是短路径if (wcsstr(dll, mi.szModule) || wcsstr(mi.szModule, dll)) {if (hSnapshot != NULL) CloseHandle(hSnapshot);return TRUE;}mi.dwSize = sizeof(MODULEENTRY32W);bRet = Module32NextW(hSnapshot, &mi);}if (hSnapshot != NULL) CloseHandle(hSnapshot);return FALSE;
}BOOL ZwCreateThreadExInjectDll(DWORD dwProcessId,const wchar_t* pszDllFileName
)
{size_t pathSize = (wcslen(pszDllFileName) + 1) * sizeof(wchar_t);// 1.打开目标进程HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, // 打开权限FALSE, // 是否继承dwProcessId); // 进程 PIDif (hProcess == nullptr){printf("错误:打开目标进程失败!\n");return FALSE;}// 2.在目标进程中申请空间LPVOID lpPathAddr = VirtualAllocEx(hProcess, 0, pathSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (lpPathAddr == nullptr){printf("错误:在目标进程中申请空间失败!\n");CloseHandle(hProcess);return FALSE;}// 3.在目标进程中写入Dll路径if (FALSE == WriteProcessMemory(hProcess, lpPathAddr, pszDllFileName, pathSize, NULL)) // 实际写入大小{printf("错误:目标进程中写入Dll路径失败!\n");CloseHandle(hProcess);return FALSE;}//4.加载ntdll.dllHMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");if (hNtdll == nullptr){printf("错误:加载ntdll.dll失败!\n");CloseHandle(hProcess);return FALSE;}//5.获取LoadLibraryA的函数地址//FARPROC可以自适应32位与64位FARPROC pFuncProcAddr = GetProcAddress(GetModuleHandle(L"Kernel32.dll"),"LoadLibraryW");if (pFuncProcAddr == nullptr){printf("错误:获取LoadLibrary函数地址失败!\n");CloseHandle(hProcess);return FALSE;}//6.获取ZwCreateThreadEx函数地址,该函数在32位与64位下原型不同//_WIN64用来判断编译环境 ,_WIN32用来判断是否是Windows系统
#ifdef _WIN64typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(PHANDLE ThreadHandle,ACCESS_MASK DesiredAccess,LPVOID ObjectAttributes,HANDLE ProcessHandle,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,ULONG CreateThreadFlags,SIZE_T ZeroBits,SIZE_T StackSize,SIZE_T MaximumStackSize,LPVOID pUnkown);
#elsetypedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(PHANDLE ThreadHandle,ACCESS_MASK DesiredAccess,LPVOID ObjectAttributes,HANDLE ProcessHandle,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,BOOL CreateSuspended,DWORD dwStackSize,DWORD dw1,DWORD dw2,LPVOID pUnkown);
#endif typedef_ZwCreateThreadEx ZwCreateThreadEx =(typedef_ZwCreateThreadEx)GetProcAddress(hNtdll, "ZwCreateThreadEx");if (ZwCreateThreadEx == nullptr){printf("错误:获取ZwCreateThreadEx函数地址失败!\n");CloseHandle(hProcess);return FALSE;}//7.在目标进程中创建远线程HANDLE hRemoteThread = NULL;DWORD lpExitCode = 0;DWORD dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL,hProcess,(LPTHREAD_START_ROUTINE)pFuncProcAddr, lpPathAddr, 0, 0, 0, 0, NULL);if (hRemoteThread == nullptr){printf("错误:目标进程中创建线程失败!\n");CloseHandle(hProcess);return FALSE;}// 8.等待线程结束WaitForSingleObject(hRemoteThread, -1);GetExitCodeThread(hRemoteThread, &lpExitCode);if (lpExitCode == 0){printf("错误:目标进程中注入 DLL 失败,请检查提供的 DLL 是否有效!\n");CloseHandle(hProcess);return FALSE;}// 9.清理环境VirtualFreeEx(hProcess, lpPathAddr, 0, MEM_RELEASE);CloseHandle(hRemoteThread);CloseHandle(hProcess);FreeLibrary(hNtdll);return TRUE;
}

子类化效果:

子类化效果

完整项目

你可以从这里获取完整的项目代码:

链接:https://pan.baidu.com/s/1Q3og8dgv7lyzXsrGqZy4kg?pwd=6666 
提取码:6666 

相关文献

  • https://www.cnblogs.com/1yzq/p/12939791.html
  • https://learn.microsoft.com/zh-cn/windows/win32/api/commctrl/nf-commctrl-setwindowsubclass
  • https://stackoverflow.com/questions/65695710/how-to-subclass-another-application-control-using-dll-hook-in-win32
  • https://www.purebasic.fr/english/viewtopic.php?t=81714

文章出处链接:[https://blog.csdn.net/qq_59075481/article/details/140334106]。 

本文发布于:2024.07.10,修改于:2024.07.10。

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

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

相关文章

vue3 antdv Modal通过设置内容里的容器的最小高度,让Modal能够适当的变高一些

1、当收款信息Collapse也折叠的时候&#xff0c;我们会发现Modal的高度也变成了很小。 2、我们希望高度稍微要高一些&#xff0c;这样感觉上面显示的Modal高度太小了&#xff0c;显示下面的效果。 3、初始的时候&#xff0c;想通过class或者style或者wrapClassName来实现&#…

SCSA第四天

ASPF FTP --- 文件传输协议 Tftp --- 简单文件传输协议 FTP协议相较于Tftp协议 ---- 1&#xff0c;需要进行认证 2&#xff0c;拥有一套完整的命令集 用户认证 防火墙管理员认证 ---- 校验登录者身份合法性 用户认证 --- 上网行为管理中的一环 上网用户认证 --- 三层认证…

【区块链 + 智慧政务】省级一体化区块链平台 | FISCO BCOS应用案例

在加强数字政府建设的大背景下&#xff0c;科大讯飞广泛应用数字技术于政府管理服务&#xff0c;推动政府数字化、智能化运行。同时&#xff0c; 统筹推进业务、数据和技术的融合&#xff0c;提升跨地域、跨层级、跨部门和跨业务的协同管理和服务水平。 当前政务信息化建设中&…

交易员需要克服的十大心理问题

撰文&#xff1a;Koroush AK 编译&#xff1a;Chris&#xff0c;Techub News 本文来源香港Web3媒体&#xff1a;Techub News 一个交易者在交易上所犯下的最大的错误可能更多来自于心态的失衡而并非技术上的失误&#xff0c;类似的情况已经发生在了无数交易者身上。作为交易者…

[论文笔记]RAPTOR: RECURSIVE ABSTRACTIVE PROCESSING FOR TREE-ORGANIZED RETRIEVAL

引言 今天带来又一篇RAG论文笔记&#xff1a;RAPTOR: RECURSIVE ABSTRACTIVE PROCESSING FOR TREE-ORGANIZED RETRIEVAL。 检索增强语言模型能够更好地适应世界状态的变化并融入长尾知识。然而&#xff0c;大多数现有方法只能从检索语料库中检索到短的连续文本片段&#xff0…

shark云原生-日志体系-filebeat高级配置(适用于生产)-更新中

文章目录 1. filebeat.inputs 静态日志收集器2. filebeat.autodiscover 自动发现2.1. autodiscover 和 inputs2.2. 如何配置生效2.3. Providers 提供者2.4. Providers kubernetes2.5. 配置 templates2.5.1. kubernetes 自动发现事件中的变量字段2.5.2 配置 templates 2.6. 基于…

无法访问。你可能没有权限使用网络资源。请与这台服务器的管理员联系以查明你是否有访问权限。【解决办法】

问题描述 新建好一台windows虚拟机&#xff0c;两台设备网络是互通的&#xff0c;但是物理机在访问虚拟机的网络共享文件资源时&#xff0c;出现图下所示的报错&#xff1a;XXX无法访问。你可能没有权限使用网络资源。请与这台服务器的管理员联系以查明你是否有访问权限。用户…

LoRaWAN网络协议Class A/Class B/Class C三种工作模式说明

LoRaWAN是一种专为广域物联网设计的低功耗广域网络协议。它特别适用于物联网&#xff08;IoT&#xff09;设备&#xff0c;可以在低数据速率下进行长距离通信。LoRaWAN 网络由多个组成部分构成&#xff0c;其中包括节点&#xff08;终端设备&#xff09;、网关和网络服务器。Lo…

MATLAB engine for python调用m文件函数输出变量值python调用MATLAB函数混合编程

MATLAB engine for python调用m文件函数输出变量值python调用MATLAB函数混合编程 说明(废话)解决方案总结 说明(废话) python调用MATLAB函数&#xff0c;MATLAB函数实现在m文件&#xff0c;python直接调用MATLAB中的函数。 首先还是要安装好MATLAB engine python setup.py ins…

技术文件国产化准备

技术文档的本地化涉及调整内容以满足特定目标市场的文化、语言和技术要求。这一过程超越了简单的翻译&#xff0c;确保文件在文化上适合预期受众&#xff0c;在技术上准确无误。适当的准备对于成功的本地化至关重要&#xff0c;以下步骤概述了一种全面的方法。 分析目标受众 …

勇攀新高峰|暴雨信息召开2024年中述职工作会议

7月8日至9日&#xff0c;暴雨信息召开2024年中述职工作会议&#xff0c;总结回顾了上半年的成绩和不足&#xff0c;本次会议采用线上线下的方式举行&#xff0c;公司各部门管理人员、前台市场营销人员参加述职&#xff0c;公司领导班子出席会议。 本次述职采取了现场汇报点评的…

关于数组的常见算法

一、案例一 案例说明 案例&#xff1a;定义一个int型的一维数组&#xff0c;包含10个元素&#xff0c;分别赋一些随机整数&#xff0c;然后求出所有元素的最大值&#xff0c;最小值&#xff0c;总和&#xff0c;平均值&#xff0c;并输出出来 要求&#xff1a;所有随机数都是两…

51单片机:电脑通过串口控制LED亮灭(附溢出率和波特率详解)

一、功能实现 1.电脑通过串口发送数据&#xff1a;0F 2.点亮4个LED 二、注意事项 1.发送和接受数据的文本模式 2.串口要对应 3.注意串口的波特率要和程序中的波特率保持一致 4.有无校验位和停止位 三、如何使用串口波特率计算器 1.以本程序为例 2.生成代码如下 void Uar…

学圣学最终的目的是:达到思无邪的状态( 纯粹、思想纯正、积极向上 )

学圣学最终的目的是&#xff1a;达到思无邪的状态&#xff08; 纯粹、思想纯正、积极向上 &#xff09; 中华民族&#xff0c;一直以来&#xff0c;教学都是以追随圣学为目标&#xff0c;所以中华文化也叫圣学文化&#xff0c;是最高深的上等学问&#xff1b; 圣人那颗心根本…

数据存储方案选择:ES、HBase、Redis、MySQL与MongoDB的应用场景分析

一、概述 1.1 背景 在当今数据驱动的时代&#xff0c;选择合适的数据存储技术对于构建高效、可靠的信息系统至关重要。随着数据量的爆炸式增长和处理需求的多样化&#xff0c;市场上涌现出了各种数据存储解决方案&#xff0c;每种技术都有其独特的优势和适用场景。Elasticsear…

大模型/NLP/算法面试题总结2——transformer流程//多头//clip//对比学习//对比学习损失函数

用语言介绍一下Transformer的整体流程 1. 输入嵌入&#xff08;Input Embedding&#xff09; 输入序列&#xff08;如句子中的单词&#xff09;首先通过嵌入层转化为高维度的向量表示。嵌入层的输出是一个矩阵&#xff0c;每一行对应一个输入单词的嵌入向量。 2. 位置编码&…

你知道滚筒式高速视觉检测机外观怎么“看”出产品质量吗?

点火线圈胶套是一种用于保护点火线圈绝缘部分的胶质套管。这种胶套通常由高温耐磨的橡胶或硅胶材料制成&#xff0c;具有良好的绝缘性能和耐高温性能。点火线圈胶套的作用是防止点火线圈与外部环境接触&#xff0c;防止受潮、灰尘或化学物质的侵蚀&#xff0c;同时起到绝缘和保…

准大一新生开学千万要带证件照用途大揭秘

1、提前关注好都有哪些考场&#xff0c;以及这些考场大致在网页的哪个位置。比如我选对外经贸大学&#xff0c;我就直接找到第二个点进去。 2、电脑上同时开了谷歌浏览器和IE浏览器&#xff0c;以及手机也登陆了。亲测下来&#xff0c;同一时间刷新&#xff0c;谷歌浏览器能显示…

【架构】分布式与微服务架构解析

分布式与微服务架构解析 一、分布式1、什么是分布式架构2、为什么需要分布式架构3、分布式架构有哪些优势&#xff1f;4、分布式架构有什么劣势&#xff1f;5、分布式架构有哪些关键技术&#xff1f;6、基于分布式架构如何提高其高性能&#xff1f;7、如何基于架构提高系统的稳…

开源无人机从入门到炸机,共需要几步?

阿木实验室2024年的重磅新品 Prometheus 仿真笔记本已经上架有一段时间了&#xff0c;近日&#xff0c;该产品的研发负责人廖工受邀到直播间与开发者们深度解读了Prometheus仿真笔记本的设计理念。直播过程中&#xff0c;廖工不仅展示了该产品的功能demo&#xff0c;解答技术开…