Regardless of deceptive advertising and marketing, Israeli firm TeleMessage, utilized by Trump officers, can entry plaintext chat logs


Thank you for reading this post, don't forget to subscribe!

Regardless of their deceptive advertising and marketing, TeleMessage, the corporate that makes a modified model of Sign utilized by senior Trump officers, can entry plaintext chat logs from its prospects.

On this publish I give a excessive stage overview of how the TeleMessage pretend Sign app, referred to as TM SGNL, works and why it is so insecure. Then I give a radical evaluation of the supply code for TM SGNL’s Android app, and what led me to conclude that TeleMessage can entry plaintext chat logs. Lastly, I again up my evaluation with as-of-yet unpublished particulars concerning the hack of TeleMessage.

However first, this is a fast timeline of occasions.

  • On Thursday, 404 Media reported that within the Reuters photograph displaying former Nationwide Safety Advisor and battle prison Mike Waltz checking his Sign messages underneath the desk, he was really utilizing an obscure modified Sign app referred to as TM SGNL, and never the true and really safe Sign app.
  • On Friday, I wrote an evaluation of every part I might discover out about TM SGNL utilizing OSINT, together with the truth that it is almost inconceivable to put in with no gadget enrolled in an MDM service that is tied to an Apple Enterprise Supervisor or a Google Enterprise account.
  • On Saturday, after discovering that TeleMessage revealed the supply code for the TM SGNL apps for Android and iPhone themselves, I re-published them on GitHub with the purpose of constructing them simpler to analysis. (It appears to be like just like the iOS supply code is definitely simply unmodified Sign, so possibly they really solely revealed their Android code.)
  • On Saturday night time, an nameless supply advised me they hacked TeleMessage.
  • On Sunday, I, together with Joseph Cox, revealed an article concerning the hack to 404 Media (and to my weblog).
  • On Monday, NBC Information reported that TeleMessage suspended its service after a second hacker breached TeleMessage and “downloaded a big cache of recordsdata.”
  • At this time, Senator Ron Wyden revealed a letter, which cites the 404 Media article and my evaluation of TM SGNL, to Lawyer Normal Pam Bondi, requesting that the Justice Division examine the “severe menace to U.S. nationwide safety posed by TeleMessage, a federal contractor that offered dangerously insecure communications software program to the White Home and different federal companies.”

The devastating hacks verify the evaluation that I am sharing on this publish: that TeleMessage’s server – hosted on the general public AWS cloud, run by an Israeli firm that is led by a former IDF spook – has plaintext entry to the Sign chat logs they’re archiving (together with chat logs for Telegram, WeChat, and WhatsApp).

Should you discover this fascinating, subscribe to get these posts emailed on to your inbox. If you wish to help my work, contemplating changing into a paid supporter.

Desk of Contents

Overview

TeleMessage makes modified variations of fashionable messaging apps, together with Sign, WhatsApp, Telegram, and WeChat. The modified Sign app is sort of fully an identical to the genuine model of Sign, besides it additionally archives copies of each message to a vacation spot decided by the TeleMessage buyer. (Presumably, WhatsApp, Telegram, and WeChat work in the identical method, however I’ve solely analyzed the TM SGNL for Android supply code.)

The TM SGNL app

TM SGNL is interoperable with Sign. When a TM SGNL person registers a brand new account, they’re registering it with the official Sign server. TM SGNL customers can ship messages to Sign customers and visa versa. Should you’re a Sign person, you haven’t any method of realizing if you’re speaking to a TM SGNL person, as a result of the apps are almost an identical and use the identical infrastructure.

That is how Mike Waltz might by accident add The Atlantic editor-in-chief Jeffrey Goldberg to a bunch chat the place they mentioned bombing an residence constructing stuffed with civilians: Waltz was presumably utilizing TM SGNL, and Goldberg was presumably utilizing Sign.

Observe that we do not know the way lengthy Trump officers have been utilizing TM SGNL. It is attainable they have been utilizing it since round Trump’s inauguration on January 20. If that is so, the chat logs for the Sign group the place Waltz invited Goldberg could have been collected by TeleMessage. Nevertheless it’s additionally attainable that they solely began utilizing TM SGNL after Signalgate, possibly as a solution to try to begin complying with file retaining legal guidelines. To this point, we do not know the timeline. This is able to be good to resolve to know what legal guidelines they’ve already damaged.

Anyway, on Mike Waltz’s cellphone – and fairly doubtless, the telephones of Pete Hegseth, Marco Rubio, Tulsi Gabbard, JD Vance, and plenty of others, probably even together with Donald Trump – the TM SGNL app principally works like this:

  • Each message the TM SGNL app sees will get saved in a SQLite database with a standing column set to WaitingToBeDelivered.
  • TM SGNL registers with the cellphone’s background syncing service. Regularly, the app runs some sync code that selects messages which can be WaitingToBeDelivered. If there are any, it delivers them to TeleMessage’s archive server at https://archive.telemessage.com, and updates their standing to Despatched.

TeleMessage’s archive server

As I mentioned in my preliminary OSINT evaluation, in keeping with this documentation PDF, the admins for organizations that use TeleMessage arrange archive plans and assign customers to them.

How TM SNGL archive plans work, on web page 22 of the documentation PDF

Every archive plan has a supply messaging app (like TM SGNL) and a vacation spot, which is managed by the TeleMessage buyer. Locations can embody Microsoft 365, e mail servers (SMTP), or file servers (SFTP). The admin assigns TeleMessage customers – like Mike Waltz – to an archive plan, which determines the place their chat logs will get archived.

As soon as the TM SGNL app sends chat logs to the archive server, the archive server is meant to do one thing like this: It appears to be like up the person that despatched the chat log, then appears to be like up that person’s archive plan, then forwards the messages to vacation spot outlined within the archive plan (through SMTP or SFTP), and presumably (however who actually is aware of for certain) deletes the chat logs from the archive server.

This is a diagram of how the entire system seems to work:

Diagram of how TeleMessage archives Sign messages, based mostly on my evaluation of the supply code

Microsoft 365

The archive server seems to attach on to SMTP and SFTP locations to push chat logs. Nevertheless, in keeping with Microsoft’s documentation, Microsoft 365 works the alternative method: Microsoft 365 logs into the archive server and pulls the chat logs as soon as a day. Their docs state:

The Sign Archiver connector that you just create within the Microsoft Purview portal connects to the TeleMessage web site daily and transfers the e-mail messages from the earlier 24 hours to a safe Azure Storage space within the Microsoft Cloud.

They even revealed their very own diagram explaining the way it works:

