初心者用Writeup置き場(仮)ε≡≡ヘ( ´Д`)ノ

Ciruelaによるメモの仮置き場です!

セキュリティキャンプ2018応募課題晒し

まえがき

セキュリティキャンプ全国2018の選考を通過したので,応募課題を晒そうと思います.一応注記しておきますが,私はプログラミングは大学に入ってから学び始め,低レベル開発経験などもゼロ.セキュリティ関係を学び始めたのは去年12月で,プロでもなんでもありません.そんなことはどうでもよく,今回は”プロじゃなくても受かる可能性はある”とのメッセージを込めて,恥ずかしながら応募課題を晒すことにしました.
応募したきっかけは,今年度が始まり,B2ながら大学の研究室にお邪魔させていただいたときの先輩方(プロ)のお言葉です.僕も(おそらく例に違わず)応募課題の問題の難易度や,晒された応募課題の質と量に圧倒され,当初は来年受けるつもりでいました.しかし,先輩方は「まあ受けてみなよ」「受かるでしょ」と背中を押してくださいました.応募課題に受かることはスタートラインでしかないのですが,感謝してもしきれません!m(_ _)m

選択したトラックについて

トラックA(脆弱性マルウェア解析トラック)を選択しました.なぜなら僕にとってはこの応募課題が一番易しい(あくまで比較的に)と思ったからです.具体的には,他のトラックの課題は「~の課題を述べよ」のように抽象的であり,ある程度経験がないと論述は厳しいと思った一方,トラックAの問題は割りと具体的で,経験は要求されず,書きやすく感じました.

課題晒しにあたって

原文はやや個人情報を含んでいるので,そのような部分は[---REDUCTED---]と書いています.また,かなり支離滅裂な部分があると思います.ご容赦ください!

まずは問題を

#脆弱性・マルウェア解析トラック
設問ひとつあたり4,096文字以内で答えてください。
また正解がある設問については、"正解しているかどうか"より
"正解にたどり着くまでのプロセスや熱意"を重要視しています。
答えにたどり着くまでの試行錯誤や自分なりの工夫等を書いて、精一杯アピールしてください。

##問1
あなたが今まで作ってきたソフトウェアにはどのようなものがありますか?
また、それらはどんな言語やライブラリを使って作ったのか、
どこにこだわって作ったのか、たくさん自慢してください。

##問2
今までに解析したことのあるソフトウェアやハードウェアにはどのようなものがありますか?また、その解析目的や解析方法、工夫した点があればそれらも教えてください。

##問3
Twitterアカウント、Github、ブログ、公開している資料等がありましたら、URL等を記載してください。

##問4
あなたが今年のセキュリティ・キャンプで受講したいと思っている講義は何ですか?(複数可)
またそれらを受講したい理由を教えてください。

##問5
自分が最も技術的に興味を持った脆弱性をひとつ挙げ、技術的詳細(脆弱性の原因、攻略方法、対策方法など)
について分かったことや思ったこと、調査の過程で工夫したこと等を報告してください。
その際、書籍やウェブサイトを調べて分かったことはその情報源を明記し、
自分が独自に気付いたことや思ったことはそれと分かる形で報告してください。
また脆弱性の攻略方法を試す際は、他者に迷惑を掛けないように万全の措置をとってください。

##問6
以下にDebian 8.10(amd64)上で動作するプログラムchal00のmain関数の逆アセンブル結果があります
("objdump -d chal00"の出力結果のうち、main関数の箇所を抜粋しました)。
このプログラムは、コマンドライン引数としてある特定の文字列を指定されたときのみ実行結果が0となり、
それ以外の場合は実行結果が1となります。
この実行結果が0となる特定の文字列を探し、その文字列を得るまでに考えたことや試したこと、
使ったツール、抱いた感想等について詳細に報告してください。
```
00000000004003c0 <main>:
  4003c0:       48 b8 0f 0e 0d 0b 00    movabs $0xc0601000b0d0e0f,%rax
  4003c7:       01 06 0c
  4003ca:       83 ff 02                cmp    $0x2,%edi
  4003cd:       48 89 44 24 f0          mov    %rax,-0x10(%rsp)
  4003d2:       48 b8 04 05 08 0a 02    movabs $0x70903020a080504,%rax
  4003d9:       03 09 07
  4003dc:       48 89 44 24 f8          mov    %rax,-0x8(%rsp)
  4003e1:       b8 01 00 00 00          mov    $0x1,%eax
  4003e6:       75 59                   jne    400441 <main+0x81>
  4003e8:       48 8b 56 08             mov    0x8(%rsi),%rdx
  4003ec:       31 c0                   xor    %eax,%eax
  4003ee:       89 c1                   mov    %eax,%ecx
  4003f0:       48 ff c0                inc    %rax
  4003f3:       80 7c 02 ff 00          cmpb   $0x0,-0x1(%rdx,%rax,1)
  4003f8:       75 f4                   jne    4003ee <main+0x2e>
  4003fa:       83 f9 08                cmp    $0x8,%ecx
  4003fd:       b8 01 00 00 00          mov    $0x1,%eax
  400402:       75 3d                   jne    400441 <main+0x81>
  400404:       48 8b 32                mov    (%rdx),%rsi
  400407:       31 c0                   xor    %eax,%eax
  400409:       30 c9                   xor    %cl,%cl
  40040b:       48 89 f2                mov    %rsi,%rdx
  40040e:       48 d3 ea                shr    %cl,%rdx
  400411:       83 e2 0f                and    $0xf,%edx
  400414:       0f b6 54 14 f0          movzbl -0x10(%rsp,%rdx,1),%edx
  400419:       48 d3 e2                shl    %cl,%rdx
  40041c:       83 c1 04                add    $0x4,%ecx
  40041f:       48 09 d0                or     %rdx,%rax
  400422:       83 f9 40                cmp    $0x40,%ecx
  400425:       75 e4                   jne    40040b <main+0x4b>
  400427:       48 33 05 92 ff ff ff    xor    -0x6e(%rip),%rax        # 4003c0 <main>
  40042e:       48 ba 85 03 0e 67 b3    movabs $0x600967b3670e0385,%rdx
  400435:       67 09 60
  400438:       48 39 d0                cmp    %rdx,%rax
  40043b:       0f 95 c0                setne  %al
  40043e:       0f b6 c0                movzbl %al,%eax
  400441:       c3                      retq
```

##問7
chal01.zipには、USBメモリのディスクイメージusb_disk_modified.img が入っています。
そのイメージファイルをマウントしようとしましたが、エラーが出てマウントできませんでした。
どうやらどこかが壊れているようです。
ファイルシステムの構造を調べ、壊れていると思った全ての箇所を修正し、
マウントツール(*1)でマウントできるようにした後、
ファイルシステム上に存在するPDFファイルを救い出してください。
* (*1)マウントツールの例
    * mount (for linux)
    * hdiutil (for macos)
    * FTK Imager(for Windows)

また、以下の設問の答えとその解き方を、ファイルシステムの調査結果を用いて説明してください。
答えのみの場合は正解とはみなしません。修正箇所が複数ある場合は、全て列挙してください。
1. このイメージの元のファイルシステムの名称は?
2. どのオフセットを何のバイト列に置き換えて直したのか?壊れていると判断した理由は?
3. そのオフセットの位置は何という名称の構造体の何というメンバがあるはずなのか?
4. PDFファイルのハッシュ値をすべてsha256で答えよ。

回答

グレー背景となっている部分が回答です.

  • 問1
①連立方程式計算機
大学ではじめにC言語を習ったので、それを実際に使ってプログラミングに慣れたいと思ったのと、線形代数学の授業や課題で連立方程式を扱うことが多く、私自身ケアレスミスがかなり多いので、自分で解いた問題の連立方程式を使う部分について、きちんと正解しているかを確認するために作った。
基本構造としては、ユーザがテキストファイルに解きたい連立方程式の各未知数の係数を書き込み、そのファイルから係数部分を読み取って、線形代数の講義で習った通り基本変形を行うことでプログラムが解き、たとえば
{A=3
{B=1
というふうにそれぞれの解をコマンドプロンプト上で表示させる。係数入力用テキストファイルには、もしA+2B=3、(-1/8)A-4B+C=0という3元一次連立方程式を解かせたいとすると、以下のように行列に似た形式で入力する。
1 2 0 3
-1/8 -4 1 0
ただし、線形代数の教科書(中高でもそうである)では整数でない解についてはもちろん分数で記載していたので、解は整数または分数で出力させるようにしてある。そのために、少々複雑になってしまったが、プログラムでは入力した係数のすべてを分数に直してから基本変形を行うようにした。
また、式の個数に対し未知数の個数が多ければ、どれかを適当な実数とおかなければならないので、それも実装した。係数行列の行、列数をそれぞれL,R(L<R)とすると、列交換を行わない限り第L+1,L+2,…,Rの未知数を任意の実数とおく。
また、答えが見やすいように表示方法にも次のような工夫をした。
・先頭の係数については、+符号をつけない。
・分数は括弧()でくくり、符号+-は括弧の前に置く。
・未知数を定数と置いた時には、最後に「(ただし、a,b,…は任意の定数)」と表示する。
係数入力ファイルにも、入力失敗を検知するためのしくみを設けた。
・未知数の個数がマクロ定義による制限を超えている。
・数字と、マイナスやスラッシュ以外の文字が入力されている。
・正しく行列形式になっていない。たとえば1行目には9個係数が書かれているのに2行目には8個しか書かれていないなど。
・小数点、スラッシュが1係数あたり2個以上入力されている。
・マイナスが係数の先頭にない。
・EOFの行を除き、まったく入力されていない行がある。
・分数の分母、分子として小数が入力されている。
最近は放置気味であったが、応募を機にまだ腕はないながらもGitHubアカウントを作ったので、今後は複数の連立方程式を一気に解いてくれるようにしたり、計算途中で分母分子が大きくなったときにオーバーフローが起きてまったく違う答えを出力しないように分数から浮動小数点数形式に直して計算したり、解けない問題をもっと探して修正をしたり、入力制限やエラー検出を増やしたりして、クオリティを上げて、後輩に使わせてあげたりしたい。

②自分で脆弱性を持つWebサイトを作り、攻撃と防御を経験する。
昨年12月から、CTFや様々な本、[---REDUCTED---]で攻撃手法やアセンブリ言語を勉強していたものの、セキュリティに関しては初心者である今の状態から、あとたった数年でセキュリティのプロとして新しい技術を瞬時に吸収できるようになったり、ディジタルフォレンジックや脆弱性の検証などができるまでに成長するまでの道筋がどうしてもつかめなかったので、今年4月にセキュリティに詳しい先輩方がいらっしゃるという研究室に訪問させていただき、相談をしたところ、「攻撃の練習をCTFでするだけではなく、実際に脆弱性のあるWebサービスを作ってみて、どうやってWebページが作られているのかだとか、攻撃を防ぐ機構を実際に体験して知ったほうがいいよ」との助言をいただいたので、localhost上で練習用サイトを構築しはじめた。具体的には、色んな脆弱性を持つ、例えばデータベースに登録された名前を入力して、パスワードを確認するなどの簡素なWebページを作って、実際にSQLインジェクションなどが成功するのかを検証している。それをやってみようと思ったときに、どうやってlocalhost上でサーバを立てるのか、どこにhtmlやphpのファイルを置くのかといった初歩的なこともわからず、そしてphpでどのようにMySQLなどのデータベース管理システムに接続するのかも、実際にCTFでSQLインジェクションをやったことがあるにもかかわらず知らなかったのがとても衝撃的だった。少し調べると、xamppという、ApacheやMySQLなどが含まれるlocal環境でのWebサービス開発用ソフトウェアがあったので、それを利用した。今のところ、SQLに関連した脆弱性では「' or '1'='1」などの入力を利用したパスワード認証の突破や、Union-based-SQL-injectionができるページを作った。これからは、htmlspecialchars()関数などによるエスケープを施してみたり、バインド機構を実装して、その防御効果を検証していく。また、XSSについても、クッキーの抜き出しや、UTF-7と認識させてエスケープ処理を回避するような基本的なXSSを試してみたい。また、Pythonでソケットプログラミングを習い始めたので、Time-based-blind-SQL-injectionなどのブラインドSQLインジェクションを試しながら、ソケットプログラミングの練習をしようと思っている。

書いてて苦しかったです.他の応募課題ではみんな自作OSとかコンパイラ作成とか高度なことをやっているので,経験ではまったく及ばないからです.来年受けようと思ったのも,これが理由です.アレを見た方はご存知かと思いますが,僕はプログラミング言語のベストプラクティスさえできていません.ただ,雑魚なりに色々書きました.しかしこんな僕でも受かったということは,他の応募課題がすごすぎて打ちひしがれている方々にも十分チャンスがあるということなのだ!!!!!!!!

  • 問2
これまでに何かを自主的に解析したことはなかったが、2月にNeverLAN CTFというCTFで、hashcatというオープンソースソフトウェアを用いて、WPA2-PSKの4wayハンドシェイクのパケットから辞書攻撃によってパスワードクラックをするという問題があったことを思い出したので、家で10年ほど前から使用しているWi-Fiアクセスポイントの暗号化方式は推奨されるものとなっているか、私でもできるほど簡単にクラックできてしまわないかをこの機会に調べてみることにした。
通信の暗号化方式は、アクセスポイント(親機)に書いてあり、128bitのWEPであった。
WEP暗号化方式は脆弱であり、104bitのWEP鍵については2008年に神戸大と広島大が鍵の解読を瞬時に行えることを発表しており、WEPからWPA/WPA2へと暗号化方式を移行するように推奨している。
今回は、http://ra66itblog.hateblo.jp/entry/2016/05/29/203015を参考に、Kali-linux 2018.1を利用して自宅Wi-Fiアクセスポイントのパスワードの解析を試みた。
まず、iwconfigコマンドを打ち、動作している無線LANインターフェイスの情報を確認した。しかし、次のように表示されてしまった。
eth0     no wireless extensions.
lo       no wireless extensions.
この表示について調べてみたところ、原因は、私のPCのKali LinuxはVirtualbox仮想化ソフトを利用して、ホストOSのWindows 10 proにゲストOSとしてインストールされているが、ネットワークアダプタの設定がブリッジ接続であるために、無線LANアダプタが認識されていないからであるということがわかった。
そこで、この表示について検索してみると、compat-wireless-2010-06-26というドライバを用いるとwlan0インターフェイスを作成できることが分かった。それをダウンロードしたのち、当該ディレクトリ内でmake loadコマンドを打つと、無線LANインターフェイスとしてwlan0が認識された。
wlan0が認識されたところで、次にairmon-ng start wlan0と打ち、解析に用いるパケットを収集できるようにwlan0をモニタモードに変更した。iwconfigコマンドで、wlan0についてMode:Monitorとなっており、モニタモードになっていることが確認できた。また、無線LANインタフェース名がwlan0monと変化した。
次に、airodump-ng wlan0monコマンドによって、近くにあるアクセスポイントを探し、それぞれのBSSIDや暗号化方式、通信の強度などを表示させようとした。ここで、本来なら自宅アクセスポイントを含む最低1つのアクセスポイントの情報が表示されるはずであったが、何も表示されなかった。
同じ症状にあっている人はいないかgoogle検索をすると、動画(https://www.youtube.com/watch?v=jpzZe7PO8TY)を見つけた。そのコメント欄にはいくつかの対処法が書かれていた。
(1)VirtualBoxの機能拡張パッケージをダウンロードし、USB規格の設定をUSB2.0またはUSB3.0に変更する。
(2)Kali Linuxにデフォルトで搭載されているwifiteという別のアプリでWEP解析を行う。
しかしながら、(1)は効果がなく、(2)ではairodump-ngと同様に、アクセスポイントが発見されなかったため解析することができなかった。
もっと調べると、compat-wireless-2010-06-26を使ってwlan0インターフェイスを認識させて同じ手順を踏み、airodump-ngコマンドを実行して同じ問題に当たったという動画が見つかり、その次の動画(https://www.youtube.com/watch?v=ZdKUd1XaMYg)では、次の3つの対処法が紹介されていた。
(1)VirtualBoxなどの仮想環境上ではなく、デュアルブート環境でKali Linuxを動かす。
(2)外部から無線LANアダプタを取り付ける。
(3)マシンのアップデートを行う。
というように、今までのcompat-wirelessを利用した方法ではWEP解析はできないことがわかった。
この問題にとりかかったときには課題提出締め切りまであと3日というところだったので、これまでに書いたこと程度のことしか行えなかったが、今度はUSBの無線LANアダプタを使用することで、多くのWEP解析方法紹介サイトと同様な環境下で自宅のアクセスポイントのWEP解析ができるのかどうかを試そうと思っている。また、aircrack-ngでは飛び交っているパケットを傍受してWEP解析を行うことから、Wiresharkで得たパケットのみで解析する方法もあると思うので、もう少し調べたい。

これが一番苦し紛れです.この問題の回答を書き始めたのが締め切り3日前だったのはしょうがないとして,問題がプロ諸氏にとってはお茶の子さいさいなもの,それでいてメインの部分の前でつまづき,終了.これはいかに熱意重視とはいえ,落選決定だと思いました.恥ずかしい……!!!次の問題に移りましょうか()

  • 問3
[Twitterアカウント]
名前:Ciruela
ユーザー名:@__Xcyba17her_

[GitHubアカウント]
https://github.com/Ciruela-Xcyba17her

[はてなブログ]
http://verliezer93764.hatenablog.jp/

Githubアカウントを急遽作ったんですが,今まで述べましたように,無知が露呈しています.しかし,他のアカウントも含め,自分がどういう人物か知ってもらうため,正直に載せました.この行為が吉と出たか大凶と出たか,僕にはわかりません.

  • 問4
【A1~3】「インシデントレスポンスで攻撃者を追いかけろ」
今回セキュリティキャンプに応募した理由の一つとして、セキュリティ技術者が現場でどのような技術を用いて、どのようにして人々の情報を守り、犯人の追跡をするのかを知ることによって、「初心者である今からセキュリティ技術者として人々を守る力を持つようになるまでの道筋・勉強方法がつかめない」という最大の悩みを解決し、これからの大学・大学院生活における学び方に生かしていきたいというものがある。この講義では、マルウェア解析、デジタルフォレンジック、ログ解析などを仮想的であってもインシデントが発生した環境で手を動かして経験することができる絶好の機会なので、セキュリティ技術者となるためには自分は何をどのように学ぶべきなのか、今後の学び方を身に着けたい。

【A4】「IN-DEPTH STATIC MALWARE ANALYSIS」
今年2月からアセンブリ言語を自習しはじめ、CTFでも、何回か変数と数値を比較しているだけであったり、コードがせいぜい30行程度であるようなプログラムに関する易しめの問題をだんだん解けるようになってきた。しかしながら、メガバイト単位の大きいプログラムであったり、数~数十キロバイトのサイズでも、何も出力せず、ウィンドウも表示しないようなプログラムには手が出ない状況である。この講義では、攻撃者が作るプログラムの静的解析を経験することによって、長いマルウェアコードの中からどのようにして素早く核となるコードを見つけ、その挙動を見抜くか、そしてもともと攻撃者の使うコードにはどのような関数が使われているのかを学び、その後CTFなどで解析の練習を重ね、静的解析の力をつけていくきっかけにしたい。

【E5】「Linuxカーネル脆弱性入門」
問5では、EternalBlueについて調べたが、途中で「非ページプールでのバッファオーバーフローを起こす」ことについて、まず非ページプールとはどこのことなのか、またそれはどのように使われるのかについてかなりの時間を費やし、最後にはなんとか脆弱性攻撃のおおまかな原因や方法はつかめたが、実際の攻撃のイメージがわかなかった。でも、攻撃者たちはそのようなコンピュータの仕組みについてはもちろん熟知しており、その知識をもとにマルウェアを作るはずである。よって、守る側の自分もLinuxカーネルなどについての知識についてはつけておかなければならないと強く感じたので、低いレイヤを対象とした脆弱性についてもすぐに理解できるように、この講義を通してカーネルの基本や学び方を身に着けたいと思っている。

【C6】「パターン認識とセキュリティ」
去年に人工知能分野が最近急速な成長を遂げつつあることを知り、それを何らかの形でセキュリティ分野に応用すれば、増え続けるサイバー攻撃に対する防御側の不足を補えるのでは?という思いはあった。日経bpの「すべてわかるセキュリティ大全2018」という本では、マカフィー社のMcafee Endpoint Threat Defenceやシマンテック社のSymantec Endpoint Protectionなどのウイルス対策ソフトでは機械学習アルゴリズムを導入していることが紹介されており、[---REDUCTED---]、機械学習には非常に興味を持っている。この応募課題が終わり次第機械学習に入門していこうと思っているので、この講義に参加することで、機械学習をどのようにしてセキュリティ技術に応用できるのかを知りたい。

【A7】「本当にわかるSpectreとMeltdown」
私は、CPUはただ演算命令に従ってALUを動かし、演算をこなすだけのものと思っていたので、セキュリティには無関係であると思っており、CPUに脆弱性が見つかったときには、どうして命令をこなすだけのCPUが脆弱性を持つのかと思い、非常に驚いた。今回、この講義を利用して、今まで学んでこなかったCPUにはただ演算をこなすだけではなく、どんな役割や能力を持っているのかを知り、SpectreとMeltdownはどういうところにつけこんで攻撃をするのか、どんな被害が想定されるのかを確実に理解して、コンピュータの設計に関する知識として備えたいと思っている。

ただ思っていることを書きました.特に,僕自身セキュキャンは今後何をどう学んでいくかの参考にしたかったので,その講義をセキュキャン修了後にどのように生かしていきたいのかを書きました.

  • 問5
調べた脆弱性:EternalBlue
脆弱性を調べる上で、はじめはそれを概観しながらわからない単語を調べることで確実に理解を進めていき、次々と深くまで、単語の意味を調べつつ、進めていくことにした。
EternalBlueのCVE識別番号は、CVE-2017-0143である。まずは、EternalBlueは具体的にどのような脆弱性なのかを概観した。[1]のCurrent Descriptionという見出しによると、この脆弱性は、「遠隔地にいる攻撃者が、作成した(特殊な)パケットを送ることで、WindowsのSMBv1(SMB version 1.0)サーバ上で任意のコードを実行(RCE)できる」というものである。[2]によると、SMBとは、Server Message Blockの略称で、Windows OSで構成されたLAN上でファイルを共有するサービスの基礎となっているプロトコルである。OSI参照モデルではL6,7に位置しており、クライアント-サーバ型の構造をしている。
次に、そのSMBv1サーバ上でどのようにして任意コードを実行させるのか、より詳しい記述を探した。その結果、[3]が見つかった。かなり詳しく書かれていたので、このページの理解を目指すことにした。[3]によると、EternalBlueはSMBv1の3つのバグを使う。

(1)"Wrong Casting Bug"
SMBプロトコル上で、OS2 FEAをNT FEAに変換する過程にバグがある。
Os2FeaList構造体にSizeOfListInBytesというメンバがあり、SrvOs2FeaListSizeToNTという関数は何バイトのOS2 FEAをNT FEAに変換するかを計算しこの変数にその結果を格納する。その計算の過程は本来DWORD型の範囲で行われるべきであるが、上位2バイトを考えずにWORD型の範囲で行われてしまうことがある。一方、返り値としてもNT FEAのサイズは計算されるが、それは正しく行われる。よって、[3]にあげられている例のように、変換前にSizeOfListInBytesの値が0x10000であった場合に、変換すべきOS2FEAのサイズが0x1ff5d(バグがなければ0xff5d)となっており、これは変換後の本来のNTFEAのサイズよりも大きくなる。これはout-of-bounds write(領域外メモリへの書き込み)を発生させる。

(2)"Wrong Parsing Function Bug"
SMBプロトコルを通じてファイルを送信する際、SMB_COM_TRANSACTION2とSMB_COM_NT_TRANSACTという、データに関するサブコマンド群を使う。単一のパケットとして送信するにはデータの送信量が膨大すぎるときは、それぞれのサブコマンドの末尾に_SECONDARYを付けたSECONDARYサブコマンドを使用する。ここで、SMB_COM_TRANSACTION2とSMB_COM_NT_TRANSACT、そしてそれぞれのSECONDARYサブコマンドではどちらも送信できるデータ量の上限をヘッダに定義するが、前者ではWORD型とする一方、後者ではDWORD型で設定することとなっており、差異がある。また、最初にSMB_COM_NT_TRANSACTコマンド(データ量の最大値はDWORD型で設定)を打ち、その後SMB_COM_TRANSACTION2_SECONDARYコマンド(データ量の最大値はWORD型)で送信することも可能である。これにより、送られてきたOS2 FEAのサイズをWORD型で計算させて領域外メモリへの書き込みを行わせる(1)のバグを起こすことができる。

(3)"Non-paged Pool Allocation Bug"
SMBクライアントがサーバに対し認証され、接続およびセッションを確立するにはSMB_COM_SESSION_SETUP_ANDXリクエストを送る必要がある。SMB_COM_SESSION_SETUP_ANDXリクエストのフォーマットには2種類あり、どちらの認証でもリクエストはSMB_ParametersとSMB_Dataという2つの部分に分かれる。また、サーバ側では、それらが揃っているのかをSrvValidateSmbという関数が確認する。その中で、BlockingSessionSetupAndXという関数がSMB_Dataのデータを抽出するが、クライアントとサーバの間でリクエストフォーマットの認識の齟齬が生じることがあり、ByteCountというSMB_Dataのサイズを示すパラメータを違ったオフセットから読んでしまい、ByteCountを本来と異なる値で読んでしまう(本来より大きく計算される)ことがある。それによって、小さなパケットでも、非ページプールでの大きい領域の確保が行われることがある。

RCEを行うためには、非ページプール上で今までに述べた3つのバグを利用し、MDL Overwriteなどを行う。MDLとは、I/O命令に使用され、実メモリ上では一群のデータはバラバラに配置されることに対し、連続的にデータが配置される仮想メモリからポインタで実メモリを指すための構造である。SRVNETバッファのヘッダにはMDL構造があり、その前の領域からバッファオーバーフローを起こすことができれば、MDLを書き換えることができる。そのためには、まず複数のSRVNETバッファを生成させるカーネルグルーミングや、領域の解放を行い、バグ(3)で確保させておいた実質の空き領域とSRVNETバッファを連続させる。ただし、SRVNETバッファはSMBv2を実現するsrvnet.sysによって、SRVバッファはSMBv1を実現するsrv.sysによって生成され、カーネルグルーミングによって発生させるSRVNETバッファについては(1)と(2)のバグは起こさせない。また、HAL’s Heapという、アドレス位置が不変なカーネル構造体があり、それはWindows 8以前のバージョンで実行権限を持つ。つまり、MDL上のポインタをHAL's Heapに書き換えれば、HAL’s Heapに任意のシェルコードを書き込ませてRCEを実行することができる。そのためには、(1)と(2)のバグによって、バグ(3)により確保された空き領域上でSRVバッファによるオーバーフローを起こさせ、MDLを書き換える。

次に、私の環境でこの脆弱性のエクスプロイトが実験できるかどうか調べたところ、多くのWebページや動画がMetasploitという脆弱性のペネトレーションテストソフトを用いていた。また、EternalBlueに加え、バックドアを設置するDoublePulsarを併用したMetasploit用コードがGitHub上で公開されており(https://github.com/ElevenPaths/EternalBlue-Doublepulsar-Metasploit)、それを利用した方法もあった。今回はそれを利用してみる。方法は、まずGithubなどから入手した。Metasploitを起動した後、use xxx(xxxは攻撃用コード)として攻撃方法を設定して、setコマンドで攻撃に要する各パラメータを設定する。exploitコマンドで攻撃を開始し、テストターゲットとしてhttps://developer.microsoft.com/en-us/microsoft-edge/tools/vms/からWindows7の仮想環境"IE8 on win7"をダウンロードして使用し、攻撃はKali Linux 2018.1を使用した。また、間違いを防ぐため、攻撃の際には、このPC全体が外部アクセスポイントに接続しないようにした。また、Windows7の方ではホストオンリーアダプタの設定を行い、IPアドレスを固定した。
しかし、私の環境では攻撃はうまくいかなかった。auxiliary/scanner/smb/smb_ms17_010を用いると、脆弱性を攻撃できそうなときにそれを意味する表示がされるが、その表示もされなかった。どこかで設定が間違っているか、"IE8 on win7"がすでに対策をしていると考えられるが、今回は断念した。この脆弱性の対策としては、Microsoft社の出しているパッチを適用すればよい。EternalBlueについて調べてみて、正直にいってその原理は私にとってとても難しいものであった(カーネルに関する知識を持っていなかった)。しかしながら、実際の攻撃はMetasploitによって簡単に行えてしまう。これは、知識のない者でも簡単に攻撃を行えることを意味している。サイバー犯罪を抑えるためには、OSSも含め、攻撃能力をもつツールの公開を制限したり、ダークウェブ上での流通にも大いに警戒する必要があると思った。

参照ページ:
[1] https://nvd.nist.gov/vuln/detail/CVE-2017-0143「CVE-2017-0143 Detail」
[2] http://www.atmarkit.co.jp/ait/articles/0410/29/news103.html「第20回 ファイル共有プロトコルSMB/CIFS(その1) (1/3)」
[3] https://research.checkpoint.com/eternalblue-everything-know/#bugb「EternalBlue ? Everything There Is To Know」

SpectreとMeltdownが気になっていたが,脳内で話題だったEternalBlueについて調べました.これがまた難しかった.SMBって何?サブコマンドって??非ページプールって??と,いちいち出てくる用語がわからない状態.
出てくる単語の多くがわからなくて,ググりまくりました.しかし実際に設定等をいじることはなかったので,今でもはっきりは理解できていないと思います.しかもソースが読み慣れてない英語で,ますます理解に時間がかかりました.いちいち調べていった結果,文字数はどんどん膨れ上がり,気づいたら6000文字オーバー.削りまくった結果,特に最後のあたりは詰め込んだ感じになっています.ただ,最後まで一定の理解はできるように頑張りました
この問題の影響で,いわゆる低レイヤと呼ばれる部分に深い興味を持っています!30日でOSを作るアレ,やってみたい.

  • 問6
アドレスAより割り当てられている命令について、「Aの行の命令」と書くことにする。まずは、ジャンプ命令の場所を抜き出した。すると400e36と4003f8と400402と400425の行にジャンプ命令があり、そのうち4003e6と400402の行のものは、main関数終了に向かってジャンプを行わせるものとなっており、その前には必ずraxに1を格納するmov命令がある。一般に、関数の返り値を格納するためにはraxが使用される。それらのジャンプを行わなかった場合、40043bの行にsetneという命令があるが、これは400438の行のcmp命令でゼロフラグが立つ、つまり等しいという結果が出ればalに0を格納し、そうでなければ1を格納するものである。したがって、400438の行までプログラムが進行し、raxとrdxの値が等しいという条件を満たせばプログラムは0を返すということがわかる。
ここからは、命令を1つ1つ確認していくことにしたが、早速4003caの行のcmp命令に困惑した。ここでediの値が数値2と等しくなければ先ほど述べた通り返り値は1になってしまう。ところが、このcmp命令以前には、ediに操作をする命令が書かれていない。そこで、問題文が「プログラムが0を返すようなコマンドライン引数を見つけよ」という趣旨だったことを参考に、Ubuntu 16.04 LTS上で、コマンドライン引数を指定したときどんな逆アセンブルコードが生成されるのか調べるために、以下のサンプルCプログラム(test.c)を作った。
//プログラム先頭
#include<stdio.h>
int main(int argc,char *argv[]){
	return 0;
}
//プログラム終了
与えられた逆アセンブル結果ではediと2の比較はほぼ先頭で行われていたので、gdbを用いて逆アセンブルし、main関数の前でrdiの値を調べた。その結果、rdiの値は、設定するコマンドライン引数の個数によって変化し、常にrdiの値が(コマンドライン引数の個数)-1となった。よって、rdiの値は、実行用文字列「./test」を含んだすべてのコマンドライン引数の個数で、main関数に入る前に代入されると考えた(なお、https://simple-teq.net/assembly/assm-14.htmlにその旨が書いてあったことを後に見つけた)。
したがって、このcmp命令は指定したコマンドライン引数の個数が1個であることを確認する働きをしていると結論した。
さて、アセンブリコードに戻る。4003c0の行から4003dcの行にかけては、raxを用いてスタック構造にに順次値をコピーしている。Debianではバイトオーダとしてリトルエンディアンを採用しているので、ここでは(espの値-16)のアドレスから(espの値-1)のアドレスの16バイトにかけて0f,0e,0d,…,03,09,07という数値が代入されていったことになる。
4003eeの行から4003f8の行にかけては、4003f3 の行のcmpb命令、4003f8の行のjne命令によるループ構造になっている。
ループの中で、(rdx-1+rax*1)というアドレスの計算が行われているが、そのベースとなっているrdx、すなわち4003e8の行でrdxに代入したrsi+8というアドレスは何を意味するかが分からなかった。rsi:ソースインデックスレジスタは、一般的にはベースとなるアドレスからのオフセットを表したり、文字列操作のための文字列のコピー元アドレスを格納したりする役目を持つが、この「文字列」というものが、問題にある「コマンドライン引数に指定する文字列」にあたるのではないかと考えたので、再びUbuntu 16.04 LTSで、rsiの指すアドレスとコマンドライン引数のアドレスの関係を調べるためのサンプルプログラムを書いた。
//プログラム先頭
#include<stdio.h>
int main(int argc,char *argv[]){
	printf(“%p\n”,argv[1]);
	printf(“%p\n”,argv[2]);
	printf(“%p\n”,argv[3]);
}
//プログラム終了
gdbを用いて、set args ABCD EFGH IJKLコマンドで3つのコマンドライン引数を指定し、最初のprintfの前でプログラムを止め、x/20x $rsiコマンドによって確認した。その結果は以下の通りである。
0x7fffffffe27e: 0x41 0x42 0x43 0x44 0x00 0x45 0x46 0x47
0x7fffffffe286: 0x48 0x00 0x49 0x4a 0x4b 0x4c 0x00 0x58
0x7fffffffe28e: 0x44 0x47 0x5f 0x56
この結果から、rsiはコマンドライン引数のアドレスを指すために使われ、各コマンドライン引数の終端には、0x00(文字列終端を示す)が格納されると考えた。すなわち与えられた逆アセンブルコードの4003f3の行では、(rdx-1+rax*1)のrdxにrsi+8を代入して(rsi+7+rax*1)というアドレスの中身(1バイト)と0を比較しているが、これはコマンドライン引数について、その終端であるかどうかをraxをカウントとして用いることで1バイトずつ確認していると考えられる。また、rsi+8というオフセット付きのアドレス指定となっているのが気になったが、rsiの指すアドレス以降には、”./test”というような実行用文字列が格納されることがわかった。よって、4003eeの行から4003f8の行までのループは、後のjne命令によりecxの値が8でなければmain関数が1を返してしまうことを考え、コマンドライン引数の長さが8バイトであることを確認する働きをしていると結論した。
400404の行ではrsiに先ほどの8バイトのコマンドライン引数を代入している。
400407の行ではeaxの値を0に、400409の行でカウントの値を0にしている。40040bの行から400425の行はループとなっており、400419の行でecxに4を加え、400422の行でecxの値と0x40を比較し、等しければループを抜けることから、ecxがカウンタの働きをし、このループは計16回繰り返される。
X回目のループの中で何を行っているのかをまとめる。まず、40040bの行から400411の行にかけてはコマンドライン引数を16進で表した場合の下から第X桁目を抽出する。それを仮にAとおく(0<=A<=15)。次に、初めに定義したスタック構造について、(rsp-16+A)のアドレスに位置する1バイトを取り出す。取り出した値を仮にBとおく。このとき、rsp-0x10というアドレスからrsp-1というアドレスまで16バイトにかけて0f 0e 0d 0b 00 01 06 0c 04 05 08 0a 02 03 09 07という並びで定数が格納されていることに注意する。すなわち、Bの取りうる値は0,1,2,…,e,fの,1桁の16進数である。400419の行から40041fの行にかけては、raxの下位から第X桁に対し、Bとor演算を行わせている。ここで、ループに入る前にはraxの値は0であるから、ループを経るごとにraxの値が下位から1桁ずつ決定されることがわかる。
ループの先を読んでみる。400427の行では、raxにrip-0x6e(mainの先頭を指す)のアドレスの内容とraxの内容とのXOR演算の結果を格納する。ここで、rip-0x6eというアドレスを参照した場合、単純に考えればニーモニックで命令を示す48とb8も含まれてしまい、仮にchal00がCなど高級言語で書かれているとしたら、はたして命令を示す機械語を利用するプログラムを書けるのかが疑問だった。しかし、あくまでも処理はアセンブリ言語における命令に厳格に従うと考え、リトルエンディアンを考慮して、rip-0x6eを参照した値は、0x01000b0d0e0fb848であるとした。400438の行ではraxとrdxの比較を行い、40043bでは最初に述べたsetne命令を実行する。ここでalが0に設定されればよいので、rax=rdx=0x600967b3670e0385となるようにしなければならない。したがって、ループ直後にはraxの値が0x01000b0d0e0fb848 XOR 0x600967b3670e0385 = 0x61096cbe6901bbcdとなればよい。
すなわち、ループ内で行っていることは考えると、コマンドライン引数として指定するのは、16進表記で、上位から1桁目が[rsp-0x10+0x6]=6より6、2桁目が[rsp-0x10+0x5]=1より5、3桁目が[rsp-0x10+0x4]=0より4、4桁目が[rsp-0x10+0xe]=9よりe、…と求めてゆき、0x654e67316e453372である。これは1バイトの文字列”eNg1nE3r”をASCII Codeで変換したものである。したがって、コマンドライン引数を”eNg1nE3r”とした場合のみchal00は数値0を返す。

とりあえず1行ずつ読んでいき,適宜命令についてググりました.また,資料が見つからなければ,簡単に実験をして近い内容をつかもうとしました.難しそうでも思い切って取り組んでみると,この問題はできるかもしれません.スタックポインタの動きについてはあっているのかわかりません.

  • 問7
Windows 10 Proからこの問題を解いた。

設問1.
拡張子偽装などはないとする。拡張子が.imgであるので、これはMacによって作られたディスクイメージファイルである。なので、まずはMac OSがサポートするファイルシステムを調べた。しかしながらそれだけでは何の情報もつかめなかったので、とりあえず与えられたイメージディスクファイルをバイナリエディタStirlingで開き、マジックナンバーや、先頭・末尾に近い部分から情報を得ようとした。その結果、ファイルシステム一覧のWebページにも載っていたNTFSというASCII文字列が0x6fバイト目からみられた。NTFSは、Windows NT系で多く使われるファイルシステムである。Macでは、"NTFS-3G for Mac OS X"や、“Paragon NTFS for Mac OS X"というドライバをインストールすれば読み書きが可能になる。
また、文字列"NTFS"が見つかった、イメージファイルの先頭部分をFTK Imagerは"MBR"と識別していた。問題で与えられたのは"usbディスクイメージ"であるから、この部分はMBRではなくてPBRなのではないか、また、FTK Imager上でMBRとなっているのは、ここの内容が壊れているために正しく読めていないからではないかと考えた。したがって、まずはPBRの修正を試みることにした。
ファイルシステムは、NTFSであると予想した。しかしながら、PBRに偶然にNTFSという特定の4バイトの文字列が入るとは考えにくいものの、決定的な証拠はわからなかった。

設問2と設問3.
設問1を通して、まずはPBRが破損しているかどうかを確かめることにした。PBRはBIOS Parameter Block(BPB), Extended BPB, Boot Code, End of sector Marker(内容はブートセクタ終了を示す0xAA55)の4つの部分で構成されている。各構造体やメンバの名称はhttp://ultradefrag.sourceforge.net/doc/man/ntfs/ntfs_layout.h.htmlを参照した。以降、修復場所を示していく。

1.オフセット0x3から始まる4バイト
これは、"NTFS_BOOT_SECTOR"構造体の"oem_id"というサイズが8バイトのメンバで、ファイルシステムがNTFSであることを示すために必要である。本来"NTFS"という4バイトのASCII文字列と0x20が4バイト分の合計8バイトが入るが、"NTFS"となる部分がすべて0になってしまっている。したがって、"NTFS"を表す"4E 54 46 53"に書き直す。その後の4バイトにはすべて0x20が入るが、これはそのまま存在している。

2.オフセット0xbから始まる2バイト
これは、"BIOS_PARAMETER_BLOCK"構造体の、"bytes_per_sector"というサイズが2バイトのメンバで、1セクタあたりのバイト数を示す。HDDのセクタサイズには、512バイトと、4Kバイト(4096バイト)のものが存在する。後者はHDDの大容量化によって2010年ごろから現れ、アドバンスド・フォーマットという規格が2009年11月に定められている。ただ、NTFSのセクタサイズは512バイトが一般的であるため、512(0x200)バイトと考えた。ところで、オフセット0x28から始まる8バイトは"BIOS_PARAMETER_BLOCK"構造体の"number_of_sectors"というメンバであり、パーティションのセクタの数を示す。したがって、"bytes_per_sector"と"number_of_sectors"の値を掛け合わせると、このディスクイメージファイルのサイズと等しくなるはずである。エクスプローラによると、このディスクイメージファイルのサイズは104857600バイトである。一方、bytes_per_sector=0x200(バイト)、number_of_sectors=0x31fffであることから、掛け合わせた値は104857088となり、もう512(バイト)を加えれば104857600となる。このことから、bytes_per_sectorの値は0x200でよく、また、PBRはセクタとしては数えられないものと考えられる。したがって、bytes_per_sector=0x200(バイト)、number_of_sectors=0x31fffが正しい値と考え、"00 00"になっているところを、リトルエンディアンを考慮し"00 02"と書き直した。

3.オフセット0xdから始まる1バイト
これは、"BIOS_PARAMETER_BLOCK"構造体の、"sectors_per_cluster"というサイズが1バイトのメンバで、1クラスタあたりのセクタの数を示す。その値は0x08であることが多い。ここで、オフセット0x30には8バイトのサイズを持つ"NTFS_BOOT_SECTOR"構造体の"mft_lcn"というメンバがあり、これはMFTの割り当てられている最初のクラスタの番号を示す。すなわち、バイナリエディタ上で、MFTの内容が始まるアドレスと、(bytes_per_sector)*(sectors_per_cluster)*(mft_lcn)という積で求められる値は等しくなるはずである。結果としては、MFTの先頭アドレスは0x2155000であった一方、積の値は0x200*0x8*0x2155=0x2155000であり、等しくなった。よって、bytes_per_sector=0x0200,sectors_per_cluster=0x08,mft_lcn=0x2155が正しい値であると考えられる。
与えられたディスクイメージファイルでは、0x08という値が入るところが、0x00になっている。したがって、"08"と書き直す。

ここまで修正して、FTK Imagerがファイル構造を認識するようになった。また、[root]ディレクトリに、iir_vol37.pdfとiir_vol38.pdfというpdfファイルがあった。しかし、前者はサイズなどの情報や内容が認識されていたのに、後者はファイル名とファイルタイプが"$I30 INDX Entry"となっていること以外に何も情報がなく、内容を確認できなかった。しかし、ファイル情報がこれだけ確認されているということは、ファイル情報を管理するMFTに何か問題があると思ったので、iir_vol38.pdfのMFT情報を見てみることにした。その場所をバイナリエディタで探すためには、ファイル名を用いた。MFTにおいてはファイル名の情報はUnicodeで掲載されているため、検索することができる。そこで、MFT情報を見つけ、さらに次のように修正した。

4.バイナリエディタ上でアドレス0x2165000から始まる4バイト
これは、MFT_RECORD構造体の、magicという4バイトのメンバで、シグネチャとも呼ばれる。その値は、ASCII文字で"BAAD"か"FILE"のいずれかである。通常は"FILE"であるが、ファイルエントリが壊れている場合、"BAAD"となる。さて、"iir_vol38.pdf"のMFT情報が先ほど見つかったが、その先頭は、"BAAD"となっており、壊れていることになっている。ここではiir_vol38.pdfの内容を見たいので、"FILE"に直す。
直した結果、FTK Imager上でiir_vol37.pdfと同様に、サイズや内容などの情報が表示されるようになった。

ここで、FTKからディスクイメージのマウントを試みると、マウントは成功した。しかしながら、Windows10に搭載されているマウント機能ではなぜかマウントできず、疑問が残った。

設問4.
HashSumというハッシュ値計算ソフトウェアを用いて、以上の手順で手に入れたiir_vol37.pdfとiir_vol38.pdfのSHA-256ハッシュ値を求めた。その結果、iir_vol37.pdfのハッシュ値は5d62f82532d62645cd67b312546a599d90d15ea09c5ee5dbd61c5d90fc945ad3であり、iir_vol38.pdfのハッシュ値は86eb9f1fe34087f709a659b051d4d06930078c72d420683f2bf89455f31eb55fであった。ここで、私はこの問題がほかの問題よりも単純に感じたため、念のためにpdfファイルにもディスクイメージファイルが壊れたことによる内容の変化が起きなかったのかについて考えることにした。SHA-256は弱衝突耐性を持ち、1ビットでも内容が狂えば、本来導出されるべきハッシュ値と大きく異なってしまう。iir_vol37.pdfやiir_vol38.pdfをgoogle検索したところ、同じ内容のpdfファイルがあり、公開されていた。そこで、それらをダウンロードし、HashSumでハッシュ値を調べたところ、先ほどの2つのハッシュ値と同じ値であった。したがって、救い出した2つのファイルは公開されているものと内容が同一であり、また、公開されているこの2つのファイルがダウンロードされてから私が救い出すまでに改ざんがなく、ディスクイメージファイルの故障の影響を受けなかったと考えられる。すなわち、救い出したpdfファイルのSHA-256ハッシュ値は上にあげた通りで正しいと考えられる。

ファイルシステムの名称がわかったら比較的早く進みました.この問題も,少しずつ調べながら進めていけば,解けるかもしれません(この問題を見たとき,正直に言うと自分は「ファイルシステムって何??」という状態でした笑).ただ,なぜWindows10が搭載するマウント機能でマウントできなかったのかが謎で,それを考えるのに時間を無駄に使ってしまいました.

最後に

セキュキャンが自分の丈にあっているのかは正直なところとても不安です.でも,そんな態度では決して最前線には立てないので,恐れずに挑戦して,セキュキャンをきっかけに,自分自身で考えて成長できるようになりたいと思います!

Angstrom CTF 2018 write-up

 3月16日から22日にかけて行われたAngstrom CTF 2018に参加してきました!開始早々ドタバタあったそうですが、典型的?な問題が多く、とても勉強になりました。得点は1435点でした。

angstromCTF

150点以上の問題はほぼ解けていませんのでご注意ください(m´・ω・`)m ゴメン…

