NFLabs. エンジニアブログ

セキュリティやソフトウェア開発に関する情報を発信する技術者向けのブログです。

OffSec資格 「OSED」合格体験記


はじめに

この記事は、NFLaboratories Advent Calendar 2023 - Adventar 11日目の記事です。
皆様、毎日お疲れ様です。教育ソリューション担当の番場です。
2023年11月にOffSec社の提供している資格であるOSEDを取得したので、当資格の概要や取得するまでの道のりを紹介したいと思います。

  • OffSecの関連資格に興味がある。
  • OSED受験を考えているが、日本語の参考記事が少ない。
  • どうすれば取得できるのか?どのような技術を身につけられるのか?具体的に知りたい。

などの方は是非参考にしてみて下さい。

OSEDとは?

OSEDは、EXP-301というトレーニングを受講し、試験に合格することで得られる資格です。また、EXP-301: Windows User Mode Exploit Developmentとは、OffSec社が提供する学習コースです。

EXP-301の内容は、Windowsのユーザーモードで動くプログラムのExploit開発ができるようになるためのトレーニングとなっており、簡単なスタックオーバーフローからDEP/ASLRバイパス、バイナリ解析を利用した脆弱性の発見にもフォーカスしています。

以上の内容から、EXP-301を受講すると脆弱性の発見~Exploit開発までに必要な知識を得る事ができます。

EXP-301の構成

EXP-301はTRAINING MATERIALという教育資料や動画/ExerciseやExtraMilesという各セクション用の演習問題や発展問題がセットになった学習用コンテンツとCHALLENGE LABSと呼ばれる演習環境で構成されています。

TRAINING MATERIALのうちの教育資料は、以下の内容で構成されています。
※syllabusについてはこちらを参照下さい。

  • WinDbg and x86 Architecture
  • Exploiting Stack Overflows
  • Exploiting SEH overflows
  • Introduction to IDA Pro
  • Overcoming Space Restrictions: Egghunters
  • Creating Custom Shellcode
  • Reverse Engineering for Bugs
  • Stack Overflows and DEP Bypass
  • Stack Overflows and ASLR Bypass
  • Format String Specifier Attack Part I
  • Format String Specifier Attack Part II
    • ※Windows x86のスタックベースのExploit開発がメインです。

上記内容を参照すると、解析に慣れていない方にとっては、少々敷居の高い内容に見えてしまいますが、Windowsにおけるx86アーキテクチャの概要説明として「スタックとは何か?」や「呼び出し規約」「各CPUレジスタの用途」等の基本的な内容も資料内で説明してくれています。

また、WinDbgやIDA Freeの使用方法も説明してくれているのでそこまで敷居は高くないと思います。

EXP-301を受講する前に身に着けておくと楽になるスキル

私が考えるEXP-301を受験する前に身に着けておくと楽になるスキルは以下の通りです。

  • 基本的なPython3の構文を理解し、意図したヘッダ/ボディを含むHTTPリクエストを送信するコードを直ぐに記述できる。
  • x86アセンブリ言語のコードを読んで、動作を理解できる。
    • 更に、アセンブリを読んだ時にC言語のソースコードをイメージできる。
  • プログラム実行時にメモリ領域がどのように管理されているかを知っている。(スタックの積み上げ順序やデータの格納順序の理解程度で問題ありません。)

併せて、スタックベースでもヒープベースでも良いのでBuffer Overflowを用いてShellcodeを実行した経験がある方は、より資料の内容を理解しやすいと思います。

※もちろん、EXP-301の受講を通じて上記のスキルを身につけられますのでご安心ください。あくまで身に着けておくと楽になるスキルと考えています。

EXP-301を受講した後に身につけられるスキル

TRAINING MATERIALやCHALLENGE LABSの内容から以下のスキルを身に着ける事ができます。

【解析に関わる技術】

  • WinDbgでの動的解析/IDAでの静的解析を組み合わせた解析技術
    • 二つのツール間で解析対象のベースアドレスを同期させ、静的解析にてローカル変数/レジスタへの格納値や条件分岐の分岐先を予測しつつ、動的解析にて意図した値が格納されているかを確認します。
    • 私は、静的解析を行って、ある程度脆弱性のありそうな場所に見切りをつけた後に、動的解析を使用しながら、送信したペイロードがどの処理を辿っているかを確認していました。🤖
    • ※脆弱性発見の技術に焦点を当てているのでマルウェア解析の技術とは少々異なります。

【Stack Overflowに関わる技術】

  • SEH(Structured Exception Handling)
    • SEHとは、構造化例外処理とも呼ばれるWindows OSにおけるプログラムの例外処理用に実装された機構です。
  • Egghunter
    • クラッシュ時点で使用できるスタック領域が少ない場合に、メモリ上に配置したShellcodeを探し出し先頭アドレスに遷移させるアセンブリ(Egghunter)を作成します。
    • 操作可能なメモリ領域にPivotするための目印となるバイナリパターンがEggで、それを探し出す処理を実装するからEgghunterです。🥚
  • ROP(Return-Oriented Programming)
    • Return-Oriented Programmingは直訳すると、リターン指向プログラミングですが、簡単に説明すると「命令+RET命令」が存在するメモリアドレス指定を繰り返すことでDEPが有効化されたバイナリ上でも悪意のある操作を実現しようという内容です。
    • 「命令+RET命令」の組み合わせをROPガジェットと呼び、バイナリ上に存在するROPガジェットを探す作業は、宝さがし💎に似ているため個人的にはとても好きです。CTF等で扱った経験がありご存知の方も多いと思います。
    • 後ほど簡単なROPガジェットとその動作について紹介します。
  • DEP/ASLR bypass
    • DEPについては、ROPを使用して回避できますが、ASLRの場合にはヒープやスタック等の領域が仮想アドレス空間上のランダムなアドレスに配置されてしまっているので、主に以下のような方法で回避する必要があります。
      • 1.ASLRが無効化されているモジュールを探す。
        • ←モダンなプログラムでは有効化されている場合が多い。
      • 2.ASLRが有効化されているバイナリでも、メモリ上に配置された際に一部のbitが固定されることを利用し、部分的な上書きを実行する。
        • ←ROPを併用する場合は、使用できるガジェットが限られてしまう。
      • 3.ベースアドレスをブルートフォースする。
        • ←アクセスできない無効なアドレスをブルートフォース途中で指定してもプログラム自体がクラッシュしないか、クラッシュしても自動的に再起動するプログラムに対してしか実行できない。
        • ←ハイエントロピーASLRが有効化されている環境では計算量が非常に大きくなってしまう。
      • 4.Info Leak(Information Leakage)関連の脆弱性を使用してモジュールのアドレスを取得する。
        • ←Info Leak関連の脆弱性がないと実行できない。

上記の手法のうち、EXP-301では、4番目のInfo Leakの手法を中心に演習を実施していきます。

【開発に関わる技術】

  • Python3での開発技術
    • 全てのExploit CodeはPython3で開発します。また、OSEDではリモートからペイロードを送信し、プログラムに対して値を入力する事でShellcodeの発火を行うまでが焦点になっています。
    • よって、ペイロードを送信するときには意図した値をパケットに含めるようにPython3のコードを記述する必要があります。
      • より厳密には、ペイロードを受け取ったプログラムでメモリに値を格納するときには、(OSEDではx86系のCPUを標的としているために)リトルエンディアンを考慮してコードを記述します。
    • これらの「値の送信→メモリへの格納→プログラムでの処理」までを意図した流れで行えるように開発を行います。🐍
  • Shellcodeの開発技術
    • アセンブリで記述した処理内容を16進数に変換し、Shellcodeを作成します。この時にShellcodeのバイト数を減らし、処理を効率化するためにEDTから取得したWindows APIの名前をror命令を使用してハッシュ化(API Hashing)したり、Windows APIの関数呼び出し時のスタックを再現したりと工夫を施します。🐚

少々長くなりましたが、イメージを具体化したい方のためにも詳しく書きました。
なお、以下のようなエクスプロイト開発は含まれないコースになっています。

  • Heap領域を対象にしたメモリ破壊
  • 64bitアプリケーションを対象にしたエクスプロイト
  • Windowsのカーネルドライバを対象にした解析と脆弱性発見

上記は、EXP-301の上位互換であるEXP-401/OSEEを受講し、学習する必要があります。

私は解析が比較的好きな方なので総じてとても楽しく、毎日ワクワクしながら資料や演習環境と向き合っていました。

特に実業務内でバックエンドの処理が分からず、ずっと悩んでいる事があったので、それよりも低レイヤで処理を一つずつ整理している方が、ずっと楽しいと感じました。🥳

Windows OSに関わる処理の細かい部分まで追っていけますので、学べる内容は多いと思います。また、脆弱性ってどうやって探すんだろう?に興味がある方は是非、受けてみてください。

