web-dev-qa-db-ja.com

LinuxカーネルROP-カーネルコンテキストからユーザーランドに戻る?

脆弱なLinuxカーネルモジュール(32ビット)を使用していますが、これをうまく利用でき、そこから特権を獲得しました。基本的に、私のエクスプロイトはROPチェーンを使用してSMEPを無効にし、ユーザーランドにマップされた私のシェルコードに直接ジャンプします。ユーザーランドのシェルコードがcommit_creds(prepare_kernel_creds(0));を呼び出し、ユーザーランドコードに戻ります。

カーネルモードからユーザーモードに戻る方法がわかりません。いくつかの記事では、ユーザーモードに戻るにはiretアセンブリ命令を使用する必要があると指摘しています。シェルコードの後に​​明白にiretを挿入しましたが、動作しないようです。

デバイスファイルに書き込み、呼び出しトレースから:

? vfs_write
? SyS_write
? do_fast_syscall_32
? entry_SYSENTER_32

これは高速なシステムコールであり、sysexit命令を介して戻る必要があることに注意してください。

さて、どうすればカーネルをパニックにすることなくユーザーランドに戻ることができますか?実行する必要がある呼び出し(iret/sysexit)と、それをきれいに実行する方法を知る必要があります。

(私はIntelのマニュアルや他のたくさんのリソースを見てきましたが、今まで何も助けになりませんでした。)

5

私はシェルコードを別の方法で書いてしまいました。どうやって戻るかわからなかったので、カーネルにユーザーランドに戻る際の重労働を任せました。アイデアは、特権エスカレーションビットを実行し、レジスターとスタックを修正した状態で、脆弱な関数が戻るはずの場所にジャンプして戻ることでした。

カーネルが脆弱な関数から(オーバーフローしていない状態で)戻るとすぐに、gdbを介して何かに気付きました。 (アドレスは架空のものですが、とにかく概念を説明しています。)

(gdb) x/i $eip
0xadd1: ret
(gdb) x/xw $esp
0xadd1: 0xadd2
(gdb) x/6i 0xadd2
0xadd2: add esp,0x40
0xadd3: pop ...
0xadd4: pop ...
0xadd5: pop ...
0xadd6: pop ...
0xadd7: ret

したがって、戻り直後はx40バイトのスタックは使用されていませんであり、add esp命令で単純に消えます。したがって、この事実を利用して、24バイトの長さのROPチェーンを作成しました(この質問を書いているときにすでに作成しており、その仕事はSMEPを無効にしてユーザーランドのシェルコードにジャンプすることでした)。戻り時に4バイトでEIPが上書きされ、残りの20バイト(0x14)がそれに続いて未使用のスタックに入ります。これにより、未使用のスタックの0x2cバイトが残ります。

ただし、0xadd2に戻ると、スタックからさらに0x14バイトの貴重なレジスタ情報が失われ、レジスタが無効なデータでいっぱいになる危険があります。ユーザーランドシェルコードでadd 0x2cからespにして、実際のadd命令をスキップして、直接0xadd3にジャンプできます。

また、デバッグセッションからも、eaxebxを除くすべてが適切に復元されていました。オーバーフローによって両方が破棄されたため、関数が正常に復帰した場合と同様の値でそれらを復元する必要がありました。 (これを行うのは簡単でした:0xadd2にブレークポイントを設定し、info regから値を抽出しました)

だから、私の最終的なユーザーランドのシェルコードはこれでした:

Execute privesc payload -> add esp,0x2c -> register fixup -> jump to 0xadd3

これを行うと、コードパスは完全にクリーンに戻り、カーネルはユーザーモードに戻るというタスクを実行しました。

4