- 面试题
- .java到dex文件的过程?
- 常见的 APk 的加固的手段有哪些
- Android 四大组件有哪些?
- 安卓文件的结构
- 在 smali 代码当中非static方法中,()为方法的第二个参数
- smali 语法考察下面的smali代码的对应的Java代码是什么
- 在smali代码当中.implements的作用是什么
- 安卓反调试一般有哪些手段?怎么防范
- ARM汇编 B、BL、BX、BLX区别和指令含义
- IDA快捷键
- ARM中的程序状态寄存器(CPSR)
- IDA动态调试ide流程
- Xposed hook 的原理
- frida hook 的原理
- 从ARM汇编当中找到这一段代码当中一共有多少个参数寄存器,并且各自的数据类型
.java → .class → .dex
同时需要准备的还有 Android 应用的打包的流程
- Java混淆
- 资源文件混淆
- 签名校验
- 模拟器检测
同时需要准备的还有应对这些加固手段的方法
- Activity
- Service服务
- Content Provider内容提供者
- BroadcastReceiver广播接收器
-
assets目录
- 这里面一般放的是资源,这里面的资源通常是没有编译过的,可以直接用,有图片、js、html等。
-
res目录
- 放的资源,程序的图标、样式、布局、XML等,编译之后的文件,直接查看是乱码,需要反编译的!APKtool即可反编译查看。
-
META-INF 目录
- 签名和系统校验的文件
-
lib目录
- 目录放的一般是so文件,也就是本地代码,好几个文件夹一般...MIPS、x86、x64等,CPU的一些平台。
-
AndroidManifest.xml 清单文件
- 直接查看大部分是乱码,在Androidkiller当中查看,能够看得到APK需要使用系统的权限、包名、APK是否支持调试等等内容,等配置信息
-
resources.arsc
- 编译之后的文件,语言包、程序内容等。
在非 static 方法当中 第一个参数是
在 static 方法当中 第一个参数
const-string v0, "NDKLIB"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
对应的 Java 代码
System.loadLibrary("NDKLIB")
.method public static a()V
.locals 2
sget-object v0, Lcom/a/a/b/a;->aPG:Lcom/a/a/b/a;
if-eqz v0, :cond_1
sget-object v0, Lcom/a/a/b/a;->aPG:Lcom/a/a/b/a;
iget-object v1, v0, Lcom/a/a/b/a;->aPI:Lcom/a/a/b/c;
if-eqz v1, :cond_0
:try_start_0
iget-object v0, v0, Lcom/a/a/b/a;->aPI:Lcom/a/a/b/c;
invoke-virtual {v0}, Lcom/a/a/b/c;->join()V
:try_end_0
.catch Ljava/lang/InterruptedException; {:try_start_0 .. :try_end_0} :catch_0
:cond_0
:goto_0
const/4 v0, 0x0
sput-object v0, Lcom/a/a/b/a;->aPG:Lcom/a/a/b/a;
:cond_1
return-void
:catch_0
move-exception v0
invoke-virtual {v0}, Ljava/lang/InterruptedException;->printStackTrace()V
goto :goto_0
.end method
对应的 Java 代码
public static void a()
{
a locala;
if (aPG != null)
{
locala = aPG;
if (locala.aPI == null) {}
}
try
{
locala.aPI.join();
aPG = null;
return;
}
catch (InterruptedException localInterruptedException)
{
for (;;)
{
localInterruptedException.printStackTrace();
}
}
}
它是接口关键字
同时需要准备的还有其他的smali当中的关键字
-
关键文件检测(主要就是检测Android_server)
- 这个主要就是Android_server 文件的检测,那么他的方式就是修改文件名,修改文件名字的方法就是
mv [Android_serverFileName] [Android_serverNewName]
- 这个主要就是Android_server 文件的检测,那么他的方式就是修改文件名,修改文件名字的方法就是
-
端口检测
-
修改端口(Android_server 的默认端口号是 23946 ,Frida server 的默认端口是 27042,),修改端口的方法就是
./as -p31928
-
启动 frida_server / Android_server ,然后查看 /proc/net/tcp 文件内容,果然发现了 frida_server 对应的端口
-
int check_debug_port() { //特征端口字符串数组 0x5D8A是23946的十六进制 69a2是27042十六进制 //这里为了提高精确度 加个 : char* p_strPort_ary[] = {":5D8A", ":69A2"}; int n_port_num = 2; //特征端口数量 //找到特征端口数量 返回值 int n_find_num = 0; //初始化文件指针 路径 和缓冲区 FILE* fp = 0; char c_line_buf[MAX_LENGTH] = {0}; char* p_str_tcp = "/proc/net/tcp"; fp = fopen(p_str_tcp, "r"); if(NULL == fp ) { return -1; } //读取文件 看当前文件包含了几个特征端口号 while(fgets(c_line_buf, MAX_LENGTH - 1, fp)) { for (int i = 0; i < n_port_num; ++i) { //如果从当前文本行 找到特定端口号 char* p_line = p_strPort_ary[i]; if(NULL != strstr(c_line_buf, p_line)) { n_find_num++; } } memset(c_line_buf, 0, MAX_LENGTH); } fclose(fp); //返回找到的特征端口数量 return n_find_num; }
-
定位方法:读取端口时,一般都会读取 /proc/net/tcp文件,所以可以搜索关键字,或者 popen(管道执行命令) fgets(读取文件行)这种api进行定位。
-
绕过手法:换个端口就可,android_server 换端口
-
-
Ptrace检测
-
首先 ptrace 是 linux 提供的 API , 可以监视和控制进程运行,可以动态修改进程的内存,寄存器值。一般被用来调试。ida调试so,就是基于ptrace实现的。因为一个进程只能被ptrace一次, 所以进程可以自己ptrace自己,这样ida和别的基于ptrace的工具和调试器或就无法调试这个进程了
-
int check_ptrace() { // 被调试返回-1,正常运行返回0 int n_ret = ptrace(PTRACE_TRACEME, 0, 0, 0); if(-1 == n_ret) { printf("阿偶,进程正在被调试\n"); return -1; } printf("没被调试 返回值为:%d\n",n_ret); return 0; }
-
定位方法:直接在ptrace函数下断点。
-
绕过方法:手动配置,或者用 frida 之类的工具 hook ptrace 直接返回 0
-
-
TracerPid检测
-
TracerPid 是进程的一个属性值,如果为 0,表示程序当前没有被调试,如果不为 0,表示正在被调试, TracerPid 的值是调试程序的进程id
-
通过在手机当中执行
cat /proc/[pid]/status
就能够看得到 TracerPid -
#define MAX_LENGTH 260 //获取tracePid int get_tarce_pid() { //初始化缓冲区变量和文件指针 char c_buf_line[MAX_LENGTH] = {0}; char c_path[MAX_LENGTH] = {0}; FILE* fp = 0; //初始化n_trace_pid 获取当前进程id int n_pid = getpid(); int n_trace_pid = 0; //拼凑路径 读取当前进程的status sprintf(c_path, "/proc/%d/status", n_pid); fp = fopen(c_path, "r"); //打不开文件就报错 if (fp == NULL) { return -1; } //读取文件 按行读取 存入缓冲区 while (fgets(c_buf_line, MAX_LENGTH, fp)) { //如果没有搜索到TracerPid 继续循环 if (0 == strstr(c_buf_line, "TracerPid")) { memset(c_buf_line, 0, MAX_LENGTH); continue; } //初始化变量 char *p_ch = c_buf_line; char c_buf_num[MAX_LENGTH] = {0}; //把当前文本行 包含的数字字符串 转成数字 for (int n_idx = 0; *p_ch != '\0'; p_ch++) { //比较当前字符的ascii码 看看是不是数字 if (*p_ch >= 48 && *p_ch <= 57) { c_buf_num[n_idx] = *p_ch; n_idx++; } } n_trace_pid = atoi(c_buf_num); break; } fclose(fp); return n_trace_pid; }
-
定位方法:一般检测 TracerPid 都会读取
/proc/进程号/status
这个文件所以可以直接搜索 /status 这种字符串,这里也会用到 getpid , fgets 这种 API ,所以也可以通过这两个 api 定位。 -
绕过方法
- 直接手动patch, nop掉调用
- 编译内核,修改linux kernel源代码,让 TracerPid永久为0
-
-
自带调试检测函数 →
android.os.Debug.isDebuggerConnected()
-
自带调试检测api, 被调试时候返回 true, 否则返回 false
-
import static android.os.Debug.isDebuggerConnected; public static boolean is_debug() { boolean b_ret = isDebuggerConnected(); return b_ret; }
-
定位方法:直接搜索
isDebuggerConnected
函数名即可。 -
绕过手法:frida 之类的工具直接 hook 函数,直接返回 false。
-
字母 B: 跳转 类似 jmp 字母 L: 把下一条指令地址存入LR寄存器字母 X: arm 和 thumb 指令的切换
所以,4条指令的的含义就是
- B 这里跟x86汇编的 jmp比较像,可以理解成无条件跳转
- BL :这里理解成 字母B + 字母L 作用是 把下一条指令地址存入LR寄存器 然后跳转。 像x86汇编里面的 call , 只不过call指令把下一条指令的地址压入栈,BL是把下一条指令的地址放到 LR寄存器。
- BX 这里理解成 字母B + 字母X 这里表示跳转到一个地址,同时切换指令模式 当前如果是arm 就会切换成 Thumb 如果是Thumb 就会切换成arm
- BLX 这里是 字母B + 字母L + 字母X 表示跳转到一个新的地址,跳转的时候把下一条指令地址存入LR寄存器 同时切换指令模式 arm转thumb thumb转arm可以这样去理解: blx = call + 切换指令模式
同时需要准备的还有ARM汇编的语法
- G :跳转到指定地址
- Shift + F12:字符串窗口,用于字符串搜索
- Y:修改变量类型 函数声明快捷键,除了修改变量类型 也可以修改函数的返回值类型 和 参数类型
- X : 查看 变量 常量 函数 的引用,在定位算法的时候 用x查看关键变量的引用也是很有效的一种方式,同样可以按X查看常量的引用 定位一些字符串到底在哪个函数还是蛮好用的
- Ctrl+S:查看节表
- N:重命名
同时需要准备的还有其他的IDA的快捷键
-
N=1 表示运算的结果为负数;N=0 表示运算的结果为正数或零
-
Z=1 表示运算的结果为零;Z=0表示运算的结果为非零;
-
T当该位为1时,程序运行于THUMB状态,否则运行于ARM状态
简介流程
- 首先将 Android_server 这个文件push到手机 /data/local/tmp 当中,至于到底是32还是64那么就看你的软件或者是你的手机是64还是32的
- 然后再进入 adb shell 的 /data/local/tmp 当中对这个Android_server 进行改名,为了防止关键文件检测,mv file newfile
- 打开 root 权限(su)然后对这个Android_server 进行赋权 chmod 777 newfile
- 运行android_server ./newfile -p31928 改一个端口号,防止端口检测
- 端口转化 adb forward tcp 31928 tcp31928 因为已经转换了端口
- 将这个要调试的软件安装在手机当中 adb install 【application】
- 挂载程序 adb shell am -D -n 包名/类名 (这个通过 aapt dump badging 【application】)进行查看,这个时候手机上面会出现 waitting for Debug 的字样
- 这个时候的debug调试模式就是,打开 IDA ,选择debugger → Attach → Remote ARM/Android debugger。选择其中的Hostname 127.0.0.1 port 就是前面端口转换的端口号,选择你的进程,PC指针停在靠前的位置,打开,debugger options 选择三项,F9运行,停在断点的位置,IDA显示running
- 打开 DDMS 会发现正在调试的这个软件是有一个红色的虫子。获得这个额当前调试进程的端口号
- 输入 jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700(这里的端口号就是DDMS里面的端口号)
- 红色的bug变成绿色的bug
- 选择三项,然后点击 F9运行,加载你 libjavandk.so 的so文件,在model当中找到这个so文件,接着找到对应的jni_onload 或者是 Java_ 在这个位置下一个breakpoint
- F9点击运行,F5 能够汇编和Java代码进行转换
- c,p能够将汇编代码转换成Java代码,p是一个函数一个函数的转换(因为有end标识符),c是一条一条的转换
安卓所有的APP进程是用 Zygote (孵化器)进程启动的。Xposed 替换了 Zygote 进程对应的可执行文件 /system/bin/app_process ,每启动一个新的进程,都会先启动 Xposed 替换过的文件,都会加载 Xposed 相关代码。这样就注入了每一个 app 进程。
frida注入原理frida 注入是基于 ptrace实现的。frida 调用 ptrace 向目标进程注入了一个 frida-agent-xx.so 文件。后续操作是这个so文件跟 frida-server 通讯实现的 ida 调试也是基于 ptrace 实现的。
那为什么有人能动静结合用 frida 和 ida一起调试哪?一个进程只能被ptrace一次,那这里为啥两个能结合?
答案是:先用frida注入,然后用调试器调试。frida在使用完ptrace之后 马上就释放了,并没有一直占用,所以 ida 后续是可以附加,继续使用 ptrace 的。