OSEDを取得するまでの道のり

試験までの準備

大まかな試験対策項目とそのスケジュール(期間)は以下の通りです。

※EXP-301の登録時には、90日コースを選択していました。少々時間がなく焦りましたが、最終的には約20日残して試験合格まで達成できたので、事前知識があれば90日コースでも良いと思います。

試験対策
期間
補足
1.Udemy
Exploit Development Tutorial for Hackers and Pentesters
7~8月 ・Udemyで受験できる有料コース
・Linuxベースの内容だが、ROPの基礎等を学習可能
2.EXP-301
TRAINING MATERIAL
8~9月 ExerciseとExtraMilesも併せて実施
3.EXP-301
CHALLENGE LABS
9~10月 3つのChallangeで構成
1.Exploit Development Tutorial for Hackers and Pentesters

上記の補足でも記載いたしましたが、LinuxベースのExploit開発の基礎的な内容ですので完全にOSEDの内容と合致するわけではありません。また、非常に初歩的な内容も含まれているので、復習という意味も含めて受講しました。

初めにメモリや実行可能ファイルの仕組みを学んだ後に、スタックオーバーフローを使用した攻撃手法を学べます。

また、面白かったのはDEPやASLRのバイパス以外にもRELRO(RELocation Read Only)と呼ばれる、「GOTを含む特定のセクションをRead Onlyに設定できるセキュリティ機構」をバイパスする手法を学べたことです。

最終的に、OSEDに活きた内容とそうでない内容がありましたが学びは多かったです。

2.EXP-301 TRAINING MATERIAL

TRAINING MATERIALの中にはExerciseという基本問題とExtraMilesという発展問題が存在します。私はExerciseを確実に実施し、ExtraMilesは余裕があれば取り組むという方針で行いました。

最終的には、ほぼすべてのExtraMilesも含めて完了しましたが、一つのExtra Mileだけは、非常に難しく完了に至りませんでした。

ちなみに、完了できなかったExtraMilesには「This exercise is not for the faint heart.」というメッセージが記載されています。訳すと、「心臓の弱い人はご遠慮ください。」というジェットコースターのような注意書きになっています。🎢

続きは、心臓が元気で時間のある時にやりたいと思います。

3.EXP-301 CHALLENGE LABS

CHALLENGE LABSは3つの課題から構成されています。課題に取り組む際には、以下のツールを使用すると、WinDbgとサービス起動のポチポチ作業から解放されるのでお勧めです。


実際の演習の内容については、ネタバレするので記述しませんが、最後のチャレンジは少々難しく、1週間(実質30時間くらい)ほど時間がかかりました。

本番の一部の問題とCHALLENGE LABSの問題が、難易度や構成について似通っており、独力でやり切れるようになっておくと良いと思います。💪

試験当日の流れ

OSEDの試験は、47時間45分の演習と24時間のレポート作成で構成されています。

60点が合格点となっており、2つの課題は30点ずつ、1つの課題は40点となっています。つまり、3つの課題のうち2つ以上を完了すれば合格点には達します。

試験当日のタイムスケジュールは以下の通りです。

日数 時刻 内容
day1 13:00 午後から試験開始。課題1から取り組む。
day1 17:00 課題1が完成。限られたROPガジェットに少々戸惑ったが予定通りに完了。
day1 20:00 課題2が完成。こちらは、難しくなく手順通りにこなすだけだった印象。
day2 2:00 課題3の解析に苦戦。頭が飽和したので、2時間ほど睡眠。
day2 8:00 課題3の解析が完了。脆弱性探しの旅へ。
day2 18:00 分岐の解析がおおよそ終了。脆弱性がありそうな場所にあたりをつける。
day2 22:00 脆弱性の発見が終了。達成感。
day3 2:00 気付いたら達成感とともに寝ていた。
day3 8:00 最後の課題に少々手間取ったが、何とか完成。全試験内容は完了。
day3 24:00 レポート作成完了。約100枚のレポートに。
  • 全ての課題を終えられた達成感は凄まじく、終わった後に散歩したときの空はとても青く感じました。🌍
  • レポート提出後、1営業日くらいで結果が送付されてきました。


終わってみて思うこと

試験内容やTRAINING MATERIALの資料/Exerciseの内容等、勉強になる事ばかりでした。やはりOffsec系の試験は見えない世界を広げてくれる気がします。🪐

