Skip to content

Latest commit

 

History

History
438 lines (300 loc) · 15.1 KB

面试题.md

File metadata and controls

438 lines (300 loc) · 15.1 KB

面试题


.java到dex文件的过程?

.java → .class → .dex

同时需要准备的还有 Android 应用的打包的流程


常见的 APk 的加固的手段有哪些

  1. Java混淆
  2. 资源文件混淆
  3. 签名校验
  4. 模拟器检测

同时需要准备的还有应对这些加固手段的方法


Android 四大组件有哪些?

  1. Activity
  2. Service服务
  3. Content Provider内容提供者
  4. BroadcastReceiver广播接收器

安卓文件的结构

  • assets目录

    • 这里面一般放的是资源,这里面的资源通常是没有编译过的,可以直接用,有图片、js、html等。
  • res目录

    • 放的资源,程序的图标、样式、布局、XML等,编译之后的文件,直接查看是乱码,需要反编译的!APKtool即可反编译查看。
  • META-INF 目录

    • 签名和系统校验的文件
  • lib目录

    • 目录放的一般是so文件,也就是本地代码,好几个文件夹一般...MIPS、x86、x64等,CPU的一些平台。
  • AndroidManifest.xml 清单文件

    • 直接查看大部分是乱码,在Androidkiller当中查看,能够看得到APK需要使用系统的权限、包名、APK是否支持调试等等内容,等配置信息
  • resources.arsc

    • 编译之后的文件,语言包、程序内容等。

在 smali 代码当中非static方法中,()为方法的第二个参数

在非 static 方法当中 第一个参数是 $P_0$ → this ,$P_1$ → 第一个参数

在 static 方法当中 第一个参数 $P_0$ → 是一个参数


smali 语法考察下面的smali代码的对应的Java代码是什么

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代码当中.implements的作用是什么

它是接口关键字

同时需要准备的还有其他的smali当中的关键字


安卓反调试一般有哪些手段?怎么防范

  1. 关键文件检测(主要就是检测Android_server)

    1. 这个主要就是Android_server 文件的检测,那么他的方式就是修改文件名,修改文件名字的方法就是 mv [Android_serverFileName] [Android_serverNewName]
  2. 端口检测

    1. 修改端口(Android_server 的默认端口号是 23946 ,Frida server 的默认端口是 27042,),修改端口的方法就是 ./as -p31928

    2. 启动 frida_server / Android_server ,然后查看 /proc/net/tcp 文件内容,果然发现了 frida_server 对应的端口

    3. 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;
      }
    4. 定位方法:读取端口时,一般都会读取 /proc/net/tcp文件,所以可以搜索关键字,或者 popen(管道执行命令) fgets(读取文件行)这种api进行定位。

    5. 绕过手法:换个端口就可,android_server 换端口

  3. Ptrace检测

    1. 首先 ptrace 是 linux 提供的 API , 可以监视和控制进程运行,可以动态修改进程的内存,寄存器值。一般被用来调试。ida调试so,就是基于ptrace实现的。因为一个进程只能被ptrace一次, 所以进程可以自己ptrace自己,这样ida和别的基于ptrace的工具和调试器或就无法调试这个进程了

    2. 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;
      }
    3. 定位方法:直接在ptrace函数下断点。

    4. 绕过方法:手动配置,或者用 frida 之类的工具 hook ptrace 直接返回 0

  4. TracerPid检测

    1. TracerPid 是进程的一个属性值,如果为 0,表示程序当前没有被调试,如果不为 0,表示正在被调试, TracerPid 的值是调试程序的进程id

    2. 通过在手机当中执行 cat /proc/[pid]/status 就能够看得到 TracerPid

    3. #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;
      }
    4. 定位方法:一般检测 TracerPid 都会读取 /proc/进程号/status 这个文件所以可以直接搜索 /status 这种字符串,这里也会用到 getpid , fgets 这种 API ,所以也可以通过这两个 api 定位。

    5. 绕过方法

      1. 直接手动patch, nop掉调用
      2. 编译内核,修改linux kernel源代码,让 TracerPid永久为0
  5. 自带调试检测函数 → android.os.Debug.isDebuggerConnected()

    1. 自带调试检测api, 被调试时候返回 true, 否则返回 false

    2. import static android.os.Debug.isDebuggerConnected;
      
      public static boolean is_debug()
      {
          boolean b_ret = isDebuggerConnected();
          return b_ret;
      }
    3. 定位方法:直接搜索 isDebuggerConnected 函数名即可。

    4. 绕过手法:frida 之类的工具直接 hook 函数,直接返回 false。


ARM汇编 B、BL、BX、BLX区别和指令含义

字母 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汇编的语法


IDA快捷键

  • G :跳转到指定地址
  • Shift + F12:字符串窗口,用于字符串搜索
  • Y:修改变量类型 函数声明快捷键,除了修改变量类型 也可以修改函数的返回值类型 和 参数类型
  • X : 查看 变量 常量 函数 的引用,在定位算法的时候 用x查看关键变量的引用也是很有效的一种方式,同样可以按X查看常量的引用 定位一些字符串到底在哪个函数还是蛮好用的
  • Ctrl+S:查看节表
  • N:重命名

同时需要准备的还有其他的IDA的快捷键


ARM中的程序状态寄存器(CPSR)

  • N=1 表示运算的结果为负数;N=0 表示运算的结果为正数或零

  • Z=1 表示运算的结果为零;Z=0表示运算的结果为非零;

  • T当该位为1时,程序运行于THUMB状态,否则运行于ARM状态


IDA动态调试ide流程

简介流程

  1. 首先将 Android_server 这个文件push到手机 /data/local/tmp 当中,至于到底是32还是64那么就看你的软件或者是你的手机是64还是32的
  2. 然后再进入 adb shell 的 /data/local/tmp 当中对这个Android_server 进行改名,为了防止关键文件检测,mv file newfile
  3. 打开 root 权限(su)然后对这个Android_server 进行赋权 chmod 777 newfile
  4. 运行android_server ./newfile -p31928 改一个端口号,防止端口检测
  5. 端口转化 adb forward tcp 31928 tcp31928 因为已经转换了端口
  6. 将这个要调试的软件安装在手机当中 adb install 【application】
  7. 挂载程序 adb shell am -D -n 包名/类名 (这个通过 aapt dump badging 【application】)进行查看,这个时候手机上面会出现 waitting for Debug 的字样
    1. 这个时候的debug调试模式就是,打开 IDA ,选择debugger → Attach → Remote ARM/Android debugger。选择其中的Hostname 127.0.0.1 port 就是前面端口转换的端口号,选择你的进程,PC指针停在靠前的位置,打开,debugger options 选择三项,F9运行,停在断点的位置,IDA显示running
  8. 打开 DDMS 会发现正在调试的这个软件是有一个红色的虫子。获得这个额当前调试进程的端口号
  9. 输入 jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700(这里的端口号就是DDMS里面的端口号)
  10. 红色的bug变成绿色的bug
  11. 选择三项,然后点击 F9运行,加载你 libjavandk.so 的so文件,在model当中找到这个so文件,接着找到对应的jni_onload 或者是 Java_ 在这个位置下一个breakpoint
  12. F9点击运行,F5 能够汇编和Java代码进行转换
  13. c,p能够将汇编代码转换成Java代码,p是一个函数一个函数的转换(因为有end标识符),c是一条一条的转换

Xposed hook 的原理

安卓所有的APP进程是用 Zygote (孵化器)进程启动的。Xposed 替换了 Zygote 进程对应的可执行文件 /system/bin/app_process ,每启动一个新的进程,都会先启动 Xposed 替换过的文件,都会加载 Xposed 相关代码。这样就注入了每一个 app 进程。


frida hook 的原理

frida注入原理frida 注入是基于 ptrace实现的。frida 调用 ptrace 向目标进程注入了一个 frida-agent-xx.so 文件。后续操作是这个so文件跟 frida-server 通讯实现的 ida 调试也是基于 ptrace 实现的。

那为什么有人能动静结合用 frida 和 ida一起调试哪?一个进程只能被ptrace一次,那这里为啥两个能结合?

答案是:先用frida注入,然后用调试器调试。frida在使用完ptrace之后 马上就释放了,并没有一直占用,所以 ida 后续是可以附加,继续使用 ptrace 的。


从ARM汇编当中找到这一段代码当中一共有多少个参数寄存器,并且各自的数据类型


so层加载流程