ご指摘のとおり、文字列を参照するアセンブリ手順は通常、次のようになります。
push offset aString
組み立ててリンクした後、これは実際のアドレスに解決されます。
push 0x00ABCDEF
これにより、次の 2 つのオプションが提供されます。
- データの書き込み:の内容を変更します
aString
(つまり、 が指すメモリ0x00ABCDEF
)
- コードを書く:への参照を変更する
aString
書き込みデータ
標準 C 文字列リテラル (メモリ内の文字の不変配列) を含むソース コードがコンパイルされると、通常、実行時に文字列は他のすべての読み取り専用データと共に読み取り専用ページにマップされます。このデータは通常、プログラムのメモリ フットプリントを削減するために連続してパックされます。これは、より大きな文字列を書き込もうとしてヒットしている問題です。次のデータを上書きすると、この上書きされたデータへの参照はすべて、大きな文字列の中央を指すようになります。
データを変更してより長い文字列を書き込むことは簡単ではありません。これは、元の機能的な動作を失わないようにするために、文字列の後のすべてのデータを前方にシフトする必要があるためです。その後、シフトされたデータへのすべての参照を更新する必要があります (その一部はポインター演算で動的に計算される場合があります)。私が言うように、このプロセスは自明ではありません - 完全な (もしあれば) シンボリック情報なしで、再配置に関してリンカーのタスクを再現しようとしています。
コードを書く
簡単な方法は、新しい文字列を任意の場所に書き込むことです。これは、プロセスで既に使用されていないが予約済みのメモリ (一般に「コード ケーブ」と呼ばれます) であるか、DLL を挿入するときにマップする文字列リテラルである可能性があります。または、注入後に実行時にこれを動的に割り当てることもできます。
次のステップは、すべての参照を見つけてaString
、代わりに新しい文字列を参照するように置き換えることです。
ボーナス方法:)
このレベルでリバース エンジニアリングを掘り下げているため、迂回/傍受/インストルメンテーションの概念に出くわした可能性があります。ここで同様のアプローチを適用して、すべての参照をインターセプトし、実行時にリダイレクトすることができます。これにより、上記の「コードを書く」方法よりもパフォーマンスが大幅に低下しますが、すべてのアクセスが捕捉され、リダイレクトされることが保証されます。
アクセス時のハードウェア ブレークポイントは、文字列が指すデータに設定されます。ブレークポイントがトリガーされると、一部のレジスタが文字列のアドレスを保持します。アセンブリでは、これは次のようになります。
mov esi, 0x00ABCDEF
...
最初の文字にアクセスすると、コードは次のようになります。
mov al, byte ptr ds:[esi]
ブレークポイントに到達すると、スレッド コンテキスト ( SetThreadContext
Windows の場合) を設定esi
して、新しい文字列を指すように値を変更できます。