• Deutsch
  • English
  • Deutsch
  • English
  • Home
  • Blog
  • UFADE

    • What is UFADE?
    • Installation
    • Connect devices
    • Navigation
    • Reporting
    • Extraction
    • Logging
    • Developer options
    • Advanced Options
    • Data Operations
  • Testpoints
  • Legal

That still only counts as one! - iLEAPP Sticker Animation (Zalo Parser)

iLEAPP and Zalo


I recently encountered the Zalo app for the first time in a work context. It is a (or rather, "the") Vietnamese messaging app. It also offers cloud storage and voice calling.

Commercial tools do a decent job of parsing the app. However, the presentation and interpretation sometimes differ significantly, and time and again, cryptic texts were displayed that raised more questions than they helped to clarify.

I soon learned that my colleagues at Customs frequently deal with Zalo, and once again Bruno Fischer provided me with a set of test data.

Fortunately, Zalo data is also included in an iTunes backup, so a PRFS backup with UFADE is all that’s needed to process Zalo with iLEAPP.


Locations

In the PRFS or FFS backup, user data can be found at: /private/var/mobile/Containers/Data/Application/{uuid}/Documents/. In an older version of the app, the chat.sqlite file already contained all of the user’s individual chats. In Bruno’s test data, this database existed but did not contain any data in the relevant ChatEntity table. However, in all versions examined, the chat_dbs subdirectory was present, and within it was another directory named only with numbers (e.g., 450986601). This is the user's Zalo ID.

This directory usually contains several databases. A database represents a chat (e.g., 454362368.db / the other person’s Zalo ID) or a group chat (e.g., group_621589592.db / the group’s Zalo ID).

Users and groups can be mapped using the profile.sqlite and chatgroup.sqlite databases.

chat.sqlite

Chats

Zalo-ID = ProfileEntity.userid
Name = ProfileEntity.displayname

chatgroup.sqlite

Groupchats

Zalo-ID = GroupEntity.groupid
Name = GroupEntity.name

Regardless of whether it is a chat or group chat database, the structure of the ChatContent table is always the same in the comparison data provided:


ColumnContent
SenderIDUser ID / Zalo ID of the message sender (String)
MsgTypeMessage type (Integer)
TimeStampMessage timestamp in UNIX format (Integer)
MsgContentActual message (String)
BinNetBinary blob containing additional message data
LocalPathPath to media files

In addition, there are other columns, but they are not currently included in this presentation.

By comparing the data with the display in the app itself, the following message types (MsgType) were identified:


MsgTypeInterpretation
0textmessage
1Media file / Online link to the media
2Media file / Online link to the media
3Media file / Online link to the media
4Media file / Online link to the media
6Voicenote (Online link)
10Reference to stickers (We'll be doing some crafts here later)
12recommened.calltime in BinNet -> Call (voice or video)
12recommened.misscall in BinNet -> Missed call (voice or video)
12recommened.groupcall in BinNet -> Group call
12recommened.link in BinNet -> Web link
12recommened.user in BinNet -> User contact information
15System message
18Location (latitude, longitude in BinNet)
19Media file / Online link to the media
20System message
22File / Online link to the file (e.g., PDF)
23Media file / Online link to the media
24System message
26Poll
36System message

Data and Media

Media references are handled in various ways. In the simplest case, there is an entry in the LocalPath column that links directly to an attachment.

In addition, file and media references can be stored in the BinNet blob.

Media:

The Documents directory also contains a folder named after the user's Zalo ID. Subdirectories have been created here corresponding to the Zalo IDs of the conversation partners and groups. These are further subdivided according to file type (jpg, mp4, aac, etc.). The filenames in these directories can also be found in BinNet. This allows media to be assigned to individual messages based on the filename and the contact’s Zalo ID:

Entry in BinNet

Media-reference

File in the backup

Media-file

Incidentally, here’s an interesting point: In addition to standard JPEG files, Zalo also uses the newer JPEG XL format. Currently, only a few browsers support this format (Firefox and Chrome currently only in beta releases). Even if the database contains a reference to "jxl," the media files are saved with the ".jpg" extension.

This discovery and its reporting to the LEAPP core developers led to changes across all LEAPP projects, including LAVA, so that JPEG XL files are now displayed correctly when viewing the report via LAVA.


Files

Additional files may be stored in the Files directory. Files are organized according to the following scheme: /Files/{file's MD5 hash}/{filename}. In these cases, both the checksum and the filename are stored in BinNet:

Entry in BinNet

File-reference

File in the backup

Datei

Stickers

Now it's time to get creative. We've determined that stickers can be identified by MsgType 10. Messages with type 10, for example, have the following structure in MsgContent: [^10365,22505^]
These numbers can also be found in the sticker.sqlite database, where they are referred to as "cateid" (first number: 10365) and "eid" (second number: 22505). In addition, some (often not all) of these numbers can be found under Documents/Sticker/Snapshot. A complete sticker path is structured as follows: Documents/Sticker/Snapshot/{cateid}/{eid}/. The eid directory contains one or more PNG files and a metadata file.

If there is only one file, it can be imported directly into iLEAPP using the check_in_media function. If there are multiple PNG files, the situation is currently different, as iLEAPP only allows one media file per record (in this case, a message).

However, for the subsequent display of conversations in LAVA, the plan is to replicate the user experience as closely as possible anyway. So let’s just assemble the animated stickers before they are displayed.

The following example is taken from the directory Documents/Sticker/Snapshot/10365/22505/:

01.png02.png03.png04.png
1234
05.png06.png07.png08.png
1234

In addition, the metadata file contains information about the animation:

{"duration":[100],"fkey":0,"version":0}

This gives us not only the individual frames but also the display duration of each frame (100 ms in this case). Since only a single time value is specified, it applies to all frames.

Pillow must be installed as a prerequisite for iLEAPP and, conveniently, is capable of creating animated GIFs. This is achieved using the following helper function:

from io import BytesIO
from PIL import Image

def build_gif(png_files, metadata_file):
    """Recreates Stickers from multiple PNG files"""
    with open(metadata_file, "r", encoding="utf-8") as f:
        metadata = json.load(f)
    duration_list = metadata.get("duration", [100])
    if len(duration_list) == 1:
        duration_list = duration_list * len(png_files)
    frames = [Image.open(p).convert("RGBA") for p in sorted(png_files)]
    output = BytesIO()
    frames[0].save(
        output,
        format="GIF",
        save_all=True,
        append_images=frames[1:],
        duration=duration_list,
        loop=0,
        disposal=2,
        transparency=0
    )
    output.seek(0)
    return output

The function returns the following GIF (which is certainly of great artistic value) as a bytes object:

Gif Animation

This can now be made available in the report using the check_in_embedded_media function, and it greets us cheerfully in LAVA's conversation view once the report has been generated:

LAVA Animation

You can see LAVA here with the German translation by Bruno Fischer. Unless otherwise specified, LAVA launches directly in the system language.


Conclusion

  • There is a new iLEAPP parser that does not require FFS (already merged into iLEAPP)
  • LEAPPs can now recognize and correctly classify JPEG XL files
  • LAVA can now display JPEG XL
  • Displaying chats in LAVA as conversations is a huge improvement
  • I now know how to wiggle a butt...