
محلل بايت كود جافا قابل للتخصيص عبر قواعد JSON
محلل كود بايت (Bytecode) لجافا قابل للتخصيص عبر قواعد JSON. أداة سطر أوامر تستقبل مسارًا يحتوي على ملف Jar أو War واحد أو أكثر، وتحللها باستخدام القواعد المقدمة وتنشئ تقارير HTML بالنتائج.
usage: java -jar cba-cli.jar [OPTIONS] -a DIRECTORY_TO_ANALYZE
-a,--analyze <pathToAnalyze> مسار الدليل المراد تحليله.
-c,--checks <checks...> قائمة مفصولة بمسافات من الفحوصات المخصصة
التي سيتم تشغيلها في التحليل.
-f,--custom-file <customFile> حدد ملفًا بتنسيق JSON لتشغيل قواعد مخصصة.
اقرأ المزيد على
https://github.com/fergarrui/custom-bytecode-analyzer.
-h,--help طباعة هذه الرسالة.
-i,--items-report <maxItems> أقصى عدد من العناصر لكل تقرير. إذا تجاوز
عدد المشكلات التي تم العثور عليها هذه القيمة،
سيتم تقسيم التقرير إلى ملفات مختلفة. مفيد إذا كنت
تتوقع عددًا كبيرًا من المشكلات في التقرير. القيمة الافتراضية: 50.
-o,--output <outputDir> الدليل لحفظ التقرير. تحذير -
إذا كانت هناك تقارير محفوظة بالفعل في هذا
الدليل فسيتم استبدالها.
الافتراضي هو "report".
-v,--verbose-debug زيادة مستوى التفاصيل إلى وضع التصحيح.
-vv,--verbose-trace زيادة مستوى التفاصيل إلى وضع التتبع - يجعله أبطأ، استخدمه فقط عند الحاجة.
يمكن تحديد ملف القواعد باستخدام الوسيطة -f,--custom-file. الملف بتنسيق JSON وله الهيكل التالي:
finalيمكنك أيضًا مراجعة net.nandgr.cba.custom.model.Rules.java للاطلاع على الهيكل في كود Java.
توجد بالفعل عدة قواعد ضمن الدليل examples. على أي حال، فيما يلي أمثلة لكل قاعدة.
إذا احتجنا إلى العثور على الفئات التي تحتوي على إلغاء تسلسل مخصص، يمكننا القيام بذلك بسهولة. تحدد الفئة إلغاء التسلسل المخصص من خلال تنفيذ private void readObject(ObjectInputStream in). لذلك نحتاج فقط إلى العثور على جميع الفئات التي تم تعريف هذه الطريقة فيها. سيكون كافيًا تعريف قاعدة كما يلي:
{
"rules": [{
"name": "Custom deserialization",
"methods": [{
"name": "readObject",
"visibility": "private",
"parameters" : [{
"type" : "java.io.ObjectInputStream"
}]
}]
}]
}
سيُبلغ عن الطرق ذات الرؤية private، والاسم readObject ومعامل من النوع java.io.ObjectOutputStream. المعاملات عبارة عن مصفوفة، إذا تم تحديد أكثر من واحد، يجب أن تتطابق جميعها للإبلاغ. نظرًا لأن لدينا قاعدة واحدة فقط، سيتم إنشاء تقرير باسم: custom-deserialization-0.html.
في هذه الحالة، يجب تعريف قاعدة واحدة بطريقتين. نفس القاعدة كما في المثال السابق لإلغاء التسلسل، وقاعدة جديدة لمطابقة private void writeObject(ObjectOutputStream out). كما هو موضح في هيكل JSON أعلاه، الخاصية rules.rule.methods هي مصفوفة من الطرق، لذا يمكن كتابة قاعدة مثل هذه:
{
"rules": [{
"name": "Custom serialization and deserialization",
"methods": [{
"name": "readObject",
"visibility": "private",
"parameters" : [{
"type" : "java.io.ObjectInputStream"
}]
},{
"name": "writeObject",
"report": "false",
"visibility": "private",
"parameters" : [{
"type" : "java.io.ObjectOutputStream"
}]
}]
}]
}
تم تعيين الخاصية report إلى false لتجنب الإبلاغ مرتين لنفس القاعدة. نحن نستخدم الطريقة الثانية كشرط فقط، ولكن الإبلاغ عن طرق readObject فقط يجب أن يكون كافيًا للغرض من هذه القاعدة.
إذا لم يتم تعريف خاصية، فسوف تتطابق دائمًا على أنها true. على سبيل المثال، هذه القاعدة ستعيد جميع تعريفات الطرق:
{
"rules": [{
"name": "Method definitions",
"methods": [{
}]
}]
}
يمكن أيضًا العثور على استدعاءات الطرق. سيكون JSON في هذه الحالة:
{
"rules": [{
"name": "String equals",
"invocations": [{
"owner": "java.lang.String",
"method": {
"name": "equals"
}
}]
}]
}
الخاصية owner تحدد الفئة التي تحتوي على الطريقة.
مثال آخر لاستدعاء طريقة أكثر فائدة قليلاً من السابق:
{
"rules": [{
"name": "Method invocation by reflection",
"invocations": [{
"owner": "java.lang.reflect.Method",
"method": {
"name": "invoke"
}
}]
}]
}
هو نفسه أي استدعاء طريقة، ولكن اسم الطريقة في هذه الحالة يجب أن يكون <init>.
{
"rules": [{
"name" : "String instantiation",
"invocations" : [{
"owner" : "java.lang.String",
"method" : {
"name" : "<init>"
}
}]
}]
}
ستجد هذه القاعدة حالات مثل:
[...]
String s = new String("foo");
[...]
في هذا المثال، نريد العثور على استخدامات إلغاء التسلسل (وليس الفئات التي تحدد سلوكيات التسلسل كما في الأمثلة السابقة). يحدث إلغاء التسلسل عندما يتم استدعاء ObjectInputStream.readObject(). على سبيل المثال في مقتطف الكود هذا:
ObjectInputStream in = new ObjectInputStream(fileInputStream);
Object o = in.readObject();
لذا نحتاج إلى العثور على استدعاءات طرق من ObjectInputStream بالاسم readObject. لكنها ستجد الكثير من النتائج الإيجابية الخاطئة في سياق البحث، لأنه عندما تحدد فئة إلغاء تسلسل مخصص، فإنها تقوم باستدعاء لهذه الطريقة داخل طريقة private void readObject(ObjectInputStream in)، وهذا سيلوث التقرير كثيرًا. إذا أردنا استبعاد هذه الحالات والاحتفاظ فقط بإلغاء التسلسل الحقيقي، يمكن استخدام الخاصية notFrom:
{
"rules": [{
"name": "Deserialization usage",
"invocations": [{
"owner": "java.io.ObjectInputStream",
"method": {
"name": "readObject"
},
"notFrom": {
"name": "readObject",
"visibility": "private"
}
}]
}]
}
هذا الملف سيجد استدعاءات java.io.ObjectInputStream.readObject() إذا لم يتم الاستدعاء داخل طريقة private void readObject(ObjectInputStream in).
فئة تم تجميعها بهذا الكود لن يتم الإبلاغ عنها:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Object o = in.readObject();
}
ولكن هذه سيتم الإبلاغ عنها:
public Object deserializeObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Object o = in.readObject();
return o;
}
يمكن تعيين الخاصية from في الاستدعاءات بنفس طريقة notFrom، لكن النتيجة ستكون عكسية: ستطابق فقط إذا تم الاستدعاء من الطريقة المحددة.
يمكن استخدام الخاصية superClass في هذه الحالة. إذا أردنا العثور على جميع الفئات التي تمتد javax.servlet.http.HttpServlet، يمكن أن تكون القاعدة:
{
"rules": [{
"name": "Java servlets",
"superClass" : "javax.servlet.http.HttpServlet"
}]
}
يمكن كتابة قاعدة للعثور على الفئات التي تطبق مصفوفة من الواجهات. إذا تم تعريف أكثر من واجهة في القاعدة، يجب على الفئة تنفيذها جميعًا للإبلاغ عنها. إذا أردنا العثور على الفئات التي تطبق javax.net.ssl.X509TrustManager، ستكون القاعدة:
{
"rules": [{
"name": "X509TrustManager implementations",
"interfaces" : ["javax.net.ssl.X509TrustManager"]
}]
}
يرجى ملاحظة أن interfaces هي مصفوفة، لذا تأكد من إضافة السلاسل بين قوسين مربعين، على سبيل المثال: ["interface1", "interface2", ...].
كما يتم دعم التعليقات التوضيحية (Annotations). يمكن تعريف خصائص تعليقات توضيحية متعددة في قاعدة (للعثور على تعليقات توضيحية للفئة)، أو في الطرق أو المتغيرات (المعاملات أو المتغيرات المحلية). إذا تم العثور عليها جميعًا في الفئة التي تم تحليلها، سيتم الإبلاغ عنها.
على سبيل المثال، إذا أردنا العثور على نقاط نهاية Spring، فسنبحث عن الفئات أو الطرق التي تحتوي على التعليق التوضيحي org.springframework.web.bind.annotation.RequestMapping. لذا، يمكن أن تكون القاعدة:
{
"rules": [{
"name": "Spring endpoint - class annotation",
"annotations" : [{
"type" : "org.springframework.web.bind.annotation.RequestMapping"
}]
},
{
"name": "Spring endpoint - method annotation",
"methods" : [{
"annotations" : [{
"type" : "org.springframework.web.bind.annotation.RequestMapping"
}]
}]
}]
}
يمكن استخدام الخاصية rule.fields للعثور على حقول الفئة. إذا أردنا العثور على حقول String خاصة بأسماء كلمات مرور، يمكن استخدام قاعدة مثل هذه:
{
"rules": [{
"name" : "Password fields",
"fields" : [
{
"visibility" : "private",
"type" : "java.lang.String",
"nameRegex" : "(password|pass|psswd|passwd)"
}
]
}]
}
للعثور على المتغيرات، يمكن استخدام rule.variables. هذه الخاصية ستُبلغ عن المتغيرات المحلية ومتغيرات وسيطات الطريقة.
إذا أردنا العثور على جميع المتغيرات من النوع javax.servlet.http.Part، يمكن أن تكون القاعدة:
{
"rules": [{
"name" : "Servlet upload file",
"methods" : [{
"variables" : [{
"type" : "javax.servlet.http.Part"
}]
}]
}]
}
يمكن تعريف قواعد متعددة في نفس ملف JSON. سيتم معالجتها والإبلاغ عنها بشكل منفصل ولن تؤثر على بعضها البعض. يمكننا دمج بعض الأمثلة السابقة:
{
"rules": [{
"name": "Custom deserialization",
"methods": [{
"name": "readObject",
"visibility": "private",
"parameters": [{
"type" : "java.io.ObjectInputStream"
}]
}]
},{
"name": "Method invocation by reflection",
"invocations": [{
"owner": "java.lang.reflect.Method",
"method": {
"name": "invoke"
}
}]
}]
}
هنا، لدينا قاعدتان ("Custom deserialization" و "Method invocation by reflection"). سيتم معالجتهما كما لو قمت بتنفيذ عمليتين منفصلتين. وسيتم إنشاء تقرير لكل قاعدة. إذا كانت للقواعد نفس الاسم، فسيتم الإبلاغ عنها في نفس الملف.
يمكن تنزيل المشروع وبناؤه لإضافة قواعد مخصصة أكثر تعقيدًا في كود Java غير مغطاة بتنسيق JSON. توجد بالفعل ثلاثة أمثلة ضمن الحزمة net.nandgr.cba.visitor.checks. وهي: CustomDeserializationCheck, DeserializationCheck و InvokeMethodCheck. يمكنك إنشاء القواعد الخاصة بك عن طريق توسيع net.nandgr.cba.custom.visitor.base.CustomAbstractClassVisitor.
كما ذكر أعلاه، يتم إنشاء التقارير افتراضيًا ضمن مجلد report. سيكون لكل قاعدة ملف منفصل ما لم تكن لها نفس الاسم.
إذا كان التقرير كبيرًا جدًا، يمكنك تقسيمه باستخدام المعامل -i,--items-report <maxItems>، وسيحمل كل منها الوسيطة المحددة أو أقل (إذا كان الأخير).
كل عنصر تم الإبلاغ عنه يحدد الجرة (jar) التي تم العثور عليها فيها، واسم الفئة واسم الطريقة (إذا كان ذا صلة). كما يعرض النسخة المفكوكة (decompiled) من الفئة لتسهيل الفحص البصري السريع.
مثال على كيفية عرض العناصر لقاعدة للعثور على إنشاءات java.io.File:

عند البحث عن أخطاء أمنية، من المفيد جدًا وجود رسم بياني للاستدعاءات. في الوقت الحالي، يتم إنشاء ملف بسيط متوافق مع DOT تحت دليل report.
يحتوي الرسم البياني على جميع التدفقات المحتملة التي يمكن من خلالها استدعاء المشكلات التي تم العثور عليها. على سبيل المثال، إذا تم استخدام قاعدة للعثور على إلغاء التسلسل،
سيتم إنشاء رسم بياني يحتوي على جميع المسارات الممكنة المؤدية إلى الطريقة التي تستدعي عملية إلغاء التسلسل.
الملف هو call-graph.dot وسيبدو هكذا (هذا مثال بسيط للغاية):
graph callGraph {
"demo.callgraph.Class1:method1" -- "demo.callgraph.Class2:method2"
"demo.callgraph.Class3:method3" -- "demo.callgraph.Class2:method2"
}
لعرضه بطريقة مرئية، يمكن استخدام DOT (أو أي برنامج متوافق). على سبيل المثال، لتحويل الملف إلى svg:
dot -Tsvg call-graph.dot -o call-graph.svg
يتم ذلك تلقائيًا افتراضيًا إذا تم العثور على DOT في PATH النظام. إذا لم يكن كذلك، يمكن تثبيت DOT في أنظمة Debian باستخدام sudo apt-get install graphviz.
سينشئ ملف SVG باسم call-graph.svg يمكن تحويله إلى PNG أو تصوره باستخدام برامج مثل inkscape أو فقط firefox.
مثال بسيط جدًا للملف أعلاه call-graph.dot، سيكون:

