#! /usr/bin/env python2 ## vim:set ts=4 sw=4 et: -*- coding: utf-8 -*- # # bin2h.py -- # # This file is part of the UPX executable compressor. # # Copyright (C) 1996-2024 Markus Franz Xaver Johannes Oberhumer # All Rights Reserved. # # UPX and the UCL library are free software; you can redistribute them # and/or modify them under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. # If not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # Markus F.X.J. Oberhumer Laszlo Molnar # # import getopt, os, re, struct, sys, zlib class opts: dry_run = 0 ident = None methods = [ 0 ] mname = "STUB_COMPRESS_METHOD" mode = "c" verbose = 0 # /*********************************************************************** # // write header # ************************************************************************/ def w_header_c(w, ifile, ofile, n): w("/* %s\n created from %s, %d (0x%x) bytes\n" % (os.path.basename(ofile), os.path.basename(ifile), n, n)) w("""\n\ This file is part of the UPX executable compressor. Copyright (C) 1996-2024 Markus Franz Xaver Johannes Oberhumer Copyright (C) 1996-2024 Laszlo Molnar Copyright (C) 2000-2024 John F. Reiser All Rights Reserved. UPX and the UCL library are free software; you can redistribute them and/or modify them under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Markus F.X.J. Oberhumer Laszlo Molnar John F. Reiser */\n\n""") # /*********************************************************************** # // write data # ************************************************************************/ class DataWriter: def __init__(self, w): self.w = w self.pos = None def w_bol(self, pos): self.w("/* 0x%04x */ " % (pos)) self.pos = pos def w_eol(self, fill=""): if self.pos is not None: self.w(fill.rstrip() + "\n") class DataWriter_c(DataWriter): def w_data(self, data): w, n = self.w, len(data) for i in range(n): if i & 15 == 0: self.w_eol() self.w_bol(i) w("%3d" % ord(data[i])) if i != n - 1: w(",") self.w_eol() class DataWriter_gas(DataWriter): def w_data(self, data): w, n = self.w, len(data) for i in range(n): if i & 15 == 0: self.w_eol() self.w_bol(i) w(".byte ") else: w(",") w("%3d" % ord(data[i])) self.w_eol() class _DataWriter_gas_u32(DataWriter): def w_data(self, data): w, n = self.w, len(data) assert n % 4 == 0, n for i in range(0, n, 4): if i & 15 == 0: self.w_eol() self.w_bol(i) w(".int ") else: w(",") v = struct.unpack(self.DECODE, data[i:i+4]) assert len(v) == 1, v w("0x%08x" % (v[0] & 0xffffffffL)) self.w_eol() class DataWriter_gas_be32(_DataWriter_gas_u32): DECODE = ">i" class DataWriter_gas_le32(_DataWriter_gas_u32): DECODE = " 1: if method_index == 0: w("#if (%s == %d)\n\n" % (opts.mname, method)) elif method_index < len(methods) - 1: w("\n#elif (%s == %d)\n\n" % (opts.mname, method)) else: w("\n#else\n\n") if opts.ident: if opts.mode == "c": w_checksum_c(w, opts.ident.upper(), odata) if 0: # idea: put all stubs in a dedicated section so that UPX compresses better #w("#if defined(__ELF__)\n") #w('__attribute__((__section__("upx_stubs")))\n') #w("#endif\n") w("ATTRIBUTE_FOR_STUB(%s)\n" % (opts.ident)) w("unsigned char %s[%d] = {\n" % (opts.ident, len(odata))) if opts.mode == "c": DataWriter_c(w).w_data(odata) elif opts.mode == "gas": DataWriter_gas(w).w_data(odata) elif opts.mode == "gas-be32": DataWriter_gas_be32(w).w_data(odata) elif opts.mode == "gas-le32": DataWriter_gas_le32(w).w_data(odata) elif opts.mode == "nasm": DataWriter_nasm(w).w_data(odata) else: assert 0, ("invalid mode", opts.mode) if opts.ident: if opts.mode == "c": w("};\n") if len(methods) > 1: if method_index == len(methods) - 1: w("\n#endif\n") # /*********************************************************************** # // compress stub # ************************************************************************/ def encode_compressed_stub_header(method, idata, odata): assert 0 < method <= 255 if len(idata) <= 65535: h = "UPX#" + struct.pack("= len(idata): # not compressible return 0, idata assert len(odata) <= len(idata), "stub compression failed" return method, odata # /*********************************************************************** # // main # ************************************************************************/ def main(argv): try: assert 0 except AssertionError: pass else: raise Exception("fatal error - assertions not enabled") shortopts, longopts = "qv", [ "compress=", "dry-run", "ident=", "mode=", "quiet", "verbose" ] xopts, args = getopt.gnu_getopt(argv[1:], shortopts, longopts) for opt, optarg in xopts: if 0: pass elif opt in ["-q", "--quiet"]: opts.verbose = opts.verbose - 1 elif opt in ["-v", "--verbose"]: opts.verbose = opts.verbose + 1 elif opt in ["--compress"]: opts.methods = map(int, optarg.split(",")) elif opt in ["--dry-run"]: opts.dry_run = opts.dry_run + 1 elif opt in ["--ident"]: opts.ident = optarg elif opt in ["--mode"]: opts.mode = optarg.lower() else: assert 0, ("getopt problem:", opt, optarg, xopts, args) assert len(args) == 2 ifile = args[0] ofile = args[1] # check file size st = os.stat(ifile) if 1 and st.st_size <= 0: print >> sys.stderr, "%s: ERROR: empty file" % (ifile) sys.exit(1) if 1 and st.st_size > 128*1024: print >> sys.stderr, "%s: ERROR: file is too big (%d bytes)" % (ifile, st.st_size) sys.exit(1) # read ifile ifile = os.path.normpath(ifile) ifp = open(ifile, "rb") idata = ifp.read() ifp.close() assert len(idata) == st.st_size # opts.ident if opts.ident in ["auto", "auto-stub"]: s = os.path.basename(ifile) s = re.sub(r"\.(bin|out)$", "", s) s = re.sub(r"[-.]", "_", s) if opts.ident in ["auto-stub"]: s = "stub_" + s opts.ident = s if opts.ident: assert re.search(r"^[a-zA-Z]", opts.ident), opts.ident assert not re.search(r"[^a-zA-Z0-9_]", opts.ident), opts.ident # compress stubs # (process in reverse order so that incompressible do not get sorted first) mdata, mdata_odata = [], {} assert len(opts.methods) >= 1 r_methods = opts.methods[:] r_methods.reverse() for method in r_methods: method, odata = compress_stub(method, idata) if mdata_odata.has_key(method): assert mdata_odata[method] == odata else: mdata_odata[method] = odata mdata.append(method) assert len(mdata) >= 1 mdata.reverse() ##print opts.methods, [(i, len(mdata_odata[i])) for i in mdata] # write ofile if opts.dry_run: ofp = None def dummy_write(s): pass w = dummy_write else: if ofile == "-": ofp = sys.stdout else: ofp = open(ofile, "wb") w = ofp.write if opts.mode == "c": if opts.verbose >= 0: w_header_c(w, ifile, ofile, len(idata)) w("/* clang" + "-format" + " off */\n\n") for i in range(len(mdata)): write_stub(w, mdata_odata[mdata[i]], i, mdata) if ofp: if ofp is sys.stdout: ofp.flush() else: ofp.close() # write an extra C file so that we can test the total size of the stubs: # $ gcc -Wall -c test_size*.c # $ size --totals test_size*.o # current results (2022-12-22): # 89 files, 1,082,956 bytes if 0 and not opts.dry_run: if opts.ident and ofile and ofile != "-": tfp = open("test_size_" + ofile + ".c", "wb") tfp.write("const\n") tfp.write('#include "' + ofile + '"\n') tfp.close() if __name__ == "__main__": sys.exit(main(sys.argv))