針對suctf的python**好
一題,通過解析內容還原pyc的指令碼
對於不同的解析內容需要另加修改,但原理一致–將元素按照 格式識別符號-len-內容的形式遞迴填入即可
值得說明的一點是指令碼無法還原出原來一模一樣的pyc,但可以還原出相同的py檔案
這是因為python在編譯pyc的時候,會將一些同樣的字串,通過引用來使用,即格式識別符號「r」;另外還有一些字串使用的是interend,格式識別符號』t』
這些表現出來的形式與常規字串』s』相同,因此不易區分。而pyc中用常規字串來解析,在執行和反編譯的時候也是沒有區別的
原理是這樣的:引用字串只是為了壓縮pyc的大小,使得同樣的字串不用出現多次。而未經優化的pyc也是可以正常使用的。
指令碼如下:
from struct import pack
defp32
(i):
return pack(", i)
defstring
(d, fout):
l = len(d)
fout.write(b"s")
if(type(d)==type("1")):
if(d!=""
and d[0]=="'"):
d = d.strip("'")
l -= 2
d = d.encode()
l = p32(l)
fout.write(l)
fout.write(d)
deftumple
(d, fout):
l = p32(len(d))
fout.write(b"(")
fout.write(l)
for s in d:
string(s, fout)
deftumple_line
(i, l, fout):
fout.write(b'(')
fout.write(p32(l))
while(data[i][:5]!="names"):
# print(data[i])
if(data[i]=="none"):
fout.write(b"n")
elif(data[i]=="code"):
i = analyse("code", i+1, fout)-1
elif(data[i][0]=='('):
analyse("tumple", i, fout)
elif(data[i][0]=="'"):
string(data[i][1:-1], fout)
else:
fout.write(b"i")
fout.write(p32(int(data[i])))
i += 1
return i
defcalc_len
(i):
l = 0
n = 1
while(n):
if(data[i].find("argcount")!=-1):
n += 1
if(n==1):
l += 1
# print(n, l, data[i])
if(data[i][:6]=="lnotab"):
n -= 1
i += 1
return l-8
defanalyse
(kind, i, fout):
# print(data[i])
global data
if(kind=="code"):
fout.write(b"c")
argcount = int(data[i].split(' ')[1])
i += 1
fout.write(p32(argcount))
nlocals = int(data[i].split(' ')[1])
i += 1
fout.write(p32(nlocals))
stacksize = int(data[i].split(' ')[1])
i += 1
fout.write(p32(stacksize))
flags = int(data[i].split(' ')[1], 16)
i += 1
fout.write(p32(flags))
i = analyse("co_code", i, fout)
i = analyse("consts", i, fout)
i = analyse("names", i, fout)
i = analyse("varnames", i, fout)
i = analyse("freevars", i, fout)
i = analyse("cellvars", i, fout)
i = analyse("filename", i, fout)
i = analyse("name", i, fout)
i = analyse("firstlineno", i, fout)
i = analyse("lnotab", i, fout)
if(kind=="co_code"):
if(len(data[i].split(' '))==1):
s = ""
while(data[i]!="consts"):
i += 1
s += data[i]
s = s.replace("consts", "")
else:
s = data[i].split(" ")[1]
i += 1
try:
s = bytes.fromhex(s)
except:
s = bytes.fromhex("0"+s)
string(s, fout)
i += 1
if(kind=="consts"):
l = calc_len(i)
i = tumple_line(i, l, fout)
if(kind in ["names", "varnames", "freevars", "cellvars", "tumple"]):
if(data[i][0]!="("):
data[i] = "".join(data[i].split(' ')[1:])
d = data[i].replace("(", "").replace(")", "").replace(" ", "").split(",")
if(d[-1]==""):
d.pop(-1)
tumple(d, fout)
i += 1
if(kind in ["filename", "name"]):
d = data[i].split(" ")[1].replace("'", "")
string(d, fout)
i += 1
if(kind=="firstlineno"):
d = int(data[i].split(" ")[1])
fout.write(p32(d))
i += 1
if(kind=="lnotab"):
try:
s = data[i].split(" ")[1]
except:
s = ""
try:
s = bytes.fromhex(s)
except:
s = bytes.fromhex("0"+s)
string(s, fout)
i += 1
return i
if(__name__=="__main__"):
fout = open("output.pyc", "wb")
with open("opcode.txt","r", encoding='utf-8') as f:
data = f.readlines()
# utf-8格式的opcode使用下述語句解析,因為showfile重定向的結果出來是utf-8格式的orz
# data = f.read().replace("\x00", "").split("\n\n")
for i in range(len(data)):
data[i] = data[i].strip()
print(data)
magic = bytes.fromhex(data[0].split(" ")[1])
moddate = bytes.fromhex(data[1].split(" ")[1])
fout.write(magic)
fout.write(moddate)
t = analyse("code", 3, fout)
print("all lines:", t)
fout.close()
寫的比較亂(:з」∠)有問題可以私戳我詢問,如果日後有功夫會回來重構一下的xd 逆向 簡單的pyc
這個是i c的比賽逆向題目 比賽還沒結束qaq 題目內容很簡單 提示說要逆向乙個pyc 直接拉進去 執行 得到如下內容 import base64 def encode message s for i in message x ord i 32 x x 16 s chr x return base6...
關於pyc檔案的逆向
關於pyc檔案的逆向 最近感覺遇到的pyc檔案逆向的越來越多了,所以就來總結下。參考了大佬的blog 0x1 pyc的檔案結構 在命令列輸入 python m filename.py的時候,便會得到乙個對應的filename.pyc。拖進hxd中看二進位制。其中,開頭的4個位元組是magic num...
pyc逆向之opcode簡單置換
最近做了一道pyc的逆向題,主要難點在於python環境的opcode被置換,就簡單記錄一下相關知識。opcode其實是指python原始碼的操作碼,python源 py編譯後可以得到二進位制檔案 pyc,pyc檔案中就含有opcode序列。對於不同版本的python,其opcode是不完全相同的,...