0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

リバースエンジニアリングへの道 - その20

Posted at

リバースエンジニアリングへの道

出田 守です。
最近、情報セキュリティに興味を持ち、『リバースエンジニアリング-Pythonによるバイナリ解析技法』という本(以降、「教科書」と呼びます)を読みました。
「こんな世界があるのか!かっこいい!」と感動し、私も触れてみたいということでド素人からリバースエンジニアリングができるまでを書いていきたいと思います。
ちなみに、教科書ではPython言語が使用されているので私もPython言語を使用しています。
ここを見ていただいた諸先輩方からの意見をお待ちしております。

軌跡

リバースエンジニアリングへの道 - その19

環境

OS: Windows10 64bit Home (日本語)
CPU: Intel® Core™ i3-6006U CPU @ 2.00GHz × 1
メモリ: 2048MB
Python: 3.6.5

私の環境は、普段Ubuntu16.04を使っていますが、ここではWindows10 64bitを仮想マシン上で立ち上げております。
ちなみに教科書では、Windowsの32bitで紹介されています。

さらっとELF

前回までは、逆アセンブラについて学んでいました。前回メモリエディタを学ぼうと言っていましたが・・・やめます!笑
逆アセンブラを学んでいるうちになんとなーくメモリエディタの基本的なところも理解できましたので(恐らく)。
なので今回は逆アセンブラでも少し必要になった「ELF」について学びます。ドキュメント( https://refspecs.linuxfoundation.org/elf/elf.pdf )、実際のELFヘッダファイル(elf.h)、Webサイトを参考に学んでいきます。

ELFとは

ドキュメントによると、ELF(The Executable and Linking Format)は元々、UNIX System Laboratories(USL)がApplication Binary Interface(ABI)の一部として開発・公開されました。ABIは、同じプロセッサ環境内でアプリケーションが実行できるようにするインタフェースのことらしいです。The Tool Interface Standards committee(TIS)は、様々なOSの32bitインテルアーキテクチャ環境で動作するポートブルオブジェクトファイルフォーマットとして、進化したELF標準を選択しました。
ELF標準は複数のOSにまたがって拡張するバイナリインタフェース定義のセットを開発者に提供することで、ソフトウェア開発を簡素化することを目的としました。これにより、異なるインタフェース実装の数が減少し、コードの記録と再コンパイルの必要性が軽減されます。
つまり、たまたまABIとして作ったELFが一般に広まって、標準となっていったということでしょうかね。
あと、このドキュメントは32bit環境に焦点を置いているようです。

オブジェクトファイル

ELFには主に3つのオブジェクトファイルが存在するようです。

  1. relocatable file(再配置可能ファイル)
    他のオブジェクトファイルでリンクするために適切なコードとデータを保持し、executable fileまたはshared object fileを作成します。
  2. executable file(実行可能ファイル)
    実行するための適切なプログラムを保持します。
  3. shared object file(共有オブジェクトファイル)
    2つのコンテキストでリンクするために適切なコードとデータを保持します。一つ目のリンクエディタは他のrelocatable fileとshared object fileを使って、他のオブジェクトファイルを作成します。二つ目の動的リンカはexecutable fileと他のshared object fileを結合し、プロセスイメージを作成します。

リンクプログラムと実行プログラムのファイルフォーマットは以下です。
[ELFドキュメントより]
Screenshot from 2018-09-02 11-41-02.png

ELFヘッダはファイルの最初に存在し、ファイル構成を示す「ロードマップ」を保持しています。
セクションはリンクするために命令、データ、シンボルテーブル、再配置情報などの大部分のオブジェクトファイル情報を保持しています。
プログラムヘッダテーブルはプロセスイメージがどのように作られたかをシステムに通知します。プロセスイメージをビルドするために使用するファイルはプログラムヘッダテーブルを持つ必要があります。relocatable fileはこれを必要としません。
セクションヘッダテーブルはファイルのセクション情報を含んでいます。
各セクションはテーブルのエントリを持っています。
各エントリはセクション名、セクションサイズなどの情報を提供します。リンクするときに使用されるファイルはセクションヘッダテーブルを持つ必要があります。

ELFヘッダ

ELFファイルに実際のサイズが含まれているため、一部のオブジェクトファイル制御構造を大きくすることが出来ます。オブジェクトファイルフォーマットが変更された場合、プログラムは制御構造が予想よりも大きいまたは小さくてもかまいません。

ここで私の環境(Ubuntu 16.04LTS)でのelf.h内のELFヘッダファイルを見てみます。

sudo file / -name elf.hで検索するといっぱいelf.hが出てきました。とりあえず、/usr/include/elf.hが位置的にリーダーっぽそうなので(リーダーってなんや笑)、これを参考にしていきます。

elf.h
#define EI_NIDENT (16)

typedef struct
{
  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */
  Elf32_Half	e_type;			/* Object file type */
  Elf32_Half	e_machine;		/* Architecture */
  Elf32_Word	e_version;		/* Object file version */
  Elf32_Addr	e_entry;		/* Entry point virtual address */
  Elf32_Off	e_phoff;		/* Program header table file offset */
  Elf32_Off	e_shoff;		/* Section header table file offset */
  Elf32_Word	e_flags;		/* Processor-specific flags */
  Elf32_Half	e_ehsize;		/* ELF header size in bytes */
  Elf32_Half	e_phentsize;		/* Program header table entry size */
  Elf32_Half	e_phnum;		/* Program header table entry count */
  Elf32_Half	e_shentsize;		/* Section header table entry size */
  Elf32_Half	e_shnum;		/* Section header table entry count */
  Elf32_Half	e_shstrndx;		/* Section header string table index */
} Elf32_Ehdr;

typedef struct
{
  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */
  Elf64_Half	e_type;			/* Object file type */
  Elf64_Half	e_machine;		/* Architecture */
  Elf64_Word	e_version;		/* Object file version */
  Elf64_Addr	e_entry;		/* Entry point virtual address */
  Elf64_Off	e_phoff;		/* Program header table file offset */
  Elf64_Off	e_shoff;		/* Section header table file offset */
  Elf64_Word	e_flags;		/* Processor-specific flags */
  Elf64_Half	e_ehsize;		/* ELF header size in bytes */
  Elf64_Half	e_phentsize;		/* Program header table entry size */
  Elf64_Half	e_phnum;		/* Program header table entry count */
  Elf64_Half	e_shentsize;		/* Section header table entry size */
  Elf64_Half	e_shnum;		/* Section header table entry count */
  Elf64_Half	e_shstrndx;		/* Section header string table index */
} Elf64_Ehdr;

上が32bit用で下が64bit用ですね。両方にサイズ以外の違いはなさそうです。

  • e_ident
    固定値といくつかの情報が示されているようです。
    • 0:EI_MAG0
      固定値。ELFMAG0:0x7f
    • 1:EI_MAG1
      固定値。ELFMAG1:'E'
    • 2:EI_MAG2
      固定値。ELFMAG2:'L'
    • 3:EI_MAG3
      固定値。ELFMAG3:'F'
    • 4:EI_CLASS
      ファイルクラス。
      無効クラス。ELFCLASSNONE:0
      32bitオブジェクト。ELFCLASS32:1
      64bitオブジェクト。ELFCLASS64:2
    • 5:EI_DATA
      データエンコーディング。
      無効なデータエンコーディング。ELFDATANONE:0
      2の補数リトルエンディアン。ELFDATA2LSB:1
      2の補数ビッグエンディアン。ELFDATA2MSB:2
    • 6:EI_VERSION
      ELFヘッダバージョン。現在は下で説明するEV_CURRENTの値を持ちます。
    • 7:EI_OSABI
      OS ABI識別情報。
      Unix System V ABI。ELFOSABI_NONE,ELFOSABI_SYSV:0
      HP-UX。ELFOSABI_HPUX:1
      NetBSD。ELFOSABI_NETBSD:2
      GNU拡張ELF。ELFOSABI_GNU, ELFOSABI_LINUX:3
      Sun Solaris。ELFOSABI_SOLARIS:6
      IBM AIX。ELFOSABI_AIX:7
      SGI Irix。ELFOSABI_IRIX:8
      FreeBSD。ELFOSABI_FREEBSD:9
      Compaq TRU64 Unix。ELFOSABI_TRU64:10
      Novell Modesto。ELFOSABI_MODESTO:11
      OpenBSD。ELFOSABI_OPENBSD:12
      ARM EABI。ELFOSABI_ARM_AEABI:64
      ARM。ELFOSABI_ARM:64
      スタンドアロン組み込みアプリケーション。ELFOSABI_STANDALONE:255
    • 8:EI_ABIVERSION
      ABIバージョン。
    • 9:EI_PAD
      これはe_identで使用しないByteの開始位置を示します。これらのByteは予約済みで、0にセットされます。
  • e_type
    オブジェクトファイルタイプ。
    ファイルタイプなし。ET_NONE:0
    relocatable file。ET_REL:1
    executable file。ET_EXEC:2
    shared object file。ET_DYN:3
    core file。ET_CORE:4
  • e_machine
    個々のファイルに必要なアーキテクチャを示します。
    マシンなし。EM_NONE:0
    AT&T WE 32100。EM_M32:1
    SUN SPARC。EM_SPARC:2
    Intel 80386。EM_386:3
    Motorola m68k family。EM_68K:4
    Motorola m88k family。EM_88K:5
    Intel 80860。EM_860:6
    MIPS R3000 big-endian。EM_MIPS:7
    IBM System 370。EM_S370:8
    ...
    Tilera TILE-Gx。EM_TILEGX:191
  • e_version
    オブジェクトファイルバージョン。
    無効なELFバージョン。EV_NONE:0
    現在のバージョン。EV_CURRENT:1
  • e_entry
    仮想アドレスのエントリーポイント。こんなところに書いてあったんですね!笑
  • e_phoff
    プログラムヘッダテーブルのファイルオフセット。
  • e_shoff
    セクションヘッダテーブルのファイルオフセット。
  • e_flags
    プロセッサ指定フラグ。
    PowerPC embedded flag。EF_PPC_EMB:0x80000000
    PowerPC -mrelocatable flag。EF_PPC_RELOCATABLE:0x00010000
    PowerPC -mrelocatable-lib。EF_PPC_RELOCATABLE_LIB:0x00008000
  • e_ehsize
    ELFヘッダサイズ。
  • e_phentsize
    プログラムヘッダテーブルエントリサイズ。
  • e_phnum
    プログラムヘッダテーブルエントリカウント。
  • e_shentsize
    セクションヘッダテーブルエントリサイズ。
  • e_shnum
    セクションヘッダテーブルエントリカウント。
  • e_shstrndx
    セクション名文字列テーブルのインデックス。

では実際にELFヘッダを表示させてみたいと思います。

elf_header.c
#include <stdio.h>
#include <elf.h>

int main(int argc, char *argv[]) {
  int i = 0;
  Elf64_Ehdr elf_header;

  FILE *fd = fopen(argv[1], "rb");
  if (fd) {
    fread(&elf_header, 1, sizeof(elf_header), fd);
    for (i=0; i<EI_NIDENT; i++) {
      printf("e_ident[%d]=0x%x\n", i, elf_header.e_ident[i]);
    }
    printf("e_type=0x%x\n", elf_header.e_type);
    printf("e_machine=0x%x\n", elf_header.e_machine);
    printf("e_version=0x%x\n", elf_header.e_version);
    printf("e_entry=0x%x\n", elf_header.e_entry);
    printf("e_phoff=0x%x\n", elf_header.e_phoff);
    printf("e_shoff=0x%x\n", elf_header.e_shoff);
    printf("e_flags=0x%x\n", elf_header.e_flags);
    printf("e_ehsize=0x%x\n", elf_header.e_ehsize);
    printf("e_phnum=0x%x\n", elf_header.e_phnum);
    printf("e_shentsize=0x%x\n", elf_header.e_shentsize);
    printf("e_shnum=0x%x\n", elf_header.e_shnum);
    printf("e_shstrndx=0x%x\n", elf_header.e_shstrndx);
    fclose(fd);
  }

  return 0;
}

C言語は久しぶりなので、合っていますでしょうか。
試しにこのプログラム自身で試すと、とりあえず表示はできました。
共有オブジェクトファイルなんですね。実行ファイルだと思っていました。
あと、e_flagsは値が入っていないですね。

次はSectionを見ていきます。

Section

オブジェクトファイルのセクションヘッダテーブルを使用すると、すべてのファイルセクションを見つけることができます。ELFヘッダのe_shoffはセクションヘッダテーブルの始点からのオフセットです。e_shnumはセクションヘッダテーブルに含まれるエントリの数です。e_shentsizeは各エントリのByteサイズです。
一部のセクションヘッダテーブルインデックスは予約されています。

特別セクションインデックス

  • SHN_UNDEF
    未定義セクション:0
  • SHN_LORESERVE
    予約インデックスの始点:0xff00
  • SHN_LOPROC
    プロセッサ固有の始点:0xff00
  • SHN_BEFORE
    Order section before all others (Solaris):0xff00 (分かりません)
  • SHN_AFTER
    Order section before all others (Solaris):0xff01 (分かりません)
  • SHN_HIPROC
    プロセッサ固有の終点:0xff1f
  • SHN_LOOS
    OS固有の始点:0xff20
  • SHN_HIOS
    OS固有の終点:0xff3f
  • SHN_ABS
    シンボルなどの対応する参照の絶対値を指定:0xfff1
  • SHN_COMMON
    共通シンボルの指定??:0xfff2
  • SHN_XINDEX
    余分のテーブルインデックス:0xffff
  • SHN_HIRESERVE
    予約インデックスの終点:0xffff

SectionはEFLヘッダ、プログラムヘッダテーブル、セクションヘッダテーブルを除くオブジェクトファイルのすべてのアクションの記載を含みます。

Section Header

elf.h
typedef struct
{
  Elf32_Word	sh_name;		/* Section name (string tbl index) */
  Elf32_Word	sh_type;		/* Section type */
  Elf32_Word	sh_flags;		/* Section flags */
  Elf32_Addr	sh_addr;		/* Section virtual addr at execution */
  Elf32_Off	sh_offset;		/* Section file offset */
  Elf32_Word	sh_size;		/* Section size in bytes */
  Elf32_Word	sh_link;		/* Link to another section */
  Elf32_Word	sh_info;		/* Additional section information */
  Elf32_Word	sh_addralign;		/* Section alignment */
  Elf32_Word	sh_entsize;		/* Entry size if section holds table */
} Elf32_Shdr;

typedef struct
{
  Elf64_Word	sh_name;		/* Section name (string tbl index) */
  Elf64_Word	sh_type;		/* Section type */
  Elf64_Xword	sh_flags;		/* Section flags */
  Elf64_Addr	sh_addr;		/* Section virtual addr at execution */
  Elf64_Off	sh_offset;		/* Section file offset */
  Elf64_Xword	sh_size;		/* Section size in bytes */
  Elf64_Word	sh_link;		/* Link to another section */
  Elf64_Word	sh_info;		/* Additional section information */
  Elf64_Xword	sh_addralign;		/* Section alignment */
  Elf64_Xword	sh_entsize;		/* Entry size if section holds table */
} Elf64_Shdr;
  • sh_name
    セクション名(文字列テーブルインデックス)。
  • sh_type
    • SHT_NULL
      未使用セクションヘッダテーブル始点:0
    • SHT_PROGBITS
      プログラムデータ:1
    • SHT_SYMTAB
      シンボルテーブル:2
    • SHT_STRTAB
      文字列テーブル:3
    • SHT_RELA
      再配置エントリ:4
    • SHT_HASH
      シンボルハッシュテーブル:5
    • SHT_DYNAMIC
      ダイナミックリンク情報:6
    • SHT_NOTE
      ノート:7
    • SHT_NOBITS
      データなしのプログラムスペース(bss):8
    • SHT_REL
      再配置エントリ:9
    • SHT_SHLIB
      予約済み:10
    • SHT_DYNSYM
      ダイナミックリンクシンボルテーブル:11
    • SHT_INIT_ARRAY
      コンストラクタの配列:14
    • SHT_FINI_ARRAY
      デストラクタの配列:15
    • SHT_PREINIT_ARRAY
      プリコンストラクタの配列:16
    • SHT_GROUP
      Sectionグループ:17
    • SHT_SYMTAB_SHNDX
      拡張Sectionインデックス:18
    • SHT_NUM
      定義された型の番号
      (以下省略)
  • sh_flags
    セクションの属性のフラグ。
    • SHF_WRITE
      書き込み可能:0x1(1<<0)
    • SHF_ALLOC
      プロセス実行中にメモリを占有:0x2(1<<1)
    • SHF_EXECINSTR
      実行可能:0x4(0<<2)
    • SHF_MERGE
      マージされる可能性がある:0x8(0<<3)
    • SHF_STRINGS
      NULL文字を含む文字列:0x16(0<<4)
      (以下省略)
  • sh_addr
    実行時のSection仮想アドレス
  • sh_offset
    Sectionのオフセット
  • sh_size
    SectionのByteサイズ
  • sh_link
    他のSectionへのリンク
  • sh_info
    追加Section情報
  • sh_addralign
    Sectionアライメント
  • sh_entsize
    Sectionテーブルを持つ場合のエントリサイズ

では、セクションヘッダを実際に見てみます。

sh_header.c
#include <stdio.h>
#include <elf.h>

int main(int argc, char *argv[]) {
  int i = 0;
  Elf64_Ehdr elf_header;
  Elf64_Shdr sh_header;

  FILE *fd = fopen(argv[1], "rb");
  if (fd) {
    // elf header
    fread(&elf_header, 1, sizeof(elf_header), fd);
    printf("e_shoff=0x%x\n", elf_header.e_shoff);
    printf("e_shentsize=0x%x\n", elf_header.e_shentsize);
    printf("e_shnum=0x%x\n", elf_header.e_shnum);
    printf("e_shstrndx=0x%x\n", elf_header.e_shstrndx);

    // section header
    fseek(fd, elf_header.e_shoff, SEEK_SET);
    /* for (i=0; i<elf_header.e_shnum; i++) { */
    for (i=0; i<2; i++) {
      fread(&sh_header, 1, sizeof(sh_header), fd);
      printf("---------------------\n");
      printf("sh_name=0x%x\n", sh_header.sh_name);
      printf("sh_type=0x%x\n", sh_header.sh_type);
      printf("sh_flags=0x%x\n", sh_header.sh_flags);
      printf("sh_addr=0x%x\n", sh_header.sh_addr);
      printf("sh_offset=0x%x\n", sh_header.sh_offset);
      printf("sh_size=0x%x\n", sh_header.sh_size);
      printf("sh_link=0x%x\n", sh_header.sh_link);
      printf("sh_info=0x%x\n", sh_header.sh_info);
      printf("sh_addralign=0x%x\n", sh_header.sh_addralign);
      printf("sh_entsize=0x%x\n", sh_header.sh_entsize);
      printf("---------------------\n");
    }
    fclose(fd);
  }

  return 0;
}

すべて出力すると多かったので、2番目まで出力するようにしました。
elfヘッダにはセクションヘッダテーブルの始点とセクション数、セクションエントリサイズなどが格納されているので、それを利用しました。
一番初めのセクションヘッダテーブルには0しか格納されていませんでした。
セクション名(sh_name)には文字列テーブルへのインデックス番号が格納されていて、参照するとセクション名が得られるようです。

この他にもプログラムヘッダテーブルや文字列テーブル、シンボルテーブルなどが存在しますが、先へ進みたいのでここまでにします。必要になったら適宜参照していきます。

まとめ

  1. ELFには3つのオブジェクトファイルが存在する
  2. ELFヘッダにはファイルの全体的な情報が格納されている
  3. Sectionヘッダには各セクション情報が格納されている
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?