ångstromCTF 2020 Writeup

この大会は2020/3/14 9:00(JST)~2020/3/19 9:00(JST)に開催されました。
今回もチームで参戦。結果は4335点で1596チーム中17位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity Check (MISC 5)

Discordに入るが、フラグが見つからない。#rolesチャネルでフラグアイコンでReactすると、新しいチャネルが現れた。#generalチャネルのトピックにフラグが書いてあった。

actf{never_gonna_let_you_down}

ws1 (MISC 70)

httpでフィルタリングすると、No.17のパケットで以下のパラメータでPOSTしていることがわかる。

Form item: "flag" = "actf{wireshark_isn't_so_bad_huh-a9d8g99ikdf}"
actf{wireshark_isn't_so_bad_huh-a9d8g99ikdf}

ws2 (MISC 80)

httpでフィルタリングすると、No.54のパケットでflag.jpgをPOSTしていることがわかる。flag.jpgをエクスポートすると、画像にフラグが書いてあった。
f:id:satou-y:20200327221931j:plain

actf{ok_to_b0r0s-4809813}

ws3 (MISC 180)

gitでリポジトリへのデータの更新などがされている通信のようだ。データサイズの大きいNo.76のパケットを見てみる。エクスポートして、バイナリで"PACK"以降を保存する。

$ git init newrepo
$ cd newrepo
$ mv ../76.pack .
$ git unpack-objects < 76.pack
Unpacking objects: 100% (3/3), done.
$ cd .git/objects
$ ls -lR
.:
合計 0
drwxrwxrwx 1 root root 0  314 15:49 34
drwxrwxrwx 1 root root 0  314 15:49 87
drwxrwxrwx 1 root root 0  314 15:49 fe
drwxrwxrwx 1 root root 0  314 15:30 info
drwxrwxrwx 1 root root 0  314 15:30 pack

./34:
合計 1
-r-xr-xr-x 1 root root 165  314 15:49 b1647544bdcf0e896e080ec84bb8b57cccc8d0

./87:
合計 1
-r-xr-xr-x 1 root root 87  314 15:49 872f28963e229e8271e0fab6a557a1e5fb5131

./fe:
合計 18
-r-xr-xr-x 1 root root 17714  314 15:49 3f47cbcb3ad8e946d0aad59259bdb1bc9e63f2

./info:
合計 0

./pack:
合計 0
$ python -c 'import zlib; print zlib.decompress(open("34/b1647544bdcf0e896e080ec84bb8b57cccc8d0").read())'
commit 228tree 87872f28963e229e8271e0fab6a557a1e5fb5131
parent 129b99f3e90fe8faa5ed9b4e18bfb6c0cb5ce340
author JoshDaBosh <jwanggt@gmail.com> 1584054396 -0400
committer JoshDaBosh <jwanggt@gmail.com> 1584054396 -0400

suiper secret stuff

$ python -c 'import zlib; print zlib.decompress(open("87/872f28963e229e8271e0fab6a557a1e5fb5131").read())'
tree 73100644 README.md���Ǭ�H���'�Se��T�>100644 flag.jpg�?G��:��FЪՒY����c�
$ python -c 'import zlib; print zlib.decompress(open("fe/3f47cbcb3ad8e946d0aad59259bdb1bc9e63f2").read())' | xxd
00000000: 626c 6f62 2031 3933 3730 00ff d8ff e000  blob 19370......
00000010: 104a 4649 4600 0101 0000 4800 4800 00ff  .JFIF.....H.H...
00000020: db00 4300 0806 0607 0605 0807 0707 0909  ..C.............
00000030: 080a 0c14 0d0c 0b0b 0c19 1213 0f14 1d1a  ................
00000040: 1f1e 1d1a 1c1c 2024 2e27 2022 2c23 1c1c  ...... $.' ",#..
00000050: 2837 292c 3031 3434 341f 2739 3d38 323c  (7),01444.'9=82<
00000060: 2e33 3432 ffdb 0043 0109 0909 0c0b 0c18  .342...C........
00000070: 0d0d 1832 211c 2132 3232 3232 3232 3232  ...2!.!222222222
00000080: 3232 3232 3232 3232 3232 3232 3232 3232  2222222222222222
00000090: 3232 3232 3232 3232 3232 3232 3232 3232  2222222222222222
000000a0: 3232 3232 3232 3232 32ff c000 1108 0225  222222222......%
                :
                :
00004b60: 53f5 4440 dbd4 fd53 6f53 f544 40db d4fd  S.D@...SoS.D@...
00004b70: 536f 53f5 4440 dbd4 fd53 6f53 f544 40db  SoS.D@...SoS.D@.
00004b80: d4fd 536f 53f5 4440 dbd4 fd53 6f53 f544  ..SoS.D@...SoS.D
00004b90: 40db d4fd 536f 53f5 4440 dbd4 fd53 6f53  @...SoS.D@...SoS
00004ba0: f544 40db d4fd 536f 53f5 4441 fae5 f528  .D@...SoS.DA...(
00004bb0: 0222 0fff d90a                           ."....

flag.jpgのバイナリデータが入っていることがわかる。該当箇所を抽出し、JPG画像にする。

import zlib

with open('3f47cbcb3ad8e946d0aad59259bdb1bc9e63f2', 'rb') as f:
    data = f.read()

flag = zlib.decompress(data)[11:]

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

f:id:satou-y:20200327222227j:plain

actf{git_good_git_wireshark-123323}

Git Good (WEB 70)

gitが残っていることはタイトルから推測できる。

$ curl https://gitgood.2020.chall.actf.co/.git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
$ curl https://gitgood.2020.chall.actf.co/.git/refs/heads/master
e975d678f209da09fff763cd297a6ed8dd77bb35

この情報から段階を追って、情報を引き出していく。

$ wget https://gitgood.2020.chall.actf.co/.git/objects/e9/75d678f209da09fff763cd297a6ed8dd77bb35
--2020-03-15 08:23:12--  https://gitgood.2020.chall.actf.co/.git/objects/e9/75d678f209da09fff763cd297a6ed8dd77bb35
gitgood.2020.chall.actf.co (gitgood.2020.chall.actf.co) をDNSに問いあわせています... 52.207.14.64
gitgood.2020.chall.actf.co (gitgood.2020.chall.actf.co)|52.207.14.64|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 177 [application/octet-stream]
`75d678f209da09fff763cd297a6ed8dd77bb35' に保存中

75d678f209da09fff76 100%[==================>]     177  --.-KB/s    時間 0s    

2020-03-15 08:23:13 (16.1 MB/s) - `75d678f209da09fff763cd297a6ed8dd77bb35' へ保存完了 [177/177]

$ python -c 'import zlib; print zlib.decompress(open("75d678f209da09fff763cd297a6ed8dd77bb35").read())'
commit 227tree 9402d143d3d7998247c95597b63598ce941e7bcb
parent 6b3c94c0b90a897f246f0f32dec3f5fd3e40abb5
author aplet123 <noneof@your.business> 1583598464 +0000
committer aplet123 <jasonqan2004@gmail.com> 1583598464 +0000

Initial commit

$ wget https://gitgood.2020.chall.actf.co/.git/objects/94/02d143d3d7998247c95597b63598ce941e7bcb
--2020-03-15 08:27:01--  https://gitgood.2020.chall.actf.co/.git/objects/94/02d143d3d7998247c95597b63598ce941e7bcb
gitgood.2020.chall.actf.co (gitgood.2020.chall.actf.co) をDNSに問いあわせています... 52.207.14.64
gitgood.2020.chall.actf.co (gitgood.2020.chall.actf.co)|52.207.14.64|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 224 [application/octet-stream]
`02d143d3d7998247c95597b63598ce941e7bcb' に保存中

02d143d3d7998247c95 100%[==================>]     224  --.-KB/s    時間 0s    

2020-03-15 08:27:02 (17.0 MB/s) - `02d143d3d7998247c95597b63598ce941e7bcb' へ保存完了 [224/224]

$ python -c 'import zlib; print zlib.decompress(open("02d143d3d7998247c95597b63598ce941e7bcb").read())' | xxd
00000000: 7472 6565 2032 3432 0031 3030 3634 3420  tree 242.100644 
00000010: 2e67 6974 6967 6e6f 7265 00c2 658d 7d1b  .gitignore..e.}.
00000020: 3184 8c3b 7196 0543 cb03 68e5 6cd4 c731  1..;q..C..h.l..1
00000030: 3030 3634 3420 696e 6465 782e 6874 6d6c  00644 index.html
00000040: 0063 8887 a549 7326 5c42 8cd5 1ce6 dfd4  .c...Is&\B......
00000050: 8f19 6d91 c431 3030 3634 3420 696e 6465  ..m..100644 inde
00000060: 782e 6a73 0049 b319 c37d c674 bca6 82ca  x.js.I...}.t....
00000070: b0f2 5064 73dd a6bd 9a31 3030 3634 3420  ..Pds....100644 
00000080: 7061 636b 6167 652d 6c6f 636b 2e6a 736f  package-lock.jso
00000090: 6e00 789f a5ca f452 f5f6 f25b fa9b 1c0a  n.x....R...[....
000000a0: b1d5 93dc e1b3 3130 3036 3434 2070 6163  ......100644 pac
000000b0: 6b61 6765 2e6a 736f 6e00 8f08 af35 205d  kage.json....5 ]
000000c0: 0ba8 0e94 b4f4 3063 1103 9d62 e138 3130  ......0c...b.810
000000d0: 3036 3434 2074 6869 7369 7374 6865 666c  0644 thisisthefl
000000e0: 6167 2e74 7874 0024 7c9d 491c 0d2d 6da5  ag.txt.$|.I..-m.
000000f0: e93a fcd0 681b 3edd 7ccd 700a            .:..h.>.|.p.

