<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
<!-- code_chunk_output -->
<!-- /code_chunk_output -->
从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){
int 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));
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;
}
}
return gotsize/sizeof(Elf64_Sym);
}
之后把这两个函数调用写入线程函数就好了,我这里直接用0x25000了通过libart.so的程序头可以解析出来,每10秒检测一次
int *so ;
int size ;//写到全局变量里面循环就不会多次调用了
void anti1(){
while (1) {
pthread_mutex_lock(&mutex);
for (int n = 0; n < size; n++) {
// __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子进程,今天的内容就到这里,感谢大家观看。