脆弱な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のマニュアルや他のたくさんのリソースを見てきましたが、今まで何も助けになりませんでした。)
私はシェルコードを別の方法で書いてしまいました。どうやって戻るかわからなかったので、カーネルにユーザーランドに戻る際の重労働を任せました。アイデアは、特権エスカレーションビットを実行し、レジスターとスタックを修正した状態で、脆弱な関数が戻るはずの場所にジャンプして戻ることでした。
カーネルが脆弱な関数から(オーバーフローしていない状態で)戻るとすぐに、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
にジャンプできます。
また、デバッグセッションからも、eax
とebx
を除くすべてが適切に復元されていました。オーバーフローによって両方が破棄されたため、関数が正常に復帰した場合と同様の値でそれらを復元する必要がありました。 (これを行うのは簡単でした:0xadd2
にブレークポイントを設定し、info reg
から値を抽出しました)
だから、私の最終的なユーザーランドのシェルコードはこれでした:
Execute privesc payload -> add esp,0x2c -> register fixup -> jump to 0xadd3
これを行うと、コードパスは完全にクリーンに戻り、カーネルはユーザーモードに戻るというタスクを実行しました。