[[666]] から少し寄り道して、エクスポート情報をダンプしてみる。 エクスポート情報は、インポート情報よりも単純な作りになっている。 + IMAGE_OPTIONAL_HEADER の DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] から IMAGE_EXPORT_DIRECTORY 構造体を取得 + IMAGE_EXPORT_DIRECTORY のAddressOfFunctions, AddressOfNames, AddressOfNameOrdinals をダンプ ++ AddressOfFunctionsは、エクスポートしたシンボルのアドレス(DWORD)の配列の先頭RVA。NumberOfFunctionsが配列の要素数。 ++ AddressOfNames, AddressOfNameOrdinalsは、エクスポートしたシンボル名と序数の配列の先頭RVA。NumberOfNamesが配列の要素数。 +++ AddressOfNamesの各要素は、シンボル名へのRVA(DWORD)になっている。 +++ AddressOfNameOrdinalsの各要素は、序数(WORD)になっている。 なおエクスポートシンボルが別のDLLに転送(forwarding)される場合は、AddressOfFunctionsの該当シンボルのRVAが実行コードのアドレスではなく、IMAGE_EXPORT_DIRECTORY構造体と同じセクション内のRVAを指し、"モジュール名.シンボル名"で転送先のDLL名とシンボル名を指定するようになっている。 #more|| #outline|| ---- * ソース全体 show_exports.py: #code|python,numbers|> import sys, logging, struct, ctypes # {{{ data structure definitions BYTE = ctypes.c_ubyte WORD = ctypes.c_ushort LONG = ctypes.c_long DWORD = ctypes.c_ulong class IMAGE_DOS_HEADER(ctypes.Structure): _fields_ = [ ('e_magic', WORD), # Magic number ('e_cblp', WORD), # Bytes on last page of file ('e_cp', WORD), # Pages in file ('e_crlc', WORD), # Relocations ('e_cparhdr', WORD), # Size of header in paragraphs ('e_minalloc', WORD), # Minimum extra paragraphs needed ('e_maxalloc', WORD), # Maximum extra paragraphs needed ('e_ss', WORD), # Initial (relative) SS value ('e_sp', WORD), # Initial SP value ('e_csum', WORD), # Checksum ('e_ip', WORD), # Initial IP value ('e_cs', WORD), # Initial (relative) CS value ('e_lfarlc', WORD), # File address of relocation table ('e_ovno', WORD), # Overlay number ('e_res', WORD * 4), # Reserved words ('e_oemid', WORD), # OEM identifier (for e_oeminfo) ('e_oeminfo', WORD), # OEM information; e_oemid specific ('e_res2', WORD * 10), # Reserved words ('e_lfanew', LONG), # File address of new exe header ] class IMAGE_FILE_HEADER(ctypes.Structure): _fields_ = [ ('Machine', WORD), ('NumberOfSections', WORD), ('TimeDateStamp', DWORD), ('PointerToSymbolTable', DWORD), ('NumberOfSymbols', DWORD), ('SizeOfOptionalHeader', WORD), ('Characteristics', WORD), ] class IMAGE_DATA_DIRECTORY(ctypes.Structure): _fields_ = [ ('VirtualAddress', DWORD), ('Size', DWORD), ] IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16 IMAGE_DIRECTORY_ENTRY_EXPORT = 0 # Export Directory IMAGE_DIRECTORY_ENTRY_IMPORT = 1 # Import Directory IMAGE_DIRECTORY_ENTRY_RESOURCE = 2 # Resource Directory IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3 # Exception Directory IMAGE_DIRECTORY_ENTRY_SECURITY = 4 # Security Directory IMAGE_DIRECTORY_ENTRY_BASERELOC = 5 # Base Relocation Table IMAGE_DIRECTORY_ENTRY_DEBUG = 6 # Debug Directory IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7 # Architecture Specific Data IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8 # RVA of GP IMAGE_DIRECTORY_ENTRY_TLS = 9 # TLS Directory IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10 # Load Configuration Directory IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11 # Bound Import Directory in headers IMAGE_DIRECTORY_ENTRY_IAT = 12 # Import Address Table IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13 # Delay Load Import Descriptors IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14 # COM Runtime descriptor class IMAGE_OPTIONAL_HEADER(ctypes.Structure): _fields_ = [ ('Magic', WORD), ('MajorLinkerVersion', BYTE), ('MinorLinkerVersion', BYTE), ('SizeOfCode', DWORD), ('SizeOfInitializedData', DWORD), ('SizeOfUninitializedData', DWORD), ('AddressOfEntryPoint', DWORD), ('BaseOfCode', DWORD), ('BaseOfData', DWORD), ('ImageBase', DWORD), ('SectionAlignment', DWORD), ('FileAlignment', DWORD), ('MajorOperatingSystemVersion', WORD), ('MinorOperatingSystemVersion', WORD), ('MajorImageVersion', WORD), ('MinorImageVersion', WORD), ('MajorSubsystemVersion', WORD), ('MinorSubsystemVersion', WORD), ('Win32VersionValue', DWORD), ('SizeOfImage', DWORD), ('SizeOfHeaders', DWORD), ('CheckSum', DWORD), ('Subsystem', WORD), ('DllCharacteristics', WORD), ('SizeOfStackReserve', DWORD), ('SizeOfStackCommit', DWORD), ('SizeOfHeapReserve', DWORD), ('SizeOfHeapCommit', DWORD), ('LoaderFlags', DWORD), ('NumberOfRvaAndSizes', DWORD), ('DataDirectory', IMAGE_DATA_DIRECTORY * IMAGE_NUMBEROF_DIRECTORY_ENTRIES ), ] class IMAGE_NT_HEADERS(ctypes.Structure): _fields_ = [ ('Signature', DWORD), ('FileHeader', IMAGE_FILE_HEADER), ('OptionalHeader', IMAGE_OPTIONAL_HEADER), ] IMAGE_SIZEOF_SHORT_NAME = 8 class IMAGE_SECTION_HEADER_MISC(ctypes.Union): _fields_ = [ ('PhysicalAddress', DWORD), ('VirtualSize', DWORD), ] class IMAGE_SECTION_HEADER(ctypes.Structure): _fields_ = [ ('Name', BYTE * IMAGE_SIZEOF_SHORT_NAME), ('Misc', IMAGE_SECTION_HEADER_MISC), ('VirtualAddress', DWORD), ('SizeOfRawData', DWORD), ('PointerToRawData', DWORD), ('PointerToRelocations', DWORD), ('PointerToLinenumbers', DWORD), ('NumberOfRelocations', WORD), ('NumberOfLinenumbers', WORD), ('Characteristics', DWORD), ] class IMAGE_IMPORT_DESCRIPTOR_MISC(ctypes.Union): _fields_ = [ # 0 for terminating null import descriptor ('Characteristics', DWORD), # RVA to original unbound IAT (PIMAGE_THUNK_DATA) ('OriginalFirstThunk', DWORD), ] class IMAGE_IMPORT_DESCRIPTOR(ctypes.Structure): _fields_ = [ ('Misc', IMAGE_IMPORT_DESCRIPTOR_MISC), # 0 if not bound, # -1 if bound, and real date/time stamp # in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) # O.W. date/time stamp of DLL bound to (Old BIND) ('TimeDateStamp', DWORD), ('ForwarderChain', DWORD), # -1 if no forwarders ('Name', DWORD), # RVA to IAT (if bound this IAT has actual addresses) ('FirstThunk', DWORD), ] class IMAGE_IMPORT_BY_NAME(ctypes.Structure): _fields_ = [ ('Hint', WORD), ('Name', BYTE), ] class IMAGE_THUNK_DATA(ctypes.Union): _fields_ = [ ('ForwarderString', DWORD), # PBYTE ('Function', DWORD), # PDWORD ('Ordinal', DWORD), ('AddressOfData', DWORD), # PIMAGE_IMPORT_BY_NAME ] IMAGE_ORDINAL_FLAG64 = 0x8000000000000000 IMAGE_ORDINAL_FLAG32 = 0x80000000 def IMAGE_ORDINAL64(Ordinal): return (Ordinal & 0xffff) def IMAGE_ORDINAL32(Ordinal): return (Ordinal & 0xffff) def IMAGE_SNAP_BY_ORDINAL64(Ordinal): return ((Ordinal & IMAGE_ORDINAL_FLAG64) != 0) def IMAGE_SNAP_BY_ORDINAL32(Ordinal): return ((Ordinal & IMAGE_ORDINAL_FLAG32) != 0) class IMAGE_EXPORT_DIRECTORY(ctypes.Structure): _fields_ = [ ('Characteristics', DWORD), ('TimeDateStamp', DWORD), ('MajorVersion', WORD), ('MinorVersion', WORD), ('Name', DWORD), ('Base', DWORD), ('NumberOfFunctions', DWORD), ('NumberOfNames', DWORD), ('AddressOfFunctions', DWORD), ('AddressOfNames', DWORD), ('AddressOfNameOrdinals', DWORD), ] # }}} if 2 != len(sys.argv): print 'usage: python %s filename' % sys.argv[0] quit() file_name = sys.argv[1] logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(name)s %(levelname)s %(message)s', ) log = logging.getLogger('main') try: f = open(file_name, 'rb') except IOError, e: log.error("open(%s) faileed." % file_name) log.error(e) raise e # read IMAGE_DOS_HEADER f.seek(0, 0) sz = ctypes.sizeof(IMAGE_DOS_HEADER) data = f.read(sz) dos_header = IMAGE_DOS_HEADER() fit = min(len(data), sz) ctypes.memmove(ctypes.addressof(dos_header), data, fit) # read IMAGE_NT_HEADERS f.seek(dos_header.e_lfanew, 0) sz = ctypes.sizeof(IMAGE_NT_HEADERS) data = f.read(sz) pe_header = IMAGE_NT_HEADERS() fit = min(len(data), sz) ctypes.memmove(ctypes.addressof(pe_header), data, fit) file_header = pe_header.FileHeader opt_header = pe_header.OptionalHeader # enumerate IMAGE_SECTION_HEADER array section_headers = [] section_header_sz = ctypes.sizeof(IMAGE_SECTION_HEADER) for i in range(file_header.NumberOfSections): data = f.read(section_header_sz) section_header = IMAGE_SECTION_HEADER() fit = min(len(data), sz) ctypes.memmove(ctypes.addressof(section_header), data, fit) section_headers.append(section_header) # load to memory f.seek(0, 0) file_bytes = f.read() # read all file contents image_buf = ctypes.create_string_buffer(opt_header.SizeOfImage) ctypes.memset(image_buf, 0, opt_header.SizeOfImage) image_buf_ptr = ctypes.addressof(image_buf) # copy headers ctypes.memmove( image_buf_ptr, file_bytes, opt_header.SizeOfHeaders ) # copy section datas for h in section_headers: section_virtual_addr = h.VirtualAddress pointer_to_rawdata = h.PointerToRawData size_of_rawdata = h.SizeOfRawData if pointer_to_rawdata : ctypes.memmove( image_buf_ptr + section_virtual_addr, file_bytes[pointer_to_rawdata:pointer_to_rawdata + size_of_rawdata], size_of_rawdata) export_directory_entry = opt_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] export_directory_rva = export_directory_entry.VirtualAddress export_directory_head = image_buf_ptr + export_directory_rva export_directory = IMAGE_EXPORT_DIRECTORY() ctypes.memmove( ctypes.addressof(export_directory), export_directory_head, ctypes.sizeof(IMAGE_EXPORT_DIRECTORY)) print "IMAGE_EXPORT_DIRECTORY:" print "Characteristics = %04X" % export_directory.Characteristics print "TimeDateStamp = %04X" % export_directory.TimeDateStamp print "MajorVersion = %02X" % export_directory.MajorVersion print "MinorVersion = %02X" % export_directory.MinorVersion name = ctypes.c_char_p(image_buf_ptr + export_directory.Name) print "Name = %s [RVA=%04X]" % (name.value, export_directory.Name) print "Base = %04X" % export_directory.Base print "NumberOfFunctions = %04X" % export_directory.NumberOfFunctions print "NumberOfNames = %04X" % export_directory.NumberOfNames print "AddressOfFunctions RVA = %04X" % export_directory.AddressOfFunctions print "AddressOfNames RVA = %04X" % export_directory.AddressOfNames print "AddressOfNameOrdinals RVA = %04X" % export_directory.AddressOfNameOrdinals def is_forwarded(sheads, ed_rva, func_rva): for sh in sheads: rva_min = sh.VirtualAddress rva_max = sh.VirtualAddress + sh.Misc.VirtualSize - 1 if rva_min <= ed_rva and ed_rva <= rva_max: if rva_min <= func_rva and func_rva <= rva_max: return True return False print "-----------------" print "Functions:" function_rva_sz = ctypes.sizeof(DWORD) offset = image_buf_ptr + export_directory.AddressOfFunctions for i in xrange(export_directory.NumberOfFunctions): function_rva = DWORD() ctypes.memmove( ctypes.addressof(function_rva), offset, function_rva_sz) offset = offset + function_rva_sz print "\tFunction[%d] RVA = %08X" % (i, function_rva.value), if is_forwarded( section_headers, export_directory_rva, function_rva.value): fname = ctypes.c_char_p(image_buf_ptr + function_rva.value) print " Forwarded To : '%s'" % fname.value print print "-----------------" print "Names:" name_rva_sz = ctypes.sizeof(DWORD) offset = image_buf_ptr + export_directory.AddressOfNames for i in xrange(export_directory.NumberOfNames): name_rva = DWORD() ctypes.memmove( ctypes.addressof(name_rva), offset, name_rva_sz) offset = offset + name_rva_sz name = ctypes.c_char_p(image_buf_ptr + name_rva.value) print "\tName[%d] = %s [RVA=%04X]" % (i, name.value, name_rva.value) print "-----------------" print "Ordinals:" ordinal_sz = ctypes.sizeof(WORD) offset = image_buf_ptr + export_directory.AddressOfNameOrdinals for i in xrange(export_directory.NumberOfNames): ordinal = WORD() ctypes.memmove( ctypes.addressof(ordinal), offset, ordinal_sz) offset = offset + ordinal_sz print "\tOrdinal[%d] RAW=%d, +BASE=%d" % ( i, ordinal.value, ordinal.value + export_directory.Base) ||< * 解説 ソースコードの275行目まではIMAGE_EXPORT_DIRECTORYの定義が加わっている(185行目)以外は [[666]] と同様。 それ以降について簡単に紹介する。 ** IMAGE_OPTIONAL_HEADER の DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] から IMAGE_EXPORT_DIRECTORY 構造体を取得 この辺は見たまま。 #code|python|> export_directory_entry = opt_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] # IMAGE_EXPORT_DIRECTORY構造体のRVAを取得して・・・ export_directory_rva = export_directory_entry.VirtualAddress # ベースアドレスに加算。 export_directory_head = image_buf_ptr + export_directory_rva # ctypesモジュールのmemmoveを使ってIMAGE_EXPORT_DIRECTORYとしてメモリイメージをコピー export_directory = IMAGE_EXPORT_DIRECTORY() ctypes.memmove( ctypes.addressof(export_directory), export_directory_head, ctypes.sizeof(IMAGE_EXPORT_DIRECTORY)) # あとは各メンバを16進ダンプ表示。 print "IMAGE_EXPORT_DIRECTORY:" print "Characteristics = %04X" % export_directory.Characteristics print "TimeDateStamp = %04X" % export_directory.TimeDateStamp print "MajorVersion = %02X" % export_directory.MajorVersion print "MinorVersion = %02X" % export_directory.MinorVersion name = ctypes.c_char_p(image_buf_ptr + export_directory.Name) print "Name = %s [RVA=%04X]" % (name.value, export_directory.Name) print "Base = %04X" % export_directory.Base print "NumberOfFunctions = %04X" % export_directory.NumberOfFunctions print "NumberOfNames = %04X" % export_directory.NumberOfNames print "AddressOfFunctions RVA = %04X" % export_directory.AddressOfFunctions print "AddressOfNames RVA = %04X" % export_directory.AddressOfNames print "AddressOfNameOrdinals RVA = %04X" % export_directory.AddressOfNameOrdinals ||< ** Export Address Table(EAT) をダンプする まず、EAT内の各RVAについて実際のシンボルアドレスなのか、転送のためIMAGE_EXPORT_DIRECTORYと同じセクション内なのかを判別する関数を用意する: #code|python|> # sheadsはsection_headers, ed_rvaはIMAGE_EXPORT_DIRECTORY構造体のRVA, func_rvaはシンボルのRVA def is_forwarded(sheads, ed_rva, func_rva): for sh in sheads: # セクションのRVAの範囲(min, max)を計算する。 rva_min = sh.VirtualAddress rva_max = sh.VirtualAddress + sh.Misc.VirtualSize - 1 if rva_min <= ed_rva and ed_rva <= rva_max: if rva_min <= func_rva and func_rva <= rva_max: # ed_rvaとfunc_rvaが両方範囲内なら、同じセクションにあるので転送有りと判断 return True return False ||< 実際にEATを調べる。この辺は [[666]] に至まで頻出した定型パターンで、要素(今回はDWORD)の型分だけoffsetを増加していき、ctypesモジュールのmemmove()でメモリイメージをコピーしていく。 #code|python|> print "-----------------" print "Functions:" function_rva_sz = ctypes.sizeof(DWORD) offset = image_buf_ptr + export_directory.AddressOfFunctions for i in xrange(export_directory.NumberOfFunctions): function_rva = DWORD() ctypes.memmove( ctypes.addressof(function_rva), offset, function_rva_sz) offset = offset + function_rva_sz print "\tFunction[%d] RVA = %08X" % (i, function_rva.value), if is_forwarded( section_headers, export_directory_rva, function_rva.value): # 転送有りなら、RVAを文字列ポインタとして読み込み、表示 fname = ctypes.c_char_p(image_buf_ptr + function_rva.value) print " Forwarded To : '%s'" % fname.value print ||< ** Export Name Table (ENT) をダンプする IMAGE_EXPORT_DIRECTORYのAddressOfNamesのRVAから、EATと同様にDWORDとして配列要素を辿っていく。 #code|python|> print "-----------------" print "Names:" name_rva_sz = ctypes.sizeof(DWORD) offset = image_buf_ptr + export_directory.AddressOfNames for i in xrange(export_directory.NumberOfNames): name_rva = DWORD() ctypes.memmove( ctypes.addressof(name_rva), offset, name_rva_sz) offset = offset + name_rva_sz name = ctypes.c_char_p(image_buf_ptr + name_rva.value) print "\tName[%d] = %s [RVA=%04X]" % (i, name.value, name_rva.value) ||< 序数テーブルの方も、AddressOfNameOrdinalsから同様に辿っていく。 #code|python|> print "-----------------" print "Ordinals:" ordinal_sz = ctypes.sizeof(WORD) offset = image_buf_ptr + export_directory.AddressOfNameOrdinals for i in xrange(export_directory.NumberOfNames): ordinal = WORD() ctypes.memmove( ctypes.addressof(ordinal), offset, ordinal_sz) offset = offset + ordinal_sz print "\tOrdinal[%d] RAW=%d, +BASE=%d" % ( i, ordinal.value, ordinal.value + export_directory.Base) ||< * dumpbin の出力と比較 [[668]] でのサンプル、dll_lib.dllについてdumpbinの出力と比べてみる。 ** "dumpbin /exports" #pre||> File Type: DLL Section contains the following exports for dll_lib.dll 00000000 characteristics 4C05F0F2 time date stamp Wed Jun 02 14:49:38 2010 0.00 version 5 ordinal base 9 number of functions 7 number of names ordinal hint RVA name 9 0 00001020 bar 10 1 00001000 foo 11 2 00001040 func1 5 3 00001050 func2 7 4 00001070 func4 12 5 00001090 funcX 13 6 000010A0 funcY 6 00001060 [NONAME] 8 00001080 [NONAME] ||< ** "show_exports.py" #pre||> IMAGE_EXPORT_DIRECTORY: Characteristics = 0000 TimeDateStamp = 4C05F0F2 MajorVersion = 00 MinorVersion = 00 Name = dll_lib.dll [RVA=9B46] Base = 0005 NumberOfFunctions = 0009 NumberOfNames = 0007 AddressOfFunctions RVA = 9AF8 AddressOfNames RVA = 9B1C AddressOfNameOrdinals RVA = 9B38 ----------------- Functions: Function[0] RVA = 00001050 Function[1] RVA = 00001060 Function[2] RVA = 00001070 Function[3] RVA = 00001080 Function[4] RVA = 00001020 Function[5] RVA = 00001000 Function[6] RVA = 00001040 Function[7] RVA = 00001090 Function[8] RVA = 000010A0 ----------------- Names: Name[0] = bar [RVA=9B52] Name[1] = foo [RVA=9B56] Name[2] = func1 [RVA=9B5A] Name[3] = func2 [RVA=9B60] Name[4] = func4 [RVA=9B66] Name[5] = funcX [RVA=9B6C] Name[6] = funcY [RVA=9B72] ----------------- Ordinals: Ordinal[0] RAW=4, +BASE=9 Ordinal[1] RAW=5, +BASE=10 Ordinal[2] RAW=6, +BASE=11 Ordinal[3] RAW=0, +BASE=5 Ordinal[4] RAW=2, +BASE=7 Ordinal[5] RAW=7, +BASE=12 Ordinal[6] RAW=8, +BASE=13 ||< ** 比較結果 show_exports.pyの結果から、ENT, 序数テーブルについてはシンボル名の昇順、EATについては対応する序数の昇順に並んでいるように思われる。 序数テーブルの要素数は、ENTと同じ数しか用意されていない。つまり序数のみでエクスポートしたシンボルについては、ENTや序数テーブルには記録されない。 dumpbinの出力結果を見ると、序数のみのエクスポートシンボルについては空いている序数を自動的に振られているようだ。 例えば"func1"というシンボルのアドレスを取得したい場合は・・・ + ENTを探索して、"func1"が3番目に見つかる。 + 序数テーブルの3番目は、RAWデータで"6"(Ordinal Baseを加算すると11)、つまりEAT[6]。 + EAT[6]をみると、RVAで 0x00001040 となり、これは"dumpbin /exports"の出力結果と一致する。 序数"13"からシンボルのアドレスを取得したい場合は・・・ + 13からOrdinal Baseの5を引くと "8"、つまりEAT[8]。 + EAT[8]をみると、RVAで 0x000010A0 となり、これは"dumpbin /exports"の出力結果と一致する。 序数"5"の場合はOrdinal Baseを引くと0, つまりEAT[0]になり、0x00001050 となりこれも"dumpbin /exports"と一致する。 show_exports.pyの場合は「シンボル名<>序数<>アドレス」の対応付けまでは行っていない。"dumpbin /exports"であれば対応付けが済んだ一覧を表示してくれるので、実際はdumpbinの方が便利だろう。