$ wget https://gitgood.2020.chall.actf.co/.git/objects/6b/3c94c0b90a897f246f0f32dec3f5fd3e40abb5
--2020-03-15 08:38:58--  https://gitgood.2020.chall.actf.co/.git/objects/6b/3c94c0b90a897f246f0f32dec3f5fd3e40abb5
gitgood.2020.chall.actf.co (gitgood.2020.chall.actf.co) をDNSに問いあわせています... 52.207.14.64
gitgood.2020.chall.actf.co (gitgood.2020.chall.actf.co)|52.207.14.64|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 166 [application/octet-stream]
`3c94c0b90a897f246f0f32dec3f5fd3e40abb5' に保存中

3c94c0b90a897f246f0 100%[==================>]     166  --.-KB/s    時間 0s    

2020-03-15 08:38:59 (15.7 MB/s) - `3c94c0b90a897f246f0f32dec3f5fd3e40abb5' へ保存完了 [166/166]

$ python -c 'import zlib; print zlib.decompress(open("3c94c0b90a897f246f0f32dec3f5fd3e40abb5").read())'
commit 210tree b630430d9d393a6b143af2839fd24ac2118dba79
author aplet123 <noneof@your.business> 1583598444 +0000
committer aplet123 <jasonqan2004@gmail.com> 1583598444 +0000

