移动安全入门
编写安卓程序
xml界面类似于web的界面 layout为布局,wrap_content为填充布局,表示填充满
<?xml version="1.0" encoding="utf-8"?>
<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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="用户名" />
<EditText
android:id="@+id/editTextTextPersonName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:inputType="textPersonName"
android:text="User" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="密码" />
<EditText
android:id="@+id/editTextTextPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:inputType="textPassword" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="登陆" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
登陆按钮监听代码
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
EditText Name;
EditText Pass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Name = findViewById(R.id.editTextTextPersonName);
Pass = findViewById(R.id.editTextTextPassword);
Button Login = findViewById(R.id.button);
Login.setOnClickListener(new View.OnClickListener() { //监听有没有点击按钮控件 如果点击了就会执行onClick函数
@Override
public void onClick(View view) {
check(Name.getText().toString().trim(), Pass.getText().toString().trim()); //调用check函数
}
});
}
public void check(String name,String pass) //自定义函数check 这里用来检查用户名和密码是否是hfdcxy和1234
{
if(name.equals("hfdcxy")&&pass.equals("1234"))
{
Toast.makeText(MainActivity.this,"登录成功", Toast.LENGTH_SHORT).show();//弹框
}
else
Toast.makeText(MainActivity.this,"登录失败", Toast.LENGTH_SHORT).show();//弹框
}
}
课后作业
使用Intent来传递数据
登陆界面
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
EditText Name;
EditText Pass;
TextView test;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test=findViewById(R.id.textView4);
//test.setText("123");
Name = findViewById(R.id.editTextTextPersonName);
Pass = findViewById(R.id.editTextTextPassword);
Button Login = findViewById(R.id.button);
Login.setOnClickListener(new View.OnClickListener() { //监听有没有点击按钮控件 如果点击了就会执行onClick函数
@Override
public void onClick(View view) {
go_to();
}
});
}
public void go_to()
{
Intent intent=new Intent(MainActivity.this,MainActivity2.class);
//intent = Intent(this,MineOrdersListActivity::class.java)
intent.putExtra("show_name",Name.getText().toString());
startActivity(intent);
}
}
跳转界面
//import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity2 extends AppCompatActivity {
TextView Name1;
EditText Name2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
//final View rowView = inflater.inflate(R.layout.activity_main, null);
Intent intent=getIntent();
int i = intent.getIntExtra("show_name",0);
String show_name=intent.getStringExtra("show_name");
Name1=(TextView) findViewById(R.id.textView3);
Name1.setText(show_name);
}
}
破解安卓程序
(1)if-eqz vA, vB, :cond_**" 如果vA等于vB则跳转到:cond_**
(2)if-nez vA, vB, :cond_**" 如果vA不等于vB则跳转到:cond_**
回编译遇到错误apktools版本问题同时也可能存在frame_work框架问题
https://blog.csdn.net/Andrio/article/details/103887045
调试smali代码
版本Andriod4.1.2,和正常的调试方法不一样记录一下
Andriod studio 打开apk,此时出现的都是smali文件,并且提示需要附加java源代码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KrM632Ci-1618733777063)(https://raw.githubusercontent.com/p1aymaker/picture/master/20210122200619.png)]
利用命令得到java文件
jadx.bat -d java_source app.apk
之后就可以对java文件下断点调试了
手机连接上之后,使其进入调试状态
adb shell pm list package
adb shell am start -D -n com.example.myapplication/com.example.myapplication.MainActivity
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L3Auhye0-1618733777066)(https://raw.githubusercontent.com/p1aymaker/picture/master/20210122201928.png)]
点击此即可开始调试java代码
smali代码中插入log
从int转string的log插入
const-string v0, "\u8fd9\u4e2a\u503c\u662f"
invoke-virtual {p0}, Lhfdcxy05/com/myapplication/MainActivity;->fun1()I
move-result v1
invoke-static {v1}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v1
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
string类型的log插入
const-string v0, "\u8fd9\u4e2a\u503c\u662f"
invoke-virtual {p0}, Lhfdcxy05/com/myapplication/MainActivity;->fun3()I
move-result-object v1
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
Andriod中编写第一个so
https://blog.csdn.net/luo_boke/article/details/107306531
参考文章
课后作业
package com.example.soprogram;
import androidx.appcompat.app.AppCompatActivity;
s
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
int a=10;
int b=12;
tv.setText(String.valueOf(add(a,b)));
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native int add(int a,int b);
}
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jint JNICALL
Java_com_example_soprogram_MainActivity_add(
JNIEnv* env,
jobject /* this */,
jint a,
jint b
) {
return a+b;
}
javac已经替代了javah
要进到java目录下
javac -h . myJNI.java
IDA破解so
首先利用apktool进行解包
apktool d 解包。
apktool_2.5.0.jar b -o new.apk app-debug
打包完毕之后用Andriod Killer进行签名
但是有点奇怪的是加载的是armeabi-v7a目录下的lib
不过我的cpu是arm-v8的。。
1|equuleus:/ $ cat /proc/cpuinfo
Processor : AArch64 Processor rev 12 (aarch64)
processor : 0
BogoMIPS : 38.40
IDA爆破签名验证
签名验证:通过验证签名是否改变来判断程序是否被破解
so里对外调用函数在IDA种也可以找到,可以通过此定位关键点
尝试通过导入jni.h来设置结构体增强.so代码可读性
BNE: 数据跳转指令,标志寄存器中Z标志位不等于零时, 跳转到BNE后标签处 xx D1
BEQ: 数据跳转指令,标志寄存器中Z标志位等于零时, 跳转到BEQ后标签处 xx D0
xx=相差指令*2+4
IDA动态破解验证登陆
AndroidManifest.xml查看程序入口 这里 android:debuggable="true"表示此apk可以动态调试
首先将andriod_server push到手机/data/local/tmp下
端口转发之后开始调试,发现只存在system/bin/sh文件原因是真机没root权限
需要用夜神模拟器连接,不过夜神模拟器架构为x86,因此需要android_x86_server,同时IDA attach要选择Remote Linux Debugger
首先连接夜神模拟器
adb connect 127.0.0.1:62001
关闭Andriod_server
kill -9 pid
端口转发
adb forward tcp:23946 tcp:23946
静态分析反调试
dalvik是执行的时候编译+运行,安装比较快,开启应用比较慢,应用占用空间小
ART是安装的时候就编译好了,执行的时候直接就可以运行的,安装慢,开启应用快,占用空间大
fd文件描述符
- 端口23946检测
- 进程遍历检测
- 父进程检测是否包含zygote
- 自身进程名检测,是否为com.xxx的格式
- 线程数量检测,一般有十几个线程,如果自己加载就一个
- /proc/pid/fd/检测文件数量
- 信号处理机制,IDA会截获信号
- tracepid:调试该进程的pid
- 自带API反调试
- ptrace自身看是否成功
- hash检测
- BKPT指令查询
- 读取进程的status或stat来检测tracepid,TracePid字段就是跟踪进程的pid.调试状态下tracepid不为0
- 单步调试陷阱,有点像Windows下的Debugger Blocker
- 花指令
- 时间差检测
- 进程信息查询
第一种:
/proc/pid/status
/proc/pid/task/pid/status
TracerPid非0
statue字段中写入t(tracing stop)
第二种:
/proc/pid/stat
/proc/pid/task/pid/stat
第二个字段是t(T)
第三种:
/proc/pid/wchan
/proc/pid/task/pid/wchan
ptrace_stop
- 可以通过Inotify系列api来监控mem或pagemap的打开或访问事件, 一旦发生时间就结束进程来阻止dump。
没有找到关键函数名称可能是由于JNI_OnLoad来动态注册
找到JNI_OnLoad函数,导入头文件结构体
此时可以看到一处反调试为遍历进程反调试
动态注册函数在.data.rel.ro.local 区段中
找到一处端口检测反调试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uOuBInfB-1618733777075)(https://raw.githubusercontent.com/p1aymaker/picture/master/20210128025853.png)]
再看下.init_array创建了一个线程,线程功能如下
同样是一处反调试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sRLCGB5q-1618733777076)(https://raw.githubusercontent.com/p1aymaker/picture/master/20210128030158.png)]
xpose模块
参考:
https://www.kancloud.cn/a6260362/study-android-and-web/1646986
分别用不同版本来进行Hook
Android 4.0.3-4.4:Dalvik
Android 5.0以上:ART
其中踩了一个坑load moudle出了问题导致Hook失败
https://blog.csdn.net/OneT1me/article/details/93968206
需要将gradle中的东西给注释掉
// implementation files('libs\\api-82.jar')
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'
- 编译:javac HelloWorld.java
- 打包:一个包内的所有class文件打包为一个jar文件
conpileonly:只在编译时有效,不会参与打包
implementation:依赖的库将会参与编译和打包,该依赖方式所依赖的库不会传递,只会在当前module中生效
public class HookMain implements IXposedHookLoadPackage {
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
//固定格式
findAndHookMethod(
"android.telephony.TelephonyManager", //要hook的包名+类名
lpparam.classLoader, //classLoader固定
"getDeviceId", //要hook的方法名
//方法参数 没有就不填
new XC_MethodHook() {
@Override
//方法执行前执行
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
}
//方法执行后执行,改方法的返回值一定要在方法执行完毕后更改
protected void afterHookedMethod(MethodHookParam param)
throws Throwable {
param.setResult("355888888888888");
}
}
);
}
}
一般是先运行程序,再加载Xposed模块
游戏进行Hook
import android.content.Context;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import de.robv.android.xposed.XposedHelpers;
public class HookMain implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
// XposedBridge.log("=========Loaded app: " + lpparam.packageName);
if (lpparam.packageName.equals("com.jimmy.beauty.pick")) {
XposedHelpers.findAndHookMethod("com.jimmy.beauty.pick.Util", lpparam.classLoader, "getMoney",
Context.class,
XC_MethodReplacement.returnConstant(100));
}
}
}
Xposed原理
Android进程的父进程——Zygote(孵化)进程,该进程的启动配置在/init.rc脚本中,而Zygote进程对应的执行文件是/system/bin/app_process
是用自己实现的app_process替换掉了系统原本提供的app_process**,加载一个额外的jar包,然后入口从原来的com.android.internal.osZygoteInit.main()
被替换成了de.robv.android.xposed.XposedBridge.main()
检测
https://tech.meituan.com/2018/02/02/android-anti-hooking.html
PackageManager
的API来遍历系统中App的安装情况来辨别是否有安装Xposed Installer
相关的软件包- 自造异常读取栈检测是否存在Xposed调用方法
- java方法有没有变为Native JNI方法
- XposedHelper
- "/proc/self/maps"检测