Last active
June 28, 2025 19:13
-
-
Save KasparNagu/9ee02cb62d81d9e4c7a833518a710d6e to your computer and use it in GitHub Desktop.
Script to extract Advanced Installer Exes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import sys | |
import struct | |
import os | |
#inspired by https://aluigi.altervista.org/bms/advanced_installer.bms | |
#with some additionaly reverse engeneering, quite heursitic (footer search, xor guessing etc) | |
#licence: public domain | |
class AdvancedInstallerFileInfo: | |
def __init__(self, name, size, offset, xorSize): | |
self.name = name | |
self.size = size | |
self.offset = offset | |
self.xorSize = xorSize | |
def __repr__(self): | |
return "[%s size=%d offset=%d]" % (self.name, self.size, self.offset) | |
class AdvancedInstallerFileReader: | |
def __init__(self,filehandle,size,keepOpen,xorLength): | |
self.filehandle = filehandle | |
self.size = size | |
self.xorLength = xorLength | |
self.pos = 0 | |
self.keepOpen = keepOpen | |
def xorFF(self,block): | |
if isinstance(block,str): | |
return "".join([chr(ord(i)^0xff) for i in block]) | |
else: | |
return bytes([i^0xff for i in block]) | |
def read(self,size = None): | |
if size is None: | |
return self.read(self.size - self.pos) | |
if self.pos < self.xorLength: | |
xorLen = min(self.xorLength - self.pos, size) | |
xorBlock = self.filehandle.read(xorLen) | |
xorLenEffective = len(xorBlock) | |
self.pos += xorLenEffective | |
xorBlock = self.xorFF(xorBlock) | |
if xorLenEffective < size: | |
return xorBlock + self.read(size - xorLenEffective) | |
return xorBlock | |
blk = self.filehandle.read(min(size,self.size - self.pos)) | |
self.pos += len(blk) | |
return blk | |
def close(self): | |
if not self.keepOpen: | |
self.filehandle.close() | |
def __enter__(self): | |
return self | |
def __exit__(self, type, value, traceback): | |
self.close() | |
class AdvancedInstallerReader: | |
def __init__(self,filename,debug=None): | |
self.filename = filename | |
self.filehandle = open(filename,"rb") | |
self.search_back = 10000 | |
self.xorSize = 0x200 | |
self.footer_position = None | |
self.debug = debug | |
self.threadsafeReopen = False | |
self.files = [] | |
def close(self): | |
self.filehandle.close() | |
def search_footer(self): | |
for i in range(0,10000): | |
self.filehandle.seek(-i,os.SEEK_END) | |
magic = self.filehandle.read(10) | |
if magic == b"ADVINSTSFX": | |
self.footer_position = i + 0x48 - 12 | |
break | |
if self.footer_position is None: | |
raise Exception("ADVINSTSFX not found") | |
def read_footer(self): | |
if self.footer_position is None: | |
self.search_footer() | |
self.filehandle.seek(-self.footer_position,os.SEEK_END) | |
footer = self.filehandle.read(0x48) | |
offset, self.nfiles, dummy1, offset1, self.info_off, file_off, hexhash, dummy2, name, = struct.unpack("<llllll32sl12s", footer) | |
if self.debug: | |
self.debug.write("offset=%d files=%d offset1=%d info_off=%d file_off=%d hexhash=%s name=%s\n" % (offset,self.nfiles,offset1,self.info_off,file_off,hexhash,name)) | |
def read_info(self): | |
self.read_footer() | |
self.files = [] | |
self.filehandle.seek(self.info_off,os.SEEK_SET) | |
for i in range(0,self.nfiles): | |
info = self.filehandle.read(24) | |
dummy1, dummy2, dummy3, size, offset, namesize = struct.unpack("<llllll",info) | |
if self.debug: | |
self.debug.write(" size=%d offset=%d namesize=%d dummy1=0x%x dummy2=0x%x dummy3=0x%x\n" % (size,offset,namesize,dummy1,dummy2,dummy3)) | |
if namesize < 0xFFFF: | |
name = self.filehandle.read(namesize*2) | |
name = name.decode("UTF-16") | |
if self.debug: | |
self.debug.write(" name=%s\n" % name) | |
self.files.append(AdvancedInstallerFileInfo(name,size,offset,self.xorSize if dummy3 == 2 else 0)) | |
else: | |
raise Exception("Invalid name size %d" % namesize) | |
def open(self,infoFile): | |
if isinstance(infoFile,AdvancedInstallerFileInfo): | |
if self.threadsafeReopen: | |
fh = open(self.filename,"rb") | |
else: | |
fh = self.filehandle | |
fh.seek(infoFile.offset,os.SEEK_SET) | |
return AdvancedInstallerFileReader(fh,infoFile.size,not self.threadsafeReopen,infoFile.xorSize) | |
else: | |
if not self.files: | |
self.read_info() | |
for f in files: | |
if f.name == infoFile: | |
return self.open(f) | |
return None | |
def infolist(self): | |
if not self.files: | |
self.read_info() | |
return self.files | |
def __enter__(self): | |
return self | |
def __exit__(self, type, value, traceback): | |
self.close() | |
def __repr__(self): | |
return "[path=%s footer=%s nFiles=%d]" % (self.filename,self.footer_position,len(self.files)) | |
if __name__ == "__main__": | |
import argparse | |
parser = argparse.ArgumentParser(description="Advanced Installer Extractor") | |
parser.add_argument('file', type=str, help="Advanced Installer to open") | |
parser.add_argument('files', type=str,nargs="*", help="Files to consider") | |
parser.add_argument('-x','--extract', default=False, action="store_true",help="Extract to current directory") | |
parser.add_argument('-l','--list', default=False, action="store_true",help="List files") | |
parser.add_argument('-v','--verbose', default=False, action="store_true",help="Debug output") | |
args = parser.parse_args(); | |
considerFiles = set(args.files) | |
with AdvancedInstallerReader(args.file,sys.stdout if args.verbose else None) as ar: | |
for f in ar.infolist(): | |
if not considerFiles or f.name in considerFiles: | |
if args.list: | |
print(f) | |
if args.extract: | |
path = f.name.replace("\\","/") | |
dirname = os.path.dirname(path) | |
if dirname: | |
if not os.path.exists(dirname): | |
os.makedirs(dirname) | |
with ar.open(f) as inf, open(path,"wb") as out: | |
while True: | |
blk = inf.read(1<<16) | |
if len(blk) == 0: | |
break | |
out.write(blk) | |
if args.verbose: | |
print(ar) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment