10

作成しているより単純なコンパイラ用のコードを生成しましたが、そのコードをELFファイルに配置する方法を考えていましたか?

libelfを使ってみましたが、テーブルの整理方法に頭を悩ませているようには見えません。

私はデータを使用していないので、必要なのは.textセクションだけだと思います。

生成されたx86コードのバッファーがある場合、実行可能な単純な.textセクションだけでELFファイルを作成するにはどうすればよいですか?

4

1 に答える 1

9

簡潔な答え

できません !

探している機能は、実際には「リンカー」と呼ばれるビルド ツールの一部です。いくつかの未解決のシンボル エラーに加えて、時々その存在が見過ごされてしまうという事実にもかかわらず、ビルド チェーンの最も重要なコンポーネントの 1 つです。

長い答え

ここでは、何らかの方法でバイナリを実行する方法についていくつかのアイデアを示します。

制限

以下で説明する方法のいずれかが機能するのは、

  • マシンコードには絶対アドレスのジャンプは含まれていません。ジャンプを正しい宛先にパッチするには再配置情報が必要になるためです。

  • プログラムは、入力として使用されるバイナリ ファイルの最初から開始します。

    これは、バイナリ データへのオフセットを使用して、ファイルの先頭の「正しい」場所に追加の (相対) ジャンプ命令を追加することで、簡単に回避できます。

推奨される回避策

非常に単純な「自己完結型」の含まれるバイナリの場合、外部依存関係がなく、 (!) 絶対ジャンプ命令を使用せずに、一連の生のマシン命令だけで与えられます。手動で行うよりも、既に使用している方が簡単な場合があります。代わりに既存のリンカー。

生のマシン命令 (次の例ではmain.bin ) で構成されるファイルが与えられた場合、最初のステップでは、そこから共有オブジェクト (例ではmain.o ) を生成します。

objcopy -I binary -B i386 -O elf32-i386 --rename-section .data=.text main.bin main.o

生成されたオブジェクトのシンボル テーブルを見てみましょうreadelf -S:

Symbol table '.symtab' contains 5 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 SECTION LOCAL  DEFAULT    1 
     2: 00000000     0 NOTYPE  GLOBAL DEFAULT    1 _binary_main_bin_start
     3: 0000000c     0 NOTYPE  GLOBAL DEFAULT    1 _binary_main_bin_end
     4: 0000000c     0 NOTYPE  GLOBAL DEFAULT  ABS _binary_main_bin_size

入力ファイルの開始、終了、およびサイズに応じて、記号_binary_..._start_binary_..._endおよびが追加されていることがわかります。_binary_..._sizeこれらを使用して、実行可能ファイルへのエントリ ポイントをリンカーに渡すことができます。

ld --entry=_binary_main_bin_start main.o -o main

探している実行可能ファイルを生成する必要があります。

手動生成

または、実行中の実行可能ファイルを取得するために必要な情報だけを含む elf ファイルを手動で作成することもできます。

elf 形式にあまり慣れていない場合は、仕様を確認することをお勧めします ( http://refspecs.linuxfoundation.org/で入手可能)。また、マニュアル ページ ( man elf) は非常に網羅的であるため、これも良い情報源になる可能性があります。

最もシンプルに保つために、絶対に必要なものだけを使用することが目標になります。

仕様を調べると、どのような状況でも必要な唯一のコンポーネントが elf ヘッダーであることがわかります。セクション ヘッダー テーブルは共有オブジェクトにのみ必要であり、プログラム ヘッダー テーブルは実行可能ファイルにのみ必要です。

PT_LOAD実行可能ファイルを作成したいので、実行可能ファイルのメモリ レイアウト全体を記述するタイプの 1 つのエントリを持つプログラム ヘッダー テーブルのみを使用します。

アライメントの制約を満たすために、プロセス イメージにはバイナリの内容全体が含まれます (ソース: man elf)。

... ロード可能なプロセス セグメントは、ページ サイズを法として、p_vaddr と p_offset の値が一致している必要があります。

これにより、elf ファイルの最終的なレイアウトが次のようになる理由が明確になるはずです。

struct Binary {
  Elf32_Ehdr ehdr;
  Elf32_Phdr phdr;
  char code[];
};

Elf32_Ehdr と Elf32_Phdr のほとんどのフィールドは固定されているため、初期化子で既に設定できます。後で調整が必要な唯一のフィールドは、プログラム ヘッダー テーブル エントリ内のロードされたセグメントのサイズ (.p_filesz および .p_memsz) を記述するフィールドです。

stdin から入力を取得し、stdout に書き込む (したがって、 のように使用される./a.out <main.bin >executable) これは、説明されているセットアップを実装する方法です。

#include <stdio.h>
#include <stddef.h>
#include <elf.h>
#include <string.h>
#include <stdlib.h>

#define BUFFER_SIZE 1024
char buffer[BUFFER_SIZE];

void *read_all (int *filesize) {
  void *data = NULL;
  int offset = 0;
  int size = 0;

  while ((size = fread (buffer, 1, sizeof (buffer), stdin)) > 0) {
    if ((data = realloc (data, offset + size)) == NULL)
      exit (-1);
    memcpy (data + offset, buffer, size);
    offset += size;
  }
  *filesize = offset;
  return data;
}


#define LOAD_ADDRESS 0x8048000

struct Binary {
  Elf32_Ehdr ehdr;
  Elf32_Phdr phdr;
  char code[];
};

int main (int argc, char *argv[]) {

  void *code;
  int code_size;

  struct Binary binary = {
    /* ELF HEADER */
    .ehdr = {
      /* general */
      .e_ident   = {
        ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3,
        ELFCLASS32, 
        ELFDATA2LSB,
        EV_CURRENT,
        ELFOSABI_LINUX,
      },
      .e_type    = ET_EXEC,
      .e_machine = EM_386,
      .e_version = EV_CURRENT,
      .e_entry   = LOAD_ADDRESS + (offsetof (struct Binary, code)),
      .e_phoff   = offsetof (struct Binary, phdr),
      .e_shoff   = 0,
      .e_flags   = 0,
      .e_ehsize   = sizeof (Elf32_Ehdr),
      /* program header */
      .e_phentsize = sizeof (Elf32_Phdr),
      .e_phnum     = 1,
      /* section header */
      .e_shentsize = sizeof (Elf32_Shdr),
      .e_shnum     = 0,
      .e_shstrndx  = 0
    },

    /* PROGRAM HEADER */
    .phdr = {
      .p_type   = PT_LOAD,
      .p_offset = 0,
      .p_vaddr = LOAD_ADDRESS,
      .p_paddr = LOAD_ADDRESS,
      .p_filesz = 0,
      .p_memsz = 0,
      .p_flags = PF_R | PF_X,
      .p_align = 0x1000
    }
  };

  if ((code = read_all (&code_size)) == NULL)
    return -1;

  /* fix program header */
  binary.phdr.p_filesz = sizeof (struct Binary) + code_size;
  binary.phdr.p_memsz = sizeof (struct Binary) + code_size;

  /* write binary */
  fwrite (&binary, sizeof (struct Binary), 1, stdout);
  fwrite (code, 1, code_size, stdout);

  free (code);

  return 0;
}
于 2013-03-09T23:35:27.633 に答える