Skip to content

Instantly share code, notes, and snippets.

@smb
Forked from stbuehler/bma2otp.rb
Last active August 29, 2015 14:05
Show Gist options
  • Save smb/4bdbbce3ebd41ddbf91b to your computer and use it in GitHub Desktop.
Save smb/4bdbbce3ebd41ddbf91b to your computer and use it in GitHub Desktop.
#!/usr/bin/ruby
# REQUIRES:
# * rooted android, as otherwise you can't read the applications private data
# qrcode: https://code.google.com/p/qrencode-win32/downloads/list
# This script "decrypts" the token from the internal state of the
# Battle.net Mobile Authenticator on android application, converting
# it into an "otpauth" url (https://code.google.com/p/google-authenticator/wiki/KeyUriFormat)
# and (using qrencode and display) displays it as QR code on the screen
# to scan it with any compatible app (FreeOTP for example; Google authenticator doesn't support digits=8)
# The decrypted token can either be read manually with root on the device (see below),
# or, if the device is attached and debug mode enabled, directly read by the script.
# Internals:
#
# The secret token (and serial) for the Battle.net Mobile Authenticator on android is stored
# in the file: /data/data/com.blizzard.bma/shared_prefs/com.blizzard.bma.AUTH_STORE.xml
# in the property "com.blizzard.bma.AUTH_STORE.HASH":
#
# <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
# <map>
# <long name="com.blizzard.bma.AUTH_STORE.CLOCK_OFFSET" value="[clock offset]" />
# <int name="com.blizzard.bma.AUTH_STORE_HASH_VERSION" value="10" />
# <string name="com.blizzard.bma.AUTH_STORE.HASH">["encrypted" token]</string>
# <long name="com.blizzard.bma.AUTH_STORE.LAST_MODIFIED" value="[timestamp]" />
# </map>
# The encrypted token is a hex string, encoding 57 bytes; decode it into a byte array, decrypt it with
# the xor "mask". The decrypted token now consists of 40 bytes hex-encoding the secret and 17 bytes with
# the serial (US|EU)-\d{4}-\d{4}-\d{4}
# The hex-decoded secret can be used with TOTP (RFC 6238; X = 30, T0 = 0, digit = 8) to generate
# the authentication codes.
def base32(str)
cDIGITS = ('A'..'Z').to_a + ('2'..'7').to_a
dMASK = 0x1f
cSHIFT = 5
bytes = str.unpack('C*')
paddedLen = 8 * ((bytes.length + 4)/5)
bits = 0
haveBits = 0
b32 = []
bytes.each do |byte|
bits = (bits << 8) | byte
haveBits += 8
while haveBits >= cSHIFT
b32 << cDIGITS[dMASK & (bits >> (haveBits - cSHIFT))]
haveBits -= cSHIFT
end
bits &= dMASK
end
if haveBits > 0
b32 << cDIGITS[dMASK & (bits << (cSHIFT - haveBits))]
end
b32.join + "=" * (paddedLen - b32.length)
end
def otpauth(serial, token)
"otpauth://totp/#{serial}:#{serial}?secret=#{base32(token)}&issuer=#{serial}&digits=8"
end
mask = [57,142,39,252,80,39,106,101,96,101,176,229,37,244,192,108,4,198,16,117,40,107,142,122,237,165,157,169,129,59,93,214,200,13,47,179,128,104,119,63,165,155,164,124,23,202,108,100,121,1,92,29,91,139,143,107,154]
STDOUT.puts "Enter encrypted token (or press enter to read with adb shell): "
token = STDIN.readline.strip
if token.length == 0
IO.popen(["adb", "shell", "cat", "/data/data/com.blizzard.bma/shared_prefs/com.blizzard.bma.AUTH_STORE.xml"], "r") do |i|
i.readlines.each do |line|
m = /<string name="com.blizzard.bma.AUTH_STORE.HASH">(.*)<\/string>/.match(line)
token = m[1] if m
end
end
end
token = [token].pack('H*').unpack('C*').zip(mask).map { |a,b| a ^ b }.pack('C*')
serial = token[40..-1]
token = [token[0..39]].pack('H*')
otpurl = otpauth(serial, token)
puts otpurl
require 'tempfile'
img = Tempfile.new(['otpauth_qr', '.png'])
if system("qrcode", "-s", "10", "-o", img.path, otpurl)
puts "press escape to exit 'display'"
FileUtils.cp(img.path, Dir.pwd)
else
STDERR.puts "'qrencode' failed, maybe binary is not available?"
end
gets
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment