手把手教你逆向分析 Android 程序(连载二)

1. Android 的签名保护机制到底是什么?

Android 系统禁止更新安装签名不一致的 Apk,如果我们修改了 Apk 又用别的签名文件签名,肯定是不一致的。

我们从签名工具 autoSign 分析,看一下 sign.bat 文件内容:

-----------------------------------

@ECHO OFF

Echo Auto-sign Created By Dave Da illest 1

Echo Update.zip is now being signed and will be renamed to update_signed.zip

java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk

Echo Signing Complete

Pause

EXIT

-----------------------------------

看一下 java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk 这行的意义:

以testkey.x509.pem 这个公钥文件和 testkey.pk8 这个私钥文件对 update.apk 进行签名,签名后保存为 update_signed.apk。

我们可以看到签名前和签名后比较,签名后的文件中多了一个文件夹“META-INF”,里面有三个文件 MANIFEST.MF 、 CERT.SF 、 CERT.RSA。

我们通过 jd-gui 工具打开 signapk.jar,找到 main 函数,通过这个函数跟踪代码


1.addDigestsToManifest 这个函数,遍历 Apk 中所有文件,对非文件夹非签名文件的文件逐个生成 SHA1 数字签名信息,再 base64 编码。

然后再写入 MANIFEST.MF 文件中,生成文件如下:

-----------------------------------

Manifest-Version: 1.0

Created-By: 1.0 (Android)

Name: res/drawable-xhdpi/ic_launcher.png

SHA1-Digest: AfPh3OJoypH966MludSW6f1RHg4=

Name: AndroidManifest.xml

SHA1-Digest: NaPhUBH5WO7uGk/CfRu/SHsCvW0=

Name: res/drawable-mdpi/ic_launcher.png

SHA1-Digest: RRxOSvpmhVfCwiprVV/wZlaqQpw=

Name: res/drawable-hdpi/ic_launcher.png

SHA1-Digest: Nq8q3HeTluE5JNCBpVvNy3BXtJI=

Name: res/layout/activity_main.xml

SHA1-Digest: kxwMyILwF2K+n9ziNhcQqcCGWIU=

Name: resources.arsc

SHA1-Digest: q7Ystu6WoSWih53RGKXtE3LeTdc=

Name: classes.dex

SHA1-Digest: Ao1WOs5PXMxsWTDsjSijS2tfnHo=

Name: res/drawable-xxhdpi/ic_launcher.png

SHA1-Digest: GVIfdEOBv4gEny2T1jDhGGsZOBo=

-----------------------------------

SHA1 生成的摘要信息,如果你修改了某个文件,Apk 安装校验时,取到的该文件的摘要与 MANIFEST.MF 中对应的摘要不同,则安装不成功。

2.接下来对之前生成的 manifest 使用 SHA1withRSA 算法, 用私钥签名,writeSignatureFile 这个函数,最后生成 CERT.SF 文件,如下:

-----------------------------------

Signature-Version: 1.0

Created-By: 1.0 (Android)

SHA1-Digest-Manifest: pNZ9UXN9GMqTgqAwKD6uEN6aD34=

Name: res/drawable-xhdpi/ic_launcher.png

SHA1-Digest: cIga++hy5wqjHl9IHSfbg8tqCug=

Name: AndroidManifest.xml

SHA1-Digest: oRzzLkwuvxC78suvJcAEvTqcjSA=

Name: res/drawable-mdpi/ic_launcher.png

SHA1-Digest: VY7kOF8E3rn8EUTvQC/DcBEN6kQ=

Name: res/drawable-hdpi/ic_launcher.png

SHA1-Digest: stS7pUucSY0GgAVoESyO3Y7SanU=

Name: res/layout/activity_main.xml

SHA1-Digest: Yr3img6SqiKB+1kwcg/Fga2fwcc=

Name: resources.arsc

SHA1-Digest: j1g8I4fI9dM9hAFKEtS9dHsqo5E=

Name: classes.dex

SHA1-Digest: Sci9MmGXNGnZ1d04rCrEEV7MWn4=

Name: res/drawable-xxhdpi/ic_launcher.png

SHA1-Digest: KKqaLh/DVvFp+v1KoaDw7xETvrI=

-----------------------------------

用私钥通过 RSA 算法对 manifest 里的摘要信息进行加密,安装的时候只能通过公钥解密,解密之后才能获得正确的摘要,再对比。

3.最后就是如何生成 CERT.RSA,打开这个文件看到的是乱码,说明整个文件都被编码加密了,而且这个文件和公钥有关,从源码中看出他是通过 PKCS7 将整个文件加密了。

