ビルド後のバイナリー実行ファイルにポストプロセスで変数の値を書き換える方法

# 概要

とある C++ のソフトウェア開発プロジェクトで新人もそろそろ卒業かなという若いイケメンくんが

「先輩！うちのプロジェクトのバイナリー実行ファイルに含まれている変数 `mojimoji` の値をビルド（翻訳＋リンク）した後で書き換えるなんて無茶な事をしたいかもしれないです！！」

そんな状況が発生していたので早起きして業務時間外に Qiita にやり方を書いておくサンタクロース風のシャイなプレゼント方式を取ることにした私だった。（†0）

# 状況の例

- a.cxx にはビルド後に書き換えたい変数 `mojimoji` や `mojisize` が含まれているとします。

例えば↓こんなのですね。

```cpp
static volatile constexpr char mojimoji[] = "abcd1234";
static volatile constexpr unsigned char mojisize = sizeof( mojimoji );

#include <iostream>

auto main() -> int
{
  std::cout << ( const char* )mojimoji << " " << std::to_string( mojisize );
}
```

`g++ -std=c++14 -O3 a.cxx` などして翻訳すると 実行ファイル `a` （mingw なら `a.exe` ） ができて、これは実行すると `abcd1234 9` と表示してくれます。（†1）

さて、例えばこれを `ajipon 9` と表示するように C++ プロジェクト自体のリビルド無しで、ポストプロセスだけでどうにかしたいのです。

# 方法

## 確認

### nm: シンボルエントリーの確認

はじめに、 `a.exe` に `mojimoji` や `mojisize` のシンボルエントリーがあるか確認してみましょう:

`nm -C a.exe | grep moji` すると、

```
00000000004a7018 d mojimoji
00000000004a7010 d mojisize
```

ありました。（†2）このように出てくる状態なら簡単にどうにかなるでしょう。たぶん。

この表示からは、バイナリーの実行時の仮想メモリー上のアドレス（今回は使いません）、 `d` なのでデータセクションに居ること、そしてシンボル名が `mojimoji` などで存在しているとわかります。

### objdump: シンボルの位置

#### セクション情報

`objdump -x a.exe | grep -10 Sections:` などしてセクション情報を確認しましょう:

```
          pc+0x04: alloc small area: rsp = rsp - 0x28
        Handler: 00000000004a6780.
        User data:
          000: ff ff 01 00
 00000000004d4998 (rva: 000d4998): 00000000004a6d40 - 00000000004a6d4a
        Version: 1, Flags: none
        Nbr codes: 2, Prologue size: 0x04, Frame offset: 0x0, Frame reg: rbp
          pc+0x04: FPReg: rbp = rsp + 0x0 (info = 0x0)
          pc+0x01: push rbp

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         000a5db0  0000000000401000  0000000000401000  00000600  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE, DATA
  1 .data         00002f28  00000000004a7000  00000000004a7000  000a6400  2**5
                  CONTENTS, ALLOC, LOAD, DATA
  2 .rdata        0000fbe0  00000000004aa000  00000000004aa000  000a9400  2**6
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .pdata        0000b04c  00000000004ba000  00000000004ba000  000b9000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .xdata        0000e9a0  00000000004c6000  00000000004c6000  000c4200  2**2
```

`.data` セクションはバイナリーファイル上のオフセットアドレス `000a6400` に居るとわかりました。†3

#### シンボルのアドレス

続いて、 `objdump -x a.exe | grep moji` などして `mojimoji` のアドレスオフセットを確認しましょう:

```
[134](sec  2)(fl 0x00)(ty   0)(scl   3) (nx 0) 0x0000000000000018 _ZL8mojimoji
[138](sec  2)(fl 0x00)(ty   0)(scl   3) (nx 0) 0x0000000000000010 _ZL8mojisize
```

`_ZL8mojimoji` （マングリングされた `mojimoji`）と `_ZL8mojisize` （マングリングされた `mojisize`）がセクションのアドレスオフセット `0x18` と `0x10` にそれぞれ居るとわかります。

### ここまでにわかっている事を整理

- `mojimoji` は `.data` セクションに居る
- `.data` セクションはファイルオフセット `0x0a6400` バイトに居る
- `mojimoji` ( マングリングされた状態だと `_ZL8mojimoji` ) はセクションのオフセット `0x18` に居る
    - `mojisize` （ `_ZL8mojisize` ） はセクションのオフセット `0x10` に居る

と、言うわけで、バイナリーファイル `a.exe` のオフセットで:

- `0x0a6400` + `0x18` = `0x0a6418` : `mojimoji` が居る
- `0x0a6400` + `0x10` = `0x0a6410` : `mojisize` が居る

となります。

### 実際のデータを確認

`od -A x -j 0x0a6418 -a a.exe | head -n1` してバイナリーファイル `a.exe` のファイルオフセットアドレス `0x0a6418` からの `mojimoji` のバイト列をASCII文字のダンプで確認してみます:

```
0a6418   a   b   c   d   1   2   3   4 nul nul nul nul nul nul nul nul
```

居ましたヽ(´ー｀)ノ

`od -A x -j 0x0a6410 -t d1 a.exe | head -n1` して `0x0a6410` の `mojisize` を 10進数のダンプで確認してみます:

```
0a6410    9    0    0    0    0    0    0    0   97   98   99  100   49   50   51   52
```

居ましたヽ(´ー｀)ノ

## バイナリーファイルの変数を書き換えて実行

`cp a.exe b.exe` などしてコピーした `b.exe` の `0x0a6418` からの 9 bytes を `"ajipon\0\0\0"` に書き換えて `b.exe` を実行してみましょう: †4

```
./b
ajipon 9
```

できましたヽ(´ー｀)ノ

# 註

- †0: 記事冒頭の概要本文中のどうでもよさそうな表現はフィクションです。
- †1: `volatile` を付けないと `-O3` でシンボルが吹き飛ぶ可能性があります。`nm`などでシンボルが見つからない場合の原因となる可能性があります。
- †2: `nm` のオプション `-C` を付けないと C++ のマングリングされたシンボル名が表示される事になります。複雑な階層構造になっていたりするとひうまんの脳には辛いので C++er は `nm` の `-C` は `c++` の `-std=c++14` と同じくらい標準的に付けるものだと思って差し支えありません。
- †3: `grep` せずに ` > a.objdump.txt` や ` | less` などして読むと出力にちゃんとカラムがそれぞれ何であるか丁寧に出力されていたりします。やり方としての実用上は不要ですが1度は出力全体を眺めておく事をお勧めします。
- †4: コマンドだけでバイナリー差分を書き換えるのは面倒かなと思ったので今回は書き換えに [Bz](http://www.forest.impress.co.jp/library/software/binaryeditbz/) を使いました。