cmd.exeの「歴史的理由」を取り除く

画像



ソフトウェア開発の分野は、「歴史的理由」という用語が最も頻繁に使用される人間活動の分野の1つです。 理解できる-さまざまなオペレーティングシステム、ブラウザーなどのカーネルのような多くの「長期にわたる」プロジェクトは、開発者の中の完璧主義者が反対を言っても、すべての人がその行動を変えるわけではないものの、膨大なもので彼らの存在の過程で成長しています。 ほとんどの場合、コードの大部分は長い間会社で働いていないプログラマーによって書かれたものであり、まだこの企業と自分の人生を結びつけているプログラマーでさえ、ソフトウェア複合体の他のコンポーネントが特定の変更に通常対応することを疑っています。 「いいえ、そのままにしておきます。」



この一例がcmd.exeです。 はい、はい、これは、Windowsファミリのすべての最新の(そうではない)オペレーティングシステムの提供に含まれているのと同じコマンドラインインタープリターです。 彼はかなりの歴史的理由を蓄積してきました-少なくともこのインタープリターに貼り付けてコピーする方法を思い出すだけで十分です(公平のために、Windows 10ではこの状況がついに修正され、 ConEmuのようなアプリケーションがこれを大いに助けます)。 しかし、今日は他の動作について説明します。これにより、cmd.exeに遭遇した人は初めて、これがまったく必要ないように思われます。



ご存知のように、cmd.exeが受け入れるコマンドの1つは「CD」です。 このコマンドの公式ヘルプは次を報告します。



C:\ Users \ Nikita.Trophimov> CD /?

現在のディレクトリの名前を表示または変更します。

[...]


すべてがシンプルに思えます。 引数なしでCDを呼び出す-現在のディレクトリへのパスが標準出力に表示され、別のディレクトリを引数として渡す-現在のディレクトリを指定されたディレクトリに変更する。 ユーザーがディスクと同時にディレクトリを変更することに決めた場合、ここでの落とし穴が始まります。 たとえば、「C:\ Windows \ system32」ディレクトリにいる場合、「CD D:\ books」コマンドはまったく何もしません。 私の意見では、これには新規ユーザーにとって明らかなものはまったくないので、Googleまたは公式文書によって保存されます。



/ Dスイッチを使用して、電流の変更に加えて電流ドライブを変更します

ドライブのディレクトリ。


もちろん、この問題とそのような振る舞いの出現の理由は、インターネット(たとえば、 ここ )で何度も議論されているので、そのようなことについては触れません。 代わりに、cmd.exeをデバッグして、「/ D」キーを明示的に指定する必要をなくします。



プロセスがどのように進んだか、そしてその結果は、カットの下で読みました。



OSのWindowsファミリの増加部分は64ビットですが、これは私の場合も例外ではありません。 すべての標準ユーティリティ(calc.exe、taskmgr.exe、cmd.exeなど)も64ビットアナログを取得しました。これらはデフォルトでオペレーティングシステムとともに提供されます。 逆に、これは、この場合、残念ながら、以前の記事で既におなじみのOllyDbgを使用できないことを意味します(たとえば、 ここで見つけることができます )(ところで、x64サポートの作業はまだ進行中です )。



どのようなオプションがありますか? 少なくともIDA Proと比較的新しいx64_dbgx64で動作します。 残念ながら、IDA Proの有料版のみがx64をサポートしているため、2番目のオプションで停止することをお勧めします。



cmd.exeのコピーを作成し、x64_dbgの最新バージョンのスナップショットをダウンロードして実行し、調査する実行可能ファイルをロードします。



画像



プログラムがブレークポイントで停止するまでF9を押します(ここでもOllyDbgの非常に多くのホットキーが機能するので便利です)。CPUのコンテンツを右クリックし、[検索]-> [文字列参照]ウィンドウで文字列「/ D」を探します:



画像



実行中のcmd.exeプロセスのウィンドウにコマンド「CD / DD:\ books」を入力して(もちろん、別のドライブにいると仮定して)、アドレス0x7F6D01F972Aのブレークで停止します。



画像



ブレークポイントの隣には、 _wcsnicmp関数の呼び出しがあり、渡された行の指定されたバイト数を比較するために使用されます。



画像



x86とは異なり、x64はまったく異なる呼び出し規則を使用することを理解することが重要です。



Microsoft x64呼び出し規則は、Microsoft WindowsおよびプリブートUEFI(x86-64のロングモード用)で使用されます。 最初の4つの整数またはポインター引数(順番に)にレジスタRCX、RDX、R8、R9を使用し、浮動小数点引数にXMM0、XMM1、XMM2、XMM3を使用します。 追加の引数がスタックにプッシュされます(右から左)。 64ビット以下の場合、整数の戻り値(x86と同様)がRAXで返されます。 浮動小数点の戻り値はXMM0で返されます。 64ビット長未満のパラメーターはゼロ拡張されません。 上位ビットはゼロになりません


この場合、「/ D」および「/ DD:\ books」は文字列引数として_wcsnicmp関数に渡され、R8レジスタは比較する必要があるバイト数に関する情報を格納します(この場合は2)。 もちろん、この場合、_wcsnicmp関数を呼び出した結果、EAXレジスタはゼロになり、プログラムはアドレス0x7F6D01F97F2に移動します。



最初に思い浮かぶのは、この遷移を無条件にすることです( JE命令をJMPに変更します)。したがって、プログラムは引数「/ D」が常に渡されたと見なします。 やってみましょう。 F9を押し、均一なソースデータの前のディレクトリに移動し、コマンド「CD D:\ books」を入力し(「/ D」キーがないことに注意してください)、 je cmd.7F6D01F97F2命令で0x7F6D01F9747にある行を選択し 、スペースバーを押してJEJMPに変更します。「Fill with NOP's」の横にチェックマークを付けることを忘れないでください。



画像



もう一度F9キーを押して、チームがまだ誤って作業を完了したことを確認しますが、少なくとも前回のように黙っていませんでした。



画像



JMP'eを中断し、トレースに取り組んでいます。 ジャンプの直後に、文字列の「トリミングされた」バージョンがRCXレジスタに保存されます。RCXレジスタは、 RBXレジスタで指定されたアドレスに保存されます。 より正確には、最初の2文字が削除されます(2、文字列はUnicodeであるため、 _wcsnicmp関数の署名と文字列リテラルの前の文字「L」で理解できるため、それぞれに2つが必要です。バイト、およびコマンドはRBX + 4を使用して文字列を「切り捨て」ます:



画像



これは、目的のプログラムを含む行からディレクトリへのパスを削除するためだけに行われると推測するのは簡単です。 "/ D"キーは2文字で構成されます。 もちろん、これを行う必要はもうありません。 現在、このようなアクションは、ユーザーが指定したディレクトリへのパスの一部を「トリミング」します。 さて、この命令をlea rcx、qword ptr ds:[rbx] (値はRCXレジスタに入力する必要があるため、埋めることはできません)に置き換えます。



画像



ここでも、「/ D」キーを指定せずにコマンドを入力します。そして、目的のディレクトリへの移行が実際に実行されていることがわかります。



変更を保存するには、Ctrl-Pで「パッチ」メニューを開き、必要なすべての変更が強調表示されていることを確認し、「パッチファイル」ボタンをクリックして、cmd.exeのパッチバージョンの名前を選択します。



残念ながら、「%WINDIR%\ system32」ディレクトリの元のcmd.exeをパッチを適用したもので置き換えても、Windowsは以前のファイルの実行可能バージョンをキャッシュから復元するので、パッチを適用したバイナリに別のショートカットを作成して使用します。



あとがき



時には小さなことでも私たちの生活を楽にし、より楽しくすることができます。逆に、状況を悪化させるだけです。 「/ D」フラグが欠落しているという落とし穴ですでに何度かトリップしている場合は、デバッガを選択してこの状況を修正してみませんか? バグと「歴史的理由」は常に発生することを忘れないでください。開発者は常にそれらを編集することを計画しているわけではありません。



公平に言えば、 PowerShellでは 、CDコマンドに「/ D」キーを指定する必要はありませんでした。



ご清聴ありがとうございました。また、この記事が誰かに役立つことを願っています。



All Articles