Androids_encryption:
我們先看它有3個選項可以選並且都是做不同的動作(廢話XD) 我們直接看它的程式碼
上面這段是共用的部分
import sys
import base64
from Crypto.Cipher import AES
from secrets import flag, key1, iv1
def to_blocks(txt):
return [txt[i*BLOCK_SIZE:(i+1)*BLOCK_SIZE] for i in range(len(txt)//BLOCK_SIZE)]
def xor(b1, b2=None):
if isinstance(b1, list) and b2 is None:
assert len(set([len(b) for b in b1])) == 1, 'xor() - Invalid input size'
assert all([isinstance(b, bytes) for b in b1]), 'xor() - Invalid input type'
x = [len(b) for b in b1][0]*b'\x00'
for b in b1:
x = xor(x, b)
return x
assert isinstance(b1, bytes) and isinstance(b2, bytes), 'xor() - Invalid input type'
return bytes([a ^ b for a, b in zip(b1, b2)])
BUFF = 256
BLOCK_SIZE = 16
iv2 = AES.new(key1, AES.MODE_ECB).decrypt(iv1)
key2 = xor(to_blocks(flag))
然後下面選擇後程式碼跑的部分
選擇1時跳到這然後要你輸入input(16bite)
def enc_plaintext():
print('Plaintext: ', end='')
txt = base64.b64decode(input().rstrip())
print(encrypt(txt, key1, iv1))
跳到->
def encrypt(txt, key1, iv1):
global key2, iv2
assert len(key1) == BLOCK_SIZE, f'Invalid key1 size'
assert len(iv1) == BLOCK_SIZE, 'Invalid IV size'
assert len(txt) % BLOCK_SIZE == 0, 'Invalid plaintext size'
bs = len(key1)
blocks = to_blocks(txt)
ctxt = b''
aes = AES.new(key1, AES.MODE_ECB)
curr = iv1
for block in blocks:
ctxt += aes.encrypt(xor(block, curr))
curr = xor(ctxt[-bs:], block)
iv2 = AES.new(key2, AES.MODE_ECB).decrypt(iv2)
key2 = xor(to_blocks(ctxt))
return str(base64.b64encode(iv1+ctxt), encoding='utf8')
這邊可以注意到的是key2 = xor(to_blocks(ctxt))這段
Ps. a[0] xor \x00 *len(a[0]) 因為任何數 xor 00 都是自己不會變
最後回傳一個iv1+ctxt的值並且要注意到的是它回傳的值都是用base64去加密過的
選擇2時跳到這然後會回傳一個enc_flag(這是一個用key2,iv2去加密過的flag)
def enc_flag():
print(encrypt(flag, key2, iv2))
跳到->
def encrypt(txt, key, iv):
global key2, iv2
assert len(key) == BLOCK_SIZE, f'Invalid key size'
assert len(iv) == BLOCK_SIZE, 'Invalid IV size'
assert len(txt) % BLOCK_SIZE == 0, 'Invalid plaintext size'
bs = len(key)
blocks = to_blocks(txt)
ctxt = b''
aes = AES.new(key, AES.MODE_ECB)
curr = iv
for block in blocks:
ctxt += aes.encrypt(xor(block, curr))
curr = xor(ctxt[-bs:], block)
iv2 = AES.new(key2, AES.MODE_ECB).decrypt(iv2)
key2 = xor(to_blocks(ctxt))
return str(base64.b64encode(iv+ctxt), encoding='utf8')
一樣注意到的是 xor 和 輸入值和回傳值是用base64加密的部分
Ps.key2的部分會因為之前講的xor特性所以key2=cipher
Ps.全部的 inputs和outputs都是做base64加密
跑2的時候會複寫一次iv2,key2然後會收到command給的iv2+flag_cipher我們這邊接收到回傳後可以用第一步的key2和新的iv2去做使用去解Flag
解題方法:
我取用下面某一篇的範例去跟著做並且去解說
1.enc_plaintext()
我們先send一個隨便的payload(16bit)我們可以收到iv1+cipher,從密碼我們可以知道新的key2因為xor特性所以
key2 = a[0] 所以key2就是first block 的密碼
response1 = 'qal7b3mi7fEvSccj+NcaYtqU4i4io4qT1g88K9wY2nQ='
iv_plus_ctext = base64.b64decode(response1)
iv= enc_m[:BLOCK_SIZE] #BLOCK_SIZE==16
ctxt = enc_m[BLOCK_SIZE:] # IV is 16 bytes long
key2 = xor(to_blocks(ctext))
2. enc_flag()
接下來我們送2 給command我們會收到iv2+flag_cipher我們這邊接收到的回傳可以用第一步的key2去拿到新的iv2因為它執行的
iv2 = AES.new(key2, AES.MODE_ECB).decrypt(iv2) 所以我們可以更新key2現在的值所以我們現在有key2,iv2了可以用他們去解密flag了
enc_flag = '36X0Ug8ZEIvrRDeus6c3GBynEY7La36H0/A1Bqoy87go8FyYOeRQOuN7b0fXJXMYqWZ9lo9MWkS8EaN9/8Tl7A=='
enc_flag = base64.b64decode(enc_flag)
iv2 = enc_flag[:BLOCK_SIZE] #BLOCK_SIZE== 0 to 16
ctxt = enc_flag[BLOCK_SIZE:] #BLOCK_SIZE==16 to end
3.解出Flag並合併
這邊要講道AES PCBC加解碼特性請看下圖 ※這張圖非常重要可以搭程式碼先看一遍,不過一定要看的懂因為這是解出Flag的關鍵請一定要看懂不然下面的解題程式碼一定看不懂QQ
我們可以看到它是用以下方法做的
aes = AES.new(key2, AES.MODE_ECB)
加密運作:
它運作是cipher1(=C1) = aes(text1(=P1) xor iv) , 然後接下來每個區塊是
cipher2(=C2) = aes(text2(=P2) xor (text1(=P1) xor cipher1(=C1) ))這樣下去運作加密
解密運作:
所以我們解密可以這樣下去做
text1(=P1) = iv xor aes.decrypt( cipher1(=C1) ) ,然後接下來每個區塊是
textI(=PI) = xor(aes.decrypt( cipherI(=CI) ) xor ( cipherJ(=CJ),textJ(=PJ) ) )
※I是當前的位置J=I-1 EX:I=2 , J=1
所以我們得出
from Crypto.Cipher import AES
iv2 = enc_flag[:16]
c1 = enc_flag[16:32]
c2 = enc_flag[32:48]
c3 = enc_flag[48:64]
aes = AES.new(key2, AES.MODE_ECB)
p1 = xor(aes.decrypt(c1),iv2)
p2 = xor(aes.decrypt(c2),xor(c1,p1))
p3 = xor(aes.decrypt(c3),xor(c2,p2))
print(p1+p2+p3)
最後可以解出CTF-BR{kn3W_7h4T_7hEr3_4r3_Pc8C_r3pe471ti0ns?!?}
我記得當初在解的時候看得出來是AES加密可是卡在AES的知識不足,不知道整麼解就卡住了QQ如果有錯或是觀念不對 歡迎糾正^^
兩個網頁的解法程式碼都在下面
推這兩個比較容易看懂 第2個有範例可以跟著做
基本詳細觀念看這個
圖片來源:
#https://qiita.com/takdcloose/items/fe9b9cac417676103b17 程式碼跑範例 import base64 from Crypto.Cipher import AES
BUFF = 256 BLOCK_SIZE = 16
def to_blocks(txt): return [txt[i*BLOCK_SIZE:(i+1)*BLOCK_SIZE] for i in range(len(txt)//BLOCK_SIZE)]
def xor(b1, b2=None): if isinstance(b1, list) and b2 is None: assert len(set([len(b) for b in b1])) == 1, 'xor() - Invalid input size' assert all([isinstance(b, bytes) for b in b1]), 'xor() - Invalid input type' x = [len(b) for b in b1][0]*b'\x00' for b in b1: x = xor(x, b) return x assert isinstance(b1, bytes) and isinstance(b2, bytes), 'xor() - Invalid input type' return bytes([a ^ b for a, b in zip(b1, b2)])
# firstly, i'll make an arbitrary key2 enc_m = 'qal7b3mi7fEvSccj+NcaYtqU4i4io4qT1g88K9wY2nQ=' enc_m = base64.b64decode(enc_m) iv= enc_m[:BLOCK_SIZE] ctxt = enc_m[BLOCK_SIZE:] key2 = xor(to_blocks(ctxt))
# Secondly, get iv2 enc_flag = '36X0Ug8ZEIvrRDeus6c3GBynEY7La36H0/A1Bqoy87go8FyYOeRQOuN7b0fXJXMYqWZ9lo9MWkS8EaN9/8Tl7A==' enc_flag = base64.b64decode(enc_flag) iv2= enc_flag[:BLOCK_SIZE] #ctxt = enc_flag[BLOCK_SIZE:]
# Next, culculate newly iv2,key2 using ctxt, key2 and iv2 above #iv2 = AES.new(key2, AES.MODE_ECB).decrypt(iv2) key2 = xor(to_blocks(ctxt))
# Now we have iv2 and key2. let's decrypt the flag enc_flag = '36X0Ug8ZEIvrRDeus6c3GBynEY7La36H0/A1Bqoy87go8FyYOeRQOuN7b0fXJXMYqWZ9lo9MWkS8EaN9/8Tl7A==' enc_flag = base64.b64decode(enc_flag) ctxt = enc_flag[BLOCK_SIZE:] b1 = xor(AES.new(key2, AES.MODE_ECB).decrypt(ctxt[:BLOCK_SIZE]) , iv2)
curr = xor(b1, ctxt[:BLOCK_SIZE]) b2 = xor(AES.new(key2, AES.MODE_ECB).decrypt(ctxt[BLOCK_SIZE:BLOCK_SIZE*2]) , curr)
curr = xor(b2, ctxt[BLOCK_SIZE:BLOCK_SIZE*2]) b3 = xor(AES.new(key2, AES.MODE_ECB).decrypt(ctxt[BLOCK_SIZE*2:BLOCK_SIZE*3]) , curr)
print(b1+b2+b3) |
#https://reveng-ctf.com/crypto/pwn2win-androids-encryption.html#step-by-step 程式碼跑範例 import base64 from Crypto.Cipher import AES
BUFF = 256 BLOCK_SIZE = 16
def to_blocks(txt): return [txt[i*BLOCK_SIZE:(i+1)*BLOCK_SIZE] for i in range(len(txt)//BLOCK_SIZE)]
def xor(b1, b2=None): if isinstance(b1, list) and b2 is None: assert len(set([len(b) for b in b1])) == 1, 'xor() - Invalid input size' assert all([isinstance(b, bytes) for b in b1]), 'xor() - Invalid input type' x = [len(b) for b in b1][0]*b'\x00' for b in b1: x = xor(x, b) return x assert isinstance(b1, bytes) and isinstance(b2, bytes), 'xor() - Invalid input type' return bytes([a ^ b for a, b in zip(b1, b2)])
def encrypt(txt, key, iv): global key2, iv2 assert len(key) == BLOCK_SIZE, f'Invalid key size' assert len(iv) == BLOCK_SIZE, 'Invalid IV size' assert len(txt) % BLOCK_SIZE == 0, 'Invalid plaintext size' bs = len(key) blocks = to_blocks(txt) ctxt = b'' aes = AES.new(key, AES.MODE_ECB) curr = iv for block in blocks: ctxt += aes.encrypt(xor(block, curr)) curr = xor(ctxt[-bs:], block) iv2 = AES.new(key2, AES.MODE_ECB).decrypt(iv2) key2 = xor(to_blocks(ctxt)) return str(base64.b64encode(iv+ctxt), encoding='utf8')
response1 = 'qal7b3mi7fEvSccj+NcaYtqU4i4io4qT1g88K9wY2nQ=' iv_plus_ctext = base64.b64decode(response1) ctext = iv_plus_ctext[16:] # IV is 16 bytes long key2 = xor(to_blocks(ctext))
enc_flag = '36X0Ug8ZEIvrRDeus6c3GBynEY7La36H0/A1Bqoy87go8FyYOeRQOuN7b0fXJXMYqWZ9lo9MWkS8EaN9/8Tl7A==' enc_flag = base64.b64decode(enc_flag)
iv2 = enc_flag[:16] c1 = enc_flag[16:32] c2 = enc_flag[32:48] c3 = enc_flag[48:64]
aes = AES.new(key2, AES.MODE_ECB)
p1 = xor(aes.decrypt(c1),iv2) p2 = xor(aes.decrypt(c2),xor(c1,p1)) p3 = xor(aes.decrypt(c3),xor(c2,p2))
print(p1+p2+p3) |