PE 文件格式详解(上)

2018 年 11 月 30 日 计算机与网络安全

一次性进群,长期免费索取教程,没有付费教程。

教程列表见微信公众号底部菜单

进微信群回复公众号:微信群;QQ群:16004488


微信公众号:计算机与网络安全

ID:Computer-network

PE文件格式是微软Windows NT内核系列系统和Win32子集中可执行的二进制文件格式。这种文件格式是微软基于COFF文件格式的设计思想设计的,并于1993年被TIS委员会批准。COFF(Common Object File Format,通用目标文件格式)是应用于数种UNIX系统和VMS系统中的目标文件和可执行文件的格式。


PE文件格式无论是在免杀技术中还是在反病毒领域里都是一个极其重要的知识点,表1是本文涉及到的名词解释。

一、MS-DOS头


MS-DOS头存在于每个PE文件中,它的存在完全是出于兼容性的考虑。MS-DOS头后面紧跟着DOS Sub,这两个部分构成了MS-DOS中可执行文件的基本要素。


因此,当PE文件运行于MS-DOS系统时,并不会出现不可预料的错误,因为它会执行DOS Sub中的代码。大多数情况下段的代码仅仅会显示"This program cannot run in DOS mode"或"This program requires run under windows",大致意思是告诉用户这个文件并不能在当前环境(MS-DOS)中运行。


MS-DOS头结构代码如代码清单1所示。

代码清单1 MS-DOS头结构

在代码清单1中,1)~19)的含义如下。


1)MZ标志位:MS-DOS可执行文件的标识位(也叫做魔数),表示这是一个MS-DOS下的可执行文件。


2)文件末页字节数:在可执行页(页大小为512B)最后一页中的字节数。如果其值为0,则表示最后一页被全部占用(即有效值为0x200=512)。


3)文件页数:可执行文件中的页数(包括最后一页)。


4)重定位:DOS Sub中重定位项的数目,可能为0。


5)区段中头部大小:程序的数据起始于头部之后,此字段可以用来计算出相应的文件偏移(包括重定位项)。如果头部大小不是512B的倍数,一些操作系统或程序可能会执行失败。


6)最小附加内存段需求:除了代码大小外最少需分配的内存段大小,如果无法分配相应内存,则程序将不能启动。


7)最大附加内存段需求:除了代码大小外最多分配的内存段大小。通常,操作系统会为程序保留所有剩余的常规内存,但是此字段会对此做出限制。


8)初始SS值:用来初始化SS(堆栈段)以启动可执行文件。


9)初始SP值:用来初始化SP(堆栈指针寄存器)的值。


10)校验和:可执行文件的校验和(或直接置为0)。


11)初始IP值:初始化与启动可执行文件相关的IP寄存器值(程序入口点)。


12)初始CS值:初始化与启动可执行文件相关的CS段(与上一字段组成CS:IP)。


13)重定位表偏移:重定位表的偏移。


14)附加数:代码附加数(为0x0则代表仅有主程序)。


15)保留:保留4个WORD大小的空间留作他用。


16)OEM标识:OEM厂商的ID。


17)OEM信息:OEM厂商的信息。


18)保留:保留10个WORD大小的空间留作他用。


19)新文件头地址:新的EXE文件头偏移地址。

1、重要字段


MS-DOS头部的字段一般情况下只有第一个e_magic与最后一个e_lfanew是我们需要关注的。


第一个e_magic字段的值为0x4D5A,Win32 SDK使用#define IMAGE_DOS_SIGNATURE定义了这个值。

此字段可以告诉我们这是一个Microsoft旗下MS-DOS系统中的可执行文件。


最后一个e_lfanew字段可以引导我们找到新的EXE文件头(这个偏移指向的EXE文件头几乎总是PE文件头),从而进一步判断这个可执行文件是否为PE文件。


2、其他字段


