summaryrefslogtreecommitdiff
path: root/SectionDoubleP.py
diff options
context:
space:
mode:
authorn0p <0x90@n0p.cc>2013-08-04 21:16:41 +0200
committern0p <0x90@n0p.cc>2013-08-04 21:16:41 +0200
commit78261f3ddbe5b08f138dfbd58f07bf5aab479ead (patch)
tree384512420cc289677a476d96bdc30f9da43fda17 /SectionDoubleP.py
downloadSectionDoubleP-78261f3ddbe5b08f138dfbd58f07bf5aab479ead.tar.gz
SectionDoubleP-78261f3ddbe5b08f138dfbd58f07bf5aab479ead.zip
First commit.
Diffstat (limited to 'SectionDoubleP.py')
-rw-r--r--SectionDoubleP.py294
1 files changed, 294 insertions, 0 deletions
diff --git a/SectionDoubleP.py b/SectionDoubleP.py
new file mode 100644
index 0000000..00b0ac1
--- /dev/null
+++ b/SectionDoubleP.py
@@ -0,0 +1,294 @@
+""" Tested with pefile 1.2.10 on 32bit PE executable files.
+
+ An implementation to push or pop a section header to the section table of a PE file.
+ For further information refer to the docstrings of pop_back/push_back.
+
+ by n0p
+"""
+
+import pefile, pydasm, sys
+
+class SectionDoublePError(Exception):
+ pass
+
+class SectionDoubleP:
+ def __init__(self, pe):
+ self.pe = pe
+
+ def __adjust_optional_header(self):
+ """ Recalculates the SizeOfImage, SizeOfCode, SizeOfInitializedData and
+ SizeOfUninitializedData of the optional header.
+ """
+
+ # SizeOfImage = ((VirtualAddress + VirtualSize) of the new last section)
+ self.pe.OPTIONAL_HEADER.SizeOfImage = (self.pe.sections[-1].VirtualAddress +
+ self.pe.sections[-1].Misc_VirtualSize)
+
+ self.pe.OPTIONAL_HEADER.SizeOfCode = 0
+ self.pe.OPTIONAL_HEADER.SizeOfInitializedData = 0
+ self.pe.OPTIONAL_HEADER.SizeOfUninitializedData = 0
+
+ # Recalculating the sizes by iterating over every section and checking if
+ # the appropriate characteristics are set.
+ for section in self.pe.sections:
+ if section.Characteristics & 0x00000020:
+ # Section contains code.
+ self.pe.OPTIONAL_HEADER.SizeOfCode += section.SizeOfRawData
+ if section.Characteristics & 0x00000040:
+ # Section contains initialized data.
+ self.pe.OPTIONAL_HEADER.SizeOfInitializedData += section.SizeOfRawData
+ if section.Characteristics & 0x00000080:
+ # Section contains uninitialized data.
+ self.pe.OPTIONAL_HEADER.SizeOfUninitializedData += section.SizeOfRawData
+
+ def __add_header_space(self):
+ """ To make space for a new section header a buffer filled with nulls is added at the
+ end of the headers. The buffer has the size of one file alignment.
+ The data between the last section header and the end of the headers is copied to
+ the new space (everything moved by the size of one file alignment). If any data
+ directory entry points to the moved data the pointer is adjusted.
+ """
+
+ FileAlignment = self.pe.OPTIONAL_HEADER.FileAlignment
+ SizeOfHeaders = self.pe.OPTIONAL_HEADER.SizeOfHeaders
+
+ data = '\x00' * FileAlignment
+
+ # Adding the null buffer.
+ self.pe.__data__ = (self.pe.__data__[:SizeOfHeaders] + data +
+ self.pe.__data__[SizeOfHeaders + len(data):])
+
+ section_table_offset = (self.pe.DOS_HEADER.e_lfanew + 4 +
+ self.pe.FILE_HEADER.sizeof() + self.pe.FILE_HEADER.SizeOfOptionalHeader)
+
+ # Copying the data between the last section header and SizeOfHeaders to the newly allocated
+ # space.
+ offset_new_section = section_table_offset + self.pe.FILE_HEADER.NumberOfSections*0x28
+ size = SizeOfHeaders - offset_new_section
+ data = self.pe.get_data(offset_new_section, size)
+ self.pe.set_bytes_at_offset(offset_new_section + FileAlignment, data)
+
+ # Checking data directories if anything points to the space between the last section header
+ # and the former SizeOfHeaders. If that's the case the pointer is increased by FileAlignment.
+ for dir in self.pe.OPTIONAL_HEADER.DATA_DIRECTORY:
+ if offset_new_section < dir.VirtualAddress and dir.VirtualAddress < SizeOfHeaders:
+ dir.VirtualAddress += FileAlignment
+
+ self.pe.OPTIONAL_HEADER.SizeOfHeaders += FileAlignment
+
+ # The raw addresses of the sections are adjusted.
+ section_raw_address = section_table_offset + 0x14
+ for section in self.pe.sections:
+ self.pe.set_dword_at_offset(section_raw_address, section.PointerToRawData+FileAlignment)
+ section_raw_address += 0x28
+
+ self.pe.parse_sections(section_table_offset)
+
+ def __is_null_data(self, data):
+ """ Checks if the given data contains just null bytes.
+ """
+
+ for char in data:
+ if char != '\x00':
+ return False
+ return True
+
+ def pop_back(self):
+ """ Removes the last section of the section table.
+ Deletes the section header in the section table, the data of the section in the file,
+ pops the last section in the sections list of pefile and adjusts the sizes in the
+ optional header.
+ """
+
+ # Checking if there are any sections to pop.
+ if ( self.pe.FILE_HEADER.NumberOfSections > 0
+ and self.pe.FILE_HEADER.NumberOfSections == len(self.pe.sections)):
+
+ # Stripping the data of the section from the file.
+ if self.pe.sections[-1].SizeOfRawData != 0:
+ self.pe.__data__ = self.pe.__data__[:-self.pe.sections[-1].SizeOfRawData]
+
+ # Overwriting the section header in the binary with nulls.
+ # Getting the address of the section table and manually overwriting
+ # the header with nulls unfortunally didn't work out.
+ self.pe.sections[-1].Name = '\x00'*8
+ self.pe.sections[-1].Misc_VirtualSize = 0x00000000
+ self.pe.sections[-1].VirtualAddress = 0x00000000
+ self.pe.sections[-1].SizeOfRawData = 0x00000000
+ self.pe.sections[-1].PointerToRawData = 0x00000000
+ self.pe.sections[-1].PointerToRelocations = 0x00000000
+ self.pe.sections[-1].PointerToLinenumbers = 0x00000000
+ self.pe.sections[-1].NumberOfRelocations = 0x0000
+ self.pe.sections[-1].NumberOfLinenumbers = 0x0000
+ self.pe.sections[-1].Characteristics = 0x00000000
+
+ self.pe.sections.pop()
+
+ self.pe.FILE_HEADER.NumberOfSections -=1
+
+ self.__adjust_optional_header()
+ else:
+ raise SectionDoublePError("There's no section to pop.")
+
+ def push_back(self, Name=".NewSec", VirtualSize=0x00000000, VirtualAddress=0x00000000,
+ RawSize=0x00000000, RawAddress=0x00000000, RelocAddress=0x00000000,
+ Linenumbers=0x00000000, RelocationsNumber=0x0000, LinenumbersNumber=0x0000,
+ Characteristics=0xE00000E0, Data=""):
+ """ Adds the section, specified by the functions parameters, at the end of the section
+ table.
+ If the space to add an additional section header is insufficient, a buffer is inserted
+ after SizeOfHeaders. Data between the last section header and the end of SizeOfHeaders
+ is copied to +1 FileAlignment. Data directory entries pointing to this data are fixed.
+
+ A call with no parameters creates the same section header as LordPE does. But for the
+ binary to be executable without errors a VirtualSize > 0 has to be set.
+
+ If a RawSize > 0 is set or Data is given the data gets aligned to the FileAlignment and
+ is attached at the end of the file.
+ """
+
+ if self.pe.FILE_HEADER.NumberOfSections == len(self.pe.sections):
+
+ FileAlignment = self.pe.OPTIONAL_HEADER.FileAlignment
+ SectionAlignment = self.pe.OPTIONAL_HEADER.SectionAlignment
+
+ if len(Name) > 8:
+ raise SectionDoublePError("The name is too long for a section.")
+
+ if ( VirtualAddress < (self.pe.sections[-1].Misc_VirtualSize +
+ self.pe.sections[-1].VirtualAddress)
+ or VirtualAddress % SectionAlignment != 0):
+
+ if (self.pe.sections[-1].Misc_VirtualSize % SectionAlignment) != 0:
+ VirtualAddress = \
+ (self.pe.sections[-1].VirtualAddress + self.pe.sections[-1].Misc_VirtualSize -
+ (self.pe.sections[-1].Misc_VirtualSize % SectionAlignment) + SectionAlignment)
+ else:
+ VirtualAddress = \
+ (self.pe.sections[-1].VirtualAddress + self.pe.sections[-1].Misc_VirtualSize)
+
+ if VirtualSize < len(Data):
+ VirtualSize = len(Data)
+
+ if (len(Data) % FileAlignment) != 0:
+ # Padding the data of the section.
+ Data += '\x00' * (FileAlignment - (len(Data) % FileAlignment))
+
+ if RawSize != len(Data):
+ if ( RawSize > len(Data)
+ and (RawSize % FileAlignment) == 0):
+ Data += '\x00' * (RawSize - (len(Data) % RawSize))
+ else:
+ RawSize = len(Data)
+
+
+ section_table_offset = (self.pe.DOS_HEADER.e_lfanew + 4 +
+ self.pe.FILE_HEADER.sizeof() + self.pe.FILE_HEADER.SizeOfOptionalHeader)
+
+ # If the new section header exceeds the SizeOfHeaders there won't be enough space
+ # for an additional section header. Besides that it's checked if the 0x28 bytes
+ # (size of one section header) after the last current section header are filled
+ # with nulls/ are free to use.
+ if ( self.pe.OPTIONAL_HEADER.SizeOfHeaders <
+ section_table_offset + (self.pe.FILE_HEADER.NumberOfSections+1)*0x28
+ or not self.__is_null_data(self.pe.get_data(section_table_offset +
+ (self.pe.FILE_HEADER.NumberOfSections)*0x28, 0x28))):
+
+ # Checking if more space can be added.
+ if self.pe.OPTIONAL_HEADER.SizeOfHeaders < 0x1000:
+
+ self.__add_header_space()
+ print "Additional space to add a new section header was allocated."
+ else:
+ raise SectionDoublePError("No more space can be added for the section header.")
+
+
+ # The validity check of RawAddress is done after space for a new section header may
+ # have been added because if space had been added the PointerToRawData of the previous
+ # section would have changed.
+ if (RawAddress != (self.pe.sections[-1].PointerToRawData +
+ self.pe.sections[-1].SizeOfRawData)):
+ RawAddress = \
+ (self.pe.sections[-1].PointerToRawData + self.pe.sections[-1].SizeOfRawData)
+
+
+ # Appending the data of the new section to the file.
+ if len(Data) > 0:
+ self.pe.__data__ = self.pe.__data__[:] + Data
+
+ section_offset = section_table_offset + self.pe.FILE_HEADER.NumberOfSections*0x28
+
+ # Manually writing the data of the section header to the file.
+ self.pe.set_bytes_at_offset(section_offset, Name)
+ self.pe.set_dword_at_offset(section_offset+0x08, VirtualSize)
+ self.pe.set_dword_at_offset(section_offset+0x0C, VirtualAddress)
+ self.pe.set_dword_at_offset(section_offset+0x10, RawSize)
+ self.pe.set_dword_at_offset(section_offset+0x14, RawAddress)
+ self.pe.set_dword_at_offset(section_offset+0x18, RelocAddress)
+ self.pe.set_dword_at_offset(section_offset+0x1C, Linenumbers)
+ self.pe.set_word_at_offset(section_offset+0x20, RelocationsNumber)
+ self.pe.set_word_at_offset(section_offset+0x22, LinenumbersNumber)
+ self.pe.set_dword_at_offset(section_offset+0x24, Characteristics)
+
+ self.pe.FILE_HEADER.NumberOfSections +=1
+
+ # Parsing the section table of the file again to add the new section to the sections
+ # list of pefile.
+ self.pe.parse_sections(section_table_offset)
+
+ self.__adjust_optional_header()
+ else:
+ raise SectionDoublePError("The NumberOfSections specified in the file header and the " +
+ "size of the sections list of pefile don't match.")
+
+def print_section_info(pe):
+ for section in pe.sections:
+ print section
+
+ # If you don't have pydasm installed comment the rest of the function out.
+ print "The instructions at the beginning of the last section:"
+
+ ep = pe.sections[-1].VirtualAddress
+ ep_ava = ep+pe.OPTIONAL_HEADER.ImageBase
+ data = pe.get_memory_mapped_image()[ep:ep+6]
+ offset = 0
+ while offset < len(data):
+ i = pydasm.get_instruction(data[offset:], pydasm.MODE_32)
+ print pydasm.get_instruction_string(i, pydasm.FORMAT_INTEL, ep_ava+offset)
+ offset += i.length
+
+def main(argv):
+ pe = pefile.PE("putty.exe")
+
+ sections = SectionDoubleP(pe)
+
+ # JMP 0044C4DF
+ # NOP
+ # 0x0044C4DF is the entry point of putty.exe (at least of my version) when it's mapped at
+ # 0x00400000.
+ data="\xE9\xDA\xF4\xFC\xFF\x90"
+
+ try:
+ # Characteristics: Executable as code, Readable, Contains executable code
+ sections.push_back(Characteristics=0x60000020, Data=data)
+ sections.push_back(Characteristics=0x60000020, Data=data)
+ except SectionDoublePError as e:
+ print e
+
+ print "Information on every section after two sections have been added:"
+ print_section_info(pe)
+
+ try:
+ sections.pop_back()
+ except SectionDoublePError as e:
+ print e
+
+ print "\nInformation on every section after one of the added sections has been removed:"
+ print_section_info(pe)
+
+ pe.OPTIONAL_HEADER.AddressOfEntryPoint = pe.sections[-1].VirtualAddress
+
+ pe.write(filename="putty - modded.exe")
+
+if __name__ == '__main__':
+ main(sys.argv[1:])