Mach-O形式の調査~導入編~

PEフォーマットは以前調査したので、今度はApple製品で採用されているMach-O(マークオー)形式の調査をしてみたいと思います。まずは最小サンプルの実行体を用意して既存ツールでちょっと確認してみます。

#include 

int main() {
  printf( "Hello,world\n" );
  return 0;
}
#include 

int main() {
  std::cout << "Hello,world(C++)" << std::endl;
  return 0;
}

このようなサンプルを準備して、依存する他のライブラリの情報がどうなっているかを確認します。

$ otool -L sample01.out
sample01.out:
  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)

$ otool -L sample02.out 
sample02.out:
  /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 56.0.0)
  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)

Mach-O形式は Appleのサイトに仕様がきちんと公開されているのでこれを見ればよいです(Mac OS X ABI Mach-O File Format Reference)。
この情報を元に自分のプログラムコードで各情報を取得&表示ということをしばらくはやってみたいと思います。

Mach-O概要

Mach-O(マーク・オー, Mach object)はMacで採用されているアプリバイナリインターフェース(ABI)です。ファイルの構造としては先頭にヘッダ構造を持ち、ロードコマンド、データ(各セグメントやセクション情報)、と記録されています。

ヘッダ部分では、マジックナンバーと対象とするアーキテクチャ情報、後続のデータに関する個数などの情報を記録しています。
ロードコマンド部分では、後続するデータ部分の位置・構造についての情報を保持しています。
データ部分には、セグメントが記録され、この中にセクション情報が複数個入っています。セグメントはアプリケーションをロードする際に仮想メモリにどのように領域をマッピングするかといった情報を定義しています。ページ属性もここに含まれます。

先頭のヘッダ部分についての構造は以下となります。32bit,64bitで最初から構造体が違うので注意が必要です。

struct mach_header {
  uint32_t magic;
  cpu_type_t cputype;
  cpu_subtype_t cpusubtype;
  uint32_t filetype;
  uint32_t ncmds;
  uint32_t sizeOfCmds;
  uint32_t flags;
};

struct mach_header64 {
  uint32_t magic;
  cpu_type_t cputype;
  cpu_subtype_t cpusubtype;
  uint32_t filetype;
  uint32_t ncmds;
  uint32_t sizeOfCmds;
  uint32_t flags;
  uint32_t reserved;
};

これらの構造体は loader.h にて定義されています。
この cputypeメンバが CPU_TYPE_I386 や CPU_TYPE_x86_64 は Intel Macを示すとのことです。そもそも 32bit/64bitでは magicメンバがすでに違うようで、MH_MAGIC(0xfeedface) と MH_MAGIC_64(0xfeedfacf) とマジックが異なっていました。

この後続くロードコマンド部分は load_command構造体を先頭とするデータブロックの配列として記録されています。

struct load_command {
  uint32_t cmd;
  uint32_t cmdSize;
};

このcmdメンバの値に応じて後続するデータの意味が変わり、ブロックのサイズとしては cmdSize が示す物となっています。cmdの指す内容は、 LC_SEGMENT, LC_SEGMENT_64, LC_SYMTAB,... と多岐にわたります。これについては公式リファレンスの Table 4 を参照して下さい。

mach_headerの部分を読み込んで表示してみるプログラムを作りました。
実行するとこのようになります。

Mach-O 64bit
  CPU TYPE      : x86_x64
  CPU SUB TYPE  : i386 ALL(Lib64)
  FileType      : MH_EXECUTE
  Cmd(s)        : 16
  CmdSize       : 1296
  Flag(s)       : MH_NOUNDEFS MH_TWOLEVEL MH_PIE (200085)

このプログラムはWindows(VisualStudio2012)で作成したので、定義を自前で行っているためコードが長めです。Mac環境ならば、loader.hやmach/machine.h のヘッダインクルードですむところが多いです。

#include 
#include 
#include 
#include "macho_cputype.h" // mach/machine.hの代わりに用意.

#define MH_MAGIC     (0xfeedfaceU)
#define MH_MAGIC_64  (0xfeedfacfU)

// mach header. file type
#define MH_OBJECT    (0x1)
#define MH_EXECUTE	(0x2)
#define MH_FVMLIB	(0x3)
#define MH_CORE		(0x4)
#define MH_PRELOAD	(0x5)
#define MH_DYLIB	(0x6)
#define MH_DYLINKER (0x7)
#define MH_BUNDLE	(0x8)
#define MH_DYLIB_STUB	(0x9)
#define MH_DSYM		(0xA)
#define MH_KEXT_BUNDLE	(0xB)

// mach header. flags
#define MH_NOUNDEFS  (0x1)
#define MH_INCRLINK  (0x2)
#define MH_DYLDLINK  (0x4)
#define MH_BINDATLOAD (0x8)
#define MH_PREBOUND		(0x10)
#define MH_SPLIT_SEGS	(0x20)
#define MH_LAZY_INIT	(0x40)
#define MH_TWOLEVEL		(0x80)
#define MH_FORCE_FLAT	(0x100)
#define MH_NOMULTDEFS	(0x200)
#define MH_NOFIXPREBINDING	(0x400)
#define MH_PREBINDABLE	(0x800)
#define MH_ALLMODSBOUND	(0x1000)
#define MH_SUBSECTIONS_VIA_SYMBOLS	(0x2000)
#define MH_CANONICAL	(0x4000)
#define MH_WEAK_DEFINES	(0x8000)
#define MH_BINDS_TO_WEAK	(0x10000)
#define MH_ALLOW_STACK_EXECUTION	(0x20000)
#define MH_ROOT_SAFE	(0x40000)
#define MH_SETUID_SAFE	(0x80000)
#define MH_NO_REEXPORTED_DYLIBS (0x100000)
#define MH_PIE		(0x200000)
#define MH_DEAD_STRIPPABLE_DYLIB	(0x400000)
#define MH_HAS_TLV_DESCRIPTORS		(0x800000)
#define MH_NO_HEAP_EXECUTION		(0x1000000)

typedef uint32_t cpu_type_t;
typedef uint32_t cpu_subtype_t;

#include 
struct mach_header {
	uint32_t magic;
	cpu_type_t cpuType;
	cpu_subtype_t cpuSubType;
	uint32_t fileType;
	uint32_t nCmds;
	uint32_t sizeOfCmds;
	uint32_t flags;
};
struct mach_header_64 {
	uint32_t magic;
	cpu_type_t cpuType;
	cpu_subtype_t cpuSubType;
	uint32_t fileType;
	uint32_t nCmds;
	uint32_t sizeOfCmds;
	uint32_t flags;
	uint32_t reserved;
};
#include 

int main() {
	FILE* fp = fopen( "mach-o.out", "rb" );
	void* bin = NULL;
	int   size = 0;
	if( fp ) {
		fseek( fp, 0, SEEK_END );
		size = (int)ftell( fp );
		rewind(fp);
		bin = malloc( size );
		fread( bin, 1, size, fp );
		fclose( fp ); fp = 0;
	}

	uint32_t magic = *(uint32_t*)bin;
	if( magic == MH_MAGIC ) {
		readMacho32( bin, size );
	} else if( magic == MH_MAGIC_64 ) {
		readMacho64( bin, size );
	}


	if( bin ) {
		free( bin );
	}
	return 0;
}

const char* GetCPUType( uint32_t cpuType ) {
	const char* pRet = "Unknown";
	switch( cpuType ) {
	case CPU_TYPE_MC680XX: pRet = "MC680xx"; break;
	case CPU_TYPE_X86: pRet = "x86"; break;
	case CPU_TYPE_X86_64: pRet = "x86_x64"; break;
	case CPU_TYPE_POWERPC: pRet = "PowerPC"; break;
	case CPU_TYPE_POWERPC64: pRet = "PowerPC64"; break;
	case CPU_TYPE_ARM: pRet = "ARM"; break;
	default: break;
	}
	return pRet;
}
const char* GetCPUSubType( uint32_t cpuType, uint32_t cpuSubType ) {
	const char* pRet = "Unknown";
	switch( cpuType ) {
	case CPU_TYPE_MC680XX:
		switch( cpuSubType ) {
		case CPU_SUBTYPE_MC680XX_ALL:/* CPU_SUBTYPE_MC68030 */
			pRet = "MC680xx ALL"; break;
		case CPU_SUBTYPE_MC68040:
			pRet = "MC68040"; break;
		case CPU_SUBTYPE_MC68030_ONLY:
			pRet = "MC68030 ONLY"; break;
		}
		break;
	case CPU_TYPE_X86:
		/*break;*/
	case CPU_TYPE_X86_64:
		switch( cpuSubType & ~CPU_SUBTYPE_MASK) {
		case CPU_SUBTYPE_I386_ALL: /*CPU_SUBTYPE_I386*/
			pRet = "i386 ALL"; break;
		case CPU_SUBTYPE_I486:
			pRet = "i486"; break;
		case CPU_SUBTYPE_I486SX: pRet = "i486SX"; break;
		case CPU_SUBTYPE_I586:/*CPU_SUBTYPE_PENTIUM*/
			pRet = "Pentium"; break; 
		case CPU_SUBTYPE_PENTIUMPRO: pRet = "PentiumPro";break;
		case CPU_SUBTYPE_PENTIUMII_M3: pRet = "PentiumII(M3";break;
		case CPU_SUBTYPE_PENTIUMII_M5: pRet = "PentiumII(M5";break;
		case CPU_SUBTYPE_CELERON: pRet = "Celeron"; break;
		case CPU_SUBTYPE_MOBILE_CELERON: pRet = "Mobile Celeron"; break;
		case CPU_SUBTYPE_PENTIUM_3: pRet = "Pentium3"; break;
		case CPU_SUBTYPE_MOBILE_PENTIUM_3: /*CPU_SUBTYPE_PENTIUM_3_XEON*/
			pRet = "Mobile Pentium3"; break; 
		case CPU_SUBTYPE_PENTIUM_M: pRet = "Pentium M"; break;
		case CPU_SUBTYPE_PENTIUM_4: pRet = "Pentium4"; break;
		case CPU_SUBTYPE_MOBILE_PENTIUM_4: pRet = "Mobile Pentium4"; break;
		case CPU_SUBTYPE_ITANIUM: pRet = "Itanium"; break;
		case CPU_SUBTYPE_ITANIUM_2: pRet = "Itanium2"; break;
		case CPU_SUBTYPE_XEON: pRet = "XEON"; break;
		case CPU_SUBTYPE_XEON_MP: pRet = "XEON MP"; break;
		}
		break;
	case CPU_TYPE_POWERPC:
		/* break; */
	case CPU_TYPE_POWERPC64:
		switch( cpuSubType & ~CPU_SUBTYPE_MASK ) {
		case CPU_SUBTYPE_POWERPC_ALL  : pRet = "PowerPC ALL"; break;
		case CPU_SUBTYPE_POWERPC_601  : pRet = "PowerPC 601"; break;
		case CPU_SUBTYPE_POWERPC_602  : pRet = "PowerPC 602"; break;
		case CPU_SUBTYPE_POWERPC_603  : pRet = "PowerPC 603"; break;
		case CPU_SUBTYPE_POWERPC_603e : pRet = "PowerPC 603e"; break;
		case CPU_SUBTYPE_POWERPC_603ev: pRet = "PowerPC 603ev"; break;
		case CPU_SUBTYPE_POWERPC_604  : pRet = "PowerPC 604"; break;
		case CPU_SUBTYPE_POWERPC_604e : pRet = "PowerPC 604e"; break;
		case CPU_SUBTYPE_POWERPC_620  : pRet = "PowerPC 620"; break;
		case CPU_SUBTYPE_POWERPC_750  : pRet = "PowerPC 750"; break;
		case CPU_SUBTYPE_POWERPC_7400 : pRet = "PowerPC 7400"; break;
		case CPU_SUBTYPE_POWERPC_7450 : pRet = "PowerPC 7450"; break;
		case CPU_SUBTYPE_POWERPC_970  : pRet = "PowerPC 970"; break;
		}
		break;
	case CPU_TYPE_ARM:
		switch( cpuSubType & ~CPU_SUBTYPE_MASK ) {
		case CPU_SUBTYPE_ARM_ALL	: pRet = "ARM ALL"; break;
		case CPU_SUBTYPE_ARM_V4T	: pRet = "ARM V4T"; break;
		case CPU_SUBTYPE_ARM_V6 	: pRet = "ARM V6"; break;
		case CPU_SUBTYPE_ARM_V5TEJ  : pRet = "ARM V5TEJ"; break;
		case CPU_SUBTYPE_ARM_XSCALE	: pRet = "ARM XSCALE"; break;
		case CPU_SUBTYPE_ARM_V7	 : pRet = "ARM V7"; break; 
		case CPU_SUBTYPE_ARM_V7F : pRet = "ARM V7F"; break;
		case CPU_SUBTYPE_ARM_V7K : pRet = "ARM V7K"; break;
		}
		break;
	default: break;
	}
	return pRet;
}
const char* GetFileType( uint32_t fileType ) {
	const char* pRet = "Unknown";
	switch( fileType ) {
	case MH_OBJECT  : pRet = "MH_OBJECT"; break;
	case MH_EXECUTE	: pRet = "MH_EXECUTE"; break;
	case MH_FVMLIB	: pRet = "MH_FVMLIB"; break;
	case MH_CORE	: pRet = "MH_CORE"; break;
	case MH_PRELOAD	: pRet = "MH_PRELOAD"; break;
	case MH_DYLIB	: pRet = "MH_DYLIB"; break;
	case MH_DYLINKER: pRet = "MH_DYLINKER"; break;
	case MH_BUNDLE	: pRet = "MH_BUNDLE"; break;
	case MH_DYLIB_STUB: pRet = "MH_DYLIB_STUB"; break;
	case MH_DSYM : pRet = "MH_DSYM"; break;
	case MH_KEXT_BUNDLE: pRet = "MH_KEXT_BUNDLE"; break;
	}
	return pRet;
}

#define DISP_MACHO_FLAGS( X ) if( flags & X ) { printf( "%s ", #X ); }
void DispFlags( uint32_t flags ) {
	printf( "  Flag(s)\t: " );
	DISP_MACHO_FLAGS( MH_NOUNDEFS )
	DISP_MACHO_FLAGS( MH_INCRLINK )
	DISP_MACHO_FLAGS( MH_BINDATLOAD )
	DISP_MACHO_FLAGS( MH_PREBOUND )
	DISP_MACHO_FLAGS( MH_SPLIT_SEGS )
	DISP_MACHO_FLAGS( MH_LAZY_INIT )
	DISP_MACHO_FLAGS( MH_TWOLEVEL )
	DISP_MACHO_FLAGS( MH_FORCE_FLAT)
	DISP_MACHO_FLAGS( MH_NOMULTDEFS )
	DISP_MACHO_FLAGS( MH_NOFIXPREBINDING )
	DISP_MACHO_FLAGS( MH_PREBINDABLE )
	DISP_MACHO_FLAGS( MH_ALLMODSBOUND )
	DISP_MACHO_FLAGS( MH_SUBSECTIONS_VIA_SYMBOLS )
	DISP_MACHO_FLAGS( MH_CANONICAL )
	DISP_MACHO_FLAGS( MH_WEAK_DEFINES )
	DISP_MACHO_FLAGS( MH_BINDS_TO_WEAK )
	DISP_MACHO_FLAGS( MH_ALLOW_STACK_EXECUTION )
	DISP_MACHO_FLAGS( MH_ROOT_SAFE )
	DISP_MACHO_FLAGS( MH_SETUID_SAFE )
	DISP_MACHO_FLAGS( MH_NO_REEXPORTED_DYLIBS )
	DISP_MACHO_FLAGS( MH_PIE )
	DISP_MACHO_FLAGS( MH_DEAD_STRIPPABLE_DYLIB )
	DISP_MACHO_FLAGS( MH_HAS_TLV_DESCRIPTORS )
	DISP_MACHO_FLAGS( MH_NO_HEAP_EXECUTION )
	printf( "(%X)\n", flags );
}

