C言語とgdbを用いたデコンパイルでバーナム暗号を復号化する
IT技術
デコンパイルの説明
近年、サイバー犯罪などが増加し、個人・企業ともに、常にセキュリティインシデントに陥る危険性を持っています。
セキュリティインシデント
コンピュータの利用や情報管理、情報システム運用に関して保安上の脅威となること
しかし、セキュリティの人材は、ほとんど全ての業界で不足していると言われています。
そのため、エンジニアであれば、専門家でなくとも、社内のセキュリティの担当をする可能性があります。
そこで今回は、セキュリティ解析の中でも様々な知識を必要とする「デコンパイル」について説明します。
デコンパイルとは?
コンパイルを行うと、実行可能ファイルが生成されます。
実行可能ファイルからは、通常の方法ではソースコードを参照することができません。
「デコンパイル」とは、その実行可能ファイルから、内部のメモリ情報を参考にすることで、元々のソースコードを再現することです。
デコンパイルの活用法
「デコンパイル」には、以下のような活用法があります。
- 実行ファイルが悪意をもったものか判断する
- 自らのファイルをデコンパイルされにくくすることで、模倣への対策を行う
それでは、実際に「デコンパイル」の手順を見てみましょう。
注意事項
今回は、以下の記事で使用した CpawCTF の問題を利用します。
なお、CTF の問題がなくても、デコンパイルの手順がわかるように記述しています。
注意点
- デコンパイルした結果から模倣したソフトを配布することは、法律に反します。
(CTFなどのデコンパイルされることを想定されているものは除く) - 今回のコードは、 Windows10 と CentOS7 で動作確認を行っています。
- Windows10では、Visual Studio 2019 を利用してコンパイルします。
- CentOS7 では、ターミナルから gcc を用いてコマンドラインでコンパイルします。
- 情報は、2020年5月7日現在のものです。
- 今回紹介されている問題を CpawCTF で解くには、level1, level2 までのすべての問題を解いておく必要があります。
デコンパイルを行う
Q23.[Reversing]またやらかした!
200pt
またprintf()をし忘れたプログラムが見つかった。
とある暗号を解くプログラムらしい…
reversing2000
問題
問題は、以下のページを参照してください。
(level1、level2 の問題を見てからアクセスしてください)
【CpawCTF】
https://ctf.cpaw.site/questions.php?qnum=23
実行環境
今回の説明では、Linux 環境での操作を前提に説明します。
なお、ファイルをデコンパイルするときは、端末に悪影響がないように仮想環境を利用することをお勧めします。
プログラムを実行してみる
1$ chmod 777 rev200
2$ ./rev200
「rev200」は実行ファイルですが、実行しても何も起きません。
しかし、本当に何もしないプログラムなのでしょうか。
デコンパイル実行
gdb コマンドを用いて、中のメモリを閲覧してみましょう。
1$ gdb ./rev200
2(gdb) disas main
これでメイン関数が実行されます。
実行結果
以下のような結果になると思います。
問題をダウンロードできない場合は、こちらのデコンパイル結果を参照してください。
10x080483ed <+0>: push ebp
20x080483ee <+1>: mov ebp,esp
30x080483f0 <+3>: push edi
40x080483f1 <+4>: push ebx
50x080483f2 <+5>: add esp,0xffffff80
60x080483f5 <+8>: mov DWORD PTR [ebp-0x78],0x7a
7//とにかく値を代入していきますbitずつずれていきます。またなぜかメモリが降順です。
80x080483fc <+15>: mov DWORD PTR [ebp-0x74],0x69
90x08048403 <+22>: mov DWORD PTR [ebp-0x70],0x78
100x0804840a <+29>: mov DWORD PTR [ebp-0x6c],0x6e
110x08048411 <+36>: mov DWORD PTR [ebp-0x68],0x62
120x08048418 <+43>: mov DWORD PTR [ebp-0x64],0x6f
130x0804841f <+50>: mov DWORD PTR [ebp-0x60],0x7c
140x08048426 <+57>: mov DWORD PTR [ebp-0x5c],0x6b
150x0804842d <+64>: mov DWORD PTR [ebp-0x58],0x77
160x08048434 <+71>: mov DWORD PTR [ebp-0x54],0x78
170x0804843b <+78>: mov DWORD PTR [ebp-0x50],0x74
180x08048442 <+85>: mov DWORD PTR [ebp-0x4c],0x38
190x08048449 <+92>: mov DWORD PTR [ebp-0x48],0x38
200x08048450 <+99>: mov DWORD PTR [ebp-0x44],0x64
210x08048457 <+106>: mov DWORD PTR [ebp-0x7c],0x19
220x0804845e <+113>: lea ebx,[ebp-0x40]
230x08048461 <+116>: mov eax,0x0
240x08048466 <+121>: mov edx,0xe
250x0804846b <+126>: mov edi,ebx
260x0804846d <+128>: mov ecx,edx
270x0804846f <+130>: rep stos DWORD PTR es:[edi],eax
280x08048471 <+132>: mov DWORD PTR [ebp-0x80],0x0 //カウンタを0にする。
290x08048478 <+139>: jmp 0x8048491 <main+164>
30
31//ループが開始されます
32
330x0804847a <+141>: mov eax,DWORD PTR [ebp-0x80]
340x0804847d <+144>: mov eax,DWORD PTR [ebp+eax*4-0x78] //配列の値を代入
350x08048481 <+148>: xor eax,DWORD PTR [ebp-0x7c] //排他的論理和を取る
36/*ここで使っている「0x7c」は「0x08048457 <+106>: mov DWORD PTR [ebp-0x7c],0x19」
37 の値です。つまり 25 と排他的論理和を取っていることになります。*/
380x08048484 <+151>: mov edx,eax
390x08048486 <+153>: mov eax,DWORD PTR [ebp-0x80]
400x08048489 <+156>: mov DWORD PTR [ebp+eax*4-0x40],edx
410x0804848d <+160>: add DWORD PTR [ebp-0x80],0x1 //配列要素を1足している
420x08048491 <+164>: cmp DWORD PTR [ebp-0x80],0xd //繰り返し回数を比較
430x08048495 <+168>: jle 0x804847a <main+141> //繰り返し数が足りなければ戻る
44
45//+<141>に戻ります
46
470x08048497 <+170>: mov eax,0x0
480x0804849c <+175>: sub esp,0xffffff80
490x0804849f <+178>: pop ebx
50---Type <return> to continue, or q <return> to quit---
510x080484a0 <+179>: pop edi
520x080484a1 <+180>: pop ebp
530x080484a2 <+181>: ret
コメント文に、実際にどのようなことが行われているか書いています。
結果解析
35 行目を例に、読み解き方を見てみましょう。
10x08048481 <+148>: xor eax,DWORD PTR [ebp-0x7c]
先頭から「レジスタ上の位置」「命令」「対象となる変数など」を表しています。
「命令」の部分は、「アセンブラ言語」という機械語とプログラミング言語の中間のような言語で書かれています。
読み解いてみる
<+141> から <+168> までがループです。
ebx-0x80 から配列を逆順に、0x19 という値と排他的論理和を取り続けています。
4 ずつデクリメントしているのは、初めの配列の定義で数字がメモリを占有する量を加味して、4 ずつずらして代入しているからです。
大事な箇所は
解析では、危険な命令が実行されている箇所に注意して、確認する必要があります。
特に大切なのは、排他的論理和をとっている xor と、比較を行っている cmp の部分です。
C 言語で結果を再現してみる
解析結果を参考に、C 言語でこの結果を再現してみます。
C言語で再現したコード
1#include <stdio.h>
2
3int main(void){
4 int i;
5 int ebp;
6 unsigned int edi [14];
7 unsigned int edx[14];
8 //配列に値を代入していく
9 edi[0] = 0x7a;
10 edi[1] = 0x69;
11 edi[2] = 0x78;
12 edi[3] = 0x6e;
13 edi[4] = 0x62;
14 edi[5] = 0x6f;
15 edi[6] = 0x7c;
16 edi[7] = 0x6b;
17 edi[8] = 0x77;
18 edi[9] = 0x78;
19 edi[10] = 0x74;
20 edi[11] = 0x38;
21 edi[12] = 0x38;
22 edi[13] = 0x64;
23
24 for(i=0; i<14; i++){
25 printf("%c", edi[i]);
26 }
27 printf("\n");
28
29 //暗号化し表示する はじめ
30 ebp = 0;
31 for(ebp=0;ebp <14;ebp++) {
32 edx[ebp] = edi[ebp] ^ 0x19; //「^」は排他的論理和を取る演算子です。
33 }
34 for(i=0; i<14; i++){
35 printf("%c", edx[i]);
36 }
37 //暗号化し表示する おわり
38
39 return 0;
40}
もとのプログラムにはなかった、配列の中を表示する機能を追加しています。
最初の配列の値
実行をしても、私たちには表示機能があるものしか見ることができません。
初めの配列の中身を表示すると、以下のようになります。
1zixnbo|kwxt88d
排他的論理和をとると
最初の値について、排他的論理和をとった後の値を表示すると、以下のような結果になりました。
1cpaw{vernam!!}
暗号化方式 Vernam
このプログラムでは、何をしていたのか分かりましたか?
答えは、全ての文字を一定の値と排他的論理和を取る「バーナム(Vernam)暗号」という暗号化です。
この暗号化方式の特徴は、もう一度、同じ値と排他的論理和をとれば元の値に戻る、という点です。
他の暗号化方式と組み合わせて使われることの多い、暗号化方式です。
さいごに
いかがでしたでしょうか。
今回は gdb を用いて、実行ファイルのアセンブラコードを閲覧しました。
そして、その中にあったバーナム暗号のプログラムを再現してみました。
実行した見かけだけではわからない機能が、ファイルに隠されていることはよくあります。
特に、悪意のあるファイルには注意しましょう。
こちらの記事もオススメ!
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit
おすすめ記事
immichを知ってほしい
2024.10.31