摘要:
近來,一款名為Burpsuite的商業版安全工具慘遭破解,網上出現了一款名為BurpLoader的惡意軟體,該軟體破壞了Burpsuite的認證流程,給世界和平帶來了嚴重威脅。本系列文章通過對BurpLoader的幾個版本的逆向分析,分析Burpsuite的破解原理,分析Burpsuite認證體系存在的安全性漏洞。
JD-GUI的用途與缺陷:
JD-GUI是一款從JAVA位元組碼中還原JAVA原始碼的免費工具,一般情況下使用這款工具做JAVA逆向就足夠了,但是由於其原理是從JAVA位元組碼中按照特定結構來還原對應的JAVA原始碼,因此一旦位元組碼結構被打亂(比如說使用混淆器),那麼JD-GUI就會失去它的作用,如圖為使用JD-GUI開啟Burpsuite時的顯示:
顯然,JD-GUI沒能還原JAVA原始碼出來,因為Burpsuite使用了混淆器打亂了位元組碼結構 所以,JD-GUI適用於‘沒有使用混淆器’的JAVA位元組碼,而缺陷是一旦位元組碼結構被打亂,則無法發揮它的作用
位元組碼分析:
Java的位元組碼並不像普通的二進位代碼在電腦中直接執行,它通過JVM引擎在不同的平台和電腦中運行。
JVM是一個基於棧結構的虛擬電腦,使用的是JVM作業碼(及其助記符),在這一點上和普通二進位反組譯碼的過程非常相似。 對Java位元組碼進行反編譯其實非常簡單,JDK內建的Javap工具即可完成這項任務。
樣本:對Javar.class進行反編
注意javap的-c參數是顯示詳細代碼,否則只顯示method,而按照java的老規矩Javar不要加尾碼名 同時你也可以使用eclipse的外掛程式Bytecode Visualizer來反編譯位元組碼
注意右面的流程圖,大家在上程式設計導論課時都畫過吧,現在發現它的用途了吧,一眼就看出是一個if-else結構,前兩句定義i變數,然後取i=2壓棧常數1,比對i和1以後就都java.lang.system.out了,一個輸出wooyun,一個輸出lxj616。
老版本的BurpLoader分析:
隨著Burpsuite的更新,BurpLoader也在跟著進行更新,我們從老版本的BurpLoader入手,簡要分析一下之前老版本的burpsuite破解原理。 本處選用了1.5.01版本的BurpLoader進行分析 首先試著用JD-GUI載入BurpLoader:
成功還原了BurpLoader原始碼,只可惜由於是對burpsuite的patch,所以burpsuite的混淆在burploader裡仍然可讀性極差,不過可以推斷burploader本身沒有使用混淆工具。
<span class="keyword" style="font-weight: bold;">public</span> <span class="keyword" style="font-weight: bold;">static</span> <span class="keyword" style="font-weight: bold;">void</span> main(String[] args)
{
<span class="keyword" style="font-weight: bold;">try</span>
{
<span class="keyword" style="font-weight: bold;">int</span> ret = JOptionPane.showOptionDialog(<span class="keyword" style="font-weight: bold;">null</span>, <span class="string" style="color: #dd1144;">"This program can not be used for commercial purposes!"</span>, <span class="string" style="color: #dd1144;">"BurpLoader by larry_lau@163.com"</span>, <span class="number" style="color: #009999;">0</span>, <span class="number" style="color: #009999;">2</span>, <span class="keyword" style="font-weight: bold;">null</span>, <span class="keyword" style="font-weight: bold;">new</span> String[] { <span class="string" style="color: #dd1144;">"I Accept"</span>, <span class="string" style="color: #dd1144;">"I Decline"</span> }, <span class="keyword" style="font-weight: bold;">null</span>);
<span class="comment" style="font-style: italic; color: #999988;">//顯示選擇對話方塊:這程式是出於學習目的寫的,作者郵箱larry_lau(at)163.com </span>
<span class="keyword" style="font-weight: bold;">if</span> (ret == <span class="number" style="color: #009999;">0</span>) <span class="comment" style="font-style: italic; color: #999988;">//選擇我同意</span>
{
<span class="comment" style="font-style: italic; color: #999988;">//以下用到的是java反射機制,不懂反射請百度</span>
<span class="keyword" style="font-weight: bold;">for</span> (<span class="keyword" style="font-weight: bold;">int</span> i = <span class="number" style="color: #009999;">0</span>; i < clzzData.length; i++)
{
Class clzz = Class.forName(clzzData[i]);
<span class="comment" style="font-style: italic; color: #999988;">//是burpsuite的靜態類(名字被混淆過了,也沒必要列出了)</span>
Field field = clzz.getDeclaredField(fieldData[i]);
<span class="comment" style="font-style: italic; color: #999988;">//靜態類中的變數也被混淆過了,也不必列出了</span>
field.setAccessible(<span class="keyword" style="font-weight: bold;">true</span>);
<span class="comment" style="font-style: italic; color: #999988;">//訪問private必須先設定這個,不然會報錯</span>
field.<span class="keyword" style="font-weight: bold;">set</span>(<span class="keyword" style="font-weight: bold;">null</span>, strData[i]);
<span class="comment" style="font-style: italic; color: #999988;">//把變數設定成strData(具體那一長串到底是什麼暫不討論)</span>
}
Preferences prefs = Preferences.userNodeForPackage(StartBurp.<span class="keyword" style="font-weight: bold;">class</span>);
<span class="comment" style="font-style: italic; color: #999988;">//明顯preferences是用來儲存設定資訊的</span>
<span class="keyword" style="font-weight: bold;">for</span> (<span class="keyword" style="font-weight: bold;">int</span> i = <span class="number" style="color: #009999;">0</span>; i < keys.length; i++)
{
<span class="comment" style="font-style: italic; color: #999988;">// key和val能猜出是什麼吧</span>
String v = prefs.<span class="keyword" style="font-weight: bold;">get</span>(keys[i], <span class="keyword" style="font-weight: bold;">null</span>);
<span class="keyword" style="font-weight: bold;">if</span> (!vals[i].equals(v))
{
prefs.put(keys[i], vals[i]);
}
}
StartBurp.main(args);
}
}
<span class="keyword" style="font-weight: bold;">catch</span> (Exception e)
{
JOptionPane.showMessageDialog(<span class="keyword" style="font-weight: bold;">null</span>, <span class="string" style="color: #dd1144;">"This program can only run with burpsuite_pro_v1.5.01.jar"</span>, <span class="string" style="color: #dd1144;">"BurpLoader by larry_lau@163.com"</span>,
<span class="number" style="color: #009999;">0</span>);
}
}
}
因此,BurpLoader的原理就是偽造有效Key來通過檢測,Key的輸入是通過preference來注入的,而我猜測它為了固定Key的計算方法,通過反射把一些環境變數固定成常量了
新版本的BurpLoader分析:
以下用1.6beta版的BurpLoader進行分析: 首先用JD-GUI嘗試開啟BurpLoader:
看來這個版本的BurpLoader對位元組碼使用了混淆,這條路走不通了 於是直接讀位元組碼吧!
大家可以看到這裡的字串都是混淆過的,每一個都jsr到151去解密
這段解密代碼特點非常明顯,一個switch走5條路,給221傳不同的解密key,這不就是Zelix KlassMaster的演算法嗎? 簡單的異或而已,輕鬆寫出解密機:
<span class="keyword" style="font-weight: bold;">public</span> <span class="class" style="font-weight: bold; color: #445588;"><span class="keyword">class</span> <span class="title">Verify</span> {</span>
<span class="keyword" style="font-weight: bold;">private</span> <span class="keyword" style="font-weight: bold;">static</span> String decrypt(String str) {
<span class="keyword" style="font-weight: bold;">char</span> key[] = <span class="keyword" style="font-weight: bold;">new</span> <span class="keyword" style="font-weight: bold;">char</span>[] {<span class="number" style="color: #009999;">73</span>,<span class="number" style="color: #009999;">25</span>,<span class="number" style="color: #009999;">85</span>,<span class="number" style="color: #009999;">1</span>,<span class="number" style="color: #009999;">29</span>};
<span class="keyword" style="font-weight: bold;">char</span> arr[] = str.toCharArray();
<span class="keyword" style="font-weight: bold;">for</span> (<span class="keyword" style="font-weight: bold;">int</span> i = <span class="number" style="color: #009999;">0</span>; i < arr.length; i++) {
arr[i] ^= key[i % <span class="number" style="color: #009999;">5</span>];
}
<span class="keyword" style="font-weight: bold;">return</span> <span class="keyword" style="font-weight: bold;">new</span> String(arr);
}
<span class="keyword" style="font-weight: bold;">public</span> <span class="keyword" style="font-weight: bold;">static</span> <span class="keyword" style="font-weight: bold;">void</span> main (String args[]) {
System.out.println(decrypt(<span class="string" style="color: #dd1144;">"%x'sdgu4t3#x#`egj"hs.7%m|/7;hp+l&/S t7tn5v:j'}_dx%"</span>));
}
}
裡面的5個密鑰就是上圖bipush的傳參,別忘了iconst_1的那個1 解密出來是:larry.lau.javax.swing.plaf.nimbus.NimbusLook:4
其實這裡解密出字串沒有什麼用處,因為我們已經拿到老版本的原始碼了,不過在別的軟體逆向分析中可能會非常有用
總結&POC
以下為我修改後的BurpLoader,其中的惡意代碼我已經去除,並將修改前的原值輸出,大家可以在添加burpsuite jar包後編譯運行這段代碼
<span class="keyword" style="font-weight: bold;">package</span> stratburp;
<span class="keyword" style="font-weight: bold;">import</span> burp.StartBurp;
<span class="keyword" style="font-weight: bold;">import</span> java.lang.reflect.Field;
<span class="keyword" style="font-weight: bold;">import</span> java.util.prefs.Preferences;
<span class="keyword" style="font-weight: bold;">import</span> javax.swing.JOptionPane;
<span class="keyword" style="font-weight: bold;">public</span> <span class="class" style="font-weight: bold; color: #445588;"><span class="keyword">class</span> <span class="title">startburp</span>
{</span>
<span class="keyword" style="font-weight: bold;">private</span> <span class="keyword" style="font-weight: bold;">static</span> <span class="keyword" style="font-weight: bold;">final</span> String[] clzzData = { <span class="string" style="color: #dd1144;">"burp.ecc"</span>, <span class="string" style="color: #dd1144;">"burp.voc"</span>, <span class="string" style="color: #dd1144;">"burp.jfc"</span>,
<span class="string" style="color: #dd1144;">"burp.gtc"</span>, <span class="string" style="color: #dd1144;">"burp.zi"</span>, <span class="string" style="color: #dd1144;">"burp.q4c"</span>, <span class="string" style="color: #dd1144;">"burp.pid"</span>, <span class="string" style="color: #dd1144;">"burp.y0b"</span> };
<span class="keyword" style="font-weight: bold;">private</span> <span class="keyword" style="font-weight: bold;">static</span> <span class="keyword" style="font-weight: bold;">final</span> String[] fieldData = { <span class="string" style="color: #dd1144;">"b"</span>, <span class="string" style="color: #dd1144;">"b"</span>, <span class="string" style="color: #dd1144;">"c"</span>, <span class="string" style="color: #dd1144;">"c"</span>, <span class="string" style="color: #dd1144;">"c"</span>, <span class="string" style="color: #dd1144;">"b"</span>, <span class="string" style="color: #dd1144;">"c"</span>, <span class="string" style="color: #dd1144;">"c"</span> };
<span class="keyword" style="font-weight: bold;">private</span> <span class="keyword" style="font-weight: bold;">static</span> <span class="keyword" style="font-weight: bold;">final</span> String errortip = <span class="string" style="color: #dd1144;">"This program can only run with burpsuite_pro_v1.5.01.jar"</span>;
<span class="keyword" style="font-weight: bold;">private</span> <span class="keyword" style="font-weight: bold;">static</span> <span class="keyword" style="font-weight: bold;">final</span> String[] keys = { <span class="string" style="color: #dd1144;">"license1"</span>, <span class="string" style="color: #dd1144;">"uG4NTkffOhFN/on7RT1nbw=="</span> };
<span class="keyword" style="font-weight: bold;">public</span> <span class="keyword" style="font-weight: bold;">static</span> <span class="keyword" style="font-weight: bold;">void</span> main(String[] args)
{
<span class="keyword" style="font-weight: bold;">try</span>
{
<span class="keyword" style="font-weight: bold;">for</span> (<span class="keyword" style="font-weight: bold;">int</span> i = <span class="number" style="color: #009999;">0</span>; i < clzzData.length; i++)
{
Class clzz = Class.forName(clzzData[i]);
Field field = clzz.getDeclaredField(fieldData[i]);
field.setAccessible(<span class="keyword" style="font-weight: bold;">true</span>);
<span class="comment" style="font-style: italic; color: #999988;">//field.set(null, strData[i]); </span>
System.out.println(field.get(<span class="keyword" style="font-weight: bold;">null</span>));
}
Preferences prefs = Preferences.userNodeForPackage(StartBurp.class);
<span class="keyword" style="font-weight: bold;">for</span> (<span class="keyword" style="font-weight: bold;">int</span> i = <span class="number" style="color: #009999;">0</span>; i < keys.length; i++)
{
String v = prefs.get(keys[i], <span class="keyword" style="font-weight: bold;">null</span>);
System.out.println(prefs.get(keys[i], <span class="keyword" style="font-weight: bold;">null</span>));
}
StartBurp.main(args);
}
<span class="keyword" style="font-weight: bold;">catch</span> (Exception e)
{
JOptionPane.showMessageDialog(<span class="keyword" style="font-weight: bold;">null</span>, <span class="string" style="color: #dd1144;">"This program can only run with burpsuite_pro_v1.5.01.jar"</span>, <span class="string" style="color: #dd1144;">"Notice"</span>,<span class="number" style="color: #009999;">0</span>);
}
}
}
其效果如截圖所示
其中前8行輸出為之前BurpLoader惡意修改的目標原值(對我的電腦而言),同一台裝置運行多少遍都是不變的,後面的key由於我之前運行過BurpLoader因此是惡意修改後的值(但是由於前8行沒有修改因此不能通過Burpsuite驗證),可見BurpLoader其實是使用了同一個密鑰來註冊所有不同電腦的,只不過修改並固定了某些參與密鑰計算的環境變數而已,這大概就是Burpsuite破解的主要思路了,至於最初能用的license是怎麼計算出來的,我們以後再研究