私は、私が見るセキュリティ関連のビデオに関するメモを書き留め始めました(迅速なリコールの方法として)。
これらは初心者にとってより便利かもしれません。
ここでのメモの順序は、難易度ではなく、それらをどのように書く方法の逆年代順にあります(つまり、最新の最初)。
この作業は、Creative Commons Attribution-Commercial-Sharealike 4.0 International Licenseの下でライセンスされています。
2017年8月12日に書かれています
Gynvaelの自信CTF 2017のライブストリームの影響を受けています。そして、彼のGoogle CTF Quals 2017 Livestreamによってここにあります
時には、課題がVMを実装することにより複雑なタスクを実装する場合があります。 VMを完全にエンジニアリングし、課題の解決に取り組む必要があるとは限りません。時には、少し再生できます。何が起こっているのかを知ったら、VMに接続して、必要なものにアクセスできます。さらに、VMSではタイミングベースのサイドチャネル攻撃が容易になります(主に実行された「実際の」命令の数が多いためです。
バイナリの暗号化的に興味深い機能は、定数を探してオンラインで検索するだけで、認識し、迅速に再評価することができます。標準の暗号関数の場合、これらの定数は関数を迅速に推測するのに十分です。よりシンプルな暗号関数は、さらに簡単に認識できます。多くのXORやそのようなことが起こっているのを見て、簡単に識別できる定数がない場合は、おそらく手持ちの暗号である(そしておそらく壊れている可能性がある)。
時には、IDAをヘクスレイで使用する場合、分解ビューが逆コンパイルビューよりも優れている場合があります。これは、分解ビューで多くの合併症が起こっているように見えることに気付いた場合に特に当てはまりますが、分解ビューで繰り返しパターンに気付きます。 (スペースバーを使用して2つをすばやく切り替えることができます)。たとえば、(固定されたサイズの)ビッグインテガーライブラリが実装されている場合、逆コンパイルビューはひどいですが、分解ビューは簡単に理解できます( adcなどの繰り返しの「キャリー」指示により、簡単に認識できます)。さらに、このように分析する場合、IDAのグラフビューで「グループノード」機能を使用することは、各ノードが何をするかを理解して、グラフの複雑さをすばやく減らすのに非常に役立ちます。
奇妙なアーキテクチャの場合、優れたエミュレータを持つことは非常に便利です。特に、メモリのダンプを与えることができるエミュレータを使用して、エミュレータからメモリを出したら、何が起こっているのかをすばやく把握し、興味深い部分を認識できます。さらに、快適な言語(Pythonなど)で実装されたエミュレータを使用すると、自分の好きなものを正確に実行できることを意味します。たとえば、コードの興味深い部分が複数回実行したい場合(たとえば、ブルートフォースなど)、エミュレータを使用して、完全なプログラムを実行する必要があるのではなく、コードのその部分のみを実行するものをすばやくコーディングできます。
怠け者であることは、リーインが良いときは良いことです。エンジニアリングを逆にするのに時間を無駄にしないでください。しかし、Reingのより困難なタスクを実際に行うことに費やされる時間を短縮できるように、Reingの時間を短縮できるように、Reingを実行するのに十分な時間を費やしてください。このような状況では、各関数を徹底的に分析するのに時間をかけすぎずに、さまざまな機能をすばやく見てみることです。機能が何であるかをすぐに評価します(たとえば、「暗号のように見えます」、または「メモリ管理のように見える」など)
不明なハードウェアやアーキテクチャの場合、Googleで十分な時間を費やすと、ツールをより迅速に構築するのに役立つ多くの便利なツールやドキュメントで幸運になるかもしれません。多くの場合、玩具エミュレータなどの実装は、始めるべき迅速なポイントとして役立つ可能性があります。または、クイック「修正」スクリプトを書くことができる興味深い情報(ビットマップの保存方法や文字列の保存方法など)を取得し、通常のツールを使用して興味深いものがあるかどうかを確認することができます。
GIMP(画像操作ツール)には、生のピクセルデータを見るための非常にクールなオープン/ロード機能があります。これを使用して、生のバイナリデータで資産または反復構造をすばやく探すことができます。設定をいじるのに時間を費やして、そこからより多くの情報を収集できるかどうかを確認してください。
2017年7月2日に書かれています
infoseciitr #binチャットで @p4n74および @h3rcul35との議論の影響を受けます。特に剥がれたとき、初心者がより大きなチャレンジバイナリから始めるのに苦労することがあることについて議論していました。
REチャレンジを解決するか、それをPWNすることができるようにするには、効果的に悪用できるように、最初に与えられたバイナリを分析する必要があります。バイナリは剥がされる可能性があるため( fileを使用して見つかった)、分析を開始する場所を知っている必要があります。
バイナリの脆弱性を探しているとき(そして私が集めたものから、異なるCTFチームには異なる好みがあります)。
1.1。完全なコードをcに伝達します
この種の分析はまれですが、小さなバイナリには非常に便利です。アイデアは、コード全体をリバースエンジニアリングすることです。すべての関数がIDAで開かれ(逆コンパイラビューを使用)、名前変更(ショートカット:n)とリチピング(ショートカット:Y)を使用して、逆コンパイルコードをより読みやすくします。次に、すべてのコードが個別の.cファイルにコピー/エクスポートされます。これをコンパイルして、元のバイナリに同等の(同じではない)バイナリを取得できます。次に、ソースコードレベルの分析を実行し、Vulnsなどを見つけることができます。脆弱性のポイントが見つかったら、エクスプロイトはIDAのきれいに分解されたソースに続いて、分解ビューを並べて並べて続くことで構築されます(2つの間をすばやく切り替えて、グラフをすばやく切り替えてテキストビューを使用します)。
1.2。逆コンパイルの最小分析
ほとんどのバイナリは比較的役に立たないため、これは非常に頻繁に行われます(攻撃者の観点から)。疑わしい、またはvulnに導く可能性のある機能を分析するだけです。これを行うには、開始するアプローチがいくつかあります。
1.2.1。メインから始めます
通常、剥がれたバイナリの場合、メインでさえラベル付けされていません(IDA 6.9以降はそれをマークします)が、時間が経つにつれて、エントリポイントからメインに到達する方法を認識することを学びます(IDAはデフォルトで開きます)。あなたはそれにジャンプして、そこから分析を開始します。
1.2.2。関連する文字列を見つけます
時には、出力される可能性のある特定の文字列などを知っていることがあります。文字列ビュー(ショートカット:Shift+F12)にジャンプし、弦を見つけて、Xrefs(ショートカット:X)を使用して後方に動作できます。 Xrefsを使用すると、メイン(または知っているポイント)に到達するまで、そのチェーン内のすべての関数でXrefsを使用することにより、その文字列への関数のパスを見つけることができます。
1.2.3。いくつかのランダム関数から
特定の文字列が役立たない場合があり、メインから始めたくない場合があります。代わりに、関数全体のリスト全体をすばやくめくって、疑わしいように見える関数(多くの定数やXORなどの多数のXORなど)を探したり(Malloc、FreeなどのXrefs)を呼び出したり、そこから始めて、両方の前方に進み、両方の前方に行きます(コールする機能に続いて)(機能のXrefs)
1.3。純粋な分解分析
逆コンパイルビューを使用できない場合があります(奇妙なアーキテクチャ、アンチポンコンポレーションのテクニック、手書きのアセンブリ、または不必要に複雑すぎる逆コンパイルのため)。その場合、分解ビューを純粋に見ることは完全に有効です。 (新しいアーキテクチャの場合)自動コメントをオンにすることは非常に便利です。これは、各指示を説明するコメントを示しています。さらに、ノードカラー化とグループノードの機能は非常に役立ちます。これらのいずれかを使用していなくても、分解に定期的にコメントをマークすることは大いに役立ちます。私が個人的にこれを行っている場合は、Pythonのようなコメントを書き留めて、すぐにPythonに手動で輸送できるようにします(特にZ3などを使用する必要があるREチャレンジに役立ちます)。
1.4。 BAPなどのプラットフォームを使用します。
この種の分析は(セミ)自動化されており、通常ははるかに大きなソフトウェアに対してより有用であり、CTFSで直接使用されることはほとんどありません。
ファジングは、実際にそれを理解することなく、vulnに迅速に到達するための効果的なテクニックになります。ファザーを使用することにより、多くの低い屈曲フルーツスタイルのvulnsを取得できます。これは、実際のVulnに到達するために分析してトリアージ化する必要があります。詳細については、ファジングと遺伝的ファジングの基本に関する私のメモをご覧ください。
動的分析は、静的分析を使用してVULNを見つけた後に使用して、迅速にエクスプロイトを構築するのに役立ちます。あるいは、Vuln自体を見つけるために使用することもできます。通常、デバッガー内の実行可能ファイルを起動し、バグをトリガーするコードパスに沿って進ろうとします。適切な場所にブレークポイントを配置し、レジスタ/ヒープ/スタック/などの状態を分析することで、何が起こっているのかをよく理解できます。また、デバッガーを使用して、興味深い機能をすばやく識別することもできます。これは、たとえば、最初にすべての機能に一時的なブレークポイントを設定することで実行できます。その後、2つのウォークを行うために進みます - 1つはすべての面白くないコードパスを通過します。そして、1つだけの興味深いパスを通して。最初の散歩は、すべての関心のない機能をすべて旅行し、それらのブレークポイントを無効にし、それによって興味深いものを2回目の散歩中にブレークポイントとして現れます。
分析のための私の個人的なスタイルは、通常、メインから(または非コンソールベースのアプリケーションの場合、文字列から)静的分析から始めて、奇妙に見える関数を迅速に見つけるために取り組むことです。その後、ここから時間を費やして前後に分岐し、コメントを定期的に書き留め、変数を継続的に名前を変更してリテイピングして、逆コンパイルを改善します。他の人と同様に、私はApple、Banana、Carrotなどの名前を使用して、一見便利に見えますが、まだ未知の機能/変数/などのように分析が容易になります(FUNC_123456スタイルの名前を追跡するのは難しすぎます)。また、IDAの構造ビューを定期的に使用して、構造(および酵素)を定義して、逆コンパイルをさらに良くします。 Vulnを見つけたら、通常、PWNTOOLSでスクリプトを書くことに移行します(そして、それを使用してgdb.attach()を呼び出します)。このようにして、私は何が起こっているのかを多くのコントロールすることができます。 GDB内では、通常、Plane GDBを使用しますが、必要に応じてPEDAを即座にロードするコマンドpedaを追加しました。
私のスタイルは間違いなく進化しています。自分のツールや、物事をスピードアップするために書いたカスタムツールにも慣れてきたからです。他の分析スタイルや、私のスタイルの変更の可能性があると聞いてうれしいです。あなたが持っているコメント/批判/賞賛のために、いつものように、私はtwitter @jay_f0xtr0tで連絡することができます。
2017年6月4日に書かれています
Gynvael Coldwindによるこの素晴らしいライブストリームの影響を受けて、彼はROPの基本について議論し、いくつかのヒントとトリックを提供します
リターンオリエンテッドプログラミング(ROP)は、NX(非実行可能メモリ)保護をバイパスするために使用される古典的な搾取技術の1つです。 Microsoftは、NXをDEP(データ実行防止)として組み込んでいます。 Linuxなどでも効果的です。つまり、この保護により、シェルコードをヒープ/スタックに配置して、ジャンプするだけで実行することができなくなります。そのため、コードを実行できるようにするために、既存のコード(メインバイナリ、またはそのライブラリ - Linux上のLIBC、LDDなどにジャンプします。 ROPは、すでにそこにあるこのコードの断片を再利用し、それらの断片を組み合わせてあなたがやりたいことをする方法を見つけ出すことで生じます(もちろん、惑星をハッキングします!!!)。
もともと、ROPはRET2LIBCで始まり、その後、より多くの小さなコードを使用することにより、時間の経過とともにより高度になりました。 ROPは、それを軽減するための追加の保護のために「死んでいる」と言う人もいるかもしれませんが、多くのシナリオ(そして多くのCTFには間違いなく必要です)で悪用される可能性があります。
ROPの最も重要な部分はガジェットです。ガジェットは「ROPの使用可能なコード」です。それは通常、 retで終わるコードの部分を意味します(ただし、他の種類のガジェットも有用かもしれません。 pop eax; jmp eaxなど)。これらのガジェットを結びつけて、 ROPチェーンとして知られているエクスプロイトを形成します。
ROPの最も重要な仮定の1つは、スタックを制御できることです(つまり、スタックポインターが制御するバッファーを指します)。これが真でない場合は、ROPチェーンを構築する前にこのコントロールを獲得するために、他のトリック(スタックピボットなど)を適用する必要があります。
ガジェットをどのように抽出しますか?ダウンロード可能なツール(Ropgadgetなど)またはオンラインツール(Ropshellなど)を使用するか、独自のツールを作成します(必要に応じて特定の課題に微調整できるため、より困難な課題にはより困難な課題に役立ちます)。基本的に、これらのガジェットにジャンプできるアドレスが必要です。これは、ASLRなどに問題がある可能性がある場合です(その場合、実際にROPを実行する前に、アドレスのリークが得られます)。
それでは、これらのガジェットをどのように使用してロプチェーンを作るのでしょうか?まず「基本的なガジェット」を探します。これらは、私たちのために簡単なタスクを実行できるガジェットです( pop ecx; retガジェットを配置することでECXに値をロードするために値をロードし、その後にロードする値が続き、その後のチェーンの残りの部分が続き、値がロードされた後に戻ります)。最も便利な基本ガジェットは、通常、「レジスタを設定する」、「レジスタによって指し示されたアドレスにレジスタ値を保存する」などです。
これらのプリミティブ関数から構築して、より高いレベルの機能を獲得することができます(私の投稿タイトルのExploation Abstractionと同様)。たとえば、Set-Register、およびStore-Value-at-Addressガジェットを使用して、特定のアドレスを特定の値で設定できる「ポーク」関数を考え出すことができます。これを使用して、特定の場所に特定の場所に記憶のある場所に保存できる「ポークストリング」関数を構築できます。ポークストリングができたので、メモリ内で必要な構造を作成できるため、基本的にほとんど完了し、必要なパラメーターで必要な機能を呼び出すこともできます(セットレジスタができ、値をスタックに配置できるため)。
これらの低次のプリミティブからより複雑なことを行うより大きな機能に構築する最も重要な理由の1つは、間違いを犯す可能性を減らすことです(それ以外の場合はROPで一般的です)。
ROPのためのより複雑なアイデア、テクニック、ヒントがありますが、それはおそらく別の時間の間、別のメモのトピックです:)
PS:Gynには、読む価値があるかもしれない返品指向の搾取に関するブログ投稿があります。
2017年5月27日に書かれました。 2017年5月29日に延長
Gynvael Coldwindによるこの驚くべきライブストリームの影響を受け、そこで彼は遺伝的ファジングの背後にある基本的な理論について語り、基本的な遺伝的ファザーを構築し始めます。その後、彼はこのライブストリームの実装を完了します。
「Advanced」ファジング(私の「ファジングの基本」ノートに記載されている盲目のファッツァーと比較して)。また、バイトなどを変更/変異させますが、盲目の「ダム」ファッツァーよりも少し賢くなります。
なぜ遺伝的ファザーが必要なのですか?
一部のプログラムは、脆弱性が到達するために満たすために多くの条件を必要とする可能性があるため、愚かなファッツァーに対して「厄介」である可能性があります。馬鹿げたファッツァーでは、進歩を遂げているかどうかは考えられていないため、この発生の可能性は非常に低くなります。具体的な例として、 if a: if b: if c: if d: crash! (それをクラッシャーコードと呼びましょう)、この場合、プログラムをクラッシュさせるために4つの条件を満たす必要があります。ただし、4つの突然変異a 、 b 、 c 、 dがすべて同時に発生する可能性が非常に低いという理由だけで、馬鹿げたファッツァーはa条件を乗り越えることができない可能性があります。実際、たとえそれがaのことで進行したとしても、次の突然変異はプログラムについて何も知らないからといって戻るかもしれませ!a 。
待って、この種の「バッドケース」プログラムが表示されるのはいつですか?
1つの例を挙げると、ファイル形式のパーサーでは非常に一般的です。いくつかの特定のコードパスに到達するには、複数のチェックを過ぎて「この値はこれでなければなりません。その値はそれでなければならず、他の値は他の何かでなければなりません」などです。さらに、ほとんどの現実世界ソフトウェアは「複雑ではない」ものではなく、ほとんどのソフトウェアには多くの多くの可能なコードパスがあり、その一部は州内の多くのものが正しくセットアップされた後にのみアクセスできます。これにより、これらのプログラムのコードパスの多くは、基本的に馬鹿げたファッツァーにとってアクセスできません。さらに、いくつかのパスが完全にアクセスできない場合があります(単に狂ったほどありそうにないのではなく)。これらのパスのいずれかにバグがある場合、愚かなファッツァーはそれらを見つけることができません。
では、愚かなファッツァーよりもどうやってうまくやるのでしょうか?
上記のクラッシャーコードのコントロールフローグラフ(CFG)を検討してください。偶然に馬鹿げたファッツァーが突然a場合、新しいノードに到達したことも認識されませんが、これを無視し続けてサンプルを破棄します。一方、AFL(およびその他の遺伝的または「スマート」ファッツァー)が行うことは、これを新しい情報(「新しく到達したパス」)として認識し、このサンプルをコーパスに新しい初期ポイントとして保存します。これが意味することは、今ではファッツァーがaブロックから開始してさらに移動できることです。もちろん、時々、それb aサンプルから!aこれもまた新しいノードに到達するため、コーパスに新しいサンプルを追加します。これは続き、ますます多くの可能性のあるパスをチェックし、最終的にcrash! 。
なぜこれが機能するのですか?
変異したサンプルをコーパスに追加することにより、グラフをさらに探索することで(つまり、以前に調査されていない部分に到達します)、以前は到達できなかった領域に到達することができ、そのような領域を曖昧にすることができます。そのような領域を曖昧にすることができるので、それらの地域のバグを発見できるかもしれません。
なぜそれは遺伝的ファジングと呼ばれるのですか?
この種の「スマート」ファジングは、遺伝的アルゴリズムのようなものです。標本の突然変異とクロスオーバーは、新しい標本を引き起こします。テストされた条件により適した標本を保持します。この場合、条件は「グラフ内のノードの数に達しましたか?」です。より多くを通過するものは保持できます。これは遺伝的アルゴとまったく同じではありませんが、バリエーションです(未開の領域を横断するすべての標本を保持しているため、クロスオーバーはしません)が、同じ名前を取得するのに十分に似ています。基本的に、既存の集団からの選択、続いて突然変異が続き、その後にフィットネステスト(新しい領域が見られたかどうか)、および繰り返します。
待ってください、それで私たちは到達していないノードを追跡するだけですか?
いいえ、そうではありません。 AFLは、ノードではなく、グラフ内のエッジトラバーサルを追跡します。さらに、「エッジが移動したかどうか」と言っているだけでなく、エッジが何回横断されたかを追跡します。エッジがトラバースされている場合、0、1、2、4、8、16、...時代、それは「新しいパス」と見なされ、コーパスへの追加につながります。これは、ノードではなくエッジを見ることがアプリケーションの状態を区別するためのより良い方法であり、エッジトラバーサルの指数関数的に増加するカウントを使用すると、より多くの情報が得られるためです(エッジは2回のトラバースとはまったく異なりますが、トラバースされた10は11回とは異なります)。
それで、あなたは遺伝的ファザーで何とすべてが必要ですか?
2つのことが必要です。最初の部分はトレーサー(またはトレースインストルメンテーション)と呼ばれます。基本的に、アプリケーションでどの命令が実行されたかを示します。 AFLは、コンピレーション段階の間にジャンプすることにより、簡単な方法でこれを行います。アセンブリの生成後、しかしプログラムを組み立てる前に、基本的なブロックを探します(エンディングを探し、ジャンプ/ブランチタイプの指示をチェックします)。ソースコードがない場合は、トレースに他の手法(PIN、デバッガーなど)を使用できます。結局のところ、Asanでさえカバレッジ情報を提供できます(これについてはドキュメントを参照)。
2番目の部分では、Tracerが指定したカバレッジ情報を使用して、表示された新しいパスを追跡し、将来のランダム選択のために生成されたサンプルをコーパスに追加します。
トレーサーを作るための複数のメカニズムがあります。それらはソフトウェアベース、またはハードウェアベースにすることができます。ハードウェアベースの場合、たとえば、メモリにバッファーが与えられた場合、そのバッファに移動されたすべての基本ブロックの情報を記録する場合、いくつかのIntel CPU機能が存在します。これはカーネル機能なので、カーネルはそれをサポートし、API(Linuxが行う)として提供する必要があります。ソフトウェアベースの場合、コードを追加するか、デバッガー(一時的なブレークポイントを使用する、または単一ステッピングを使用して)を使用するか、住所消毒剤のトレース能力を使用するか、フック、エミュレーター、または他のさまざまな方法を使用します。
メカニズムを区別する別の方法は、ブラックボックストレース(変更されていないバイナリのみを使用できる場合)またはソフトウェアホワイトボックストレース(ソースコードにアクセスできる場所で、コード自体を変更してトレースコードを追加する)のいずれかです。
AFLは、コンパイル中にトレースの方法として(またはQEMUエミュレーションを介して)ソフトウェア機器を使用します。 Honggfuzzは、ソフトウェアとハードウェアベースのトレース方法の両方をサポートしています。他のスマートファッツァーは異なるかもしれません。 Gynが構築するものは、住所Sanitizer(ASAN)によって提供されるトレース/カバレッジを使用します。
一部のファザーは、forkserverやその他のそのようなアイデアを作成するなど、「スピードハック」(つまり、ファジング速度を上げる)を使用します。ある時点でこれらを調べる価値があるかもしれません:)
2017年4月20日に書かれています
Gynvael Coldwindのこの素晴らしいライブストリームの影響を受け、そこで彼はファジングとは何かについて語り、基本的なファッツァーをゼロから構築します!
そもそもファザーとは何ですか?そして、なぜ私たちはそれを使用するのですか?
入力データを取得するライブラリ/プログラムがあると考えてください。入力は、何らかの方法で構成されている場合があります(たとえば、PDF、またはPNG、またはXMLなど。ただし、「標準」形式である必要はありません)。セキュリティの観点から見ると、入力とプロセス /ライブラリ /プログラムの間にセキュリティ境界がある場合は興味深いものであり、その境界を超えて意図しない動作を引き起こす「特別な入力」を渡すことができます。ファザーはこれを行うそのような方法の1つです。これは、通常の実行(安全に処理されたエラーを含む)またはクラッシュのいずれかにつながるために、入力内の物事を「それを破壊する可能性がある可能性がある)で「変異」することによって行われます。これは、エッジケースのロジックがうまく処理されていないために発生する可能性があります。
クラッシュは、エラー条件の最も簡単な方法です。他にもいるかもしれません。たとえば、ASAN(住所消毒剤)などを使用すると、より多くのものを検出することにつながる可能性があります。これはセキュリティの問題かもしれません。たとえば、バッファーの単一のバイトオーバーフローはそれ自体でクラッシュを引き起こすことはない場合がありますが、ASANを使用することにより、ファッツァーでもこれをキャッチできる可能性があります。
ファザーのもう1つの可能な用途は、1つのプログラムをファジングすることによって生成された入力を別のライブラリ/プログラムで使用し、違いがあるかどうかを確認できることです。たとえば、いくつかの高精度の数学ライブラリエラーがこのように認められました。ただし、これは通常、セキュリティの問題につながるわけではないため、これに集中することはありません。
ファザーはどのように機能しますか?
ファザーは基本的に、アプリケーションの状態空間を探索して、クラッシュ /セキュリティの状態を「ランダムに」検索しようとする変異体消去繰り返しループです。それはエクスプロイト、ただの脆弱性を見つけません。ファッツァーの主要な部分は、ミューテーター自体です。これについては後で詳しく説明します。
ファザーからの出力?
ファッツァーでは、デバッガーが(時には)アプリケーションに添付され、クラッシュから何らかのレポートを取得し、セキュリティのvuln vsが良性の(しかしおそらく重要な)クラッシュとして後で分析できるようにします。
最初にファズするのに最適なプログラムの領域を決定する方法は?
ファジングするときは、通常、プログラムの単一または小さなセットに集中したいと考えています。これは通常、主に実行される実行量を減らすために行われます。通常、解析と処理のみに集中します。繰り返しますが、セキュリティの境界は、どの部分が私たちにとって重要であるかを決定する上で非常に重要です。
ファザーの種類?
ファザーに与えられた入力サンプルはコーパスと呼ばれます。オールドスクールファッツァー(別名「ブラインド」/「ダム」ファッツァー)では、大きなコーパスに必要がありました。新しいもの(別名「遺伝的」ファッツァ、たとえばAFL)は、必ずしもそのような大きなコーパスを必要としません。
ファザーはどのように役立ちますか?
ファッザーは、主に「低い果物」に役立ちます。複雑なロジックバグは見つかりませんが、バグを簡単に見つけることができます(実際には手動分析中に見逃しやすいこともあります)。このメモ全体で入力を言うかもしれませんが、通常は入力ファイルを参照するかもしれませんが、それだけではありません。ファザーは、Stdinまたは入力ファイルまたはネットワークソケットなどの入力を処理できます。しかし、あまり一般性を失うことなく、今のところ単なるファイルと考えることができます。
(基本的な)ファザーを書く方法は?
繰り返しになりますが、それはただ変異体の繰り返しループである必要があります。ターゲットを頻繁に呼び出すことができる必要があります( subprocess.Popen )。また、プログラム(例:ファイル)に入力を渡し、クラッシュ( SIGSEGVなどがキャッチできる例外を引き起こす)を検出できる必要があります。これで、入力ファイルのミューテーターを記述し、変異したファイルでターゲットを呼び出し続ける必要があります。
ミューテーター?何?!?
複数の可能な突然変異体が存在する可能性があります。簡単(つまり、実装が簡単な)は、ビットを変異させたり、バイトを変異させたり、「魔法」値に変異させることです。クラッシュの可能性を高めるために、1ビットまたは何かを変更する代わりに、複数を変更することができます(パラメーター化された割合の一部がありますか?)。また、(ランダム変異の代わりに)、バイト/単語/dwords/などをいくつかの「魔法」値に変更することもできます。魔法の値は0 、 0xff 、 0xffff 、 0xffffffff (32ビットINT_MIN )、 0x7fffffff (32ビットINT_MAX )などです。基本的に、セキュリティ問題0x80000000引き起こすのに一般的なものを選択します(いくつかのエッジケースをトリガーする可能性があるため)。プログラムに関する詳細情報を知っていれば、よりスマートな突然変異体を書くことができます(たとえば、文字列ベースの整数については、整数文字列を"65536"または-1などに変更するものを書くことができます)。チャンクベースの突然変異体は、ピースを動かしている可能性があります(基本的に、入力の再編成)。添加剤/アプリデンディングミューテーターも機能します(たとえば、バッファーへのより大きな入力を引き起こします)。トランスターも機能する可能性があります(たとえば、EOFがうまく処理されない場合があります)。基本的に、物事を操作する創造的な方法をたくさん試してみてください。プログラムに関する経験(および一般的な搾取)があればあるほど、より有用な突然変異体が可能になる可能性があります。
しかし、この「遺伝的」ファジングは何ですか?
それはおそらく後の議論です。ただし、いくつかの最新の(オープンソース)ファザーへのいくつかのリンクは、AFLとホングファズです。
2017年4月7日に書かれています
Picoctf 2017の素晴らしいチャレンジからの影響(コンテストがまだ進行中であるため、Challenge Wewheldの名前)
警告:このメモは、一部の読者にとっては単純/明白に思えるかもしれませんが、最近までレイヤー化が私には明確ではなかったので、言う必要があります。
もちろん、プログラミングするとき、私たち全員が抽象化を使用します。クラスとオブジェクト、または機能、またはメタ融合、ポリ型、またはモナド、またはファンクター、またはすべてのジャズであるかどうか。しかし、搾取中に本当にそのようなことをすることができますか?明らかに、前述の抽象化を実装する際に行われる間違いを悪用することができますが、ここでは、私は何か違うことについて話しています。
複数のCTFを超えて、以前にエクスプロイトを書いたときはいつでも、それはシェルをドロップするアドホックエクスプロイトスクリプトでした。私は驚くべきpwntoolsをフレームワークとして使用しています(サービスに接続し、物事や王朝などを変換するため)が、それはそれについてです。それぞれのエクスプロイトは、任意のコード実行の目標に向けて取り組むためのアドホックな方法である傾向がありました。ただし、この現在の課題は、「高度な」フォーマット文字列の悪用に関する以前のメモについて考えて、一貫した方法でエクスプロイトを重ね、異なる抽象化層を移動して最終的に必要な目標に到達できることを実感しました。
一例として、脆弱性をロジックエラーにすることを考えてみましょう。これにより、バッファの後の小さな範囲のどこかで、4バイトの読み取り/書き込みを行うことができます。私たちは、これを完全に悪用して、コードの実行、そして最後にフラグを獲得したいと考えています。
このシナリオでは、この抽象化はshort-distance-write-anything原始であると考えます。これ自体で、明らかに私たちは多くのことをすることができません。それにもかかわらず、私は小さなPython関数vuln(offset, val)を作ります。ただし、バッファの直後に有用なデータ/メタデータがあるかもしれwrite-anything-anywhereんread-anywhereつまり、以前に定義されたvuln()関数を呼び出す短いPython関数を書きます。これらのget_mem(addr)およびset_mem(addr, val)関数は、 vuln()関数を使用してポインターを上書きするだけで(この現在の例で)単純に作成されます。
これで、これらのget_mem()とset_mem()抽象化を行った後、基本的にget_mem()から2つのアドレスを漏らし、libcデータベースと比較することにより、反Aslr抽象化を作成します(@niklasbはデータベースを作成してくれてありがとう)。これらのオフセットにより、 libc_base確実に提供します。これにより、GOTの機能をLIBCの他の機能に置き換えることができます。
これにより、本質的にEIPをコントロールできました(必要なときに正確に機能の1つを「トリガー」できる瞬間)。さて、残っているのは、適切なパラメーターでトリガーを呼び出すことです。そこで、パラメーターを個別の抽象化として設定してから、 trigger()を呼び出し、システムにシェルアクセスがあります。
TL; DR:小さな搾取プリミティブ(パワーがあまりない)を構築でき、それらを組み合わせてより強力なプリミティブの階層を構築することで、完全な実行を得ることができます。
2017年4月6日に書かれています
Gynvael ColdWindによるこの素晴らしいライブストリームの影響を受け、そこで彼はフォーマット文字列の搾取について話します
単純なフォーマット文字列エクスプロイト:
%pを使用して、スタックにあるものを確認できます。フォーマット文字列自体がスタック上にある場合、アドレス(たとえばfoo )をスタックに配置してから、位置指定n$を使用してそれを求めることができます(たとえば、 AAAA %7$p 、7がスタックの位置である場合、 AAAA 0x41414141を返す可能性があります)。 We can then use this to build a read-where primitive, using the %s format specifier instead (for example, AAAA %7$s would return the value at the address 0x41414141, continuing the previous example). We can also use the %n format specifier to make it into a write-what-where primitive. Usually instead, we use %hhn (a glibc extension, iirc), which lets us write one byte at a time.
We use the above primitives to initially beat ASLR (if any) and then overwrite an entry in the GOT (say exit() or fflush() or ...) to then raise it to an arbitrary-eip-control primitive, which basically gives us arbitrary-code-execution .
Possible difficulties (that make it "advanced" exploitation):
If we have partial ASLR , then we can still use format strings and beat it, but this becomes much harder if we only have one-shot exploit (ie, our exploit needs to run instantaneously, and the addresses are randomized on each run, say). The way we would beat this is to use addresses that are already in the memory, and overwrite them partially (since ASLR affects only higher order bits). This way, we can gain reliability during execution.
If we have a read only .GOT section, then the "standard" attack of overwriting the GOT will not work. In this case, we look for alternative areas that can be overwritten (preferably function pointers). Some such areas are: __malloc_hook (see man page for the same), stdin 's vtable pointer to write or flush , etc. In such a scenario, having access to the libc sources is extremely useful. As for overwriting the __malloc_hook , it works even if the application doesn't call malloc , since it is calling printf (or similar), and internally, if we pass a width specifier greater than 64k (say %70000c ), then it will call malloc, and thus whatever address was specified at the global variable __malloc_hook .
If we have our format string buffer not on the stack , then we can still gain a write-what-where primitive, though it is a little more complex. First off, we need to stop using the position specifiers n$ , since if this is used, then printf internally copies the stack (which we will be modifying as we go along). Now, we find two pointers that point ahead into the stack itself, and use those to overwrite the lower order bytes of two further ahead pointing pointers on the stack, so that they now point to x+0 and x+2 where x is some location further ahead on the stack. Using these two overwrites, we are able to completely control the 4 bytes at x , and this becomes our where in the primitive. Now we just have to ignore more positions on the format string until we come to this point, and we have a write-what-where primitive.
Written on 1st April 2017
Influenced by this amazing live stream by Gynvael Coldwind, where he explains about race conditions
If a memory region (or file or any other resource) is accessed twice with the assumption that it would remain same, but due to switching of threads, we are able to change the value, we have a race condition.
Most common kind is a TOCTTOU (Time-of-check to Time-of-use), where a variable (or file or any other resource) is first checked for some value, and if a certain condition for it passes, then it is used. In this case, we can attack it by continuously "spamming" this check in one thread, and in another thread, continuously "flipping" it so that due to randomness, we might be able to get a flip in the middle of the "window-of-opportunity" which is the (short) timeframe between the check and the use.
Usually the window-of-opportunity might be very small. We can use multiple tricks in order to increase this window of opportunity by a factor of 3x or even up to ~100x. We do this by controlling how the value is being cached, or paged. If a value (let's say a long int ) is not aligned to a cache line, then 2 cache lines might need to be accessed and this causes a delay for the same instruction to execute. Alternatively, breaking alignment on a page, (ie, placing it across a page boundary) can cause a much larger time to access. This might give us higher chance of the race condition being triggered.
Smarter ways exist to improve this race condition situation (such as clearing TLB etc, but these might not even be necessary sometimes).
Race conditions can be used, in (possibly) their extreme case, to get ring0 code execution (which is "higher than root", since it is kernel mode execution).
It is possible to find race conditions "automatically" by building tools/plugins on top of architecture emulators. For further details, http://vexillium.org/pub/005.html
Written on 31st Mar 2017
Influenced by this amazing live stream by Gynvael Coldwind, where he is experimenting on the heap
Use-after-free:
Let us say we have a bunch of pointers to a place in heap, and it is freed without making sure that all of those pointers are updated. This would leave a few dangling pointers into free'd space. This is exploitable by usually making another allocation of different type into the same region, such that you control different areas, and then you can abuse this to gain (possibly) arbitrary code execution.
Double-free:
Free up a memory region, and the free it again. If you can do this, you can take control by controlling the internal structures used by malloc. This can get complicated, compared to use-after-free, so preferably use that one if possible.
Classic buffer overflow on the heap (heap-overflow):
If you can write beyond the allocated memory, then you can start to write into the malloc's internal structures of the next malloc'd block, and by controlling what internal values get overwritten, you can usually gain a read-what-where primitive, that can usually be abused to gain higher levels of access (usually arbitrary code execution, via the GOT PLT , or __fini_array__ or similar).