案情简介
某日,警方接到举报,举报人称近期互联网上出现了一涉黄网站极为活跃并大肆推广。警方跟进线索分析后,找到相应的网站进行了摸排调查,最终锁定网站的运营者李安弘,警方在对其实施抓捕的现场对电子数据进行了提取固定。
通过对李安弘的审讯,警方了解到,其雇佣了技术人员帮忙架设淫秽视频平台,并找到境外团队对网站进行推广,经过审讯和调查,该嫌疑人还有多种其他违法行为,用以牟利。
请各位参赛选手对检材进行分析,尝试还原整个案件和关键信息。
(本故事纯属虚构,仅为考试设计,可能会出现各检材间时间细节无法对齐现象,请忽略)
容器密码
FIC-{e404d6e66586e9460c23755afab5a872bcf78ab4}
计算机部分
从来没见过的Deepin操作系统,应该是基于linux内核的
好多记录了答案的应用不在桌面上,要在下方任务栏找

有点恶心
1. 分析计算机检材,操作系统版本号为
23.1

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

注意不要跟上面那个回复:Token 限时免费领的邮件搞混了
3. 分析计算机检材,李安弘电脑中记录的黄金换现金的商家联系方式为
136 1281 7854
这种一般都是记录在记事本、便签之类的应用里面,但是找了半天没找到
最后在下方的启动器中找到了语音记事本

4. 分析计算机检材,推广设计图中的apk下载链接为
https://drive.google.com/file/d/1z3aRS-lkaJYKm7Cp1XjtUmVPsOEVW2fV/view?usp=sharing
在邮件中发现与推广设计图相关的邮件

在仿真中通过启动器找到邮箱,找到这封邮件

还有3个附件,统统下载下来

看起来推广设计图被加密了,先看一看加密图片查看.html

图片应该是被rsa加密了,那么public.txt应该给出了rsa相关参数

public.txt给出了rsa模数:
57751892008149574447756694613209346511056045951970458143905594411398554113111623746466692172544473909892773600617029641656248235151775166339061269972238018743173330948084699695182438765935110193323089354031112350869626121317836465551360104372140181097747761558797918522051881262043738603183528521379831286761
和公钥指数:
65537
根据html,我们需要根据和恢复出私钥指数
回顾一下rsa:
- 随机选取两个不同的大质数和
- 计算模数
- 计算欧拉函数
- 选取公钥指数,满足,且,即互质,一般取65537
- 计算私钥指数
即为在模的乘法逆元
这里已知了和,就可以尝试分解出和,计算出,进而计算出
本题存在的漏洞是和比较接近,可以用Fermat法分解出来
上脚本
from math import isqrt, gcd
from random import randrange
import time
import sys
# =========================
# RSA 参数
# =========================
n = 57751892008149574447756694613209346511056045951970458143905594411398554113111623746466692172544473909892773600617029641656248235151775166339061269972238018743173330948084699695182438765935110193323089354031112350869626121317836465551360104372140181097747761558797918522051881262043738603183528521379831286761
e = 65537
# =========================================================
# 1. 小素数试除
# =========================================================
def trial_division(n, limit=100000):
print("[*] Stage 1: small prime trial division")
for i in range(2, limit):
if n % i == 0:
return i, n // i
return None
# =========================================================
# 2. Fermat 分解(p≈q)
# =========================================================
def fermat_factor(n):
print("[*] Stage 2: Fermat factorization")
a = isqrt(n)
if a * a < n:
a += 1
step = 0
while True:
b2 = a * a - n
b = isqrt(b2)
if b * b == b2:
p = a - b
q = a + b
if p * q == n:
return p, q
a += 1
step += 1
if step % 200000 == 0:
print(f" iter={step}")
# =========================================================
# 3. Pollard Rho(通用)
# =========================================================
def pollard_rho(n):
print("[*] Stage 3: Pollard Rho")
if n % 2 == 0:
return 2
while True:
x = randrange(2, n - 1)
y = x
c = randrange(1, n - 1)
d = 1
while d == 1:
x = (pow(x, 2, n) + c) % n
y = (pow(y, 2, n) + c) % n
y = (pow(y, 2, n) + c) % n
d = gcd(abs(x - y), n)
if d != n:
return d
# =========================================================
# 主流程
# =========================================================
start = time.time()
p = q = None
# -------------------------
# Stage 1
# -------------------------
res = trial_division(n)
if res:
p, q = res
print("[+] Found by trial division")
# -------------------------
# Stage 2
# -------------------------
if not p:
res = fermat_factor(n)
if res:
p, q = res
print("[+] Found by Fermat")
# -------------------------
# Stage 3
# -------------------------
if not p:
factor = pollard_rho(n)
p = factor
q = n // factor
print("[+] Found by Pollard Rho")
# =========================================================
# 输出 RSA 私钥
# =========================================================
print("\n==============================")
print("[+] p =", p)
print("[+] q =", q)
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
print("[+] d =", d)
print("==============================")
print(f"[*] time = {time.time() - start:.2f}s")
'''
输出结果:
[*] Stage 1: small prime trial division
[*] Stage 2: Fermat factorization
[+] Found by Fermat
==============================
[+] p = 7599466560762641743052493422970151283281211357103780410638724588686758080375558505939025432223250000657857469723929623791715127854747607854590078838861499
[+] q = 7599466560762641743052493422970151283281211357103780410638724588686758080375558505939025432223250000657857469723929623791715127854747607854590078838861739
[+] d = 35523357349779905170178187485659935251135867134837314931979222758324282670976025397371947065926313872401811028702468817834912779886756496413632572670718195811151125658055182908097177746721685442556966643339035563906268671458582959248629716030078694677882031903468615733997083390985840508421177858054952995297
==============================
[*] time = 0.02s
'''
用html解密一下

还是张涩图,扫个码就能看到链接

5. 分析计算机检材,李安弘电脑vpn软件开放的代理端口为
9527
在启动器里面找到vpn软件Clash Verge,首页里面就有

设置里面也能看到

在控制中心-网络-系统代理里面也能看到

6. 分析计算机检材,李安弘电脑中AI软件当前使用的模型类型为
OpenRouter
在下方任务栏中找到UOS AI

在设置中能找到模型类型

7. 分析计算机检材,李安弘电脑中AI软件当前使用的模型apiKey为
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
从上一题的图片中我们能看到apiKey,但并不完整
只能尝试找UOS Ai的数据库,搜索一下UOS
最后找到数据库/var/home/lha/.local/share/deepin/uos-ai-assistant/db/basic

在llm表的account_proxy字段找到apiKey

8. 分析计算机检材,李安弘电脑中勒索软件提供的解密服务联系方式为
找了半天都没找可以软件,只能猜测藏在被加密的分区里面了
刚好在手机的便签发现了一张图片

其中记录的vc容器密码为9ed2@99y8.com.cn,直接拿去解密被加密的分区
解密完成后看到一个.hidden文件很可疑,看一看是什么

文件中的两个文件202603121310.mp4和get_token_linux,在这个加密分区中都有
其中202603121310.mp4只有声音没有画面,应该已经被勒索软件加密了
看一看get_token_linux

通过文件头可以看出这是一个.elf可执行文件,基本可以确定是勒索软件了
逆向!!!
放到ida里面,找了一圈没什么可用信息,直接来看main_main()函数
// main.main
void __golang main_main()
{
char v0; // cl
void *v1; // rax
string_0 *array; // rax
int len; // rbx
uint8 v4[8]; // [rsp+1h] [rbp-65h] BYREF
unsigned __int64 v5; // [rsp+9h] [rbp-5Dh]
_QWORD v6[3]; // [rsp+11h] [rbp-55h] BYREF
uint8 ptr[5]; // [rsp+29h] [rbp-3Dh] BYREF
int v8; // [rsp+2Eh] [rbp-38h]
string_0 *v9; // [rsp+36h] [rbp-30h]
_OWORD a[2]; // [rsp+3Eh] [rbp-28h] BYREF
retval_4AADE0 v11; // 0:kr20_40.40
string_0 v12; // 0:rax.8,8:rbx.8
string_0 v13; // 0:rax.8,8:rbx.8
io_Writer_0 v14; // 0:rax.8,8:rbx.8
io_Writer_0 v15; // 0:rax.8,8:rbx.8
_slice_interface_ v16; // 0:rcx.8,8:rdi.16
_slice_interface_ v17; // 0:rcx.8,8:rdi.16
qmemcpy(ptr, "*.mp4", sizeof(ptr));
v12 = runtime_slicebytetostring(0, ptr, 5);
v11 = path_filepath_globWithLimit(v12, 0);
array = v11.matches.array;
len = v11.matches.len;
if ( !v11.err.tab && v11.matches.len )
{
v0 = 0;
while ( len > 0 )
{
v8 = len;
v9 = array;
v0 |= main__a(*array, 0x539u);
len = v8 - 1;
array = v9 + 1;
}
if ( v0 )
{
*(_QWORD *)v4 = 0xAFE886AFE5A3A7E8LL;
v5 = 0xBBB3E7BBB3E720B7LL;
qmemcpy(v6, "beijixin996@tutanota.com", sizeof(v6));
v13 = runtime_slicebytetostring(0, v4, 40);
a[1] = 0;
v1 = runtime_convTstring(v13);
*(_QWORD *)&a[1] = &RTYPE_string;
*((_QWORD *)&a[1] + 1) = v1;
v14.data = os_Stdout;
v14.tab = (internal_abi_ITab *)&go_itab__ptr_os_File_comma_io_Writer;
v16.array = (interface__0 *)&a[1];
v16.len = 1;
v16.cap = 1;
fmt_Fprint(v14, v16);
time_Sleep(1000000000);
*(_QWORD *)&a[0] = &RTYPE_string;
*((_QWORD *)&a[0] + 1) = &off_4FCB10;
v15.data = os_Stdout;
v15.tab = (internal_abi_ITab *)&go_itab__ptr_os_File_comma_io_Writer;
v17.array = (interface__0 *)a;
v17.len = 1;
v17.cap = 1;
fmt_Fprint(v15, v17);
}
}
}
可以看到有个邮箱字符串,应该就是解密服务的联系方式
9.分析计算机检材,李安弘电脑中记录的存放黄金的保险柜编号是
997546
显然这道题需要解密被加密的视频,因此需要进一步逆向分析勒索软件
简要分析一下上面的main_main()函数,程序会搜索电脑中的.mp4视频文件,调用main__a()函数对视频进行加密,跟进main__a()函数
// main._a
bool __golang main__a(string_0 _b, uint32 _c)
{
int v2; // rax
int v3; // rsi
unsigned __int64 v4; // rcx
int v5; // rdi
__int128 v6; // kr00_16
unsigned __int32 v7; // r9d
unsigned __int32 i; // edx
unsigned __int32 v10; // r11d
__int64 v11; // rdx
unsigned __int64 v12; // r12
unsigned __int64 v13; // r13
_DWORD sep[9]; // [rsp+0h] [rbp-24h] BYREF
_slice_uint8_0 v17; // 0:rdi.24
_slice_uint8_0 v18; // 0:kr48_24.24
retval_4A0100 File; // 0:kr20_40.40
File = os_ReadFile(_b);
if ( File._r1.tab )
return 0;
*(_QWORD *)&sep[5] = File._r0.array;
*(_QWORD *)&sep[3] = File._r0.cap;
*(_QWORD *)&sep[1] = File._r0.len;
sep[0] = 1868788851;
v17.array = (uint8 *)sep;
v17.len = 4;
v17.cap = 4;
v2 = bytes_Index(File._r0, v17);
if ( v2 == -1 )
return 0;
v4 = v2 + 12;
v5 = *(_QWORD *)&sep[1];
if ( *(__int64 *)&sep[1] < v2 + 12 )
return 0;
if ( *(_QWORD *)&sep[3] < v4 )
runtime_panicSliceAcap();
if ( v4 < v2 + 8 )
runtime_panicSliceB();
v6 = *(_OWORD *)&sep[3];
v7 = _byteswap_ulong(*(_DWORD *)(*(_QWORD *)&sep[5] + ((v2 + 8) & ((8 - (*(_QWORD *)&sep[3] - v2)) >> 63))));
if ( *(__int64 *)&sep[1] < v2 + 4LL * v7 + 12 )
return 0;
for ( i = 0; i < v7; i = v10 + 1 )
{
v10 = i;
v11 = 4 * i;
v12 = v2 + v11 + 16;
v13 = v2 + v11 + 12;
if ( (unsigned __int64)v6 < v12 )
runtime_panicSliceAcap();
if ( v12 < v13 )
runtime_panicSliceB();
*(_DWORD *)(*((_QWORD *)&v6 + 1) + (v13 & ((12 - ((__int64)v6 - (v2 + v11))) >> 63))) = _byteswap_ulong(
_c
+ _byteswap_ulong(*(_DWORD *)(*((_QWORD *)&v6 + 1) + (v13 & ((12 - ((__int64)v6 - (v2 + v11))) >> 63)))));
}
v18.array = (uint8 *)*((_QWORD *)&v6 + 1);
v18.len = v5;
v18.cap = v3;
return (unsigned __int64)os_WriteFile(_b, v18, 0x1A4u).tab == 0;
}
加密逻辑很简单,就是对mp4文件的一个“索引表”加上了一个固定偏移0x539(也就是1337),导致播放器去错误的位置读取视频数据

解密只需要把偏移量减去0x539即可
请ai写脚本解密.hidden文件中提到的202603121310.mp4
import struct
# ===== 在这里填写被加密的 MP4 文件路径 =====
INPUT_PATH = "202603121310.mp4"
OUTPUT_PATH = "202603121310_dec.mp4"
# ==========================================
OFFSET = 0x539 # 病毒加的偏移量 1337
def fix_mp4(input_path, output_path):
with open(input_path, 'rb') as f:
data = bytearray(f.read())
pos = data.find(b'stco')
if pos == -1:
print("未找到 stco,该文件不是被此病毒加密的 MP4")
return
entry_count, = struct.unpack('>I', data[pos + 8: pos + 12])
entries_start = pos + 12
for i in range(entry_count):
ep = entries_start + i * 4
old, = struct.unpack('>I', data[ep: ep + 4])
struct.pack_into('>I', data, ep, (old - OFFSET) & 0xFFFFFFFF)
with open(output_path, 'wb') as f:
f.write(data)
print(f"解密成功!输出文件:{output_path}")
fix_mp4(INPUT_PATH, OUTPUT_PATH)
input("按回车退出...")
解密出来一段ai视频

10.分析计算机检材,李安弘电脑中记录的保险柜密码是
583985
搜索保险,定位到/var/root/文档/zhongyao/保险箱的秘密.et

导出到本地看一看,发现是一个excel表格

只有无意义的点阵,可能又被加密了
在仿真的tool目录下找到了一个疑似用于加密excel的脚本

function GenerateStego() {
var _0x5e2b = "baidu.com";
var _0x4c1a = 85;
var _0x2f8d = Application.ActiveSheet;
if (_0x2f8d.Shapes.Count > 0) {
_0x2f8d.Shapes.SelectAll();
Application.Selection.ShapeRange.Delete();
}
var _0x1b4f = _0x3a9c(_0x5e2b);
for (var _0x1122 = 0; _0x1122 < _0x1b4f.length; _0x1122++) {
var _0x4455 = _0x1b4f[_0x1122];
var _0x1a3c = _0x4455.x + 100;
var _0x1a4d = _0x4455.y + 100;
var _0x5566 = ((_0x1a3c ^ _0x4c1a) * 1000) + (_0x1a4d ^ _0x4c1a);
var _0x7788 = Math.random() * 600;
var _0x8899 = Math.random() * 400;
var _0x99aa = _0x2f8d.Shapes.AddShape(1, _0x7788, _0x8899, 3, 3);
_0x99aa.AlternativeText = _0x5566.toString();
_0x99aa.Fill.ForeColor.RGB = Math.floor(Math.random() * 16777215);
_0x99aa.Line.Visible = false;
}
alert("\u5df2\u5c06 " + _0x5e2b + " \u5236\u4f5c\u4e3a " + _0x1b4f.length + " \u4e2a\u70b9\u9635\u3002");
}
function _0x3a9c(_0xde11) {
var _0xfe22 = [];
var _0xcc33 = 25;
var _0xfont = {
'0':[[1,0],[2,0],[0,1],[3,1],[0,2],[3,2],[0,3],[3,3],[1,4],[2,4]],
'1':[[2,0],[1,1],[2,1],[2,2],[2,3],[1,4],[2,4],[3,4]],
'2':[[1,0],[2,0],[0,1],[3,1],[2,2],[1,3],[0,4],[1,4],[2,4],[3,4]],
'3':[[0,0],[1,0],[2,0],[3,1],[1,2],[2,2],[3,3],[0,4],[1,4],[2,4]],
'4':[[3,0],[2,1],[3,1],[1,2],[3,2],[0,3],[1,3],[2,3],[3,3],[4,3],[3,4]],
'5':[[0,0],[1,0],[2,0],[0,1],[0,2],[1,2],[2,2],[3,3],[0,4],[1,4],[2,4]],
'6':[[1,0],[2,0],[0,1],[0,2],[1,2],[2,2],[0,3],[3,3],[1,4],[2,4]],
'7':[[0,0],[1,0],[2,0],[3,0],[3,1],[2,2],[1,3],[1,4]],
'8':[[1,0],[2,0],[0,1],[3,1],[1,2],[2,2],[0,3],[3,3],[1,4],[2,4]],
'9':[[1,0],[2,0],[0,1],[3,1],[1,2],[2,2],[3,2],[3,3],[2,4]],
'a':[[1,2],[2,2],[3,2],[0,3],[3,3],[1,4],[2,4],[3,4]],
'b':[[0,0],[0,1],[0,2],[1,2],[2,2],[0,3],[3,3],[0,4],[1,4],[2,4]],
'c':[[1,0],[2,0],[3,0],[0,1],[0,2],[0,3],[1,4],[2,4],[3,4]],
'd':[[3,0],[3,1],[1,2],[2,2],[3,2],[0,3],[3,3],[1,4],[2,4],[3,4]],
'e':[[1,0],[2,0],[0,1],[0,2],[1,2],[2,2],[0,3],[1,4],[2,4]],
'f':[[1,0],[2,0],[1,1],[0,2],[1,2],[2,2],[1,3],[1,4]],
'g':[[1,2],[2,2],[3,2],[0,3],[3,3],[1,4],[2,4],[3,4],[3,5],[1,6],[2,6]],
'h':[[0,0],[0,1],[0,2],[1,2],[2,2],[0,3],[3,3],[0,4],[3,4]],
'i':[[1,0],[1,2],[1,3],[1,4]],
'j':[[2,0],[2,2],[2,3],[2,4],[2,5],[1,6],[0,5]],
'k':[[0,0],[0,1],[0,2],[0,3],[0,4],[2,2],[1,3],[3,3],[2,4]],
'l':[[1,0],[1,1],[1,2],[1,3],[1,4]],
'm':[[0,1],[1,1],[2,1],[3,1],[4,1],[0,2],[2,2],[4,2],[0,3],[4,3]],
'n':[[0,1],[1,0],[2,0],[0,2],[3,2],[0,3],[3,3],[0,4],[3,4]],
'o':[[1,1],[2,1],[0,2],[3,2],[1,3],[2,3]],
'p':[[0,2],[1,2],[2,2],[0,3],[3,3],[0,4],[1,4],[2,4],[0,5],[0,6]],
'q':[[1,2],[2,2],[0,3],[3,2],[3,3],[3,4],[3,5],[4,5]],
'r':[[0,2],[0,3],[0,4],[1,2],[2,2]],
's':[[1,0],[2,0],[3,0],[0,1],[1,2],[2,2],[3,3],[0,4],[1,4],[2,4]],
't':[[1,0],[1,1],[1,2],[1,3],[1,4],[0,2],[2,2]],
'u':[[0,2],[0,3],[3,2],[3,3],[1,4],[2,4],[3,4]],
'v':[[0,0],[4,0],[1,2],[3,2],[2,4]],
'w':[[0,2],[0,3],[1,4],[2,3],[3,4],[4,2],[4,3]],
'x':[[0,0],[4,0],[1,1],[3,1],[2,2],[1,3],[3,3],[0,4],[4,4]],
'y':[[0,0],[4,0],[1,1],[3,1],[2,2],[2,3],[1,4]],
'z':[[0,0],[1,0],[2,0],[3,0],[2,1],[1,2],[0,3],[0,4],[1,4],[2,4],[3,4]],
':':[[1,1],[1,3]],
'@':[[1,0],[2,0],[0,1],[3,1],[0,2],[2,2],[3,2],[0,3],[1,4],[2,4]],
'.':[[1,4]]
};
for (var i = 0; i < _0xde11.length; i++) {
var _char = _0xde11[i];
var _matrix = _0xfont[_char] || [[0,0]];
for (var j = 0; j < _matrix.length; j++) {
_0xfe22.push({x: i * _0xcc33 + _matrix[j][0]*4, y: _matrix[j][1]*4});
}
}
return _0xfe22;
}
事实证明这并不是加密而是隐写
脚本里面的baidu.com只是一个演示用例,和解码结果没关系
脚本将原始字符串逐字符渲染为点阵图形,进一步根据坐标编码为一个数字,再在excel表格中创建大量彩色点阵,将数字写进每个点的文字属性中

