1. 引言

在诸多的场景中(例如软件测试,软件安全研究等领域)经常需要分析在目标进程中        具体加载了哪些模块(DLL),以及所加载的模块的信息(如模块基地址,映射文件大小等)。获取这windows进程加载的模块信息,曾经有一个行之有效又很便捷的方法,使用windows提供PSAPI(psapi.dll,windows进程状态信息接口)提供的相关的接口就可以快捷的获取进程及进程加载的模块信息。有关PSAPI接口可以参考psapi.h或者微软的官方文档Psapi.h header - Win32 apps | Microsoft Docs。在这里笔者说一句与本文主题无关的题外话,研究windows内核尽量参考一手的windows官方文档,笔者有一个习惯,在对内核相关的研读时都是尽可能的阅读英文版的windows官方文档和使用windows的原版的工具(例如windbg),孜孜不倦,唯求其真。

图1-PSAPI微软官方文档

然而,在这个win64操作系统已经普及,32位程序尚未没落,依然大行其道的时代里,使用PSAPI获取进程中的模块信息已经不再那么有效。在win64的wow64环境中运行的32位程序获取64位进程中模块信息,就无比尴尬,因为windows的PSAPI是不支持的。

我们可以在微软的官方文档中关于EnumProcessModules函数的描述(https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodules) 一探究竟,如图-2和图-3官方文档中所描述。其大意是说在64位的程序中枚举其它进程中的模块信息可以调用EnumProcessModuleEx接口。在wow64环境运行的32位程序中获取64位进程中的模块信息会返回错误代码299(GetLastError),其对应的错误信息为“仅完成部分的 ReadProcessMemory 或 WriteProcessMemory 请求。”。

图-2 64位程序使用EnumProcessModulesEx接口

图-3 EnumProcessModules不支持32位程序中枚举64位程序模块

既然问题摆在我们面前,那么是否有破解之道呢?这正是笔者写这篇文章的目的,在本文中,会详细阐述一种更底层的,放之四海而皆准的,获取windows进程中模块的方法。该方法适用32为程序和64位程序枚举其它进程(包括32位和64位)中的模块。“授之于鱼不如授之于渔”,然而,笔者写本文的目的不仅仅是分享如何获取进程中的模块信息,而是尽量描述清楚解决该类问题的思路,希望能起到抛砖引玉的作用,能启发读者朋友解决类似的问题。由于笔者技术水平有限,如若有错误或不当欢迎各位业界同仁不吝赐教()。

熟悉windows内核的朋友都知道,PEB(Process Environment Block,进程环境块)是一个重要的内核数据结构,windows的每个运行的进程中都维护一个PEB数据块其中记录着进程相关的各种信息。在PEB有一个PEB_LDR_DATA的数据,该数据记录着进程已经加载的模块信息。其实windows的PSAPI中的EnumProcessModules的底层实现也是通过PEB_LDE_DATA来遍历进程加载的模块的。“山穷水尽疑无路,柳岸花明又一村”,那么,问题的解决方案似乎近在咫尺了。

2. PEB数据结构

2.1 MSDN文档中的PEB数据结构

PEB的数据结构会随着操作系统的版本而有差异,可以在微软官方文档(https://docs.microsoft.com/en-us/windows/win32/api/winternl)查看PEB极其相关的数据结构定义。

图4-PEB及相关的数据结构

如上图所示的数据结构是在微软官方文档上摘录的,在PEB,PEB_LDR_DATA及LDR_DATA_TABLE_ENTRY结构体中有诸多的Reserved(保留)的数据项,表明这些数据项的定义可能会随着数据版本的变化而有差异,这些数据项尽量不要使用,如果在迫不得已的情况下使用,要做好操作系统不同版本间的兼容处理。

在PEB结构体中的偏移0x0C处指向进程已加载的模块结构体PEB_LDR_DATA指针,关于PEB_LDR_DATA结构体在微软的官宣文档上是如此介绍的”Contains information about the loaded modules for the process.”。关于PEB结构体中其它数据项笔者在此不再赘述,有兴趣的朋友可自行查阅微软官宣文档,或者联系笔者做进一步的沟通交流()。

在PEB_LDR_DATA结构体中,InMemoryOrderModuleList是一个双向链表节点(LIST_ENTRY结构体),其FLink和Blink均指向已加载的模块信息的结构体LDR_DATA_TABLE_ENTRY的头部。在微软的官方文档中关于InMemoryOrderModuleList是这样描述的“The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. For more information, see Remarks.”。

在LDR_DATA_TABLE_ENTRY结构体中的ImMemoryOrderModuleList同样指向进程中已记载的相邻(上一个和下一个)模块的LDR_DATA_TABLE_ENTRY结构体的头部。这样就可以通过PEB—>LDR遍历所有的模块了。

图5-进程已加载模块双向链表示意图

如图-5所示,笔者画出了LDR_DATA_TABLE_ENTRY双向链表示意图,双向链条的最后一个节点是尾节点,其是无效的模块(并非真是的模块),这一点需要特别注意。在上图中从PEB数据块出发,沿着绿色的箭头(FLink)遍历,即可枚举windows进程已加载的模块。

然而,微软的官方文档或许是处于版本兼容的考虑(真实的意图不得而知),对PEB结构的介绍中使用Reserved(保留)隐藏了一些细节。如果要对这些细节进行尽一步得探究,我们可以借助微软的调试工具windbg去进行深入的分析。

2.2 真实的PEB结构

其实真的PEB_LDR_DATA和LDR_DATA_TABLE_ENTRY结构体中有三个LINK_ENTRY节点分别是InLoadOrderModuleList,InMemoryOrderModuleList,InInitializationOrderModuleList。分别是按照加载次序,在内存中的映像地址(Image)顺序,初始化顺序进行组织的。我们可以使用windbg附加某个32位的进程,分别使用“dt _PEB_LDR_DATA”和”dt _LDR_DATA_TABLE_ENTRY”指令查看PEB_LDR_DATA和LDR_DATA_TABLE_ENTRY结构中的数据项。有关PEB相关的其它数据结构有兴趣的朋友可以使用windbg自行分析,笔者不再赘述,如有必要也可以和笔者进行进一步的沟通交流。

图-6 32位程序PEB_LDR_DATA结构体

图-7 32位程序LDR_DATA_TABLE_ENTRY结构体

2.3 32位程序和64位程序PEB结构差异

PEB结构体在32位程序和64位程序中差异比较大,最显著的差异是指针在32位程序中是32为的,然而在64位程序中是64位的,要有其它的一些细节上的差异笔者也不再一一列举,可以通过windbg查看相应的结构体在32位程序和64位程序中的差异。具体方法使用windbg分别附加32位和64位程序使用”dt _PEB”命令查看PEB结构体(也可以查看其它结构体),如下图所示(截图只显示了部分的结构体)。

在实际的应用中,我们一定要注意区分操作系统的版本,以及程序是32位程序还是程序,以做特定的兼容处理。

图-8 32位程序和64位程序PEB结构体差异

3. 通过PEB遍历已加载模块的方法

通过以上章节的叙述,是否觉得通过PEB结构体遍历程序已加载的块,已经豁然开朗,近在咫尺了?然而,笔者很遗憾的说”NO”,正所谓“道高一尺,魔高一丈“,我们面临的依然会有很多的障碍,不经风雨怎见得彩虹。这所有的一切,且听笔者娓娓道来。

3.1 读取PEB结构体的数据的方法

如何才能读取到PEB结构体的数据?在笔者所知的有两种方法,其一是通过TEB结构体读取PEB指针,另外一种方法,是使用Windows提供的API接口读取。下面笔者就这两种方法逐一介绍。

3.1.1 通过TEB读取PEB结构体数据

TEB(Thread Environment Block)是线程环境块,该结构中包含了系统频繁使用的与线程相关的数据。进程中的每个线程都有一个自己的TEB块。在TEB中有一个指向进程PEB结构体的指针,在32位程序中其偏移量为0x30,在64为程序中其偏移量为0x60。同样我们在windbg中使用“!teb”和”dt _TEB”命令可以分别查看TEB块数据和TEB数据结构,如下图所示。

图9-TEB数据结构

根据TEB结构的描述,那么我们可知,只要能获取TEB结构体数据,我们就可以顺利成章的读取道PEB结构的数据。熟悉系统内核的朋友都知道,fs寄存器其实就指向了当前先线程的TEB数据块。那么我们可以通过汇编指令mov eac, fs[0x30]或mov eax, fs[0x60]来获进程的PEB结构体地址。

当然,我们也可以通过Windows API NtCurrentTeb 来获取当前线程的TEB结构体指针,然而不幸的是该API仅在Win7及以后的版本支持,在微软的官方文档上是如此进行说明的“Minimum supported client       Available in Windows 7 and later versions of Windows.“。

然而,无论通过汇编指令还是通过NtCurrentTeb接口来获取TEB的地址,都是有局限的。因为,通过这种方法只能获取到当前进程的PEB,在实际的应用场景中遍历本进程的加载的模块意义不大,大多场景是分析其它第三方的应用(进程)所加载的模块。或许,有的朋友会说,可以通过注入的方式,达到目的。注入固然是可以的,但是注入太重,笔者不建议使用。

那么是否有更“轻量级“的方法,来遍历其它第三方应用所加载模块的方法呢?答案是肯定的,下面笔者就介绍使用windows api来读取第三方应用的PEB的方法。

3.1.2 通过NtQueryInformationProcess读取PEB数据

在NtDll.dll的API接口NtQueryInformationProcess支持查询进程的PEB数据,调用该方法的流程是首先使用OpenProcess打开已经存在的进程对象,并返回目标进程对象的句柄。分配PEB结构的内存,调用NtQueryInformationProcess传入目标进程对象句柄,已分配的PEB结构体内存指针,使用ProcessBasicInformation(0)作为ProcessInformationClass参数的值调用NtQueryInformationProcess来读取目标进程的PEB结构体的数据。关于NtQueryInformationProcess说明请参考NtQueryInformationProcess function (winternl.h) - Win32 apps | Microsoft Docs。

图-10 NtQueryInformationProcess官方文档说明

然而,到此是否意味着大功告成呢?答案否也,因为如果在32位程序中调用NtQueryInformationProcess来获取64位程序的PEB是不可以的。在32位程序中,获取64位程序PEB结构体的数据,需要调用在微软官方文档上尚未公开的API接口NtWow64QueryInformationProcess64,因为在官宣文档中没有公开,若要使用就需要对该接口进行逆向分析,该接口的声明与NtQueryInformationProcess类似。同样在32位程序中读取64位进程中的数据调用ReadProcessMemory接口也是不可以的,需要调用尚未公开的NtWow64ReadVirtualMemory64接口,该接口的声明ReadProcessMemory类似,差别就在于与指针,数据长度相关的参数类型为64位的。

3.2 示例程序

通过以上章节的描述,想必朋友们对于如何遍历第三方进程中已加载的模块,已经胸有成竹了。下面笔者对于整个流程进行梳理,并使用曾经风靡一时,时至今日已经没落,但尚未完全退出历史舞台的delphi语言进行实现。其实使用什么语言并不重要,对于程序员来时内核原理,基础算法,编码艺术等才是极为重要的硬实力。

3.2.1 遍历进程已加载模块流程

如下图所示在32位程序中遍历目标进程已加载的模块的流程如下图所示。

图-11 遍历目标进程已加载模块流程

3.2.2 示例程序核心代码片段

使用delphi编写的示例代码的核心代码段如下,关于结构体的声明及Windows API的声明可以参考以上章节的叙述,笔者不再赘述。

function getModuleList(HProcess: THandle; var sParamete: string): TModuleList;
var bRet: Boolean;dwNtState: NTSTATUS;bWinIs64, bProcWow64: Boolean;cMemLen, cWritenLen, cReadLen, cUSBufLen: Cardinal;pBasicInfo32: PPROCESS_BASIC_INFORMATION32;pBasicInfo64: PPROCESS_BASIC_INFORMATION64;pPEBData32: PPEB32;pPEBData64: PPEB64;pLDR32: PPEB_LDR_DATA32;pLDR64: PPEB_LDR_DATA64;pLDREntry32: PLDR_DATA_TABLE_ENTRY32;pLDREntry64: PLDR_DATA_TABLE_ENTRY64;pNextLDREntry32, pUSBuf: Pointer;pNextLDREntry64: UInt64;cReadLen64: Int64;pParam32: PRTL_USER_PROCESS_PARAMETERS32;pParam64: PRTL_USER_PROCESS_PARAMETERS64;pModule: PModuleInfo;sModuleFile: string;
beginEnableDebugPriv;Result := TModuleList.Create;bWinIs64 := IsWin64;bProcWow64 := IsWow64Process(HProcess);pBasicInfo32 := nil;pBasicInfo64 := nil;pPEBData32 := nil;pPEBData64 := nil;pLDR32 := nil;pLDR64 := nil;pLDREntry32 := nil;pLDREntry64 := nil;pParam32 := nil;pParam64 := nil;tryif bWinIs64 and (not bProcWow64) thenbegin//目标程序位64位程序//1. 查询目标进程基信息cMemLen := SizeOf(PROCESS_BASIC_INFORMATION64);pBasicInfo64 := GetMemory(cMemLen);FillChar(pBasicInfo64^, cMemLen, #0);dwNtState := NtWow64QueryInformationProcess64(HProcess, ProcessBasicInformation,pBasicInfo64, cMemLen, cWritenLen);if dwNtState < 0 thenbeginFreeAndNil(Result);raise Exception.Create(Format('%d-%s', [GetLastError, SysErrorMessage(GetLastError)]));end;//2. 读取PEB数据cMemLen := SizeOf(PEB64);pPEBData64 := GetMemory(cMemLen);FillChar(pPEBData64^, cMemLen, #0);dwNtState := NtWow64ReadVirtualMemory64(HProcess, pBasicInfo64^.PebBaseAddress,pPEBData64, cMemLen, cReadLen64);if dwNtState < 0 thenbeginFreeAndNil(Result);raise Exception.Create(Format('%d-%s', [GetLastError, SysErrorMessage(GetLastError)]));end;//3. 读取ParametercMemLen := SizeOf(RTL_USER_PROCESS_PARAMETERS64);pParam64 := GetMemory(cMemLen);FillChar(pParam64^, cMemLen, #0);sParamete := '';dwNtState := NtWow64ReadVirtualMemory64(HProcess, pPEBData64^.ProcessParameters,pParam64, cMemLen, cReadLen64);if dwNtState >= 0 thenbegincUSBufLen := pParam64^.ComandLine.Length + 2;pUSBuf := GetMemory(cUSBufLen);FillChar(pUSBuf^, cUSBufLen, #0);dwNtState := NtWow64ReadVirtualMemory64(HProcess, pParam64^.ComandLine.Buffer,pUSBuf, pParam64^.ComandLine.Length, cReadLen64);if dwNtState >= 0 thensParamete := WideCharToString(PWideChar(pUSBuf));FreeMemory(pUSBuf);end;//4. 读取LDR数据cMemLen := SizeOf(PEB_LDR_DATA64);pLDR64 := GetMemory(cMemLen);FillChar(pLDR64^, cMemLen, #0);dwNtState := NtWow64ReadVirtualMemory64(HProcess, pPEBData64^.Ldr, pLDR64,cMemLen, cReadLen64);if dwNtState < 0 thenbeginFreeAndNil(Result);raise Exception.Create(Format('%d-%s', [GetLastError, SysErrorMessage(GetLastError)]));end;//5. 循环读取LDR_EntrycMemLen := SizeOf(LDR_DATA_TABLE_ENTRY64);pLDREntry64 := GetMemory(cMemLen);FillChar(pLDREntry64^, cMemLen, #0);pNextLDREntry64 := pLDR64^.InLoadOrderModuleList.FLink;while 1 = 1 dobegindwNtState := NtWow64ReadVirtualMemory64(HProcess, pNextLDREntry64, pLDREntry64,cMemLen, cReadLen64);if dwNtState < 0 thenbeginFreeAndNil(Result);raise Exception.Create(Format('%d-%s', [GetLastError, SysErrorMessage(GetLastError)]));end;if pLDREntry64^.InLoadOrderLinks.FLink = pLDR64^.InLoadOrderModuleList.FLink thenBreak;cUSBufLen := pLDREntry64^.FullDllName.Length + 2;pUSBuf := GetMemory(cUSBufLen);FillChar(pUSBuf^, cUSBufLen, #0);dwNtState := NtWow64ReadVirtualMemory64(HProcess, pLDREntry64^.FullDllName.Buffer,pUSBuf, cUSBufLen, cReadLen64);if dwNtState < 0 thenbeginFreeAndNil(Result);raise Exception.Create(Format('%d-%s', [GetLastError, SysErrorMessage(GetLastError)]));end;New(pModule);StrCopy(@pModule^.DllBase[0], PChar(IntToHex(pLDREntry64^.DllBase, 16)));StrCopy(@pModule^.EntryPoint[0], PChar(IntToHex(pLDREntry64^.EntryPoint, 16)));pModule^.ImageSize := pLDREntry64^.SizeOfImage;pModule^.LoadCount := pLDREntry64^.LoadCount;sModuleFile := WideCharToString(PWideChar(pUSBuf));StrCopy(@pModule^.ModuleFile, PChar(sModuleFile));Result.Add(pModule);FreeMemory(pUSBuf);pNextLDREntry64 := pLDREntry64^.InLoadOrderLinks.FLink;end;endelsebegin//目标程序是32位程序//1. 查询目标进程基信息cMemLen := SizeOf(PROCESS_BASIC_INFORMATION32);pBasicInfo32 := GetMemory(cMemLen);FillChar(pBasicInfo32^, cMemLen, #0);dwNtState := NtQueryInformationProcess(HProcess, ProcessBasicInformation,pBasicInfo32, cMemLen, cWritenLen);if dwNtState < 0 thenbeginFreeAndNil(Result);raise Exception.Create(Format('%d-%s', [GetLastError, SysErrorMessage(GetLastError)]));end;//2. 读取PEB数据cMemLen := SizeOf(PROCESS_BASIC_INFORMATION32);pPEBData32 := GetMemory(cMemLen);FillChar(pPEBData32^, cMemLen, #0);bRet := ReadProcessMemory(hProcess, pBasicInfo32.PebBaseAddress, pPEBData32,cMemLen, cReadLen);if not bRet thenbeginFreeAndNil(Result);raise Exception.Create(Format('%d-%s', [GetLastError, SysErrorMessage(GetLastError)]));end;//3. 读取ParametercMemLen := SizeOf(RTL_USER_PROCESS_PARAMETERS32);pParam32 := GetMemory(cMemLen);FillChar(pParam32^, cMemLen, #0);sParamete := '';bRet := ReadProcessMemory(HProcess, pPEBData32^.ProcessParameters, pParam32,cMemLen, cReadLen);if bRet thenbegincUSBufLen := pParam32^.CommandLine.Length + 2;pUSBuf := GetMemory(cUSBufLen);FillChar(pUSBuf^, cUSBufLen, #0);bRet := ReadProcessMemory(HProcess, pParam32^.CommandLine.Buffer, pUSBuf,pParam32^.CommandLine.Length, cReadLen);if bRet thensParamete := WideCharToString(PWideChar(pUSBuf));FreeMemory(pUSBuf);end;//4. 读取LDR数据cMemLen := SizeOf(PEB_LDR_DATA32);pLDR32 := GetMemory(cMemLen);FillChar(pLDR32^, cMemLen, #0);bRet := ReadProcessMemory(HProcess, pPEBData32^.Ldr, pLDR32, cMemLen, cReadLen);if not bRet thenbeginFreeAndNil(Result);raise Exception.Create(Format('%d-%s', [GetLastError, SysErrorMessage(GetLastError)]));end;//5. 循环读取LDR_EntrycMemLen := SizeOf(LDR_DATA_TABLE_ENTRY32);pLDREntry32 := GetMemory(cMemLen);FillChar(pLDREntry32^, cMemLen, #0);pNextLDREntry32 := pLDR32^.InLoadOrderModuleList.FLink;while 1 = 1 dobeginbRet := ReadProcessMemory(HProcess, pNextLDREntry32, pLDREntry32, cMemLen, cReadLen);if not bRet thenbeginFreeAndNil(Result);raise Exception.Create(Format('%d-%s', [GetLastError, SysErrorMessage(GetLastError)]));end;if pLDREntry32^.InLoadOrderLinks.FLink = pLDR32^.InLoadOrderModuleList.FLink thenBreak;cUSBufLen := pLDREntry32^.FullDllName.Length + 2;pUSBuf := GetMemory(cUSBufLen);FillChar(pUSBuf^, cUSBufLen, #0);bRet := ReadProcessMemory(HProcess, pLDREntry32^.FullDllName.Buffer, pUSBuf, cUSBufLen, cReadLen);if not bRet thenbeginFreeAndNil(Result);raise Exception.Create(Format('%d-%s', [GetLastError, SysErrorMessage(GetLastError)]));end;New(pModule);StrCopy(@pModule^.DllBase[0], PChar(IntToHex(pLDREntry32^.DllBase, 8)));StrCopy(@pModule^.EntryPoint[0], PChar(IntToHex(pLDREntry32^.EntryPoint, 8)));pModule^.ImageSize := pLDREntry32^.SizeOfImage;pModule^.LoadCount := pLDREntry32^.LoadCount;pModule^.TimeStamp := pLDREntry32^.TimeDateStamp;sModuleFile := WideCharToString(PWideChar(pUSBuf));StrCopy(@pModule^.ModuleFile, PChar(sModuleFile));Result.Add(pModule);FreeMemory(pUSBuf);pNextLDREntry32 := pLDREntry32^.InLoadOrderLinks.FLink;end;end;finallyif Assigned(pBasicInfo32) thenFreeMemory(pBasicInfo32);if Assigned(pBasicInfo64) thenFreeMemory(pBasicInfo64);if Assigned(pPEBData32) thenFreeMemory(pPEBData32);if Assigned(pPEBData64) thenFreeMemory(pPEBData64);if Assigned(pLDR32) thenFreeMemory(pLDR32);if Assigned(pLDR64) thenFreeMemory(pLDR64);if Assigned(pLDREntry32) thenFreeMemory(pLDREntry32);if Assigned(pLDREntry64) thenFreeMemory(pLDREntry64);if Assigned(pParam32) thenFreeMemory(pParam32);if Assigned(pParam64) thenFreeMemory(pParam64);end;
end;

3.2.3 进程模块查看器

如下图所示,这是笔者使用delphi开发的遍历进程模块的工具。该工具的下载地:进程模块查看器,支持32位和64位进程-WindowsServer文档类资源-CSDN下载。

4.结束语

由于笔者技术能力有限,本文如若有错误或不当之处请不吝赐教。如果本文对您有帮助,有劳各位朋友点赞加关注,您的支持是我源源不断的动力。

枚举windows进程模块的几种方法—PEB内核结构详解相关推荐

  1. 枚举Windows进程中模块的几种方法-PEB内核结构详解

    1. 引言 在诸多的场景中(例如软件测试,软件安全研究等领域)经常需要分析在目标进程中具体加载了哪些模块(DLL),以及所加载的模块的信息(如模块基地址,映射文件大小等).获取这windows进程加载 ...

  2. 遍历Map的四种方法之map.entry详解

    Map.entrySet() 这个方法返回的是一个Set<Map.Entry<K,V>>,Map.Entry 是Map中的一个接口,他的用途是表示一个映射项(里面有Key和Va ...

  3. Git恢复之前版本的两种方法reset、revert详解

    一.问题描述 在利用github实现多人合作程序开发的过程中,我们有时会出现错误提交的情况,此时我们希望能撤销提交操作,让程序回到提交前的样子,本文总结了两种解决方法:回退(reset).反做(rev ...

  4. 用python画六瓣雪花_python-turtle-画雪花-2种方法及效果的详解

    #python3.8#xuguojun#2020.1.30#导出模块,这样导出比代码较简洁,但是注意r和后面RGB的r,所以我改为d代替R(r) importturtle as timportrand ...

  5. 用python画雪花飘落_python-turtle-画雪花-2种方法及效果的详解

    #python3.8#xuguojun#2020.1.30#导出模块,这样导出比代码较简洁,但是注意r和后面RGB的r,所以我改为d代替R(r) importturtle as timportrand ...

  6. Python中json模块的load/loads方法实战及参数详解

    文章目录 前言 正文 1. loads方法与load方法的异同 1.1不相同点: 1.2 相同点 1.3 例子 2. 转换成Python对象 3. json.load(s)的参数 3.1 s参数 3. ...

  7. linux下没有yum命令,linux下配置yum的三种方法与yum命令详解

    (一).制作YUM本地源: YUM简介: YUM是Yellow dog Updater Modified的简称,yum是软件的仓库,它可以是http或ftp站点,也可以是本地软件池,但必须包含rpm的 ...

  8. 计算字符串长度的三种方法(库函数 指针 )【详解】

    目录 求字符串长度的一般原理 方法一:strlen函数 函数原型 使用方法 方法二:指针+整数 方法三:指针-指针 总结: 求字符串长度的一般原理 求字符串长度简单来说就是计算一个字符串(字符数组)中 ...

  9. python安装到桌面的路径是什么_Python 获取windows桌面路径的5种方法小结

    这里介绍了5中python获取window桌面路径的方法,获取这个路径有什么用呢?一般是将程序生成的文档输出到桌面便于查看编辑. 前两个方法是通过注册表来获取当前windows桌面绝对路径,比较推荐使 ...

最新文章

  1. 2022-2028年中国玫瑰花行业市场研究及前瞻分析报告
  2. Android 数据显示控件(ListView实战演练)
  3. linux guard什么进程,使用linux系统性能监控工具KSysguard监控远端主机介绍
  4. 工作292:数据绑定逻辑处理
  5. hdu 4454 Stealing a Cake 三分法
  6. 菜鸟学习笔记:Java提升篇8(线程2——线程的基本信息、线程安全、死锁、生产者消费者模式、任务调度)
  7. 自己动手,刷一台迷你缓存服务器玩玩
  8. JavaScript 工具库:Cloudgamer JavaScript Library v0.1 发布
  9. POJ 1155 TELE 树形DP
  10. 阿里云ECS更换系统时提示主机名不支持windows镜像要求的解决方法
  11. RISC-V 之一 使用 ARM CMSIS 的 SVD 文件辅助调试
  12. 信息检索(IR)笔记1: 倒排索引(Inverted Index)
  13. [ 人力资源面试篇 ] 应届生 “ HR 面 “ 面试分析
  14. javaCSGO赛事管理系统springbootvueweb
  15. 宏碁传奇Go评测 怎么样
  16. 2007年大连软件出口全国第三 荣获中国软件出口(外包)政府推进奖
  17. 用javascript函数设置延时执行jQuery
  18. php项目宝塔搭建实战前后端Niushop开源商城系统
  19. 内涵段子被永久关停;抖音上线反沉迷系统;苹果推红色版iPhone 8丨价值早报
  20. 万象物语怎么在电脑上玩 万象物语电脑版玩法教程

热门文章

  1. 不同的去耦电容 阻抗VS频率
  2. 为什么显示芒果tv服务器异常怎么办,芒果tv打不开怎么办 芒果tv打不开解决方法...
  3. 启动马达接线实物图_电动机星三角换接起动原理图解
  4. 截图指定区域图片并导出至PDF文件
  5. 年薪达不到23.5万全额退款 | 人工智能核心能力培养计划
  6. 计算机配套产品分录,外购库存商品会计分录怎么做?如何做账?据说聪明的会计都会这样做!...
  7. 东方财富服务器稳定吗,东方财富客户端如何设置自动选择服务器 设置有条件选股方法...
  8. 模拟集成电路应用之Burn in
  9. 小技巧--PPT播放控制,可控制每页时间
  10. 借助 Docker 来搭 Nginx 的积木:快速实现高性能二维码服务