- Android新攻防技術(shù)研究與應(yīng)用
通用軟件保護(hù)手段
- 源碼層級保護(hù):在編碼過程中實現(xiàn)的攻防手段
- ① 增加分析復(fù)雜度:多線程捻激、虛函數(shù)谢肾、模板望薄、靜態(tài)STL庫、回調(diào)等
- ② 常規(guī)攻防能力:AntiHook/AntiDebug/AntiInject/RootDetect/EmulatorDetect/...
- 混淆技術(shù):
- ③ 代碼混淆:如OLLVM
- ④ 常量混淆:主要是字符串混淆
- ⑤ 二進(jìn)制層級保護(hù):在生成軟件后肋联,對二進(jìn)制進(jìn)行處理進(jìn)行加固的手段
本文著重介紹①和③,其他方式網(wǎng)上資料較多這里不再贅述延欠。對于②提供如下參考資料:
https://github.com/obfuscator-llvm/obfuscator
Android常規(guī)攻防能力表:
調(diào)試檢測(Debug) | 反調(diào)試(AntiDebug) | 注入檢測(Inject) | 反注入(AntiInject) | Hook檢測(Hook)) | 反Hook(AntiHook) | Root檢測(RootDetect) | 模擬器檢測(EmulatorDetect) |
---|---|---|---|---|---|---|---|
進(jìn)程名檢測 默認(rèn)調(diào)試端口檢測 進(jìn)程調(diào)試狀態(tài)檢測 內(nèi)存斷點(diǎn)檢測 內(nèi)存讀寫檢測 <font color=#FF0000 bgcolor=orange>調(diào)試協(xié)議測試</font> 進(jìn)程文件二進(jìn)制匹配 |
ptrace保護(hù) <font color=#FF0000 bgcolor=orange>JDWP握手信號攔截</font> <font color=#FF0000 bgcolor=orange>JDWP調(diào)試模型破壞</font> Jni層反調(diào)試 |
常見注入工具檢測 環(huán)境變量檢測 加載模塊檢測 注入端口檢測 |
檢測加載模塊 | 常見Hook框架檢測 系統(tǒng)文件修改檢測 進(jìn)程模塊檢測 <font color=#FF0000 bgcolor=orange>Java native hook檢測</font> |
內(nèi)存模塊恢復(fù) | 常見su文件檢測 系統(tǒng)目錄權(quán)限檢測 Root工具檢測 |
系統(tǒng)屬性檢測 特殊文件檢測 手機(jī)號饮睬、硬件ID檢測 …… |
注:加紅為我們自己新增的高級功能
C++模板元常量字符串混淆
??模板元是C++ 11引入的新概念,用于將一些簡單函數(shù)執(zhí)行流程(分支/循環(huán))放在編譯期完成纱新,以提高運(yùn)行期性能甚至完成一些特殊功能展氓。在展示一個完整的模板元編程實現(xiàn)字符串加密函數(shù)前,這里先提供一些準(zhǔn)備知識:
- C++ 11提供constexpr關(guān)鍵字用于定義編譯期常量
- inline關(guān)鍵字是向編譯器請求內(nèi)聯(lián)脸爱;force_inline關(guān)鍵字是強(qiáng)制編譯器內(nèi)聯(lián)
- TIME用于在編譯期獲取時間
line01 #define force_inline __attribute__((always_inline))
line02 #define naked __attribute__ ((naked))
line03
line04 #ifndef vxSEED
line05 // If you don't specify the seed for algorithms, the time when compilation
line06 // started will be used, seed actually changes the results of algorithms...
line07 #define vxSEED ((__TIME__[7] - '0') * 1 + (__TIME__[6] - '0') * 10 + \
line08 (__TIME__[4] - '0') * 60 + (__TIME__[3] - '0') * 600 + \
line09 (__TIME__[1] - '0') * 3600 + (__TIME__[0] - '0') * 36000)
line0A #endif
line0B // The constantify template is used to make sure that the result of constexpr
line0C // function will be computed at compile-time instead of run-time
line0D template<uint32_t Const>
line0E struct vxConstantify {
line0F enum {
line10 VALUE = Const
line11 };
line12 };
line13
line14 // Compile-time mod of a linear congruential pseudorandom number generator,
line15 // the actual algorithm was taken from "Numerical Recipes" book
line16 constexpr uint32_t vxRandom(uint32_t Id) {
line17 return (1013904223 + 1664525 * ((Id > 0) ? (vxRandom(Id - 1)) : (vxSEED))) &
line18 0xFFFFFFFF;
line19 }
line1A
line1B // Compile-time random macros, can be used to randomize execution
line1C // path for separate builds, or compile-time trash code generation
line1D #define vxRANDOM(Min, Max) (Min + (vxRAND() % (Max - Min + 1)))
line1E #define vxRAND() (vxConstantify<vxRandom(__COUNTER__ + 1)>::VALUE)
line1F
line20 // Compile-time recursive mod of string hashing algorithm,
line21 // the actual algorithm was taken from Qt library (this
line22 // function isn't case sensitive due to vxTolower)
line23 constexpr char vxTolower(char Ch) {
line24 return (Ch >= 'A' && Ch <= 'Z') ? (Ch - 'A' + 'a') : (Ch);
line25 }
line26
line27 constexpr uint32_t vxHashPart3(char Ch, uint32_t Hash) {
line28 return ((Hash << 4) + vxTolower(Ch));
line29 }
line2A
line2B constexpr uint32_t vxHashPart2(char Ch, uint32_t Hash) {
line2C return (vxHashPart3(Ch, Hash) ^ ((vxHashPart3(Ch, Hash) & 0xF0000000) >> 23));
line2D }
line2E
line2F constexpr uint32_t vxHashPart1(char Ch, uint32_t Hash) {
line30 return (vxHashPart2(Ch, Hash) & 0x0FFFFFFF);
line31 }
line32
line33 constexpr uint32_t vxHash(const char *Str) {
line34 return (*Str) ? (vxHashPart1(*Str, vxHash(Str + 1))) : (0);
line35 }
line36
line37 // Compile-time generator for list of indexes (0, 1, 2, ...)
line38 template<uint32_t...>
line39 struct vxIndexList {
line3A };
line3B template<typename IndexList, uint32_t Right>
line3C struct vxAppend;
line3D template<uint32_t... Left, uint32_t Right>
line3E struct vxAppend<vxIndexList<Left...>, Right> {
line3F typedef vxIndexList<Left..., Right> Result;
line40 };
line41 template<uint32_t N>
line42 struct vxIndexes {
line43 typedef typename vxAppend<typename vxIndexes<N - 1>::Result, N - 1>::Result Result;
line44 };
line45 template<>
line46 struct vxIndexes<0> {
line47 typedef vxIndexList<> Result;
line48 };
line49
line4A template<uint8_t XorKey, uint8_t BitShiftKey, typename IndexList>
line4B struct vxEncStr;
line4C
line4D template<uint8_t XorKey, uint8_t BitShiftKey, uint32_t... Idx>
line4E struct vxEncStr<XorKey, BitShiftKey, vxIndexList<Idx...> > {
line4F uint8_t Value[sizeof...(Idx) + 1]; // Buffer for a string
line50 // 這里若不設(shè)置alinline則最大加密50字節(jié)
line51
line52 constexpr force_inline uint8_t vxEncCh(const char Ch, uint32_t Idx_) const {
line53 // do XOR
line54 return (uint8_t)((((Ch & 0xFF) ^ ((XorKey + Idx_) & 0xFF)) >> BitShiftKey) |
line55 (((Ch & 0xFF) ^ ((XorKey + Idx_) & 0xFF)) << (CHAR_BIT - BitShiftKey)));
line56 }
line57
line58
line59 // Compile-time constructor 有的編譯器數(shù)組初始化使用'{'遇汞,有的使用'('
line5A constexpr force_inline vxEncStr(const char *const Str) : Value{vxEncCh(Str[Idx], Idx)...} {
line5B static_assert(BitShiftKey < 8 && BitShiftKey >= 0, "Invalild BitShiftKey");
line5C static_assert(XorKey < 0x100 && XorKey >= 0, "Invalild XorKey");
line5D }
line5E
line5F // Run-time decryption
line60 char *decrypt() {
line61 for (uint32_t Idx_ = 0; Idx_ < sizeof...(Idx); Idx_++) {
line62 // do XoR XorKey + Idx_ may exceed 255
line63 Value[Idx_] = (uint8_t)((((Value[Idx_] & 0xFF) << BitShiftKey) |
line64 ((Value[Idx_] & 0xFF) >> (CHAR_BIT - BitShiftKey))) ^ ((XorKey + Idx_) & 0xFF));
line65 }
line66 Value[sizeof...(Idx)] = '\0';
line67 return (char*)Value;
line68 }
line69 };
line6A
line6B // Compile-time hashing macro, hash values changes using the first pseudorandom number in sequence
line6C #define vxHASH(Str) (uint32_t)(vxConstantify<vxHash(Str)>::VALUE ^ vxConstantify<vxRandom(1)>::VALUE)
line6D // Compile-time string encryption macro
line6E #define vxStrEnc_(Size, Str) (vxEncStr<vxRANDOM(0, 0xFF), vxRANDOM(0, CHAR_BIT - 1), \
line6F vxIndexes<Size - 1>::Result>(Str).decrypt())
line70 #define vxStrEnc(Str) vxStrEnc_(sizeof(Str), Str)
line71 #define vxStrEncDbl(Str) vxStrEnc_(sizeof(Str), vxStrEnc_(sizeof(Str), Str))
line72 #define vxStrEncTri(Str) vxStrEnc_(sizeof(Str), vxStrEnc_(sizeof(Str), vxStrEnc_(sizeof(Str), Str)))
說明如下:
- vxRandom為模板元函數(shù),用于在編譯期生成隨機(jī)數(shù)簿废,(模擬rand實現(xiàn))空入,每次調(diào)用該函數(shù)以獲取不同隨機(jī)數(shù)
- vxHASH為模板元函數(shù),用于在編譯期對靜態(tài)字符串生成散列值
- vxStrEnc為模板元函數(shù)族檬,用于在編譯期對靜態(tài)字符串實施加密歪赢,這樣在編譯生成的二進(jìn)制中只會存在經(jīng)過加密的16進(jìn)制序列,該序列在運(yùn)行期在vxStrEnc調(diào)用點(diǎn)使用decrypt解密单料。注意decrypt需要在運(yùn)行期執(zhí)行因此不能是模板元函數(shù)
- vxStrEncDbl和vxStrEncTri是進(jìn)行多次加密埋凯,增強(qiáng)復(fù)雜度
- 模板元函數(shù)對于靜態(tài)字符串或靜態(tài)數(shù)組的處理,是通過模板迭代成單個元素扫尖,這一過程較為復(fù)雜白对,上述代碼借助vxIndexList生成一個靜態(tài)索引數(shù)組來實現(xiàn)迭代字符串元素
??綜上,模板元編程是將運(yùn)行時的某些操作放到編譯期來做换怖,提高了程序運(yùn)行效率甩恼,也會略微增加生成二進(jìn)制大小;而上述代碼的加密極大增強(qiáng)了分析復(fù)雜度条摸,結(jié)合OLLVM效果更好
調(diào)試檢測
常用調(diào)試器進(jìn)程名檢測/二進(jìn)制匹配
- 對于C調(diào)試器采用進(jìn)程檢測
??如果當(dāng)前進(jìn)程被調(diào)試則必然存在調(diào)試器進(jìn)程悦污,因此可以通過進(jìn)程名方式檢測,存在下面是常見調(diào)試器對應(yīng)的進(jìn)程名及二進(jìn)制中特征字符串
IDA -> android_server / android_server_pie -> “IDA Android 32-bit remote”
GDB -> gdb / gdbserver -> “GNU gdbserver”
LLDB -> lldbserver
- Adbwifi類APP進(jìn)程檢測
??adbwifi是在Root權(quán)限下屈溉,使用wifi進(jìn)行Java/C層調(diào)試的工具塞关,因此若存在這種進(jìn)程有可能設(shè)備已經(jīng)Root或正在進(jìn)行調(diào)試,我收集了一些常用包名用于檢測這類調(diào)試器:
com.gikir.gikdbg com.soynerdito.adbnetworkenabler
adb.wifi.woaiwhz.wifiadbandroid com.twiceyuan.devmode
chendx.wifiadb.main com.vn.tooa.adbwireless
cn.com.wxtech.adbwireless com.wmendez.adbovertcp
com.adam.adbWifi com.youzi.adbwifi
com.adb com.zhoupeng.adbwireless
com.androidfree.adbwifi fr.ydelouis.yrelessadb
com.eboy.adbwireless hcursor.adbcontrol
com.fly.wifiadb info.nakajimadevnakajima.adboverlanswitcher
com.ilovn.app.wifi_adb jps.android.adbtcp
com.liam_w.networkadb me.meowo.adb
com.palmcrust.yawadb moe.haruue.wadb
com.rair.adbwifi net.wiagames.adbon
com.rockolabs.adbkonnect rfo.mougino.waf
com.ryosoftware.adbw ru.bartwell.easyremoteadb
com.scopionstudio.adbwifi vn.android.adbwireless
za.co.henry.hsu.adbwirelessbyhenry
二進(jìn)制匹配和進(jìn)程名檢測技術(shù)總結(jié):
優(yōu)點(diǎn):二進(jìn)制文件比對這種檢測方式可以彌補(bǔ)進(jìn)程名檢測的不足子巾。
缺點(diǎn):始終是最簡單的調(diào)試檢測方式帆赢,也最容易被繞過
常用調(diào)試器端口檢測
??移動端采用遠(yuǎn)程調(diào)試器的調(diào)試方式很常見,其結(jié)構(gòu)是遠(yuǎn)程調(diào)試服務(wù)器+本地調(diào)試器线梗,通過調(diào)試協(xié)議進(jìn)行通信椰于,其中遠(yuǎn)程調(diào)試器server端安裝在移動設(shè)備上,完成如下斷點(diǎn)等所有實際操作仪搔,而本地調(diào)試器位于主機(jī)上瘾婿,接受用戶指令轉(zhuǎn)化成調(diào)試協(xié)議數(shù)據(jù)發(fā)送給服務(wù)端。作為服務(wù)器就要在設(shè)備端綁定端口烤咧,因此可以檢測某些默認(rèn)端口號來檢測調(diào)試器的存在偏陪,如:IDA默認(rèn)調(diào)試端=23946。(同理還有注入服務(wù)器煮嫌、Hook服務(wù)器)
??本地端口開放狀態(tài)可以通過讀取/proc/net/tcp和/proc/net/udp解析笛谦。Android基于linux系統(tǒng),存在特殊的unix文件句柄用于socket通信昌阿,Android Studio使用lldb調(diào)試采用這種方式調(diào)試饥脑,可以在/proc/net/unix發(fā)現(xiàn)蹤跡。
??由于采用默認(rèn)端口的服務(wù)端很少懦冰,因此對處于ESTABLISHED狀態(tài)的服務(wù)端灶轰,這里提供協(xié)議測試方式分辨調(diào)試服務(wù)器(對于已連接狀態(tài)的服務(wù)端無法檢測)(該方式由于與服務(wù)器進(jìn)行通信,有一定可能使本地服務(wù)器崩潰)刷钢,目的如下:
- 檢測調(diào)試器
??在本地調(diào)試器連接到該遠(yuǎn)程調(diào)試服務(wù)器之前笋颤,通過發(fā)送特定bit請求,觀察服務(wù)器響應(yīng)内地,如果響應(yīng)匹配調(diào)試器的響應(yīng)bit伴澄,則判定為調(diào)試器 - 反調(diào)試
??若判定為調(diào)試器,則不進(jìn)行釋放socket句柄操作瓤鼻,這樣對于只接受單連接的服務(wù)器,本地調(diào)試器無法再連接該服務(wù)器贤重,也就無法繼續(xù)調(diào)試茬祷,如GDB的gdbserver、IDA的android_server并蝗。某些特殊調(diào)試器甚至存在允許關(guān)閉自身的指令祭犯,因此可以通過發(fā)送特定bit請求使服務(wù)器關(guān)閉秸妥。這種方式對于接受多鏈接的服務(wù)器無效。
一次典型的gdb協(xié)議通信如下:
Send Rreceive
+ => qSupported:multiprocess+;swbreak+;hwbreak+;
QStartNoAckMode => +$OK
調(diào)試器端口檢測技術(shù)總結(jié):
優(yōu)點(diǎn):端口協(xié)議測試這種檢測方式沃粗,對于尚未發(fā)起調(diào)試攻擊過程有一定阻礙作用
缺點(diǎn):無法對已經(jīng)建立的調(diào)試過程產(chǎn)生影響
硬件可調(diào)試性檢測
??這部分是對設(shè)備是否開啟調(diào)試功能及APP自身是否開啟調(diào)試功能的這類環(huán)境變量進(jìn)行檢測粥惧,包括如下字段:
init.svc.adbd adbd是否運(yùn)行
persis.sys.usb.config usb是否連接
sys.usb.config 。最盅。突雪。。
sys.usb.state 涡贱。咏删。。问词。
ro.debuggable 設(shè)備是否可調(diào)試
development_settings_enabled 是否開啟開發(fā)者模式
FLAG_DEBUGGABLE app是否可調(diào)試
isDebuggerConnected JDWP調(diào)試器是否連接(dalvik/art)
??其中ro.debuggable需要設(shè)備擁有Root權(quán)限才可修改督函,開啟后所有App均處于可調(diào)試狀態(tài),無論App本身的設(shè)置激挪,F(xiàn)LAG_DEBUGGABLE則是App自身對Java層可調(diào)試性的配置辰狡,Release版會自動設(shè)為False
進(jìn)程調(diào)試狀態(tài)檢測
??C層調(diào)試器在連接后,調(diào)用到系統(tǒng)調(diào)用ptrace垄分,這個過程會留下一些痕跡宛篇,最明顯的是/proc/pid/status中的TracerPid字段,該處存放調(diào)試器的進(jìn)程ID锋喜。該標(biāo)記可由本進(jìn)程些己、父進(jìn)程、子進(jìn)程讀取嘿般。同理/proc/pid/stat第二個字段會在本進(jìn)程在調(diào)試器中暫停時被系統(tǒng)置為T/t段标,但由于本進(jìn)程已經(jīng)暫停,該標(biāo)記只能由父進(jìn)程或子進(jìn)程讀取炉奴。類似的還有/proc/pid/wchan的ptrace_stop標(biāo)志逼庞。
??上面這些僅僅是針對子進(jìn)程,然而linux支持只對某個線程做調(diào)試瞻赶,這樣就可以繞過上面的檢測赛糟,因此這里需要遍歷所有子進(jìn)程并檢測標(biāo)志位,由于調(diào)試器附加進(jìn)程后會寫進(jìn)程和所有子線程的標(biāo)志位砸逊,因此無需對/proc/pid/做單獨(dú)檢測璧南,目前計劃采用fork子進(jìn)程方式監(jiān)測如下位置
/proc/[pid]/task/[tid]/status TracerPid: [pid]
/proc/[pid]/task/[tid]/stat T/t
/proc/[pid]/task/[tid]/wchan ptrace_stop
??另一個檢測點(diǎn)是ptrace的PTRACE_TRACEME,對于App進(jìn)程該語句會讓zygote附加調(diào)試自身师逸,由于只能被一個調(diào)試器調(diào)試司倚,因此其他調(diào)試器無法繼續(xù)調(diào)試本進(jìn)程。而如果該語句執(zhí)行失敗說明有調(diào)試器優(yōu)先zygote附加本進(jìn)程。
作為輔助檢測點(diǎn)动知,檢測isDebuggerConnected及 jni層的dalvik/art實現(xiàn)dvmDbgIsDebuggerConnected/Dbg::IsDebuggerActive
斷點(diǎn)檢測
??在進(jìn)行調(diào)試時攻擊者經(jīng)常下斷點(diǎn)以便攔截到代碼執(zhí)行或數(shù)據(jù)抓取皿伺,因此對通過斷點(diǎn)檢測到調(diào)試器存在,斷點(diǎn)類型如下
- C層軟件斷點(diǎn)
??軟件斷點(diǎn)是通過修改特定指令所在的內(nèi)存數(shù)據(jù)為特殊指令盒粮,使進(jìn)程在執(zhí)行到該處時因為斷點(diǎn)指令或異常而中斷在調(diào)試器中鸵鸥。因此可以通過比對代碼與文件的數(shù)據(jù)比較檢測到斷點(diǎn)的存在(也可能是hook或其他情況)。為了提高速度丹皱,先暫存文件數(shù)據(jù)的哈希值妒穴,每次檢測將該哈希與內(nèi)存哈希進(jìn)行比較,若不同再檢測是否存在斷點(diǎn)种呐。下面是常見的軟件斷點(diǎn)機(jī)器碼模式(參照GDB/LLDB/IDA等調(diào)試器的實現(xiàn))
ARCH SWBP(little-endian)
Arm 01 00 9f ef
arm-eabi f0 01 f0 e7
thumb 01 de
thumb2 f0 f7 00 a0
mips 0d 00 05 00
arm64 00 00 20 d4 (aarch64)
x86/x64 cc
??這里特殊的一點(diǎn)是軟件斷點(diǎn)可以通過異常實現(xiàn)宰翅,因此理論上寫入無效指令觸發(fā)異常即可,無需一定匹配上述模式
- C層硬件(觀察)斷點(diǎn)
??硬件斷點(diǎn)是處理器提供的用于調(diào)試的一組寄存器功能爽室,通過設(shè)置寄存器實現(xiàn)在內(nèi)存處讀汁讼、寫、執(zhí)行時中斷到調(diào)試器阔墩。因此通過檢測特定寄存器的數(shù)據(jù)嘿架,可以檢測是否被調(diào)試。(暫不實現(xiàn))
文件系統(tǒng)監(jiān)控檢測
??調(diào)試器在附加進(jìn)程后會讀取該進(jìn)程/proc/pid下的一些數(shù)據(jù)啸箫,其中一些行為可明顯區(qū)分出調(diào)試器行為
Gdbserver 讀取/proc/pid/mem 讀寫內(nèi)存
Android_server 讀取/proc/pid/maps 獲取模塊信息
反調(diào)試
Java虛擬機(jī)反調(diào)試
??原理:Android Java層調(diào)試采用JDWP調(diào)試協(xié)議耸彪,而該協(xié)議實現(xiàn)于libart.so/libdvm.so中,遠(yuǎn)程調(diào)試過程是一個網(wǎng)絡(luò)通信過程忘苛,從源碼分析可以發(fā)現(xiàn)JdwpState是一個函數(shù)指針數(shù)組蝉娜,部分結(jié)構(gòu)如下:
void *accept; // 服務(wù)端接收遠(yuǎn)端調(diào)試連接
void *establish; // 建立調(diào)試器服務(wù)端
void *shutdown; // 結(jié)束進(jìn)程
void *processIncoming; // 處理遠(yuǎn)端發(fā)來的調(diào)試消息
??調(diào)試過程:establish->accept->processIncoming->processIncoming->…->shutdown;每當(dāng)用戶下斷點(diǎn)時扎唾,主機(jī)上客戶端(jdb)會將該請求發(fā)到移動端的調(diào)試器服務(wù)端(libdvm)召川,調(diào)用processIncoming處理該消息。因此只要將processIncoming指向自定義回調(diào)即可檢測到JDWP調(diào)試過程胸遇,而使其指向shutdown則會在下次調(diào)試事件時退出進(jìn)程荧呐。JDWP調(diào)試分為adb和socket兩種方式,因此有2套JdwpState對象纸镊,分別處理通過socket直接進(jìn)行JDWP調(diào)試和通過adb橋進(jìn)行JDWP調(diào)試的通信邏輯
??實現(xiàn):dalvik虛擬機(jī)實現(xiàn)在libdvm.so中倍阐,而art虛擬機(jī)實現(xiàn)在libart.so中;libdvm.so導(dǎo)出dvmJdwpAndroidAdbTransport/dvmJdwpAndroidSocketTransport函數(shù)逗威,可得到getState函數(shù)指針峰搪,通過調(diào)用getState()得到JdwpState結(jié)構(gòu);libart.so則導(dǎo)出art::JDWP::JdwpAdbState/art::JDWP::JdwpSocketState虛表指針凯旭,也是JdwpState類似結(jié)構(gòu)概耻。利用該方式實現(xiàn)Java調(diào)試檢測:
- 將processIncoming設(shè)置為自定義回調(diào)楣颠,可以檢測到啟動以后adbd嘗試連接app jdwp端口的行為,adbd只有打開開發(fā)者模式以后才會存在咐蚯。調(diào)試器一旦連接則會經(jīng)過processIncoming,但經(jīng)過processIncoming的連接不一定是調(diào)試器行為弄贿,也可能是adbd的通信
- 由于Android7.0增加了系統(tǒng)保護(hù)機(jī)制春锋,使得dlopen對某些敏感動態(tài)庫操作失敗,因此采用Hook read函數(shù)差凹,檢測jdwp協(xié)議握手字段JDWP-Handshake期奔。若sosafe模塊加載晚于jdwp調(diào)試過程建立,該法無效
Java虛擬機(jī)反調(diào)試技術(shù)總結(jié):
優(yōu)點(diǎn):利用Android虛擬機(jī)自身原理進(jìn)行反調(diào)試危尿,隱蔽性和實用性較強(qiáng)呐萌,使調(diào)試器拒絕服務(wù)
缺點(diǎn):Android7.0開啟了系統(tǒng)動態(tài)庫保護(hù)機(jī)制,無法使用獲取到私有函數(shù)谊娇,無法利用這種方式
JNI層反調(diào)試
??由于JNI調(diào)試器(gdbserver/android_server/…)最終通過內(nèi)核ptrace與應(yīng)用程序交互肺孤,在檢測到調(diào)試器直接退出進(jìn)程是明智的選擇,這里檢測到調(diào)試器后的執(zhí)行延時退出济欢,使用內(nèi)聯(lián)匯編的exit函數(shù)或異常機(jī)制實現(xiàn)退出赠堵,避免被很容易的檢測到。后期加入擦除回溯棧之類的保護(hù)措施法褥,防止被調(diào)試器跟蹤到關(guān)鍵代碼
掛鉤檢測
常用HOOK工具包名檢測
??一般掛鉤是通過注入和Hook框架工具茫叭,取得目標(biāo)進(jìn)程控制權(quán),然后通過inline hook/got hook等方式進(jìn)行掛鉤半等。因此收集Xposed/Substrate等工具安裝信息作為最簡單的檢測方式揍愁,包名分別為de.robv.android.xposed.installer com.saurik.substrate。這里先通過java層獲取package那么杀饵,如果失敗則通過執(zhí)行pm list package命令從native層直接獲取所有安裝包名莽囤,避免被攔截到
系統(tǒng)文件改動檢測
??Xposed/Substrate這類工具通過修改系統(tǒng)文件的方式,盡可能將自身在最早的時機(jī)加載:
- Substrate通過替換liblog.so凹髓,將自身加載時機(jī)提前到app_process運(yùn)行前烁登,liblog.so加載前的時機(jī)
- Xposed通過替換app_process,將自身加載時機(jī)提前到app_process執(zhí)行時
??通過檢測替換后文件特征蔚舀,確定系統(tǒng)文件是否被修改過饵沧,即可得知系統(tǒng)是否安裝Hook框架;由于app_process可能有/system/bin/app_process /system/bin/app_process32 /system/bin/app_process64共存的情況赌躺,所以取當(dāng)前進(jìn)程/proc/self/exe指向的app_process文件為準(zhǔn)
進(jìn)程模塊檢測
??Xposed/Frida這類工具通過在zygote中加載模塊實現(xiàn)某些功能狼牺,由于zygote是所有App進(jìn)程的孵化器,因此zygote注入的模塊自然的被App進(jìn)程繼承礼患。通過檢測zygote進(jìn)程和App進(jìn)程的加載模塊(/proc/self/maps)是钥,可以檢測是否被掛鉤和注入掠归,已知Android平臺掛鉤/注入工具模塊名如下
ProbeDroid libProbeDroid.so
Frida frida-agent.so frida-gadget.so
Cydia Substrate libsubstrate.so libDalvikLoader.so libAndroidCydia.so libAndroidLoader.so *.cy.so
Xposed XposedBridge.jar
Dynamorio libdynamorio.so
??由于某些是開源項目,模塊名可以人為修改悄泥,因此在模塊名檢測的基礎(chǔ)上增加對二進(jìn)制模塊文件的匹配虏冻,其中.cy.so是用于substrate的自定義注入模塊
Java層調(diào)用棧檢測
??在Hook框架生效時,如果當(dāng)前線程恰好在Hook框架調(diào)用范圍內(nèi)弹囚,會在線程回溯棧產(chǎn)生Hook框架自身的函數(shù)和類名:
Xposed de.robv.android.xposed.XposedBridge
Cydia Substarte com.saurik.substrate.MS$2.invoked
??經(jīng)過分析厨相,這種方式并不可取,因為要求當(dāng)前線程已經(jīng)處于被hook框架調(diào)用的代碼中鸥鹉,檢測率極低且結(jié)果也不可靠
Java類成員函數(shù)屬性校驗
??由于常見的對于Java函數(shù)的掛鉤方式是修改Java函數(shù)為native函數(shù)實現(xiàn)蛮穿,因此通過檢測各個Java類成員函數(shù)是否為Native屬性判斷是否存在Java層掛鉤。在檢測前提前下發(fā)一個包含所有非native函數(shù)(包括所有系統(tǒng)和app的類成員函數(shù))的數(shù)據(jù)表毁渗,在檢測時刻比對當(dāng)前所有已加載類的native函數(shù)存在該表中的情況践磅,即為存在掛鉤。(由于App可隨時加載dex灸异,且每次檢測只能獲取到當(dāng)前已load的class府适,因此下發(fā)非native表比native函數(shù)表可靠)。這種方式目前只實現(xiàn)了Dalvik部分肺樟,Art由于各版本私有函數(shù)存在差異细溅,比較復(fù)雜。
??這種方式利用的是JDWP通信接口儡嘶,以Dalvik為例喇聊,dvmDbgGetClassList函數(shù)可以獲取當(dāng)前所有已加載類,dvmDbgOutputAllMethods函數(shù)可以獲取到所有類成員函數(shù)信息蹦狂,通過這兩個函數(shù)就可以遍歷所有已加載類成員函數(shù)誓篱。
加載模塊內(nèi)存校驗
??由于sosafe模塊加載時機(jī)可能晚于掛鉤時機(jī),因此需要比對文件和內(nèi)存模塊的區(qū)別凯楔,判斷是否存在Jni層掛鉤窜骄。通常hook框架使用inline hook/got hook,因此會修改elf代碼段和got表摆屯,這樣就會產(chǎn)生和文件不一致的情況邻遏。通過檢測文件和內(nèi)存哈希監(jiān)測是否存在Jni層hook,即使是Java層Hook虐骑,仍然會在Native層有Hook行為准验,因此這種方式可以檢測Java/Jni層Hook。具體操作如下:
- 啟動時初始化模塊表廷没,記錄elf文件的.text段哈希
- 在Hook檢測時糊饱,獲取內(nèi)存elf的.text段哈希,若不同則判斷是否為幾種情況之一:軟件斷點(diǎn) /Inline Hook/文件修改颠黎,若發(fā)現(xiàn)inline hook需要判斷跳轉(zhuǎn)點(diǎn)是否位于廠商模塊/系統(tǒng)自帶模塊/百度安全組件另锋,不是則記錄
- 在Hook檢測時滞项,枚舉所有g(shù)ot表元素,檢查指針?biāo)鶎倌K夭坪,不是廠商模塊/系統(tǒng)自帶模塊/百度安全組件則記錄
注入檢測
常用注入工具進(jìn)程名檢測/二進(jìn)制匹配
檢測常見注入工具的服務(wù)器進(jìn)程文判,如Frida-server adbiserver
檢測環(huán)境變量注入
檢測LD_LIBRARY_PATH LD_PRELOAD是否存在不合法路徑
加載模塊名檢測/二進(jìn)制匹配
與掛鉤檢測相同
常用注入工具端口檢測
Frida-server常用27042端口通信
ROOT檢測
常用ROOT工具路徑包名檢測
- Root工具:利用漏洞下載su到本地,如360一鍵root
- 刷機(jī)工具:通過刷入Root過的系統(tǒng)鏡像實現(xiàn)Root室梅,如刷機(jī)大師
- 權(quán)限管理工具:在已獲得Root權(quán)限的機(jī)器上管理Root權(quán)限
- 其他工具:需要Root權(quán)限執(zhí)行的app律杠,如hook框架
常見su文件路徑檢測
??設(shè)備Root后,一定在本地存在su文件以便提權(quán)竞惋,因此可以通過檢測su存在判斷設(shè)備是否root。Su文件路徑選擇常見路徑+環(huán)境變量$PATH灰嫉,su文件名選擇收集到的su文件名拆宛。后期可能加入檢測su文件是否可執(zhí)行的邏輯,而由于是否成功獲取root權(quán)限取決于權(quán)限管理器讼撒,因此可能給用戶彈窗或獲取失敗的情況
常見路徑 常見su
/sbin/ su
/vendor/bin/ us
/system/sbin/ .su
/system/bin/ .suv
/system/xbin/ .suo
/system/usr/ .uv
/system/bin/.ext/ au
/system/bin/failsafe/ k.sud
/system/sd/sbin/ ku.sud
/system/sd/xbin/ .rgs
/system/sd/bin/ .tmpsu
/system/usr/we-need-root/ daemonsu
/system/sd/ kinguser_su"
/system/ cm_su
/data/local/
/data/local/bin/
/data/local/xbin/
/data/local/sbin/
常見系統(tǒng)目錄權(quán)限修改檢測
??未Root的手機(jī)某些文件夾是擁有r--或---權(quán)限的浑厚,如默認(rèn)情況/data文件夾不可讀,而在root后這些文件夾屬性會發(fā)生變化根盒,利用這個特性可檢測是否root钳幅,默認(rèn)只讀目錄:
/data /vendor/bin /system/bin /etc
/ /sys /system/xbin /proc
/system /sbin /system/sbin /dev
虛擬機(jī)檢測
檢測設(shè)備特征
某些模擬器會在以下系統(tǒng)屬性中存在特征,比如nox炎滞,goldfish, ttVM….
ro.build.description
ro.hardware
ro.product.brand
ro.product.device
ro.product.manufacturer
...
有些模擬器采用默認(rèn)手機(jī)號:
15555215554
15555215556
15555215558
...
有些模擬器采用固定hardwareid
000000000000000
e21833235b6eef10
012345678912345
...
有些模擬器是基于qemu/virtualBox敢艰,因此系統(tǒng)目錄存在特殊文件:
/dev/socket/genyd
/dev/socket/baseband_genyd
/dev/socket/qemud
/dev/qemu_pipe
...