CnC communication of a fake Aarogya Setu COVID-19 app

Update Aug 14: Fixed sha256 of sample (previous sha256 was another malicious covid-19 contact tracing app, but not exactly the one I analyzed in this article).

Update Aug 17: Added link to dummy server’s code.

Aarogya Setu is the Indian open source COVID-19 contact tracing app. Like many other COVID-19 tracing apps, it has many malicious copy-cats (see here). In this article, we are going to focus on one of the malicious apps (sha256: 885d07d1532dcce08ae8e0751793ec30ed0152eee3c1321e2d051b2f0e3fa3d7) and how it communicates with its CnC.

This malware was first seen by @malwrhunterteam in May 2020, distributed by phishing link http://prettysavantwholesale[.]com/Aarogya-Setu-v1.5.apk (no longer active). It poses as the Aarogya Setu app.

Screenshot of a smartphone showing the malicious app in the top left corner, using the Aarogya Setu icon
Fig. 1: The malware is installed and its icon fakes the legitimate Aarogya Setu app (see top left corner)

Communication with the CnC

The malware connects a the remote CnC. The code below shows the creation of the socket. The CnC host and port — like all other malware settings — are saved in the malware’s shared preferences file (, and use meaningless 5-digit labels as keys to settings. For example, 10334 stores the CnC port, and 10335 the CnC host.

Fig. 2: Reading CnC host and port from shared preferences

If those settings are not present in the shared preferences file, the default values are taken from the malware’ resources.

Fig. 3: Default CnC host and port number
Fig. 4: Code of the malware connecting to the remote CnC using a socket

Replacing the CnC with a dummy server

The CnC ( no longer responding. To understand what it was receiving, I built a dummy server which displays incoming messages.

First, we need to divert packets sent to the CnC to the local host ( On Linux systems, an iptable command does that:

sudo iptables -t nat -A OUTPUT -p all -d -j DNAT — to-destination

Then, we implement a dummy server, with a socket server listening on port 29491, launch it and wait for the malicious app to connect:

$ python3 
Logger configured
Starting up server on port 29491
Listening… (max clients=10)
Connecting (‘’, 59674) — client no. (1)…
IN: b’33\x00\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00+(\xcd-0402\xb1\xc8+\xcd\xc9\x01\x00}4.\xed\r\x00\x00\x00'

Packet structure

If we search through references using the socket, we see the code writes to the socket as follows:

Fig. 5: Malware’s CnC packet format
  1. The message is gzipped
  2. The packet is prefixed by the gzip’s length and a 0x00.

Uncompressing messages

So, we update our dummy server’s implementation to automatically gunzip messages. There is a first packet of length 1439 we’ll discuss later. Then, we notice 2 messages of 33 bytes decompress to pump10248null. We’ll explain those first.

[SpyServer] 08/14/2020 10:09:20 IN: b’1439\x00\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xadU\xc9\xd2\xab\xb8\x0e~\x95^\xf5\x86\xba\xc5\x14\x02,za\xc0 [..]
[SpyServer] 08/14/2020 10:09:20 Packet indicates length=1439
[SpyServer] 08/14/2020 10:09:20 get_msg_header() returns marker=4 msg_len=1439
[SpyServer] 08/14/2020 10:09:20 decompress(): marker=4 msg_len=1439
[SpyServer] 08/14/2020 10:09:34 Packet indicates length=33
[SpyServer] 08/14/2020 10:09:34 get_msg_header() returns marker=2 msg_len=33
[SpyServer] 08/14/2020 10:09:34 decompress(): marker=2 msg_len=33
[SpyServer] 08/14/2020 10:09:34 msg_len=33 msg=b’pump10248null’
[SpyServer] 08/14/2020 10:09:56 IN: b’33\x00\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00+(\xcd-0402\xb1\xc8+\xcd\xc9\x01\x00}4.\xed\r\x00\x00\x00'
[SpyServer] 08/14/2020 10:09:56 Packet indicates length=33
[SpyServer] 08/14/2020 10:09:56 get_msg_header() returns marker=2 msg_len=33
[SpyServer] 08/14/2020 10:09:56 decompress(): marker=2 msg_len=33
[SpyServer] 08/14/2020 10:09:56 msg_len=33 msg=b’pump10248null’

Those “pump” messages (likely heartbeat messages) come from this piece of code:

Fig. 6: Calls method below with argument “pump”

This calls the method send_gzip_array() we showed at Figure 5. Precisely, it creates a message “pump” + “10248” + “null” and gzips it. Which is exactly what we see in our server :)

Before that, our server’s logs show a longer message which ends with LShrSoXErMBSp2N7jllAdHD4dmK1lQHleQSHuAdU3ciRGBIva9zOuaYkJBYMQlJ2dx3czKTkWcDH/2Q==10249MD4G10249100&true10249–1024910248null.The values 10249 are typically commands from the malware. We notice a longer value ending by the noticeable == which typically indicates Base64. So, we get all that Base64 encoded value and decode it: echo "..." | base64 -d.

Fig. 7: Decoding the Base64 buffer. It outputs binary data. Notice the JFIF magic, typical to JPEG images

It outputs binary info beginning with JFIF: that’s a JPEG image! We dump the decoded bytes in a file and view the resulting image.

Fig. 8: This is the decoded image sent by the malware to the server. I admit it is not very interesting in that particular case, but it is an accurate screenshot my emulator’s wallpaper.

If we read the beginning of the message again, we have 1033210249510249null & null10249/9j/… . We search for 10332 in the code and find the spot that generates this message:

Fig. 9: Method that generates the packet containing the wallpaper screenshot

Precisely, the message that will be sent (gzipped) contains:

  1. “10332”
  2. “10249”. This is a delimiter we see in numerous other messages.
  3. an integer representing the screen mode. In our case, we had value “5” which means the screen was on. See Figure 10.
  4. “10249”
  5. SMS origin: in case a SMS was received, this will be a message like “SMS[ originating phone number]”. In our case, there was no SMS, so this is “null”.
  6. “&”
  7. phonenumber: in case a call was placed, this will contain the outgoing phone number. Null in our case.
  8. “10249”
  9. Wallpaper: a screenshot of the current wallpaper, resized to 48x48 pixels (0x30). See Figure 11.
  10. “10249”
  11. Network type. In our case MD4G which a string the malware uses for NETWORK_TYPE_LTE.
  12. “10249”
  13. Battery level and whether the phone is plugged or not. In our case, the emulator was considered to be 100% charged, and plugged 😉. See Figure 12.
  14. “10249–10249”
  15. “10248null”. This footer is added to all messages that get gzipped (see Figure 5)
Fig. 10: Retrieving the current screen mode: on (5), locked (3), error (6)…
Fig. 11: Grabbing the current wallpaper and resizing it
Fig. 12: Get battery level and if plugged or not

Happily, everything gets an explanation when you disassemble code 😃

— Cryptax

Mobile and IoT malware researcher. The postings on this account are solely my own opinion and do not represent my employer.