文章旨在幫助那些認(rèn)為標(biāo)準(zhǔn)混淆器可以保護(hù)其知識(shí)產(chǎn)權(quán)不被盜用的初級(jí)開(kāi)發(fā)人員,而不是培訓(xùn)黑客。
案例1:認(rèn)證服務(wù)器
客戶端-服務(wù)器模型早在互聯(lián)網(wǎng)出現(xiàn)之前就已經(jīng)存在,并且仍然廣泛使用。同時(shí),有一個(gè)傳言說(shuō),在沒(méi)有客戶端-服務(wù)器部分的情況下無(wú)法構(gòu)建軟件保護(hù),但接下來(lái)我們將展示這只是一個(gè)謬論。
下圖顯示了服務(wù)器-客戶端數(shù)據(jù)流的圖表。出于清晰起見(jiàn),程序片段位于圖表外部。所謂的關(guān)鍵點(diǎn)已經(jīng)標(biāo)出,并將從后面的內(nèi)容中得到解釋。
對(duì)于認(rèn)證服務(wù)器可靠性的謬論非常普遍,因此我們將從這開(kāi)始講解。我們有一個(gè)服務(wù)器,它接收來(lái)自程序的請(qǐng)求,并在檢查后發(fā)送響應(yīng):程序是否可以繼續(xù)工作或需要付費(fèi)注冊(cè)?我們通過(guò)搜索與局域網(wǎng)或互聯(lián)網(wǎng)通信相關(guān)的類(即java.net.*包中的類,如HttpURLConnection等)來(lái)找到簡(jiǎn)單獲取請(qǐng)求的關(guān)鍵點(diǎn)。
讓我們從開(kāi)發(fā)人員的角度和黑客的角度來(lái)看待這個(gè)圖表。在此模型中使用的應(yīng)用程序中有兩個(gè)重要點(diǎn):發(fā)送請(qǐng)求的位置和從服務(wù)器接收響應(yīng)的位置。
原始Java方法的片段,用于執(zhí)行請(qǐng)求-響應(yīng)操作:
boolean authenticate(String requestURL, String params) { URL url = null; HttpURLConnection conn = null; try { url = new URL(requestURL); conn = (HttpURLConnection) url.openConnection(); conn.connect(); } catch (IOException e) { showError("Failed connect to " + requestURL + "."); return false; // Warning } String response = ""; if (conn != null) { try (OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream())) { writer.write(params); // Warning writer.flush(); // Warning writer.close(); } catch (IOException e) { showError("Failed write params " + params + "."); return false; // Warning } try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { String line = ""; while ((line = reader.readLine()) != null) { response += line + "\n"; } reader.close(); } catch (IOException e) { showError("Failed read " + response + "."); return false; // Warning } if (conn != null) conn.disconnect(); } return response.indexOf("activated") > -1; // Error }
容易受攻擊的命令后面跟隨著注釋。
與黑客所看到的混淆后的 Java 方法相同:
static boolean Z(String a, String aa) { String var2 = a; HttpURLConnection var4 = null; try { (var4 = (HttpURLConnection)(new URL(var2)).openConnection()).connect(); } catch (IOException var36) { return true; /* false; */ } String var6 = ""; if (var4 != null) { Object var5; Throwable var10000; try { a = null; var5 = null; try { OutputStreamWriter var3 = new OutputStreamWriter(var4.getOutputStream()); try { // var3.write(a); // var3.flush(); var3.close(); } finally { if (var3 != null) { var3.close(); } } } catch (Throwable var38) { if (a == null) { var10000 = var38; } else { if (a != var38) { a.addSuppressed(var38); } var10000 = a; } // throw var10000; } } catch (IOException var39) { z("Failed connect to " + a + "."); return true; /* false; */ } HttpURLConnection var45; label534: { try { a = null; var5 = null; try { BufferedReader var43 = new BufferedReader(new InputStreamReader( var4.getInputStream())); boolean var21 = false; try { var21 = true; a = ""; BufferedReader var44 = var43; while(true) { if ((a = var44.readLine()) == null) { var43.close(); var21 = false; break; } var6 = var6 + a + "\n"; var44 = var43; } } finally { if (var21) { if (var43 != null) { var43.close(); } } } if (var43 != null) { var45 = var4; var43.close(); break label534; } } catch (Throwable var41) { if (a == null) { var10000 = var41; } else { if (a != var41) { a.addSuppressed(var41); } var10000 = a; } // throw var10000; } } catch (IOException var42) { z("Failed to read " + var2 + "."); return true; /* false; */ } var45 = var4; } if (var45 != null) { var4.disconnect(); } } return true; /* var6.indexOf(z("0'%-'%%!5")) > -1; */ }
很容易看出,通過(guò)將第 97 行以及之前的所有return...s替換為return true, 以及注釋第 19 行和第 20 行,我們就得到了程序的免費(fèi)版本。請(qǐng)注意,服務(wù)器嘗試確定當(dāng)前誰(shuí)正在申請(qǐng)身份驗(yàn)證以及有多少此類請(qǐng)求將失?。ㄟ@是多個(gè)人的許可證的情況)。
在混淆的代碼中,找到方法的輸出可能要困難得多,但是使用探路者方法,或者更好地像狗尋找獵物一樣,遲早可以找到它。黑客從類HttpURLConnection變量或類似類變量的第一個(gè)定義開(kāi)始,沿著線索到達(dá)關(guān)鍵點(diǎn):var4 -> var4 -> ... var3 -> var3 ... -> var4 -> return ... 混淆器引入的其余垃圾可以被忽略。在這種情況下,就像在許多其他情況下一樣,程序的安全性僅取決于黑客的聰明才智,而不取決于您的努力。我們將在下一部分討論這個(gè)問(wèn)題。
案例2:支付并運(yùn)行
以下是Pay and Play圖表,顯示對(duì)應(yīng)的組件和數(shù)據(jù)流。
本模型沒(méi)有試用版。首先付款,然后獲取服務(wù)。黑客無(wú)法進(jìn)行任何攻擊。
對(duì)于開(kāi)發(fā)人員來(lái)說(shuō),下面的句子已經(jīng)成為常規(guī)規(guī)則:“幾乎任何代碼在足夠的時(shí)間和努力下都可以被逆向工程?;煜骺梢允狗聪蚬こ套兊酶永щy和經(jīng)濟(jì)上不切實(shí)際?!钡@是一個(gè)錯(cuò)誤的觀點(diǎn)。存在一種所謂的“偷一次,賣很多次”的攻擊。黑客不是為自己而是為了出售而入侵程序。例如,你仍然可以以幾乎零成本在線購(gòu)買非法副本的MS Office、Windows 7或10以及許多其他軟件。
因此,黑客需要購(gòu)買該程序并執(zhí)行之前描述的客戶端-服務(wù)器方案中的步驟:將接受激活密鑰的相應(yīng)行替換為返回true,并刪除付款訂單中的命令。
為了抵御這種最簡(jiǎn)單的攻擊,開(kāi)發(fā)人員使用從服務(wù)器(激活密鑰)接收的任何Java加密算法的密鑰加密部分代碼。加密部分包括密鑰驗(yàn)證和成功后啟動(dòng)主程序的過(guò)程。以下是代碼片段,包括解密和加載類的代碼。
這段代碼在這里以及接下來(lái)的部分中都使用了。認(rèn)證器
public class Authenticator { public Authenticator(byte[] key, long ... l) { SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); IvParameterSpec iv = new IvParameterSpec(key); try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); CipherInputStream cis = new CipherInputStream(getClass().getResourceAsStream("anyfolder/anyname"), cipher); ByteArrayOutputStream baos = new ByteArrayOutputStream(); copyStream(cis, baos); byte[] bytes = baos.toByteArray(); Class<?> klass = new ClassLoader() { Class<?> define(byte[] bytes, int offset, int length) { Class<?> klass = super.defineClass(null, bytes, 0, length); return klass; } }.define(bytes, 0, bytes.length); klass.getConstructor().newInstance(); } catch (Throwable t) { System.exit(1); } } }
和啟動(dòng)器,應(yīng)使用相同的激活密鑰進(jìn)行加密,并放置在任何資源文件夾中。
public class Launcher { args = Preloader.args; public Launcher() { Application.run(args); } }
其中封裝了Preloader類,該類設(shè)置args值和在付款確認(rèn)后從服務(wù)器接收到的激活密鑰。
public class Preloader { private static byte[] key; public static String[] args; public static void main(String[] args) { receiveConfirmation(); encryptClass(); new Authenticator(key); } private static void receiveConfirmation() { String confirm = responce(); String[] parts = confirm.split(":"); key = hexToBytes(parts[0]); } private static void encryptClass() { IvParameterSpec iv = new IvParameterSpec(key); SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); InputStream is = Preloader.class.getResourceAsStream("/Launcher.class"); CipherInputStream cis = new CipherInputStream(is, cipher); String file = "C:\\Workspaces\\anyfolder\\anyname"; File targetFile = new File(file); OutputStream outStream = new FileOutputStream(targetFile); copyStream(cis, outStream); cis.close(); outStream.close(); } catch (Throwable t) { System.exit(1); } } private static byte[] hexToBytes(String hex) { byte[] bytes = new byte[hex.length() / 2]; for (int i= 0; i < bytes.length; i++) { try { bytes[i] = (byte) (0xff & Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16)); } catch (Exception e) { return null; } } return bytes; } private static void copyStream(InputStream in, OutputStream out) { byte[] buffer = new byte[4096]; int read; try { while ((read = in.read(buffer)) != -1) { out.write(buffer, 0, read); } } catch (IOException e) { System.exit(1); } } private static void sendPaymentOrder(float sum) { ... } private static String responce() { ... } }
這種保護(hù)方法的優(yōu)點(diǎn)是明顯的:
沒(méi)有可以被修改的邏輯表達(dá)式和變量。
路徑追蹤在認(rèn)證器類結(jié)束,無(wú)法偽造或規(guī)避。
沒(méi)有進(jìn)入主程序的入口點(diǎn),甚至沒(méi)有名稱。
我們暫且不涉及復(fù)制保護(hù)的問(wèn)題。
重命名變量、文件和類不會(huì)影響安全級(jí)別,但可能會(huì)產(chǎn)生某種印象。
案例3:"Time Bomb"
下圖顯示了Time Bomb模型的部件和數(shù)據(jù)流。
此模型適用于限時(shí)試用和訂閱。
在這里,逆時(shí)針計(jì)時(shí)器充當(dāng)服務(wù)器。它位于程序內(nèi)部,因?yàn)閷⑵浞胖迷诜?wù)器上會(huì)使我們返回到之前的方案。請(qǐng)求是"剩余多少時(shí)間",響應(yīng)是時(shí)間 > 0 ? 運(yùn)行 : 退出。關(guān)鍵點(diǎn)在于計(jì)數(shù)器本身和計(jì)數(shù)器的輸出。類似于這樣的請(qǐng)求->調(diào)用計(jì)數(shù)器->響應(yīng)->切換過(guò)期/否->
Launcher類中添加了靜態(tài)字段start和period,并對(duì)構(gòu)造函數(shù)進(jìn)行了小幅修改。
public class Launcher { private static long start; private static long period; private static String[] args; public class Application { public static void main(String[] args) { ... } } static { args = Preloader.args; start = Preloader.start; period = Preloader.period; } public Launcher() { if (System.currentTimeMillis() - start < period) { Application.main(args); } else { System.exit(1); } } }
與案例1不同,黑客無(wú)法使用與時(shí)間相關(guān)的Java關(guān)鍵字(方法)以及要更改的行或行來(lái)找到Launcher類。
因此,必須可靠地隱藏此類以防止黑客攻擊。目前,加密是最合適的手段。我們采取以下措施:首先,加密Launcher.class字節(jié),其次,將它們移動(dòng)到/anyfolder文件夾中,將類重命名為任何名稱,然后使用與案例 2中的Authenticator類相同的密鑰解密。
public class Authenticator { public Authenticator(byte[] key, long ... l) { ... try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); ... klass.getConstructor().newInstance(); } catch (Throwable t) { System.exit(1); } } }
Preloader和Launcher類是Pay and Play模型類的修改版本;分別添加了工作開(kāi)始時(shí)間和持續(xù)時(shí)間參數(shù)start和period,以及總金額。
但是,所有試圖通過(guò)加密隱藏激活密鑰的嘗試都受挫,因?yàn)槊荑€和類都會(huì)在JVM內(nèi)存中解密。黑客可以使用內(nèi)存轉(zhuǎn)儲(chǔ)獲取所需的數(shù)據(jù)。首先,它使用Java Tools API,下面是代碼:
DumperAgent:
public class DumperAgent implements ClassFileTransformer { public static void premain(String args, Instrumentation instr) { agentmain(args, instr); } public static void agentmain(String agentArgs, Instrumentation instr) { instr.addTransformer(new DumperAgent(), true); Class<?>[] classes = instr.getAllLoadedClasses(); try { instr.retransformClasses(classes); } catch (UnmodifiableClassException e) {} } public byte[] transform(ClassLoader loader, String className, Class<?> redefinedClass, ProtectionDomain protDomain, byte[] classBytes) { dumpClass(className, classBytes); return null; } private static void dumpClass(String className, byte[] classBytes) { try { className = className.replace("/", File.separator); // ... FileOutputStream fos = new FileOutputStream(fileName); fos.write(classBytes); fos.close(); } catch (Exception e) { e.printStackTrace(); } } }
Preloader and Launcher classes are modifications of the Pay and Play model classes; parameters have been added for the Start and Time of work, start and period, respectively, as well as sum.
public class Preloader { private static float sum = 1000000.00f; // added for Case 3 private static byte[] key; public static String[] args; public static long start; // added for Case 3 public static long period; // added for Case 3 public static void main(String[] args) { sendPaymentOrder(sum); // added for Case 3 receiveConfirmation(); encryptClass(); new Authenticator(key, start, period); } private static void receiveConfirmation() { String confirm = responce(); String[] parts = confirm.split(":"); key = hexToBytes(parts[0]); start = Long.parseLong(parts[1]); // added for Case 3 period = Long.parseLong(parts[2]); // added for Case 3 } private static void encryptClass() { IvParameterSpec iv = new IvParameterSpec(key); SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); InputStream is = Preloader.class.getResourceAsStream("/Launcher.class"); CipherInputStream cis = new CipherInputStream(is, cipher); String file = "C:\\Workspaces\\anyfolder\\anyname"; File targetFile = new File(file); OutputStream outStream = new FileOutputStream(targetFile); copyStream(cis, outStream); cis.close(); outStream.close(); } catch (Throwable t) { System.exit(1); } } private static byte[] hexToBytes(String hex) { ... return bytes; } private static void copyStream(InputStream in, OutputStream out) { ... } private static void sendPaymentOrder(float sum) { ... } }
但是,所有通過(guò)加密隱藏 激活密鑰的嘗試都會(huì)因密鑰和類在 JVM 內(nèi)存中被解密這一事實(shí)而受挫。黑客可以使用內(nèi)存轉(zhuǎn)儲(chǔ)來(lái)獲取他需要的數(shù)據(jù)。首先,它使用 Java 工具 API。代碼如下:
public class DumperAgent implements ClassFileTransformer { public static void premain(String args, Instrumentation instr) { agentmain(args, instr); } public static void agentmain(String agentArgs, Instrumentation instr) { instr.addTransformer(new DumperAgent(), true); Class<?>[] classes = instr.getAllLoadedClasses(); try { instr.retransformClasses(classes); } catch (UnmodifiableClassException e) {} } public byte[] transform(ClassLoader loader, String className, Class<?> redefinedClass, ProtectionDomain protDomain, byte[] classBytes) { dumpClass(className, classBytes); return null; } private static void dumpClass(String className, byte[] classBytes) { try { className = className.replace("/", File.separator); // ... FileOutputStream fos = new FileOutputStream(fileName); fos.write(classBytes); fos.close(); } catch (Exception e) { e.printStackTrace(); } } }
And Attacher:
public class Attacher { private static String pathToAttacherJar, pid; public static void main(String[] args) throws Exception { VirtualMachine vm = VirtualMachine.attach(pid); vm.loadAgent(pathToAttacherJar, null); } }
不幸的是,這次嘗試也沒(méi)有成功。對(duì) Java 運(yùn)行時(shí)參數(shù)的簡(jiǎn)單分析會(huì)檢測(cè)到不需要的參數(shù),并且程序會(huì)在初始化時(shí)停止工作,而沒(méi)有時(shí)間加載任何內(nèi)容。
static { RuntimeMXBean mxBean = ManagementFactory.getRuntimeMXBean(); if (mxBean.getInputArguments().contains("-XX:-DisableAttachMechanism") || mxBean.getInputArguments().contains("-javaagent:")) { System.exit(1); } }
這個(gè)片段應(yīng)該放在通常包含main(String[] args)方法的第一個(gè)類中。
你能做什么?
首先,你需要學(xué)會(huì)以黑客的眼光來(lái)看待問(wèn)題。這意味著要識(shí)別你的程序所屬的架構(gòu),尋找現(xiàn)有的漏洞,以及可能存在的程序漏洞和如何進(jìn)行攻擊。
記住,你總是可以找到針對(duì)任何攻擊的保護(hù)措施。同時(shí)也要記住,“偷一次,多次出售”的攻擊存在,但“做好保護(hù),長(zhǎng)期保護(hù)”的防御也同樣存在。
不要僅僅依賴混淆器、服務(wù)器或加密技術(shù)。只保護(hù)關(guān)鍵點(diǎn)和程序的重要部分。利用程序本身的結(jié)構(gòu),從外部看待它。安全性應(yīng)與你的程序設(shè)計(jì)一樣精心設(shè)計(jì)。
結(jié)束語(yǔ)
代碼保護(hù)是一個(gè)過(guò)程,而不是最終結(jié)果。新的黑客攻擊方法正在被發(fā)明,新版本的JVM發(fā)布,允許更多地操縱JVM內(nèi)存等等。這類似于病毒與殺毒軟件之間的戰(zhàn)爭(zhēng)。
換句話說(shuō),絕對(duì)的武器和絕對(duì)的保護(hù)都不存在,并且也無(wú)法存在。任何新的攻擊方法都會(huì)引起相應(yīng)的防御方法來(lái)抵御這種攻擊。
在幾乎所有安全算法開(kāi)發(fā)的情況下,應(yīng)使用類似于量子物理學(xué)中的觀察效應(yīng)的入侵檢測(cè)過(guò)程。任何觀察(干預(yù))、重置、遠(yuǎn)程代理等操作都會(huì)對(duì)觀察環(huán)境造成干擾,這可以被注意到并采取保護(hù)措施。文章來(lái)源:http://www.zghlxwxcb.cn/article/670.html
因此,我們有一個(gè)攻防系統(tǒng)的經(jīng)典例子。這意味著對(duì)于每一次攻擊行動(dòng),都會(huì)有相應(yīng)的防御行動(dòng),反之亦然。請(qǐng)注意,防御始終處于最佳位置。文章來(lái)源地址http://www.zghlxwxcb.cn/article/670.html
到此這篇關(guān)于如何加固基于Java的程序以防止黑客攻擊的文章就介紹到這了,更多相關(guān)內(nèi)容可以在右上角搜索或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!