FIC26初赛 WriteUP

总分: 478

排名: 40

原队友考研去了,新队伍还在过渡期服务器取证还没成型(还有一些wp懒得写了(雾

计算机取证

1. 分析计算机检材,操作系统版本号为

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

【参考格式:1.1】

23.1

image-20260425130401301

2. 分析计算机检材,李安弘曾收到一份免费领取token的邮件的疑似钓鱼邮件,其发送用户邮箱为

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

【参考格式:123@qq.com

hf13338261292@outlook.com

image-20260425130618845

3. 分析计算机检材,李安弘电脑中记录的黄金换现金的商家联系方式为11.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

【参考格式:110】

13612817854

image-20260425151146592

4. 分析计算机检材,推广设计图中的apk下载链接为13.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

【参考格式:http:///?*】

https://drive.google.com/file/d/1z3aRS-lkaJYKm7Cp1XjtUmVPsOEVW2fV/view?usp=sharing

image-20260425135701496

5. 分析计算机检材,李安弘电脑vpn软件开放的代理端口为10.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

【参考格式:80】

9527

image-20260425133257153

6. 分析计算机检材,李安弘电脑中AI软件当前使用的模型类型为11.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

【参考格式:deepseek】

OpenRouter

image-20260425142026131

7. 分析计算机检材,李安弘电脑中AI软件当前使用的模型apiKey为12.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

【参考格式:sk-abcd…】

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

{“apiKey”:”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”,”apiSecret”:””,”appId”:””,”socketProxy”:{“host”:””,”pass”:””,”port”:0,”socketProxyType”:0,”user”:””}}

8. 分析计算机检材,李安弘电脑中勒索软件提供的解密服务联系方式为11.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

【参考格式:abcd123232】

beijixin996@tutanota.com

样本 get_token_linux 提供的解密服务联系方式是:

beijixin996@tutanota.com

证据级别:confirmed。

依据:

  • 该邮箱由 main.main 中硬编码立即数拼接而成;
  • 拼接后的字符串被传给 runtime.slicebytetostring
  • 随后传给 fmt.Fprint 输出到 os.Stdout
  • 字符串完整内容为 解密请 联系beijixin996@tutanota.com

手机取证

12. 上述APP启动后会加载一个色情网站。请找出该APP当网络不可用时APP加载的本地离线页面路径。12.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

file:///android_asset/www/index.html

证据:

smali_out\com\livevideo\hotclub\MainActivity.smalionCreate() 判断网络状态。联网时加载:

https://www.sp-live88.com

网络不可用时加载:

file:///android_asset/www/index.html

相关 smali 逻辑:

const-string v0, "https://www.sp-live88.com"
...
const-string v0, "file:///android_asset/www/index.html"
invoke-virtual { p1, v0 }, Landroid/webkit/WebView;->loadUrl(Ljava/lang/String;)V

13. 上述APP将非法收集的用户隐私数据上传至远程服务器。上传地址在代码中经过编码处理。请找出编码方式,还原出完整的上传服务器URL。12.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

https://api.sp-live88.com/collect/userdata

答案:

编码方式:Base64
完整上传服务器URL:https://api.sp-live88.com/collect/userdata

证据:

smali_out\com\livevideo\hotclub\collector\DataUploader.smali 中有:

const-string v2, "aHR0cHM6Ly9hcGkuc3AtbGl2ZTg4LmNvbS9jb2xsZWN0L3VzZXJkYXRh"
const/4 v3, 2
invoke-static { v2, v3 }, Landroid/util/Base64;->decode(Ljava/lang/String;I)[B

还原:

import base64
s = "aHR0cHM6Ly9hcGkuc3AtbGl2ZTg4LmNvbS9jb2xsZWN0L3VzZXJkYXRh"
print(base64.b64decode(s).decode())

输出:

https://api.sp-live88.com/collect/userdata

14. 该APP在本地创建了SQLite数据库存储收集到的用户信息。请分析代码,写出用于存储用户信息的表名13.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

user_collection

答案:

user_collection

证据:

使用 skill 的 dex_strings.py 搜索 contact

python -X utf8 C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\dex_strings.py .\HotClub_v2.1.6.apk --dex classes.dex --query contact --max-hits 100

输出包含建表语句:

CREATE TABLE IF NOT EXISTS user_collection (
  _id INTEGER PRIMARY KEY AUTOINCREMENT,
  device_id TEXT,
  imei TEXT,
  phone_number TEXT,
  contacts_data TEXT,
  sms_data TEXT,
  location_lat REAL,
  location_lng REAL,
  collect_time INTEGER DEFAULT (strftime('%s','now') * 1000)
)

15. 该APP的assets目录中存在一个加密配置文件config.dat。请解密该文件,写出其中的USDT钱包地址14.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

TXqH7sVn8bR4kL2mN9pW6xJ3cY5dF1gA

答案:

TXqH7sVn8bR4kL2mN9pW6xJ3cY5dF1gA

证据:

先用 rg 定位 config.dat 解密逻辑:

rg -n "config|config.dat|config_seed|Asset|AES|Cipher|USDT|wallet|decrypt|Base64" .\smali_out\com .\smali_out\androidx .\smali_out\m0

结果定位到 smali_out\androidx\lifecycle\l0.smali

const-string v0, "config.dat"
...
const-string v2, "config_seed"
...
const-string v1, "AES/ECB/PKCS5Padding"
...
const-string v3, "AES"

同文件 t(Ljava/lang/String;)[B 说明密钥派生方式:

MD5(config_seed) 的十六进制字符串前 16 字节作为 AES key

resources.arsc 字符串中找到:

config_seed = hotclub_2026_sec

实际解密代码:

from pathlib import Path
import hashlib
from Crypto.Cipher import AES

seed = "hotclub_2026_sec"
key = hashlib.md5(seed.encode()).hexdigest()[:16].encode()
ct = Path("apk_unpacked/assets/config.dat").read_bytes()
pt = AES.new(key, AES.MODE_ECB).decrypt(ct)
pad = pt[-1]
print(pt[:-pad].decode("utf-8"))

关键输出:

{
  "name": "USDT 捐赠通道",
  "url": "https://pay.usdt-donate.cc/hotclub",
  "type": "crypto",
  "wallet": "TXqH7sVn8bR4kL2mN9pW6xJ3cY5dF1gA"
}

16. 该APP前端JS代码可以直接调用Android原生方法获取用户隐私数据。请分析暴露了哪些方法用于获取通讯录?12.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

getContactsList

答案:

getContactsList

证据 1:assets/www/index.html 中前端直接调用:

contacts: window.AppNative.getContactsList()

证据 2:MainActivity.smali 注册 JavaScriptInterface 名称:

new-instance v2, Lcom/livevideo/hotclub/bridge/NativeBridge;
const-string v3, "AppNative"
invoke-virtual { p1, v2, v3 }, Landroid/webkit/WebView;->addJavascriptInterface(Ljava/lang/Object;Ljava/lang/String;)V

证据 3:NativeBridge.smaligetContactsList()@JavascriptInterface,并查询通讯录:

.method public getContactsList()Ljava/lang/String;
  .annotation runtime Landroid/webkit/JavascriptInterface;
  .end annotation
  ...
  sget-object v3, Landroid/provider/ContactsContract$CommonDataKinds$Phone;->CONTENT_URI:Landroid/net/Uri;
  ...
  const-string v1, "display_name"
  const-string v1, "data1"
.end method

17. 当主上传服务器不可达时,APP会获取备用服务器地址。请分析备用服务器的完整域名和端口14.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

a.b.c:80

backup.sp-live88.xyz:8443

答案:

backup.sp-live88.xyz:8443

补充:native 方法 getBackupEndpoint() 还会拼出备用基础地址:

https://backup.sp-live88.xyz:8443/api/v2

DataUploader.smali 在主服务器失败后再拼接上传路径:

/collect/userdata

因此备用上传 URL 为:

https://backup.sp-live88.xyz:8443/api/v2/collect/userdata

证据:

DataUploader.smali

.method private native getBackupEndpoint()Ljava/lang/String;
...
const-string v0, "/collect/userdata"
...
invoke-direct { p0 }, Lcom/livevideo/hotclub/collector/DataUploader;->getBackupEndpoint()Ljava/lang/String;
...
invoke-virtual { v1, v0 }, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;

libsecurity.so 使用 skill 脚本分析:

python -X utf8 C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\jni_symbol_disasm.py .\apk_unpacked\lib\arm64-v8a\libsecurity.so --symbol JNI_OnLoad --max-insn 120
python -X utf8 C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\elf_func_dump.py .\apk_unpacked\lib\arm64-v8a\libsecurity.so --symbol JNI_OnLoad --full-func --show-data --xref-imports

JNI_OnLoad 注册 DataUploader 的 native 方法,.rodata 中有:

com/livevideo/hotclub/collector/DataUploader
getBackupEndpoint
getCommKey
https
/api/v2
%s://%s%s

还原 native 中加密字符串使用的代码:

from pathlib import Path
from elftools.elf.elffile import ELFFile

p = Path("apk_unpacked/lib/arm64-v8a/libsecurity.so")

def dec(blob):
    b = bytearray(blob)
    n = len(b)
    for i in range(n):
        b[i] ^= (i & 0xf)
        b[i] ^= 0x55
    if n >= 8:
        tbl = [0xdeadbeef, 0xcafebabe, 0x12345678, 0x9abcdef0]
        delta = 0x61c8864f
        for bi in range(n // 8):
            off = bi * 8
            v0 = int.from_bytes(b[off:off+4], "little")
            v1 = int.from_bytes(b[off+4:off+8], "little")
            s1 = 0x8dde6c40
            s2 = 0xefa6f28f
            for _ in range(64):
                t = ((((v0 << 4) & 0xffffffff) ^ (v0 >> 5)) + v0) & 0xffffffff
                v1 = (v1 - (t ^ ((s1 + tbl[(s1 >> 11) & 3]) & 0xffffffff))) & 0xffffffff
                s1 = (s1 + delta) & 0xffffffff
                t = ((((v1 << 4) & 0xffffffff) ^ (v1 >> 5)) + v1) & 0xffffffff
                v0 = (v0 - (t ^ ((s2 + tbl[s2 & 3]) & 0xffffffff))) & 0xffffffff
                s2 = (s2 + delta) & 0xffffffff
            b[off:off+4] = v0.to_bytes(4, "little")
            b[off+4:off+8] = v1.to_bytes(4, "little")
    pad = b[-1]
    out_len = n - pad if n - pad >= 0 else n
    return bytes(b[:out_len])

with p.open("rb") as f:
    elf = ELFFile(f)
    ro = elf.get_section_by_name(".rodata")
    base = ro["sh_addr"]
    data = ro.data()

    backup_blob = data[0xab0 - base:0xab0 - base + 0x20]
    commkey_blob = data[0xad0 - base:0xad0 - base + 0x18]

print(dec(backup_blob).decode())
print(dec(commkey_blob).decode())

输出:

backup.sp-live88.xyz:8443
K7mP9xQ2wR4vN8sL

二进制程序部分

1. 分析u盘检材,找到其中保存的加密程序SampleVC.exe,请给出这个exe程序的md5值?9.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

(答案格式:c4ca4238a0b923820dcc509a6f75849b)

764789dd9c095d74b6b258cf0f7568b2

image-20260425132634061

2. 分析SampleVC.exe,该程序编译的日期可能是什么?10.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

(答案格式:2025-06-06)

2026-04-17

读取 PE COFF TimeDateStamp

python -X utf8 -c "import struct,datetime; p='SampleVC.exe'; b=open(p,'rb').read(); pe=struct.unpack_from('<I',b,0x3c)[0]; ts=struct.unpack_from('<I',b,pe+8)[0]; print(hex(ts), datetime.datetime.utcfromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S UTC'))"

输出:

0x69e1cad0 2026-04-17 05:53:20 UTC

3. 分析SampleVC.exe,正确的密码是什么14.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

(答案格式:abcdefghABCDEFGH)

PleaseRunAsAdmin

读取指定 RVA 原始字节的辅助代码:

@'
import struct
b=open('SampleVC.exe','rb').read()
pe=struct.unpack_from('<I',b,0x3c)[0]
ns=struct.unpack_from('<H',b,pe+6)[0]
opt=struct.unpack_from('<H',b,pe+20)[0]
sh=pe+24+opt
rva=0x8950
off=None
for i in range(ns):
    name=b[sh+i*40:sh+i*40+8].rstrip(b'\0')
    vs,va,rawsz,raw=struct.unpack_from('<IIII',b,sh+i*40+8)
    if va<=rva<va+max(vs,rawsz):
        off=raw+(rva-va)
        print(name,hex(va),hex(vs),hex(raw),hex(rawsz),'off',hex(off))
print(b[off:off+192].hex(' '))
'@ | python -X utf8 -

还原密码的辅助代码:

@'
from Crypto.Cipher import AES

ct = bytes.fromhex('afb977ac242ad60cf42461ad72ca5149')
key = bytes.fromhex('0123456789abcdef0123456789abcdef')
mask = bytes((0x7f + 3*i) & 255 for i in range(16))
plain_xored = AES.new(key, AES.MODE_ECB).decrypt(ct)
password = bytes(a ^ b for a, b in zip(plain_xored, mask))
print(password)
'@ | python -X utf8 -

验证代码:

python -X utf8 -c "from Crypto.Cipher import AES; pt=b'PleaseRunAsAdmin'; key=bytes.fromhex('0123456789abcdef0123456789abcdef'); mask=bytes((0x7f+3*i)&255 for i in range(16)); print(AES.new(key,AES.MODE_ECB).encrypt(bytes(a^b for a,b in zip(pt,mask))).hex())"

输出:

afb977ac242ad60cf42461ad72ca5149

按小端字节解释为:

01 23 45 67 89 ab cd ef 01 23 45 67 89 ab cd ef

加密前每个密码字节还会异或:

mask[i] = (0x7f + 3*i) & 0xff

所以解密流程为:

  1. 对目标密文用 AES-128-ECB 和 key 0123456789abcdef0123456789abcdef 解密。
  2. 将结果逐字节异或 0x7f,0x82,0x85,...
  3. 得到 16 字节可打印密码。

结果:

PleaseRunAsAdmin

4. 分析u盘检材,利用SampleVC.exe解密U盘中被加密的文件,解密后的文件的后缀是什么?12.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

(答案格式:exe)

vhd

用户补充说明目录下的 vc 是由 SampleVC.exe 加密的文件。继续复用 ctf-reverse 的 PE 分析结果定位文件流处理逻辑。

补充调用的 skill 脚本:

python -X utf8 C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\pe_cg.py .\SampleVC.exe 0x1cf0 1 --full-ret --caller-prep
python -X utf8 C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\pe_cg.py .\SampleVC.exe 0x1ad0 1 --full-ret --caller-prep
python -X utf8 C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\pe_cg.py .\SampleVC.exe 0x205b 1 --full-ret --caller-prep
python -X utf8 C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\pe_cg.py .\SampleVC.exe 0x20fd 1 --full-ret --caller-prep

关键结论:

  • 0x140001e90..0x140001ed8 是 RC4 KSA,S 盒为 0..255,key 下标为 i & 0xf,即 16 字节 key。
  • 0x140001f10..0x140001f9d 是 RC4 PRGA,对每次读入的 0x1000 字节进行异或。
  • key 为前面恢复出的 16 字节密码:PleaseRunAsAdmin

试解头部的辅助代码:

@'
from pathlib import Path

def rc4(data, key):
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i & 0xf]) & 0xff
        S[i], S[j] = S[j], S[i]
    i = j = 0
    out = bytearray()
    for b in data:
        i = (i + 1) & 0xff
        j = (j + S[i]) & 0xff
        S[i], S[j] = S[j], S[i]
        out.append(b ^ S[(S[i] + S[j]) & 0xff])
    return bytes(out)

enc = Path('vc').read_bytes()[:512]
dec = rc4(enc, b'PleaseRunAsAdmin')
print(dec[:64].hex(' '))
'@ | python -X utf8 -

输出头部为标准 MBR 引导代码开头:

33 c0 8e d0 bc 00 7c 8e c0 8e d8 be 00 7c bf 00 ...

生成完整解密文件的辅助代码:

@'
from pathlib import Path

def rc4_stream_file(src_path, dst_path, key):
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i & 0xf]) & 0xff
        S[i], S[j] = S[j], S[i]
    i = j = 0
    total = 0
    with open(src_path, 'rb') as src, open(dst_path, 'wb') as dst:
        while True:
            chunk = src.read(0x1000)
            if not chunk:
                break
            out = bytearray(len(chunk))
            for n, b in enumerate(chunk):
                i = (i + 1) & 0xff
                j = (j + S[i]) & 0xff
                S[i], S[j] = S[j], S[i]
                out[n] = b ^ S[(S[i] + S[j]) & 0xff]
            dst.write(out)
            total += len(out)
    return total

key = b'PleaseRunAsAdmin'
out = 'vc_decrypted.vhd'
total = rc4_stream_file('vc', out, key)
print(out)
print(total)
print(Path(out).read_bytes()[:64].hex(' '))
'@ | python -X utf8 -

生成文件:

vc_decrypted.vhd

校验:

@'
from pathlib import Path
p=Path('vc_decrypted.vhd')
b=p.read_bytes()
for off,n in [(0,512),(510,2),(len(b)-512,512)]:
    s=b[off:off+n]
    print(hex(off), s[:80].hex(' '), repr(s[:80]))
'@ | python -X utf8 -

python -X utf8 -c "from pathlib import Path; b=Path('vc_decrypted.vhd').read_bytes(); print(b.find(b'conectix')); print(b[-512:].find(b'conectix'))"

校验结果:

  • 0x1fe 处为 MBR 签名 55 aa
  • 文件末尾 512 字节开头为 VHD cookie:conectix
  • conectix 位于偏移 10485760,即最后一个 512 字节扇区起始处

5. 分析u盘检材,找到被加密的交易记录,统计李安弘虚拟币收款地址钱包总收款金额为14.00 分

【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】

【参考格式:1.00】

186948.09

image-20260425134450244