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
|
# 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
|
## Features
|
||||||
- Executable unpacking
|
- Executable unpacking
|
||||||
|
|
@ -15,6 +17,7 @@
|
||||||
|
|
||||||
| Packer Version | Notes | Unpack with Flags |
|
| 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` |
|
| 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` |
|
| 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 ` |
|
| 7.80 | Automatically tested in CI for x86/x64 binaries | `-pe 7_80 --legacy-fs ` |
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
#-*- coding: utf-8 --
|
# -*- coding: utf-8 --
|
||||||
__version__ = '0.2.2'
|
__version__ = "0.2.6"
|
||||||
__author__ = 'mos9527'
|
__author__ = "mos9527"
|
||||||
|
|
|
||||||
|
|
@ -1,386 +1,563 @@
|
||||||
#-*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copy
|
# Copy
|
||||||
from itertools import dropwhile
|
from itertools import dropwhile
|
||||||
import struct,os,array,sys, logging
|
import struct, os, array, sys, logging
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from mmap import mmap,ACCESS_READ
|
from mmap import mmap, ACCESS_READ
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from evbunpack.aplib import decompress
|
from aplib import decompress
|
||||||
from evbunpack.const import *
|
from evbunpack.const import *
|
||||||
from evbunpack import __version__
|
from evbunpack import __version__
|
||||||
logger = logging.getLogger('evbunpack')
|
|
||||||
|
|
||||||
FOLDER_ALTNAMES = {
|
logger = logging.getLogger("evbunpack")
|
||||||
'%DEFAULT FOLDER%' : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
def write_bytes(fd,out_fd,size,chunk_sizes=None,chunk_process=None,default_chunksize=65536,desc='Extracting...'):
|
FOLDER_ALTNAMES = {"%DEFAULT FOLDER%": ""}
|
||||||
bytes_read = 0
|
|
||||||
|
|
||||||
|
def write_bytes(
|
||||||
|
fd,
|
||||||
|
out_fd,
|
||||||
|
size,
|
||||||
|
chunk_sizes=None,
|
||||||
|
chunk_process=None,
|
||||||
|
default_chunksize=65536,
|
||||||
|
desc="Extracting...",
|
||||||
|
):
|
||||||
|
bytes_read = 0
|
||||||
bytes_wrote = 0
|
bytes_wrote = 0
|
||||||
inital_offset = fd.tell()
|
inital_offset = fd.tell()
|
||||||
|
stat_mxlen = 0
|
||||||
while bytes_read < size:
|
while bytes_read < size:
|
||||||
sys.stderr.write('%s: total=%8xh read=%8xh\r' % (desc,size,bytes_read))
|
stat = "%s: %.2f%% total=%8xh read=%8xh \r" % (
|
||||||
chunk_size = next(chunk_sizes) if chunk_sizes else default_chunksize
|
desc,
|
||||||
size_to_read = min(chunk_size,size - (fd.tell() - inital_offset))
|
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)
|
chunk = fd.read(size_to_read)
|
||||||
bytes_read += len(chunk)
|
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)
|
bytes_wrote += out_fd.write(chunk)
|
||||||
sys.stderr.write('\n')
|
sys.stderr.write(" " * stat_mxlen + "\r")
|
||||||
return bytes_wrote
|
return bytes_wrote
|
||||||
|
|
||||||
|
|
||||||
def get_size_by_struct(struct_):
|
def get_size_by_struct(struct_):
|
||||||
fmt , desc = make_format_by_struct(struct_)
|
fmt, desc = make_format_by_struct(struct_)
|
||||||
return struct.calcsize(fmt)
|
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_))
|
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):
|
def make_format_by_struct(struct, *args):
|
||||||
fmt,desc = make_format_by_struct(structure)
|
fmt, desc = zip(*filter(lambda p: isinstance(p, tuple), struct))
|
||||||
return struct.pack(fmt,*args)
|
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):
|
def unpack(structure, buffer, *args, **extra):
|
||||||
'''Unpack buffer by structure given'''
|
"""Unpack buffer by structure given"""
|
||||||
fmt,desc = make_format_by_struct(structure,*args)
|
fmt, desc = make_format_by_struct(structure, *args)
|
||||||
unpacked = struct.unpack_from(fmt, buffer, 0)
|
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)
|
p = src.read(2)
|
||||||
while (p[0]!=0 or p[1]!=0):
|
while p[0] != 0 or p[1] != 0:
|
||||||
blkFilename.extend(p)
|
blkFilename.extend(p)
|
||||||
p = src.read(2)
|
p = src.read(2)
|
||||||
block = blkFilename + src.read(1)
|
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):
|
def read_header_node(src):
|
||||||
return unpack(EVB_NODE_OPTIONAL_PE_FILE, read_bytes_by_struct(src,EVB_NODE_OPTIONAL_PE_FILE))
|
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):
|
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):
|
def read_pack_header(src):
|
||||||
return unpack(EVB_NODE_MAIN, read_bytes_by_struct(src,EVB_NODE_MAIN))
|
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):
|
def pe_external_tree(fd):
|
||||||
# Before calling, make sure cursor is already at where
|
# Before calling, make sure cursor is already at where
|
||||||
# the following bytes are b`EVB\x00`
|
# 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)
|
hdr = read_pack_header(fd)
|
||||||
assert hdr['signature'] == EVB_MAGIC, "Invalid signature"
|
assert hdr["signature"] == EVB_MAGIC, "Invalid signature"
|
||||||
main_node = read_main_node(fd)
|
main_node = read_main_node(fd)
|
||||||
abs_offset = fd.tell() + main_node['size'] - 12 # offset from the head of the stream
|
abs_offset = (
|
||||||
fd.seek(-1,1)
|
fd.tell() + main_node["size"] - 12
|
||||||
|
) # offset from the head of the stream
|
||||||
|
fd.seek(-1, 1)
|
||||||
yield main_node
|
yield main_node
|
||||||
max_object_count = 0
|
max_object_count = 0
|
||||||
current_object_count = 0
|
current_object_count = 0
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
header_node = read_header_node(fd)
|
header_node = read_header_node(fd)
|
||||||
named_node = read_named_node(fd)
|
named_node = read_named_node(fd)
|
||||||
except struct.error:
|
except struct.error:
|
||||||
return # Potential EOF exception
|
return # Potential EOF exception
|
||||||
if named_node['type'] == NODE_TYPE_FILE:
|
if named_node["type"] == NODE_TYPE_FILE:
|
||||||
optional_node = read_optional_file_node(fd)
|
optional_node = read_optional_file_node(fd)
|
||||||
optional_node['offset'] = abs_offset
|
optional_node["offset"] = abs_offset
|
||||||
abs_offset += optional_node['stored_size']
|
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']
|
|
||||||
current_object_count += 1
|
current_object_count += 1
|
||||||
else:
|
elif named_node["type"] == NODE_TYPE_FOLDER:
|
||||||
return # assuming finished
|
optional_node = {}
|
||||||
named_node['name'] = named_node['name'].decode('utf-16-le')
|
fd.seek(25, 1)
|
||||||
yield {**header_node,**named_node,**optional_node}
|
max_object_count += header_node["objects_count"]
|
||||||
if current_object_count > max_object_count and max_object_count > 0:
|
current_object_count += 1
|
||||||
return
|
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):
|
def legacy_pe_tree(fd):
|
||||||
# Older executables has their file table and content placed together
|
# Older executables has their file table and content placed together
|
||||||
# Courtesy of evb-extractor!
|
# Courtesy of evb-extractor!
|
||||||
hdr = read_pack_header(fd)
|
hdr = read_pack_header(fd)
|
||||||
assert hdr['signature'] == EVB_MAGIC, "Invalid signature"
|
assert hdr["signature"] == EVB_MAGIC, "Invalid signature"
|
||||||
seek_origin = 0
|
seek_origin = 0
|
||||||
max_object_count = 0
|
max_object_count = 0
|
||||||
current_object_count = 0
|
current_object_count = 0
|
||||||
while True:
|
while True:
|
||||||
seek_origin = fd.tell()
|
seek_origin = fd.tell()
|
||||||
try:
|
try:
|
||||||
header_node = read_header_node(fd)
|
header_node = read_header_node(fd)
|
||||||
named_node = read_named_node(fd)
|
named_node = read_named_node(fd)
|
||||||
except struct.error:
|
except struct.error:
|
||||||
return # Potential EOF exception
|
return # Potential EOF exception
|
||||||
if named_node['type'] == NODE_TYPE_FILE:
|
if named_node["type"] == NODE_TYPE_FILE:
|
||||||
fd.seek(seek_origin + header_node['size'] + 4 - get_size_by_struct(EVB_NODE_OPTIONAL_PE_FILE))
|
fd.seek(
|
||||||
optional_node = read_optional_legacy_pe_file_node(fd)
|
seek_origin
|
||||||
optional_node['offset'] = fd.tell()
|
+ header_node["size"]
|
||||||
fd.seek(optional_node['stored_size'],1)
|
+ 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
|
current_object_count += 1
|
||||||
elif named_node['type'] == NODE_TYPE_FOLDER:
|
elif named_node["type"] == NODE_TYPE_FOLDER:
|
||||||
optional_node = {}
|
optional_node = {}
|
||||||
fd.seek(seek_origin + header_node['size'] + 4)
|
fd.seek(seek_origin + header_node["size"] + 4)
|
||||||
max_object_count += header_node['objects_count']
|
max_object_count += header_node["objects_count"]
|
||||||
current_object_count += 1
|
current_object_count += 1
|
||||||
elif named_node['type'] == NODE_TYPE_MAIN:
|
elif named_node["type"] == NODE_TYPE_MAIN:
|
||||||
optional_node = {}
|
optional_node = {}
|
||||||
fd.seek(seek_origin + header_node['size'] + 4)
|
fd.seek(seek_origin + header_node["size"] + 4)
|
||||||
else:
|
else:
|
||||||
return # assuming finished
|
return # assuming finished
|
||||||
named_node['name'] = named_node['name'].decode('utf-16-le')
|
named_node["name"] = named_node["name"].decode("utf-16-le")
|
||||||
yield {**header_node,**named_node,**optional_node}
|
yield {**header_node, **named_node, **optional_node}
|
||||||
if current_object_count > max_object_count and max_object_count > 0:
|
if current_object_count > max_object_count and max_object_count > 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def completed(generator):
|
def completed(generator):
|
||||||
# Complete building the tree before we'd read the file
|
# Complete building the tree before we'd read the file
|
||||||
for item in list(generator):
|
for item in list(generator):
|
||||||
yield item
|
yield item
|
||||||
|
|
||||||
def process_file_node(fd,path,node):
|
|
||||||
with open(path,'wb') as output:
|
def process_file_node(fd, path, node):
|
||||||
rsize = node['original_size']
|
with open(path, "wb") as output:
|
||||||
ssize = node['stored_size']
|
rsize = node["original_size"]
|
||||||
offset = node['offset']
|
ssize = node["stored_size"]
|
||||||
|
offset = node["offset"]
|
||||||
fd.seek(offset)
|
fd.seek(offset)
|
||||||
if rsize != ssize: # Compression detected
|
if rsize != ssize: # Compression detected
|
||||||
chunks_blk = read_chunk_block(fd)
|
chunks_blk = read_chunk_block(fd)
|
||||||
blkChunkData = fd.read(chunks_blk['size'] - get_size_by_struct(EVB_CHUNK_BLOCK))
|
blkChunkData = fd.read(
|
||||||
arrChunkData = (val for idx,val in enumerate(array.array('I',blkChunkData)) if idx % 3 == 0)
|
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)
|
# 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...
|
# 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
|
# Thus filtering only every 3rd elements works. Which should always give us Chunk size
|
||||||
# Even if the last 8 bytes is missing
|
# Even if the last 8 bytes is missing
|
||||||
wsize = write_bytes(
|
wsize = write_bytes(
|
||||||
fd,output,
|
fd,
|
||||||
size=ssize - chunks_blk['size'],
|
output,
|
||||||
|
size=ssize - chunks_blk["size"],
|
||||||
chunk_sizes=arrChunkData,
|
chunk_sizes=arrChunkData,
|
||||||
chunk_process=decompress,
|
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"
|
assert wsize == rsize, "Incorrect size"
|
||||||
else:
|
else:
|
||||||
write_bytes(
|
write_bytes(
|
||||||
fd,output,
|
fd,
|
||||||
|
output,
|
||||||
size=ssize,
|
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
|
warnings_issued = 0
|
||||||
from pefile import PE,OPTIONAL_HEADER_MAGIC_PE_PLUS
|
from pefile import PE, OPTIONAL_HEADER_MAGIC_PE_PLUS
|
||||||
logger.debug('Loading PE...')
|
|
||||||
pe = PE(input_file,fast_load=True)
|
logger.debug("Loading PE...")
|
||||||
|
pe = PE(input_file, fast_load=True)
|
||||||
arch_64 = pe.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE_PLUS
|
arch_64 = pe.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE_PLUS
|
||||||
logger.debug('PE loaded. Arch=%s' % ('x64' if arch_64 else 'x86'))
|
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
|
pe.__data__ = bytearray(
|
||||||
# Helpers
|
pe.__data__
|
||||||
find_section = lambda name:next(filter(lambda x:name in x.Name,pe.sections))
|
) # This allows us to apply slicing on the PE data
|
||||||
find_data_directory = lambda name:next(filter(lambda x:name in x.name,pe.OPTIONAL_HEADER.DATA_DIRECTORY))
|
# Helpers
|
||||||
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)))
|
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
|
# Data
|
||||||
logger.info('Unpacking with variant: %s' % pe_variant)
|
logger.info("Unpacking with variant: %s" % pe_variant)
|
||||||
enigma1 = pe.__data__[find_section(b'.enigma1').PointerToRawData:]
|
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)
|
hdr = unpack(EVB_ENIGMA1_HEADER.get_struct(arch_64, pe_variant), enigma1)
|
||||||
# Restore section with built-in offsets. All these ADDRESSes are VAs
|
# Restore section with built-in offsets. All these ADDRESSes are VAs
|
||||||
find_data_directory('IMPORT').VirtualAddress = hdr['IMPORT_ADDRESS']
|
find_data_directory("IMPORT").VirtualAddress = hdr["IMPORT_ADDRESS"]
|
||||||
find_data_directory('IMPORT').Size = hdr['IMPORT_SIZE']
|
find_data_directory("IMPORT").Size = hdr["IMPORT_SIZE"]
|
||||||
find_data_directory('RELOC').VirtualAddress = hdr['RELOC_ADDRESS']
|
find_data_directory("RELOC").VirtualAddress = hdr["RELOC_ADDRESS"]
|
||||||
find_data_directory('RELOC').Size = hdr['RELOC_SIZE']
|
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(
|
||||||
logger.debug('Reloc -> VA=0x%x Size=0x%x' % (hdr['RELOC_ADDRESS'],hdr['RELOC_SIZE']))
|
"Import -> VA=0x%x Size=0x%x" % (hdr["IMPORT_ADDRESS"], hdr["IMPORT_SIZE"])
|
||||||
if hdr['RELOC_SIZE'] == 0 or hdr['IMPORT_SIZE'] == 0:
|
)
|
||||||
|
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
|
warnings_issued += 1
|
||||||
logger.warning('Import/Reloc table size is zero. This may indicate that the header is incorrectly parsed.')
|
logger.warning(
|
||||||
logger.debug('Rebuilding Exception directory...')
|
"Import/Reloc table size is zero. This may indicate that the header is incorrectly parsed."
|
||||||
|
)
|
||||||
|
logger.debug("Rebuilding Exception directory...")
|
||||||
# Rebuild the 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_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_struct = PE64_EXCEPTION if arch_64 else PE_EXCEPTION
|
||||||
exception_end = 0
|
exception_end = 0
|
||||||
for i in range(0,exception_dir.Size,get_size_by_struct(exception_struct)):
|
for i in range(0, exception_dir.Size, get_size_by_struct(exception_struct)):
|
||||||
block = unpack(exception_struct,exception_data[i:])
|
block = unpack(exception_struct, exception_data[i:])
|
||||||
block['section'] = pe.get_section_by_rva(block['BEGIN_ADDRESS'])
|
block["section"] = pe.get_section_by_rva(block["BEGIN_ADDRESS"])
|
||||||
exception_end = i
|
exception_end = i
|
||||||
if b'.enigma' in block['section'].Name:
|
if b".enigma" in block["section"].Name:
|
||||||
break
|
break
|
||||||
exception_data = exception_data[:exception_end]
|
exception_data = exception_data[:exception_end]
|
||||||
# Destory .enigma* sections
|
# 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.
|
# 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".enigma2"
|
||||||
assert pe.sections.pop().Name == b'.enigma1'
|
assert pe.sections.pop().Name == b".enigma1"
|
||||||
pe.FILE_HEADER.NumberOfSections -= 2
|
pe.FILE_HEADER.NumberOfSections -= 2
|
||||||
# NOTE: .enigma1 contains the VFS, as well as some Optional PE Header info as descrbied above
|
# 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
|
# Append the exception section and assign the pointers
|
||||||
if (exception_data):
|
if exception_data:
|
||||||
# Reassign the RVA & sizes
|
# Reassign the RVA & sizes
|
||||||
logger.debug('Rebuilt Exception directory. Size=0x%x' % len(exception_data))
|
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
|
# 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.
|
# PEs with overlays won't work at all if EVB packed them.
|
||||||
# We must remove the sections and do NOT append anything new
|
# We must remove the sections and do NOT append anything new
|
||||||
try:
|
try:
|
||||||
section, offset = search_pattern_in_sections(b'\x00' * 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))
|
logger.debug(
|
||||||
pe.__data__[offset:offset+len(exception_data)] = exception_data
|
"Found suitable section to place Exception Directory. Name=%s RVA=0x%x"
|
||||||
section.SizeOfRawData = max(section.SizeOfRawData,len(exception_data))
|
% (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.VirtualAddress = pe.get_rva_from_offset(offset)
|
||||||
exception_dir.Size = len(exception_data)
|
exception_dir.Size = len(exception_data)
|
||||||
except StopIteration as e:
|
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
|
warnings_issued += 1
|
||||||
exception_dir.VirtualAddress = 0
|
exception_dir.VirtualAddress = 0
|
||||||
exception_dir.Size = 0
|
exception_dir.Size = 0
|
||||||
else:
|
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.VirtualAddress = 0
|
||||||
exception_dir.Size = 0
|
exception_dir.Size = 0
|
||||||
# Serach for TLS in memory map since it's copied to the header
|
# 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:
|
try:
|
||||||
tls_data = hdr['TLS']
|
tls_data = hdr["TLS"]
|
||||||
section, offset = search_pattern_in_sections(tls_data[:12])
|
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.VirtualAddress = pe.get_rva_from_offset(offset)
|
||||||
tls_dir.Size = 40 if arch_64 else 24
|
tls_dir.Size = 40 if arch_64 else 24
|
||||||
except StopIteration as e:
|
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.VirtualAddress = 0
|
||||||
tls_dir.Size = 0
|
tls_dir.Size = 0
|
||||||
# Write to new file
|
# Write to new file
|
||||||
new_file_data = pe.write()
|
new_file_data = pe.write()
|
||||||
with open(output_file,'wb+') as f:
|
with open(output_file, "wb+") as f:
|
||||||
write_bytes(BytesIO(new_file_data),f,len(new_file_data),desc='Saving PE')
|
write_bytes(BytesIO(new_file_data), f, len(new_file_data), desc="Saving PE")
|
||||||
logger.info('Unpacked PE saved: %s' % output_file)
|
logger.info("Unpacked PE saved: %s" % output_file)
|
||||||
if warnings_issued:
|
if warnings_issued:
|
||||||
logger.warning('There were %d warning(s) issued during the restoration process.' % warnings_issued)
|
logger.warning(
|
||||||
logger.warning('Please try using other pe_variant or check the original PE for any issues. Current variant: %s' % pe_variant)
|
"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
|
def search_for_magic(fd, size, magic):
|
||||||
for i in range(0,size,CHUNKSIZE):
|
with mmap(fd.fileno(), offset=0, length=size, access=ACCESS_READ) as mm:
|
||||||
with mmap(fd.fileno(),offset=i,length=min(CHUNKSIZE,size - i),access=ACCESS_READ) as mm:
|
result = mm.find(magic)
|
||||||
result = mm.find(magic)
|
if result >= 0:
|
||||||
if result >= 0:
|
logger.debug("Found magic at %xh" % result)
|
||||||
logger.debug('Found magic at %xh' % result)
|
return result
|
||||||
return result
|
|
||||||
return -1
|
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
|
size = os.stat(file).st_size
|
||||||
with open(file,'rb') as fd:
|
with open(file, "rb") as fd:
|
||||||
magic = search_for_magic(fd,size,EVB_MAGIC)
|
magic = search_for_magic(fd, size, EVB_MAGIC)
|
||||||
assert magic >= 0, "EVB filesystem magic not found. Cannot proceed."
|
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)
|
fd.seek(magic)
|
||||||
if legacy_fs:
|
if legacy_fs:
|
||||||
nodes = completed(legacy_pe_tree(fd))
|
nodes = completed(legacy_pe_tree(fd))
|
||||||
else:
|
else:
|
||||||
nodes = completed(pe_external_tree(fd))
|
nodes = completed(pe_external_tree(fd))
|
||||||
depths = dict()
|
depths = dict()
|
||||||
|
|
||||||
def write_line(s):
|
def write_line(s):
|
||||||
sys.stderr.write(s + '\n')
|
sys.stderr.write(s + "\n")
|
||||||
|
|
||||||
def get_prefix(level):
|
def get_prefix(level):
|
||||||
prefix = '└───' if depths[level] else '├───'
|
prefix = "└───" if depths[level] else "├───"
|
||||||
for _ in range(level,0,-1):
|
for _ in range(level, 0, -1):
|
||||||
if _ != 0: prefix = '│ ' if not depths[_ - 1] else ' ' +prefix
|
if _ != 0:
|
||||||
|
prefix = "│ " if not depths[_ - 1] else " " + prefix
|
||||||
return 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'])
|
def traverse_next_node(node, pfx=out_dir, depth=0):
|
||||||
assert ('\\' not in node['name']) and ('/' not in node['name']) and (':' not in node['name']), f'Invalid character in node name: {node["name"]}'
|
if node["type"] == NODE_TYPE_FOLDER:
|
||||||
assert node['name'] != '..' and node['name'] != '.', 'node name cannot be either . or ..'
|
node["name"] = FOLDER_ALTNAMES.get(node["name"], node["name"])
|
||||||
path = os.path.normpath(os.path.join(pfx,node['name'])).replace('\\','/')
|
assert (
|
||||||
write_line(' ' + get_prefix(depth) + ' ' + path)
|
("\\" not in node["name"])
|
||||||
if node['type'] == NODE_TYPE_FILE and not fs_listing_only: process_file_node(fd,path,node)
|
and ("/" not in node["name"])
|
||||||
elif node['type'] == NODE_TYPE_FOLDER:
|
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:
|
if not os.path.isdir(path) and not fs_listing_only:
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
for _ in range(0,node['objects_count']):
|
for _ in range(0, node["objects_count"]):
|
||||||
last = _ == node['objects_count'] - 1
|
last = _ == node["objects_count"] - 1
|
||||||
depths[depth + 1] = last
|
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:
|
try:
|
||||||
main_node = next(nodes)
|
main_node = next(nodes)
|
||||||
write_line('Filesystem:')
|
write_line("Filesystem:")
|
||||||
for _ in range(main_node['objects_count']):
|
for _ in range(main_node["objects_count"]):
|
||||||
last = _ == main_node['objects_count'] - 1
|
last = _ == main_node["objects_count"] - 1
|
||||||
depths[0] = last
|
depths[0] = last
|
||||||
traverse_next_node(next(nodes))
|
traverse_next_node(next(nodes))
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
logger.error('The filetable appears to be corrupted. Cannot proceed any further.')
|
logger.error("The filetable appears to be corrupted.")
|
||||||
logger.error('Please try toggling the --legacy-fs flag to solve this issue.')
|
logger.error("Toggling `--legacy-fs` may remedy the issue.")
|
||||||
return
|
return
|
||||||
except AssertionError as e:
|
logger.info("Extraction complete")
|
||||||
logger.error('While extracting package %s' % e)
|
|
||||||
return
|
|
||||||
logger.info('Extraction complete')
|
def main(
|
||||||
|
in_file: str,
|
||||||
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):
|
out_dir: str = ".",
|
||||||
logger.info('Enigma Virtual Box Unpacker v%s' % __version__)
|
out_pe: str = "",
|
||||||
logger.debug('File: %s' % in_file)
|
ignore_fs: bool = False,
|
||||||
os.makedirs(out_dir,exist_ok=True)
|
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:
|
if legacy_fs:
|
||||||
logger.warning('Legacy mode for filesystem extraction enabled')
|
logger.warning("Legacy mode for filesystem extraction enabled")
|
||||||
if pe_variant:
|
if pe_variant:
|
||||||
logger.warning('Legacy mode for PE restoration enabled')
|
logger.warning("Legacy mode for PE restoration enabled")
|
||||||
if fs_listing_only:
|
if fs_listing_only:
|
||||||
logger.warning('Listing virtual filesystem only')
|
logger.warning("Listing virtual filesystem only")
|
||||||
if ignore_fs:
|
if ignore_fs:
|
||||||
logger.warning('Skipping virtual FS extraction')
|
logger.warning("Skipping virtual FS extraction")
|
||||||
else:
|
else:
|
||||||
logger.info('Extracting virtual filesystem')
|
logger.info("Extracting virtual filesystem")
|
||||||
try:
|
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:
|
except Exception as e:
|
||||||
logger.error('Unhandled exception occured while extracting virtual filesystem: %s' % e)
|
logger.error("Unhandled exception occured while extracting VFS: %s" % e)
|
||||||
raise e
|
|
||||||
if ignore_pe:
|
if ignore_pe:
|
||||||
logger.warning('Skipping executable restoration')
|
logger.warning("Skipping executable restoration")
|
||||||
else:
|
else:
|
||||||
logger.info('Restoring executable')
|
logger.info("Restoring executable")
|
||||||
if not out_pe:
|
if not out_pe:
|
||||||
out_pe = os.path.join(out_dir, os.path.basename(in_file))
|
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:
|
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:
|
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__():
|
def __main__():
|
||||||
parser = ArgumentParser(description='Enigma Virtual Box Unpacker')
|
parser = ArgumentParser(description="Enigma Virtual Box Unpacker")
|
||||||
parser.add_argument('--log-level',help='Set log level',default='INFO',choices=['DEBUG','INFO','WARNING','ERROR','CRITICAL'])
|
parser.add_argument(
|
||||||
group = parser.add_argument_group('Flags')
|
"--log-level",
|
||||||
group.add_argument('-l', '--list',help='Don\'t extract the files and print the table of content to stderr only',action='store_true')
|
help="Set log level",
|
||||||
group.add_argument('--ignore-fs',help='Don\'t extract virtual filesystem',action='store_true')
|
default="INFO",
|
||||||
group.add_argument('--ignore-pe',help='Don\'t restore the executable',action='store_true')
|
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
||||||
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("Flags")
|
||||||
group = parser.add_argument_group('Overrides')
|
group.add_argument(
|
||||||
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='')
|
"-l",
|
||||||
group = parser.add_argument_group('Input')
|
"--list",
|
||||||
group.add_argument('file', help='File to be unpacked')
|
help="Don't extract the files and print the table of content to stderr only",
|
||||||
group.add_argument('output', help='Output folder')
|
action="store_true",
|
||||||
args = parser.parse_args()
|
)
|
||||||
logging.basicConfig(level=args.log_level, format='%(levelname)s: %(message)s')
|
group.add_argument(
|
||||||
sys.exit(main(args.file,args.output,args.out_pe,args.ignore_fs,args.ignore_pe,args.legacy_fs,args.pe_variant,args.list))
|
"--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__":
|
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 --
|
# -*- coding: utf-8 --
|
||||||
EVB_MAGIC = b'EVB\x00'
|
EVB_MAGIC = b"EVB\x00"
|
||||||
|
|
||||||
|
|
||||||
PE_EXCEPTION = [
|
PE_EXCEPTION = [
|
||||||
('I','BEGIN_ADDRESS'),
|
("I", "BEGIN_ADDRESS"),
|
||||||
('I','END_ADDRESS'),
|
("I", "END_ADDRESS"),
|
||||||
('I','HANDLER_PTR'),
|
("I", "HANDLER_PTR"),
|
||||||
('I','HANDLER_DATA'),
|
("I", "HANDLER_DATA"),
|
||||||
('I','PROLOG_ADDRESS'),
|
("I", "PROLOG_ADDRESS"),
|
||||||
]
|
]
|
||||||
|
|
||||||
PE64_EXCEPTION = [
|
PE64_EXCEPTION = [
|
||||||
('I','BEGIN_ADDRESS'),
|
("I", "BEGIN_ADDRESS"),
|
||||||
('I','END_ADDRESS'),
|
("I", "END_ADDRESS"),
|
||||||
('I','UNWIND_INFO'),
|
("I", "UNWIND_INFO"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class EVB_ENIGMA1_HEADER:
|
class EVB_ENIGMA1_HEADER:
|
||||||
x64 = {
|
x64 = {
|
||||||
'10_70':[
|
"10_70": [
|
||||||
('32s', 'TLS'),
|
("32s", "TLS"),
|
||||||
# Padding Begin
|
# Padding Begin
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''),
|
("Q", ""),
|
||||||
# Padding End
|
("Q", ""),
|
||||||
('Q', 'UNK_1'),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''),
|
# Padding End
|
||||||
('I','IMPORT_ADDRESS'),
|
("Q", "UNK_1"),
|
||||||
('I','IMPORT_SIZE'),
|
("Q", ""),
|
||||||
('I','RELOC_ADDRESS'),
|
("Q", ""),
|
||||||
('I','RELOC_SIZE'),
|
("Q", ""),
|
||||||
('I','TLS_ADDRESS'),
|
("I", "IMPORT_ADDRESS"),
|
||||||
('I','TLS_SIZE'),
|
("I", "IMPORT_SIZE"),
|
||||||
|
("I", "RELOC_ADDRESS"),
|
||||||
|
("I", "RELOC_SIZE"),
|
||||||
|
("I", "TLS_ADDRESS"),
|
||||||
|
("I", "TLS_SIZE"),
|
||||||
],
|
],
|
||||||
'9_70':[
|
"9_70": [
|
||||||
('32s', 'TLS'),
|
("32s", "TLS"),
|
||||||
# Padding Begin
|
# Padding Begin
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''),
|
("Q", ""),
|
||||||
('I', ''),
|
("Q", ""),
|
||||||
# Padding End
|
("Q", ""),
|
||||||
('Q', 'UNK_1'),
|
("Q", ""),
|
||||||
('Q', ''),
|
("I", ""),
|
||||||
('I','IMPORT_ADDRESS'),
|
# Padding End
|
||||||
('I','IMPORT_SIZE'),
|
("Q", "UNK_1"),
|
||||||
('I','RELOC_ADDRESS'),
|
("Q", ""),
|
||||||
('I','RELOC_SIZE'),
|
("I", "IMPORT_ADDRESS"),
|
||||||
('I','TLS_ADDRESS'),
|
("I", "IMPORT_SIZE"),
|
||||||
('I','TLS_SIZE'),
|
("I", "RELOC_ADDRESS"),
|
||||||
],
|
("I", "RELOC_SIZE"),
|
||||||
'7_80':[
|
("I", "TLS_ADDRESS"),
|
||||||
('32s', 'TLS'),
|
("I", "TLS_SIZE"),
|
||||||
|
],
|
||||||
|
"7_80": [
|
||||||
|
("32s", "TLS"),
|
||||||
# Padding Begin
|
# Padding Begin
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''),
|
("Q", ""),
|
||||||
# Padding End
|
("Q", ""),
|
||||||
('Q', 'UNK_1'),
|
("Q", ""),
|
||||||
('Q', ''),
|
("Q", ""),
|
||||||
('I','IMPORT_ADDRESS'),
|
# Padding End
|
||||||
('I','IMPORT_SIZE'),
|
("Q", "UNK_1"),
|
||||||
('I','RELOC_ADDRESS'),
|
("Q", ""),
|
||||||
('I','RELOC_SIZE'),
|
("I", "IMPORT_ADDRESS"),
|
||||||
('I','TLS_ADDRESS'),
|
("I", "IMPORT_SIZE"),
|
||||||
('I','TLS_SIZE'),
|
("I", "RELOC_ADDRESS"),
|
||||||
]
|
("I", "RELOC_SIZE"),
|
||||||
|
("I", "TLS_ADDRESS"),
|
||||||
|
("I", "TLS_SIZE"),
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
x86 = {
|
x86 = {
|
||||||
'10_70':[
|
"10_70": [
|
||||||
('16s', 'TLS'),
|
("16s", "TLS"),
|
||||||
# Padding Begin
|
# Padding Begin
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
# Padding End
|
("Q", ""),
|
||||||
('Q', 'UNK_1'), ('Q', 'UNK_2'),
|
("Q", ""),
|
||||||
('I', 'UNK_3'),
|
("Q", ""),
|
||||||
('I','IMPORT_ADDRESS'),
|
# Padding End
|
||||||
('I','IMPORT_SIZE'),
|
("Q", "UNK_1"),
|
||||||
('I','RELOC_ADDRESS'),
|
("Q", "UNK_2"),
|
||||||
('I','RELOC_SIZE'),
|
("I", "UNK_3"),
|
||||||
('I','TLS_ADDRESS'),
|
("I", "IMPORT_ADDRESS"),
|
||||||
('I','TLS_SIZE'),
|
("I", "IMPORT_SIZE"),
|
||||||
|
("I", "RELOC_ADDRESS"),
|
||||||
|
("I", "RELOC_SIZE"),
|
||||||
|
("I", "TLS_ADDRESS"),
|
||||||
|
("I", "TLS_SIZE"),
|
||||||
],
|
],
|
||||||
'9_70' : [
|
"9_70": [
|
||||||
('16s', 'TLS'),
|
("16s", "TLS"),
|
||||||
# Padding Begin
|
# Padding Begin
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
# Padding End
|
("Q", ""),
|
||||||
('Q', 'UNK_1'), ('Q', 'UNK_2'),
|
("Q", ""),
|
||||||
('I','IMPORT_ADDRESS'),
|
("Q", ""),
|
||||||
('I','IMPORT_SIZE'),
|
# Padding End
|
||||||
('I','RELOC_ADDRESS'),
|
("Q", "UNK_1"),
|
||||||
('I','RELOC_SIZE'),
|
("Q", "UNK_2"),
|
||||||
('I','TLS_ADDRESS'),
|
("I", "IMPORT_ADDRESS"),
|
||||||
('I','TLS_SIZE'),
|
("I", "IMPORT_SIZE"),
|
||||||
],
|
("I", "RELOC_ADDRESS"),
|
||||||
'7_80' : [
|
("I", "RELOC_SIZE"),
|
||||||
('16s', 'TLS'),
|
("I", "TLS_ADDRESS"),
|
||||||
|
("I", "TLS_SIZE"),
|
||||||
|
],
|
||||||
|
"7_80": [
|
||||||
|
("16s", "TLS"),
|
||||||
# Padding Begin
|
# Padding Begin
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
('Q', ''), ('Q', ''),
|
("Q", ""),
|
||||||
# Padding End
|
("Q", ""),
|
||||||
('Q', 'UNK_1'),
|
("Q", ""),
|
||||||
('I', 'UNK_3'),
|
("Q", ""),
|
||||||
('I','IMPORT_ADDRESS'),
|
# Padding End
|
||||||
('I','IMPORT_SIZE'),
|
("Q", "UNK_1"),
|
||||||
('I','RELOC_ADDRESS'),
|
("I", "UNK_3"),
|
||||||
('I','RELOC_SIZE'),
|
("I", "IMPORT_ADDRESS"),
|
||||||
('I','TLS_ADDRESS'),
|
("I", "IMPORT_SIZE"),
|
||||||
('I','TLS_SIZE'),
|
("I", "RELOC_ADDRESS"),
|
||||||
]
|
("I", "RELOC_SIZE"),
|
||||||
|
("I", "TLS_ADDRESS"),
|
||||||
|
("I", "TLS_SIZE"),
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_options():
|
def get_options():
|
||||||
return list(EVB_ENIGMA1_HEADER.x86.keys())
|
return list(EVB_ENIGMA1_HEADER.x86.keys())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_struct(arch_64 : bool, key : str):
|
def get_struct(arch_64: bool, key: str):
|
||||||
if arch_64:
|
if arch_64:
|
||||||
return EVB_ENIGMA1_HEADER.x64[key]
|
return EVB_ENIGMA1_HEADER.x64[key]
|
||||||
else:
|
else:
|
||||||
return EVB_ENIGMA1_HEADER.x86[key]
|
return EVB_ENIGMA1_HEADER.x86[key]
|
||||||
|
|
||||||
|
|
||||||
EVB_PACK_HEADER = [
|
EVB_PACK_HEADER = [
|
||||||
('4s', 'signature'), # Would always be ('EVB\x00') if valid
|
("4s", "signature"), # Would always be ('EVB\x00') if valid
|
||||||
('60s',''),
|
("60s", ""),
|
||||||
]
|
]
|
||||||
|
|
||||||
EVB_CHUNK_BLOCK = [
|
EVB_CHUNK_BLOCK = [
|
||||||
('I','size'),
|
("I", "size"),
|
||||||
('I',''),
|
("I", ""),
|
||||||
]
|
]
|
||||||
|
|
||||||
EVB_HEADER_NODE = [
|
EVB_HEADER_NODE = [
|
||||||
('I','size'),
|
("I", "size"),
|
||||||
('8s',''),
|
("8s", ""),
|
||||||
('I','objects_count'),
|
("I", "objects_count"),
|
||||||
]
|
]
|
||||||
|
|
||||||
EVB_NODE_MAIN = [
|
EVB_NODE_MAIN = [
|
||||||
('I', 'size'),
|
("I", "size"),
|
||||||
('8s', ''),
|
("8s", ""),
|
||||||
('I', 'objects_count'),
|
("I", "objects_count"),
|
||||||
]
|
]
|
||||||
|
|
||||||
EVB_NODE_NAMED = [
|
EVB_NODE_NAMED = [
|
||||||
('%ds', 'name'), # args[0] - filename buffer length
|
("%ds", "name"), # args[0] - filename buffer length
|
||||||
('B', 'type'),
|
("B", "type"),
|
||||||
]
|
]
|
||||||
|
|
||||||
EVB_NODE_OPTIONAL_FILE = [
|
EVB_NODE_OPTIONAL_FILE = [
|
||||||
('2s', ''),
|
("2s", ""),
|
||||||
('I', 'original_size'),
|
("I", "original_size"),
|
||||||
('4s',''),
|
("4s", ""),
|
||||||
('8s','filetime1'),
|
("8s", "filetime1"),
|
||||||
('8s','filetime2'),
|
("8s", "filetime2"),
|
||||||
('8s','filetime3'),
|
("8s", "filetime3"),
|
||||||
('15s',''),
|
("15s", ""),
|
||||||
('I', 'stored_size'),
|
("I", "stored_size"),
|
||||||
]
|
]
|
||||||
|
|
||||||
EVB_NODE_OPTIONAL_PE_FILE = [
|
EVB_NODE_OPTIONAL_PE_FILE = [
|
||||||
('2s', ''),
|
("2s", ""),
|
||||||
('I', 'original_size'),
|
("I", "original_size"),
|
||||||
('4s',''),
|
("4s", ""),
|
||||||
('8s','filetime1'),
|
("8s", "filetime1"),
|
||||||
('8s','filetime2'),
|
("8s", "filetime2"),
|
||||||
('8s','filetime3'),
|
("8s", "filetime3"),
|
||||||
('7s',''),
|
("7s", ""),
|
||||||
('I', 'stored_size'),
|
("I", "stored_size"),
|
||||||
('4s',''),
|
("4s", ""),
|
||||||
]
|
]
|
||||||
|
|
||||||
NODE_TYPE_MAIN = 0
|
NODE_TYPE_MAIN = 0
|
||||||
NODE_TYPE_FILE = 2
|
NODE_TYPE_FILE = 2
|
||||||
NODE_TYPE_FOLDER = 3
|
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()
|
long_description = fh.read()
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
|
|
@ -18,9 +18,7 @@ setuptools.setup(
|
||||||
"License :: OSI Approved :: Apache Software License",
|
"License :: OSI Approved :: Apache Software License",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
],
|
],
|
||||||
install_requires=[
|
install_requires=["pefile", "aplib"],
|
||||||
"pefile"
|
|
||||||
],
|
|
||||||
entry_points={"console_scripts": ["evbunpack=evbunpack.__main__:__main__"]},
|
entry_points={"console_scripts": ["evbunpack=evbunpack.__main__:__main__"]},
|
||||||
python_requires='>=3.0',
|
python_requires=">=3.0",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,106 @@
|
||||||
import os, logging
|
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):
|
def unpack_exec(pe_file, **kw):
|
||||||
from evbunpack.__main__ import main
|
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:
|
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
|
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():
|
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():
|
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():
|
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():
|
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():
|
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():
|
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():
|
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():
|
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