void readMacho32( void* binary, int size ) {
	printf( "Mach-O 32bit\n" );
	mach_header* header = (mach_header*)binary;
	const char* typeCpu = GetCPUType( header->cpuType );
	const char* typeCpuSub = GetCPUSubType( header->cpuType, header->cpuSubType );
	const char* fileType = GetFileType( header->fileType );
	printf( "  CPU TYPE\t: %s\n", typeCpu );
	printf( "  CPU SUB TYPE\t: %s%s\n", typeCpuSub, (header->cpuSubType & CPU_SUBTYPE_MASK) == CPU_SUBTYPE_LIB64 ? "(Lib64)" : "" );
	printf( "  FileType\t: %s\n", fileType );
	printf( "  Cmd(s)\t: %d\n", header->nCmds );
	printf( "  CmdSize\t: %d\n", header->sizeOfCmds );
	DispFlags( header->flags );
}

void readMacho64( void* binary, int size ) {
	printf( "Mach-O 64bit\n" );
	mach_header_64* header = (mach_header_64*)binary;
	const char* typeCpu = GetCPUType( header->cpuType );
	const char* typeCpuSub = GetCPUSubType( header->cpuType, header->cpuSubType );
	const char* fileType = GetFileType( header->fileType );
	printf( "  CPU TYPE\t: %s\n", typeCpu );
	printf( "  CPU SUB TYPE\t: %s%s\n", typeCpuSub, (header->cpuSubType & CPU_SUBTYPE_MASK) == CPU_SUBTYPE_LIB64 ? "(Lib64)" : "" );
	printf( "  FileType\t: %s\n", fileType );
	printf( "  Cmd(s)\t: %d\n", header->nCmds );
	printf( "  CmdSize\t: %d\n", header->sizeOfCmds );
	DispFlags( header->flags );
}

このヘッダの後にはロードコマンドが続きます。とりあえずロードコマンドの中身のうち、後続のデータであるセクションに関する物(LC_SEGMENT,LC_SEGMENT_64)だけ表示して構造を追いかけてみます。

プログラムコードは以下のようになります。

void dispLCSegment32( segment_command* lc ) {
	printf( "LC_SEGMENT\n" );
	printf( "    %s\n", lc->segName );
	printf( "    vmaddr=0x%08x, vmsize=0x%08x\n", lc->vmaddr, lc->vmsize );
	printf( "    fileoff=0x%08x, filesize=0x%08x\n", lc->fileOff, lc->fileSize );
	printf( "    maxprot=0x%08x, initprot=0x%08x\n", lc->maxprot, lc->initprot );
	printf( "    nSect(s)=%d\n", lc->nSects );
}
void dispLCSegment64( segment_command_64* lc ) {
	printf( "LC_SEGMENT_64\n" );
	printf( "    %s\n", lc->segName );
	printf( "    vmaddr=0x%016llx, vmsize=0x%016llx\n", lc->vmaddr, lc->vmsize );
	printf( "    fileoff=0x%016llx, filesize=0x%016llx\n", lc->fileOff, lc->fileSize );
	printf( "    maxprot=0x%08x, initprot=0x%08x\n", lc->maxprot, lc->initprot );
	printf( "    nSect(s)=%d\n", lc->nSects );
}

void readLoadCommands( void* binary, int cmds ) {
	load_command* cmd = (load_command*)binary;
	printf( "Load Command(s)\n" );
	for( int i = 0; i < cmds; ++i ) {
		printf( "  [%2d] : ", i );
		switch( cmd->cmd ) {
		case LC_SEGMENT:
			dispLCSegment32( (segment_command*)cmd );
			break;
		case LC_SEGMENT_64:
			dispLCSegment64( (segment_command_64*)cmd );
			break;
		default:
			printf( "0x%x, %d\n", cmd->cmd, cmd->cmdSize );
			break;
		}
		cmd = (load_command*)( (char*)cmd + cmd->cmdSize);
	}
}

実行結果