هناك بعض القيود، على سبيل المثال، إذا كان العنصر المطلوب موجودًا في طريقة java.lang.Runnable.run() أو طريقة مشابهة، فلن يجد من أين يتم تنفيذ الخيط.
أيضًا، الرسم البياني ينظف الدورات (cycles) لتجنب الأخطاء StackOverflowError، ويتم ذلك بطريقة متحفظة قليلاً حتى لا يتم استنزاف ذاكرة النظام أثناء تحليل دليل كبير.
ستتم إضافة المزيد من الخيارات في الإصدارات المستقبلية.
java -jar cba-cli-<version>.jar -a /path/with/jars -f /path/with/json/file/rules.json
لاستخدام قواعد Java المخصصة، يجب تحديد أسماء الفئات كوسيطات لـ -c.
java -jar cba-cli-<version>.jar -a /path/with/jars -c DeserializationCheck
تقبل قائمة مفصولة بمسافات، لذلك يمكن تعريف قواعد مخصصة متعددة (كل قاعدة ستنشئ تقريرًا منفصلاً):
java -jar cba-cli-<version>.jar -a /path/with/jars -c DeserializationCheck InvokeMethodCheck CustomDeserializationCheck YourCustomRule
java -jar cba-cli-<version>.jar -a /path/with/jars -f /path/with/json/file/rules.json -c YourCustomRule1 YourCustomRule2
للعثور على الأخطاء، يمكن زيادة مستوى التفاصيل. مستوى التصحيح:
java -jar cba-cli-<version>.jar -a /path/with/jars -c YourCustomRule1 -v
مستوى التتبع:
java -jar cba-cli-<version>.jar -a /path/with/jars -c YourCustomRule1 -vv
في الوقت الحالي، يجب تحويل APK إلى JAR أولاً لتحليله.
d2j-dex2jar.sh -f -o app_to_analyze.jar app_to_analyze.apk-a.يوجد بالفعل ملف جرة قابل للتنفيذ تحت دليل bin على: https://github.com/fergarrui/custom-bytecode-analyzer/blob/master/bin/cba-cli-0.1-SNAPSHOT.jar . إذا كنت ترغب في إجراء تعديلات أو إضافة قواعد مخصصة، يمكن بناء المشروع عن طريق:
git clone https://github.com/fergarrui/custom-bytecode-analyzer.git
cd custom-bytecode-analyzer
mvn clean package
سيتم إنشاء ملفي جرة تحت مجلد target. الملف cba-cli-<version>.jar يحتوي على جميع التبعيات وهو قابل للتنفيذ. يمكن تشغيله باستخدام java -jar cba-cli-<version>.jar.