Utilizing a connector to archive Sign communication knowledge in Microsoft 365, from Microsoft’s documentation

Why that is so terribly insecure

Sign is the gold normal of end-to-end encrypted messaging apps.

Messages are encrypted between endpoints – whether or not that is a cellphone operating Sign, a pc operating Sign Desktop, or perhaps a cellphone operating TM SGNL. The Sign server, and any web eavesdroppers, can’t entry the chat logs.

Nevertheless, as soon as they’re at an endpoint, they’re in plaintext (in the event that they weren’t, you would not have the ability to learn your texts). At this level, they’re protected by numerous types of disk encryption relying on the gadget. That is how Sign messages generally find yourself as proof in court docket information: somebody’s cellphone or laptop computer with Sign put in was searched, after the messages have been already decrypted.

TM SGNL utterly breaks this safety. The communication between the TM SGNL app and the ultimate archive vacation spot just isn’t end-to-end encrypted.

TeleMessage lies about this of their advertising and marketing materials, claiming that TM SGNL helps “Finish-to-Finish encryption from the cell phone by to the company archive.”

This is a screenshot from an archived model of their web site (since they’ve taken down all the content material):

TeleMessage falsely claims that TM SGNL helps “Finish-to-Finish encryption from the cell phone by to the company archive”

As an alternative, TM SGNL sends the plaintext, already decrypted variations of chat logs to the archive server. (There could be some encryption concerned a number of the time, which I’m going over the “Corroborative proof from the hack” part on the backside.)

The archive server then forwards these to the vacation spot.

Anyway, after TM SGNL decrypts messages, it sends the plaintext chat logs to TeleMessage’s archive server. At this level, lots of people might need entry to the chat logs.

The archive server was hosted within the public AWS cloud of their knowledge heart in Northern Virginia. This isn’t an accredited place to retailer categorised data, and probably rogue AWS workers might have had entry. This server was open to the general public – anybody on the planet might ship HTTP requests to it to attempt to get chat logs again in a response. On Saturday, a kind of folks did.

I am saying the server “was” hosted in AWS as a result of TeleMessage took down their archive server shortly after we revealed the story concerning the hack. As of this writing, their archive server is offline.

TeleMessage additionally could be sharing chat logs with Israeli intelligence.

TeleMessage is an Israeli firm. It was based in 1999 by Man Levit, the identical 12 months he left his job within the Israel Protection Pressure the place he “served as the top of the planning and improvement of one of many IDF’s Intelligence elite technical items,” in keeping with his bio on the TeleMessage web site (they’ve since taken all of the content material off their web site).

Levit, and others at his agency, have entry to the archive server and all the chat logs it comprises. I do not know what Israel’s model of the Patriot Act is like, however I do know that it will be trivial for TeleMessage so as to add a little bit of code that forwards a duplicate of every part to Israeli intelligence – far less complicated than it was so as to add code to Sign to ahead a duplicate to themselves.

To be clear, there is no proof but that TeleMessage is sharing chat logs with the Israeli authorities. However the truth that they designed their archiving system to not be end-to-end encrypted, and that they lie about it, is sort of a giant purple flag.

That is the app that Mike Waltz makes use of. Whereas we do not actually know the timeline, it is attainable that everybody within the 19-person Signalgate group, the place they mentioned bombing Yemen, have been additionally utilizing this app identical app. These embody JD Vance, John Ratcliffe, Marco Rubio, Pete Hegseth, Stephen Miller, Tulsi Gabbard, and others. It is believable that Israeli intelligence has been studying the inner chat logs of essentially the most highly effective members of Trump’s authoritarian authorities.

Evaluation of the TM SGNL Android supply code

I’ve solely analyzed the the TM SGNL Android supply code, not the iPhone supply code. I am assuming that the iPhone app works in the identical method because the Android app, and something that I be taught from analyzing the Android app is relevant to the iPhone app as nicely.

I’ve centered on Android as a result of it is a lot simpler to reverse engineer Android apps than iPhone apps. There are wonderful instruments accessible for analyzing Android apps, like apktool, apkeep, and even the official Android Studio. Moreover, Android apps are programmed in Java and Kotlin (which each compile to Java bytecode), and it is easy to decompile Java bytecode, turning it again into human readable supply code, and making it a lot simpler to work with.

Additionally, whereas TeleMessage posted hyperlinks to ZIP recordsdata claiming to be comprise the Android and iOS supply code on their web site, the iOS model seems to really be the supply code for Sign for iOS itself, with none further TeleMessage code added.

💡

As you may see within the LICENSE file included with the supply code, TM SGNL is licensed underneath GNU Affero Normal Public License v3.0. This offers me, and everybody else, the limitless proper to entry, analyze, reverse engineer, and just about do anything we want to with the code, as long as we launch any spinoff works underneath the identical license.

I’ve solely had just a few days to have a look at this code to this point – and I paused work on analyzing it so I might break the story about TeleMessage getting hacked – so there’s quite a bit that I nonetheless do not absolutely perceive. It is also attainable I’ve gotten issues unsuitable – please let me know if that is the case. In any case, I am displaying all of my work right here in order that others can reproduce it and construct on it.

Decompiling shared libraries

The TM SGNL supply code is usually the identical because the Sign for Android supply code, however there are some variations. A lot of the new code may be present in these three locations:

The app/src/tm/java/org/ folder comprises TeleMessage code.

TeleMessage code in app/src/tm/java/org/

The app/src/important/java/org/tm/archive/ folder additionally comprises TeleMessage code:

Extra TeleMessage code in app/src/important/java/org/tm/archive/

And at last, the app/libs/ folder comprises TeleMessage’s shared libraries in Android archive (.aar) format. These embody androidcopysdk-signal, authenticatorsdk-signal, and frequent. Shared libraries are re-usable items of code that may be imported into totally different initiatives. These libraries seem to comprise code that may even be shared in TeleMessage’s different Android apps for WhatsApp, WeChat, and Telegram. The Android archive recordsdata are compressed Java bytecode.

Shared libraries in app/libs/

Step one to reverse engineering that is to show these shared libraries into precise human-readable (or a minimum of, human nerd-readable) Java code.

There are numerous native instruments that may do that, however I’ve discovered utilizing on-line companies to decompile Android apps is the only strategy. I ran the discharge variations of the shared libraries by this Android archive decompiler, and it gave me zip recordsdata with the decompiled supply code.

To make issues simpler for others to observe alongside, I’ve made a brand new git department in my TM-SGNL-Android repository referred to as libs and I dedicated the decompiled shared library code there. I’ve made the libs department the default department, too. Now you can entry all the shared library code within the apps/libs/ folder of the libs department:

The app/libs/ folder, with folders stuffed with human-readable Java supply code

Now that all of us have entry to the identical supply code, I’ll attempt to give a short tour of what I’ve discovered to be the related items of code to carry me to my conclusion.

Essential elements

Listed below are some essential elements within the codebase:

  • SignalDatabase: This class represents the SQLite database that shops all the Sign knowledge (messages, attachments, media, threads, identities, drafts, teams, recipients, stickers, reactions, and so forth), for delivering to the archive server.
  • DataGrabber: This class (within the androidcopysdk shared library) appears to do just a few issues.
    • A lot of its 2,577 strains of code are dedicated to capturing the cellphone’s SMS and MMS messages, in addition to name logs – although I do not suppose this code is used within the SGNL app.
    • It additionally has a setMessage methodology, which the SGNL app does use. On this methodology, it makes use of a ContentResolver to retailer messages in a separate SQLite database, staging messages earlier than they get despatched to the archive server. I am going to name this the staging database.
  • TeleMessageApplicationDependencyProvider: This class offers entry to the SDK module (an object with pointers to varied singleton elements just like the DataGrabber and SignalDatabase objects).
    • It additionally offers entry to the messageStoreObserver. This object can have processors, that are principally hooks that get executed when message statuses change.
    • ArchiveMessagesProcessor: It is a messageStoreObserver processor. When messages in SignalDatabase get created (or edited or deleted), this runs DataGrabber.setMessage to retailer the message within the staging database.
  • SyncAdapter: This class (additionally within the androidcopysdk shared library) is an Android sync adapter. It defines a background service that, each time the onPerformSync methodology known as, makes use of the ContentResolver to pick out WaitingToBeDelivered knowledge from the staging database, after which submits it to the TeleMessage’s archive server.

Now I am going to enter a bit extra element about these elements, and the way they tie collectively.

SignalDatabase

This is the start of the SignalDatabase class definition. As you may see, it defines the tables that will likely be used to retailer various kinds of Sign knowledge:

open class SignalDatabase(non-public val context: Software, databaseSecret: DatabaseSecret, attachmentSecret: AttachmentSecret) :
  SQLiteOpenHelper(
    context,
    DATABASE_NAME,
    databaseSecret.asString(),
    null,
    SignalDatabaseMigrations.DATABASE_VERSION,
    0,
    SqlCipherErrorHandler(DATABASE_NAME),
    SqlCipherDatabaseHook(),
    true
  ),
  SignalDatabaseOpenHelper, IDatabase { // TM_SA implement IDatabase

  val messageTable: MessageTable = TeleMessageTable(context, this)  // TM_SA TeleMessageTable
  val attachmentTable: AttachmentTable = TeleAttachmentTable(context, this, attachmentSecret) // TM_SA TeleAttachmentTable
  val mediaTable: MediaTable = MediaTable(context, this)
  val threadTable: ThreadTable = ThreadTable(context, this)
  val identityTable: IdentityTable = IdentityTable(context, this)
  val draftTable: DraftTable = DraftTable(context, this)
  val groupTable: GroupTable = GroupTable(context, this)
  val recipientTable: RecipientTable = RecipientTable(context, this)
  val groupReceiptTable: GroupReceiptTable = GroupReceiptTable(context, this)
  val preKeyDatabase: OneTimePreKeyTable = OneTimePreKeyTable(context, this)
  val signedPreKeyTable: SignedPreKeyTable = SignedPreKeyTable(context, this)
  val sessionTable: SessionTable = SessionTable(context, this)
  val senderKeyTable: SenderKeyTable = SenderKeyTable(context, this)
  val senderKeySharedTable: SenderKeySharedTable = SenderKeySharedTable(context, this)
  val pendingRetryReceiptTable: PendingRetryReceiptTable = PendingRetryReceiptTable(context, this)
  val searchTable: SearchTable = SearchTable(context, this)
  val stickerTable: StickerTable = StickerTable(context, this, attachmentSecret)
  val storageIdDatabase: UnknownStorageIdTable = UnknownStorageIdTable(context, this)
  val remappedRecordTables: RemappedRecordTables = RemappedRecordTables(context, this)
  val mentionTable: MentionTable = MentionTable(context, this)
  val paymentTable: PaymentTable = PaymentTable(context, this)
  val chatColorsTable: ChatColorsTable = ChatColorsTable(context, this)
  val emojiSearchTable: EmojiSearchTable = EmojiSearchTable(context, this)
  val messageSendLogTables: MessageSendLogTables = MessageSendLogTables(context, this)
  val avatarPickerDatabase: AvatarPickerDatabase = AvatarPickerDatabase(context, this)
  val reactionTable: ReactionTable = ReactionTable(context, this)
  val notificationProfileDatabase: NotificationProfileDatabase = NotificationProfileDatabase(context, this)
  val donationReceiptTable: DonationReceiptTable = DonationReceiptTable(context, this)
  val distributionListTables: DistributionListTables = DistributionListTables(context, this)
  val storySendTable: StorySendTable = StorySendTable(context, this)
  val cdsTable: CdsTable = CdsTable(context, this)
  val remoteMegaphoneTable: RemoteMegaphoneTable = RemoteMegaphoneTable(context, this)
  val pendingPniSignatureMessageTable: PendingPniSignatureMessageTable = PendingPniSignatureMessageTable(context, this)
  val callTable: CallTable = CallTable(context, this)
  val kyberPreKeyTable: KyberPreKeyTable = KyberPreKeyTable(context, this)
  val callLinkTable: CallLinkTable = CallLinkTable(context, this)

The onCreate methodology creates the tables if they do not exist.

I am going to must dig into the code additional to see the precise locations the place it occurs, however I imagine at any time when the TM SGNL app receives or sends Sign messages, they get inserted into this database.

DataGrabber

Right here is the setMessage methodology within the DataGrabber class:

public synchronized void setMessage(MessageDetailsArchive messageDetailsArchive) {
    Log.d("information", "setTextMessage begin");
    String lasttime = getLastMessageByType(this.mContext, MessageType.SMS);
    Lengthy.valueOf(lasttime).longValue();
    ContentValues contentValues = prepareValues(messageDetailsArchive.getProtocol(), messageDetailsArchive.getToPhonesArray(), messageDetailsArchive.getFromPhoneNumber(), messageDetailsArchive.getBody(), messageDetailsArchive.getId(), messageDetailsArchive.getDate(), messageDetailsArchive.getSubject(), messageDetailsArchive.getMyNumber(), messageDetailsArchive.getChatMode(), messageDetailsArchive.getChatName(), messageDetailsArchive.getChatId(), messageDetailsArchive.getFromName(), messageDetailsArchive.getFromValue(), messageDetailsArchive.getToNameArray(), messageDetailsArchive.getToPhoneNumberArrayValue(), messageDetailsArchive.getMessageType());
    contentValues.put("kind", MessageType.SMS.title());
    Uri path = this.mContext.getContentResolver().insert(MessageContentProvider.CONTENT_URI, contentValues);
    Log.d("grabber", "insert message and id and time , id: " + path.getPath() + " " + messageDetailsArchive.getDate() + " " + messageDetailsArchive.getId() + "  sub = " + messageDetailsArchive.getSubject());
    SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this.mContext).edit();
    editor.putString(MessageType.SMS.title() + DATE_OF_MESSAGE, messageDetailsArchive.getDate()).apply();
    Log.d("information", "setTextMessage finish");
    CommonUtils.startBackupService(this.mContext);
}

Discover that regardless that we’re storing Sign messages, this line explicitly units the message kind to SMS, which will likely be essential later:

contentValues.put("kind", MessageType.SMS.title());

This line of code inserts the message into the staging database:

Uri path = this.mContext.getContentResolver().insert(MessageContentProvider.CONTENT_URI, contentValues);

And this line of code triggers the SyncAdapter to run:

CommonUtils.startBackupService(this.mContext);

ArchiveMessagesProcessor

It is a processor within the messageStoreObserver. At any time when a message in SignalDatabase is created/modified, the processAfterMessageStateChanged methodology will get referred to as:

@Override // com.tm.androidcopysdk.gadget.MessageStoreProcessor
protected void processAfterMessageStateChanged(@NotNull ArchiveMessage message, @Nullable ArchiveMessage present) {
    Intrinsics.checkNotNullParameter(message, "message");
    String cleanAccountPhoneNumber = message.getCleanAccountPhoneNumber();
    if (cleanAccountPhoneNumber == null || cleanAccountPhoneNumber.size() == 0) {
        Log.d(getTag(), "ignoring archive message " + message.getArchiveId() + ", account cellphone quantity is lacking.");
        return;
    }
    boolean isNewEdit = message.isNewEdit(present);
    if (!isArchivingSupported(message, isNewEdit) || message.getTimestamp().getValue() <= 0) {
        Log.d(getTag(), "ignoring unsupported message " + message.getArchiveId() + " of kind " + message.getType() + " created at " + message.getTimestamp().getValue() + '.');
    } else if (message.hasDeletions()) {
        archiveDeletedMessage(message, present);
    } else if (isNewEdit) {
        archiveEditMessage(message, present);
    } else {
        ArchiveMessageType kind = message.getType();
        change (kind == null ? -1 : WhenMappings.$EnumSwitchMapping$0[type.ordinal()]) {
            case 1:
                archiveMessage(message, present);
                return;
            case 2:
                archiveMmsMessage(message, present);
                return;
            case 3:
                archiveCallMessage(message, present);
                return;
            default:
                Log.w(getTag(), "unsure  deal with " + message + ' ' + present + ' ' + this.detailsConverter.convert(message, present));
                return;
        }
    }
}

As you may see, this code will archive messages after they get deleted (archiveDeletedMessage), after they get edited (archiveEditMessage), in addition to after they get created (archiveMessage).

This is the code in archiveMessage:

non-public ultimate void archiveMessage(ArchiveMessage message, ArchiveMessage present) {
    if (present != null) {
        Log.d(getTag(), "message " + message.getArchiveId() + " was already archived, skipping.");
    } else if (message.getStatus() == MessageStatus.Sending && !message.getChat().isSecret()) {
        Log.d(getTag(), "message " + message.getArchiveId() + " continues to be in 'Sending' state, skipping.");
    } else {
        MessageDetailsArchive particulars = this.detailsConverter.convert(message, present);
        Log.d(getTag(), "archiveMessage " + message + ' ' + particulars);
        this.module.getDataGrabber().setMessage(particulars);
    }
}

When a message is archived, it runs setMessage on DataGrabber, which, as described above, inserts the message into the staging database and triggers the sync adapter.

SyncAdapter

The SyncAdapter‘s onPerformSync methodology is simply too lengthy to cite in full right here, so I’ll simply quote items of it at a time. That is the strategy that Android will name within the background at common intervals, or that the app itself can set off, reminiscent of when a brand new message is available in.

Close to the start of the strategy, this code creates a NetworkManager object, which is the category used to speak with TeleMessage’s archive server.

String baseurl = bundle.getString("baseurl");
String keeperUrl = PreferenceManager.getDefaultSharedPreferences(this.mContext).getString("keeperUrl", null);
if (!TextUtils.isEmpty(keeperUrl)) {
    baseurl = keeperUrl;
}
NetworkManager nm = new NetworkManager(this.mContext, baseurl);
String myNumber = FlavorSettings.getInstance().getMSISDN(PreferenceManager.getDefaultSharedPreferences(getContext()).getString("phonenumber", "999999999"));

When it creates the NetworkManager object, it passes within the base URL. The way it determines this URL is a bit complicated. However in the end, if keeperUrl just isn’t empty, then it makes use of that worth as the bottom URL. As I’ll describe beneath, the keeperUrl worth comes from ArchiveConstants:

const val prodKeeper = "https://archive.telemessage.com"

It additionally units myNumber to be the present person’s cellphone quantity.

Subsequent in SyncAdapter‘s onPerformSync methodology, this code makes a SQL question to the staging database, choosing all messages which can be ready to be delivered (for the sake of simpler studying, I’ve added some newlines to the code displayed right here, however I have never modified any of it):

lengthy installation_date = PrefManager.getLongPref(
    this.mContext.getApplicationContext(), 
    PrefManagerConstants.SHARED_PREFERENCE_INSTALLATION_DATE_KEY, 
    PreferenceManager.getDefaultSharedPreferences(this.mContext).getLong(PrefManagerConstants.SHARED_PREFERENCE_INSTALLATION_DATE_KEY, 0L)
);
Log.d(TAG, "installation_date: " + installation_date);
Log.d(TAG, "baseurl: " + baseurl);
String[] situation = {
    String.valueOf(installation_date), 
    MessageContentProvider.MessageDeliveryStatus.WaitingToBeDelivered.title()
};
Cursor cur = this.mContentResolver.question(
    MessageContentProvider.CONTENT_URI, 
    null, 
    "date >= ? AND standing = ? ", 
    situation, 
    "date DESC"
);
Log.d(TAG, "rely: " + cur.getCount());
boolean z = 0 != (this.mContext.getApplicationInfo().flags & 2);
cur.moveToFirst();

First, it pulls installation_date from the app preferences – this was the timestamp that TM SGNL was put in.

Then it defines situation to be an array with the primary merchandise a string model of the installation_date timestamp, and the second merchandise the message supply standing WaitingToBeDelivered.

Then, utilizing the ContentResolver, it queries the staging database to pick out all messages the place the date is bigger than or equal to installation_date, and the standing is WaitingToBeDelivered, with the outcomes sorted by date in descending order.

Subsequent in onPerformSync, this code begins looping by each message discovered within the SQL outcomes, submitting them to the archive server:

do {
    String typestr = cur.getString(cur.getColumnIndex("kind"));
    if (MessageType.valueOf(typestr) == MessageType.SMS) {
        BodyBase sr = Packager.packENATextMessage(cur, myNumber);
        DBKeepAliveQueryHelper.updateSendToServerTime(this.mContext, ((TelemessageArchiverMessage) sr).getNativeId(), String.valueOf(System.currentTimeMillis()));
        Log.d("community", "ship message:" + ((TelemessageArchiverMessage) sr).getNativeId());
        Response res = nm.begin(sr, this, getContext().getApplicationContext(), false);
        Log.d("community", "after despatched object");

The code checks if the message kind is MessageType.SMS right here, however as I described within the DataGrabber part above, Sign messages are handled as SMS messages.

It then creates a variable sr which comprises the present message row from the SQL question, together with the person’s cellphone quantity. Within the following two strains, discover that it typecasts sr to TelemessageArchiverMessage, which means that sr is of kind TelemessageArchiverMessage.

Lastly, it runs nm.begin within the NetworkManager object, passing within the sr variable with the message knowledge. That is the the place the precise HTTP request to the bottom URL (the TeleMessage archive server) occurs.

NetworkManager

Let’s take a fast peak over in NetworkManager.begin:

public  retrofit2.Response begin(BodyBase message, HandleResponseListener listener, Context context, boolean log) {
    this.mListener = listener;
    Log.d("community", "began api name");
    INetworkProvider networkProvider = context.getApplicationContext().networkProvider();
    TMCredentialsStore credentialsStore = TMCredentialsStore.getInstance(context);
    networkProvider.headersInterceptor().setAuthentication(credentialsStore.userName(context), credentialsStore.password(context));
    AndroidCopyServerAPI TMAndroidCopyAPI = (AndroidCopyServerAPI) networkProvider.service(this.mBaseUrl, AndroidCopyServerAPI.class);
    Name caller = null;
    if (message instanceof TeleMessageMMSArchive) {
        if (!CommonUtils.isUserArchive(context)) {
            caller = TMAndroidCopyAPI.postTelemessageMMSMessage(NetworkManagerAPIUtil.getPostMessageURLByAPIVersion(context), new EnaMmsRequestBody(context, (TeleMessageMMSArchive) message));
        }
    } else if (message instanceof TelemessageArchiverMessage) {
        if (!CommonUtils.isUserArchive(context)) {
            networkProvider.headersInterceptor().setMessageId(message.getMessageId());
            caller = TMAndroidCopyAPI.postSMSMessage(NetworkManagerAPIUtil.getPostMessageURLByAPIVersion(context), (TelemessageArchiverMessage) message);
        }
    } else if (message instanceof SMSMessageRecord) {

That is the code that makes an API name. There is a huge if/else block that checks for various kinds of messages. If the message kind is TelemessageArchiverMessage (which it’s for these Sign messages), it runs this line of code:

caller = TMAndroidCopyAPI.postSMSMessage(NetworkManagerAPIUtil.getPostMessageURLByAPIVersion(context), (TelemessageArchiverMessage) message);

Should you take a look at AndroidCopyServerAPI.postSMSMessage, you will see that it makes a POST request to https://archive.telemessage.com/api/relaxation/archive/telemessageincomingmessage/, with the message content material within the physique:

@POST("api/relaxation/archive/telemessageincomingmessage")
Name postSMSMessage(@Physique SMSMessageRecord sMSMessageRecord);

To be clear, that is the place TM SGNL sends the plaintext, already decrypted Sign message to TeleMessage’s archive server.

Tying all of it collectively

That was quite a bit. Now I’ll tie all of it collectively from the start, the TeleMessageSignalApplication object, which is created when the app is created.

This is the onCreate methodology, which known as when the app begins:

override enjoyable onCreate() {
  tremendous.onCreate()
  Log.createInstance(applicationContext)
  ArchiveLogger.sendArchiveLog("TeleMessage logger created")

  initializeSdk()
  initArchiveUrlsAndStartArchive()
}

This runs initializeSdk, adopted by initArchiveUrlsAndStartArchive. This is initializeSdk:

non-public enjoyable initializeSdk() {
  val module = getSdkModule(requireNotNull(SignalDatabase.occasion))
  val messageObserver = TeleMessageApplicationDependencyProvider.messageStoreObserver
  messageObserver.addProcessor(ArchiveMessagesProcessor(module))
  messageObserver.addProcessor(SendSignatureProcessor(module))
  messageObserver.initialize(module)
}

This initializes the SDK module, passing within the SignalDatabase occasion, which initializes that too.

Then it creates the message observer, and provides the ArchiveMessagesProcessor as a processor, passing within the SDK module (and therefore, the SignalDatabase).

Now at any time when messages get added to SignalDatabase, the next will occur:

  • ArchiveMessageProcessor.processAfterMessageStateChanged will run, calling DataGrabber.setMessage.
  • DataGrabber.setMessage will save the message within the staging database and set off the sync adapter to run.
  • SyncAdapter.onPerformSync will choose the message from the staging database and cross it into NetworkManager.begin.
  • NetworkManager.begin will ship the message to TeleMessage’s archive server at https://archive.telemessage.com.

Subsequent, the TeleMessageSignalApplication‘s onCreate methodology calls initArchiveUrlsAndStartArchive. You may learn the code for that methodology right here, however the essential half is the place it it units the URLs:

CommonUtils.setUrl(context, ArchiveConstants.charlieProduction, ArchiveConstants.prodKeeper)

These values are outlined in ArchiveConstants:

const val charlieProduction = "https://relaxation.telemessage.com"
const val prodKeeper = "https://archive.telemessage.com"

So principally, I said above, prodKeeper is about to https://archive.telemessage.com, and that is in the end the place chat logs get despatched. And at last, this methodology begins the sync adapter:

CommonUtils.startBackupService(context)

After which the app begins, with Sign messages getting synced to the archive server within the background.

Conclusion

There’s nonetheless numerous this codebase that I have never absolutely wrapped my thoughts round.

However in any case of this evaluation, it certain appeared to me like TM SGNL is solely submitting plaintext chat logs to the TeleMessage archive server, utterly breaking Sign’s E2EE.

Nonetheless, I’ve solely manually analyzed the supply code. I have never tried operating it, a lot much less establishing a dummy archive server to see what knowledge the app tries to ship (one thing that’s price attempting, however that I have never had almost sufficient time to implement).

However on Saturday night time, a hacker exploited a vulnerability within the archive server and exfiltrated plaintext messages from it, together with Sign messages, as I reported in 404 Media. This confirms my findings that communication between the TM SGNL app and the archive vacation spot just isn’t end-to-end encrypted, and that the archive server has entry to plaintext chat logs.

Corroborative proof from the hack

As Joseph Cox and I reported, a hacker was capable of get hold of “snapshots of information passing by TeleMessage’s servers at a cut-off date.” The snapshots contained fragments of information, together with chat logs in JSON format, that was within the archive server’s reminiscence in the intervening time it was exploited.

To show that the archive server has plaintext entry to talk logs, I am sharing redacted samples of a few of that knowledge right here. Beneath is a plaintext Sign message, a plaintext Telegram message, and a plaintext WhatsApp message.

I am additionally sharing an encrypted WhatsApp message. As I hinted at earlier on this publish, a number of the chat logs have encrypted content material, and I’ve not but uncovered how that works.

The snapshots of information from my supply contained fragments of WeChat messages, however no full WeChat messages, so I haven’t got an instance of these to point out.

Lastly, I am additionally sharing a redacted screenshot of a personal key I discovered within the snapshots of information from the archive server.

A plaintext Sign message

We talked about a Sign message that was current in a kind of snapshots:

A message despatched to a bunch chat referred to as “Upstanding Residents Brigade” included within the hacked knowledge says its “supply kind” is “Sign,” indicating it got here from TeleMessage’s modified model of the messaging app. The message itself was a hyperlink to this tweet posted on Sunday which is a clip of an NBC Meet the Press interview with President Trump about his memecoin. The hacked knowledge consists of cellphone numbers that have been a part of the group chat.

This is the JSON object model of that Sign message. I’ve redacted the cellphone numbers on this JSON object by changing them with ==redacted==.

{
    "typ": "RawMessage",
    "gatewayReceivedDate": 1746387273449,
    "companion": "NONE",
    "securityContent": null,
    "sourceService": null,
    "internalSecurityData": {
        "model": "0.0.2",
        "internalDecryptionData": {
            "typ": "nothing",
            "encryptionType": "DO_NOTHING",
            "params": {}
        }
    },
    "networkType": "SIGNAL",
    "sourceType": "SIGNAL",
    "ownerExtClassId": null,
    "physique": {
        "proprietor": {
            "worth": "==redacted==",
            "kind": "PHONE"
        },
        "messageId": "==redacted==",
        "messageType": "APP_MESSAGE",
        "messageTime": 1746387273000,
        "sender": {
            "worth": "==redacted==",
            "kind": "PHONE"
        },
        "recipients": [
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            }
        ],
        "path": "IN",
        "topic": "Sign message from ==redacted== to talk group Upstanding Residents Brigade",
        "textField": {
            "extractor": {
                "typ": "WrapperExt",
                "knowledge": "https://x.com/degeneratenews/standing/1919109508120371209?s=46"
            },
            "size": 60
        },
        "attachment": [
            {
                "name": "C2-C233F59B-FEC7-4A78-90E0-F29342B15C23.jpg",
                "contentType": "image/jpeg",
                "digest": null,
                "content": ":/shared/cloud/apigateway/attachments/2025_05_04_19_tm_tmp/str6706918773573153616.txt",
                "attachmentSize": {
                    "attachmentSizeType": "BASE64",
                    "sizeInBytes": 72936
                },
                "attachmentDate": null
            }
        ],
        "messageStatus": "NA",
        "callInfo": null,
        "companion": null,
        "groupData": {
            "title": "Upstanding Residents Brigade",
            "id": "",
            "kind": "BROADCAST"
        },
        "threadID": "tm-1441784229",
        "threadName": null,
        "subUserId": 0,
        "participantEnrichments": {},
        "originalMessageData": null,
        "ban": null,
        "acceptedPayloadIdentifier": "8b6ab08e-edef-4d44-a0fe-2be24f66f907",
        "groupName": "Upstanding Residents Brigade",
        "groupId": "",
        "groupMessage": false,
        "textual content": "https://x.com/degeneratenews/standing/1919109508120371209?s=46"
    },
    "kafkafied": true
}

This plaintext chat log comprises the next data, extracted from the archive server:

  • networkType and sourceType are each SIGNAL
  • messageTime is 1746387273000, a Unix timestamp that corresponds to Sunday, Could 4th, 2025, at 3:34pm Japanese time
  • It features a cellphone quantity for the proprietor and sender, together with the 5 recipients within the Sign group
  • The e-mail topic line, Sign message from ==redacted== to talk group Upstanding Residents Brigade
  • groupName of Upstanding Residents Brigade
  • textual content of https://x.com/degeneratenews/standing/1919109508120371209?s=46

Clearly, TeleMessage’s archive server has plaintext entry to Sign messages.

A plaintext Telegram message

In our reporting, we talked about that a number of the knowledge exfiltrated from the archive server seems to belong to Coinbase. This is an instance of a redacted plaintext Telegram message, apparently from a Coinbase worker.

{
    "typ": "RawMessage",
    "gatewayReceivedDate": 1746322842371,
    "companion": "NONE",
    "securityContent": null,
    "sourceService": null,
    "internalSecurityData": {
        "model": "0.0.2",
        "internalDecryptionData": {
            "typ": "nothing",
            "encryptionType": "DO_NOTHING",
            "params": {}
        }
    },
    "networkType": "TELEGRAM",
    "sourceType": "TELEGRAM",
    "ownerExtClassId": null,
    "physique": {
        "proprietor": {
            "worth": "==redacted==",
            "kind": "PHONE"
        },
        "messageId": "EDIT-1746100232_7221627375_0-1",
        "messageType": "APP_MESSAGE",
        "messageTime": 1746114419000,
        "sender": {
            "worth": "UNKNOWN",
            "kind": "ALPHANUMERIC"
        },
        "recipients": [
            {
                "value": "==redacted==",
                "type": "PHONE"
            }
        ],
        "path": "IN",
        "topic": "Telegram message from [Intl Ex] Elk (Falcon Capital) - Coinbase to channel [Intl Ex] Elk (Falcon Capital) - Coinbase",
        "textField": {
            "extractor": {
                "typ": "WrapperExt",
                "knowledge": "Hello @==redacted== please discover the newest report: https://coinbase.sendsafely.com/obtain/?==redacted=="
            },
            "size": 211
        },
        "attachment": null,
        "messageStatus": "NA",
        "callInfo": null,
        "companion": null,
        "groupData": {
            "title": "",
            "id": "",
            "kind": "CHAT"
        },
        "threadID": "815213310",
        "threadName": null,
        "subUserId": 0,
        "participantEnrichments": {
            "{"worth":"UNKNOWN","kind":"ALPHANUMERIC"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            }
        },
        "originalMessageData": null,
        "ban": null,
        "acceptedPayloadIdentifier": "f298b776-d5d4-42da-89b3-95d2d4dd5345",
        "groupName": "",
        "groupId": "",
        "groupMessage": false,
        "textual content": "Hello @==redacted== please discover the newest report: https://coinbase.sendsafely.com/obtain/?==redacted=="
    },
    "kafkafied": true
}

This plaintext chat log comprises the next data, extracted from the archive server:

  • networkType and sourceType are each TELEGRAM
  • messageTime is 1746114419000, a Unix timestamp that corresponds to Could 1, 2025 at 11:46 AM Japanese time (I am truthfully unsure why a message from Could 1 was within the archive server’s reminiscence at this second)
  • It consists of the cellphone variety of the sender underneath proprietor
  • The e-mail topic line, Telegram message from [Intl Ex] Elk (Falcon Capital) - Coinbase to channel [Intl Ex] Elk (Falcon Capital) - Coinbase
  • textual content of Hello @==redacted== please discover the newest report: https://coinbase.sendsafely.com/obtain/?==redacted== (this message included the hyperlink to a shared doc on Coinbases’s SendSafely account, which I’ve redacted)

A plaintext WhatsApp message

I do not know a lot about this WhatsApp message, or the group it was despatched to. However this is a redacted pattern:

{
    "typ": "RawMessage",
    "gatewayReceivedDate": 1746319698418,
    "companion": "NONE",
    "securityContent": null,
    "sourceService": null,
    "internalSecurityData": {
        "model": "0.0.2",
        "internalDecryptionData": {
            "typ": "nothing",
            "encryptionType": "DO_NOTHING",
            "params": {}
        }
    },
    "networkType": "WHATSAPP_CLOUD_ARCHIVER",
    "sourceType": "WHATSAPP_CLOUD_ARCHIVER",
    "ownerExtClassId": null,
    "physique": {
        "proprietor": {
            "worth": "==redacted==",
            "kind": "PHONE"
        },
        "messageId": "544323f6830025dd76a3",
        "messageType": "APP_MESSAGE",
        "messageTime": 1746319698000,
        "sender": {
            "worth": "==redacted==",
            "kind": "PHONE"
        },
        "recipients": [
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            },
            {
                "value": "==redacted==",
                "type": "PHONE"
            }
        ],
        "path": "IN",
        "topic": "WhatsApp message from ==redacted== to Yenta AF chat group",
        "textField": {
            "extractor": {
                "typ": "WrapperExt",
                "knowledge": "And u look so fairly Dani"
            },
            "size": 25
        },
        "attachment": null,
        "messageStatus": "NA",
        "callInfo": null,
        "companion": null,
        "groupData": {
            "title": "Yenta AF",
            "id": "17862479908-1457977547@g.us",
            "kind": "CHAT"
        },
        "threadID": "tm-643721925",
        "threadName": "Yenta AF",
        "subUserId": 0,
        "participantEnrichments": {
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            }
        },
        "originalMessageData": null,
        "ban": null,
        "acceptedPayloadIdentifier": "62db9b07-89c7-46a0-853b-550a9f6c0bb8",
        "groupName": "Yenta AF",
        "groupId": "17862479908-1457977547@g.us",
        "groupMessage": true,
        "textual content": "And u look so fairly Dani"
    },
    "kafkafied": true
}

This plaintext chat log comprises the next data extracted from the archive server:

  • networkType and sourceType are each WHATSAPP_CLOUD_ARCHIVER
  • messageTime is 1746319698000, which is a Unix timestamp that corresponds to Could 3, 2025 at 8:48 PM Japanese time
  • sender‘s cellphone quantity
  • Telephone numbers and first and final names for all 12 members of the WhatsApp group
  • The e-mail topic line, WhatsApp message from ==redacted== to Yenta AF chat group
  • groupName is Yenta AF
  • textual content of And u look so fairly Dani

TeleMessage’s archive server has plaintext entry to a minimum of some WhatsApp messages.

An encrypted WhatsApp message

A number of the knowledge passing by TeleMessage’s archive server is encrypted, and I have never but uncovered precisely why. This is an instance of an encrypted WhatsApp message.

I’ve redacted the plaintext metadata on this pattern. I have never redacted the encrypted session key or the ciphertext of the message.

{
    "typ": "RawMessage",
    "gatewayReceivedDate": 1746322853699,
    "companion": "DEFAULT",
    "securityContent": {
        "encryptionData": {
            "keyId": "08a7fe36a6ddd8bca98ef407e254ecc230ef80b0b39ce1753371c1a8f7427cb1",
            "encSessionKey": "y7BbPA1mpNLkFGjcVRZWTRNbf22lWvjEtqfhzTYlq4V32Jg94jUWzu+krLkJ34ff2AVqJ5gTUhEsh/suPn5ZRjDCzRw3z/XXeW+LftJL0GpHkI4mICzHzTYzMfeEHPmdJRZV8chuAWVL04XfGxEkFjg4ExQKDykhL9KnXxzPre7smyMwEsY3VQ+yx5pVONvCoWLBfKZ6jxDDHAyiw9DhMu97X0WmL5Xb0vkvKxXk6hyyH8oY23pCSiVID4/4waRi5iv7bbbMNhiaJWEyl9lqQ1BmnjqvwO75xYtMCemL/eCd/VEfsA8oxxEnHns6PEga3U/c2JLNGFDQEeU92mXQg8zCPV9qfkYCNYPmGgBcxtnaJxisfkfnVioe50wYxiQ8Ml88xptHa1ZHtajR1Si1puTc8UmueZ2Zem2dUTijXeK07upG+ou+XzhN/uonnpVlPKNd7b8KN/cP5MPwTHTMzpSh5bxQA3zuzqGUgYziUuxQp+VFB1Y3/v7/ba0zlYawmvCC3RN7md9/4bkGfpktii/sm+MsarwVPL2dD7wbzJYvZMo67HQEXW1zhG1hslro9HNJU+e7cgyNTHHdbWd09i5xe9WfMQGi+ty5yrehRDYizC++s7H78WhmMUcQVAjnNaOhJSYqxsLVKVNzdO4NuO70uWTL5x8re/sPKJo8SJO1e64CZlozqyopyycFgaEl"
        },
        "integrityHeaderContent": "7C967D5A3BD5C59284EAB8D2CE56A79DA6012EE6B8BC2037AEE1CD5042C15E01"
    },
    "sourceService": null,
    "internalSecurityData": {
        "model": "0.0.2",
        "internalDecryptionData": {
            "typ": "hybrid",
            "params": {
                "ENC_SESSION_KEY": {
                    "typ": "encskey",
                    "worth": "y7BbPA1mpNLkFGjcVRZWTRNbf22lWvjEtqfhzTYlq4V32Jg94jUWzu+krLkJ34ff2AVqJ5gTUhEsh/suPn5ZRjDCzRw3z/XXeW+LftJL0GpHkI4mICzHzTYzMfeEHPmdJRZV8chuAWVL04XfGxEkFjg4ExQKDykhL9KnXxzPre7smyMwEsY3VQ+yx5pVONvCoWLBfKZ6jxDDHAyiw9DhMu97X0WmL5Xb0vkvKxXk6hyyH8oY23pCSiVID4/4waRi5iv7bbbMNhiaJWEyl9lqQ1BmnjqvwO75xYtMCemL/eCd/VEfsA8oxxEnHns6PEga3U/c2JLNGFDQEeU92mXQg8zCPV9qfkYCNYPmGgBcxtnaJxisfkfnVioe50wYxiQ8Ml88xptHa1ZHtajR1Si1puTc8UmueZ2Zem2dUTijXeK07upG+ou+XzhN/uonnpVlPKNd7b8KN/cP5MPwTHTMzpSh5bxQA3zuzqGUgYziUuxQp+VFB1Y3/v7/ba0zlYawmvCC3RN7md9/4bkGfpktii/sm+MsarwVPL2dD7wbzJYvZMo67HQEXW1zhG1hslro9HNJU+e7cgyNTHHdbWd09i5xe9WfMQGi+ty5yrehRDYizC++s7H78WhmMUcQVAjnNaOhJSYqxsLVKVNzdO4NuO70uWTL5x8re/sPKJo8SJO1e64CZlozqyopyycFgaEl",
                    "kind": "ENC_SESSION_KEY"
                },
                "PUBLIC_KEY_ID": {
                    "typ": "pubkey",
                    "worth": "08a7fe36a6ddd8bca98ef407e254ecc230ef80b0b39ce1753371c1a8f7427cb1",
                    "kind": "PUBLIC_KEY_ID"
                }
            },
            "encryptionType": "HYBRID"
        }
    },
    "networkType": "WHATSAPP_ARCHIVER",
    "sourceType": "WHATSAPP_ARCHIVER",
    "ownerExtClassId": null,
    "physique": {
        "proprietor": {
            "worth": "==redacted==",
            "kind": "PHONE"
        },
        "messageId": "b849fa65750cbed6cf5be5cb9a69d943",
        "messageType": "APP_MESSAGE",
        "messageTime": 1746322850000,
        "sender": {
            "worth": "==redacted==",
            "kind": "PHONE"
        },
        "recipients": [
            {
                "value": "==redacted==",
                "type": "PHONE"
            }
        ],
        "path": "IN",
        "topic": "STATUS - WhatsApp message from ==redacted== to ==redacted==",
        "textField": {
            "extractor": {
                "typ": "WrapperExt",
                "knowledge": "CrlDZrXBN1jL+V7Yht6lDpx4oFHQ05M6kVM7HjecbWY1smMWZkK0yzGxV96TiKlReLanCHtYkxJkF0PR74ePIQ=="
            },
            "size": 88
        },
        "attachment": [
            {
                "name": "eee44412-b56f-47ff-9548-f00fc3ed1cd3.jpg",
                "contentType": "image/jpeg",
                "digest": null,
                "content": ":/shared/cloud/apigateway/attachments/2025_05_04_01_tm_tmp/str13594102991037415607.txt",
                "attachmentSize": {
                    "attachmentSizeType": "BASE64",
                    "sizeInBytes": 44352
                },
                "attachmentDate": null
            }
        ],
        "messageStatus": "NA",
        "callInfo": null,
        "companion": null,
        "groupData": {
            "title": "",
            "id": "standing@broadcast",
            "kind": "CHAT"
        },
        "threadID": "1790853601",
        "threadName": null,
        "subUserId": 0,
        "participantEnrichments": {
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            },
            "{"worth":"==redacted==","kind":"PHONE"}": {
                "firstName": "==redacted==",
                "lastName": "==redacted=="
            }
        },
        "originalMessageData": null,
        "ban": null,
        "acceptedPayloadIdentifier": "b7b88772-f3b7-4e20-8bc8-074ab7cb75f7",
        "groupName": "",
        "groupId": "standing@broadcast",
        "groupMessage": true,
        "textual content": "CrlDZrXBN1jL+V7Yht6lDpx4oFHQ05M6kVM7HjecbWY1smMWZkK0yzGxV96TiKlReLanCHtYkxJkF0PR74ePIQ=="
    },
    "kafkafied": true
}

This encrypted WhatsApp chat log is totally different than the plaintext one in just a few methods:

  • The networkType and sourceType are each WHATSAPP_ARCHIVER, whereas within the plaintext WhatsApp message they have been WHATSAPP_CLOUD_ARCHIVER
  • The encrypted message comprises encryption data in securityContent and internalSecurityData, whereas within the plaintext WhatsApp message they did not
  • textual content is a Base64 block of encrypted textual content as a substitute of plaintext message content material

Even with encryption, this chat log comprises the next plaintext data:

  • The sender and recipients cellphone numbers and full names
  • The e-mail topic line, STATUS - WhatsApp message from ==redacted== to ==redacted==
  • messageTime is 1746322850000, a Unix timestamp that corresponds to Could 3, 2025 at 9:40 PM Japanese time

Personal key materials

As Joseph and I discussed in our reporting in 404 Media, the snapshots of information from the archive server contained extra than simply chat logs. There have been usernames and plaintext passwords – the hacker used these to login to archive web site, having access to an inventory of apparently 747 Customs and Border Safety workers.

But in addition, I discovered non-public key materials within the snapshots, although I’ve not but uncovered what the keys are for. This is one of many non-public keys I discovered, redacted, in fact:

A redacted non-public key

Should you discovered this fascinating, subscribe to get these posts emailed on to your inbox. If you wish to help my work, contemplating changing into a paid supporter.