Load Command(s)
  [ 0] : LC_SEGMENT_64
    __PAGEZERO
    vmaddr=0x0000000000000000, vmsize=0x0000000100000000
    fileoff=0x0000000000000000, filesize=0x0000000000000000
    maxprot=0x00000000, initprot=0x00000000
    nSect(s)=0
  [ 1] : LC_SEGMENT_64
    __TEXT
    vmaddr=0x0000000100000000, vmsize=0x0000000000001000
    fileoff=0x0000000000000000, filesize=0x0000000000001000
    maxprot=0x00000007, initprot=0x00000005
    nSect(s)=6
  [ 2] : LC_SEGMENT_64
    __DATA
    vmaddr=0x0000000100001000, vmsize=0x0000000000001000
    fileoff=0x0000000000001000, filesize=0x0000000000001000
    maxprot=0x00000007, initprot=0x00000003
    nSect(s)=2
  [ 3] : LC_SEGMENT_64
    __LINKEDIT
    vmaddr=0x0000000100002000, vmsize=0x0000000000001000
    fileoff=0x0000000000002000, filesize=0x0000000000000130
    maxprot=0x00000007, initprot=0x00000001
    nSect(s)=0
  [ 4] : 0x80000022, 48
  [ 5] : 0x2, 24
  [ 6] : 0xb, 80
  [ 7] : 0xe, 32
  [ 8] : 0x1b, 24
  [ 9] : 0x24, 16
  [10] : 0x2a, 16
  [11] : 0x80000028, 24
  [12] : 0xc, 56
  [13] : 0x26, 16
  [14] : 0x29, 16
  [15] : 0x2b, 16

表示させてみてわかったことは LoadCommandのセグメント種が出現した後には、さらにセクション用のデータが続く場合があるということです。これも解釈して表示するようにしてみたところ以下の結果となりました。

Load Command(s)
  [ 0] : LC_SEGMENT_64
    __PAGEZERO
    vmaddr=0x0000000000000000, vmsize=0x0000000100000000
    fileoff=0x0000000000000000, filesize=0x0000000000000000
    maxprot=0x00000000, initprot=0x00000000
    nSect(s)=0
  [ 1] : LC_SEGMENT_64
    __TEXT
    vmaddr=0x0000000100000000, vmsize=0x0000000000001000
    fileoff=0x0000000000000000, filesize=0x0000000000001000
    maxprot=0x00000007, initprot=0x00000005
    nSect(s)=6
      section[0] : __TEXT [__text]
      section[1] : __TEXT [__stubs]
      section[2] : __TEXT [__stub_helper]
      section[3] : __TEXT [__cstring]
      section[4] : __TEXT [__unwind_info]
      section[5] : __TEXT [__eh_frame]
  [ 2] : LC_SEGMENT_64
    __DATA
    vmaddr=0x0000000100001000, vmsize=0x0000000000001000
    fileoff=0x0000000000001000, filesize=0x0000000000001000
    maxprot=0x00000007, initprot=0x00000003
    nSect(s)=2
      section[0] : __DATA [__nl_symbol_ptr]
      section[1] : __DATA [__la_symbol_ptr]
  [ 3] : LC_SEGMENT_64
    __LINKEDIT
    vmaddr=0x0000000100002000, vmsize=0x0000000000001000
    fileoff=0x0000000000002000, filesize=0x0000000000000130
    maxprot=0x00000007, initprot=0x00000001
    nSect(s)=0

このセクション用のデータとは以下の構造体です.
2014/05現在、Appleのリファレンスに記載してある section_64構造体が間違っています。下記のものが正しいです(ヘッダでもこうなっているので、ドキュメントのミスでしょう)

struct section {
	char sectName[16];
	char segName[16];
	uint32_t addr;
	uint32_t size;
	uint32_t offset;
	uint32_t align;
	uint32_t reloff;
	uint32_t nreloc;
	uint32_t flags;
	uint32_t reserved1;
	uint32_t reserved2;
};
struct section_64 {
	char sectName[16];
	char segName[16];
	uint64_t addr;
	uint64_t size;
	uint32_t offset;
	uint32_t align;
	uint32_t reloff;
	uint32_t nreloc;
	uint32_t flags;
	uint32_t reserved1;
	uint32_t reserved2;
	uint32_t reserved3;
};

まとめ

PEフォーマットと比べるとシンプルな構造のように思えます。ここまでの関連性について図示してみるとこうなりました。ロードコマンドの部分もいわゆるチャンク構造のようになっています。このMach-o形式は実行体だけでなく中間形式でも使われているようです。

macho-format

データ解析 プログラミング
すらりんをフォローする
すらりん日記

コメント

タイトルとURLをコピーしました