There are many channels on Telegram that many people call “anonymous”. Their authors like to divide their own accounts into “private” and “work” accounts, customize their privacy to the maximum, and follow the best practices of OSINT countermeasures.
Finding out the identity of the author of such a Telegram channel sometimes becomes difficult. By using a separate account, he leaves no trace behind him, and sometimes he doesn't even use it: many like to specify other sources of communication, like a separate email.
Over time, the channels of these anonymous people become popular, and the authors think about a beautiful design for their brainchild. Telegram provides a lot of creative opportunities to show style: your own background, stickers and emoji.
Authors of Telegram channels order branded stickers from designers and use the @Stickers bot to “register” them in the messenger.
Many use their personal accounts for this purpose without thinking that this becomes their Achilles heel, which can lead to their complete de-anonymization.
In this article we will write our own module for Maltego, which will be able to reveal the identity of the author of an anonymous Telegram channel in two clicks. We will also prove it in practice!
In Telegram, each sticker pack has its own ID, which can be obtained using the API. For example, let's take the stickers https://t.me/addstickers/HotCherry, here is their visual representation (that is, what the user sees), and what the API sends.
The key field here is the ID field, which at first glance seems to be a unique identifier of the stickerpack and does not indicate its author in any way. However, if you look deeper, it is not so.
There is an operation on a number called binary shift to the right. Its essence is to shift all the bits of the number to the right by a certain number of digits, and fill the free space on the left with zeros.
Let's perform a binary shift to the right by 32, using the example of the number returned by the API.
The number 1391391008142393345
can be represented in binary form, it will be the digit with 0
written in red against it. And then start moving this number to the right, adding zeros to the left - and at the end you will get the digit opposite to which is written 32
.
If you translate it into a regular binary value, you will get a shorter and more recognizable identifier 323958464
. As we managed to find out, this is not the identifier of a stickerpack, but the identifier of a specific Telegram user.
The user with this identifier in Telegram is the creator of the stickerpack.
I showed you the process of converting from a stickerpack to the profile ID of its owner:
But there is a problem: from the numeric ID we get, we can't directly find out what kind of user this user is: what is his username, avatar and so on, because there is simply no such method in the API. So how did I get the user @owlhug
, the creator of the Hot Cheery stickerpack?
There is a reverse API method that allows you to get a user's profile and ID by username. You have it called every time you open the link [t.me/<login](http://t.me/<login) user>
or search by username in Telegram.
You can use this legitimate method, scan all Telegram users (for example, by taking them from all public chats), and map ID<>username
. Fortunately, I didn't have to do this myself, someone-else did the job for us.
There is a bot that has scanned a lot of users, got all the IDs for them, and now gives free search by them to everyone. This bot's name is tgdb_bot
.
So the whole floo looks like this:
tgdb_bot
.username
as output
And so with every stickerpack that was published in the target channel. If it is a stickerpack branded for a particular channel, there is a chance that it was created by the channel administrator from his personal account.
Manually doing it, of course, is very time-consuming and not convenient.
So we will train Maltego to do everything automatically, let's get started!
This chapter is about installing and configuring everything you need to get up and running.
Here everything is very simple - you need to download from the official sites:
And install them on your computer by running the appropriate installers.
Maltego is an open source investigative program, primarily a paid program. But it has a Community Edition, - a slightly reduced version, which is quite enough for small tasks. Its serious limitation is no more than 12 results per conversion, but it won't be a hindrance for this article.
To use Maltego CE, you need to download Maltego from the official site to your computer, run it and pass a simple registration. To do this, click the corresponding button under the Maltego CE (Free) bar or simply follow this link.
The program will send us to a website where we will have to fill in a couple of fields.
Once we pass the registration, we go back to the app, click Login and accept the user agreement.
Log in and go through all the other steps, leaving all the default settings.
Final Ninth Paragraph.
Finally, everything is ready to go.
In my opinion, Maltego has a pretty simple and intuitive interface that is easy to understand. However, you may have questions, so let me explain the basics.
Here are the main features we will be using throughout this article:
These three complementary points constitute the fundamental pillars of Maltego.
It's time to do some development: create Entities, Transforms, and write code.
There are many different entities in Maltego - bitcoin addresses, organizations, IP addresses, and more can be added to the graph.
However, it doesn't have entities by default, which would allow us to fully work with Telegram itself.
So we'll have to create them ourselves, we'll create three entities:
In order to start an entity, you need to choose Entities → Manage Entities in the top menu.
This opens a window with many preset entities. From Entity Manager we can import new entities, export existing ones, or create our own entities.
Click on New Entity and get into the mode of creating a new entity.
Let's fill in the fields:
It should look like this.
The second step is the step of populating the main property or caption that will be displayed below our entity. Besides the main property, there are additional properties that we can create for our transformations.
But right now we will only fill in the main one, let's describe it:
string
, because short text will be written here as a value.onetimeusername
, this is the username of Pavel Durov in Telegram. All new entities will appear with a pre-filled property from this value, it can also be left empty.
Once an entity is created, it will appear in Entity Manager. You can find it by keyword. There is also one preset here that relates to Telegram, but it is completely meaningless, so we don't use it.
Now the entity we just created can be dragged and dropped onto the graph. As you can see, it inherits the icon we chose when we created it and the default value. The other properties of the entity are not shown in the graph.
An entity is a convention with which we have represented a real Telegram user on the graph. In our example, this is the profile of the user onetimeusername, which in reality looks like this.
Following the image of the first entity, let's create the remaining two entities as well. This will be the Sticker Set entity, which in Telegram can be displayed like this.
Each Sticker Set has its own name and unique username, by which you can add it to your sticker collection and use it later. Click Create Entity and fill in all the fields in the same way as we did earlier with the profile.
The last in line is a Telegram channel, in the messenger itself it can be displayed like this.
But, again, let me clarify: entities in Maltego are templates that can be used to represent any channel, user or sticker set by filling in the appropriate fields when we add them to the graph.
Let's customize the last entity and its properties.
Finally, all entities are ready for further development.
We can already place them on the graph, but that's not enough. It's time to bring them back to life.
Now, finally, let's start programming. Don't worry, it will take very little time to write the minimal logic and your Transform. I'll be using Visual Studio Code to write the code, but you can use any other editor you like.
Maltego has a library for Python called Maltego TRX - you can't write your Transform without it, you need it to integrate with the main Maltego client. Use the command line to install the library.
pip install maltego-trx
Now you can create a project for work, to do this you need to type in the command line
maltego-trx start new_project
where new_project
is the name of the project, for me it is maltego-telegram
.
A folder with a new project and pre-prepared files will be created.
Let's analyze each of them in order:
Now open the folder in VSCodium and start writing the code itself. Since we are going to extract data from Telegram, we need an appropriate library to interact with its MTProto API. The Pyrogram library will work for us.
You can install it using the command
pip install pyrogram
Now you need to log in using this library. Beforehand, you need to go to https://core.telegram.org/api/obtaining_api_id and, following the instructions, get api_id
and api_hash
values for authorization under your account.
Once the tokens have been obtained, write the following code in a separate login.py
file.
All it does is log in under your account and write you messages in your saved messages that everything is working. Our Maltego module uses Telegram MTProto API, so we can't do without authorization.
Now run the script using the command
You will need to enter:
Once done, this will appear in your saved messages
And a session file is created. This is crucial, because Maltego does not provide an input option for Local Transforms, and we would be stuck at the point of logging in. Authorization only takes place the first time, and each subsequent time the existing session from the my_account.session
file will be used.
In the settings.py
file we remove the standard content (since it is useless in the case of Local Transforms), and replace it with the Config class, which will load the secrets needed for the work: these are already familiar api_id
and api_hash
, as well as bot_token
, which will be needed in the course of work.
The secrets themselves are now loaded from the config.ini
file. So before you can use the module, you have to fill this file and execute login.py
- otherwise there is no other way.
Time to move on to the Transforms folder to write the basic logic. We need to create a file that has a telling name and uses the PascalCase
notation - I've reviewed many modules and almost every one uses it.
This is because all Python classes are capitalized, and the name of the file must match the name of the main class that will be inherited from DiscoverableTransform
, otherwise Maltego won't recognize it.
So, our first file is ChannelToStickerSet.py
, and it will be responsible for dumping all the stickers from the Telegram channel specified by the user, as well as building a further graph.
Let's consider lines 19-30. As I wrote above, it is mandatory to specify a class that matches the file name. And also register the @registry.register_transform
decorator with the mandatory parameters:
display_name
is the label that characterizes Transform. It appears when you right-click on an Entity and want to select a further action. With us, it will characterize the transformation into a sticker set.input_entity
is the entity that is taken as input. Transform will apply to itdescription
- a short description
I have also specified an optional output_entities
parameter that will be displayed in the Transforms editing interface, in Maltego, I have specified it purely for convenience.
The first thing we do, after registering all the formalities, is to get channel
, the property that specifies the username of the channel.
username = request.getProperty("properties.channel")
.
Next, I call the asynchronous fetch_stickers
function, which does all the basic work of finding stickers. This is done by reading all the messages in the channel and filtering exclusively those that fall under the “sticker” criteria.
async def fetch_stickers(username):
sticker_sets = []
async with app:
async for message in app.get_chat_history(username):
if message.sticker is not None and message.sticker not in sticker_sets:
sticker_sets.append(message.sticker)
return sticker_sets
The final step is to traverse all the collected stickerpacks and call the addEntity function, which creates the entity.
stickerset_entity = response.addEntity("interlinked.telegram.StickerSet", value=sticker_set.set_name)
The parameters passed to it are:
interlinked.telegram.StickerSet
- UniqueName of the entity, which we specified when creating it as soon as we familiarized with Maltegovalue
- the value itself, which will be written to the Main Property of the entity.
This completes the writing of the foot Transform. It will be used to build a graph with many links from the channel to the stickers that were published in it.
Let's move on to the second file, this is StickerSetToOwner.py
, which will be responsible for converting from the stickerpacks we got in the previous step to those who registered them in Telegram, using the @Stickers
bot.
The contents of this file are as follows:
The big fetch_sticker_set_owner
function is the one that does most of the work. It takes the name of the stickerpack as input and calculates who created it.
Let's understand step by step how it works:
GetStickerSet
method is called, which returns information about the stickerpack, including its unique identifier, a long positive integer. sticker_set = await app.invoke(
functions.messages.GetStickerSet(
stickerset=types.InputStickerSetShortName(
short_name=short_name
),
hash=0
),
)
owner_id = sticker_set.set.id >> 32
if inline_results := inline_search.results:
message_text = inline_results[0].send_message.message
username_match = re.search(r'(@\\S+)', message_text)
if username_match:
username = username_match[0]
with contextlib.suppress(Exception):
owner = await app.get_users(username)
return owner
But the bot doesn't always find the user, so there is a Fallback with ID return.
At the end, the usual logic with the creation of an entity is executed, this time it is UserProfile
, which we created earlier, the main property of which records the obtained username
of the user.
stickerset_owner_entity = response.addEntity(
"interlinked.telegram.UserProfile",
value=owner.username
)
The final touch is to add the written Transforms to Maltego.
In project.py
you need to call the write_local_mtz
function, which will create a special file with the extension .mtz
for importing into Maltego. This file will contain metadata information about all Transforms, the path to the files to be called (ChannelToStickerSet.py
and StickerSetToOwner.py
), and the path to the interpreter.
In project.py
, to do this, call the write_local_mtz
function, which will create a special file with the .mtz
extension for importing into Malego. This file will contain metadata information about all Transforms, the path to the files to be called (ChannelToStickerSet.py
and StickerSetToOwner.py
), and the path to the interpreter.
Next, we do
python project.py
And we see the final file telegram.mtz
appear in the folder.
Everything is ready to import this into Maltego and check the results. To do this, go back to the program and select Import → Import Config from the top menu. The program will prompt you to upload a file - choose the one that appeared in the previous step.
Maltego will display the Transforms that will be imported into the program. This is done in case you write new Transforms and the program does not import the ones you wrote earlier. Select all of them from the list and click “Next”.
Finish.
Transforms are imported into the program and now we can check how it will work. To do this, add a Telegram channel entity to our graph from the menu on the left.
Double-click on the entity and change the username. I have entered here the username of my channel, which was specially created for testing this module.
The contents of this channel itself are three stickerpacks from different sets.
Now, if you click on the created entity, you will see a Transform that wasn't there before - this is the result of our work.
Click it. We wait for the script to run and see three new stickerpacks that were previously published in the channel appear before our eyes.
Now select any stickerpack, click on PCM and “To Sticker Set Owner”.
Magic happened before our eyes! We got all the stickerpacks ever published and were able to recognize their owners in just a couple of clicks.
All stickerpack owners are known, but the only thing missing is beauty. In order to clearly understand what kind of stickerpacks they are, we would like to display their previews, as well as avatars of their creators. This will require some small improvements, which we will do now.
We refine already existing mechanisms - we learn to add new properties and fill them, so that in the end it turned out beautifully, and was not ashamed to share graphs.
Go to the familiar Entity Manager and search for the keyword “Telegram” so that we have all the entities at our fingertips. We will have to edit all 3 entities and add new properties to them (such as previews and titles), so we'll have to do a bit of work.
Open the first entity - Telegram User. And add an ID property for it. This is necessary in case we fail to convert the user ID into username, but we still need to save its ID (in case we manage to run it through other sources manually?).
Add also properties:
$trim($property($properties.first_name) $property($properties.last_name))
function concatenates two strings, first name and last name, and removes the extra spaces on the sides.
It is also worth mentioning that Maltego has two types of variables for images:
I would prefer the second type with local images, but it didn't work correctly for me (when I tried to specify an image for an entity, it didn't save), so we use URL. This carries some security risks, which I will discuss below.
After you've filled everything in, you should get something like what's on the screenshot:
Now switch to the Display Settings tab. Here you can customize the properties that the entity will display. We are only interested in two of them:
We set up the Telegram channel in the same way: it has a username (which we already set up when we first created the entity), as well as a logo (which here is called Photo), and the name of the channel itself.
And Sticker Set. In Telegram terminology, Thumbnail should be stored as an image.
That's the end of the property customization, it's time to add some minor updates to the code.
I created a new file utils.py
and wrote in it a wrapper over functions to handle HTTP requests from the Requests library. The difference between it and the usual one is that it can execute different types of requests, and retry them if the connection failed the first time for some reason.
Getting previews for stickers is done in several steps:
The getStickerSet
method is called to get information about the sticker. The response will contain a file_id
that points to the preview image. This is typically the optimized image of the first sticker in the set.
Next, we need to get the file_path
to generate the final link. This is done by querying the getFile
method and specifying the file_id
from the first clause as a parameter of the same name.
When the file_path
is obtained, the preview finally downloads it via a direct link.
I have formalized all this floo as a separate class StickerSetPhotoFetcher
, which uses the same bot_token
from the config. But why do we need it when we already use MTProto API with Telegram library?
As I mentioned above, even if you download an image using Pyrogram, for some reason it is not imported correctly into Telegram. This may be a problem with my PC or the Community version of Maltego.
Since there was no solution with a static file, the only way to directly link to it is through the usual API, using raw HTTP requests, which is what we use.
The risk to consider here is that Maltego stores a direct link to the file, with your bot's token. So start separate bots that are not linked in any way to real projects, and share projects with caution.
The final step is to call the function function and pass the name of the stickerpack to it. Once the image is received, it can be written to the properties.thumbnail
property
stickerset_entity.addProperty("properties.thumbnail", value=thumbnail)
We do the same with the name, it was already returned to us by the regular API when we got set_name
from the sticker set.
stickerset_entity.addProperty("properties.title", value=sticker_set.title)
In the end, it should look like this:
In the case when we get a Telegram user and the StickerSetToOwner.py
file, everything is much simpler - it is enough to take a photo from the t.me/<username>
page and save a link to it. The username is taken from the same place.
Now that everything is ready, it's time to go back to Matego and see how it works. This will be the smallest subchapter in this epic article, so without further ado:
It works perfectly!
At the beginning of the article I claimed that this method can be used to de-anonymize the owner of a Telegram channel, but I did not provide evidence - it's time to correct myself.
For example, I took a random channel.
It is called “Comrade Z major” and has almost 17 thousand subscribers. Initially, the posts in it were published ostensibly on behalf of a “comrade major” from the FSB with short text posts, random photos of people in epaulets and memes with Dzerzhinsky.
In 2019, it actively covered the protests, publishing puns and justifying violence by security forces, and has now turned into a news channel with Kremlin-inspired narratives and pro-war posts (which we will return to later).
The contacts include a channel with three subscribers @silovikicat
without any information, as well as the mail mayorfsb.protonmail.com
, about which there is no information in public sources. So it's time to try the Maltego way of searching.
Your personal FSB handler. The most scandalous telegram channel. For support questions contact @silovikicat or email [email protected].
Add the channel to the program and search for stickerpacks and their creators:
Among the stickerpacks of Pepe Frog, Slutsky and Taxi Ultima, which do not stand out in any remarkable way. However, there is one special one that bears the same name as the channel itself, MayorFSB, with the exact same nickname.
The contents of the stickerpack are photos of the enforcers with “branded quotes,” with the last sticker in the set pointing to the @MAYORFSB
channel itself, the contents of which we just checked out above.
Inscriptions on stickers, from left to right:
@MAYORFSB
Maltego shows that the creator of the stickerpack is a certain “Mikhail K” with the username kapyshonchik. If you open his profile in Telegram, you can also see the characteristic description “I oppress soy, promote imperialism, do colonization. Expensive.”
It's easily found through a Google search. Here, for example, is the Facebook profile of “Mikhail Kapyushonov”, which has exactly the same username https://www.facebook.com/kapyshonchik/.
We also managed to find a LinkedIn page where he already has his real name - Mikhail Grachev. The avatar matches one of those published on Facebook.'
In addition, his profile in printdirect.ru, a website where you can create a T-shirt with your own design, was also found. The user with the nickname kapyshonchik created a single T-shirt - and it has an artwork and a quote by Dzerzhinsky.
On Instagram, Mikhail publishes a photo against the background of the FSB building on Lubyanka, as well as a facade sign of the FSB, this time from St. Petersburg. I am sure these are not the only photos on his phone.
At the stage of analyzing the channel “Comrade Z Major”, I also noticed that he actively promotes the Russian battalion “Espanola”. There are more than 20 references in the channel and all in a positive way.
The core of the “Espanyola” personnel is made up of soccer ultras. Many members of this battalion fought in Ukraine
on the side of Russia, some of them have neo-Nazi views.
I thought that these reposts were no coincidence, so I decided to check Espanyola's Telegram channel, it is not anonymous and it has contacts for communication. It doesn't publish stickers, but at the end of each post you can see the captions generated with branded emoji.
An emoji search led to a faceless profile under the nickname izvekovaelvira
The same nickname can be found on Telegram, and it's a woman in a relationship with Dzerzhinsky fan, Mikhail Grachev.
So, I did a little research and found out that kapyshonchik
:
There is no direct evidence that Mikhail Grachev is the administrator of the MayorFSB channel, but there are many indirect factors that indicate that he is associated with it.
This is where our article comes to an end. I wrote in detail about how to write modules for Maltego on my example with the search for stickerpack owners, and also did my little investigation with the search for the administrator of the pro-Russian channel “Comrade Z Major”.
I hope this article was useful to you! I would appreciate your feedback.
Source code: https://github.com/vognik/maltego-telegram