Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee7b647acf | ||
|
|
349cfe9b26 | ||
|
|
2cdb918692 | ||
|
|
7504cfa9ad |
10 changed files with 754 additions and 557 deletions
|
|
@ -1,5 +1,7 @@
|
|||
# evbunpack
|
||||
[Enigma Virtual Box](https://enigmaprotector.com/) unpacker
|
||||
[](https://github.com/mos9527/evbunpack/blob/main/.github/workflows/build-and-publish.yml) [](https://GitHub.com/mos9527/evbunpack/releases/) [](https://github.com/psf/black)
|
||||
|
||||
[Enigma Virtual Box](https://enigmaprotector.com/en/downloads/changelogenigmavb.html) unpacker
|
||||
|
||||
## Features
|
||||
- Executable unpacking
|
||||
|
|
@ -15,6 +17,7 @@
|
|||
|
||||
| Packer Version | Notes | Unpack with Flags |
|
||||
| - | - | - |
|
||||
| 11.00 | Automatically tested in CI for x86/x64 binaries. | `-pe 10_70` |
|
||||
| 10.70 | Automatically tested in CI for x86/x64 binaries. | `-pe 10_70` |
|
||||
| 9.70 | Automatically tested in CI for x86/x64 binaries. | `-pe 9_70` |
|
||||
| 7.80 | Automatically tested in CI for x86/x64 binaries | `-pe 7_80 --legacy-fs ` |
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
#-*- coding: utf-8 --
|
||||
__version__ = '0.2.2'
|
||||
__author__ = 'mos9527'
|
||||
# -*- coding: utf-8 --
|
||||
__version__ = "0.2.6"
|
||||
__author__ = "mos9527"
|
||||
|
|
|
|||
|
|
@ -1,386 +1,563 @@
|
|||
#-*- coding: utf-8 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copy
|
||||
from itertools import dropwhile
|
||||
import struct,os,array,sys, logging
|
||||
import struct, os, array, sys, logging
|
||||
from argparse import ArgumentParser
|
||||
from mmap import mmap,ACCESS_READ
|
||||
from mmap import mmap, ACCESS_READ
|
||||
from io import BytesIO
|
||||
from evbunpack.aplib import decompress
|
||||
from aplib import decompress
|
||||
from evbunpack.const import *
|
||||
from evbunpack import __version__
|
||||
logger = logging.getLogger('evbunpack')
|
||||
|
||||
FOLDER_ALTNAMES = {
|
||||
'%DEFAULT FOLDER%' : ''
|
||||
}
|
||||
logger = logging.getLogger("evbunpack")
|
||||
|
||||
def write_bytes(fd,out_fd,size,chunk_sizes=None,chunk_process=None,default_chunksize=65536,desc='Extracting...'):
|
||||
bytes_read = 0
|
||||
FOLDER_ALTNAMES = {"%DEFAULT FOLDER%": ""}
|
||||
|
||||
|
||||
def write_bytes(
|
||||
fd,
|
||||
out_fd,
|
||||
size,
|
||||
chunk_sizes=None,
|
||||
chunk_process=None,
|
||||
default_chunksize=65536,
|
||||
desc="Extracting...",
|
||||
):
|
||||
bytes_read = 0
|
||||
bytes_wrote = 0
|
||||
inital_offset = fd.tell()
|
||||
stat_mxlen = 0
|
||||
while bytes_read < size:
|
||||
sys.stderr.write('%s: total=%8xh read=%8xh\r' % (desc,size,bytes_read))
|
||||
chunk_size = next(chunk_sizes) if chunk_sizes else default_chunksize
|
||||
size_to_read = min(chunk_size,size - (fd.tell() - inital_offset))
|
||||
stat = "%s: %.2f%% total=%8xh read=%8xh \r" % (
|
||||
desc,
|
||||
100 * bytes_read / size,
|
||||
size,
|
||||
bytes_read,
|
||||
)
|
||||
stat_mxlen = max(stat_mxlen, len(stat))
|
||||
sys.stderr.write(stat)
|
||||
chunk_size = next(chunk_sizes) if chunk_sizes else default_chunksize
|
||||
size_to_read = min(chunk_size, size - (fd.tell() - inital_offset))
|
||||
chunk = fd.read(size_to_read)
|
||||
bytes_read += len(chunk)
|
||||
chunk = chunk if not chunk_process else chunk_process(chunk)
|
||||
chunk = chunk if not chunk_process else chunk_process(chunk)
|
||||
bytes_wrote += out_fd.write(chunk)
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.write(" " * stat_mxlen + "\r")
|
||||
return bytes_wrote
|
||||
|
||||
|
||||
def get_size_by_struct(struct_):
|
||||
fmt , desc = make_format_by_struct(struct_)
|
||||
fmt, desc = make_format_by_struct(struct_)
|
||||
return struct.calcsize(fmt)
|
||||
|
||||
def read_bytes_by_struct(src,struct_):
|
||||
|
||||
def read_bytes_by_struct(src, struct_):
|
||||
return src.read(get_size_by_struct(struct_))
|
||||
|
||||
def make_format_by_struct(struct, *args):
|
||||
fmt, desc = zip(*filter(lambda p:isinstance(p, tuple),struct))
|
||||
fmt = ('<' if type(struct[-1]) != str else struct[-1]) + ("".join(fmt)) % args
|
||||
return fmt,desc
|
||||
|
||||
def pack(structure,*args):
|
||||
fmt,desc = make_format_by_struct(structure)
|
||||
return struct.pack(fmt,*args)
|
||||
def make_format_by_struct(struct, *args):
|
||||
fmt, desc = zip(*filter(lambda p: isinstance(p, tuple), struct))
|
||||
fmt = ("<" if type(struct[-1]) != str else struct[-1]) + ("".join(fmt)) % args
|
||||
return fmt, desc
|
||||
|
||||
|
||||
def pack(structure, *args):
|
||||
fmt, desc = make_format_by_struct(structure)
|
||||
return struct.pack(fmt, *args)
|
||||
|
||||
|
||||
def unpack(structure, buffer, *args, **extra):
|
||||
'''Unpack buffer by structure given'''
|
||||
fmt,desc = make_format_by_struct(structure,*args)
|
||||
"""Unpack buffer by structure given"""
|
||||
fmt, desc = make_format_by_struct(structure, *args)
|
||||
unpacked = struct.unpack_from(fmt, buffer, 0)
|
||||
return {**{k: v for k, v in zip(desc, unpacked) if k},**extra}
|
||||
return {**{k: v for k, v in zip(desc, unpacked) if k}, **extra}
|
||||
|
||||
def read_named_node(src):
|
||||
blkFilename = bytearray()
|
||||
|
||||
def read_named_node(src):
|
||||
blkFilename = bytearray()
|
||||
p = src.read(2)
|
||||
while (p[0]!=0 or p[1]!=0):
|
||||
while p[0] != 0 or p[1] != 0:
|
||||
blkFilename.extend(p)
|
||||
p = src.read(2)
|
||||
p = src.read(2)
|
||||
block = blkFilename + src.read(1)
|
||||
return unpack(EVB_NODE_NAMED, block, len(blkFilename),offset=src.tell())
|
||||
return unpack(EVB_NODE_NAMED, block, len(blkFilename), offset=src.tell())
|
||||
|
||||
def read_header_node(src):
|
||||
return unpack(EVB_HEADER_NODE,read_bytes_by_struct(src,EVB_HEADER_NODE))
|
||||
|
||||
def read_optional_legacy_pe_file_node(src):
|
||||
return unpack(EVB_NODE_OPTIONAL_PE_FILE, read_bytes_by_struct(src,EVB_NODE_OPTIONAL_PE_FILE))
|
||||
def read_header_node(src):
|
||||
return unpack(EVB_HEADER_NODE, read_bytes_by_struct(src, EVB_HEADER_NODE))
|
||||
|
||||
|
||||
def read_optional_legacy_pe_file_node(src):
|
||||
return unpack(
|
||||
EVB_NODE_OPTIONAL_PE_FILE, read_bytes_by_struct(src, EVB_NODE_OPTIONAL_PE_FILE)
|
||||
)
|
||||
|
||||
|
||||
def read_optional_file_node(src):
|
||||
return unpack(
|
||||
EVB_NODE_OPTIONAL_FILE, read_bytes_by_struct(src, EVB_NODE_OPTIONAL_FILE)
|
||||
)
|
||||
|
||||
def read_optional_file_node(src):
|
||||
return unpack(EVB_NODE_OPTIONAL_FILE, read_bytes_by_struct(src,EVB_NODE_OPTIONAL_FILE))
|
||||
|
||||
def read_chunk_block(src):
|
||||
return unpack(EVB_CHUNK_BLOCK, read_bytes_by_struct(src,EVB_CHUNK_BLOCK))
|
||||
return unpack(EVB_CHUNK_BLOCK, read_bytes_by_struct(src, EVB_CHUNK_BLOCK))
|
||||
|
||||
def read_pack_header(src):
|
||||
return unpack(EVB_PACK_HEADER, read_bytes_by_struct(src,EVB_PACK_HEADER))
|
||||
|
||||
def read_main_node(src):
|
||||
return unpack(EVB_NODE_MAIN, read_bytes_by_struct(src,EVB_NODE_MAIN))
|
||||
def read_pack_header(src):
|
||||
return unpack(EVB_PACK_HEADER, read_bytes_by_struct(src, EVB_PACK_HEADER))
|
||||
|
||||
|
||||
def read_main_node(src):
|
||||
return unpack(EVB_NODE_MAIN, read_bytes_by_struct(src, EVB_NODE_MAIN))
|
||||
|
||||
|
||||
def pe_external_tree(fd):
|
||||
# Before calling, make sure cursor is already at where
|
||||
# the following bytes are b`EVB\x00`
|
||||
# Both PE and external packages work with this method
|
||||
# Both PE and external packages work with this method
|
||||
hdr = read_pack_header(fd)
|
||||
assert hdr['signature'] == EVB_MAGIC, "Invalid signature"
|
||||
main_node = read_main_node(fd)
|
||||
abs_offset = fd.tell() + main_node['size'] - 12 # offset from the head of the stream
|
||||
fd.seek(-1,1)
|
||||
assert hdr["signature"] == EVB_MAGIC, "Invalid signature"
|
||||
main_node = read_main_node(fd)
|
||||
abs_offset = (
|
||||
fd.tell() + main_node["size"] - 12
|
||||
) # offset from the head of the stream
|
||||
fd.seek(-1, 1)
|
||||
yield main_node
|
||||
max_object_count = 0
|
||||
current_object_count = 0
|
||||
while True:
|
||||
while True:
|
||||
try:
|
||||
header_node = read_header_node(fd)
|
||||
named_node = read_named_node(fd)
|
||||
named_node = read_named_node(fd)
|
||||
except struct.error:
|
||||
return # Potential EOF exception
|
||||
if named_node['type'] == NODE_TYPE_FILE:
|
||||
optional_node = read_optional_file_node(fd)
|
||||
optional_node['offset'] = abs_offset
|
||||
abs_offset += optional_node['stored_size']
|
||||
current_object_count += 1
|
||||
elif named_node['type'] == NODE_TYPE_FOLDER:
|
||||
optional_node = {}
|
||||
fd.seek(25,1)
|
||||
max_object_count += header_node['objects_count']
|
||||
return # Potential EOF exception
|
||||
if named_node["type"] == NODE_TYPE_FILE:
|
||||
optional_node = read_optional_file_node(fd)
|
||||
optional_node["offset"] = abs_offset
|
||||
abs_offset += optional_node["stored_size"]
|
||||
current_object_count += 1
|
||||
else:
|
||||
return # assuming finished
|
||||
named_node['name'] = named_node['name'].decode('utf-16-le')
|
||||
yield {**header_node,**named_node,**optional_node}
|
||||
if current_object_count > max_object_count and max_object_count > 0:
|
||||
return
|
||||
elif named_node["type"] == NODE_TYPE_FOLDER:
|
||||
optional_node = {}
|
||||
fd.seek(25, 1)
|
||||
max_object_count += header_node["objects_count"]
|
||||
current_object_count += 1
|
||||
else:
|
||||
return # assuming finished
|
||||
named_node["name"] = named_node["name"].decode("utf-16-le")
|
||||
yield {**header_node, **named_node, **optional_node}
|
||||
if current_object_count > max_object_count and max_object_count > 0:
|
||||
return
|
||||
|
||||
|
||||
def legacy_pe_tree(fd):
|
||||
# Older executables has their file table and content placed together
|
||||
# Courtesy of evb-extractor!
|
||||
# Courtesy of evb-extractor!
|
||||
hdr = read_pack_header(fd)
|
||||
assert hdr['signature'] == EVB_MAGIC, "Invalid signature"
|
||||
seek_origin = 0
|
||||
assert hdr["signature"] == EVB_MAGIC, "Invalid signature"
|
||||
seek_origin = 0
|
||||
max_object_count = 0
|
||||
current_object_count = 0
|
||||
while True:
|
||||
while True:
|
||||
seek_origin = fd.tell()
|
||||
try:
|
||||
header_node = read_header_node(fd)
|
||||
named_node = read_named_node(fd)
|
||||
except struct.error:
|
||||
return # Potential EOF exception
|
||||
if named_node['type'] == NODE_TYPE_FILE:
|
||||
fd.seek(seek_origin + header_node['size'] + 4 - get_size_by_struct(EVB_NODE_OPTIONAL_PE_FILE))
|
||||
optional_node = read_optional_legacy_pe_file_node(fd)
|
||||
optional_node['offset'] = fd.tell()
|
||||
fd.seek(optional_node['stored_size'],1)
|
||||
return # Potential EOF exception
|
||||
if named_node["type"] == NODE_TYPE_FILE:
|
||||
fd.seek(
|
||||
seek_origin
|
||||
+ header_node["size"]
|
||||
+ 4
|
||||
- get_size_by_struct(EVB_NODE_OPTIONAL_PE_FILE)
|
||||
)
|
||||
optional_node = read_optional_legacy_pe_file_node(fd)
|
||||
optional_node["offset"] = fd.tell()
|
||||
fd.seek(optional_node["stored_size"], 1)
|
||||
current_object_count += 1
|
||||
elif named_node['type'] == NODE_TYPE_FOLDER:
|
||||
elif named_node["type"] == NODE_TYPE_FOLDER:
|
||||
optional_node = {}
|
||||
fd.seek(seek_origin + header_node['size'] + 4)
|
||||
max_object_count += header_node['objects_count']
|
||||
fd.seek(seek_origin + header_node["size"] + 4)
|
||||
max_object_count += header_node["objects_count"]
|
||||
current_object_count += 1
|
||||
elif named_node['type'] == NODE_TYPE_MAIN:
|
||||
optional_node = {}
|
||||
fd.seek(seek_origin + header_node['size'] + 4)
|
||||
else:
|
||||
return # assuming finished
|
||||
named_node['name'] = named_node['name'].decode('utf-16-le')
|
||||
yield {**header_node,**named_node,**optional_node}
|
||||
elif named_node["type"] == NODE_TYPE_MAIN:
|
||||
optional_node = {}
|
||||
fd.seek(seek_origin + header_node["size"] + 4)
|
||||
else:
|
||||
return # assuming finished
|
||||
named_node["name"] = named_node["name"].decode("utf-16-le")
|
||||
yield {**header_node, **named_node, **optional_node}
|
||||
if current_object_count > max_object_count and max_object_count > 0:
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
def completed(generator):
|
||||
# Complete building the tree before we'd read the file
|
||||
for item in list(generator):
|
||||
for item in list(generator):
|
||||
yield item
|
||||
|
||||
def process_file_node(fd,path,node):
|
||||
with open(path,'wb') as output:
|
||||
rsize = node['original_size']
|
||||
ssize = node['stored_size']
|
||||
offset = node['offset']
|
||||
|
||||
def process_file_node(fd, path, node):
|
||||
with open(path, "wb") as output:
|
||||
rsize = node["original_size"]
|
||||
ssize = node["stored_size"]
|
||||
offset = node["offset"]
|
||||
fd.seek(offset)
|
||||
if rsize != ssize: # Compression detected
|
||||
chunks_blk = read_chunk_block(fd)
|
||||
blkChunkData = fd.read(chunks_blk['size'] - get_size_by_struct(EVB_CHUNK_BLOCK))
|
||||
arrChunkData = (val for idx,val in enumerate(array.array('I',blkChunkData)) if idx % 3 == 0)
|
||||
if rsize != ssize: # Compression detected
|
||||
chunks_blk = read_chunk_block(fd)
|
||||
blkChunkData = fd.read(
|
||||
chunks_blk["size"] - get_size_by_struct(EVB_CHUNK_BLOCK)
|
||||
)
|
||||
arrChunkData = (
|
||||
val
|
||||
for idx, val in enumerate(array.array("I", blkChunkData))
|
||||
if idx % 3 == 0
|
||||
)
|
||||
# Chunk data comes in 12-bytes rotation: Chunk size (4bytes), Total size (4bytes), Padding (4bytes)
|
||||
# But with the last Chunk size, it does not come with Total size or Padding...
|
||||
# Thus filtering only every 3rd elements works. Which should always give us Chunk size
|
||||
# Even if the last 8 bytes is missing
|
||||
wsize = write_bytes(
|
||||
fd,output,
|
||||
size=ssize - chunks_blk['size'],
|
||||
fd,
|
||||
output,
|
||||
size=ssize - chunks_blk["size"],
|
||||
chunk_sizes=arrChunkData,
|
||||
chunk_process=decompress,
|
||||
desc='Decompressing File [offset=0x%x, offsetBlk=0x%x]' % (fd.tell(),chunks_blk['size'])
|
||||
desc="Decompressing File [offset=0x%x, offsetBlk=0x%x]"
|
||||
% (fd.tell(), chunks_blk["size"]),
|
||||
)
|
||||
assert wsize == rsize,"Incorrect size"
|
||||
else:
|
||||
assert wsize == rsize, "Incorrect size"
|
||||
else:
|
||||
write_bytes(
|
||||
fd,output,
|
||||
fd,
|
||||
output,
|
||||
size=ssize,
|
||||
desc='Writing File [size=0x%x, offset=0x%x]' % (ssize,offset)
|
||||
desc="Writing File [size=0x%x, offset=0x%x]" % (ssize, offset),
|
||||
)
|
||||
|
||||
def restore_pe(input_file : str, output_file : str, pe_variant : str):
|
||||
|
||||
def restore_pe(input_file: str, output_file: str, pe_variant: str):
|
||||
warnings_issued = 0
|
||||
from pefile import PE,OPTIONAL_HEADER_MAGIC_PE_PLUS
|
||||
logger.debug('Loading PE...')
|
||||
pe = PE(input_file,fast_load=True)
|
||||
from pefile import PE, OPTIONAL_HEADER_MAGIC_PE_PLUS
|
||||
|
||||
logger.debug("Loading PE...")
|
||||
pe = PE(input_file, fast_load=True)
|
||||
arch_64 = pe.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE_PLUS
|
||||
logger.debug('PE loaded. Arch=%s' % ('x64' if arch_64 else 'x86'))
|
||||
pe.__data__ = bytearray(pe.__data__) # This allows us to apply slicing on the PE data
|
||||
# Helpers
|
||||
find_section = lambda name:next(filter(lambda x:name in x.Name,pe.sections))
|
||||
find_data_directory = lambda name:next(filter(lambda x:name in x.name,pe.OPTIONAL_HEADER.DATA_DIRECTORY))
|
||||
search_pattern_in_sections = lambda pattern:next(dropwhile(lambda x: x[1] == -1, ((section, pe.__data__.find(pattern,section.PointerToRawData, section.PointerToRawData + section.SizeOfRawData)) for section in pe.sections)))
|
||||
logger.debug("PE loaded. Arch=%s" % ("x64" if arch_64 else "x86"))
|
||||
pe.__data__ = bytearray(
|
||||
pe.__data__
|
||||
) # This allows us to apply slicing on the PE data
|
||||
# Helpers
|
||||
find_section = lambda name: next(
|
||||
filter(lambda x: name in x.Name, pe.sections), None
|
||||
)
|
||||
find_data_directory = lambda name: next(
|
||||
filter(lambda x: name in x.name, pe.OPTIONAL_HEADER.DATA_DIRECTORY)
|
||||
)
|
||||
search_pattern_in_sections = lambda pattern: next(
|
||||
dropwhile(
|
||||
lambda x: x[1] == -1,
|
||||
(
|
||||
(
|
||||
section,
|
||||
pe.__data__.find(
|
||||
pattern,
|
||||
section.PointerToRawData,
|
||||
section.PointerToRawData + section.SizeOfRawData,
|
||||
),
|
||||
)
|
||||
for section in pe.sections
|
||||
),
|
||||
)
|
||||
)
|
||||
# Data
|
||||
logger.info('Unpacking with variant: %s' % pe_variant)
|
||||
enigma1 = pe.__data__[find_section(b'.enigma1').PointerToRawData:]
|
||||
logger.info("Unpacking with variant: %s" % pe_variant)
|
||||
enigma1 = find_section(b".enigma1")
|
||||
assert (
|
||||
enigma1
|
||||
), "Cannot find .enigma1 section. The file is likely not packed with Enigma Virtual Box, or has obfuscated section names."
|
||||
enigma1 = pe.__data__[enigma1.PointerToRawData :]
|
||||
hdr = unpack(EVB_ENIGMA1_HEADER.get_struct(arch_64, pe_variant), enigma1)
|
||||
# Restore section with built-in offsets. All these ADDRESSes are VAs
|
||||
find_data_directory('IMPORT').VirtualAddress = hdr['IMPORT_ADDRESS']
|
||||
find_data_directory('IMPORT').Size = hdr['IMPORT_SIZE']
|
||||
find_data_directory('RELOC').VirtualAddress = hdr['RELOC_ADDRESS']
|
||||
find_data_directory('RELOC').Size = hdr['RELOC_SIZE']
|
||||
logger.debug('Import -> VA=0x%x Size=0x%x' % (hdr['IMPORT_ADDRESS'],hdr['IMPORT_SIZE']))
|
||||
logger.debug('Reloc -> VA=0x%x Size=0x%x' % (hdr['RELOC_ADDRESS'],hdr['RELOC_SIZE']))
|
||||
if hdr['RELOC_SIZE'] == 0 or hdr['IMPORT_SIZE'] == 0:
|
||||
find_data_directory("IMPORT").VirtualAddress = hdr["IMPORT_ADDRESS"]
|
||||
find_data_directory("IMPORT").Size = hdr["IMPORT_SIZE"]
|
||||
find_data_directory("RELOC").VirtualAddress = hdr["RELOC_ADDRESS"]
|
||||
find_data_directory("RELOC").Size = hdr["RELOC_SIZE"]
|
||||
logger.debug(
|
||||
"Import -> VA=0x%x Size=0x%x" % (hdr["IMPORT_ADDRESS"], hdr["IMPORT_SIZE"])
|
||||
)
|
||||
logger.debug(
|
||||
"Reloc -> VA=0x%x Size=0x%x" % (hdr["RELOC_ADDRESS"], hdr["RELOC_SIZE"])
|
||||
)
|
||||
if hdr["RELOC_SIZE"] == 0 or hdr["IMPORT_SIZE"] == 0:
|
||||
warnings_issued += 1
|
||||
logger.warning('Import/Reloc table size is zero. This may indicate that the header is incorrectly parsed.')
|
||||
logger.debug('Rebuilding Exception directory...')
|
||||
logger.warning(
|
||||
"Import/Reloc table size is zero. This may indicate that the header is incorrectly parsed."
|
||||
)
|
||||
logger.debug("Rebuilding Exception directory...")
|
||||
# Rebuild the exception directory
|
||||
exception_dir = find_data_directory('EXCEPTION')
|
||||
exception_dir = find_data_directory("EXCEPTION")
|
||||
exception_raw_ptr = pe.get_offset_from_rva(exception_dir.VirtualAddress)
|
||||
exception_data = pe.__data__[exception_raw_ptr:exception_raw_ptr + exception_dir.Size]
|
||||
exception_data = pe.__data__[
|
||||
exception_raw_ptr : exception_raw_ptr + exception_dir.Size
|
||||
]
|
||||
exception_struct = PE64_EXCEPTION if arch_64 else PE_EXCEPTION
|
||||
exception_end = 0
|
||||
for i in range(0,exception_dir.Size,get_size_by_struct(exception_struct)):
|
||||
block = unpack(exception_struct,exception_data[i:])
|
||||
block['section'] = pe.get_section_by_rva(block['BEGIN_ADDRESS'])
|
||||
for i in range(0, exception_dir.Size, get_size_by_struct(exception_struct)):
|
||||
block = unpack(exception_struct, exception_data[i:])
|
||||
block["section"] = pe.get_section_by_rva(block["BEGIN_ADDRESS"])
|
||||
exception_end = i
|
||||
if b'.enigma' in block['section'].Name:
|
||||
if b".enigma" in block["section"].Name:
|
||||
break
|
||||
exception_data = exception_data[:exception_end]
|
||||
# Destory .enigma* sections
|
||||
pe.__data__ = pe.__data__[:find_section(b'.enigma1').PointerToRawData] + pe.__data__[find_section(b'.enigma2').PointerToRawData + find_section(b'.enigma2').SizeOfRawData:]
|
||||
pe.__data__ = (
|
||||
pe.__data__[: find_section(b".enigma1").PointerToRawData]
|
||||
+ pe.__data__[
|
||||
find_section(b".enigma2").PointerToRawData
|
||||
+ find_section(b".enigma2").SizeOfRawData :
|
||||
]
|
||||
)
|
||||
# If original program has a overlay, this will perserve it. Otherwise it's okay to remove them anyway.
|
||||
assert pe.sections.pop().Name == b'.enigma2'
|
||||
assert pe.sections.pop().Name == b'.enigma1'
|
||||
pe.FILE_HEADER.NumberOfSections -= 2
|
||||
assert pe.sections.pop().Name == b".enigma2"
|
||||
assert pe.sections.pop().Name == b".enigma1"
|
||||
pe.FILE_HEADER.NumberOfSections -= 2
|
||||
# NOTE: .enigma1 contains the VFS, as well as some Optional PE Header info as descrbied above
|
||||
# NOTE: .enigma2 is a aplib compressed loader DLL. You can decompress it with aplib provided in this repo
|
||||
# NOTE: .enigma2 is a aplib compressed loader DLL. You can decompress it with aplib provided in this repo
|
||||
# Append the exception section and assign the pointers
|
||||
if (exception_data):
|
||||
# Reassign the RVA & sizes
|
||||
logger.debug('Rebuilt Exception directory. Size=0x%x' % len(exception_data))
|
||||
if exception_data:
|
||||
# Reassign the RVA & sizes
|
||||
logger.debug("Rebuilt Exception directory. Size=0x%x" % len(exception_data))
|
||||
# Find where this could be placed at...since EVB clears the original exception directory listings
|
||||
# PEs with overlays won't work at all if EVB packed them.
|
||||
# We must remove the sections and do NOT append anything new
|
||||
try:
|
||||
section, offset = search_pattern_in_sections(b'\x00' * len(exception_data))
|
||||
logger.debug('Found suitable section to place Exception Directory. Name=%s RVA=0x%x' % (section.Name.decode(),offset - section.PointerToRawData))
|
||||
pe.__data__[offset:offset+len(exception_data)] = exception_data
|
||||
section.SizeOfRawData = max(section.SizeOfRawData,len(exception_data))
|
||||
section, offset = search_pattern_in_sections(b"\x00" * len(exception_data))
|
||||
logger.debug(
|
||||
"Found suitable section to place Exception Directory. Name=%s RVA=0x%x"
|
||||
% (section.Name.decode(), offset - section.PointerToRawData)
|
||||
)
|
||||
pe.__data__[offset : offset + len(exception_data)] = exception_data
|
||||
section.SizeOfRawData = max(section.SizeOfRawData, len(exception_data))
|
||||
exception_dir.VirtualAddress = pe.get_rva_from_offset(offset)
|
||||
exception_dir.Size = len(exception_data)
|
||||
except StopIteration as e:
|
||||
logger.warning('Cannot place Exception Directory. It\'s highly likely that the unpacked PE won\'t work.')
|
||||
logger.warning(
|
||||
"Cannot place Exception Directory. It's highly likely that the unpacked PE won't work."
|
||||
)
|
||||
warnings_issued += 1
|
||||
exception_dir.VirtualAddress = 0
|
||||
exception_dir.Size = 0
|
||||
else:
|
||||
logger.debug('Original program does not contain Exception Directory.')
|
||||
logger.debug("Original program does not contain Exception Directory.")
|
||||
exception_dir.VirtualAddress = 0
|
||||
exception_dir.Size = 0
|
||||
# Serach for TLS in memory map since it's copied to the header
|
||||
tls_dir = find_data_directory('TLS')
|
||||
tls_dir = find_data_directory("TLS")
|
||||
try:
|
||||
tls_data = hdr['TLS']
|
||||
tls_data = hdr["TLS"]
|
||||
section, offset = search_pattern_in_sections(tls_data[:12])
|
||||
logger.debug('TLS Directory found. Offset=0x%x Section=%s' % (offset,section.Name.decode()))
|
||||
logger.debug(
|
||||
"TLS Directory found. Offset=0x%x Section=%s"
|
||||
% (offset, section.Name.decode())
|
||||
)
|
||||
tls_dir.VirtualAddress = pe.get_rva_from_offset(offset)
|
||||
tls_dir.Size = 40 if arch_64 else 24
|
||||
except StopIteration as e:
|
||||
logger.debug('TLS Directory not found. Original program probably does not have TLS data.')
|
||||
logger.debug(
|
||||
"TLS Directory not found. Original program probably does not have TLS data."
|
||||
)
|
||||
tls_dir.VirtualAddress = 0
|
||||
tls_dir.Size = 0
|
||||
# Write to new file
|
||||
new_file_data = pe.write()
|
||||
with open(output_file,'wb+') as f:
|
||||
write_bytes(BytesIO(new_file_data),f,len(new_file_data),desc='Saving PE')
|
||||
logger.info('Unpacked PE saved: %s' % output_file)
|
||||
with open(output_file, "wb+") as f:
|
||||
write_bytes(BytesIO(new_file_data), f, len(new_file_data), desc="Saving PE")
|
||||
logger.info("Unpacked PE saved: %s" % output_file)
|
||||
if warnings_issued:
|
||||
logger.warning('There were %d warning(s) issued during the restoration process.' % warnings_issued)
|
||||
logger.warning('Please try using other pe_variant or check the original PE for any issues. Current variant: %s' % pe_variant)
|
||||
logger.warning(
|
||||
"There were %d warning(s) issued during the restoration process."
|
||||
% warnings_issued
|
||||
)
|
||||
logger.warning(
|
||||
"Please try using other pe_variant or check the original PE for any issues. Current variant: %s"
|
||||
% pe_variant
|
||||
)
|
||||
|
||||
def search_for_magic(fd,size,magic):
|
||||
CHUNKSIZE = 16 * 2**20 # 16MB
|
||||
for i in range(0,size,CHUNKSIZE):
|
||||
with mmap(fd.fileno(),offset=i,length=min(CHUNKSIZE,size - i),access=ACCESS_READ) as mm:
|
||||
result = mm.find(magic)
|
||||
if result >= 0:
|
||||
logger.debug('Found magic at %xh' % result)
|
||||
return result
|
||||
|
||||
def search_for_magic(fd, size, magic):
|
||||
with mmap(fd.fileno(), offset=0, length=size, access=ACCESS_READ) as mm:
|
||||
result = mm.find(magic)
|
||||
if result >= 0:
|
||||
logger.debug("Found magic at %xh" % result)
|
||||
return result
|
||||
return -1
|
||||
|
||||
def unpack_files(file : str, out_dir : str, legacy_fs : bool, fs_listing_only : bool):
|
||||
|
||||
def unpack_files(file: str, out_dir: str, legacy_fs: bool, fs_listing_only: bool):
|
||||
size = os.stat(file).st_size
|
||||
with open(file,'rb') as fd:
|
||||
magic = search_for_magic(fd,size,EVB_MAGIC)
|
||||
assert magic >= 0, "EVB filesystem magic not found. Cannot proceed."
|
||||
with open(file, "rb") as fd:
|
||||
magic = search_for_magic(fd, size, EVB_MAGIC)
|
||||
assert (
|
||||
magic >= 0
|
||||
), "EVB filesystem magic not found. It's highly likely that this file is not produced by Enigma Virtual Box."
|
||||
fd.seek(magic)
|
||||
if legacy_fs:
|
||||
nodes = completed(legacy_pe_tree(fd))
|
||||
else:
|
||||
nodes = completed(pe_external_tree(fd))
|
||||
depths = dict()
|
||||
|
||||
def write_line(s):
|
||||
sys.stderr.write(s + '\n')
|
||||
sys.stderr.write(s + "\n")
|
||||
|
||||
def get_prefix(level):
|
||||
prefix = '└───' if depths[level] else '├───'
|
||||
for _ in range(level,0,-1):
|
||||
if _ != 0: prefix = '│ ' if not depths[_ - 1] else ' ' +prefix
|
||||
prefix = "└───" if depths[level] else "├───"
|
||||
for _ in range(level, 0, -1):
|
||||
if _ != 0:
|
||||
prefix = "│ " if not depths[_ - 1] else " " + prefix
|
||||
return prefix
|
||||
def traverse_next_node(node,pfx=out_dir,depth=0):
|
||||
if node['type'] == NODE_TYPE_FOLDER: node['name'] = FOLDER_ALTNAMES.get(node['name'],node['name'])
|
||||
assert ('\\' not in node['name']) and ('/' not in node['name']) and (':' not in node['name']), f'Invalid character in node name: {node["name"]}'
|
||||
assert node['name'] != '..' and node['name'] != '.', 'node name cannot be either . or ..'
|
||||
path = os.path.normpath(os.path.join(pfx,node['name'])).replace('\\','/')
|
||||
write_line(' ' + get_prefix(depth) + ' ' + path)
|
||||
if node['type'] == NODE_TYPE_FILE and not fs_listing_only: process_file_node(fd,path,node)
|
||||
elif node['type'] == NODE_TYPE_FOLDER:
|
||||
|
||||
def traverse_next_node(node, pfx=out_dir, depth=0):
|
||||
if node["type"] == NODE_TYPE_FOLDER:
|
||||
node["name"] = FOLDER_ALTNAMES.get(node["name"], node["name"])
|
||||
assert (
|
||||
("\\" not in node["name"])
|
||||
and ("/" not in node["name"])
|
||||
and (":" not in node["name"])
|
||||
), f'Invalid character in node name: {node["name"]}'
|
||||
assert (
|
||||
node["name"] != ".." and node["name"] != "."
|
||||
), "node name cannot be either . or .."
|
||||
path = os.path.normpath(os.path.join(pfx, node["name"])).replace("\\", "/")
|
||||
write_line(" " + get_prefix(depth) + " " + path)
|
||||
if node["type"] == NODE_TYPE_FILE and not fs_listing_only:
|
||||
process_file_node(fd, path, node)
|
||||
elif node["type"] == NODE_TYPE_FOLDER:
|
||||
if not os.path.isdir(path) and not fs_listing_only:
|
||||
os.makedirs(path)
|
||||
for _ in range(0,node['objects_count']):
|
||||
last = _ == node['objects_count'] - 1
|
||||
for _ in range(0, node["objects_count"]):
|
||||
last = _ == node["objects_count"] - 1
|
||||
depths[depth + 1] = last
|
||||
traverse_next_node(next(nodes),pfx=path,depth=depth + 1)
|
||||
traverse_next_node(next(nodes), pfx=path, depth=depth + 1)
|
||||
|
||||
try:
|
||||
main_node = next(nodes)
|
||||
write_line('Filesystem:')
|
||||
for _ in range(main_node['objects_count']):
|
||||
last = _ == main_node['objects_count'] - 1
|
||||
write_line("Filesystem:")
|
||||
for _ in range(main_node["objects_count"]):
|
||||
last = _ == main_node["objects_count"] - 1
|
||||
depths[0] = last
|
||||
traverse_next_node(next(nodes))
|
||||
except StopIteration:
|
||||
logger.error('The filetable appears to be corrupted. Cannot proceed any further.')
|
||||
logger.error('Please try toggling the --legacy-fs flag to solve this issue.')
|
||||
except StopIteration:
|
||||
logger.error("The filetable appears to be corrupted.")
|
||||
logger.error("Toggling `--legacy-fs` may remedy the issue.")
|
||||
return
|
||||
except AssertionError as e:
|
||||
logger.error('While extracting package %s' % e)
|
||||
return
|
||||
logger.info('Extraction complete')
|
||||
|
||||
def main(in_file : str, out_dir : str = '.', out_pe : str = '', ignore_fs: bool = False, ignore_pe: bool = False, legacy_fs: bool = False, pe_variant: str = '10_70', fs_listing_only: bool = False):
|
||||
logger.info('Enigma Virtual Box Unpacker v%s' % __version__)
|
||||
logger.debug('File: %s' % in_file)
|
||||
os.makedirs(out_dir,exist_ok=True)
|
||||
logger.info("Extraction complete")
|
||||
|
||||
|
||||
def main(
|
||||
in_file: str,
|
||||
out_dir: str = ".",
|
||||
out_pe: str = "",
|
||||
ignore_fs: bool = False,
|
||||
ignore_pe: bool = False,
|
||||
legacy_fs: bool = False,
|
||||
pe_variant: str = "10_70",
|
||||
fs_listing_only: bool = False,
|
||||
):
|
||||
logger.info("Enigma Virtual Box Unpacker v%s" % __version__)
|
||||
logger.debug("File: %s" % in_file)
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
if legacy_fs:
|
||||
logger.warning('Legacy mode for filesystem extraction enabled')
|
||||
logger.warning("Legacy mode for filesystem extraction enabled")
|
||||
if pe_variant:
|
||||
logger.warning('Legacy mode for PE restoration enabled')
|
||||
logger.warning("Legacy mode for PE restoration enabled")
|
||||
if fs_listing_only:
|
||||
logger.warning('Listing virtual filesystem only')
|
||||
logger.warning("Listing virtual filesystem only")
|
||||
if ignore_fs:
|
||||
logger.warning('Skipping virtual FS extraction')
|
||||
logger.warning("Skipping virtual FS extraction")
|
||||
else:
|
||||
logger.info('Extracting virtual filesystem')
|
||||
logger.info("Extracting virtual filesystem")
|
||||
try:
|
||||
unpack_files(in_file,out_dir,legacy_fs,fs_listing_only)
|
||||
unpack_files(in_file, out_dir, legacy_fs, fs_listing_only)
|
||||
except AssertionError as e:
|
||||
logger.error("While extracting VFS: %s" % e)
|
||||
except Exception as e:
|
||||
logger.error('Unhandled exception occured while extracting virtual filesystem: %s' % e)
|
||||
raise e
|
||||
logger.error("Unhandled exception occured while extracting VFS: %s" % e)
|
||||
if ignore_pe:
|
||||
logger.warning('Skipping executable restoration')
|
||||
logger.warning("Skipping executable restoration")
|
||||
else:
|
||||
logger.info('Restoring executable')
|
||||
logger.info("Restoring executable")
|
||||
if not out_pe:
|
||||
out_pe = os.path.join(out_dir, os.path.basename(in_file))
|
||||
logger.info('Using default executable save path: %s' % out_pe)
|
||||
logger.info("Using default executable save path: %s" % out_pe)
|
||||
try:
|
||||
restore_pe(in_file,out_pe,pe_variant)
|
||||
restore_pe(in_file, out_pe, pe_variant)
|
||||
except AssertionError as e:
|
||||
logger.error("While restoring executable: %s" % e)
|
||||
except Exception as e:
|
||||
logger.error('Unhandled exception occured while restoring executable: %s' % e)
|
||||
logger.error(
|
||||
"Unhandled exception occured while restoring executable: %s" % e
|
||||
)
|
||||
|
||||
|
||||
def __main__():
|
||||
parser = ArgumentParser(description='Enigma Virtual Box Unpacker')
|
||||
parser.add_argument('--log-level',help='Set log level',default='INFO',choices=['DEBUG','INFO','WARNING','ERROR','CRITICAL'])
|
||||
group = parser.add_argument_group('Flags')
|
||||
group.add_argument('-l', '--list',help='Don\'t extract the files and print the table of content to stderr only',action='store_true')
|
||||
group.add_argument('--ignore-fs',help='Don\'t extract virtual filesystem',action='store_true')
|
||||
group.add_argument('--ignore-pe',help='Don\'t restore the executable',action='store_true')
|
||||
group.add_argument('--legacy-fs',help='Use legacy mode for filesystem extraction',action='store_true')
|
||||
group.add_argument('-pe','--pe-variant',help='Unpacker variant to use when unpacking EXEs. default=%(default)s', default='9_70', choices=EVB_ENIGMA1_HEADER.get_options())
|
||||
group = parser.add_argument_group('Overrides')
|
||||
group.add_argument('--out-pe', help='(If the executable is to be recovered) Where the unpacked EXE is saved. Leave as-is to save it in the output folder.',default='')
|
||||
group = parser.add_argument_group('Input')
|
||||
group.add_argument('file', help='File to be unpacked')
|
||||
group.add_argument('output', help='Output folder')
|
||||
args = parser.parse_args()
|
||||
logging.basicConfig(level=args.log_level, format='%(levelname)s: %(message)s')
|
||||
sys.exit(main(args.file,args.output,args.out_pe,args.ignore_fs,args.ignore_pe,args.legacy_fs,args.pe_variant,args.list))
|
||||
parser = ArgumentParser(description="Enigma Virtual Box Unpacker")
|
||||
parser.add_argument(
|
||||
"--log-level",
|
||||
help="Set log level",
|
||||
default="INFO",
|
||||
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
||||
)
|
||||
group = parser.add_argument_group("Flags")
|
||||
group.add_argument(
|
||||
"-l",
|
||||
"--list",
|
||||
help="Don't extract the files and print the table of content to stderr only",
|
||||
action="store_true",
|
||||
)
|
||||
group.add_argument(
|
||||
"--ignore-fs", help="Don't extract virtual filesystem", action="store_true"
|
||||
)
|
||||
group.add_argument(
|
||||
"--ignore-pe", help="Don't restore the executable", action="store_true"
|
||||
)
|
||||
group.add_argument(
|
||||
"--legacy-fs",
|
||||
help="Use legacy mode for filesystem extraction",
|
||||
action="store_true",
|
||||
)
|
||||
group.add_argument(
|
||||
"-pe",
|
||||
"--pe-variant",
|
||||
help="Unpacker variant to use when unpacking EXEs. default=%(default)s",
|
||||
default="9_70",
|
||||
choices=EVB_ENIGMA1_HEADER.get_options(),
|
||||
)
|
||||
group = parser.add_argument_group("Overrides")
|
||||
group.add_argument(
|
||||
"--out-pe",
|
||||
help="(If the executable is to be recovered) Where the unpacked EXE is saved. Leave as-is to save it in the output folder.",
|
||||
default="",
|
||||
)
|
||||
group = parser.add_argument_group("Input")
|
||||
group.add_argument("file", help="File to be unpacked")
|
||||
group.add_argument("output", help="Output folder")
|
||||
args = parser.parse_args()
|
||||
logging.basicConfig(level=args.log_level, format="%(levelname)s: %(message)s")
|
||||
sys.exit(
|
||||
main(
|
||||
args.file,
|
||||
args.output,
|
||||
args.out_pe,
|
||||
args.ignore_fs,
|
||||
args.ignore_pe,
|
||||
args.legacy_fs,
|
||||
args.pe_variant,
|
||||
args.list,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
__main__()
|
||||
__main__()
|
||||
|
|
|
|||
|
|
@ -1,172 +0,0 @@
|
|||
#-*- coding: utf-8 --
|
||||
"""A pure Python module for decompressing aPLib compressed data
|
||||
|
||||
Adapted from the original C source code from http://ibsensoftware.com/files/aPLib-1.1.1.zip
|
||||
Approximately 20 times faster than other Python implementations.
|
||||
Compatible with both Python 2 and 3.
|
||||
"""
|
||||
import struct
|
||||
from binascii import crc32
|
||||
from io import BytesIO
|
||||
|
||||
__all__ = ['APLib', 'decompress']
|
||||
__version__ = '0.6'
|
||||
__author__ = 'Sandor Nemes'
|
||||
|
||||
|
||||
class APLib(object):
|
||||
|
||||
__slots__ = 'source', 'destination', 'tag', 'bitcount', 'strict'
|
||||
|
||||
def __init__(self, source, strict=True):
|
||||
self.source = BytesIO(source)
|
||||
self.destination = bytearray()
|
||||
self.tag = 0
|
||||
self.bitcount = 0
|
||||
self.strict = bool(strict)
|
||||
|
||||
def getbit(self):
|
||||
# check if tag is empty
|
||||
self.bitcount -= 1
|
||||
if self.bitcount < 0:
|
||||
# load next tag
|
||||
self.tag = ord(self.source.read(1))
|
||||
self.bitcount = 7
|
||||
|
||||
# shift bit out of tag
|
||||
bit = self.tag >> 7 & 1
|
||||
self.tag <<= 1
|
||||
|
||||
return bit
|
||||
|
||||
def getgamma(self):
|
||||
result = 1
|
||||
|
||||
# input gamma2-encoded bits
|
||||
while True:
|
||||
result = (result << 1) + self.getbit()
|
||||
if not self.getbit():
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
def depack(self):
|
||||
r0 = -1
|
||||
lwm = 0
|
||||
done = False
|
||||
|
||||
try:
|
||||
|
||||
# first byte verbatim
|
||||
self.destination += self.source.read(1)
|
||||
|
||||
# main decompression loop
|
||||
while not done:
|
||||
if self.getbit():
|
||||
if self.getbit():
|
||||
if self.getbit():
|
||||
offs = 0
|
||||
for _ in range(4):
|
||||
offs = (offs << 1) + self.getbit()
|
||||
|
||||
if offs:
|
||||
self.destination.append(self.destination[-offs])
|
||||
else:
|
||||
self.destination.append(0)
|
||||
|
||||
lwm = 0
|
||||
else:
|
||||
offs = ord(self.source.read(1))
|
||||
length = 2 + (offs & 1)
|
||||
offs >>= 1
|
||||
|
||||
if offs:
|
||||
for _ in range(length):
|
||||
self.destination.append(self.destination[-offs])
|
||||
else:
|
||||
done = True
|
||||
|
||||
r0 = offs
|
||||
lwm = 1
|
||||
else:
|
||||
offs = self.getgamma()
|
||||
|
||||
if lwm == 0 and offs == 2:
|
||||
offs = r0
|
||||
length = self.getgamma()
|
||||
|
||||
for _ in range(length):
|
||||
self.destination.append(self.destination[-offs])
|
||||
else:
|
||||
if lwm == 0:
|
||||
offs -= 3
|
||||
else:
|
||||
offs -= 2
|
||||
|
||||
offs <<= 8
|
||||
offs += ord(self.source.read(1))
|
||||
length = self.getgamma()
|
||||
|
||||
if offs >= 32000:
|
||||
length += 1
|
||||
if offs >= 1280:
|
||||
length += 1
|
||||
if offs < 128:
|
||||
length += 2
|
||||
|
||||
for _ in range(length):
|
||||
self.destination.append(self.destination[-offs])
|
||||
|
||||
r0 = offs
|
||||
|
||||
lwm = 1
|
||||
else:
|
||||
self.destination += self.source.read(1)
|
||||
lwm = 0
|
||||
|
||||
except (TypeError, IndexError):
|
||||
if self.strict:
|
||||
raise RuntimeError('aPLib decompression error')
|
||||
|
||||
return bytes(self.destination)
|
||||
|
||||
def pack(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def decompress(data, strict=True):
|
||||
packed_size = None
|
||||
packed_crc = None
|
||||
orig_size = None
|
||||
orig_crc = None
|
||||
|
||||
if data.startswith(b'AP32') and len(data) >= 24:
|
||||
# data has an aPLib header
|
||||
header_size, packed_size, packed_crc, orig_size, orig_crc = struct.unpack_from('=IIIII', data, 4)
|
||||
data = data[header_size : header_size + packed_size]
|
||||
|
||||
if strict:
|
||||
if packed_size is not None and packed_size != len(data):
|
||||
raise RuntimeError('Packed data size is incorrect')
|
||||
if packed_crc is not None and packed_crc != crc32(data):
|
||||
raise RuntimeError('Packed data checksum is incorrect')
|
||||
|
||||
result = APLib(data, strict=strict).depack()
|
||||
|
||||
if strict:
|
||||
if orig_size is not None and orig_size != len(result):
|
||||
raise RuntimeError('Unpacked data size is incorrect')
|
||||
if orig_crc is not None and orig_crc != crc32(result):
|
||||
raise RuntimeError('Unpacked data checksum is incorrect')
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
# self-test
|
||||
data = b'T\x00he quick\xecb\x0erown\xcef\xaex\x80jumps\xed\xe4veur`t?lazy\xead\xfeg\xc0\x00'
|
||||
assert decompress(data) == b'The quick brown fox jumps over the lazy dog'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -1,188 +1,212 @@
|
|||
#-*- coding: utf-8 --
|
||||
EVB_MAGIC = b'EVB\x00'
|
||||
# -*- coding: utf-8 --
|
||||
EVB_MAGIC = b"EVB\x00"
|
||||
|
||||
|
||||
PE_EXCEPTION = [
|
||||
('I','BEGIN_ADDRESS'),
|
||||
('I','END_ADDRESS'),
|
||||
('I','HANDLER_PTR'),
|
||||
('I','HANDLER_DATA'),
|
||||
('I','PROLOG_ADDRESS'),
|
||||
("I", "BEGIN_ADDRESS"),
|
||||
("I", "END_ADDRESS"),
|
||||
("I", "HANDLER_PTR"),
|
||||
("I", "HANDLER_DATA"),
|
||||
("I", "PROLOG_ADDRESS"),
|
||||
]
|
||||
|
||||
PE64_EXCEPTION = [
|
||||
('I','BEGIN_ADDRESS'),
|
||||
('I','END_ADDRESS'),
|
||||
('I','UNWIND_INFO'),
|
||||
("I", "BEGIN_ADDRESS"),
|
||||
("I", "END_ADDRESS"),
|
||||
("I", "UNWIND_INFO"),
|
||||
]
|
||||
|
||||
|
||||
class EVB_ENIGMA1_HEADER:
|
||||
x64 = {
|
||||
'10_70':[
|
||||
('32s', 'TLS'),
|
||||
"10_70": [
|
||||
("32s", "TLS"),
|
||||
# Padding Begin
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''),
|
||||
# Padding End
|
||||
('Q', 'UNK_1'),
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''),
|
||||
('I','IMPORT_ADDRESS'),
|
||||
('I','IMPORT_SIZE'),
|
||||
('I','RELOC_ADDRESS'),
|
||||
('I','RELOC_SIZE'),
|
||||
('I','TLS_ADDRESS'),
|
||||
('I','TLS_SIZE'),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
# Padding End
|
||||
("Q", "UNK_1"),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("I", "IMPORT_ADDRESS"),
|
||||
("I", "IMPORT_SIZE"),
|
||||
("I", "RELOC_ADDRESS"),
|
||||
("I", "RELOC_SIZE"),
|
||||
("I", "TLS_ADDRESS"),
|
||||
("I", "TLS_SIZE"),
|
||||
],
|
||||
'9_70':[
|
||||
('32s', 'TLS'),
|
||||
"9_70": [
|
||||
("32s", "TLS"),
|
||||
# Padding Begin
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''),
|
||||
('I', ''),
|
||||
# Padding End
|
||||
('Q', 'UNK_1'),
|
||||
('Q', ''),
|
||||
('I','IMPORT_ADDRESS'),
|
||||
('I','IMPORT_SIZE'),
|
||||
('I','RELOC_ADDRESS'),
|
||||
('I','RELOC_SIZE'),
|
||||
('I','TLS_ADDRESS'),
|
||||
('I','TLS_SIZE'),
|
||||
],
|
||||
'7_80':[
|
||||
('32s', 'TLS'),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("I", ""),
|
||||
# Padding End
|
||||
("Q", "UNK_1"),
|
||||
("Q", ""),
|
||||
("I", "IMPORT_ADDRESS"),
|
||||
("I", "IMPORT_SIZE"),
|
||||
("I", "RELOC_ADDRESS"),
|
||||
("I", "RELOC_SIZE"),
|
||||
("I", "TLS_ADDRESS"),
|
||||
("I", "TLS_SIZE"),
|
||||
],
|
||||
"7_80": [
|
||||
("32s", "TLS"),
|
||||
# Padding Begin
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''),
|
||||
# Padding End
|
||||
('Q', 'UNK_1'),
|
||||
('Q', ''),
|
||||
('I','IMPORT_ADDRESS'),
|
||||
('I','IMPORT_SIZE'),
|
||||
('I','RELOC_ADDRESS'),
|
||||
('I','RELOC_SIZE'),
|
||||
('I','TLS_ADDRESS'),
|
||||
('I','TLS_SIZE'),
|
||||
]
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
# Padding End
|
||||
("Q", "UNK_1"),
|
||||
("Q", ""),
|
||||
("I", "IMPORT_ADDRESS"),
|
||||
("I", "IMPORT_SIZE"),
|
||||
("I", "RELOC_ADDRESS"),
|
||||
("I", "RELOC_SIZE"),
|
||||
("I", "TLS_ADDRESS"),
|
||||
("I", "TLS_SIZE"),
|
||||
],
|
||||
}
|
||||
|
||||
x86 = {
|
||||
'10_70':[
|
||||
('16s', 'TLS'),
|
||||
"10_70": [
|
||||
("16s", "TLS"),
|
||||
# Padding Begin
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''), ('Q', ''),
|
||||
# Padding End
|
||||
('Q', 'UNK_1'), ('Q', 'UNK_2'),
|
||||
('I', 'UNK_3'),
|
||||
('I','IMPORT_ADDRESS'),
|
||||
('I','IMPORT_SIZE'),
|
||||
('I','RELOC_ADDRESS'),
|
||||
('I','RELOC_SIZE'),
|
||||
('I','TLS_ADDRESS'),
|
||||
('I','TLS_SIZE'),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
# Padding End
|
||||
("Q", "UNK_1"),
|
||||
("Q", "UNK_2"),
|
||||
("I", "UNK_3"),
|
||||
("I", "IMPORT_ADDRESS"),
|
||||
("I", "IMPORT_SIZE"),
|
||||
("I", "RELOC_ADDRESS"),
|
||||
("I", "RELOC_SIZE"),
|
||||
("I", "TLS_ADDRESS"),
|
||||
("I", "TLS_SIZE"),
|
||||
],
|
||||
'9_70' : [
|
||||
('16s', 'TLS'),
|
||||
"9_70": [
|
||||
("16s", "TLS"),
|
||||
# Padding Begin
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''), ('Q', ''),
|
||||
# Padding End
|
||||
('Q', 'UNK_1'), ('Q', 'UNK_2'),
|
||||
('I','IMPORT_ADDRESS'),
|
||||
('I','IMPORT_SIZE'),
|
||||
('I','RELOC_ADDRESS'),
|
||||
('I','RELOC_SIZE'),
|
||||
('I','TLS_ADDRESS'),
|
||||
('I','TLS_SIZE'),
|
||||
],
|
||||
'7_80' : [
|
||||
('16s', 'TLS'),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
# Padding End
|
||||
("Q", "UNK_1"),
|
||||
("Q", "UNK_2"),
|
||||
("I", "IMPORT_ADDRESS"),
|
||||
("I", "IMPORT_SIZE"),
|
||||
("I", "RELOC_ADDRESS"),
|
||||
("I", "RELOC_SIZE"),
|
||||
("I", "TLS_ADDRESS"),
|
||||
("I", "TLS_SIZE"),
|
||||
],
|
||||
"7_80": [
|
||||
("16s", "TLS"),
|
||||
# Padding Begin
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''), ('Q', ''),
|
||||
('Q', ''), ('Q', ''),
|
||||
# Padding End
|
||||
('Q', 'UNK_1'),
|
||||
('I', 'UNK_3'),
|
||||
('I','IMPORT_ADDRESS'),
|
||||
('I','IMPORT_SIZE'),
|
||||
('I','RELOC_ADDRESS'),
|
||||
('I','RELOC_SIZE'),
|
||||
('I','TLS_ADDRESS'),
|
||||
('I','TLS_SIZE'),
|
||||
]
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
("Q", ""),
|
||||
# Padding End
|
||||
("Q", "UNK_1"),
|
||||
("I", "UNK_3"),
|
||||
("I", "IMPORT_ADDRESS"),
|
||||
("I", "IMPORT_SIZE"),
|
||||
("I", "RELOC_ADDRESS"),
|
||||
("I", "RELOC_SIZE"),
|
||||
("I", "TLS_ADDRESS"),
|
||||
("I", "TLS_SIZE"),
|
||||
],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_options():
|
||||
return list(EVB_ENIGMA1_HEADER.x86.keys())
|
||||
|
||||
@staticmethod
|
||||
def get_struct(arch_64 : bool, key : str):
|
||||
def get_struct(arch_64: bool, key: str):
|
||||
if arch_64:
|
||||
return EVB_ENIGMA1_HEADER.x64[key]
|
||||
else:
|
||||
return EVB_ENIGMA1_HEADER.x86[key]
|
||||
|
||||
|
||||
EVB_PACK_HEADER = [
|
||||
('4s', 'signature'), # Would always be ('EVB\x00') if valid
|
||||
('60s',''),
|
||||
("4s", "signature"), # Would always be ('EVB\x00') if valid
|
||||
("60s", ""),
|
||||
]
|
||||
|
||||
EVB_CHUNK_BLOCK = [
|
||||
('I','size'),
|
||||
('I',''),
|
||||
("I", "size"),
|
||||
("I", ""),
|
||||
]
|
||||
|
||||
EVB_HEADER_NODE = [
|
||||
('I','size'),
|
||||
('8s',''),
|
||||
('I','objects_count'),
|
||||
("I", "size"),
|
||||
("8s", ""),
|
||||
("I", "objects_count"),
|
||||
]
|
||||
|
||||
EVB_NODE_MAIN = [
|
||||
('I', 'size'),
|
||||
('8s', ''),
|
||||
('I', 'objects_count'),
|
||||
("I", "size"),
|
||||
("8s", ""),
|
||||
("I", "objects_count"),
|
||||
]
|
||||
|
||||
EVB_NODE_NAMED = [
|
||||
('%ds', 'name'), # args[0] - filename buffer length
|
||||
('B', 'type'),
|
||||
EVB_NODE_NAMED = [
|
||||
("%ds", "name"), # args[0] - filename buffer length
|
||||
("B", "type"),
|
||||
]
|
||||
|
||||
EVB_NODE_OPTIONAL_FILE = [
|
||||
('2s', ''),
|
||||
('I', 'original_size'),
|
||||
('4s',''),
|
||||
('8s','filetime1'),
|
||||
('8s','filetime2'),
|
||||
('8s','filetime3'),
|
||||
('15s',''),
|
||||
('I', 'stored_size'),
|
||||
("2s", ""),
|
||||
("I", "original_size"),
|
||||
("4s", ""),
|
||||
("8s", "filetime1"),
|
||||
("8s", "filetime2"),
|
||||
("8s", "filetime3"),
|
||||
("15s", ""),
|
||||
("I", "stored_size"),
|
||||
]
|
||||
|
||||
EVB_NODE_OPTIONAL_PE_FILE = [
|
||||
('2s', ''),
|
||||
('I', 'original_size'),
|
||||
('4s',''),
|
||||
('8s','filetime1'),
|
||||
('8s','filetime2'),
|
||||
('8s','filetime3'),
|
||||
('7s',''),
|
||||
('I', 'stored_size'),
|
||||
('4s',''),
|
||||
("2s", ""),
|
||||
("I", "original_size"),
|
||||
("4s", ""),
|
||||
("8s", "filetime1"),
|
||||
("8s", "filetime2"),
|
||||
("8s", "filetime3"),
|
||||
("7s", ""),
|
||||
("I", "stored_size"),
|
||||
("4s", ""),
|
||||
]
|
||||
|
||||
NODE_TYPE_MAIN = 0
|
||||
NODE_TYPE_FILE = 2
|
||||
NODE_TYPE_FOLDER = 3
|
||||
NODE_TYPE_MAIN = 0
|
||||
NODE_TYPE_FILE = 2
|
||||
NODE_TYPE_FOLDER = 3
|
||||
|
|
|
|||
12
setup.py
12
setup.py
|
|
@ -1,6 +1,6 @@
|
|||
import setuptools,evbunpack
|
||||
import setuptools, evbunpack
|
||||
|
||||
with open("README.md", "r",encoding='utf-8') as fh:
|
||||
with open("README.md", "r", encoding="utf-8") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
setuptools.setup(
|
||||
|
|
@ -18,9 +18,7 @@ setuptools.setup(
|
|||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
install_requires=[
|
||||
"pefile"
|
||||
],
|
||||
install_requires=["pefile", "aplib"],
|
||||
entry_points={"console_scripts": ["evbunpack=evbunpack.__main__:__main__"]},
|
||||
python_requires='>=3.0',
|
||||
)
|
||||
python_requires=">=3.0",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,36 +1,106 @@
|
|||
import os, logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
os.chdir(os.path.join(os.path.dirname(__file__),'tests'))
|
||||
|
||||
TEMP_OUTPUT_FILENAME = '_unpacked.exe'
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
os.chdir(os.path.join(os.path.dirname(__file__), "tests"))
|
||||
|
||||
TEMP_OUTPUT_FILENAME = "_unpacked.exe"
|
||||
|
||||
|
||||
def unpack_exec(pe_file, **kw):
|
||||
from evbunpack.__main__ import main
|
||||
main(pe_file, 'output', os.path.join('.',TEMP_OUTPUT_FILENAME), **kw)
|
||||
return_code = os.system(TEMP_OUTPUT_FILENAME)
|
||||
|
||||
main(pe_file, "output", os.path.join(".", TEMP_OUTPUT_FILENAME), **kw)
|
||||
return_code = os.system(TEMP_OUTPUT_FILENAME)
|
||||
if return_code != 0:
|
||||
logging.error('Failed to execute unpacked file. Exit code: %d', return_code)
|
||||
logging.error("Failed to execute unpacked file. Exit code: %d", return_code)
|
||||
return return_code
|
||||
|
||||
|
||||
def test_unpack11_00_x64():
|
||||
assert (
|
||||
unpack_exec(
|
||||
"x64_PackerTestApp_packed_20240826.exe", legacy_fs=False, pe_variant="10_70"
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_unpack10_80_x64():
|
||||
assert unpack_exec('x64_PackerTestApp_packed_20240613.exe', legacy_fs = False, pe_variant = '10_70') == 0
|
||||
assert (
|
||||
unpack_exec(
|
||||
"x64_PackerTestApp_packed_20240613.exe", legacy_fs=False, pe_variant="10_70"
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_unpack10_70_x64():
|
||||
assert unpack_exec('x64_PackerTestApp_packed_20240522.exe', legacy_fs = False, pe_variant = '10_70') == 0
|
||||
assert (
|
||||
unpack_exec(
|
||||
"x64_PackerTestApp_packed_20240522.exe", legacy_fs=False, pe_variant="10_70"
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_unpack9_70_x64():
|
||||
assert unpack_exec('x64_PackerTestApp_packed_20210329.exe', legacy_fs = False, pe_variant = '9_70') == 0
|
||||
assert (
|
||||
unpack_exec(
|
||||
"x64_PackerTestApp_packed_20210329.exe", legacy_fs=False, pe_variant="9_70"
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_unpack7_80_x64():
|
||||
assert unpack_exec('x64_PackerTestApp_packed_20170713.exe', legacy_fs = True, pe_variant = '7_80') == 0
|
||||
assert (
|
||||
unpack_exec(
|
||||
"x64_PackerTestApp_packed_20170713.exe", legacy_fs=True, pe_variant="7_80"
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_unpack11_00_x86():
|
||||
assert (
|
||||
unpack_exec(
|
||||
"x86_PackerTestApp_packed_20240826.exe", legacy_fs=False, pe_variant="10_70"
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_unpack10_80_x86():
|
||||
assert unpack_exec('x86_PackerTestApp_packed_20240613.exe', legacy_fs = False, pe_variant = '10_70') == 0
|
||||
assert (
|
||||
unpack_exec(
|
||||
"x86_PackerTestApp_packed_20240613.exe", legacy_fs=False, pe_variant="10_70"
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_unpack10_70_x86():
|
||||
assert unpack_exec('x86_PackerTestApp_packed_20240522.exe', legacy_fs = False, pe_variant = '10_70') == 0
|
||||
assert (
|
||||
unpack_exec(
|
||||
"x86_PackerTestApp_packed_20240522.exe", legacy_fs=False, pe_variant="10_70"
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_unpack9_70_x86():
|
||||
assert unpack_exec('x86_PackerTestApp_packed_20210329.exe', legacy_fs = False, pe_variant = '9_70') == 0
|
||||
assert (
|
||||
unpack_exec(
|
||||
"x86_PackerTestApp_packed_20210329.exe", legacy_fs=False, pe_variant="9_70"
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_unpack7_80_x86():
|
||||
assert unpack_exec('x86_PackerTestApp_packed_20170713.exe', legacy_fs = True, pe_variant = '7_80') == 0
|
||||
assert (
|
||||
unpack_exec(
|
||||
"x86_PackerTestApp_packed_20170713.exe", legacy_fs=True, pe_variant="7_80"
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
|
|
|||
97
tests/PackerProject.evb
Normal file
97
tests/PackerProject.evb
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="windows-1252"?>
|
||||
<>
|
||||
<InputFile>C:\Users\mos9527\evbunpack\tests\x64_PackerTestApp.exe</InputFile>
|
||||
<OutputFile>C:\Users\mos9527\evbunpack\tests\x64_PackerTestApp_packed_20240826.exe</OutputFile>
|
||||
<Files>
|
||||
<Enabled>True</Enabled>
|
||||
<DeleteExtractedOnExit>False</DeleteExtractedOnExit>
|
||||
<CompressFiles>False</CompressFiles>
|
||||
<Files>
|
||||
<File>
|
||||
<Type>3</Type>
|
||||
<Name>%DEFAULT FOLDER%</Name>
|
||||
<Action>0</Action>
|
||||
<OverwriteDateTime>False</OverwriteDateTime>
|
||||
<OverwriteAttributes>False</OverwriteAttributes>
|
||||
<HideFromDialogs>0</HideFromDialogs>
|
||||
<Files>
|
||||
<File>
|
||||
<Type>2</Type>
|
||||
<Name>README.txt</Name>
|
||||
<File>C:\Users\mos9527\evbunpack\tests\README_packed.txt</File>
|
||||
<ActiveX>False</ActiveX>
|
||||
<ActiveXInstall>False</ActiveXInstall>
|
||||
<Action>0</Action>
|
||||
<OverwriteDateTime>False</OverwriteDateTime>
|
||||
<OverwriteAttributes>False</OverwriteAttributes>
|
||||
<PassCommandLine>False</PassCommandLine>
|
||||
<HideFromDialogs>0</HideFromDialogs>
|
||||
</File>
|
||||
</Files>
|
||||
</File>
|
||||
</Files>
|
||||
</Files>
|
||||
<Registries>
|
||||
<Enabled>False</Enabled>
|
||||
<Registries>
|
||||
<Registry>
|
||||
<Type>1</Type>
|
||||
<Virtual>True</Virtual>
|
||||
<Name>Classes</Name>
|
||||
<ValueType>0</ValueType>
|
||||
<Value/>
|
||||
<Registries/>
|
||||
</Registry>
|
||||
<Registry>
|
||||
<Type>1</Type>
|
||||
<Virtual>True</Virtual>
|
||||
<Name>User</Name>
|
||||
<ValueType>0</ValueType>
|
||||
<Value/>
|
||||
<Registries/>
|
||||
</Registry>
|
||||
<Registry>
|
||||
<Type>1</Type>
|
||||
<Virtual>True</Virtual>
|
||||
<Name>Machine</Name>
|
||||
<ValueType>0</ValueType>
|
||||
<Value/>
|
||||
<Registries/>
|
||||
</Registry>
|
||||
<Registry>
|
||||
<Type>1</Type>
|
||||
<Virtual>True</Virtual>
|
||||
<Name>Users</Name>
|
||||
<ValueType>0</ValueType>
|
||||
<Value/>
|
||||
<Registries/>
|
||||
</Registry>
|
||||
<Registry>
|
||||
<Type>1</Type>
|
||||
<Virtual>True</Virtual>
|
||||
<Name>Config</Name>
|
||||
<ValueType>0</ValueType>
|
||||
<Value/>
|
||||
<Registries/>
|
||||
</Registry>
|
||||
</Registries>
|
||||
</Registries>
|
||||
<Packaging>
|
||||
<Enabled>False</Enabled>
|
||||
</Packaging>
|
||||
<Options>
|
||||
<ShareVirtualSystem>False</ShareVirtualSystem>
|
||||
<MapExecutableWithTemporaryFile>True</MapExecutableWithTemporaryFile>
|
||||
<TemporaryFileMask/>
|
||||
<AllowRunningOfVirtualExeFiles>True</AllowRunningOfVirtualExeFiles>
|
||||
<ProcessesOfAnyPlatforms>False</ProcessesOfAnyPlatforms>
|
||||
</Options>
|
||||
<Storage>
|
||||
<Files>
|
||||
<Enabled>False</Enabled>
|
||||
<Folder>%DEFAULT FOLDER%\</Folder>
|
||||
<RandomFileNames>False</RandomFileNames>
|
||||
<EncryptContent>False</EncryptContent>
|
||||
</Files>
|
||||
</Storage>
|
||||
</>
|
||||
BIN
tests/x64_PackerTestApp_packed_20240826.exe
Normal file
BIN
tests/x64_PackerTestApp_packed_20240826.exe
Normal file
Binary file not shown.
BIN
tests/x86_PackerTestApp_packed_20240826.exe
Normal file
BIN
tests/x86_PackerTestApp_packed_20240826.exe
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue