本节内容
- 阻止GDB调试
- 阻止反汇编、代码混淆
- monkey patch[付费阅读]
- 缓冲区溢出攻击[付费阅读]
有些时候我们希望我们的程序不要被别人用gdb调试,用gdb调试可以抓很多东西,从中能获得很多信息。我们可以把符号表删掉加大反汇编难度,但是用gdb调试依然没有问题的,无非就是耐心的问题。
我们用一个例子怎么样阻止别人拿gdb打开我们的程序,这也是很简单的技巧。
$ cat antigdb.c
#include <stdio.h>
#include <unistd.h>
#include <sys/ptrace.h>
int main()
{
if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) {
printf("ANTI GDB!\n");
return -1;
}
printf("hello, world!\n");
return 0;
}
ptrace是个系统调用,操作系统通过这个系统调用给调试器提供相应的功能,比如像gdb调试我们进程的时候,它会通过系统调用查看相应的内核数据,我们通过PTRACE_TRACEME参数来找到是否有人用这样的系统调用挂到我们程序上我们就知道是否给gdb给挂着了。在不同平台系统调用可能不太一样,但都有类似的功能。这个只是简单的玩法,因为有些时候阻止别人很正常。
我们简单编译执行下。
$ gcc -g -O0 -o test antigdb.c && ./test
当我们使用gdb调试时候,那段代码就会探测到给gdb挂住了,挂住以后就直接中止当前进程。这个做法很简单,因为属于保护措施的一种。如果只是这样保护的话老鸟很容易把你这段代码删掉以后可以了。
$ gdb test
$ l
推荐:http://www.ouah.org/linux-anti-debugging.txt
阻止反汇编、代码混淆
这也是很常见的东西,我们说过我们可以把符号表删掉,反汇编时候就不能用符号来定位,因为找不到这些符号信息了,因为这时候看到的整个汇编代码已经没有办法区分出具体的哪块了,它整个text段合并在一起了,你搞不清楚开始是哪个函数。删除符号表只是反汇编给别人带来一些麻烦,但是并没有阻止别人反汇编。
$ strip test
$ objdump -d -M intel test
怎么样阻止别人反汇编呢?其实这个原理说白了是一种欺骗的手段,我们知道汇编代码是怎么样实现的呢?首先从text段开始找到起始位置,运行期的addr,静态的offset。然后读第一个字节时候是一个汇编指令,根据这个指令判断后面参数的长度,可能nop没有参数,xor两个参数,每个参数有多长,这样来计算这条指令大概是几个字节。我们就知道有的指令一个字节,有的指令三个字节,根据这些东西能判断出这条指令开始位置,结束位置。但是这种反汇编有个问题在于它并不能正常执行。所以它很依赖于正常格式,比如说从左到右的解析。
那么我们为了欺骗反汇编器我们可以写这样的代码,第一行代码是正常的,比如说一个跳转指令,jmp到某个位置,然后第二行代码比如说我输入伪指令,假如A3,正常情况下A3有两个参数,但是我就写A3,第三行才是正常的操作。第一行跳转直接跳到第三行,把第二行绕过去。那么我们程序正常执行是没有问题的,因为第二行的确是错误的,但是执行的时候并没有执行这一行。问题是在运行期动态的时候jmp才有效,反汇编时候它是静态的,首先解析第一行没有问题,接下来读第二行的时候A3指令后面认为有两个参数,它就会把第三行的东西作为参数合并到第二行去了,接下来后面所有的行都对不齐了,这样一来整个反汇编都全乱了。这就是很典型的给反汇编器挖了一个坑。
$ cat falsedis.s
.global _start
_start:
jmp label+1;
label:
// 0x90 nop
// 0xe9 false disassembly
.byte 0x90
mov $0x1, %eax
mov $0x2, %ebx
add %eax, %ebx
这是一个用汇编写的一个程序,入口jmp label+1
,label
是地址定位的,+1
就是把.byte 0xe9
绕过去就是从下一行开始,程序执行就是到mov $0x1, %eax
。.byte 0xe9
不管是对的还是错的对于我们都没有影响。
$ gcc -c -o test.o falsedis.s
$ ld -o test test.o
$ objdump -d -M intel test
编译完我们可以看出来jmp直接跳到mov执行了,.byte 0x90
根本没有执行,0x90
翻译成汇编指令是nop
,nop是什么都不干,没有参数的,所以这时候只有一个字节,这时候没有错位。
我们接下来把0x90
修改0xe9
,但是它的长度就不是一个字节了。
.global _start
_start:
jmp label+1;
label:
// 0x90 nop
// 0xe9 false disassembly
.byte 0xe9
mov $0x1, %eax
mov $0x2, %ebx
add %eax, %ebx
$ gcc -c -o test.o falsedis.s
$ ld -o test test.o
$ objdump -d -M intel test
这时候程序依然跳转,没有问题的。但是反汇编时候完蛋了,0xe9
后面本来是第三行的东西它把它们当作参数来用了,接下来所有的反汇编就全部乱套了。因为每个汇编指令后面都有不定长度的参数。
这就是典型的让反汇编器的定位出错,这就是很典型的混淆手段。我们在.Net或者Java通常会使用大量的混淆器来保护我们的代码,其实有很多种做法。
假设正常的指令带两个参数,然后下一条指令带一个参数。我们怎么混淆呢?先打乱,然后用跳转指令把整个流程打乱。这是一种很典型的把原来顺序打乱中间加入跳转指令,这样反汇编时候就跳来跳去的。还有些复杂的做法,比如我可以在JIT前面挂一个Hook,然后把指令加密,比如正常指令mov是1,我提供一个码表做映射把1改成5,除非用Hook进行还原,否则你直接对指令解密时候没有码表的话就出错了,这也是一种常见混淆加密的手段,就是我在运行期只能通过码表和Hook程序才能还原成正确的汇编指令。
所有的这些都会依赖汇编本身的一些功能。另外还可以提供类似花指令的做法。就是用来干扰别人对你的程序用来反汇编。还有些做法把你的程序压缩,加个壳,你除非先突破这个壳否则里面数据拿不到。