ほぼすべての Mac アプリをクラックする方法 (およびそれを防ぐ方法)


Mac がセキュリティ攻撃やウイルスの標的になることはほとんどありませんが、ソフトウェアの著作権侵害には無縁ではありません。おそらく、Mac アプリは非常に簡単にクラックされるためです。ここではそれが起こる可能性とそれを防ぐ方法を説明します。

あなたの Mac アプリをクラックする方法

免責事項: 私はソフトウェアの著作権侵害に熱烈に反対しており、著作権侵害には参加しません。この記事を著作権侵害を推奨していると見る人もいますが、そうではありませんのでご安心ください。しかし、問題を曖昧にして無視することが容認できる解決策であるとは思いません。

そうですね、特にあなたというわけではありませんが、ここで言うあなたとは平均的な Mac 開発者のことです。 Mac アプリをクラックするのは簡単すぎます。簡単すぎます。たった 1 つのターミナル シェルでアプリをハッキングする方法を説明することで、これが最も一般的に行われる方法を明らかにし、私から身を守るよう説得していただければ幸いです。この種のハッキングを防ぐためのヒントをいくつか紹介してこの記事を終了します。

手順を進めるには、いくつかのコマンド ライン ユーティリティが必要になります。 Xcode ツールをインストールする必要があります。そして最後に、操作するアプリが必要になります。私が選んだ過剰、私がずっと前に書いたシェアウェアアプリ。

まずは、必要な 2 つのユーティリティ (otx と class-dump) があることを確認することから始めましょう。使うのが好きです自作私が選んだパッケージマネージャーとして。 vim を含むコマンド ライン ユーティリティのみを使用することに注意してください。 GUI を好む場合は、お好みのコード エディター、HexFiend および otx の GUI アプリを自由に使用してください。

$ sudo brew install otx
$ sudo brew install class-dump

最初のステップは、ターゲット アプリのヘッダーを侵入することです。このヘッダーは、何も知らずに開発者によって紳士的にそのまま残されています。

$ cd Exces.app/Contents/MacOS
$ class-dump Exces | vim

周囲をブラウズして、次の宝石を見つけます。

@interface SSExcesAppController : NSObject
{
[...]
BOOL registred;
[...]
- (void)verifyLicenseFile:(id)arg1;
- (id)verifyPath:(id)arg1;
- (BOOL)registred;

ここには何があるの?! (綴りが悪い) 変数と、登録に関連する 3 つのメソッドのように見えます。これからは、これらのシンボルに重点を置くことができます。これらのメソッドのソース コードを逆アセンブルして、さらに調べてみましょう。

$ otx Exces -arch i386

Exces はユニバーサル バイナリであり、アクティブなアーキテクチャのみを扱う必要があることに注意してください。この場合はインテルの i386 です。 verifyLicenseFile: が何を行うのか見てみましょう。

-(void)[SSExcesAppController verifyLicenseFile:]
[...]
+34 0000521e e8c21e0100 calll 0x000170e5 -[(%esp,1) verifyPath:]
+39 00005223 85c0 testl %eax,%eax
+41 00005225 0f84e2000000 je 0x0000530d
[...]
+226 000052de c6472c01 movb $0x01,0x2c(%edi) (BOOL)registred
[...]

これは直接の Objective-C コードではなく、C をコンパイルしたアセンブリです。各行の最初の部分であるオフセット +34 は、命令がメソッド内に何バイトあるかを示します。 0000521e はプログラム内の命令のアドレスです。 e8c21e0100 はバイトコードの命令です。 calll 0x000170e5 はアセンブリ言語の命令です。 -[(%esp,1) verifyPath:] は、otx がバイナリ内に残されたシンボルから Obj-C で表す命令を収集できるものです。

これを念頭に置くと、verifyLicenseFile: がメソッド verifyPath: を呼び出し、その後、登録されたブール値のインスタンス変数を設定していることがわかります。おそらく verifyPath: がライセンス ファイルの有効性をチェックするメソッドであると推測できます。ヘッダーから、verifyPath: はオブジェクトを返すため、パッチするには複雑すぎることがわかります。ブール値を処理するものが必要です。

gdb デバッガーで Exces を起動し、verifyLicenseFile: が呼び出されるタイミングを確認してみましょう。

$ gdb Exces
(gdb) break [SSExcesAppController verifyLicenseFile:]
Breakpoint 1 at 0x5205
(gdb) run

噛みつきはありません。起動時にブレークポイントにヒットしません。 verifyLicenseFile: と verifyPath: が 2 つの別個のメソッドであるのには十分な理由があると考えられます。 verifyLicenseFile: にパッチを適用して、registred を常に true に設定することもできますが、verifyLicenseFile: はおそらく、ユーザーが入力したライセンス ファイルをチェックするためにのみ呼び出されます。 gdb を終了し、代わりに verifyPath: を呼び出す別のコードを検索しましょう。 otx ダンプの awakeFromNib で次を見つけます。

-(void)[SSExcesAppController awakeFromNib]
[...]
+885 00004c8c a1a0410100 movl 0x000141a0,%eax verifyPath:
+890 00004c91 89442404 movl %eax,0x04(%esp)
+894 00004c95 e84b240100 calll 0x000170e5 -[(%esp,1) verifyPath:]
+899 00004c9a 85c0 testl %eax,%eax
+901 00004c9c 7409 je 0x00004ca7
+903 00004c9e 8b4508 movl 0x08(%ebp),%eax
+906 00004ca1 c6402c01 movb $0x01,0x2c(%eax) (BOOL)registred
+910 00004ca5 eb7d jmp 0x00004d24 return;
[...]

コードは verifyLicenseFile: とほぼ同じです。何が起こるかというと、次のとおりです。

  • verifyPath: が呼び出されます。 (+894コール)

  • テストは呼び出しの結果に基づいて行われます。 (+899テストル)

  • テキストの結果に基づいて、等しい場合にジャンプします。 (+901 je) je または jne (等しくない場合にジャンプ) が続くテストは、if ステートメントのアセンブリ言語です。

  • ジャンプしなかった場合、登録された ivar が設定されます。

awakeFromNib は起動時に実行されるため、このチェックをオーバーライドすれば、アプリが登録されていると思わせることができると考えて間違いありません。これを行う最も簡単な方法は、je を jne に変更し、本質的にその意味を逆にすることです。

ダンプ内で任意の jne ステートメントを検索し、それを je と比較します。

+901 00004c9c 7409 je 0x00004ca7
+14 00004d9f 7534 jne 0x00004dd5 return;

7409 は je 0x00004ca7 のバイナリ コードです。 7534 も同様のバイナリ コードです。 je のバイナリ コードをアドレス 00004c9c の 7534 に変更するだけで、クラックが得られるはずです。 gdb でテストしてみましょう。

$ gdb Exces
(gdb) break [SSExcesAppController awakeFromNib]
Breakpoint 1 at 0x4920
(gdb) r
(gdb) x/x 0x00004c9c
0x4c9c <-[SSExcesAppController awakeFromNib]+901>: 0x458b0974

awakeFromNib で中断するので、アプリがフリーズしている間にいじることができます。 x/x は、メモリ内の指定されたアドレスにあるコードを読み取ります。ここで、注意すべき紛らわしい点、それがエンディアンです。ディスク上ではバイナリ コードは正常ですが、Intel は最上位バイトを最後に配置するリトル エンディアン システムであるため、メモリ内の 4 バイト ブロックごとに反転されます。したがって、アドレス 0x4c9c のコードは 0x458b0974 として出力されますが、実際には 0x74098b45 です。先ほどの最初の 2 バイト 7409 を認識します。

最初の 2 バイトを 7534 に切り替える必要があります。方法をよりよく理解できるように、メソッドを逆アセンブルすることから始めましょう。関連するステートメントを見つけます。

0x00004c9c <-[SSExcesAppController awakeFromNib]+901>: je 0x4ca7 <-[SSExcesAppController awakeFromNib]+912>

次に、メモリ内のコードを編集しましょう。

(gdb) set {char}0x00004c9c=0x75
(gdb) x/x 0x00004c9c
0x4c9c <-[SSExcesAppController awakeFromNib]+901>: 0x458b0975
(gdb) set {char}0x00004c9d=0x34
(gdb) x/x 0x00004c9c
0x4c9c <-[SSExcesAppController awakeFromNib]+901>: 0x458b3475

ここでは、最初のバイトを 0x00004c9c に設定します。単純に 16 進数でカウントすることで、次のバイトがアドレス 0x00004c9d に来ることがわかり、そのように設定します。変更が正しく行われたかどうかを確認するために、もう一度分解してみましょう。

(gdb) disas
0x00004c9c <-[SSExcesAppController awakeFromNib]+901>: jne 0x4cd2 <-[SSExcesAppController awakeFromNib]+955>

おっと、間違えてジャンプ先を+912から+955に変更してしまいました。バイト コードの最初のバイト (74) は je/jne を表し、2 番目のバイトはオフセット、つまりジャンプするバイト数であることがわかります。 74 を 75 に変更するだけでよく、09 を 34 に変更する必要はありません。間違いを修正しましょう。

(gdb) set {char}0x00004c9c=0x75
(gdb) set {char}0x00004c9d=0x09

そして再度確認してみると…

0x00004c9c <-[SSExcesAppController awakeFromNib]+901>: jne 0x4ca7 <-[SSExcesAppController awakeFromNib]+912>

万歳!これは良さそうですね!アプリを実行して、亀裂を鑑賞しましょう。

(gdb) continue

うわー!勝利!私たちは参加しており、アプリは私たちが正規の顧客であると認識しています。無駄に過ごしてパーティーをする時間です! (サンフランシスコのダウンタウンにある Vessel ナイトクラブをお勧めします。) そうですね。この変更を永続的なものにする必要があります。現状では、gdb を終了するとすぐにすべてが消去されます。ディスク上の実際のバイナリ ファイル内のコードを編集する必要があります。編集したバイナリのうち、バイナリ全体で繰り返されないほど十分な大きさのチャンクを見つけてみましょう。

(gdb) x/8x 0x00004c9c
0x4c9c <-[SSExcesAppController awakeFromNib]+901>: 0x458b0975 0x2c40c608 0x8b7deb01 0xa4a10855
0x4cac <-[SSExcesAppController awakeFromNib]+917>: 0x89000141 0x89082454 0x89042444 0x26e82414

これはコードのメモリ表現であり、0x00004c9c で始まる 4 バイトの 8 ブロック全体です。エンディアンを考慮して、それらを逆にする必要があり、次のようになります。

0x75098b45 0x08c6402c 0x01eb7d8b 0x5508a1a4
0x41010089 0x54240889 0x44240489 0x1424e826

一連の最初のバイトは、74 を 75 に切り替えたものです。これを元に戻すと、元のバイナリ コードは次のように推測できます。

0x74098b45 0x08c6402c 0x01eb7d8b 0x5508a1a4
0x41010089 0x54240889 0x44240489 0x1424e826

バイナリを 16 進エディタで開いてみましょう。私は vim を使用しましたが、この時点では任意の 16 進エディタを自由に使用してください。 HexFiend には優れた GUI があります。

(gdb) quit
$ vim Exces

これはバイナリを ASCII テキストとしてロードしますが、あまり役に立ちません。次のように 16 進数に変換します。

:%!xxd

vim は次のように 16 進数をフォーマットします。

0000000: cafe babe 0000 0002 0000 0012 0000 0000 ................

コロンの前の最初の部分はブロックのアドレスです。これに 16 バイトが続き、2 バイトのセグメントに分割されます。ちなみに、すべての Mach-O バイナリは 16 進バイトのカフェベイブで始まります。ドランク・カーネルのプログラマーはおそらく面白いと思ったでしょう。美しい 16 進コードがロードされたので、コードの最初の 2 バイトを検索して置換しましょう。

/7409

くそー。結果が多すぎて意味がわかりません。さらに 2 バイトを追加しましょう。代わりに「7409 8b45」を検索すると、結果が 1 つだけ表示されます。

001fc90: 0089 4424 04e8 4b24 0100 85c0 7409 8b45 ..D$..K$....t..E

それを次のように編集します。

001fc90: 0089 4424 04e8 4b24 0100 85c0 7509 8b45 ..D$..K$....t..E

それをバイナリ形式に変換して戻し、保存して終了します。

:%!xxd -r
:wq

そして…完了です!作業を確認するには、gdb でアプリを起動し、[SSExcesAppController awakeFromNib] にブレークして逆アセンブルします。

$ gdb Exces
(gdb) break [SSExcesAppController awakeFromNib]
Breakpoint 1 at 0x4c90
(gdb) r
(gdb) disas

私たちの仕事を賞賛してください:

0x00004c9c <-[SSExcesAppController awakeFromNib]+901>: jne 0x4ca7 <-[SSExcesAppController awakeFromNib]+912>

gdb を終了し、Finder からアプリを再起動して、リート栄光。

これを防ぐ方法

Objective-C を使用すると、アプリの内部を非常に簡単に変更できます。アプリのライセンス メカニズムを純粋な C でプログラムしてみてください。そうすることで、バイナリを回避する方法を見つけることがすでに困難になります。また、デバッグ シンボルの除去、PT_DENY_ATTACH の使用、バイナリのチェックサムの実行という 3 つの簡単なヒントについての私の古い記事もお読みください。これを実装すると、アプリのクラックが非常に困難になります。

本当に熟練したハッカーは常にあなたの保護を回避する方法を見つけますが、最低限のセキュリティを実装すれば、アマチュアの 99% を排除できます。私は熟練したハッカーではありませんが、非常に基本的な知識があったので、すぐにこれを解体してしまいました。上記のさまざまな簡単なヒントを実装するのにほとんど時間はかかりませんが、私にとってはかなりの苦痛で、諦めていたでしょう。

Kenneth Ballenegger はクールな Mac および iPhone ソフトウェアを開発しています。彼のところを訪ねて個人ブログデザイン、ソフトウェア、生活の世界についてさらに執筆してください。彼に連絡できるのは次のアドレスです。[メールで保護されています]