Home Cereberus 2024
Post
Cancel

Cereberus 2024

First, we begin with basic analysis. This involves checking its hash on VirusTotal and extracting hashes using HashMyFile.

img_1

Checking on VirusTotal.

img_1

After obtaining some hits, it’s time to invest more time in understanding its workings. While we can check for relations on VirusTotal, I prefer to handle the analysis independently from this point onward. Since the file type is apk, we can utilize jadx-gui for a more in-depth analysis.

We proceed by checking the native libraries to determine which architecture is supported. In this case, we were fortunate that it supports x86. Therefore, we don’t need to delve into the hassle of emulating other architectures for dynamic analysis.

img_1

Checking the Manifest File for entry points and the permissions the app is requesting is crucial for understanding its functionality. Permissions provide hints about the type of activities we can anticipate from the sample. In this case, permissions such as accessing SMS, recording audio, using the camera, accessing location, and reading external storage suggest spyware capabilities once confirmed as malware.

Other noteworthy permissions include writing permission to external storage, indicating a potential for downloading the next stage to obscure file paths to avoid association. This speculation proved true in this case. Post notification permissions may be utilized to lure victims into engaging in nefarious activities. Bluetooth permissions can also be leveraged to discover other available devices nearby.

Before the main function is called, we observe com.cliff.own.KTcMkQrAeSoAqTtWfHlDgHtBwInHdYfLaPiShZsIh being invoked.

img_1

From this point onward, I’ll focus on crucial parts of the analysis. We observe the use of techniques to create strings at runtime. One solution is to copy and paste the Java code provided by JADX, replacing Android native code with Java code. Alternatively, we can hook these functions using Frida to observe the arguments being passed and the values being returned. In this case, it resolves to Android.app.ActivityThread.

img_1

img_1

There were strings intended as taunts by the malware author, such as Use the same scan engine, the same virus definition of the world's top commercial antivirus software.

img_1

After spending some time analyzing the code statically, it was discovered that it is dynamically loading classes, which solves the mystery of ru..... It appears that this method is dynamically loaded from /data/user/0/com.cliff.own/app_DynamicOptDex/hjAWR.json. Some hurdles were encountered where certain code seemed to be added merely for distraction. At times, it appears to be doing nothing significant, while other times it performs actions that can be safely ignored, as illustrated in the example below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  if (find_full_path) {
            this.const_int_3246 = (this.const_int_16462 * 44) + 66 + this.const_int_8284;
            StringBuffer stringBuffer = new StringBuffer();
            for (int i10 = 0; i10 < 5; i10++) {
                this.const_int_8284 = ((this.const_int_3246 - 16) + 43) - this.const_int_16462;
            }
            if (find_full_path) {
                wrapper_for_loading_class(full_path_to_hjAWR_json, file_get_absolutePath, stringBuffer, this.context_null); // FEaGuJtZkDePjRpCyMjAgAx.staffhoney is called: field=private java.lang.ClassLoader android.app.LoadedApk.mClassLoader, str=/data/user/0/com.cliff.own/app_DynamicOptDex/hjAWR.json, str2=/data/user/0/com.cliff.own/app_DynamicOptDex, str3=, weakReference=java.lang.ref.WeakReference@e9db828
                for (int i11 = 1; i11 < 22; i11++) { // FEaGuJtZkDePjRpCyMjAgAx.staffhoney is called: field=private java.lang.ClassLoader android.app.LoadedApk.mClassLoader, str=/data/user/0/com.cliff.own/app_DynamicOptDex/hjAWR.json, str2=/data/user/0/com.cliff.own/app_DynamicOptDex, str3=, weakReference=java.lang.ref.WeakReference@e9db828
                    this.const_int_3246 = (-1773) + this.const_int_16462 + this.const_int_8284 + this.const_int_3246;
                }
            }

//  code is clean by me to get some sense of what happening . 

After inspecting the path, it was found that the hjAWR.json file is not a JSON file, as the code hinted. Instead, it turned out to be a zip file containing a classes.dex file upon unzipping it. Hashes of both hjAWR.json and classes.dex files were checked. There were no hits on VirusTotal (VT).

img_1

img_1 img_1

Some of the Frida hook scripts I have utilized up to this juncture, with their outputs provided as comments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(()=>{let JUjCsQtEjYlSwRzKkHtGrJfRqThOhBmJcBq = Java.use("com.cliff.own.JUjCsQtEjYlSwRzKkHtGrJfRqThOhBmJcBq");
JUjCsQtEjYlSwRzKkHtGrJfRqThOhBmJcBq["middlegravity"].implementation = function (str, context, str2) {
    console.log(`JUjCsQtEjYlSwRzKkHtGrJfRqThOhBmJcBq.middlegravity is called: str=${str}, context=${context}, str2=${str2}`);
    let result = this["middlegravity"](str, context, str2);
    console.log(`JUjCsQtEjYlSwRzKkHtGrJfRqThOhBmJcBq.middlegravity result=${result}`);
    return result;
};
})

OUTPUT ::::

Pixel 3::com.cliff.own ]-> JUjCsQtEjYlSwRzKkHtGrJfRqThOhBmJcBq.middlegravity is called: str=/data/user/0/com.cliff.own/app_DynamicOptDex/hjAWR.json, context=android.app.ContextImpl@522362f, str2=hjAWR.json
JUjCsQtEjYlSwRzKkHtGrJfRqThOhBmJcBq.middlegravity result=true


