1、媒介
對(duì)用戶在Android移動(dòng)設(shè)備商保留首要的隱私文件,凡是采取一些加密保留的軟件。但在手機(jī)上實(shí)現(xiàn)隱私空間的軟件鱗次櫛比,可是標(biāo)題問題在于打開文件都需要利用該隱私空間,將加密文件解密惠姑且文件,然后再選擇利用法度打開文件。這將致利用戶首要文件在設(shè)備上明文的存在,存在泄漏的風(fēng)險(xiǎn)。
并且按照筆者的調(diào)研,對(duì)360隱私空間,利用法度對(duì)姑且文件點(diǎn)竄后不克不及再逆向加密回密文,導(dǎo)致加密把持只能一次進(jìn)行。LBE隱私空間,相對(duì)較好,但其姑且文件存在生命周期太長。
是以筆者經(jīng)由過程現(xiàn)有常識(shí)會(huì)商一種采取hook手藝實(shí)現(xiàn)的透明加解密編制,不需要在設(shè)備上生成姑且文件,從而呵護(hù)用戶首要隱私。
2、手藝要點(diǎn)
因?yàn)锳ndroid是基于linux內(nèi)核的開源系統(tǒng),按照說話環(huán)境不合可以分為Java層、Native C層、Linux Kernel層。Java層的安然是利用Java說話開辟,基于SDK,能實(shí)現(xiàn)的功能相對(duì)簡單。Linux Kernel層安然,需要從源碼做起,編譯本身的系統(tǒng),通用性不強(qiáng)。是以在Native C層,經(jīng)由過程JNI開辟,可利用linux供給的函數(shù)實(shí)現(xiàn)更多的功能。
在hook API方面與linux的hook近似利用ptrace 函數(shù)與plt表實(shí)現(xiàn),還可以采取Inline hook的編制實(shí)現(xiàn),可是不是是很不變,把持難度大年夜。其本質(zhì)都是劫持函數(shù)調(diào)用。
可是因?yàn)樘幱贚inux用戶態(tài),每個(gè)過程都有本身自力的過程空間,所以必需先注進(jìn)到所要hook的過程空間,點(diǎn)竄其內(nèi)存中的過程代碼,替代此中過程表的符號(hào)地址,是以其保存空間是所注進(jìn)的過程,只能對(duì)某一過程進(jìn)行HOOK。
Ptrace函數(shù)是調(diào)試法度所用,功能強(qiáng)大年夜,不但可以附加某一過程(PTRACE_ATTACH),并且可以肆意點(diǎn)竄方針過程的內(nèi)存空間(PTRACE_PEEKDATA,讀內(nèi)存。PTRACE_POKEDATA,寫內(nèi)存),乃至是存放器(PTRACE_SETREGS,PTRACE_GETREGS)
根基流程是操縱存放器指令間斷:
?、?PTRACE_ATTACH,綁定方針過程。
?、?PTRACE_GETREGS,獲得方針過程存放器狀況,并保留。
?、?PTRACE_PEEKDATA與PTRACE_POKEDATA共同,保留原代碼,寫進(jìn)要注進(jìn)的代碼到當(dāng)前運(yùn)行位置。
?、?PTRACE_SETREGS,恢復(fù)存放器狀況,并繼續(xù)履行,這是注進(jìn)的代碼開端在方針過程內(nèi)履行,注進(jìn)代碼完成HOOK,過程與Windows下類似。
?、?在HOOK完成后,注進(jìn)的代碼履行int3被ptrace捕獲,方針過程再次暫停履行。
?、?PTRACE_GETREGS,再次保留存放器。
?、?PTRACE_PEEKDATA與PTRACE_POKEDATA共同還原代碼。
?、?PTRACE_SETREGS,恢復(fù)存放器,方針過程繼續(xù)履行。
?、?PTRACE_DETACH,撤消綁定方針過程。
參考LBE實(shí)現(xiàn)道理和看雪上關(guān)于Hook Ioctl的文章都根基上遵循這類流程實(shí)現(xiàn)HOOK。
在大白hook工作機(jī)制后,對(duì)實(shí)現(xiàn)Android上的透明加解密需要找到open和close函數(shù)的符號(hào)存在哪個(gè)動(dòng)態(tài)鏈接庫中,hook該利用法度的這個(gè)動(dòng)態(tài)鏈接庫,在open把持進(jìn)行的時(shí)辰,將密文文件分塊解密到內(nèi)存中,并將該內(nèi)存中的文件標(biāo)識(shí)符返回。在close把持進(jìn)行的時(shí)辰將內(nèi)存中的明文加密到本地密文存儲(chǔ)。
3、關(guān)頭流程
1、瀏覽Android代碼查找打開文件和封鎖文件過程。這是我們實(shí)現(xiàn)透明加解密的關(guān)頭。
參考http://blog.chinaunix.net/uid-26926660-id-3326678.html的編制
可以發(fā)現(xiàn)讀取文件流的函數(shù)最終經(jīng)由過程JNI編制的read函數(shù)實(shí)現(xiàn),一樣打開文件的把持最終都回結(jié)于open函數(shù)。
而實(shí)現(xiàn)Java代碼的JNI撐持的動(dòng)態(tài)庫是nativehelper.so是以我們需要hook的動(dòng)態(tài)庫即nativehelper.so。
注:在Android初期版本即android2.3、Android4.0上open和close符號(hào)在nativehelper.so中,該文件有140k大年夜小。而在android4.1版本以上,谷歌重寫了android原生庫的實(shí)現(xiàn),nativehelper.so被拆分,筆者在4.0平臺(tái)進(jìn)行的開辟并沒有瀏覽尋覓4.1版本之上的。
2、進(jìn)行過程注進(jìn)和ELF節(jié)替代
過程注進(jìn)就是將一段代碼拷貝到方針過程,然后讓方針過程履行這段代碼的手藝。因?yàn)槿缭S的代碼機(jī)關(guān)起來比較復(fù)雜,所以實(shí)際環(huán)境下,只將很少的代碼注進(jìn)到方針過程,而將真正干事的代碼放到一個(gè)共享庫中,即.so文件。被注進(jìn)的那段代碼只負(fù)責(zé)加載這個(gè).so,并履行里面的函數(shù)。因?yàn)?so中的函數(shù)是在方針過程中履行的,所以在.so中的函數(shù)可以點(diǎn)竄方針過程空間的任何內(nèi)存,當(dāng)然也能夠加鉤子,從而達(dá)到改變方針過程工作機(jī)制的目標(biāo)。
當(dāng)然不是任何過程都有權(quán)限履行注進(jìn)把持的。Android平臺(tái)上的過程注進(jìn)是基于ptrace()的,要調(diào)用ptrace()需要有root權(quán)限。今朝市道上的主流安然軟件也都是基于過程注進(jìn)來治理和節(jié)制其他利用過程的。這也就是為甚么這些安然軟件需要獲得root權(quán)限的啟事。
關(guān)于若何.so注進(jìn)的實(shí)現(xiàn),可以參考看雪論壇的上的一個(gè)注進(jìn)庫LibInject 和洗大年夜師的一個(gè)開源項(xiàng)目Android Injector Library。
筆者對(duì)這類兩種編制都有嘗試,對(duì)Libinject,就是向方針過程中注進(jìn)libhook.so,起首調(diào)用ptrace()函數(shù),掛起該過程。然后遍歷過程加載的libc.so,經(jīng)由過程dlopen和dlsym函數(shù)點(diǎn)竄arm存放器的值,然后壓進(jìn)參數(shù),so路徑,并將之前找到的dlopen地址壓進(jìn)存放器,直接把持blx,便可讓方針過程調(diào)用dlopen加載我們的so,同理dlsym調(diào)用我們的so里的函數(shù)。
注進(jìn)完成后,會(huì)調(diào)用libhook.so庫中的hook_entry()函數(shù),該函數(shù)實(shí)現(xiàn)加載hook函數(shù)實(shí)現(xiàn)的動(dòng)態(tài)庫,并對(duì)libnativehelper.so的got表和plt表的遍歷和點(diǎn)竄。點(diǎn)竄成本身編寫的實(shí)現(xiàn)open函數(shù)和close函數(shù)的動(dòng)態(tài)庫中的符號(hào)地址。是以需要注進(jìn)兩個(gè)庫,因?yàn)閘ibhook.so在履行完后需要detach方針過程,從而釋放,而具體把持的動(dòng)態(tài)庫需要常駐內(nèi)存。實(shí)現(xiàn)常駐內(nèi)存需要在hook_entry()函數(shù)中顯式加載動(dòng)態(tài)庫。
以上注進(jìn)今后的過程都由本身編程實(shí)現(xiàn),可以或許加深對(duì)ELF格局理解。
而采取Android Injector Library則相對(duì)簡單,在調(diào)用可履行法度的主函數(shù)中實(shí)現(xiàn)便可:
這個(gè)文件編譯生成注進(jìn)進(jìn)口和符號(hào)表替代邏輯。
* 1、在該函數(shù)中加載libhook.so經(jīng)由過程此中的do_hook函數(shù)返回本來的open和close地址和要替代的新的open和close函數(shù)地址
* 2、然后靜態(tài)打開libnativehelper動(dòng)態(tài)庫,讀取其布局遍歷節(jié)表,找到全局符號(hào)表(GOT表),該表存儲(chǔ)了外部依托符號(hào)的地址
* 3、遍歷GOT表找到本來的open函數(shù)和close函數(shù)地址,別離替代為新的open函數(shù)和新的close函數(shù)便可

