-
-
Save boy-hack/dbfef2a3eff6b7b00791f6a9714b8aea to your computer and use it in GitHub Desktop.
学习pe,用python生成pe文件
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 学习pe的最好方法,就是自己写一个PE文件。这个例子展示了用python生成一个pe文件 | |
import struct | |
import time | |
MZ_MAGIC = 0x5A4D | |
PE_MAGIC = 0x4550 | |
IMAGE_FILE_MACHINE_I386 = 0x014c | |
IMAGE_SCN_MEM_EXECUTE = 0x20000000 # Section is executable. | |
IMAGE_SCN_MEM_READ = 0x40000000 # Section is readable. | |
IMAGE_SCN_MEM_WRITE = 0x80000000 # Section is writeable. | |
IMAGE_SCN_CNT_CODE = 0x00000020 | |
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 | |
class IMAGE_DOS_HEADER(object): | |
''' | |
DOS头只关心 magic 和 e_lfanew 位置就行 | |
''' | |
e_magic = MZ_MAGIC | |
e_cblp, e_cp, e_crlc, e_cparhdr, e_minalloc, e_maxalloc, e_ss, e_sp, \ | |
e_csum, e_ip, e_cs, e_lfarlc, e_ovno, e_res, e_oemid, \ | |
e_oeminfo, e_res2, e_lfanew = [0] * 18 | |
def __init__(self): | |
self.fmt = "<30HL" | |
# 小端模式 30个H(unsigned short 占2byte) 后一个是L(unsigned long 占 4byte) | |
self.e_res = [0, 0, 0, 0] | |
self.e_res2 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] | |
def raw(self): | |
return struct.pack(self.fmt, self.e_magic, self.e_cblp, self.e_cp, | |
self.e_crlc, self.e_cparhdr, self.e_minalloc, | |
self.e_maxalloc, self.e_ss, self.e_sp, self.e_csum, | |
self.e_ip, self.e_cs, self.e_lfarlc, self.e_ovno, | |
self.e_res[0], self.e_res[1], self.e_res[2], self.e_res[3], | |
self.e_oemid, self.e_oeminfo, | |
self.e_res2[0], self.e_res2[1], self.e_res2[2], self.e_res2[3], | |
self.e_res2[4], self.e_res2[5], self.e_res2[6], self.e_res2[7], | |
self.e_res2[8], self.e_res2[9], self.e_lfanew) | |
# e_lfanew是文件偏移 | |
def getPEOffset(self): | |
return self.e_lfanew | |
def getSize(self): | |
''' | |
DOS头,SIZE:30*2+1*4=64 | |
:return: | |
''' | |
return struct.calcsize(self.fmt) | |
class IMAGE_NT_HEADER_32(object): | |
def __init__(self): | |
self.Signature = PE_MAGIC | |
self.file_header = self.IMAGE_FILE_HEADER() | |
self.optional_header = self.IMAGE_OPTIONAL_HEADER32() | |
def getSize(self): | |
''' | |
PE文件头,SIZE:4+20+224=248 | |
:return: | |
''' | |
return 4 + self.file_header.getSize() + self.optional_header.getSize() | |
def raw(self): | |
return struct.pack("<L", self.Signature) + self.file_header.raw() + self.optional_header.raw() | |
class IMAGE_FILE_HEADER: | |
Machine, \ | |
NumberOfSections, \ | |
TimeDateStamp, \ | |
PointerToSymbolTable, \ | |
NumberOfSymbols, \ | |
SizeOfOptionalHeader, \ | |
Characteristics = IMAGE_FILE_MACHINE_I386, 0, 0, 0, 0, 0, 0 | |
def __init__(self): | |
self.fmt = "<2H3L2H" | |
def getSize(self): | |
''' | |
PE文件逻辑分布的信息,SIZE:2*2+3*4+2*2=20 | |
:return: | |
''' | |
return struct.calcsize(self.fmt) | |
def raw(self): | |
return struct.pack(self.fmt, self.Machine, | |
self.NumberOfSections, | |
self.TimeDateStamp, | |
self.PointerToSymbolTable, | |
self.NumberOfSymbols, | |
self.SizeOfOptionalHeader, | |
self.Characteristics) | |
class IMAGE_OPTIONAL_HEADER32: | |
Magic = 0x10b # 32位为0x10B,64位为0x20B,ROM镜像为0x107 | |
MajorLinkerVersion = 0 | |
MinorLinkerVersion = 0 | |
SizeOfCode = 0 # 一般放在“.text”节里。如果有多个代码节的话,它是所有代码节的和。必须是FileAlignment的整数倍,是在文件里的大小。 | |
SizeOfInitializedData = 0 | |
SizeOfUninitializedData = 0 | |
AddressOfEntryPoint = 0 # 代码入口点的偏移量,RVA | |
BaseOfCode = 0 # 代码基址,可执行代码的偏移值,RVA | |
BaseOfData = 0 # 数据基址,已初始化数据的偏移值,RVA | |
ImageBase = 0 # 程序默认装入基地址,提供整个二进制文件包括所有头的优先(线性)载入地址,RVA | |
SectionAlignment = 0 | |
FileAlignment = 0 | |
MajorOperatingSystemVersion = 0 | |
MinorOperatingSystemVersion = 0 | |
MajorImageVersion = 0 | |
MinorImageVersion = 0 | |
MajorSubsystemVersion = 4 | |
MinorSubsystemVersion = 0 | |
Win32VersionValue = 0 | |
SizeOfImage = 0 # 内存中整个PE映像体的尺寸。它是所有头和节经过节对齐处理后的大小。 | |
SizeOfHeaders = 0 # DOS头、PE头、区块表的总大小,也就等于文件尺寸减去文件中所有节的尺寸。可以以此值作为PE文件第一节的文件偏移量。 | |
CheckSum = 0 # 映像效验和 | |
Subsystem = 2 # 文件子系统,NT用来识别PE文件属于哪个子系统。 对于大多数Win32程序,只有两类值: Windows GUI 和Windows CUI (控制台) 2 for GUI, 3 for non-GUI | |
DllCharacteristics = 0 | |
SizeOfStackReserve = 0 | |
SizeOfStackCommit = 0 | |
SizeOfHeapReserve = 0 | |
SizeOfHeapCommit = 0 | |
LoaderFlags = 0 | |
NumberOfRvaAndSizes = 0x10 # 指定DataDirectory的数组个数,由于以前发行的Windows NT的原因,它只能为16。 -> 00 00 00 10 | |
DATA_DIRECTORY = [] | |
def __init__(self): | |
self.fmt = "<HBB9L6H4L2H6L" | |
def getSize(self): | |
''' | |
SIZE:19*4+9*2+2*1+16*8=224 | |
:return: | |
''' | |
selfsize = struct.calcsize(self.fmt) | |
for image_data in self.DATA_DIRECTORY: | |
selfsize += image_data.getSize() | |
return selfsize | |
def raw(self): | |
selfdata = struct.pack(self.fmt, self.Magic, | |
self.MajorLinkerVersion, | |
self.MinorLinkerVersion, | |
self.SizeOfCode, | |
self.SizeOfInitializedData, | |
self.SizeOfUninitializedData, | |
self.AddressOfEntryPoint, | |
self.BaseOfCode, | |
self.BaseOfData, | |
self.ImageBase, | |
self.SectionAlignment, | |
self.FileAlignment, | |
self.MajorOperatingSystemVersion, | |
self.MinorOperatingSystemVersion, | |
self.MajorImageVersion, | |
self.MinorImageVersion, | |
self.MajorSubsystemVersion, | |
self.MinorSubsystemVersion, | |
self.Win32VersionValue, | |
self.SizeOfImage, | |
self.SizeOfHeaders, | |
self.CheckSum, | |
self.Subsystem, | |
self.DllCharacteristics, | |
self.SizeOfStackReserve, | |
self.SizeOfStackCommit, | |
self.SizeOfHeapReserve, | |
self.SizeOfHeapCommit, | |
self.LoaderFlags, | |
self.NumberOfRvaAndSizes) | |
for image_data in self.DATA_DIRECTORY: | |
selfdata += image_data.raw() | |
return selfdata | |
class IMAGE_DATA_DIRECTORY: | |
VirtualAddress = 0 | |
Size = 0 | |
def __init__(self): | |
pass | |
def raw(self): | |
return struct.pack("<2L", self.VirtualAddress, self.Size) | |
def getSize(self): | |
return 0x4 * 2 | |
class Section: | |
def __init__(self): | |
self.fmt = "<LLLLLLHHL" | |
self.Name = "" | |
self.VirtualSize = self.VirtualAddress = self.SizeOfRawData = self.PointerToRawData = \ | |
self.PointerToRelocations = self.PointerToLinenumbers = \ | |
self.NumberOfRelocations = self.NumberOfLinenumbers = \ | |
self.Characteristics = 0 | |
# VirtualSize 被实际使用的区块大小,也可是PhysicalAddress,在可执行文件中,它是内容的大小.在目标文件中,它是内容重定位到的地址; | |
# VirtualAddress 区块的RAV地址(相对虚拟地址)。,节中数据的RVA。 | |
# SizeOfRawData 该块在磁盘中所占的大小,原始数据大小,经过文件对齐处理后节尺寸,PE装载器提取本域值了解需映射入内存的节字节数 | |
# PointerToRawData 该块在磁盘文件中的偏移,文件偏移,这是节基于文件的偏移量,PE装载器通过本域值找到节数据在文件中的位置。 | |
def getSize(self): | |
return struct.calcsize(self.fmt) + 8 | |
def has(self, rva, imagebase=0): | |
return (self.VirtualAddress + imagebase) <= rva < (self.VirtualAddress + self.VirtualSize + imagebase) | |
def hasOffset(self, offset): | |
return self.PointerToRawData <= offset < (self.PointerToRawData + self.VirtualSize) | |
def raw(self): | |
self.Name = (self.Name + "\x00" * (8 - len(self.Name)))[:8] | |
return self.Name.encode() + struct.pack(self.fmt, self.VirtualSize, | |
self.VirtualAddress, self.SizeOfRawData, self.PointerToRawData, | |
self.PointerToRelocations, self.PointerToLinenumbers, | |
self.NumberOfRelocations, self.NumberOfLinenumbers, | |
self.Characteristics) | |
class ImportDescriptor: | |
def __init__(self): | |
self.fmt = "<LLLLL" | |
self.OriginalFirstThunk = self.TimeDateStamp = self.ForwarderChain = self.Name = \ | |
self.FirstThunk = 0 | |
def raw(self): | |
return struct.pack(self.fmt, self.OriginalFirstThunk, self.TimeDateStamp, self.ForwarderChain, self.Name, \ | |
self.FirstThunk) | |
def getSize(self): | |
return struct.calcsize(self.fmt) | |
class ImageThunkData32: | |
Function = 0 | |
def getSize(self): | |
return 4 | |
def raw(self): | |
return struct.pack("<L", self.Function) | |
class ImageImportByName: | |
def __init__(self): | |
self.fmt = "<H" | |
self.Hint = 0 | |
self.Name = "" | |
def getSize(self): | |
size = len(self.Name) + 3 # 1 for \0 + 2 for Hint | |
if size % 2: | |
size += 1 # Padding | |
return size | |
def raw(self): | |
raw = struct.pack(self.fmt, self.Hint) + self.Name.encode() + b"\x00" | |
if len(raw) % 2: | |
raw += "\0" # padding | |
return raw | |
def align(idx, aligment): | |
return (idx + aligment) & ~(aligment - 1) | |
def dword(v): | |
return struct.pack("<L", v) | |
if __name__ == '__main__': | |
length = 0 | |
mz = IMAGE_DOS_HEADER() | |
mz.e_lfanew = mz.getSize() | |
length += mz.getSize() | |
# 设置pe头入口 | |
pe = IMAGE_NT_HEADER_32() | |
pe.file_header.NumberOfSections = 2 # section数量 | |
pe.file_header.TimeDateStamp = int(time.time()) | |
pe.file_header.Characteristics = 1 + 2 + 4 + 256 | |
# refer https://blog.csdn.net/qiming_zhang/article/details/7309909#3.2.2 | |
pe.optional_header.AddressOfEntryPoint = 0x1000 | |
pe.optional_header.ImageBase = 0x400000 | |
pe.optional_header.SectionAlignment = 0x1000 | |
pe.optional_header.FileAlignment = 0x200 | |
for i in range(pe.optional_header.NumberOfRvaAndSizes): | |
pe.optional_header.DATA_DIRECTORY.append(pe.IMAGE_DATA_DIRECTORY()) | |
pe.file_header.SizeOfOptionalHeader = pe.optional_header.getSize() | |
length += pe.getSize() | |
print("PE Header Size:{}".format(pe.file_header.SizeOfOptionalHeader)) | |
# .text section | |
text = Section() | |
text.Name = ".text" | |
text.Characteristics = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE | |
text.VirtualAddress = 0x1000 | |
# .rdata | |
rdata = Section() | |
rdata.Name = ".rdata" | |
rdata.Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | |
rdata.VirtualAddress = 0x2000 | |
length += text.getSize() + rdata.getSize() | |
# pading | |
pe.optional_header.SizeOfHeaders = align(length, pe.optional_header.FileAlignment) | |
padding = (pe.optional_header.SizeOfHeaders - length) * b'\x00' | |
length = pe.optional_header.SizeOfHeaders | |
importer = { | |
"user32.dll": ["MessageBoxA"], | |
"kernel32.dll": ["ExitProcess"] | |
} | |
ConstString = { | |
"title": "这是一个标题", | |
"msg": "这是内容,看到我你就成功了~" | |
} | |
replaceTable = {} | |
index = 1 | |
for k, vlist in importer.items(): | |
for func in vlist: | |
replaceTable[func] = b"\xcc" * 2 + struct.pack("<H", index) | |
index += 1 | |
for item in ConstString: | |
replaceTable[item] = b"\xcc" * 2 + struct.pack("<H", index) | |
index += 1 | |
# 写入text代码 | |
section_text = b"" | |
section_text += b"\x6a\x40" | |
section_text += b"\x68" + replaceTable["title"] # cccccc01 后面用作替换 title | |
section_text += b"\x68" + replaceTable["msg"] # cccccc02 后面用作替换 msg | |
section_text += b"\x6a\x00" | |
section_text += b"\xff\x15" + replaceTable["MessageBoxA"] # cccccc03 messagebox地址 | |
# push 40 // style | |
# push title | |
# push text | |
# push 0 // hwnd | |
# call messagebox | |
section_text += b"\x6a\x00" | |
section_text += b"\xff\x15" + replaceTable["ExitProcess"] # cccccc04 exitprocess地址 | |
# push 0 | |
# call exitprocess | |
section_text += b"\x90" * 4 | |
address = text.VirtualAddress + pe.optional_header.ImageBase + len(section_text) | |
section_text += ConstString["title"].encode("mbcs") + b"\x00" * 2 | |
section_text = section_text.replace(replaceTable["title"], dword(address)) | |
address = text.VirtualAddress + pe.optional_header.ImageBase + len(section_text) | |
section_text += ConstString["msg"].encode("mbcs") + b"\x00" * 2 | |
section_text = section_text.replace(replaceTable["msg"], dword(address)) | |
text.VirtualSize = len(section_text) | |
# text.SizeOfRawData = | |
text.SizeOfRawData = align(text.VirtualSize, pe.optional_header.FileAlignment) | |
text.PointerToRawData = length | |
section_text += b"\x00" * (text.SizeOfRawData - len(section_text)) | |
length += len(section_text) | |
# .rdata 导入表初始化 | |
importerStruct = {} | |
offset = 0 | |
for dll, funcs in importer.items(): | |
importdesc = ImportDescriptor() | |
importdesc.OriginalFirstThunk = 0x00 | |
importdesc.FirstThunk = 0 | |
importdesc.Name = 0 | |
offset += importdesc.getSize() | |
importerStruct[dll] = { | |
"funcList": funcs, | |
"importdesc": importdesc, | |
"chunk": {} | |
} | |
pe.optional_header.DATA_DIRECTORY[1].VirtualAddress = rdata.VirtualAddress | |
pe.optional_header.DATA_DIRECTORY[1].Size = offset | |
offset += 20 | |
# 导入表填值 | |
address = rdata.VirtualAddress + offset | |
section_rdata2 = b"" | |
# 计算字符串地址 | |
for dll in importerStruct.keys(): | |
importerStruct[dll]["importdesc"].Name = address | |
dlldata = dll.encode() + b"\x00" | |
section_rdata2 += dlldata | |
address += len(dlldata) | |
# ImageImportByName内存布局 | |
for dll in importerStruct.keys(): | |
for func in importerStruct[dll]["funcList"]: | |
iname = ImageImportByName() | |
iname.Name = func | |
section_rdata2 += iname.raw() | |
chunk = ImageThunkData32() | |
chunk.Function = address | |
address += iname.getSize() | |
importerStruct[dll]["chunk"][func] = chunk | |
section_rdata2 += b"\x00" * 4 | |
address += 4 | |
# ThunkData32 OriginalFirstThunk 内存布局 | |
for dll in importerStruct.keys(): | |
importerStruct[dll]["importdesc"].OriginalFirstThunk = address | |
chunks = importerStruct[dll]["chunk"] | |
for chunk in chunks.values(): | |
section_rdata2 += chunk.raw() | |
address += chunk.getSize() | |
section_rdata2 += b"\x00" * 4 | |
address += 4 | |
# ThunkData32 FirstThunk 内存布局 | |
chunkRecord = {} | |
for dll in importerStruct.keys(): | |
importerStruct[dll]["importdesc"].FirstThunk = address | |
chunks = importerStruct[dll]["chunk"] | |
for name, chunk in chunks.items(): | |
section_rdata2 += chunk.raw() | |
address += chunk.getSize() | |
chunkRecord[name] = address + pe.optional_header.ImageBase - 4 | |
section_rdata2 += b"\x00" * 4 | |
address += 4 | |
# 替换api的地址 | |
for dll in importerStruct.keys(): | |
for func in importerStruct[dll]["funcList"]: | |
section_text = section_text.replace(replaceTable[func], dword(chunkRecord[func])) | |
section_rdata = b"" | |
for dll in importerStruct.keys(): | |
section_rdata += importerStruct[dll]["importdesc"].raw() | |
section_rdata += b"\x00" * 20 | |
section_rdata += section_rdata2 | |
rdata.VirtualSize = len(section_rdata) | |
rdata.SizeOfRawData = len(section_rdata) | |
rdata.PointerToRawData = length | |
section_rdata += b"\x00" * (rdata.SizeOfRawData - len(section_rdata)) | |
length += len(section_rdata) | |
# 最后数据的完善 | |
# pe.optional_header.SizeOfImage = align(length, pe.optional_header.SectionAlignment) # // Image大小,内存中整个PE文件的映射的尺寸,可比实际的值大,必须是SectionAlignment的整数倍 | |
pe.optional_header.SizeOfImage = pe.optional_header.SizeOfHeaders + align(text.SizeOfRawData, | |
pe.optional_header.SectionAlignment) + align( | |
rdata.SizeOfRawData, pe.optional_header.SectionAlignment) | |
# 生成二进制 | |
code = b"" | |
code += mz.raw() | |
code += pe.raw() | |
# 生成section代码 | |
code += text.raw() | |
code += rdata.raw() | |
code += padding | |
# 生成每个section具体代码 | |
code += section_text | |
code += section_rdata | |
with open("test.exe", "wb") as f: | |
f.write(code) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment