From 04cbbb01f6b493bb1c183a5f05182c34d07e4a0d Mon Sep 17 00:00:00 2001 From: LuK1337 Date: Wed, 13 Nov 2024 17:14:35 +0100 Subject: [PATCH] Add !gota command Kanged from https://github.com/tangalbert919/google-ota-prober --- .gitignore | 1 + Dockerfile | 2 +- cogs/gota.py | 108 +++++++++++++++++++++++ main.py | 10 ++- proto/android_checkin.proto | 96 +++++++++++++++++++++ proto/checkin-generator.proto | 152 +++++++++++++++++++++++++++++++++ proto/checkin.proto | 155 ++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 8 files changed, 523 insertions(+), 2 deletions(-) create mode 100644 cogs/gota.py create mode 100644 proto/android_checkin.proto create mode 100644 proto/checkin-generator.proto create mode 100644 proto/checkin.proto diff --git a/.gitignore b/.gitignore index 274844c..711e75b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ __pycache__/ .envrc praw.ini +proto/*_pb2.py .direnv/ diff --git a/Dockerfile b/Dockerfile index 55af21d..b03e443 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ RUN GO111MODULE=on go install github.com/DarthSim/overmind/v2@latest FROM python:3.11-alpine COPY --from=overmind /go/bin/overmind /usr/local/bin/overmind -RUN apk add --no-cache curl build-base tmux redis git +RUN apk add --no-cache curl build-base tmux redis git protoc RUN curl -L https://github.com/oliver006/redis_exporter/releases/download/v1.31.4/redis_exporter-v1.31.4.linux-amd64.tar.gz -o exporter.tgz \ && tar xvzf exporter.tgz \ && cp redis_exporter-*/redis_exporter /usr/local/bin/redis_exporter \ diff --git a/cogs/gota.py b/cogs/gota.py new file mode 100644 index 0000000..37c19a0 --- /dev/null +++ b/cogs/gota.py @@ -0,0 +1,108 @@ +import asyncio +import binascii +import gzip +import io +import os +import random + +import discord +import requests +from discord.ext import commands + +from proto import checkin_generator_pb2 + + +class GoogleOTA(commands.Cog): + HEADERS = { + 'accept-encoding': 'gzip, deflate', + 'content-encoding': 'gzip', + 'content-type': 'application/x-protobuffer', + } + + @commands.Cog.listener() + async def on_ready(self): + print(f"Loaded {__name__}") + + @commands.command(hidden=True) + async def gota(self, ctx, fingerprint): + checkin_request = checkin_generator_pb2.AndroidCheckinRequest() + checkin_request.imei = self.generate_imei() + checkin_request.id = 0 + checkin_request.digest = self.generate_digest() + checkin_request.checkin.build.id = fingerprint + checkin_request.checkin.build.timestamp = 0 + checkin_request.checkin.build.device = fingerprint.split('/')[2].split(':')[0] + checkin_request.checkin.lastCheckinMsec = 0 + checkin_request.checkin.roaming = "WIFI::" + checkin_request.checkin.userNumber = 0 + checkin_request.checkin.deviceType = 2 + checkin_request.checkin.voiceCapable = False + checkin_request.checkin.unknown19 = "WIFI" + checkin_request.locale = 'en-US' + checkin_request.macAddr.append(self.generate_mac()) + checkin_request.timeZone = 'America/New_York' + checkin_request.version = 3 + checkin_request.serialNumber = self.generate_serial() + checkin_request.macAddrType.append('wifi') + checkin_request.fragment = 0 + checkin_request.userSerialNumber = 0 + checkin_request.fetchSystemUpdates = 1 + checkin_request.unknown30 = 0 + + data = io.BytesIO() + + with gzip.GzipFile(fileobj=data, mode='wb') as f: + f.write(checkin_request.SerializeToString()) + + resp = requests.post('https://android.googleapis.com/checkin', + data=data.getvalue(), + headers=self.HEADERS) + + checkin_response = checkin_generator_pb2.AndroidCheckinResponse() + checkin_response.ParseFromString(resp.content) + + update_title = None + update_url = None + + for entry in checkin_response.setting: + if entry.name == b'update_title': + update_title = entry.value.decode() + elif entry.name == b'update_url': + update_url = entry.value.decode() + + if update_title and update_url: + embed = discord.Embed(title=update_title) + embed.add_field(name="URL", value=update_url, inline=False) + await self.reply_and_delete(ctx, content=None, embed=embed) + else: + await self.reply_and_delete(ctx, content='Not found :(') + + @staticmethod + def generate_imei(): + imei = [random.randint(0, 9) for _ in range(15)] + return ''.join(map(str, imei)) + + @staticmethod + def generate_mac(): + return binascii.b2a_hex(os.urandom(6)) + + @staticmethod + def generate_serial(): + serial = [random.choice('0123456789abcdef') for _ in range(8)] + return ''.join(serial) + + @staticmethod + def generate_digest(): + digest = [random.choice('0123456789abcdef') for _ in range(40)] + return '1-' + ''.join(digest) + + @staticmethod + async def reply_and_delete(ctx, *args, **kwargs): + message = await ctx.reply(*args, **kwargs) + await asyncio.sleep(60) + await message.delete() + await ctx.message.delete() + + +async def setup(bot): + await bot.add_cog(GoogleOTA(bot)) diff --git a/main.py b/main.py index 1028ca9..9b79490 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,17 @@ -import asyncio import os +import subprocess +from glob import glob import discord from discord.ext import commands +dir_path = os.path.dirname(os.path.realpath(__file__)) + +for name in glob(f"{dir_path}/proto/*.proto"): + subprocess.run( + ["protoc", "--proto_path=./proto", "--python_out=./proto", os.path.basename(name)], + cwd=dir_path) + class Bot(commands.Bot): def __init__(self, command_prefix, *, intents, **options): super().__init__(command_prefix, intents=intents, **options) diff --git a/proto/android_checkin.proto b/proto/android_checkin.proto new file mode 100644 index 0000000..145de4a --- /dev/null +++ b/proto/android_checkin.proto @@ -0,0 +1,96 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Logging information for Android "checkin" events (automatic, periodic +// requests made by Android devices to the server). + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +package checkin_proto; + +// Build characteristics unique to the Chrome browser, and Chrome OS +message ChromeBuildProto { + enum Platform { + PLATFORM_WIN = 1; + PLATFORM_MAC = 2; + PLATFORM_LINUX = 3; + PLATFORM_CROS = 4; + PLATFORM_IOS = 5; + // Just a placeholder. Likely don't need it due to the presence of the + // Android GCM on phone/tablet devices. + PLATFORM_ANDROID = 6; + } + + enum Channel { + CHANNEL_STABLE = 1; + CHANNEL_BETA = 2; + CHANNEL_DEV = 3; + CHANNEL_CANARY = 4; + CHANNEL_UNKNOWN = 5; // for tip of tree or custom builds + } + + // The platform of the device. + optional Platform platform = 1; + + // The Chrome instance's version. + optional string chrome_version = 2; + + // The Channel (build type) of Chrome. + optional Channel channel = 3; +} + +// Information sent by the device in a "checkin" request. +message AndroidCheckinProto { + // Miliseconds since the Unix epoch of the device's last successful checkin. + optional int64 last_checkin_msec = 2; + + // The current MCC+MNC of the mobile device's current cell. + optional string cell_operator = 6; + + // The MCC+MNC of the SIM card (different from operator if the + // device is roaming, for instance). + optional string sim_operator = 7; + + // The device's current roaming state (reported starting in eclair builds). + // Currently one of "{,not}mobile-{,not}roaming", if it is present at all. + optional string roaming = 8; + + // For devices supporting multiple user profiles (which may be + // supported starting in jellybean), the ordinal number of the + // profile that is checking in. This is 0 for the primary profile + // (which can't be changed without wiping the device), and 1,2,3,... + // for additional profiles (which can be added and deleted freely). + optional int32 user_number = 9; + + // Class of device. Indicates the type of build proto + // (IosBuildProto/ChromeBuildProto/AndroidBuildProto) + // That is included in this proto + optional DeviceType type = 12 [default = DEVICE_ANDROID_OS]; + + // For devices running MCS on Chrome, build-specific characteristics + // of the browser. There are no hardware aspects (except for ChromeOS). + // This will only be populated for Chrome builds/ChromeOS devices + optional checkin_proto.ChromeBuildProto chrome_build = 13; + + // Note: Some of the Android specific optional fields were skipped to limit + // the protobuf definition. + // Next 14 +} + +// enum values correspond to the type of device. +// Used in the AndroidCheckinProto and Device proto. +enum DeviceType { + // Android Device + DEVICE_ANDROID_OS = 1; + + // Apple IOS device + DEVICE_IOS_OS = 2; + + // Chrome browser - Not Chrome OS. No hardware records. + DEVICE_CHROME_BROWSER = 3; + + // Chrome OS + DEVICE_CHROME_OS = 4; +} diff --git a/proto/checkin-generator.proto b/proto/checkin-generator.proto new file mode 100644 index 0000000..3ef0e7f --- /dev/null +++ b/proto/checkin-generator.proto @@ -0,0 +1,152 @@ +syntax = "proto2"; + +package tutorial; + +message AndroidBuildProto { + message ClientGroups { + optional int32 version = 1; + optional string name = 2; + } + optional string id = 1; + optional string hardware = 2; + optional string brand = 3; + optional string radio = 4; + optional string bootloader = 5; + optional string clientId = 6; + optional int64 timestamp = 7; + optional int32 googleServices = 8; + optional string device = 9; + optional int32 sdkVersion = 10; + optional string model = 11; + optional string manufacturer = 12; + optional string product = 13; + optional bool otaInstalled = 14; + repeated ClientGroups groups = 15; + optional string securityPatch = 19; +} + +message AndroidCheckinReasonProto { + optional int32 reasonType = 1; + optional int32 attemptCount = 2; + optional string sourcePackage = 3; + optional string sourceClass = 4; + optional bool sourceForce = 5; +} + +message AndroidCheckinProto { + optional AndroidBuildProto build = 1; + optional int64 lastCheckinMsec = 2; + repeated AndroidEventProto event = 3; + repeated AndroidStatisticProto stat = 4; + repeated string requestedGroup = 5; // unused + optional string cellOperator = 6; + optional string simOperator = 7; + optional string roaming = 8; + optional int32 userNumber = 9; + optional int32 deviceType = 14 [default = 1]; + optional AndroidCheckinReasonProto reason = 15; + optional bool voiceCapable = 18; + optional string unknown19 = 19; // unclear what this is for, but exists +} + +message AndroidEventProto { + optional string tag = 1; + optional string value = 2; + optional int64 timeMsec = 3; +} + +message AndroidStatisticProto { + optional string tag = 1; + optional int32 count = 2; + optional float sum = 3; +} + +message DeviceConfigurationProto { + optional int32 touchScreen = 1; + optional int32 keyboard = 2; + optional int32 navigation = 3; + optional int32 screenLayout = 4; + optional bool hasHardKeyboard = 5; + optional bool hasFiveWayNavigation = 6; + optional int32 screenDensity = 7; + optional int32 glEsVersion = 8; + repeated string systemSharedLibrary = 9; + repeated string systemAvailableFeature = 10; + repeated string nativePlatform = 11; + optional int32 screenWidth = 12; + optional int32 screenHeight = 13; + repeated string systemSupportedLocale = 14; + repeated string glExtension = 15; + optional int32 deviceClass = 16; + optional int32 maxApkDownloadSizeMb = 17[default = 50]; + optional int32 smallestScreenWidthDP = 18; + optional int32 lowRamDevice = 19[default=0]; + optional int64 totalMemoryBytes = 20[default=8354971648]; + optional int32 maxNumOf_CPUCores = 21[default=8]; + repeated DeviceFeature deviceFeature = 26; + optional bool keyGuardDeviceSecure = 28[default=true]; + optional int32 unknown30 = 30[default=4]; +} + +message DeviceFeature { + optional string name = 1; + optional int32 value = 2; +} + +message AndroidCheckinRequest { + optional string imei = 1; // used by GSM phones + optional int64 id = 2[default=0]; + optional string digest = 3; + optional AndroidCheckinProto checkin = 4; + optional string desiredBuild = 5; // DEPRECATED: Use AndroidCheckinProto.requestedGroup + optional string locale = 6; + optional int64 loggingId = 7; + optional string marketCheckin = 8; // unused + repeated string macAddr = 9; + optional string meid = 10; // used by CDMA phones + repeated string accountCookie = 11; + optional string timeZone = 12; + optional fixed64 securityToken = 13; + optional int32 version = 14; + repeated string otaCert = 15; + optional string serialNumber = 16; + optional string esn = 17; // used in CDMA networks + optional DeviceConfigurationProto deviceConfiguration = 18; + repeated string macAddrType = 19; + optional int32 fragment = 20; + optional string userName = 21; + optional int32 userSerialNumber = 22; + optional string droidguardResult = 24; + optional int32 fetchSystemUpdates = 29; + optional int32 unknown30 = 30; +} + +message AndroidCheckinResponse { + optional bool statsOk = 1; + repeated AndroidIntentProto intent = 2; + optional int64 timeMsec = 3; + optional string digest = 4; + repeated GservicesSetting setting = 5; + optional bool marketOk = 6; + optional fixed64 androidId = 7; + optional fixed64 securityToken = 8; + optional bool settingsDiff = 9; + repeated string deleteSetting = 10; + optional string deviceDataVersionInfo = 12; +} + +message AndroidIntentProto { + optional string action = 1; + optional string dataUri = 2; + optional string mimeType = 3; + optional string javaClass = 4; + repeated group Extra = 5 { + optional string name = 6; + optional string value = 7; + } +} + +message GservicesSetting { + optional bytes name = 1; + optional bytes value = 2; +} diff --git a/proto/checkin.proto b/proto/checkin.proto new file mode 100644 index 0000000..f0616ff --- /dev/null +++ b/proto/checkin.proto @@ -0,0 +1,155 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Request and reply to the "checkin server" devices poll every few hours. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package checkin_proto; + +import "android_checkin.proto"; + +// A concrete name/value pair sent to the device's Gservices database. +message GservicesSetting { + required bytes name = 1; + required bytes value = 2; +} + +// Devices send this every few hours to tell us how they're doing. +message AndroidCheckinRequest { + // IMEI (used by GSM phones) is sent and stored as 15 decimal + // digits; the 15th is a check digit. + optional string imei = 1; // IMEI, reported but not logged. + + // MEID (used by CDMA phones) is sent and stored as 14 hexadecimal + // digits (no check digit). + optional string meid = 10; // MEID, reported but not logged. + + // MAC address (used by non-phone devices). 12 hexadecimal digits; + // no separators (eg "0016E6513AC2", not "00:16:E6:51:3A:C2"). + repeated string mac_addr = 9; // MAC address, reported but not logged. + + // An array parallel to mac_addr, describing the type of interface. + // Currently accepted values: "wifi", "ethernet", "bluetooth". If + // not present, "wifi" is assumed. + repeated string mac_addr_type = 19; + + // Serial number (a manufacturer-defined unique hardware + // identifier). Alphanumeric, case-insensitive. + optional string serial_number = 16; + + // Older CDMA networks use an ESN (8 hex digits) instead of an MEID. + optional string esn = 17; // ESN, reported but not logged + + optional int64 id = 2; // Android device ID, not logged + optional int64 logging_id = 7; // Pseudonymous logging ID for Sawmill + optional string digest = 3; // Digest of device provisioning, not logged. + optional string locale = 6; // Current locale in standard (xx_XX) format + required AndroidCheckinProto checkin = 4; + + // DEPRECATED, see AndroidCheckinProto.requested_group + optional string desired_build = 5; + + // Blob of data from the Market app to be passed to Market API server + optional string market_checkin = 8; + + // SID cookies of any google accounts stored on the phone. Not logged. + repeated string account_cookie = 11; + + // Time zone. Not currently logged. + optional string time_zone = 12; + + // Security token used to validate the checkin request. + // Required for android IDs issued to Froyo+ devices, not for legacy IDs. + optional fixed64 security_token = 13; + + // Version of checkin protocol. + // + // There are currently two versions: + // + // - version field missing: android IDs are assigned based on + // hardware identifiers. unsecured in the sense that you can + // "unregister" someone's phone by sending a registration request + // with their IMEI/MEID/MAC. + // + // - version=2: android IDs are assigned randomly. The device is + // sent a security token that must be included in all future + // checkins for that android id. + // + // - version=3: same as version 2, but the 'fragment' field is + // provided, and the device understands incremental updates to the + // gservices table (ie, only returning the keys whose values have + // changed.) + // + // (version=1 was skipped to avoid confusion with the "missing" + // version field that is effectively version 1.) + optional int32 version = 14; + + // OTA certs accepted by device (base-64 SHA-1 of cert files). Not + // logged. + repeated string ota_cert = 15; + + // Honeycomb and newer devices send configuration data with their checkin. + // optional DeviceConfigurationProto device_configuration = 18; + + // A single CheckinTask on the device may lead to multiple checkin + // requests if there is too much log data to upload in a single + // request. For version 3 and up, this field will be filled in with + // the number of the request, starting with 0. + optional int32 fragment = 20; + + // For devices supporting multiple users, the name of the current + // profile (they all check in independently, just as if they were + // multiple physical devices). This may not be set, even if the + // device is using multiuser. (checkin.user_number should be set to + // the ordinal of the user.) + optional string user_name = 21; + + // For devices supporting multiple user profiles, the serial number + // for the user checking in. Not logged. May not be set, even if + // the device supportes multiuser. checkin.user_number is the + // ordinal of the user (0, 1, 2, ...), which may be reused if users + // are deleted and re-created. user_serial_number is never reused + // (unless the device is wiped). + optional int32 user_serial_number = 22; + + // NEXT TAG: 23 +} + +// The response to the device. +message AndroidCheckinResponse { + required bool stats_ok = 1; // Whether statistics were recorded properly. + optional int64 time_msec = 3; // Time of day from server (Java epoch). + // repeated AndroidIntentProto intent = 2; + + // Provisioning is sent if the request included an obsolete digest. + // + // For version <= 2, 'digest' contains the digest that should be + // sent back to the server on the next checkin, and 'setting' + // contains the entire gservices table (which replaces the entire + // current table on the device). + // + // for version >= 3, 'digest' will be absent. If 'settings_diff' + // is false, then 'setting' contains the entire table, as in version + // 2. If 'settings_diff' is true, then 'delete_setting' contains + // the keys to delete, and 'setting' contains only keys to be added + // or for which the value has changed. All other keys in the + // current table should be left untouched. If 'settings_diff' is + // absent, don't touch the existing gservices table. + // + optional string digest = 4; + optional bool settings_diff = 9; + repeated string delete_setting = 10; + repeated GservicesSetting setting = 5; + + optional bool market_ok = 6; // If Market got the market_checkin data OK. + + optional fixed64 android_id = 7; // From the request, or newly assigned + optional fixed64 security_token = 8; // The associated security token + + optional string version_info = 11; + // NEXT TAG: 12 +} diff --git a/requirements.txt b/requirements.txt index 835564f..6845d9d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,6 +27,7 @@ platformdirs==4.3.6 praw==7.8.1 prawcore==2.4.0 propcache==0.2.0 +protobuf==5.28.3 python-dateutil==2.9.0.post0 PyYAML==6.0.2 redis==5.2.0