16
21

More than 3 years have passed since last update.

リバースエンジニアリングって何?ハッキング入門してみる

Posted at

注意事項

この記事ではクラッキングを助長する意図はありません。
また、この記事で紹介する手法はあくまでも一例です。小学生でも「オレ強い」できるレベルしか紹介する気はありません。
あくまで自分の作ったソフトウェアのセキュリティ向上にお役立てください。
商用ソフトウェアではこの手法はほぼ通用しませんし、改変したソフトウェアを使用することは訴訟リスクにもなりえます。

はじめに

こんにちは。Sohと申します。普段はiOSの脱獄アプリを販売したり、自分の生活を良くするために完全独学でプログラミングしている文系大学生です。
留年し就活のきつさに溺れています。(自業自得)
突然ですが、あなたの作ったソフトウェア、オープンソースでなくてもソースコードを見れるってご存知でした?

if (purchased) {
    //購入済みの場合の処理
}
else {
    //購入していない場合の処理
}

みたいな単純な処理をしていませんか?

これ、簡単にクラックできます。小学生でもできます。

リバースエンジニアリングって、能動的に勉強しなければ案外知らないことも多いはず。
今回は初歩編で、簡単なクラッキング手法について見ていきましょう。

具体例

今回は簡単なコードとして、以下のC言語で書かれたプログラムを見てみましょう。
実行環境はmacOS Catalinaです。

test.c
#include <stdio.h>
#include <string.h>

int main(void)
{
    char key1[9] = "ABCD";
    char key2[] = "1234";
    char password[9];

    printf("パスワードを入力してください:\t");
    fgets(password, sizeof(password), stdin);

    strcat(key1, key2);

    if(strcmp(password, key1) == 0){
        printf("\n%sは正しいライセンスキーです。\n", password);
    }
    else {
        printf("\n%sは正しいライセンスキーではありません。\n", password);
    }

    return 0;
}

非常にシンプルかつ稚拙なコードですが、何をしているかわかりやすいと思います。

fgetsでユーザーからの入力を受け取り、それがABCD1234だった場合、「[入力キー]は正しいライセンスキーです」というメッセージを表示します。
その他の文字列が入力された場合は、else部分で「[入力キー]は正しいライセンスキーではありません」というメッセージを表示します。
(バイナリ内のテキストを抽出するにはstringsコマンド等が使用できますが、今回は説明を割愛します)

こちらのコードをコンパイルしたバイナリを、無料のリバースエンジニアリングツールであるGhidraに突っ込んでみます。
ちなみに、有料のリバースエンジニアリングツールでは、HopperやIDA Proなどがあります。個人的にはこちらの2つのほうが慣れているので、適切な説明ができていないかもしれませんが、
今回は初心者用ということで敷居の低いGhidraで説明させてください。

Screen Shot 2020-01-07 at 20.20.56.png

バイナリをAnalyseすると、上の画像のように、アセンブリコードとpseudocode(擬似コード)が表示されます。
main関数のpseudocodeを見ると、ほぼ完全にソースコードが再現できていることがわかります。
このままでもパスワードがABCD1234であることはわかるのですが、今回は全ての入力を正しいライセンスキーであると認識するようにしたいです。

もっとわかりやすくしたいので、Window -> Function Graphを選択します。
Screen Shot 2020-01-07 at 20.27.07.png
すると、以上のような画面が表示されます。単純なプログラムですが、グラフになったことで幾ばくかフローがわかりやすくなりました。

もう少しわかりやすくするため、Function Graphの中の子ウィンドウを右クリックし、Edit Labelから名前を変更してみます。
Screen Shot 2020-01-07 at 20.31.21.png

CALL __stubs::_strcmp
CMP EAX, 0x0
JNZ Failed

あまり深くは説明しませんが、CALLでstrcmpを呼び出し、CMP(=Compare)でEAXを0x0(=0)を比較し、JNZ(=Jump not zero)で、もし0でなければFailed部分に飛ばす、という処理を行っています。
このJNZを、右クリック->Patch Instructionから

JMP Success

に変更すると、常にSuccess部分にJumpするような処理になります。
Oキーを押して、バイナリファイルをエクスポート、実行してみます。

Screen Shot 2020-01-07 at 20.51.43.png
このように、どんなパスワードでも受け付けるようになりました。

ちなみに商用ソフトウェアでは難読化されているものが多いですし、こんなに単純な構造はしていません。
とはいえ、結局はこの応用であり、アセンブリコードを直接いじらなくても、pseudocodeを見て、FuctionをオーバーライドするようなライブラリをInjectすることも可能であり、
そこまで敷居の高いことではありません。

おわりに

あまりうまく説明できなかった気がしますし、初歩の初歩なので「こんなの知ってるよ!」という方もいるでしょうが、あまり触れる機会がない方も多いかなと思います。
リバースエンジニアリングについての日本語での文献や動画って少ないんですよね。そもそも初心者が触れるものではないのかもしれませんが。
もし一定数反響があれば、一般的な対策も書いてみたいな、と思います。
乱文失礼いたしました。

16
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
21