app逆向示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 1、抓包分析是否有需要逆向的加密字段 2、查壳分析是否加固 3、查看界面元素,看是否是原生开发的app,因为不同形式app分析方法不一样 4、传统关键代码定位方法 通过控件绑定的事件代码,来一步步定位 控件id、setOnClickListener 人肉搜索字符串 搜索链接 搜索加密的参数名:当有多个可以关键处,如何确定是哪一个?动态调试、Hook 搜索同一个数据包中,没有加密的参数名 5、关键代码快速定位 跑一下自吐算法 hook常用系统函数,如果app有调用就打印函数栈 在自制的沙盒中运行,打印app运行过程中的指令、函数调用关系等 6、逆向分析不是完全静态分析明白了才去hook,实际上是边分析边hook
|
app界面控件查看
逆向的第一步就是先让app可以运行,app可能存在手机类型检测,或者没有X86架构的so,模拟器运行不了。或者存在抓包检测,不抓包可以用,有抓包用不了
因为是简单操作,以上这些都不考虑,现在的情况是抓到包了,包含加密后的数据,如果是明文也没必要逆向了,这个时候就需要考虑怎么解密了
首先分析其开发方式,开发方式不同分析的方法也不同

通过其界面来分析开发方式,利用Android Studio中的一个的SDK下tools中的一个工具

打开后选择截图,分析页面

1 2 3 4 5 6 7 8 9 10 11 12 13
| 查看界面控件的作用 原生方式开发的app,使用Java和C++开发,加密用的是Java和C++ H5的app,使用Webview控件加载网页,加密用的JS app自动化测试,需要知道控件的ID,或者说需要定位到控件 用uiautomatorviewer.bat查看 Android SDK根 目录\tooIs\bin 这个方式也不是一直有效的,在app开发中可以作禁止截屏,那么这个工具就用不了了 禁止截屏:activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); 解除禁止:activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); // 如果非要用,学会hook之后,把这个方法hook掉即可,或者找个地方执行clearFlags把这个常量值清除掉也可以
|
这个禁止截屏的方法有很多种,这个只是一个简单的示例,也可以在so层之间调用Java方法禁止截屏,或者找更底层的方法来实现这个功能。不一定就是这一种
app壳
反编译之前先使用软件检测一下是否有壳
其实也可以之间反编译,因为加壳之后反编译结果不正常,很容易看出来,很明显的代码少,甚至没有代码
人肉搜索
搜索控件绑定事件
登录操作产生的密文
按钮ID btn_login

反编译爆搜id
findViewById
是一种寻找id的方式,下面两个是QQ登录和微信登录的按钮id

追进去

继续追


返回为true之后,执行上一个函数的login方法
搜索关键字符串
可以搜索这个相关链接,搜不到就缩小长度搜,搜 user/login 就出来了

也可以从其他字符串入手,比如这个 encrypt ,或者是登录失败的提示。因为登录逻辑一般在同一个地方的,找到一个就都找到了
搜索结果过多的时候,优先看和包名有关的类

传统搜索的弊端
通过人肉搜索是放在最后才用的,因为暴力搜索很费时费力还可能搜不到,如果开发控件的时候不将登录的逻辑写在同一个文件内,然后各种调用,各种接口就很难找到真正的功能方法。如果字符串不组合,使用相加的形式,那么单独搜索一个字符串的话可能会有上千条数据,一一分析起来非常麻烦。甚至可以做加密处理,搜索字符串根本搜不到。例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| public class login_0 extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
try { String password = "12345678";
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(password.getBytes());
byte[] digest = md5.digest();
HashMap<String,String> hashMap =new HashMap<>(); hashMap.put("password", Base64.encodeToString(digest, 0)); Log.d("xiaojianbang",hashMap.toString());
} catch (Exception e) { e.printStackTrace(); } }\ }
public class login extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
try { String password = "12345678";
Class<?> a = Class.forName(dd("amF2YS5zZWN1cml0eS5nZYWdlRGlnZXN0")); Method getInstance = a.getMethod(dd("Z2v0sW5zdGFuY2U"),String.class); Object b = getInstance.invoke(a, dd("TUQ1"));
Method update = a.getMethod(dd("dXBkYXRl"), byte[].class); update.invoke(b, password.getBytes());
Method c=a.getMethod(dd("ZGlnZXNo")); byte[] d = (byte[])c.invoke(b);
HashMap<String,String> hashMap =new HashMap<>(); hashMap.put(dd("cGFzc3dvcmQ="), Base64.encodeToString(d, 0)); Log.d("xiaojianbang",hashMap.toString());
} catch (Exception e) { e.printStackTrace(); } } public String dd(String cipherText) { byte[] decode = Base64.decode(cipherText, 0); return new String(decode); } }
|
这两种方法输出的结果是一样的,但是反编译的结果截然不同


只需要对字符串进行一个反射和加密就搜索不到了,通过反射将加密使用的方法名给隐藏起来,通过base64将字符串加密,这样一来,就大大降低了搜索找到的可能性。
常见防护
1 2 3 4 5 6 7 8 9 10 11 12
| 1、反编译一个app搜不到关键字: a) HTML5的app (HTML5的关键信息通常在js里面) b) 字符串被加密了 (通过算法或者字符串拼接等手段) c) 反射调用的相关类 d) app被加固了 (加壳) e) 动态加载的dex (jadx会将所有的dex文件反编译,但是如果有dex文件加密了,经过解密之后动态加载,这个时候是反编译不出来的。一般在分析的时候都会将重点放到classes.dex上,很容易忽略其他dex) f) 热修复 (在安卓中,有骚操作是app运行起来之后动态修改代码,静态分析不出来,这个时候就必须需要hook获取内存中的数据了) 2、简单字符串加密的实现 dex的字符串加密可以用代码自动实现,需要使用到dexlib2这个库 3、反射相关类的实现
|
Hook
1 2 3 4 5
| hook可以用来做什么 可以用来判断app执行某个操作的时候,是否经过我们的怀疑的这个函数 可以用来修改被hook函数的运行逻辑 可以用来在运行过程中,获取被hook的函数传入的具体的参数和返回值 可以用来主动调用app中的某些函数
|
hook环境之前就搭建好了,开发js文件的话直接使用VScode,需要调试的话下载node.js
关键代码定位
这个时候就需要hook了,因为无论你怎么加工包装,都是用了系统定义好了的 MessageDigest.getInstance
方法,将传入的内容打印一下,就可以直接到得明文