ククログ

株式会社クリアコード > ククログ > Mach-Oから公開されている関数名を抜き出す

Mach-Oから公開されている関数名を抜き出す

ELFから公開されている関数名を抜き出すのMach-O版です。ただし、Universal Binaryには対応していません。

Mach-Oのフォーマットの詳細はMac OS X ABI Mach-O File Format Reference(英語)を見てください。

下準備

簡略化のためファイルの内容をすべてメモリに読み込んでから処理します。コツコツ資源を利用したい場合は少しづつ読み込みながら処理することになります。

ファイルの内容を読み込むにはGLibのg_file_get_contents()が便利です。

gchar *content;
gsize length;

g_file_get_contents(filename, &content, &length, NULL);

これで、contentの中にファイルの内容が格納されました。これを使って公開されている関数名を抜き出します。

Mach-Oのフォーマットに関する情報はmach-o/loader.hで定義されています。また、シンボルテーブルの各エントリのフォーマットに関する情報はmach-o/nlist.hで定義されています。Mach-Oをパースするときはこれらのヘッダファイルを使うと便利です。ここでもこれらのヘッダファイルを使います。

#include <mach-o/loader.h>
#include <mach-o/nlist.h>

Mach-Oかどうかを判断

まず、ファイルがMach-Oかどうかを判断します。

Mach-Oは最初にヘッダが入っていて、それを見ることでMach-Oかどうかを判断することができます。ここでは、32bit環境用のMach-Oだけを対象とします。64bit環境用のMach-Oを対象とする場合はコード中のいくつかの型の最後に「_64」を追加します。(例えば、mach_header -> mach_header_64。)どちらにも対応する場合はCutterのソースを参考にしてください。

struct mach_header *header;

header = (struct mach_header *)content;
if (header->magic == MH_MAGIC) {
    /* Mach-Oファイル */
}

共有ライブラリかどうかを判断

Mach-Oであることが確認できたら、共有ライブラリかどうかを確認します。

if (header->filetype == MH_DYLIB) {
    /* 共有ライブラリ */
}

バンドルからもシンボルを取り出すことができるので、バンドルにも対応するのもよいでしょう。

if (header->filetype == MH_DYLIB || header->filetype == MH_BUNDLE) {
    /* 共有ライブラリかバンドル */
}

公開されているシンボルを探索し出力

Mac OS X ABI Mach-O File Format Referenceの「Figure 1 Mach-O file format basic structure」にある通り、ヘッダの後にはロードコマンドと呼ばれる部分が複数続きます。いくつかあるロードコマンドのうち、興味があるのは以下の2つです。

LC_SEGMENT

セグメントに関する情報を提供する。

LC_SYMTAB

シンボルに関する情報を提供する。

シンボル名とシンボルが公開されているかはLC_SYMTABから取得できます。シンボルが関数と関連づけられているかは、シンボルが__TEXTセグメントの__textセクションで定義されているかどうかで判断できます。

gsize offset;
uint32_t i, n_commands;
uint32_t section_index = 0, text_section_index = 0;

/* ファイルの先頭からコマンドの先頭までのバイト数 */
offset = sizeof(*header);
/* コマンド数 */
n_commands = header->ncmds;
for (i = 0; i < n_commands; i++) {
    struct load_command *load;

    load = (struct load_command *)(content + offset);
    switch (load->cmd) {
    case LC_SEGMENT: /* セグメント用コマンド */
    {
        struct segment_command *segment;
        struct section *section;
        gint j;

        /* セグメント */
        segment = (struct segment_command *)(content + offset);
        /* __TEXTセグメント以外は興味がない */
        if (!g_str_equal(segment->segname, "__TEXT")) {
            /* セクション数だけ数えてスキップ */
            section_index += section->nsects;
            break;
        }

        /* セクション */
        section = (struct section *)(content + offset + sizeof(*segment));
        for (j = 0; j < segment->nsects; j++, section++) {
            section_index++;
            /* __textセクションが何番目のセクションかを記録 */
            if (g_str_equal(section->sectname, "__text"))
                text_section_index = section_index;
        }
        break;
    }
    case LC_SYMTAB: /* シンボルテーブル用コマンド */
    {
        struct symtab_command *table;
        struct nlist *symbol;
        gchar *string_table;
        gint j;

        /* シンボルテーブルコマンド */
        table = (struct symtab_command *)(content + offset);
        /* シンボルリスト */
        symbol = (struct nlist *)(content + table->symoff);
        /* シンボル名が入っている文字列テーブル */
        string_table = content + table->stroff;
        for (j = 0; j < table->nsyms; j++, symbol++) {
            gboolean defined_in_section = FALSE;

            /* シンボルがセクションで定義されているか */
            if ((symbol->n_type & N_TYPE) == N_SECT)
                defined_in_section = TRUE;

                /* シンボルが__textセクションで定義されていて */
            if (defined_in_section && symbol->n_sect == text_section_index &&
                /* 公開されている */
                symbol->n_type & N_EXT) {
                gchar *name;
                int32_t string_offset;

                string_offset = symbol->n_un.n_strx;
                /* 文字列テーブルからシンボル名を取得 */
                name = string_table + string_offset;
                /* シンボル名の先頭に「_」がついているので2文字目以降を表示 */
                g_print("found: %s\n", name + 1);
            }
        }
        break;
    }
    default:
        break;
    }
    /* 次のコマンドに進む */
    offset += load->cmdsize;
}

参考

まとめ

mach-o/loader.hmach-o/nist.hを使って、BFDライブラリに依存せずに、Mach-Oから公開されている関数名を抜き出す方法を紹介しました。

関数名が取得できたら、GModuleで関数本体を取得することができます。GModuleに渡す関数名の先頭には「_」をつける必要はありません。

いずれ、PEから公開されている関数名を抜き出す方法も紹介するかもしれません。