Remeo’s Encrypting Machine
題目描述:
from Crypto.Util.number import*
from Crypto.Cipher import AES
from secret import msg,password,flag
import socketserver
import signal
assert len(msg) == 32
assert len(password) == 8
# password = "abcd%^&*" 在本地運行的時候隨便賦個password即可
def padding(msg):
return msg + bytes([0 for i in range((16 - len(msg))%16)]) # 這里的bytes()函式運行會報錯,我這直接改成str()進行本地測驗的
class Task(socketserver.BaseRequestHandler):
def _recvall(self):
BUFF_SIZE = 2048
data = b''
while True:
part = self.request.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
break
return data.strip()
def send(self, msg, newline=True):
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass
def recv(self):
return self._recvall()
def login(self):
right_num = 0
while 1:
self.send(b'[~]Please input your password:')
str1 = self.recv().strip()[:8]
true_num = 0
for i in range(len(password)):
if str1[i] != password[i]:
login = False
self.send(b'False!')
break
else:
true_num = i + 1
if right_num > true_num:
continue
else:
right_num = true_num
if true_num == len(password):
login = True
check = b''
for i in range(0x2000):
check = self.aes.encrypt(padding(check[:-1] + str1[:i+1]))
if login == True:
self.send(b"Login Success")
return True,check[:16]
return False
def handle(self):
signal.alarm(100)
self.aes = AES.new(padding(password),AES.MODE_ECB)
_,final_check = self.login()
if _ == 1:
assert msg.decode() == final_check.hex()
self.send(b'Good Morning Master!')
self.send(flag)
class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
passu
class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass
if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 9999
print("HOST:POST " + HOST+":" + str(PORT))
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()
分析程式
關鍵是Task類;其余都是實作連接的代碼
def handle(self):
signal.alarm(100)
self.aes = AES.new(padding(password),AES.MODE_ECB)
_,final_check = self.login()
if _ == 1:
assert msg.decode() == final_check.hex()
self.send(b'Good Morning Master!')
self.send(flag)
首先開始的是這個函式,里面包含了login函式,跳轉去看看login()的定義
看看我們到底傳什么東西進去:
self.send(b'[~]Please input your password:')
str1 = self.recv().strip()[:8]
這里的recv()函式在題目中有定義;實作的功能是從我們輸入的位元組中接受2048bits的量(之后爆破的程序中比如我們傳入"123",則實際上服務器程式收到的是“123123123”這樣由“123”填充到2048位元組的字串),再進行去"\n"且切前8位的片
整個程式就只有這么一個傳入點;之后再來分析傳入的數值賦給的變數str1
for i in range(len(password)):
if str1[i] != password[i]:
login = False
self.send(b'False!')
break
首先逐位判斷傳入的字符是否與password相同(不安全的比較函式)
如果一旦有一位不同則退出這個比較回圈
如果相同的話,則繼續之后的;其中有一個AES加密程序,加密結果賦值給check變數
for i in range(0x2000):
check = self.aes.encrypt(padding(check[:-1] + str1[:i+1]))
緊接著
if login == True:
self.send(b"Login Success")
return True,check[:16]
如果傳入的str1與password完全相同,則整個函式回傳True,check的前16位;實際上check的數值,我們管不了,只要傳入的str1與password相同即可得到flag
那么這個AES的加密程序又有什么用呢?
由于加密相比普通的程式邏輯上要多花一些時間(1s以內)
爆破程序分析
所以可以使用timing attack(實際上和sql時間盲注原理差不多)
在一個包含所有可能構成password的字符的字符表中進行逐位爆破(使用string.printable作為這個字符表)
檢驗相鄰的兩個爆破時間長短;
我們通過前面的分析可以知道每當一位password與str1相同時,就會進行一次AES加密,這就意味著時間差就會拉大,就更有可能判斷對正確的字符
第一層for回圈
然后開始爆破:
alph = string.printable
password = ""
for n in tqdm(range(8)): #已知password是8位,所以只需爆破8位即可
t = 0.0
temp = ""
for i in alph[:94]: # 由于94位之后都是空格或者\t,\n,很顯然不是password里面的字符,所以進行切片操作
if i != "!": # 這里不知道為什么一旦傳入"!"程式就會報錯停止運行,所以就干脆先ban掉
io.recvuntil(b"\n") # 服務器程式里面提到過每次傳給客戶機的字串之后都會加上'\n',所以以這個為界限進行傳輸和互動
io.send((password + i).encode("utf8")) #傳入的資料必須是位元組型別
start = time() # 開始時間
data = io.recvuntil(b"\n")
end = time() # 結束時間
if (end - start) > t:
temp = i
t = end - start
password += temp # 選出執行程式時間最長的那一位字符
print(password)
這樣一次爆破就完成了
第二層while回圈
但是實際上由于各種不可控因素,每次進行爆破得到的結果都不會相同
所以在爆破位次的基礎上我們還需要進行一輪爆破(判斷條件是服務器回傳的資料中有無b"Success")
在原來爆破的基礎上增加一個while回圈
alph = string.printable
password = ""
while True:
for n in tqdm(range(8)):
t = 0.0
temp = ""
for i in alph[:94]:
if i != "!":
# print(i,end="")
io.recvuntil(b"\n")
io.send((password + i + "0").encode("utf8"))
start = time()
data = io.recvuntil(b"\n")
end = time()
if (end - start) > t:
temp = i
t = end - start
# print(end - start)
password += temp
ending = time()
if ending - starting > 98:
middle = 1
print(password)
break
# print(password)
if middle == 1:
break
io.recvuntil(b"\n")
io.send(password.encode()) # 提交最后一位爆破完成之后生成的password
data = io.recvuntil(b"\n")
if b"Success" not in data:
print(password)
password = ""
continue
else:
print(password)
io.interactive()
break
第三層while回圈
運行了程式我們就會知道,每當爆破100s之后,服務器程式會自動切斷連接
從腳本中可以看到:
def handle(self):
signal.alarm(100)
那么怎么讓ta實作完全自動化的爆破腳本(不需要每過100s就手動再運行一次程式)
再加一個while回圈;在服務器切斷連接之前我們重新再次連接服務器程式,這樣100s就會重新倒計時
alph = string.printable
password = ""
while True:
io = remote("127.0.0.1","9999")
starting = time() # 一次連接開始的時間
middle = 0
while True:
for n in tqdm(range(8 - len(password))): # 8 - len(password)的目的是為了在自己切斷服務器連接之后上一次爆破的password如果沒有完成的話,接著爆破剩下的位數
t = 0.0
temp = ""
for i in alph[:94]:
if i != "!":
# print(i,end="")
io.recvuntil(b"\n")
io.send((password + i).encode("utf8"))
start = time()
data = io.recvuntil(b"\n")
end = time()
if (end - start) > t:
temp = i
t = end - start
# print(end - start)
password += temp
ending = time() # 查看已經連接多長時間了
if ending - starting > 98: # 大于98s之后就可以主動切換了,不要太貪心
middle = 1 # 使用變數傳遞的形式跳出多個回圈
print(password)
break
# print(password)
if middle == 1:
break
io.recvuntil(b"\n")
io.send(password.encode())
data = io.recvuntil(b"\n")
if b"Success" not in data:
# sum.append(password)
print(password)
password = ""
continue
else:
print(password)
break
if middle == 1:
continue
io.interactive() # 如果是因為password相同而跳出回圈就會執行正常的互動,退出機械互動
這樣就實作了一個完全自動的爆破位次的程式(但是我還沒爆破出來正確的password是什么…)
PS:由于上面的腳本是為了講清楚腳本部分的邏輯,所以單獨運行是不行的,有幾個模塊還沒引入
下面是完整的腳本
代碼實作
from pwn import *
from time import time
import string
from tqdm import tqdm
# context.log_level='debug'
alph = string.printable
password = ""
while True:
io = remote("127.0.0.1","9999")
starting = time()
middle = 0
while True:
for n in tqdm(range(8 - len(password))):
t = 0.0
temp = ""
for i in alph[:94]:
if i != "!":
# print(i,end="")
io.recvuntil(b"\n")
io.send((password + i).encode("utf8"))
start = time()
data = io.recvuntil(b"\n")
end = time()
if (end - start) > t:
temp = i
t = end - start
# print(end - start)
password += temp
ending = time()
if ending - starting > 98:
middle = 1
print(password)
break
# print(password)
if middle == 1:
break
io.recvuntil(b"\n")
io.send(password.encode())
data = io.recvuntil(b"\n")
if b"Success" not in data:
# sum.append(password)
print(password)
password = ""
continue
else:
print(password)
break
if middle == 1:
continue
io.interactive()
參考文章
(8條訊息) 20211211 美團CTF2021 Crypto方向&&Pwn方向部分WP_4XWi11的博客-CSDN博客
第二屆美團ctf預賽-writeup by WDNMD (qq.com)
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/385539.html
標籤:其他
上一篇:如何通過API為Facebook營銷活動付費以運行通過FB營銷API創建的廣告?
下一篇:xlddz(一):雜談專案由來
