從inlinehook角度檢測frida
總述
在使用frida的過程中,在我的認知里其實frida只做了2件事情,一件是注入,一件是hook,作為注入的特征我們可以通過ptrace(PTRACE_TRACEME,NULL,0,0),或者從文件里面索引有關frida字符串這樣的方式來檢測frida,那么作為hook特征是否也能夠檢測frida呢?
frida inline hook寫法
答案是肯定的,既然frida是能夠定向跳轉從而更改內存中的代碼,那么一定是用到了inlinehook的技術,那么它是怎么使用的inlinehook呢,我們可以使用ida(當然直接看frida源碼,但是那樣真的很麻煩就連我自己寫的inlinehook都很難快速的分析清楚)去看一下,我隨便hook了一個函數比如art::ClassLinker::LoadMethod,注意要先附加frida然后再使用ida鏈接,否則frida無法附加已經被ptrace附加的進程,然后,結果如下圖
function main() {
var libart = Module.enumerateSymbols("libart.so");
var addr = NULL;
for (var n in libart) {
if (libart[n].name.indexOf("lassLinker10LoadMethodERKNS") >= 0) {
addr = libart[n].address;
break;
}
}
Interceptor.attach(addr, {
onEnter: function (arg) {
}
})
}

可以看到frida的hook原理就是將函數的開頭改成這樣的一串16進制,0xd61f020058000050和我之前寫的inlinehook差不多,只是它用了x16寄存器我用了x17寄存器,那么這里就有一個思路了,我們能否通過so中是否有這一段來判斷用戶使用了frida呢?答案是肯定的,但是需要我們稍作調整,就是在每個函數的開頭檢測是否有0xd61f020058000050這樣的一段代碼,創建線程即可,那么下面就開始實現,首先試一下單個函數能不能成功,我使用了上篇文章提到的,提取符號首地址的方式findsym
void anti1(){
while (1) {
sleep(1);
int so = findsym("/system/lib64/libart.so",
"_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_21ClassDataItemIteratorENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE");
long long as = *(long long *) reinterpret_cast<long>((char *) startr - 0x25000 + so);
if(as==0xd61f020058000050) {
__android_log_print(6, "r0ysue", "i find frida %p",(long long *) reinterpret_cast<long>((char *) startr - 0x25000 + so));
}
}
}
pthread_create(&thread, nullptr, reinterpret_cast<void *(*)(void="" *)="">(anti1), nullptr);