1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(()=>{
let first_func_call = Java.use("com.cliff.own.KTcMkQrAeSoAqTtWfHlDgHtBwInHdYfLaPiShZsIh");
first_func_call["mustready"].implementation = function (str, str2, stringBuffer, context) {
    console.log(`first_func_call.mustready is called: str=${str}, str2=${str2}, stringBuffer=${stringBuffer}, context=${context}`);
    this["mustready"](str, str2, stringBuffer, context);
};
})

OUTPUT ::::

 first_func_call.mustready is called: str=/data/user/0/com.cliff.own/app_DynamicOptDex/hjAWR.json, str2=/data/user/0/com.cliff.own/app_DynamicOptDex, stringBuffer=, context=android.app.ContextImpl@e9db828

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(()=>{
let FEaGuJtZkDePjRpCyMjAgAx = Java.use("com.cliff.own.FEaGuJtZkDePjRpCyMjAgAx");
FEaGuJtZkDePjRpCyMjAgAx["staffhoney"].implementation = function (field, str, str2, str3, weakReference) {
    console.log(`FEaGuJtZkDePjRpCyMjAgAx.staffhoney is called: field=${field}, str=${str}, str2=${str2}, str3=${str3}, weakReference=${weakReference}`);
    this["staffhoney"](field, str, str2, str3, weakReference);
};
})

OUTPUT ::::

[Pixel 3::com.cliff.own ]-> FEaGuJtZkDePjRpCyMjAgAx.staffhoney is called: field=private java.lang.ClassLoader android.app.LoadedApk.mClassLoader, str=/data/user/0/com.cliff.own/app_DynamicOptDex/hjAWR.json, str2=/data/user/0/com.cliff.own/app_DynamicOptDex, str3=, weakReference=java.lang.ref.WeakReference@e9db828

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(()=>{
let first_func_call = Java.use("com.cliff.own.KTcMkQrAeSoAqTtWfHlDgHtBwInHdYfLaPiShZsIh");
first_func_call["patchverify"].implementation = function (str) {
    console.log(`first_func_call.patchverify is called: str=${str}`);
    let result = this["patchverify"](str);
    console.log(`first_func_call.patchverify result=${result}`);
    return result;
};
})

OUTPUT ::::

[Pixel 3::com.cliff.own ]-> first_func_call.patchverify is called: str=
first_func_call.patchverify result=false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let report_recieveSMSnStartJobs = Java.use("ru.plaintiff.whale.strongFlashClam");
report_recieveSMSnStartJobs["a"].implementation = function (str) {
    console.log(`report_recieveSMSnStartJobs.a is called: str=${str}`);
    let result = this["a"](str);
    console.log(`report_recieveSMSnStartJobs.a result=${result}`);
    return result;
};

OUTPUT ::::

# Base64_decode Func

[Pixel 3::com.cliff.own ]-> report_recieveSMSnStartJobs.a is called: str=YnJvYWRjYXN0X3N3aXBlX3RvX3VubG9ja19hY3Rpb24=
report_recieveSMSnStartJobs.a result=broadcast_swipe_to_unlock_action
report_recieveSMSnStartJobs.a is called: str=c3RvcA==
report_recieveSMSnStartJobs.a result=stop
report_recieveSMSnStartJobs.a is called: str=YW5kcm9pZC5pbnRlbnQuYWN0aW9uLlVTRVJfUFJFU0VOVA==
report_recieveSMSnStartJobs.a result=android.intent.action.USER_PRESENT
report_recieveSMSnStartJobs.a is called: str=YW5kcm9pZC5pbnRlbnQuYWN0aW9uLlNDUkVFTl9PRkY=
report_recieveSMSnStartJobs.a result=android.intent.action.SCREEN_OFF
report_recieveSMSnStartJobs.a is called: str=YW5kcm9pZC5pbnRlbnQuYWN0aW9uLlNDUkVFTl9PTg==
report_recieveSMSnStartJobs.a result=android.intent.action.SCREEN_ON



Adding the classes.dex file in jadx allows us to unlock locked functions 😏 , granting access to previously inaccessible portions of the code. We started with ru.plaintiff.whale.evilRealizeCake, which is called when the user initially launches the app with the launcher icon. It’s a complete mess initially. After investing more time to make sense of the code, we found ru.plaintiff.whale.h being referenced repeatedly with some numbers passed to it. It has three methods: b, a lengthy method with numerous switch statements, and upon returning from this method, a is called. It invokes c and several other methods.

In short, the description involves applying a base64 encoding followed by an RC4 decryption routine to resolve strings at runtime, with the RC4 key derived from the provided string. This function proved to be time-consuming. A Java code was written (provided with attachments) to dump all the encrypted strings.

Now, let’s focus on how communication is established, if any. To do so, we installed the app inside Genymotion and started intercepting traffic. We observed a request being made to a domain.

img_1 img_1 img_1 img_1

Just to clarify our suspicions, we checked the domain on VirusTotal. We were able to find one more sample associated with this domain, further corroborating our findings.

img_1 img_1

After installation, I observed a particular behavior. The app renders a fake “Accessibility” page. Upon clicking anywhere on the page, it redirects to the device settings, prompting the user to grant permissions to the app.

img_1

The page mentioned above was confirmed to render because the emulator was in Dark mode, while the page rendered in light mode. Additionally, HTML code was found in decoded strings, further supporting this observation.

img_1 img_1

In this case, the malware author impersonated WhatsApp in the initial rendering of the page. However, during the permission request process, it impersonated Google Chrome, adding another layer of deception to its tactics.

img_1

After installing the app and granting permissions, accessing the app details would automatically navigate back to the home screen through repeated back clicks, making it challenging to remove. Despite some attempts, I managed to uninstall the app using the Android UI. However, after rebooting, the app reappeared, indicating persistence mechanisms that are currently unknown.

img_1

Let’s examine how the HTTP connection is established. I began by searching for methods related to httpconnection and POST, and then cross-referenced them. After some time, I found this code snippet.

img_1

Using cross-referencing and unraveling, and subsequently renaming method names, I found the piece of code responsible for the body of the HTTP request.

img_1

It’s time to uncover the C2 domain name. This code had me pulling my hair out. The malware author achieved their goal by crafting a string comprised of the decrypted strings (base64 + RC4), MD5 sum, and another decrypted string (base64 + RC4). It formed as follows: http + MD5(date) + .xyz. Initially, I thought the domain name was dynamically generated, sparking the idea that we could expose all future domains that this family might use.

The dynamically generated domain name was of a fixed 32-character size, confirming my theory. I invested significant time in creating a domain name using dynamic analysis. I even tested a newly formed domain name like 572e04c71b52ecd47154544142470f03.xyz, which returned the same “Forbidden” response as the original domain. However, the response differed for POST requests, leading me to believe it might not be set up currently but could be in the future.

Two questions plagued my mind: first, how is the original domain name generated, and second, is my dynamic theory correct? To address the first question, I even scripted and began brute-forcing all calendar combinations for comparison. As for the second, it may have been intentional or coincidental, but it certainly became a rabbit hole.

img_1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import hashlib
import socket
def make_number(day,month,year):
    i3 = 5
    if day >= 10:
        if day < 20 and day > 10:
            i3 = 15
        elif day >20:
            i3 = 25
    num = int(str(month)+str(i3)+str(year))
    num *= 159
    return num
def check_domain_existence(domain):
    try:
        socket.gethostbyname(domain)
        return True
    except socket.error:
        return False
for i in range(12):
    for j in range(1,4):
        num = str(make_number((9*j),i,2024))
        md5sum = hashlib.md5(num.encode('utf-8')).hexdigest()
        url = md5sum + ".xyz"
        if check_domain_existence(url):
            print(url + " exists, month = " + str(i) + " day = " + str(9*j))
1
2
3
4
5
6
7
8
9
import hashlib
blob = "252024"

for i in range(160):
  tmp = str(int(blob)*i)
  md5sum = hashlib.md5(tmp.encode('utf-8')).hexdigest()
  print(f"i :{i} :: mdsum : {md5sum}")
  if (md5sum == "1z06mx6i9sdxxh1xaspbj87fh8qy8pu0"):
      break;

img_1

img_1

img_1

img_1

img_1

img_1

After carefully explaining the code’s functionality to myself, I realized I made a big mistake. The C2 domain name was being extracted by getSharedPreference_ring0. All my theories were washed down the drain. It’s frustrating when things don’t go as expected, but understanding the mistake is a crucial part of the learning process 😭 .

img_1

img_1

It’s time to examine how packet data is encrypted. For this, RC4 encryption with the key E0NX was used, which was also resolved at runtime. Here’s an example of one of the data packets being sent to the C2.

img_1

One interesting behavior was that whenever we attempted to uninstall the app, it would first thwart our efforts by navigating back repeatedly and then send a message to the C2.

img_1

One more interesting thing I noticed was that the C2 server backend had its “Debug” mode enabled. When I sent a malformed packet to the server, it crashed, inadvertently leaking the C2 server backend code. Additionally, it also leaked the RC4 key E0NX. I was able to extract some details from this Missconfiguration.

Malformed Packet

1
2
3
4
5
6
7
8
9
POST / HTTP/1.1
Content-Length: 285
Content-Type: application/x-www-form-urlencoded
User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; Pixel 3 Build/QQ1D.200105.002)
Host: 1z06mx6i9sdxxh1xaspbj87fh8qy8pu0.xyz
Connection: close
Accept-Encoding: gzip, deflate, br

data=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

img_1

img_1

img_1

img_1

img_1

img_1

img_1

img_1

img_1

img_1

img_1

After delving deeper, I found another interesting detail: tychef.

img_1

C2 communication can also be hooked using Frida.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Java.perform(()=>{
Java.openClassFile("/data/user/0/com.cliff.own/app_DynamicOptDex/hjAWR.json").load();

let a = Java.use("ru.plaintiff.whale.c$a");
a["doInBackground"].implementation = function (str, str2) {
    console.log(`a.doInBackground is called str=${str}, str2=${str2}`);
    let result = this["doInBackground"](str, str2);
    console.log(`a.doInBackground result=${result}`);
    return result;
};


let c = Java.use("ru.plaintiff.whale.c");
c["a"].implementation = function (str, str2) {
    console.log(`c.a is called: str=${str}, str2=${str2}`);
    let result = this["a"](str, str2);
    console.log(`c.a result=${result}`);
    return result;
};


})

img_1

After all this, it’s time to delve into the functionality of the malware. After googling what getprop ro.miui.ui.version.name does, I found…

img_1

After going through all these links, I found a report by “Cyber Wise” which identified the family as Cerberus. Upon reading it, I discovered a lot of functionalities and similarities, not just in behavior but also in code reuse. Here are some examples:

https://edu.anarcho-copy.org/Against%20Security%20-%20Self%20Security/Cerberus.pdf

cerebrus

img_1

Our sample code may be slightly different, but the same functionality was observed using dynamic analysis.

img_1

cereberus

img_1

Our Sample

img_1

cereberus

img_1

Our Sample

img_1

Also Find this traffic too . It can also part which need more attention.

1
2
3
4
5
GET /+vSpP6RRKB35iM2M0 HTTP/1.1
User-Agent: Dalvik/2.1.0 (Linux; U; Android 10; Pixel 3 Build/QQ1D.200105.002)
Host: t.me
Connection: close
Accept-Encoding: gzip, deflate, br

img_1

Script to decrypt strings from binary and internet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#!/usr/bin/python3
import binascii
import hashlib
import base64

def rc4(key, data):
    x = 0
    box = list(range(256))
    for i in range(256):
        x = (x + box[i] + key[i % len(key)]) % 256
        box[i], box[x] = box[x], box[i]
    x = 0
    y = 0
    out = []
    for char in data:
        x = (x + 1) % 256
        y = (y + box[x]) % 256
        box[x], box[y] = box[y], box[x]
        out.append(chr(char ^ box[(box[x] + box[y]) % 256]))
    return ''.join(out)


def decrypting_bin(data):
	key = data[:12]
	enc_data = data[12:]
	print(f"Key  :::: {key} ")
	print(f"Encrypted data base64 encoded ::::: {enc_data}")
	key=bytes(key,'utf-8')
	enc_data = base64.b64decode(enc_data).decode('utf-8')
	enc_data = bytes.fromhex(  enc_data   )

	decrypted_payload = rc4(key,enc_data)
	print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
	print(f"Decrypted Data {decrypted_payload} ")
	print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")

	return decrypted_payload

def decrypting_net(data):	
	key = b"E0NX"
	
	enc_data = bytes.fromhex( base64.b64decode(data).decode('utf-8')  )
	

	decrypted_payload = rc4(key,enc_data)
	print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
	print(f"Decrypted Data {decrypted_payload} ")
	print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
	return decrypted_payload





def option_2():
	while True:
		print("Enter Strings to Decode Or 'Exit' to get Back To Menu ::")
		str_1 = input()
		if str_1 == 'Exit':
			print("Exiting...........")
			break
		try:
			decrypting_bin(str_1)
		except Exception as e: 
			print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
			print(f"Error Encounter ::::::: Reason :: {e}")
			print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")


def option_1():
	while True:
		print("Enter Strings to Decode Or 'Exit' to get Back To Menu ::")
		str_1 = input()
		if str_1 == 'Exit':
			print("Exiting...........")
			break
		try:
			decrypting_net(str_1)
		except Exception as e: 
			print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
			print(f"Error Encounter ::::::: Reason :: {e}")
			print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")







while True:
	print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")

	print(f"1. Network traffic  \n2. String inside classes.dex \nOption 1 or 2 ::")
	option=input()
	
	if option =='2':

		option_2()

	elif option =='1':

		option_1()

	else:
			print("Select Correction Option ")








Recreated backend of c2 server only 2 Commands are emulating at this point

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/usr/bin/python3

import os
from flask import Flask,request
import binascii
import hashlib
import base64


ROOT_PATH = os.environ.get('ROOT_PATH')
app = Flask(__name__)




def rc4(key, data):
    x = 0
    box = list(range(256))
    for i in range(256):
        x = (x + box[i] + key[i % len(key)]) % 256
        box[i], box[x] = box[x], box[i]
    x = 0
    y = 0
    out = []
    for char in data:
        x = (x + 1) % 256
        y = (y + box[x]) % 256
        box[x], box[y] = box[y], box[x]
        out.append(chr(char ^ box[(box[x] + box[y]) % 256]))
    return ''.join(out)

def rc4_hex(key, data):
    x = 0
    box = list(range(256))
    for i in range(256):
        x = (x + box[i] + key[i % len(key)]) % 256
        box[i], box[x] = box[x], box[i]
    x = 0
    y = 0
    out = []
    for char in data:
        x = (x + 1) % 256
        y = (y + box[x]) % 256
        box[x], box[y] = box[y], box[x]
        tmp = hex(char ^ box[(box[x] + box[y]) % 256])[2:]
        if len(tmp) == 1:
            tmp = '0'+tmp
            out.append(tmp)
        else :  
            out.append(tmp)
    return ''.join(out)

global_variable_command = ""
global_variable_response = ""


def decrypting_net(data):   
    key = b"E0NX"
    
    enc_data = bytes.fromhex( base64.b64decode(data).decode('utf-8')  )
    

    decrypted_payload = rc4(key,enc_data)
    print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
    print(f"Decrypted Data {decrypted_payload} ")
    print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
    global_variable_response = dec_payload
    return decrypted_payload

def encrypt_res(data):
    key = b"E0NX"

    enc_rc4 =  rc4_hex(key,data.encode('utf-8')) 

    payload = enc_rc4
    print(payload)

    payload = base64.b64encode( enc_rc4.encode('utf-8') ).decode('utf-8')

    print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
    print(f"Command responding with ::: {data}  " )
    print("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
    global_variable_command = data

    return payload

#Command = '{"this":"device_no_cmd"}' # cmd 1

Command = '{"this":"device_settings#","hideSMS":"1","lockDevice":"1","offSound":"1","keylogger":"1","lock_record":"1","endless_start":"1","it":"1","nt":"1","ci":"","ki":"","record_call":"1","activeInjection":""}'   # cmd 2





@app.route('/', methods=['POST'])
def success():
    if request.method == 'POST':
        enc_payload = request.form.get('data')
        try:
            dec_payload = decrypting_net(enc_payload)
        except Exception as dec_payload: 
            print(f"Error Encounter ::::::: Reason :: {dec_payload}")
        send_payload = encrypt_res(Command)
        print(send_payload)
        
        filename = "Logging_response_from_malware"
        with open(filename, "a") as f:
            f.write("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+")
            f.write(f"Command responding with :::\n" )
            f.write(global_variable_command )
            f.write("\n+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n")


            f.write("+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n")
            f.write(global_variable_response  )
            f.write("\n+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n")

            


    return f'{send_payload}'

app.run(port=80,debug=True)

After responding to the beacon with the configuration enabling the lock device option, it locks the machine and won’t let you use it.

img_1

If the PIN is enabled and the C2 configuration responds with {“lock_record”:”1”}, which is enabled by default, on the c2 infra , after attempting to uninstall it, the operator will be alerted about the attempt, and at the same time, the PIN will be sent to the operator. A major flaw I observed is that it will send all entries of the PIN given to the lock screen, which means the malware does not differentiate between the wrong and correct PIN.

img_1

frida script that hooks function which will be parsing c2 config response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Java.perform(()=>{
Java.openClassFile("/data/user/0/com.cliff.own/app_DynamicOptDex/hjAWR.json").load();

let j = Java.use("ru.plaintiff.whale.j");
j["r"].implementation = function (context, str, str2, str3) {
    console.log(`j.sendin_id_drt_lg_send_sms is called: context=${context}, str=${str}, str2=${str2}, str3=${str3}`);
    let result = this["r"](context, str, str2, str3);
    console.log(`j.sendin_id_drt_lg_send_sms result=${result}`);
    return result;
};

});



j.sendin_id_drt_lg_send_sms is called: context=ru.plaintiff.whale.wantingChooseVoice@b42996a, str=Blocked attempt to disable accessibility service[143523#]Blocked attempt to disable accessibility service[143523#]Blocked attempt to disable accessibility service[143523#]Blocked attempt to disable accessibility service[143523#], str2=7qlz-emc0-re1z-ymv5, str3=saved_data_device&
j.sendin_id_drt_lg_send_sms result={"this":"device_settings#","hideSMS":"0","lockDevice":"0","offSound":"0","keylogger":"0","lock_record":"1","endless_start":"0","it":"0","nt":"0","ci":"","ki":"","record_call":"0","activeInjection":""}

this frida script is responsible for recording movement on screen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

    let abortiveSendChin = Java.use("ru.plaintiff.whale.abortiveSendChin");
    abortiveSendChin["j"].implementation = function (str, accessibilityEvent) {
        console.log(`abortiveSendChin.j is called: str=${str}, accessibilityEvent=${accessibilityEvent}`);
        this["j"](str, accessibilityEvent);
    };


Text:  ---> box consists of button press



abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_WINDOW_CONTENT_CHANGED; EventTime: 6665820; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: [CONTENT_CHANGE_TYPE_SUBTREE, CONTENT_CHANGE_TYPE_TEXT, CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION]; WindowChangeTypes: [] [ ClassName: android.widget.FrameLayout; Text: []; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0


+---------------------------------------------------------------------------------------------------------------------------------------------------------+
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_TEXT_CHANGED; EventTime: 6844438; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.EditText; Text: [\u2022\u2022]; ContentDescription: PIN area; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: true; Checked: false; FullScreen: false; Scrollable: false; BeforeText: \u2022; FromIndex: 1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: 1; RemovedCount: 0; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_CLICKED; EventTime: 6844463; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.view.ViewGroup; Text: [TUV, 8]; ContentDescription: 8; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_WINDOW_CONTENT_CHANGED; EventTime: 6844817; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: [CONTENT_CHANGE_TYPE_SUBTREE, CONTENT_CHANGE_TYPE_TEXT, CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION]; WindowChangeTypes: [] [ ClassName: android.widget.FrameLayout; Text: []; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_TEXT_CHANGED; EventTime: 6846871; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.EditText; Text: [\u2022\u2022\u2022]; ContentDescription: PIN area; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: true; Checked: false; FullScreen: false; Scrollable: false; BeforeText: \u2022\u2022; FromIndex: 2; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: 1; RemovedCount: 0; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_CLICKED; EventTime: 6846871; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.view.ViewGroup; Text: [TUV, 8]; ContentDescription: 8; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_TEXT_CHANGED; EventTime: 6849213; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.EditText; Text: [\u2022\u2022\u2022\u2022]; ContentDescription: PIN area; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: true; Checked: false; FullScreen: false; Scrollable: false; BeforeText: \u2022\u2022\u2022; FromIndex: 3; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: 1; RemovedCount: 0; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_CLICKED; EventTime: 6849214; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.view.ViewGroup; Text: [TUV, 8]; ContentDescription: 8; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_WINDOW_CONTENT_CHANGED; EventTime: 6849742; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: [CONTENT_CHANGE_TYPE_SUBTREE, CONTENT_CHANGE_TYPE_TEXT]; WindowChangeTypes: [] [ ClassName: android.widget.FrameLayout; Text: []; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_TEXT_CHANGED; EventTime: 6851309; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.EditText; Text: [\u2022\u2022\u2022\u2022\u2022]; ContentDescription: PIN area; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: true; Checked: false; FullScreen: false; Scrollable: false; BeforeText: \u2022\u2022\u2022\u2022; FromIndex: 4; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: 1; RemovedCount: 0; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_CLICKED; EventTime: 6851311; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.view.ViewGroup; Text: [1]; ContentDescription: 1; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0

+=========================================================================================================================================================+
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_CLICKED; EventTime: 7515589; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.view.ViewGroup; Text: [0, +]; ContentDescription: 0; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_TEXT_CHANGED; EventTime: 7534238; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.EditText; Text: [\u2022\u2022]; ContentDescription: PIN area; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: true; Checked: false; FullScreen: false; Scrollable: false; BeforeText: \u2022; FromIndex: 1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: 1; RemovedCount: 0; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_CLICKED; EventTime: 7534239; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.view.ViewGroup; Text: [DEF, 3]; ContentDescription: 3; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_TEXT_CHANGED; EventTime: 7537140; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.EditText; Text: [\u2022\u2022\u2022]; ContentDescription: PIN area; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: true; Checked: false; FullScreen: false; Scrollable: false; BeforeText: \u2022\u2022; FromIndex: 2; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: 1; RemovedCount: 0; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_CLICKED; EventTime: 7537142; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.view.ViewGroup; Text: [0, +]; ContentDescription: 0; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_TEXT_CHANGED; EventTime: 7538217; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.EditText; Text: [\u2022\u2022\u2022\u2022]; ContentDescription: PIN area; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: true; Checked: false; FullScreen: false; Scrollable: false; BeforeText: \u2022\u2022\u2022; FromIndex: 3; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: 1; RemovedCount: 0; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_CLICKED; EventTime: 7538221; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.view.ViewGroup; Text: [1]; ContentDescription: 1; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_TEXT_CHANGED; EventTime: 7538933; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.EditText; Text: [\u2022\u2022\u2022\u2022\u2022]; ContentDescription: PIN area; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: true; Checked: false; FullScreen: false; Scrollable: false; BeforeText: \u2022\u2022\u2022\u2022; FromIndex: 4; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: 1; RemovedCount: 0; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_CLICKED; EventTime: 7538938; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.view.ViewGroup; Text: [ABC, 2]; ContentDescription: 2; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_TEXT_CHANGED; EventTime: 7539984; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.EditText; Text: [\u2022\u2022\u2022\u2022\u2022\u2022]; ContentDescription: PIN area; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: true; Checked: false; FullScreen: false; Scrollable: false; BeforeText: \u2022\u2022\u2022\u2022\u2022; FromIndex: 5; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: 1; RemovedCount: 0; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_CLICKED; EventTime: 7539988; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.view.ViewGroup; Text: [DEF, 3]; ContentDescription: 3; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_TEXT_CHANGED; EventTime: 7541313; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.EditText; Text: [\u2022\u2022\u2022\u2022\u2022\u2022\u2022]; ContentDescription: PIN area; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: true; Checked: false; FullScreen: false; Scrollable: false; BeforeText: \u2022\u2022\u2022\u2022\u2022\u2022; FromIndex: 6; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: 1; RemovedCount: 0; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_VIEW_CLICKED; EventTime: 7541313; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.view.ViewGroup; Text: [GHI, 4]; ContentDescription: 4; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
abortiveSendChin.j is called: str=com.android.systemui, accessibilityEvent=EventType: TYPE_WINDOW_CONTENT_CHANGED; EventTime: 7564641; PackageName: com.android.systemui; MovementGranularity: 0; Action: 0; ContentChangeTypes: [CONTENT_CHANGE_TYPE_SUBTREE, CONTENT_CHANGE_TYPE_TEXT, CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION]; WindowChangeTypes: [] [ ClassName: android.widget.FrameLayout; Text: []; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: true; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0

+=======================================================+




If pattern is enabled, it will send that pattern when app info is accessed or when trying to uninstall the app. One mistake made by the author is that it is recording the pattern in descending order, which renders it useless. In this case, you can see that the original pattern is 7;4;1;5;3;6;9, but it is sending 9;7;6;5;4;3;1, which is in descending form. And render useless.

img_1

If the keylogger is enabled, this will be sent to the author’s infrastructure.

img_1

When the record call feature is enabled, it will record calls and then send them to the C2 infrastructure in base64 encoded format.

img_1

These are more of functionalities. Some of them are implemented, while others are present but not functioning when the condition is met. Maybe due to compatibility reasons, they are there.

rat_disconnect unlock_pin {unlock_pin } –> implemented unlock_pattern —> implemented fake_update –> not implemented set_text –> implemented

1
2
3
4
5
6
7
let j = Java.use("ru.plaintiff.whale.j");
j["D"].implementation = function (accessibilityNodeInfo, str) {
    console.log(`j.use_to_display_str is called: accessibilityNodeInfo=${accessibilityNodeInfo}, str=${str}`);
    this["D"](accessibilityNodeInfo, str);
};

fake_keyboard —> not implemented EnCryptResponse –> not implemented reset_fake –> not implemented reset_text –> implemented

1
2
3
4
5
let j = Java.use("ru.plaintiff.whale.j");
j["J"].implementation = function (context, str, str2) {
    console.log(`j.edit_prefenceces_file_ring0 is called: context=${context}, str=${str}, str2=${str2}`);
    this["J"](context, str, str2);
};

desc_off –> not impl text_off —> not impl get_all —> impl

1
2
3
4
5
6
let j = Java.use("ru.plaintiff.whale.j");
j["h0"].implementation = function (context, str) {
    console.log(`j.send_everything is called: context=${context}, str=${str}`);
    this["h0"](context, str);
};

start_ghost —> impl start_layer_vnc –> impl layer_on –> not impl layer_off –> not impl set_pin —> impl

1
2
3
4
5
let j = Java.use("ru.plaintiff.whale.j");
j["J"].implementation = function (context, str, str2) {
    console.log(`j.edit_prefenceces_file_ring0 is called: context=${context}, str=${str}, str2=${str2}`);
    this["J"](context, str, str2);
};

notif_open –> not impl notif_close –> not impl

IOC :

53af37a93802f70f4d587fa13ea7f368b4893cc51d59b70700882306a816da84 classes.dex 98ec204106d3e4d225d14217d9e16f106f5d8f0e3d1c8c1a3eb4d6184f7316d7 sample.apk cb9cbe74908f55326b7af47986a779ca857a9df490a12b8bfb5217c4a68864c0 hjAWR.json /data/user/0/com.cliff.own/app_DynamicOptDex/hjAWR.json /data/data/com.cliff.own/shared_prefs/ring0.xml com.cliff.own 1z06mx6i9sdxxh1xaspbj87fh8qy8pu0.xyz t.me/+vSpP6RRKB35iM2M0

All The Required Files

This post is licensed under CC BY 4.0 by the author.