Skip to content

Instantly share code, notes, and snippets.

@boy-hack

boy-hack/pe.py Secret

Last active May 8, 2021
Embed
What would you like to do?
学习pe,用python生成pe文件
# 学习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