Flare-On 2019 كتابة



-0x01 - مقدمة


هذا المقال مخصص لتحليل جميع مهام Flare-On 2019 - المسابقة السنوية للهندسة العكسية من FireEye. في هذه المسابقات ، أشارك للمرة الثانية. في العام السابق ، تمكنت من الحصول على المركز الحادي عشر من حيث وقت الانتهاء ، بعد أن حل جميع المشاكل في حوالي 13 يومًا. كانت مجموعة المهام هذا العام أسهل ، والتقيت في 54 ساعة ، مع الأخذ في نفس الوقت 3 مكان من حيث التسليم.


حاولت في هذه المقالة وصف تلك اللحظات التي أثارت اهتمامي الكبير ، وبالتالي ، فإن التحليل لن يصف روتين العمل في المؤسسة الدولية للتنمية ، وفهم خوارزميات كل وظيفة وغيرها من النقاط غير المثيرة للاهتمام. آمل أنه بعد قراءة هذا ، ستجد شيء جديد ومفيد لنفسك. يمكنك العثور على تحليل المشكلات من المؤلفين ، وكذلك بعض الإحصاءات والجوائز للفائزين هنا .


إذا كنت مهتما ، ثم مرحبا بكم في القط!


0x00 - المحتويات


  1. 0x01 - Memecat Battlestation [إصدار تجريبي لبرنامج كومبيوتري]
  2. 0x02 - طويل
  3. 0x03 - Flarebear
  4. 0x04 - Dnschess
  5. 0x05 - التجريبي
  6. 0x06 - bmphide
  7. 0x07 - wopr
  8. 0x08 - ثعبان
  9. 0x09 - إعادة تحميل
  10. 0x0A - موغاتو
  11. 0x0B - vv_max
  12. 0x0C - مساعدة
  13. 0x0D - ملخص


0x01 - Memecat Battlestation [إصدار تجريبي لبرنامج كومبيوتري]


مرحبًا بك في تحدي التوهج السادس!

هذه هي لعبة بسيطة. عكس ذلك هندسياً لمعرفة "رموز الأسلحة" التي تحتاج إلى إدخالها لهزيمة كل من العدوين وستكشف شاشة النصر العلم. أدخل العلم هنا على هذا الموقع للتسجيل والانتقال إلى المستوى التالي.

* هذا التحدي مكتوب في. NET. إذا لم يكن لديك بالفعل أداة هندسية عكسية مفضلة. أوصي dnSpy

** إذا كنت قد قمت بالفعل بحل النسخة الكاملة من هذه اللعبة في جناحنا في BlackHat أو الإصدار اللاحق على twitter ، تهانينا ، أدخل العلم من شاشة النصر الآن لتجاوز هذا المستوى.

وضعت هذه المهمة مسبقًا كجزء من Black Hat USA 2019 ، في نفس الوقت الذي قررت فيه ذلك. لا أتذكر كيف حلها المهمة بسيطة للغاية ، لذلك لن نفكر في حلها.



0x02 - طويل


سر هذا التحدي المقبل مخفي بذكاء. ومع ذلك ، مع النهج الصحيح ، لن يستغرق إيجاد الحل وقتًا طويلاً.

بالنظر إلى x86. exe الملف. عند محاولة البدء ، يتم عرض رسالة بالمحتويات التالية:



عند تحليل التطبيق ، قد تجد أن الرسالة مخزنة في بعض الترميز بطول حرف متغير (من 1 إلى 4 بايت). عندما يتم استدعاء وظيفة فك التشفير ، فإنها تتلقى طول النتيجة المتوقعة ، وهي أقصر من الرسالة نفسها ، وهذا هو السبب في أن العلم غير مرئي. يمكنك إصلاح قيمة الطول التي تم تمريرها إلى الوظيفة في وضع التصحيح والحصول على الرسالة كاملة مع العلم:



يمكنك أيضًا إعادة كتابة خوارزمية فك التشفير في Python والحصول على العلم:


msg = [ ... ] #      output = [] i = 0 while i < len(msg): if (msg[i] >> 3) == 0x1e: out_char = ( ((msg[i + 3] & 0x3F) << 0 ) | ((msg[i + 2] & 0x3F) << 6 ) | ((msg[i + 1] & 0x3F) << 12) | ((msg[i + 0] & 7) << 18) ) output.append(out_char) i += 4 elif (msg[i] >> 4) == 0x0e: out_char = ( ((msg[i + 2] & 0x3F) << 0 ) | ((msg[i + 1] & 0x3F) << 6 ) | ((msg[i + 0] & 0xF) << 12) ) output.append(out_char) i += 3 elif (msg[i] >> 5) == 6: out_char = ( ((msg[i + 1] & 0x3F) << 0 ) | ((msg[i + 0] & 0xF) << 6 ) ) output.append(out_char) i += 2 else: output.append(msg[i]) i += 1 print(bytes([i for i in output])) # b'I never broke the encoding: I_a_M_t_h_e_e_n_C_o_D_i_n_g@flare-on.com' 


0x03 - Flarebear


لقد أنشأنا في Flare حيواننا الأليف Tamagotchi ، وهو flarebear. إنه صعب للغاية. أبقيه على قيد الحياة وسعيد وسيعطيك العلم.

في هذه المهمة ، يتم تقديم ملف apk لنظام Android . النظر في طريقة الحل دون بدء التطبيق نفسه.


الخطوة الأولى هي الحصول على الكود المصدري للتطبيق. للقيام بذلك ، باستخدام مجموعة dex2jar المساعدة dex2jar بتحويل apk إلى jar ، ثم احصل على شفرة مصدر Java باستخدام أداة فك الترميز ، والتي أفضل استخدام cfr .


 ~/retools/d2j/d2j-dex2jar.sh flarebear.apk java -jar ~/retools/cfr/cfr-0.146.jar --outputdir src flarebear-dex2jar.jar 

من خلال تحليل التعليمات البرمجية المصدر للتطبيق ، يمكنك العثور على طريقة مثيرة للاهتمام .danceWithFlag() ، والتي توجد في ملف FlareBearActivity.java . داخل .danceWithFlag() ، يتم فك تشفير موارد التطبيق raw باستخدام طريقة .decrypt(String, byte[]) ، الوسيطة الأولى هي السلسلة التي تم الحصول عليها باستخدام طريقة .getPassword() . بالتأكيد العلم في موارد مشفرة ، لذلك دعونا نحاول فك تشفيرها. للقيام بذلك ، قررت إعادة كتابة الكود الذي تم فك تشفيره قليلاً ، والتخلص من تبعيات Android وترك فقط الطرق الضرورية لفك التشفير ، بحيث يمكن تجميع الشفرة الناتجة. علاوة على ذلك ، أثناء التحليل ، وجد أن طريقة .getPassword() تعتمد على ثلاث قيم لحالة عدد صحيح. تكمن كل قيمة في فاصل زمني صغير من 0 إلى N ، بحيث يمكنك استعراض جميع القيم الممكنة في البحث عن كلمة المرور المطلوبة.


والنتيجة هي الكود التالي:


