home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

日記/2010/05/31/PEファイルのインポート情報を表示するPython(作りかけ)

日記/2010/05/31/PEファイルのインポート情報を表示するPython(作りかけ)

日記 / 2010 / 05 / 31 / PEファイルのインポート情報を表示するPython(作りかけ)
id: 666 所有者: msakamoto-sf    作成日: 2010-06-01 21:29:02
カテゴリ: Python Windows 

日記/2010/05/31/PEファイルをメモリ上に「仮」ロードするPython(作りかけ) の続き。

とりあえずインポート情報("Import Directory" Data Directory)を元にIAT(Import Address Table)とINT(Import Name Table)を取り出すことが出来た。序数でimportしたのとか、"Bound Import Directory"やDelayed DLL Loading使ってるのは未対応。

IATをダンプ・・・といっても、OSにロードされていないPEファイルの状態では、アドレステーブルの内容はINTのコピーになっているので見ても面白くない。

さすがに解説を加えないと(後で自分自身が)理解不能になりつつあるので、ソースを載せた後、ポイントとなる部分だけ解説しておく。


ソース全体

show_imports.py:

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)
 
 
# }}}
 
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)
 
import_directory_entry = opt_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
import_directory_rva = import_directory_entry.VirtualAddress
import_directory_head = image_buf_ptr + import_directory_rva
 
# build IMAGE_IMPORT_DESCRIPTOR array
import_descriptors = []
sz = ctypes.sizeof(IMAGE_IMPORT_DESCRIPTOR)
offset = 0
while 1:
  import_descriptor = IMAGE_IMPORT_DESCRIPTOR()
  ctypes.memmove(
      ctypes.addressof(import_descriptor), 
      import_directory_head + offset,
      sz)
  offset = offset + sz
  if 0 == import_descriptor.FirstThunk:
    break
  import_descriptors.append(import_descriptor)
 
# enumerate IMAGE_IMPORT_DESCRIPTOR array
for d in import_descriptors:
  print "------------"
  print "OriginalFirstThunk(INT) RVA = %04X" % d.Misc.OriginalFirstThunk
  print "TimeDateStamp = %04X" % d.TimeDateStamp
  print "ForwarderChain = %04X" % d.ForwarderChain
  module_name = ctypes.c_char_p(image_buf_ptr + d.Name)
  print "Name = %s [RVA=%04X]" % (module_name.value, d.Name)
  print "FirstThunk(IAT) RVA = %04X" % d.FirstThunk
  # enumerate FirstThunk records
  ft_thunk_head = image_buf_ptr + d.FirstThunk
  ft_thunk_sz = ctypes.sizeof(IMAGE_THUNK_DATA)
  ft_thunks = []
  ft_thunk_offset = 0
  while 1:
    ft_thunk = IMAGE_THUNK_DATA()
    ctypes.memmove(
        ctypes.addressof(ft_thunk),
        ft_thunk_head + ft_thunk_offset,
        ft_thunk_sz)
    ft_thunk_offset = ft_thunk_offset + ft_thunk_sz
    if 0 == ft_thunk.Ordinal :
      break
    ft_thunks.append(ft_thunk)
  for ft_thunk in ft_thunks:
    print "FirstThunk: %08X" % ft_thunk.Ordinal
  # enumerate OriginalFirstThunk records
  if 0 != d.Misc.OriginalFirstThunk :
    oft_thunk_head = image_buf_ptr + d.Misc.OriginalFirstThunk
    oft_thunk_sz = ctypes.sizeof(IMAGE_THUNK_DATA)
    oft_thunks = []
    oft_thunk_offset = 0
    while 1:
      oft_thunk = IMAGE_THUNK_DATA()
      ctypes.memmove(
          ctypes.addressof(oft_thunk),
          oft_thunk_head + oft_thunk_offset,
          oft_thunk_sz)
      oft_thunk_offset = oft_thunk_offset + oft_thunk_sz
      if 0 == oft_thunk.Ordinal :
        break
      oft_thunks.append(oft_thunk)
    for oft_thunk in oft_thunks:
      print "OriginalFirstThunk: %08X" % oft_thunk.Ordinal
      hint_data = IMAGE_IMPORT_BY_NAME()
      hint_data_sz = ctypes.sizeof(IMAGE_IMPORT_BY_NAME)
      ctypes.memmove(
          ctypes.addressof(hint_data),
          image_buf_ptr + oft_thunk.AddressOfData,
          hint_data_sz)
      print "\tHint: %04X" % hint_data.Hint
      hint_data_name_ptr = image_buf_ptr + \
          oft_thunk.AddressOfData + \
          ctypes.sizeof(WORD)
      hint_data_name = ctypes.c_char_p(hint_data_name_ptr)
      print "\tName: %s" % hint_data_name.value

