諸事情により簡易的でよいので.exeファイルの解析をしたくなった。.exeファイルをバイナリエディタで見てみて、全く同じプロジェクトを二回以上ビルドした結果が完全一致しない。理由の一つがタイムスタンプなので、これをいじってみる。
VC++でコンソールアプリケーションを作成する。この時、余計なものが埋め込まれるとそれだけビルド時の変化が大きくなる可能性があるので、わかる範囲で不要なオプションを取り除く。
・.pdbファイルを出力しない
プロジェクトのプロパティ → リンカー → デバッグ → デバッグ情報の生成 → いいえ
・マニフェストを埋め込まない
プロジェクトのプロパティ → リンカー → マニフェスト ファイル → マニフェストの生成 → いいえ
・ASLRをしない(Address Space Layout Randomization)
ASLRはexe内のデータの位置をランダムにするクラッキング対策のオプションで、多分悪影響を与えるので切っておく。/DYNAMICBASE:NOを設定。
プロジェクトのプロパティ → リンカー → 詳細設定 → ランダム化されたベースアドレス → いいえ
まずビルドし、上書きされないように.exeファイルの名前を変える。それからもう一度ビルドする。
右クリック→プロパティ→詳細 で更新日時を確認。
実際にはここの表示を変えるわけではないのだが、バイナリ中の値を解析してこの日付とおおむね同じものが出てくれば正解だとわかるので参考にする。
WinMergeで二つの.exeをバイナリ比較する。手元のバージョンでは、CompareボタンのプルダウンにBinary比較がある。
一部違う場所が出てくるので、その右4バイトに着目する。時間を置かずにビルドすると、数秒しか違わないはずなので、違いはせいぜい下1バイトになる可能性が高い。intelのCPUはリトルエンディアンなので、バイトの並び順が逆になっている。従って変化している場所を含めて右に4バイトがタイムスタンプとなる。
上記で得られた値 0x65abe167 が実際にビルド時刻であることを確認するため、以下のプログラムを書いて確認する。
#include <ctime> #include <string> #include <iostream> #pragma warning(disable:4996) // タイムスタンプから時刻表示を作成 std::string TimestampToDate(time_t timestamp) { struct tm* tm = localtime(×tamp);// 整数値 timestamp を tm 構造体に変換 char str[50]; strftime(str, sizeof(str), "%Y/%m/%d %H:%M:%S", tm);// tmを文字列化 return std::string(str); } int main() { // タイムスタンプ(16進数指定) // バイナリに埋め込まれている値はリトルエンディアンなので // バイナリエディタ上の表示とバイト単位で逆順にして入力する time_t timestamp = 0x65abe167; std::cout << TimestampToDate(timestamp) << std::endl; return 0; }
結果
確かにファイルのプロパティ上の表示と一致する内容を得られたので、この数値がタイムスタンプであることは理解できた。今度はこれを書き換えてみる。
まず、適当な日付をタイムスタンプに変換するため、以下のプログラムを書いて走らせる。
#include <ctime> #include <string> #include <iostream> #pragma warning(disable:4996) // 時刻からタイムスタンプを作成 time_t DateToTimestamp(const std::string date) { struct tm tm = {}; int year, month, day; int hour, minute, second; sscanf(date.c_str(), "%d/%d/%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second ); tm.tm_year = year - 1900; tm.tm_mon = month - 1; tm.tm_mday = day; tm.tm_hour = hour; tm.tm_min = minute; tm.tm_sec = second; return mktime(&tm); } int main() { std::string date_str = "2022/01/01 5:25:30"; // 適当な日付 unsigned int timestamp = DateToTimestamp(date_str); printf("%x \n", timestamp); }
結果
こうして得られたタイムスタンプを、バイナリエディタで書き込む。書き込みにはStirlingを使っている。入力時、リトルエンディアンなのでバイト単位で逆順に指定する。
タイムスタンプはビルド時の色々なステップで入れられるらしいので、同じ値を見つけたらすべて書き換える。
先ほどはファイルのプロパティから確認したが、この日付はファイルのプロパティには実は表示されないので、PEヘッダを読むツールで確認する。探したところPEviewが適している。
以下からPEview version 0.9.9 ( .zip 31KB )をダウンロードする。
http://wjradburn.com/software/