私たちの企業では、タスクは
非常に重要なファイルを転送する際のセキュリティレベルを高めることでした。 一般に、単語ごとに、一定の量を蓄積しているため、scpを使用して転送し、eTokenタイプのリモートコントロールに認証用の証明書の秘密キーを保存する必要があるという結論に達しました。
アイデアは良いように見えましたが、それを実装する方法は? それから、クライアントバンクが会計で働いていなかったことを思い出しました。話をする名前etsdk.dllのライブラリがないことを呪い、好奇心が私を襲い、それを選んだのです。
一般に、開発会社はWebサイトでSDKを配布していますが、そのためにはソフトウェア開発会社として登録する必要があり、これは明らかに私ではありません。 インターネットでドキュメントを見つけることができませんでしたが、好奇心が勝ち、それを自分で理解することにしました。 図書館-ここにあります、時間があります、誰が私を止めるのですか?
私が最初にしたことは、NirSoft DLL Export Viewerを起動したことで、ライブラリによってエクスポートされた適切な関数のリストが表示されました。 リストは見栄えが良く、トークンを操作するときのアクションのロジックとシーケンスがトレースされます。 ただし、1つのリストでは不十分であり、どのパラメーターを、どの順序で転送し、結果を取得するかを理解する必要があります。
そして、私たちの若さを思い出してOllyDbgバージョン2.01を起動し、クライアントバンクで使用されるccom.dll暗号システムCrypto-Comライブラリをロードし、その中にetsdk.dllライブラリを使用して、その方法を理解し始めます。
実行可能ファイルがないため、ライブラリはOllyバンドルのloaddll.exeを使用してロードされるため、完全なデバッグを夢見ることさえできません。 実際、デバッガーを逆アセンブラーとして使用します(はい、IDAがありますが、私はIDAを使用したことはなく、一般に支払われます)。
コンテキストメニューを呼び出して、[検索]> [すべてのモジュール間呼び出し]を選択し、名前で結果を整理し、ET *で始まる関数を探しますが、見つかりません。 これは、ライブラリが動的に接続することを意味するため、同じリストでGetProcAddress呼び出しを探し、それらを調べて、特定の試みでETReadersEnumOpen関数のアドレスを見つけようとします。
悪くない。 受信した関数アドレスは、MOV DWORD PTR DSタイプのコマンドによってメモリに保存されます:[10062870]、EAX、そのような各コマンドを選択し、コンテキストメニューを呼び出し、[参照の検索]> [アドレス定数]を選択します。 開いたウィンドウに、現在のコマンドと関数呼び出しのすべての場所が表示されます。 それらを調べて、呼び出されている関数の名前をコメントにしてください。これにより、将来の生活が楽になります。
これらの関数を適切に呼び出す方法を理解する時が来ました。 最初から始めて、読者に関する情報を取得する方法を学びましょう。 ETReadersEnumOpen関数を呼び出す場所に渡し、残ったコメントのおかげで、ETReadersEnumNextとETReadersEnumNextとETReadersEnumCloseの両方が1つの関数に集中していることがわかります-明らかに、リーダーのリストを取得することに従事しています。
すべての関数はcdecl呼び出し規約を使用します。 これは、結果がEAXレジスタに返され、パラメータが右から左にスタックを通過することを意味します。 さらに、これは、すべてのパラメーターがダブルワードの次元を持ち、そうでない場合は拡張されるため、私たちの生活が簡素化されることを意味します。
ETReadersEnumOpen呼び出しの近傍を見てみましょう:
1つのパラメーターが渡されます。これは、ローカル変数へのポインターです。呼び出しの後、結果が0でない場合、制御は明示的なデバッグコードに転送され、等しい場合は先に進みます(ZFフラグとOFフラグが等しく、フラグがOFコマンドTESTは常に0にリセットされます。 したがって、私は次の順序を結びます:変数は参照によって関数に渡され、そこにいくつかの列挙識別子が返され、その結果、関数はエラーコードまたはエラーがない場合は0を返します。
ETReadersEnumNextに移動します。
2つのパラメーターが渡されます:ETReadersEnumOpen(列挙の識別子)を使用して取得した変数の値と、明らかに次の値が返されるローカル変数へのポインター。 さらに、パラメータは右から左に順番に転送されるため、最初のパラメータが識別子であり、2番目が結果ポインタです。 エラーコードは引き続きEAXを介して返され、ループの設計から判断すると、エラーを報告するためだけでなく、リストするものがないことを示すためにも使用されます。
ETReadersEnumCloseはさらに単純です。列挙識別子が渡されますが、結果は誰にも迷惑をかけません。
これらの機能の理解をテストする時が来ました。 ここで私は余談を余儀なくされました。事実、私は専門職のシステム管理者であり、したがって真面目なコンパイルされたプログラミング言語は完全に私のものではありません。 仕事には、Linux用のBashとPythonがさらに必要ですが、Windowsで何かをすばやく積み重ねる必要がある場合は、気に入った
AutoItを使用します。
私のプラスは次のとおりです。
- モビリティ(インタープリターとスクリプトエディターは完全に移植可能)、
- GUIを使用した簡単な作業、
- 外部ライブラリを接続する機能(十分ではないにしても)(プログラミング言語にとっては些細なことは知っていますが、スクリプト言語にとってはそれほど重要ではありません)
- スクリプトを実行可能ファイルに組み込む機能。
短所:
- 暗黙的な型変換と表される型の数が不十分です。
- レコード(および連想配列)とOOPの欠如(一般的には存在しますが、COMオブジェクトに対してのみ存在するため、存在しません)。
この余談は、AutoItで関数を使用する例を作成するという事実にあります。 言語での暗黙的な型付けに関連して、外部ライブラリから関数を呼び出すことはやや不格好に見えますが、機能します。
始めましょう:率直に言って、どの関数がどのサイズを返すのかわからないので、最初に大きなバッファーを与えて、何が起こるか見てみましょう。 開始するコード:
Dim $ETSdkDll=DllOpen(
実行すると、次のような結論が得られます。
Buffer before: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Buffer after: 44 6F C8 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Return value: 0
数回実行すると、最初の4バイトのみが変更されていることがわかります。つまり、4バイト整数が識別子として使用されます。つまり、この関数をこの状態に呼び出すコードをコームすることができます。
Func ETReaderEnumOpen() Local $id=DllStructCreate(
ETReadersEnumNext関数を使用した同様の実験では、バッファの最初の260バイトにリーダー名とゼロが含まれていることが示されました。 この関数への順次呼び出しは、システム内のすべてのリーダーによってリストされました(たとえば、ruTokenの下に3つが事前に作成されました)。 eTokenのリーダーは、接続されているトークンの数に応じて動的に作成され、最も興味深いことに、バッファーの261番目のバイトが1に設定されます。これは、明らかに、リーダーとライブラリの互換性を示します。 逆アセンブルされたコードを見ると、261番目のバイトが0に等しいレコードが処理されていないことがわかります。 すべてのリーダーのキロバイトバッファーの最後までの他のすべてのバイトは0で、違いはありません。
それで、読者を見つけました。次は何を理解する必要があります。 関数のリストを調べた後、呼び出しシーケンスは次のようにすべきであるという結論に達しました:最初に目的のリーダーをバインドし、この段階で挿入されたトークンに関する一般的な情報を見つけてからログインし、その後ファイルシステムにアクセスします。 したがって、次のインライン関数はETTokenBindとETTokenUnbindです。
ETTokenBindは複雑で理解しにくいように見えますが、しばらく掘り下げた後、2つのパラメーターが関数に渡されるという結論に達しました。最初のパラメーターは328バイト(0x0148)のバッファーへのポインターであり、2番目はリーダーの名前を持つ文字列へのポインターです。 実験を通して、バッファの最初の4バイトで識別子が返されることがわかりました(以降:バインディング識別子)。 残りのバッファが割り当てられているのはなぜですか-今のところは謎です。 私が実験しないトークン、バッファの残りの324バイトはゼロで満たされたままでした。 指定された識別子は論理的であり、ETTokenUnbindおよびETTokenRebind関数の引数として正常に使用されます。
行の次の関数はETRootDirOpenです。 結果へのポインター、バインディングID、および定数の3つのパラメーターが必要です。 この機能にはいくつかの機能があります。
最初:この関数の返された結果は、ゼロに等しい(成功)だけでなく、下位2バイトが数値0x6982に等しいかどうかもチェックされ、結果がこの数値に等しい場合、制御は関数に転送され、続いてETTokenLoginを呼び出してから再試行しますETRootDirOpenを呼び出します。 このことから、0x6982は「Authorization Required」を意味するエラーコードであると結論付けることができます。 今後、ファイルやフォルダーを操作する他のすべての機能は同じように配置されます。
2番目:パラメーターの1つとして、この関数は定数0xF007を取ります。 コードには他の定数を使用した呼び出しはありません。 おそらく、この定数はトークン(多くのルートフォルダー?)に書き込まれた情報を何らかの形で特徴付けているのでしょう。 2バイト定数のすべての値に対してブルートフォースを実行しようとしましたが、トークンは値0x0001、0xF001-0xF00Bにのみ応答しました(ちなみに、認証を要求しませんでした)。 後で、新しく初期化されたトークンで同じフォルダーが使用できることがわかりました。 これについてしばらく考えた後、開発者の計画によると、さまざまなルートフォルダーがさまざまな目的に使用され、0xF007はキー用であると書かれているという結論に達しました。
3番目:関数によって返された値はスクリーンショットには表示されませんが、以前に割り当てられた328バイトのバッファーの中央に戻ります。このバッファーから、検討中のトークンに関連するさまざまな識別子とデータを格納する構造であると判断できます。
承認の試みが終わったので、今度は対処します。 ETTokenLogin関数は、バインディング識別子とバッファーへのポインターという2つのパラメーターを受け取ります。 最初はバッファが何らかの結果を出力するために使用されたと考えましたが、実験は次のアルゴリズムが使用されたことを示しました:ポインタがゼロまたは空の文字列を指す場合、ライブラリはパスワードを要求するインターフェースウィンドウを描画し、空でない文字列を指す場合-この文字列パスワードとして使用されます。 ETTokenLogoutは、バインディング識別子という1つのパラメーターのみを受け入れます。
次の関数グループ:ETDirEnumOpen、ETDirEnumNext、およびETDirEnumClose。 コードを調べずにそれらを解こうとすることができます。 一般に、これらはETReadersEnum *と同じように機能するはずですが、唯一の違いは、現在のフォルダーの識別子がETDirEnumOpenにパラメーターとして渡されることです。 チェック-動作します。
ETFilesEnumOpen、ETFilesEnumNext、およびETFilesEnumClose関数グループは、同じ方法で機能するために必要なだけですが、これを確実に検証することはできません。 どうやら、調査中のトークンのルートフォルダーにファイルはないようです。つまり、ETDirOpen関数を使用してフォルダーツリーをさらに深く掘り下げます。
このAPIは、最初のパラメーターを使用して結果を返すという伝統を引き継いでいるように思われるため、今回もこれが当てはまると仮定します。 2番目のパラメーターは、関数に渡される前に、MOVZX EDI、DIコマンドを使用して変更されます。 単語はダブルワードに展開されます。 明らかに、これは4バイトのパラメーターで2バイトのフォルダー名を渡すために必要です。 さて、物事のロジックによると、3番目のパラメーターは開いているフォルダーの識別子です。 試してみました-判明しました。 ETDirCloseは驚くことなく推測できます:1パラメーター-フォルダー識別子。
そのため、トークン上のすべてのファイルとフォルダーをリストするのに十分なことを学びました。 次の簡単なコードでそれを行います(ここではDllCallを呼び出しません。記事の最後にあるモジュールテキストのすべての関数に対応します)。
Func PrintDir($Id,$Prefix) Local $EnumId=ETDirEnumOpen($Id) While 1 Local $dir=ETDirEnumNext($EnumId) If @error Then ExitLoop ConsoleWrite($Prefix&
コンソールでの結果:
Aladdin Token JC 0: (dir)1921 (dir)DDDD (file)0002 (file)0003 (file)0004 (file)0001 (file)A001 (file)B001 (file)C001 (file)AAAA (file)D001
いいね!
さて、フォルダを開いて表示する方法を学びました。ファイルを開いて読む方法を学びましょう。 ETFileOpenは3つのパラメーターを受け入れるため、最初にETDirOpenと同じことを試みます。結果、ファイル名、フォルダーID、ブレークオフ:開発者は最後の2つのパラメーターを逆にしました。 まあ、少なくともETFileCloseは驚くことなく動作します。
ETFileRead。 最悪の機能、なぜなら 5つのパラメーターを認識します。 そんなにどこ? 必要なものをリストしてみましょう:どこから読み込むか(ファイル)、どこから読み込むか(バッファ)、どれだけ読み込むか、どこから読み込むか。 何をどのように理解しようとしましょう:

ご覧のとおり、ETFileRead関数に渡される3番目のパラメーターは常に0xFFFFであるため、これが読み取りデータの長さであると想定する傾向があります。 残りの4つのパラメーターは、外部から同じ順序でFileReadHereを呼び出した関数に送られます。 次の図は、この関数の近傍呼び出しを示しています。 最初のパラメーターの値は、アドレスESI + 8のメモリから取得されます。 このアドレスへのポインターはFileOpenHere関数で使用され(同じ原則にちなんで命名されます)、開いているファイルの識別子がそこに書き込まれます。 2番目のパラメーターはゼロに等しいため、ファイルの読み取りの開始点を担当するように割り当てます。 3番目のパラメーター(ETFileReadの4番目)はなんとなく濁っているため、結果バッファーへのポインターとして割り当てます。 5番目のパラメーターはまったく異常です。 アドレスESI + 12からの単語がその中に配置され、ダブルワードに展開されます。 これまでのところ、私が見たオフセットはすべて4の倍数でした(12は4の倍数ではありません。0x12、つまり10進数で18です)。 アドレスESI + 10は付近のどこにも記載されていませんが、ESI + 0CはFileGetInfoHereに渡されるため、最初にETFileGetInfo関数を処理する必要があります。 簡単です。最初のパラメーターはファイル識別子で、2番目は結果バッファーへのポインターです。 呼び出し後、バッファ内の1、2、3、7、および8バイトが変更されます。 今後、最後の2バイトがファイルサイズであることがわかります。 ETFileRead関数と、その出力バッファを初期化する関数に渡されるのはこの値です。 ETFileGetInfoの結果の最初の2バイトはファイル名であることが判明しました。 3番目の値は理解できませんでしたが、トークン上の1つのファイルに対してのみ1に設定されました。 したがって、次のパラメータの順序が出現します。ファイル識別子、読み取り開始ポイント、読み取る最大バイト数、バッファポインタ、バッファサイズ。
ETFileGetInfoに触れたので、すぐにETDirGetInfoを実装する必要があります。パラメーターの順序は同じで、ファイルではなくフォルダーの識別子のみが関係します。 返される結果:識別子によるフォルダー名。
これで、トークンからの読み取りが完了しました。次はトークンに書き込みます。 フォルダーを作成することから始めましょう。 ETDirCreate関数のパラメーター:結果へのポインター(明らかに、フォルダーの作成後、フォルダーが開き、識別子がここに返されます)、フォルダーの名前、親フォルダーの識別子、および0。4番目のパラメーターはコードにハードコーディングされており、それが何に影響するのかまだわかりません。 任意の値に対してフォルダーが正常に作成されます。 ETDirDeleteは1つのパラメーターのみを受け入れるため、これは明らかに開いているフォルダーの識別子です。 ETFileCreateは、ETDirCreateと同様の結果へのポインター、フォルダー識別子、ファイル名、ファイルサイズ、および5番目のパラメーターの5つのパラメーターを取ります。 5番目のパラメーターがゼロ以外の値に設定されている場合、次にこのファイルに対してETFileGetInfoが呼び出されると、結果の3番目のバイト(わかりにくいもの)が1に設定されます。考えて、実験を行い、属性が設定されたら入力する必要があることを確認しましたパスワード、そうでない場合、これは必要ありません。 私が実験したトークンに、そのようなファイルが1つしかなかったことは面白いです。 他のすべてのファイルがこのキーで暗号化されることを願っています。 ETFileDeleteは、ETDirDeleteと同様に、驚くことなく機能します。
このライブラリで参照される最後の関数はETFileWriteです。 それは4つの引数を取ります:ファイル識別子、ゼロ(実験は、ファイルの先頭に相対的な混合であることを示します)、データとデータサイズのあるバッファへのポインタ。 ファイルは展開されないことに注意してください。 オフセットとファイル長の合計がファイルサイズを超える場合、記録は行われないため、ファイルサイズを変更する必要がある場合、ファイルを削除して新しいサイズで再作成する必要があります。
さらに、ライブラリのエクスポートテーブルをリコールすると、さらに5つの関数がありますが、Crypto-Com暗号化情報保護ツールと連携するこのライブラリには、それらの呼び出しは実装されていません。 幸いなことに、同じ銀行はCIPFを操作するためのMessage-Proライブラリ-mespro2.dllも配布しています。mespro2.dllはトークンも使用でき、ETTokenLabelGet呼び出しなど、もう少しトークンを使用できます。
スクリーンショットは、最初のケースでは2番目のパラメーターがゼロであり、2番目のパラメーターではある種の数値であるという2つの関数呼び出しがあることを示しています。 3番目のパラメーターは常にポインターであるため、これが結果であり、最初のパラメーターであると想定します-トークンを含むバンドルの識別子を仮定することは論理的です 2番目のパラメーターとしてゼロから開始しようとします-バッファーの最初の4バイトが値0x0000000Aに変更されました。 10、これは名前「TestToken」の長さであり、末尾にゼロバイトがあります。 しかし、ポインターによって3番目のパラメーターにダブルワードが返される場合、必要なサイズのバッファーへのポインターを2番目のパラメーターに転送する必要があることがわかります。 したがって、この順序を終了します。最初に関数を実行すると、2番目のパラメーターはNULLポインターになり、3番目はダブルワードポインターになります。 次に、必要なサイズのバッファーを初期化し、関数を2回実行します。2番目のパラメーターはバッファーへのポインターです。
しかし、さらに4つの関数の呼び出しはここでは実装されていなかったため、総当たりと直感で実装を受け取りました。呼び出された関数に渡されるパラメーターが少なすぎると、プログラム実行中に重大なエラーが発生することがわかりました。これにより、残りの関数のパラメーターの数を実験的に選択できます:
ETTokenIDGet:3
ETTokenMaxPinGet:2
ETTokenMinPinGet:2
ETTokenPinChange:2
ETTokenIDGetは単純な値を返すには多すぎるパラメーターを受け入れるため、ETTokenGetLabelと同じ方法で実行します。最初の試行で判明し、トークンの側面に書かれた数字の文字列を返します。
一方、ETTokenMaxPinGetとETTokenMinPinGetには多数のパラメーターがあり、単一の数値を返すのに理想的です。 最初のパラメーター-バンドルの識別子、2番目-数値へのポインターを試します。 その結果、トークン設定で指定されたパスワードの最大長と最小長を取得します。
ETTokenPinChangeは、名前に基づいて、パスワードをトークンにそれぞれ変更する役割を果たし、バンドル識別子と新しいパスワードを含む文字列へのポインターのみを受け入れる必要があります。 初めて試しているときに、エラーコード0x6982が表示されます。これは、知っているように、トークンにログインする必要があることを意味します。 論理的です。 ログインと短いパスワードで繰り返します-エラー0x6416を受け取ります。 パスワードの長さがポリシーに準拠していないと判断します。 長いパスワードで繰り返します-実現します。
ここで、すべての機能を1つのモジュールにまとめて保存します。他のプロジェクトに含めます。 モジュールのテキストは次のとおりです。
etsdk.au3 ;Func ETReadersEnumOpen() ;Func ETReadersEnumNext($EnumId) ;Func ETReadersEnumClose($EnumId) ;Func ETTokenBind($ReaderName) ;Func ETTokenRebind($BindId) ;Func ETTokenUnbind($BindId) ;Func ETTokenLogin($BindId,$Pin=
そのため、トークンファイルシステムで何でもできます。これを示すために、あるトークンから別のトークンに内容をコピーする簡単なスクリプトを書きました。「概念実証」レベルのスクリプト、つまり 「正しい」アプリケーションにあるはずのチェックは多くありませんが、2番目の有効なトークンを取得できます。eTokenCopy.au3 #include <etsdk.au3> #include <GUIConstantsEx.au3> #include <StaticConstants.au3> #NoTrayIcon Opt(
Crypto-Com、Crypto-Pro、Message-Pro、Signature、さらにはWillowなど、到達可能なすべての暗号情報保護ツールを試しました。これらのキーはすべて正常にコピーされ、機能しました。しかし、どのように?
キーはトークンから回復可能であるべきではありませんか?答えはeToken仕様にあります。実際には、取得できないキーが実際に存在しますが、RSAアルゴリズムを使用した暗号変換にのみ役立ちます。どのCPSSも考慮されていません...いいえ、このように:ロシア連邦の領土で使用するためにFSBによって承認されたCPSSはどれもRSAを使用しておらず、すべてGOST- *に基づいた暗号変換を使用しているため、eTokenはフラッシュドライブにすぎませんパスワードと複雑なインターフェース。