IMAGE_OPTIONAL_HEADER の DataDirectory からIMAGE_IMPORT_DESCRIPTOR の配列を抽出

メモリ上にOSロード時と同様のレイアウトでヘッダー・セクションデータを読み込んだ後、IMAGE_OPTIONAL_HEADERのDataDirectory[]のIMAGE_DIRECTORY_ENTRY_IMPORTを取得する。

import_directory_entry = opt_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
import_directory_rva = import_directory_entry.VirtualAddress
# image_buf_ptrがIMAGE_OPTIONAL_HEADERのImageBaseに相当
import_directory_head = image_buf_ptr + import_directory_rva
 
# build IMAGE_IMPORT_DESCRIPTOR array
import_descriptors = []
sz = ctypes.sizeof(IMAGE_IMPORT_DESCRIPTOR)
offset = 0
while 1:
  import_descriptor = IMAGE_IMPORT_DESCRIPTOR()
  ctypes.memmove(
      ctypes.addressof(import_descriptor), 
      import_directory_head + offset,
      sz)
  offset = offset + sz
  if 0 == import_descriptor.FirstThunk:
    # IMAGE_IMPORT_DESCRIPTORの全メンバが0なら終端だが、
    # FirstThunkの0チェックで同等の効果
    # (OriginalFirstThunkはINTが無い場合も0なので終端チェックには不適切)
    break
  import_descriptors.append(import_descriptor)

IMAGE_IMPORT_DESCRIPTOR の情報をダンプ

続くforブロックで、IMAGE_IMPORT_DESCRIPTOR構造体の各メンバを出力する。

# enumerate IMAGE_IMPORT_DESCRIPTOR array
for d in import_descriptors:
  print "------------"
  print "OriginalFirstThunk(INT) RVA = %04X" % d.Misc.OriginalFirstThunk
  print "TimeDateStamp = %04X" % d.TimeDateStamp
  print "ForwarderChain = %04X" % d.ForwarderChain
  module_name = ctypes.c_char_p(image_buf_ptr + d.Name)
  print "Name = %s [RVA=%04X]" % (module_name.value, d.Name)
  print "FirstThunk(IAT) RVA = %04X" % d.FirstThunk

NULL終端文字列については、このように

ctypes.c_char_p(アドレス)

でc_char_pオブジェクトを生成したら、その "value" 属性を通じてPythonの文字列で受け取ることが出来る。

IMAGE_IMPORT_DESCRIPTOR の FirstThunk から IMAGE_THUNK_DATA 配列(IAT)を取得

IMAGE_IMPORT_DESCRIPTORのFirstThunkから、ロード時に上書きされるImport Address Table(IAT)となるIMAGE_THUNK_DATA配列を取得する。

