ينفذ Simplify تطبيقًا افتراضيًا لفهم سلوكه ثم يحاول تحسين التعليمات البرمجية بحيث يتصرف بشكل مماثل ولكن يسهل على الإنسان فهمه. كل نوع من أنواع التحسين بسيط وعام، لذلك لا يهم نوع التشويش المحدد المستخدم.
الكود الموجود على اليسار عبارة عن فك تجميع لتطبيق مبهم، وتم إزالة التشويش من الكود الموجود على اليمين.
هناك ثلاثة أجزاء للمشروع: smalivm، وsimplify، والتطبيق التجريبي.
if
أو switch
شرطي بقيمة غير معروفة إلى أخذ كلا الفرعين. usage: java -jar simplify.jar <input> [options]
deobfuscates a dalvik executable
-et,--exclude-types <pattern> Exclude classes and methods which include REGEX, eg: "com/android", applied after include-types
-h,--help Display this message
-ie,--ignore-errors Ignore errors while executing and optimizing methods. This may lead to unexpected behavior.
--include-support Attempt to execute and optimize classes in Android support library packages, default: false
-it,--include-types <pattern> Limit execution to classes and methods which include REGEX, eg: ";->targetMethod("
--max-address-visits <N> Give up executing a method after visiting the same address N times, limits loops, default: 10000
--max-call-depth <N> Do not call methods after reaching a call depth of N, limits recursion and long method chains, default: 50
--max-execution-time <N> Give up executing a method after N seconds, default: 300
--max-method-visits <N> Give up executing a method after executing N instructions in that method, default: 1000000
--max-passes <N> Do not run optimizers on a method more than N times, default: 100
-o,--output <file> Output simplified input to FILE
--output-api-level <LEVEL> Set output DEX API compatibility to LEVEL, default: 15
-q,--quiet Be quiet
--remove-weak Remove code even if there are weak side effects, default: true
-v,--verbose <LEVEL> Set verbosity to LEVEL, default: 0
يتطلب البناء تثبيت Java Development Kit 8 (JDK).
لأن هذا المشروع يحتوي على وحدات فرعية لأطر عمل Android، إما أن يتم استنساخها باستخدام --recursive
:
git clone --recursive https://github.com/CalebFenton/simplify.git
أو قم بتحديث الوحدات الفرعية في أي وقت باستخدام:
git submodule update --init --recursive
ثم، لبناء جرة واحدة تحتوي على جميع التبعيات:
./gradlew fatjar
ستكون جرة Simplify في simplify/build/libs/
. يمكنك اختبار عملها من خلال تبسيط مثال التطبيق المبهم المقدم. إليك كيفية تشغيله (قد تحتاج إلى تغيير simplify.jar
):
java -jar simplify/build/libs/simplify.jar -it " org/cf/obfuscated " -et " MainActivity " simplify/obfuscated-app.apk
لفهم ما يتم إزالة التشويش منه، راجع الملف التمهيدي الخاص بالتطبيق المبهم.
إذا فشل Simplify، فجرّب هذه التوصيات بالترتيب:
-it
.--max-address-visits
و --max-call-depth
و --max-method-visits
الأعلى.-v
أو -v 2
وأبلغ عن المشكلة باستخدام السجلات وتجزئة DEX أو APK.إذا كان البناء على نظام Windows، وفشل البناء مع ظهور خطأ مشابه لما يلي:
تعذر العثور على Tools.jar. يرجى التحقق من أن C:Program FilesJavajre1.8.0_151 يحتوي على تثبيت JDK صالح.
هذا يعني أن Gradle غير قادر على العثور على مسار JDK مناسب. تأكد من تثبيت JDK، وقم بتعيين متغير البيئة JAVA_HOME
على مسار JDK الخاص بك، وتأكد من إغلاق وإعادة فتح موجه الأوامر الذي تستخدمه للإنشاء.
لا تخجل. أعتقد أن التنفيذ الافتراضي وإزالة التشويش يمثلان مشكلات رائعة. أي شخص مهتم يكون تلقائيًا رائعًا ويتم الترحيب بالمساهمات، حتى لو كان ذلك فقط لإصلاح خطأ مطبعي. لا تتردد في طرح الأسئلة في القضايا وتقديم طلبات السحب.
يرجى تضمين رابط إلى APK أو DEX والأمر الكامل الذي تستخدمه. وهذا يجعل إعادة إنتاج مشكلتك (وبالتالي حلها ) أسهل بكثير.
إذا لم تتمكن من مشاركة العينة، فيرجى تضمين تجزئة الملف (SHA1، SHA256، وما إلى ذلك).
إذا قامت إحدى العمليات بوضع قيمة من النوع الذي يمكن تحويله إلى ثابت مثل سلسلة أو رقم أو قيمة منطقية، فإن هذا التحسين سيستبدل تلك العملية بالثابت. على سبيل المثال:
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
invoke-static {v0}, Lmy/string/Decryptor;->decrypt(Ljava/lang/String;)Ljava/lang/String;
# Decrypts to: "Tell me of your homeworld, Usul."
move-result v0
في هذا المثال، يتم فك تشفير سلسلة مشفرة ووضعها في v0
. نظرًا لأن السلاسل النصية "قابلة للثبات"، فيمكن استبدال move-result v0
const-string
:
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
invoke-static {v0}, Lmy/string/Decryptor;->decrypt(Ljava/lang/String;)Ljava/lang/String;
const-string v0, "Tell me of your homeworld, Usul."
الرمز ميت إذا كانت إزالته لا يمكن أن تؤدي إلى تغيير سلوك التطبيق. الحالة الأكثر وضوحًا هي إذا كان الرمز غير قابل للوصول، على سبيل المثال if (false) { // dead }
). إذا كان الكود قابلاً للوصول، فيمكن اعتباره ميتًا إذا لم يؤثر على أي حالة خارج الطريقة، أي ليس له أي آثار جانبية . على سبيل المثال، قد لا يؤثر الكود على القيمة المرجعة للطريقة، أو يغير أي متغيرات للفئة، أو يقوم بأي عملية إدخال/إخراج. من الصعب تحديد هذا في التحليل الثابت. لحسن الحظ، سماليفم لا يجب أن يكون ذكيا. إنه ينفذ بغباء كل ما في وسعه ويفترض أن هناك آثارًا جانبية إذا لم يكن متأكدًا. خذ بعين الاعتبار المثال من الانتشار المستمر:
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
invoke-static {v0}, Lmy/string/Decryptor;->decrypt(Ljava/lang/String;)Ljava/lang/String;
const-string v0, "Tell me of your homeworld, Usul."
في هذا الكود، لم يعد invoke-static
يؤثر على القيمة المرجعة للطريقة ولنفترض أنها لا تفعل أي شيء غريب مثل كتابة البايتات إلى نظام الملفات أو مقبس الشبكة لذلك ليس لها أي آثار جانبية. يمكن إزالته ببساطة.
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
const-string v0, "Tell me of your homeworld, Usul."
أخيرًا، تقوم const-string
الأولى بتعيين قيمة للسجل، ولكن هذه القيمة لا يتم استخدامها أبدًا، أي أن التعيين ميت. ويمكن أيضا إزالتها.
const-string v0, "Tell me of your homeworld, Usul."
الحوزة!
أحد التحديات الرئيسية التي تواجه التحليل الثابت لجافا هو الانعكاس. ليس من الممكن معرفة أن الحجج الخاصة بأساليب الانعكاس دون إجراء تحليل دقيق لتدفق البيانات. هناك طرق ذكية ومهرة للقيام بذلك، ولكن smalivm يفعل ذلك بمجرد تنفيذ التعليمات البرمجية. عندما يجد استدعاء الأسلوب المنعكس مثل:
invoke-virtual {v0, v1, v2}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
يمكنه معرفة قيم v0
و v1
و v2
. إذا كان متأكدًا من القيم، فيمكنه استبدال استدعاء الأسلوب Method.invoke()
باستدعاء أسلوب فعلي غير منعكس. الأمر نفسه ينطبق على عمليات البحث عن الحقول والفئة المنعكسة.
بالنسبة لكل شيء لا يتناسب تمامًا مع فئة معينة، هناك تحسينات في ثقب الباب. يتضمن ذلك إزالة عمليات check-cast
غير المفيدة، واستبدال استدعاءات Ljava/lang/String;-><init>
بـ const-string
، وما إلى ذلك.
.method public static test1 () I
.locals 2
new-instance v0 , L java/lang/Integer ;
const/4 v1 , 0x1
invoke-direct { v0 , v1 }, L java/lang/Integer ; -> <init> ( I ) V
invoke-virtual { v0 }, L java/lang/Integer ; -> intValue () I
move-result v0
return v0
.end method
كل ما يفعله هذا هو v0 = 1
.
.method public static test1 () I
.locals 2
new-instance v0 , L java/lang/Integer ;
const/4 v1 , 0x1
invoke-direct { v0 , v1 }, L java/lang/Integer ; -> <init> ( I ) V
invoke-virtual { v0 }, L java/lang/Integer ; -> intValue () I
const/4 v0 , 0x1
return v0
.end method
يتم استبدال move-result v0
بـ const/4 v0, 0x1
. وذلك لأن هناك قيمة إرجاع محتملة واحدة فقط لـ intValue()I
ويمكن جعل نوع الإرجاع ثابتًا. الوسيطتان v0
و v1
لا لبس فيهما ولا تتغيران. وهذا يعني أن هناك إجماعًا على القيم لكل مسار تنفيذ محتمل في intValue()I
. أنواع أخرى من القيم التي يمكن تحويلها إلى ثوابت:
const/4
، const/16
، إلخ.const-string
const-class
.method public static test1 () I
.locals 2
const/4 v0 , 0x1
return v0
.end method
نظرًا لأن الكود الموجود أعلاه const/4 v0, 0x1
لا يؤثر على الحالة خارج الطريقة (بدون آثار جانبية)، فيمكن إزالته دون تغيير السلوك. إذا كان هناك استدعاء أسلوب كتب شيئًا ما إلى نظام الملفات أو الشبكة، فلا يمكن إزالته لأنه يؤثر على الحالة خارج الطريقة. أو إذا أخذت test()I
وسيطة قابلة للتغيير، مثل LinkedList
، فلا يمكن اعتبار أي تعليمات وصلت إليها ميتة.
أمثلة أخرى على الكود الميت:
if (false) { dead_code(); }
هذه الأداة متاحة بموجب ترخيص مزدوج: ترخيص تجاري مناسب للمشاريع مغلقة المصدر وترخيص GPL يمكن استخدامه في البرامج مفتوحة المصدر.
اعتمادا على احتياجاتك، يجب عليك اختيار واحد منهم واتباع سياساته. تتوفر تفاصيل السياسات والاتفاقيات الخاصة بكل نوع ترخيص في ملفات LICENSE.COMMERCIAL وLICENSE.GPL.