FIC26初赛 WriteUP
FIC26初赛 WriteUP
总分: 478
排名: 40
原队友考研去了,新队伍还在过渡期服务器取证还没成型(还有一些wp懒得写了(雾
计算机取证
1. 分析计算机检材,操作系统版本号为
【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】
【参考格式:1.1】
23.1

2. 分析计算机检材,李安弘曾收到一份免费领取token的邮件的疑似钓鱼邮件,其发送用户邮箱为
【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】
【参考格式:123@qq.com】

3. 分析计算机检材,李安弘电脑中记录的黄金换现金的商家联系方式为11.00 分
【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】
【参考格式:110】
13612817854

4. 分析计算机检材,推广设计图中的apk下载链接为13.00 分
【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】
【参考格式:http:///?*】
https://drive.google.com/file/d/1z3aRS-lkaJYKm7Cp1XjtUmVPsOEVW2fV/view?usp=sharing

5. 分析计算机检材,李安弘电脑vpn软件开放的代理端口为10.00 分
【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】
【参考格式:80】
9527

6. 分析计算机检材,李安弘电脑中AI软件当前使用的模型类型为11.00 分
【不区分大小写】【不区分空格】【不区分换行符 (不考虑末尾)】【不区分全半角】
【参考格式:deepseek】
OpenRouter

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】
样本 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.smali 中 onCreate() 判断网络状态。联网时加载:
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.smali 中 getContactsList() 带 @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

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
所以解密流程为:
- 对目标密文用 AES-128-ECB 和 key
0123456789abcdef0123456789abcdef解密。 - 将结果逐字节异或
0x7f,0x82,0x85,...。 - 得到 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

