欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

安卓逆向新人练手项目

程序员文章站 2022-03-23 10:37:22
前言这段时间开始接触安卓逆向,说一下我的大致的学习步骤:学一个新知识之前需要对这个知识有一定的概念,比如js逆向,肯定要抓接口,然后看参数,搜参数,打断点等。那么拿到一个APP下一步要干些什么也需要有一些概念。如何积累概念:多看一些逆向的文章,某些地方没看懂不重要,重要的是流程你有个大概了。找一些简单的例子练练手,比如吾爱破解论坛的:https://www.52pojie.cn/thread-408645-1-1.html。另外吾爱破解的移动安全区也有很多的文章可以看看。还有一些Crackme拿来练...

前言

这段时间开始接触安卓逆向,说一下我的大致的学习步骤:

  1. 学一个新知识之前需要对这个知识有一定的概念,比如js逆向,肯定要抓接口,然后看参数,搜参数,打断点等。那么拿到一个APP下一步要干些什么也需要有一些概念。如何积累概念:多看一些逆向的文章,某些地方没看懂不重要,重要的是流程你有个大概了。
  2. 找一些简单的例子练练手,比如吾爱破解论坛的:https://www.52pojie.cn/thread-408645-1-1.html。另外吾爱破解的移动安全区也有很多的文章可以看看。还有一些Crackme拿来练手,看看评论和描述,先挑简单的尝试。
  3. 看一些网上的课,免费的付费的都行。没什么推荐的,我也是刚接触不太懂哪个好。
  4. 接着你会觉得,每下载一个APP都想抓个包,然后把它拖到jeb反编译一下,这样久了就越加熟练了。

涉及软件

模拟器推荐逍遥模拟器,因为雷电4无法抓包,夜神无法安装xposed,只有逍遥啥毛病没有,我用的去广告绿色版。下载链接:https://www.52pojie.cn/forum.php?mod=viewthread&tid=1353837&highlight=%E5%D0%D2%A3

JEB和Android Killer等:https://down.52pojie.cn/Tools/Android_Tools/

抓包工具我比较喜欢charles:https://www.52pojie.cn/forum.php?mod=viewthread&tid=1350618&highlight=charles

新人练手项目

样本APP:https://wwx.lanzoui.com/iEatQmeg8je

这个APP属于比较简单的逆向,没有混淆没有加壳,虽然加密调用了so,so里面也是用的java加密库。

准备工作

打开模拟器,安装APP。在模拟器上安装charles证书,因为Android7.0不在信任用户安装的证书,所以https可能无法正常抓到包或者无法正常显示数据包。我的解决办法:先正常安装证书,然后将安装好的证书直接移动到系统证书目录去。

  1. 证书获取:charles->help->SSL proxying->save …(保存到一个目录改个名字就行)
  2. 复制到模拟器:
    adb devices 查看adb设备
    adb connect 127.0.0.1:21503 连接逍遥模拟器
    adb push 电脑证书目录 /sdcard 将证书复制到模拟器sd卡根目录
  3. 安装证书:打开设置->安全->从sd卡安装证书
  4. 移动证书到系统目录:下载安装es文件管理器或者re管理器将安装的证书移动到系统目录,用户证书目录:/data/misc/user/0/cacerts-added。系统证书目录:/system/etc/security/cacerts。
  5. 关闭模拟器重新打开,设置代理后,当模拟器有http请求时,charles就会弹出验证,问你是否允许charles记录来自某某ip的包,点允许就会有数据包了。代理设置电脑局域网IP即可,并不需要模拟器使用桥接模式。
  6. 如果还是没有包,可以看看proxy->SSL proxying settings里Enable SSL proxying有没有勾选,并且在include里add一个*:*(host和port都填*就行)
  7. 还是没有的话就只能百度了

温馨提示:在抓APP的时候可以关闭charles抓Windows的包,点击proxy->Windows proxy就可以停止抓Windows的包,当然你在Windows设置里取消全局代理一样的效果。

打开APP,查看数据包

只看首页一些栏目列表的数据包,随便搜索一下首页出现的文章的标题就能找到是哪条请求了,

URL:https://v6-gw.m.163.com/nc/api/v1/feed/dynamic/normal-list

参数有很多,可以多刷新几个请求看看哪些参数是变化的,测试发现就ts和sign是变化的,不过devId、devIdOD、lat和lon明显都有可能被加密,我们先处理sign,ts明显是时间戳。另外请求头也有很多加密值。

打开jeb,分析代码

将apk拖入到jeb的窗口,然后一直点是就行。接着直接在反编译的smali代码里搜索链接中的一部分比如normal-list。Ctrl+f是搜索快捷键,搜索的时候勾选环绕搜索和区分大小写。

接着你就会发现只搜索到了一个地方,你再次点击寻找的时候还是在原来的地方。点击normal-list所在的那行代码右键->解析,就会反编译出java代码。如图:

安卓逆向新人练手项目
安卓逆向新人练手项目
g.d很有可能就是链接的前一部分了,那么com.netease.newsreader.common.constant.g.l.c就是完整的链接了,点击c,c的背景色会变成黄色,说明被选中了,然后右键->交叉引用,就能知道这个变量在什么地方被调用了。
安卓逆向新人练手项目
可以看到有两个地方引用了这个变量,点进去看会发现第二个其实就是上面的代码,所以第一个应该就是发送请求的位置了。点进去看看代码,
安卓逆向新人练手项目
代码很长,就看后面那一部分,前面的应该只是判断参数是否正常。正常就执行后面那个,能发出请求说明肯定正常。

a.a(b.b(l.c, new NGRequestVar().setSize(Integer.valueOf(arg4)).setFn(Integer.valueOf(arg5)).setOffset(Integer.valueOf(arg3)).addExtraParam(new c("from", arg2))), arg6);

把代码分开来看

t1 = new NGRequestVar().setSize(Integer.valueOf(arg4)).setFn(Integer.valueOf(arg5)).setOffset(Integer.valueOf(arg3)).addExtraParam(new c("from", arg2)));
t2 = b.b(l.c, t1);
a.a(t2, arg6)

t1应该就是一个请求参数的容器对象,可以点进去看看,addExtraParam是添加额外参数的方法,其他几个方法有啥用就不分析了。

接着看b.b这个方法,双击点进去看看。
安卓逆向新人练手项目

看到这些参数就知道这就是拼接参数的地方,而我们要找的ts和sign都在这里生成的。ts确实是10位的时间戳,虽然他还调用了一个com.netease.newsreader.common.utils.a.a.a,但是抓包的结果和System.currentTimeMillis() / 1000L;是一样的,就不用关心他里面的逻辑了,估计就是整型转字符串的方法。

String v0 = d.a();
long v1_4 = System.currentTimeMillis() / 1000L;
String v0_1 = v0 + v1_4;
sign = b.a(com.netease.newsreader.framework.e.a.c.b(v0_1))

而点进去d.a()可以看到v0,就是下面的代码生成的也就是v0_1

public static String a() {
  Object v0 = d.v.get("nrcommon_sys_1");
   if(v0 != null && ((v0 instanceof String))) {
       return (String)v0;
   }

   String v0_1 = ((IGalaxyApi)b.a(IGalaxyApi.class)).a(Core.context());
   if(!TextUtils.isEmpty(v0_1) && (TextUtils.isEmpty(a.a()))) {
       a.a(v0_1);
   }

   d.a("nrcommon_sys_1", v0_1);
   return v0_1;
}

IGalaxyApi是自定义的一个类,点进去看b.a和b.a().a这两个方法都挺麻烦的,就不看了下去了。等下可以直接调试一下看看这个值会不会变,上面的代码将他放到一个容器里去获取,如果容器没有才生成,虽然不知道具体算法,但这说明这个值在APP启动之后就是个固定值了。

先把他当成固定值来看,也就是说v0_1是一个固定的字符串+时间戳。我们在看c.b这个方法

 public static String b(String arg2) {
        if(TextUtils.isEmpty(arg2)) {
            return arg2;
        }

        try {
            return c.a(MessageDigest.getInstance("MD5").digest(c.a(arg2, Charset.forName("UTF-8"))), false);
        }
        catch(NoSuchAlgorithmException v2) {
            throw new AssertionError(v2);
        }
    }

应该只是做个简单的md5,至于到底是不是可以调试一下看看输入和输出是不是md5的结果。最后就是b.a这个方法了。

private static String a(String arg0) {
        return com.netease.newsreader.support.utils.k.b.a(Encrypt.getEncryptedParams(arg0));
    }

com.netease.newsreader.support.utils.k.b.a这个方法只是做个简单的url编码,可以不用看,关键就是Encrypt.getEncryptedParams(arg0)
点进去看,关键代码就是这个:
Encrypt.encrypt(arg0, arg1, arg2);arg0是Core.context(),不清楚是什么,arg1就是上面传入的参数,arg2是0.而你在追encrypt方法时发现:

 private static synchronized native byte[] encrypt(Context arg0, String arg1, int arg2) {
    }

怎么什么都没有,百度了一下发现这是个native方法,方法逻辑在so里面,在前面有段System.loadLibrary("random");就是加载so文件。文件名叫librandom.so。可以在jeb左侧的工程管理器看到
安卓逆向新人练手项目
Libraries目录下的就有(arm64-v8a和armeabi这两个里面的so基本逻辑是一样的,选择哪个都行),可以选中这个so右键导出,然后用IDA查看,左侧红框对应的就是java的函数。
安卓逆向新人练手项目
这样反编译的c代码基本看不懂,不过可以看到一些关键的字符串。当然也可以导入jni.h头文件,可以让代码更像代码。具体参考:https://www.52pojie.cn/thread-732955-1-1.html。

AES/ECB/PKCS7Padding,javax/crypto/Cipher,doFinal这些字符足以说明他是调用了java的加密库,用的AES的加密。既然是调用的java加密库,我们可以用frida hook一下java的库,来获取key,然后验证一下结果对不对。后面测试发现确实就是这样一个加密。

jeb调试代码

准备工作
上面只是静态分析了代码,虽然知道他用的什么算法,但不确定他有没有魔改算法,或者做些手脚。所以我们需要获取参数的中间值来验证一下。这里介绍一下jeb的调试。

要想APP能被调试,需要APP AndroidManifest文件中包含 android:debuggable=“true”,而一般的APP肯定不会有这个,需要修改后重打包,很麻烦一般不会去这么做。这篇文章说明了所有开启调试的方法,写的很好:https://blog.csdn.net/qq_38851536/article/details/100026480。

我采用的是mprop来修改ro.debuggable这个值,不过模拟器关闭之后需要重新修改。
mprop x86版本:https://github.com/jedy/mprop
adb push mprop /data/local/tmp将mprop复制到模拟器的这个目录
adb shell
su
cd /data/local/tmp
chmod 755 mprop
./mprop ro.debuggable 1就可以了

为了方便我直接使用es文件浏览器mprop移动到了/system/bin下了,这样就可以全局使用了,不用cd到目录。

下断点

String v0_1 = v0 + v1_4;
if(!TextUtils.isEmpty(v0_1)) {
     arg6.add(new com.netease.newsreader.framework.d.a.c("sign", b.a(com.netease.newsreader.framework.e.a.c.b(v0_1))));
 }

我们主要是需要v0_1的值,和c.b计算之后的值,来验证一下c.b是不是普通的md5,还有v0这个值是不是固定的。右键String v0_1 = v0 + v1_4;这行代码点击解析就会跳到smali代码,
安卓逆向新人练手项目
对照一下java代码很容易知道v0_1和c.b所在的位置。所以我们在图上框中的两行下面一行下两个断点,断点快捷键为Ctrl+b。左侧有红点表示断点下成功了。
安卓逆向新人练手项目
这个浅蓝色背景是程序断在这一行的时候才会有的。图中我还没附加进程,应该是上次打断点留下的bug,不用管。

确认一下adb devices下有模拟器设备,没有的话adb connect 127.0.0.1:21503重新连接一下。然后打开APP,接着点击下图红框的那个按钮。

安卓逆向新人练手项目
会出现这样一个界面
安卓逆向新人练手项目
双击红框的那行就是附加了这个进程,这个是APP的包名,其他三个应该是这个APP的一些服务。

附加之后在模拟器里操作APP向下滑动加载新的文章就会触发断点。可能会多次出现下图这种情况,点击等待即可。
安卓逆向新人练手项目
程序断下之后,我们关注的是v0的值,又知道它的数据类型是string,可以直接把int改成string,复制他的值备用,复制完记得改回int
安卓逆向新人练手项目
点击第二个按钮运行,跳到下一个断点。同样改成string复制值之后改回int。
安卓逆向新人练手项目
这样我们就得到两个值,计算一下上面的值md5确实就是下面的值,这也就验证了c.b只是做了md5的计算:
string@13512:“CQlkYTE0MDdjZmM5NTYyZjUzCTYwNTkxOTMw1614743514”
string@13513:“683c1311ef69f993fe29df30d7508fcf”

安卓逆向新人练手项目
去抓包工具看一下sign,不知道哪条请求可以对比一下后面的时间戳。也可以直接在smali里面下断点获取。如何知道683c1311ef69f993fe29df30d7508fcf怎么得到sign(s1DyoPiJ5EPZK8gVtknxrQbP0ITFkr7O102aFhxwfIN48ErR02zJ6/KXOnxX046I)呢?

首先我们已经知道了他是AES/ECB/PKCS7Padding的加密模式,可以百度到这个加密模式只需要秘钥key,不需要iv。所以只要获取一下key,验证结果对不对了。

frida hook AES获取key

frida的环境就不说了,百度一下很简单的,pip装个包,在下载个x86版本的frida-server复制到模拟器改个权限运行就行。

直接拷贝官方文章中的一段代码,做些修改就可以了:https://frida.re/docs/examples/android/
安卓逆向新人练手项目
修改之后,因为key是个java的对象,需要转成js可以输出的形式,一般是16进制或者base64:

import frida, sys

def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

jscode = """
function bytesToHex(arr) {
    var str = '';
    var k, j;
    for (var i = 0; i < arr.length; i++) {
        k = arr[i];
        j = k;
        if (k < 0) {
            j = k + 256;
        }
        if (j < 16) {
            str += "0";
        }
        str += j.toString(16);
    }
    return str;
}
Java.perform(function () {
  var Cipher = Java.use('javax.crypto.Cipher');
  var Exception = Java.use('java.lang.Exception');
  var Log = Java.use('android.util.Log');

  var init = Cipher.init.overload('int', 'java.security.Key');
  init.implementation = function (opmode, key) {
    var result = init.call(this, opmode, key);
    var bytes_key = key.getEncoded();
    console.log('Cipher.init() opmode:', opmode, 'key:', bytesToHex(bytes_key));//
    //console.log(stackTraceHere());

    return result;
  };

  function stackTraceHere() {
    return Log.getStackTraceString(Exception.$new());
  }
});
"""

process = frida.get_usb_device().attach('com.netease.newsreader.activity')
script = process.create_script(jscode)
script.on('message', on_message)

script.load()
sys.stdin.read()

将上面的代码命名为hookaes.py,然后命令行运行python hookaes.py,在APP里面翻页查看输出。
安卓逆向新人练手项目
有多个key,但是只有最后两个是我翻页的时候才出现的,其他的应该是另外的接口使用的。
我们找个在线AES加密的网站验证一下这个key是不是对的。注意: key是16进制(hex)格式的,不是字符串

https://the-x.cn/cryptography/Aes.aspx
安卓逆向新人练手项目
结果和上面的是一样的,也就是说,到现在sign的加密已经分析完了。

frida hook获取中间值

上面获取v0_1是使用jeb进行调试来获取的,其实用frida获取更简单。
arg6.add(new com.netease.newsreader.framework.d.a.c("sign", b.a(com.netease.newsreader.framework.e.a.c.b(v0_1))));

想要获取v0_1和c.b的执行结果,直接hook c.b。代码如下:

import frida, sys

def on_message(message, data):
    if message['type'] == 'send':
        print(message['payload'])
    else:
        print(message)

jscode = """
Java.perform(function () {
  send("注入成功!");
  var c = Java.use('com.netease.newsreader.framework.e.a.c');
  var b = c.b;
  b.overload('java.lang.String').implementation = function (v) {
    send(v);
    var result = b.call(this, v);
    send(result);
    return result;
  };

});
"""

process = frida.get_usb_device().attach('com.netease.newsreader.activity')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()

打开APP之后,运行这个Python脚本,然后在APP滑动加载即可显示参数和返回值,也就是我们要的。

按道理来说我也可以hook b.a来获取参数和返回值,但是试了一下没有效果,也不知道什么原因。如果有知道的,希望能指教下。

本文地址:https://blog.csdn.net/Qwertyuiop2016/article/details/114290988