1. 程式人生 > 其它 >安卓逆向面試題彙總 技術篇

安卓逆向面試題彙總 技術篇

首發安全客 連結:https://www.anquanke.com/post/id/246020
因為之前發完之後發現某些地方,描述不精確,所以這裡做了一點微調

大家好,我是王鐵頭 一個乙方安全公司搬磚的菜雞
持續更新移動安全,iot安全,編譯原理相關原創視訊文章。
因為本人水平有限,文章如果有錯誤之處,還請大佬們指出,誠心誠意的接受批評。

簡介
這篇文章詳細講解了,安卓面試經常會問到的幾個技術問題。
以及相關的背景知識,技術原理。
文章中用到的資料程式碼 和作者的其他技術文章 看這裡:https://github.com/wangtietou/Wtt_Mobile_Security


本菜雞大概面試了30多家公司,因為學歷比較差(大專),很多公司看了簡歷直接就把我刷了。或者簡歷沒看就把我刷了,在boss直聘上看到大佬已讀不回 簡直是常規操作了。

很多時候根本走不到技術那裡。

走到技術那裡後,面試失敗的概率大概30%左右,有時候是因為我技術菜,有時候是因為要做的細分領域不太一致不太想幹,有時候是因為談不攏工資(我想多要一點,對方不給,哈哈哈哈)。


除了面試經驗比較多,面試別人的經驗也比較多。

因為我在公司時間也比較長,把之前招我進來的同事成功熬走了,所以現在android逆向面試,移動安全面試這塊也是我當面試官。

所以,不管是面試還是被面試,我鐵頭多少也有一些經驗。


安卓逆向面試題彙總 技術篇

面試官經常問的幾個問題如下:

  1. 常見的加固手段有哪些
  2. 安卓反除錯一般有哪些手段,怎麼去防範
  3. arm彙編 b bl bx blx 這些指令是什麼意思
  4. ida xx操作的快捷鍵是哪個?
  5. Xposed hook 原理 frida hook 原理
  6. inline hook原理
  7. ollvm 程式碼混淆你瞭解嗎?要怎麼去處理

上面是一個彙總的目錄,下面一個一個仔細拆分 詳細說說


安卓逆向面試題詳解

1)常見的加固手段

網上有的人把安卓殼分成五代殼,有人分成三代殼。

不同的人對這塊的,具體的區分和看法不同,但是五代殼更細分一些。

在加固廠商內部,用的是五代殼的標準,當然他們PPT已經出現了第6 ,7 ,8代殼。
我入行以及搬磚的時候,周圍人用的基本都是下圖的標準,所以我這裡用五代殼來描述。


上面的圖把安卓五代殼的優缺點,實現邏輯講的非常好。大佬們理解了上面這兩張圖,回答第一個問題基本就ok了。


但是,大哥們既然看到了我這個文章,大佬們就可以風騷一點多說一些,說些面試官也不知道的。
畢竟, 唬不住5k,唬得住50k
說完上面的大概就是個及格分,說點下面的,面試官如果不瞭解這塊的話當時就被你給唬住了。


大佬們如果在公司負責甲方安全,採購過企業版加固,或者在加固廠商搬過磚的話就會知道。
加固雖然大體上分為免費版和企業版。
免費版裡面有的公司基本沒啥加固選項,上傳個apk應用包梭哈就完事了。

比如這種。

有的公司還是 比較人性化的,使用者可以根據自己需求選擇加固選項。比如這種

可以看到,免費版這裡,廠商玩的花樣並不多,有的就是上傳一個包,啥加固選項沒有,有的雖然有,加固選項也就幾個。

但是企業版這裡,廠商們花樣都比較多。
假設某加固公司,企業版實現了6個功能(一般是十幾個 二十幾個 我這裡做個比喻)。
功能如下:

  1. 簽名校驗。
  2. 金鑰白盒
  3. 反xposed frida
  4. 原始碼深度混淆
  5. h5加固
  6. 記憶體防dump

這上面的功能是外掛化的,你可以根據實際應用場景選擇其中幾個功能,也可以都要。
比如你的app根本麼有h5頁面,你選個h5加固不是白花錢嗎。
這裡套餐不同,價格也是不同的。(企業殼大概一年幾萬吧)
銷售那裡不同的功能組合有不同的報價,就像A公司選了1,3,5。 你選了 2,4,6. 雖然都是企業版,但是你和別人的企業版還是有區別的。

說這些就是表示,不同apk即使用了同一家廠商的企業版加固,選擇的加固方式也是不一樣的。

