日記/2010/05/31/PEファイルのインポート情報を表示するPython(作りかけ) から少し寄り道して、エクスポート情報をダンプしてみる。
エクスポート情報は、インポート情報よりも単純な作りになっている。
- 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名とシンボル名を指定するようになっている。
ソース全体
show_exports.py:
- import sys, logging, struct, ctypes
-
-
-
- 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),
- ('e_cblp', WORD),
- ('e_cp', WORD),
- ('e_crlc', WORD),
- ('e_cparhdr', WORD),
- ('e_minalloc', WORD),
- ('e_maxalloc', WORD),
- ('e_ss', WORD),
- ('e_sp', WORD),
- ('e_csum', WORD),
- ('e_ip', WORD),
- ('e_cs', WORD),
- ('e_lfarlc', WORD),
- ('e_ovno', WORD),
- ('e_res', WORD * 4),
- ('e_oemid', WORD),
- ('e_oeminfo', WORD),
- ('e_res2', WORD * 10),
- ('e_lfanew', LONG),
- ]
-
- 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
- IMAGE_DIRECTORY_ENTRY_IMPORT = 1
- IMAGE_DIRECTORY_ENTRY_RESOURCE = 2
- IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3
- IMAGE_DIRECTORY_ENTRY_SECURITY = 4
- IMAGE_DIRECTORY_ENTRY_BASERELOC = 5
- IMAGE_DIRECTORY_ENTRY_DEBUG = 6
- IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7
- IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8
- IMAGE_DIRECTORY_ENTRY_TLS = 9
- IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10
- IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11
- IMAGE_DIRECTORY_ENTRY_IAT = 12
- IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13
- IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14
-
-
- 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_ = [
-
- ('Characteristics', DWORD),
-
- ('OriginalFirstThunk', DWORD),
- ]
-
- class IMAGE_IMPORT_DESCRIPTOR(ctypes.Structure):
- _fields_ = [
- ('Misc', IMAGE_IMPORT_DESCRIPTOR_MISC),
-
-
-
-
- ('TimeDateStamp', DWORD),
- ('ForwarderChain', DWORD),
- ('Name', DWORD),
-
- ('FirstThunk', DWORD),
- ]
-
-
- class IMAGE_IMPORT_BY_NAME(ctypes.Structure):
- _fields_ = [
- ('Hint', WORD),
- ('Name', BYTE),
- ]
-
- class IMAGE_THUNK_DATA(ctypes.Union):
- _fields_ = [
- ('ForwarderString', DWORD),
- ('Function', DWORD),
- ('Ordinal', DWORD),
- ('AddressOfData', DWORD),
- ]
-
- 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
-
-
-
- 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)
-
-
-
- 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
-
-
- 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)
-
-
- f.seek(0, 0)
- file_bytes = f.read()
- image_buf = ctypes.create_string_buffer(opt_header.SizeOfImage)
- ctypes.memset(image_buf, 0, opt_header.SizeOfImage)
- image_buf_ptr = ctypes.addressof(image_buf)
-
-
- ctypes.memmove(
- image_buf_ptr,
- file_bytes,
- opt_header.SizeOfHeaders
- )
-
-
- 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行目)以外は 日記/2010/05/31/PEファイルのインポート情報を表示するPython(作りかけ) と同様。
それ以降について簡単に紹介する。
IMAGE_OPTIONAL_HEADER の DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] から IMAGE_EXPORT_DIRECTORY 構造体を取得
この辺は見たまま。
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
Export Address Table(EAT) をダンプする
まず、EAT内の各RVAについて実際のシンボルアドレスなのか、転送のためIMAGE_EXPORT_DIRECTORYと同じセクション内なのかを判別する関数を用意する:
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
実際にEATを調べる。この辺は 日記/2010/05/31/PEファイルのインポート情報を表示するPython(作りかけ) に至まで頻出した定型パターンで、要素(今回はDWORD)の型分だけoffsetを増加していき、ctypesモジュールのmemmove()でメモリイメージをコピーしていく。
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
Export Name Table (ENT) をダンプする
IMAGE_EXPORT_DIRECTORYのAddressOfNamesのRVAから、EATと同様にDWORDとして配列要素を辿っていく。
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から同様に辿っていく。
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 の出力と比較
C言語系/memos/VC++/05, モジュール定義ファイル(".DEF")とDLLのエクスポート でのサンプル、dll_lib.dllについてdumpbinの出力と比べてみる。
"dumpbin /exports"
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"
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の方が便利だろう。
プレーンテキスト形式でダウンロード
コメント