Linuxのsyscallハンドラのエントリポイントがたくさんあるんだが -> 完全に理解した

はじめに

久しぶりの更新です,morimolymolyです.

最近,私はRing-1に興味を持ちいろいろなhypervisorを触って遊んでいます研究しています.

さて,皆さんはsyscallにはかなり馴染み深いですよね?

システムソフトウェアを書くときはもちろんのこと,exploitするときにガジェットをあーでもないこーでもないとつないでROPして呼び出したりしますよね?

私も最近syscallをRing-1から監視したりしているのですが,そこで気になったことがあったのです.

というわけでこの記事では,x86_64のマルチコアプロセッサLinux環境でのsyscall命令についてみていきます.

syscall命令

x86_64ではシステムコールを行う際には,引数をレジスタに設定して,syscall命令を呼び出します.

syscall命令はMSR(Model-specific Register)のIA32_LSTAR(0xC0000082)からシステムコールのエントリポイントのアドレスを読み出し,RIPにセットします.

神秘的なRing3からRing0の移動が,この一つの命令でできるわけです.

驚きですね.

ではIA32_LSTARの中身を読み出してみましょう.

以下のLKM(Loadable Kernel Module)はRDMSR命令を用いてIA32_LSTARを読み出してprintkしています.

gist.github.com

このLKMをビルドして実行するとカーネルメッセージにIA32_LSTARの中身が表示されるというわけです.

それではこのLKMをおもむろに何度も実行してみましょう!(なぜ)

dmesg | grep MOLY

するとどうでしょう.

何故かアドレスが異なっています!! (結果はめんどいのでのせない)

syscallハンドラが気まぐれでメモリ空間を散歩しているのでしょうか?

いえ,違います.

この挙動はマルチコアプロセッサのKPTI(Kernel Page-Table Isolation)が有効な環境においては正常な動作なのです!!

syscall_init関数

さて,IA32_LSTARLinuxのboot時,syscall_init関数によって設定されます.

/* May not be marked __init: used by software suspend */
void syscall_init(void)
{
    extern char _entry_trampoline[];
    extern char entry_SYSCALL_64_trampoline[];

    int cpu = smp_processor_id();
    unsigned long SYSCALL64_entry_trampoline =
        (unsigned long)get_cpu_entry_area(cpu)->entry_trampoline +
        (entry_SYSCALL_64_trampoline - _entry_trampoline);

    wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS);
    if (static_cpu_has(X86_FEATURE_PTI))
        wrmsrl(MSR_LSTAR, SYSCALL64_entry_trampoline);
    else
        wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);

ここの関数で行っている処理は,X86_FEATURE_PTIが有効になっている場合,つまりKPTIが有効になっている場合に,wrmsr命令でIA32_LSTARSYSCALL64_entry_trampolineを書き込んでいます.

SYSCALL64_entry_trampolineは,ご覧の通り,cpuのidによってアドレスが異なるようです.

つまりcpuのコアごとにIA32_LSTARの値が異なることは正解なわけです.

また,先程のLKMでアドレスが異なっていたのも,実行するコアが異なっていたからですね.

entry_SYSCALL_64_trampoline

さて,entry_SYSCALL_64_trampolineでは何をしているのでしょうか.

 .pushsection .entry_trampoline, "ax"

/*
 * The code in here gets remapped into cpu_entry_area's trampoline.  This means
 * that the assembler and linker have the wrong idea as to where this code
 * lives (and, in fact, it's mapped more than once, so it's not even at a
 * fixed address).  So we can't reference any symbols outside the entry
 * trampoline and expect it to work.
 *
 * Instead, we carefully abuse %rip-relative addressing.
 * _entry_trampoline(%rip) refers to the start of the remapped) entry
 * trampoline.  We can thus find cpu_entry_area with this macro:
 */

#define CPU_ENTRY_AREA \
    _entry_trampoline - CPU_ENTRY_AREA_entry_trampoline(%rip)

/* The top word of the SYSENTER stack is hot and is usable as scratch space. */
#define RSP_SCRATCH    CPU_ENTRY_AREA_entry_stack + \
            SIZEOF_entry_stack - 8 + CPU_ENTRY_AREA

ENTRY(entry_SYSCALL_64_trampoline)
    UNWIND_HINT_EMPTY
    swapgs

    /* Stash the user RSP. */
    movq %rsp, RSP_SCRATCH

    /* Note: using %rsp as a scratch reg. */
    SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp

    /* Load the top of the task stack into RSP */
    movq CPU_ENTRY_AREA_tss + TSS_sp1 + CPU_ENTRY_AREA, %rsp

    /* Start building the simulated IRET frame. */
    pushq    $__USER_DS           /* pt_regs->ss */
    pushq    RSP_SCRATCH          /* pt_regs->sp */
    pushq    %r11             /* pt_regs->flags */
    pushq    $__USER_CS           /* pt_regs->cs */
    pushq    %rcx             /* pt_regs->ip */

    /*
    * x86 lacks a near absolute jump, and we can't jump to the real
    * entry text with a relative jump.  We could push the target
    * address and then use retq, but this destroys the pipeline on
    * many CPUs (wasting over 20 cycles on Sandy Bridge).  Instead,
    * spill RDI and restore it in a second-stage trampoline.
    */
    pushq    %rdi
    movq $entry_SYSCALL_64_stage2, %rdi
    JMP_NOSPEC %rdi
END(entry_SYSCALL_64_trampoline)

    .popsection

コメントにもある通り,このcpu_entry_area's trampolineはコアごとにremapされるようです.

そして,syscall_init関数でIA32_LSTARに書き込んでいたのは,RIPベースの相対アドレスの計算を行って導き出した,実際にマップされたコードのアドレスというわけです.

またentry_SYSCALL_64_trampolineはその名の通り,いくつかの段階をへて本来のシステムコールを行う前のトランポリンのようなコードです.

まあ今回は各段階のコードは読みませんが.

さて,ではなぜこのような複雑なマッピングを行っているのでしょうか?

コアごとにハンドラを配置する必要性がわかりません.

コアごとにハンドラがマッピングされるワケ

ぐぐると,以下のようなLKMLのパッチ情報にたどり着きました. LKML: Thomas Gleixner: [patch 19/60] x86/entry/64: Create a per-CPU SYSCALL entry trampoline

syscallが行われる際,FLAGS以外のすべてのレジスタとともにハンドラへ突入します.

当然,ユーザ空間のRSPをどこかに保存してカーネルのスタックのアドレスを示してやる必要がありますね.

通常,per-CPUdataセクション(コアごと)と呼ばれる空間にユーザ空間のRSPは格納されことになっています.

その後にentryの際にSWAPGSをしてGSレジスタベースでper-CPUdataセクションへアクセスします.

しかし,KPTIが導入された場合大きな問題が生じてきます.

KPTIによりユーザ空間とカーネル空間は別のページテーブルにマップされるからです.

つまり,CR3レジスタカーネル用とユーザ空間用とで書き換えなければならないのです!!(CR3レジスタに入るのはページディレクトリのベースアドレス.ページングに詳しくない人はアドレス解決に必要な基本のアドレスと思ってもらえれば良き!)

従来どおりGSレジスタベースでアクセスすればいいと思いますが,GSベースのアクセスの場合,ユーザ空間用のページに重要なperCPUdataがマッピングされていなければならないので,Meltdownなどの脆弱性によっていろいろと情報が盗み出されてしまう恐れがあります!!!!(CR3をカーネル用に書き換えたいのに特権がないとアクセスできないデータ……しんどい……)

CR3レジスタは即値で代入できず,レジスタを介する必要がるのだが,syscallハンドラで自由に使えるレジスタは大変少ないのです.

ではエントリ時のレジスタをみてみよう.

  • rax system call number
  • rcx return address
  • r11 saved rflags(これはばんばん変動する)
  • rdi arg0
  • rsi arg1
  • rdx arg2
  • r10 arg3 (CのABIによるとRCXに移す必要がある)
  • r8 arg4
  • r9 arg5
  • (note: r12-r15, rbp, rbx はABIで呼び出し先で自由に使えるアドレスと定義されている)

少なすぎィッ!! ということでどうにかしてperCPUdataセクションへユーザ空間のRSPを保存しつつ,CR3を書き換えなければならない.

さて,ここでcpu_entry_area's trampoline(syscallはんどら)をCPUごとに一定間隔でマッピングしてみると……なんとハンドラのアドレスから相対的にperCPUdataセクションにアクセスすることができるのです!!

つまりGSレジスタベースでアクセスする必要がなくなった!!やったあ! 実際はRSPを介してCR3のスイッチングを行っているみたいですね. いやあ複雑なマッピングをしてまでセキュリティを向上させなければ行けないのは大変ですねえ.

まとめ

syscallハンドラがKPTI環境でCPUコアごとにマッピングされているのは,重要なデータをユーザ空間とおなじページにマッピングされないようにしながら,CR3(ページディレクトリのベースアドレス)をスイッチングする必要があったからです!! セキュリティのコストってすごいねえ……

告知

twitter復活しました!

twitter復活しましたのでフォローおねがいします.

twitter.com

チームHyperVillage

