iOS APP安全雜談之三

wyzsk發表於2020-08-19
作者: 高小廚 · 2015/10/15 10:12

0x00 序


聽說最近IOS讓人操碎了心,不到三個月的時間裡先後發生太極越獄被曝存在後門22萬iCloud賬號及機密資訊被多款內建後門越獄外掛竊取並洩露XcodeGhost事件多個知名企業中槍,嚇的大家紛紛改了密碼。當然像XcodeGhost這樣的事情使用者是沒辦法避免的,其編譯的APP畢竟是在AppStore上上架的,但是還有一大部分的安全問題其實是完全可以避免的,那就是使用者不要越獄,而廠商的APP在執行前檢查執行的裝置是否安全。現在也有很多APP在使用前都會檢查裝置是否越獄,如果越獄了則強制退出或者只是提醒一下。那麼這次我們就來聊聊越獄之後的那點事。PS:由於本人能力有限,文章難免會有些錯誤,還望小夥伴們見諒。

0x01 越獄之後更美好?


越獄之後可以幹什麼,可以免費的玩各種付費的遊戲,可以自動的搶紅包,可以在接聽電話時自動錄音,是的,想想還有點小激動。

圖1 其實嚮往自由沒什麼錯

但事實是越獄之後的風險是使用者和廠商都不想看到的,攻擊者可以在越獄環境下改變你的APP執行邏輯(例如透過更改函式的返回值來繞過手勢密碼驗證)、可以透過複製應用資料克隆使用者或者登陸任意使用者、可以透過打補丁的方式繞過目標APP的一些限制等,當然以上的這些危害都是需要一個前提條件:目標APP在越獄環境下能正常使用。

圖2 你以為越獄之後就自由了?

0x02 工具準備


  • 越獄的iPhone或iPad(我這裡演示的是IOS 8.4,低版本或高版本的可能會有些不同);
  • IDA或者Hopper Disassembler;
  • Xcode(主要使用其附帶的LLDB),當然對於低版本的IOS可以使用gdb,LLDB具體如何除錯可以參考這裡
  • OpenSSH,itools等其他工具。

0x03 方法準備


檢測越獄的幾種方法(可參考《Hacking and Securing ios Application》):

(1)沙盒的完整性校驗

一些越獄工具會移除沙盒的限制,使程式可以不受限制的執行,這裡要說的是關於fork函式的限制。fork函式可以允許你的程式生成一個新的程式,如果沙盒被破壞或者程式在沙盒外執行,那麼fork函式就會成功執行,如果沙盒沒有被篡改則fork函式執行失敗。這裡我們透過fork()的返回值判斷子程式是否成功,程式程式碼如下:

#!c
#include <stdio.h>
#include <stdlib.h>
static inline int sandbox_integrity_compromised(void) __attribute__((always_inline));
int sandbox_integrity_compromised(void){
    int result = fork();
    if (!result)
        exit(0);
    if (result >= 0)
        return 1;
    return 0;
}
int main(int argc,char *argv[]){
    if(sandbox_integrity_compromised())
    {
    printf("Device is JailBroken\n");
    }else{
    printf("Device is not JailBroken\n");
    }
    return 0;
}

(2)檔案系統檢測

1)越獄檔案是否存在

越獄之後的小夥伴們一定對Cydia程式很熟悉吧,它是最受歡迎的第三方程式安裝器,大部分的越獄工具都會自動給裝置進行安裝。所以我們可以檢測第三方程式檔案是否存在來判斷裝置是否越獄,例如檢測Cydia程式是否存在。

2) /etc/fstab檔案大小

fstab檔案通常被越獄工具替換使root分割槽有讀寫的許可權,但是APP不允許檢視該檔案的內容,所以我們使用stat函式獲得此檔案的大小,再根據檔案的大小來判斷是否越獄。我這裡顯示越獄後該檔案的大小為67位元組,有資料中說如果沒有越獄該檔案的大小應該為80位元組。

圖3 使用itools檢視fstab檔案大小

3) 符號連結檢測

IOS的磁碟被劃分為兩個分割槽:容量較小的是系統分割槽,另一個是比較大的資料分割槽。IOS預裝的APP會安裝在系統分割槽下的/Applications資料夾下,但是系統分割槽在裝置升級時會被覆蓋且容量太小,所以一些越獄工具會重定向這個目錄到一個大的使用者分割槽。通常情況下/Applications資料夾會被符號連結到/var/stash檔案目錄下,APP可以透過lstat函式檢測/Applications的屬性,如若是目錄則代表未越獄,如若是符號連結則代表是越獄裝置。

圖4 使用itools檢視符號連結

圖5 使用命令列檢視符號連結

(3)檢測system( )函式的返回值等多種方法

這個方法來源於一個網站,同時還介紹了其他幾種檢測越獄的方法。其中一個方法就是檢測system( )函式的返回值,呼叫System( )函式,不需要任何引數。在越獄裝置上會返回1,在非越獄裝置上會返回0。

0x04 可以動手了


我們使用檔案檢測的第一種方法來動手測試一下我們的越獄裝置。程式碼如下:

#!c
#include <sys/stat.h>
#include <stdio.h>
int isJailBroken();
int main(){
    if(isJailBroken()){
        printf("Device is JailBroken\n");
    }else{
        printf("Device is not JailBroken\n");
    }
    return 0;
}
int isJailBroken(){
    struct stat buf;
    int exist = 0;
    char * jbFiles[] =
{"/usr/sbin/sshd","/bin/bash","/Applications/Cydia.app","/private/var/lib/apt","/Libra  ry/MobileSubstrate/MobileSubstrate.dylib"};
    for(int i=0;i < sizeof(jbFiles)/sizeof(char*);i++){
        exist = stat((jbFiles[i]),&buf);
        if(exist == 0){
            return 1;
        }
    }
    return 0;
}

以上程式碼主要檢測了/usr/sbin/sshd/bin/bash/Applications/Cydia.app/private/var/lib/apt/Library/MobileSubstrate/MobileSubstrate.dylib這幾個檔案是否存在,如果存在則證明該裝置已經越獄。

圖6 檢測越獄的POC

在Mac下使用以下命令編譯程式碼:

clang -framework Foundation -arch arm64 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk ~/Desktop/jailbreak.c -o ~/Desktop/jailbreak64  -miphoneos-version-min=5.0

編譯之後將二進位制檔案複製到越獄裝置當中並在命令列中執行它。下圖可看到執行程式後顯示裝置已經越獄。

圖7 賦予檔案許可權並執行

0x05 道高一尺魔高一丈


一些APP正是使用了類似上面的方法來檢測自己所在裝置是否進行了越獄,為了保證自身的安全性如果裝置越獄則自動退出。是的這種做法很明智,但是這也只是增加了安全測試的難度,我們既然知道了檢測越獄的原理那麼就可以見招拆招了。

(1) 配置好LLDB和debugserver,ssh連線到裝置,在裝置上使用命令列輸入debugserver -x backboard *:1234 /jailbreak64附加到jailbreak64,並開啟1234埠,等待LLDB的接入。

圖8 在iPad端使用debugserver附加到程式

(2) Mac端切換到~/Xcode.app/Contents/Developer/usr/bin/目錄下,輸入lldb,啟動後輸入process connect connect://iosip:1234

圖9 Mac端使用LLDB連線IOS裝置

(3) 使用IDA分析程式邏輯結構,這裡有一點需要注意:IDA分析的二進位制檔案必須與LLDB除錯的二進位制檔案相同,這樣偏移前基地址、ASLR偏移、偏移後基地址才能對應得上。這裡由於我的iPad對應的ARM是ARM64,所以之前在Mac上我使用arm64編譯的程式,將編譯的程式扔到IDA中分析,這裡我使用的是IDAPro6.6中的idaq64.exe進行分析的。(這裡所說的ASLR偏移指的是偏移後模組基地址-偏移前模組基地址,偏移後模組基地址:二進位制檔案載入到記憶體模組在記憶體中的首地址;偏移前模組基地址:模組在檔案中的首地址)

圖10 ARM64編譯後的程式在IDA中的分析

根據上圖我們可以看到地址0x100007D1C下方有兩個分支,即左邊的判斷是裝置已經越獄,而右邊的判斷是裝置沒有越獄。

之所以說這裡需要注意是因為我們在使用LLDB除錯程式下斷點時需要偏移前基地址,而這個偏移前基地址不同場景下是不一樣的。下圖為ARMv7編譯後使用IDA分析的結果,可以看到我們關注的地址變為了0xBE28。

圖11 ARMv7編譯後的程式在IDA中的分析

(4) 使用LLDB檢視ASLR偏移,此時需要知道幾個指令:ni執行下一條指令但不進入函式體,si執行下一條指令會進入函式體,image list列舉當前所以模組。

圖12 使用LLDB檢視ASLR偏移

上圖顯示jailbreak64還未啟動,現在除錯還發生在dyld內部,接下來一直執行ni命令,直到輸出結果出現卡頓(大約3秒左右,可以明顯察覺到)。到這裡不要再使用ni命令,dyld已經開始載入jailbreak64,我們使用image list -o -f檢視jailbreak64的ASLR偏移。

圖13 多次執行ni命令後檢視jailbreak64的偏移

根據上圖顯示jailbreak64的ASLR偏移為0x40000,所以可以確定斷點位置下在‘0x40000+0x100007D1C’處。

(5) 根據以上資訊我們就可以安心的下斷點了,使用br s -a 地址用來下斷點,使用指令p可以列印某處的值,使用指令register write給指定的暫存器賦值。

圖14 下斷點

上圖是在‘0x40000+0x100007D1C’處設定斷點

圖15 檢視x0的值並重新賦值來更改程式邏輯

我們使用p命令檢視一下此時x0的值,發現x0為1,使用register write命令將該x0的值改為0,然後輸入命令c繼續,可以看到我們成功的繞過了越獄檢測,程式執行結果為裝置未越獄。

在Cydia上可以安裝xCon軟體,據說是目前為止最強大的越獄檢測繞過工具,然而我安裝在我的iPad上並不適用,應該暫不支援IOS8.4吧。xCon可以繞過四種越獄檢測方法(根據特定的越獄檔案及檔案許可權判斷裝置是否越獄;根據沙盒完整性判斷裝置是否越獄;根據檔案系統的分割槽是否發生變化來檢查裝置是否越獄;根據是否安裝ssh來判斷裝置是否越獄)。用最近比較流行的一句話就是“你有一百種方式來檢測越獄”,而別人也有一百種方式來抵抗你的檢測。

0x06 魔高一尺道高一丈


剛才我們演示的是LLDB除錯來繞過越獄檢測,那麼如果我的程式阻止偵錯程式附加怎麼辦?我們來看下面的測試程式碼。

#!c
#include <sys/stat.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <unistd.h>
inline int checkDebugger() __attribute__((always_inline));
int main(){
    if(checkDebugger()){
        printf("Debugger attached\n");
        return 0;
    }
    printf("Debug detection bypassed\n");
    return 0;
}    

int checkDebugger(){
    int name[4];
    struct kinfo_proc info;
    size_t info_size = sizeof(info);
    info.kp_proc.p_flag = 0;
    name[0] = CTL_KERN;
    name[1] = KERN_PROC;
    name[2] = KERN_PROC_PID;
    name[3] = getpid();
    if(sysctl(name,4,&info,&info_size,NULL,0) == -1){
        return 1;
    }
    return (info.kp_proc.p_flag & P_TRACED) ? 1 : 0;
}

當一個應用被除錯的時候,會給程式設定一個標識(P_TRACED),可以檢測該程式是否有設定這個標識來檢測程式是否正在被除錯來保護應用。如果該程式發現自己被偵錯程式附加了程式,那麼將會輸出Debugger attached,如果正常執行該程式則會輸出Debug detection bypassed。實驗結果如下:

圖16 正常執行的輸出結果

圖17 使用LLDB偵錯程式附加後的輸出結果

在實際應用中可以在得知自己的程式被偵錯程式附加後自動退出,當然這也是可以透過下斷點的方式繞過,但是可以透過多處呼叫該方法來增加攻擊的難度。同時上面的程式碼採用宣告行內函數的方法,使編譯器將函式功能插到每處程式碼被呼叫的地方,而不至於某個特定的功能函式被劫持。

當然除了上述的這種反除錯的方法,還可以透過最佳化標記、去除符號等方法使反彙編複雜化。

0x07 冤冤相報何時了


0x06中所說的反除錯還可以透過打補丁的方式繞過,這樣就不需要每次都下斷點繞過,也會有更多的時間用LLDB來除錯其他核心業務。是不是感覺有些亂?因為的確是攻擊和防禦的方法都有很多,廠商採用多種防禦手段來增加攻擊成本,而攻擊者為了利益或者抱有挑戰心理的態度來見招拆招,真是冤冤相報啊。

參考文獻:

  1. [美]Jonathan Zdziarski《Hacking and Securing ios Application》
  2. 沙梓社,吳航《ios應用逆向工程》
  3. http://blog.ioactive.com/2015/09/the-ios-get-out-of-jail-free-card.html
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!