Daily AlpacaHack Week9 (2026-2-2 ~ 2026-2-8)
Daily AlpacaHack の Writeup.
(2026/2/2 - 2026/2/8)
後から解いたものなど含むまとめ.
substance (Crypto, 2026/2/2)
- フラグに対して 2 から 2026 の間の乱数が 3 つ掛けられた暗号文が 2 つ渡される
- 二つの数の最小公倍数を取ると, フラグを含む共通部分が算出される
- 各乱数が 2 回の試行において共通の素因数を持つ数は少ないことが予想できるため, 2 つの暗号文の最小公倍数の素因数のすべての組み合わせに対して文字列変換してみることでフラグを発見できる
from Crypto.Util.number import GCD, long_to_bytes from sympy import factorint with open("./output.txt", "r") as f: enc_flag1, enc_flag2 = [int(x) for x in f.readlines()] flag_hex = GCD(enc_flag1, enc_flag2) print(factorint(flag_hex)) flag_base = 316224362225539763970988074867563404070815390505801 * 4572850661 * 503821 test_list = [2, 3, 3, 3, 3, 269] for i in range(1 << 6): tmp_base = flag_base for j in range(len(test_list)): if (i >> j) & 1: tmp_base *= test_list[j] print(long_to_bytes(tmp_base))
read-a-binary (Rev, 2026/2/3)
- Ghidra で逆コンパイルするとフラグを確認できた
Magic Engine (Web, 2026/2/4)
/secret.htmlにアクセスしたらフラグを獲得できた
RRe_Time_Limiter (Crypto, 2026/2/5)
- フラグについて 2 から 249 までの素数で剰余を取ったそれぞれの結果が渡される
- 互いに素な素数 $p_i$ について, 以下を満たす $x$ が中国剰余定理で算出可能 ($a_i$ は任意の整数)
$$x \equiv a_i \mod p_i$$
- 多くの素数に対する剰余が与えられているため $x$ がそのままフラグとなっていた
from Crypto.Util.number import long_to_bytes import sympy.ntheory.modular with open("./output.txt", "r") as f: out = eval(f.read()) primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349] (x, y) = sympy.ntheory.modular.crt(primes, out) print(long_to_bytes(x))
The World (Misc, 2026/2/6)
- サーバー時間に対して 100 秒および 100 ナノ秒以内の誤差に収まる時間を送るチャレンジ
- リモートから正確な時間を送ろうとするのはラグが不規則なため不可能
- 環境変数などでサーバーにおける時間を読み込ませることを考えたが, ナノ秒がやはり難しいと判断
- エラーメッセージがそのまま流れる仕様となっていたため
FLAGと送るとエラーメッセージにフラグの中身が表示された
misdirection (Rev, 2026/2/7)
- Ghidra で逆コンパイルしてみると偽物らしきフラグのみ存在 (入力をその偽のフラグと比較しているようだった)
- 処理を読むと
some_procにおいてstd命令というものがあった std命令は文字列操作において一文字読むごとの移動の方向を逆向きにする (参考)- よって偽のフラグと比較しているように見える部分では逆向き (メモリ上の一文字目より前の方向) に比較をしていることになる
stringsで調べると偽物らしきフラグの前にフラグをさかさまにしたような文字列が見つかり, それがフラグとなっていた
Compressor (Misc, 2026/2/8)
- (フラグの文字列) + (ユーザー入力) の文字列について
zlib.compressを行い, 結果の大きさのみを返すプログラム zlibの圧縮アルゴリズム DEFLATE は LZ77 で圧縮 -> ハフマン符号化というプロセスで圧縮を行うらしい (参考)- LZ77 は同じ文字列の並びについて圧縮するため, 1 文字ずつ (プレフィックス) + (検証したい 1 文字) + (フラグではない文字のパディング) という形で文字列を流すと検証したい 1 文字がフラグの同じ位置にある場合のみバイト数が小さくなると予想
- 以下のコードで確認したところ上の考えはあっていそう
import zlib import string flag = "Alpaca{this_is_just_a_fake_flag_for_testing_goodluck}" flag_charset = "abcdefghijklmnopqrstuvwxyz_A{}" no_flagset = "".join([x for x in string.printable if x not in flag_charset]) no_flagset = no_flagset[:len(flag)] data1 = flag.encode() + flag.encode() for char in flag_charset: tmp_flag = "Alpaca{" + char data2 = flag.encode() + tmp_flag.encode() + no_flagset[:len(flag)-len(tmp_flag)].encode() print(char, len(zlib.compress(data2)), end=", ") print() print("answer:", len(zlib.compress(data1)))
- 一文字ずつ検証していくことでフラグ獲得
from pwn import * import string _, host, port = "nc 34.170.146.252 62561".split() sh = remote(host, port) flag_charset = "abcdefghijklmnopqrstuvwxyz_A{}" no_flagset = "".join([x for x in string.printable if x not in flag_charset]) no_flagset = no_flagset[:53] flag_pref = "Alpaca{" while True: min_val = 999999999999999999999999 min_char = "@" for char in flag_charset: tmp_flag = flag_pref + char + no_flagset[:53-len(flag_pref)-1] prompt = sh.recvuntil("Your input:".encode()) sh.sendline(tmp_flag.encode()) prompt = sh.recvuntil("data:".encode()) tmp_length = int(sh.recvline().decode().split()[0].strip()) if min_val > tmp_length: min_val = tmp_length min_char = char elif min_val == tmp_length: min_char = "@" if min_char == "@": print("not updated") print(f"loop end (tmp flag: {flag_pref})") exit() flag_pref += min_char if min_char == "}": print("serach end successfully!") print("flag:", flag_pref) exit() print("tmp flag:", flag_pref)