APP渗透测试准备

环境准备

Windows10 X64
Python 3.7
OppoR9st anroid6.0.1
adb
frida
burpsuite+brida插件

安装adb

参考链接:https://wnma3mz.github.io/hexo_blog/2018/01/25/%E7%94%A8ADB%E8%B0%83%E8%AF%95%E5%AE%89%E5%8D%93%E6%89%8B%E6%9C%BA/

下载adb:https://dl.google.com/android/repository/platform-tools-latest-windows.zip

How to Install ADB on Windows, macOS, and Linux:https://www.xda-developers.com/install-adb-windows-macos-linux/

然后解压 配置环境变量。

然后手机USB数据线连接到电脑。记得打开usb调试模式,当然了你想把root了也可以。

adb连接不上的原因:https://mianao.info/2015/12/07/adb%E6%97%A0%E6%B3%95%E8%BF%9E%E6%8E%A5android%E8%AE%BE%E5%A4%87%E7%9A%84%E5%87%A0%E7%A7%8D%E5%8E%9F%E5%9B%A0

附上adb调试命令大全:https://blog.csdn.net/qq_15364915/article/details/52369266

常用adb命令小记:

# 查看所有连接设备
> adb devices

# 进行截图保存在sd卡的根目录下,名字为screen.png
> adb shell screencap -p /sdcard/screen.png
# 将截图发送到本地(当前目录下),也可以发送其他文件
> adb pull /sdcard/screen.png
# 删除本地文件
> adb shell rm /sdcard/screen.png
# 发送电脑里的文件到设备
> adb shell push screen.png /sdcard/

# 进入手机的交互环境,操作类似linux终端,exit或者Ctrl+C退出
> adb shell

# 点击手机屏幕(1000,1000)的位置
> adb shell input tap 1000 1000
# 输入字符串"helloworld",此处不能直接输入中文,且字符串不能有空格
> adb shell input text helloworld
# 滑动屏幕,从(100, 100)到(1000,1000),经历10s(也可以当作长按屏幕来使用)
> adb shell input swipe 100 100 1000 1000 10

# 查看当前运行的App, 这里Windows没有grep所以会运行失败,可以进入先进入交互环境再输入下面去掉"adb shell"命令
> adb shell dumpsys window | grep mCurrentFocus
# 或者
> adb shell dumpsys activity activities | grep mFocusedActivity

# 按下电源键
> adb shell input keyevent 26
# 按下返回键
> adb shell input keyevent 4
# 按下HOME健
> adb shell input keyevent 3
# 点亮屏幕
> adb shell input keyevent 224
# 熄灭屏幕
> adb shell input keyevent 223

# 查看手机安装了哪些App,输出按行输出App的包名
> adb shell pm list packages
# 加"-s"表示只输出系统应用
# 加"-3"表示只输出第三方应用
# 加字符串表示过滤应用名称,当然也可以使用grep

# 安装apk
> adb install <packagename>
# 卸载apk
> adb uninstall <packagename>

# 从桌面启动app
> adb shell monkey -p <packagename> -c android.intent.category.LAUNCHER 1
# 关闭app
> adb shell am force-stop <packagename>
$ adb devices

* daemon not running; starting now at tcp:5037
* daemon started successfully
List of devices attached
33b4c4be        device

说明连接上了.

查看手机cpu版本。 根据内核版本去安装相应的frida。

$ adb shell
shell@R9s:/ $ cat /proc/cpuinfo
Processor       : AArch64 Processor rev 4 (aarch64)
processor       : 0
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32
CPU implementer : 0x41
CPU architecture: 8
CPU variant     : 0x0
CPU part        : 0xd03
CPU revision    : 4
......

根据cpu版本去下载相应frida-server,手机是AArch64的。

安装frida

参考链接:https://www.jianshu.com/p/c349471bdef7

记得python版本要3.7。

3.6的话会找不到dll,看奈沙的blog是之前是3.5出现了这个问题:https://blog.csdn.net/whklhhhh/article/details/79302848

他是3.5要更新到3.6,现在我3.6不行的话更新到3.7然后并没有解决,最后解决方案是再装个frida-tools:

python3.7 -m pip install frida -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

python37 -m pip install frida-tools -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

安装好之后执行

frida-ps  

正常执行之后说明frida已经正常安装。

然后在手机上安装fridaserver

选择arm64的android-server下载:https://github.com/frida/frida/releases/download/12.7.4/frida-server-12.7.4-android-arm64.xz

$ adb push frida-server-12.7.4-android-arm64 /data/local/tmp/frida-server

frida-server-12.7.4-android-arm64: 1 file pushed. 2.5 MB/s (38414672 bytes in 14.544s)

$ adb shell

$ su

R9s:/ # whoami
root
R9s:/ # cd /data/local/tmp/
R9s:/data/local/tmp # chmod 755 frida-server
R9s:/data/local/tmp # ./frida-server

记得手机要root,不然没权限。

然后另开一个cmd。

frida-ps -U

如果hook不上就转发下端口
adb forward tcp:27042 tcp:27042

frida hook实例

以一个ctf题为例来玩一下frida:
https://github.com/ctfs/write-ups-2015/raw/master/seccon-quals-ctf-2015/binary/reverse-engineering-android-apk-1/rps.apk

参考链接:https://blog.csdn.net/zouyuanxc/article/details/80492465

frida理解:https://blog.csdn.net/jiangwei0910410003/article/details/80372118

hook分两种:

第一、Java层代码Hook操作

1、hook方法包括构造方法和对象方法,构造方法固定写法是$init,普通方法直接是方法名,参数可以自己定义也可以使用系统隐含的变量arguments获取。

2、修改方法的参数和返回值,直接调用原始方法通过传入想要修改的参数来做到修改参数的目的,以及修改返回值即可。

3、构造对象和修改对象的属性值,直接用反射进行操作,构造对象用固定写法的$new即可。

4、直接用Java的Exception对象打印堆栈信息,然后通过adb logcat -s AndroidRuntime来查看异常信息跟踪代码。

总结:获取对象的类类型是Java.use方法,方法有重载的话用overload(.......)解决。

第二、Native层代码Hook操作

1、hook导出的函数直接用so文件名和函数名即可。

2、hook未导出的函数需要计算出函数在内存中的绝对地址,通过查看maps文件获取so的基地址+函数的相对地址即可,最后不要忘了+1操作。

总结:Native中最常用的就是内存地址指针了,所以如果要正确的获取值一定要用Memory类作为辅助,特别是字符串信息。

先推荐一个神器:jadx 将apk用jadx打开 直接反汇编出源码.

package com.example.seccon2015.rock_paper_scissors;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import java.util.Random;

public class MainActivity extends Activity implements OnClickListener {
    Button P;
    Button S;
    int cnt = 0;
    int flag;
    private final Handler handler = new Handler();
    int m;
    int n;
    Button r;
    private final Runnable showMessageTask = new Runnable() {
        public void run() {
            TextView tv3 = (TextView) MainActivity.this.findViewById(R.id.textView3);
            if (MainActivity.this.n - MainActivity.this.m == 1) {
                MainActivity.this.cnt++;
                tv3.setText("WIN! +" + String.valueOf(MainActivity.this.cnt));
            } else if (MainActivity.this.m - MainActivity.this.n == 1) {
                MainActivity.this.cnt = 0;
                tv3.setText("LOSE +0");
            } else if (MainActivity.this.m == MainActivity.this.n) {
                tv3.setText("DRAW +" + String.valueOf(MainActivity.this.cnt));
            } else if (MainActivity.this.m < MainActivity.this.n) {
                MainActivity.this.cnt = 0;
                tv3.setText("LOSE +0");
            } else {
                MainActivity.this.cnt++;
                tv3.setText("WIN! +" + String.valueOf(MainActivity.this.cnt));
            }
            if (1000 == MainActivity.this.cnt) {
                tv3.setText("SECCON{" + String.valueOf((MainActivity.this.cnt + MainActivity.this.calc()) * 107) + "}");
            }
            MainActivity.this.flag = 0;
        }
    };

    public native int calc();

    static {
        System.loadLibrary("calc");
    }

    /* access modifiers changed from: protected */
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.P = (Button) findViewById(R.id.button);
        this.S = (Button) findViewById(R.id.button3);
        this.r = (Button) findViewById(R.id.buttonR);
        this.P.setOnClickListener(this);
        this.r.setOnClickListener(this);
        this.S.setOnClickListener(this);
        this.flag = 0;
    }

    public void onClick(View v) {
        if (this.flag != 1) {
            this.flag = 1;
            ((TextView) findViewById(R.id.textView3)).setText("");
            TextView tv = (TextView) findViewById(R.id.textView);
            TextView tv2 = (TextView) findViewById(R.id.textView2);
            this.m = 0;
            this.n = new Random().nextInt(3);
            tv2.setText(new String[]{"CPU: Paper", "CPU: Rock", "CPU: Scissors"}[this.n]);
            if (v == this.P) {
                tv.setText("YOU: Paper");
                this.m = 0;
            }
            if (v == this.r) {
                tv.setText("YOU: Rock");
                this.m = 1;
            }
            if (v == this.S) {
                tv.setText("YOU: Scissors");
                this.m = 2;
            }
            this.handler.postDelayed(this.showMessageTask, 1000);
        }
    }
}

代码逻辑也比较简单,按钮按一下选择石头剪刀布,然后电脑是随机石头剪刀布。赢到1000分即可。

发现是直接调用calc函数运算一下得到最后flag,hook一下方法去调用calc即可。我这里hook的是onclick函数,点击即可弹flag。
exp:

import frida, sys

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

jscode = """
Java.perform(function () {
    var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
    MainActivity.onClick.implementation = function (v) {
        send("Hook Start...");
        var returnValue = this.calc();
        send("Return:"+returnValue);
        var result = (1000+returnValue)*107;
        send("Flag:"+"SECCON{"+result.toString()+"}");
    }
});
"""

process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()

第二种方法就是hook结果,将分数改为999,然后出拳必赢,hook到onclick,再点击触发,即可弹flag


import frida, sys

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

jscode = """
Java.perform(function () {
    var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
    MainActivity.onClick.implementation = function (v) {
        send("Hook Start...");
        this.onClick(v);
        this.n.value = 0;
        this.m.value = 2;
        this.cnt.value = 999;
        send("Success!")
    }
});
"""

process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()

第三种方法就是去搞so文件了,从native层hook来获取calc的返回值。

以上是java层的hook。接下来玩一下native层的hook。

附上一个批量提取apk中so文件的脚本:

# export_so.py

#!/usr/bin/env python
# coding=utf-8
import zipfile
import os

path = "./"
so_path="./so/"
apklist=os.listdir(path)
for APK in apklist:
    if APK.endswith(".apk"):
        portion = os.path.splitext(APK)
        apkname = portion[0]
        abs_so_path=os.path.join(so_path,apkname) 
        abs_zipAPK_path=os.path.join(path,APK)
        z = zipfile.ZipFile(abs_zipAPK_path,'r')
        solists=[]
        for filename in z.namelist():
            if filename.endswith(".so"):
                sofileName = os.path.basename(filename)
                soSource = os.path.basename(os.path.dirname(filename))
                storePath=os.path.join(abs_so_path,soSource) 
                if not os.path.exists(storePath):
                    os.makedirs(storePath)
                newsofile=os.path.join(storePath,sofileName)
                f = open(newsofile,'w')
                f.write(z.read(filename))

libcalc.so是我们的目标。

丢到ida里面去看看。

拿到函数名是 Java_com_example_seccon2015_rock_1paper_1scissors_MainActivity_calc
然后去写脚本hooknative层即可。

exp:

import frida, sys
# export native function
native_hook_code = """
Java.perform(function(){
    send("Running Script");
    var exports = Module.findExportByName("libcalc.so","Java_com_example_seccon2015_rock_1paper_1scissors_MainActivity_calc");
    send("so native pointers:"+exports);
    Interceptor.attach(exports,{
        onEnter: function(args){ 
            //send("so function args is: " +args[0]+","+args[1]+","+args[2]);
            send("123");
        },
        onLeave: function(retval){
            send("so result value is :"+retval);
        }
    });
});
"""

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

process = frida.get_device_manager().enumerate_devices()[-1].attach("com.example.seccon2015.rock_paper_scissors")
script = process.create_script(native_hook_code)
script.on('message', on_message)
script.load()
sys.stdin.read()
D:\Frida>python37 rps_native_exp.py
[*] Running Script
[*] so native pointers:0xe8473c75
[*] 123
[*] so result value is :0x7

同样拿到返回值是7。然后进行运算

if (1000 == MainActivity.this.cnt) {
    tv3.setText("SECCON{" + String.valueOf((MainActivity.this.cnt + MainActivity.this.calc()) * 107) + "}");
}

///也就是(1000+7)*107结果是107749,和之前的结果一样。

安装brida

其实brida就是一个方便在frida和burpsuite通信的插件而已,核心还是frida。

brida直接在bapp store安装即可。

brida实例

不想用实战的apk来说,参考:https://www.anquanke.com/post/id/86567

脱壳

查看apk包名

aapt d badging xx.apk

脚本脱壳:

python37 dump2dex-frida.py com.xxx.xxx

com.xxx.xxx是包名

然后在 /data/data/com.freelynet.moiblecrm/目录下找到dex文件,mv到/sdcard 文件夹下

然后 adb pull出来

然后 d2j-dex2jar.bat xxx.dex
就拿到了jar包

然后jdax打开源码审计即可。