guest@blog.cmj.tw: ~/posts $

MachO 102


Still reversing the binary

簡單的增加 MachO-64 的資訊:利用 LC_SYMTAB 讓產生出來的 Mach-O 64 的檔案具有 symbol 資訊

#include <mach-o/loader.h>
struct symtab_command {
    uint32_t	cmd;		/* LC_SYMTAB */
    uint32_t	cmdsize;	/* sizeof(struct symtab_command) */
    uint32_t	symoff;		/* symbol table offset */
    uint32_t	nsyms;		/* number of symbol table entries */
    uint32_t	stroff;		/* string table offset */
    uint32_t	strsize;	/* string table size in bytes */
};

#include <mach-o/nlist.h>
/*
 * This is the symbol table entry structure for 64-bit architectures.
 */
struct nlist_64 {
    union {
        uint32_t  n_strx; /* index into the string table */
    } n_un;
    uint8_t n_type;        /* type flag, see below */
    uint8_t n_sect;        /* section number or NO_SECT */
    uint16_t n_desc;       /* see <mach-o/stab.h> */
    uint64_t n_value;      /* value of this symbol (or stab offset) */
};

在 Mach-O 64 的結構上來說,主要是利用 struct symtab_command 來記錄 SYMTAB 的資料:開頭的兩個資料, 依然屬於基本的 Commands 的資料,接著儲存 struct nlist_64 的 offset (檔案的絕對位址) 以及數量。 接著記錄整個 string table 以及整個 string table 的數量。

而整個 SYMTAB 的重點在於 nlist_64 的資料內容:一開始的 n_strx 紀錄 SYMBOL 位於 strings table 的 OFFSET (相對位址),而後續的 n_type、n_sect、n_desc 則是用來標注這個 SYMBOL 的特性。最後的 n_value 則是用來註明這個 SYMBOL 的內容。

詳細的 SYMBOL 可以從 Apple Man Page 或者是他的 header 看到:下面是節錄的內容

/*
 * Symbolic debugger symbols.  The comments give the conventional use for
 *
 * 	.stabs "n_name", n_type, n_sect, n_desc, n_value
 *
 * where n_type is the defined constant and not listed in the comment.  Other
 * fields not listed are zero. n_sect is the section ordinal the entry is
 * refering to.
 */
#define	N_GSYM	0x20	/* global symbol: name,,NO_SECT,type,0 */
#define	N_FNAME	0x22	/* procedure name (f77 kludge): name,,NO_SECT,0,0 */
#define	N_FUN	0x24	/* procedure: name,,n_sect,linenumber,address */
#define	N_STSYM	0x26	/* static symbol: name,,n_sect,type,address */
#define	N_LCSYM	0x28	/* .lcomm symbol: name,,n_sect,type,address */
#define N_BNSYM 0x2e	/* begin nsect sym: 0,,n_sect,0,address */
#define N_OPT	0x3c	/* emitted with gcc2_compiled and in gcc source */
#define	N_RSYM	0x40	/* register sym: name,,NO_SECT,type,register */
#define	N_SLINE	0x44	/* src line: 0,,n_sect,linenumber,address */
#define N_ENSYM 0x4e	/* end nsect sym: 0,,n_sect,0,address */
#define	N_SSYM	0x60	/* structure elt: name,,NO_SECT,type,struct_offset */
#define	N_SO	0x64	/* source file name: name,,n_sect,0,address */
#define	N_OSO	0x66	/* object file name: name,,0,0,st_mtime */
#define	N_LSYM	0x80	/* local sym: name,,NO_SECT,type,offset */
#define N_BINCL	0x82	/* include file beginning: name,,NO_SECT,0,sum */
#define	N_SOL	0x84	/* #included file name: name,,n_sect,0,address */
#define	N_PARAMS  0x86	/* compiler parameters: name,,NO_SECT,0,0 */
#define	N_VERSION 0x88	/* compiler version: name,,NO_SECT,0,0 */
#define	N_OLEVEL  0x8A	/* compiler -O level: name,,NO_SECT,0,0 */
#define	N_PSYM	0xa0	/* parameter: name,,NO_SECT,type,offset */
#define N_EINCL	0xa2	/* include file end: name,,NO_SECT,0,0 */
#define	N_ENTRY	0xa4	/* alternate entry: name,,n_sect,linenumber,address */
#define	N_LBRAC	0xc0	/* left bracket: 0,,NO_SECT,nesting level,address */
#define N_EXCL	0xc2	/* deleted include file: name,,NO_SECT,0,sum */
#define	N_RBRAC	0xe0	/* right bracket: 0,,NO_SECT,nesting level,address */
#define	N_BCOMM	0xe2	/* begin common: name,,NO_SECT,0,0 */
#define	N_ECOMM	0xe4	/* end common: name,,n_sect,0,0 */
#define	N_ECOML	0xe8	/* end common (local name): 0,,n_sect,0,address */
#define	N_LENG	0xfe	/* second stab entry with length information */

ELF 的 Symbol Table

同場加映 ELF 的 Symbol Table 如何建置:首先因為 ELF 的檔案在這個部分的格式比較嚴格, 所以使用 strace 來偷吃步。可以看到一開始先利用 lseek 來將已經開啟檔案的重新定位到開頭: 注意的是他利用兩次 lseek 來確定是否真的定位到檔案的開頭。接下來讀取前面的 16 bytes 來確定是否為合法的 ELF 檔案:這時候讀取到他是個 ELF 64 的檔案格式,所以再回到開頭讀取前面 64 bytes 的擋頭。

lseek(3, 0, SEEK_CUR)                   = 696
lseek(3, 0, SEEK_SET)                   = 0
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0", 16) = 16
lseek(3, 0, SEEK_SET)                   = 0
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\2\0>\0\1\0\0\0\200\0@\0\0\0\0\0"..., 64) = 64
lseek(3, 440, SEEK_SET)                 = 440
read(3, "\33\0\0\0\1\0\0\0\6\0\0\0\0\0\0\0\200\0@\0\0\0\0\0\200\0\0\0\0\0\0\0"..., 256) = 256
lseek(3, 133, SEEK_SET)                 = 133
read(3, "\0.symtab\0.strtab\0.shstrtab\0.text"..., 33) = 33
lseek(3, 440, SEEK_SET)                 = 440
read(3, "\33\0\0\0\1\0\0\0\6\0\0\0\0\0\0\0\200\0@\0\0\0\0\0\200\0\0\0\0\0\0\0"..., 256) = 256
lseek(3, 133, SEEK_SET)                 = 133
read(3, "\0.symtab\0.strtab\0.shstrtab\0.text"..., 33) = 33
lseek(3, 696, SEEK_SET)                 = 696

在這個時候就可以讀到 Elf64_Ehdr 的資訊,而在這個例子中,可以知道 ELF Section Header 的開頭在於 0x400 的位址,並且擁有 6 個 sections。所以再利用 lseek 移到檔案開頭的 0x400 的位址,並知道需要讀取 6 個 section header 的大小 (也就是 256 bytes),接下來搜尋當中關於第一個屬於 SHT_STRTAB 的 section header 。其中儲存了 string table 的內容,這個內容包含每個 section 的名稱資訊。

這就是第一階段 ELF 如何利用 string table 的方式。下面就是第一階段的結論:

/* CHEAT - Generate the string table for ELF section name */

char shstrtab[] = "\x00.shstrtab\x00.symtab\x00.strtab\x00";
Elf64_Ehdr	hdr;
Elf64_Shdr shdrs[] = {
    {
        .sh_name		= 0x0,
        .sh_type		= SHT_NULL,
        .sh_flags		= 0x0,
        .sh_addr		= 0x0,
        .sh_offset		= 0x0,
        .sh_size		= 0x0,
        .sh_link		= 0x0,
        .sh_info		= 0x0,
        .sh_addralign	= 0x0,
        .sh_entsize		= 0x0,
    }, { 	/* .symtab */
        .sh_name		= 0x9,
        .sh_type		= SHT_SYMTAB,
        .sh_flags		= 0x0,
        .sh_addr		= 0x0,
        .sh_offset		= 0x0,
        .sh_size		= 0x0,
        .sh_link		= 0x2,
        .sh_info		= 0x2,
        .sh_addralign	= 0x8,
        .sh_entsize		= 0x0,
    }, { 	/* .strtab */
        .sh_name		= 0x1,
        .sh_type		= SHT_STRTAB,
        .sh_flags		= 0x0,
        .sh_addr		= 0x0,
        .sh_offset		= 0x0,
        .sh_size		= 0x0,
        .sh_link		= 0x0,
        .sh_info		= 0x0,
        .sh_addralign	= 0x1,
        .sh_entsize		= 0x0,
    }
};

nrSym ++;	/* HACK - First symbol is empty for nm */
shdrs[1].sh_offset	= offset + sizeof(Elf64_Shdr) * (sizeof(shdrs)/sizeof(Elf64_Shdr));
shdrs[1].sh_size	= sizeof(Elf64_Sym) * nrSym;
shdrs[1].sh_entsize	= sizeof(Elf64_Sym);

/* HACK - Update strtab */
shdrs[2].sh_offset	= shdrs[1].sh_offset + shdrs[1].sh_size;
shdrs[2].sh_size	= sizeof(shstrtab) + strtablen;

在這個 case 下,我們故意在第一個 section 建構一個 NULL 的 section,接下來兩個才是我們主要想要儲存的資訊: 包含了 symbol table (也就是我們想要紀錄的資訊),以及每個 symbol 代表的 string。在上述的例子中,每個 section 的名稱也是從某個 SHT_STRTAB 中拿到的,而從哪個 section 拿到則是在一開始的 Elf64_Ehdr 中的 e_shstrndx 儲存。在每個 section 都有個 sh_name 來記錄 section name 在所屬 string table 的 offset。

Elf64_Ehdr	hdr;
hdr.e_shstrndx		= 2;	/* Which SHT_SYMTAB contains the string used on section */

接著在 .symtab 這個 section 是用來儲存 SHT_SYMTAB:也就是儲存每個 symbol 的資訊。在這個 section 會紀錄真正的 Elf64_Sym 存放在哪個地方 (也就是 sh_offset),並且相關的 SHT_STRTAB 是位在哪個 section (利用 sh_link 以及 sh_info)。