---MISC---

・Waldo2

同じjpgファイルがたくさん入っているが、どれかは必ず何かが違うはずなのでフォルダ内をサイズ順でソートすると、waldo339.jpgだけ異様にサイズが小さい。これはただのテキストファイルで、拡張子をtxtにして開くとフラグがある。

actf{r3d_4nd_wh1t3_str1p3s}

・That's Not My Name...

リンク先のgettysburg.pdfは、読み込もうとしてもエラーで読み込めない。ひとまずダウンロードしてバイナリエディタで開いてみると、先頭がPKとなっており、Zip形式の圧縮ファイルとわかる。

f:id:verliezer93764:20180323113136j:plain

解凍すると_rels、docProps、Wordというフォルダと[Content_Types].xmlというファイルが中に入っている。実はこれはZipファイルというよりもWordファイルで、TrIDに投げると画像のような結果が出る。

f:id:verliezer93764:20180323113803p:plain

拡張子をdocxにして開くと、フラグがある。

f:id:verliezer93764:20180323112842p:plain

actf{thanks_mr_lincoln_but_who_even_uses_word_anymore}

・File Transfer

与えられたpcapファイルをWireSharkで開くと、23個目のパケットでjpeg画像を受け取ったことがわかる。[ファイル]->[オブジェクトをエクスポート]->[HTTP]->[保存]にてf2J0Qiというファイルを保存し、拡張子をjpgにして開くと、フラグが手に入る。

f:id:verliezer93764:20180323114741j:plain

actf{0ver_th3_w1re}

・gif

プリン(ポケモン)のjif画像が渡される。サイズが大きいのでバイナリエディタで開いてみると、pngファイルは16進で89 50 4E 47から始まりAE 42 60 68で終わるが、これらがあちことでみられる。つまり渡されたファイルは多くのpngファイルが連結されているものだと考えられるので、これを分解する必要がある。下記のpythonスクリプトで分解した。


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import struct

infile=open("jiggs/jiggs.gif","rb")
basebintext=infile.read()
infile.close()

infile=open("jiggs/jiggs.gif","rb")
outfile=open("jiggs/1.png","wb")
i=1

final_four_bytes=["ae","42","60","82"]
latest_four_bytes=["","","",""]

for c in basebintext:

    #書き込み先ファイルに元画像から抽出した値を入れていく
    outfile.write(infile.read(1))

    #読み込んだ最新の4バイトをlatest_four_bytesに入れる
    for j in range(0,3):
        latest_four_bytes[j]=latest_four_bytes[j+1]
    latest_four_bytes[3]=str(format(c,'x'))

    
    #latest_four_bytesがPNGの終端4バイトと一致したら
    #書き込み先を新たなファイルに変える
    if latest_four_bytes==final_four_bytes:
        outfile.close()
        i+=1
        outfile=open("jiggs/"+str(i)+".png","wb")

infile.close()
outfile.close()


 これにより、いくつかのpngファイルが得られるが、上のスクリプトで分解を行っていた場合には5.pngにフラグが書かれている。

f:id:verliezer93764:20180323125916p:plain

actf{thats_not_how_you_make_gifs}

(もっといいやり方があったのかな?)

---CRYPTO---

・Warmup

ヒントを見ると、アフィン暗号らしい。Affine Cipherで、a=19,b=12としてDecryptすると、フラグが得られる。

actf{it_begins}

・Back to Base-ics

遷移先のページには次の内容が書かれている。

 Part 1: 011000010110001101110100011001100111101100110000011011100110010101011111011101000111011100110000010111110110011000110000

Part 2: 165 162 137 145 151 147 150 164 137 163 151 170 164 63 63
Part 3: 6e5f7468317274797477305f733178
Part 4: dHlmMHVyX25vX20wcmV9

 

Flag is the concatenation of the four decoded parts.

Part1,2,3はASCIIコード変換機を使って、2進数、8進数、16進数からASCII文字に変換する。Part4はhttp://www.convertstring.com/ja/EncodeDecode/Base64Decodeを使ってBase64の状態からデコードする。part1から「actf{0ne_tw0_f0」、part2から「ur_eight_sixt33」、part3から「n_th1rtytw0_s1x」、part4から「tyf0ur_no_m0re}」が得られるので、フラグは

