Writing An Interpreter In GoをRustでやった
おひさしぶりです、morimolymolyです。
Writing An Interpreter In GoをRustでやりました。
github.com
もともとはRustのチュートリアルであるところのTRPLをこなしたあとに、そこそこ量のあるコードを書きたくなったのでなにか探していたところ、インタプリタを書いてみるのも悪くないなあと思って書いてみました。
doc.rust-lang.org
最初は、Rustというか関数型言語っぽいやりかたがみにつかず、Anyトレイト連発❗みたいなクソコードを量産しましたが、時期にやり方を覚えいい感じにこーどがかけるようになりました。
ちなみにGolangの実装のままにAnyトレイト連発のクソコードは以下のブランチからみることができます。
github.com
Pratt Parserを書く部分とかも楽しかったし、おまけのマクロの実装も楽しかった。
コードを書いていくと次第に理解が深まり、本を読まずとも、機能の追加方法が思いつく瞬間があり、とても気持ちのいい体験でした。
これからは続きのWriting a Compilier in GoをRustでやってみようかなあ、と思っています。
まだrmonkeyも汚いコードを残しているので整理はしたいと思っていますが……。
なんせ休学して暇な身なのでメンタルさえ安定していれば無限にコードが書けるのでね。
今、いろいろと面白いことを考えてやっている最中なので、時期にコンパイラやインタプリタ以外のおもしろいこともここで発表できたらいいなあと思っています。
まずはメンタルを安定させてお休みするところから始めていきます。
それでは短いですが、次回の機会で。
ゼロから始めるHyper-Vのアーキテクチャ
はじめに
こんにちは,morimolymolyです.
皆さん,Hyper-Vの脆弱性報酬プログラムはごぞんじですか?
https://www.microsoft.com/en-us/msrc/bounty-hyper-v
なんとHyper-VのRCE(Remote Code Execution)を発見するだけで$250,000も貰えてしまうのです!!
最近では360 Core Securityの研究者がVMEscapeの脆弱性をみつけて$200,000をゲットしています.
Zhenhao Hong @rthhh17 from 360 IceSword Lab was rewarded $200,000 by Microsoft #Hyper-V for his VM escape #vulnerability – one of the highest MSRC bounties ever @msftsecresponse We will release details after the patch. Congrats to @rthhh17 and @pjf40490912 (Weibo: @rt_hhh, @PJF_) pic.twitter.com/1ilNgIrW7K
— 360 Threat Intelligence Center (@360CoreSec) 2019年1月3日
$250,000も貰えたら働かずに毎日ごろごろしていても数年は余裕で持ちますね.
というわけでHyper-Vの脆弱性を発見する前に,これのアーキテクチャを学ぼうというわけです.
Hypervisor概要
ハイパーバイザにはType-1(Baremetal)とType-2(Host)の2種類あります.
Type-1はハイパーバイザがハードウェア上で直接動作します.
例: Xen,KVM,BitVisor,Bareflank
Type-2はホストOS上のプロセスとして動作するハイパーバイザです.
例: VirtualBox,VMWare,QEMU,Bochs
Hyper-VはType-1ハイパーバイザにあたります.
Hyper-Vのアーキテクチャ概要
上図がHyper-Vのアーキテクチャです.
Hyper-VはAttack Surfaceをなるべくへらすために,コード量を最小限にするという設計理念があります.
そのためデバイス仮想化などの機能はHyper-Vには実装していません.
Hyper-VがインストールされたホストOSがデバイス仮想化などのあらゆるサービスを担当します.
そのおかげでHyper-VはホストOSに実装されているデバイスドライバをそのまま利用できます.
Hyper-VにはPartitionという単位でVMが存在します.
Hyper-VがインストールされたホストOSをRoot Partitionと呼び,これは仮想マシンの管理,ゲストのメモリの操作,デバイスエミュレーションサービスの提供,実デバイス操作を行います.
XenでいうとDom0(特権ドメイン)です.
ゲストマシンはPartitionと呼びます.
ゲストマシンは別のPartitionの物理メモリにアクセスできませんし,ハードウェアの直接アクセスができません.
すべてはRoot Partitionと通信してデバイスの操作などを行ってもらいます.
またこの通信は厳格に定義されたインターフェースを通じてのみ行なえます.
このアーキテクチャから分かる通り,セキュリティ野郎が注目するべきはRoot Partitionです.
詳しくは以下の記事を参照.
docs.microsoft.com
Hyper-Vのメモリ
Hyper-Vのメモリは以下のように定義されます.
- PA(Physical Address) 物理メモリ空間
- SPA(System Physical Address) RootPartitionの物理メモリ空間
- SVA(System Virtual Address) RootPartitionの仮想メモリ空間
- GPA(Guest Physical Address) ゲストの物理メモリ空間
- GVA(Guest Virtual Address) ゲストの仮想メモリ空間
SPA,SVAは他のハイパーバイザだとHPA(Host Physical Address),HVA(Host Virtual Address)と呼ばれることが多い気がします*1.
Root Partitionの役割
上図はRoot Partitionで提供されるサービスの例です.
以下に用語を説明します.
- VDEV(Virtual Device) Rootのユーザ空間で動くエミュレートまたは準仮想化されたデバイス
- VSP(Virtualization Service Provider) Rootのカーネル空間でVDEVと協調して動く準仮想化されたデバイス.
- IC(Integration Component) ゲストがアクセスできるRootのユーザ空間で動くコンポーネント
また次のようなサービスが提供されています.
このようにRootPartitionのユーザ空間でホストされるサービスもあれば,RootPartitionのカーネル空間でホストされるサービスもあります.
ゲストのデバイス操作の流れ
上図はゲストがデバイスを操作するときの概要図です.
流れは以下のとおりです.
- ゲスト内のVSC(Virtualization Service Client)がvmbusを通じてRoot Partition内のVSPに操作を依頼
- VSPがホストOSのデバイスドライバに操作を依頼
- デバイスドライバがデバイスを操作
でばストレージ操作のVSPをみてみましょう.
ユーザ空間のVSPも見てみましょう.
VMWPとはVirtual Machine Worker Processの略で仮想マシンごとに用意されるVSPを提供するためのユーザ空間のプロセスです.
この例ではユーザ空間にSMBのサービスであるVSMBを動作させています.
Hyper-VのLinuxでの実装
当然このような仮想化支援技術はゲストカーネルに実装されていなければなりません.
Linuxでは以下のようなディレクトリにコードがあるようです.
実際にヘッダファイルを眺めてみるとなかなか面白いです.
github.com
予告
次回はHyper-Vのリバースエンジニアリング環境を構築します.
また既知の脆弱性についても解説したいと思います*2.
メールアドレスが不正使用されてしまいました
こんにちは,morimolymolyです.
2019/01/11の午前2時から3時にかけて私のメールアドレスを使い,私を騙るものが5ちゃんねるや爆サイ,その他掲示板に書き込みをしていたようです.現在,その書き込みを見た方からたくさんメールが届いて迷惑しています.
私はtwitter(@morimolymoly),XMPP(moly420[at]exploit.im),slack(HyperVillage,seccamp,MMA,バイト先)以外でmorimolymolyというスクリーンネームは使用していません.また上記アカウント以外から能動的にコンタクトを取ることはありません.
もし,今後私を騙るようなものがコンタクトをしてきても無視をしてください.
所属している界隈故,マルウェアなどを用いたサイバー攻撃に発展していく恐れもあります.
くれぐれも私を騙るものからのメッセージに記載されたURLなどは決して踏まないように気をつけてください.
以下,私を騙る書き込みのスクリーンショットを公開し,この記事を終わります.
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しています.
このLKMをビルドして実行するとカーネルメッセージにIA32_LSTARの中身が表示されるというわけです.
それではこのLKMをおもむろに何度も実行してみましょう!(なぜ)
dmesg | grep MOLY
するとどうでしょう.
何故かアドレスが異なっています!! (結果はめんどいのでのせない)
syscallハンドラが気まぐれでメモリ空間を散歩しているのでしょうか?
いえ,違います.
この挙動はマルチコアプロセッサのKPTI(Kernel Page-Table Isolation)が有効な環境においては正常な動作なのです!!
syscall_init関数
さて,IA32_LSTAR
はLinuxの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_LSTAR
にSYSCALL64_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復活しましたのでフォローおねがいします.
チームHyperVillage
また,仮想化技術関連のお話ができるslack(Hypervillage)をつくりました.
私一人しかいなくて寂しいのでだれか入ってください.
あといろいろと相談とか乗ってください……
とゆーか,仮想化技術関連とかで盛り上がっているコミュニティがあったら誘ってください……. よろしくおねがいします.
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フロントエンドは片手間にちょいちょいつまむ程度になると思いますが,さらなる環境の進化をまったり楽しんでいこうと思いました.
おわり
セキュリティ・キャンプ2017全国大会に参加したという話
- はじめに
- セキュリティ・キャンプ全国大会とは?
- 参加するまで
- 参加決定から当日までの過ごし方
- キャンプ一日目
- セキュリティ基礎
- 特別講義1
- 特別講義2
- チューター紹介
- キャンプ二日目
- キャンプ3日目
- キャンプ4日目
- キャンプ5日目
- グループワーク
- さいごに
はじめに
こんにちは。
morimolymolyです。
セキュリティ・キャンプに参加したのでその感想文を投下します。
技術的な内容というよりかはポエム寄りです。