在Windows环境下,其他字段不需要我们过多关注,唯一可能有些用途就是第13个字段e_lfarlc了,偏移为0x18的e_lfarlc字段指向的重定位表头部其实也是DOS Stub的起始偏移地址,由此往前推4个字节便是指向PE文件头e_lfanew的地址。当然,大多数人更喜欢直接记住e_lfanew的偏移地址0x3C。


二、PE文件头


PE文件头位于DOS Stub后面,是一个以PE/0/0为起始标记,由MS-DOS头中e_lfanew字段指向的结构。


PE文件头是Windows NT内核下判断可执行文件的唯一有效结构,它由3个字段组成,结构如下:

其中的3个字段构成了PE文件的基本头部结构。


1、Signature字段


Signature是PE文件头的标识,其值始终为0x50450000,ASCII码为"PE/0/0",Win32 SDK使用#define IMAGE_NT_SIGNATURE定义了这个值。

2、IMAGE_FILE_HEADER结构


在IMAGE_FILE_HEADER(映像文件头)结构中包含了整个PE文件的概览信息,其中比较重要的两个字段分别表示了此PE文件的区段数量与扩展头(IMAGE_OPTIONAL_HEADER)的大小,相关代码如下:

上述结构说明如下。


1)Machine:一个2字节(16bit)的值,用以指明此文件可以运行于哪种CPU上,在不同的平台上PE文件的Machine中的值也是不一样的。Win32 SDK中使用了一组宏来表示不同CPU的标识位,表2是整理出的一些常用的值。

2)NumberOfSections:区段的数量。


3)TimeDateStamp:表明此文件是何时被创建的,自1970年1月1日以来,这个值就是用格林尼治时间(GMT)计算的秒数。这个值是一个比系统的日期/时间更为精确的文件创建指示器。可以使用_ctime函数与gmtime函数翻译或计算此字段。


4)PointerToSymbolTable:指向COFF符号表偏移的指针,由于其已经被Debug格式所替代,因此COFF符号表在现今的PE文件中已较少应用(如果符号表不存在,则字段值为0)。


5)NumberOfSymbols:符号表中符号的数量,由于COFF符号表是一个大小固定的结构,因此只有通过这个字段才能计算出COFF符号表结构的结尾。


6)SizeOfOptionalHeader:在IMAGE_FILE_HEADER结构后面的扩展头大小。一般情况下,这个结构的大小在32位的系统中是0x00E0,而在64位系统下则为0x00F0,但是这两个值为常用的最小值,不排除在人为修改后或系统升级后此值有变动的可能。


7)Characteristics:此字段用以表示PE文件的属性,可通过几个值的运算得到,一般情况下,普通EXE文件的值为0x010F,DLL文件的值为0x0210,其他类型的标志位只对特别的目标文件和库文件有效。Win32 SDK中使用了一组宏来表示不同的PE文件属性,表3是整理出的一些常用的PE文件属性值。

3、IMAGE_OPTIONAL_HEADER结构(x86/x64)


IMAGE_OPTIONAL_HEADER(映像扩展头)结构是对PE文件进行更为详细的属性设定的扩展结构(见代码清单2),这个结构与IMAGE_FILE_HEADER(映像文件头)结构合并起来统称为“PE文件头结构”。

代码清单2 映像扩展头结构

上述结构说明如下。


1)Magic:文件类型标识(普通可执行映像0x010B、ROM镜像为0x0170、PE32+为0x020B)。


2)MajorLinkerVersion:链接器的主版本号。


3)MinorLinkerVersion:链接器的子版本号。


4)SizeOfCode:所有IMAGE_SCN_CNT_CODE属性的区段总大小,此大小在计算时按照磁盘扇区字节数的整数倍计算。


5)SizeOfInitializedData:已初始化的数据块大小。


6)SizeOfUninitializedData:未初始化的数据块大小,装载程序需要在虚拟地址空间中为这些数据保留空间,但是这些块在磁盘中并不占用任何空间(在程序运行之前没有指定的值),未初始化的数据通常都位于一个名称为.bbs的区段中。


7)AddressOfEntryPoint:程序执行入口的RAV,在大多数可执行文件中,入口点(AddressOfEntryPoint)并不指向main()、winmaim()或dllmain()等函数的入口,而是指向运行时库代码,再由其调用这些函数。在DLL文件中,这个入口点有可能在进程初始化、进程关闭、线程创建与线程关闭时被调用,并且鉴于DLL文件的特殊性,这个入口点在DLL文件中可以被置为0(用连接器的/NOENTRY开关控制)。


8)BaseOfCode:代码段的起始RVA(代码段通常位于PE文件头与数据块之间,在微软连接器生成的可执行文件中这个值通常为0x00001000)。


9)BaseOfData:数据段的起始RVA(数据段通常位于PE文件头与代码段之后,这个值在64位的文件中是无效的)。


10)ImageBase:文件在内存中的首选装入地址(对于DLL文件来说,即使其未能在此地址装入,也可以将其实际装入地址称为ImageBase)。加载器将试图在此地址装入这个映像文件,如果载入成功,则装载器将跳过应用基址重定位的步骤,如果此地址被占用,则加载器会重新在正确对齐的合法地址中选择一个作为实际装载地址。


11)SectionAlignment:映像文件在被装入内存时的区段对齐大小。


12)FileAlignment:映像文件在磁盘上的区段对齐大小。


13)MajorOperatingSystemVersion:要求操作系统最低版本的主版本号。


14)MinorOperatingSystemVersion:要求操作系统最低版本的子版本号。


15)MajorImageVersion:此可执行文件的主版本号,此版本号由程序作者指定(它与后一个字段成对使用,可以被置为0,可以通过连接器开关/VERSION控制)。


16)MinorImageVersion:此可执行文件的子版本号,此版本号由程序作者指定。


17)MajorSubsystemVersion:要求最低子系统的主版本号(与后一个字段成对使用,一般情况下其值为4,可以通过连接器开关/SUBSYSTEM控制)。


18)MinorSubsystemVersion:要求最低子系统的子版本号。


19)Win32VersionValue:这是一个保留值,且必须为0x00000000。


20)SizeOfImage:映像文件装入内存后的总大小(从Image Base到最后一个区段的总大小)。


21)SizeOfHeaders:是MS-DOS头、PE头、区块表的尺寸之和。


22)CheckSum:映像文件的校验和(在一些EXE文件中此值可以为0x00000000,但在一些内核模式的驱动与系统DLL中此值必须为有效值)。


23)Subsystem:可执行文件所期望的子系统值(这个值只对EXE文件是重要的)。Win32 SDK中使用了一组宏来表示不同子系统的值,表4是整理出的一些常用子系统值。

24)DllCharacteristics:DllMain()函数何时被调用,默认为0。


25)SizeOfStackReserve:在EXE文件中,为线程保留的堆栈大小。


26)SizeOfStackCommit:在EXE文件中,栈初始内存大小(默认4KB)。


27)SizeOfHeapReserve:在EXE文件中,为进程默认堆保留的内存(默认1MB)。


28)SizeOfHeapCommit:在EXE文件中,每次指派给堆的内存大小(默认4KB)。


29)LoaderFlags:与调试有关,默认为0。


30)NumberOfRvaAndSizes:数据目录成员的数量,一般为0x00000010(16个)。


4、数据目录表


数据目录表是PE文件中各种数据结构的索引目录,由数个相同的IMAGE_DATA_DIRECTORY结构组成,其中包含了很多PE文件正常加载执行所必不可少的数据结构。


IMAGE_DATA_DIRECTORY结构的定义如下:

数据目录表在Win32 SDK中使用了一组宏来表示不同成员的信息,以下为修饰过的代码:

1)Export Directory:导出表目录,用于导出此映像中的函数示例符号,以便于其他应用程序可通过此导出符号调用此映像的函数示例(导出目录大多数用于DLL文件)。


2)Import Directory:导入表目录,用于导入此映像。


3)Resource Directory:资源目录,用于保存各种资源(包括图标、对话框等)。


4)Exception Directory:异常目录,用于保存可执行文件中异常处理的相关数据。


5)Security Directory:安全目录,一般情况下用于保存数字签名或安全证书。


6)Base Relocation Table:基址重定位表,保存着需要执行重定位的代码偏移信息。


7)Debug Directory:调试目录,用于保存符号名与调试相关信息。


8)Architecture Specific Data:版权(特殊结构)数据,保留字段,必须为0。


9)RVA of GP:全局指针偏移目录,保存着全局指针寄存器的RAV地址。


10)TLS Directory:TLS(线程局部存储器)目录,其本质上属于一个局部变量,单独存在于每个线程中。


11)Load Configuration Directory:载入配置目录,用来描述一些太大或是太复杂而不适合在PE头或选项头中描述的特征。


12)Bound Import Directory in headers:绑定输入目录,存储可以减少程序加载时间的一些API绑定信息。


13)Import Address Table:导入地址表,保存导入函数的真正地址。


14)Delay Load Import Descriptors:延迟导入描述符,通过指定可以延迟加载的DLL列表,减少程序启动之初加载DLL的数量,进而提高程序启动速度。


15)COM Runtime Descriptor:COM运行时描述符目录。


三、区段表


PE文件头的数据目录表后是区段表,区段表用来描述位于其后各个区段的各种属性。PE文件最少要有一个区段才能被加载运行。


区段表是由数个首尾相连的IMAGE_SECTION_HEADER结构体数组构成的,可以使用IMAGE_FIRST_SECTION32(NtHeader)这个宏找到第一个区段表所在的位置,示例代码如下:

1、IMAGE_SECTION_HEADER结构


IMAGE_SECTION_HEADER结构里包含了可以详细描述该区段属性的字段信息,例如区段名、长度、属性等内容,如下所示:

上面的结构说明如下。


1)Name:区段名,是一个长度为8字节的ASCII字符串数组,一般情况下区段名都以"."开始,但这并不是必须的(以"$"开头的同名区段会被合并)。


2)VirtualSize:实际被使用的区段大小(即区段在未做对齐处理前的大小),此字段在OBJ文件中为0x00000000。


3)VirtualAddress:此区段载入内存后的RAV,这个地址是按照内存页对齐的(恒为PE头结构中SectionAlignment字段的整数倍)。


4)SizeOfRawData:此区段在磁盘中的体积,这个地址是按照文件页对齐的(恒为PE头结构中FileAlignment字段的整数倍)。


5)PointerToRawData:此区段在文件中的偏移。


6)PointerToRelocations:此区段重定位表的偏移地址,它指向IMAGE_RELOCATION结构数组。


7)PointerToLinenumbers:行号表在文件中的偏移。


8)NumberOfRelocations:此区段重定位表项的数量。


9)NumberOfLinenumbers:行号表项的数量。


10)Characteristics:区段属性,用以描述此区段的读写情况、状态等属性。Win32 SDK中使用了一组宏来表示不同属性的值,表5是整理出的数据。

以上这些属性在编程中使用时可以用"|"进行合并,例如0xE0000020就是"0x20000000|0x40000000|0x80000000|0x00000020"几种属性合并后的结果,最终属性信息为“这是一个包含可执行代码的、可读、可写且可执行的区段”。


2、区段名功能约定


虽然区段名是可以自定义的,但是微软对实现各种功能区段的名称还是有一个约定俗成的命名标准的。


表6就是收集的一些有关区段名称的约定。

这些区块名一般都是由编译器在自动编译连接时生成的,但正如本小节的头一句话所说,我们是可以自己定义区段名的。在Visual Studio C++中,可以用以下代码控制编译器数据区段的名称:

3、区段对齐详解


PE文件中,每个区段与结构的起始位置都要遵守页对齐机制。在32位的平台中,一个分页的大小是4KB,所以一般情况下无论是内存中的区段对齐还是文件中的区段对齐,一般都为4KB的倍数(但PE文件对此并无强制性规定,只要是2的整数倍即可)。


PE头中的SectionAlignment字段与FileAlignment字段描述了此映像文件中的内存对齐大小与文件对齐大小。


根据以上两个字段,系统在给某一区段或其他任何位于此映像文件内的数据块分配空间时,都将按照映像所处位置并以其对齐大小的整数倍来分配空间。


举例来说,假如我们现在有一个体积(VirtualSize)为0x1201的区段,此映像文件的内存对齐与文件对齐分别为0x1000与0x200,那么这个区段在内存中实际占用的空间将为0x2000,其中后面因对齐而空出的大小为0x799字节的空间会用0x00填充。同理,由文件对齐大小为0x200可知,这个区段在文件中实际占用的空间将为0x1400,后面空出的0x199大小的空间同样会由0x00填充。图1形象地描述了这一对齐机制。

图1  文件对齐与内存对齐

4、地址转换


关于地址转换的内容已经在《PE 文件浅析》中详细描述过了,这里不赘述。之所以再次介绍这项内容,一是因为这部分内容和区段表密切相关;二是因为这部分内容非常重要,想以此引起您的关注。另外,这里还为您介绍一些地址转换的工具。


市面上支持地址转换的工具还是比较多的,如FFI、Stud PE与Load Pe等,这些工具都在界面中比较明显的位置设置了执行地址转换功能的按钮。


四、导出表


导出表是PE文件为其他应用程序提供API的一种函数示例导出方式。Windows下存在导出表的可执行文件以指定自身的一些变量、函数以及类,并将其导出,以便提供给其他第三方程序使用。


1、IMAGE_EXPORT_DIRECTORY结构


IMAGE_EXPORT_DIRECTORY(导出表)是PE文件中非常重要的一个数据结构,这个用于导出内部函数的数据结构对于从未接触过它的人来说或许有些复杂,但实际上并非如此。相关代码如下:

上面的结构说明如下。


1)Characteristics:保留,恒为0x00000000。


2)TimeDateStamp:导出表创建的时间(GMT时间)。


3)MajorVersion:导出表的主版本号。


4)MinorVersion:导出表的子版本号。


5)Name:指向模块名(此导出表所在模块的名称)的ASCII字符的RVA。


6)Base:导出表用于输出API函数索引值的基数(函数索引值=导出函数索引值-基数),一般情况下此基数值为1。


7)AddressOfFunctions:EAT中的条目数量。


8)NumberOfNames:ENT中的条目数量,ENT的条目数量小于等于EAT中的条目数量。


9)AddressOfNameOrdinal:EAT的RAV(相对虚拟地址),EAT中的每一个非0的项都对应一个被导出的函数名称或序号。


10)AddressOfNames:ENT的RAV(相对虚拟地址),ENT和EAT中的每一个非0的项都对应一个被导出的函数地址或序号。


11)AddressOfNameOrdinals:导出序号表的RAV。


2、识别导出表


从逻辑上讲,导出表由3部分构成,分别是名称表、函数表与序号表,其中函数表与序号表是必须要有的,而名称表则是可选的。


这3个表的关系也是非常简单的,序号表与名称表的作用就是索引,引导调用者找到真正需要的函数表,而函数表中保存的就是这个被导出的函数地址信息。


表7是这3张表在内存中有关存储状态的抽象例子。


由表7我们可以得知,导出表中的序号并不代表其本身在内存或PE文件中的排列顺序,而仅仅是作为一种标识,其真正的存储顺序是完全被打乱的。导出表在内存中的存储顺序是按照输出名称来确定的。

另外,文件中保存的序号也并不是我们平时调用函数时使用的,这一点一定要分清。我们平时调用函数使用的序号减去Base(序号基数)的值才能得到文件中保存的序号(这就是文件中保存的序号总是以0开始,而我们调用的序号大多以1开始的原因)。我们也将文件中保存的序号称为原始序号,而将调用函数时使用的序号称为调用序号。


下面来看一个导出表的示例PEDemo.dll,通过查看这个DLL的PE结构我们可以知道以下这些信息:


.rdata区段的RAV为0x00002000,.data区段的RAV为0x00003000。

.rdata区段的起始Offset为0x00000E00,.data区段的起始Offset为0x00001400。

通过数据目录表,可以知道导出表的RAV为0x000024F0,大小为0x00000077。


由导出表的虚拟偏移0x000024F0可知其位于区段.rdata中,并且其相对偏移为0x000004F0(也就是0x000024F0-0x00002000的结果),据此我们可以根据区段.rdata的起始文件偏移0x00000E00,计算出导出表在文件中的偏移为0x000012F0(即0x00000E00+0x000004F0)。


总结为公式如下:

知道导出表的Offset与RAV后,我们就可以计算出一个参照值,以方便后续的其他地址运算。参照值的计算公式如下:

由此公式可得,导出表的参照值为0x00001200(即0x000024F0-0x000012F0)。现在我们就可以根据这个值更加轻松地查看导出表了,如下所示:

这个导出来的十六进制格式文本就是示例PEDemo.dll的导出表,其中加边框的文本就是导出表的头部,其表示的具体信息如图2所示。

图2  导出表的头部信息

由图2可以知道,这是一个有4个导出项的导出表,其中有一个未导出名称。并且此导出表的序号是从2开始的,这意味着我们要找到原始序号为0的导出项,必须将其加上2才能找到。


通过计算参照值与这个表的后3项可以计算出,EAT、ENT与输出序号表的Offset分别为0x1318、0x1328与0x1334。


到现在为止,我们已经找到导出表中最重要的3个部分了,在上面的十六进制格式文本中用双下划线、重下划线与单下划线分别标识了EAT、ENT与输出序号表的数据内容。


由此整理出的内容如表8所示。

这个表格看似简单,其实里面含有一些值得思考的点。


1)怎么确定是第一个函数未导出名称,而不是其他函数的?


2)序号的对应方式很奇怪,我们只在导出序号表中看到了0x0001、0x0002与0x0003这3个序号,而此导出表的序号基数又是2,那第一个函数的导出序号是怎么计算出来的呢?


3)最后一个导出的符号也很奇怪,由它的RAV判断它应该是在.data段,而那个区段是不可执行的。


对于以上这3个问题,我们可以分两步来讨论。首先要讨论的就是序号问题。


我们究竟应该怎样看待序号呢?其实很简单,序号就是函数名导出表(数组)的一个索引,将其减去序号基数就可以得到相应的“函数名数组”的一个索引值。


在本例中,我们首先应该知道一个事实,即任何数组的下标都是由0开始的,而本例中的序号却是由1开始的,这就证明下标为0的函数名导出表项为空,如此一来,前两条疑问就迎刃而解了。导出表的这个特性是PE格式中的一个特殊机制,专门用来描述未导出符号的函数,这个特性值得牢记。


其次,我们要研究一下“导出函数”的地址为何指向不能执行的数据段的问题。


其实这个问题的答案简单得有些令人惊讶,因为它根本就不是一个导出函数,而是一个被导出的全局变量,这也是将“导出函数”用双引号括起来的原因。


图3形象地表示了这一逻辑。

图3  导出表结构

图3中的虚序号并不实际存在于任何存储介质中,它仅仅是一个逻辑上存在的结构,但其却是与导出函数表中的表项一一对应的,而且占用了导出函数表在存储介质中实际存在的地址。


由图3可知,由于虚序号占用了4个导出地址,所以IMAGE_EXPORT_DIRECTORY中导出序号表与导出函数名所指向的地址直接从第五个开始,而且导出序号表中的序号也是从序号4开始的,这很好地说明了虚序号在逻辑上存在的事实。

(未完待续)

微信公众号:计算机与网络安全

ID:Computer-network

【推荐书籍】
登录查看更多
0

相关内容

【实用书】Python技术手册,第三版767页pdf
专知会员服务
234+阅读 · 2020年5月21日
【干货书】流畅Python,766页pdf,中英文版
专知会员服务
224+阅读 · 2020年3月22日
机器学习速查手册,135页pdf
专知会员服务
338+阅读 · 2020年3月15日
【2020新书】Kafka实战:Kafka in Action,209页pdf
专知会员服务
67+阅读 · 2020年3月9日
【新书】Java企业微服务,Enterprise Java Microservices,272页pdf
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
87+阅读 · 2019年11月25日
新书《面向机器学习和数据分析的特征工程》,419页pdf
专知会员服务
142+阅读 · 2019年10月10日
在K8S上运行Kafka合适吗?会遇到哪些陷阱?
DBAplus社群
9+阅读 · 2019年9月4日
已删除
AI掘金志
7+阅读 · 2019年7月8日
Kali Linux 渗透测试:密码攻击
计算机与网络安全
16+阅读 · 2019年5月13日
“黑客”入门学习之“windows系统漏洞详解”
安全优佳
8+阅读 · 2019年4月17日
支持多标签页的Windows终端:Fluent 终端
Python程序员
7+阅读 · 2019年4月15日
一天精通无人中级篇:遥控器协议 S-BUS
无人机
51+阅读 · 2018年12月20日
Python | Jupyter导出PDF,自定义脚本告别G安装包
程序人生
7+阅读 · 2018年7月17日
如何用Python做舆情时间序列可视化?
CocoaChina
11+阅读 · 2017年7月21日
Doubly Attentive Transformer Machine Translation
Arxiv
4+阅读 · 2018年7月30日
Two Stream 3D Semantic Scene Completion
Arxiv
4+阅读 · 2018年7月16日
VIP会员
相关VIP内容
【实用书】Python技术手册,第三版767页pdf
专知会员服务
234+阅读 · 2020年5月21日
【干货书】流畅Python,766页pdf,中英文版
专知会员服务
224+阅读 · 2020年3月22日
机器学习速查手册,135页pdf
专知会员服务
338+阅读 · 2020年3月15日
【2020新书】Kafka实战:Kafka in Action,209页pdf
专知会员服务
67+阅读 · 2020年3月9日
【新书】Java企业微服务,Enterprise Java Microservices,272页pdf
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
87+阅读 · 2019年11月25日
新书《面向机器学习和数据分析的特征工程》,419页pdf
专知会员服务
142+阅读 · 2019年10月10日
相关资讯
在K8S上运行Kafka合适吗?会遇到哪些陷阱?
DBAplus社群
9+阅读 · 2019年9月4日
已删除
AI掘金志
7+阅读 · 2019年7月8日
Kali Linux 渗透测试:密码攻击
计算机与网络安全
16+阅读 · 2019年5月13日
“黑客”入门学习之“windows系统漏洞详解”
安全优佳
8+阅读 · 2019年4月17日
支持多标签页的Windows终端:Fluent 终端
Python程序员
7+阅读 · 2019年4月15日
一天精通无人中级篇:遥控器协议 S-BUS
无人机
51+阅读 · 2018年12月20日
Python | Jupyter导出PDF,自定义脚本告别G安装包
程序人生
7+阅读 · 2018年7月17日
如何用Python做舆情时间序列可视化?
CocoaChina
11+阅读 · 2017年7月21日
Top
微信扫码咨询专知VIP会员