3、在進(jìn)修這一過程中,需要體味linux的ELF格局,以下是進(jìn)修ELF的筆記:拜見《法度員的自我涵養(yǎng)》假定熟諳則可跳過。


ehdr->e_shstrndx索引指向shstrtab的節(jié),可以用來索引節(jié)頭的字符串名稱描述。shstrtab表(Section Header String Table)保留段表頂用到的字符串,最多見的就是段名、
常常利用的段名申明
.rodata1Read Only Data,這類段里存放的是只讀數(shù)據(jù),好比字符串常量、全局const變量。跟”.rodata”一樣
.comment存放的是編譯器版本信息
.debug 調(diào)試信息
.dynamic動(dòng)態(tài)鏈接信息
.hash 符號(hào)哈希表
.line 調(diào)試時(shí)的行號(hào)表
.note 額外的編譯器信息。好比法度的公司名、發(fā)布版本號(hào)等
.strtab String Table.字符串表
.symtab Symbol Table.符號(hào)表
.shstrtabSection String Table.段名表
.plt .got動(dòng)態(tài)鏈接的跳轉(zhuǎn)表和全局進(jìn)口表
.init .fini法度初始化與終結(jié)代碼段
符號(hào)節(jié),遍歷節(jié)頭時(shí)辰。鑒定每個(gè)節(jié)的類型是不是是SHT_SYMTAB或SHT_DYNSYM,那么對(duì)應(yīng)的節(jié)就是符號(hào)節(jié)。符號(hào)節(jié)存放的是一張符號(hào)表,符號(hào)表也是一個(gè)持續(xù)存儲(chǔ)的布局?jǐn)?shù)組.
編程過程頂用到的變量和函數(shù)都可以稱之為符號(hào),一個(gè)ELF文件中其實(shí)不只有一個(gè)符號(hào)節(jié),凡是是兩個(gè),一個(gè)為”.dynsym”的動(dòng)態(tài)節(jié)類型為SHT_DYNSYM,所有引進(jìn)的外部符號(hào)在這里有所表現(xiàn),另外一個(gè)為SHT_SYMTAB,名字為“.symtab”保留了所有有效符號(hào)信息。
Symbol Table 符號(hào)表保留了一個(gè)法度在定位和重定位時(shí)需要的定義和援引的信息。一個(gè)符號(hào)表索引是響應(yīng)的下標(biāo)。符號(hào)表的存在乎義是表此刻多個(gè)方針文件進(jìn)行鏈接的時(shí)辰,在鏈接中,方針文件之間彼此拼合實(shí)際上是方針文件之間對(duì)地址的援引,即對(duì)函數(shù)和變量的地址的援引,而函數(shù)和變量可以統(tǒng)稱為符號(hào)(Symbol),函數(shù)名或變量名就是符號(hào)名(Symbol Name)。我們可以將符號(hào)看作是是鏈接中的粘合劑,全部鏈接過程就是基于符號(hào)才可以或許準(zhǔn)確完成。在符號(hào)表”.symtab“中,其也是像段表的布局一樣,是一個(gè)數(shù)組,每個(gè)數(shù)組元素是一個(gè)固定的布局來保留符號(hào)的相干信息,好比符號(hào)名(不是字符串,而是該符號(hào)名在字符串表的下標(biāo))、符號(hào)對(duì)應(yīng)的值(多是段中的偏移,也多是符號(hào)的虛擬地址)、符號(hào)大年夜小(數(shù)據(jù)類型的大年夜小)等等。符號(hào)表中記實(shí)的通常為全局符號(hào),好比全局變量、全局函數(shù)等等。
方針文件的符號(hào)表包含定位或重定位法度符號(hào)定義和援引時(shí)所需要的信息。符號(hào)表進(jìn)口布局定義以下:
typedef struct{
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
Unsigned char st_info;
Unsigned char st_other;
Elf32_Half st_shndx;
}Elf32_Sym;
此中st_name包含指向符號(hào)表字符串表(strtab)中的索引,從而可以獲得符號(hào)名。St_value指出符號(hào)的值,多是一個(gè)盡對(duì)值、地址等。St_size指出符號(hào)相干的內(nèi)存大年夜小,好比一個(gè)數(shù)據(jù)布局包含的字節(jié)數(shù)等。St_info劃定了符號(hào)的類型和綁定屬性,指出這個(gè)符號(hào)是一個(gè)數(shù)據(jù)名、函數(shù)名、section名仍是源文件名;并且指出該符號(hào)的綁定屬性是local、global仍是weak。
GOT表和PLT表
GOT(Global Offset Table)表中每項(xiàng)都是本運(yùn)行模塊要援引的一個(gè)全局變量或函數(shù)的地址。可以用GOT表來間接援引全局變量、函數(shù),也能夠把GOT表的首地址作為一個(gè)基 準(zhǔn),用相對(duì)該基準(zhǔn)的偏移量來援引靜態(tài)變量、靜態(tài)函數(shù)。因?yàn)榧虞d器不會(huì)把運(yùn)行模塊加載到固定地址,在不合過程的地址空間中,各運(yùn)行模塊的盡對(duì)地址、相對(duì)位 置都不合。這類不合反應(yīng)到GOT表上,就是每個(gè)過程的每個(gè)運(yùn)行模塊都有自力的GOT表,所以過程間不克不及共享GOT表。

