Wing IDEセキュリティスタディ









健康! このプログラムのことを聞いたことがないのに驚かないでしょう。 私のように、 Python Debuggerが役立つ日まで。 はい、私は知っています、 pdbがありますが、その機能とその表示方法はまったく好きではありませんでした。 短い検索の後、この素晴らしい製品に出会いました。 Pythonアプリケーションのデバッグに役立つすべてのものがあります(すぐに言います。私はこの言語を勉強したことがないので、不正確な点が出てきても、誓わないでください)。



注意:記事の手順を繰り返して、あなた自身の危険とリスクで行動します!


だから、私たちは始めています...



私は言わなければならない患者は珍しいです。 まず、バイトコードではありますが、ソース(!!!)が付属しています。 第二に、それが時々起こるように...一般的に、あなたは見るでしょう。



まず、プログラムをダウンロードします( Wing IDE Professional v 5.1.4 )。 インストールし、フォルダーを検査します。 メインの実行可能ファイルは./bin/wing.exeにあります。 それを実行します。 Pythonの欠如に呪われているので、インストールしてください。 バージョン2が必要です(現在はバージョン2.7.9です )。 プログラムを再度実行します。 今回は、パッチのインストールと再起動を提案しています。 やってみましょう。



これで、ライセンスを要求するウィンドウがポップアップします(プロバージョンがあるため)。 いくつかのナンセンスを紹介しましょう:











次の答えが得られます。











面白いのは、プログラム自体がキーの長さ(ハイフンを含まない20)と、それが始まるべき文字を教えてくれることです。 原則として、これから保護の調査を開始することはすでに可能です-この行はプログラムファイルにあります。

もっと面白い。 検索結果はファイル./bin/2.7/src.zipで見つかりました!



はい、はい。 すべてが本当にそうです:プログラムにはソースが付属しています。 それらを掘り下げる必要があります。



ステージ2:ソースコードを調べる



Total CommanderTotal Archive検索をオンにして、その行をもう一度見つけます。 行はファイルにあります: ./bin / 2.7 / src.zip / process / wingctl.pyoPYOファイルは、「 最適化されたPythonバイトコードを持つバイナリです。



Pythonには幸いなことに、いくつかのバイトコードデコンパイラがあります。 検索の邪魔にならないように、便利なリンクを提供します。



  1. Easy Python DecompilerEPD -2つの逆コンパイラー接続されているシェル( Uncompyle2およびDecompyle ++ );
  2. Fork Uncompyle2-他の人が解凍できないものを時々解凍します。


そのため、src.zipアーカイブ全体をsrcフォルダーに解凍し(近くに既にsrcフォルダーがあり、そこに他のものをすべて解凍します)、 EPDを設定します。











プロセスの終了を待っており、何が起こったかを調べに行きます。 そして、出力は_disで終わるファイルを逆コンパイルしました。 名前を.pyに変更します。 すべて問題ありませんが、末尾が_dis_failedのファイルもあることがわかります。これは、逆コンパイラがこれらのファイルをマスターしなかったことを意味します。 幸いなことに、ファイルは1つしかありません: edit / editor.pyo_dis_failed



それにDecompyle ++を設定してみましょう...同じトラブル。 予備の逆コンパイラへのリンクを与えたのも不思議ではありません。 他の人が失敗したことをしたのは彼でした。 次に、srcフォルダーからすべてのpyo / pycファイルを削除し、 .py * _dis.pyに名前変更します。



次に、 opensource.zipアーカイブに対して上記のすべてを繰り返し、同じ名前の隣接フォルダーに解凍します。 external.zipアーカイブに触れないことにしました。これを調べると、Python用に個別にインストールできるライブラリがあることがわかるからです。 やってみましょう:



  1. pip install docutils
          
          



  2. py2pdf- 外部フォルダーに入れます。
  3. Imaging-1.1.7-実行してインストールします。 外部フォルダーから削除できます。
  4. pygtk-以前のファイルと同じ。


残りのライブラリ( pyscintilla2およびpysqlite )は、以前のように単にexternal.zipアーカイブから抽出され、逆コンパイルされます。



ステージ3および4:ソースコード自体。 デバッグ



Pythonスクリプトを検索した後、プログラムフォルダーのルートにあるwing.py ファイルに出会いました。 そして、最初のコメントは私たちに伝えます:

 # Top level script for invoking Wing IDE. If --use-src is specified # as an arg, then the files in WINGHOME/src, WINGHOME/external, # WINGHOME/opensource will be used; otherwise, the files in the version # specific bin directory will be used if it exists.
      
      







簡単に言うと、スクリプトに--use-srcパラメーターを指定すると、起動時にsrcのソースファイル(スクリプトではなく) Wing IDEのルートディレクトリのopensourceフォルダーが使用されます。



ルートフォルダーを見ると、別のsrcフォルダーとその中の.pyファイルが見つかりました。 それらをsrcフォルダーに上書きしてスローします(ここでは、すべて同じオリジナルで、逆コンパイルされたファイルではありません)。



ここで、3つすべてのフォルダー(少し上に表示)をプログラムのルートディレクトリにコピーします。 ローンを作ってみましょう...



Wing IDEを実行し、その中のbinディレクトリからwing.pyファイルを開きます。 次に、パラメータフィールドの[デバッグ ] -> [デバッグ環境 ]メニューで、 -- use-srcを指定します。 デバッガーを開始します( F5キー)。 フォルダーのコピーに関するすべての詐欺が成功した場合、実行中のWing IDEの 2番目のコピーを取得します。 いいね!



次に、親のWing IDEでファイルを開き、 に不良ライセンスIDwingctl.py )に関する行を見つけ、このメッセージの前にブレークを入れます。











デバッグされたWing IDEで、 [ヘルプ]-> [ライセンスを入力 ]メニューに移動し、ルールに従ってキーを入力します( 20文字、セットの最初の['T'、 'N'、 'E'、 'C​​'を覚えてください) 、「1」、「3」、「6」] ):











[ 続行]を押すと、おばあちゃんの力が得られます。 最初の興味深い関数: abstract.ValidateAndNormalizeLicenseID(id)F7で説明しましょう。 もう1つあります: __ValidateAndNormalize(id) 。 それに行きましょう。



最初の検証チェック:

 for c in code: if c in ('-', ' ', '\t'): pass elif c not in textutils.BASE30: code2 += c badchars.add(c) else: code2 += c
      
      





ライセンスID文字がtextutils.BASE30セットに属している必要があることがわかります。

 BASE30 = '123456789ABCDEFGHJKLMNPQRTVWXY'
      
      





__ValidateAndNormalize(id) noの他のチェックと同様 入力した識別子を修正し、再度繰り返します。 すでに最初の文字のチェックに合格しています:

 if len(id2) > 0 and id2[0] not in kLicenseUseCodes: errs.append(_('Invalid first character: Should be one of %s') % str(kLicenseUseCodes))
      
      





次に、2番目の文字を示します。

 if len(id2) > 1 and id2[1] != kLicenseProdCode:
      
      





 kLicenseProdCodes = {config.kProd101: '1', config.kProdPersonal: 'L', config.kProdProfessional: 'N', config.kProdEnterprise: 'E'} kLicenseProdCode = kLicenseProdCodes[config.kProductCode]
      
      





なぜなら Professionalバージョンがあり、2番目の文字はNでなければなりません-正しい、そして戻ります。 abstract.ValidateAndNormalizeLicenseID(id)はエラーなしで歩いた。 素晴らしい。 おっと:

 if len(errs) == 0 and id[0] == 'T': errs.append(_('You cannot enter a trial license id here'))
      
      





Fixim(私はEを選択しました)、そして続行します。 コードの下に目を向けると、以前のチェック以外に何も見つかりませんでしたので、 F5でのデバッグを大胆に手放しました。 新しいウィンドウ:











ランダムなテキストを入力すると、エラーメッセージ(再び20文字で、アクティベーションコードはAXXで始まるはずです)が表示され、ファイル内で検索され、ブレークに配置されます。











最初のチェック関数: abstract.ValidateAndNormalizeActivation(act) 。 再びBASE30アフィリエーションをチェックします。 既に渡したプレフィックスを確認します。

 if id2[:3] != kActivationPrefix: errs.append(_("Invalid prefix: Should be '%s'") % kActivationPrefix)
      
      





次の興味深い場所:

 err, info = self.fLicMgr._ValidateLicenseDict(lic2, None) if err == abstract.kLicenseOK:
      
      





self.fLicMgr._ValidateLicenseDictに移動します 。 ここで、ライセンスからのハッシュが形成されます:

 lichash = CreateActivationRequest(lic) act30 = lic['activation'] if lichash[2] not in 'X34': hasher = sha.new() hasher.update(lichash) hasher.update(lic['license']) digest = hasher.hexdigest().upper() lichash = lichash[:3] + textutils.SHAToBase30(digest) errs, lichash = ValidateAndNormalizeRequest(lichash)
      
      





このブロックを実行した後にlichashの内容を見ると、そのテキストはアクティベーションコード入力ボックスに表示されるリクエストコードに似ていますが、いくつかの数字は異なります。 さて、アクティベーションに影響を与えないランダムな部分がいくつかあると思います(偶然、さらに確認されます!)。



次に、最初の3文字がアクティベーションコードから切り取られ、ハイフンが削除され、 BASE16に変換され、必要に応じてゼロが追加されます。

 act = act30.replace('-', '')[3:] hexact = textutils.BaseConvert(act, textutils.BASE30, textutils.BASE16) while len(hexact) < 20: hexact = '0' + hexact
      
      





そして、それは最も興味深いものです:

 valid = control.validate(lichash, lic['os'], lic['version'][:lic['version'].find('.')], hexact)
      
      





一部のコントロールvalidate関数を呼び出し、 lichash要求コード )、キーが作成されるオペレーティングシステムの名前、プログラムのバージョン、および変換されたアクティベーションコードを渡します。 なぜこの場所に注意を払ったのですか? 実際、このコントロールpyd-ファイル( 監視するオブジェクトの名前を追加し、 __ file__フィールドを確認することで確認できます )であり、1つのエクスポートされた関数( validateではない)彼女がする方法を知っている。 さて、 Hex Raysデコンパイラからそれを見てみましょう...



ステップ5:これはPythonではありません



IDA Proコントロールctlutil.pyd )をプルし、エクスポートされたinitctlutil関数を確認します。

 int initctlutil() { return Py_InitModule4(aCtlutil, &off_10003094, 0, 0, 1013); }
      
      





off_10003094は、エクスポートされたメソッドの名前とアドレスが示される構造です。 ここに検証があります:

 .data:100030A4 dd offset aValidate ; "validate" .data:100030A8 dd offset sub_10001410
      
      





sub_10001410プロシージャを含むすべてのコードの中で、これは最も興味深いように見えます。

 if ( sub_10001020(v6, &v9) || strcmp(&v9, v7) ) { result = PyInt_FromLong(0); }
      
      





sub_10001020に行きましょう 。 視覚的に変数に名前を付けるのではなく、名前を付けて適切に名前を付けるのは興味深いでしょう。 やってみましょう。 IDA Proデバッガーをセットアップします。











スクリーンショットからはすべてが明らかだと思います。最終的にpyd-ファイルをロードするアプリケーションを指定しました。



ここで、ブレーカーをsub_10001020の先頭に配置し、変数と入力パラメーターの調査を開始します。 短いデバッグプロセスの後、次の関数リストにアクセスします。

Convert_reqest_key関数コード
 int __usercall convert_reqest_key@<eax>(char *version@<eax>, const char *platform@<ecx>, const char *activation_key, char *out_key) { unsigned int len_1; // edi@1 const char *platform_; // esi@1 char *version_; // ebx@1 int ver_; // eax@2 signed int mul1; // ecx@3 signed int mul2; // esi@3 signed int mul3; // ebp@3 bool v11; // zf@15 const char *act_key_ptr; // eax@31 char v13; // dl@32 const char *act_key_ptr_1; // eax@35 unsigned int len_2; // ecx@35 char v16; // dl@36 const char *act_key_ptr_2; // eax@39 unsigned int len_3; // ecx@39 char v19; // dl@40 int P3_; // ebx@42 const char *act_key_ptr_3; // eax@45 unsigned int len_4; // ecx@45 char v23; // dl@46 unsigned int P4; // ebp@47 signed int mul4; // [sp+10h] [bp-18h]@0 unsigned int P3; // [sp+14h] [bp-14h]@1 unsigned int P2; // [sp+18h] [bp-10h]@1 unsigned int P1; // [sp+1Ch] [bp-Ch]@1 len_1 = 0; platform_ = platform; version_ = version; P1 = 0; P2 = 0; P3 = 0; if ( !strcmp(platform, aWindows) ) { ver_ = (unsigned __int8)*version_; if ( *version_ == '2' ) { mul1 = 142; mul2 = 43; mul3 = 201; mul4 = 38; goto LABEL_31; } if ( (_BYTE)ver_ == '3' ) { mul1 = 23; mul2 = 163; mul3 = 2; mul4 = 115; goto LABEL_31; } if ( (_BYTE)ver_ == '4' ) { mul1 = 17; mul2 = 87; mul3 = 120; mul4 = 34; goto LABEL_31; } } else if ( !strcmp(platform_, aMacosx) ) { ver_ = (unsigned __int8)*version_; if ( *version_ == '2' ) { mul1 = 41; mul2 = 207; mul3 = 104; mul4 = 77; goto LABEL_31; } if ( (_BYTE)ver_ == '3' ) { mul1 = 128; mul2 = 178; mul3 = 104; mul4 = 95; goto LABEL_31; } if ( (_BYTE)ver_ == '4' ) { mul1 = 67; mul2 = 167; mul3 = 74; mul4 = 13; goto LABEL_31; } } else { v11 = strcmp(platform_, aLinux) == 0; LOBYTE(ver_) = *version_; if ( v11 ) { if ( (_BYTE)ver_ == '2' ) { mul1 = 48; mul2 = 104; mul3 = 234; mul4 = 247; goto LABEL_31; } if ( (_BYTE)ver_ == '3' ) { mul2 = 52; mul1 = 254; mul3 = 98; mul4 = 235; goto LABEL_31; } if ( (_BYTE)ver_ == '4' ) { mul1 = 207; mul2 = 45; mul3 = 198; mul4 = 189; goto LABEL_31; } } else { if ( (_BYTE)ver_ == '2' ) { mul1 = 123; mul2 = 202; mul3 = 97; mul4 = 211; goto LABEL_31; } if ( (_BYTE)ver_ == '3' ) { mul1 = 127; mul2 = 45; mul3 = 209; mul4 = 198; goto LABEL_31; } if ( (_BYTE)ver_ == '4' ) { mul2 = 4; mul1 = 240; mul3 = 47; mul4 = 98; goto LABEL_31; } } } if ( (_BYTE)ver_ == '5' ) { mul1 = 7; mul2 = 123; mul3 = 23; mul4 = 87; } else { mul1 = 0; mul2 = 0; mul3 = 0; } LABEL_31: act_key_ptr = activation_key; do v13 = *act_key_ptr++; while ( v13 ); if ( act_key_ptr != activation_key + 1 ) { do P1 = (P1 * mul1 + activation_key[len_1++]) & 0xFFFFF; while ( len_1 < strlen(activation_key) ); } act_key_ptr_1 = activation_key; len_2 = 0; do v16 = *act_key_ptr_1++; while ( v16 ); if ( act_key_ptr_1 != activation_key + 1 ) { do P2 = (P2 * mul2 + activation_key[len_2++]) & 0xFFFFF; while ( len_2 < strlen(activation_key) ); } act_key_ptr_2 = activation_key; len_3 = 0; do v19 = *act_key_ptr_2++; while ( v19 ); if ( act_key_ptr_2 != activation_key + 1 ) { P3_ = 0; do P3_ = (P3_ * mul3 + activation_key[len_3++]) & 0xFFFFF; while ( len_3 < strlen(activation_key) ); P3 = P3_; } act_key_ptr_3 = activation_key; len_4 = 0; do v23 = *act_key_ptr_3++; while ( v23 ); P4 = 0; if ( act_key_ptr_3 != activation_key + 1 ) { do P4 = (P4 * mul4 + activation_key[len_4++]) & 0xFFFFF; while ( len_4 < strlen(activation_key) ); } sprintf(out_key, a_5x_5x_5x_5x, P1, P2, P3, P4); return 0; }
      
      







そして、この関数を呼び出す場所は次の形式を取ります。

 if ( convert_reqest_key(version, platform, request_key, out_key) || strcmp(out_key, act_key_hash) ) { result = PyInt_FromLong(0); }
      
      





これらすべてから、 convert_reqest_key関数を使用してリクエストコードが変換され、変換されたアクティベーションコードと比較されると結論付けることができます。 その変換を覚えていますか?

次に、必要に応じて、最初の3文字がアクティベーションコードから切り取られ、ハイフンが削除され、 BASE16に変換され、ゼロが埋め込まれます


したがって、正しいアクティベーションコードを取得するために、次の操作を実行できます。

  1. 変換関数convert_reqest_keyを実行します。
  2. strcmpの実行場所で、 out_keyの内容を探します。
  3. out_keyの先頭にある余分なゼロを削除します。
  4. out_keyBASE30に変換し直します。
  5. 削除された文字を行の先頭に追加します( AXX )。
  6. オプションで、5文字ごとにハイフンを付けます。


私は愚かに哲学するつもりはありませんが、 Pythonプログラムコードに印刷を絞り込みます。

 print("AXX" + textutils.BaseConvert("FCBCFEFD2FF684FA6A4F", textutils.BASE16, textutils.BASE30))
      
      





出力にキーがあります:

ウィングライド-2015/05/24 04:03:47- AXX3Q6BQHKQ773D24P58




アクティベーションキーの入力フィールドに入力すると、大事なものを受け取りました。











結果



ご覧のように、ハッキングのプロセスはそれほど面倒ではありません! それのコンパイルされたバージョンであなた自身のソースコードを探る...これは、もちろん面白いです。



著者がソースをプログラムに添付した理由はわかりません(ほとんどの場合、バイトコードの形で)。 しかし、これには価値がないことを理解していると思います!



どうもありがとう。



All Articles