Introduction
#define ARTICLE_STATUS "0.03%"
Remote Administration refers to any method of controlling a computer from a remote location.
Software that allows remote administration is becoming increasingly common and is often used when it is difficult or impractical to be physically near a system in order to use it, or in order to access web material that is not available in one's location, for example viewing the BBC iPlayer from outside the United Kingdom. A remote location may refer to a computer in the next room or one on the other side of the world. It may also refer to both legal and illegal remote administration.
*** Wikipedia
Here we are again and it's time to throw some light on something more advanced than a remote shell.
In this article, I will present one of many implementations of its structure and small example of its functionality. So let's skip unnecessary stories and get one step closer to the subject.
*** cross
Introduction and Prerequisites
To get a point about the whole thing that will be discussed here, you need some basic knowledge about:
- Linux and Windows systems
- C/C++ programming for Windows and Linux
- Perl scripting language
- Windows kernel mode drivers development
- Network programming
- Programming kernel mode network clients / Winsock Kernel
- Gtk+ GUI programming, or
- C# .NET programming
- Windows kernel mode hooks
I have used these tools and, I am not forcing you to follow me step by step – our main task could be accomplished in a different ways:
- Windows Driver Kit version >= 6
- Physical Linux with gcc compiler and Gtk+ GUI (development + runtime) libs sat up nicely
- Physical Windows with Visual Studio Pro 2008 and Perl interpreter
- VMWare workstation / server
- Virtual Linux System with Perl interpreter
- Virtual Windows System (Vista / Server 2008 / 7)
- ... some Driver Loader
Final 'like in real life' test will require this. However, you can pack and mix all of this into one Windows System (Vista / Server 2008 / 7). A few words before we start. This project – is not something big and scary. I have got plans to make something 4x times bigger but resigned due to many facts. Lack of time, for example. These days we all live fast, want to do a lot, want to learn, want to get more knowledge – life is not enough. Here I will show a 'skeleton', and if you will find it somehow interesting, useful, if this will bring you some new ideas – then you have a good base to start from, make it bigger. Another thing, Remote Administration Systems are often associated with malicious software, trojans for example, botnets, etcetera. How you will use my code – it is up to you. You can make something legit from it and there are many examples of legit software which use the same functionality. And many examples of botnets.
While the term "botnet" can be used to refer to any group of bots, such as IRC Bots, this word is generally used to refer to a collection of compromised computers (called Zombie computers) running software, usually installed via drive-by downloads exploiting Web browser vulnerabilities, worms, Trojan horses, or backdoors, under a common command-and-control infrastructure.
*** Wikipedia
Let's proceed with caution. ;)
Structure
Ehm, the whole thing will have a 'triangle like' structure. Consider the following components:
- Master machine - machine from which we will establish control
- Server machine - here we will place our server (this point will be explained later)
- Client machine - machine which will be under our control / administration
Master Machine <---> Server Machine <---> Client Machine
Now why such structure? Let's assume that I am remotely administrating in real time 100 machines, anyways, number could be bigger. I want to know (or I need to know) what is happening on each machine. The easiest way to make it real - force every machine to send status information to my personal computer at home. But how about the fact, that maybe I don't have to much bandwidth or I got limited amount of traffic, besides, not every message is critical and requires my attention - some of the messages I can simply check out a little bit later and I want to receive only critical reports in real time? To avoid creating a situation in which a lot of 'trash' traffic will come to my personal network, I will implement 'Server In The Middle', a server between me and client machine, which will decide what to send to me.
I hope you got the point. Now, how I will code all this. First of, if we are going to administrate / control something, we need some nice user interface for it. Mate, I am not going to mess with the console application, no way. Here we need our handy skills in creating GUI interfaces. Second, which OS do we have? Linux? Windows? Well, I got both. Assuming that I am working on Linux - I will use Gtk+ as my GUI library for my Administration Console; let's name it now, R-manager for example. Do you like its new name? ;) You can use some other if wish. Ok, that's clear for now. But how about sysadmins, who work on windows boxes? Let's not forget about them and code another version of R-manager, this time we will use C# .NET. Or you can compile Gtk+ version on Windows - you just need libs. So we have to code 2 versions of R-manager, clear.
Work name: R-manager.
Now what about 'Server In The Middle'? By the way - this is its name, my friends, - SITM Server. I have chosen Perl scripting language for this task. Earlier I said that you can use just one operating system for testing this project and I haven't lied. You can run this Server on windows box as well - just need to setup Perl interpreter on windows machine and that's all! Oh, one thing will be unavailable - server will not run as daemon (in windows terms - service) unless you will be able to setup Daemon Perl module. Ok.
Work name: SITM Server.
Now, client machine. Our client machine is running Windows server 2008. Now we have to code an application which will provide some control over this machine for us. I could have it done with usage of Win32 API, code simple ring3 app and so what? Where is some fun in it? Boring, I am bored to death... That is why my client app will be kernel mode network driver. Actually that is even better! While we are in kernel, we got a lot more fun and, we got total control over client machine. And that is a good opportunity to have some practice with Winsock Kernel, WSK, - new NPI by Microsoft.
Work name: R-client.
Alright, we know what we have to do. We know it is an easy task. We are doing it for fun and practice. These examples here will represent minimum of functionality, basic things, a skeleton let's say. Let's proceed to a next chapter.
R-client
So, what functionality are we going to implement? Let's do something simple like this: After R-client (driver) is initialized, it will wait for our 'order' to protect itself from unload by setting up hook on single function - NtUnloadDriver
. If user will try to unload our driver with usage of the above function - R-client will send a message to us, informing about unload attempt. Simple, isn't it? Then we will code such small app and test it in real life. So our driver will receive messages from us and send messages to us. I mean, to SITM Server. In some cases, it will be encrypting outcoming messages with simple rot47 cipher and decrypting incoming. It will use UDP protocol for this and will be acting as client and server. And last, it will hook NtUnloadDriver
. Now we got the full picture of our R-client driver.
- Network based messaging system
- Encryption / decryption
- Kernel mode hook
Let's start from the very last point.
*** NtUnloadDriver kernel mode hook ***
This we will accomplish by inserting an unconditional 5-byte jump into original NtUnloadDriver
function entry point what will result in redirection execution of NtUnloadDriver
to our function, let's say: NewNtUnloadDriver
. And then our NewNtUnloadDriver
will decide what to do next. I am not getting into details assuming that you have the required knowledge, but let's just clarify a few things.
Our function is pretty trivial:
NTSTATUS __stdcall NewZwUnloadDriver(IN PUNICODE_STRING DriverServiceName){
WCHAR Path[1024];
RtlZeroMemory(Path, sizeof(Path)); swprintf(Path, L"%wZ", DriverServiceName); if(wcscmp(Path,
L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Services\\test.sys") == 0){
DbgPrint("R-client: Access denied!\n");
SendMessage("rcli!Something tried to unload me");
return STATUS_ACCESS_DENIED;
} else { DbgPrint("DriverServiceName: %wZ", DriverServiceName);
return OriginalZwUnloadDriver(DriverServiceName); }
}
By reading the comments in the code, you see how it works. I will split further explanation into the following points:
- Identify original function structure
- Machine code before
- Hook implementation
- Machine code after
- Conclusion
1. NtUnloadDriver C code
NTSTATUS NTAPI NtUnloadDriver (IN PUNICODE_STRING DriverServiceName){
return IopUnloadDriver(DriverServiceName, FALSE);
}
2. Machine Code Before
3. Hook Implementation
==> Locate space for 'jmp' insertion
==> Calculate Callgate
==> Memory allocation
==> Save original function address
==> Insert 'jmp' to our new function
=> Disable kernel memory protection
=> Insert 'jmp'
=> Enable memory protection
To accomplish this task, we use Length-Disassembler Engine by (C)ZOMbiE (included within the project).
while (CollectedSpace < 5){
GetInstLenght(Inst, &Size); (unsigned long)Inst += Size; CollectedSpace += Size; }
CallGateSize = CollectedSpace + 5;
CallGate = (void *)ExAllocatePool(NonPagedPool, CallGateSize);
memset(CallGate, 0x90, CallGateSize);
memcpy(CallGate, Addr, CollectedSpace);
memset(Addr, 0x90, CollectedSpace);
*((unsigned long *)(((unsigned long)CallGate + CollectedSpace) + 0x1)) =
(((unsigned long)Addr + 0x5) - ((unsigned long)CallGate + CollectedSpace) - 0x5);
*((unsigned char *)((unsigned long)CallGate + CollectedSpace)) = 0xe9;
*((unsigned long *)(((unsigned long)Addr) + 0x1)) = (((unsigned long)NewFunc) -
((unsigned long)Addr) - 0x5);
*((unsigned char *)((unsigned long)Addr)) = 0xe9;
Typedefs:
typedef NTSTATUS (* _ZwUnloadDriver)(IN PUNICODE_STRING DriverServiceName);
_ZwUnloadDriver OriginalZwUnloadDriver;
And last step:
__asm
{
cli
mov eax,cr0
mov CR0Reg,eax
and eax,0xFFFEFFFF
mov cr0,eax
}
OriginalZwUnloadDriver =
(_ZwUnloadDriver)SetHook(
(PVOID)KeServiceDescriptorTable.ServiceTableBase[*(PULONG)((ULONG)(ZwUnloadDriver)+1)],
NewZwUnloadDriver
);
__asm
{
mov eax,CR0Reg
mov cr0,eax
sti
}
4. Machine Code After
5. Conclusion
After all, we have the original function address and can jump to it whenever we wish from our NewUnloadDriver
. With this template and thanks to ZOMbiE's disasm engine, you can implement your own kernel mode hooks. You can hide files, folders, forbid access to any place you want, manipulate remote system the way you want. I have chosen to implement such technique in this project as it was the most suitable to show how we can create simple monitoring routines. In the source files included within this article, you can find a test utility which will try to unload driver with usage of NtUnloadDriver
so you just have to compile it and make a test. In general, it looks like this:
int main(){
BOOL en;
UNICODE_STRING u_str;
WCHAR RegUniPath[MAX_PATH] =
L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Services\\test.sys";
NtFunctionsInit();
RtlAdjustPrivilege(10, TRUE, AdjustCurrentProcess, &en);
RtlInitUnicodeString(&u_str, RegUniPath);
if(NtUnloadDriver(&u_str) != STATUS_SUCCESS)MessageBox(0, "Unable to unload driver!",
"ERROR", MB_ICONERROR);
return 0;
}
*** Encryption / Decryption ***
Well, that is almost an unnecessary thing in our project due to a lot of things that still remain not shown, however:
char *code_rot(char *cod_dec){
char *p = cod_dec;
while(*p) {
if(*p >= '!' && *p <= 'O')
*p = ((*p + 47) % 127);
else if(*p >= 'P' && *p <= '~')
*p = ((*p - 47) % 127);
p++;
}
return cod_dec;
}
As simple as it gets, my friends. :)
*** Network based messaging system ***
Winsock Kernel (WSK) is a kernel-mode Network Programming Interface (NPI). With WSK, kernel-mode software modules can perform network I/O operations using the same socket programming concepts that are supported by user-mode Winsock2. The WSK NPI supports familiar socket operations such as socket creation, binding, connection establishment, and data transfers (send and receive). However, while the WSK NPI supports most of the same socket programming concepts as user-mode Winsock2, it is a completely new and different interface with unique characteristics such as asynchronous I/O that uses IRPs and event callbacks to enhance performance.
*** MSDN
As part of the introduction, let me present a small framework, provided by (C)MaD, which consists of the following function wrappers, making kernel sockets programming easier than ever.
NTSTATUS NTAPI SocketsInit();
VOID NTAPI SocketsDeinit();
PWSK_SOCKET NTAPI
CreateSocket(
__in ADDRESS_FAMILY AddressFamily,
__in USHORT SocketType,
__in ULONG Protocol,
__in ULONG Flags
);
NTSTATUS NTAPI
CloseSocket(
__in PWSK_SOCKET WskSocket
);
NTSTATUS NTAPI
Connect(
__in PWSK_SOCKET WskSocket,
__in PSOCKADDR RemoteAddress
);
PWSK_SOCKET NTAPI
SocketConnect(
__in USHORT SocketType,
__in ULONG Protocol,
__in PSOCKADDR RemoteAddress,
__in PSOCKADDR LocalAddress
);
LONG NTAPI
Send(
__in PWSK_SOCKET WskSocket,
__in PVOID Buffer,
__in ULONG BufferSize,
__in ULONG Flags
);
LONG NTAPI
SendTo(
__in PWSK_SOCKET WskSocket,
__in PVOID Buffer,
__in ULONG BufferSize,
__in_opt PSOCKADDR RemoteAddress
);
LONG NTAPI
Receive(
__in PWSK_SOCKET WskSocket,
__out PVOID Buffer,
__in ULONG BufferSize,
__in ULONG Flags
);
LONG NTAPI
ReceiveFrom(
__in PWSK_SOCKET WskSocket,
__out PVOID Buffer,
__in ULONG BufferSize,
__out_opt PSOCKADDR RemoteAddress,
__out_opt PULONG ControlFlags
);
NTSTATUS NTAPI
Bind(
__in PWSK_SOCKET WskSocket,
__in PSOCKADDR LocalAddress
);
PWSK_SOCKET NTAPI
Accept(
__in PWSK_SOCKET WskSocket,
__out_opt PSOCKADDR LocalAddress,
__out_opt PSOCKADDR RemoteAddress
);
We are not going to explore the most deepest aspects of Winsock Kernel here, because it is not the point. To implement any type of connection-oriented sockets using WSK in our example, we first need:
- Register winsock kernel application
- Create socket
- Bind socket to a local transport address
R-Client uses UDP protocol for network communication with SITM Server, then our socket will be a datagram socket. After binding to a local address, driver creates a system thread, which then receives in a loop datagrams and depending on incoming messages from Server – performs some action. For example: 1.Got message "hi" ---> send message "Hello :)" Let's see some code at last. ;)
NTSTATUS InitNetworking(){
NTSTATUS Status = STATUS_UNSUCCESSFUL;
SOCKADDR_IN LocalAddress;
Status = SocketsInit(); if (!NT_SUCCESS(Status)) { DbgPrint("SocketsInit() failed with status 0x%08X\n", Status);
PsTerminateSystemThread(Status);
} else DbgPrint("Kernel Sockets Initialized successfully!\n");
g_ServerSocket = CreateSocket
(AF_INET, SOCK_DGRAM, IPPROTO_UDP, WSK_FLAG_DATAGRAM_SOCKET); if (g_ServerSocket == NULL) {
DbgPrint("CreateSocket() returned NULL\n");
PsTerminateSystemThread(Status);
} else DbgPrint ("Socket created!\n");
LocalAddress.sin_family = AF_INET;
LocalAddress.sin_addr.s_addr = inet_addr(RCLI_ADDR);
LocalAddress.sin_port = HTONS(SERVER_PORT);
Status = Bind(g_ServerSocket, (PSOCKADDR)&LocalAddress); if (!NT_SUCCESS(Status)) { DbgPrint("Bind() failed with status 0x%08X\n", Status);
CloseSocket(g_ServerSocket);
g_ServerSocket = NULL;
PsTerminateSystemThread(Status);
} else DbgPrint("Binded to local address...\n");
return STATUS_SUCCESS;
}
NOTE* g_ServerSocket
is our global variable and now is our fresh and shining, initialized datagram socket for later use. And that's it! Now R-Client driver can receive and send messages with usage of g_ServerSocket
until it is closed. Finally, some fresh air needed. Let's make our way into the network...
NTSTATUS SendMessage(char *message){
NTSTATUS Status = STATUS_UNSUCCESSFUL;
SOCKADDR_IN ServerAddr, LocalAddress;
char encoded[1024];
RtlZeroMemory(encoded, sizeof(encoded));
strcpy(encoded, message);
code_rot(encoded);
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.s_addr = inet_addr(SERVER_ADDR); ServerAddr.sin_port = HTONS(6666);
Status = SendTo(g_ServerSocket, encoded,
strlen(encoded), (PSOCKADDR)&ServerAddr);
if (!NT_SUCCESS(Status)) {
DbgPrint("Could not send data! Status 0x%08X\n", Status);
PsTerminateSystemThread(Status);
} else DbgPrint("Data sent!\n");
return STATUS_SUCCESS;
}
static VOID UdpServer(PVOID Context){
..................
SendMessage(status); ........................................
......................................
while(1){
Status = ReceiveFrom(g_ServerSocket, response, 1024, NULL, NULL);
if(strcmp(response, "hook") == 0){ DbgPrint("Setting up hooks...\n");
KeDelayExecutionThread(KernelMode,FALSE,&Interval ); SetHooks(); SendMessage("rcli!NtUnloadDriver Hooked"); } else if(strcmp(response, "hi") == 0){ KeDelayExecutionThread(KernelMode,FALSE,&Interval );
SendMessage("rcli!Hello :)"); }
..............
}
.................
You may ask about "rcli!" at the beginning of message. Well, it informs the server that the message came from R-Client, exclamation mark is splitter, and the rest, what is after is actual message.
__Begin: (Receive ==> Process ==> Send) goto __Begin;
Phew, done. R-Client explanation reached its final step.
#undef ARTICLE_STATUS #define ARTICLE_STATUS "43%"
Server In The Middle
Perl is a high-level, general-purpose, interpreted, dynamic programming language. Perl was originally developed by Larry Wall, a linguist working as a systems administrator for NASA, in 1987, as a general-purpose Unix Scripting language to make report processing easier. Since then, it has undergone many changes and revisions and become widely popular amongst programmers. Larry Wall continues to oversee development of the core language, and its upcoming version, Perl 6. Perl borrows features from other programming languages including C, shell scripting, AWK, and sed. The language provides powerful text processing facilities without the arbitrary data length limits of many contemporary Unix tools, facilitating easy manipulation of text files. It is also used for graphics programming, system administration, network programming, applications that require database access and CGI programming on the Web. Perl is nicknamed "the Swiss Army chainsaw of programming languages" due to its flexibility and adaptability.
*** Wikipedia
I think that description speaks for itself and explains the decision of choosing Perl as the programming language for the upcoming task. Need to code right away some fast and stable script – Perl is your friend. Perl was my first programming language, it stayed with me until today and will stay until I die.
print " Hi there :) \n"; # we rock!
Now, focus. We are not going to waste a lot of time on this script 'coz we want it fast and we want it right away, right? Neither will I repeat myself about its mission. The first of, after R-Client initial message appears, SITM Server checks in MYSQL Database – are there some records of just connected remote machine. In case of one machine, I mean, if we have just one remote machine under surveillance – I don't think there is a reason for playing with databases. But if we are talking about 100 / 1000 machines, or even 10 – I think there is. Well, my question, have you created your database already?
my $dbhost = "localhost";
my $dbuser = "root";
my $dbpass = "";
my $table = "machines";
my $db = "project";
sub SetDB {
my $nodb = undef;
my $dbh_create = DBI->connect("dbi:mysql:$nodb:$dbhost",$dbuser,$dbpass) ;
# connect to database
my $sql_create = "create database $db"; #query to execute
my $sth_create = $dbh_create->prepare($sql_create); # prepare query
$sth_create->execute or die "MYSQL error while creating Database!
($DBI::errstr)\n"; # execute it or die
# we are not going anywhere without our database
my $dbh = DBI->connect("dbi:mysql:$db:$dbhost",$dbuser,$dbpass) ;
my $sql = "create table `$table` (".
"`id` int(10) not null auto_increment,".
"`ip_as_name` varchar(150) not null default '',".
"`port` varchar(50) not null default '',".
" primary key(`id`),".
" unique key `id`(`id`)".
") type=MyISAM comment='' AUTO_INCREMENT=1 ;";
my $sth = $dbh->prepare($sql); # prepare table creation query
$sth->execute or die "MYSQL error while creating table!
($DBI::errstr)\n"; # execute it or die
#not going anywhere neither if the above function failed
print "MYSQL Database setup finished\nDetailes:\nDatabase Host:
$dbhost\nDatabase User: $dbuser\
Database Password: $dbpass\nTable: $table\n"; # print some stats
return "DBCREATED"; # return some value, in some case we may need it later
}
Those are my default settings, default fields and rows. We are still not even close.
sub FetchMachines {
my ($ip_addr) = @_; # that is IN parameter
my $online;
my $container; my $num; my $i;
$container .= "machines$J";
my $dbh = DBI->connect("dbi:mysql:$db:$dbhost",$dbuser,$dbpass) ;
my $sq = "select count(*) from $table";
my $st = $dbh->prepare($sq);
$st->execute or print("MYSQL error while fetching machines list!
($DBI::errstr)");
while (@row = $st->fetchrow_array){
$container .= $row[$i++];
}
my $sql = "select * from $table";
my $sth = $dbh->prepare($sql);
$sth->execute or print("MYSQL error while fetching machines list!
($DBI::errstr)");
$container .= "$J"; # $J is a splitter '!'
while (@row = $sth->fetchrow_array) {
$container .= $row[1].$online;
$container .= "$J";
}
&SendStatus ( "mcs", $J, "$container", $ip_addr, $SEND_PORT);
# $SEND_PORT – is a binded port of administrator's machine
}
The above routine performs a trivial task:
- Fetches machines from database
- Fetches number of machines in database
- NOTE *: In our example, we are talking all the time about one single lonely machine ;)
And then it sends collected data to .. for example, me.
sub AddMachine {
my ($ip_as_name, $port) = @_; # input parameters
my $trash = "Server~#";
my $add_err = "($DBI::errstr) while registering new machine!($ip_as_name)";
my $check_err = "MYSQL error ($DBI::errstr)
while checking new machine ($ip_as_name)!";
print "registering ".$ip_as_name."\n";
my $dbh = DBI->connect("dbi:mysql:$db:$dbhost",$dbuser,$dbpass) ;
my $sql_ex = "select * from $table where ip_as_name = '$ip_as_name'";
my $sth_ex = $dbh->prepare($sql_ex);
$sth_ex->execute or &SendStatus("nfo", $J, "$trash$J$check_err", $MASTER_IP);
if($sth_ex->rows == 1){
print "This machine is registered!\n".$sth_ex->rows."\n";
return "here";
} else {
my $sql = "insert into $table (ip_as_name, port) values
('$ip_as_name', '$port')";
my $sth = $dbh->prepare($sql);
$sth->execute or &SendStatus( "nfo", $J,
"$trash$J$add_err", $MASTER_IP) and return "error";
return "registered";
}
}
This one adds machine to the database and checks if the machine already stored in database. And what does that mysterious "SendStatus
" do? Here you go:
sub SendStatus {
my($header, $splitter, $message, $ipaddress, $port) = @_; # Input params
socket(SOCKET, PF_INET, SOCK_DGRAM, $proto) or print
"Cannot create socket!\n";
#^ socket creation
$ipaddr = inet_aton($ipaddress); # ip addr
$portaddr = sockaddr_in($port, $ipaddr); # port and ip
my $msg = "$header$splitter$message"; # combined message
send(SOCKET, $msg, 0, $portaddr); # send data
shutdown(SOCKET, 0); # I/we have stopped reading data
shutdown(SOCKET, 1); # I/we have stopped writing data
shutdown(SOCKET, 2); # I/we have stopped using this socket
print $msg."\n";
return;
}
By the way, I am sorry if I am going too fast but I am assuming that you have appropriate knowledge level to get the code on the fly and are not concerned about comments... Are you ready for the last shot? After we'll catch some breath and discuss a couple of things. The main function is ahead which does all the job. It receives from one machine and sends to another, processing data, anyways, welcome to the abyss ;] Just joking :)
sub UdpListener{
my $response;
my $dec;
my $binded = 0;
my $trash = "Server~#";
my $rcli_sign = "R-Client~#";
my $rcli_here = "reg_ok";
my $rcli_regok = "registered";
my $xXx = $gpp1[rand(@gpp1)]." ".$gpp2[rand(@gpp2)]."
".$gpp3[rand(@gpp3)]."\n".$help;
my $check_err = "Cannot execute mysql query
while checking machine($remoteaddress)!\n";
__init:
$response = "";
my $q=CGI->new();
socket(SOCKET, PF_INET, SOCK_DGRAM, $proto) or print
"Cannot create listening socket!\n";
setsockopt(SOCKET, SOL_SOCKET, SO_REUSEADDR, 1) ;
$ipaddr = inet_aton($VMWareIp);
$portaddr = sockaddr_in($LISTEN_PORT, $ipaddr);
bind(SOCKET, $portaddr) or die "Could not bind! $!";
if($binded == 0){
print "Binded\n";
} else {
print "Rebinded\n";
}
my $remoteaddress = $q->remote_addr();
while(1){
$binded = 1;
$dec = "";
recv(SOCKET,$response,1000,0);
$dec = rot47($response);
print $dec."\n";
@words = split(/$J/, $dec);
if($words[0] eq "?"){
print "Help information sent\n";
&SendStatus("nfo", $J, "$trash$J$xXx", $MASTER_IP, $SEND_PORT);
DestroySocket(SOCKET); goto __init;
} elsif($words[0] eq "machine"){
print $words[1]." ".$words[2]."\n";
} elsif(index($words[0], "stats") > -1 ||
index($words[0], "statistics") > -1){
&FetchMachines($MASTER_IP);
DestroySocket(SOCKET); goto __init;
} elsif($words[0] eq "die"){
&SendStatus("nfo", $J, "$trash$J$death", $MASTER_IP);
DestroySocket(SOCKET);
system("clear");
die "\nMaster ordered me to die ;(\n";
} elsif($words[0] eq "rcli"){
if($words[1] eq "status"){
my $MACH_PORT = $words[2];
$MACHINE_ADDR = $words[3];
my $dbh = DBI->connect
("dbi:mysql:$db:$dbhost",$dbuser,$dbpass) ; my $data;
my $sql = "select * from $table
where ip_as_name = '$MACHINE_ADDR'";
my $sth = $dbh->prepare($sql);
$sth->execute or &SendStatus
("nfo", $J, "$trash$J$check_err") and goto __init;
if($sth->rows == 1){
print "Machine ($MACHINE_ADDR) is here.\n";
&SendStatus("", "", "$rcli_here",
$MACHINE_ADDR, $MACH_PORT);
DestroySocket(SOCKET); goto __init;
} else {
print "Machine ($MACHINE_ADDR)
is not detected in our DB, adding...";
my $status = AddMachine
($MACHINE_ADDR, $MACH_PORT);
if($status eq "registered"){
&SendStatus("", "",
"$rcli_regok", $MACHINE_ADDR, $MACH_PORT);
DestroySocket(SOCKET); goto __init;
} else {
die "i dont want to check
for any errors =/";
}
}
} else {
my $rcli_msg = $words[1];
&SendStatus("nfo", $J,
"$rcli_sign$J$rcli_msg\n", $MASTER_IP, $SEND_PORT);
DestroySocket(SOCKET); goto __init;
}
} elsif($words[0] eq "2rcli"){
my $MACHINE_ADDR = $words[1];
my $rcli_msg = $words[2];
&SendStatus("", "", "$rcli_msg", $MACHINE_ADDR, $MACHINE_PORT);
DestroySocket(SOCKET); goto __init;
} else {
&SendStatus("nfo", $J, "$trash$J$whatever\n",
$MASTER_IP, $SEND_PORT);
DestroySocket(SOCKET); goto __init;
}
}
}
Stop. Yeah, that's it ... for now ;) This whole script uses 3 Perl modules:
- Socket (use Socket;)
- DBI (use DBI;)
- CGI (use CGI;)
It is easy to notice that socket module here is the most important and more often used. Assuming that you are familiar with at least BSD sockets – there is nothing new. You may have some considerations about DBI module and what it does. The DBI module enables your Perl applications to access multiple database types transparently. You can connect to MySQL, MSSQL, Oracle, Informix, Sybase, ODBC, etc. without having to know the different underlying interfaces of each. The API defined by DBI will work on all these database types and many more. You can connect to multiple databases of different types at the same time and easily move data between them. The DBI layer allows you to do this simply and powerfully.
*** from dbi.perl.org
In short, DBI provides support in your application for interactions with different types of databases. In our case – MYSQL Database. There are 5 main functions:
connect
– connect to a database prepare
– prepare SQL query execute
– execute SQL query disconnect
– disconnect from database do
– a faster way to execute SQL query when no data is returned
Example:
my $dbh = DBI->connect("dbi:mysql:$db:$dbhost",$dbuser,$dbpass) ;
my $sq = "select count(*) from $table";
my $st = $dbh->prepare($sq);
$st->execute;
SITM server uses this module for adding machine in database and for keep it there, also for checking if it is there.
while (@row = $sth->fetchrow_array) {
$container .= $row[1].$online;
$container .= "$J";
}
This example processes returned data. Another module is CGI. CGI.pm is a stable, complete and mature solution for processing and preparing HTTP requests and responses. Major features include processing form submissions, file uploads, reading and writing cookies, query string generation and manipulation, and processing and preparing HTTP headers. Some HTML generation utilities are included as well. CGI.pm performs very well in a vanilla CGI.pm environment and also comes with built-in support for mod_perl and mod_perl2 as well as FastCGI. It has the benefit of having developed and refined over 10 years with input from dozens of contributors and being deployed on thousands of websites. CGI.pm has been included in the Perl distribution since Perl 5.4, and has become a de-facto standard.
*** from perldoc.perl.org
In a manner of speaking, it is initialized here, but not used in fact.
my $q=CGI->new();
my $remoteaddress = $q->remote_addr();
These 2 lines of code are responsible for retrieving IP address of connected remote machine, but in case of our project – will do nothing because it will not fetch LAN IPs or virtual subnet IPs. You can use CGI instead when you are dealing with real servers and each has an external IP address.
@words = split(/$J/, $dec);
Here you can observe the fact I was talking about some time ago, about splitters and message parts. Now we have tokens from @words array, and for example if the very first token equals rcli, the script knows that it should send the actual message to master IP. If token equals 2rcli – message should be sent to remote client, and so on. Gosh, I need a smoke and coffee – I have been writing this paper for about 20 hours now, along with finishing and testing the project itself. In the meantime, enjoy the picture I've created just now :)
picture made by me, autodesk 3ds max, Adobe Photoshop, gimp and Windows paint
#undef ARTICLE_STATUS #define ARTICLE_STATUS "67%"
R-manager
R-Manager – our monitor and control application, with graphical user interface, placed on our personal machine, Linux and Windows editions. Well, it's time to lift the curtain on a little bit and present to you how they look like.
*** R-Manager C# .NET Windows Edition ***
*** R-Manager Gtk+ Linux Edition ***
Pretty simple interfaces with minimum of functionalities which makes them easier to understand for a novice and easier to customize for advanced programmer to fit one's needs. Well, I am not going to "reinvent the wheel", describing specifics of each programming language I have used in these examples, but rather point to 2 major web resources where you can find complete documentation and learn from it: Microsoft Developers Network and Gnome Library. And yet again, the same networking design we have here: our application binds datagram socket, receives, and sends messages. At the screenshots above one element of graphical interface are missing. A networking initialization button. I left this up to you, when you will decide to launch it – just hit the button. In case of Gtk+ version – this button called "Network", in case of C# - Initialize (red colour); buttons will disappear then. Let's take a look at what happens.
Gtk+
int run_network_function(){
gtk_widget_hide(GTK_WIDGET(run_network)); pthread_t thread_id; thdata thread_data; CLEAR(thread_data.local_port); const char *LocalPort = gtk_entry_get_text
(GTK_ENTRY(local_port_ent)); strcpy(thread_data.local_port, LocalPort); pthread_create (&thread_id, NULL, &UdpListener, &thread_data); }
C# .NET
private void Button_Click(object sender, System.EventArgs e)
{
Thread trd = new Thread
(new ThreadStart(this.ThreadTask));
trd.IsBackground = true;
trd.Start();
start_server.Hide();
}
Let's take a closer look at what happens in each thread.
Gtk+
void * UdpListener(void *ptr){
thdata *data;
data = (thdata *) ptr;
char *t; int i;
int sock, sockh;
char message[10000];
char *token[256];
struct sockaddr_in our_addr, serv_addr;
socklen_t length;
char *machine_name;
int machines_count;
char xXx[1024];
char new_line[] = "\n";
bzero(&our_addr,sizeof(our_addr));
our_addr.sin_family = AF_INET;
our_addr.sin_addr.s_addr=htonl(INADDR_ANY);
our_addr.sin_port=htons(atoi(data->local_port));
sock=socket(AF_INET,SOCK_DGRAM,0); bind(sock, (struct sockaddr *)&our_addr,
sizeof(our_addr)); printf("udp server started!\n");
while(1){ length = sizeof(serv_addr);
sockh = recvfrom(sock,message,10000,0,(struct sockaddr *)&serv_addr,&length);
message[sockh] = 0;
t = strtok(message, "!");
for(i = 0; t; t = strtok(NULL,"!"), i++)
token[i] = t; if(strcmp(token[0], "mcs") == 0){ machines_count = atoi(token[2]); machine_name = token[3]; gtk_list_store_append (store_ex, &iter_ex); gtk_list_store_set (store_ex, &iter_ex, COLUMN_MACHINE, machine_name,
-1);
} else if (strcmp(token[0], "nfo") == 0){ strcpy(xXx, token[1]); strcat(xXx, " ");
strcat(xXx, token[2]);
Update_Buffer_And_Scroll_To_End
(log_view, xXx); }
CLEAR(message); continue; }
}
Old good UNIX sockets.
C# .NET
private void ThreadTask()
{
int recv;
String TimeAndDate = "";
DateTime thisDate = DateTime.Now;
TimeAndDate = String.Format("{0:G}", thisDate);
byte[] data = new byte[1024];
String LocalPort = local_port_ent.Text;
int LoPo = System.Int32.Parse(LocalPort);
IPEndPoint ipep = new IPEndPoint
(IPAddress.Any, LoPo);
Socket newsock = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
newsock.Bind(ipep);
IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
EndPoint Remote = (EndPoint)(sender);
termo_label.Text += TimeAndDate + " UDP Local Server Started!\n";
while (true)
{
String response = "";
recv = newsock.ReceiveFrom(data, ref Remote);
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
response = enc.GetString(data);
Array.Clear(data, 0, data.Length);
char[] separator = {'!'};
string[] token = response.Split(separator);
if (String.Compare(token[0], "mcs") == 0)
{
machine_name.Text = token[3];
}
else if (String.Compare(token[0], "nfo") == 0)
{
termo_label.Text += token[1] +
" " + token[2];
}
SendMessage(terminal_pannel.Handle, WM_VSCROLL,
(IntPtr)SB_PAGEDOWN, IntPtr.Zero);
}
}
Let's examine what happens when Send button is pressed. Notice: you don't need to press send button at all, you just can hit ENTER while typing in Message entry / textbox field.
Gtk+
int TransmitData(){
int result;
regex_t rx;
regmatch_t *matches;
char buf[] = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
bool Send2Machine;
char MainMessage[1024];
char no_serv_err[] = "\nServer IP not specified! Terminating task...\n";
printf("TransmitData called!\n");
CLEAR(MainMessage);
const char *MachineName = gtk_entry_get_text(GTK_ENTRY(machine_ent));
const char *MessageEnt = gtk_entry_get_text(GTK_ENTRY(message_ent));
const char *ServerAddr = gtk_entry_get_text(GTK_ENTRY(serv_host_ent));
const char *ServerPort = gtk_entry_get_text(GTK_ENTRY(serv_port_ent));
const char *LocalPort = gtk_entry_get_text(GTK_ENTRY(local_port_ent));
bool server_check_button_status =
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(server_check_button));
if(strcmp(MessageEnt, "clear") == 0){ clear_output(); return 1; }
if(strcmp(MessageEnt, "exit") == 0) exit(0);
result = regcomp( &rx, buf, REG_EXTENDED ); matches = (regmatch_t *)malloc( (rx.re_nsub + 1) *
sizeof(regmatch_t) );
if(strcmp(ServerAddr, "") == 0){ Update_Buffer_And_Scroll_To_End(log_view, no_serv_err);
return 1;
}
result = regexec( &rx, ServerAddr, rx.re_nsub + 1,
matches, 0 ); if (!result) { printf("Valid IP!\n");
} else { Update_Buffer_And_Scroll_To_End(log_view, "Invalid server IP\n");
return 1;
}
if(strcmp(MachineName, "") != 0) { Send2Machine = true; if(server_check_button_status == true){ Send2Machine = false;
goto next; }
result = regexec( &rx, MachineName,
rx.re_nsub + 1, matches, 0 ); if (!result) printf("Valid machine's IP!\n"); else { Update_Buffer_And_Scroll_To_End(log_view, "Invalid Machine IP!\n");
return 1;
}
} else {
Send2Machine = false;
}
next:
if(Send2Machine){ printf("sending data to machine!\n");
strcpy(MainMessage, "2rcli!");
strcat(MainMessage, MachineName);
strcat(MainMessage, "!");
strcat(MainMessage, MessageEnt);
char *RottedMainMessage = code_rot_ex(MainMessage);
UdpSender((char *)ServerAddr, (char *)ServerPort, RottedMainMessage);
CLEAR(MainMessage);
} else { strcpy(MainMessage, MessageEnt);
char *RottedMainMessage = code_rot_ex(MainMessage);
printf("machine name is null - sending data to server!\n");
UdpSender((char *)ServerAddr, (char *)ServerPort, RottedMainMessage);
CLEAR(MainMessage); }
}
int UdpSender(char *addr, char *port, char *message){
int sock, sockh;
char recv_msg[10000];
struct sockaddr_in serv_ddr;
bzero(&serv_ddr,sizeof(serv_ddr));
serv_ddr.sin_family = AF_INET;
serv_ddr.sin_addr.s_addr=inet_addr(addr);
serv_ddr.sin_port=htons(atoi(port));
sock = socket(AF_INET,SOCK_DGRAM,0);
sendto(sock,message,strlen(message),0,
(struct sockaddr *)&serv_ddr,sizeof(serv_ddr));
return 0;
}
C# .NET
Here we are doing exactly the same things like in previous functions, so will skip commenting.
public bool IsValidIP(string addr)
{
string pattern = @"\b(25[0-5]|2[0-4][0-9]|[01]?
[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b";
Regex check = new Regex(pattern);
bool valid = false;
valid = check.IsMatch(addr, 0);
return valid;
}
private void Send_Function(object sender, System.EventArgs e) {
byte[] data = new byte[1024];
bool SendToMachine = false;
String DestMSG;
String message;
String dest_ip = serv_host_ent.Text;
String MachineIp = machine_name_ent.Text;
if (String.Compare(dest_ip, "") == 0)
{
termo_label.Text += "\nServer IP not specified! Terminating task...\n";
return;
}
if (String.Compare(MachineIp, "") != 0)
{
SendToMachine = true;
if (goto_server_hax.Checked)
{
SendToMachine = false;
goto __next;
}
if (IsValidIP(MachineIp) == false)
{
termo_label.Text += "\nMachine IP is not valid!
Terminating task...\n";
return;
}
}
__next:
if (IsValidIP(dest_ip) == false)
{
termo_label.Text += "\nIP is not valid! Terminating task...\n";
return;
}
IPEndPoint ipep = new IPEndPoint(
IPAddress.Parse(dest_ip), 6666);
Socket server = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
DestMSG = message_entry.Text;
if (SendToMachine)
message = "2rcli!" + MachineIp + "!" + DestMSG;
else message = DestMSG;
if (String.Compare(message, "") == 0) {
termo_label.Text += "\nNo message! Terminating task...\n";
return;
}
message_entry.Text = "";
if (!SendToMachine)
termo_label.Text += "\n" +
Environment.MachineName + "~# " + message + "\n";
else
termo_label.Text += "\n" +
Environment.MachineName + "~# " + DestMSG + "\n";
if (String.Compare(DestMSG, "exit") == 0)
{
Quit_Function(sender, e);
return;
}
if (String.Compare(DestMSG, "clear") == 0)
{
termo_label.Text = "";
return;
}
data = Encoding.ASCII.GetBytes(Rot47c(message));
server.SendTo(data, data.Length, SocketFlags.None, ipep);
SendMessage(terminal_pannel.Handle, WM_VSCROLL,
(IntPtr)SB_PAGEDOWN, IntPtr.Zero);
return;
}
The rest of the code mainly regards graphical interface. The last, almost not important thing, I would like to propose my simple driver loader for loading R-Client driver, that's of course if you don't have your own. Well, it is written in Gtk+ (compiled for Windows) and has the following options:
- [+] Load driver with
ZwSetSystemInformation
- [+] Load driver with
NtLoadDriver
- [+] Load driver with Service Control Manager
- [+] Unload driver
- [+] Delete driver file
- [+] Delete driver registry entries
- [+] Injection of driver loading routine into another process
- [+] Injection with
CreateRemoteThread
- [+] Injection with
RtlCreateUserThread
Assuming that it is part of this project - source and binary of DLoad
are included.
#undef ARTICLE_STATUS #define ARTICLE_STATUS "98.5%"
OUTRO
To do for you my friend:
- Configure R-client, R-manager, SITM server properly and compile
- Run SITM server
- Run R-manager
- Load R-client
- Read the code
- I could have forgotten about something and that's why bugs could appear (I am a human and make mistakes for some reasons)
This article was written for educational purposes and for fun. I wish to thank you for reading all that I have written and I wish to thank the CodeProject team for reviewing my article and publishing. And who knows, maybe there will appear an extended edition of this paper and of the whole project. Best regards, always yours cross / csrss.
#undef ARTICLE_STATUS #define ARTICLE_STATUS "100%" PsTerminateSystemThread(0);
Additional Web Resources
History
- 20th October, 2009: Initial version