移动安全 安全管理 应用案例 网络威胁 系统安全应用安全 数据安全 云安全
当前位置: 主页 > 信息安全 > 应用安全 >

颤抖了吗?九步逆向破解银行安然令牌

时间:2014-03-05 16:30来源:TuZhiJiaMi企业信息安全专家 点击:
作者历经千辛万苦,逆向了一个银步履态口令的APP,过程艰辛盘曲。最后发现,其实生成算法也不算很复杂,此中首要利用了 android_id 系统时候戳 作为生成变量。看起来高大年夜上的动态口令
Tags应用安全(1006)银行安全(5)  

  作者历经千辛万苦,逆向了一个银步履态口令的APP,过程艰辛盘曲。最后发现,其实生成算法也不算很复杂,此中首要利用了 android_id 系统时候戳 作为生成变量。看起来高大年夜上的动态口令,在逆向工程师抽丝剥茧的阐发下,亦不外如斯哇~

  正文

  我此次要对全巴西最大年夜的银行之一开刀,我也常常会利用到这家银行所供给的办事。他利用包含用户暗码在内的多种路子来验证用户的身份。我逆向的动态口令(OPT)也是此中一种,并且我把他移植到了Arduino-compatible 平台上。

  免责声明和更多的免责声明

  在文章中我过去掉落了敏感的信息,以呵护无辜的人的好处。并且这项研究的成果也不足以让我可以或许黑掉落他人的银行账户。即便一个具有root权限的第三方歹意利用,在没有足够的账户信息的前提下,都不克不及摹拟动态口令的生成。并且,此次研究并没有发现任何代码层面的缝隙,这家银行的生成算法乃至比谷歌的认证算法还要安然,乃至可以说这篇文章是对该银行APP安然性的一次嘉奖。他的动态生成算法,完全合适TOTP规范。把数据安然做到了极致。

  下面就是免责声明 balabala一大年夜堆,请谅解小编就不翻译了,想看的可以往原文地址不雅看

颤抖了吗?九步逆向破解银行安然令牌

  凡是对新用户,他们会获得一张密保卡,可是密保卡这东西是极其不便利的。别的一个更好的编制就是利用android app生成一个动态口令,大年夜胆的猜想,这个口令的生成可能把手机号,或是pin值作为参数。在我每次刷机,或改换手机的时辰,都要从头绑定一遍这个安然办事。当然过程比较简单,但仍是令我不爽。所以我决定,逆向这个android APP做一个本身的动态暗码生成东西。

  Activating the application 激活利用

  在浏览源码之前,我仍是喜好先把利用下载下来看看他长啥样,下面是三个阶段的截图。

颤抖了吗?九步逆向破解银行安然令牌

颤抖了吗?九步逆向破解银行安然令牌

颤抖了吗?九步逆向破解银行安然令牌

  第一张是安装时的截图,看一下他所需要的权限,这可不是开辟人员为何好玩才加上往的。他们此中有些乃至有可能影响到动态口令的生成。第二张是激活界面,需要填四个数字,这四个数字只能经由过程给银行打德律风被奉告。第三幅图就是,成功激活后生成的动态口令。

  The toolset 东西相干

  逆向android利用需要用到的几个小东西,鄙人面列出来了,其实就是网上常常利用的几种东西。

  Android SDK

  供给adb这个强大年夜的号令行东西,提取apk文件,和获得手机信息端赖它。

  dex2jar

  这个东西可以吧dex转换成jar包的情势

  JD, JD-GUI

  这个就是··java反编译东西,直接出源码

  Eclipse

  这个就不多说了,地球人都知道。

  Getting the APK file from the phone 第一步:获得APK文件

  这个相当简单的嘛~。可以直接在play里面下,也可利用ADB在手机中把他抓出来。

  查找包名

  $ ./adb shell pm list packages | grep mybank

  package:com.mybank

  肯定路径

  $ ./adb shell pm list packages | grep mybank

  package:com.mybank

  下载

  $ ./adb pull /data/app/com.mybank-1.apk

  2950 KB/s (15613144 bytes in 5.168s)

  第二步:解紧缩APK文件

  APK直接可以被解紧缩,其实他就是一个紧缩包文件,只是后缀不合。解紧缩今后的classes.dex文件中包含了java源代码信息。

  解紧缩

  $ unzip com.mybank-1.apk

  (file list omitted for brevity)

  把dex转换成jar文件

  $ mv classes.dex com.mybank-1.dex

  $ ./d2j-dex2jar.sh com.mybank-1.dex

  dex2jar com.mybank-1.dex -> com.mybank-1-dex2jar.jar

  第三步:看代码

  把jar包放进JD—GUI里面,便可以看到源代码了。

颤抖了吗?九步逆向破解银行安然令牌

  很等闲的便可以发现几个比较特别的包,br.com.mybank.integrador.token, br.com.othercompany.token , com.mybank.varejo.token毫无疑问核心代码就在里面,只不外代码应当被混合了。

  第四步:经由过程异常字符串反混合

  代码中常常的良多字符串名都被混合了(其实是加密了,不外加密的秘钥在代码中能找到~,作者说是混合就是混合把~),这比较蛋疼。要知道,代码中的字符串会对逆向起到很大年夜的帮忙。

  public void trocaPINcomLogin(int paramInt, boolean paramBoolean, Perfil paramPerfil)

  {

  if (paramPerfil == null)

  throw new IllegalArgumentException(a.a("1p5/eEf/sl3kbeUcP509qg=="));

  if (!this.jdField_a_of_type_U.jdField_a_of_type_JavaUtilHashtable.contains(paramPerfil))

  throw new RuntimeException(a.a("86jcmKgr/ZshQu9aGVbuGscy2nHW4UEWqudRoUXhImQ=") + a.a("7u8KqqwqUD3a7FM339fp6pRrxUtQrHDMyqvZ6A2MurQ="));

  if ((this.jdField_a_of_type_BrComOtherCompanyTokenParamsGerenciador.isPinObrigatorio()) &&(!paramBoolean))

  throw new RuntimeException(a.a("aMsL/5kjkXKD4K1SvpTuuJZUS0U0fL19UT2GxjJ/QzQ="));

  Configuracao localConfiguracao = paramPerfil.getConfiguracao();

  if ((localConfiguracao.a().a()) &&(paramPerfil != this.jdField_a_of_type_BrComOtherCompanyTokenPerfil))

  throw new RuntimeException(a.a("ASszutKFJW3iqDb7X/+vqAcYxTLXN2SJOIs0ne596Pu3ZoRxjiiscwhV6fT70efX"));

  localConfiguracao.a().a(paramInt);

  localConfiguracao.a().a(paramBoolean);

  this.jdField_a_of_type_U.a(paramPerfil);

  if (!paramPerfil.equals(this.jdField_a_of_type_BrComOtherCompanyTokenPerfil))

  a(paramPerfil);

  }

  不外荣幸的是,在抛出异常的语句中,我们可以找到一些蛛丝马迹,我们经由过程不雅察可以发现,混合字符串的函数是a.a。按照这些信息的提示,我们可以猜想a.a是一个解密有关的类。瓜熟蒂落,我们直接往a函数中阐发解密所利用的代码。

  这是阐发完a类以后的一些额外收成

  p类是一个base64解密的类。

  b类,实现了AES的功能。搜刮这个类当中的一些字符串,我发现它是收集上的一个开源实现 Paulo Barreto's JAES中的内容。

  a类中的private static byte[]是混合所利用的秘钥,可以经由过程一个简短的法度来反混合。

  不外不幸的是,a.a不单单是JAES的AES加密的包装,此中也包含着本身实现的一些加密。

  不外这都不是事儿,我仍是把a.a中的解密函数用python实现了。

  def decodeExceptionString(str):

  aesKey =

  xorKey =

  blockSize = 16

  aes = AES(aesKey)

  stringBytes = Base64.decode(str)

  outputString = ""

  for blockStart in xrange(0, len(stringBytes), blockSize):

  encryptedBlock = stringBytes[blockStart:blockStart+blockSize]

  plaintextBlock = aes.decrypt(encryptedBlock)

  outputString += plaintextBlock ^ xorKey

  xorKey = encryptedBlock

  return outputString

  简而言之,除AES和混合秘钥,这个类还实现了CBC(暗码段链接)。

  实验一下上述代码的功能

  $ ./decode "ASszutKFJW3iqDb7X/+vqAcYxTLXN2SJOIs0ne596Pu3ZoRxjiiscwhV6fT70efX"

  N?o possvel alterar PIN sem estar logado.

  这段葡萄牙语的意思是,it is not possible to change PIN without being logged in。看来代码运行的还不错。

  第五步:逆向核心代码–随奥秘码生成过程

  解决了,字符串混合的标题问题,接下来个就要弄清晰随奥秘码的生成过程了~找啊找啊找啊找~~~~找了好久,我终究发现了一个切进点,br.com.othercompany.token.dispositivo.OTP这个类。下面是它抛出的一些异常,反混合以后我们可以看到原文。

  public String calculate() throws TokenException {

  int i = (int)Math.max(Math.min((this.a.getConfiguracao().getAjusteTemporal() + Calendar.getInstance().getTime().getTime() - 1175385600000L) / 36000L, 2147483647L), -2147483648L);

  a();

  if (i <0)

  throw new TokenException("Janela negativa"), i);

  int j = (0x3 &this.a.getConfiguracao().getAlgoritmos().a) >>0;

  switch (j)

  {

  default:

  throw new TokenException("Algoritmo inválido:" + j, i);

  case 0:

  return a(i);

  case 1:

  }

  return o.a(this.a.getConfiguracao().getChave().a(20), i);

  }

  很等闲读懂,变量i是一个时候戳,从2007年4月11日到此刻的秒数除以36,36就是每个动态口令的存活时候。

  至于为甚么是2007年4月11日,我就不知道了,大年夜概是法度员他老婆的生日 : )

  他还引进了一个批改函数getAjusteTemporal(),为体味决各地区的时差标题问题。上文代码中的o.a函数是用于天活泼态暗码,他的两个参数一个是刚才说到的时候戳,还有一个是遗传byte数组(应当是一个密钥)。

  第六步:寻觅密钥

  看一下,生成语句的这段调用,this.a.getConfiguracao().getChave().a(20) this.a 是一个Perfil (profile) 对象,getConfiguracao() 返回一个Configuracao (settings) 对象getChave()返回一个z类,a(int)返回一个byte数组,这个数组就是key。

  z类中的字符串,也颠末端混合,可是比较简单,反混合过程就不提了.查看一下c类中的a(int)函数,是返回一个byte数组,长度截取到参数值。Perfil (profile) 对象反而是由PersistenciaDB类成立的,这个类中也包含了良多被混合的字符串。

  a = a.a("DwYyIlrWxIS9ruNMCKH/PQ==");

  b = a.a("SceoTjidi0XqlgRUo9hcDw==");

  c = a.a("yrYBlcp8nEfVKUT9WSqTqA==");

  d = a.a("jUTzBfsP/AO/Kx/1+VQ3CQ==");

  e = a.a("Y56SnU/pIKROPCLHu7oFuw==") + b + a.a("38oyp4eW3xqT3TaMfWZ5RA==") + "_id" + a.a("3Q+FCEVH2PxZ31ms4WHHwNB40EbmtWzHPhwoaB1nM7lGr+9zZzuVpx5iZ4YR+KUw") + c + a.a("bYYIl6LtqthcUCCFFb7JCRSC8zr5hKIFXe5JHFCCkZA=") + d + a.a("ENCtPBu4RtFta2XI1GsQag==") + a.a("ImPhDy43f+Nr4G5ofkZz+g==");

  好在a.a的机制我们前面研究过,翻译出的原文以下。

  a = "token.db";

  b = "perfis";

  c = "nome";

  d = "data";

  e = "create table perfis (_id integer primary key autoincrement, nome text not null, data blob not null);";

  竟然是一条,SQL语句,真是很有趣,本来它利用数据库来存储建设信息。此中还有一个文件名token.db,很多是一个SQLite数据库。紧接着经由过程研究PersistenciaDB类中的 carregar(load)函数,我们可以肯定这一设法,他经由过程SQLiteDatabase类来拜候这个数据库。

  不外我接下来发现数据blob(binary large object)在carragar函数中被aa.a(和上文中的a.a不是一码事)这个函数加密了,这个函数领受blob数据,和一串16个字符的密钥。

  在研究aa.a这个函数之前。我们先研究一下解密Blob的密钥,他作为carregar的一个参数传递进来。由PersistenciaUtils这个类产生。下面是这个类的进口。

  public class PersistenciaUtils {

  public static byte[] getChave(Context paramContext, byte[] paramArrayOfByte) {

  try {

  byte[] arrayOfByte = MessageDigest.getInstance("SHA-1").digest(getId(paramContext).getBytes());

  return arrayOfByte;

  } catch (NoSuchAlgorithmException localNoSuchAlgorithmException) {

  }

  return new byte[20];

  }

  public static String getId(Context paramContext) {

  String str = Settings.System.getString(paramContext.getContentResolver(), "android_id");

  if (str == null)

  str = "";

  return str;

  }

  }

  从代码可以看出,先获得android_id的SHA-1摘要,假定获得掉败就是用一个默许的16进制hash字符串。

  利用adb看看我的android_id

  $ ./adb shell

  shell@hammerhead:/$ content query --uri content://settings/secure --projection name:value --where "name='android_id'"

  Row: 0 name=android_id, value=0123456789abcdef

  shell@hammerhead:/$ exit

  a.aa把blob分成良多段,96位的头,16位的随机数,16位的标签和为加密数据预留的空间。

  进一步研究aa这个类,我们的到了,以下的信息。

  类a实现了 EAX AEAD(Authenticated Encryption with Associated Data)

  类f实现了 CMAC (Cipher-based Message Authentication Code)

  类h实现了 CTR (counter) mode 告白计费的功能

  以上三个类的算法实现都取自JAES库

  类l实现了 SHA-1 哈希算法 ,有趣的是类PersistenciaUtil并没有益用它,而是利用了MessageDigest这个函数来替代它。

  类m实现了 HMAC (keyed-Hash Message Authentication Code) 算法

  类n,包装了l和m,供给了HMAC-SHA1接口

  最首要的我发现,aa.a函数是经由过程CMAC标签进行加密的,写出python的解密代码。

  def decodeBlob(datablob, android_id):

  header = datablob[:96]

  nonce = datablob[96:112]

  tag = datablob[112:128]

  cryptotext = datablob[128:]

  key1 = SHA1(android_id)[:16]

  aes = AES(key1)

  cmac = CMAC(aes)

  cmac.update(header)

  key2 = cmac.getTag()

  eax = EAX(key2, aes)

  (validTag, plaintext) = eax.checkAndDecrypt(cryptotext, tag)

  if validTag:

  return plaintext

  假定EAX验证成功,aa.a返回解密的内容给PersistenciaDB利用。

  再来看PersistenciaDB这个类,此中的a编制可以解析明文数据并把它变成一个perfil对象,并反序列化之,把其变成一个包含着bool,short,byte的数组。

  此中以下3个,是关头的数据,按照偏移量反推出偏移。

  pin = int(blob[82:86])

  key = blob[38:70]

  timeOffset = long(blob[90:98])

  这就是密钥所需要的三个数据,只要三个都准确,法度便可以正常运行。

  第七步:深度理解代码

  上文中获得的秘钥在OPT 类中被截取到20个字节,并和时候戳一路传送到o.a编制中,这个别例援引了上文提到的良多类,所以,比较轻松的就写出了python代码~

  def generateToken(key, timestamp):

  message = [0] * 8

  for i in xrange(7, 0, -1):

  message[i] = timestamp &0xFF

  timestamp >>= 8

  hmacSha1 = HMAC_SHA1(key)

  hmacSha1.update(message)

  hash = hmacSha1.getHash()

  k = 0xF &hash[-1]

  m = ((0x7F &hash[k]) <<24 | (0xFF &hash[(k + 1)]) <<16 | (0xFF &hash[(k + 2)]) <<8 | 0xFF &hash[(k + 3)]) % 1000000;

  return "%06d" % m

  根基时候戳是一个占8字节的长整形,手动把它转换成大年夜端的byte数组,接着利用HMAC-SHA1,获得hash最后四位作为整数读取的索引。利用这个整形,mod 1000000,就是我们的随奥秘码了,简单的超乎我的想象~~

  顿时我在谷歌的TOTP认证的实现代码中,发现了很类似的一段。

  public String generateResponseCode(byte[] challenge)

  throws GeneralSecurityException {

  byte[] hash = signer.sign(challenge);

  // Dynamically truncate the hash

  // OffsetBits are the low order bits of the last byte of the hash

  int offset = hash[hash.length - 1] &0xF;

  // Grab a positive integer value starting at the given offset.

  int truncatedHash = hashToInt(hash, offset) &0x7FFFFFFF;

  int pinValue = truncatedHash % (int) Math.pow(10, codeLength);

  return padOutput(pinValue);

  }

  其实他们利用了基底蕴同的算法。

  第九步:克隆

  此刻一个德州出品的Stellaris LaunchPad正躺在我面前,我预备了以下的库~

  Cryptosuite

  Arduino的加密算法库 (包含 SHA and HMAC-SHA)

  RTClib

  JeeNodes and Arduinos所利用的轻量级时候日期库.

  2x16LCD_library

  A library for 2x16 LCD (like JDH162A or HD44780) written for Energia and Stellaris Launchpad (LM4F).

  RTC有一部门需要改进。因为Stellaris LaunchPad没有板载实不时钟,内部时钟需要在每次启动时设置,并且需要一台电脑来辅助,这是很麻烦的工作。

  完全代码以下。

  #include

  #include

  #include

  RTC_Millis RTC;

  void setup() {

  RTC.begin(DateTime(__DATE__, __TIME__));

  LCD.init(PE_3, PE_2, PE_1, PD_3, PD_2, PD_1);

  LCD.print("Token");

  LCD.print("valverde.me", 2, 1);

  delay(1000);

  LCD.clear();

  }

  char token[6];

  uint8_t message[8];

  long timestamp = 0;

  long i = 0;

  uint8_t key[] = {};

  void showToken() {

  long now = RTC.now().get() - 228700800 + 7200;

  i = now / 36;

  int timeLeft = now % 36;

  for(int j = 7; j >= 0; j--) {

  message[j] = ((byte)(i &0xFF));

  i >>= 8;

  }

  Sha1.initHmac(key, 20);

  Sha1.writebytes(message, 8);

  uint8_t * hash = Sha1.resultHmac();

  int k = 0xF &hash[19];

  int m = ((0x7F &hash[k]) <<24 | (0xFF &hash[(k + 1)]) <<16 | (0xFF &hash[(k + 2)]) <<8 | 0xFF &hash[(k + 3)]) % 1000000;

  LCD.print(m, 2, 1);

  LCD.print(36 - timeLeft, 2, 15);

  }

  void loop() {

  LCD.clear();

  LCD.print("Current token:");

  showToken();

  delay(1000);

  }

  最后作者还分享了一个,解决Arduino时候标题问题标小技能~这里省略啦~感欢愉爱好的同窗可以往原文看。

------分隔线----------------------------
  • 上一篇:没有了
  • 下一篇:没有了

推荐内容