elf反调试初探

云水 云水     2022-11-02     118

关键词:

ELF反调试初探

http://www.freebuf.com/sectool/83509.html

 

ELF(Executable and Linkable Format)是Unix及类Unix系统下可执行文件、共享库等二进制文件标准格式。为了提高动态分析的难度,我们往往需要对ELF文件增加反调试措施。本文便对相关技术进行了总结归纳。

1.背景知识

1.1 ELF文件布局

ELF文件主要由以下几部分组成:

 

(1) ELF header
(2) Program header table,对应于segments
(3) Section header table,对应于sections
(4) 被Program header table或Sectionheader table指向的内容

注意这里segments与sections是两个不同的概念。之间的关系如下:

(1) 每个segments可以包含多个sections
(2) 每个sections可以属于多个segments
(3) segments之间可以有重合的部分

可以说,一个segment是若干个sections的组合。

下图是readelf工具输出的某ELF文件segments与sections信息:

技术分享图片

可以看到,segments部分包含各segments的地址、偏移、属性等;而sections部分则依次列出每个segment所包含的sections。注意到,sections通常为全小写字母,而segments通常为全大写字母。

此外,这两者之间另一个关键不同点是,sections包含的是链接时需要的信息,而segments包含运行时需要的信息。即,在链接时,链接器通过section header table去寻找sections;在运行时,加载器通过program header table去寻找segments。可见下图:

技术分享图片

一些比较重要的sections如下:

(1) .init_array: 动态库加载或可执行文件开始执行前调用的函数列表
(2) .text: 代码
(3) .got: Global offset table(GOT),包含加载时需要重定位的变量的地址
(4) .got.plt: 包含动态库中函数地址的GOT

一些比较重要的segments如下:

(1) LOAD: 运行时需要被加载进内存的segment
(2) GNU_STACK: 决定运行时栈是否可执行
(3) DYNAMIC: 动态链接信息,对应于.dynamicsection

1.2常用工具

在静态、动态分析ELF文件时,经常用到以下工具:

(1) ptrace    

#include <sys/ptrace.h>
 long ptrace(enum __ptrace_requestrequest, pid_t pid,
                 void *addr,void *data);

系统调用。用于监控其他进程,被gdb, strace, ltrace等使用

(2) strace

命令行工具。用于追踪进程与内核的交互,如系统调用、信号传递

(3) ltrace

命令行工具。类似于strace,但主要用于追踪库函数调用

(4) readelf/objdump

静态分析工具。用于读取ELF文件信息。

2.反调试技术

一般地,反调试是通过比较程序在未被调试和被调试两种运行状况下的不同点,来进行检测或中止程序运行。具体地,这里介绍几种常见的反调试方法。

2.1 ptrace自身进程

在同一时间,进程最多只能被一个调试器进行调试。于是,我们可以通过调试进程自身,来判断是否已经有其他进程(调试器)的存在。

具体地,我们使用ptrace来调试自身。示例代码如下:

#include <stdio.h>
#include <sys/ptrace.h>
int main(int argc, char *argv[]) 
    if(ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) 
       printf("Debugger detected");
       return 1;
     
   printf("All good");
   return 0;

这里我们使用‘PTRACE_TRACEME‘来指明进程将被调试,在此种情况下其他参数会被忽略。如果已经存在调试器,那么这次ptrace调用会失败,返回-1。由此我们可以实现对调试器的检测。实际运行结果如下图所示:

技术分享图片

2.2检查父进程名称

通常,我们在使用gdb调试时,是通过gdb <TARGET>这种方式进行的。而这种方式是启动gdb,fork出子进程后执行目标二进制文件。因此,二进制文件的父进程即为调试器。我们可通过检查父进程名称来判断是否是由调试器fork。示例代码如下

#include <stdio.h>
#include <string.h>
 
int main(int argc, char *argv[]) 
   char buf0[32], buf1[128];
   FILE* fin;
 
   snprintf(buf0, 24, "/proc/%d/cmdline", getppid());
   fin = fopen(buf0, "r");
   fgets(buf1, 128, fin);
   fclose(fin);
 
   if(!strcmp(buf1, "gdb")) 
       printf("Debugger detected");
       return 1;
     
   printf("All good");
   return 0;

这里我们通过getppid获得父进程的PID,之后由/proc文件系统获取父进程的命令内容,并通过比较字符串检查父进程是否为gdb。实际运行结果如下图所示:

技术分享图片

2.3检查进程运行状态

2.2节所提到的反调试方法,前提是被调试程序由调试器启动。但调试器也可以通过attach到某个已有进程的方法进行调试。这种情况下,被调试进程的父进程便不是调试器了。

在这种情况下,我们可以通过直接检查进程的运行状态来判断是否被调试。而这里使用到的依然是/proc文件系统。具体地,我们检查/proc/self/status文件。当进程正常运行而未被调试时,该文件的内容如下图:

而当我们使用gdb <TARGET FILE> <TARGET PID>命令,attach到目标进程进行调试后,status文件内容变化如下图:

技术分享图片

可见,进程状态由sleeping变为tracing stop,TracerPid也由0变为非0的数,即调试器的PID。由此,我们便可通过检查status文件中TracerPid的值来判断是否有正在被调试。示例代码如下:

#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) 
   int i;
   scanf("%d", &i);
   char buf1[512];
   FILE* fin;
   fin = fopen("/proc/self/status", "r");
   int tpid;
   const char *needle = "TracerPid:";
   size_t nl = strlen(needle);
   while(fgets(buf1, 512, fin)) 
       if(!strncmp(buf1, needle, nl)) 
           sscanf(buf1, "TracerPid: %d", &tpid);
           if(tpid != 0) 
                printf("Debuggerdetected");
                return 1;
           
       
    
   fclose(fin);
   printf("All good");
   return 0;

实际运行结果如下图所示:

技术分享图片

值得注意的是,/proc目录下包含了进程的大量信息。我们在这里是读取status文件,此外,也可通过/proc/self/stat文件来获得进程相关信息,包括运行状态。 

2.4设置程序运行最大时间

这种方法经常在CTF比赛中看到。由于程序在调试时的断点、检查修改内存等操作,运行时间往往要远大于正常运行时间。所以,一旦程序运行时间过长,便可能是由于正在被调试。 

具体地,在程序启动时,通过alarm设置定时,到达时则中止程序。示例代码如下:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void alarmHandler(int sig) 
   printf("Debugger detected");
   exit(1);

void__attribute__((constructor))setupSig(void) 
   signal(SIGALRM, alarmHandler);
   alarm(2);

int main(int argc, char *argv[]) 
   printf("All good");
   return 0;

在此例中,我们通过__attribute__((constructor)),在程序启动时便设置好定时。实际运行中,当我们使用gdb在main函数下断点,稍候片刻后继续执行时,则触发了SIGALRM,进而检测到调试器。如下图所示:

技术分享图片

顺便一提,这种方式可以轻易地被绕过。我们可以设置gdb对signal的处理方式,如果我们选择将SIGALRM忽略而非传递给程序,则alarmHandler便不会被执行,如下图所示:

技术分享图片

2.5检查进程打开的filedescriptor

如2.2中所说,如果被调试的进程是通过gdb <TARGET>的方式启动,那么它便是由gdb进程fork得到的。而fork在调用时,父进程所拥有的fd(file descriptor)会被子进程继承。由于gdb在往往会打开多个fd,因此如果进程拥有的fd较多,则可能是继承自gdb的,即进程在被调试。 

具体地,进程拥有的fd会在/proc/self/fd/下列出。于是我们的示例代码如下:

#include <stdio.h>
#include <dirent.h>
int main(int argc, char *argv[]) 
   struct dirent *dir;
   DIR *d = opendir("/proc/self/fd");
   while(dir=readdir(d)) 
       if(!strcmp(dir->d_name, "5")) 
           printf("Debugger detected");
           return 1;
       
    
   closedir(d);
   printf("All good");
   return 0;

这里,我们检查/proc/self/fd/中是否包含fd为5。由于fd从0开始编号,所以fd为5则说明已经打开了6个文件。如果程序正常运行则不会打开这么多,所以由此来判断是否被调试。运行结果见下图:

技术分享图片

3.总结

以上列出的反调试技术中,往往只进行了一次检测。为了提高强度,我们可以fork得到一个新的进程,在这个子进程中,每隔一段时间对父进程进行一次检测。 

然而,这些技术都面临存在另外一个致命弱点:可以通过反汇编静态分析,找到相应的检测代码并修改,从而绕过反调试检测。因此,我们通常还需要对二进制文件进行混淆以对抗静态分析,这样与反调试技术相结合,才能得到理想的保护效果。

* 作者:银河实验室(企业账号),转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

 

反调试手法之createprocess反调试(代码片段)

              反调试手法之CreateProcess反调试在学习Win32创建进程的时候.我们发现了有一个进程信息结构体.STARTUPINFO.这个结构体可以实现反调试.具体CreateProcess可以参考上一篇博客.:  https://www.cnblogs.com/iBi... 查看详情

过反调试

...二者本为一体破解技术就不要我多介绍了,下面我来介绍反调试技术也就是所谓的防破解技术反调试技术可以简单通俗的理解为:防止OD分析软件的技术,也就是反调试技术那么反调试技术又有几种呢?下面我介绍几种常用反调... 查看详情

反调试-checkremotedebuggerpresent

和IsDebuggerPresent差不多简单的反调试技术 查看详情

反调试-isdebuggerpresent

目前这个是我知道的最简单的反调试方法了 查看详情

反调试

反调试IsDebuggerPresent()__readgsqword(0x60)得到指向PEB64的指针,pPeb->BeingDebugged.__readfsdword(0x30)CheckRemoteDebuggerPresent(GetCurrentProcess(),&bIsDbgPresent)BOOL HeapFlags(),64位下,得到指向PEB的地址加 查看详情

.NET:如何隔离反调试类库?

】.NET:如何隔离反调试类库?【英文标题】:.NET:Howtoisolateananti-debuggingclasslibrary?【发布时间】:2022-01-2212:28:51【问题描述】:我构建了一个.NET类库并使用混淆器通过反调试对其进行混淆。我使用我的混淆类库构建了一个测试项... 查看详情

javascript奇淫技巧:反调试

JavaScript奇淫技巧:反调试本文,将分享几种JS代码反调试技巧,目标是:实现防止他人调试、动态分析自己的代码。检测调试,方法一:用console.log检测代码:varc=newRegExp("1");c.toString=function()alert("检测到调试")console.log(c);原理:... 查看详情

《逆向工程核心原理》学习笔记:反调试技术(代码片段)

目录前言一、反调试技术概况二、静态反调试技术1、PEB2、NtQueryInformationProcess()(1)ProcessDebugPort(0x7)(2)ProcessDebugObjectHandle(0x1E)(3)ProcessDebugFlags(0x1F)(4)例子(5)破解之法3、NtQuerySystemIn... 查看详情

一种基于tls的高级反调试技术(代码片段)

...越引起人们的重视。在反盗版技术中,起最大作用的当属反调试技术。然而传统的反调试技术都存在一个弱点:他们都在程序真正开始执行之后才采取反调试手段。实际上在反调试代码被执行前,调试器有大量的时间来影响程序... 查看详情

java层反调试

...调式,在build.prop(boot.img),ro.debugable=1。一:实例演示java层反调试以“百度加固”为例。1.将样本拖入jdax-gui中,进行反编译,来到加固程序的onCreate里,如下图所示。2 查看详情

反调试技术常用api,用来对付检测od和自动退出程序

在调试一些病毒程序的时候,可能会碰到一些反调试技术,也就是说,被调试的程序可以检测到自己是否被调试器附加了,如果探知自己正在被调试,肯定是有人试图反汇编啦之类的方法破解自己。为了了解如何破解反调试技术... 查看详情

《逆向工程核心原理》学习笔记:反调试技术(代码片段)

目录前言一、反调试技术概况二、静态反调试技术1、PEB2、NtQueryInformationProcess()(1)ProcessDebugPort(0x7)(2)ProcessDebugObjectHandle(0x1E)(3)ProcessDebugFlags(0x1F)(4)例子 查看详情

《逆向工程核心原理》学习笔记:反调试技术(代码片段)

目录前言一、反调试技术概况二、静态反调试技术1、PEB2、NtQueryInformationProcess()(1)ProcessDebugPort(0x7)(2)ProcessDebugObjectHandle(0x1E)(3)ProcessDebugFlags(0x1F)(4)例子 查看详情

ios逆向笔记之反调试以及反反调试和反反反调试ptrace篇

参考技术A1.ptrace(processtrace进程跟踪)为了方便软件的开发和调试,UNIX早期版本就提供了一种对运行进程进行跟踪和控制的手段,那就是系统调用ptrace.通过ptrace可以实现对另一个进程实现调试和跟踪.同时,ptrace提供了一个非常有用的... 查看详情

c++反调试(基于seh的setunhandledexceptionfilter)

终于!终于把静态反调试串了一遍,虽然没有包含全部但是也对反调试的轮廓有了初步的认识。但是这一切才刚刚开始,翻过一座山(坑),还有另一座山(坑)在等着你?刚发现csdn还有个发表情的功能,这就是传说中的彩蛋吗... 查看详情

frida反调试

...某app时,想用frida进行hook分析其执行流程,遇到了frida的反调试。这里记录一下出现问题的情况和反调试的实现.Android8frida12.11.18windows10使用frida-U注入app结果如下这里有两个com.shark.tracerpidapp名称的进程我们直接进入adb查看进程可... 查看详情

逆向分析反调试程序

...行下正常2.打密码密码错误3.用OD调试发现报异常说明做了反调试(OD不要用插件不然他会反反调试看不到)4.(这里要用到win7了因为win10报异常不知道在哪里)win10效果win7效果可以看出异常地址在哪里5.知道异常地址在哪了用ida静... 查看详情

ida动态调试破解alicrackme与反调试对抗(代码片段)

文章目录前言APK破解(上)1.1静态分析1.2动态分析反调试对抗2.1Ptrace2.2全局调试2.3反反调试APK破解(下)3.1重新编译3.2动态调试总结前言在前面的文章中IDA动态调试破解EXE文件与分析APK流程介绍了IDA对APK进行动态调试分析的简单流程&#... 查看详情