也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大
少走了弯路,也就错过了风景,无论如何,感谢经历
转移发布平台通知:将不再在CSDN博客发布新文章,敬请移步知识星球
感谢大家一直以来对我CSDN博客的关注和支持,但是我决定不再在这里发布新文章了。为了给大家提供更好的服务和更深入的交流,我开设了一个知识星球,内部将会提供更深入、更实用的技术文章,这些文章将更有价值,并且能够帮助你更好地解决实际问题。期待你加入我的知识星球,让我们一起成长和进步
Android安全付费专栏长期更新,本篇最新内容请前往:
通过之前的文章了解,同学们应该都知道了APK调试的前提条件是app可调试的状态,但一般开发人员在发版的时候,会发release版。因为在一般的手机上,release版本的应用是不可以被调试的,相对来说起到了保护app的作用
PS:在最初的时候,开发Android是没有gradle的,那时候发release版不像现在在gradle配置好就行了,是直接操作AndroidManifest.xml文件中 <application>
标签的属性 android:debuggable="true"
Android系统会通过APP的AndroidManifest文件中设置android:debuggable 属性,去验证App可不可以调试。假如想关掉这个系统验证,一种是重新刷入boot.img 修改方法,另一种是通过hook修改
一般大多数情况下,通常都是通过android:debuggable="true"来进行调试。APK可调试必须具备的条件(两个满足之一),如下:
android:debuggable="true"
./default.prop
中ro.debuggable的值必须为1(系统默认调试,在bulid.prop(boot.img)
,ro.debugable=1
)[车联网安全自学篇] Android安全之Android中allowBackup属性浅析「含案例实验」:
[车联网安全自学篇] Android安全之不反编译APK情况下添加android:debuggable属性
假设:android:debuggable=“false” 怎么办?
首先,一般情况下发行版的APP都会将android:debuggable=""
设置为 false,使第三方不能直接调试分析APP,这也是厂商出于安全的考虑。
此时同学们,可能会想到,需要反编译Apk并将android:debuggable=""
设置为true。修改完成后进行回编译APK且签名。
重打包APK可能会遇到繁杂的代码、诡异的反抓包,so层的加密……等奇奇怪怪的反重打包技术,而如果对APP进行重打包,那就要面对APP额外的保护措施,例如重打包失败,签名验证等。
假设:./default.prop中ro.debuggable
的值为0,怎么办?
直接修改这个值是成功不了的?这个在之前的文章里面有提到过,因为ro开头的属性是不允许后期修改的,这个值只在系统启动时,也就是开机时才会读取和加载一次。那重启?但每次重启,这个值就会恢复默认,所以就造成了一个死循环。
修改上面说的系统属性值的三种方式:
ES文件浏览器查看内存中的debuggable
或getprop命令查看内存中的debuggable
getprop ro.debuggable
点击单步调试按钮或按快捷键F8
切换到logcat查看日志,我打印出的i的值
比如我们的for循环当中调用了一个stepNext(int i)方法,当我们走到这里想看看这个方法里面的运行过程的时候我们可以这样,当走到这个方法的时候我们可以按下F7或如下图的图标
可以让我们看到程序现在所调用的所有方法的实现会让你跟着它走一遍,研究源码使用非常方便
例如,如果我们的一个流程当中,包括调用的方法,如果有断点走到下一个断点,如果没有断点,而是在一个调用的方法当中,会跳出这个方法,继续走
会很快执行到下一个断点的位置,而且可以静如任何调用的方法
如果设置了多个断点,但现在需要直接跳转到下一个断点,那么直接点击下图图标即可
如果想观察1个或者几个变量的值的变化;如果在Variables显示面版中观察;如果程序这里有太多太多的自定义变量和系统变量了,那么就难观察了。
此时,可以点击Watches、点击+号,然后输入变量的名称回车就OK了,而且还会有历史记录
或者选择[Variables]中的变量名然后点击[右键],选择[Add to Watches],然后Watches面板中就有了
在程序中有很多的条件语句和循环语句,调试也是比较耗时的,我们可以通过快速设置变量的值来加快调试速度
此时,可以选择[Variables]中的变量名然后点击[右键],选择[Set Value…]或者选择之后直接F2(下图为Variables面板)
点击之后,可以看到所有的断点,以及位置代码,也可以设置一些属性
[停止调试]不是让程序停止,而是跳过所有调试
下载地址:https://pan.baidu.com/s/1DqQtBGJxbHIfp4_HoCaiDQ
提取码:0f60
https://security.tencent.com/index.php/opensource/detail/17
https://www.open-open.com/lib/view/open1426304176732.html
https://repo.xposed.info/module/com.jecelyin.buildprop
https://github.com/deskid/XDebug
配置到Android studio的,配置了才能方便看smali文件
安装ideasmali插件,选择File->Settings->Plugins,安装之前下载的ideasmali插件
https://bitbucket.org/JesusFreke/smali/downloads/
Detect It Easy简称Die,是一款专业查壳工具,比PEID强大得多,能查一次查到底。并支持超大文件读取,其他查壳工具无法打开的程序,这个都能读取。虽然没有PEID出名,但是相当的强大,完全可以替代PEID
https://github.com/horsicq/Detect-It-Easy
https://github.com/sulab999/ApkMessenger
https://www.ghxi.com/apkinfo.html
deviceid、appver、appid、chno的参数值是固定不变的
is_default 是非必填项
login_uid 登录后获取的值
article_id 每篇文章的id
sign 它是64位十六进制数,猜测是两个md5拼接,可以理解为它可能是加密的内容
在APP里面,开发人员为了复用性,很多时候APP会将BASE_URL,也就是域名+域名后面的虚拟目录/请求参数拆分开来
将APK后缀改为zip,里面有两个classes.dex 文件
dex2jar 反编译为Java代码
d2j-dex2jar.bat classes.dex
d2j-dex2jar.bat classes2.dex
jadx 依次打开classes-dex2jar.jar、classes2-dex2jar.jar
get_article_info.php
从上面的搜索当中,可以看出只有m字符串是符合要求的,双击代码进去看一下,在这个config(配置)包里,以类变量的方式存放着大量的字符串,如果想引用它,就是b.m
右键查看用例,看一下这个url在哪儿被使用了,发现只有一处
双击第二行的代码,查看详细引用
它包装了一个a方法来取我们的目标URL,再次查找用例
我们这里点开com.sina.sinablog.network.d.a
,可以发现其实它就是a方法上面的那个方法
由上述的消息,我们知道APP发送的网络请求,首先要进行字段的获取和拼接,但在Java中往往由集合map完成,格式类似于{”id“:3,“name”:“lilac”},PUT存入、GET取出,这儿就是一个典型的Hashmap。
注:什么是Map集合?
Map用于保存具有映射关系的数据,Map集合里保存着两组值,一组用于保存Map的ley,另一组保存着Map的value
和查字典类似,通过key找到对应的value,通过页数找到对应的信息。用学生类来说,key相当于学号,value对应name,age,sex等信息。用这种对应关系方便查找
那么在APK中,它的第一步是初始化一个map,之后存入了对应的消息,看着和我们在Fiddler 抓包APP GET请求中的字段一致。
接下来,我们用Smali动态调试跟踪一下集合m从初始化到存入数据的全部过程,但是其实接下来的演示操作不用动态调试也是完全可以的达到最终,但为了更好的观察APP在动态调试时的参数调用以及动态调试操作的过程,我们依然选择用动态调试来做演示。
java -jar apktool.jar d -f xlblog.apk -o xlblog
.method
和.end method
分别是方法开始和结束的地方注:d.smali 我们需要关注的关键代码,如下
# virtual methods
.method protected a()Ljava/lang/String;
.locals 1
.prologue
.line 27
const-string v0, "http://app.blog.sina.com.cn/api/article/get_article_info.php"
return-object v0
.end method
.method public a(Lcom/sina/sinablog/network/d$a;Ljava/lang/String;Ljava/lang/String;I)V
.locals 4
.prologue
const/4 v3, 0x0
.line 14
invoke-static {}, Lcom/sina/sinablog/network/d;->m()Ljava/util/HashMap;
move-result-object v0
.line 15
const-string v1, "article_id"
invoke-virtual {v0, v1, p2}, Ljava/util/HashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
.line 16
const-string v1, "blog_uid"
invoke-virtual {v0, v1, p3}, Ljava/util/HashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
.line 17
const-string v1, "is_default"
invoke-static {v3}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v2
invoke-virtual {v0, v1, v2}, Ljava/util/HashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
.line 18
invoke-virtual {p1, v0}, Lcom/sina/sinablog/network/d$a;->setParams(Ljava/util/HashMap;)V
.line 19
invoke-virtual {p0}, Lcom/sina/sinablog/network/d;->a()Ljava/lang/String;
move-result-object v0
invoke-virtual {p1, v0}, Lcom/sina/sinablog/network/d$a;->setUrl(Ljava/lang/String;)V
.line 20
invoke-static {}, Ljava/lang/System;->currentTimeMillis()J
move-result-wide v0
invoke-virtual {p1, v0, v1}, Lcom/sina/sinablog/network/d$a;->setRequestTime(J)V
.line 21
invoke-virtual {p1, v3}, Lcom/sina/sinablog/network/d$a;->setIsMainThread(Z)V
.line 22
invoke-virtual {p0, p1}, Lcom/sina/sinablog/network/d;->a(Lcom/sina/sinablog/network/bf;)V
.line 23
return-void
.end method
对比jadx反编译的结果与d.smali的内容的关键节点是一样的,由此确认就是这个smali文件
调试工具的话,几乎所有的主流Java IDE配上smalidea插件都可以对Smali进行动态调试,除此之外,JEB也可以直接调试Smali,IDA也有调试DEX的能力,还有Qtrace等等工具,但调试Smali,建议使用Android Studio+smalidea插件这个组合,操作简单,功能强大,效果也很稳定
打开Android Studio工具,点击File,选择Settings,点击Plugin,再点击installplugin from disk。找到之前下载的smalidea插件选中点击OK,插件就添加成功了,会弹出弹窗,提示重启AndroidStudio,点击重启
android studio smalidea plugin导入版本低的smalide 会报错,在新版的Android studio 中已经不支持了,会出现如下报错:
解决办法如下:
smalidea v0.06 版本修复了新版的Android studio 中不支持的这个问题,下载地址:https://bitbucket.org/JesusFreke/smalidea/downloads/
更新日志https://github.com/JesusFreke/smalidea
注:Android Studio 支持导入apk,启动 Android Studio 或者 点击File 是否有 Profile or Debug APK 这个选项,然后导入APK
在Android Studio工程中右键点击smali文件夹,设定MarkDirectory as -> Sources Root
打开Android Studio的File-> Project Structure选择,选择对应的JDK
注:Android Studio 连接模拟器会出现失败现象,因为在SDK默认的路径路面,已经有一个adb了,但连接模拟器需要对应的adb,我这里以逍遥游模拟器为例,将下图标注丢到SDK路径下即可解决逍遥游报错的问题
adb push mprop /data/local/tmp/
adb shell
chmod +x /data/local/tmp/mprop
su
cd /data/local/tmp/mprop
./mprop ro.debuggable 1
setprop ro.debuggable 1
getprop ro.debuggable
调试模式启动app
adb shell am start -D -n 包名/.你要调试的界面
例如:
adb shell am start -D -n com.sina.sinablog/.MainActivity
查看进程号
adb shell ps | findstr 包名
例如:
adb shell ps | findstr com.sina.sinablog
端口映射(使用 jdwp 转发端口)
adb forward tcp:调试端口号 jdwp:进程号
例如:
adb forward tcp:8700 jdwp:6549
显然Smali代码更长的那个才是我们需要的重载方法,在代码左边空白处单击即可下断点
正式开始断点调试之前,首先同学们来了解一下,下面这行代码,不熟悉的可以去看博主同学之前的Smali语法总结的文章,下面的smali语法大致的意思是调用m()方法初始化HashMap并将其值赋给v0(或存储到寄存器v0中)
.line 14
invoke-static {}, Lcom/sina/sinablog/network/d;->m()Ljava/util/HashMap;
move-result-object v0
接下来,等程序运行完move-result-object v0 这一行后,在Watches监视器中添加v0,如下图所示:
随后按F8快捷键或点击Step Over 图标来一步步看着程序往下一步走时v0的变化,可以看到程序在初始化HashMap时,把方法已经塞进去五个字段了,一步步F8,会发现v0里的字段越来越多,没过多久,Get请求的九个字段就全部存储到寄存器v0中了
光靠F8一行一行走是没办法得知的,可以退出调试模式,更加精细的看一下,F7进入到子方法,在m方法中,获得了5个字段,然后Shift+F8跳出方法,返回之前进入子方法停留的地方
需要安装smalidea工具,此处不介绍了,请往前面翻看即可
案例APK:AliCrackme_1.apk
java -jar apktool.jar d -f AliCrackme_1.apk -o AliCrackme
注:-d参数,代表反编译得到的smali是Java文件,这里指文件后缀名是Java。如果不带这个参数,后缀名是smali的,否则后最美是java的(其实只是后缀名变了,里面的内容还是smali)
在AndroidManifest.xml的application添加属性:android:debuggable="true"
;android:debuggable="true"
它表示该APP应用是否是Debug版本,这将会影响到该APP应用是否能被调试,所以为了调试必须设置为true
一路默认即可
导入成功后,我们主要关注的smali文件
apktool b -d out -o 3.apk
或
apktool b -d out
安装后打开apk,发现是空白,这里是进入了调试状态,并不是出现错误
打开eclipse–>新建java project(第一次打开在project中找)–>更改默认路径为 out目录–>选择smali–>finished
adb kill-server
adb start-server
adb remount
PS:也能是adb版本跟Android Studion默认的adb版本有冲突,把模拟器的adb覆盖到Android Studio SDK目录下即可
设置断点(可在OnCreate函数或OnClick函数等的下一语句下断点),这里需要首先分析源代码,找出自己想要下断点的地方,我们通过运行程序知道此次是需要破解密码,那入手点是否能从按钮处寻求突破,通过查看activity_main.xml 文件知道button按钮定义的是@+id/button
这个值
然后全局搜索button,一个个找,查找我们想要的button的关键节点,此处直接给出了有关联的button的地方,如下:
从上图中,可以发现我们从values/public.xml(这个文件很重要,是我们在寻找突破口的重要关键,比如我们有时候需要通过字符串内容来定位到关键点)文件中发现了button的这个值,然后通过这个值确认了button在smali里面关键代码处的位置
MainActivity 中使用了findViewById()方法来获得其中的Button元素,然后给Button创建了一个点击监听事件,如下:
到内部类MainActivity$1,搜搜看,肯定实现了OnClickListener接口,那么直接搜onClick方法
在调试服务端这里会看到两个端口号:8600/8700,首先在这里的端口号,代表的是,远程调试服务器端的端口,下面简单来看一下,Java中的调试系统:
为了更好的了解远程调试,这里顺带讲一下DDMS,它的三个角色:
1)JDB Client端(被调试的客户端),这里我们可以认为我们需要破解的程序就是客户端,如果一个程序可以被调试,当启动的时候,会有一个jdwp线程用来和远程调试服务端进行通信
需要破解的程序启动了JDWP线程,注意这个线程也只有当程序是debug模式下才有的,也就是AndroidManifest.xml中的debug属性值必须是true的时候,也就是一开始为什么一开始时要修改这个值的原因
2)JDWP协议(用于传输调试信息的,比如调试的行号,当前的局部变量的信息等),这个就可以说明,为什么我们在一开始的时候,反编译成java文件,因为为了Eclipse导入能够识别的Java文件,然后为什么能够调试呢?因为smali文件中有代码的行号和局部变量等信息,所以可以进行调试的
3)JDB Server端(远程调试的服务端,一般是有JVM端),就是开启一个JVM程序来监听调试端,这里就可以认为是本地的PC机,当然这里必须有端口用来监听,那么上面的8600端口就是这个作用,而且这里端口是从8600开始,后续的程序端口后都是依次加1的,比如其他调试程序
注:也可以使用adb jdwp
命令查看,当前设备中可以被调试的程序的进程号信息
adb shell dumpsys activity top
打上断点,点击右上方的小绿色虫子
使用F6单步调试,F5单步跳入,F7单步跳出进行操作,发现该APP使用v3变量保存了输入的密码
F7 继续往下走,这里看到调用了MainActivity的getTableFromPic方法,获取一个String字符串,从变量的值来看,貌似不是规则的字符串内容,应该是做了加密处理
接下来又遇到一个关键点getPwdFromPic,从字面意义上看,应该是获取正确的密码,用于后面的密码字符串比对
在这里好像对密码做了一次验证的样子,但密码的内容,依然貌似是一个不规则的字符串,跟之前获取的table字符串内容格式很小,继续往下一步走
调用了系统的Log打印,log的tag就是v6保存的值:lil
看到v3是保存的我们输入的密码内容,这里使用utf-8获取他的字节数组,然后传递给access$0方法,使用F7进入这个方法:
就是把传递进来的字节数组,循环遍历,取出字节值,然后转化成int类型,然后在调用上面获取到的table字符串的chatAt来获取指定的字符,使用StringBuilder进行拼接,然后返回即可
v2就是我们拼接加密之后的内容,如下:
Shift+F8 跳出,查看,我们返回来加密的内容是:123456,也就是说123456=>么广亡门义之,密码就不对了,我们输入的内容加密后是v2跟正确的密码v4对比,显示是错误的
再往下面就是密码比对了… …
梳理一下流程:
access$0
方法,获取加密之后的内容access$0
方法中在调用bytesToAliSmsCode方法,获取加密之后的内容通过上面的分析之后知道了获取加密之后的输入内容和正确的密码内容做比较,那现在我们已经掌握了该APP的:密钥库字符串和正确的加密之后的密码,以及加密的逻辑
破解思路:已知道加密之后的字符组成的字符串,可以通过遍历加密之后的字符串,循环遍历,获取字符,然后再去密钥库找到指定的index,然后再转成byte保存到字节数组,最后用utf-8获取一个字符串,最后得出的结果即是破解的密码,如下:
package com.java.poc;
public class AliCrackme_P {
public static void main(String[] args) {
// 密钥字符串内容(汉字)
String tableStr = "一乙二十丁厂七卜人入八九几儿了力乃刀又三于干亏士工土才寸下大丈与万上小口巾山千乞川亿个勺久凡及夕丸么广亡门义之尸弓己已子卫也女飞刃习叉马乡丰王井开夫天无元专云扎艺木五支厅不太犬区历尤友匹车巨牙屯比互切瓦止少日中冈贝内水见午牛手毛气升长仁什片仆化仇币仍仅斤爪反介父从今凶分乏公仓月氏勿欠风丹匀乌凤勾文六方火为斗忆订计户认心尺引丑巴孔队办以允予劝双书幻玉刊示末未击打巧正扑扒功扔去甘世古节本术可丙左厉右石布龙平灭轧东卡北占业旧帅归且旦目叶甲申叮电号田由史只央兄叼叫另叨叹四生失禾丘付仗代仙们仪白仔他斥瓜乎丛令用甩印乐";
// 程序内部正确的密码(汉字)
String passwordStr = "义弓么丸广之";
char[] passwordIndex = new char[passwordStr.length()];
for (int i=0;i<passwordIndex.length;i++){
int index = tableStr.indexOf(passwordStr.charAt(i)); // 返回passwordStr字符串值,并在字符串中首次出现的位置第一个字符,每次+1循环计算是否匹配APP正确的密码
passwordIndex[i] = (char)(index);
}
System.out.println("Password:"+new String(passwordIndex));
}
}
定位当前界面
adb shell dumpsys activity top
adb shell am start -D -n com.example.simpleencryption/.MainActivity
点击绿色小虫子
接下来就是一步步的调试了,后续步骤跟普通调试差不多
以前旧的方法中会在最初的入口处添加waitForDebugger代码进行调试或者使用adb shell am start -D -n com.example.simpleencryption/.MainActivity
来以Debug模式启动应用,但在新的动态调试方法中 Android Studio(3.x以上版本)即使没有添加android.os.Debug.waitForDebugger(),一样可以打断点调试,但是如果添加了android.os.Debug.waitForDebugger(),一样的可以,启动APP的时候会出现一个Wait debug的对话框,但我这里是出现一个白板(如下图),但不影响实际的调试操作
一般的入口Activity,就是程序启动的地方。查找这个Activity的话,方法太多。例如在AndroidManifest.xml中找到,因为入口Activity的action.MAIN和category是固定的,或者aapt查看apk的内容方式,以及用ADB 查看apk 当前的activity(adb dumpsys activity top
)
添加调试语句设置断点,在主入口Activity的OnCreate函数的第一行添加waitForDebugger代码:
invoke-static {}, Landroid/os/Debug;->waitForDebugger()V
对应的JAVA代码
android.os.Debug.waitForDebugger()
adb shell ps
adb forward tcp:8700 jdwp:1206
其中的“tcp”是之前配置调试环境时指定的端口号,“jdwp”这里指的是我们要调试的程序的进程pid。这里是将本地端口(PC端口)映射到远程端口(手机端口),之后PC端访问8700端口的数据包,会自动转发到手机的1206端口
后续步骤跟直接不插入等待调试代码的方法一样
参考链接:
https://www.cnblogs.com/lsgxeva/p/13490827.html
https://blog.csdn.net/ming54ming/article/details/115049718
http://www.520monkey.com/archives/619
你以为你有很多路可以选择,其实你只有一条路可以走
版权说明:如非注明,本站文章均为 扬州驻场服务-网络设备调试-监控维修-南京泽同信息科技有限公司 原创,转载请注明出处和附带本文链接。
请在这里放置你的在线分享代码