about summary refs log tree commit diff stats
path: root/src/miasm/loader/pe.py
diff options
context:
space:
mode:
authorTheofilos Augoustis <theofilos.augoustis@gmail.com>2025-10-14 09:09:29 +0000
committerTheofilos Augoustis <theofilos.augoustis@gmail.com>2025-10-14 09:09:29 +0000
commit579cf1d03fb932083e6317967d1613d5c2587fb6 (patch)
tree629f039935382a2a7391bce9253f6c9968159049 /src/miasm/loader/pe.py
parent51c15d3ea2e16d4fc5f0f01a3b9befc66b1f982e (diff)
downloadfocaccia-miasm-ta/nix.tar.gz
focaccia-miasm-ta/nix.zip
Convert to src-layout ta/nix
Diffstat (limited to 'src/miasm/loader/pe.py')
-rw-r--r--src/miasm/loader/pe.py1693
1 files changed, 1693 insertions, 0 deletions
diff --git a/src/miasm/loader/pe.py b/src/miasm/loader/pe.py
new file mode 100644
index 00000000..1252e70e
--- /dev/null
+++ b/src/miasm/loader/pe.py
@@ -0,0 +1,1693 @@
+#! /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",
+}