Windows: Interactive shells Part 4


Just to summarize what we’ve looked at so far in parts 1-3

  1. Create a simple shell where a socket is assigned to stdin, stdout and stderr of cmd.exe.
  2. Use synchronization to handle unexpected closure of socket or termination of cmd.exe.
  3. Implement a simple packet protocol to enable file transfers.

Now we look at improving shell from part 3 to include an encryption layer which should make traffic analysis more difficult.

The Microsoft Crypto API is available to all versions of windows since Windows NT4 and although I may not use it for any future parts to this series, that’s what we use here.

I should say at this point, anyone who wants a secure method of interacting with windows really should use a proven, robust protocol like SSH. I’m not attempting to provide an alternative solution here, just exploring how it might be done without SSH and nothing more.

Traffic Analysis

The first 3 shell examples use absolutely no encryption or even basic obfuscation of traffic sent between 2 systems which makes them vulnerable to traffic analysis.

Someone on the same network with packet capture tools will be able see everything. Just to demonstrate, here’s a snapshot of wireshark capturing traffic between two instances of the shell shown in part 1.


It’s no problem to see what’s being sent and received on each end of the connection.

In 1995, Tatu Ylönen, a researcher at Helsinki University of Technology, Finland invented the SSH protocol as a response to password sniffing attacks at his university.

You might be asking yourself, why not use SSH or SSL libraries for a shell like this?
Most versions of Windows don’t have any simple API to support these protocols and my goal was to try minimize dependencies on external/third party libraries.

The closest thing to supporting either of these protocols on windows would be SChannel which we will look at in future.

Public Key Exchange

One of the fundamental problems with early encryption was how to securely share symmetric encryption keys. Public Key encryption was invented for that reason and I discuss the history of it very briefly in an earlier post about Modular Exponentiation.

Initially, I had chosen Diffie-Hellman-Merkle key exchange but it was much slower than RSA. RSA is more efficient because the public exponent used has fewer bits than would a random private key generated for DHM. RSA key exchange only requires one modular exponentiation from the client side whereas DHM requires 2.

It’s outside the scope of my knowledge which method is more secure.

Symmetric Key Encryption

AES-256 is used in CTR mode which acts like a stream cipher. Messages are padded to be a multiple of 16 bytes. This prevents revealing the underlying length of plaintext.

AES-256 was not supported by Crypto API until the release of SP2 for XP.

Data Integrity

Most secure protocols use a MAC (Message Authentication Code) or Digital Signature to ensure integrity of the encrypted data or plaintext being transmitted.

This shell uses HMAC-SHA256 for the MAC and is derived from the encrypted data. Like AES-256, SHA256 was only made available in Crypto API since XP SP2, so this shell won’t work on windows OS prior to this release without changing the source.


Both server and client acquire a crypto provider that supports our encryption algorithms but only the server needs to generate an RSA key pair. The default length of key is 2048-bits.

