Q21.[Reversing]reversing easy! 実際にバイナリファイルを書き換えたい
概要
CpawCTFの第21問の問題
フラグを出す実行ファイルがあるのだが、プログラム(elfファイル)作成者が出力する関数を書き忘れてしまったらしい…
当初の回答はlinuxのstringコマンドで出力された中身を提出したが、「出力する関数を書き忘れてしまったらしい…」とあるので出力する方法があるっぽく見える書き方なので、もう少し掘り下げてみた。
使用したツール(コマンド)
- Ghidra
- hexedit
- xxd
手順
- 実行ファイルをGhidraに読み込ませる
- 怪しいソースコードを見つける
- 実行ファイルを編集するツール(ここではhexeditとxxd)で編集
1. 実行ファイルをGhidraに読み込ませる
Ghidraに実行ファイル(reverse100)を読み込ませソースコードを解析してもらうと以下のようになった
ここにGhidraの使い方を追加すべきか?
Ghidraで解析すると以下の内容。
undefined4 main(void)
{
int in_GS_OFFSET;
undefined2 local_4b;
undefined local_49;
int local_48;
int local_44;
int local_40 [9];
undefined4 local_1a;
undefined2 local_16;
int local_14;
local_14 = *(int *)(in_GS_OFFSET + 0x14);
local_1a = 0x77617063;
local_16 = 0x7b;
local_40[0] = 0x79;
local_40[1] = 0x61;
local_40[2] = 0x6b;
local_40[3] = 0x69;
local_40[4] = 0x6e;
local_40[5] = 0x69;
local_40[6] = 0x6b;
local_40[7] = 0x75;
local_40[8] = 0x21;
local_4b = 0xa7d;
local_49 = 0;
local_44 = 5;
printf("%s",&local_1a);
if (local_44 != 5) {
for (local_48 = 0; local_48 < 9; local_48 = local_48 + 1) {
putchar(local_40[local_48]);
}
}
printf("%s",&local_4b);
if (local_14 != *(int *)(in_GS_OFFSET + 0x14)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}
0x79
などは16進数の文字コードでこれがつながることで答えのフラッグになる。
怪しいのはここ
if (local_44 != 5) {
for (local_48 = 0; local_48 < 9; local_48 = local_48 + 1) {
putchar(local_40[local_48]);
}
putchar(○○)
は、1文字ずつ文字や数字の出力ができるC言語の入出力関数の一つ。
local_44が5でなければ文字列の出力ができそう。つまりここを編集できれば出力可能である。
local_44の値をどのように変更すべきか。
バイナリエディタの編集
変更すべき変数が見つかったので、その変数がバイナリファイルのどこにあるかを探索する。
探索はGhidraのデコンパイルしたソースコードから、local44の変数をクリック(画像の黒丸)し、Bytesの項目(画像の赤丸)をクリックすると、該当する変数の部分にジャンプできる。
該当部分のメモリアドレスを覚えておき※、バイナリエディタで編集する。
一応バイナリファイルのcpuの命令コードを理解していれば、objdump
から特定することも出来なくはないが、Ghidraなどの逆アセンブルツールを使ったほうが楽。
※書いた人は、変数を定義しているパターンを見つけた後、元のコードでlocal44に代入してそうな部分で見つけた。
hexeditで編集する
編集すべき場所がなんとなく特定できたところで、編集を行う。
windowsにもいくつかバイナリエディタがインストールできるらしいが、ChatGPTに聞いたところ、linuxのhexeditやxxdを提案されたのでこれを使う。
-
sudo apt install hexedit
でhexeditをインストール -
hexedit 実行ファイル
でコマンドを実行(実行ファイルは事前に保存しておくと良い 1敗) - 該当するメモリの部分に行き、
C7 44 24 1C 05
→C7 44 24 1C 01
などに変更 - 保存
xxdで編集する
xxdはファイルを16進数などにダンプするもの
-
xxd 実行ファイル > 実行ファイル.hex
のようにhexを作成する -
vim
やnano
などのテキストエディタで編集→保存 xxd -r 実行ファイル.hex 保存先の実行ファイル
これでいけるはず
まとめ
実際に変数の値をするだけで出力できるので、問題作成者の意図的にはこれが回答となるのだろう。
流れとしては、
変更したいメモリアドレスを特定(デコンパイルしたソースコードから探す)→バイナリエディタで編集
別の問題で実行ファイルに出力関数などを入れることもできないのかなどを考えたが、これは難しいらしいので今は考えないものとする。
(おまけ)linuxのコマンドだけでバイナリファイルを解釈できるのか
今回Windows上でGhidraを利用したが、極力既存のツールなどで解析できないか(linuxであればlinux内限定で解析するなど)を試した。
objdump
でアセンブリコード(cpuがどのような命令を受けているのかを見ることができるもの)を確認することができる。
objdump -d ファイル名
テキストファイルに保存したい場合、
objdump -d ファイル名 > 保存先テキスト.txt
.init
や.plt
や.text
や.fini
などがあるが、mainがソースコードに該当する部分なのでそこから解釈ができる。(この辺りの解釈についても自分の理解を書こうとしたが、これを記載すると何が書きたいのかとっ散らかるのでここでやめておく)
0804849d <main>:
804849d: 55 push %ebp
804849e: 89 e5 mov %esp,%ebp
80484a0: 83 e4 f0 and $0xfffffff0,%esp
80484a3: 83 ec 50 sub $0x50,%esp
80484a6: 65 a1 14 00 00 00 mov %gs:0x14,%eax
80484ac: 89 44 24 4c mov %eax,0x4c(%esp)
80484b0: 31 c0 xor %eax,%eax
80484b2: c7 44 24 46 63 70 61 movl $0x77617063,0x46(%esp)
80484b9: 77
80484ba: 66 c7 44 24 4a 7b 00 movw $0x7b,0x4a(%esp)
80484c1: c7 44 24 20 79 00 00 movl $0x79,0x20(%esp)
80484c8: 00
80484c9: c7 44 24 24 61 00 00 movl $0x61,0x24(%esp)
80484d0: 00
80484d1: c7 44 24 28 6b 00 00 movl $0x6b,0x28(%esp)
80484d8: 00
80484d9: c7 44 24 2c 69 00 00 movl $0x69,0x2c(%esp)
80484e0: 00
80484e1: c7 44 24 30 6e 00 00 movl $0x6e,0x30(%esp)
80484e8: 00
80484e9: c7 44 24 34 69 00 00 movl $0x69,0x34(%esp)
80484f0: 00
80484f1: c7 44 24 38 6b 00 00 movl $0x6b,0x38(%esp)
80484f8: 00
80484f9: c7 44 24 3c 75 00 00 movl $0x75,0x3c(%esp)
8048500: 00
8048501: c7 44 24 40 21 00 00 movl $0x21,0x40(%esp)
一応ChatGPTに聞くことでどのような内容かをある程度推定してくれるので、解釈自体は可能。(コードは間違っていたが)
-
.init
- プログラムの実行前に実行される初期化処理
-
.plt
- 共有ライブラリ内の関数を呼び出す
- 例えばprintなどもlibuxに共有ライブラリとしてある
-
.text
- プログラムの主要なコード(命令)がある
- ソースコードは基本ここ
-
.fini
- プログラムの終了処理
- リソースの開放などに使われるらしい
Discussion