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)。