// initialize context
// as server, generate key pair
int spp_init (spp_ctx *c, int mode)

  c->hProv    = 0;
  c->hPublic  = 0;
  c->hPrivate = 0;
  c->hSession = 0;
  c->secure   = 0;
  c->enc_id   = CALG_AES_256;
  c->key_len  = 2048;         // RSA key length
  c->mode     = mode;         // SPP_SERVER or SPP_CLIENT
  c->mac_len  = 0;
  r=CryptAcquireContext (&c->hProv, NULL, NULL, PROV_RSA_AES, 
  // server or client mode?
  if (r && c->mode==SPP_SERVER) 
    // generate RSA key pair
    r=CryptGenKey (c->hProv, CALG_RSA_KEYX, 
      c->key_len << 16 | CRYPT_EXPORTABLE, &c->hPrivate);
  return r;

Generating a Counter value (Initialization Vector)

I wanted to ensure the counter was securely shared between client and server. We have some options, although this isn’t a comprehensive list by any means.

  1. Encrypt 16 null bytes or some predefined values using the session key and XOR the result against 16 bytes generated by CryptGenRandom
  2. Generate a new key with CryptGenKey and send to server.

I’d be happy to hear more suggestions but I chose option 2 as I didn’t want to call spp_crypt function before an IV had been shared.

A random key is generated with CryptGenKey()
This is exported as a SIMPLEBLOB using the public key sent from server. Since it’s now encrypted, only the server should be able to decrypt it.

Both client and server export the key as a PLAINTEXTBLOB which allows us to access the raw key and use this for encryption using CryptEncrypt API.

typedef struct _pt_blob_t {
  DWORD      dwKeySize;
  BYTE       rgbKeyData[32];
} pt_blob, *ppt_blob;

// as a server, import iv, export as plaintextblob
// as a client, generate iv, export as plaintextblob
int spp_setiv (spp_ctx *c, spp_blk *iv)
  BOOL      r;
  BYTE      buf[128];
  ppt_blob  pb;
  memset (buf, 0, 128);
  if (c->mode==SPP_SERVER)
    // import encrypted iv
    r=CryptImportKey (c->hProv, iv->buf, iv->len, 
      c->hPrivate, CRYPT_EXPORTABLE, &hKey);
  } else {
    // generate new key
    r=spp_genkey (c, &hKey, iv);
  // if key generated/imported
  if (r) {
    // export as PLAINTEXTKEYBLOB
    r=CryptExportKey (hKey, 0, PLAINTEXTKEYBLOB, 
      0, buf, &c->iv_len);
    if (r) {
      // copy 32-bytes to iv buffer
      memcpy (c->iv, pb->rgbKeyData, 256/8);
    // destroy key object
    CryptDestroyKey (hKey);
  return r;

The exchange

Asymmetric or public key exchange was invented to facilitate sharing of symmetric keys and s4 uses RSA.

RSA key exchange is much faster than Diffie-Hellman-Merkle.

The main function to exchange TEK (Traffic Encryption Keys) is performed in a function called spp_handshake. The following are steps involved.

  • As a server connected to client
  1. Generate RSA key pair
  2. Export public key
  3. Send public key to client
  4. Wait for session key
  5. Import session key
  6. Wait for CTR value
  7. Import CTR value
  • As a client connected to server
  1. Wait for public key from server
  2. Import public key
  3. Generate a session key
  4. Send session key to server
  5. Generate a CTR from key
  6. Send CTR to server
// perform key exchange with spp server or client
// depends on mode in context
int spp_handshake (spp_ctx *c)
  int     r=0;
  spp_blk pubkey, seskey, ctr;
  if (c->mode==SPP_SERVER)
    // get the public key
    printf ("[ getting public key\n");
    if (spp_getkey(c, &pubkey))
      // send to client
      printf ("[ sending public key\n");
      if (spp_send(c, &pubkey)==SPP_ERR_OK)
        // wait for session key
        printf ("[ receiving session key\n");
        if (spp_recv(c, &seskey)==SPP_ERR_OK)
          // import it
          printf ("[ importing session key\n");
          if (spp_setkey(c, &seskey))
            // wait for ctr
            printf ("[ receiving ctr\n");
            if (spp_recv(c, &ctr)==SPP_ERR_OK)
              // import, then export to c->ctr
              printf ("[ setting ctr\n");
              r=spp_setctr(c, &ctr);
  } else {
    // wait for public key
    printf ("[ waiting for public key\n");
    if (spp_recv(c, &pubkey)==SPP_ERR_OK)
      // import it
      printf ("[ importing public key\n");
      if (spp_setkey(c, &pubkey))
        // generate session key
        printf ("[ generating session key\n");
        if (spp_getkey(c, &seskey))
          // send session key
          printf ("[ sending session key\n");
          if (spp_send(c, &seskey)==SPP_ERR_OK)
            // get random ctr
            printf ("[ generating ctr\n");
            if (spp_setctr(c, &ctr))
              // send ctr
              printf ("[ sending ctr\n");
              r=(spp_send(c, &ctr)==SPP_ERR_OK);
  // if no error, set secure flag
  if (r) { 
  return r;


Once we’ve established a session key and initialization vector, CTR mode is used to encrypt and decrypt traffic between each system.

CTR mode turns a block cipher into a stream cipher and is much more ideal for a shell like this where small chunks of data are being transmitted.

Although it isn’t supported by Microsoft Crypto API which I’ve discussed here, we only have to encrypt the IV using ECB mode and XOR the plaintext with the ciphertext result.

// xor pt with ct
uint32_t memxor (uint8_t *pt, uint8_t *ct, uint32_t len)
  uint32_t i;
  len=(len>SPP_BLK_LEN) ? SPP_BLK_LEN:len;
  for (i=0; i<len; i++) {
    pt[i] ^= ct[i];
  return len;

void update_ctr (uint8_t *ctr)
  int i;
  for (i=SPP_CTR_LEN-1; i>=0; i--) {
    if (ctr[i]) {

// encrypt or decrypt in CTR mode
int spp_crypt (spp_ctx *c, void *in, uint32_t len)
  DWORD r, ctr_len, inlen=len;
  PBYTE p=(PBYTE)in;
  while (inlen > 0)
    // copy ctr into temp space
    memcpy (ctr, c->ctr, SPP_CTR_LEN);
    // encrypt with AES-256 in ECB mode
    r=CryptEncrypt (c->hSession, 0, FALSE, 
      0, ctr, &ctr_len, SPP_CTR_LEN);
    // xor against plaintext
    r=memxor (p, ctr, inlen);
    // update ctr
    // advance plaintext position
    p += r;
    // decrease length of input
    inlen -= r;
  return r;

Message Authentication Code

The session key is used to generate MAC of encrypted data. I considered using a separate key but believe this is sufficient. Comments?

// generate mac of data and save in mac
int spp_mac (spp_ctx *c, void *in, uint32_t inlen, void *mac)
  BOOL       r=FALSE;
  HMAC_INFO  hinfo;
  DWORD      macLen;
  // create HMAC using the session key
  r=CryptCreateHash (c->hProv, CALG_HMAC, c->hSession, 0, &hMAC);
  if (r) {
    // use HMAC-SHA-256
    ZeroMemory (&hinfo, sizeof(hinfo));
    hinfo.HashAlgid = CALG_SHA_256;
    r=CryptSetHashParam (hMAC, HP_HMAC_INFO, (PBYTE)&hinfo, 0);
    if (r) {
      // hash input
      r=CryptHashData(hMAC, in, inlen, 0);
      // get the hash
      if (r) {
        r=CryptGetHashParam(hMAC, HP_HASHVAL, mac, &macLen, 0);
    CryptDestroyHash (hMAC);
  return r;

Traffic Analysis of encrypted data

Now that we’ve a method of key exchange and encryption which should prevent most people from viewing traffic sent between two systems. Let’s see what wireshark gives us.

The following shows key exchange and the result of ‘whoami’ and ‘dir’ commands.



Where do we go from here? One last thing to implement is a server for Linux but since Crypto API has many opaque structures, it might not be the best solution for something like this.

At some point in the future, I’ll implement a server for Linux but the client component is unlikely to use Crypto API.


Although I’ve tried to minimize bugs in this shell, it hasn’t been tested extensively and in its current state probably is unstable 🙂

I’ve no problem releasing sources under a BSD license with the understanding anyone using it doesn’t expect this application to be free of potential bugs/flaws so use with caution.

Sources and binary can be downloaded from here.

This entry was posted in cryptography, networking, programming, security, windows and tagged , , , , , , , , . Bookmark the permalink.

One Response to Windows: Interactive shells Part 4

  1. Pingback: Shellcode: A Windows PIC using RSA-2048 key exchange, AES-256, SHA-3 | modexp

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s