让ai写个解码脚本

"""
隐写提取脚本 - 对应 GenerateStego() JS 脚本的逆向解码
支持 .et / .xls (OLE2) 和 .xlsx (OOXML) 格式
原理:
- 图形 AlternativeText 存储编码值 = ((x+100) ^ 85) * 1000 + ((y+100) ^ 85)
- 点坐标按列排布,字符间距 25px,像素缩放 4x
- 通过点阵字体字典匹配还原原始字符串
"""
import sys
import os
# ── 目标文件(直接修改这里) ───────────────────────────────────────────────
FILEPATH = r"保险箱的秘密.et"
# ── 字体字典(与 JS 完全一致) ──────────────────────────────────────────────
FONT = {
'0':[[1,0],[2,0],[0,1],[3,1],[0,2],[3,2],[0,3],[3,3],[1,4],[2,4]],
'1':[[2,0],[1,1],[2,1],[2,2],[2,3],[1,4],[2,4],[3,4]],
'2':[[1,0],[2,0],[0,1],[3,1],[2,2],[1,3],[0,4],[1,4],[2,4],[3,4]],
'3':[[0,0],[1,0],[2,0],[3,1],[1,2],[2,2],[3,3],[0,4],[1,4],[2,4]],
'4':[[3,0],[2,1],[3,1],[1,2],[3,2],[0,3],[1,3],[2,3],[3,3],[4,3],[3,4]],
'5':[[0,0],[1,0],[2,0],[0,1],[0,2],[1,2],[2,2],[3,3],[0,4],[1,4],[2,4]],
'6':[[1,0],[2,0],[0,1],[0,2],[1,2],[2,2],[0,3],[3,3],[1,4],[2,4]],
'7':[[0,0],[1,0],[2,0],[3,0],[3,1],[2,2],[1,3],[1,4]],
'8':[[1,0],[2,0],[0,1],[3,1],[1,2],[2,2],[0,3],[3,3],[1,4],[2,4]],
'9':[[1,0],[2,0],[0,1],[3,1],[1,2],[2,2],[3,2],[3,3],[2,4]],
'a':[[1,2],[2,2],[3,2],[0,3],[3,3],[1,4],[2,4],[3,4]],
'b':[[0,0],[0,1],[0,2],[1,2],[2,2],[0,3],[3,3],[0,4],[1,4],[2,4]],
'c':[[1,0],[2,0],[3,0],[0,1],[0,2],[0,3],[1,4],[2,4],[3,4]],
'd':[[3,0],[3,1],[1,2],[2,2],[3,2],[0,3],[3,3],[1,4],[2,4],[3,4]],
'e':[[1,0],[2,0],[0,1],[0,2],[1,2],[2,2],[0,3],[1,4],[2,4]],
'f':[[1,0],[2,0],[1,1],[0,2],[1,2],[2,2],[1,3],[1,4]],
'g':[[1,2],[2,2],[3,2],[0,3],[3,3],[1,4],[2,4],[3,4],[3,5],[1,6],[2,6]],
'h':[[0,0],[0,1],[0,2],[1,2],[2,2],[0,3],[3,3],[0,4],[3,4]],
'i':[[1,0],[1,2],[1,3],[1,4]],
'j':[[2,0],[2,2],[2,3],[2,4],[2,5],[1,6],[0,5]],
'k':[[0,0],[0,1],[0,2],[0,3],[0,4],[2,2],[1,3],[3,3],[2,4]],
'l':[[1,0],[1,1],[1,2],[1,3],[1,4]],
'm':[[0,1],[1,1],[2,1],[3,1],[4,1],[0,2],[2,2],[4,2],[0,3],[4,3]],
'n':[[0,1],[1,0],[2,0],[0,2],[3,2],[0,3],[3,3],[0,4],[3,4]],
'o':[[1,1],[2,1],[0,2],[3,2],[1,3],[2,3]],
'p':[[0,2],[1,2],[2,2],[0,3],[3,3],[0,4],[1,4],[2,4],[0,5],[0,6]],
'q':[[1,2],[2,2],[0,3],[3,2],[3,3],[3,4],[3,5],[4,5]],
'r':[[0,2],[0,3],[0,4],[1,2],[2,2]],
's':[[1,0],[2,0],[3,0],[0,1],[1,2],[2,2],[3,3],[0,4],[1,4],[2,4]],
't':[[1,0],[1,1],[1,2],[1,3],[1,4],[0,2],[2,2]],
'u':[[0,2],[0,3],[3,2],[3,3],[1,4],[2,4],[3,4]],
'v':[[0,0],[4,0],[1,2],[3,2],[2,4]],
'w':[[0,2],[0,3],[1,4],[2,3],[3,4],[4,2],[4,3]],
'x':[[0,0],[4,0],[1,1],[3,1],[2,2],[1,3],[3,3],[0,4],[4,4]],
'y':[[0,0],[4,0],[1,1],[3,1],[2,2],[2,3],[1,4]],
'z':[[0,0],[1,0],[2,0],[3,0],[2,1],[1,2],[0,3],[0,4],[1,4],[2,4],[3,4]],
':':[[1,1],[1,3]],
'@':[[1,0],[2,0],[0,1],[3,1],[0,2],[2,2],[3,2],[0,3],[1,4],[2,4]],
'.':[[1,4]],
}
XOR_KEY = 85
SPACING = 25 # 字符间距(像素)
SCALE = 4 # 点阵缩放倍数
# ── 解码单个 AlternativeText 编码值 → (x, y) ──────────────────────────────
def decode_value(encoded: int):
x_part = encoded // 1000
y_part = encoded % 1000
x = (x_part ^ XOR_KEY) - 100
y = (y_part ^ XOR_KEY) - 100
return x, y
def is_valid_point(x, y):
"""过滤合法的点阵坐标"""
return x >= 0 and 0 <= y <= 28 and y % SCALE == 0
# ── 点集 → 字符匹配(F1 分数) ─────────────────────────────────────────────
def match_char(point_set: set):
best_char, best_f1 = '?', -1.0
for char, matrix in FONT.items():
matrix_set = set(map(tuple, matrix))
overlap = len(point_set & matrix_set)
precision = overlap / len(point_set) if point_set else 0
recall = overlap / len(matrix_set) if matrix_set else 0
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) else 0
if f1 > best_f1:
best_f1, best_char = f1, char
return best_char, best_f1
# ── 可视化点阵 ─────────────────────────────────────────────────────────────
def visualize(point_set: set):
if not point_set:
return
max_x = max(p[0] for p in point_set)
max_y = max(p[1] for p in point_set)
for row in range(max_y + 1):
print(" " + "".join("#" if (col, row) in point_set else "." for col in range(max_x + 1)))
# ══════════════════════════════════════════════════════════════════════════════
# OLE2 格式提取(.et / .xls)
# ══════════════════════════════════════════════════════════════════════════════
def extract_from_ole(filepath: str):
import struct
try:
import olefile
except ImportError:
print("[!] 缺少依赖:pip install olefile")
sys.exit(1)
print(f"[*] 读取 OLE2 文件: {filepath}")
ole = olefile.OleFileIO(filepath)
data = ole.openstream('Workbook').read()
ole.close()
# 解析 BIFF 记录
records, i = [], 0
while i < len(data) - 3:
rec_type = struct.unpack('<H', data[i:i+2])[0]
rec_len = struct.unpack('<H', data[i+2:i+4])[0]
rec_data = data[i+4:i+4+rec_len]
records.append((rec_type, rec_data))
i += 4 + rec_len
if rec_len == 0 and rec_type == 0:
i += 1
mso_records = [d for t, d in records if t == 0x00EC]
print(f"[*] 找到 {len(mso_records)} 条 MSODRAWING 记录")
alt_texts = []
for rec_data in mso_records:
# 尝试 UTF-16LE
j = 0
while j < len(rec_data) - 1:
code = struct.unpack('<H', rec_data[j:j+2])[0]
if 0x30 <= code <= 0x39:
num_str, k = "", j
while k < len(rec_data) - 1:
c = struct.unpack('<H', rec_data[k:k+2])[0]
if 0x30 <= c <= 0x39:
num_str += chr(c); k += 2
else:
break
if len(num_str) >= 4:
alt_texts.append(num_str)
j = k
else:
j += 2
# 回退 ANSI
if not alt_texts:
j = 0
while j < len(rec_data):
if 0x30 <= rec_data[j] <= 0x39:
num_str, k = "", j
while k < len(rec_data) and 0x30 <= rec_data[k] <= 0x39:
num_str += chr(rec_data[k]); k += 1
if len(num_str) >= 4:
alt_texts.append(num_str)
j = k
else:
j += 1
return alt_texts
# ══════════════════════════════════════════════════════════════════════════════
# OOXML 格式提取(.xlsx)
# ══════════════════════════════════════════════════════════════════════════════
def extract_from_xlsx(filepath: str):
import zipfile, re
print(f"[*] 读取 XLSX 文件: {filepath}")
alt_texts = []
with zipfile.ZipFile(filepath, 'r') as z:
# 遍历所有工作表的 drawing XML
drawing_files = [n for n in z.namelist() if 'drawing' in n and n.endswith('.xml')]
print(f"[*] 找到 {len(drawing_files)} 个 drawing 文件")
for df in drawing_files:
xml = z.read(df).decode('utf-8', errors='ignore')
# 提取 descr 属性(AlternativeText 在 OOXML 中对应 descr)
matches = re.findall(r'descr=["\']([^"\']+)["\']', xml)
for m in matches:
if m.isdigit() and len(m) >= 4:
alt_texts.append(m)
return alt_texts
# ══════════════════════════════════════════════════════════════════════════════
# 主流程
# ══════════════════════════════════════════════════════════════════════════════
def decode(filepath: str):
ext = os.path.splitext(filepath)[1].lower()
if ext in ('.et', '.xls'):
raw_values = extract_from_ole(filepath)
elif ext == '.xlsx':
raw_values = extract_from_xlsx(filepath)
else:
print(f"[!] 不支持的文件格式: {ext}(支持 .et / .xls / .xlsx)")
return ""
print(f"[*] 提取到 {len(raw_values)} 个原始编码值")
# 解码坐标 & 过滤
valid_points = []
for num_str in raw_values:
try:
x, y = decode_value(int(num_str))
if is_valid_point(x, y):
valid_points.append((x, y))
except ValueError:
pass
print(f"[*] 有效点数: {len(valid_points)}")
if not valid_points:
print("[!] 未找到有效点,请检查文件或参数")
return ""
# 按字符列分组
char_points: dict[int, set] = {}
for x, y in valid_points:
char_idx = x // SPACING
local_x = (x % SPACING) // SCALE
local_y = y // SCALE
char_points.setdefault(char_idx, set()).add((local_x, local_y))
print(f"[*] 识别到 {len(char_points)} 个字符位置\n")
# 逐字符匹配,始终显示点阵
result = ""
for idx in sorted(char_points.keys()):
pts = char_points[idx]
char, f1 = match_char(pts)
result += char
print(f" [字符 {idx:02d}] 匹配='{char}' F1={f1:.2f} 点数={len(pts)}")
visualize(pts)
print()
print("=" * 50)
print(f" 解码结果: {result}")
print("=" * 50)
return result
if __name__ == "__main__":
if not os.path.exists(FILEPATH):
print(f"[!] 文件不存在: {FILEPATH}")
print( " 请把文件放在脚本同目录,或修改顶部 FILEPATH 为完整路径")
else:
decode(FILEPATH)
'''
输出内容:
[*] 读取 OLE2 文件: 保险箱的秘密.et
[*] 找到 171 条 MSODRAWING 记录
[*] 提取到 171 个原始编码值
[*] 有效点数: 171
[*] 识别到 21 个字符位置
[字符 00] 匹配='b' F1=1.00 点数=10
#...
#...
###.
#..#
###.
[字符 01] 匹配='a' F1=1.00 点数=8
....
....
.###
#..#
.###
[字符 02] 匹配='o' F1=1.00 点数=6
....
.##.
#..#
.##.
[字符 03] 匹配='x' F1=1.00 点数=9
#...#
.#.#.
..#..
.#.#.
#...#
[字符 04] 匹配='i' F1=1.00 点数=4
.#
..
.#
.#
.#
[字符 05] 匹配='a' F1=1.00 点数=8
....
....
.###
#..#
.###
[字符 06] 匹配='n' F1=1.00 点数=9
.##.
#...
#..#
#..#
#..#
[字符 07] 匹配='g' F1=1.00 点数=11
....
....
.###
#..#
.###
...#
.##.
[字符 08] 匹配='u' F1=1.00 点数=7
....
....
#..#
#..#
.###
[字符 09] 匹配='i' F1=1.00 点数=4
.#
..
.#
.#
.#
[字符 10] 匹配='m' F1=1.00 点数=10
.....
#####
#.#.#
#...#
[字符 11] 匹配='i' F1=1.00 点数=4
.#
..
.#
.#
.#
[字符 12] 匹配='m' F1=1.00 点数=10
.....
#####
#.#.#
#...#
[字符 13] 匹配='a' F1=1.00 点数=8
....
....
.###
#..#
.###
[字符 14] 匹配=':' F1=1.00 点数=2
..
.#
..
.#
[字符 15] 匹配='5' F1=1.00 点数=11
###.
#...
###.
...#
###.
[字符 16] 匹配='8' F1=1.00 点数=10
.##.
#..#
.##.
#..#
.##.
[字符 17] 匹配='3' F1=1.00 点数=10
###.
...#
.##.
...#
###.
[字符 18] 匹配='9' F1=1.00 点数=9
.##.
#..#
.###
...#
..#.
[字符 19] 匹配='8' F1=1.00 点数=10
.##.
#..#
.##.
#..#
.##.
[字符 20] 匹配='5' F1=1.00 点数=11
###.
#...
###.
...#
###.
==================================================
解码结果: baoxianguimima:583985
==================================================
'''
保险柜密码是583985
服务器部分
服务器检材有两个镜像文件

开始以为是服务器集群,结果放到取证工具里啥也识别不出
还以为需要raid重组,后来发现好像可以直接仿真

将两个镜像依次导入即可

登录进来发现有图形界面
如果上来直接做题是不对的,因为现在在docker容器里面

如何判断当前是否在docker容器中
cat /proc/1/cgroup
- 在容器中,会看到
docker或kubepods等字样 - 不在容器中,会看到唯一的
0::/或1:name=systemd:/init.scope、system.slice、user.slice等字样

显然这个图形化界面在docker容器中
回到宿主机的方式为Ctrl+Alt+F2,如果要切回容器内的图形化界面的话就是Ctrl+Alt+F1

尝试ssh连接的时候发现没有安装ssh服务,手动安装一下,再修改一下配置文件/etc/ssh/sshd_config就可以连接了
1. 该服务器主机操作系统版本为
Debian GNU/Linux 13 (trixie)
cat /etc/os-release

2. 该服务器根分区硬盘的uuid号为【参考格式:a1b2-c3】
3231e52f-5e15-44c4-b224-e29cb4201c0e
lsblk -f



由于问的是根分区,因此我们关注挂载位置,也就是MOUNTPOINTS字段为/的硬盘
3. 该服务器中最新的docker镜像创建时间为【参考格式:2020-01-01T00:00:00.012345678Z】
2026-04-16T07:15:50.535713491Z
查看镜像信息
docker images

发现最新的镜像为u22,但没有显示时间信息
docker inspect u22

4. 该服务器根分区快照路径为【参考格式:/abc/def】
root/history
在第2题中我们可以确定根分区类型为btrfs,使用命令
btrfs subvolume list /

@rootfs是当前系统真正的根分区子卷,root/history就是根分区快照路径
5. 该网站后台管理入口对应的文件名为【参考格式:123.txt】
user.php
网站根目录为/var/www/html/maccms10
查看目录下的文件user.php

猜测本文件原本就是后台入口文件admin.php,后来被改名为user.php
这里发现网站已经可以打开了,那么拼接/user.php试一试

发现确实是后台管理入口
6. 该网站设置的icp备案号为【参考格式:icp123】
icp1919810
icp备案号一般都在前端页面中,查看index.html

7. 该网站设置的主域名为【参考格式:www.abc.com】
还是在上一题的前端页面index.html中搜索

也可以查看网站的配置文件./application/extra/maccms.php(不知道怎么找到的)

答案就在site_url字段
8. 该网站分类3中,视频的拼音为【参考格式:abc】
sipaanshe
这一题需要连接数据库,在第11题中,我们已经得到了连接数据库需要的必要信息
- 数据库类型:mysql
- ip:10.0.3.100
- 端口:3306
- 用户名:aa
- 密码:123456

要走ssh隧道

打开网站,看一看分类3有什么

根据视频的名字,再查看mac2数据库中的mac_vod表就能找到答案

9. 该站点设置页面中,被使用的前端模板来自于哪个源文件?【参考格式:abc.def】
在配置文件中过滤模板的英文’template’

找到模板的目录为001tep
进入./template/001tep,发现文件info.ini,查看一下

可以看出这就是前端设计的模板
10. 该网站的伪静态规则配置文件sm3值为
e73407468e6f52af54c7b14632eeeb9be25b05106d06c4c3085fc843c223793f
进入目录./说明文档/伪静态规则,其中有一个rewrite说明.txt文件
rewrite = url重写作用:把动态网址伪装成静态网址,浏览器看着是静态 html,服务器实际跑的是 PHP/Java 等动态程序
明显是和伪静态相关的,打开看看

从中可以看出网站使用的是nginx,伪静态规则存路径为/etc/nginx/sites-enabled/default
直接计算sm3即可
openssl sm3 /etc/nginx/sites-enabled/default
11. 该网站关联的数据库的ip地址为
10.0.3.100
数据库关系到后端,查看application文件夹,其中有个database.php,显然和数据库有关,看一看

数据库地址只给了域名,并没有给ip
我们可以查看hosts文件(hosts文件实现域名到ip的本地映射)

从文件中我们还能看出数据库的端口为3306,看一看服务器监听的端口
ss -tulnp

显然没有3306端口
如果数据库安装在宿主机上,地址一定是127.0.0.1或localhost,而不是mytidb,因此数据库可能在容器里面
看一看服务器安装了哪些容器服务
ps aux | grep -E "dockerd|lxd|lxcfs|containerd|podman|crio|kata|firecracker|runc" | grep -v grep
#能够涵盖大部分的容器服务
回显
root 1004 0.0 0.0 453988 2692 ? Ssl 06:37 0:02 /usr/bin/lxcfs /var/lib/lxcfs
root 1135 0.0 0.6 1798704 24404 ? Ssl 06:37 0:02 /usr/bin/containerd
root 1587 0.0 0.9 1989052 39660 ? Ssl 06:37 0:02 /usr/sbin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root 2157 0.0 0.2 2133472 9972 ? Sl 06:37 0:02 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 1c79e988f43b73c0fecead1b143d67864fd4883e82fa65aeebde04b814a9dd7c -address /run/containerd/containerd.sock
| 进程 | 作用 |
|---|---|
| /var/lib/lxcfs | LXC容器服务 |
| /usr/sbin/dockerd | docker容器服务 |
| /usr/bin/containerd | 底层运行时,各种容器服务运行的依赖 |
| /usr/bin/containerd-shim-runc-v2 | 容器执行器,用于启动容器进程 |
这个服务器有docker和LXC两个容器服务,分别看一下

可以确定数据库在LXC容器中,查看详细信息
lxc-info -n mytidb

还有一种方案,就是看arp
arp -n

同样能找到ip
12. 该网站数据库使用了哪一类容器技术
LXC
见上一题的分析
13. 运行在4000端口的备份数据库版本号为
8.0.11-TiDB-v7.5.0
在navicat中切换4000端口连接(其他保持不变)

14. 新注册用户数量最多的日期为
2026-04-15
在mac_user表中

user_reg_time字段就是用户注册时间戳,将该字段转换为标准日期并进行聚合,并按人数降序排列
SELECT
DATE(FROM_UNIXTIME(user_reg_time)) AS reg_date,
COUNT(*) AS user_count
FROM mac_user
WHERE user_reg_time > 0
GROUP BY reg_date
ORDER BY user_count DESC
LIMIT 1;

15. 马慧美最后一次登录该网站的ip为
51.43.21.163
看一下mac_user表的user_name字段,大致看一下名字的存储方式

user_last_login_ip字段存储用户登录该网站的ip
筛选一下”Ma Hui Mei”,用inet_ntoa()函数将整数形式的ip转换为点分十进制
SELECT user_name, inet_ntoa(user_last_login_ip) FROM `mac_user` where user_name = 'Ma Hui Mei';

16. 以下哪个文件系统未被使用
A.ntfs B.btrfs C.xfs D.Lvm
AC
使用命令
lsblk -f
查看系统所有磁盘、分区,并显示文件系统类型、uuid和挂载点

关注FSTYPE字段,btrfs和Lvm均出现了,而ntfs和xfs没有出现
17. 该服务器安装了以下那些数据库服务
A.mysql B.GuessDB C.tidb D.postgresql E.Mariadb
ACD
直接看系统中正在运行的进程,针对字符串过滤
root@ubuntu:/var/www/html/maccms10# ps -ef | grep -E 'mysql|mariadb|postgres|tidb|guessdb'
root 1321 1 0 May07 ? 00:00:00 [lxc monitor] /var/lib/lxc mytidb
postgres 1561 1 0 May07 ? 00:00:00 /usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
postgres 1562 1561 0 May07 ? 00:00:00 postgres: 17/main: checkpointer
postgres 1563 1561 0 May07 ? 00:00:00 postgres: 17/main: background writer
postgres 1580 1561 0 May07 ? 00:00:00 postgres: 17/main: walwriter
postgres 1581 1561 0 May07 ? 00:00:00 postgres: 17/main: autovacuum launcher
postgres 1582 1561 0 May07 ? 00:00:00 postgres: 17/main: logical replication launcher
postgres 1750 1322 0 May07 ? 00:00:00 /lib/systemd/systemd-resolved
106 1904 1322 0 May07 ? 00:03:55 /usr/sbin/mysqld
root 2444 1906 2 May07 ? 00:37:25 /root/.tiup/components/tidb/v7.5.0/tidb-server -P 4000 --store=tikv --host=0.0.0.0 --status=10080 --path=10.0.3.100:2379 --log-file=/root/.tiup/data/aamac/tidb-0/tidb.log --config=/root/.tiup/data/aamac/tidb-0/tidb.toml
root 25308 4065 0 03:25 pts/0 00:00:00 grep -E mysql|mariadb|postgres|tidb|guessdb
root@ubuntu:/var/www/html/maccms10#
最后一个进程是grep本身进程,可忽略
其他进程中出现了mysql、tidb和postgresql
互联网部分
1. 售卖卡密的公开群组ID为
FIC_2026
依然是看配置文件./application/extra/maccms.php

2. 备份数据库中视频图片的文件名为
7b3fdd9d464ce48e7f20cd45f918c9a6.jpg
在服务器部分的第13题我们已知端口为4000的数据库就是备份数据库
再次连接这个数据库,查看mac数据库的mac_vod表

只有1条记录,vod_pic字段就是答案
3. ngrok提供的域名为
blemish-junior-unengaged.ngrok-free.dev
ngrok是一种内网穿透工具,可以把本机上的服务,通过一个公网域名临时暴露到互联网
我们这里直接在网站根目录下启动ngrok
ngrok http 80
上面命令的功能是将本地的80端口(可以任意选取)映射到一个公网地址

域名就是blemish-junior-unengaged.ngrok-free.dev
二进制程序部分
二进制程序藏在u盘里
1. 分析u盘检材,找到其中保存的加密程序SampleVC.exe,请给出这个exe程序的md5值?
764789dd9c095d74b6b258cf0f7568b2

导出计算下哈希

2. 分析SampleVC.exe,该程序编译的日期可能是什么?
2026-04-17
编译日期可以用die查看
编译日期就在时间戳字段

3. 分析SampleVC.exe,正确的密码是什么?
PleaseRunAsAdmin
先直接运行看看

