本文版權(quán)歸贛州兆鑫軟件所有,原創(chuàng)未經(jīng)許可禁止轉(zhuǎn)載!
本文僅作學(xué)習(xí)交流目的信姓,提倡軟件正版,反對盜版绸罗!
工欲善其事必先利其器
- Java Decompiler 1.4.0财破,簡稱JD
- InteliJ IDEA 2017,簡稱IDEA
- dirtyJOE
- Oracle JDK 1.8
做事要先掌握大局
- 將已簽名包轉(zhuǎn)化為未簽名包从诲;
- 定位關(guān)鍵代碼位置左痢;
- 修改class文件字節(jié)碼;
- 替換class文件重新打包系洛;
不行動總也不能成功
1.將已簽名包轉(zhuǎn)化為未簽名包
如果你用到的Jar文件使用了簽名俊性,它會保證里面的每個class文件不能被修改,所以即使你成功修改了class文件中的字節(jié)碼描扯,得到的Jar也是無法運(yùn)行的定页。這些經(jīng)過簽名的Jar包的META-INF文件夾中一般包含了*.SF
和相應(yīng)的*.RSA
文件。這些文件記錄Jar包中每個文件的簽名信息绽诚,以保證代碼不被篡改典徊。
使用下面的方法可以重新生成一個未簽名Jar包杭煎。參考自stackoverflow作者Houtman的回答
// 使用JDK編譯代碼
javac JarUnsigner.java
// 執(zhí)行JarUnsigner,如果是同一個文件夾
java -cp . JarUnsigner <inJar> <outJar>
附上源代碼卒落,免得回答被刪
// JarUnsigner.java
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class JarUnsigner {
private static final String MANIFEST = "META-INF/MANIFEST.MF";
public static void main(String[] args){
if (args.length!=2){
System.out.println("Arguments: <infile.jar> <outfile.jar>");
System.exit(1);
}
String infile = args[0];
String outfile = args[1];
if ((new File(outfile)).exists()){
System.out.println("Output file already exists:" + outfile);
System.exit(1);
}
try{
ZipFile zipFile = new ZipFile(infile);
final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outfile));
for (Enumeration e = zipFile.entries(); e.hasMoreElements();) {
ZipEntry entryIn = (ZipEntry) e.nextElement();
if (! exclude_file( entryIn.getName() ) ) {
/* copy the entry as-is */
zos.putNextEntry( new ZipEntry( entryIn.getName() ));
InputStream is = zipFile.getInputStream(entryIn);
byte[] buf = new byte[1024];
int len;
while ((len = (is.read(buf))) > 0) {
zos.write(buf, 0, len);
}
zos.closeEntry();
} else {
if (MANIFEST.equals(entryIn.getName())){
/* if MANIFEST, adjust the entry */
zos.putNextEntry(new ZipEntry(MANIFEST));
// manifest entries until first empty line. i.e. the 'MainAttributes' section
// (this method is used so to keep the formatting exactly the same)
InputStream mIS = zipFile.getInputStream(entryIn);
BufferedReader in = new BufferedReader(new InputStreamReader(mIS));
String line = in.readLine();
byte[] mNL = "\n".getBytes("UTF-8");
while( line != null && !line.trim().isEmpty() ) {
zos.write( line.getBytes("UTF-8"));
zos.write( mNL );
line = in.readLine();
}
zos.write( mNL );
zos.closeEntry();
}else{
/* else: Leave out the Signature files */
}
}
}
zos.close();
System.out.println("Successfully unsigned " + outfile);
}catch(IOException ex){
System.err.println("Error for file: " + infile);
ex.printStackTrace();
System.exit(1);
}
}
/**
* Exclude .SF signature file
* Exclude .RSA and DSA (signed version of .SF file)
* Exclude SIG- files (unknown sign types for signed .SF file)
* Exclude Manifest file
* @param filename
* @return
*/
public static boolean exclude_file(String filename){
return filename.equals("META-INF/MANIFEST.MF") ||
filename.startsWith("META-INF/SIG-") ||
filename.startsWith("META-INF/") && ( filename.endsWith(".SF") || filename.endsWith(".RSA") || filename.endsWith(".DSA") );
}
}
2. 定位代碼關(guān)鍵位置
舉一個需要輸入序列號才能試用的庫文件的例子羡铲,但是為了保護(hù)Jar包作者權(quán)益,對包名進(jìn)行打碼了儡毕。通過Jar包的說明書也切,可以知道如何使用它,就知道是什么地方輸入序列號啦腰湾,要不然是個正常人也沒法用對吧雷恃。例如
// 文檔說這樣子可以驗(yàn)證序列號
authentication.User("333");
authentication.Serial("94306-56191-128286-2967422");
rock & roll
- 使用JD反編譯Jar包,然后
Save All Sources
费坊,具體步驟這里省略倒槐; - 從IDEA新建一個工程,把反編譯的源代碼放到源目錄附井,Jar包可能是經(jīng)過混淆過的讨越,不過這個關(guān)系不大,我們字節(jié)碼都能改羡忘,還怕看不懂混淆谎痢?繼續(xù)往下走
- 右擊
authentication
這個類文件,選擇Find Usages
卷雕,快捷鍵一般是Ctrl+G
节猿,看到有個叫做p.java的文件用到,做了一些判斷操作Blabla漫雕,看屏幕截圖1:
屏幕截圖1 - 點(diǎn)開這個p.java類搜索到的地方滨嘱,可以看到,它在比較一個返回結(jié)果浸间,然后給出不同的錯誤提示太雨,這個結(jié)果是由一個t.a(三個參數(shù))的方法計算得到,并且看得出來當(dāng)計算結(jié)果的a屬性值為0時就是
Licence OK!
魁蒜,看屏幕截圖2:
屏幕截圖2 - 好囊扳,我們再來看t.java這個文件的a方法又有什么東東(按住
Ctrl
鍵,鼠標(biāo)點(diǎn)擊那個t.a)兜看,我們只要保證他得到的結(jié)果的a屬性等于0就好了锥咸,可以看到只有一種情況下a屬性才會等于0,其他時候都是等于-1的细移,到這一步IDEA的使命就完成了搏予,看屏幕截圖3:
屏幕截圖3
結(jié)果
通過上面5步(取決于不同的包,不一定都是5步)弧轧,我們知道了需要改動t
類里面的a
方法雪侥,讓它里面操作屬性a
時碗殷,值為-1
的都改為0
就能成功。
3. 修改class文件字節(jié)碼
接下來就要使用dirtyJOE軟件來定位并修改字節(jié)碼了速缨,作者也試過Class Editor, Java Bytecode Editor都因年久失修锌妻,沒改成功。通過查閱JVM文檔我們知道給整數(shù)賦值有幾種指令鸟廓,這里就說兩種:
-
iconst_<i>
i可以為m1,0,1,2,3,4,5
从祝,分別設(shè)置的值為-1,0,1,2,3,4,5
襟己,指令16進(jìn)制字節(jié)碼分別是02
設(shè)置-1引谜,03
設(shè)置0,04
設(shè)置1擎浴,以此類推 -
bipush <i>
i范圍可以是0-255员咽,指令16進(jìn)制字節(jié)碼是10
,比如代碼里面有的21就是bipush 21
贮预,16字節(jié)碼表示為10 15
let's go
-
使用dirtyJOE打開
t.class
文件贝室,切換到 Methods 頁上,如下圖所示:
image.png -
雙擊我們需要修改的方法(同名時通過比對方法簽名來區(qū)分)仿吞,進(jìn)入編輯界面滑频,圖中的兩個指令的組合就是將值
-1
賦值給t$a.a
屬性,雙擊圖中的iconst_ml
唤冈,字節(jié)碼是02
峡迷,改為03
,然后回車即可你虹,可以使用Ctrl+F
查找多處進(jìn)行修改:
image.png 關(guān)閉編輯窗口绘搞,保存修改。
warning
要保證字節(jié)碼的數(shù)量不增多傅物,也不減少夯辖,因?yàn)轭惡皖愔g代碼調(diào)用跟字節(jié)位置關(guān)系密切。不然會導(dǎo)致修改的class文件無法使用董饰。原來一行是1個字節(jié)的蒿褂,繼續(xù)用1個字節(jié),2個的就繼續(xù)兩個卒暂,3個的繼續(xù)保持三個啄栓。
4. 替換class文件重新打包
這是最簡單的一步,將修改好的class文件介却,使用zip壓縮工具替換掉即可谴供。如果是你Windows用戶不推薦使用WinRaR,可以將jar文件(第一步生成的未簽名包)重命名為zip文件齿坷,然后選擇用Windows資源管理器
打開桂肌,將修改的class文件復(fù)制粘貼進(jìn)去数焊。