KazuminEngine

プログラマーの日記

いつかの心...SecHack365....!

こんにちは、かずみんです。

SecHack365の3期生です。2019年。

終了してから一年が経つので、今更感ありますが、経験したことなどを書いていきます。あまり長い文量は、書きません。

「文章が短い」ということは、「学んだことが少ない」ということではないので、注意しましょう。

開発経験

私は、大学の講義で、一週間ほどグループで開発したりしましたが、SecHack365でエミュレーターを開発するまでは、長期間の開発経験というものはありませんでした。

また、コードを毎日書いていき、胸を張って「作った」と言えるものはありませんでした。

そうゆう意味で、長期間にほぼ毎日コードを書いて作品を作る経験はとても良い経験になりました。

自立支援

私は、軽度の難聴なのです。SecHack365では、多種多様な人の意見を聞いたりするのに、耳で聞いたりしないといけなかったですが、SecHackが始まった当初は、自分の耳との付き合い方がわかっていませんでした。

しかし、運営の方のサポートもあり、聞こえない自分と人との接し方の相談に乗ってもらったり、応援してもらったりして、自立するための基礎力がついたと思います。

「耳が悪いのでもっと大きな声で言ってください。」と言うなどの技を学びました。最初は、それが言えないほどだった....

また、発表など時は、マイクを通して、字幕を作成してもらうなどの支援をしてもらいました。

トレーナーなどのあなたが耳が悪いことを知っているから大丈夫、などかなり良心的な雰囲気も作ってくれました。

後、アンケートなどで講演をゆっくりはっきり喋ってくれ、とリクエストすると次にそうなっていた記憶があります。アンケートは、素直に書きましょう。運営が良心的にめっちゃ頑張ってくれるので、ちゃんと書こう。(こうゆうところ、すごく教育に情熱を傾けてるな、と感じる)

SecHack365の前に、Hardeing(イベント)があった時は、うまくコミュニケーションが取れませんでしたが、終了後に同じようなイベントがオンラインでありましたが、結構コミュニケーション取れた気がするので、

SecHack365で成長できた気がします。

とてもいい経験になったので、今後どこでもやっていける自信がついた気がします。

これから、応募する人は、もし直したいところや成長したいところがあれば、受かってから相談するといいです。例えば、人付き合いが下手で、もっとうまくひとと付き合いたいとか。(もっとうまく付き合いたい。と思っているのがポイント。思ってることはすごいこと、それを伝えよう。)。後、自閉症傾向があって不安とか、鬱病が治ったばかりで心配とか。絶対協力してくれます。勇気を出して、言いましょう。また、車椅子生活していて大丈夫ですか?などだと参加申し込む前に相談しましょう。2020年は、オンラインなので、ちょっとわからないですが。

修了後

SecHack365では、typetalkを使って、連絡やらコミュニケーションを行うのですが、私は、自分専用のトピック(部屋のようなもの)を作って、トレーナー、一期二期生を全部入れています。自分専用トピックを作っている人はちらほらいるのですが、全期横断個人チャンネルにしたのはたぶん私だけです。

たまに、いろんな人からいいねがつくので、結構見てくれているのだなー。と思っています。

話が脱線しそうになりましたが、

私はこのトピックにTwitterのように呟いています。

たまに、エミュレーター作りで、全部お世話になった川合さんから素敵な助言が入ったりしました。(これが嬉しくて、私はたびたび幸せな気持ちになりました。)

ある日SecHack365で作ったエミュレータに細工するとハイパーバイザーになるんじゃないかと降ってきまして、(私は前々からそれが作りたかった。)それを呟くと、なんと坂井先生が「できるよと」、そして、ロジックやら細かいことを教えてくれました。

そのアイディアでサイボウズラボユースに応募するとなんと受かりました。

そのトピックに毎日のようの呟いてきたおかげで、ラボユースに受かることができたと思っています。

ラボユースの契約は2021 1月 - 3月で終わりましたが、以下の記事がその私の成果発表の補足ブログです。

sigactionシステムコールによるホスト型ハイパーバイザー開発調査~サイボウズ・ラボユース10期生~ - KazuminEngine

あとで、ラボユースのブログの方で、またまとめが記事が投稿されると思います。

まとめ

2021年4月からは、また、新たなことが始まりますが、今後どうなっていくのか楽しみで仕方ありません。 おわり。

sigactionシステムコールによるホスト型ハイパーバイザー開発調査~サイボウズ・ラボユース10期生~

こんにちは、かずみんです。この記事は、サイボーズラボユースの発表会の持ち時間が7分しかなく、詳細なことが話せないので、発表を補うことが目的です。と言っても、掲示板のメモを時系列順に書いて行っただけの物になったりしています。

まずは、どのようにして応募に至ったか

SecHack365のIMでいろいろ呟いているのだけれど、次のように投稿したら、坂井さんから「できるよ」とのことで、「signalシステムコール を使えば、できちゃうと思います。」とのことで、作り始めたのが始まり。私は、前々からハイパーバイザー に憧れがあり作りたくもあり、手始めにエミュレータから作り始めたのである。

投稿:「閃いたんだけど、このエミュレーターに追加実装で、センシティブじゃ無い命令を直接実行させてあげて、ゲスト用のプロセスを作ってやるとハイパーパイザーになりそうだけど。なります?」

システムコール を使うような実装は、初めてだったので一ヶ月ぐらいかけて、signalシステムコール などを使って、cpuで命令を直接実行し、センシティブな命令をトラップするプロトライプが完成した。

基本的な実装方法は、坂井さんのホームページの

各種実験用サンプル

に書いてある。基本的なロジックはこのページに書いてあるので、読んでください。

一月ぐらいかかったのは、osをメモリに読み込んで、実行し、エミュレートした際に、EIPを更新するのを忘れて、同じ命令を繰り返し実行しちゃうとかで、いろいろ試行錯誤していたからである。結果的にはEIP++したけど、試行錯誤している段階では、iretしてコンテキストスイッチして解決しようなどとしていた。また、結果的には使ってないのだけれど、get_sigframe() setup_sigcontext() struct sigframe struct sigcontextをインポートするのにも時間がかかった。(それも初めてだった。)また、__builtin_frame_addressを使って、ゲスト実行時のコンテキスト情報がうまくとれなかったのも原因。これもかなり時間がかかって、諦めて、代わりに、sigactionシステムコール を使うことで、ゲスト実行時のコンテキスト情報を取得した。ちなみに、sigactionの方を使うのがモダンらしい。signalシステムコール をは古くて、代わりにsigactionシステムコール を使ってね。とマニュアルに書いてある。坂井さんがsignalシステムコール を用いて実装しているのは、一般にそれを使っている人が多いので、理解しやすく受け入れやすいためだそうだ。

ここらへんで、12月中頃にラボユースに応募したら、面接して受かったので、1/8日頃から契約を開始した。c++の書き方などがいろいろわからなかったので、『C/C++によるソフトウェア開発』の光成メンターに応募した。ちなみに、面接の時に、星野さんがいたけど、それ以来一度も関わっていない。

面接では、「VMMを作りたい」と話したところ、よくわかってもらえなかったので、なぜか考えていたら、自分の作りたいものはハイパーバイザー だったので、そのことをあとでメールして光成さんと星野さんに伝えてもらった。また、そこまで実装した分で「

ホスト型ハイパーバイザー の作り方 part.1 - KazuminEngine

」という記事を書いたので、メールを送って、二人にリンクを共有してもらった。私のhvの基本ロジックは、この記事に書いてあるので、これを読むと、あとの説明がより理解できると思います。

このメールは気軽に送れた。相手側が原因で、面接が五分程度遅れたこともあり、「何か質問などがございましたら、メールしてください」と言われていたからだ。もちろん、この記事をみて、応募しようかなと思った人も、面接開始時間が遅れなくても、気軽に質問メールなどを送って欲しい。

ラボユース開始

活動中は、サイボウズOfficeの個人作業掲示板にTwittterみたいに、やったこと悩んでいることメモなど、綴っていくのだけれど、それを時系列順に書いていく。決して、見やすいことはないだろうが書いていく。一行が一つの仕事の単位だと思って、読んでいただくと。

作業を開始する前に、メンターの光成さんと打ち合わせをした。勤務の仕方など3月までのゴールについて話した。ここでは、三月までにはりぼてosの画面描画を成功させることを目標としようと話した。なぜ、はりぼてosを動かすのかというと、私がSecHack365で作ったはりぼてOSが動くエミュレータを拡張して実装していくからである。極論、はりぼてOSをうごかしたい思いはないが、xv6などを動かす前に機能が少ないはりぼてosをまずは動かせるようにしようとの魂胆である。また、始まる前は毎日フルで入るつもりだったが、様子見で、二週間程度、月水金で入ることが決まった。

まず、リポジトリを作った。https://github.com/kazuminn/ghyper

メモリ上にあるバイナリをステップ実行する術を探していたら、坂井先生のハローワールド本にちょうど書いていたのを発見した。layout asmすればよかった。 また、display/i $pcとすれば、ステップ実行ごとに命令を表示できることをメンターさんに教えてもらった。前者だと、表示が崩れたり、終了すると画面に何も残らないので、後者を使うことにした。

次に、紹介するのはかなり大きな問題だった。push %es命令がシグナルを出して来れなくて、例外捕虜できない問題にぶち当たった。gdbデバッグして、SIGILLシグナルが連続して発行されていることが確認された。なので、例外ハンドラー実行中に例外が発生しているのが原因なんじゃないかと思い。ハンドラー実行中に例外を発行できないようにした。設定したけど、現状変わらないので、どうやら、それが原因じゃないことがわかった。悩んでいると、毎週水曜日の定例が開催され、そこで、詰まっていることを話したら、どうしていくのがいいのかを教えてもらった。qemu上で同じことをしたら、push %esしたら、大丈夫かどうかをまずは確認してね。とのことである。さらに、正しく動いているものを確認しましょう。とのことで、最小構成のpush %esが動く小さいプログラムが動くコードを書いて、それを実行して、HVのコードで動かないことがあるなら、コメントアウトなどをして、動く状態に持っていき、それからコードを戻しながら、動かなくなった時を見つけて、その原因を探るのが良いと教えてもらいました。バイナリエディタでpush %csに変えると上手く動くことが判明し、push %esの一つ上から順に一つ上に開始位置を変更して実行して、動かなくなる命令を発見した。それは、espを変更する命令だった。それが原因で、スタックを変更するといろいろ狂うのがわかった。その命令をバイナリエディターでnop(何もしない命令)に変更すると、push %esが例外ハンドリングされた。どうやって、解決したらいいのか案が浮かばないので、坂井さんに状況を説明すると、sigaltactionシステムコール とSA_ONSTACKを使って、シグナル処理ようのスタックを別に割り当てると回避できるかもしれない。とのことであった。三十分ぐらいで実装を完了して、この件は解決した。

ここぐらいから、10:00-17:00勤務して行った。夕方だれるので、五時まで。

開発はiMacのメモリ4GBでしているのだが、ブラウザなどの他のアプリを落とさないと、実行できなくなってきたので、物理メモリを12GBに増やした。4000円ほどで購入した。

次に、lock wbinvd命令にぶち当たったのだが、これがおかしかった。objdumpしたものと、ソースコードが違うので、なんでだろうと、悩んでいると。メンターさんによると、wbinvd命令にはlockプレフィックスはつかないらしい。いろいろ考えて判明したのだが、osのはじめは16bit codeなので、16bitモードで実行しないといけなかった。 面倒だったので、32bitモードから開始することにした。push %esもプレフィックスがずれていて、そんな命令実行されないようだった。

とりあえず、eipを飛ばすだけで、32bitモードに入った。

mov_rm32_imm命令を実装。エミュレーターのmodrm関数を流用して、ちょっと変えて、使った。

mov_rm32_r32命令を実装

ここで、レジスタ退避とレジスタ復帰が必要なことがわかり、例外ハンドラーのはじめと終わりに挟むことにした。実装したんだけど、ずっとバグってる。

エミュレーションをインラインアセンブリでできないかと試行錯誤してみたが、結構複雑になり、結局それはやめた。

エミュレーターのメモリバッファを~4GB領域に配置できれば、ほぼトラップされないので、早くなるんじゃないかと考えたが、そこにメモリバッファを置くのは不可能だったので、諦めた。