Main.java
 import java.io.InputStream; import java.nio.charset.Charset; import java.security.Key; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.KeySpec; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; import java.util.Collections; import java.io.*; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; public final class Main { public static void main (String args []) throws Exception { Main a = new Main(); InputStream inputStream = new FileInputStream("ecstatic"); long fileSize = new File("ecstatic").length(); byte[] file1 = new byte[(int) fileSize]; inputStream.read(file1); inputStream = new FileInputStream("ecstatic2"); fileSize = new File("ecstatic2").length(); byte[] file2 = new byte[(int) fileSize]; inputStream.read(file2); for(int i = 0; i < 9; i++) { for(int j = 0; j < 7; j++) { for(int k = 1; k < 16; k++) { String pass = a.getPassword(i, j, k); try { byte[] out1 = a.decrypt(pass, file1); byte[] out2 = a.decrypt(pass, file2); OutputStream outputStream = new FileOutputStream("out1"); outputStream.write(out1); outputStream = new FileOutputStream("out2"); outputStream.write(out2); System.out.println("yep!"); } catch (javax.crypto.BadPaddingException ex) { } } } } } public final byte[] decrypt(Object object, byte[] arrby) throws Exception { Object object2 = Charset.forName("UTF-8"); object2 = "pawsitive_vibes!".getBytes((Charset)object2); object2 = new IvParameterSpec((byte[])object2); object = ((String)object).toCharArray(); Object object3 = Charset.forName("UTF-8"); object3 = "NaClNaClNaCl".getBytes((Charset)object3); object = new PBEKeySpec((char[])object, (byte[])object3, 1234, 256); object = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret((KeySpec)object); object3 = new SecretKeySpec(((SecretKey)object).getEncoded(), "AES"); object = Cipher.getInstance("AES/CBC/PKCS5Padding"); ((Cipher)object).init(2, (Key)object3, (AlgorithmParameterSpec)object2); object = ((Cipher)object).doFinal(arrby); return (byte [])object; } public final String getPassword(int n, int n2, int n3) { String string2 = "*"; String string3 = "*"; switch (n % 9) { case 8: { string2 = "*"; break; } case 7: { string2 = "&"; break; } case 6: { string2 = "@"; break; } case 5: { string2 = "#"; break; } case 4: { string2 = "!"; break; } case 3: { string2 = "+"; break; } case 2: { string2 = "$"; break; } case 1: { string2 = "-"; break; } case 0: { string2 = "_"; } } switch (n3 % 7) { case 6: { string3 = "@"; break; } case 4: { string3 = "&"; break; } case 3: { string3 = "#"; break; } case 2: { string3 = "+"; break; } case 1: { string3 = "_"; break; } case 0: { string3 = "$"; } case 5: } String string4 = String.join("", Collections.nCopies(n / n3, "flare")); String string5 = String.join("", Collections.nCopies(n2 * 2, this.rotN("bear", n * n2))); String string6 = String.join("", Collections.nCopies(n3, "yeah")); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(string4); stringBuilder.append(string2); stringBuilder.append(string5); stringBuilder.append(string3); stringBuilder.append(string6); return stringBuilder.toString(); } public final String rotN(String charSequence, int n) { Collection<String> collection = new ArrayList(charSequence.length()); for (int i = 0; i < charSequence.length(); ++i) { char c; char c2 = c = charSequence.charAt(i); if (Character.isLowerCase(c)) { char c3; c2 = c3 = (char)(c + n); if (c3 > 'z') { c2 = c3 = (char)(c3 - n * 2); } } collection.add(Character.valueOf(c2).toString()); } return collection.stream().collect(Collectors.joining()); // return ArraysKt.joinToString$default(CollectionsKt.toCharArray((List)collection), (CharSequence)FLARE_BEAR_NAME, null, null, 0, null, null, 62, null); } } 

سنقوم باستخراج الموارد المشفرة وتجميع وتشغيل الملف الناتج:


 $ ~/retools/apktool/apktool d flarebear.apk $ cp flarebear/res/raw/* . $ javac Main.java $ java Main 

لحسن الحظ ، واحد فقط من خيارات كلمة المرور التي تحددها مناسب. نتيجة لذلك ، نحصل على صورتين مع العلم:


 ~/flareon2019/3 - Flarebear$ file out* out1: PNG image data, 2100 x 2310, 8-bit/color RGB, non-interlaced out2: PNG image data, 2100 x 2310, 8-bit/color RGB, non-interlaced 




0x04 - Dnschess


قادنا بعض زيارات الشبكة المشبوهة إلى برنامج الشطرنج غير المصرح به الذي يعمل على سطح مكتب أوبونتو. يبدو أن هذا هو عمل قراصنة الكمبيوتر عبر الإنترنت. ستحتاج إلى اتخاذ الخطوات الصحيحة لحل هذه الخطوة. حظا سعيدا

تحتوي هذه المهمة على تفريغ حركة المرور وملف قابل للتنفيذ ELF ChessUI ومكتبة ChessAI.so . من خلال تشغيل الملف القابل للتنفيذ ، يمكنك رؤية رقعة الشطرنج.



لنبدأ التحليل مع تفريغ حركة المرور.



تتكون كل حركة المرور من استعلامات إلى خادم DNS النوع A تتكون الاستعلامات نفسها من أسماء القطع ووصف الحركة في لعبة الشطرنج والجزء الثابت .game-of-thrones.flare-on.com ، على سبيل المثال rook-c3-c6.game-of-thrones.flare-on.com . في الجزء الثابت ، يمكنك بسهولة العثور على المكان المناسب في مكتبة ChessAI.so :


 signed __int64 __fastcall getNextMove(int idx, const char *chess_name, unsigned int pos_from, unsigned int pos_to, \__int64 a5) { struct hostent *v9; // [rsp+20h] [rbp-60h] char *ip_addr; // [rsp+28h] [rbp-58h] char dns_name; // [rsp+30h] [rbp-50h] unsigned __int64 v12; // [rsp+78h] [rbp-8h] v12 = __readfsqword(0x28u); strcpy(&dns_name, chess_name); pos_to_str(&dns_name, pos_from); pos_to_str(&dns_name, pos_to); strcat(&dns_name, ".game-of-thrones.flare-on.com"); v9 = gethostbyname(&dns_name); if ( !v9 ) return 2LL; ip_addr = *v9->h_addr_list; if ( *ip_addr != 127 || ip_addr[3] & 1 || idx != (ip_addr[2] & 0xF) ) return 2LL; sleep(1u); flag[2 * idx] = ip_addr[1] ^ key[2 * idx]; flag[2 * idx + 1] = ip_addr[1] ^ key[2 * idx + 1]; *(_DWORD *)a5 = (unsigned __int8)ip_addr[2] >> 4; *(_DWORD *)(a5 + 4) = (unsigned __int8)ip_addr[3] >> 1; strcpy((char *)(a5 + 8), off_4120[idx]); return (unsigned __int8)ip_addr[3] >> 7; } 

يمكن ملاحظة ذلك من الكود الذي يستند إلى عناوين ip المستلمة ، يتم فك تشفير سلسلة بايت معينة ، والتي يتم تخزينها في منطقة ذاكرة أخرى ، والتي اتصلت بها.


لحل هذه المهمة ، أول ما عليك فعله هو الحصول على جميع عناوين ip من تفريغ حركة المرور. يمكنك القيام بذلك باستخدام الأمر التالي:


 tshark -r capture.pcap | grep -P -o '127.(\d+).(\d+).(\d+)' | grep -v '127.0.0.1' 

ips حفظ جميع عناوين ip في ملف ips ، ips استخدام رمز Python التالي للحصول على العلامة:


 with open('ips') as f: ips = f.read().split() flag = bytearray(64) key = b'yZ\xb8\xbc\xec\xd3\xdf\xdd\x99\xa5\xb6\xac\x156\x85\x8d\t\x08wRMqT}\xa7\xa7\x08\x16\xfd\xd7' for ip in ips: a, b, c, d = map(int, ip.split('.')) if d & 1: continue idx = c & 0xf if idx > 14: continue flag[2*idx] = b ^ key[2*idx] flag[2*idx + 1] = b ^ key[2*idx + 1] print(flag.decode() + '@flare-on.com') # LooksLikeYouLockedUpTheLookupZ@flare-on.com 


0x05 - التجريبي


حاول شخص ما في فريق Flare إقناعنا بمهاراتهم الديموغرافية. يبدو فارغا. معرفة ما إذا كان يمكنك معرفة ذلك أو ربما سيكون علينا لاطلاق النار عليهم. لا ضغط.

4k.exe الملف القابل للتنفيذ 4k.exe ، والذي يستخدم DirectX . عند بدء التشغيل ، يتم عرض شعار FlareOn الدوار في النافذة الرئيسية.



يكشف التحليل الثابت للبرنامج عن وظيفة واحدة ، وهي نقطة الدخول. في المحتوى ، تشبه الدالة تنفيذ فك تشفير التعليمات البرمجية. لن نضيع الوقت في تحليل خوارزمية هذه الوظيفة ، فقط ضع نقطة توقف على تعليمات ret ونرى أين يتم نقل التحكم. بعد العودة ، نجد 0x00420000 على العنوان 0x00420000 ، وهو الرمز الذي يتم تفكيكه على أنه شيء مناسب:



ثم تقرر نقل هذا الرمز من وضع التصحيح إلى قاعدة بيانات IDA باستخدام API ومتابعة التحليل الثابت.


يستورد الكود الجديد في البداية الوظائف الضرورية من المكتبات المختلفة. يمكن أيضًا استعادة جدول هذه الوظائف في الديناميات. والنتيجة هي مجموعة الوظائف التالية:



نقطة الدخول "الحقيقية" للبرنامج هي:



لاحظ إنشاء DeviceInterface نوع IDirect3DDevice9 ** . في المستقبل ، يتم استخدام هذه الواجهة بشكل نشط ، ولتبسيط الاتجاه المعاكس ، من الضروري تحديد جدول بأساليبه. كان من الممكن العثور بسرعة على تعريف الواجهة ، على سبيل المثال ، هنا . قمت بتحليل هذا الجدول وقمت بتحويله إلى بنية IDA . يمكن لتطبيق النوع الناتج على DeviceInterface تبسيط تحليل الرمز الإضافي بشكل ملحوظ. توضح اللقطات التالية نتيجة أداة فك التشفير للوظيفة الرئيسية لدورة عرض المشهد قبل وبعد الكتابة.




بناءً على مزيد من التحليل ، تم العثور على شبكتين مضلعتين (mesh ، polygon mesh) في البرنامج ، على الرغم من أنه عندما يتم تشغيل البرنامج ، فإننا نرى كائنًا واحدًا فقط. أيضًا ، عند إنشاء شبكات ، يتم تشفير رؤوسها باستخدام XOR ، مما يثير الشكوك أيضًا. دعونا فك وتصور القمم. الشبكة الثانية هي الأكثر أهمية ، منذ ذلك الحين لديها أكثر بكثير القمم. matplotlib أن قمت matplotlib كل القمم ، وجدت أن إحداثي Z لكل منهما يساوي 0 ، لذلك من أجل التصور ، تم رسم رسومات بيانية ثنائية الأبعاد باستخدام matplotlib . تم إيقاف تشغيل التعليمات البرمجية والنتيجة التالية مع إشارة:


 import struct import matplotlib.pyplot as plt with open('vertexes', 'rb') as f: data = f.read() n = len(data) // 4 data = list(struct.unpack('{}I'.format(n), data)) key = [0xCB343C8, 0x867B81F0, 0x84AF72C3] data = [data[i] ^ key[i % 3] for i in range(len(data))] data = struct.pack('{}I'.format(n), *data) data = list(struct.unpack('{}f'.format(n), data)) x = data[0::3] y = data[1::3] z = data[2::3] print(z) plt.plot(x, y) plt.show() 



0x06 - bmphide


تايلر دين ارتفع جبل. Elbert (أطول جبل في كولورادو) في الثانية صباحًا لالتقاط هذه الصورة في الوقت المثالي. لا تخطي يوم الساق. وجدنا هذه الصورة وقابلة للتنفيذ على محرك الإبهام غادر على رأس درب. هل يمكن الوثوق به؟

في المهمة ، يتم image.bmp الملف القابل للتنفيذ image.bmp والصورة image.bmp . يمكن افتراض أن بعض الرسائل مخفية في الصورة باستخدام طرق إخفاء المعلومات.


تتم كتابة الثنائية في C# ، لذلك استخدمت الأداة المساعدة dnSpy للتحليل. يمكنك أن تلاحظ على الفور أن معظم أسماء الطرق مبهمة. إذا نظرت إلى طريقة Program.Main ، فيمكنك فهم منطق البرنامج ووضع افتراضات حول الغرض من بعضها:


 // BMPHIDE.Program // Token: 0x06000018 RID: 24 RVA: 0x00002C18 File Offset: 0x00002C18 private static void Main(string[] args) { Program.Init(); Program.yy += 18; string filename = args[2]; string fullPath = Path.GetFullPath(args[0]); string fullPath2 = Path.GetFullPath(args[1]); byte[] data = File.ReadAllBytes(fullPath2); Bitmap bitmap = new Bitmap(fullPath); byte[] data2 = Program.h(data); Program.i(bitmap, data2); bitmap.Save(filename); } 

  • تتم تهيئة التطبيق باستخدام الأسلوب Program.Init()
  • قراءة ملف البيانات وملف الصور
  • باستخدام طريقة byte [] Program.h(byte []) ، يتم تنفيذ بعض تحويل البيانات
  • باستخدام الأسلوب Program.i(Bitmap, byte[]) ، يتم إدراج البيانات المحولة في الصورة
  • يتم حفظ الصورة الناتجة باسم جديد.

عند تهيئة التطبيق ، A استدعاء الأساليب المختلفة للفئة A أظهر تحليل سطحي للفصل تشابه بعض طرقه مع أساليب obfuscator AntiTamper.JIT.cs (ملف AntiTamper.JIT.cs ). التطبيق محمي حقًا من تصحيح الأخطاء. في الوقت نفسه ، لم يكن من الممكن إزالة آليات الحماية باستخدام الأداة المساعدة de4dot ، لذلك تقرر مواصلة التحليل.


ضع في اعتبارك طريقة Program.i ، والتي تُستخدم لإدخال البيانات في صورة ما.


 public static void i(Bitmap bm, byte[] data) { int num = Program.j(103); for (int i = Program.j(103); i < bm.Width; i++) { for (int j = Program.j(103); j < bm.Height; j++) { bool flag = num > data.Length - Program.j(231); if (flag) { break; } Color pixel = bm.GetPixel(i, j); int red = ((int)pixel.R & Program.j(27)) | ((int)data[num] & Program.j(228)); int green = ((int)pixel.G & Program.j(27)) | (data[num] >> Program.j(230) & Program.j(228)); int blue = ((int)pixel.B & Program.j(25)) | (data[num] >> Program.j(100) & Program.j(230)); Color color = Color.FromArgb(Program.j(103), red, green, blue); bm.SetPixel(i, j, color); num += Program.j(231); } } } 

تشبه إلى حد كبير LSB الكلاسيكية ، ومع ذلك ، في الأماكن التي يتوقع فيها ثوابت ، يتم استخدام أسلوب int Program.j(byte) . تعتمد نتيجة عملها على القيم العالمية المختلفة التي تم الحصول عليها ، بما في ذلك أثناء التهيئة في أسلوب Program.Init() . وقد تقرر عدم عكس عمله ، ولكن للحصول على جميع القيم الممكنة في وقت التشغيل. يسمح dnSpy بتحرير كود التطبيق dnSpy وحفظ الوحدات المعدلة. نستفيد من هذا ونعيد كتابة Program.Main الطريقة كما يلي:


 private static void Main(string[] args) { Program.Init(); Program.yy += 18; for (int i = 0; i < 256; i++) { Console.WriteLine(string.Format("j({0}) = {1}", i, Program.j((byte)i))); } } 

عند بدء التشغيل ، نحصل على القيم التالية:


 E:\>bmphide_j.exe j(0) = 206 j(1) = 204 j(2) = 202 j(3) = 200 j(4) = 198 j(5) = 196 j(6) = 194 j(7) = 192 j(8) = 222 j(9) = 220 j(10) = 218 j(11) = 216 j(12) = 214 j(13) = 212 j(14) = 210 j(15) = 208 j(16) = 238 j(17) = 236 j(18) = 234 j(19) = 232 j(20) = 230 ... 

استبدال المكالمات إلى Program.j في الأسلوب Program.j مع الثوابت الناتجة:


 public static void i(Bitmap bm, byte[] data) { int num = 0; for (int i = 0; i < bm.Width; i++) { for (int j = 0; j < bm.Height; j++) { bool flag = num > data.Length - 1; if (flag) { break; } Color pixel = bm.GetPixel(i, j); int red = ((int)pixel.R & 0xf8) | ((int)data[num] & 0x7); int green = ((int)pixel.G & 0xf8) | (data[num] >> 3 & 0x7); int blue = ((int)pixel.B & 0xfc) | (data[num] >> 6 & 0x3); Color color = Color.FromArgb(0, red, green, blue); bm.SetPixel(i, j, color); num += 1; } } } 

أصبح من الواضح الآن كيفية إدراج كل بايت من الرسالة في الصورة:


  • يتم وضع البتات من 0 إلى 2 في 3 LSBs للقناة الحمراء للنقطة
  • يتم وضع البتات من 3 إلى 5 في 3 LSBs للقناة الخضراء للنقطة
  • يتم وضع البتات من 6 إلى 7 في LSBs 2 من القناة الزرقاء للنقطة

بعد ذلك ، حاولت تكرار خوارزمية طريقة تحويل البيانات ، لكن نتيجة الحساب لم تتطابق مع مخرجات البرنامج. كما اتضح فيما بعد ، فإن الفئة A لها أيضًا وظيفة لاستبدال الطرق (في A.VerifySignature(MethodInfo m1, MethodInfo m2) ) وتعديل IL لرمز بايت الطريقة (في A.IncrementMaxStack ).


لتحديد الأساليب التي يجب استبدالها في Program ، في Program.Init ، يتم تجزئة شفرة bytecode IL للطرق ومقارنتها بالقيم المحسوبة مسبقًا. في المجموع ، يتم استبدال طريقتين. لمعرفة أي منها ، سنقوم بتشغيل التطبيق تحت مصحح الأخطاء عن طريق تعيين نقاط التوقف على مكالمات A.VerifySignature ، ويجب تخطي المكالمة A.CalculateStack() في Program.Init ، بسبب يمنع تصحيح الأخطاء.



نتيجة لذلك ، يمكنك أن ترى أن يتم استبدال الأسلوب Program.b بواسطة Program.b ، ويتم استبدال Program.d بواسطة Program.d .


أنت الآن بحاجة إلى التعامل مع تعديل الرمز البريدي:


 private unsafe static uint IncrementMaxStack(IntPtr self, A.ICorJitInfo* comp, A.CORINFO_METHOD_INFO* info, uint flags, byte** nativeEntry, uint* nativeSizeOfCode) { bool flag = info != null; if (flag) { MethodBase methodBase = Ac(info->ftn); bool flag2 = methodBase != null; if (flag2) { bool flag3 = methodBase.MetadataToken == 100663317; if (flag3) { uint flNewProtect; A.VirtualProtect((IntPtr)((void*)info->ILCode), info->ILCodeSize, 4u, out flNewProtect); Marshal.WriteByte((IntPtr)((void*)info->ILCode), 23, 20); Marshal.WriteByte((IntPtr)((void*)info->ILCode), 62, 20); A.VirtualProtect((IntPtr)((void*)info->ILCode), info->ILCodeSize, flNewProtect, out flNewProtect); } else { bool flag4 = methodBase.MetadataToken == 100663316; if (flag4) { uint flNewProtect2; A.VirtualProtect((IntPtr)((void*)info->ILCode), info->ILCodeSize, 4u, out flNewProtect2); Marshal.WriteInt32((IntPtr)((void*)info->ILCode), 6, 309030853); Marshal.WriteInt32((IntPtr)((void*)info->ILCode), 18, 209897853); A.VirtualProtect((IntPtr)((void*)info->ILCode), info->ILCodeSize, flNewProtect2, out flNewProtect2); } } } } return A.originalDelegate(self, comp, info, flags, nativeEntry, nativeSizeOfCode); } 

من الواضح أنه سيتم تعديل الأساليب التي تحتوي على قيم MetadataToken محددة ، وهي 0x6000015 و 0x6000014 . تتوافق هذه الرموز مع أساليب Program.h و Program.g . يحتوي dnSpy على محرر hex مدمج ، يتم فيه تمييز بيانات الطريقة عند dnSpy (مظلل باللون الأرجواني) ورمز البايت (مظلل باللون الأحمر) ، كما هو موضح في لقطة الشاشة. يمكنك الانتقال إلى الطريقة المطلوبة في محرر hex بالنقر فوق العنوان المقابل في التعليق قبل الأسلوب الذي تم إلغاء ترجمته (على سبيل المثال ، File Offset: 0x00002924 ).



دعونا نحاول تطبيق جميع التعديلات الموضحة: dnSpy نسخة من الملف ، في أي محرر سداسي عشرية ، سنقوم بتغيير القيم في الإزاحات اللازمة ، التي تعلمناها من dnSpy محل الأساليب a -> b and c -> d في Program.h . نحن أيضا إزالة من Program.Init جميع المكالمات إلى الوحدة النمطية A إذا تم تنفيذ كل شيء بشكل صحيح ، فعندما نحاول إدراج بعض الرسائل في الصورة باستخدام التطبيق المعدل ، فسوف نحصل على نفس النتيجة عند تشغيل التطبيق الأصلي. توضح لقطات الشاشة أدناه الشفرة التي تم فك تشفيرها عن طرق التطبيقات الأصلية والمعدلة.




يبقى إنشاء خوارزمية تحويل عكسي. الأمر بسيط للغاية ، لذا سأقدم فقط نص Python الناتج:


 from PIL import Image # Rotate left: 0b1001 --> 0b0011 rol = lambda val, r_bits, max_bits: \ (val << r_bits%max_bits) & (2**max_bits-1) | \ ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits))) # Rotate right: 0b1001 --> 0b1100 ror = lambda val, r_bits, max_bits: \ ((val & (2**max_bits-1)) >> r_bits%max_bits) | \ (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1)) rol8 = lambda a, b: rol(a, b, 8) ror8 = lambda a, b: ror(a, b, 8) def extract(fname): img = Image.open(fname) w, h = img.size result = bytearray() for i in range(w): for j in range(h): r, g, b = img.getpixel((i, j)) # print('{:02x} {:02x} {:02x}'.format(r, g, b)) byte = (r & 0b111) | ((g & 0b111) << 3) | ((b & 0b11) << 6) result.append(byte) return result enc = extract('image.bmp') n = len(enc) dec = bytearray() def g(idx): b = ((idx + 1) * 309030853) & 0xff k = ((idx + 2) * 209897853) & 0xff return b ^ k j = 0 for i in range(n): x = enc[i] x = rol8(x, 3) x ^= g(2*i + 1) x = ror8(x, 7) x ^= g(2*i + 0) dec.append(x) with open('output', 'wb') as f: f.write(dec) 

عن طريق تشغيل هذا البرنامج النصي ، نحصل على صورة bmp أخرى بدون علم. تكرار الإجراء على ذلك ، نحصل على الصورة النهائية مع العلم.




0x07 - wopr


استخدمنا مهارات القرصنة على الكمبيوتر الخاص بنا "للعثور" على الذكاء الاصطناعي على حاسوب عملاق عسكري. إنها تشبه بشدة فيلم 1983 WarGames الكلاسيكي. ربما الحياة يقلد الفن؟ إذا تمكنت من العثور على رموز الإطلاق بالنسبة لنا ، فسنسمح لك بالمرور إلى التحدي التالي. نحن نعد بعدم بدء حرب نووية حرارية.

في المهمة ، worp.exe تطبيق وحدة التحكم worp.exe . على ما يبدو ، لحلها ، تحتاج إلى التقاط بعض التعليمات البرمجية.



يوضح تحليل نقطة الدخول أن هذا أرشيف يعتمد الاستخراج الذاتي. عند بدء التشغيل ، _MEIPASS2 متغير البيئة _MEIPASS2 . في حالة عدم وجود هذا المتغير ، يتم إنشاء دليل مؤقت يتم فيه _MEIPASS2 محتويات الأرشيف ، ويبدأ التطبيق مرة أخرى مع متغير البيئة المحدد _MEIPASS2 . محتوى الأرشيف:


 . ├── api-ms-win-core-console-l1-1-0.dll ├── ... ├── ... ├── api-ms-win-crt-utility-l1-1-0.dll ├── base_library.zip ├── _bz2.pyd ├── _ctypes.pyd ├── _hashlib.pyd ├── libcrypto-1_1.dll ├── libssl-1_1.dll ├── _lzma.pyd ├── pyexpat.pyd ├── python37.dll ├── select.pyd ├── _socket.pyd ├── _ssl.pyd ├── this │  ├── __init__.py │  └── key ├── ucrtbase.dll ├── unicodedata.pyd ├── VCRUNTIME140.dll └── wopr.exe.manifest 1 directory, 56 files 

اذا حكمنا من خلال المحتوى ، نحن نتعامل مع تطبيق Python معبأ في exe . دعماً لهذا ، في الثنائية الرئيسية ، يمكنك العثور على استيراد ديناميكي للوظائف المقابلة لمكتبة Python : PyMarshal_ReadObjectFromString و PyEval_EvalCode وغيرها. لمزيد من التحليل ، تحتاج إلى استخراج شفرة Python . للقيام بذلك ، احفظ محتويات الأرشيف من الدليل المؤقت _MEIPASS2 المسار إليه في متغير البيئة _MEIPASS2 . قم بتشغيل الثنائي الرئيسي في وضع التصحيح عن طريق تعيين نقطة توقف على وظيفة PyMarshal_ReadObjectFromString . هذه الوظيفة تأخذ كوسيطات مؤشر إلى مخزن مؤقت برمز Python التسلسلي وطولها. تفريغ محتويات المخزن المؤقت بطول معروف لكل من المكالمات. تلقيت مكالمتين فقط ، بينما في الثانية ، يكون الكائن التسلسلي أكبر بكثير ، وسوف نقوم بتحليله.


إحدى الطرق البسيطة إلى حد ما لتحليل البيانات المستلمة هي تحويلها إلى تنسيق ملفات .pyc ( Python bytecode المترجمة) uncompyle6 باستخدام uncompyle6 . للقيام بذلك ، يكفي إضافة رأس 16 بايت إلى البيانات المستلمة. نتيجة لذلك ، حصلت على الملف التالي:


 00000000: 42 0d 0d 0a 00 00 00 00 de cd 57 5d 00 00 00 00 B.........W].... 00000010: e3 00 00 00 00 00 00 00 00 00 00 00 00 09 00 00 ................ 00000020: 00 40 00 00 00 73 3c 01 00 00 64 00 5a 00 64 01 .@...s<...dZd 00000030: 64 02 6c 01 5a 01 64 01 64 02 6c 02 5a 02 64 01 dlZddlZd 

بعد ذلك ، نقوم uncompyle6 الملف الناتج باستخدام uncompyle6 :


 uncompyle6 task.pyc > task.py 

إذا حاولنا تشغيل الملف BOUNCE = pkgutil.get_data('this', 'key') على استثناء في السطر BOUNCE = pkgutil.get_data('this', 'key') . تم إصلاح ذلك بسهولة عن طريق تعيين محتويات ملف key من الأرشيف إلى متغير BOUNCE . إعادة تشغيل البرنامج النصي ، سنرى فقط نقش LOADING... على ما يبدو ، يتم استخدام بعض التقنيات في المهمة التي تمنع إزالة التجميع. ننتقل إلى تحليل شفرة Python الناتجة. في النهاية ، نرى الدورة التالية:


 for i in range(256): try: print(lzma.decompress(fire(eye(__doc__.encode()), bytes([i]) + BOUNCE))) except Exception: pass 

يمكنك أن تفهم أن وظيفة print قد تم تجاوزها بالفعل على أنها exec ، وأن __doc__.encode() تعتمد فقط على __doc__.encode() - النص في بداية الملف. print print try-except . . , __doc__ . __doc__ :


 import marshal with open('pycode1', 'rb') as inp: data = inp.read() code = marshal.loads(data) doc = code.co_consts[0] with open('doc.txt', 'w') as outp: outp.write(doc) 

, __doc__ . , i , . . wrong :


 trust = windll.kernel32.GetModuleHandleW(None) 

, . 0x100000 wrong , . , .


. z3 :


 from z3 import * from stage2 import wrong xor = [212, 162, 242, 218, 101, 109, 50, 31, 125, 112, 249, 83, 55, 187, 131, 206] h = list(wrong()) h = [h[i] ^ xor[i] for i in range(16)] b = 16 * [None] x = [] for i in range(16): x.append(BitVec('x' + str(i), 32)) b[0] = x[2] ^ x[3] ^ x[4] ^ x[8] ^ x[11] ^ x[14] b[1] = x[0] ^ x[1] ^ x[8] ^ x[11] ^ x[13] ^ x[14] b[2] = x[0] ^ x[1] ^ x[2] ^ x[4] ^ x[5] ^ x[8] ^ x[9] ^ x[10] ^ x[13] ^ x[14] ^ x[15] b[3] = x[5] ^ x[6] ^ x[8] ^ x[9] ^ x[10] ^ x[12] ^ x[15] b[4] = x[1] ^ x[6] ^ x[7] ^ x[8] ^ x[12] ^ x[13] ^ x[14] ^ x[15] b[5] = x[0] ^ x[4] ^ x[7] ^ x[8] ^ x[9] ^ x[10] ^ x[12] ^ x[13] ^ x[14] ^ x[15] b[6] = x[1] ^ x[3] ^ x[7] ^ x[9] ^ x[10] ^ x[11] ^ x[12] ^ x[13] ^ x[15] b[7] = x[0] ^ x[1] ^ x[2] ^ x[3] ^ x[4] ^ x[8] ^ x[10] ^ x[11] ^ x[14] b[8] = x[1] ^ x[2] ^ x[3] ^ x[5] ^ x[9] ^ x[10] ^ x[11] ^ x[12] b[9] = x[6] ^ x[7] ^ x[8] ^ x[10] ^ x[11] ^ x[12] ^ x[15] b[10] = x[0] ^ x[3] ^ x[4] ^ x[7] ^ x[8] ^ x[10] ^ x[11] ^ x[12] ^ x[13] ^ x[14] ^ x[15] b[11] = x[0] ^ x[2] ^ x[4] ^ x[6] ^ x[13] b[12] = x[0] ^ x[3] ^ x[6] ^ x[7] ^ x[10] ^ x[12] ^ x[15] b[13] = x[2] ^ x[3] ^ x[4] ^ x[5] ^ x[6] ^ x[7] ^ x[11] ^ x[12] ^ x[13] ^ x[14] b[14] = x[1] ^ x[2] ^ x[3] ^ x[5] ^ x[7] ^ x[11] ^ x[13] ^ x[14] ^ x[15] b[15] = x[1] ^ x[3] ^ x[5] ^ x[9] ^ x[10] ^ x[11] ^ x[13] ^ x[15] solver = Solver() for i in range(16): solver.add(x[i] < 128) for i in range(16): solver.add(b[i] == h[i]) if solver.check() == sat: m = solver.model() print(bytes([m[i].as_long() for i in x])) else: print('unsat') 

, : 5C0G7TY2LWI2YXMB




0x08 — snake


The Flare team is attempting to pivot to full-time twitch streaming video games instead of reverse engineering computer software all day. We wrote our own classic NES game to stream content that nobody else has seen and watch those subscribers flow in. It turned out to be too hard for us to beat so we gave up. See if you can beat it and capture the internet points that we failed to collect.

NES - . FCEUX , .. . , .



, , 0x25 . , . NES - IDA . inesldr . 0x25 . C82A , . 0x33 .



, — 0x32 0x25 . , . , FCEUX . .




0x09 — reloadered


This is a simple challenge, enter the password, receive the key. I hear that it caused problems when trying to analyze it with ghidra. Remember that valid flare-on flags will always end with @flare-on.com

reloaderd.exe , . , , . , , XOR , @FLAG.com , .



, NOP . , , . . , . , , . , , NOP , .


, , XOR , . @flare-on.com , . :


 flag = bytearray(b'D)6\n)\x0f\x05\x1be&\x10\x04+h0/\x003/\x05\x1a\x1f\x0f8\x02\x18B\x023\x1a(\x04*G?\x04&dfM\x107>(>w\x1c?~64*\x00') for i in range(0x539): for j in range(0x34): if (i % 3) == 0 or (i % 7) == 0: flag[j] ^= (i & 0xff) end = b'@flare-on.com' def xor(a, b): return bytes([i^j for i, j in zip(a, b)]) for i in range(len(flag)): print(i, xor(end, flag[i:])) print(xor(flag, b'3HeadedMonkey'*4)) 



0x0A — Mugatu


Hello,

I'm working an incident response case for Derek Zoolander. He clicked a link and was infected with MugatuWare! As a result, his new headshot compilation GIF was encrypted.

To secure an upcoming runway show, Derek needs this GIF decrypted; however, he refuses to pay the ransom.

We received an additional encrypted GIF from an anonymous informant. The informant told us the GIF should help in our decryption efforts, but we were unable to figure it out.

We're reaching out to you, our best malware analyst, in hopes that you can reverse engineer this malware and decrypt Derek's GIF.

I've included a directory full of files containing:
  • MugatuWare malware
  • Ransom note (GIFtToDerek.txt)
  • Encrypted headshot GIF (best.gif.Mugatu)
  • Encrypted informant GIF (the_key_to_success_0000.gif.Mugatu)


شكرا،
Roy

:


  • best.gif.Mugatu
  • GIFtToDerek.txt
  • Mugatuware.exe
  • the_key_to_success_0000.gif.Mugatu

, , GIF -. , .Mugatu . Mugatuware.exe . , — . , , .



IDA , :


 import ida_segment import ida_name import ida_bytes import ida_typeinf idata = ida_segment.get_segm_by_name('.idata') type_map = {} for addr in range(idata.start_ea, idata.end_ea, 4): name = ida_name.get_name(addr) if name: tp = ida_typeinf.idc_get_type(addr) if tp: type_map[name] = tp for addr in range(idata.start_ea, idata.end_ea, 4): imp = ida_bytes.get_dword(addr) if imp != 0: imp_name = ida_name.get_name(imp) name_part = imp_name.split('_')[-1] ida_name.set_name(addr, name_part + '_imp') if name_part in type_map: tp = type_map[name_part] ida_typeinf.apply_decl(addr, tp.replace('(', 'func(') + ';') 

:



, , in-memory PE -. , CrazyPills!!! . , . Sleep , http -. , , , . , , , . .



- :


  • ;
  • ;
  • Mailslots ;
  • really, really, really, ridiculously good looking gifs ;
  • .gif . .Mugatu . GIFtToDerek.txt .

, — 8 . XOR CrazyPills!!! , . , :



XTEA , — BYTE , DWORD . . Python :


 def crypt(a, b, key): i = 0 for _ in range(32): t = (i + key[i & 3]) & 0xffffffff a = (a + (t ^ (b + ((b >> 5) ^ (b << 4))))) & 0xffffffff i = (0x100000000 + i - 0x61C88647) & 0xffffffff t = (i + key[(i >> 11) & 3]) & 0xffffffff b = (b + (t ^ (a + ((a >> 5) ^ (a << 4))))) & 0xffffffff return a, b def decrypt(a, b, key): i = 0xc6ef3720 for _ in range(32): t = (i + key[(i >> 11) & 3]) & 0xffffffff b = (0x100000000 + b - (t ^ (a + ((a >> 5) ^ (a << 4))))) & 0xffffffff i = (i + 0x61C88647) & 0xffffffff t = (i + key[i & 3]) & 0xffffffff a = (0x100000000 + a - (t ^ (b + ((b >> 5) ^ (b << 4))))) & 0xffffffff return a, b 

, the_key_to_success_0000.gif.Mugatu . , . :



, , . C . GIF -.


 #include <stdio.h> #include <unistd.h> void decrypt(unsigned int * inp, unsigned int * outp, unsigned char * key) { unsigned int i = 0xc6ef3720; unsigned int a = inp[0]; unsigned int b = inp[1]; unsigned int t; for(int j = 0; j < 32; j++) { t = i + key[(i >> 11) & 3]; b -= t ^ (a + ((a >> 5) ^ (a << 4))); i += 0x61C88647; t = i + key[i & 3]; a -= t ^ (b + ((b >> 5) ^ (b << 4))); } outp[0] = a; outp[1] = b; } int main() { int fd = open("best.gif.Mugatu", 0); unsigned int inp[2]; unsigned int outp[2]; unsigned int key = 0; read(fd, inp, 8); close(fd); for(unsigned long long key = 0; key < 0x100000000; key++) { if((key & 0xffffff) == 0) { printf("%lf\n", ((double)key) / ((double)0x100000000) * 100.0); } decrypt(inp, outp, &key); if( ((char *)outp)[0] == 'G' && ((char *)outp)[1] == 'I' && ((char *)outp)[2] == 'F' && ((char *)outp)[5] == 'a') { printf("%#llx\n", key); } } } 

0xb1357331 :




0x0B — vv_max


Hey, at least its not subleq.

vv_max.exe , . 256- . AVX2 , vpermd , vpslld . - :


 0000 clear_regs 0001 r0 = 393130324552414c46 0023 r1 = 3030303030303030303030303030303030303030303030303030303030303030 0045 r3 = 1a1b1b1b1a13111111111111111111151a1b1b1b1a1311111111111111111115 0067 r4 = 1010101010101010080408040201101010101010101010100804080402011010 0089 r5 = b9b9bfbf041310000000000000000000b9b9bfbf04131000 00ab r6 = 2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f 00cd r10 = 140014001400140014001400140014001400140014001400140014001400140 00ef r11 = 1100000011000000110000001100000011000000110000001100000011000 0111 r12 = ffffffff0c0d0e08090a040506000102ffffffff0c0d0e08090a040506000102 0133 r13 = ffffffffffffffff000000060000000500000004000000020000000100000000 0155 r16 = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0177 r17 = 6a09e667bb67ae853c6ef372a54ff53a510e527f9b05688c1f83d9ab5be0cd19 0199 r18 = 428a2f9871374491b5c0fbcfe9b5dba53956c25b59f111f1923f82a4ab1c5ed5 01bb r19 = 300000002000000010000000000000007000000060000000500000004 01dd r20 = 0 01ff r21 = 100000001000000010000000100000001000000010000000100000001 0221 r22 = 200000002000000020000000200000002000000020000000200000002 0243 r23 = 300000003000000030000000300000003000000030000000300000003 0265 r24 = 400000004000000040000000400000004000000040000000400000004 0287 r25 = 500000005000000050000000500000005000000050000000500000005 02a9 r26 = 600000006000000060000000600000006000000060000000600000006 02cb r27 = 700000007000000070000000700000007000000070000000700000007 02ed r20 = vpermd(r0, r20) 02f1 r21 = vpermd(r0, r21) 02f5 r22 = vpermd(r0, r22) 02f9 r23 = vpermd(r0, r23) 02fd r24 = vpermd(r0, r24) 0301 r25 = vpermd(r0, r25) 0305 r26 = vpermd(r0, r26) 0309 r27 = vpermd(r0, r27) 030d r7 = vpsrld(r1, 4) 0311 r28 = r20 ^ r21 0315 r28 = r28 ^ r22 0319 r28 = r28 ^ r23 031d r28 = r28 ^ r24 0321 r28 = r28 ^ r25 0325 r28 = r28 ^ r26 0329 r28 = r28 ^ r27 032d r7 = r7 & r6 0331 r29 = vpslld(r17, 7) 0335 r30 = vpsrld(r17, 25) 0339 r15 = r29 | r30 033d r8 = vpcmpeqb(r1, r6) 0341 r29 = vpslld(r17, 21) 0345 r30 = vpsrld(r17, 11) 0349 r29 = r29 | r30 034d r15 = r15 ^ r29 0351 r8 = vpcmpeqb(r1, r6) 0355 r29 = vpslld(r17, 26) 0359 r30 = vpsrld(r17, 6) 035d r29 = r29 | r30 0361 r15 = r15 ^ r29 0365 r29 = r20 ^ r16 0369 r30 = r20 & r18 036d r29 = r29 ^ r30 0371 r15 = add_d(r29, r15) 0375 r20 = add_d(r15, r0) 0379 r7 = add_b(r8, r7) 037d r29 = r20 ^ r28 0381 r17 = vpermd(r29, r19) 0385 r7 = vpshufb(r5, r7) 0389 r29 = vpslld(r17, 7) 038d r30 = vpsrld(r17, 25) 0391 r15 = r29 | r30 0395 r29 = vpslld(r17, 21) 0399 r30 = vpsrld(r17, 11) 039d r29 = r29 | r30 03a1 r15 = r15 ^ r29 03a5 r29 = vpslld(r17, 26) 03a9 r30 = vpsrld(r17, 6) 03ad r29 = r29 | r30 03b1 r15 = r15 ^ r29 03b5 r2 = add_b(r1, r7) 03b9 r29 = r21 ^ r16 03bd r30 = r21 & r18 03c1 r29 = r29 ^ r30 03c5 r15 = add_d(r29, r15) 03c9 r21 = add_d(r15, r0) 03cd r29 = r21 ^ r28 03d1 r17 = vpermd(r29, r19) 03d5 r20 = r20 ^ r21 03d9 r29 = vpslld(r17, 7) 03dd r30 = vpsrld(r17, 25) 03e1 r15 = r29 | r30 03e5 r29 = vpslld(r17, 21) 03e9 r30 = vpsrld(r17, 11) 03ed r29 = r29 | r30 03f1 r15 = r15 ^ r29 03f5 r29 = vpslld(r17, 26) 03f9 r30 = vpsrld(r17, 6) 03fd r29 = r29 | r30 0401 r15 = r15 ^ r29 0405 r7 = vpmaddubsw(r2, r10) 0409 r29 = r22 ^ r16 040d r30 = r22 & r18 0411 r29 = r29 ^ r30 0415 r15 = add_d(r29, r15) 0419 r22 = add_d(r15, r0) 041d r29 = r22 ^ r28 0421 r17 = vpermd(r29, r19) 0425 r20 = r20 ^ r22 0429 r29 = vpslld(r17, 7) 042d r30 = vpsrld(r17, 25) 0431 r15 = r29 | r30 0435 r29 = vpslld(r17, 21) 0439 r30 = vpsrld(r17, 11) 043d r29 = r29 | r30 0441 r15 = r15 ^ r29 0445 r29 = vpslld(r17, 26) 0449 r30 = vpsrld(r17, 6) 044d r29 = r29 | r30 0451 r15 = r15 ^ r29 0455 r2 = vpmaddwd(r7, r11) 0459 r29 = r23 ^ r16 045d r30 = r23 & r18 0461 r29 = r29 ^ r30 0465 r15 = add_d(r29, r15) 0469 r23 = add_d(r15, r0) 046d r29 = r23 ^ r28 0471 r17 = vpermd(r29, r19) 0475 r20 = r20 ^ r23 0479 r29 = vpslld(r17, 7) 047d r30 = vpsrld(r17, 25) 0481 r15 = r29 | r30 0485 r29 = vpslld(r17, 21) 0489 r30 = vpsrld(r17, 11) 048d r29 = r29 | r30 0491 r15 = r15 ^ r29 0495 r29 = vpslld(r17, 26) 0499 r30 = vpsrld(r17, 6) 049d r29 = r29 | r30 04a1 r15 = r15 ^ r29 04a5 r29 = r24 ^ r16 04a9 r30 = r24 & r18 04ad r29 = r29 ^ r30 04b1 r15 = add_d(r29, r15) 04b5 r24 = add_d(r15, r0) 04b9 r29 = r24 ^ r28 04bd r17 = vpermd(r29, r19) 04c1 r20 = r20 ^ r24 04c5 r29 = vpslld(r17, 7) 04c9 r30 = vpsrld(r17, 25) 04cd r15 = r29 | r30 04d1 r29 = vpslld(r17, 21) 04d5 r30 = vpsrld(r17, 11) 04d9 r29 = r29 | r30 04dd r15 = r15 ^ r29 04e1 r29 = vpslld(r17, 26) 04e5 r30 = vpsrld(r17, 6) 04e9 r29 = r29 | r30 04ed r15 = r15 ^ r29 04f1 r29 = r25 ^ r16 04f5 r30 = r25 & r18 04f9 r29 = r29 ^ r30 04fd r15 = add_d(r29, r15) 0501 r25 = add_d(r15, r0) 0505 r29 = r25 ^ r28 0509 r17 = vpermd(r29, r19) 050d r20 = r20 ^ r25 0511 r2 = vpshufb(r2, r12) 0515 r29 = vpslld(r17, 7) 0519 r30 = vpsrld(r17, 25) 051d r15 = r29 | r30 0521 r29 = vpslld(r17, 21) 0525 r30 = vpsrld(r17, 11) 0529 r29 = r29 | r30 052d r15 = r15 ^ r29 0531 r29 = vpslld(r17, 26) 0535 r30 = vpsrld(r17, 6) 0539 r29 = r29 | r30 053d r15 = r15 ^ r29 0541 r29 = r26 ^ r16 0545 r30 = r26 & r18 0549 r29 = r29 ^ r30 054d r15 = add_d(r29, r15) 0551 r26 = add_d(r15, r0) 0555 r29 = r26 ^ r28 0559 r17 = vpermd(r29, r19) 055d r20 = r20 ^ r26 0561 r29 = vpslld(r17, 7) 0565 r30 = vpsrld(r17, 25) 0569 r15 = r29 | r30 056d r29 = vpslld(r17, 21) 0571 r30 = vpsrld(r17, 11) 0575 r29 = r29 | r30 0579 r15 = r15 ^ r29 057d r29 = vpslld(r17, 26) 0581 r30 = vpsrld(r17, 6) 0585 r29 = r29 | r30 0589 r15 = r15 ^ r29 058d r2 = vpermd(r2, r13) 0591 r29 = r27 ^ r16 0595 r30 = r27 & r18 0599 r29 = r29 ^ r30 059d r15 = add_d(r29, r15) 05a1 r27 = add_d(r15, r0) 05a5 r29 = r27 ^ r28 05a9 r17 = vpermd(r29, r19) 05ad r20 = r20 ^ r27 05b1 r19 = ffffffffffffffffffffffffffffffffffffffffffffffff 05d3 r20 = r20 & r19 05d7 r31 = 2176620c3a5c0f290b583618734f07102e332623780e59150c05172d4b1b1e22 

FLARE2019 . , , . , FLARE2019 . r2 r20 . , r20 . r2 — 6 r2 . , 6 . Frida :


 # vvmax.py from __future__ import print_function import frida import string import hexdump def check(val): global gdata with open('vvmax.js', 'r') as f: script_src = f.read() pid = frida.spawn(['vv_max.exe', 'FLARE2019', val.ljust(32, 'a')]) session = frida.attach(pid) script = session.create_script(script_src) def handler(message, data): handler.data = data script.on('message', handler) script.load() frida.resume(pid) while not hasattr(handler, 'data'): pass session.detach() return handler.data alph = string.printable def to_bits(x): return ''.join(bin(ord(i))[2:].zfill(8) for i in x) target = to_bits('pp\xb2\xac\x01\xd2^a\n\xa7*\xa8\x08\x1c\x86\x1a\xe8E\xc8)\xb2\xf3\xa1\x1e\x00\x00\x00\x00\x00\x00\x00\x00') password = '' while len(password) != 32: for c in alph: data = to_bits(check(password + c)) i = 6*len(password + c) if data[:i] == target[:i]: password += c i += 1 break print() print('----->', `password`) print() 

 // vvmax.js var modules = Process.enumerateModules(); var base = modules[0].base; Interceptor.attach(base.add(0x1665), function() { var p = this.context.rdx.add(0x840); var res = p.readByteArray(32); send(null, res); }); 

:




0x0C — help


You're my only hope FLARE-On player! One of our developers was hacked and we're not sure what they took. We managed to set up a packet capture on the network once we found out but they were definitely already on the system. I think whatever they installed must be buggy — it looks like they crashed our developer box. We saved off the dump file but I can't make heads or tails of it — PLEASE HELP!!!!!!

. RAM - . 4444 , 6666 , 7777 8888 . , , , RAM -. volatility . volatility Win10x64_15063 , , Win7SP1x64 , .


volatility :


 $ volatility --profile Win7SP1x64 -f help.dmp modules Volatility Foundation Volatility Framework 2.6 Offset(V) Name Base Size File ------------------ -------------------- ------------------ ------------------ ---- 0xfffffa800183e890 ntoskrnl.exe 0xfffff80002a49000 0x5e7000 \SystemRoot\system32\ntoskrnl.exe ... 0xfffffa800428ff30 man.sys 0xfffff880033bc000 0xf000 \??\C:\Users\FLARE ON 2019\Desktop\man.sys 

:


 $ volatility --profile Win7SP1x64 -f help.dmp moddump --base 0xfffff880033bc000 -D drivers Volatility Foundation Volatility Framework 2.6 Module Base Module Name Result ------------------ -------------------- ------ 0xfffff880033bc000 man.sys Error: e_magic 0000 is not a valid DOS signature. 

, . volshell .


 $ volatility --profile Win7SP1x64 -f help.dmp volshell In [1]: db(0xfffff880033bc000) 0xfffff880033bc000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0xfffff880033bc070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ In [2]: db(0xfffff880033bc000 + 0x1100) 0xfffff880033bd100 01 48 8b 4c 24 20 48 8b 44 24 28 48 89 41 08 48 .HL$.HD$(HAH 0xfffff880033bd110 83 c4 18 c3 cc cc cc cc cc cc cc cc cc cc cc cc ................ 0xfffff880033bd120 48 89 4c 24 08 48 83 ec 38 48 8b 44 24 40 0f be HL$.H..8H.D$@.. 0xfffff880033bd130 48 43 48 8b 44 24 40 0f be 40 42 83 c0 01 3b c8 HCH.D$@..@B...;. 0xfffff880033bd140 7e 27 45 33 c9 41 b8 15 5b 00 00 48 8d 15 de 44 ~'E3.A..[..H...D 0xfffff880033bd150 00 00 48 8d 0d 07 45 00 00 ff 15 71 4f 00 00 c7 ..H...E....qO... 0xfffff880033bd160 44 24 20 00 00 00 00 eb 08 c7 44 24 20 01 00 00 D$........D$.... 0xfffff880033bd170 00 48 8b 44 24 40 48 8b 80 b8 00 00 00 48 83 c4 .HD$@H......H.. In [4]: man = addrspace().read(0xfffff880033bc000, 0xf000) In [5]: with open('man_writeup.sys', 'wb') as f: ...: f.write(man) ...: 

, , moddump . . . - , , .


RC4 . , .


user-space . DLL - . DLL - ( m.dll ), . , . :


  • ( +0x8 )
  • _EPROCESS ( +0x68 )
  • ( +0x48 )
  • ( +0x58 )

DLL - RC4 , 0x2c - , 0x48 .


volatility volshell :


 import struct from Crypto.Cipher import ARC4 head = 0xfffff880033c8158 krnl = addrspace() def u64(x): return struct.unpack('Q', x)[0] fd = u64(krnl.read(head, 8)) while True: proc_addr = u64(krnl.read(fd + 0x68, 8)) base = u64(krnl.read(fd + 0x48, 8)) key = krnl.read(fd + 0x48, 0x2c) sz = u64(krnl.read(fd + 0x58, 8)) fd = u64(krnl.read(fd, 8)) p = obj.Object('_EPROCESS', proc_addr, krnl) print p.ImageFileName.v(), hex(proc_addr), hex(base), hex(sz) proc_space = p.get_process_address_space() dump = proc_space.read(base, sz) if dump[:0x100] == '\x00' * 0x100: dump = ARC4.new(key).decrypt(dump) with open('proc_{:016x}'.format(base), 'wb') as f: f.write(dump) if fd == head: break 

, , RC4 . IDA , , :


IDA
 from __future__ import print_function import sys import re from idaapi import get_func, decompile, get_name_ea, auto_wait, BADADDR from idaapi import cot_call, cot_obj, init_hexrays_plugin, qexit import ida_typeinf import ida_lines def rc4(key, data): S = list(range(256)) j = 0 for i in list(range(256)): j = (j + S[i] + ord(key[i % len(key)])) % 256 S[i], S[j] = S[j], S[i] j = 0 y = 0 out = [] for char in data: j = (j + 1) % 256 y = (y + S[j]) % 256 S[j], S[y] = S[y], S[j] out.append(chr(ord(char) ^ S[(S[j] + S[y]) % 256])) return ''.join(out) def decrypt_stack_str_args(ea): func = get_func(ea) if func is None: return try: c_func = decompile(func) c_func.pseudocode except Exception as ex: return for citem in c_func.treeitems: citem = citem.to_specific_type if citem.is_expr() and\ citem.op == cot_call and\ citem.ea == ea: args = [] key = citem.a[0] key_len = citem.a[1] s = citem.a[2] s_len = citem.a[3] def get_var_idx(obj): while obj.opname != 'var': if obj.opname in ('ref', 'cast'): obj = obj.x else: raise Exception('can\'t find type') return obj.v.idx if key_len.opname != 'num' or s_len.opname != 'num': print('[!] can\'t get length: 0x{:08x}'.format(ea)) else: try: key_len_val = key_len.n._value s_len_val = s_len.n._value print('0x{:08x}'.format(ea), 'key_len =', key_len_val, ', s_len =', s_len_val) hx_view = idaapi.open_pseudocode(ea, -1) key_var_stkoff = hx_view.cfunc.get_lvars()[get_var_idx(key)].location.stkoff() s_var_stkoff = hx_view.cfunc.get_lvars()[get_var_idx(s)].location.stkoff() key_var = [v for v in hx_view.cfunc.get_lvars() if v.location.stkoff() == key_var_stkoff][0] tif = ida_typeinf.tinfo_t() ida_typeinf.parse_decl(tif, None, 'unsigned __int8 [{}];'.format(key_len_val), 0) hx_view.set_lvar_type(key_var, tif) s_var = [v for v in hx_view.cfunc.get_lvars() if v.location.stkoff() == s_var_stkoff][0] tif = ida_typeinf.tinfo_t() ida_typeinf.parse_decl(tif, None, 'unsigned __int8 [{}];'.format(s_len_val + 1), 0) hx_view.set_lvar_type(s_var, tif) key_var = [v for v in hx_view.cfunc.get_lvars() if v.location.stkoff() == key_var_stkoff][0] s_var = [v for v in hx_view.cfunc.get_lvars() if v.location.stkoff() == s_var_stkoff][0] key_regex = re.compile('{}\[(.+)\] = (.+);'.format(key_var.name)) s_regex = re.compile('{}\[(.+)\] = (.+);'.format(s_var.name)) key = bytearray(key_len_val) s = bytearray(s_len_val + 1) src = '\n'.join([ida_lines.tag_remove(i.line) for i in hx_view.cfunc.pseudocode]) for i, j in s_regex.findall(src): s[int(i)] = (0x100 + int(j)) & 0xff for i, j in key_regex.findall(src): key[int(i)] = (0x100 + int(j)) & 0xff key = ''.join(chr(i) for i in key) s = ''.join(chr(i) for i in s) result = rc4(key, s[:-1]) # unicode to ascii if set(ord(i) for i in result[1::2]) == {0}: result = 'wide_' + ''.join(result[0::2]) hx_view.rename_lvar(s_var, 's_' + result, True) except Exception as ex: print('[!] error: {}'.format(ex)) print('#### decryption helper script ####') xref_to = get_name_ea(BADADDR, 'decrypt_stack_str') xref_from = get_first_cref_to(xref_to) while xref_from != BADADDR: print('### 0x{:08x}'.format(xref_from)) decrypt_stack_str_args(xref_from) xref_from = get_next_cref_to(xref_to, xref_from) 

:



. :


  • m.dll — , . 4444 . — ;
  • n.dll192.168.1.243 ;
  • c.dllRC4 . ;
  • k.dll — (keylogger);
  • s.dll — ;
  • f.dll — .

, XOR 8. 4444 , .. . : , — . , - .


( 4444 ) . , - . , . :


  • keys.kdb
  • C:\
  • C:\keypass\keys.kdb

, f.dll : keys.kdb , .


6666 . LZNT1 RC4 XOR . , XOR - , .. . RC4 , RAM -: FLARE ON 2019 . , GetUserNameA , , - , RC4 . LZNT1 :


 from ctypes import * nt = windll.ntdll for fname in ['input']: with open(fname, 'rb') as f: buf = f.read() dec_data = create_string_buffer(0x10000) final_size = c_ulong(0) status = nt.RtlDecompressBuffer( 0x102, # COMPRESSION_FORMAT_LZNT1 dec_data, # UncompressedBuffer 0x10000, # UncompressedBufferSize c_char_p(buf), # CompressedBuffer 0xFFFFFF, # CompressedBufferSize byref(final_size) # FinalUncompressedSize ) with open(fname + '.uncompressed', 'wb') as f: f.write(dec_data.raw[:final_size.value]) 

6666 . :


 00000000: CC 69 94 FA 6A 37 18 29 CB 8D 87 EF 11 63 8E 73 .i..j7.).....cs 00000010: FE AB 43 3B B3 94 28 4B 4D 19 00 00 00 4F DB C7 ..C;..(KM....O.. 00000020: F3 1E E4 13 15 34 8F 51 A9 2B C2 D7 C1 96 78 F7 .....4.Q.+....x. 00000030: 91 98 

, :


 00000000: 19 00 00 00 4F DB C7 F3 1E E4 13 15 34 8F 51 A9 ....O.......4.Q. 00000010: 2B C2 D7 C1 96 78 F7 91 98 +....x... 

4 — , 25. :


 00000000: 12 B0 00 43 3A 5C 6B 65 79 70 61 04 73 73 01 70 ...C:\keypa.ss.p 00000010: 73 2E 6B 64 62 s.kdb 

C:\keypass\keys.kdb . , , . 6666KeePass .


7777 BMP . XOR , , , .. . , , KeePass .




8888 k.dll — .


 C:\Windows\system32\cmd.exe nslookup googlecom ping 1722173110 nslookup soeblogcom nslookup fiosquatumgatefiosrouterhome C:\Windows\system32\cmd.exe Start Start menu Start menu chrome www.flare-on.com - Google Chrome tis encrypting something twice better than once Is encrypting something twice better than once? - Google Search - Google Chrome Start Start menu Start menu keeKeePass <DYN_TITLE> th1sisth33nd111 KeePass keys.kdb - KeePass Is encrypting something twice better than once? - Google Search - Google Chrome Start Start menu Start menu KeePass <DYN_TITLE> th1sisth33nd111 Open Database - keys.kdb KeePass Start Start menu Start menu KeePass Start menu Start menu Start menu KeePass <DYN_TITLE> th1sisth33nd111 

th1sisth33nd111 , . , . , keylogger . , , ping . hashcat KeePass , . :


 $ strings help.dmp | grep -i '3nd!' !s_iS_th3_3Nd!!! 

Th .



. , -.



0x0D —


, . , , . , volatility , . ( UTC+3:00):


Source: https://habr.com/ru/post/ar469393/


All Articles