蛇足ではありますが、OSCE3を取得するための3資格(OSED/OSWE/OSEP)のうちDiscordに登録している合格者が最も少ないのが、OSEDとなります。

※OffSec社の管理しているDiscordサーバ上での取得結果となります。人数につきましては、2023年10月26日に取得した内容となります。
※OSCE3の内容については、是非
弊社エンジニアブログの記事をご参照ください。

やはり、低レイヤの内容が多い事もあって、受験されづらいんですかね...🤔

まだまだ、実際の脆弱性を探すのには身につけられていない知識だらけなので、ゼロデイ脆弱性の発見やCVEを採番できるようになりたいです。👾

Appendix:ROPの紹介

OSEDでは、取り扱えないと試験に臨めないレベルで重要なROPについて補足として説明したいと思います。
上述した通り、ROPは「命令+RET命令」が存在するメモリアドレス指定を繰り返すものですが、これでは何を言っているのか分からないですよね...すいません。

プログラミング技法の一つであるとされるROPですが、文章で説明するよりも動きを見た方が早いと思いますのでスライドに起こしてみました。一つのスライドを進めるのがステップイン実行を一回進めているのと同じ操作とします。

※スタック上の値は既にスタックオーバーフローによって書き換えられており、EIPの制御を取得済みのものとします。
※メモリアドレスの値についても簡略化のために分かりやすい値にしておりますので、実際の値とは異なる点についてご了承ください。
※前提として、以下を理解しておく必要があります。

  • 「EIPには次に実行する機械語のアドレスがセットされていること。」
  • 「RET命令ではスタック上の値(ESPが指す値)をPOPしてEIPに格納し、対象のアドレスにJMPしていること。」
  • 「POP実行後にはESPの値が加算されていること。」

「命令+RET命令」の組み合わせとは、スライド例の「add eax, 0x08 + ret」を指しています。これらのRET命令で終了する機械語のアドレスをスタック上に積み上げると、実行させたい処理を繰り返すことができるため、理論上あらゆる処理が実装できるようになります。

例えば、EAXとEBXを減算するような処理を実行したい場合は、「sub eax, ebx + ret」の組み合わせを実行しているEXEやDLL等から探し出し、スタックに積めば良いだけです。(x86アセンブリでは、「sub eax, ebx + ret」は機械語で表すと「0x29 0xd8 0xc3」となるためこれらの16進数の羅列を保存しているアドレスを探します。)

このように、機械語を連続して処理させることで目的を達成していくわけです。実際には、欲しい組み合わせがすぐに見つかるわけではありませんので、限られたROPガジェットの中で目的の処理を作っていく必要があります。この作業が宝探しに似ており、非常に限られたガジェットの中で目的を達成した時の喜びはひとしおです。

ここで、問題です。以下の簡単なROPを読んでみて、どんな処理になるかを考えてみてください。ropの変数値は標的上でスタックに展開されています。各アドレスの指す機械語については、コメントアウトを参考にしてください。

※スライド同様にメモリアドレスについては実際の値とは異なります。また、pack()については値をリトルエンディアンで変数に格納しているに過ぎませんので無視していただいて大丈夫です。

...
rop += pack("<L", (0x10010101))   # pop ecx ; ret
rop += pack("<L", (0x88888888))
rop += pack("<L", (0x10120101))   # add eax, ecx ; ret
rop += pack("<L", (0x10010101))   # pop ecx ; ret
rop += pack("<L", (0x77777878))
rop += pack("<L", (0x10120101))   # add eax, ecx ; ret
...

すぐに解説してしまいますが、上記は「add eax, 0x100」を実行するためのROPになっています。ROPでは特定のレジスタに即値を格納するガジェットが少ないかつNULLバイトを含められない場合が多い(スタックオーバーフロー時にNULLバイトが文字列終端とみなされてしまう)ため、上記のように回りくどいやり方を取っています。

OSEDの試験では、最終的にWindows APIを実行してShellcodeを発火させるので、上記よりもずっと複雑かつコードが長くなります。少しでも興味がわいた方は是非、OSED試験で体感してみて下さい。👍

最後に

最後まで読んでくださってありがとうございました。私事ながら、OSCP/OSEPは既に取得しており、OSCE3まで残すはOSWEのみとなりました。

OSCE3ホルダーを目指して日々研鑽を続け、少しでも貢献できるように精進する決意表明にて、本記事の締めとさせていただきます。