push 4
push offset isdebugged
push 7 ProcessDebugPort
push -1
call NtQueryInformationProcess
test eax, eax
jne @ExitError
cmp isdebugged, 0
jne @DebuggerDetected在本例中,首先把NtQueryInformationProcess的参数压入堆栈。这些参数介绍如下:第一个是句柄(在本例中是0),第二个是进程信息的长度(在本例中为4字节),接下来是进程信息类别(在本例中是7,表示ProcessDebugPort),下一个是一个变量,用于返回是否存在调试器的信息。如果该值为非零值,那么说明该进程正运行在一个调试器下;否则,说明一切正常。最后一个参数是返回长度。使用这些参数调用NtQueryInformationProcess后的返回值位于isdebugged中。随后测试该返回值是否为0即可。另外,还有其他一些检测调试器的方法,如检查设备列表是否含有调试器的名称,检查是否存在用于调试器的注册表键,以及通过扫描内存以检查其中是否含有调试器的代码等。另一种非常类似于EPO的方法是,通知PE加载器通过PE头部中的线程局部存储器(TLS)表项来引用程序的入口点。这会导致首先执行TLS中的代码,而不是先去读取程序的入口点。因此,TLS在程序启动就可以完成反调试所需检测。从TLS启动时,使得病毒得以能够在调试器启动之前就开始运行,因为一些调试器是在程序的主入口点处切入的。4.探测单步执行恶意软件还能够通过检查单步执行来检测调试器。要想检测单步执行的话,我们可以把一个值放进堆栈指针,然后看看这个值是否还在那里。如果该值在那里,这意味着,代码正在被单步执行。当调试器单步执行一个进程时,当其取得控制时需要把某些指令压入栈,并在执行下一个指令之前将其出栈。所以,如果该值仍然在那里,就意味着其它正在运行的进程已经在使用堆栈。下面的示例代码展示了恶意软件是如何通过堆栈状态来检测单步执行的:Mov bp,sp选择堆栈指针Push ax 将ax压入堆栈Pop ax 从堆栈中选择该值Cmp word ptr [bp -2],ax 跟堆栈中的值进行比较Jne debug 如果不同,说明发现了调试器。 如上面的注释所述,一个值被压入堆栈然后又被d出。如果存在调试器,那么堆栈指针–2位置上的值就会跟刚才d出堆栈的值有所不同,这时就可以采取适当的行动。5.在运行时中检测速度衰减通过观察程序在运行时是否减速,恶意代码也可以检测出调试器。如果程序在运行时速度显著放缓,那就很可能意味着代码正在单步执行。因此如果两次调用的时间戳相差甚远,那么恶意软件就需要采取相应的行动了。Linux跟踪工具包LTTng/LTTV通过观察减速问题来跟踪病毒。当LTTng/LTTV追踪程序时,它不需要在程序运行时添加断点或者从事任何分析。此外,它还是用了一种无锁的重入机制,这意味着它不会锁定任何Linux内核代码,即使这些内核代码是被跟踪的程序需要使用的部分也是如此,所以它不会导致被跟踪的程序的减速和等待。6.指令预取如果恶意代码篡改了指令序列中的下一条指令并且该新指令被执行了的话,那么说明一个调试器正在运行。这是指令预取所致:如果该新指令被预取,就意味着进程的执行过程中有其他程序的切入。否则,被预取和执行的应该是原来的指令。7.自修改代码恶意软件也可以让其他代码自行修改(自行修改其他代码),这样的一个例子是HDSpoof。这个恶意软件首先启动了一些异常处理例程,然后在运行过程中将其消除。这样一来,如果发生任何故障的话,运行中的进程会抛出一个异常,这时病毒将终止运行。此外,它在运行期间有时还会通过清除或者添加异常处理例程来篡改异常处理例程。在下面是HDSpoof清除全部异常处理例程(默认异常处理例程除外)的代码。exception handlers before:0x77f79bb8 ntdll.dll:executehandler2@20 + 0x003a
0x0041adc9 hdspoof.exe+0x0001adc9
0x77e94809 __except_handler3exception handlers after:0x77e94809 __except_handler30x41b770: 8b44240c mov eax,dword ptr [esp+0xc]
0x41b774: 33c9 xor ecx,ecx
0x41b776: 334804 xor ecx,dword ptr [eax+0x4]
0x41b779: 334808 xor ecx,dword ptr [eax+0x8]
0x41b77c: 33480c xor ecx,dword ptr [eax+0xc]
0x41b77f: 334810 xor ecx,dword ptr [eax+0x10]
0x41b782: 8b642408 mov esp,dword ptr [esp+0x8]
0x41b786: 648f0500000000 pop dword ptr fs:[0x0]下面是HDSpoof创建一个新的异常处理程序的代码。0x41f52b: add dword ptr [esp],0x9ca0x41f532: push dword ptr [dword ptr fs:[0x0]0x41f539: mov dword ptr fs:[0x0],esp8.覆盖调试程序信息一些恶意软件使用各种技术来覆盖调试信息,这会导致调试器或者病毒本身的功能失常。通过钩住中断INT 1和INT 3(INT 3是调试器使用的 *** 作码0xCC),恶意软件还可能致使调试器丢失其上下文。这对正常运行中的病毒来说毫无妨碍。另一种选择是钩住各种中断,并调用另外的中断来间接运行病毒代码。下面是Tequila 病毒用来钩住INT 1的代码:new_interrupt_one: push bp mov bp,sp cs cmp b[0a],1 masm mod. needed je 0506 masm mod. needed cmp w[bp+4],09b4 ja 050b masm mod. needed push ax push es les ax,[bp+2] cs mov w[09a0],ax masm mod. needed cs mov w[09a2],es masm mod. needed cs mov b[0a],1 pop es pop ax and w[bp+6],0feff pop bp iret一般情况下,当没有安装调试器的时候,钩子例程被设置为IRET。V2Px使用钩子来解密带有INT 1和INT 3的病毒体。在代码运行期间,会不断地用到INT 1和INT 3向量,有关计算是通过中断向量表来完成的。一些病毒还会清空调试寄存器(DRn的内容。有两种方法达此目的,一是使用系统调用NtGetContextThread和NtSetContextThread。而是引起一个异常,修改线程上下文,然后用新的上下文恢复正常运行,如下所示: push offset handlerpush dword ptr fs:[0]mov fs:[0],espxor eax, eaxdiv eax generate exceptionpop fs:[0]add esp, 4continue execution...handler:mov ecx, [esp+0Ch] skip divadd dword ptr [ecx+0B8h], 2 skip divmov dword ptr [ecx+04h], 0 clean dr0mov dword ptr [ecx+08h], 0 clean dr1mov dword ptr [ecx+0Ch], 0 clean dr2mov dword ptr [ecx+10h], 0 clean dr3mov dword ptr [ecx+14h], 0 clean dr6mov dword ptr [ecx+18h], 0 clean dr7xor eax, eaxret上面的第一行代码将处理程序的偏移量压入堆栈,以确保当异常被抛出时它自己的处理程序能取得控制权。之后进行相应设置,包括用自己异或自己的方式将eax设为0,以将控制权传送给该处理程序。div eax 指令会引起异常,因为eax为0,所以AX将被除以零。该处理程序然后跳过除法指令,清空dr0-dr7,同样也把eax置0,表示异常将被处理,然后恢复运行。9.解除调试器线程我们可以通过系统调用NtSetInformationThread从调试器拆卸线程。为此,将ThreadInformationClass设为0x11(ThreadHideFromDebugger)来调用NtSetInformationThread,如果存在调试器的话,这会将程序的线程从调试器拆下来。以下代码就是一个例子:push 0push 0push 11h ThreadHideFromDebuggerpush -2call NtSetInformationThread在本例中,首先将NtSetInformationThread的参数压入堆栈,然后调用该函数来把程序的线程从调试器中去掉。这是因为这里的0用于线程的信息长度和线程信息,传递的-2用于线程句柄,传递的11h用于线程信息类别,这里的值表示ThreadHideFromDebugger。10.解密解密可以通过各种防止调试的方式来进行。有的解密依赖于特定的执行路径。如果这个执行路径没被沿用,比如由于在程序中的某个地方启动了一个调试器,那么解密算法使用的值就会出错,因此程序就无法正确进行自身的解密。HDSpoof使用的就是这种技术。一些病毒使用堆栈来解密它们的代码,如果在这种病毒上使用调试器,就会引起解密失败,因为在调试的时候堆栈为INT 1所用。使用这种技术的一个例子是W95/SK病毒,它在堆栈中解密和构建其代码;另一个例子是Cascade病毒,它将堆栈指针寄存器作为一个解密密钥使用。代码如下所示:lea si, Start position to decryptmov sp, 0682 length of encrypted bodyDecrypt:xor [si], sidecryption key/counter 1xor [si], sp decryption key/counter 2inc siincrement one counterdec spdecrement the otherjnz Decrypt loop until all bytes are decryptedStart:Virus body对于Cascade病毒如何使用堆栈指针来解密病毒体,上面代码中的注释已经做了很好的说明。相反,Cryptor病毒将其密钥存储在键盘缓冲区中,这些密钥会被调试器破坏。Tequila使用解密器的代码作为解密钥,因此如果解密器被调试器修改后,那么该病毒就无法解密了。下面是Tequila用于解密的代码:perform_encryption_decryption: mov bx,0 mov si,0960 mov cx,0960 mov dl,b[si] xor b[bx],dl inc si inc bx cmp si,09a0 jb 0a61 masm mod. needed mov si,0960 loop 0a52 masm mod. needed retthe_file_decrypting_routine: push cs pop ds mov bx,4 mov si,0964 mov cx,0960 mov dl,b[si] add b[bx],dl inc si inc bx cmp si,09a4 jb 0a7e masm mod. needed mov si,0964 loop 0a6f masm mod. needed jmp 0390masm mod. needed人们正在研究可用于将来的新型反调试技术,其中一个项目的课题是关于多处器计算机的,因为当进行调试时,多处理器中的一个会处于闲置状态。这种新技术使用并行处理技术来解密代码。二、逆转录病毒逆转录病毒会设法禁用反病毒软件,比如可以通过携带一列进程名,并杀死正在运行的与表中同名的那些进程。许多逆转录病毒还把进程从启动列表中踢出去,这样该进程就无法在系统引导期间启动了。这种类型的恶意软件还会设法挤占反病毒软件的CPU时间,或者阻止反病毒软件连接到反病毒软件公司的服务器以使其无法更新病毒库。三、混合技术W32.Gobi病毒是一个多态逆转录病毒,它结合了EPO和其他一些反调试技术。该病毒还会在TCP端口666上打开一个后门。Simile(又名Metaphor)是一个非常有名的复合型病毒,它含有大约14,000行汇编代码。这个病毒通过寻找API调用ExitProcess()来使用EPO,它还是一个多态病毒,因为它使用多态解密技术。它的90%代码都是用于多态解密,该病毒的主体和多态解密器在每次感染新文件时,都会放到一个半随机的地方。Simile的第一个有效载荷只在3月、6月、9月或12月份才会激活。在这些月份的17日变体A和B显示它们的消息。变体C在这些月份的第18日显示它的消息。变体A和B中的第二个有效载荷只有在五月14日激活,而变体C中的第二个有效载荷只在7月14日激活。Ganda是一个使用EPO的逆转录病毒。它检查启动进程列表,并用一个return指令替换每个启动进程的第一个指令。这会使所有防病毒程序变得毫无用处。四、小结本文中,我们介绍了恶意软件用以阻碍对其进行逆向工程的若干反调试技术,同时介绍了逆转录病毒和各种反检测技术的组合。我们应该很好的理解这些技术,只有这样才能够更有效地对恶意软件进行动态检测和分析。
你不记得如何在代码中插入探针点了吗? 没问题!了解如何使用uprobe和kprobe来动态插入它们吧。 基本上,程序员需要在源代码汇编指令的不同位置插入动态探针点。探针点
探针点是一个调试语句,有助于探索软件的执行特性(即,执行流程以及当探针语句执行时软件数据结构的状态)。printk是探针语句的最简单形式,也是黑客用于内核攻击的基础工具之一。
因为它需要重新编译源代码,所以printk插入是静态的探测方法。内核代码中重要位置上还有许多其他静态跟踪点可以动态启用或禁用。 Linux内核有一些框架可以帮助程序员探测内核或用户空间应用程序,而无需重新编译源代码。Kprobe是在内核野中代码中插入探针点的动态方法之一,并且uprobe在用户应用程序中执行此 *** 作。
使用uprobe跟踪用户空间
可以通过使用thesysfs接口或perf工具将uprobe跟踪点插入用户空间代码。
使用sysfs接口插入uprobe
考虑以下简单测试代码,没有打印语句,我们想在某个指令中插入探针:
[source,c\n.test.c
#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>
编译代码并找到要探测的指令地址:
# gcc -o test test.\n# objdump -d test
假设我们在ARM64平台上有以下目标代码:
0000000000400620 <func_1>: 400620\t90000080\tadr\tx0, 410000 <__FRAME_END__+0xf6f8>
并且我们想在偏移量0x620和0x644之间插入探针。执行以下命令:
# echo 'p:func_2_entry test:0x620' >/sys/kernel/debug/tracing/uprobe_event\n# echo 'p:func_1_entry test:0x644' >>/扰弊sys/kernel/debug/tracing/uprobe_event\n# echo 1 >/sys/kernel/debug/tracing/events/uprobes/enable# ./test&
在上面的第一个和第二个echo语句中,p告诉我们这是一个简单的测试。(探测器可以是简单的或返回的。)func_n_entry是我们在跟踪输出中看到的名称,名称是可选字段,如果没有提供,我们应该期待像p_test_0x644这样的颂李山名字。test 是我们要插入探针的可执行二进制文件。如果test 不在当前目录中,则需要指定path_to_test / test。
0x620或0x640是从程序启动开始的指令偏移量。请注意>>在第二个echo语句中,因为我们要再添加一个探针。所以,当我们在前两个命令中插入探针点之后,我们启用uprobe跟踪,当我们写入events/ uprobes / enable时,它将启用所有的uprobe事件。程序员还可以通过写入在该事件目录中创建的特定事件文件来启用单个事件。一旦探针点被插入和启用,每当执行探测指令时,我们可以看到一个跟踪条目。
读取跟踪文件以查看输出:
# cat /sys/kernel/debug/tracing/trac\n# tracer: no\n\n# entries-in-buffer/entries-written: 8/8\n#P:\n\n# _-----=>irqs-of\n# / _----=>need-resche\n# | / _---=>hardirq/softir\n# || / _--=>preempt-dept\n# ||| / dela\n# TASK-PID CP\n# |||| TIMESTAMP FUNCTION# | | | |||| | |
我们可以看到哪个CPU完成了什么任务,什么时候执行了探测指令。
返回探针也可以插入指令。当返回该指令的函数时,将记录一个条目:
# echo 0 >/sys/kernel/debug/tracing/events/uprobes/enabl\n# echo 'r:func_2_exit test:0x620' >>/sys/kernel/debug/tracing/uprobe_event\n# echo 'r:func_1_exit test:0x644' >>/sys/kernel/debug/tracing/uprobe_event\n# echo 1 >/sys/kernel/debug/tracing/events/uprobes/enable
这里我们使用r而不是p,所有其他参数是相同的。请注意,如果要插入新的探测点,需要禁用uprobe事件:
test-3009 [002] .... 4813.852674: func_1_entry: (0x400644)
上面的日志表明,func_1返回到地址0x4006b0,时间戳为4813.852691。
# echo 0 >/sys/kernel/debug/tracing/events/uprobes/enabl\n# echo 'p:func_2_entry test:0x630' >/sys/kernel/debug/tracing/uprobe_events count=%x\n# echo 1 >/sys/kernel/debug/tracing/events/uprobes/enabl\n# echo >/sys/kernel/debug/tracing/trace# ./test&
当执行偏移量0x630的指令时,将打印ARM64 x1寄存器的值作为count =。
输出如下所示:
test-3095 [003] .... 7918.629728: func_2_entry: (0x400630) count=0x1
使用perf插入uprobe
找到需要插入探针的指令或功能的偏移量很麻烦,而且需要知道分配给局部变量的CPU寄存器的名称更为复杂。 perf是一个有用的工具,用于帮助引导探针插入源代码中。
除了perf,还有一些其他工具,如SystemTap,DTrace和LTTng,可用于内核和用户空间跟踪;然而,perf与内核配合完美,所以它受到内核程序员的青睐。
# gcc -g -o test test.c# perf probe -x ./test func_2_entry=func_\n# perf probe -x ./test func_2_exit=func_2%retur\n# perf probe -x ./test test_15=test.c:1\n# perf probe -x ./test test_25=test.c:25 numbe\n# perf record -e probe_test:func_2_entry -e\nprobe_test:func_2_exit -e probe_test:test_15\n-e probe_test:test_25 ./test
如上所示,程序员可以将探针点直接插入函数start和return,源文件的特定行号等。可以获取打印的局部变量,并拥有许多其他选项,例如调用函数的所有实例。 perf探针用于创建探针点事件,那么在执行./testexecutable时,可以使用perf记录来探测这些事件。当创建一个perf探测点时,可以使用其他录音选项,例如perf stat,可以拥有许多后期分析选项,如perf脚本或perf报告。
使用perf脚本,上面的例子输出如下:
# perf script
使用kprobe跟踪内核空间
与uprobe一样,可以使用sysfs接口或perf工具将kprobe跟踪点插入到内核代码中。
使用sysfs接口插入kprobe
程序员可以在/proc/kallsyms中的大多数符号中插入kprobe;其他符号已被列入内核的黑名单。还有一些与kprobe插入不兼容的符号,比如kprobe_events文件中的kprobe插入将导致写入错误。 也可以在符号基础的某个偏移处插入探针,像uprobe一样,可以使用kretprobe跟踪函数的返回,局部变量的值也可以打印在跟踪输出中。
以下是如何做:
disable all events, just to insure that we see only kprobe output in trace\n# echo 0 >/sys/kernel/debug/tracing/events/enabledisable kprobe events until probe points are inseted\n# echo 0 >/sys/kernel/debug/tracing/events/kprobes/enableclear out all the events from kprobe_events\n to insure that we see output foronly those for which we have enabled
[root@pratyush ~\n# more /sys/kernel/debug/tracing/trace# tracer: no\n\n# entries-in-buffer/entries-written: 9037/9037\n#P:8\n# _-----=>irqs-of\n# / _----=>need-resche\n# | / _---=>hardirq/softirq#\n|| / _--=>preempt-depth#\n ||| / delay# TASK-PID CPU#\n |||| TIMESTAMP FUNCTION#\n | | | |||| | |
使用perf插入kprobe
与uprobe一样,程序员可以使用perf在内核代码中插入一个kprobe,可以直接将探针点插入到函数start和return中,源文件的特定行号等。程序员可以向-k选项提供vmlinux,也可以为-s选项提供内核源代码路径:
# perf probe -k vmlinux kfree_entry=kfre\n# perf probe -k vmlinux kfree_exit=kfree%retur\n# perf probe -s ./ kfree_mid=mm/slub.c:3408 \n# perf record -e probe:kfree_entry -e probe:kfree_exit -e probe:kfree_mid sleep 10
使用perf脚本,以上示例的输出:
关于Linux命令的介绍,看看《linux就该这么学》,具体关于这一章地址3w(dot)linuxprobe/chapter-02(dot)html
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)