Managing PE Files With Overlays

    Published: 2024-09-16. Last Updated: 2024-09-16 05:31:25 UTC
    by Xavier Mertens (Version: 1)
    0 comment(s)

    There is a common technique used by attackers: They append some data at the end of files (this is called an overlay). This can be used for two main reasons: To hide the appended data from the operating system (steganography). By example, you can append a text file at the end of a JPEG image. When your favourite image viewer will process the picture, it will just ignore the "rogue" data. Here is a PNG picture that has a text file (dir output) added at the end:

    The second reason is to defeat security controls and tools by creating a very big file. For performance reasons, many tools won't scan or inspect big files. So attackers will append data to increase the file size. Usually, data are just a suite of zeroes because the compression ration is excellent. Here is recent example of files that I discovered:

    remnux@remnux:/MalwareZoo/20240910$ file 'Payment Confirmation.tgz'
    Payment Confirmation.tgz: gzip compressed data, last modified: Tue Sep 10 06:05:16 2024, from FAT filesystem (MS-DOS, OS/2, NT), original size modulo 2^32 750001664 gzip compressed data, unknown method, ASCII, extra field, has comment, from FAT filesystem (MS-DOS, OS/2, NT), original size modulo 2^32 750001664
    remnux@remnux:/MalwareZoo/20240910$ ls -l 'Payment Confirmation.tgz'
    -rwx------ 1 remnux remnux 1167212 Sep 10 03:11 'Payment Confirmation.tgz'
    

    The file is 1.1MB in size but it contains a pretty big executable (less common):

    remnux@remnux:/MalwareZoo/20240910$ tar tzvf 'Payment Confirmation.tgz'
    -rwxr-xr-x 0/0       750000000 2024-09-10 02:04 Payment Confirmation.exe
    

    If you unpack and inspect the file manually, you'll see that it contains indeed a huge amount of NULL bytes:

    remnux@remnux:/MalwareZoo/20240910$ xxd 'Payment Confirmation.exe'
    ...
    00093fa0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00093fb0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00093fc0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00093fd0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00093fe0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00093ff0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000940a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000940b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000940c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000940d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000940e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000940f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094100: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094110: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094120: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094130: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094140: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094150: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094160: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094170: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094180: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094190: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000941a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000941b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000941c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000941d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000941e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    000941f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094200: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094210: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094220: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094230: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094240: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094250: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094260: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094270: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00094280: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    

    From a Windows loader point of view, this is not an issue: it will just ignore all the bytes that are not interesting to execute the process. Thank you Microsoft!

    How can you analyze the PE file without being annoyed by the overlay? Just remove it! If you can try to manipulate the file with your favourite text editor, there is an easy way to perform this task in a few lines of Python. A classic PE files looks like this (in a very simple way!): 

    In the PE headers, you can find a list of all the sections present in the file with two parameters:

    • The section offset (PointerToRawData)
    • The section size (SizeOfRawData)

    It's easy to get the overlay offset: PointerToRawData + SizeOfRawData. This value should be the end of the file. If the file size on disk is bigger, we have an overlay!

    Python has a great module called pefile[1] that helps to investigate executables. I wrote a small script to remove an overlay from a PE file:

    #!/usr/bin/python3
    #
    # Detects if a PE file has an overlay.
    # If yes, it creates a new PE file without the extra data.
    #
    import os
    import sys
    import pefile
    
    def detect_overlay(pe_filename):
        '''
        Detects and removes overlay from a PE file
        '''
        try:
            pe = pefile.PE(pe_filename)
        except Exception as e: 
            print(f"Can't open the PE file: {e}")
            return
    
        # Display sections
        print(f"{'Section Name':<15} {'Virtual Size':<15} {'Raw Size':<15} {'Raw Offset':<15}")
        print("="*58)
        for s in pe.sections:
            s_name = s.Name.decode('utf-8').rstrip('\x00')
            virtual_size = s.Misc_VirtualSize
            raw_size = s.SizeOfRawData
            raw_offset = s.PointerToRawData
            print(f"{s_name:<15} {virtual_size:<15} {raw_size:<15} {raw_offset:<15}")
    
        # The offset at which the PE sections end
        last_section = pe.sections[-1]
        end_of_pe = last_section.PointerToRawData + last_section.SizeOfRawData
    
        # The actual file size
        file_size = os.path.getsize(pe_filename)
    
        if file_size > end_of_pe:
            overlay_size = file_size - end_of_pe
            print(f"Overlay detected: {overlay_size} bytes")
            try:
                with open(pe_filename, 'rb') as infile:
                    data = infile.read(end_of_pe)
            except Exception as e:
                print(f"Can't open {pe_filename}: {e}")
                return
    
            name, ext = os.path.splitext(pe_filename)
            new_pe_filename = f"{name}-clean{ext}"
            try:
                with open(new_pe_filename + "", 'wb') as outfile:
                    outfile.write(data)
            except Exception as e:
                print(f"Can't write {new_pe_filename}: {e}")
                return
            new_file_size = os.path.getsize(new_pe_filename)
            print(f"New PE dumped: {new_pe_filename} (Size: {new_file_size})")
        else:
            print("No overlay detected")
    
    if __name__ == "__main__":
        if len(sys.argv) != 2:
            print("Usage: python3 overlay.py <pefilename>")
            sys.exit(1)
    
        detect_overlay(sys.argv[1])
    

    Let's process our big PE file:

    remnux@remnux:/MalwareZoo/20240910$ python3 overlay.py Payment-Confirmation.exe
    Section Name    Virtual Size    Raw Size        Raw Offset     
    ==========================================================
    .text           587568          587776          512            
    .rsrc           1580            2048            588288         
    .reloc          12              512             590336         
    Overlay detected: 749409152 bytes
    New PE dumped: Payment-Confirmation-clean.exe (Size: 590848)

    Now, you can investigate the new sample as usual...

    Be careful with overlays! Most of the time, they are just NULL bytes but they may contain useful data that will be used by the malware at execution time (configuration, shellcode, ...) 

    The PE file was another XWorm... 

    [1] https://pypi.org/project/pefile/

    Xavier Mertens (@xme)
    Xameco
    Senior ISC Handler - Freelance Cyber Security Consultant
    PGP Key

    0 comment(s)
    ISC Stormcast For Monday, September 16th, 2024 https://isc.sans.edu/podcastdetail/9138

      Comments


      Diary Archives