结合程序名称里面的VC不难猜测这应该是一个解密加密容器的工具,只有当用户输入了正确的密码才予以解密
接下来就是喜闻乐见的逆向环节,请出ida
无论是字符串定位还是从main()函数入手都没办法丝滑地定位到程序的主要逻辑,这里本人是接mcp分析得到程序主要逻辑在sub_140002200()函数
__int64 sub_140002200()
{
unsigned __int64 v0; // rax
__int64 v1; // rcx
const WCHAR *v2; // rdx
int v3; // r8d
LPARAM *v4; // rdx
CHAR *lpMultiByteStr; // rbx
_OWORD *v6; // rax
_DWORD v8[4]; // [rsp+40h] [rbp-C0h] BYREF
char v9; // [rsp+50h] [rbp-B0h]
char Str1[4]; // [rsp+58h] [rbp-A8h] BYREF
int v11; // [rsp+5Ch] [rbp-A4h]
int v12; // [rsp+60h] [rbp-A0h]
int v13; // [rsp+64h] [rbp-9Ch]
char v14; // [rsp+68h] [rbp-98h]
LPARAM v15[2]; // [rsp+70h] [rbp-90h] BYREF
__int128 v16; // [rsp+80h] [rbp-80h]
__int128 v17; // [rsp+90h] [rbp-70h]
__int128 v18; // [rsp+A0h] [rbp-60h]
char Str2[16]; // [rsp+B0h] [rbp-50h] BYREF
__int128 v20; // [rsp+C0h] [rbp-40h]
__int128 v21; // [rsp+D0h] [rbp-30h]
__int128 v22; // [rsp+E0h] [rbp-20h]
__int128 v23; // [rsp+F0h] [rbp-10h]
__int128 v24; // [rsp+100h] [rbp+0h]
int v25; // [rsp+110h] [rbp+10h]
wchar_t lParam[256]; // [rsp+120h] [rbp+20h] BYREF
memset(lParam, 0, sizeof(lParam));
*(_OWORD *)v15 = 0;
v16 = 0;
v17 = 0;
v18 = 0;
SendMessageW(hWnd, 0xDu, 0x200u, (LPARAM)lParam);
SendMessageW(qword_140008798, 0xDu, 0x40u, (LPARAM)v15);
v0 = -1;
do
++v0;
while ( lParam[v0] );
if ( !v0 || v0 > 0xFF )
{
v2 = &word_1400056A8;
goto LABEL_19;
}
v1 = -1;
do
++v1;
while ( *((_WORD *)v15 + v1) );
if ( v1 != 16 )
{
v2 = (const WCHAR *)&unk_1400056B8;
LABEL_19:
MessageBoxW(0, v2, &word_14000569C, 0);
return 0;
}
v3 = 0;
v4 = v15;
do
{
if ( (unsigned __int16)(*(_WORD *)v4 - 32) > 0x5Eu )
{
v2 = (const WCHAR *)&unk_1400056C8;
goto LABEL_19;
}
++v3;
v4 = (LPARAM *)((char *)v4 + 2);
}
while ( (unsigned __int64)v3 < 0x10 );
lpMultiByteStr = (CHAR *)operator new(0x10u);
WideCharToMultiByte(0xFDE9u, 0, (LPCWCH)v15, -1, lpMultiByteStr, 16, 0, 0);
v8[0] = 1732584193;
v8[1] = -271733879;
v25 = 0;
*(_OWORD *)Str2 = 0;
v8[2] = 1732584193;
v20 = 0;
v8[3] = -271733879;
v21 = 0;
v9 = 0;
v22 = 0;
v23 = 0;
v24 = 0;
v6 = operator new(0x11u);
*v6 = *(_OWORD *)lpMultiByteStr;
sub_140001230(Str2, v6, v8);
*(_DWORD *)Str1 = -1401439825;
v11 = 215362084;
v12 = -1386142476;
v13 = 1230097010;
v14 = 0;
if ( !strncmp(Str1, Str2, 0x10u) )
{
if ( (unsigned int)sub_140001CF0(lParam, (__int64)lpMultiByteStr) == 1 )
MessageBoxW(0, &Text, &Caption, 0);
else
MessageBoxW(0, &word_1400056F0, &Caption, 0);
}
else
{
MessageBoxW(0, &word_140005700, &word_14000569C, 0);
}
return 0;
}
先看到最后的if校验处

一看就是一道经典的ctf逆向题,输入值经过加密后得到ciper与一个内置的target值作比较
根据最后的0x10u可以猜测输入的密码长度为16,但是目前Str1和Str2都还没有被赋值,无法确定哪个是ciper哪个是target,动调一下
两次动调输入不同的值,发现Str2会随输入变化而变化,而Str1的值始终为AF B9 77 AC 24 2A D6 0C F4 24 61 AD 72 CA 51 49
因此Str2就是ciper,Str1是target,那么上面那个调用了Str2的sub_140001230()函数就是加密函数了
sub_140001230()函数还调用了v6和v8,v8在上面有赋值,猜测就是key


现在现在密文和key都已经拿到,分析加密算法即可,跟进分析sub_140001230()函数
大致分析一下可以确定加密算法
- 对输入进行逐字节异或
for i in range(16):
p[i] ^= (3*i + 127)
- 标准AES-128 ECB加密(其中调用
sub_140001010()函数负责轮密钥异或)
接下来就可以写脚本解密了
from Crypto.Cipher import AES
# key
key = bytes.fromhex("0123456789ABCDEF0123456789ABCDEF")
# 密文
ciphertext = bytes.fromhex("AFB977AC242AD60CF42461AD72CA5149")
# AES-128 ECB 解密
cipher = AES.new(key, AES.MODE_ECB)
data = cipher.decrypt(ciphertext)
# 还原 XOR 扰动
plain = bytearray(data)
for i in range(16):
plain[i] ^= (3*i + 127) & 0xFF
print(plain)
print(plain.decode(errors="ignore"))
'''
输出内容:
bytearray(b'PleaseRunAsAdmin')
PleaseRunAsAdmin
'''
拿到密码PleaseRunAsAdmin
密码似乎在提示我们需要以管理员身份运行解密程序
4. 分析u盘检材,利用SampleVC1234.exe解密U盘中被加密的文件,解密后的文件的后缀是什么?
.vhd
u盘中存在一个名为vc的文件,猜测就是加密容器

解密并不容易,经过多次实验发现成功解密需要两个条件
- SampleVC.exe需要以管理员身份运行
- 文件存放路径要有中文(对你没有听错)
满足以上两个条件可以成功解密

虽然提示挂载失败,但是生成了一个vc.vhd磁盘映像文件
如果存储路径全英文则会提示挂载成功,但是vc.vhd生成一下就消失了,如下图

应该是出题人的失误,不管了反正本题答案是.vhd
5. 分析u盘检材,找到被加密的交易记录,统计李安弘虚拟币收款地址钱包总收款金额为
186948.09
直接双击vc.vhd可以看到里面有一个.xlsx表格

记录了几个虚拟币地址的交易流水,直接截图丢给ai写一个excel函数或py脚本计算收款金额即可,这里本人选择的是excel函数

