Flydragon's Blog
976 words
5 minutes
Hacktheon Sejong 2025 Write-up

visitor badge

前言#

Hacktheon Sejong 據說是韓國版的金盾獎,看到前年很多人飛韓國參加就一直蠻想打的
因為限制只有大學生能夠參賽,所以是第一次參加,也就代表 Cake 的好隊友們不能陪我打了 QQ
這次跟 albert、呂老闆、slash(他去打黑客松了,嚴厲斥責) 一起打,最後是第 42 名。註:前 10 名可以去韓國打決賽

順帶一提,國籍可以填 Taiwan,填 ROC 的話國旗會是 China

alt text

防雷寫在前面:這篇 Write-up 都是 20+ solve 的水題,且大量使用 LLM,想看更完整的解答可以等 牛肉湯 / I’m down QQ / 吉娃娃 的 Write-up

賽制#

  • Jeopardy
  • 分數線性降低、無首殺分
  • 分為 Advanced 組和 Beginner 組,題目似乎相同
  • 題目分階段解鎖,解掉當前階段任一題目就會開放下一階段的題目

Reverse#

Barcode#

ELF 會讀入一個 hex-string 並根據 hex-string 輸出 ’*’ 和 ’ ’ 組成的圖形,給輸出檔案要求回推原始輸入

範例:

❯ ./barcode 0x1111111122222222
 *   *  
 *   *  
 *   *  
 *   *  
*   *   
*   *   
*   *   
*   *   

乖乖打開 IDA 逆,總之關鍵在這 alt text 輸入的 hex-string 會被切成每 8 個 bytes 一組,分別代表 8x8 的 Block,除了第 0 個 Block 以外,對每個 Block 都先 XOR -1 (就是 NOT) 再和前一個 Block 做一次 xor,最後根據每個 bit 輸出結果

逆完把邏輯丟給 ChatGPT 再修一下 Spec 就能生出解題腳本了

import sys 
MASK = 0xFFFFFFFFFFFFFFFF  
def slurp_blocks(stdin_lines): 
    rows = [ln.rstrip('\n') for ln in stdin_lines if len(ln.rstrip('\n')) == 8] 
    return [rows[i:i + 8] for i in range(0, len(rows), 8)] 
 
def block_to_int(block): 
    v = 0 
    for r in range(8): 
        for c in range(8): 
            if block[r][c] == '*': 
                v |= 1 << (r * 8 + c) 
    return v 
 
def recover_orig(out_blocks): 
    orig = [out_blocks[0]] 
    for i in range(1, len(out_blocks)): 
        orig.append(((~out_blocks[i]) & MASK) ^ orig[i-1]) 
    return orig 

def main(): 
    out_blocks = [block_to_int(b) for b in slurp_blocks(sys.stdin)] 
 
    orig = recover_orig(out_blocks) 
    hex_string = '0x' + ''.join(f'{v:016x}' for v in orig) 
    print(hex_string) 
 
if __name__ == '__main__': 
    main()

Bridge#

apk 裡有 encode 跟 decode 能 call,給 encode 過的結果,flag 是 decode 的結果

看到 apk 就拿出 android 死人三件套

  • jadx
  • apktool
  • frida

這次只用到 jadx 跟 frida 而已
從 Mainactivity 往上追

alt text alt text alt text 追到最後會發現 load 一個 Library alt text 要逆這個光聽就覺得麻煩了,所以還是動態解就好

把 Frida server 推到模擬器上面待機之後
Attach 並執行腳本 call decode 就好了

frida -U -p <bridge_pid> -l .\solve.js
Java.perform(function() {
    var BridgeLib = Java.use('com.hacktheon.bridge.BridgeLib');
    console.log('Hooked');

    var enc = "4658hg76<h85eed73ihghidi8ehf<78;";
    var decoded = BridgeLib.decode(enc);
    console.log('Flag: ' + decoded);
});

I love reversing#

Pyinstaller 打包的 exe 檔,做了甚麼不重要

自從上次打 EOF Final 被 pyc 坑慘之後就一直記著這個西方神秘網站 PyLingual 再搭配 pyinstxtractor 就能解了

先用 pyinstxteactor 拆出 .pyc 檔案,再用 PyLingual 還原回 Python 檔

根據題目說明可以知道 flag 就是 2.593627

# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: infect.py
# Bytecode version: 3.12.0rc2 (3531)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import requests
from flask import Flask, request, jsonify
app = Flask(__name__)

def infect(location_data):
    location_data['latitude'] += 2.593627
    location_data['longitude'] += 2.593627
    return location_data

@app.route('/location_data', methods=['POST'])
def location_data():
    location_data = request.json
    print('Received data from attack instruction PC:', location_data)
    location_data = infect(location_data)
    url = 'http://192.168.101.101:4653/location_data'
    response = requests.post(url, json=location_data)
    print('Response from ship node:', response.text)
    return jsonify({'message': 'Data forwarded to ship node successfully!'})
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=4653)

Forensics#

Shadow of the System#

給了一個 SYSTEM 檔案,要求找到可疑行為後印出的字串

用 RegExp.exe 可以在 ControlSet001 底下找到很多 Service,根本看不完

alt text

查 cmd.exe 也一堆
最後從題目敘述通靈搜 backdoor 找到了

alt text

註:有試過直接 grep 找不到,後來發現要搜 unicode

Watch#

給了 Cache0000.bin 要還原出當時 RDP notepad 寫的字串

可以用 BMC-Tool

python3 bmc-tools/bmc-tools.py -s rdp/Cache0000.bin -d output -b

可以找到這個
alt text

註: -b 可以自動拼成大圖

Hidden message#

給張圖,要找隱藏訊息

StegOnline 可以簡單解掉
Extract LSB 就可以看到 flag alt text

後記#

感覺下次可以報 Advanced,好像比較容易進決賽

唉真的越來越菜,要多打一點 CTF 了
拜託揪我

Hacktheon Sejong 2025 Write-up
https://flydragonw.github.io/posts/hacktheon_2025/
Author
Flydragon
Published at
2025-04-26