而且,一些行業的客戶,加固廠商各自也會有針對行業的一些加固手法。
比如一些手遊,加固廠商就會有一些反外掛的操作,針對記憶體讀寫的強檢測,一些金融客戶哪,因為對使用者資訊保密程度要求高,就會做一些安全鍵盤和防錄屏截圖操作。

這裡一些加固公司還把加固方式也做成了外掛化,比如一個apk,同時用2代殼和4代殼的加固方式都用上。2代殼不落地載入結合4代殼dexvmp,或者3代殼指令抽取結合4代殼dexvmp,這裡混合也是他們的常用套路,不會影響app正常執行。

說到這裡有的大佬可能會疑惑,2代3代4代不是不同的加固方式嗎?是怎麼結合的哪?這裡我解釋一下
假設加固廠商拿到了一個未加固的dex, 那麼2 3 4代殼子是怎麼結合的。

  1. dex比較重要的部分,比如演算法部分,登入模組,這塊的方法內容被抽取轉換成自定義的指令格式,然後呼叫系統底層的jni方法執行。(4代殼dexvmp)

  2. 其他不重要方法體直接抽空, 單獨加密,執行的時候方法體內容再動態還原(3代抽取)。

  3. 載入這個dex的時候(現在的dex已經經過了上面2步處理 裡面的方法很多被抽空,一些被dexvmp保護), 並不是寫出到檔案系統用 dexclassloader這樣的api去載入, 而是讀到記憶體中直接載入,直接呼叫c層API載入記憶體中的dex(2代不落地載入)

還有一些更深度的定製,反正有錢就是大爺,你錢多幹啥都可以商量,一般企業殼加固後你還是可以看到廠商的特徵加固檔案。比如你看到libjiagu.so就覺得是360 ,深度定製後,特徵檔案你一個都找不到,而且還可以實現一些定製化的需求。

企業版功能外掛化,套餐化,加殼方式組合這些東西,一般來說很多人是不知道的,所以說說這些,能很快的把你從眾多普通面試者中區分出來。

把這一點說上,到時候面試官說不定因為過於欣賞你,把他大學剛畢業,沒有男朋友的妹妹介紹給你了。

所以,當面試官問加固方式這塊的時候,你除了把兩張圖的內容說清楚,還可以清清嗓子,一臉高手寂寞的神情。

悠悠地說:
其實吧,很多我搞過的企業殼,看的出來挺多都是定製化的,有的是2代殼結合4代殼的加固,有的是2代3代混合4代。
感覺很多企業殼根據不同的業務場景,買了不同的加固套餐,比如xx應用,我脫殼的時候,發現有 清場sdk, ollvm混淆。 另一個企業殼根本就沒有這些,大部分邏輯在後端,不過也搞了金鑰白盒和H5加固。
還有一些遊戲的企業殼,記憶體讀寫明顯防護是比較厲害的。金融這塊的也基本都有安全鍵盤,和防截圖的一些保護。
這時候,狀若無意的對面試官說:“你說是吧”。

perfect.

2)安卓逆向反除錯的手段有哪些

這裡比較常用的反除錯手段有

  1. 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函式下斷點。
繞過方法:手動patch,或者用frida之類的工具hook ptrace直接返回0.
例項演示


  1. TracerPid檢測:

背景知識:TracerPid是程序的一個屬性值,如果為0,表示程式當前沒有被除錯,如果不為0,表示正在被除錯, TracerPid的值是除錯程式的程序id。
實現程式碼:

#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定位。
繞過手法

  1. 直接手動patch, nop掉呼叫
  2. 編譯核心,修改linux kernel原始碼,讓 TracerPid永久為0. 修改方法 https://cloud.tencent.com/developer/article/1193431
    例項演示

這裡用android studio 除錯app 檢視app程序對應的 status,status裡檢視TracerPid的值

可以看到TracerPid的值 是偵錯程式的程序id。

沒被除錯的時候,TracerPid的值是0。


  1. 自帶除錯檢測函式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.


  1. 檢測偵錯程式埠 比如 ida 23946 frida 27042 之類的
    背景知識:偵錯程式服務端預設會開啟一些特定埠,方便客戶端通過電腦usb線,或者直接通過區域網進行連線。
    實現程式碼:
//返回找到的特徵埠數量
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進行定位。

案例演示

這裡啟動 frida_server,然後檢視/proc/net/tcp檔案內容,果然發現了frida_server對應的埠。

繞過手法:換個埠就可。

android_server 換埠
這裡注意 -p 和 埠之間是沒有空格的 直接連線

/data/local/tmp/android_server -p8888 //執行android_server  以埠8888執行
adb forward tcp:8888 tcp:8888 		  //轉發埠 8888

frida-server 切換埠 這裡切換成 6666埠

/data/local/tmp/frida_server -l 0.0.0.0:6666      //啟動frida_server 監聽6666
adb forward tcp:6666 tcp:6666				      //轉發6666埠
frida -H 127.0.0.1:6666 package_name -l hook.js   //注入js

  1. 根據時間差反除錯
    背景知識:在關鍵邏輯的開始和結束的地方,獲取當前的秒數。結束時間減去開始時間,如果超過一定時間,認定是在除錯。因為程式執行速度很快的,卡到2-3秒執行完,除非你邏輯好多,演算法很複雜,要不基本不大可能。

    繞過方法:手動nop掉。

    案例演示

這裡不用說的太全,說幾個常見的就行了。說全了時間也不太夠。

3)arm彙編 B、BL、BX、BLX區別和指令含義

這裡對這幾條指令有個簡單記憶的方法 那就是對幾條指令中的字母單獨記憶,然後遇到字母的組合,就把字母代表的含義加起來就可了。

單獨記憶法:

字母 B: 跳轉 類似jmp
字母 L: 把下一條指令地址存入LR暫存器
字母 X: arm和thumb指令的切換

注意:這樣去記 是為了快速記住上面幾條指令的含義 而不是 單字母本身在彙編裡面有這些含義


所以,4條指令的的含義就是

  1. B 這裡跟x86彙編的 jmp比較像,可以理解成無條件跳轉

  2. BL :這裡理解成 字母B + 字母L 作用是 把下一條指令地址存入LR暫存器 然後跳轉。 像x86彙編裡面的 call , 只不過call指令把下一條指令的地址壓入棧,BL是把下一條指令的地址放到 LR暫存器。

  3. BX 這裡理解成 字母B + 字母X 這裡表示跳轉到一個地址,同時切換指令模式 當前如果是arm 就會切換成 Thumb 如果是Thumb 就會切換成arm

  4. BLX 這裡是 字母B + 字母L + 字母X 表示跳轉到一個新的地址,跳轉的時候把下一條指令地址存入LR暫存器 同時切換指令模式 arm轉thumb thumb轉arm
    可以這樣去理解: blx = call + 切換指令模式

4)ida 使用 快捷鍵

G :跳轉到指定地址

Shift + F12:字串視窗,用於字串搜尋

Y:修改變數型別 函式宣告快捷鍵

除了修改變數型別 也可以修改函式的返回值型別 和 引數型別

X : 檢視 變數 常量 函式 的引用

在定位演算法的時候 用x檢視關鍵變數的引用也是很有效的一種方式

同樣可以按X檢視常量的引用 定位一些字串到底在哪個函式還是蠻好用的

Ctrl+S:檢視節表

5)frida hook原理 xposed注入原理

  1. frida注入原理
    frida 注入是基於 ptrace實現的。frida 呼叫ptrace向目標程序注入了一個frida-agent-xx.so檔案。後續騷操作是這個so檔案跟frida-server通訊實現的
    ida除錯也是基於 ptrace實現的。
    那為什麼有人能動靜結合用 frida 和 ida一起除錯哪?一個程序只能被ptrace一次,那這裡為啥兩個能結合?
    答案是:先用frida注入,然後用偵錯程式除錯。
    frida在使用完ptrace之後 馬上就釋放了,並沒有一直佔用,所以ida後續是可以附加,繼續使用ptrace的。
  1. xposed注入原理
    安卓所有的APP程序是用 Zygote(孵化器)程序啟動的。
    Xposed替換了 Zygote 程序對應的可執行檔案/system/bin/app_process,每啟動一個新的程序,都會先啟動xposed替換過的檔案,都會載入xposed相關程式碼。這樣就注入了每一個app程序。

6)inline hook原理

這裡 我畫了一個圖,大佬們自己看圖
原理描述:修改函式頭,跳轉到自定義函式,自定義函式就是自己想執行的邏輯,執行完自己的邏輯再跳轉回來。

7) ollvm 程式碼混淆瞭解過嗎 ,一般怎麼處理

一般這個難度的問題會放到靠後,除非你在簡歷裡就寫了自己錘過很多 ollvm混淆過的程式碼.
這裡大佬們要是實在不會 對這塊沒啥瞭解,也建議大佬們掙扎一下,把下面我列的說一下 。也能爭取點卷面分

ollvm是一個程式碼混淆的框架
這個框架通過以下三種方式實現了程式碼混淆

英文全稱 簡稱/引數表示
控制流平坦化 Control Flow Flattening fla
虛假控制流 Bogus Control Flow bcf
指令替換 Instructions Substitution sub

這三種可以全部選擇。也可以隨意組合,具體怎樣組合看具體根據具體場景去決定。

下面一個一個詳細講解

  1. 被混淆前的原始碼 在ida中的樣子

    在沒有使用控制流平坦化之前 程式碼在反編譯工具裡面看的都是比較清晰的

#include <cstdio>

int main(int n_argc, char** argv)
{
	int n_num = n_argc * 2;
	//scanf("%2d", &n_num);

	if (20 == n_num)
	{
		puts("20");
	}
	if(10 == n_num)
	{
	  	puts("10");
	}
	if(2 == n_num)
	{
	  	puts("2");
	}

	puts("error");

	return -1;
}

拖入ida後 流程圖如下 這裡可以看到流程還是很清晰的

下面是 原始碼 加了不同引數後 被ollvm混淆後的樣子


這裡我用自己的話簡單描述 ollvm的3種混淆方式

  1. fla 控制流平坦化
    混淆前混淆後如下圖所示:

    混淆前:

混淆後:

​ 程式碼本來是依照邏輯順序執行的,控制流平坦化是把,原來的程式碼的基本塊拆分。

​ 把本來順序執行的程式碼塊用 switch case打亂分發,根據case值的變化,把原本程式碼的邏輯連線起來。讓你不知 道程式碼塊的原本順序。讓逆向的小老弟一眼看過去不知道所以然,不知道怎麼去分析。

  1. bcf 虛假控制流:一般是通過全域性變數,構造恆等式(一定會成立),和恆不等式(一定不成立),插入大量這種看似有用,實際上就是在為難你的程式碼。

    if(x == 0)
    {
       ...程式碼A 
    }
    if(y == 0)
    {
      ...程式碼B
    }
    

    上面寫了兩段虛擬碼。 假設 x的值是0 y的值是1
    那麼 在上面的程式碼中
    if(x == 0) 這個條件一定是成立的
    if(y == 0)這個條件是一定不成立的。

    bcf虛假控制流,通過構造x,y 這種全域性變數。讓編譯器不能推斷x,y的值.(不透明維詞)
    通過大量插入一些跟上面類似的恆等式,和恆不等式(不可達分支),然後在這些分支在裡面寫一些程式碼,把原邏輯串聯起來。

  2. sub指令替換
    指令替換對程式的基本塊架構沒有任何影響。對比下面兩個圖 混淆跟沒有混淆進行對比之後,可以發現。
    程式控制流和基本塊的順序,執行流程沒有什麼變化。

    當然這也跟這個函式基本沒啥運算指令有關係。

    只是把 x = x + 1 這樣的程式碼 替換成類似於 x = x + 2 + 1 - 2 這樣的程式碼

    增大程式碼體積,把簡單的指令變複雜。增大分析的難度

這裡,大佬們在回答 ollvm這塊的話 把我上面寫的說一下就大概差不多了。

面試官如果問大佬們怎麼解決:

大佬們可以這麼說

  1. 通過unicorn 模擬執行去除控制流平坦化
    https://bbs.pediy.com/thread-252321.htm

  2. 通過angr 符號執行 去除控制流平坦化
    https://security.tencent.com/index.php/blog/msg/112

  3. 通過angr 符號執行 去除虛假控制流
    https://bbs.pediy.com/thread-266005.htm

  4. 通過Miasm符號執行移除OLLVM虛假控制流
    https://www.52pojie.cn/thread-995577-1-1.html

總結:

上面講解了安卓逆向面試中,經常問的幾個技術問題,背後的原理,該怎麼回答。

當然除了技術篇,還會問一些發展方向,技術追求,看你穩定性之類的。

希望大佬們都能順利拿到 offer, 如果看完文章有所收穫,而且還順利入職的話,大佬們可以過來還願下。

以上

2021.7.1 王鐵頭於公司辦公樓

大佬們可以這麼說

  1. 通過unicorn 模擬執行去除控制流平坦化
    https://bbs.pediy.com/thread-252321.htm

  2. 通過angr 符號執行 去除控制流平坦化
    https://security.tencent.com/index.php/blog/msg/112

  3. 通過angr 符號執行 去除虛假控制流
    https://bbs.pediy.com/thread-266005.htm

  4. 通過Miasm符號執行移除OLLVM虛假控制流
    https://www.52pojie.cn/thread-995577-1-1.html

相關參考:
https://segmentfault.com/a/1190000037697547
https://blog.csdn.net/earbao/article/details/82379117
https://blog.csdn.net/qq_42186263/article/details/113711359

--文章結束--

持續更新移動安全,iot安全,編譯原理相關原創視訊文章
演示視訊:https://space.bilibili.com/430241559