view-android-settings.py
3.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/usr/bin/env python3
# Requires: PyCrypto
import sys
import base64
import re
import sqlite3
from Crypto.Cipher import AES
def get_setting(path, name):
"""Gets a setting value from the settings database."""
db = sqlite3.connect(path)
r = db.execute("SELECT value FROM setting WHERE key = ?", (name,)).fetchone()
return r[0] if r else None
def crazy_operation(key, constant):
"""Derives an AES key from two values using an unknown algorithm."""
def byte(n):
return n & 0xff
arr = [0] * 16
arr[0] = byte(key)
arr[1] = byte(key - 71)
arr[2] = byte(key - 142)
for i in range(3, 16):
arr[i] = byte(i ^ (0xffffffb9 ^ (arr[i - 3] ^ arr[i - 2])))
if constant < 2 and constant > -2:
constant = 0xfffffffffffb389d + 0xd2dfaf * constant;
i = 0
k = -7
larr = len(arr)
for _ in range(0, larr):
k1 = (i + 1) & (larr - 1)
l1 = constant * arr[k1] + k
k = byte(l1 >> 32)
i2 = l1 + k
if i2 < k:
i2 += 1
k += 1
arr[k1] = byte(-2 - i2)
i = k1
return bytes(arr)
def decrypt_setting(value, key):
"""Decrypts an encrypted setting using the supplied 8-bit integer key."""
ciphertext = base64.b64decode(value)
# generate AES key from 8-bit key
aes_key = crazy_operation(key, 0xec4ba7)
# decrypt setting with AES
aes = AES.new(aes_key, AES.MODE_ECB)
plaintext = aes.decrypt(ciphertext)
if len(plaintext) == 0:
return plaintext
# remove PKCS#7 padding
plaintext = plaintext[0:-plaintext[-1]]
return plaintext
def java_string_hash(string):
"""Equivalent of java.lang.String.hashCode()."""
r = 0
for c in string:
r = (31 * r + ord(c)) & 0xffffffff
return r
def is_profile_auth_key(value):
"""Checks if a value looks like a PROFILE_AUTH_KEY."""
return re.match(b"^u[a-z0-9]{32}:[a-zA-Z0-9+/]+$", value) != None
def bruteforce_key(auth_key_value):
"""Brute forces the key for a PROFILE_AUTH_KEY."""
for key in range(0x00, 0xff):
plaintext = decrypt_setting(auth_key_value, key)
if is_profile_auth_key(plaintext):
return key
raise Exception("Couldn't brute force key.")
def get_encrypted_setting(path, key, name):
"""Shorthand for getting the value of an encrypted settings."""
value = get_setting(path, name)
if value is None:
return None
return decrypt_setting(value, key).decode("utf-8")
if __name__ == "__main__":
if len(sys.argv) == 1:
print("USAGE: {0} SQLITE_DB [ANDROID_ID]".format(sys.argv[0]))
print()
sys.exit(1)
path = sys.argv[1]
auth_key_value = get_setting(path, "PROFILE_AUTH_KEY")
if len(sys.argv) == 2:
print("No ANDROID_ID given, bruteforcing.")
key = bruteforce_key(auth_key_value)
else:
key = java_string_hash(sys.argv[2])
auth_key_plaintext = decrypt_setting(auth_key_value, key)
if not is_profile_auth_key(auth_key_plaintext):
print("Key seems to be wrong.")
sys.exit(2)
mid, auth_key = get_encrypted_setting(path, key, "PROFILE_AUTH_KEY").split(":")
print("User MID: " + mid)
print("Auth key: " + auth_key)
print("Name: " + get_encrypted_setting(path, key, "PROFILE_NAME"))
print("LINE ID: " + get_encrypted_setting(path, key, "PROFILE_ID"))
print("Region: " + get_encrypted_setting(path, key, "PROFILE_REGION"))
print("Phone: " + get_encrypted_setting(path, key, "PROFILE_NORMALIZED_PHONE"))