Example #1
0
def test_blob_io_swap_bytes():
    fin = six.BytesIO(b'abcdefgh' * 2**18)

    blob = wrap_io(fin)
    blob = rope(blob[:4], b'x', blob[6:])

    assert bytes(blob[:7]) == b'abcdxgh'
Example #2
0
def test_empty_wrap():
    fin = six.BytesIO(b'')
    blob = wrap_io(fin)

    assert len(blob) == 0
    assert bytes(blob) == b''
    assert not list(blob.chunks)
Example #3
0
def test_slices():
    fin = six.BytesIO(b'abcdefgh')
    blob = wrap_io(fin)

    assert len(blob[2:6]) == 4
    assert bytes(blob[2:6]) == b'cdef'

    with pytest.raises(IndexError):
        blob[2:6:2]
Example #4
0
def test_moving_target():
    fin = six.BytesIO(b'abcdefgh')
    blob = wrap_io(fin)

    assert len(blob) == 8

    fin.seek(4)
    fin.truncate()

    with pytest.raises(IOError):
        bytes(blob)
Example #5
0
def test_indexes():
    fin = six.BytesIO(b'abcdefgh')
    blob = wrap_io(fin)

    assert len(blob) == 8
    assert blob[0] == b'a'[0]
    assert blob[7] == b'h'[0]
    assert blob[-1] == b'h'[0]
    assert blob[-8] == b'a'[0]

    with pytest.raises(IndexError):
        blob[8]

    with pytest.raises(IndexError):
        blob[-9]
Example #6
0
    def parse(fin):
        fin.seek(0)

        hdr = _OTF_OFFSET_TABLE.parse(fin)
        table_hdrs = [
            _OTF_TABLE_RECORD.parse(fin)
            for i in six.moves.range(hdr.numTables)
        ]
        table_hdrs.sort(key=lambda tab: tab.offset)

        blob = grope.wrap_io(fin)
        tables = [
            OtfUnparsedTable(tab.tag, blob[tab.offset:tab.offset + tab.length])
            for tab in table_hdrs
        ]
        return OpenTypeFont(tables)
Example #7
0
def test_blob_io():
    fin = six.BytesIO(b'abcdefgh' * 2**18)

    blob = wrap_io(fin)
    assert isinstance(blob, rope)

    assert len(blob) == 2**21
    assert blob[0] == b'a'[0]
    assert blob[7] == b'h'[0]
    assert blob[8] == b'a'[0]
    assert blob[-1] == b'h'[0]

    assert isinstance(blob[2:3], rope)
    assert list(blob[2:3]) == [b'c'[0]]
    assert bytes(blob[2:3]) == b'c'

    assert isinstance(blob[2:4], rope)
    assert bytes(blob[2:4]) == b'cd'

    chunks = list(blob.chunks)
    assert 1 < len(chunks) < 2**8
Example #8
0
def get_resources_from_file(exe_fp) -> Iterable[ResourceEntry]:
    try:
        pe = parse_pe(grope.wrap_io(exe_fp))
    except RuntimeError as rte:
        if "Not a PE file" in str(rte):
            pe = None
        else:
            raise

    if pe:
        for type_id, resources_of_type_map in pe.parse_resources().items():
            for res_id, lang_to_res in resources_of_type_map.items():
                for lang, data in lang_to_res.items():
                    yield ResourceEntry(type_id=type_id,
                                        res_id=res_id,
                                        lang_id=lang,
                                        data=bytes(data))
        return
    # Assume NE then...
    exe_fp.seek(0)
    from res_extract.ne_resources import read_ne_resources

    yield from read_ne_resources(exe_fp)
Example #9
0
def test_repr():
    fin = six.BytesIO(b'abcd')
    blob = wrap_io(fin)

    assert repr(blob)
def main():
    ap = argparse.ArgumentParser(
        fromfile_prefix_chars='@',
        description=
        "Parses and edits resources in Windows executable (PE) files.")
    ap.add_argument(
        '--remove-signature',
        action='store_true',
        help=
        "remove the signature. If the file contains one, editing the file will fail"
    )
    ap.add_argument(
        '--ignore-trailer',
        action='store_true',
        help=
        "keep trailing data (typically in a setup program) intact, move them if necessary"
    )
    ap.add_argument('--remove-trailer',
                    action='store_true',
                    help="remove any trailing data from the output")
    ap.add_argument(
        '--update-checksum',
        action='store_true',
        help=
        "set the correct checksum (can be slow on large files), zero it out otherwise"
    )
    ap.add_argument('--clear',
                    '-C',
                    action='store_true',
                    help="remove existing resources, except for the manifest")
    ap.add_argument('--clear-manifest',
                    action='store_true',
                    help="remove the manifest resource")

    gp = ap.add_argument_group('informational (applied before any edits)')
    gp.add_argument('--print-tree',
                    '-t',
                    action='store_true',
                    help="prints the outline of the resource tree")
    gp.add_argument('--print-version',
                    '-v',
                    action='store_true',
                    help="prints all version info structures")

    gp = ap.add_argument_group('editor commands (can be used multiple times)')
    gp.add_argument(
        '--apply',
        '-A',
        action='append',
        metavar="RES",
        default=[],
        help="apply a custom .res file, overwrite any matching resource entries"
    )
    gp.add_argument(
        '--add-dependency',
        '-M',
        action='append',
        metavar="DEP",
        default=[],
        help=
        "add dependency. DEP should be a space separated list of key=value pairs, e.g. "
        +
        "\"type=win32 name=Microsoft.Windows.Common-Controls version=6.0.0.0 processorArchitecture=* publicKeyToken=6595b64144ccf1df language=*\""
    )
    gp.add_argument(
        '--set-version',
        '-V',
        action='append',
        metavar="STR",
        help=
        "updates the specified version-info field, e.g. FileVersion=\"1, 2, 3, 4\""
    )
    gp.add_argument(
        '--set-resource',
        '-R',
        metavar=('TYPE', 'NAME', 'LANG', 'FILE'),
        nargs=4,
        action='append',
        default=[],
        help=
        'set a resource entry to the contents of a file, e.g. "-R RT_RCDATA prog.exe 0 prog.exe"'
    )

    ap.add_argument(
        '--output',
        '-o',
        help=
        "write the edited contents to OUTPUT instead of editing the input file in-place"
    )
    ap.add_argument('file', help="the PE file to parse and edit")

    if not sys.argv[1:]:
        ap.print_help()
        return 0

    args = ap.parse_args()

    fin = open(args.file, 'rb')
    pe = parse_pe(grope.wrap_io(fin))
    resources = pe.parse_resources()
    if args.print_tree:
        if resources is None:
            print('no resources in the PE file')
        else:
            print('resources:')
            for resource_type in resources:
                print('  {}'.format(
                    KnownResourceTypes.get_type_name(resource_type)))
                for name in resources[resource_type]:
                    print('    {}'.format(name))
                    for lang in resources[resource_type][name]:
                        print('      {}: size={}'.format(
                            lang, len(resources[resource_type][name][lang])))

    if resources is None:
        resources = {}

    if args.print_version:
        for name in resources[RT_VERSION]:
            for lang in resources[RT_VERSION][name]:
                print('version info: {} {}'.format(name, lang))

                vi = parse_version_info(resources[RT_VERSION][name][lang])
                fixed = vi.get_fixed_info()

                print('  file version: {}'.format(fixed.file_version))
                print('  product version: {}'.format(fixed.product_version))
                for k in fixed.descriptor.names:
                    print('  {}: 0x{:x}'.format(k, getattr(fixed, k)))

    if not args.clear and not args.apply and not args.add_dependency and not args.set_version and not args.set_resource:
        return 0

    if pe.has_trailer():
        if not args.ignore_trailer and not args.remove_trailer:
            print(
                'error: the file contains trailing data, ignore with --ignore-trailer',
                file=sys.stderr)
            return 1

        if args.remove_trailer:
            pe.remove_trailer()

    if pe.has_signature():
        if not args.remove_signature and not args.remove_trailer:
            print('error: the file contains a signature', file=sys.stderr)
            return 1

        pe.remove_signature()

    if not pe.is_dir_safely_resizable(IMAGE_DIRECTORY_ENTRY_RESOURCE):
        print('error: the resource section is not resizable: {}'.format(
            args.file),
              file=sys.stderr)
        return 3

    if args.clear:
        resources = {k: v for k, v in resources.items() if k == RT_MANIFEST}

    if args.clear_manifest:
        if RT_MANIFEST in resources:
            del resources[RT_MANIFEST]

    for res_file in args.apply:
        res_fin = open(res_file, 'rb')
        # must not close res_fin until the ropes are gone

        r = parse_prelink_resources(grope.wrap_io(res_fin))
        for resource_type in r:
            for name in r[resource_type]:
                for lang in r[resource_type][name]:
                    resources.setdefault(resource_type, {}).setdefault(
                        name, {})[lang] = r[resource_type][name][lang]

    for rtype, rname, lang, inname in args.set_resource:
        res_fin = open(inname, 'rb')
        rtype = getattr(KnownResourceTypes, rtype, rtype)
        if rname.startswith('#'):
            rname = int(rname[1:], 10)
        else:
            rname = rname.upper()
        resources.setdefault(rtype, {}).setdefault(
            rname, {})[int(lang)] = grope.wrap_io(res_fin)

    if args.add_dependency:
        man_data = None
        for name in resources.get(RT_MANIFEST, ()):
            for lang in resources[name]:
                if man_data is not None:
                    print('error: multiple manifest resources found',
                          file=sys.stderr)
                    return 4
                man_data = resources[name][lang]
                man_name = name
                man_lang = lang

        if man_data is None:
            man_doc = xml.dom.minidom.getDOMImplementation().createDocument(
                None, 'dependency', None)
            man = man_doc.documentElement
        else:
            man_doc = xml.dom.minidom.parseString(bytes(man_data))
            man = man_doc.documentElement

        dependent_assembly = man_doc.getElementById('dependentAssembly')
        if not dependent_assembly:
            dependent_assembly = man_doc.createElement('dependentAssembly')
            man.append(dependent_assembly)

        for dep in args.add_dependency:
            dep_elem = man_doc.createElement('assemblyIdentity')
            for tok in dep.split():
                k, v = tok.split('=', 1)
                dep_elem.attrib[k] = v
            dependent_assembly.appendChild(dep_elem)

        resources[RT_MANIFEST][man_name][
            man_lang] = b'\xef\xbb\xbf' + man_doc.toxml(encoding='utf-8')

    if args.set_version:
        ver_data = None
        for name in resources.get(RT_VERSION, ()):
            for lang in resources[RT_VERSION][name]:
                if ver_data is not None:
                    print('error: multiple manifest resources found',
                          file=sys.stderr)
                    return 4
                ver_data = resources[RT_VERSION][name][lang]
                ver_name = name
                ver_lang = lang

        if ver_data is None:
            ver_data = VersionInfo()

        params = {}
        for param in args.set_version:
            toks = param.split('=', 1)
            if len(toks) != 2:
                print('error: version infos must be in the form "name=value"',
                      file=sys.stderr)
                return 2
            name, value = toks

            if value.startswith('/') and value.endswith('/'):
                pattern, sub = value[1:-1].split('/', 1)
                r = re.compile(pattern)
                params[name] = _ReReplace(r, sub)
            else:
                params[name] = _IdentityReplace(value)

        vi = parse_version_info(ver_data)

        fvi = vi.get_fixed_info()
        if 'FileVersion' in params:
            ver = Version(params['FileVersion'](None))
            fvi.dwFileVersionMS, fvi.dwFileVersionLS = ver.get_ms_ls()
        if 'ProductVersion' in params:
            ver = Version(params['ProductVersion'](None))
            fvi.dwProductVersionMS, fvi.dwProductVersionLS = ver.get_ms_ls()
        vi.set_fixed_info(fvi)

        sfi = vi.string_file_info()
        for _, strings in sfi.items():
            for k, fn in params.items():
                val = fn(strings.get(k, ''))
                if val:
                    strings[k] = val
                elif k in strings:
                    del strings[k]
        vi.set_string_file_info(sfi)
        resources[RT_VERSION][ver_name][ver_lang] = vi.pack()

    prepacked = pe_resources_prepack(resources)
    addr = pe.resize_directory(IMAGE_DIRECTORY_ENTRY_RESOURCE, prepacked.size)
    pe.set_directory(IMAGE_DIRECTORY_ENTRY_RESOURCE, prepacked.pack(addr))

    if not args.output:
        fout, fout_name = tempfile.mkstemp(dir=os.path.split(args.file)[0])
        fout = os.fdopen(fout, mode='w+b')
        try:
            grope.dump(pe.to_blob(update_checksum=args.update_checksum), fout)

            fin.close()
            fout.close()
        except:
            fout.close()
            os.remove(fout_name)
            raise
        else:
            os.remove(args.file)
            os.rename(fout_name, args.file)

    else:
        with open(args.output, 'wb') as fout:
            grope.dump(pe.to_blob(), fout)

    return 0