# enumerate FirstThunk records
  ft_thunk_head = image_buf_ptr + d.FirstThunk
  ft_thunk_sz = ctypes.sizeof(IMAGE_THUNK_DATA)
  ft_thunks = []
  ft_thunk_offset = 0
  while 1:
    ft_thunk = IMAGE_THUNK_DATA()
    ctypes.memmove(
        ctypes.addressof(ft_thunk),
        ft_thunk_head + ft_thunk_offset,
        ft_thunk_sz)
    ft_thunk_offset = ft_thunk_offset + ft_thunk_sz
    if 0 == ft_thunk.Ordinal :
      break
    ft_thunks.append(ft_thunk)
  for ft_thunk in ft_thunks:
    print "FirstThunk: %08X" % ft_thunk.Ordinal

PEファイルの段階では、OriginalFirstThunkと同じ内容で埋められている。OSのロードにより、実際のエキスポート関数のアドレスに上書きされる。
この場合エキスポート関数をcallする実行コードは次のようになっている:

CALL DWORD PTR DS:0x0040A114]

この"0x0040A114"がIATの1エントリのアドレスを示している。OSのロードで0x0040A114番地の値が実際のエキスポート関数のアドレスに上書きされることで、そのアドレスをCALLすることになる。

IMAGE_IMPORT_DESCRIPTOR の OriginalFirstThunk から IMAGE_THUNK_DATA 配列(INT)を取得

IMAGE_IMPORT_DESCRIPTORのOriginalFirstThunkから、インポート情報の「原本」としてロード後も変更されずに保持される Import Name Table (INT) となるIMAGE_THUNK_DATA配列を取得する。
OriginalFirstThunkが0でない場合にINTを取得・表示する。

# enumerate OriginalFirstThunk records
  if 0 != d.Misc.OriginalFirstThunk :
    oft_thunk_head = image_buf_ptr + d.Misc.OriginalFirstThunk
    oft_thunk_sz = ctypes.sizeof(IMAGE_THUNK_DATA)
    oft_thunks = []
    oft_thunk_offset = 0
    while 1:
      oft_thunk = IMAGE_THUNK_DATA()
      ctypes.memmove(
          ctypes.addressof(oft_thunk),
          oft_thunk_head + oft_thunk_offset,
          oft_thunk_sz)
      oft_thunk_offset = oft_thunk_offset + oft_thunk_sz
      if 0 == oft_thunk.Ordinal :
        break
      oft_thunks.append(oft_thunk)
    for oft_thunk in oft_thunks:
      print "OriginalFirstThunk: %08X" % oft_thunk.Ordinal
      hint_data = IMAGE_IMPORT_BY_NAME()
      hint_data_sz = ctypes.sizeof(IMAGE_IMPORT_BY_NAME)
      ctypes.memmove(
          ctypes.addressof(hint_data),
          image_buf_ptr + oft_thunk.AddressOfData,
          hint_data_sz)
      print "\tHint: %04X" % hint_data.Hint
      hint_data_name_ptr = image_buf_ptr + \
          oft_thunk.AddressOfData + \
          ctypes.sizeof(WORD)
      hint_data_name = ctypes.c_char_p(hint_data_name_ptr)
      print "\tName: %s" % hint_data_name.value

OriginalFirstThunkからIMAGE_THUNK_DATAの配列を取得する。今回はIMAGE_THUNK_DATAのAddressOfDataが関数名とHintを示すIMAGE_IMPORT_BY_NAME構造体へのRVAを指すので、各ThunkData毎にIMAGE_IMPORT_BY_NAME構造体を取り出し、表示する。
IMAGE_IMPORT_BY_NAMEのNameメンバは、直接関数名の1文字目を指している。C言語なら"&"演算子で

&(hint.Name)

としてメンバのアドレスを取り出せるところだが、Pythonのctypesでは上手い方法が見つからなかったので、泥臭いが手動でアドレスのオフセットを計算している。


プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2010-06-01 22:00:31
md5:cd1ee4b8fa8d9aea209256855f14697c
sha1:9f01e6ab1589ccc5a500787794c84e5be59495f5
コメント
コメントを投稿するにはログインして下さい。