また,仮想化技術関連のお話ができるslack(Hypervillage)をつくりました.
私一人しかいなくて寂しいのでだれか入ってください.
あといろいろと相談とか乗ってください……

Slack

とゆーか,仮想化技術関連とかで盛り上がっているコミュニティがあったら誘ってください……. よろしくおねがいします.

Dentoo.LT #21に登壇します

2018/11/11(日)に電通大で最もハッキーなサークルMMAが主催するDentoo.LTに登壇します! ハイパーバイザーとかBareflankな話をしようと思ってるのでぜひきてくれよな!!

【ポエム】WEBフロントエンド入門してみた

こんにちは,morimolymolyです.
レポート提出間近なのになんとなくwebフロントエンド入門してみました.

早速今回作ったページはこちら.

https://github.com/morimolymoly/dead-github.iogithub.com


入門する前の僕にとってのwebフロントエンドといえば,サーバーサイドでレンダリングしたページのDOMをごにょるために,スパイス程度にjqueryを使う程度でした.しかしふと,javascriptでクールにSPAっぽいの作れるようになりたいよなあと思って1日digってみました.*1
結論から言うと,1日程度では大したことはできなかったのですが*2,最近のjavascript事情とかはそこそこ把握できたし,ES6がどうのとかwebpackは〜みたいな話にもちょっとはついていけるくらいの知識がつきました.

さて,早速今回digった技術をご紹介します.

  • Vue.js
  • ES6とか新しめのjs
  • webpack
  • babel

まあご紹介しますと言いましたが,技術的な話を書くつもりはありません.
ガーッとまとめるなら,javascriptがそこそこまともな言語(ES6~)になって,でもまともなjavascriptを動かすには互換性の問題があるのでトランスパイル(ES5とかほとんどのブラウザで実行できるものに変換)するのにbabelとかつかって,散らばったモジュールをくっつけるのにwebpackを使って,めちゃくちゃ便利なVueなどのフレームワークを使ってアプリをつくるって感じです.今回は直接babelやwebpackのコマンドを叩くことはあまりせず,vue-cliというVue.jsが誇る激強テンプレート作成コマンドを使って開発しました.Vue.jsが強すぎるしハマったので,フレームワークに関してはよほど変化がない限りこれを使っていくと思います.

「webフロントエンドとか新しいもの好きが色々飛びついてメンテナンス性を失ってブツブツ言うアレだろ?」みたいな認識だったのですが,一日digってみると,どうして彼らが新しい技術を追求し,さすらい続けるのかが理解できました.おそらくwebフロントエンドは片手間にちょいちょいつまむ程度になると思いますが,さらなる環境の進化をまったり楽しんでいこうと思いました.

おわり

*1:浅はかなり

*2:成果を見れば一瞬でわかる

UARTで懲りずにルーターをハックした。あと間違えて殺した。2

  • はじめに
  • 下調べ
    • 怪しいポートを探す
    • UARTのピンアウトとボーレートを特定する
      • GNDを探す
      • VccとTxを探す
      • ボーレートを当てる
      • Rxを探す
  • UART Exploit
    • Linuxのシェルを取る
    • U-bootのシェルを取る
  • まとめ
  • 最後に
  • 参考
続きを読む

UARTでルーターのハッキングを試みてぶっ殺した話

  • はじめに
  • UARTとは?
  • 下調べ
    • ボード観察
    • UARTピンの見極め
    • ボーレートを当てる
  • Bus PirateでUARTを扱ってみる
    • bus pirateとボードを接続する
    • UART通信をしてみる
    • 通信結果
  • flash romに対するglitch attack
    • 攻撃方法
    • アドレス入力ポートへの攻撃
    • データI/Oに対する攻撃
    • めちゃくちゃにglitch!
  • 最後に
  • 参考
続きを読む

セキュリティ・キャンプ2017全国大会に参加したという話

  • はじめに
  • セキュリティ・キャンプ全国大会とは?
  • 参加するまで
  • 参加決定から当日までの過ごし方
  • キャンプ一日目
    • セキュリティ基礎
    • 特別講義1
    • 特別講義2
    • チューター紹介
  • キャンプ二日目
  • キャンプ3日目
    • B4.Embedded System Reverse Engineering 101
    • B5.信じて送り出した家庭用ルータがNetBSDにドハマリしてloginプロンプト を返してくるようになるわけがない
    • BoF
  • キャンプ4日目
    • D6. LSMから見た Linux カーネルのセキュリティ
    • A7.ファジング実習
  • キャンプ5日目
    • グループワーク
  • さいごに

はじめに

こんにちは。
morimolymolyです。
セキュリティ・キャンプに参加したのでその感想文を投下します。
技術的な内容というよりかはポエム寄りです。

続きを読む