diff --git a/config.toml b/config.toml index c0cc5fb..ebfcb4d 100644 --- a/config.toml +++ b/config.toml @@ -30,8 +30,19 @@ enableGitInfo = true [markup] [markup.highlight] - codeFences = true - noClasses = false + anchorLineNos = false + codeFences = true + guessSyntax = true + hl_Lines = '' + hl_inline = false + lineAnchors = '' + lineNoStart = 1 + lineNos = false + lineNumbersInTable = true + noClasses = true + noHl = false + style = "gruvbox" + tabWidth = 4 [markup.tableOfContents] startLevel = 1 diff --git a/content/posts/cracking_att_vvm_cipher.md b/content/posts/cracking_att_vvm_cipher.md index abe62b9..511812f 100644 --- a/content/posts/cracking_att_vvm_cipher.md +++ b/content/posts/cracking_att_vvm_cipher.md @@ -7,7 +7,7 @@ draft: false I've been wanting to get the AT&T visual voicemail "protocol" (ADVVM) working in the LineageOS dialer. I thought I had made a breakthrough with the discovery of the prefix in front of the VVM mail server address: -``` +```url srv=2:vvm.mobile.att.net ``` [Another user raised an issue pointing to the same @@ -21,7 +21,7 @@ STATUS SMS. This brings us to the subject of the mysterious data SMS coming in on port 5499 that I have wondered about ever since I discovered them in the logs when first "implementing" ADVVM. They can be triggered by sending a message of this format: -``` +```url GET?c=ATTV:/:&v=1.0&l=<10-digit phone number>&AD ``` These SMS seemingly contain everything *useful* that the STATUS SMS contains, @@ -54,7 +54,7 @@ how? 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 removing the upper bits: -``` +```python def get_stripped(text): return [ord(c) & 0x0f for c in text] ``` @@ -70,7 +70,7 @@ Duh! XOR is a reversible, non-destructive operator that [works great for ciphering](https://en.m.wikipedia.org/wiki/XOR_cipher). Let's try it: -``` +```python def xor_cipher(cipher, secret): if isinstance(cipher[0], str): cipher = get_stripped(cipher) @@ -92,13 +92,13 @@ Well, this doesn't quite work. When run with the ciphertext and phone number, 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 insight. Below is the one with my throwaway number: -``` +```python xor_cipher("[VW^QW\\W_X0", "00000000000") [11, 6, 7, 14, 1, 7, 12, 7, 15, 8, 0] ``` 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: -``` +```python first_pass = ''.join(chr(i) for i in xor_cipher("[VW^QW\\W_X0", "7345839476")) xor_cipher(first_pass, "00000000000") [12, 5, 3, 11, 9, 4, 5, 3, 8, 14, 0] @@ -107,11 +107,11 @@ xor_cipher(first_pass, "00000000000") Now this is interesting! The output of this is the same for both of my phone lines. Additionally, it is identical to the output from my initial decode above. I think we've found a secondary secret, meaning the algorithm is: -``` +```pseudocode secret XOR phonenumber XOR plaintext = ciphertext ``` Let's verify: -``` +```python def decode(cipher, phonenumber): secret = [12, 5, 3, 11, 9, 4, 5, 3, 8, 14] @@ -127,7 +127,7 @@ 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: -``` +```python lookup_table = [ ['[', 'V', 'W', '^', 'Q', 'W', '\\', 'W', '_', 'X'], ['Z', 'W', 'V', '_', 'P', 'V', ']', 'V', '^', 'Y'], @@ -151,6 +151,8 @@ def validate_decode(table, phone): print(f'Success! decode("{ciphertext}", ...) == "{plaintext}"') validate_decode(lookup_table, "7345839476") +``` +```stdout Success! decode("[VW^QW\W_X", ...) == "0000000000" Success! decode("ZWV_PV]V^Y", ...) == "1111111111" Success! decode("YTU\SU^U]Z", ...) == "2222222222" @@ -170,7 +172,7 @@ 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 work for me. I verified that the number and lookup table do not work with my decode as well: -``` +```python kop316_lookup_table = [ [ 'X', 'T', 'Q', '^', 'Z', 'S', 'U', 'U', '_', 'Y' ], [ 'Y', 'U', 'P', '_', '[', 'R', 'T', 'T', '^', 'X' ], @@ -184,6 +186,8 @@ kop316_lookup_table = [ [ 'Q', ']', 'X', 'W', 'S', 'Z', '\\', '\\', 'V', 'P' ], ] validate_decode(kop316_lookup_table, "2065550100") +``` +```stdout Failed on "6140620777" != decode("XTQ^ZSUU_Y", ...) Failed on "7051731666" != decode("YUP_[RTT^X", ...) Failed on "4362402555" != decode("ZVS\XQWW][", ...) @@ -196,7 +200,7 @@ Failed on "14912814108151515" != decode("P\YVR[]]WQ", ...) Failed on "15813915119141414" != decode("Q]XWSZ\\VP", ...) ``` I also tried solving with both of these alternatives instead with no luck: -``` +```pseudocode secret XOR plaintext = ciphertext phonenumber XOR plaintext = ciphertext ``` @@ -204,17 +208,17 @@ 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 -``` +```pseudocode ciphertext XOR plaintext = secret ``` Let's try: -``` +```python xor_cipher("XTQ^ZSUU_Y", "0000000000") [8, 4, 1, 14, 10, 3, 5, 5, 15, 9] ``` And then validating it: -``` +```python kop316_lookup_table = [ ["X", "T", "Q", "^", "Z", "S", "U", "U", "_", "Y"], ["Y", "U", "P", "_", "[", "R", "T", "T", "^", "X"], @@ -245,6 +249,8 @@ def validate_old_decode(table): print(f'Success! decode("{ciphertext}", ...) == "{plaintext}"') validate_old_decode(kop316_lookup_table) +``` +```stdout Success! decode("XTQ^ZSUU_Y", ...) == "0000000000" Success! decode("YUP_[RTT^X", ...) == "1111111111" Success! decode("ZVS\XQWW][", ...) == "2222222222"