動(dòng)態(tài)鏈接機(jī)制
起首回顧一下Linux平臺(tái)上,一個(gè)模塊甲需要調(diào)用別的一個(gè)模塊乙中的函數(shù)時(shí)的動(dòng)態(tài)鏈接機(jī)制:
1、模塊甲在編譯期間,將要援引的模塊乙的名字與函數(shù)名寫進(jìn)本身的符號(hào)表。
2、運(yùn)行期模塊甲調(diào)用時(shí),調(diào)用流程是從調(diào)用代碼到PLT表到GOT表再跳進(jìn)模塊乙。
而若何包管模塊甲的代碼能從其PLT/GOT跳到準(zhǔn)確的模塊乙進(jìn)口,這就是鏈接器做的工作。
尺度Linux鏈接器是ld.so,撐持懶綁定,也就是說,模塊甲在編譯期間生成的調(diào)用模塊乙的原始代碼,流程是從調(diào)用代碼到PLT表到鏈接器。運(yùn)行期第一次調(diào)模塊乙時(shí),起首進(jìn)進(jìn)鏈接器,鏈接器按照調(diào)用信息加載模塊乙搜索其符號(hào)并將找到的函數(shù)地址填進(jìn)GOT表,以后的后續(xù)調(diào)用流程就直接走PLT/GOT表了。這類機(jī)制能削減加載時(shí)的開消,為Linux發(fā)行版等采取。
Android當(dāng)然內(nèi)核基于Linux,但其動(dòng)態(tài)鏈接機(jī)制卻不是ld.so而是自帶的linker,不撐持懶綁定。也就是說,上述模塊甲乙假定在Android平臺(tái)上,則是模塊甲加載時(shí),linker就會(huì)按照模塊甲中的.rel.plt表和字符串表中的內(nèi)容加載模塊乙并搜刮其所需函數(shù)地址并預(yù)先填進(jìn)GOT表。以后調(diào)用流程每次都直接走PLT/GOT表,不再進(jìn)linker,PLT表中也省往了跳至linker的代碼,這類流程和“勤奮”綁定近似,倒是為反對(duì)供給了一點(diǎn)便利。假定反對(duì)懶綁定的進(jìn)口時(shí)模塊乙還沒加載地址也沒找到,反對(duì)就沒法進(jìn)行了。
要反對(duì)模塊甲對(duì)乙的調(diào)用,一般思路是經(jīng)由過程ptrace長途注進(jìn)并加載一新反對(duì)模塊至模塊甲,并搜刮模塊甲的GOT表,找到對(duì)模塊乙的調(diào)用地址,改成新模塊內(nèi)的某函數(shù)地址,然后新模塊內(nèi)的這個(gè)函數(shù)在進(jìn)行了本身的措置后,再跳到模塊乙中。
Android和Linux的鏈接器不合導(dǎo)致了內(nèi)存布局的差別,也導(dǎo)致了網(wǎng)優(yōu)勢行的Linux注進(jìn)與HOOK的編制行不通。網(wǎng)上的編制是經(jīng)由過程ptrace注進(jìn)后,搜刮dynamic的section中的PLTGOT區(qū),往里頭取link_map以遍歷此過程所加載的模塊來搜刮需要hook的函數(shù)地址。但Android上,dynamic的section的PLTGOT區(qū)前幾項(xiàng)都是空的,沒有l(wèi)ink_map這個(gè)數(shù)據(jù)布局,只能經(jīng)由過程闡發(fā)/proc//maps來遍歷模塊。
4、瀏覽代碼中的寄望事項(xiàng)
在Android Injector Library瀏覽過程中有幾個(gè)需要寄望的處所。
1)操縱捕獲SIGSEGV的無效內(nèi)存援引或段弊端的異常旌旗燈號(hào)來履行ptrace。
2)ptrace(PTRACE_PEEKTEXT, pid, addr, data)
描述:從內(nèi)存地址中讀取一個(gè)字節(jié),pid暗示被跟蹤的子過程,內(nèi)存地址由addr給出,data為用戶變量地址用于返回讀到的數(shù)據(jù)。
在Linux(i386)頂用戶代碼段與用戶數(shù)據(jù)段重合所以讀代替碼段和數(shù)據(jù)段數(shù)據(jù)措置是一樣的。
3)linker 首要用于實(shí)現(xiàn)共享庫的加載與鏈接。它撐持利用法度對(duì)庫函數(shù)的隱式和顯式調(diào)用。查找/system/bin/linker中加載的libdl.so,加載位置固定,定義了dlopen,dlcose,dlsym,dlerror。
4)有以下代碼理解,即dynsym和symtab的關(guān)系

5)在代碼中有關(guān)于dynsym符號(hào)讀取挨次的弊端。可是不影響利用。利用androidSDK下的東西readelf

5、需要編寫本身的open函數(shù)和close函數(shù)實(shí)現(xiàn)加解密把持
該過程利用Android 平臺(tái)下的openssl EVP編程,該過程的難度不大年夜。
關(guān)頭點(diǎn)一是在于利用密鑰空間機(jī)關(guān)。保舉密鑰空間利用數(shù)組。利用char*字符串即便在字符串最后存在’’也會(huì)因?yàn)閮?nèi)存中的其他內(nèi)容影響密鑰初始化,呈現(xiàn)意想不到的標(biāo)題問題。
關(guān)頭點(diǎn)二在close時(shí),參數(shù)只有文件描述符,可以經(jīng)由過程下述代碼獲得文件名。
關(guān)頭點(diǎn)三在于利用Openssl進(jìn)行對(duì)稱加解密時(shí)會(huì)填充到響應(yīng)的塊大年夜小,需要手動(dòng)剝離這些填充??梢圆扇H通用填充編制機(jī)關(guān)填充,或自立機(jī)關(guān)密文文件頭記實(shí)填充大年夜小。
http://en.wikipedia.org/wiki/Padding_(cryptography)
6、記得在Makefile文件中加進(jìn)
LOCAL_LDLIBS+=-L$(SYSROOT)/usr/lib -llog
LOCAL_LDLIBS+=-L$(SYSROOT)/usr/lib -lcrypto
LOCAL_LDLIBS+=-L$(SYSROOT)/usr/lib -lssl
7、需要再進(jìn)行密鑰治理模塊的開辟,該過程不再描述。
4、總結(jié)
該種方案可以或許實(shí)此刻android平臺(tái)上的透明加解密。不足的地方在于需要利用root權(quán)限,提早捕獲用戶法度啟動(dòng),對(duì)其進(jìn)行hook。在移動(dòng)設(shè)備上效力是瓶頸,并且文件不宜過大年夜。對(duì)docxlspdfppttxt等文本、jpg等圖片撐持較好,其他格局的文件筆者沒有進(jìn)行測試。