haha I lied this is the actual initial commit

$ wget https://gitgood.2020.chall.actf.co/.git/objects/b6/30430d9d393a6b143af2839fd24ac2118dba79
--2020-03-15 08:40:48--  https://gitgood.2020.chall.actf.co/.git/objects/b6/30430d9d393a6b143af2839fd24ac2118dba79
gitgood.2020.chall.actf.co (gitgood.2020.chall.actf.co) をDNSに問いあわせています... 52.207.14.64
gitgood.2020.chall.actf.co (gitgood.2020.chall.actf.co)|52.207.14.64|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 224 [application/octet-stream]
`30430d9d393a6b143af2839fd24ac2118dba79' に保存中

30430d9d393a6b143af 100%[==================>]     224  --.-KB/s    時間 0s    

2020-03-15 08:40:49 (16.6 MB/s) - `30430d9d393a6b143af2839fd24ac2118dba79' へ保存完了 [224/224]

$ python -c 'import zlib; print zlib.decompress(open("30430d9d393a6b143af2839fd24ac2118dba79").read())' | xxd
00000000: 7472 6565 2032 3432 0031 3030 3634 3420  tree 242.100644 
00000010: 2e67 6974 6967 6e6f 7265 00c2 658d 7d1b  .gitignore..e.}.
00000020: 3184 8c3b 7196 0543 cb03 68e5 6cd4 c731  1..;q..C..h.l..1
00000030: 3030 3634 3420 696e 6465 782e 6874 6d6c  00644 index.html
00000040: 0063 8887 a549 7326 5c42 8cd5 1ce6 dfd4  .c...Is&\B......
00000050: 8f19 6d91 c431 3030 3634 3420 696e 6465  ..m..100644 inde
00000060: 782e 6a73 0049 b319 c37d c674 bca6 82ca  x.js.I...}.t....
00000070: b0f2 5064 73dd a6bd 9a31 3030 3634 3420  ..Pds....100644 
00000080: 7061 636b 6167 652d 6c6f 636b 2e6a 736f  package-lock.jso
00000090: 6e00 789f a5ca f452 f5f6 f25b fa9b 1c0a  n.x....R...[....
000000a0: b1d5 93dc e1b3 3130 3036 3434 2070 6163  ......100644 pac
000000b0: 6b61 6765 2e6a 736f 6e00 8f08 af35 205d  kage.json....5 ]
000000c0: 0ba8 0e94 b4f4 3063 1103 9d62 e138 3130  ......0c...b.810
000000d0: 3036 3434 2074 6869 7369 7374 6865 666c  0644 thisisthefl
000000e0: 6167 2e74 7874 000f 5259 8006 f9cd b21d  ag.txt..RY......
000000f0: b2f4 c8d4 4d70 5356 3028 9b0a            ....MpSV0(..

$ wget https://gitgood.2020.chall.actf.co/.git/objects/0f/52598006f9cdb21db2f4c8d44d70535630289b
--2020-03-15 08:46:03--  https://gitgood.2020.chall.actf.co/.git/objects/0f/52598006f9cdb21db2f4c8d44d70535630289b
gitgood.2020.chall.actf.co (gitgood.2020.chall.actf.co) をDNSに問いあわせています... 52.207.14.64
gitgood.2020.chall.actf.co (gitgood.2020.chall.actf.co)|52.207.14.64|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 87 [application/octet-stream]
`52598006f9cdb21db2f4c8d44d70535630289b' に保存中

52598006f9cdb21db2f 100%[==================>]      87  --.-KB/s    時間 0s    

2020-03-15 08:46:04 (6.49 MB/s) - `52598006f9cdb21db2f4c8d44d70535630289b' へ保存完了 [87/87]

$ python -c 'import zlib; print zlib.decompress(open("52598006f9cdb21db2f4c8d44d70535630289b").read())'
blob 75actf{b3_car3ful_wh4t_y0u_s3rve_wi7h}

btw this isn't the actual git server
actf{b3_car3ful_wh4t_y0u_s3rve_wi7h}

Revving Up (REV 50)

とりあえず実行してみる。

team5665@actf:/problems/2020/revving_up$ ./revving_up
Congratulations on running the binary!
Now there are a few more things to tend to.
Please type "give flag" (without the quotes).
give flag
Good job!
Now run the program with a command line argument of "banana" and you'll be done!

引数にbananaを指定してもう一度実行する。

team5665@actf:/problems/2020/revving_up$ ./revving_up banana
Congratulations on running the binary!
Now there are a few more things to tend to.
Please type "give flag" (without the quotes).
give flag
Good job!
Well I think it's about time you got the flag!
actf{g3tting_4_h4ng_0f_l1nux_4nd_b4sh}
actf{g3tting_4_h4ng_0f_l1nux_4nd_b4sh}

Windows of Opportunity (REV 50)

IDA Freewareで開くと、すぐにフラグがわかる。
f:id:satou-y:20200327222710p:plain

actf{ok4y_m4yb3_linux_is_s7ill_b3tt3r}

Taking Off (REV 70)

Ghidraでデコンパイルする。

undefined8 main(int iParm1,long lParm2)

{
  int iVar1;
  undefined8 uVar2;
  size_t sVar3;
  long in_FS_OFFSET;
  uint local_b4;
  uint local_b0;
  uint local_ac;
  int local_a8;
  int local_a4;
  char *local_a0;
  byte local_98 [136];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts("So you figured out how to provide input and command line arguments.");
  puts("But can you figure out what input to provide?");
  if (iParm1 == 5) {
    string_to_int(*(undefined8 *)(lParm2 + 8),&local_b4,&local_b4);
    string_to_int(*(undefined8 *)(lParm2 + 0x10),&local_b0,&local_b0);
    string_to_int(*(undefined8 *)(lParm2 + 0x18),&local_ac,&local_ac);
    iVar1 = is_invalid((ulong)local_b4);
    if (iVar1 == 0) {
      iVar1 = is_invalid((ulong)local_b0);
      if (iVar1 == 0) {
        iVar1 = is_invalid((ulong)local_ac);
        if ((iVar1 == 0) && (local_ac + local_b0 * 100 + local_b4 * 10 == 0x3a4)) {
          iVar1 = strcmp(*(char **)(lParm2 + 0x20),"chicken");
          if (iVar1 == 0) {
            puts("Well, you found the arguments, but what\'s the password?");
            fgets((char *)local_98,0x80,stdin);
            local_a0 = strchr((char *)local_98,10);
            if (local_a0 != (char *)0x0) {
              *local_a0 = 0;
            }
            sVar3 = strlen((char *)local_98);
            local_a4 = (int)sVar3;
            local_a8 = 0;
            while (local_a8 <= local_a4) {
              if ((local_98[(long)local_a8] ^ 0x2a) != desired[(long)local_a8]) {
                puts("I\'m sure it\'s just a typo. Try again.");
                uVar2 = 1;
                goto LAB_00400bc7;
              }
              local_a8 = local_a8 + 1;
            }
            puts("Good job! You\'re ready to move on to bigger and badder rev!");
            print_flag();
            uVar2 = 0;
            goto LAB_00400bc7;
          }
        }
      }
    }
    puts("Don\'t try to guess the arguments, it won\'t work.");
    uVar2 = 1;
  }
  else {
    puts("Make sure you have the correct amount of command line arguments!");
    uVar2 = 1;
  }
LAB_00400bc7:
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return uVar2;
}

undefined8 is_invalid(int iParm1)

{
  undefined8 uVar1;
  
  if ((iParm1 < 0) || (9 < iParm1)) {
    uVar1 = 1;
  }
  else {
    uVar1 = 0;
  }
  return uVar1;
}

                             desired                                         XREF[3]:     Entry Point(*), main:00400b75(*), 
                                                                                          main:00400b7c(R)  
        00602090 5a 46 4f        undefine
                 4b 59 4f 
                 0a 4d 43 
           00602090 5a              undefined15Ah                     [0]                               XREF[3]:     Entry Point(*), main:00400b75(*), 
                                                                                                                     main:00400b7c(R)  
           00602091 46              undefined146h                     [1]
           00602092 4f              undefined14Fh                     [2]
           00602093 4b              undefined14Bh                     [3]
           00602094 59              undefined159h                     [4]
           00602095 4f              undefined14Fh                     [5]
           00602096 0a              undefined10Ah                     [6]
           00602097 4d              undefined14Dh                     [7]
           00602098 43              undefined143h                     [8]
           00602099 5c              undefined15Ch                     [9]
           0060209a 4f              undefined14Fh                     [10]
           0060209b 0a              undefined10Ah                     [11]
           0060209c 4c              undefined14Ch                     [12]
           0060209d 46              undefined146h                     [13]
           0060209e 4b              undefined14Bh                     [14]
           0060209f 4d              undefined14Dh                     [15]
           006020a0 2a              undefined12Ah                     [16]

引数に関する条件は以下の通り。

・引数は4つ指定。
・argv[3] + argv[2] * 100 + argv[1] * 10 == 0x3a4
・argv[1]~argv[3]は0~9
・argv[4] == "chicken"

さらにパスワードの入力が必要。パスワードは以下の条件を満たすものになる。

・パスワードdisiredの各文字と0x2aのXOR
disired = [0x5a, 0x46, 0x4f, 0x4b, 0x59, 0x4f, 0x0a, 0x4d, 0x43, 0x5c, 0x4f,
    0x0a, 0x4c, 0x46, 0x4b, 0x4d, 0x2a]

password = ''
for code in disired:
    password += chr(code ^ 0x2a)

print password

パスワードは"please give flag"。shell serverで実行する。

team5665@actf:/problems/2020/taking_off$ ./taking_off 3 9 2 chicken
So you figured out how to provide input and command line arguments.
But can you figure out what input to provide?
Well, you found the arguments, but what's the password?
please give flag
Good job! You're ready to move on to bigger and badder rev!
actf{th3y_gr0w_up_s0_f4st}
actf{th3y_gr0w_up_s0_f4st}

Keysar (CRYPTO 40)

Keyed Caesar。http://rumkin.com/tools/cipher/caesar-keyed.phpで、以下のパラメータを指定して復号する。

key: ANGSTROMCTF
ciphertext: agqr{yue_stdcgciup_padas}
actf{yum_delicious_salad}

Reasonably Strong Algorithm (CRYPTO 70)

factordbでnを素因数分解する。

n = 9336949138571181619 * 13536574980062068373

あとはそのまま復号する。

from Crypto.Util.number import *

n = 126390312099294739294606157407778835887
e = 65537
c = 13612260682947644362892911986815626931
p = 9336949138571181619
q = 13536574980062068373

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m)
print flag
actf{10minutes}

Wacko Images (CRYPTO 90)

コードを見ると、各ピクセルのRGBの値を以下のように変換していることがわかるので、逆算していけばよい。

R: R * key[0] % 251
G: G * key[1] % 251
B: B * key[2] % 251
from PIL import Image
from Crypto.Util.number import *

img = Image.open('enc.png').convert('RGB')
w, h = img.size

key = [41, 37, 23]

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

for y in range(h):
    for x in range(w):
        pixel = list(img.getpixel((x, y)))
        for i in range(3):
            pixel[i] = (pixel[i] * inverse(key[i], 251)) % 251
        output_img.putpixel((x, y), tuple(pixel))

output_img.save('flag.png')

f:id:satou-y:20200327223903p:plain
復号した画像にフラグが書いてあった。

actf{m0dd1ng_sk1llz}

Confused Streaming (CRYPTO 100)

ソースコードを見ると、このような条件になっている。

・seedはシステム固定
・e: ランダム100~1000
・d = ランダム1~100

以下のいずれかの条件を満たす場合はNG
・b*b < 4*a*c
・a==0
・b==0
・c==0
・Decimal(b*b-4*a*c).sqrt().to_integral_value()**2==b*b-4*a*c
・abs(a)>1000
・abs(b)>1000
・abs(c)>1000

keystreamは少々複雑なので、e, dのブルートフォースで、flagの形式になるものを探す。a, b, cは何でもよいので、適当において試す。例えば、a=10, b=40, c=10は問題ないので、それで試してみる。

import socket
import binascii
from decimal import *

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

def keystream(key, e, d):
    in_e = e
    while True:
        ret = Decimal('0.' + str(key ** in_e).split('.')[-1])
        for i in range(d):
            ret *= 2
        yield int((ret // 1) % 2)
        in_e += 1

def bin_2_bytes(s):
    ret = ''
    for i in range(0, len(s), 8):
        ret += chr(int(s[i:i+8], 2))
    return ret

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crypto.2020.chall.actf.co', 20601))

a = 10
b = 40
c = 10

data = recvuntil(s, ': ')
print data + str(a)
s.sendall(str(a) + '\n')

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

data = recvuntil(s, ': ')
print data + str(c)
s.sendall(str(c) + '\n')

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

key = (Decimal(b*b-4*a*c).sqrt() - Decimal(b))/Decimal(a*2)

found = False
for e in range(100, 1001):
    for d in range(1, 101):
        k = keystream(key, e, d)
        bin_flag = ''
        for i in data:
            bin_flag += str(next(k) ^ int(i))
        flag = bin_2_bytes(bin_flag)
        if flag.startswith('actf{') and flag.endswith('}'):
            print flag
            found = True
            break
    if found:
        break

実行結果は以下の通り。

a: 10
b: 40
c: 10
01100001011000110111010001100110011110110110010001101111011101110110111001011111011101000110111101011111011101000110100001100101010111110110010001100101011000110110100101101101011000010110110001111101
actf{down_to_the_decimal}
actf{down_to_the_decimal}

one time bad (CRYPTO 100)

$ nc misc.2020.chall.actf.co 20301
Welcome to my one time pad service!
It's so unbreakable that *if* you do manage to decrypt my text, I'll give you a flag!
You will be given the ciphertext and key for samples, and the ciphertext for when you try to decrypt. All will be given in base 64, but when you enter your answer, give it in ASCII.
Enter:
	1) Request sample
	2) Try your luck at decrypting something!
> 1
FAcWGSQ/ORQDIDkGLBslOx8KOzAzPwQTKyod with key c1RMUkVoSkZNdktIb09Ra1hhQ1lQalBXRVN2
> 2
PTUY
Your answer: FAcWGSQ/ORQDIDkGLBslOx8KOzAzPwQTKyod
Wrong! The correct answer was Xfv with key eSn

サーバのソースコードを見ると、以下のことがわかる。

time.time()がseed

■1
p: 1~30文字の英大小文字
k: pの長さと同じ英大小文字
x: p ^ k
-> x, kのbase64を表示

■2
p: 1~30文字の英大小文字
k: pの長さと同じ英大小文字
x: p ^ k
-> xのbase64表示
-> pを答えれば、フラグが表示される。

1で多少の時間のずれを吸収すれば、2でpを答えることができる。

#!/usr/bin/env python3
import socket
import random, time
import string
import base64

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

def otp(a, b):
    r = ""
    for i, j in zip(a, b):
        r += chr(ord(i) ^ ord(j))
    return r

def genSample():
    p = ''.join([string.ascii_letters[random.randint(0, len(string.ascii_letters)-1)] for _ in range(random.randint(1, 30))])
    k = ''.join([string.ascii_letters[random.randint(0, len(string.ascii_letters)-1)] for _ in range(len(p))])

    x = otp(p, k)

    return x, p, k

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('misc.2020.chall.actf.co', 20301))

data = recvuntil(s, b'> ')
print(data + '1')
s.sendall(b'1\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
now_x = data.split(' with key ')[0]
now_k = data.split(' with key ')[1]

seed = int(time.time()) - 5
for i in range(10):
    random.seed(seed + i)
    x, p, k = genSample()
    x = base64.b64encode(x.encode()).decode()
    k = base64.b64encode(k.encode()).decode()
    if x == now_x and k == now_k:
        break

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

x, p, k = genSample()
x = base64.b64encode(x.encode()).decode()
assert data == x

data = recvuntil(s, b': ')
print(data + p)
s.sendall(p.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

Welcome to my one time pad service!
It's so unbreakable that *if* you do manage to decrypt my text, I'll give you a flag!
You will be given the ciphertext and key for samples, and the ciphertext for when you try to decrypt. All will be given in base 64, but when you enter your answer, give it in ASCII.
Enter:
	1) Request sample
	2) Try your luck at decrypting something!
> 1
PjIIPjEJDjYcJRI4 with key WWFPcEdodHpIamda
> 2
ASoJHh4rJDY=
Your answer: HRmLnxfx
actf{one_time_pad_more_like_i_dont_like_crypto-1982309}
actf{one_time_pad_more_like_i_dont_like_crypto-1982309}

Survey (MISC 5)

アンケートに答えたら、フラグが表示された。

actf{never_gonna_run_around_and_desert_you}