总结:1.签名只是对完整性和签名发布机构的校验机制 2.不能阻止 Apk 被修改,只是签名无法保持一致 3.不同私钥对应着不同的公钥,实质上不同的公钥就代表了不同的签名。

2. Android 系统如何获取签名

我们从获取下面一段代码开始分析:

@Override

public PackageManager getPackageManager() {

if (mPackageManager != null) {

return mPackageManager ;

}

IPackageManager pm = ActivityThread.getPackageManager ();

if (pm != null) {

// Doesn't matter if we make more than one instance.

return (mPackageManager = new ApplicationPackageManager(this, pm));

}

return null ;

}

可以看到 ActivityThread 中方法 getPackageManager 获取  IPackageManager。

继续看 ActivityThread 的代码:

public static IPackageManager getPackageManager() {

if (sPackageManager != null) {

//Slog.v("PackageManager", "returning cur default = " + sPackageManager);

return sPackageManager ;

}

IBinder b = ServiceManager.getService("package");

//Slog.v("PackageManager", "default service binder = " + b);

sPackageManager = IPackageManager.Stub.asInterface(b);

//Slog.v("PackageManager", "default service = " + sPackageManager);

return sPackageManager;

}

看源码知道是通过 ServiceManager.getService(“package”);获取 PackageManagerService 并得到 IBinder对象,然后通过  asInterface 函数取得接口类 IPackageManager 实例。

然后作为参数构造 ApplicationPackageManager,再看 ApplicationPackageManager 这个类里的方法:

@Override

public PackageInfo getPackageInfo(String packageName, int flags)

throws NameNotFoundException {

try {

PackageInfo pi = mPM.getPackageInfo(packageName, flags, mContext.getUserId());

if (pi != null) {

return pi;

}

} catch (RemoteException e) {

throw new RuntimeException( "Package manager has died" , e);

}

throw new NameNotFoundException(packageName);

}

这里的mPM就是上面构造函数传进来的 IPackageManager,就可以调用 PackageManagerService 的方法了。

下图是 PackagerManager 静态类结构图:


ok,看完上图的结构图,继续跟代码 ,看到这里是 mPM 继续调用的getPackageInfo(代理模式),通过进程通信到 PackageManagerService 中执行响应操作:

@Override

public PackageInfo getPackageInfo(String packageName, int flags, int userId) {

if (!sUserManager.exists(userId)) return null;

enforceCrossUserPermission(Binder.getCallingUid (), userId, false, "get package info");

// reader

synchronized (mPackages) {

PackageParser.Package p = mPackages.get(packageName);

if (DEBUG_PACKAGE_INFO)

Log.v (TAG, "getPackageInfo " + packageName + ": " + p);

if (p != null) {

return generatePackageInfo(p, flags, userId);

}

if((flags & PackageManager. GET_UNINSTALLED_PACKAGES ) != 0) {

return generatePackageInfoFromSettingsLPw(packageName, flags, userId);

}

}

return null ;

}

这里 mPackages 是 hashMap,其调用put方法的时机是在 scanPackageLI 方法中,而 scanPackageLI 的调用地方是在程序安装和替换函数中,还有就是 scanDirLi 中,代码略。

scanDirLi 是用来扫描一些系统目录的的,在 PackageManagerService 的构造函数中调用的:

File dataDir = Environment. getDataDirectory();

mAppDataDir = new File(dataDir, "data");

mAppInstallDir = new File(dataDir, "app");

mAppLibInstallDir = new File(dataDir, "app-lib" );

mAsecInternalPath = new File(dataDir, "app-asec" ).getPath();

mUserAppDataDir = new File(dataDir, "user");

mDrmAppPrivateInstallDir = new File(dataDir, "app-private" );

上面是扫描的位置↑↑↑↑↑↑↑

PackageManagerService 处理各种应用的安装、卸载、管理等工作,开机时由 systemServer 启动此服务。就是说之前安装过的应用或者系统应用信息都会在开机扫描过程中存到 mPackages 这个 hashMap 中。开机后用户的安装操作也会同样存到这个 hashMap 里面。

继续看 getPackageInfo,调用的 generatePackageInfo , 里面调用的是 PackageParser 中的 generatePackageInfo,继续跟。

这个函数的代码比较长,只贴出部分关键代码:

if ((flags&PackageManager. GET_SIGNATURES ) != 0) {

int N = (p.mSignatures != null) ? p.mSignatures.length : 0;

if (N > 0) {

pi.signatures = new Signature[N];

System.arraycopy (p.mSignatures, 0, pi. signatures, 0 , N);

}

}

return pi;

这段代码之前主要是做一些复制的操作,就是 new 一个 PackageInfo,然后把 PackageParser.Package 对象中的一些内容复制到这个 PackageInfo 中。

从这段代码可以看出来,最终得到的 signatures 信息就是中 p(PackageParser.Package )中的成员 mSignatures 中得来的。

好了,现在就是看这个PackageParser.Package是从哪来的了,通过跟踪代码,installPackageLI 和 scanPackageLI 中的:

final PackageParser.Package pkg = pp.parsePackag (tmpPackageFile, null, mMetrics, parseFlags);

这行代码只是生成了 pkg,并没有赋值里面的 mSignatures,继续跟踪,找到函数 collectCertificatesLI,找到 pp.collectCertificates(pkg , parseFlags)。

PS:最终又进行了一系列的跟代码,找到了 JarVerifier.java 这个类的 readCertificates 这个就是用来读取.RSA 文件的,最终我们看到的里面的代码是通过 loadCertificates 取得的 certs 赋值给了 pkg.mSignatures。

至此,获取签名的所有逻辑就算是简单的过一遍了,以下是简略流程图:(发现 ppt 画图比 Windows 画图工具好用多了,哈哈)


在跟代码的过程中也找到了签名对比的函数 compareSignatures ,有空自己看看就好了。

OK,绕了这么久我们终于找到源头了,获取签名就是在 META-INF 中寻找,并解析。试想一下,如果我们修改了这个函数,让他解析原来正版的 META-INF 中的 CERT.RSA 文件,这样就可以伪造为真正的签名了。

那么我们就想到了 HOOK,(很多人都是从看雪论坛上找到的一篇文章看到的http://bbs.pediy.com/showthread.php?t=186054hook 的原来简单来说就是,找到原函数和新函数的指针位置,然后兑换内容,将新函数替代原函数。

关于 hook 呢,网上也有很多框架可以使用,比如:

1.Cydia substrate :http://www.cydiasubstrate.com/

2.Xposed :http://repo.xposed.info/

网上也有很多教程可以看看。

乌云(wooyun)上有一篇很有意思的教程,就是利用 hook 进行微信运动作弊,原帖地址:http://drops.wooyun.org/tips/8416

下图就是我用了上面的方法产生的效果,还差点被微信部门的人请去喝茶。



这里用的是 Xposed 框架,原理就是 hook 了手机的计步传感器的队列函数,然后把步数的返回值每步乘1000返回,前提是,你的手机硬件本身有计步传感器功能,这里微信运动里面列出了支持的手机列表:https://kf.qq.com/touch/sappfaq/151013AvyyeQ151013r63qmq.html?platform=15好像最高就是98800了,可能是微信做了步数限制吧。

我这里用的是小米4联通版,发现虽然是1000基数的加,但是好像隔了很久才变化,估计又是 MIUI 做了一些省电策略,传感器的采集做了对齐吧?

Xposed 框架,很多玩机爱好者,会拿它修改一些主题,字体之类的,或者系统界面,定制自己想要的系统插件等等。然而,也有缺点,需要手机root,而且这个框架,还有可能让手机变砖,还有的系统可能对这个框架支持的不好,或者不支持。XDA 论坛里面也有很多大神把 Xposed 对某些机型做了适配,大神一般都是说,如果手机变砖他们不负责,哈哈。

正式由于这些框架的诸多不便,root 等等的问题,于是就有了一些非 root 的 hook 的黑科技,比如阿里巴巴的开源框架 Dexposed(https://github.com/alibaba/dexposed)其也是根据 Xposed 框架修改而来的,不过看 github 上他们也好久没更新了。

也有像其他个人写的和这种比较类似的框架,这里就不介绍了。但是这类框架的缺点就是,只能在该进程下hook,不能全局 hook,即只对这个进程的应用起作用,不能对另一个应用起作用,优点是可以 hook 自定义函数也能hook 系统函数,并且不用 root 和重启。阿里用这个框架来打在线热补丁。

那对于 App 内部签名校验的就不用再搜相应的代码了,直接 hook 就一步到位了,android.app.ApplicationPackageManager 这个类的 getPackageInfo 这个方法直接把正确的签名返回就好了,接下来我们就需要把hook的代码注入到某个 App 里就好了。

未完待续。。。。。。


欢迎阅读:手把手教你逆向分析 Android 程序(连载三)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容