21.1 使用PEfile分析PE文件
PeFile模块是Python
中一个强大的便携式第三方PE
格式分析工具,用于解析和处理Windows
可执行文件。该模块提供了一系列的API接口,使得用户可以通过Python
脚本来读取和分析PE文件的结构,包括文件头、节表、导入表、导出表、资源表、重定位表等等。此外,PEfile模块还可以帮助用户进行一些恶意代码分析,比如提取样本中的字符串、获取函数列表、重构导入表、反混淆等等。PEfile模块是Python中处理PE文件的重要工具之一,广泛应用于二进制分析、安全研究和软件逆向工程等领域。
由于该模块为第三方模块,在使用之前读者需要在命令行下执行pip install pefile
命令安装第三方库,当安装成功后即可正常使用,如下所示则是该模块的基本使用方法,读者可自行学习理解。
21.1.1 打开并加载PE文件
如下这段代码封装并实现了OpenPeFile
函数,可用于打开一个PE文件,在其内部首先判断了可执行文件是否被压缩如果被压缩则会通过zipfile
模块将压缩包读入内存并调用C2BIP3
函数将数据集转换为2字节,接着再执行pefile.PE()
函数,该函数可用于将可执行文件载入,至此读者可在主函数内通过pe.dump_dict()
的方式输出该PE文件的所有参数,由于输出的是字典,读者可以使用字典与列表的方式灵活的提取出该程序的所有参数信息。
import sys
import zipfile
import pefile
# 如果是Python3则转换为2字节
def C2BIP3(string):
if sys.version_info[0] > 2:
return bytes([ord(x) for x in string])
else:
return string
# 打开文件
def OpenPeFile(filename):
# 判断是否是ZIP压缩包
if filename.lower().endswith('.zip'):
try:
oZipfile = zipfile.ZipFile(filename, 'r')
file = oZipfile.open(oZipfile.infolist()[0], 'r', C2BIP3('infected'))
except Exception:
print(sys.exc_info()[1])
sys.exit()
oPE = pefile.PE(data=file.read())
file.close()
oZipfile.close()
# 如果是空则
elif filename == '':
oPE = False
return oPE
# 否则直接打开文件
else:
oPE = pefile.PE(filename)
return oPE
if __name__ == "__main__":
pe = OpenPeFile("d://lyshark.exe")
print(pe.FILE_HEADER.dump())
print(pe.dump_dict())
21.1.2 解析PE头部数据
如下代码实现了解析PE结构中头部基本数据,在GetHeader
函数内,我们首先通过pe.FILE_HEADER.Machine
成员判断当前读入的文件的位数信息,通过pe.FILE_HEADER.Characteristics
可判断PE文件的类型,通常为EXE可执行文件或DLL动态链接库文件,通过AddressOfEntryPoint
加上ImageBase
则可获取到程序的实际装载地址,压缩数据的计算可通过hashlib
模块对PE文件字节数据进行计算摘要获取,最后是附加数据,通过get_overlay_data_start_offset
则可获取到,并依次循环即可输出所有附加数据。
import hashlib
import pefile
# 计算得到数据长度,自动使用推荐大小
def NumberOfBytesHumanRepresentation(value):
if value <= 1024:
return '%s bytes' % value
elif value < 1024 * 1024:
return '%.1f KB' % (float(value) / 1024.0)
elif value < 1024 * 1024 * 1024:
return '%.1f MB' % (float(value) / 1024.0 / 1024.0)
else:
return '%.1f GB' % (float(value) / 1024.0 / 1024.0 / 1024.0)
# 获取PE头部基本信息
def GetHeader(pe):
raw = pe.write()
# 扫描基本信息
print("-" * 50)
print("程序基本信息")
print("-" * 50)
if (hex(pe.FILE_HEADER.Machine) == "0x14c"):
print("程序位数: {}".format("x86"))
if (hex(pe.FILE_HEADER.Machine) == "0x8664"):
print("程序位数: {}".format("x64"))
if (hex(pe.FILE_HEADER.Characteristics) == "0x102"):
print("程序类型: Executable")
elif (hex(pe.FILE_HEADER.Characteristics) == "0x2102"):
print("程序类型: Dynamic link library")
if pe.OPTIONAL_HEADER.AddressOfEntryPoint:
oep = pe.OPTIONAL_HEADER.AddressOfEntryPoint + pe.OPTIONAL_HEADER.ImageBase
print("实际入口: {}".format(hex(oep)))
print("映像基址: {}".format(hex(pe.OPTIONAL_HEADER.ImageBase)))
print("虚拟入口: {}".format(hex(pe.OPTIONAL_HEADER.AddressOfEntryPoint)))
print("映像大小: {}".format(hex(pe.OPTIONAL_HEADER.SizeOfImage)))
print("区段对齐: {}".format(hex(pe.OPTIONAL_HEADER.SectionAlignment)))
print("文件对齐: {}".format(hex(pe.OPTIONAL_HEADER.FileAlignment)))
print("区块数量: {}".format(int(pe.FILE_HEADER.NumberOfSections + 1)))
print('熵值比例: %f (Min=0.0, Max=8.0)' % pe.sections[0].entropy_H(raw))
# 计算压缩数据
print("-" * 50)
print("计算压缩数据")
print("-" * 50)
print('MD5 : %s' % hashlib.md5(raw).hexdigest())
print('SHA-1 : %s' % hashlib.sha1(raw).hexdigest())
print('SHA-256 : %s' % hashlib.sha256(raw).hexdigest())
print('SHA-512 : %s' % hashlib.sha512(raw).hexdigest())
# 扫描文件末尾是否存在附加数据
print("-" * 50)
print("扫描附加数据")
print("-" * 50)
overlayOffset = pe.get_overlay_data_start_offset()
if overlayOffset != None:
print("起始文件位置: 0x%08x"%overlayOffset)
overlaySize = len(raw[overlayOffset:])
print("长度: 0x%08x %s %.2f%%"%(overlaySize, NumberOfBytesHumanRepresentation(overlaySize), float(overlaySize) / float(len(raw)) * 100.0))
print("MD5: %s" %hashlib.md5(raw[overlayOffset:]).hexdigest())
print("SHA-256: %s" %hashlib.sha256(raw[overlayOffset:]).hexdigest())
if __name__ == "__main__":
pe = pefile.PE("d://lyshark.exe")
GetHeader(pe)
21.1.3 解析节表数据
运用PEFile
模块解析节表也很容易,如下代码中分别实现了两个功能函数,函数ScanSection()
用于输出当前文件的所有节表数据,其中通过pe.FILE_HEADER.NumberOfSections
得到节表数量,并通过循环的方式依次解析pe.sections
中的每一个节中元素,函数CheckSection()
则可用于计算PE
文件节大小以及节MD5
值,完整代码如下所示;
import hashlib
import pefile
# 计算得到数据长度,自动使用推荐大小
def NumberOfBytesHumanRepresentation(value):
if value <= 1024:
return '%s bytes' % value
elif value < 1024 * 1024:
return '%.1f KB' % (float(value) / 1024.0)
elif value < 1024 * 1024 * 1024:
return '%.1f MB' % (float(value) / 1024.0 / 1024.0)
else:
return '%.1f GB' % (float(value) / 1024.0 / 1024.0 / 1024.0)
# 输出所有的节
def ScanSection(pe):
print("-" * 100)
print("{:10s}{:10s}{:10s}{:10s}{:10s}{:10s}{:10s}{:10s}".
format("序号","节区名称","虚拟偏移","虚拟大小","实际偏移","实际大小","节区属性","熵值"))
print("-" * 100)
section_count = int(pe.FILE_HEADER.NumberOfSections + 1)
for count,item in zip(range(1,section_count),pe.sections):
print("%d\t\t\t%-10s\t0x%.8X\t0x%.8X\t0x%.8X\t0x%.8X\t0x%.8X\t%f"
%(count,(item.Name).decode("utf-8"),item.VirtualAddress,item.Misc_VirtualSize,item.PointerToRawData,item.SizeOfRawData,item.Characteristics,item.get_entropy()))
print("-" * 100)
# 计算所有节的MD5
def CheckSection(pe):
print("-" * 100)
print("序号\t\t节名称\t\t文件偏移\t\t大小\t\tMD5\t\t\t\t\t\t\t\t\t\t节大小")
print("-" * 100)
# 读取PE文件到内存
image_data = pe.get_memory_mapped_image()
section_count = int(pe.FILE_HEADER.NumberOfSections + 1)
for count,item in zip(range(1,section_count),pe.sections):
section_data = image_data[item.PointerToRawData: item.PointerToRawData + item.SizeOfRawData - 1]
data_size = NumberOfBytesHumanRepresentation(len(section_data))
hash_value = hashlib.md5(section_data).hexdigest()
print("{}\t{:10s}\t{:10X}\t{:10X}\t{:30s}\t{}".format(count,(item.Name).decode("utf-8"),item.PointerToRawData,item.SizeOfRawData,hash_value,data_size))
print("-" * 100)
if __name__ == "__main__":
pe = pefile.PE("d://lyshark.exe")
ScanSection(pe)
CheckSection(pe)
21.1.4 节区RVA与FOA互转
此处计算节偏移地址,相信读者能理解,在之前的文章中我们详细的介绍了PE文件如何进行RVA
与FOA
以及VA
之间的转换的,如果是在平时的恶意代码分析中需要快速实现转换那么使用Python将是一个不错的选择,如下代码中RVAToFOA
可将一个RVA
相对地址转换为FOA
文件偏移,FOAToRVA
则可实现将一个FOA文件偏移转换为RVA先对地址,当然PeFile模块内也提供了get_rva_from_offset
实现从FOA转RVA,get_offset_from_rva
则是从RVA到FOA,读者可自行选择不同的转换方式。
import pefile
# 将RVA转换为FOA的函数
def RVAToFOA(pe,rva):
for item in pe.sections:
Section_Start = item.VirtualAddress
Section_Ends = item.VirtualAddress + item.SizeOfRawData
if rva >= Section_Start and rva < Section_Ends:
return rva - item.VirtualAddress + item.PointerToRawData
return -1
# 将FOA文件偏移转换为RVA相对地址
def FOAToRVA(pe,foa):
ImageBase = pe.OPTIONAL_HEADER.ImageBase
NumberOfSectionsCount = pe.FILE_HEADER.NumberOfSections
for index in range(0,NumberOfSectionsCount):
PointerRawStart = pe.sections[index].PointerToRawData
PointerRawEnds = pe.sections[index].PointerToRawData + pe.sections[index].SizeOfRawData
if foa >= PointerRawStart and foa <= PointerRawEnds:
rva = pe.sections[index].VirtualAddress + (foa - pe.sections[index].PointerToRawData)
return rva
return -1
# 内部功能实现FOA->RVA互转
def inside(pe):
# 从FOA获取RVA 传入十进制
rva = pe.get_rva_from_offset(3952)
print("对应内存RVA: {}".format(hex(rva)))
# 从RVA获取FOA 传入十进制
foa = pe.get_offset_from_rva(rva)
print("对应文件FOA: {}".format(foa))
if __name__ == "__main__":
pe = pefile.PE("d://lyshark.exe")
ref = RVAToFOA(pe,4128)
print("RVA转FOA => 输出十进制: {}".format(ref))
ref = FOAToRVA(pe,1056)
print("FOA转RVA => 输出十进制: {}".format(ref))
21.1.5 解析数据为Hex格式
如下代码片段实现了对PE文件的各种十六进制操作功能,封装cDump()
类,该类内由多个类函数可以使用,其中HexDump()
可用于将读入的PE文件以16进制方式输出,HexAsciiDump()
则可用于输出十六进制以及所对应的ASCII格式,GetSectionHex()
用于找到PE文件的.text
节,并将此节内的数据读入到内存中,这段代码可以很好的实现对PE文件的十六进制输出与解析,读者可在实际开发中使用。
import pefile
from io import StringIO
import sys
import re
dumplinelength = 16
def CIC(expression):
if callable(expression):
return expression()
else:
return expression
def IFF(expression, valueTrue, valueFalse):
if expression:
return CIC(valueTrue)
else:
return CIC(valueFalse)
class cDump():
def __init__(self, data, prefix='', offset=0, dumplinelength=16):
self.data = data
self.prefix = prefix
self.offset = offset
self.dumplinelength = dumplinelength
# 输出指定位置的十六进制格式
def HexDump(self):
oDumpStream = self.cDumpStream(self.prefix)
hexDump = ''
for i, b in enumerate(self.data):
if i % self.dumplinelength == 0 and hexDump != '':
oDumpStream.Addline(hexDump)
hexDump = ''
hexDump += IFF(hexDump == '', '', ' ') + '%02X' % self.C2IIP2(b)
oDumpStream.Addline(hexDump)
return oDumpStream.Content()
def CombineHexAscii(self, hexDump, asciiDump):
if hexDump == '':
return ''
countSpaces = 3 * (self.dumplinelength - len(asciiDump))
if len(asciiDump) <= self.dumplinelength / 2:
countSpaces += 1
return hexDump + ' ' + (' ' * countSpaces) + asciiDump
# 输出指定位置的十六进制格式以及ASCII字符串
def HexAsciiDump(self):
oDumpStream = self.cDumpStream(self.prefix)
hexDump = ''
asciiDump = ''
for i, b in enumerate(self.data):
b = self.C2IIP2(b)
if i % self.dumplinelength == 0:
if hexDump != '':
oDumpStream.Addline(self.CombineHexAscii(hexDump, asciiDump))
hexDump = '%08X:' % (i + self.offset)
asciiDump = ''
if i % self.dumplinelength == self.dumplinelength / 2:
hexDump += ' '
hexDump += ' %02X' % b
asciiDump += IFF(b >= 32 and b <= 128, chr(b), '.')
oDumpStream.Addline(self.CombineHexAscii(hexDump, asciiDump))
return oDumpStream.Content()
class cDumpStream():
def __init__(self, prefix=''):
self.oStringIO = StringIO()
self.prefix = prefix
def Addline(self, line):
if line != '':
self.oStringIO.write(self.prefix + line + '\n')
def Content(self):
return self.oStringIO.getvalue()
@staticmethod
def C2IIP2(data):
if sys.version_info[0] > 2:
return data
else:
return ord(data)
# 只输出十六进制数据
def HexDump(data):
return cDump(data, dumplinelength=dumplinelength).HexDump()
# 输出十六进制与ASCII字符串
def HexAsciiDump(data):
return cDump(data, dumplinelength=dumplinelength).HexAsciiDump()
# 找到指定节并读取hex数据
def GetSectionHex(pe):
ImageBase = pe.OPTIONAL_HEADER.ImageBase
for item in pe.sections:
# 判断是否是.text节
if str(item.Name.decode('UTF-8').strip(b'\x00'.decode())) == ".text":
# print("虚拟地址: 0x%.8X 虚拟大小: 0x%.8X" %(item.VirtualAddress,item.Misc_VirtualSize))
VirtualAddress = item.VirtualAddress
VirtualSize = item.Misc_VirtualSize
ActualOffset = item.PointerToRawData
StartVA = hex(ImageBase + VirtualAddress)
StopVA = hex(ImageBase + VirtualAddress + VirtualSize)
print("[+] 代码段起始地址: {} 结束: {} 实际偏移:{} 长度: {}".format(StartVA, StopVA, ActualOffset, VirtualSize))
# 获取到.text节区间内的数据
hex_code = pe.write()[ActualOffset: VirtualSize]
return hex_code
else:
print("程序中不存在.text节")
return 0
return 0
REGEX_STANDARD = '[\x09\x20-\x7E]'
def ExtractStringsASCII(data):
regex = REGEX_STANDARD + '{%d,}'
return re.findall(regex % 4, data)
def ExtractStringsUNICODE(data):
regex = '((' + REGEX_STANDARD + '\x00){%d,})'
return [foundunicodestring.replace('\x00', '') for foundunicodestring, dummy in re.findall(regex % 4, data)]
# 将传入Hex字符串以每16字符分割在一个列表内
def ExtractStrings(data):
return ExtractStringsASCII(data) + ExtractStringsUNICODE(data)
if __name__ == "__main__":
pe = pefile.PE("d://lyshark.exe")
# 得到.text节内数据
ref = GetSectionHex(pe)
# 转为十六进制格式
dump_hex = HexDump(ref)
print(dump_hex)
# 打包为每16字符一个列表
dump_list = ExtractStrings(dump_hex)
print(dump_list)
21.1.6 解析数据目录表
数据目录表用于记录可执行文件的数据目录项在文件中的位置和大小。数据目录表共有16个条目,每个条目都对应着一个数据目录项,每个数据目录项都描述了可执行文件中某一部分的位置和大小。
数据目录表的解析可以使用pe.OPTIONAL_HEADER.NumberOfRvaAndSizes
首先获取到数据目录表的个数,接着二通过循环个数依次解包OPTIONAL_HEADER.DATA_DIRECTORY
里面的每一个列表,在循环列表时依次解包输出即可。
import pefile
# 将RVA转换为FOA的函数
def RVAToFOA(pe,rva):
for item in pe.sections:
Section_Start = item.VirtualAddress
Section_Ends = item.VirtualAddress + item.SizeOfRawData
if rva >= Section_Start and rva < Section_Ends:
return rva - item.VirtualAddress + item.PointerToRawData
return -1
# 扫描数据目录表
def ScanOptional(pe):
optional_size = pe.OPTIONAL_HEADER.NumberOfRvaAndSizes
print("数据目录表个数: {}".format(optional_size))
print("-" * 100)
print("编号 \t\t\t 目录RVA\t\t 目录FOA\t\t\t 长度\t\t 描述信息")
print("-" * 100)
for index in range(0,optional_size):
va = int(pe.OPTIONAL_HEADER.DATA_DIRECTORY[index].VirtualAddress)
print("%03d \t\t 0x%08X\t\t 0x%08X\t\t %08d \t\t"
%(index,
pe.OPTIONAL_HEADER.DATA_DIRECTORY[index].VirtualAddress,
RVAToFOA(pe,va),
pe.OPTIONAL_HEADER.DATA_DIRECTORY[index].Size
),end="")
if index == 0:
print("Export symbols")
if index == 1:
print("Import symbols")
if index == 2:
print("Resources")
if index == 3:
print("Exception")
if index == 4:
print("Security")
if index == 5:
print("Base relocation")
if index == 6:
print("Debug")
if index == 7:
print("Copyright string")
if index == 8:
print("Globalptr")
if index == 9:
print("Thread local storage (TLS)")
if index == 10:
print("Load configuration")
if index == 11:
print("Bound Import")
if index == 12:
print("Import Address Table")
if index == 13:
print("Delay Import")
if index == 14:
print("COM descriptor")
if index == 15:
print("NoUse")
if __name__ == "__main__":
pe = pefile.PE("d://lyshark.exe")
ScanOptional(pe)
21.1.7 解析导入导出表
导入表和导出表都是PE文件中的重要数据结构,分别记录着一个模块所导入和导出的函数和数据,如下所示则是使用PeFile
模块实现对导入表与导出表的解析工作,对于导入表ScanImport
的解析需要通过pe.DIRECTORY_ENTRY_IMPORT
获取到完整的导入目录,并通过循环的方式输出x.imports
中的数据即可,而对于导出表ScanExport
则需要在pe.DIRECTORY_ENTRY_EXPORT.symbols
导出符号中解析获取。
import pefile
# 输出所有导入表模块
def ScanImport(pe):
print("-" * 100)
try:
for x in pe.DIRECTORY_ENTRY_IMPORT:
for y in x.imports:
print("[*] 模块名称: %-20s 导入函数: %-14s" %((x.dll).decode("utf-8"),(y.name).decode("utf-8")))
except Exception:
pass
print("-" * 100)
# 输出所有导出表模块
def ScanExport(pe):
print("-" * 100)
try:
for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
print("[*] 导出序号: %-5s 模块地址: %-20s 模块名称: %-15s"
%(exp.ordinal,hex(pe.OPTIONAL_HEADER.ImageBase + exp.address),(exp.name).decode("utf-8")))
except:
pass
print("-" * 100)
if __name__ == "__main__":
pe = pefile.PE("d://lyshark.exe")
ScanImport(pe)
ScanExport(pe)
本文作者: 王瑞
本文链接: https://www.lyshark.com/post/92a3370c.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
21.1 使用PEfile分析PE文件的更多相关文章
- PE文件详解(七)
本文转载自小甲鱼PE文件讲解系列原文传送门 这次主要说明导出表,导出表一般记录着文件中函数的地址等相关信息,供其他程序调用,常见的.exe文件中一般不存在导出表,导出表更多的是存在于dll文件中.一般 ...
- 分析PE
PE文件是Windows平台下可执行文件标准格式,浓缩了微软工程师的设计精华,历经40年依旧保持着原有的设计.分析PE文件对于研究Windows操作系统有着重要的实践意义,对于逆向分析有着重要的指导作 ...
- PE文件学习系列笔记四-C++实现PE文件的分析
合肥程序员群:49313181. 合肥实名程序员群:128131462 (不愿透露姓名和信息者勿加入) Q Q:408365330 E-Mail:egojit@qq.com 综述: 首 ...
- PE文件学习系列二 DOS头分析
合肥程序员群:49313181. 合肥实名程序员群 :128131462 (不愿透露姓名和信息者勿加入)Q Q:408365330 E-Mail:egojit@qq.com PE文件结 ...
- 一个PE文件的逆向分析
一个PE文件的逆向分析 idf-ctf上有个题,是PE文件的逆向,反正对我来说做出来就是有意思的题,做不出来就没劲.言归正传,下面看一下吧 大家想玩可以去这个地方去拿题http://pan.baidu ...
- PE文件附加数据感染之Worm.Win32.Agent.ayd病毒分析
一.基本信息 样本名称:1q8JRgwDeGMofs.exe 病毒名称:Worm.Win32.Agent.ayd 文件大小:165384 字节 文件MD5:7EF5D0028997CB7DD3484A ...
- PE文件加节感染之Win32.Loader.bx.V病毒分析
一.病毒名称:Win32.Loader.bx.V 二.分析工具:IDA 5.5.OllyDebug.StudPE 三.PE文件加节感染病毒简介 PE病毒感染的方式比较多,也比较复杂也比较难分析,下面就 ...
- 手写PE文件(二)
[文章标题]: 纯手工编写的PE可执行程序 [文章作者]: Kinney [作者邮箱]: mohen_ng@sina.cn [下载地址]: 自己搜索下载 [使用工具]: C32 [操作平台]: win ...
- 获取pe文件的文件类型
工程文件petype.cpp通过调用pefile类中的函数获取文件类型. 文件类型的判断通过5个监测点完成. 监测点1:dos头的e_magic 监测点2:nt头的Signature 监测点3:文件头 ...
- 浅析MSIL中间语言——PE文件结构篇
一.开篇 开篇我想讲一下于本文无关的话题,其实我很想美化一下自己博客园一直没时间弄,无意间找了博客园李宝亨的博客园里面有一篇分享自己主题的文章,我就将这个模板暂时用作我的blog主题,我要讲述一个关于 ...
随机推荐
- SpringBoot集成Jpa对数据进行排序、分页、条件查询和过滤
之前介绍了SpringBoot集成Jpa的简单使用,接下来介绍一下使用Jpa连接数据库对数据进行排序.分页.条件查询和过滤操作.首先创建Springboot工程并已经继承JPA依赖,如果不知道可以查看 ...
- 2022-05-30:给定一个n*2的二维数组,表示有n个任务。 一个信息是任务能够开始做的时间,另一个信息是任务的结束期限,后者一定大于前者,且数值上都是正数, 你作为单线程的人,不能并行处理任务,
2022-05-30:给定一个n*2的二维数组,表示有n个任务. 一个信息是任务能够开始做的时间,另一个信息是任务的结束期限,后者一定大于前者,且数值上都是正数, 你作为单线程的人,不能并行处理任务, ...
- 2022-04-21:给定一个包含 [0,n) 中不重复整数的黑名单 blacklist, 写一个函数从 [0, n) 中返回一个不在 blacklist 中的随机整数, 对它进行优化使其尽量少调用系
2022-04-21:给定一个包含 [0,n) 中不重复整数的黑名单 blacklist, 写一个函数从 [0, n) 中返回一个不在 blacklist 中的随机整数, 对它进行优化使其尽量少调用系 ...
- 2021-02-19:给定一个二维数组matrix,一个人必须从左上角出发,最后到达右下角。沿途只可以向下或者向右走,沿途的数字都累加就是距离累加和。请问最小距离累加和是多少?
2021-02-19:给定一个二维数组matrix,一个人必须从左上角出发,最后到达右下角.沿途只可以向下或者向右走,沿途的数字都累加就是距离累加和.请问最小距离累加和是多少? 福哥答案2021-02 ...
- 创建CMDB项目
- C盘清理,移动node 依赖和缓存文件
由于先前安装的node 没有做任何配置,都是傻瓜式下一步,导致了我很多依赖都放置C盘,内存占用过多:也不太好管理所有觉得将它移动到node安装目录 一.新建文件夹 在原本安装的nodejs目录下新建 ...
- CSS文本过长时设置省略号
写页面时很多时候会遇到一个容器中文本内容超出,使用overflow : hidden;,但是跟用户体验不太友好,设置overflow : scroll; 会出现滚动看着也不爽,所以我就想使用css 设 ...
- ASP.NET Core 6框架揭秘实例演示[36]:HTTPS重定向
HTTPS是确保传输安全最主要的手段,并且已经成为了互联网默认的传输协议.不知道读者朋友们是否注意到当我们利用浏览器(比如Chrome)浏览某个公共站点的时候,如果我们输入的是一个HTTP地址,在大部 ...
- 让优惠再续一年!SHPC 老客专享
最近云筏君经常收到自家小伙伴发来的关于产品活动的关心慰问,掐指一算,哦,原来是一年一度大家喜闻乐见的剁手节(学名"双十一")马上要来了! 大家都知道,云筏家的产品向来主打高性价比, ...
- STL-priority_queue(ACM)
1.无法访问v.front().v.back() 2.是一个 堆,默认为大根堆,改造后为小根堆 大根堆 重构函数(默认)(大根堆) priority_queue<int> v; 基本操作 ...