Skip to content

Instantly share code, notes, and snippets.

@stbuehler
Created January 25, 2014 14:10
Show Gist options
  • Star 28 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save stbuehler/8616943 to your computer and use it in GitHub Desktop.
Save stbuehler/8616943 to your computer and use it in GitHub Desktop.
Decode the Battle.net Mobile Authenticator (android) secret data into an otpauth url. Requires a way to access the private data of the application (i.e. a rooted android).
#!/usr/bin/ruby
# REQUIRES:
# * rooted android, as otherwise you can't read the applications private data
# * to display the qr code "qrencode" (http://fukuchi.org/works/qrencode/)
# and "display" from ImageMagick
# 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("qrencode", "-s", "5", "-o", img.path, otpurl)
puts "press escape to exit 'display'"
STDERR.puts "'display' failed, maybe binary is not available" if not system("display", img.path)
else
STDERR.puts "'qrencode' failed, maybe binary is not available?"
end
@HacKanCuBa
Copy link

You, sir, deserve a medal!
I'm gonna try this soon. License of this code? Public?
How did you find out how the "encrypted" secret was stored? And which xor mask was to apply?
Cheers!

@HacKanCuBa
Copy link

It works perfectly fine, just requieres packages:
ruby imagemagick qrencode

@gasinvein
Copy link

Awesome! Great job.

@comigor
Copy link

comigor commented Jul 11, 2016

Thank you very much!

@jleclanche
Copy link

jleclanche commented Jul 26, 2016

This is a python library which can directly generate seeds from the Battle.net API, and dump the secret/otpauth QR url:

https://github.com/jleclanche/python-bna

@WhyNotHugo
Copy link

@jleclanche Thanks for developing that for us!

@nysander
Copy link

nysander commented Oct 4, 2018

great script, thanks

@JonJaded
Copy link

@jleclanche You're a godsend! Thank you as well, @stbuehler !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment