cea-sec/miasm

View on GitHub
miasm/loader/pe.py

Summary

Maintainability
F
3 wks
Test Coverage
#! /usr/bin/env python

from __future__ import print_function
from builtins import range, str
from collections import defaultdict
import logging
import struct

from future.builtins import int as int_types
from future.utils import PY3

from miasm.core.utils import force_bytes
from miasm.loader.new_cstruct import CStruct
from miasm.loader.strpatchwork import StrPatchwork

log = logging.getLogger("pepy")
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter("[%(levelname)-8s]: %(message)s"))
log.addHandler(console_handler)
log.setLevel(logging.WARN)


class InvalidOffset(Exception):
    pass


class Doshdr(CStruct):
    _fields = [("magic", "u16"),
               ("cblp", "u16"),
               ("cp", "u16"),
               ("crlc", "u16"),
               ("cparhdr", "u16"),
               ("minalloc", "u16"),
               ("maxalloc", "u16"),
               ("ss", "u16"),
               ("sp", "u16"),
               ("csum", "u16"),
               ("ip", "u16"),
               ("cs", "u16"),
               ("lfarlc", "u16"),
               ("ovno", "u16"),
               ("res", "8s"),
               ("oemid", "u16"),
               ("oeminfo", "u16"),
               ("res2", "20s"),
               ("lfanew", "u32")]


class NTsig(CStruct):
    _fields = [("signature", "u32"),
               ]


class Coffhdr(CStruct):
    _fields = [("machine", "u16"),
               ("numberofsections", "u16"),
               ("timedatestamp", "u32"),
               ("pointertosymboltable", "u32"),
               ("numberofsymbols", "u32"),
               ("sizeofoptionalheader", "u16"),
               ("characteristics", "u16")]


class Optehdr(CStruct):
    _fields = [("rva", "u32"),
               ("size", "u32")]


def get_optehdr_num(nthdr):
    numberofrva = nthdr.numberofrvaandsizes
    parent = nthdr.parent_head
    entry_size = 8
    if parent.Coffhdr.sizeofoptionalheader < numberofrva * entry_size + len(parent.Opthdr):
        numberofrva = (parent.Coffhdr.sizeofoptionalheader - len(parent.Opthdr)) // entry_size
        log.warn('Bad number of rva.. using default %d' % numberofrva)
        numberofrva = 0x10
    return numberofrva


class Opthdr32(CStruct):
    _fields = [("magic", "u16"),
               ("majorlinkerversion", "u08"),
               ("minorlinkerversion", "u08"),
               ("SizeOfCode", "u32"),
               ("sizeofinitializeddata", "u32"),
               ("sizeofuninitializeddata", "u32"),
               ("AddressOfEntryPoint", "u32"),
               ("BaseOfCode", "u32"),
               ("BaseOfData", "u32"),
               ]


class Opthdr64(CStruct):
    _fields = [("magic", "u16"),
               ("majorlinkerversion", "u08"),
               ("minorlinkerversion", "u08"),
               ("SizeOfCode", "u32"),
               ("sizeofinitializeddata", "u32"),
               ("sizeofuninitializeddata", "u32"),
               ("AddressOfEntryPoint", "u32"),
               ("BaseOfCode", "u32"),
               ]


class NThdr(CStruct):
    _fields = [("ImageBase", "ptr"),
               ("sectionalignment", "u32"),
               ("filealignment", "u32"),
               ("majoroperatingsystemversion", "u16"),
               ("minoroperatingsystemversion", "u16"),
               ("MajorImageVersion", "u16"),
               ("MinorImageVersion", "u16"),
               ("majorsubsystemversion", "u16"),
               ("minorsubsystemversion", "u16"),
               ("Reserved1", "u32"),
               ("sizeofimage", "u32"),
               ("sizeofheaders", "u32"),
               ("CheckSum", "u32"),
               ("subsystem", "u16"),
               ("dllcharacteristics", "u16"),
               ("sizeofstackreserve", "ptr"),
               ("sizeofstackcommit", "ptr"),
               ("sizeofheapreserve", "ptr"),
               ("sizeofheapcommit", "ptr"),
               ("loaderflags", "u32"),
               ("numberofrvaandsizes", "u32"),
               ("optentries", "Optehdr", lambda c:get_optehdr_num(c))
               ]


class Shdr(CStruct):
    _fields = [("name", "8s"),
               ("size", "u32"),
               ("addr", "u32"),
               ("rawsize", "u32"),
               ("offset", "u32"),
               ("pointertorelocations", "u32"),
               ("pointertolinenumbers", "u32"),
               ("numberofrelocations", "u16"),
               ("numberoflinenumbers", "u16"),
               ("flags", "u32")]


    def get_data(self):
        parent = self.parent_head
        data = parent.img_rva[self.addr:self.addr + self.size]
        return data

    def set_data(self, data):
        parent = self.parent_head
        parent.img_rva[self.addr] = data


    data = property(get_data, set_data)

class SHList(CStruct):
    _fields = [
        ("shlist", "Shdr", lambda c:c.parent_head.Coffhdr.numberofsections)]

    def add_section(self, name="default", data=b"", **args):
        s_align = self.parent_head.NThdr.sectionalignment
        s_align = max(0x1000, s_align)

        f_align = self.parent_head.NThdr.filealignment
        f_align = max(0x200, f_align)
        size = len(data)
        rawsize = len(data)
        if len(self):
            addr = self[-1].addr + self[-1].size
            s_last = self[0]
            for section in self:
                if s_last.offset + s_last.rawsize < section.offset + section.rawsize:
                    s_last = section
            offset = s_last.offset + s_last.rawsize
        else:
            s_null = bytes(Shdr.unpack(b"\x00" * 0x100))
            offset = self.parent_head.Doshdr.lfanew + len(self.parent_head.NTsig) + len(
                self.parent_head.Coffhdr) + self.parent_head.Coffhdr.sizeofoptionalheader + len(bytes(self.parent_head.SHList) + s_null)
            addr = 0x2000
        # round addr
        addr = (addr + (s_align - 1)) & ~(s_align - 1)
        offset = (offset + (f_align - 1)) & ~(f_align - 1)

        attrs = {"name": name, "size": size,
                 "addr": addr, "rawsize": rawsize,
                 "offset": offset,
                 "pointertorelocations": 0,
                 "pointertolinenumbers": 0,
                 "numberofrelocations": 0,
                 "numberoflinenumbers": 0,
                 "flags": 0xE0000020,
                 "data": data
        }
        attrs.update(args)
        section = Shdr(self.parent_head, _sex=self.parent_head._sex,
                 _wsize=self.parent_head._wsize, **attrs)
        section.data = data

        if section.rawsize > len(data):
            section.data = section.data + b'\x00' * (section.rawsize - len(data))
            section.size = section.rawsize
        section.data = bytes(StrPatchwork(section.data))
        section.size = max(s_align, section.size)

        self.append(section)
        self.parent_head.Coffhdr.numberofsections = len(self)

        length = (section.addr + section.size + (s_align - 1)) & ~(s_align - 1)
        self.parent_head.NThdr.sizeofimage = length
        return section

    def align_sections(self, f_align=None, s_align=None):
        if f_align == None:
            f_align = self.parent_head.NThdr.filealignment
            f_align = max(0x200, f_align)
        if s_align == None:
            s_align = self.parent_head.NThdr.sectionalignment
            s_align = max(0x1000, s_align)

        if self is None:
            return

        addr = self[0].offset
        for section in self:
            raw_off = f_align * ((addr + f_align - 1) // f_align)
            section.offset = raw_off
            section.rawsize = len(section.data)
            addr = raw_off + section.rawsize

    def __repr__(self):
        rep = ["#  section         offset   size   addr     flags   rawsize  "]
        for i, section in enumerate(self):
            name = force_bytes(section.name)
            out = "%-15s" % name.strip(b'\x00').decode()
            out += "%(offset)08x %(size)06x %(addr)08x %(flags)08x %(rawsize)08x" % section
            out = ("%2i " % i) + out
            rep.append(out)
        return "\n".join(rep)

    def __getitem__(self, item):
        return self.shlist[item]

    def __len__(self):
        return len(self.shlist)

    def append(self, section):
        self.shlist.append(section)


class Rva(CStruct):
    _fields = [("rva", "ptr"),
               ]


class Rva32(CStruct):
    _fields = [("rva", "u32"),
               ]


class DescName(CStruct):
    _fields = [("name", (lambda c, raw, off: c.gets(raw, off),
                         lambda c, value: c.sets(value)))
               ]

    def gets(self, raw, off):
        name = raw[off:raw.find(b'\x00', off)]
        return name, off + len(name) + 1

    def sets(self, value):
        return force_bytes(value) + b"\x00"


class ImportByName(CStruct):
    _fields = [("hint", "u16"),
               ("name", "sz")
               ]


class ImpDesc_e(CStruct):
    _fields = [("originalfirstthunk", "u32"),
               ("timestamp", "u32"),
               ("forwarderchain", "u32"),
               ("name", "u32"),
               ("firstthunk", "u32")
               ]


class struct_array(object):

    def __init__(self, target_class, raw, off, cstr, num=None):
        self.l = []
        self.cls = target_class
        self.end = None
        i = 0
        if not raw:
            return

        while (num == None) or (num and i < num):
            entry, length = cstr.unpack_l(raw, off,
                                          target_class.parent_head,
                                          target_class.parent_head._sex,
                                          target_class.parent_head._wsize)
            if num == None:
                if raw[off:off + length] == b'\x00' * length:
                    self.end = b'\x00' * length
                    break
            self.l.append(entry)
            off += length
            i += 1

    def __bytes__(self):
        out = b"".join(bytes(x) for x in self.l)
        if self.end is not None:
            out += self.end
        return out

    def __str__(self):
        if PY3:
            return repr(self)
        return self.__bytes__()

    def __getitem__(self, item):
        return self.l.__getitem__(item)

    def __len__(self):
        return len(self.l)

    def append(self, entry):
        self.l.append(entry)

    def insert(self, index, entry):
        self.l.insert(index, entry)


class DirImport(CStruct):
    _fields = [("impdesc", (lambda c, raw, off:c.gete(raw, off),
                            lambda c, value:c.sete(value)))]

    def gete(self, raw, off):
        if not off:
            return None, off
        if self.parent_head._wsize == 32:
            mask_ptr = 0x80000000
        elif self.parent_head._wsize == 64:
            mask_ptr = 0x8000000000000000

        ofend = off + \
                self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_IMPORT].size
        out = []
        while off < ofend:
            if not 0 <= off < len(self.parent_head.img_rva):
                break
            imp, length = ImpDesc_e.unpack_l(raw, off)
            if (raw[off:off+length] == b'\x00' * length or
                imp.name == 0):
                # Special case
                break
            if not (imp.originalfirstthunk or imp.firstthunk):
                log.warning("no thunk!!")
                break

            out.append(imp)
            off += length
            imp.dlldescname = DescName.unpack(raw, imp.name, self.parent_head)
            if imp.originalfirstthunk and imp.originalfirstthunk < len(self.parent_head.img_rva):
                imp.originalfirstthunks = struct_array(self, raw,
                                                       imp.originalfirstthunk,
                                                       Rva)
            else:
                imp.originalfirstthunks = None

            if imp.firstthunk and imp.firstthunk  < len(self.parent_head.img_rva):
                imp.firstthunks = struct_array(self, raw,
                                               imp.firstthunk,
                                               Rva)
            else:
                imp.firstthunks = None
            imp.impbynames = []
            if imp.originalfirstthunk and imp.originalfirstthunk < len(self.parent_head.img_rva):
                tmp_thunk = imp.originalfirstthunks
            elif imp.firstthunk:
                tmp_thunk = imp.firstthunks
            for i in range(len(tmp_thunk)):
                if tmp_thunk[i].rva & mask_ptr == 0:
                    try:
                        entry = ImportByName.unpack(raw,
                                                    tmp_thunk[i].rva,
                                                    self.parent_head)
                    except:
                        log.warning(
                            'cannot import from add %s' % tmp_thunk[i].rva
                        )
                        entry = 0
                    imp.impbynames.append(entry)
                else:
                    imp.impbynames.append(tmp_thunk[i].rva & (mask_ptr - 1))
        return out, off

    def sete(self, entries):
        return b"".join(bytes(entry) for entry in entries) + b"\x00" * (4 * 5)

    def __len__(self):
        length = (len(self.impdesc) + 1) * (5 * 4)  # ImpDesc_e size
        rva_size = self.parent_head._wsize // 8
        for entry in self.impdesc:
            length += len(entry.dlldescname)
            if entry.originalfirstthunk and self.parent_head.rva2off(entry.originalfirstthunk):
                length += (len(entry.originalfirstthunks) + 1) * rva_size
            if entry.firstthunk:
                length += (len(entry.firstthunks) + 1) * rva_size
            for imp in entry.impbynames:
                if isinstance(imp, ImportByName):
                    length += len(imp)
        return length

    def set_rva(self, rva, size=None):
        self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_IMPORT].rva = rva
        rva_size = self.parent_head._wsize // 8
        if not size:
            self.parent_head.NThdr.optentries[
                DIRECTORY_ENTRY_IMPORT].size = len(self)
        else:
            self.parent_head.NThdr.optentries[
                DIRECTORY_ENTRY_IMPORT].size = size
        rva += (len(self.impdesc) + 1) * 5 * 4  # ImpDesc size
        for entry in self.impdesc:
            entry.name = rva
            rva += len(entry.dlldescname)
            if entry.originalfirstthunk:  # and self.parent_head.rva2off(entry.originalfirstthunk):
                entry.originalfirstthunk = rva
                rva += (len(entry.originalfirstthunks) + 1) * rva_size
            # XXX rva fthunk not patched => keep original func addr
            # if entry.firstthunk:
            #    entry.firstthunk = rva
            # rva+=(len(entry.firstthunks)+1)*self.parent_head._wsize//8 # Rva size
            if entry.originalfirstthunk and entry.firstthunk:
                if isinstance(entry.originalfirstthunks, struct_array):
                    tmp_thunk = entry.originalfirstthunks
                elif isinstance(entry.firstthunks, struct_array):
                    tmp_thunk = entry.firstthunks
                else:
                    raise RuntimeError("No thunk!")
            elif entry.originalfirstthunk:  # and self.parent_head.rva2off(entry.originalfirstthunk):
                tmp_thunk = entry.originalfirstthunks
            elif entry.firstthunk:
                tmp_thunk = entry.firstthunks
            else:
                raise RuntimeError("No thunk!")

            if tmp_thunk == entry.originalfirstthunks:
                entry.firstthunks = tmp_thunk
            else:
                entry.originalfirstthunks = tmp_thunk
            for i, imp in enumerate(entry.impbynames):
                if isinstance(imp, ImportByName):
                    tmp_thunk[i].rva = rva
                    rva += len(imp)

    def build_content(self, raw):
        if self.parent_head._wsize == 32:
            mask_ptr = 0x80000000
        elif self.parent_head._wsize == 64:
            mask_ptr = 0x8000000000000000

        dirimp = self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_IMPORT]
        of1 = dirimp.rva
        if not of1:  # No Import
            return
        raw[self.parent_head.rva2off(of1)] = bytes(self)
        for entry in self.impdesc:
            raw[self.parent_head.rva2off(entry.name)] = bytes(entry.dlldescname)
            if (entry.originalfirstthunk and
                self.parent_head.rva2off(entry.originalfirstthunk)):
                # Add thunks list and terminating null entry
                off = self.parent_head.rva2off(entry.originalfirstthunk)
                raw[off] = bytes(entry.originalfirstthunks)
            if entry.firstthunk:
                # Add thunks list and terminating null entry
                off = self.parent_head.rva2off(entry.firstthunk)
                raw[off] = bytes(entry.firstthunks)
            if (entry.originalfirstthunk and
                self.parent_head.rva2off(entry.originalfirstthunk)):
                tmp_thunk = entry.originalfirstthunks
            elif entry.firstthunk:
                tmp_thunk = entry.firstthunks
            else:
                raise RuntimeError("No thunk!")
            for j, imp in enumerate(entry.impbynames):
                if isinstance(imp, ImportByName):
                    raw[self.parent_head.rva2off(tmp_thunk[j].rva)] = bytes(imp)

    def get_dlldesc(self):
        out = []
        for impdesc in self.impdesc:
            dllname = impdesc.dlldescname.name
            funcs = []
            for imp in impdesc.impbynames:
                if isinstance(imp, ImportByName):
                    funcs.append(imp.name)
                else:
                    funcs.append(imp)
            entry = ({"name": dllname, "firstthunk": impdesc.firstthunk}, funcs)
            out.append(entry)
        return out

    def __repr__(self):
        rep = ["<%s>" % self.__class__.__name__]
        for i, entry in enumerate(self.impdesc):
            out = "%2d %-25s %s" % (i, repr(entry.dlldescname), repr(entry))
            rep.append(out)
            for index, imp in enumerate(entry.impbynames):
                out = "    %2d %-16s" % (index, repr(imp))
                rep.append(out)
        return "\n".join(rep)

    def add_dlldesc(self, new_dll):
        rva_size = self.parent_head._wsize // 8
        if self.parent_head._wsize == 32:
            mask_ptr = 0x80000000
        elif self.parent_head._wsize == 64:
            mask_ptr = 0x8000000000000000
        new_impdesc = []
        of1 = None
        for import_descriptor, new_functions in new_dll:
            if isinstance(import_descriptor.get("name"), str):
                import_descriptor["name"] = import_descriptor["name"].encode()
            new_functions = [
                funcname.encode() if isinstance(funcname, str) else funcname
                for funcname in new_functions
            ]
            for attr in ["timestamp", "forwarderchain", "originalfirstthunk"]:
                if attr not in import_descriptor:
                    import_descriptor[attr] = 0
            entry = ImpDesc_e(self.parent_head, **import_descriptor)
            if entry.firstthunk != None:
                of1 = entry.firstthunk
            elif of1 == None:
                raise RuntimeError("set fthunk")
            else:
                entry.firstthunk = of1
            entry.dlldescname = DescName(self.parent_head, name=entry.name)
            entry.originalfirstthunk = 0
            entry.originalfirstthunks = struct_array(self, None,
                                                     None,
                                                     Rva)
            entry.firstthunks = struct_array(self, None,
                                             None,
                                             Rva)

            impbynames = []
            for new_function in new_functions:
                rva_ofirstt = Rva(self.parent_head)
                if isinstance(new_function, int_types):
                    rva_ofirstt.rva = mask_ptr + new_function
                    ibn = new_function
                elif isinstance(new_function, bytes):
                    rva_ofirstt.rva = True
                    ibn = ImportByName(self.parent_head)
                    ibn.name = new_function
                    ibn.hint = 0
                else:
                    raise RuntimeError('unknown func type %s' % new_function)
                impbynames.append(ibn)
                entry.originalfirstthunks.append(rva_ofirstt)
                rva_func = Rva(self.parent_head)
                if isinstance(ibn, ImportByName):
                    rva_func.rva = 0xDEADBEEF  # default func addr
                else:
                    # ord ?XXX?
                    rva_func.rva = rva_ofirstt.rva
                entry.firstthunks.append(rva_func)
                of1 += rva_size
            # for null thunk
            of1 += rva_size
            entry.impbynames = impbynames
            new_impdesc.append(entry)
        if self.impdesc is None:
            self.impdesc = struct_array(self, None,
                                        None,
                                        ImpDesc_e)
            self.impdesc.l = new_impdesc
        else:
            for entry in new_impdesc:
                self.impdesc.append(entry)

    def get_funcrva(self, dllname, funcname):
        dllname = force_bytes(dllname)
        funcname = force_bytes(funcname)

        rva_size = self.parent_head._wsize // 8
        if self.parent_head._wsize == 32:
            mask_ptr = 0x80000000 - 1
        elif self.parent_head._wsize == 64:
            mask_ptr = 0x8000000000000000 - 1

        for entry in self.impdesc:
            if entry.dlldescname.name.lower() != dllname.lower():
                continue
            if entry.originalfirstthunk and self.parent_head.rva2off(entry.originalfirstthunk):
                tmp_thunk = entry.originalfirstthunks
            elif entry.firstthunk:
                tmp_thunk = entry.firstthunks
            else:
                raise RuntimeError("No thunk!")
            if isinstance(funcname, bytes):
                for j, imp in enumerate(entry.impbynames):
                    if isinstance(imp, ImportByName):
                        if funcname == imp.name:
                            return entry.firstthunk + j * rva_size
            elif isinstance(funcname, int_types):
                for j, imp in enumerate(entry.impbynames):
                    if not isinstance(imp, ImportByName):
                        if tmp_thunk[j].rva & mask_ptr == funcname:
                            return entry.firstthunk + j * rva_size
            else:
                raise ValueError('Unknown: %s %s' % (dllname, funcname))

    def get_funcvirt(self, dllname, funcname):
        rva = self.get_funcrva(dllname, funcname)
        if rva == None:
            return
        return self.parent_head.rva2virt(rva)


class ExpDesc_e(CStruct):
    _fields = [("characteristics", "u32"),
               ("timestamp", "u32"),
               ("majorv", "u16"),
               ("minorv", "u16"),
               ("name", "u32"),
               ("base", "u32"),
               ("numberoffunctions", "u32"),
               ("numberofnames", "u32"),
               ("addressoffunctions", "u32"),
               ("addressofnames", "u32"),
               ("addressofordinals", "u32"),
               ]


class DirExport(CStruct):
    _fields = [("expdesc", (lambda c, raw, off:c.gete(raw, off),
                            lambda c, value:c.sete(value)))]

    def gete(self, raw, off):
        off_o = off
        if not off:
            return None, off
        off_sav = off
        if off >= len(raw):
            log.warn("export dir malformed!")
            return None, off_o
        expdesc = ExpDesc_e.unpack(raw,
                                   off,
                                   self.parent_head)
        if self.parent_head.rva2off(expdesc.addressoffunctions) == None or \
                self.parent_head.rva2off(expdesc.addressofnames) == None or \
                self.parent_head.rva2off(expdesc.addressofordinals) == None:
            log.warn("export dir malformed!")
            return None, off_o
        self.dlldescname = DescName.unpack(raw, expdesc.name, self.parent_head)
        try:
            self.f_address = struct_array(self, raw,
                                          expdesc.addressoffunctions,
                                          Rva32, expdesc.numberoffunctions)
            self.f_names = struct_array(self, raw,
                                        expdesc.addressofnames,
                                        Rva32, expdesc.numberofnames)
            self.f_nameordinals = struct_array(self, raw,
                                               expdesc.addressofordinals,
                                               Ordinal, expdesc.numberofnames)
        except RuntimeError:
            log.warn("export dir malformed!")
            return None, off_o
        for func in self.f_names:
            func.name = DescName.unpack(raw, func.rva, self.parent_head)
        return expdesc, off_sav

    def sete(self, _):
        return bytes(self.expdesc)

    def build_content(self, raw):
        direxp = self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_EXPORT]
        of1 = direxp.rva
        if self.expdesc is None:  # No Export
            return
        raw[self.parent_head.rva2off(of1)] = bytes(self.expdesc)
        raw[self.parent_head.rva2off(self.expdesc.name)] = bytes(self.dlldescname)
        raw[self.parent_head.rva2off(self.expdesc.addressoffunctions)] = bytes(self.f_address)
        if self.expdesc.addressofnames != 0:
            raw[self.parent_head.rva2off(self.expdesc.addressofnames)] = bytes(self.f_names)
        if self.expdesc.addressofordinals != 0:
            raw[self.parent_head.rva2off(self.expdesc.addressofordinals)] = bytes(self.f_nameordinals)
        for func in self.f_names:
            raw[self.parent_head.rva2off(func.rva)] = bytes(func.name)

        # XXX BUG names must be alphanumeric ordered
        names = [func.name for func in self.f_names]
        names_ = names[:]
        if names != names_:
            log.warn("unsorted export names, may bug")

    def set_rva(self, rva, size=None):
        rva_size = self.parent_head._wsize // 8
        if self.expdesc is None:
            return
        self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_EXPORT].rva = rva
        if not size:
            self.parent_head.NThdr.optentries[
                DIRECTORY_ENTRY_EXPORT].size = len(self)
        else:
            self.parent_head.NThdr.optentries[
                DIRECTORY_ENTRY_EXPORT].size = size
        rva += len(self.expdesc)
        self.expdesc.name = rva
        rva += len(self.dlldescname)
        self.expdesc.addressoffunctions = rva
        rva += len(self.f_address) * 4
        self.expdesc.addressofnames = rva
        rva += len(self.f_names) * 4
        self.expdesc.addressofordinals = rva
        rva += len(self.f_nameordinals) * 2  # Ordinal size
        for func in self.f_names:
            func.rva = rva
            rva += len(func.name)

    def __len__(self):
        rva_size = self.parent_head._wsize // 8
        length = 0
        if self.expdesc is None:
            return length
        length += len(self.expdesc)
        length += len(self.dlldescname)
        length += len(self.f_address) * 4
        length += len(self.f_names) * 4
        length += len(self.f_nameordinals) * 2  # Ordinal size
        for entry in self.f_names:
            length += len(entry.name)
        return length

    def __repr__(self):
        rep = ["<%s>" % self.__class__.__name__]
        if self.expdesc is None:
            return "\n".join(rep)

        rep = ["<%s %d (%s) %s>" % (self.__class__.__name__,
                                    self.expdesc.numberoffunctions, self.dlldescname, repr(self.expdesc))]
        tmp_names = [[] for _ in range(self.expdesc.numberoffunctions)]
        for i, entry in enumerate(self.f_names):
            tmp_names[self.f_nameordinals[i].ordinal].append(entry.name)
        for i, entry in enumerate(self.f_address):
            tmpn = []
            if not entry.rva:
                continue
            out = "%2d %.8X %s" % (i + self.expdesc.base, entry.rva, repr(tmp_names[i]))
            rep.append(out)
        return "\n".join(rep)

    def create(self, name='default.dll'):
        self.expdesc = ExpDesc_e(self.parent_head)
        for attr in ["characteristics",
                     "timestamp",
                     "majorv",
                     "minorv",
                     "name",
                     "base",
                     "numberoffunctions",
                     "numberofnames",
                     "addressoffunctions",
                     "addressofnames",
                     "addressofordinals",
                     ]:
            setattr(self.expdesc, attr, 0)

        self.dlldescname = DescName(self.parent_head)
        self.dlldescname.name = name
        self.f_address = struct_array(self, None,
                                      None,
                                      Rva32)
        self.f_names = struct_array(self, None,
                                    None,
                                    Rva32)
        self.f_nameordinals = struct_array(self, None,
                                           None,
                                           Ordinal)
        self.expdesc.base = 1

    def add_name(self, name, rva=0xdeadc0fe, ordinal=None):
        if self.expdesc is None:
            return
        names = [func.name.name for func in self.f_names]
        names_s = names[:]
        names_s.sort()
        if names_s != names:
            log.warn('tab names was not sorted may bug')
        names.append(name)
        names.sort()
        index = names.index(name)
        descname = DescName(self.parent_head)

        descname.name = name
        wname = Rva32(self.parent_head)

        wname.name = descname
        woffset = Rva32(self.parent_head)
        woffset.rva = rva
        wordinal = Ordinal(self.parent_head)
        # func is append to list
        if ordinal is None:
            wordinal.ordinal = len(self.f_address)
        else:
            wordinal.ordinal = ordinal

        self.f_address.append(woffset)
        # self.f_names.insert(index, wname)
        # self.f_nameordinals.insert(index, wordinal)
        self.f_names.insert(index, wname)
        self.f_nameordinals.insert(index, wordinal)
        self.expdesc.numberofnames += 1
        self.expdesc.numberoffunctions += 1

    def get_funcrva(self, f_str):
        if self.expdesc is None:
            return None
        for i, entry in enumerate(self.f_names):
            if f_str != entry.name.name:
                continue
            ordinal = self.f_nameordinals[i].ordinal
            rva = self.f_address[ordinal].rva
            return rva
        return None

    def get_funcvirt(self, addr):
        rva = self.get_funcrva(addr)
        if rva == None:
            return
        return self.parent_head.rva2virt(rva)


class Delaydesc_e(CStruct):
    _fields = [("attrs", "u32"),
               ("name", "u32"),
               ("hmod", "u32"),
               ("firstthunk", "u32"),
               ("originalfirstthunk", "u32"),
               ("boundiat", "u32"),
               ("unloadiat", "u32"),
               ("timestamp", "u32"),
               ]


class DirDelay(CStruct):
    _fields = [("delaydesc", (lambda c, raw, off:c.gete(raw, off),
                              lambda c, value:c.sete(value)))]

    def gete(self, raw, off):
        if not off:
            return None, off

        ofend = off + \
            self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_DELAY_IMPORT].size
        out = []
        while off < ofend:
            if off >= len(raw):
                log.warn('warning bad reloc offset')
                break

            delaydesc, length = Delaydesc_e.unpack_l(raw,
                                                     off,
                                                     self.parent_head)
            if raw[off:off+length] == b'\x00' * length:
                # Special case
                break
            off += length
            out.append(delaydesc)

        if self.parent_head._wsize == 32:
            mask_ptr = 0x80000000
        elif self.parent_head._wsize == 64:
            mask_ptr = 0x8000000000000000

        parent = self.parent_head
        for entry in out:
            isfromva = (entry.attrs & 1) == 0
            if isfromva:
                isfromva = lambda x: parent.virt2rva(x)
            else:
                isfromva = lambda x: x
            entry.dlldescname = DescName.unpack(raw, isfromva(entry.name),
                                                self.parent_head)
            if entry.originalfirstthunk:
                addr = isfromva(entry.originalfirstthunk)
                if not 0 <= addr < len(raw):
                    log.warning("Bad delay")
                    break
                entry.originalfirstthunks = struct_array(self, raw,
                                                         addr,
                                                         Rva)
            else:
                entry.originalfirstthunks = None

            if entry.firstthunk:
                entry.firstthunks = struct_array(self, raw,
                                                 isfromva(entry.firstthunk),
                                                 Rva)
            else:
                entry.firstthunk = None

            entry.impbynames = []
            if entry.originalfirstthunk and self.parent_head.rva2off(isfromva(entry.originalfirstthunk)):
                tmp_thunk = entry.originalfirstthunks
            elif entry.firstthunk:
                tmp_thunk = entry.firstthunks
            else:
                print(ValueError("no thunk in delay dir!! "))
                return
            for i in range(len(tmp_thunk)):
                if tmp_thunk[i].rva & mask_ptr == 0:
                    imp = ImportByName.unpack(raw,
                                              isfromva(tmp_thunk[i].rva),
                                              self.parent_head)
                    entry.impbynames.append(imp)
                else:
                    entry.impbynames.append(
                        isfromva(tmp_thunk[i].rva & (mask_ptr - 1)))
                    # print(repr(entry[-1]))
                    # raise ValueError('XXX to check')
        return out, off

    def sete(self, entries):
        return b"".join(bytes(entry) for entry in entries) + b"\x00" * (4 * 8)  # DelayDesc_e

    def __len__(self):
        rva_size = self.parent_head._wsize // 8
        length = (len(self.delaydesc) + 1) * (4 * 8)  # DelayDesc_e
        for entry in self.delaydesc:
            length += len(entry.dlldescname)
            if entry.originalfirstthunk and self.parent_head.rva2off(entry.originalfirstthunk):
                length += (len(entry.originalfirstthunks) + 1) * rva_size
            if entry.firstthunk:
                length += (len(entry.firstthunks) + 1) * rva_size
            for imp in entry.impbynames:
                if isinstance(imp, ImportByName):
                    length += len(imp)
        return length

    def set_rva(self, rva, size=None):
        rva_size = self.parent_head._wsize // 8
        self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_DELAY_IMPORT].rva = rva
        if not size:
            self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_DELAY_IMPORT].size = len(self)
        else:
            self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_DELAY_IMPORT].size = size
        rva += (len(self.delaydesc) + 1) * (4 * 8)  # DelayDesc_e
        parent = self.parent_head
        for entry in self.delaydesc:
            isfromva = (entry.attrs & 1) == 0
            if isfromva:
                isfromva = lambda x: self.parent_head.rva2virt(x)
            else:
                isfromva = lambda x: x

            entry.name = isfromva(rva)
            rva += len(entry.dlldescname)
            if entry.originalfirstthunk:  # and self.parent_head.rva2off(entry.originalfirstthunk):
                entry.originalfirstthunk = isfromva(rva)
                rva += (len(entry.originalfirstthunks) + 1) * rva_size
            # XXX rva fthunk not patched => fun addr
            # if entry.firstthunk:
            #    entry.firstthunk = rva
            #    rva+=(len(entry.firstthunks)+1)*pe.Rva._size
            if entry.originalfirstthunk and self.parent_head.rva2off(entry.originalfirstthunk):
                tmp_thunk = entry.originalfirstthunks
            elif entry.firstthunk:
                tmp_thunk = entry.firstthunks
            else:
                raise RuntimeError("No thunk!")
            for i, imp in enumerate(entry.impbynames):
                if isinstance(imp, ImportByName):
                    tmp_thunk[i].rva = isfromva(rva)
                    rva += len(imp)

    def build_content(self, raw):
        if len(self.parent_head.NThdr.optentries) < DIRECTORY_ENTRY_DELAY_IMPORT:
            return
        dirdelay = self.parent_head.NThdr.optentries[
            DIRECTORY_ENTRY_DELAY_IMPORT]
        of1 = dirdelay.rva
        if not of1:  # No Delay Import
            return
        raw[self.parent_head.rva2off(of1)] = bytes(self)
        for entry in self.delaydesc:
            raw[self.parent_head.rva2off(entry.name)] = bytes(entry.dlldescname)
            if entry.originalfirstthunk and self.parent_head.rva2off(entry.originalfirstthunk):
                raw[self.parent_head.rva2off(entry.originalfirstthunk)] = bytes(entry.originalfirstthunks)
            if entry.firstthunk:
                raw[self.parent_head.rva2off(entry.firstthunk)] = bytes(entry.firstthunks)
            if entry.originalfirstthunk and self.parent_head.rva2off(entry.originalfirstthunk):
                tmp_thunk = entry.originalfirstthunks
            elif entry.firstthunk:
                tmp_thunk = entry.firstthunks
            else:
                raise RuntimeError("No thunk!")
            for j, imp in enumerate(entry.impbynames):
                if isinstance(imp, ImportByName):
                    raw[self.parent_head.rva2off(tmp_thunk[j].rva)] = bytes(imp)

    def __repr__(self):
        rep = ["<%s>" % self.__class__.__name__]
        for i, entry in enumerate(self.delaydesc):
            out = "%2d %-25s %s" % (i, repr(entry.dlldescname), repr(entry))
            rep.append(out)
            for index, func in enumerate(entry.impbynames):
                out = "    %2d %-16s" % (index, repr(func))
                rep.append(out)
        return "\n".join(rep)

    def add_dlldesc(self, new_dll):
        if self.parent_head._wsize == 32:
            mask_ptr = 0x80000000
        elif self.parent_head._wsize == 64:
            mask_ptr = 0x8000000000000000
        new_impdesc = []
        of1 = None
        new_delaydesc = []
        for import_descriptor, new_functions in new_dll:
            if isinstance(import_descriptor.get("name"), str):
                import_descriptor["name"] = import_descriptor["name"].encode()
            new_functions = [
                funcname.encode() if isinstance(funcname, str) else funcname
                for funcname in new_functions
            ]
            for attr in ["attrs", "name", "hmod", "firstthunk", "originalfirstthunk", "boundiat", "unloadiat", "timestamp"]:
                if not attr in import_descriptor:
                    import_descriptor[attr] = 0
            entry = Delaydesc_e(self.parent_head, **import_descriptor)
            # entry.cstr.__dict__.update(import_descriptor)
            if entry.firstthunk != None:
                of1 = entry.firstthunk
            elif of1 == None:
                raise RuntimeError("set fthunk")
            else:
                entry.firstthunk = of1
            entry.dlldescname = DescName(self.parent_head, name=entry.name)
            entry.originalfirstthunk = 0
            entry.originalfirstthunks = struct_array(self, None,
                                                     None,
                                                     Rva)
            entry.firstthunks = struct_array(self, None,
                                             None,
                                             Rva)

            impbynames = []
            for new_function in new_functions:
                rva_ofirstt = Rva(self.parent_head)
                if isinstance(new_function, int_types):
                    rva_ofirstt.rva = mask_ptr + new_function
                    ibn = None
                elif isinstance(new_function, bytes):
                    rva_ofirstt.rva = True
                    ibn = ImportByName(self.parent_head)
                    ibn.name = new_function
                    ibn.hint = 0
                else:
                    raise RuntimeError('unknown func type %s' % new_function)
                impbynames.append(ibn)
                entry.originalfirstthunks.append(rva_ofirstt)

                rva_func = Rva(self.parent_head)
                if ibn != None:
                    rva_func.rva = 0xDEADBEEF  # default func addr
                else:
                    # ord ?XXX?
                    rva_func.rva = rva_ofirstt.rva
                entry.firstthunks.append(rva_func)
                of1 += 4
            # for null thunk
            of1 += 4
            entry.impbynames = impbynames
            new_delaydesc.append(entry)
        if self.delaydesc is None:
            self.delaydesc = struct_array(self, None,
                                          None,
                                          Delaydesc_e)
            self.delaydesc.l = new_delaydesc
        else:
            for entry in new_delaydesc:
                self.delaydesc.append(entry)

    def get_funcrva(self, func):
        for entry in self.delaydesc:
            isfromva = (entry.attrs & 1) == 0
            if isfromva:
                isfromva = lambda x: self.parent_head.virt2rva(x)
            else:
                isfromva = lambda x: x
            if entry.originalfirstthunk and self.parent_head.rva2off(isfromva(entry.originalfirstthunk)):
                tmp_thunk = entry.originalfirstthunks
            elif entry.firstthunk:
                tmp_thunk = entry.firstthunks
            else:
                raise RuntimeError("No thunk!")
            if isinstance(func, bytes):
                for j, imp in enumerate(entry.impbynames):
                    if isinstance(imp, ImportByName):
                        if func == imp.name:
                            return isfromva(entry.firstthunk) + j * 4
            elif isinstance(func, int_types):
                for j, imp in enumerate(entry.impbynames):
                    if not isinstance(imp, ImportByName):
                        if isfromva(tmp_thunk[j].rva & 0x7FFFFFFF) == func:
                            return isfromva(entry.firstthunk) + j * 4
            else:
                raise ValueError('unknown func type %r' % func)

    def get_funcvirt(self, addr):
        rva = self.get_funcrva(addr)
        if rva == None:
            return
        return self.parent_head.rva2virt(rva)


class Rel(CStruct):
    _fields = [("rva", "u32"),
               ("size", "u32")
               ]


class Reloc(CStruct):
    _fields = [("rel", (lambda c, raw, off:c.gete(raw, off),
                        lambda c, value:c.sete(value)))]

    def gete(self, raw, off):
        rel = struct.unpack('H', raw[off:off + 2])[0]
        return (rel >> 12, rel & 0xfff), off + 2

    def sete(self, value):
        return struct.pack('H', (value[0] << 12) | value[1])

    def __repr__(self):
        return '<%d %d>' % (self.rel[0], self.rel[1])


class DirReloc(CStruct):
    _fields = [("reldesc", (lambda c, raw, off:c.gete(raw, off),
                            lambda c, value:c.sete(value)))]

    def gete(self, raw, off):
        if not off:
            return None, off

        ofend = off + \
            self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_BASERELOC].size
        out = []
        while off < ofend:
            if off >= len(raw):
                log.warn('warning bad reloc offset')
                break
            reldesc, length = Rel.unpack_l(raw,
                                           off,
                                           self.parent_head)
            if reldesc.size == 0:
                log.warn('warning null reldesc')
                reldesc.size = length
                break
            of2 = off + length
            if of2 + reldesc.size > len(self.parent_head.img_rva):
                log.warn('relocation too big, skipping')
                break
            reldesc.rels = struct_array(self, raw,
                                        of2,
                                        Reloc,
                                        (reldesc.size - length) // 2)  # / Reloc size
            reldesc.patchrel = False
            out.append(reldesc)
            off += reldesc.size
        return out, off

    def sete(self, entries):
        return b"".join(
            bytes(entry) + bytes(entry.rels)
            for entry in entries
        )

    def set_rva(self, rva, size=None):
        if self.reldesc is None:
            return
        self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_BASERELOC].rva = rva
        if not size:
            self.parent_head.NThdr.optentries[
                DIRECTORY_ENTRY_BASERELOC].size = len(self)
        else:
            self.parent_head.NThdr.optentries[
                DIRECTORY_ENTRY_BASERELOC].size = size

    def build_content(self, raw):
        dirrel = self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_BASERELOC]
        dirrel.size = len(self)
        of1 = dirrel.rva
        if self.reldesc is None:  # No Reloc
            return
        raw[self.parent_head.rva2off(of1)] = bytes(self)

    def __len__(self):
        if self.reldesc is None:
            return 0
        length = 0
        for entry in self.reldesc:
            length += entry.size
        return length

    def __bytes__(self):
        return b"".join(
            bytes(entry) + bytes(entry.rels)
            for entry in self.reldesc
        )

    def __str__(self):
        if PY3:
            return repr(self)
        return self.__bytes__()

    def __repr__(self):
        rep = ["<%s>" % self.__class__.__name__]
        if self.reldesc is None:
            return "\n".join(rep)
        for i, entry in enumerate(self.reldesc):
            out = "%2d %s" % (i, repr(entry))
            rep.append(out)
            """
            #display too many lines...
            for ii, m in enumerate(entry.rels):
                l = "\t%2d %s"%(ii, repr(m) )
                rep.append(l)
            """
            out = "\t%2d rels..." % (len(entry.rels))
            rep.append(out)
        return "\n".join(rep)

    def add_reloc(self, rels, rtype=3, patchrel=True):
        dirrel = self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_BASERELOC]
        if not rels:
            return
        rels.sort()
        all_base_ad = set([x & 0xFFFFF000 for x in rels])
        all_base_ad = list(all_base_ad)
        all_base_ad.sort()
        rels_by_base = defaultdict(list)
        while rels:
            reloc = rels.pop()
            if reloc >= all_base_ad[-1]:
                rels_by_base[all_base_ad[-1]].append(reloc)
            else:
                all_base_ad.pop()
                rels_by_base[all_base_ad[-1]].append(reloc)
        rels_by_base = [x for x in list(rels_by_base.items())]
        rels_by_base.sort()
        for o_init, rels in rels_by_base:
            # o_init = rels[0]&0xFFFFF000
            offsets = struct_array(self, None, None, Reloc, 0)
            for reloc_value in rels:
                if (reloc_value & 0xFFFFF000) != o_init:
                    raise RuntimeError("relocs must be in same range")
                reloc = Reloc(self.parent_head)
                reloc.rel = (rtype, reloc_value - o_init)
                offsets.append(reloc)
            while len(offsets) & 3:
                reloc = Reloc(self.parent_head)
                reloc.rel = (0, 0)
                offsets.append(reloc)
            reldesc = Rel(self.parent_head)  # Reloc(self.parent_head)
            reldesc.rva = o_init
            reldesc.size = (len(offsets) * 2 + 8)
            reldesc.rels = offsets
            reldesc.patchrel = patchrel
            # if self.reldesc is None:
            #    self.reldesc = []
            self.reldesc.append(reldesc)
            dirrel.size += reldesc.size

    def del_reloc(self, taboffset):
        if self.reldesc is None:
            return
        for rel in self.reldesc:
            of1 = rel.rva
            i = 0
            while i < len(rel.rels):
                reloc = rel.rels[i]
                if reloc.rel[0] != 0 and reloc.rel[1] + of1 in taboffset:
                    print('del reloc', hex(reloc.rel[1] + of1))
                    del rel.rels[i]
                    rel.size -= Reloc._size
                else:
                    i += 1


class DirRes(CStruct):
    _fields = [("resdesc", (lambda c, raw, off:c.gete(raw, off),
                            lambda c, value:c.sete(value)))]

    def gete(self, raw, off):
        if not off:
            return None, off
        if off >= len(self.parent_head.img_rva):
            log.warning('cannot parse resources, %X' % off)
            return None, off

        off_orig = off
        ofend = off + self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_RESOURCE].size

        resdesc, length = ResDesc_e.unpack_l(raw,
                                             off,
                                             self.parent_head)
        off += length
        nbr = resdesc.numberofnamedentries + resdesc.numberofidentries

        out = []
        tmp_off = off
        resdesc.resentries = struct_array(self, raw,
                                          off,
                                          ResEntry,
                                          nbr)
        dir_todo = {off_orig: resdesc}
        dir_done = {}
        while dir_todo:
            off, my_dir = dir_todo.popitem()
            dir_done[off] = my_dir
            for entry in my_dir.resentries:
                off = entry.offsettosubdir
                if not off:
                    # data dir
                    off = entry.offsettodata
                    if not 0 <= off < len(raw):
                        log.warn('bad resource entry')
                        continue
                    data = ResDataEntry.unpack(raw,
                                               off,
                                               self.parent_head)
                    off = data.offsettodata
                    data.s = StrPatchwork(raw[off:off + data.size])
                    entry.data = data
                    continue
                # subdir
                if off in dir_done:
                    log.warn('warning recusif subdir')
                    continue
                if not 0 <= off < len(self.parent_head.img_rva):
                    log.warn('bad resource entry')
                    continue
                subdir, length = ResDesc_e.unpack_l(raw,
                                                    off,
                                                    self.parent_head)
                nbr = subdir.numberofnamedentries + subdir.numberofidentries
                try:
                    subdir.resentries = struct_array(self, raw,
                                                     off + length,
                                                     ResEntry,
                                                     nbr)
                except RuntimeError:
                    log.warn('bad resource entry')
                    continue

                entry.subdir = subdir
                dir_todo[off] = entry.subdir
        return resdesc, off

    def build_content(self, raw):
        if self.resdesc is None:
            return
        of1 = self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_RESOURCE].rva
        raw[self.parent_head.rva2off(of1)] = bytes(self.resdesc)
        length = len(self.resdesc)
        dir_todo = {
            self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_RESOURCE].rva: self.resdesc
        }
        of1 = of1 + length
        raw[self.parent_head.rva2off(of1)] = bytes(self.resdesc.resentries)
        dir_done = {}
        while dir_todo:
            of1, my_dir = dir_todo.popitem()
            dir_done[of1] = my_dir
            raw[self.parent_head.rva2off(of1)] = bytes(my_dir)
            of1 += len(my_dir)
            raw[self.parent_head.rva2off(of1)] = bytes(my_dir.resentries)
            of_base = of1
            for entry in my_dir.resentries:
                of_base += len(entry)
                if entry.name_s:
                    raw[self.parent_head.rva2off(entry.name)] = bytes(entry.name_s)
                of1 = entry.offsettosubdir
                if not of1:
                    raw[self.parent_head.rva2off(entry.offsettodata)] = bytes(entry.data)
                    raw[self.parent_head.rva2off(entry.data.offsettodata)] = bytes(entry.data.s)
                    continue
                dir_todo[of1] = entry.subdir

    def __len__(self):
        length = 0
        if self.resdesc is None:
            return length
        dir_todo = [self.resdesc]
        dir_done = []
        while dir_todo:
            my_dir = dir_todo.pop()
            if my_dir in dir_done:
                raise ValueError('Recursive directory')
            dir_done.append(my_dir)
            length += len(my_dir)
            length += len(my_dir.resentries) * 8  # ResEntry size
            for entry in my_dir.resentries:
                if not entry.offsettosubdir:
                    continue
                if not entry.subdir in dir_todo:
                    dir_todo.append(entry.subdir)
                else:
                    raise RuntimeError("recursive dir")

        dir_todo = dir_done
        while dir_todo:
            my_dir = dir_todo.pop()
            for entry in my_dir.resentries:
                if entry.name_s:
                    length += len(entry.name_s)
                of1 = entry.offsettosubdir
                if not of1:
                    length += 4 * 4  # WResDataEntry size
                    # XXX because rva may be even rounded
                    length += 1
                    length += entry.data.size
                    continue
        return length

    def set_rva(self, rva, size=None):
        if self.resdesc is None:
            return
        self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_RESOURCE].rva = rva
        if not size:
            self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_RESOURCE].size = len(self)
        else:
            self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_RESOURCE].size = size
        dir_todo = [self.resdesc]
        dir_done = {}
        while dir_todo:
            my_dir = dir_todo.pop()
            dir_done[rva] = my_dir
            rva += len(my_dir)
            rva += len(my_dir.resentries) * 8  # ResEntry size
            for entry in my_dir.resentries:
                if not entry.offsettosubdir:
                    continue
                if not entry.subdir in dir_todo:
                    dir_todo.append(entry.subdir)
                else:
                    raise RuntimeError("recursive dir")
        dir_todo = dir_done
        dir_inv = dict([(x[1], x[0]) for x in list(dir_todo.items())])
        while dir_todo:
            rva_tmp, my_dir = dir_todo.popitem()
            for entry in my_dir.resentries:
                if entry.name_s:
                    entry.name = rva
                    rva += len(entry.name_s)
                of1 = entry.offsettosubdir
                if not of1:
                    entry.offsettodata = rva
                    rva += 4 * 4  # ResDataEntry size
                    # XXX menu rsrc must be even aligned?
                    if rva % 2:
                        rva += 1
                    entry.data.offsettodata = rva
                    rva += entry.data.size
                    continue
                entry.offsettosubdir = dir_inv[entry.subdir]

    def __repr__(self):
        rep = ["<%s>" % (self.__class__.__name__)]
        if self.resdesc is None:
            return "\n".join(rep)
        dir_todo = [self.resdesc]
        resources = []
        index = -1
        while dir_todo:
            entry = dir_todo.pop(0)
            if isinstance(entry, int):
                index += entry
            elif isinstance(entry, ResDesc_e):
                # resources.append((index, repr(entry)))
                dir_todo = [1] + entry.resentries.l + [-1] + dir_todo
            elif isinstance(entry, ResEntry):
                if entry.offsettosubdir:
                    resources.append((index, repr(entry)))
                    dir_todo = [entry.subdir] + dir_todo
                else:
                    resources.append((index, repr(entry)))
            else:
                raise RuntimeError("zarb")
        for i, resource in resources:
            rep.append(' ' * 4 * i + resource)
        return "\n".join(rep)


class Ordinal(CStruct):
    _fields = [("ordinal", "u16"),
               ]


class ResDesc_e(CStruct):
    _fields = [("characteristics", "u32"),
               ("timestamp", "u32"),
               ("majorv", "u16"),
               ("minorv", "u16"),
               ("numberofnamedentries", "u16"),
               ("numberofidentries", "u16")
               ]


class SUnicode(CStruct):
    _fields = [("length", "u16"),
               ("value", (lambda c, raw, off:c.gets(raw, off),
                          lambda c, value:c.sets(value)))
               ]

    def gets(self, raw, off):
        value = raw[off:off + self.length * 2]
        return value, off + self.length

    def sets(self, value):
        return self.value


class ResEntry(CStruct):
    _fields = [("name", (lambda c, raw, off:c._get_name(raw, off),
                         lambda c, value:c._set_name(value))),
               ("offsettodata", (lambda c, raw, off:c._get_offset(raw, off),
                                 lambda c, value:c._set_offset(value)))
               ]

    def _get_name(self, raw, off):
        self.data = None
        # off = self.parent_head.rva2off(off)
        name = struct.unpack('I', raw[off:off + 4])[0]
        self.name_s = None
        if name & 0x80000000:
            name = (name & 0x7FFFFFFF) + self.parent_head.NThdr.optentries[
                DIRECTORY_ENTRY_RESOURCE].rva  # XXX res rva??
            name &= 0x7FFFFFFF
            if name >= len(raw):
                raise RuntimeError("Bad resentry")
            self.name_s = SUnicode.unpack(raw,
                                          name,
                                          self.parent_head)
        return name, off + 4

    def _set_name(self, name):
        if self.name_s:
            rva = self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_RESOURCE].rva
            name = (self.name - rva) + 0x80000000
        return struct.pack('I', name)

    def _get_offset(self, raw, off):
        self.offsettosubdir = None
        rva = self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_RESOURCE].rva
        offsettodata_o = struct.unpack('I', raw[off:off + 4])[0]
        offsettodata = (offsettodata_o & 0x7FFFFFFF) + rva  # XXX res rva??
        if offsettodata_o & 0x80000000:
            self.offsettosubdir = offsettodata
        return offsettodata, off + 4

    def _set_offset(self, offset):
        rva = self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_RESOURCE].rva
        offsettodata = offset - rva
        if self.offsettosubdir:
            offsettodata = (self.offsettosubdir - rva) + 0x80000000
        return struct.pack('I', offsettodata)

    def __repr__(self):
        if self.name_s:
            nameid = "%s" % repr(self.name_s)
        else:
            if self.name in RT:  # and not self.offsettosubdir:
                nameid = "ID %s" % RT[self.name]
            else:
                nameid = "ID %d" % self.name
        if self.offsettosubdir:
            offsettodata = "subdir: %x" % self.offsettosubdir
        else:
            offsettodata = "data: %x" % self.offsettodata
        return "<%s %s>" % (nameid, offsettodata)


class ResDataEntry(CStruct):
    _fields = [("offsettodata", "u32"),
               ("size", "u32"),
               ("codepage", "u32"),
               ("reserved", "u32"),
               ]


class Symb(CStruct):
    _fields = [("name", "8s"),
               ("res1", "u32"),
               ("res2", "u32"),
               ("res3", "u16")]


class DirTls(CStruct):
    _fields = [
        ("data_start", "ptr"),
        ("data_end", "ptr"),
        ("addr_index", "ptr"),
        ("callbacks", "ptr"),
        ("size_of_zero", "u32"),
        ("characteristics", "u32")
    ]

    def build_content(self, raw):
        dirtls = self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_TLS]
        of1 = dirtls.rva
        if of1 is None:  # No Tls
            return
        raw[self.parent_head.rva2off(of1)] = bytes(self)

    def set_rva(self, rva, size=None):
        self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_TLS].rva = rva
        if not size:
            self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_TLS].size = len(self)
        else:
            self.parent_head.NThdr.optentries[DIRECTORY_ENTRY_TLS].size = size


DIRECTORY_ENTRY_EXPORT = 0
DIRECTORY_ENTRY_IMPORT = 1
DIRECTORY_ENTRY_RESOURCE = 2
DIRECTORY_ENTRY_EXCEPTION = 3
DIRECTORY_ENTRY_SECURITY = 4
DIRECTORY_ENTRY_BASERELOC = 5
DIRECTORY_ENTRY_DEBUG = 6
DIRECTORY_ENTRY_COPYRIGHT = 7
DIRECTORY_ENTRY_GLOBALPTR = 8
DIRECTORY_ENTRY_TLS = 9
DIRECTORY_ENTRY_LOAD_CONFIG = 10
DIRECTORY_ENTRY_BOUND_IMPORT = 11
DIRECTORY_ENTRY_IAT = 12
DIRECTORY_ENTRY_DELAY_IMPORT = 13
DIRECTORY_ENTRY_COM_DESCRIPTOR = 14
DIRECTORY_ENTRY_RESERVED = 15


RT_CURSOR = 1
RT_BITMAP = 2
RT_ICON = 3
RT_MENU = 4
RT_DIALOG = 5
RT_STRING = 6
RT_FONTDIR = 7
RT_FONT = 8
RT_ACCELERATOR = 9
RT_RCDATA = 10
RT_MESSAGETABLE = 11
RT_GROUP_CURSOR = 12
RT_GROUP_ICON = 14
RT_VERSION = 16
RT_DLGINCLUDE = 17
RT_PLUGPLAY = 19
RT_VXD = 20
RT_ANICURSOR = 21
RT_ANIICON = 22
RT_HTML = 23
RT_MANIFEST = 24


RT = {
    RT_CURSOR: "RT_CURSOR",
    RT_BITMAP: "RT_BITMAP",
    RT_ICON: "RT_ICON",
    RT_MENU: "RT_MENU",
    RT_DIALOG: "RT_DIALOG",
    RT_STRING: "RT_STRING",
    RT_FONTDIR: "RT_FONTDIR",
    RT_FONT: "RT_FONT",
    RT_ACCELERATOR: "RT_ACCELERATOR",
    RT_RCDATA: "RT_RCDATA",
    RT_MESSAGETABLE: "RT_MESSAGETABLE",
    RT_GROUP_CURSOR: "RT_GROUP_CURSOR",
    RT_GROUP_ICON: "RT_GROUP_ICON",
    RT_VERSION: "RT_VERSION",
    RT_DLGINCLUDE: "RT_DLGINCLUDE",
    RT_PLUGPLAY: "RT_PLUGPLAY",
    RT_VXD: "RT_VXD",
    RT_ANICURSOR: "RT_ANICURSOR",
    RT_ANIICON: "RT_ANIICON",
    RT_HTML: "RT_HTML",
    RT_MANIFEST: "RT_MANIFEST",
}