revise code examples
- update theme for lastmod support
This commit is contained in:
parent
ab9c7503ed
commit
9c3d6a8748
@ -2,6 +2,7 @@ baseURL = "https://beckmeyer.us/"
|
|||||||
languageCode = 'en-us'
|
languageCode = 'en-us'
|
||||||
title = "Joel Beckmeyer's Homepage"
|
title = "Joel Beckmeyer's Homepage"
|
||||||
theme = "contrast-hugo"
|
theme = "contrast-hugo"
|
||||||
|
enableGitInfo = true
|
||||||
|
|
||||||
[author]
|
[author]
|
||||||
name = "Joel Beckmeyer"
|
name = "Joel Beckmeyer"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "Cracking the AT&T VVM cipher"
|
title: "Cracking the AT&T VVM cipher"
|
||||||
tags: ["Hacking"]
|
tags: ["Hacking"]
|
||||||
date: 2024-06-19T20:16:00-04:00
|
date: 2024-06-19
|
||||||
draft: false
|
draft: false
|
||||||
---
|
---
|
||||||
I've been wanting to get the AT&T visual voicemail "protocol" (ADVVM) working
|
I've been wanting to get the AT&T visual voicemail "protocol" (ADVVM) working
|
||||||
@ -55,15 +55,8 @@ Our dictionary is self-contained in an upper 4-bit prefix. Let's focus on the
|
|||||||
bottom four bits (or [nibble](https://en.wikipedia.org/wiki/Nibble)) by
|
bottom four bits (or [nibble](https://en.wikipedia.org/wiki/Nibble)) by
|
||||||
removing the upper bits:
|
removing the upper bits:
|
||||||
```
|
```
|
||||||
def strip_prefix(char):
|
|
||||||
return ord(char)&0x0f
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we can create a bytearray containing the stripped versions of the passcode:
|
|
||||||
```
|
|
||||||
def get_stripped(text):
|
def get_stripped(text):
|
||||||
stripped = [strip_prefix(c) for c in text]
|
return [ord(c) & 0x0f for c in text]
|
||||||
return bytearray(stripped)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, how do we actually figure out the transform? There are a number of ciphers
|
Now, how do we actually figure out the transform? There are a number of ciphers
|
||||||
@ -78,19 +71,20 @@ ciphering](https://en.m.wikipedia.org/wiki/XOR_cipher).
|
|||||||
|
|
||||||
Let's try it:
|
Let's try it:
|
||||||
```
|
```
|
||||||
def decode(cipher, secret):
|
def xor_cipher(cipher, secret):
|
||||||
cipher = get_stripped(cipher)
|
if isinstance(cipher[0], str):
|
||||||
secret = get_stripped(secret)
|
cipher = get_stripped(cipher)
|
||||||
|
if isinstance(secret[0], str):
|
||||||
# remember that the cipher "passes through" digits past the 10th, so we
|
secret = get_stripped(secret)
|
||||||
# just overwrite the first 10
|
# remember that the cipher "passes through" digits past the length of the
|
||||||
|
# secret, so we just take the rest unciphered
|
||||||
text = [i^j for i, j in zip(cipher, secret)]
|
text = [i^j for i, j in zip(cipher, secret)]
|
||||||
if len(cipher) > 10:
|
if len(cipher) > len(secret):
|
||||||
text += cipher[10:]
|
text += cipher[len(secret):]
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
decode("[VW^QW\\W_X0", "7345839476")
|
xor_cipher("[VW^QW\\W_X0", "7345839476")
|
||||||
[12, 5, 3, 11, 9, 4, 5, 3, 8, 14, 0]
|
[12, 5, 3, 11, 9, 4, 5, 3, 8, 14, 0]
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -99,16 +93,15 @@ it doesn't output the plaintext password that I am expecting. Not to worry,
|
|||||||
because we can also use the plaintext instead of the actual secret to gain some
|
because we can also use the plaintext instead of the actual secret to gain some
|
||||||
insight. Below is the one with my throwaway number:
|
insight. Below is the one with my throwaway number:
|
||||||
```
|
```
|
||||||
decode("[VW^QW\\W_X0", "00000000000")
|
xor_cipher("[VW^QW\\W_X0", "00000000000")
|
||||||
[11, 6, 7, 14, 1, 7, 12, 7, 15, 8, 0, 0]
|
[11, 6, 7, 14, 1, 7, 12, 7, 15, 8, 0]
|
||||||
```
|
```
|
||||||
Ignore the slight bug due to an assumption about the length of the secret :|
|
|
||||||
The output of this is different for my throwaway and my actual phone number. So
|
The output of this is different for my throwaway and my actual phone number. So
|
||||||
there is a unique 10-digit secret. Let's try a two-step decode:
|
there is a unique 10-digit secret. Let's try a two-step decode:
|
||||||
```
|
```
|
||||||
first_pass = ''.join(chr(i) for i in decode("[VW^QW\\W_X0", "7345839476"))
|
first_pass = ''.join(chr(i) for i in xor_cipher("[VW^QW\\W_X0", "7345839476"))
|
||||||
decode(first_pass, "00000000000")
|
xor_cipher(first_pass, "00000000000")
|
||||||
[12, 5, 3, 11, 9, 4, 5, 3, 8, 14, 0, 0]
|
[12, 5, 3, 11, 9, 4, 5, 3, 8, 14, 0]
|
||||||
```
|
```
|
||||||
|
|
||||||
Now this is interesting! The output of this is the same for both of my phone
|
Now this is interesting! The output of this is the same for both of my phone
|
||||||
@ -119,25 +112,19 @@ secret XOR phonenumber XOR plaintext = ciphertext
|
|||||||
```
|
```
|
||||||
Let's verify:
|
Let's verify:
|
||||||
```
|
```
|
||||||
def decode(cipher, phonenumber, secret):
|
def decode(cipher, phonenumber):
|
||||||
cipher = get_stripped(cipher)
|
|
||||||
phonenumber = get_stripped(phonenumber)
|
|
||||||
secret = [12, 5, 3, 11, 9, 4, 5, 3, 8, 14]
|
secret = [12, 5, 3, 11, 9, 4, 5, 3, 8, 14]
|
||||||
|
|
||||||
# remember that the cipher "passes through" digits past the 10th, so we
|
first_pass = ''.join(chr(i) for i in xor_cipher(cipher, phonenumber))
|
||||||
# just overwrite the first 10
|
return xor_cipher(first_pass, secret)
|
||||||
text = [i^j^k for i, j, k in zip(cipher, phonenumber, secret)]
|
|
||||||
if len(cipher) > 10:
|
|
||||||
text += cipher[10:]
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
decode("[VW^QW\\W_X0", "7345839476")
|
decode("[VW^QW\\W_X0", "7345839476")
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||||
decode("[WU]URZPWQ", "7345839476")
|
decode("[WU]URZPWQ", "7345839476")
|
||||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||||
```
|
```
|
||||||
Beatiful! This yields the same result for both phone numbers.
|
Beatiful! This yields the same result for both phone numbers. Our secret is
|
||||||
|
[12, 5, 3, 11, 9, 4, 5, 3, 8, 14].
|
||||||
|
|
||||||
Let's validate this more completely:
|
Let's validate this more completely:
|
||||||
```
|
```
|
||||||
@ -153,17 +140,17 @@ lookup_table = [
|
|||||||
['S', '^', '_', 'V', 'Y', '_', 'T', '_', 'W', 'P'],
|
['S', '^', '_', 'V', 'Y', '_', 'T', '_', 'W', 'P'],
|
||||||
['R', '_', '^', 'W', 'X', '^', 'U', '^', 'V', 'Q'],
|
['R', '_', '^', 'W', 'X', '^', 'U', '^', 'V', 'Q'],
|
||||||
]
|
]
|
||||||
def validate_decode(table):
|
def validate_decode(table, phone):
|
||||||
for plaintext_char in range(10):
|
for plaintext_char in range(10):
|
||||||
expected_plaintext = str(plaintext_char) * 10
|
expected_plaintext = str(plaintext_char) * 10
|
||||||
ciphertext = ''.join([table[plaintext_char][i] for i in range(10)])
|
ciphertext = "".join([table[plaintext_char][i] for i in range(10)])
|
||||||
plaintext = ''.join([str(i) for i in decode(ciphertext, "7345839476")])
|
plaintext = "".join([str(i) for i in decode(ciphertext, phone)])
|
||||||
if plaintext != expected_plaintext:
|
if plaintext != expected_plaintext:
|
||||||
print(f"Failed on \"{plaintext}\" != decode(\"{ciphertext}\", ...)")
|
print(f'Failed on "{plaintext}" != decode("{ciphertext}", ...)')
|
||||||
else:
|
else:
|
||||||
print(f"Success! decode(\"{ciphertext}\", ...) == \"{plaintext}\"")
|
print(f'Success! decode("{ciphertext}", ...) == "{plaintext}"')
|
||||||
|
|
||||||
validate_decode(lookup_table)
|
validate_decode(lookup_table, "7345839476")
|
||||||
Success! decode("[VW^QW\W_X", ...) == "0000000000"
|
Success! decode("[VW^QW\W_X", ...) == "0000000000"
|
||||||
Success! decode("ZWV_PV]V^Y", ...) == "1111111111"
|
Success! decode("ZWV_PV]V^Y", ...) == "1111111111"
|
||||||
Success! decode("YTU\SU^U]Z", ...) == "2222222222"
|
Success! decode("YTU\SU^U]Z", ...) == "2222222222"
|
||||||
@ -176,7 +163,8 @@ Success! decode("S^_VY_T_WP", ...) == "8888888888"
|
|||||||
Success! decode("R_^WX^U^VQ", ...) == "9999999999"
|
Success! decode("R_^WX^U^VQ", ...) == "9999999999"
|
||||||
```
|
```
|
||||||
|
|
||||||
Addendum: After writing the majority of this, [a user pointed
|
**Addendum**:
|
||||||
|
After writing the majority of this, [a user pointed
|
||||||
out](https://gitlab.com/LineageOS/issues/android/-/issues/6964#note_1961619585)
|
out](https://gitlab.com/LineageOS/issues/android/-/issues/6964#note_1961619585)
|
||||||
that there is already some documentation on this ciphering, so I took a look.
|
that there is already some documentation on this ciphering, so I took a look.
|
||||||
Unfortunately, it looks like the cipher method has changed as this does not
|
Unfortunately, it looks like the cipher method has changed as this does not
|
||||||
@ -213,3 +201,59 @@ secret XOR plaintext = ciphertext
|
|||||||
phonenumber XOR plaintext = ciphertext
|
phonenumber XOR plaintext = ciphertext
|
||||||
```
|
```
|
||||||
But neither worked.
|
But neither worked.
|
||||||
|
|
||||||
|
There is a secret that works for decoding this, and we can find it by following
|
||||||
|
the same method from above, using the fact that
|
||||||
|
```
|
||||||
|
ciphertext XOR plaintext = secret
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's try:
|
||||||
|
```
|
||||||
|
xor_cipher("XTQ^ZSUU_Y", "0000000000")
|
||||||
|
[8, 4, 1, 14, 10, 3, 5, 5, 15, 9]
|
||||||
|
```
|
||||||
|
And then validating it:
|
||||||
|
```
|
||||||
|
kop316_lookup_table = [
|
||||||
|
["X", "T", "Q", "^", "Z", "S", "U", "U", "_", "Y"],
|
||||||
|
["Y", "U", "P", "_", "[", "R", "T", "T", "^", "X"],
|
||||||
|
["Z", "V", "S", "\\", "X", "Q", "W", "W", "]", "["],
|
||||||
|
["[", "W", "R", "]", "Y", "P", "V", "V", "\\", "Z"],
|
||||||
|
["\\", "P", "U", "Z", "^", "W", "Q", "Q", "[", "]"],
|
||||||
|
["]", "Q", "T", "[", "_", "V", "P", "P", "Z", "\\"],
|
||||||
|
["^", "R", "W", "X", "\\", "U", "S", "S", "Y", "_"],
|
||||||
|
["_", "S", "V", "Y", "]", "T", "R", "R", "X", "^"],
|
||||||
|
["P", "\\", "Y", "V", "R", "[", "]", "]", "W", "Q"],
|
||||||
|
["Q", "]", "X", "W", "S", "Z", "\\", "\\", "V", "P"],
|
||||||
|
]
|
||||||
|
|
||||||
|
def old_decode(cipher):
|
||||||
|
cipher = get_stripped(cipher)
|
||||||
|
secret = [8, 4, 1, 14, 10, 3, 5, 5, 15, 9]
|
||||||
|
|
||||||
|
return xor_cipher(cipher, secret)
|
||||||
|
|
||||||
|
def validate_old_decode(table):
|
||||||
|
for plaintext_char in range(10):
|
||||||
|
expected_plaintext = str(plaintext_char) * 10
|
||||||
|
ciphertext = "".join([table[plaintext_char][i] for i in range(10)])
|
||||||
|
plaintext = "".join([str(i) for i in old_decode(ciphertext)])
|
||||||
|
if plaintext != expected_plaintext:
|
||||||
|
print(f'Failed on "{plaintext}" != decode("{ciphertext}", ...)')
|
||||||
|
else:
|
||||||
|
print(f'Success! decode("{ciphertext}", ...) == "{plaintext}"')
|
||||||
|
|
||||||
|
validate_old_decode(kop316_lookup_table)
|
||||||
|
Success! decode("XTQ^ZSUU_Y", ...) == "0000000000"
|
||||||
|
Success! decode("YUP_[RTT^X", ...) == "1111111111"
|
||||||
|
Success! decode("ZVS\XQWW][", ...) == "2222222222"
|
||||||
|
Success! decode("[WR]YPVV\Z", ...) == "3333333333"
|
||||||
|
Success! decode("\PUZ^WQQ[]", ...) == "4444444444"
|
||||||
|
Success! decode("]QT[_VPPZ\", ...) == "5555555555"
|
||||||
|
Success! decode("^RWX\USSY_", ...) == "6666666666"
|
||||||
|
Success! decode("_SVY]TRRX^", ...) == "7777777777"
|
||||||
|
Success! decode("P\YVR[]]WQ", ...) == "8888888888"
|
||||||
|
Success! decode("Q]XWSZ\\VP", ...) == "9999999999"
|
||||||
|
```
|
||||||
|
To reiterate, the secret used here was `[8, 4, 1, 14, 10, 3, 5, 5, 15, 9]`.
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 9b3ec3d0243d3076342e53bbdcc6579265eb1cb6
|
Subproject commit ba427a30a7aecec0392fc92b0ec125fcea2a8247
|
Loading…
Reference in New Issue
Block a user