Kernel Exploitを勉強し初めて2ヶ月ほどが経ちました。 やっていて思うのは、カーネルランドのUAFやヒープオーバーフローなどは非常に強力だということです。 しかし、脆弱なドライバが提供する機能以外に、カーネルや他のドライバが提供する機能を利用することが多いので、多くの知識が無いと上手く攻撃できなかったり、成功確率が下がったりします。 例えばkUAFはとても便利なのですが、UAF対象のオブジェクトのサイズが固定の場合は、kmallocでサイズが一致してかつ「使える」オブジェクトを探す必要があります。 この記事では、そのような「使える」オブジェクトをまとめようと思います。 といっても私はKernel Exploitに関しては素人なので間違いが多いかもしれませんし、定石みたいなものを見逃しているかもしれません。 間違っている部分の指摘や、他に知っているオブジェクトがあれば是非コメントやTwitterなどで教えてください。
実験環境や書いたコードは以下のリポジトリにまとめます。
なお、検証にはLinux Kernel 4.19.98を使いました。
構造体なんて腐るほどありますが、とにかくサイズごとに使えるものを網羅したいです。 他に知ってるのあったら教えてください。 現状kmalloc-8、kmalloc-16、kmalloc-64、kmalloc-512あたりが未開拓です。
shm_file_data
- サイズ:0x20 (kmalloc-32)
- base:
ns
,vm_ops
がカーネルのデータ領域を指しているのでリーク可能。 - heap:
file
がヒープ領域を指しているのでリーク可能。 - stack:リークできない。
- RIP:たぶん取れない。
- 確保:
shmat
で共有メモリをマップする。 - 解放:
shmctl
とかで破棄するのかな? - 備考:
vm_ops
を書き換えてみたが、shmget
等で偽のvtableの関数ポインタが呼ばれる様子は特になかった。 - 参考:https://elixir.bootlin.com/linux/v4.19.98/source/ipc/shm.c#L74
0x0000: 0x0000000000000000 0x0008: 0xffffffff82292ae0 0x0010: 0xffff88800ea09700 0x0018: 0xffffffff81e15540 [+] kbase = 0xffffffff81000000 [+] kheap = 0xffff88800ea09700
seq_operations
- サイズ:0x20 (kmalloc-32)
- base:4つの関数ポインタから好きなものを使ってリーク可能。
- heap:リークできない。
- stack:リークできない。
- RIP:例えば
start
を書き換えてreadを呼べばRIPがポン!ってなる。 - 確保:
single_open
を使うファイルを開く。/proc/self/stat
とか。 - 解放:
close
する。 - 参考:https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/seq_file.h#L32
0x0000: 0xffffffff811c5f70 0x0008: 0xffffffff811c5f90 0x0010: 0xffffffff811c5f80 0x0018: 0xffffffff8120c3f0 [+] kbase = 0xffffffff81000000 Press enter to continue... [ 6.801190] BUG: unable to handle kernel paging request at 00000000deadbeef
msg_msg (+user-supplied data)
- サイズ:0x31〜0x1000 (kmalloc-64以上)
- base:リークできない。
- heap:
next
が前にmsgsndされたメッセージを指しているのでリーク可能。前にmsgsndしたデータのサイズに対応するSLUB上のアドレスを指すので便利。 - stack:リークできない。
- RIP:取れない。
- 確保:
msgget
してmsgsnd
する。 - 解放:
msgrcv
する。ただし最初にmsgsnd
したものから順番に受信されるのでkfreeの順番も送信した順番になる。 - 備考:サイズ可変で任意データを書き込めるので便利だが、最初の48バイトは構造体で使われるので書き換えられない。UAFでreadのみ可能なとき、これでヒープのアドレスをリークした後にヒープ上にデータを用意するなどが可能。ヒープスプレーにもよく使われている模様。
- 参考:https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/msg.h#L9
0x0000: 0xffff88800e1661c0 0x0008: 0xffff88800e1661c0 0x0010: 0x0000000000000001 0x0018: 0x0000000000000020 0x0020: 0x0000000000000000 0x0028: 0xffff88800e8c7f90 0x0030: 0x4242424241414141 0x0038: 0x4444444443434343 0x0040: 0x4646464645454545 0x0048: 0x0000000000000046 [+] kheap = 0xffff88800e1661c0
subprocess_info
- サイズ:0x60 (kmalloc-128)
- base:
work.func
がcall_usermodehelper_exec_work
を指しているのでリーク可能。 - heap:リーク可能だがどのSLUBのデータかは未検証。
- stack:リークできない。
- RIP:race condition等で、確保中に
cleanup
を書き換えるとRIPが取れる。(これってトリビアになりませんか?) - 確保:いろいろありそうだけど確認済みなのは
socket(22, AF_INET, 0);
などで不明なプロトコルを指定する方法。 - 解放:確保と同一パスで解放される。
- 備考:データ書き込めないし使えねーと思ったが、
info->cleanup
を設定してからif (info->cleanup) info->cleanup(info);
までに時間があるので、race conditionで高い確率でRIPを取れることに検証成功。ただし、なぜかこれでROPしてもユーザーランドに復帰後fsを使う箇所やsyscallで死ぬ。SMAPが無効ならfdをユーザーランドに書き換えてuserfaultfdと組み合わせてcleanupを上書き可能だと思う。 - 参考:https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/umh.h#L19
0x0000: 0xffff88800f254380 0x0008: 0xffff88800f254e88 0x0010: 0xffff88800f254e88 0x0018: 0xffffffff81071380 0x0020: 0x0000000000000000 0x0028: 0xffffffff82242260 0x0030: 0xffff88800e0e3b40 0x0038: 0xffffffff82242180 0x0040: 0x0000000000000000 0x0048: 0x0000010000000006 0x0050: 0x0000000000000407 0x0058: 0x0000000000000000 [+] kbase = 0xffffffff81000000 [+] kheap = 0xffff88800f254380 Press enter to continue... [ 6.801190] BUG: unable to handle kernel paging request at 00000000deadbeef
cred
- サイズ:0xa8 (kmalloc-192)
- base:リークできなさそう。
- heap:
session_keyring
等からリーク可能。対象のSLUBは未調査。 - stack:リークできなさそう。
- RIP:取れない。
- 確保:forkする。
- 解放:作ったプロセスをexitする。
- 備考:uid, gid等を持つので、0埋めできれば権限昇格できる。ただし、検証した4.19.98では
cred
はkmallocに入っていない気がする。 - 参考:https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/cred.h#L116
file
- サイズ:? (kmalloc-256)
- base:
f_op
がカーネルのデータ領域を指しているのでリーク可能。 - heap:未検証。まぁいけるやろ。
- stack:未検証。
- 確保:
shmget
で共有メモリを作成する。 - 解放:
shmctl
で破棄する。 - 備考:
f_op
を書き換えてshmctl
等を呼べばRIP制御が可能。ただ、UAF後にfile構造体がなぜかoverlapしなかったので検証に失敗。 - 参考:https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/fs.h#L891
timerfd_ctx
- サイズ:? (kmalloc-256)
- base:
tmr.function
がtimerfd_tmrproc
を指しているのでリーク可能。 - heap:
tmr.base
などからリーク可能。 - stack:リークできない。
- RIP:取れそうで取れなさそう。
- 確保:
timerfd_create
を呼ぶ。 - 解放:tfdを
close
する? - 参考:https://elixir.bootlin.com/linux/v4.19.98/source/fs/timerfd.c#L30
0x0000: 0xffff88800e216100 0x0008: 0x0000000000000000 0x0010: 0x0000000000000000 0x0018: 0x000000183ca77938 0x0020: 0x000000183ca77938 0x0028: 0xffffffff811e7ef0 0x0030: 0xffff88800f41ba80 ... [+] kbase = 0xffffffff81000000 [+] kbase = 0xffff88800f41ba80
tty_struct
- サイズ:0x2e0 (kmalloc-1024)
- base:
ops
がptm_unix98_ops
を指しているのでリーク可能。それ以外にも2箇所ほどkernelのdata領域を指していた。 - heap:
dev
,driver
など多くのオブジェクトがヒープや自身のメンバを指しているのでリーク可能。対象のSLUBは未調査。 - stack:リークできなさそう。
- 確保:
/dev/ptmx
を開く。 - 解放:開いたptmxを閉じる。
- 備考:
ops
を書き換えることでRIPが制御可能。 - 参考:https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/tty.h#L283
0x0000: 0x0000000100005401 0x0008: 0x0000000000000000 0x0010: 0xffff88800f1ed840 0x0018: 0xffffffff81e65900 0x0020: 0x0000000000000000 ... [+] kbase = 0xffffffff81000000 [+] kheap = 0xffff88800f1ed840 Press enter to continue... [ 5.413411] BUG: unable to handle kernel paging request at 00000000deadbeef
msg_msg
説明済み
setxattr
- サイズ:任意(<=65536)
- 確保:
setxattr
を呼び出す。value
に書き込みたい値のポインタを入れ、size
を指定する。 - 解放:確保と同一パスで解放される。
- 備考:userfaultfdと組み合わせて使う。msgsndでは先頭48バイトが書き換えられないので、それに対応したい場合に便利。ヒープスプレーにも使える。
sendmsg
- サイズ:任意(>=2)
- 確保:
sendmsg
を呼び出す。msg.msg_control
にポインタ、msg.msg_controllen
にサイズを入れる。 - 解放:確保と同一パスで解放される。
- 備考:setxattrと同じでuserfaultfdと組み合わせる。
[1] https://elixir.bootlin.com/linux/v4.19.98/source
[2] https://duasynt.com/blog/linux-kernel-heap-spray
[3] https://hama.hatenadiary.jp/entry/2018/12/01/000000
[4] https://smallkirby.hatenablog.com/entry/2019/11/19/225504