檢測libart是否使用inlinehook
可以看到當我以attach的方式鏈接上去的時候,成功的檢測到了frida,那么其實我們就可以遍歷符號表來獲得一個so中所有的函數首地址來檢測是否使用了frida,由於之前也說過,某些函數不在導出表中,而在符號表中,所以可能沒有辦法通過程序頭獲取,所以系統so這方面用節頭獲取,自身so方面用程序頭獲取(自身so路徑不太好確定,所以直接用內存中的就好),這里以libart.so和libnative-lib.so來做一個例子。首先就是libart.so,這里我封裝了2個函數一個是獲得所有符號的首地址,一個是獲得大小都是通過節頭表索引得到的,其中enumsym這個函數用到了2個int,第一個size是最大值,第二個start1是最小值,通過外部傳入,因為有些符號表的值大於文件所以容易溢出
int* enumsym(const char* lib,int size,int start1){
int fd;
void *start;
struct stat sb;
fd = open(lib, O_RDONLY);
fstat(fd, &sb);
start = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
Elf64_Ehdr header;
memcpy(&header, start, sizeof(Elf64_Ehdr));
int secoff = header.e_shoff;
int secsize = header.e_shentsize;
int secnum = header.e_shnum;
int secstr = header.e_shstrndx;
Elf64_Shdr strtab;
memcpy(&strtab, (char *) start + secoff + secstr * secsize, sizeof(Elf64_Shdr));
int strtaboff = strtab.sh_offset;
char strtabchar[strtab.sh_size];
memcpy(&strtabchar, (char *) start + strtaboff, strtab.sh_size);
Elf64_Shdr enumsec;
int gotoff = 0;
int gotsize = 0;
int strtabsize = 0;
int stroff = 0;
for (int n = 0; n < secnum; n++) {
memcpy(&enumsec, (char *) start + secoff + n * secsize, sizeof(Elf64_Shdr));
if (strcmp(&strtabchar[enumsec.sh_name], ".symtab") == 0) {
gotoff = enumsec.sh_offset;
gotsize = enumsec.sh_size;
}
if (strcmp(&strtabchar[enumsec.sh_name], ".strtab") == 0) {
stroff = enumsec.sh_offset;
strtabsize = enumsec.sh_size;
}
}
int realoff=0;
char relstr[strtabsize];
Elf64_Sym tmp;
memcpy(&relstr, (char *) start + stroff, strtabsize);
int* sdc= static_cast<int *="">(malloc(gotsize / sizeof(Elf64_Sym)*sizeof(int *)));//存儲返回值數組
for (int n = 0; n < gotsize; n = n + sizeof(Elf64_Sym)) {
memcpy(&tmp, (char *)start + gotoff+n, sizeof(Elf64_Sym));
// __android_log_print(6, "r0ysue", "%x",gotoff+n);
sdc[n/sizeof(Elf64_Sym)]=tmp.st_value;
if(tmp.st_value>size||tmp.st_value<start1) sdc[n="" sizeof(elf64_sym)]="start1;" }="" return="" sdc;="" int="" getsymsize(const="" char*="" lib){="" fd;="" void="" *start;="" struct="" stat="" sb;="" fd="open(lib," o_rdonly);="" *打開="" etc="" passwd="" *="" fstat(fd,="" &sb);="" 取得文件大小="" start="mmap(NULL," sb.st_size,="" prot_read,="" map_private,="" fd,="" 0);="" elf64_ehdr="" header;="" memcpy(&header,="" start,="" sizeof(elf64_ehdr));="" secoff="header.e_shoff;" secsize="header.e_shentsize;" secnum="header.e_shnum;" secstr="header.e_shstrndx;" elf64_shdr="" strtab;="" memcpy(&strtab,="" (char="" *)="" +="" secsize,="" sizeof(elf64_shdr));="" strtaboff="strtab.sh_offset;" char="" strtabchar[strtab.sh_size];="" memcpy(&strtabchar,="" strtaboff,="" strtab.sh_size);="" enumsec;="" gotoff="0;" gotsize="0;" strtabsize="0;" stroff="0;" for="" (int="" n="0;" <="" secnum;="" n++)="" {="" memcpy(&enumsec,="" if="" (strcmp(&strtabchar[enumsec.sh_name],="" ".symtab")="=" 0)="" ".strtab")="=" sizeof(elf64_sym);="" ```="" 之后把這兩個函數調用寫入線程函數就好了,我這里直接用0x25000了通過libart.so的程序頭可以解析出來,每10秒檢測一次="" ```c="" *so="" ;="" size="" 寫到全局變量里面循環就不會多次調用了="" anti1(){="" while="" (1)="" pthread_mutex_lock(&mutex);="" size;="" __android_log_print(6,="" "r0ysue",="" "i="" find="" frida="" %p="" %x="" %x",(long="" long="" )="" reinterpret_cast<long="">((char *) startr - 0x25000 + so[n]),so[n],n);
long long as = *(long long *) reinterpret_cast<long>((char *) startr - 0x25000 + so[n]);
// __android_log_print(6, "r0ysue", "i find frida %d %x %p",n,so[n],as);
if (as == 0xd61f020058000050) {
__android_log_print(6, "r0ysue", "i find frida %p",
(long long *) reinterpret_cast<long>((char *) startr - 0x25000 +
so[n]));
}
}
sleep(5);
pthread_mutex_unlock(&mutex);
}
}
void __init(){
so = enumsym("/system/lib64/libart.so", (long) end - (long) startr,0x25000);
size = getsymsize("/system/lib64/libart.so");
}
看一下結果,效果不錯就是檢測的慢一點但是也達到了預定的目標

檢測libnative-lib是否使用inlinehook
接着我們繼續搞libnative-lib,這個由於目錄特殊性所以我們很難找到它的目錄從節頭里面搜索符號地址,所以我們只能用程序頭搜索它的導出函數,這個之前的文章也講過,這里就直接貼代碼了,這里注意一點,如何確定符號表的大小,仔細觀察就知道符號表后面就是字符串表,那么用符號表偏移減去字符串表偏移就是符號表的大小
void initanti4(){//用來初始化符號表數組和大小
char line[1024];
int *startr;
int *end;
int n=1;
FILE *fp=fopen("/proc/self/maps","r");
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, "libnative-lib.so") ) {
__android_log_print(6,"r0ysue","");
if(n==1){
startr = reinterpret_cast<int *="">(strtoul(strtok(line, "-"), NULL, 16));
end = reinterpret_cast<int *="">(strtoul(strtok(NULL, " "), NULL, 16));
}
else{
strtok(line, "-");
end = reinterpret_cast<int *="">(strtoul(strtok(NULL, " "), NULL, 16));
}
n++;
}
}
int phof = 0;
Elf64_Ehdr header;
memcpy(&header, startr, sizeof(Elf64_Ehdr));
uint64 rel = 0;
size_t size = 0;
long *plt = nullptr;
char *strtab_ = nullptr;
Elf64_Sym *symtab_ = nullptr;
Elf64_Phdr cc;
memcpy(&cc, ((char *) (startr) + header.e_phoff), sizeof(Elf64_Phdr));
for (int y = 0; y < header.e_phnum; y++) {
memcpy(&cc, (char *) (startr) + header.e_phoff + sizeof(Elf64_Phdr) * y,
sizeof(Elf64_Phdr));
if (cc.p_type == 6) {
phof = cc.p_paddr - cc.p_offset;
}
}
for (int y = 0; y < header.e_phnum; y++) {
memcpy(&cc, (char *) (startr) + header.e_phoff + sizeof(Elf64_Phdr) * y,
sizeof(Elf64_Phdr));
if (cc.p_type == 2) {
Elf64_Dyn dd;
for (y = 0; y == 0 || dd.d_tag != 0; y++) {
memcpy(&dd, (char *) (startr) + cc.p_offset + y * sizeof(Elf64_Dyn) + 0x1000,
sizeof(Elf64_Dyn));
if (dd.d_tag == 5) {
strtab_ = reinterpret_cast< char *>((char *) startr + dd.d_un.d_ptr - phof);
}
if (dd.d_tag == 6) {
symtab_ = reinterpret_cast<elf64_sym *="">((
(char *) startr + dd.d_un.d_ptr - phof));
}
}
}
}
libnative= static_cast<long *="">(malloc(8*((long) (strtab_) - (long) symtab_) / sizeof(Elf64_Sym)));
for (int n=0;n<((long)(strtab_)-(long)symtab_)/sizeof(Elf64_Sym);n=n+ 1){
Elf64_Sym * s = symtab_ + n;
libnative[n]= (long)((char *) startr + s->st_value);
__android_log_print(6,"r0ysue","%p",*(long*)libnative[n]);
}
libsz=((long)(strtab_)-(long)symtab_)/sizeof(Elf64_Sym);
}
void anti4(){//主要的線程啟動的函數用來檢測frida用了全局變量
while (1) {
pthread_mutex_lock(&mutex);
__android_log_print(6, "r0ysue", "i find frida 1");
for (int n = 0; n < libsz; n++) {
long long as = *(long long *) reinterpret_cast<long>(libnative[n]);
// __android_log_print(6, "r0ysue", "i find frida %d %x %p",n,so[n],as);
if (as == 0xd61f020058000050) {
__android_log_print(6, "r0ysue", "i find frida %p",
(long long *) reinterpret_cast<long>(libnative[n]));
}
}
sleep(5);
pthread_mutex_unlock(&mutex);
}
}
void __init(){
initanti4();
pthread_create(&thread, nullptr, reinterpret_cast<void *(*)(void="" *)="">(anti4), nullptr);
}
使用frida腳本hook Java_com_r0ysue_antifrida_MainActivity_stringFromJNI,看一下效果
function main() {
var fromjni=Module.findExportByName("libnative-lib.so","Java_com_r0ysue_antifrida_MainActivity_stringFromJNI");
Interceptor.attach(fromjni,{
onEnter:function(arts){
console.log(arts);
}
})
}

從java hook 的角度檢測frida
上篇文章說了Java hook,的做法是將Java函數轉化為Native函數,所以我們可以在關鍵函數上檢測是否是Native的方法來檢測frida,當然也可以用解析dex的方式來獲得所有的類表和所有的函數名,我這里就先不搞了,有機會下篇文章再搞,我這里只提供一個最簡單的方式,就是通過Methid來獲得ArtMethod的方法檢測是否是Native函數,具體做法可以參考上一篇文章,我這里直接貼代碼了,我這里使用反射,但是注意由於線程不同所以env不同,不能將主線程的env傳入檢測線程使用。
void anti2(__int64 a1){
while (1) {
sleep(1);
if((~*(_DWORD *)(a1 + 4) & 0x80000) !=0)
__android_log_print(6,"r0ysue","i find frida %x", (~*(_DWORD *)(a1 + 4) & 0x80000) );
}
}
void __init(){
jclass ss=env->FindClass("com/r0ysue/antifrida/MainActivity");
jmethodID sss=env->GetMethodID(ss,"encr", "(I)I");
pthread_create(&thread1, nullptr, reinterpret_cast<void *(*)(void="" *)="">(anti2), sss);
}
以attach的方式附加檢測效果如下

總結
我這里提供的frida檢測方法還是比較簡單的,當然可以加上各種混淆和自實現線程創建函數,又或者是fork子進程,今天的內容就到這里,感謝大家觀看.
</elf64_sym></start1)>