ここで、push/popが普通に実行されることを話すと、それだと、メモリ不整合になるという話になりました。エミュレーターのメモリバッファ上で作業しているのに、hvのメモリをpush/popで利用しちゃうと、不整合になる。いろいろ案を出したのだが、それだと、上手くいかないとメンバーさんが言い、理由を説明してもらって、納得した。最終的に解決策として、threadを作り、その中で、allocaシステムコール でスタック全体を確保し、push/popで例外ハンドラーを起こすようにした。やり方は、まず、pthreadが動く小さいプログラムを書いて、そこで動いたのが確認できたら、hvのプロジェクトに打ち込んだ。

eflagsのレジスタ退避ができなくて、悩んでいたが、popfを使えば、できそう。と思った。

c++のgoogle testをプロジェクトに組み込んだ。オプションの順番でエラーがでて、少々てこずった。また、main関数周りでエラーが起きて、かなりてこずった。五時間ぐらいかかったのではないだろうか。c++は慣れていないので、大変だった。rustやgolangだと、組み込みのunit testがあるので、なんて便利なんだ思った。

64ビット環境で32ビットがどうやって実行されるかの質問をしていた。少し、理解した。

mov edx, esp; mov eax, 3; push eax; mov ecx, [edx] ; pop eax; バイト列が実行できるようにした。(レジスタ退避と復帰はバグているが

一つ問題を見つけました。

40 00 04 48というバイト列は 32bitモードでは

(A) inc eax add byte ptr [eax+ecx*2], al

64bitモードでは (B) add byte ptr [rax+rcx*2], al です。

だから64bit環境で32bitバイナリで(A)のつもりでビルドされたバイト列を実行すると(B)になってしまいます。そしてraxやrcxの値によってはそのまま動いてしまうでしょう。

との指摘が。

もうちょっと、詳しく説明すると、正確には、[rax+rcx*2]ですが、説明のためにこれをraxと簡略化します。 64bitモードで実行可能な32bit命令において、32bitのeax(raxの下位32bit)が、64bitとしてcpuに直接実行してもらうと、eaxがraxになる。もし、raxが正常値でsigsegvが起こらない時は、ハンドリングのしようがなく、実行したいeaxに置き換えることはできず、そのまま実行されてしまうので、実装不能である。 rcxも同様。 → 32bithvにしたら、回避できそう。ここを考えていると、契約期限が迫ってきました。

今のところ32ビット開始の以下のところが動いています。(レジスタ退避と復帰は動いていませんが。。。

   319 00000000 55                               PUSH    EBP
   320 00000001 89 E5                               MOV EBP,ESP
   321 00000003 57                                  PUSH    EDI
   322 00000004 56                                  PUSH    ESI
   323 00000005 53                                  PUSH    EBX
   324 00000006 8D 5D A4                            LEA EBX,DWORD [-92+EBP]
   325 00000009 81 EC 00000450                      SUB ESP,1104
   326 0000000F C7 85 FFFFFBE0 FFFFFFFF             MOV DWORD [-1056+EBP],-1
   327 00000019 C7 85 FFFFFBDC 00000000             MOV DWORD [-1060+EBP],0
   328 00000023 C7 85 FFFFFBD8 7FFFFFFF             MOV DWORD [-1064+EBP],2147483647
   329 0000002D C7 85 FFFFFBD4 00000000             MOV DWORD [-1068+EBP],0
   330 00000037 C7 85 FFFFFBC8 00000000             MOV DWORD [-1080+EBP],0
   331 00000041 A0 00000FF1                         MOV AL,BYTE [4081]
   332 00000046 C0 F8 04                            SAR AL,4
   333 00000049 89 C2                               MOV EDX,EAX
   334 0000004B 83 E2 07                            AND EDX,7
   335 0000004E 89 95 FFFFFBC4                      MOV DWORD [-1084+EBP],EDX
   336 00000054 C7 85 FFFFFBC0 FFFFFFFF             MOV DWORD [-1088+EBP],-1
   337 0000005E C7 85 FFFFFBBC FFFFFFFF             MOV DWORD [-1092+EBP],-1
   338 00000068 C7 85 FFFFFBB8 FFFFFFFF             MOV DWORD [-1096+EBP],-1
   339 00000072 C7 85 FFFFFBB4 00000000             MOV DWORD [-1100+EBP],0
   340 0000007C C7 85 FFFFFBB0 00000000             MOV DWORD [-1104+EBP],0

vulsで差分-(マイナス)機能がリリースされました。

vulsで差分出力機能がリリースされました!

feat(report) : Differences between vulnerability patched items by kazuminn · Pull Request #1157 · future-architect/vuls · GitHub

以前まで、前回の検知結果と比較し、増えた脆弱性やアップデートされた脆弱性一覧をservername_diff.jsonとして出力する機能はありました。要は、差分の+はありました。

しかし、-がなかったのです。

そこで、今回は、差分の-の機能が追加されました。前回の検知結果と比較し、無くなった(すでに対応された)脆弱性一覧もervername_diff.jsonとして出力できるようになりました。

使い方

差分の+

$ vuls report -diff-plus -to-localfile -format-json

とすれば、差分の+だけ、result/current/servername_diff.jsonとして出力されます。

差分の-

$ vuls report -diff-minus -to-localfile -format-json

とすれば、差分の-だけ、result/current/servername_diff.jsonとして出力されます。

+ - 両方

$ vuls report -diff -to-localfile -format-json

とすれば、+ - 両方のを含む結果が、result/current/servername_diff.jsonとして出力されます。

$ vuls report -diff-plus -diff-minus -to-localfile -format-json

とするのと同じことです。

tui

$ vuls tui -diff

とすれば、結果がcuiで解析できます。

見方

vuls tui

tui表示では CVE-xxxx-xxxxの左に+ - の表示がついています。 f:id:kazuminkun:20210209224056p:plain

report -format-list -diff

-format-listの表示でも、同様ですね。( -format-full-text -diffでも同様です。)

f:id:kazuminkun:20210209224305p:plain

report -format-one-line-text -diff

こちらでは、+ - の集計ができます。

f:id:kazuminkun:20210209224637p:plain

まとめ

vulsのSaaS版やvulsrepoでも対応するかもしれません。乞うご期待!

これで、対応済の脆弱性が便利に確認できるようになりましたね。

それでは、良いvulsライフを!

ホスト型ハイパーバイザー の作り方 part.1

こんにちは、かずみん (@k2warugaki) | Twitter です。

この記事は、Linuxその2 Advent Calendar 2020 - Qiita の21 日目の記事です。

前日の記事は、 aiinkiestism - Qiitaさんによる Zorin OSとStarLabsの紹介 - Qiitaでした。

CPUの仮想化支援機能やバイナリートランスレーションを利用しないで、linux kernelの機能を用いたホスト型ハイパーバイザーの作り方を解説します。今回は、そのpart.1です。 ※続編が出るかもしれない...?まだハイパーバイザー を開発途中で序章しか書けないので、part1としています。

大まかな手順

  • まず、好きなOSのエミュレーターを作ります。
  • 次に、ハイパーバイザー 化します。

環境

  • Ubuntu 20.04 64bit

  • サンプルコードはC++

エミュレーターを作る

今回、エミュレーターを作る部分ってのは省略します。 代わりに、昔私が書いたこの記事を見ると良いかも。エミュレーターの作り方(はりぼてOSが動く) - KazuminEngine

※バイナリトランスレーションしない、命令を全部エミュレートするものを用意してください。 エミュレートするOSによっては、ハイパーバイザー 化できないものもあるので、注意してください。

ハイパーバイザー化

概要

何ができれば、パイパーバイザーと言えるのかを説明します。

ゲスト用のプロセスを立てて、その中で、次のことをすれば良い。

  • センシティブな命令やアクセス不可の資源へのアクセスをトラップして、エミュレートしてあげる ・・・これを(1)とする

  • それ以外をcpuが直接実行 ・・・これを(2)とする

アルゴリズム

  • (1)について、

sigactionというlinuxシステムコールを使います。それを使うと、特定のシグナルが発生した際に呼び出されるハンドラーを設定することができる。

センシティブな命令とアクセス不可の資源へのアクセスをした時は、SIGSEGVシグナルかSIGILLシグナルが発行される。

それらシグナルに対して、ハンドラーを設定します。

ハンドラーが呼び出されると、その呼び出された時のコンテキスト情報がハンドラー関数の第3引数に渡されるので、それをsig_ucontext_t構造体に変換してやるとripが手に入るので、そこからオペコードなどの情報を得ます。また、レジスタなどの情報も得ることができます。 そのオペコードをエミュレーターで実行してあげます。この後に、忘れてはいけないのが、シグナル復帰するために、ripを更新することだ。

  • (2)について、

cpuが命令を直接実行するのは簡単です。まず、動的メモリに動かしたいOSを読み込んであげます。そして、その先頭アドレスを実行するように、アセンブラのjmp命令に先頭アドレスを渡してあげることでできます。(プログラムカウンターをOSの先頭アドレスにする)

実装(サンプルプログラム)

コメントを入れておくので、それを読んでください。

以下のサンプルコードでは、0xFA(cli)にしか対応していない。

全部のコードを書くと大変なことになるので、必要最低限を載せています。

以下、main関数の中。in main.cpp

int main() {
    int p_id, status;
    if ((p_id = fork()) == 0) { //子プロセススタート
        emu = new Emulator();//エミュレーター本体を作成
        emu->LoadBinary("../xv6-public/xv6.img", 0x7c00, 1024 * 1024 * 1024);//OSを動的メモリ上に読み込む
                
        struct sigaction sigact;
        sigact.sa_sigaction = trap;
        sigact.sa_flags = SA_RESTART | SA_SIGINFO;
        sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL);//SIGSEGVが呼ばれた時に呼ばれるハンドラーにtrap関数を登録
        sigaction(SIGILL, &sigact, (struct sigaction *)NULL);//SIGILLが呼ばれた時に呼ばれるハンドラーにtrap関数を登録

        _change_pc(emu->memory + 0x7c00);//アセンブラで書かれている_change_pc関数を呼び出す。

        delete emu;
        exit(EXIT_SUCCESS);
    }

    wait(&status); // 子プロセス全部終わるまで待つ
    exit(EXIT_FAILURE);
}

以下、SIGSEGVとSIGILLが起こったときに、呼び出されるtrap関数。in main.cpp

typedef struct _sig_ucontext {
    unsigned long     uc_flags;
    struct ucontext   *uc_link;
    stack_t           uc_stack;
    struct sigcontext uc_mcontext;
    sigset_t          uc_sigmask;
} sig_ucontext_t;

void trap(int sig_num, siginfo_t * info, void * ucontext){
    instruction_func_t* func;
    sig_ucontext_t* uc = (sig_ucontext_t *) ucontext;//voidポインター のコンテキストの型変換
    uint8_t * pc = (uint8_t *)uc->uc_mcontext.rip;//ripポインター を入手
                
    func = instructions16[*pc];//オペコードに対応する関数をとってくる
    
    func(emu,uc);//実行
}

以下、命令が書かれている、 instructions16配列の本体。in instruction16.cpp。

instruction_func_t* instructions16[0xffff];

namespace instruction16{
 void cli(Emulator *emu,sig_ucontext_t* uc){
  emu->set_interrupt(false);//割り込みが起きないようにする。
  uc->uc_mcontext.rip++;//ripを一つ増やす。
 }

 void InitInstructions16(){
     instruction_func_t** func = instructions16;
     func[0xFA] = cli;
 }
}

以下が、プログラムカウンターを動かしたいOSの先頭に持っていくためのアセンブリ。in pc.S。

GLOBAL _change_pc
SECTION .text
_change_pc:
    jmp rdi//rdiは第一引数。jmp命令でemu->memoryアドレスにジャンプ
    ret//戻ってこないので、なくてもいいが一応、リターン

参考文献

最後に

私はまだ、ハイパーバイザー の開発途中です。これから命令をたくさん追加していき、割り込みなどに対応していく予定です。続きの記事を書けるように、どうか温かい目で見守っていてください。

私のリポジトリは、こちらです。一人で開発していて、読みやすさとか気にしていないので、汚いコードですが大目に見てください。

https://github.com/kazuminn/EEMU

zen製fuzzerを書いた

なにをしたの?

zen言語で書かれた簡易Fuzzerをつくりました。cliコマンドにfuzzを送るものです。皆さんおなじみのCTFのcrackmeをfuzzingするイメージです。

遺伝的アルゴリズムとかでfuzzを生成するとかではなく、ランダムに文字をせいせいしています。

コードはGitHubにあります。 GitHub - kazuminn/kfuzz

使い方

最新のzenで、

zen build

してやると動かすことができます。

githubにあるコードのままだと、lsにfuzzを渡すものになっています。

実行したいコマンドを変更するには、

const argv = &mut [_][]u8{ "ls", self.fuzz };

の{}にコマンドを,区切りで”をつけて書いてやってください。そして、fuzzを出力したい場所に、self.fuzzを書いてやってください。

fuzzingに成功すると、

0,-Id2K_+}F/[Lqjc|]-QD[n^]4[j)BcRVsjiWe{X
0,-IX7Z;f'\8;"Z?G}1;$R.j#7{~TxCY:+

のように、dump.csvファイルに出力されます。 0がstatus codeで,以降がそのときのfuzzです。

fuzzの文字数を固定することができなかったので、固定されていません。(たぶん、書き方あると思うけど、固定なしに。

ある程度の長さを変更したいなら、holder関数の長さを変更すれば、多分できます。

どうやって書いたの?

この本を参考につくりました。この本ではpythonでFuzzerをつくっています。pythonと同じような関数がzenには無く、ここでのコードをそのまま流用することができなかったので、あくまで参考程度にしています。

www.amazon.co.jp

なぜZen言語で書いたのか?

ちょうどコネクトフリーのインターンを三ヶ月ぐらい休んでいたので、リハビリと勉強のためにZen言語で書きました。

 感想

インターンでは、ベアメタルで動くコードしか書いていないので、このようにOSの上で動くコードをzenで書いたのははじめてで新鮮でした。osがないと動かない関数を使うのは大変刺激的でした。

zenのドキュメントに書いていない関数をライブラリのコードを見て使い方を学ぶのは、結構難しかったです。

askiiの文字コードをアルファベットに変更する関数が見当たらないので、単純なコードをたくさん書いたんだけど、その関数がないのはな。。。(あるのかな?

文字を生成するコードもっとよく書けないかな?

zenのライブラリをもっとよく知ると、もっと良いコードを書くことができるだろう。(zenzen知らずに書いた。 https://blog.hatena.ne.jp/kazuminkun/kazuminkun.hatenablog.com/edit?entry=26006613613888079#textarea

vulsがciscoの脆弱性検知に対応できない理由

vulsにcisco脆弱性検知を実装しようと試みたが、できなかったので、その理由を述べる。

cisco脆弱性データベース(go-cve-dictionaryやら)を作るのに、2通りの方法があって、OVALとCVRFで取ってくる方法がある。

OVAL

まず、試みたのが、OVALの方。vulsは影響の受けるversionでfilterをかけるときに、versionが必要なのだが、それのデータベースを作ろうとしたができない。

なぜかというと。

すでにサポート済みのredhatなどの ovalののcommentは、seamonkey-nspr is earlier than 0:1.0.5-0.1.el3みたいのがあり、影響を受けるversionがわかるが、これがcisco OVALだと。ない。IOS version is affectedとしか書いていない。それだけでは、とても、脆弱性を含むversionがわからない。

CVRF

上記の通り、影響の受けるversionがCVRFにあると、検知できそう。

しかし、影響の受けるversionが書いてあるには、書いてあるが、記述の一貫性がなく、とても正規表現で抽出したりできない。(少なくても、私は、)

そうゆうわけで、cisco検知に対応できない。

もし、できそうなアルゴリズムがあれば教えて欲しい。このブログのコメントにでも書いて欲しい。

このブログは、10分で書いたものであるので、グダグダです。

vulsのコアのアルゴリズムについて

こんにちは、

かずみん (@warugaki_k_k) | Twitter

です。

ブログのタイトルは、ほんとは違うかも。本当は全然コアじゃないのかもしれないです。

vulsって結局どうやって脆弱性判断しているのかわからなくて、コード読みました。そしてわかったことを書きます。

どっかで読んだ記憶によると、OVALと言うもので検知されてる。何それわからない。

ovalのデータ構造やら。わかりませんでした。

(ここで、コードを読むまでの予想だが、バージョンとcveが一対一やら、なんやかんやになってる。と予想。それをどうやって流のかが不明。

まー、scan時に取得されたversionが脆弱性を含むかをfillするのは、report時なので、そのへんのコードを読むと、こんなのがgetDefsByPackNameFromOvalDB関数の

 245 definitions, err := driver.GetByPackName(r.Family, r.Release, req.packName, req.arch)

多分ここで、検査してるんだろうけど、どうやら、これは、goval-dictionaryのGetByPackName()を呼び出しているようだ。

ま、見てみると。ここで色々データを取ってくるみたい。

 func (o *RedHat) GetByPackName(driver *gorm.DB, osVer, packName, _ string) ([]models.Definition, error) {
    osVer = major(osVer)
    packs := []models.Package{}
    err := driver.Where(&models.Package{Name: packName}).Find(&packs).Error
    if err != nil && err != gorm.ErrRecordNotFound {
        return nil, err
    }

    defs := []models.Definition{}
    for _, p := range packs {
        def := models.Definition{}
        err = driver.Where("id = ?", p.DefinitionID).Find(&def).Error
        if err != nil && err != gorm.ErrRecordNotFound {
            return nil, err
        }

        root := models.Root{}
        err = driver.Where("id = ?", def.RootID).Find(&root).Error
        if err != nil && err != gorm.ErrRecordNotFound {
            return nil, err
        }

        if root.Family == config.RedHat && major(root.OSVersion) == osVer {
            defs = append(defs, def)
        }
    }

    for i, def := range defs {
        adv := models.Advisory{}
        err = driver.Model(&def).Related(&adv, "Advisory").Error
        if err != nil && err != gorm.ErrRecordNotFound {
            return nil, err
        }

        cves := []models.Cve{}
        err = driver.Model(&adv).Related(&cves, "Cves").Error
        if err != nil && err != gorm.ErrRecordNotFound {
            return nil, err
        }
        adv.Cves = cves

        bugs := []models.Bugzilla{}
        err = driver.Model(&adv).Related(&bugs, "Bugzillas").Error
        if err != nil && err != gorm.ErrRecordNotFound {
            return nil, err
        }
        adv.Bugzillas = bugs

        cpes := []models.Cpe{}
        err = driver.Model(&adv).Related(&cpes, "AffectedCPEList").Error
        if err != nil && err != gorm.ErrRecordNotFound {
            return nil, err
        }
        adv.AffectedCPEList = cpes

        defs[i].Advisory = adv

        packs := []models.Package{}
        err = driver.Model(&def).Related(&packs, "AffectedPacks").Error
        if err != nil && err != gorm.ErrRecordNotFound {
            return nil, err
        }
        defs[i].AffectedPacks = filterByMajor(packs, osVer)

        refs := []models.Reference{}
        err = driver.Model(&def).Related(&refs, "References").Error
        if err != nil && err != gorm.ErrRecordNotFound {
            return nil, err
        }
        defs[i].References = refs
    }

    return defs, nil
}

なになに、パッケージネームからディフィニッション(数字)をデータベースから取ってきて、そのディフィニッションからcveを取って来る。あ、パッケージ名に対するcveが影響するバージョンを取ってくる(def.AffectedPacks)。データベース操作がいまいち読めないから、間違ってそうだが、大筋そうだろう。絶対間違ってるけど。

これで比較する準備ができたので、vulsのisOvalDefAffected()で現在のバージョンとOVALで取ってきたバージョンを比較すればよくて。lessThan関数でgithub.com/knqyf263/go-deb-versionライブラリを使って、比較する。これで、検知が可能

と言うことで、初めの予想と同じで、バージョンとcveが一対一やら、なんやかんやになってる。それはgoval-dictionaryがやってくれているんだろうけど、わからない。今度、そこ読んでみるかと。

感想としては、バージョンごとにcveやらを持っていて、それで全部比較するとなると、すごい計算量になりそう。。。

ま、私は、vulsをこれぜ完全理解したつもりです。

vuls何もわからない......

この記事は10分で書いたので、適当です。何か、間違いや、指摘があったら、教えてほしいです。

あと、こんな記事もあります。

Vulsのコードを読む その4 Vuls reportを調べてみた - Security Index

こちらの記事は、cpeから、cveを検知する話になってます。