actf{0ne_tw0_f0ur_eight_sixt33n_th1rtytw0_s1xtyf0ur_no_m0re}

・XOR

遷移先のページには、以下の内容が書かれている。

fbf9eefce1f2f5eaffc5e3f5efc5efe9fffec5fbc5e9f9e8f3eaeee7

題名や問題文から、これは平文Pを1Byteずつに分割し、それぞれに対し固定の1Byteの鍵KとのXORを計算し、接合したものだとわかる。よって、その鍵Kを見つけて同様にXORを計算すれば平文Pを復元できる。鍵が短いので、ブルートフォースアタックで復号を試みる(先頭が「actf{」になるのを見越して鍵を復元してもよい)。


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys

ciphertext="fbf9eefce1f2f5eaffc5e3f5efc5efe9fffec5fbc5e9f9e8f3eaeee7"
i=1

fixed_ciphertext=[]
ch=""

for c in ciphertext:
    ch+=c

    if i%2==0:
        fixed_ciphertext.append(ch)
        ch=""

    i+=1

i=0
plaintextnum=[]
plaintext=""
plaintext_list=[]

for i in range(255):
    
    success_flag = True

    #暗号文Cと鍵候補iでXORをとる
    for s in fixed_ciphertext:
        asc = int(s, 16) ^ int(i)

        #XORの結果がASCIIの範囲外に
        #あった場合、次の鍵を試す
        if 32 >= asc or 127 <= asc:
            success_flag = False
            break

        plaintextnum.append(asc)

    #XORの結果のすべての文字がASCIIの
    #範囲にあれば、平文候補に加える。
    if success_flag:
        
        for num in plaintextnum:
            plaintext+=chr(num)

        plaintext_list.append(plaintext)
        plaintext=""

    plaintextnum=[]

if len(plaintext_list)==0:
    print("Failed to decrypt this ciphertext... :(")
else:
    print("Succeeded in decrypting and candidates of plaintext are below.")
    print("--------------------------------------------------------------")

    #すべての平文候補を表示する
    for text in plaintext_list:
        print(text)

    print("--------------------------------------------------------------")

input("Put ENTER to end the program...")

 結果

f:id:verliezer93764:20180323151011p:plain

actf{hope_you_used_a_script}

・Intro to RSA

 p、qが与えられているので、C^d mod nを計算することによって平文Pを復元することができる。ただし、Cもdも非常に大きいので、復号するためのC^d mod nの計算には工夫が必要。http://post1.s105.xrea.com/を参照しよう。


#拡張ユークリッド互除法「もどき」
#しかもこのコード専用
def bad_expanded_gcd(e,en):
    k=1
    while True:
        if (k*en+1)%e==0:
            return (k*en+1)//e, k
        k+=1

#solve (base^exp) mod n
def solve_exp_mod(base,exp,n):

    expbin=format(exp,'b')
    expbintext=str(expbin)
    
    plaintext=1
    
    for c in expbintext:
        if c=='1':
            plaintext=plaintext*plaintext*base%n
        else:
            plaintext=plaintext*plaintext%n

    return plaintext
            
def main():
    p=169524110085046954319747170465105648233168702937955683889447853815898670069828343980818367807171215202643149176857117014826791242142210124521380573480143683660195568906553119683192470329413953411905742074448392816913467035316596822218317488903257069007949137629543010054246885909276872349326142152285347048927
    q=170780128973387404254550233211898468299200117082734909936129463191969072080198908267381169837578188594808676174446856901962451707859231958269401958672950141944679827844646158659922175597068183903642473161665782065958249304202759597168259072368123700040163659262941978786363797334903233540121308223989457248267
    e=65537
    c=4531850464036745618300770366164614386495084945985129111541252641569745463086472656370005978297267807299415858324820149933137259813719550825795569865301790252501254180057121806754411506817019631341846094836070057184169015820234429382145019281935017707994070217705460907511942438972962653164287761695982230728969508370400854478181107445003385579261993625770566932506870421547033934140554009090766102575218045185956824020910463996496543098753308927618692783836021742365910050093343747616861660744940014683025321538719970946739880943167282065095406465354971096477229669290277771547093476011147370441338501427786766482964
    
    n=p*q
    en=(p-1)*(q-1)
    
    d,k=bad_expanded_gcd(e,en)

    # P = C^d mod n
    plaintext=solve_exp_mod(c,d,n)
    
    print("Plain text is ...")
    print("-- dec version --")
    print(plaintext)
    print("-- hex version --")
    print(format(plaintext,'x'))
    
if __name__=="__main__":
    main()

 実行結果

f:id:verliezer93764:20180323154505p:plain

16進数での出力結果をASCIIコード変換機に投げるとフラグを獲得。

actf{rsa_is_reallllly_fun!!!!!!}

・ofb

 ブロック暗号は、平文を固定長のブロックに分けて、それぞれの平文ブロックに対し暗号化をし暗号ブロックを作るということを行い、最後に暗号ブロックを結合して暗号文を完成させるという暗号方式。そしてそれぞれのブロックをどのように暗号化するか、各ブロックを暗号化させる鍵をどう生成するかによってECBモード、CBCモード、CTSモード、CFBモード、OFBモード、CTRモードがある。先ほどの問題のXORによる暗号化はある意味ではECBモードといえる。ECBモードは暗号化を通じて平文ブロックと暗号文ブロックを一対一対応させるもので、実装は簡単だが、使うべきではない。

OFBモードは、つぎのような暗号化手順である。

f:id:verliezer93764:20180323171447p:plain

ただし、数値Aを数値Bで2回XORすると数値Aに戻るという特性から、構造上は復号も同じようにできる。

さて、問題ではpythonによる暗号化プログラムと、それにより暗号化されたファイルflag.png.encが与えられる。

暗号化プログラム(encrypt.py)は下の通り。


import struct

def lcg(m, a, c, x):
	return (a*x + c) % m

m = pow(2, 32)

with open('lcg') as f:
	a = int(f.readline())
	c = int(f.readline())
	x = int(f.readline())

d = open('flag.png').read()
d += '\x00' * (-len(d) % 4)
d = [d[i:i+4] for i in range(0, len(d), 4)]

e = ''
for i in range(len(d)):
	e += struct.pack('>I', x ^ struct.unpack('>I', d[i])[0])
	x = lcg(m, a, c, x)

with open('flag.png.enc', 'w') as f:
	f.write(e)
	f.close()


まずは、上のプログラムのflag.pngとflag.png.encを入れ替える。これで復号のベースはできる。そして、次は初期化ベクトルと暗号化関数Fを明らかにする。関数Fは、lcg関数で行われている。これは線形合同法という、n+1回目の出力がX[n+1]=(a*X[n]+c) mod mで定義されている実行速度を重視した疑似乱数生成方法である。その中で、パラメータa,c,x,mについては、m以外はlcgという外部ファイルから読み込んでいる。よって、a,c,xを求めることができればこの問題を解くことができる。ところで、フラグファイルがpngならば、先頭16バイトが89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52で始まっているはずなので、flag.png.encの最初の16バイト18 9A 6D 45 89 A2 9C 3F 4E B6 9D 78 B3 6F 89 27と照らし合わせると、上図でいうK0,K1,K2,K3は

K0=89 50 4E 47 XOR 18 9A 6D 45=91CA2302(16)=2445943554(10)、

K1=0D 0A 1A 0A XOR 89 A2 9C 3F=84A88635(16)=2225636917(10)、

K2=00 00 00 0D XOR 4E B6 9D 78=4EB69D75(16)=1320590709(10)、

K3=49 48 44 52 XOR B3 6F 89 27=FA27CD75(16)=4196912501(10)

であることがわかる。また、線形合同法から

a*K0+c≡K1 (mod m)

a*K1+c≡K2 (mod m)

a*K2+c≡K3 (mod m)

すなわち

a*(K1-K0)≡(K2-K1) (mod m)

a*(K2-K1)≡(K3-K2) (mod m)

が成立する。具体的に表すと、

220306637*a≡905046208 (mod 2^32)

905046208*a≡1418645504 (mod 2^32)

である。

文字が一つになったので、aの値を求めることができる。長いブルートフォースアタックの末、a=3204287424と求まった。

次に、cを求める。cは線形合同法において足される役目となっているので、法mより大きくする必要はなく、c<=2^32である。

先ほどaが求まったので、a*K0+c≡K1 (mod m)にaを代入し、具体的には

7837506169896064896+c≡2225636917 (mod 2^32)からcの値を求めると、c=1460809397である。

最後に、先ほどの定義でいうX[0]の値についてであるが、上のencrypt.pyではlcgファイルから読み取るxの値を線形合同法で攪乱する前にXOR処理「e += struct.pack('>I', x ^ struct.unpack('>I', d[i])[0])」を行っているので、x=K0=2445943554である。以上から、lcgという名前のファイルを作り、a,c,xの順に3204287424、1460809397、2445943554と一行ずつ書いてencrypt.py(改変後)を走らせると、flag.pngが復元され、フラグが手に入る。(python2系で実行した)

参考:decrypt.py


import struct

def lcg(m, a, c, x):
	return (a*x + c) % m

m = pow(2, 32)

a = 3204287424
c = 1460809397
x = 2445943554

d = open('flag.png.enc').read()
d += '\x00' * (-len(d) % 4)
d = [d[i:i+4] for i in range(0, len(d), 4)]

e = ''
for i in range(len(d)):
	e += struct.pack('>I', x ^ struct.unpack('>I', d[i])[0])
	x = lcg(m, a, c, x)

with open('flag.png', 'w') as f:
	f.write(e)
	f.close()

f:id:verliezer93764:20180323182725p:plain

actf{pad_rng}

 

---WEB---

・get me

遷移先ページのsubmitを押すとHey, you're not authorized!と言われるが、URLに「http://web.angstromctf.com:3005/?auth=false」とauth=falseとなっているので、auth=trueにすると、フラグが表示される。

actf{why_did_you_get_me}

・Sequel

ログインページのUsername、Passwordのいずれかのフォームに「'」を入力するとエラーメッセージが表示される。仮にログインのための条件文が「user ' (入力値1) ' について、password=' (入力値2) 'が正しいか」というような構造をしていると、「'」を入力した場合構文エラーが起こってしまう。さて、usernameの欄に「admin」、passwordの欄に「' or '1'='1」と打つと、パスワード照合部分で条件が「password=' ' or '1'='1 '」となるが、'1'='1'なので、この条件は真になってしまう。その結果、adminとしてのログインが通る。

actf{sql_injection_more_like_prequel_injection}

・Source Me 2

ソースを見ると以下のようなJavaScriptがある。


    var checkLogin = function () {
        var password = document.getElementById("password").value;
        if (document.getElementById("uname").value != "admin"){
            console.log(uname);
            document.getElementById("message").innerHTML = "Only admin user allowed!";
            return;
        } else {
            var passHash = md5(password);
            if (passHash == "bdc87b9c894da5168059e00ebffb9077"){
                window.location = "./login.php?user=admin&pass=" + password;
            } else {
                document.getElementById("message").innerHTML = "Incorrect Login";
            }
        }
        return;
    }

つまり、usernameはadminで、passwordのmd5ハッシュ値がbdc87b9c894da5168059e00ebffb9077でなければならない。

Best MD5 & SHA1 Decrypter - Hash Toolkitにて逆変換すると、password1234が返ってくる。従って、Usernameの欄にはadminを、Passwordの欄にはpassword1234を入力すればよい。

actf{md5_hash_browns_and_pasta_sauce}

・MadLibs

あるWebサービスに遷移する。いろいろやっていると、なにやらソースコードを手に入れられる。


from flask import Flask, render_template, render_template_string, send_from_directory, request
from jinja2 import Environment, FileSystemLoader
from time import gmtime, strftime
template_dir = './templates'
env = Environment(loader=FileSystemLoader(template_dir))


madlib_names = ["The Tale of a Person","A Random Story"]
story_fields = {
    "The Tale of a Person":['Author Name','Adjective','Noun','Verb'],
    "A Random Story":['Author Name','Adjective','Noun','Any first name','Verb']
    }

app = Flask(__name__)
app.secret_key = open("flag.txt").read()

@app.route("/",methods=["GET"])
def home():
    return render_template("home.html",libs=madlib_names)
    
@app.route("/form/",methods=["GET"])
def madlib(templatename):
    global madlib_names
    if templatename in madlib_names:  
        return render_template("home.html",libs=madlib_names,title=templatename,fields=story_fields[templatename])
    else:
        error_message = 'The MadLib with title "' + templatename + '" could not be found.'
        return render_template("home.html",libs=madlib_names,message=error_message)

@app.route("/result/",methods=["POST"])
def output(templatename):
    
    if templatename not in madlib_names:    
        return "Template not found."
    
    inpValues = []
    for i in range(len(story_fields[templatename])):
        if not request.form[str(i+1)]: 
            return "All form fields must be filled"
        else:
            inpValues.append(request.form[str(i+1)][:24])
        
    authorName = inpValues.pop(0)[:12]
    try:
        comment = render_template_string('''This MadLib with title %s was created by %s at %s''' % (templatename, authorName, strftime("%Y-%m-%d %H:%M:%S", gmtime())))
    except:
        comment = "Error generating comment."
    return render_template("_".join(templatename.lower().split())+".html",libtitle=templatename,footer=comment, libentries=inpValues)
    

@app.route("/get-source", methods=["GET","POST"])
def source():
    return send_from_directory('./','app.py')

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=7777, threaded=True)

 Flaskとは、Pythonで使うことのできる軽量のWebフレームワークらしい。まず目に入るのは、appのsecret_keyという変数?にフラグが入れられているということ。ただ何をすればいいのかよくわからないので調べていたところ、Server-Side-Templating-Injectionなるものを知った。Flaskを使う際、テンプレートエンジンにjinja2というものを使うが、それは{{と}}で囲まれたコードを実行してしまうらしい。たとえば、{{7 * 7}}を表示させようとすると、単に49が表示される。また、app.secret_key=(フラグ文書)となっていたが、これはconfigという辞書形式のグローバル変数に保存されているらしい。そこで、とりあえず入力フォーム全てに{{config}}を入力すると、下図のようになった。なぜauthorNameにだけconfigが代入されたのかわからないし、もともと問題は解けたけれど、全体的に理解できてない。

f:id:verliezer93764:20180323231614j:plain

actf{wow_ur_a_jinja_ninja}

・File Storer

 何にも手掛かりがなかったので、ヒントを見ると"Can't solve it? Git gud."といわれた。Git gudというのは上手くなれみたいな意味らしいが、Gitというワードが出て何かあるのかなと思っていろいろ調べると、/.gitというディレクトリがあるようだ。

---RE---

IDAのFree版を使った。

・Rev1

32bitELFが渡される。これをIDAに読み込ませ、下図の分岐に注目する。見にくくなってしまったが、画面の一番上でebp+var_54というアドレスからs3cret_pa55wordという文字列を代入している。また、fgetsにより、ebp+var_4Cというアドレスから入力値を代入していく。

f:id:verliezer93764:20180324100021p:plain

そして、図の選択した部分で、eaxすなわちebp+var_4Cの中身の値(入力値)とebp+var_54の中身の値(s3cret_pa55word)とをstrcmpにより比較し、等しければ右側に、等しくなければ左に分岐する。もちろん、等しい場合、つまりs3cret_pa55wordを入力すればフラグが表示される。

・Rev2

32bitELFファイルが渡される。この問題は2段階で構成されている。まずはLevel1。

f:id:verliezer93764:20180324101305p:plain

分岐前のcmp命令により、eaxの値と11D7(16)すなわち4567(10)の値が等しければよいので、まずは4567を入力する。

次は、Level2。今回は数値を二つ入れる。

f:id:verliezer93764:20180324101852p:plain

f:id:verliezer93764:20180324101911p:plain

2枚目の最後の分岐が右側であればフラグが表示される。

最初の入力をA、次の入力をBとするならば、次の条件を満たさなければならない。

1.B<=99

2.9<B

3.A<=99

4.9<A

5.A<=B

6.B*A=0x6D67=3431(10)

これらを満たすのは、A=47、B=73である。よって、"47 73"と入力すればよい。

・Rev3

 32bitELFファイルが渡される。今回は、コマンドライン引数を1つ指定しなければならない。IDAに読み込ませると、"egzloxi|ixw]dkSe]dzSzccShejSi^3q"という文字列が気になったので、コマンドライン引数に指定するも、当然うまくいかない。

f:id:verliezer93764:20180324104304j:plain

ltraceしてみると、入力値がどうやら改造されているらしい。

abc -> ehg

abcdefghijklmn -> ehgjilk^]`_bad

f:id:verliezer93764:20180324104909p:plain

しかしながら、改造前の文字と改造後の文字は一対一対応をしているらしかったので、変換テーブルを作ってみると、"egzloxi|ixw]dkSe]dzSzccShejSi^3q"はフラグ文字列を変換したものだということが分かった。

f:id:verliezer93764:20180324105312p:plain

actf{reversing_aint_too_bad_eh?}

 

---BINARY---

まさか今回pwn系の問題に挑戦するとは思わなかった(もっと先になるかと思っていた)。

・Accumulator

渡されたC言語ソースコードは以下の通り。


#include <stdlib.h>
#include <stdio.h>

int main(){

	int accumulator = 0;
	int n;
	while (accumulator >= 0){
		printf("The accumulator currently has a value of %d.\n",accumulator);
		printf("Please enter a positive integer to add: ");

		if (scanf("%d",&n) != 1){
			printf("Error reading integer.\n");
		} else {
			if (n < 0){
				printf("You can't enter negatives!\n");
			} else {
				accumulator += n;
			}
		}
	}
	gid_t gid = getegid();
	setresgid(gid,gid,gid);
	
	printf("The accumulator has a value of %d. You win!\n", accumulator);
	system("/bin/cat flag");

}

int型変数accumulatorが0以上である間、フラグは表示されないが、負数を加えることもできない。しかし2の補数表現を使っているので、accumulatorを数値的に2147483648以上4294967293以下にしてしまえば、accumulatorは負数扱いされる。ということで、足す数も負数扱いされないように、例えばまず10を足してから2147483640を足してやればwhileループを抜けてフラグが表示される。

 

Cookie jar

渡されたC言語ソースコードは以下の通り。


#include <stdio.h>
#include <stdlib.h>

#define FLAG "----------REDACTED----------"

int main(int argc, char **argv){
  
	gid_t gid = getegid();
	setresgid(gid, gid, gid);

	int numCookies = 0;

	char buffer[64];

	puts("Welcome to the Cookie Jar program!\n");
	puts("In order to get the flag, you will need to have 100 cookies!\n");
	puts("So, how many cookies are there in the cookie jar: ");
	fflush(stdout);
	gets(buffer);

	if (numCookies >= 100){
		printf("Congrats, you have %d cookies!\n", numCookies);
		printf("Here's your flag: %s\n", FLAG);
	} else {
		printf("Sorry, you only had %d cookies, try again!\n",numCookies);
	}
		
	return 0;
}

普通にやれば変数numCookiesの値を変えられないのでフラグは見られない。しかし基本的なバッファオーバーフロー脆弱性がある。char型配列bufferにはchar型64個ぶんの領域しか確保されていないので、それ以上書き込むとnumCookiesの領域まで踏み込んでしまい、numCookiesの値が書き換えられてしまう。例えば次のように入力すれば、フラグを手に入れることができる。"z"はASCII Codeで122番目なので、うまくnumCookiesの値を書き換えることができている。

f:id:verliezer93764:20180324113054p:plain

 

・Number Guess

渡されたC言語ソースコードは以下の通り。


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>


char *flag = "REDACTED";
char buf[50];

int main(int argc, char **argv) {

	
	puts("Welcome to the number guessing game!");
	puts("Before we begin, please enter your name (40 chars max): ");
	fflush(stdout);
	fgets(buf, 40, stdin);
	buf[strlen(buf)-1] = '\0';
		
	strcat(buf, "'s guess: ");	
	puts("I'm thinking of two random numbers (0 to 1000000), can you tell me their sum?");
	
	srand(time(NULL));
	int rand1 = rand() % 1000000;
	int rand2 = rand() % 1000000;

	printf(buf);
	fflush(stdout);
	int guess;
	char num[8];
	fgets(num,8,stdin);
	sscanf(num,"%d",&guess);

	if (guess == rand1+rand2){
		printf("Congrats, here's a flag: %s\n", flag);
	} else {
		printf("Sorry, the answer was %d. Try again :(\n", rand1+rand2); 
	}
	fflush(stdout);
	return 0;
}

疑似乱数を使っているのが、シードを書き換えるのは無理そう。しかし簡単なフォーマットストリング攻撃を使うことができる。標準入力で"%x %x %x %x"などと入れると、各変数のアドレスが表示されてしまう。とりあえずrand1とrand2の値を知りたいので"%d %d %d %d %d %d %d %d %d %d"と入力すると、

f:id:verliezer93764:20180324114949p:plain

答えが3つめの値と9つめの値の和になっていることがわかる。よって、先ほどの"%d %d %d ..."をyour nameとして入力し、出力の3つめの値と9つめの値の和をとればフラグを手に入れることができる。

 

・Rop to the Top

渡されたC言語ソースコードは以下の通り。


#include <stdlib.h>
#include <string.h>
#include <stdio.h>

void the_top(){

	system("/bin/cat flag");

}

void fun_copy(char *input){

	char destination[32];
	strcpy(destination, input);
	puts("Done!");
}

int main (int argc, char **argv){
	gid_t gid = getegid();
	setresgid(gid,gid,gid);

	if (argc == 2){
		puts("Now copying input...");
		fun_copy(argv[1]);
	} else {
		puts("Usage: ./rop_to_the_top32 ");
	}

	return 0;
}

the_topという関数を実行してくれればフラグを見せてくれるが、実行してくれない。ROP攻撃というものがある。サブルーチンに入ったとき、終了時に呼び出し元のアドレスへ制御が戻るようにするためにリターンアドレスが設定されるが、それをバッファオーバーフローによって書き換えることでサブルーチン終了時に自由な場所へ制御を移してやろうという攻撃である。ためしに、gdb上でコマンドライン引数をAAAAAAAA...と設定し、プログラムを走らせてやると次のようになる。

f:id:verliezer93764:20180324120030p:plain

Segmentation fault.や0x41414141 in ?? () と表示されている。これは、プログラム上で存在しないアドレスに移動しようとしたためで、i r コマンドでレジスタの内容を確認すると、eip、すなわち次実行される命令のアドレスが0x41414141に設定されてしまっている。

さて、このリターンアドレスをthe_top関数の先頭アドレスに書き換えたい。IDAで調べてみると、これが0x080486dbであることがわかる。また、AAAAA...のどの位置で書き換えが行われているのか調べるためにコマンドライン引数をAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUに設定してみると、リターンアドレスは0x51414150となるが、'P'=0x50、'Q'=0x51なので、'PAAQ'の部分でリターンアドレスの書き換えが起こせることがわかる。

f:id:verliezer93764:20180324121501p:plain

よって、リトルエンディアンに気を付けて、引数にAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAA\xdb\x86\x04\x08を指定すると、the_top関数が実行される。しかし、gdb上では権限の問題でフラグを見ることができなかったので、次のようにgdb外で通常の状態で実行した。(どうやって16進の状態で引数を渡せるのか知らなかったので悪戦苦闘した)

f:id:verliezer93764:20180324122149p:plain

actf{strut_your_stuff}

 

読んでくれてありがとう!

NeverLAN CTF 2018 write-up (18/02/28更新)

23日から26日にかけて行われたNeverLAN CTFというオンラインCTFに参加しました。

neverlanctf.com

僕は去年12月にCTFの存在と、その楽しさを知り、CpawCTF、ksnctfといった常設のCTFに挑戦してきましたが、オンラインCTFとしてはこのNeverLAN CTFが初めての経験です。情報を学び始めたのは去年からで、主に基本情報処理技術者試験の勉強をしていたので得点は約10000pt満点中2841pt、順位はNon-Studentの参加者中で80位、というまだまだこれからな結果でした。しかしながらたくさん知ったことがありとても熱中して楽しくできたので、備忘録的にwrite-upを載せておきます。

高得点問題はほぼ解けていないので期待はしないでください(´;ω;`)

ヒントが解放されているので、でき次第更新していきます。

---Cryptography---

・I have a message for you

問題文には二進数が書かれている。これをASCIIに変換すると、

 Tmh5b2IgdmZzIGEgY2N5IGR2IHZqaGV6bXYgYnltdyBkZWNyIHhmZ3YgaCBkZmhwcmdjIHZvZXF0aiBkb21tIGdjIGhrcnNkZHcgcnIgemJ1IGdoa3FiIHB6IG5jbHhxZHd0cHVxcg==
が得られる。さらにこれをbase64デコードすると、
Nhyob vfs a ccy dv vjhezmv bymw decr xfgv h dfhprgc voeqtj domm gc hkrsddw rr zbu ghkqb pz nclxqdwtpuqr
となる。スキュタレー暗号かなと思いpythonでこの文字列を任意の太さの棒に巻き付けたときに読める文字列を出力するプログラムを書いてみたが、特に知っている単語はみられなかった。。
 
・Picture Words
換字式暗号。与えられる画像にはいろんな記号が記されているが、同じ記号に特定のアルファベットを割り当てて、できた文字列をhttps://quipqiup.com/に入力すると解読してくれる。
 
---Scripting---

・basic math

・more basic math

・even more basic math with some junk

下のpythonでなんとかなる。numbers.txtは数が並んでいるページの内容を写したもの。ただし三問目は、テキストエディタであらかじめ空白やコンマを取り外す。途中に数字ではない偽の"101"があるので注意。

sum=0
for line in open("numbers.txt","r"):
    sum+=int(line)
print(sum)

 

 ---Reversing---

※まだアセンブリ言語を目で追う程度しかできないので、この分野の更新はないです。

・commitment issues

ダウンロードしたものをバイナリエディタで開くともろにflagがある。stringsコマンドやcatコマンドでも見れるはず。CryptoでいうROT13みたいな典型的な入門問題?

 

---Interweb---

Ajax_not_sorp

Ajaxについては全く知らなかったが、「ソースを表示」させると、おそらくuser欄に入力された場合の処理を書いてある部分、26行目に

$.ajax('webhooks/get_username.php',{
とある。どうやらここからユーザ名をとって照合しているようなので、URLに/webhooks/get_username.phpを加えると、移動先のページにはMrCleanと書かれている。
そしておそらくuser欄に入力された場合の処理を書いてある部分、43行目に
$.ajax('webhooks/get_pass.php?username='+$('#name').val(),{
とある。先に得た"MrClean"も使いURLに/webhooks/get_pass.php?username=MrCleanを加えると、移動先のページにフラグが書かれている。
 
・the_red_or_blue_pill
移動先のページのURLにパラメータredまたはblueを付加したそれぞれの場合にのみページ上部に文章が表示される。よくわからなかったがページ内の文章の文意から「赤と青を一緒にやるな!!」というのが読み取れたので、パラメータをred && blueにすると、blueの場合の文章が表示された。そこでいろいろやっていたらflagが出てきた。今やってみるとなぜか出てこないのでもう少し研究します…
 
ajax_not_borax
MD5がなんとかと。先ほどのAjax_not_sorpと似ているが、ユーザとパスワードの照合部分がそれぞれソース上では

// For element with id='name', when a key is pressed run this function
      $('#name').on('keypress',function(){
        // get the value that is in element with id='name'
        var that = $('#name');
        $.ajax('webhooks/get_username.php?username='+that.val(),{
        }).done(function(data){ // once the request has been completed, run this function
            data = data.replace(/(\r\n|\n|\r)/gm,""); // remove newlines from returned data
            if(data==MD5(that.val())){ // see if the data matches what the user typed in
              that.css('border', '1px solid green'); // if it matches turn the border green
              $('#output').html('Username is correct'); // state that the user was correct
            }else{ // if the user typed in something incorrect
              that.css('border', ''); // set input box border to default color
              $('#output').html('Username is incorrect'); // say the user was incorrect
            }
          }
        );
      });
      // dito ^ but for the password input now
      $('#pass').on('keypress', function(){
        var that = $('#pass');
        $.ajax('webhooks/get_pass.php?username='+$('#name').val(),{
        }).done(function(data){
            data = data.replace(/(\r\n|\n|\r)/gm,""); // remove newlines from data
            if(MD5(data)==MD5(that.val())){
              that.css('border', '1px solid green');
              $('#output').html(data);
            }else{
              that.css('border', '');
              $('#output').html('Password is incorrect');
            }
          }
        );
      });

詳しく見ると、

if(data==MD5(that.val())){ // see if the data matches what the user typed in
if(MD5(data)==MD5(that.val())){
の行で照合している。そこで、まずuserを調べるために「検証」(ChromeではDev Tools)を開き、上の囲いの行にブレークポイントをおく。すると停止時点でdataの値にはc5644ca91d1307779ed493c4dedfdcb7という値が入っている。これはMD5ハッシュ値なのでMD5変換(MD5),MD5逆変換(MD5Reverse)にて逆変換を試みるとtideadeという結果が返ってくる。これがuserフォームに入力すべきusernameである。こんどはパスワードについて調べる。同様に下の囲いの行にブレークポイントを置いてpassword欄に文字を打つと、ブレークポイントで停止した時点でdataの値にはZmxhZ3tzZDkwSjBkbkxLSjFsczlISmVkfQ==が入っている。base64エンコードされているようなのでデコードしてみるとflagが出てくる。
 
・Das_blog
 ログインページのソース上部に次のコメントがある。このようにテストアカウントのメモをうっかり残してしまうことはときどきあるらしい。
<!-- Development test account: user: JohnsTestUser, pass: AT3stAccountForT3sting -->
 これでログインをすると、You are now logged in as JohnsTestUser with permissions userと表示される。
ここで、いまURLに/login.phpとあるのでそれを消去してログインページからトップページに戻ると、当初とは表示が異なっている。しかしながらJohnsTestUserは"user"権限であり、「特殊な権限」がないと投稿された内容を見ることができないらしい。そこで、cookieを確認すると、最後のほうにpermissions=userとある。それをBurpSuiteなどでpermissions=adminに書き換えてやって更新すると、admin権限のユーザとみなされ、flagが表示される。
 
 ・tik-tik-boom
Purvestaさんの地域の時間が表示されている。文章によると、23:59に何かが起こるらしい。23分59秒きっかりに何かするのは無謀なので、23時59分だろう。ソースを見ると、
<span hidden>username and password did not match: admin hahahaN0one1s3verGett1ngTh1sp@ssw0rd</span>
というspanがある。そしてusernameとpasswordという2つのcookieがある。おそらく23時59分にそのcookieをusername=admin,password=hahahaN0one1s3verGett1ngTh1sp@ssw0rdに書き換えてやって更新すると何かが起こる(未検証)。正解者が解いた時間もその23時59分に偏っていたのであっているはず。
(追記)何も起こりませんでした。。
だが、どうやら時間を書き換えて正解した強者がいるらしい。それができるのがプロだよなあ。
 
---Network---
・Fuzzy Packets
与えられたpcapngファイルをWiresharkで開くと、ICMPがずら~っと並んでいる。まずすべてのパケットのペイロード部分にはThis is not the flag you're looking forとあるので探索対象から外す。次に、これは2つのipアドレス間の通信なのでたとえばsourceを172.26.13.112、destinationを192.241.233.138でフィルタリングし方向を固定してみる。ここでいろんなパケットを見ると、値が変化しているのがチェックサム、そしてcodeの部分だけである。そしてそのcodeの部分は0,1,...と不規則に変化している。「この0と1を並べていくとasciiコードが出てくるのでは」と推理する。Linuxを起動し、以下のようにScapyで解析を試みる。使ったのはkali Linux 2018.1。

f:id:verliezer93764:20180228101451p:plain

ASCIIコードならば8bitのうち先頭bitが0なのであっていそう。このASCIIコードを文字に変換するとflagが手に入る。

 

---Passwords---

・Encoding != Hashing

与えられたpcapファイルをWiresharkで開くとたくさんのパケットが並んでおりやりづらいので、とりあえずプロトコル階層を見る。そこで極めて少ない数の通信しかない種類を確認すると、IPv6でのUDPIPv4でのSSDPDNS、HTTPなどがみられるが、まずはHTTPが怪しいとみる。HTTPでフィルタリングして少し探すと10464番のHTTPヘッダで認証している様子が見られる。詳しく見るとこれはBASIC認証で、認証の際入力したidやパスワードを暗号化せずに平文のままネットワークを通過させてしまう危険な認証である。そこでflagが得られる。

 

・Zip Attack

パスワードで暗号化されたZipファイルと、それに含まれているという1つのjpegファイルが渡される。

既知平文攻撃というものがある。これはある暗号化Zipファイルに入っているあるファイルAについて、それと同一の暗号化されていないファイルA(インターネット上に公開されているものも含む:以下「既知ファイル」)が存在すればその暗号化Zipファイルを開くことができるという攻撃である。

pkcrackは、それを行うツールである。使用方法は以下の通り。

./pkcrack -C [暗号化されたZipファイル] -c [暗号化されたZipファイル内にある既知ファイル] -P [(後述)] -p [既知ファイル] -d [出力zipファイル]
なお、ディレクトリ階層に注意。

f:id:verliezer93764:20180228110542p:plain

さて、上記の公式に則ってやってみると、エラーが出てきてしまった。サイズは同じなのに…?

調べると、「問題のzipファイルを上げた出題者のOSの圧縮方式と自分のOSの圧縮方式が同じでないといけない」らしい。ためしに問題Zipファイルと、自分で画像を圧縮して作ったZipファイルについて、zipinfoしてみた。

f:id:verliezer93764:20180228111210p:plain

defXとdefNで異なっている。これらが何なのかはもう少し調べたいが、これが原因だろう。

また、-Pオプションで、既知ファイルをZip化した自作のZipファイルを指定すると、これが解決できる、というような記事をみた。そして、Zipファイルを作る際に「圧縮レベル」を指定できるようだ。0が圧縮しないでただzip化するだけ、9が最高圧縮率だという。

 その圧縮レベルを調整すれば、問題ファイルと同じ圧縮方式ができるかもしれない、と思ってレベル別に10個のzipファイルを作った。すると、レベル9で作った自作zipファイルについて、上記の公式に-Pオプション指定したとき、うまくいった。

f:id:verliezer93764:20180228110652p:plain

 成功すると、パスワードなしの状態で、-dで指定したdecrypted.zipが生成され、そこにencrypted.zipの内容がコピーされている。flag.txtを見て終了。個人的には一番きつかった。

 

・The WIFI Network

与えられたpcapファイルは、WPA2-PSKの4way-handshakeの様子らしい。

ヒントを見てしまったが、hashcatというツールを使うと、このハンドシェイクの通信と辞書ファイルによってパスワードを割り出してしまうという。

まず、https://hashcat.net/cap2hccapx/でこのpcapファイルをhccapxという独自の形式に変換してもらう。

つぎに、kali linuxにはデフォルトでhashcatが入っていたので、次のようなコマンドを打つ。

hashcat -a 0 -m 2500 neverlan.hccapx [辞書ファイル]

 ただこの「辞書ファイル」が厄介で、/var/share/dictにあったデフォルトの辞書ファイルやJohnTheRipperで使ったそこそこ大きいはずのOpenwallのフリー版辞書ファイルでも結果は出ず、いろんなデモの動画で使われていたrockyou.txtという辞書ファイルでやっとパスワードが出た。時間がかかるので、解析中はほかの問題を解くとよいかも。

 

---Trivia---

大文字にする、略称などいろんな答えが期待できたのでおもったよりきつかった。How far can you go?というどこかでみたような問題がわからなかった。

 

---Blast from the Past---

唯一全部解けた問題。

Cookie_monster

移動先のページにはHe's my favorite Red guyと書かれており、Red_Guy's_nameというcookieにNameGoesHereという値が入っている。そこをElmoに書き換えるとフラグをゲット。

 

・Siths use Ubuntu (Part 1 of 3)

Ubuntuに侵入されたらしい。問題文には"You've got to figure out how they keep getting in even though we've changed the password."とある。まずは与えられたovaファイルをダウンロードしてVirtualBoxなどから開く。Things-I-should-doというテキストファイルがあるが、関係ないファイル(出題者がスターウォーズ好きなのが次の問題の答えと合わせてわかる)。

part2でもpart3でもログファイルを使うが、実際侵入されるときには改竄対象にされるらしいので注意。

 lastコマンドを使うと、最近のログイン履歴を見ることができる。

f:id:verliezer93764:20180228122551p:plain

このうち

kyrolen  pts/18  172.16.164.128 Sta Feb 25 12:52 - 17:24 (04:31)
が外部(172.16.164.128)からの不正侵入。
この時間に何があったかを/var/log配下のauth.log.1で確認すると、確かに12時52分に"Accepted password for kyrolen from 172.16.164.128"とある。その後、12:52:45にcatコマンドで/etc/crontabを覗いているのが気になる。cronは定められたコマンドを定期的に実行し、どの日時にどのcron(ファイル)を実行するかを記入するのがcrontabである。定期的に特定のプログラムを実行するように侵入者が設定しようとしているかもしれない。

f:id:verliezer93764:20180228125433p:plain

 crontabには、以下の内容が書いてあった。一番下のファイルは、5分毎に実行するようになっており、最も怪しい。

f:id:verliezer93764:20180228125803p:plain

そこで、/etc/init.d/rebelsを覗くとflagが書かれている。 

f:id:verliezer93764:20180228125816p:plain

シェルスクリプトについては知らないので、ここの内容はもう少し調べてみます。

nc.traditionalと-p 443から、何かを送信するのかなとは感じる。

 

・Siths use Ubuntu (Part 2 of 3)

かつてのパスワードを求めろ、ということでJohnTheRipperを使う。shadowファイルには提示されている/etc/shadow.backupを使う。

参考:/etc/passwdのクラックツール『John The Ripper』を使ってみた | 俺的備忘録 〜なんかいろいろ〜

 

・Siths use Ubuntu (Part 3 of 3)

問題文には"You've got to figure out how they broke in."つまりはどうやって侵入したかを問うている。認証関係のログファイルauth.log.1を再び見てみると、先ほどの12時52分の"Accepted password for kyrolen from 172.16.164.128"の上には大量のログイン失敗のログが並んでいる。そのなかにflagが紛れ込んでいた。ひょっとしたら1問目を解いているときに見つかるかもしれない。

f:id:verliezer93764:20180228141420p:plain