无聊的时候学了一下调试,在这里做一下笔记。anti-debug方法是很多的,这里先介绍几种最基本的方法。
方法一:跳到指令的中间
-
//anti-debug1.s
-
.global _start
-
_start:
-
jmp label+1;
-
label:
-
.byte 0x90
-
mov $0x1, %eax
编译方法:gcc anti-debug1.s -c -o anti-debug1.o && ld anti-debug1.o -o anti-debug1。这样定义了一个垃圾指令0x90,它在x86架构的汇编里代表nop,只占一个字节。这段代码正好跳到nop后面,所以这段代码是没有问题的。objdump -d anti-debug1输出如下:
-
08048054 <_start>:
-
8048054: eb 01 jmp 8048057 <label+0x1>
-
-
08048056 <label>:
-
8048056: 90 nop
-
8048057: b8 01 00 00 00 mov $0x1,%eax
现在我们把0x90改成一个多字节的指令0xe9(0xe9是jmp指令的一个字节)代码如下:
-
//anti-debug1.s
-
.global _start
-
_start:
-
jmp label+1;
-
label:
-
.byte 0xe9
-
mov $0x1, %eax
-
mov $0x2, %eax
然后编译,反汇编代码如下:
-
08048054 <_start>:
-
8048054: eb 01 jmp 8048057 <label+0x1>
-
-
08048056 <label>:
-
8048056: e9 b8 01 00 00 jmp 8048213 <label+0x1bd>
-
804805b: 00 b8 02 00 00 00 add %bh,0x2(%eax)
这次成功的骗过了objdump,看上去是跳到了一条指令的中间位置。但是这种方法对于调试工具(如:gdb)是没作用的。
解决方法:
解决这个anti-debug的方法比较简单,只要用二进制编辑工具把刚才的垃圾指令(0xe9)改成0x90或者其他的单字节指令然后用objdump反汇编就可以了。但有的程序会实时的检测自己的可执行文件是否被修改,如果被修改了就会做相应的处理,所以比较好的办法是用一些支持交互的反汇编工具,例如ida之类的。
方法二:实时计算跳转指令
这种方法实时的计算跳转的目的地址,当前指令的地址是存放在eip寄存器里的,只要我们修改这个寄存器的值就能改变指令的执行顺序。直接取eip的值似乎不太可能,所以要使用一对组合call+pop,call会把eip的值压入栈,然后用pop把它取出来就可以了。
-
.global _start
-
_start:
-
call earth+1
-
earth:
-
.byte 0xe9 //location_1:
-
pop %eax
-
nop
-
add $9, %eax //这个9代表从location_1(包含)到location_2(包含)之间代码占的字节数
-
push %eax
-
ret
-
.byte 0xe9 //location_2:
-
code:
-
nop
-
nop
-
nop
-
ret
可以在location_1和location_2加入其他代码,但是要把9改成相应的值。然后反汇编代码如下:
-
08048054 <_start>:
-
8048054: e8 01 00 00 00 call 804805a <earth+0x1>
-
-
08048059 <earth>:
-
8048059: e9 58 90 83 c0 jmp c88810b6 <_end+0xc083804e>
-
804805e: 09 50 c3 or %edx,-0x3d(%eax)
-
8048061: e9 90 90 90 c3 jmp cb9510f6 <_end+0xc390808e>
-
-
08048062 <Code>:
-
8048062: 90 nop
-
8048063: 90 nop
-
8048064: 90 nop
-
8048065: c3 ret
这样从反汇编代码似乎完全看不出来代码的执行顺序了。
解决方法:
这个方法也并不完美,只不过是难度稍微加大了一点把jmp指令隐藏起来了。只要把垃圾指令去掉还是很好分析的,不再详解了,自己解决。
方法三:检查int 3指令
调试工具断点的原理是在相应的地方加int 3指令。int 3对应的二进制是0xcc,可以根据某个地方的指令是不是0xcc来判断程序是否被调试。
-
#include <stdio.h>
-
-
void foo ()
-
{
-
printf ("Hello\n");
-
}
-
-
int main ()
-
{
-
if ((*(volatile unsigned *) ((unsigned) foo) & 0xff) == 0xcc) {
-
printf ("BREAKPOINT\n");
-
exit (1);
-
}
-
foo ();
-
return 0;
-
}
如果在foo函数上设置断点则会导致函数进入if语句打印BREAKPOINT,直接执行是不会有问题的。
-
(gdb) b foo
-
Breakpoint 1 at 0x80483ea
-
(gdb) run
-
Starting program: /home/weiwenzhao/test/anti-debug3
-
-
Breakpoint 1, 0x080483ea in foo ()
-
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.80.el6.i686
-
(gdb) continue
-
Continuing.
-
Hello
-
-
Program exited normally.
这个gdb在foo上设置断点然后运行的结果??为什么程序正常退出了呢。把foo函数反汇编出来看一下
-
(gdb) x /10i 0x80483e4
-
0x80483e4 <foo>: push %ebp
-
0x80483e5 <foo+1>: mov %esp,%ebp
-
0x80483e7 <foo+3>: sub $0x18,%esp
-
0x80483ea <foo+6>: movl $0x8048504,(%esp)
-
0x80483f1 <foo+13>: call 0x8048310 <puts@plt>
-
0x80483f6 <foo+18>: leave
-
0x80483f7 <foo+19>: ret
原来是gdb做了修改并没有把断点设置到函数的开始,而是往下走了几个字节。这种方法对一些其他的调试工具是管用的。
解决方法:
这个似乎gdb已经给我们例子,只要调试的时候搜一下程序看看是不是有cmp指令比较某个地址的值是不是0xcc,如果有就不要在这个地方加断点了,移动几个字节就可以了,或者调试的时候在它进行比较的地方把0xcc改成其他值。
方法四:用ptrace检查是不是被调试
-
#include <stdio.h>
-
-
int main ()
-
{
-
if (ptrace (PTRACE_TRACEME, 0, 1, 0) < 0)
-
{
-
printf ("DEBUGGING... Bye\n");
-
return 1;
-
}
-
printf ("Hello\n");
-
return 0;
-
}
一个进程只能同时被一个进程调试,这个程序如果正在被调试那么它调用了ptrace(PTRACE_TRACEME)的时候就会调用失败。
解决方法:
调试的只要在if比较的地方修改一下返回值即可。
总结一下:
以上这几种anti-debug的方法都比较简单,只要稍微动一下手脚就可以解决掉。任何anti-debug方法都不是完美的,只不过是会增加一点debug成本而已。
阅读(1326) | 评论(0) | 转发(0) |