├── .gitignore ├── LICENSE ├── PeopleNearbyActivity.patch ├── README.md ├── ingest.py ├── requirements.txt └── server ├── index.html └── server.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | venv 3 | data 4 | *.sqlite 5 | *.java 6 | *.sql -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Maximilian Jugl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /PeopleNearbyActivity.patch: -------------------------------------------------------------------------------- 1 | Index: TMessagesProj/src/main/java/org/telegram/ui/PeopleNearbyActivity.java 2 | IDEA additional info: 3 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 4 | <+>UTF-8 5 | =================================================================== 6 | --- TMessagesProj/src/main/java/org/telegram/ui/PeopleNearbyActivity.java (revision fed0c139e7f9bbcb5b082a2dc7ca9151b447e45c) 7 | +++ TMessagesProj/src/main/java/org/telegram/ui/PeopleNearbyActivity.java (date 1612273461777) 8 | @@ -37,6 +37,7 @@ 9 | import org.telegram.messenger.NotificationCenter; 10 | import org.telegram.messenger.R; 11 | import org.telegram.messenger.UserConfig; 12 | +import org.telegram.messenger.UserObject; 13 | import org.telegram.tgnet.TLRPC; 14 | import org.telegram.ui.ActionBar.ActionBar; 15 | import org.telegram.ui.ActionBar.AlertDialog; 16 | @@ -55,6 +56,7 @@ 17 | import org.telegram.ui.Components.UndoView; 18 | 19 | import java.util.ArrayList; 20 | +import java.util.Locale; 21 | 22 | import androidx.recyclerview.widget.DefaultItemAnimator; 23 | import androidx.recyclerview.widget.LinearLayoutManager; 24 | @@ -496,6 +498,7 @@ 25 | firstLoaded = true; 26 | } 27 | Location location = getLocationController().getLastKnownLocation(); 28 | + final Location locRef = location; 29 | if (location == null) { 30 | return; 31 | } 32 | @@ -529,6 +532,10 @@ 33 | req.self_expires = share == 1 ? 0x7fffffff : 0; 34 | } 35 | reqId = getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { 36 | + FileLog.d(String.format(Locale.ENGLISH, "location update [lat=%f, lng=%f, hasAcc=%b, acc=%f, hasAlt=%b, alt=%f, fixTs=%d, currentTs=%d, cmTs=%d]", 37 | + locRef.getLatitude(), locRef.getLongitude(), locRef.hasAccuracy(), locRef.getAccuracy(), locRef.hasAltitude(), locRef.getAltitude(), locRef.getTime(), 38 | + System.currentTimeMillis(), getConnectionsManager().getCurrentTime())); 39 | + 40 | reqId = 0; 41 | if (showProgressRunnable != null) { 42 | AndroidUtilities.cancelRunOnUIThread(showProgressRunnable); 43 | @@ -542,6 +549,8 @@ 44 | saveConfig = true; 45 | updateRows(true); 46 | } 47 | + 48 | + FileLog.d("peer update list start"); 49 | if (response != null && share != 2) { 50 | TLRPC.Updates updates = (TLRPC.TL_updates) response; 51 | getMessagesController().putUsers(updates.users, false); 52 | @@ -563,6 +572,13 @@ 53 | TLRPC.TL_peerLocated peerLocated = (TLRPC.TL_peerLocated) object; 54 | if (peerLocated.peer instanceof TLRPC.TL_peerUser) { 55 | users.add(peerLocated); 56 | + TLRPC.User user = getMessagesController().getUser(peerLocated.peer.user_id); 57 | + FileLog.d(String.format(Locale.ENGLISH, 58 | + "peer update [distance=%d, id=%d, displayName=\"%s\", expires=%d]", 59 | + peerLocated.distance, 60 | + peerLocated.peer.user_id, 61 | + UserObject.getUserName(user), 62 | + peerLocated.expires)); 63 | } else { 64 | chats.add(peerLocated); 65 | } 66 | @@ -585,6 +601,7 @@ 67 | checkForExpiredLocations(true); 68 | updateRows(true); 69 | } 70 | + FileLog.d("peer update list end"); 71 | if (saveConfig) { 72 | userConfig.saveConfig(false); 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tg-nearby 2 | 3 | Trilateration of nearby Telegram users as described in my corresponding [article](https://owlspace.xyz/cybersec/tg-nearby). 4 | 5 | ## Setup 6 | 7 | If you want to toy with the code in this repository and collect some location data with Telegram yourself, please keep in mind that there is a high likelihood that something will break when the Telegram Android app updates. Familiarize yourself with the following steps, make sure you know what you're doing and be prepared to troubleshoot problems when they arise. 8 | 9 | 1. Clone the Telegram Android app source code from the [official repository](https://github.com/DrKLO/Telegram) and follow the outlined steps to creating your own development build. 10 | 2. Apply the [PeopleNearbyActivity patch](PeopleNearbyActivity.patch) to the Java class of the same name. 11 | 3. Install all Python dependencies using `python -m pip install -r requirements.txt`. It is recommended to create a separate virtual environment in the root of this repository first. 12 | 13 | ## Usage 14 | 15 | **Note:** You will need a working Telegram account. When navigating to the "People Nearby" menu entry on your dev build, Telegram will ask you to share your location. This is necessary to be able to get a list of nearby users. This won't automatically show your presence to other users in proximity. 16 | 17 | 1. Install the patched Telegram Android app onto an Android device. Navigate to the "People Nearby" menu entry in the app's sidebar. 18 | 2. Keep the "People Nearby" screen open and active for as long as you wish to collect data. Walk around to achieve better location estimates. 19 | 3. Copy the corresponding log files from your device. They will be located in a directory called `logs` in the app's data directory, either on external or internal storage. You will need to copy the files either through adb or with Android Studio's integrated device file explorer. 20 | 4. Feed the logs into a SQLite database. Navigate to the root of this repository and execute `python ingest.py my_database.sqlite logs/my_awesome_log.txt`. You can append multiple logs to a single database by simply changing the log file path argument. 21 | 5. Run the web interface. Navigate to the `server` directory of this repository and execute `python server.py my_database.sqlite`. 22 | -------------------------------------------------------------------------------- /ingest.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | import sqlite3 3 | 4 | def explode_line(line): 5 | res = {} 6 | 7 | line = line.strip() 8 | line = line[line.index("[") + 1:line.index("]")] 9 | 10 | for seg in line.split(", "): 11 | kv = seg.split("=") 12 | k = kv[0] # key 13 | v = kv[1] # value 14 | 15 | if v[0] == "\"" and v[-1] == "\"": # remove quotes from strings 16 | v = v[1:-1] 17 | 18 | res[k] = v 19 | 20 | return res 21 | 22 | def main(out_path, in_path): 23 | if not os.path.exists(in_path): 24 | print("path \"{}\" does not exist".format(in_path)) 25 | return 26 | 27 | loc_tuples = [] 28 | user_tuples = [] 29 | sight_tuples = [] 30 | 31 | with open(in_path, "r", encoding="utf-8") as f: 32 | last_loc = None 33 | 34 | for line in f: 35 | if "location update" in line: 36 | l = explode_line(line) 37 | 38 | # in case the gps fix hasn't updated 39 | if last_loc is not None and l["fixTs"] == last_loc["fixTs"]: 40 | continue 41 | 42 | last_loc = l 43 | 44 | loc_tuples.append(( 45 | int(l["fixTs"]), # gps fix ts 46 | int(l["currentTs"]), # log ts 47 | int(l["cmTs"]), # cm ts 48 | float(l["lat"]), # latitude 49 | float(l["lng"]), # longitude 50 | float(l["alt"]) if bool(l["hasAlt"]) else None, # altitude 51 | float(l["acc"]) if bool(l["hasAcc"]) else None # hoz accuracy 52 | )) 53 | 54 | # skip until list start (should only be one line but still) 55 | while not "peer update list start" in line: 56 | line = next(f) 57 | 58 | # get first peer update line 59 | line = next(f) 60 | 61 | # iterate over list 62 | while not "peer update list end" in line: 63 | # hotfix 64 | if not "peer update" in line: 65 | line = next(f) 66 | continue 67 | 68 | p = explode_line(line) 69 | user_tuples.append(( 70 | int(p["id"]), # user id 71 | p["displayName"] # display name 72 | )) 73 | sight_tuples.append(( 74 | int(p["id"]), # user id 75 | int(last_loc["fixTs"]), # ts of original gps fix 76 | int(p["distance"]), # distance 77 | int(p["expires"]) # expire ts 78 | )) 79 | line = next(f) 80 | 81 | con = sqlite3.connect(out_path) 82 | 83 | con.executescript(""" 84 | CREATE TABLE IF NOT EXISTS locations ( 85 | fix_ts INTEGER, 86 | log_ts INTEGER NOT NULL, 87 | cm_ts INTEGER NOT NULL, 88 | latitude REAL NOT NULL, 89 | longitude REAL NOT NULL, 90 | altitude REAL, 91 | accuracy REAL, 92 | PRIMARY KEY (fix_ts) 93 | ); 94 | 95 | CREATE TABLE IF NOT EXISTS users ( 96 | id INTEGER, 97 | display_name TEXT NOT NULL, 98 | PRIMARY KEY (id) 99 | ); 100 | 101 | CREATE TABLE IF NOT EXISTS sightings ( 102 | id INTEGER, 103 | user_id INTEGER, 104 | fix_ts INTEGER, 105 | distance INTEGER NOT NULL, 106 | expire_ts INTEGER NOT NULL, 107 | PRIMARY KEY (id AUTOINCREMENT), 108 | FOREIGN KEY (user_id) REFERENCES users(id), 109 | FOREIGN KEY (fix_ts) REFERENCES locations(fix_ts) 110 | ); 111 | """) 112 | 113 | # there may be two updates using the same gps fix, so ignore on conflict 114 | con.executemany(""" 115 | INSERT OR IGNORE INTO locations (fix_ts, log_ts, cm_ts, latitude, longitude, altitude, accuracy) 116 | VALUES (?, ?, ?, ?, ?, ?, ?) 117 | """, loc_tuples) 118 | 119 | # users may be sighted multiple times, so ignore on conflict 120 | con.executemany("INSERT OR IGNORE INTO users (id, display_name) VALUES (?, ?)", user_tuples) 121 | con.executemany("INSERT INTO sightings (user_id, fix_ts, distance, expire_ts) VALUES (?, ?, ?, ?)", sight_tuples) 122 | con.commit() 123 | 124 | con.close() 125 | 126 | if __name__ == "__main__": 127 | if len(sys.argv) < 3: 128 | print("usage: {} outfile infile".format(sys.argv[0])) 129 | exit() 130 | 131 | main(sys.argv[1], sys.argv[2]) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | scipy 2 | geopy -------------------------------------------------------------------------------- /server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |