• トップ
  • ブログ一覧
  • C言語とgdbを用いたデコンパイルでバーナム暗号を復号化する
  • C言語とgdbを用いたデコンパイルでバーナム暗号を復号化する

    メディアチームメディアチーム
    2020.08.19

    IT技術

    デコンパイルの説明

    近年、サイバー犯罪などが増加し、個人・企業ともに、常にセキュリティインシデントに陥る危険性を持っています。

    セキュリティインシデント
    コンピュータの利用や情報管理、情報システム運用に関して保安上の脅威となること

    しかし、セキュリティの人材は、ほとんど全ての業界で不足していると言われています。

    そのため、エンジニアであれば、専門家でなくとも、社内のセキュリティの担当をする可能性があります。

    そこで今回は、セキュリティ解析の中でも様々な知識を必要とする「デコンパイル」について説明します。

    デコンパイルとは?

    コンパイルを行うと、実行可能ファイルが生成されます。

    実行可能ファイルからは、通常の方法ではソースコードを参照することができません。

    「デコンパイル」とは、その実行可能ファイルから、内部のメモリ情報を参考にすることで、元々のソースコードを再現することです。

    デコンパイルの活用法

    「デコンパイル」には、以下のような活用法があります。

    1. 実行ファイルが悪意をもったものか判断する
    2. 自らのファイルをデコンパイルされにくくすることで、模倣への対策を行う

    それでは、実際に「デコンパイル」の手順を見てみましょう。

    注意事項

    今回は、以下の記事で使用した CpawCTF の問題を利用します。

    なお、CTF の問題がなくても、デコンパイルの手順がわかるように記述しています。

    featureImg2020.06.11【第1回】セキュリティエンジニアを目指して「CTF」を解く!セキュリティエンジニアを目指して「CTF」を解いてくパソコンだけでなく、多くの通信機器がインターネットにつながる時代と...

    featureImg2020.06.12【第2回】セキュリティエンジニアを目指して「CTF」を解く!【第2回】セキュリティエンジニアを目指して「CTF」を解いていくセキュリティエンジニアへの需要が高まっているこの時代。...

    注意点

    1. デコンパイルした結果から模倣したソフトを配布することは、法律に反します。
      (CTFなどのデコンパイルされることを想定されているものは除く)
    2. 今回のコードは、 Windows10 と CentOS7 で動作確認を行っています。
    3. Windows10では、Visual Studio 2019 を利用してコンパイルします。
    4. CentOS7 では、ターミナルから gcc を用いてコマンドラインでコンパイルします。
    5. 情報は、2020年5月7日現在のものです。
    6. 今回紹介されている問題を 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 を用いて、実行ファイルのアセンブラコードを閲覧しました。

    そして、その中にあったバーナム暗号のプログラムを再現してみました。

    実行した見かけだけではわからない機能が、ファイルに隠されていることはよくあります。

    特に、悪意のあるファイルには注意しましょう。

    こちらの記事もオススメ!

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    メディアチーム
    メディアチーム
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background