================================================================================
                          How to cook a covert channel

                               v1.0 - April 2006

                                Gray World Team
                           http://www.gray-world.net
================================================================================

================================================================================
This paper was originally released in the Hakin9 magazine issue  04/06  (Windows
Rootkits) : Check their website at http://hakin9.org
================================================================================

================================================================================
Copyright (c) 2006, Gray World Team <team [at] gray-world.net>.
Permission is granted to copy, distribute and/or modify this document under  the
terms of the GNU Free Documentation License, Version 1.2 or  any  later  version
published by the Free Software Foundation; with  the  Invariant  Sections  being
LIST THEIR  TITLES,  with  the  Front-Cover  Texts  being  LIST,  and  with  the
Back-Cover Texts being LIST.
You should have received a copy of the license with this document and it  should
be present in the fdl.txt file. 
If you did not receive this file or if you don't think this fdl.txt  license  is
correct,  have  a  look  at  the  official   http://www.fsf.org/licenses/fdl.txt
licence file.
================================================================================

=======
SUMMARY
=======

INTRODUCTION

1. RECETTE
1.1 The cookie theory
1.2 Thinking about the recette
1.3 Our beta recette
1.4 Telling about the recette to friends
2. FAST FOOD COOKING
2.1 The server
2.2 The client
2.3 How does it looks like
2.4 hazard game
3. PRIATNOVO APETITA
3.1 Location of cookies
3.2 Second level caching

WEBOGRAPHY

THANKS

ANNEX
Annex 1 : I am up python example
Annex 2 : Xoring cookie python example
Annex 3 : Hungry ? ([SCAPY] required) 

================================================================================

============
INTRODUCTION
============

Before starting to cook your covert channel, you first have to think  about  the
recette : decide *how* your covert channel will look like,  *what*  it  will  be
used for (antipasti or dessert ?) and finally *when* you'll  have  your  dinner.
Today's menu focuses on HTTP cookies so let's review the recette  and  start  to
cook.

================================================================================

==========
1. RECETTE
==========

We all know about HTTP and cookies. If you ever bought something on the Internet
(or last time someone did it for you ;)), you probably used cookies to  maintain
a logical session with the remote server. So, how would it be  possible  to  use
these cookies entities as a stealth communication channel ?

1.1 The cookie theory
---------------------

Let's review the [RFC_2109] document which describes various interesting  points
regarding the logical sessions creation :

  //--------------------------------------------------------------------------\\

  1 "[...] designers' paradigm for sessions created by the exchange of cookies
    these key attributes:
      1. Each session has a beginning and an end.
      2. Each session is relatively short-lived.
      3. Either the user agent or the origin server may terminate a session."

  2 "To initiate a session, the origin server returns an extra response header
    to the client, Set-Cookie. [...] A user agent  returns  a  Cookie  request
    header [...] to the origin server if it chooses to continue a session. The
    origin server may ignore it or use it to determine the  current  state  of
    the session. It may send back to the client a Set-Cookie  response  header
    with the same or different information,  or  it  may  send  no  Set-Cookie
    header at all."

  3 "Servers may return a Set-Cookie response headers with any response.  User
    agents should send Cookie request headers, subject to other rules detailed
    below,  with  every   request.  An  origin  server  may  include  multiple
    Set-Cookie headers in a response."

  4 "Set-Cookie Syntax : [...]
    cookie = NAME "=" VALUE *(";" cookie-av) [...]
    NAME=VALUE
    Required. The name of the state information ("cookie") is NAME ,  and  its
    value is VALUE. [...] The VALUE is opaque to the user  agent  and  may  be
    anything the origin server chooses to send, possibly in a  server-selected
    printable ASCII encoding. "Opaque" implies that the content is of interest
    and relevance only to the origin server. The  content  may,  in  fact,  be
    readable by anyone that examines the Set-Cookie header."
    
  5 "An origin server must be cognizant of the effect of possible  caching  of
    both the returned resource and the Set-Cookie header. [...] If the  cookie
    is intended for use by a single user, the Set-cookie header should  not be
    cached. A Set-cookie header that is intended  to  be  shared  by  multiple
    users may be cached."

  \\--------------------------------------------------------------------------//

We understand that the session may be terminated by the server (we'll thereafter
use 'server' to speak about the HTTP server and use 'client' to speak about  the
HTTP client) or by the client and that sessions should not last too long. (1)

We notice that a client "should" send cookie(s) with every request to the server
and that the server may send a cookie to a client even if the client didn't  ask
for it. (2) and (3)

We also notice that the cookie value is "opaque" to the client and thus is  also
"opaque" for an host willing to monitor the sessions. (4)

Finally, we suppose that it is not something suspicious to ask caching  services
not to cache the cookies sent by client and server if the cookie is intended for
use by a single user. (5)

Note : Of course, reading (5) the other way means we may have an opportunity  to
use these caching services as a second-level relay to store and forward data  to
multiple clients with or without server. We already know this  is  possible  for
any HTTP entity but maybe will it be possible for the cookies too.

1.2 Thinking about the recette
------------------------------

"A covert channel is a  communication  channel  that  is  not  designed  and/nor
intended to exist and that can be used to transfer information in a manner  that
violates the  existing  security  policy.  [...]  Various  parameters  exist  to
characterize  covert channels : Noise, Bandwidth/Capacity,  Synchronization  and 
Aggregation [...], Latency and Stealthiness" [CC]

The recette of the day will focus on preparing, step  by  step, a 'new'  control
communication channel (Refer to [CC] for the difference between control and data
communication channels) which will be as stealth as possible.

As we cook a stealth communication channel, we consider that  bandwidth/capacity
and latency parameters are not key factors.

We cook a communication channel over the HTTP protocol. It means that  the  HTTP
server needs an HTTP client contact before being able to send any  data.  As  we
focus  on a control communication channel, we also have to restrict  the  amount
of data and the emission frequency parameters the HTTP client uses to  send  and
receive data from the HTTP server.

We won't discuss the active warden problem as it would involve him to alter  and
keep track of any cookie he detects (not a so good idea to change only parts  of
the cookie...) and finally we will suppose that everything but our  cookies  are
seen as standard to a potential detection system  (Network  layers  factors  and
HTTP protocol behavior).

1.3 Our beta recette
--------------------

The information container model the HTTP client and the HTTP server will use  is
as simple as :

  -------------------------------------------------------------
  Checksum : default size 2 bytes
  Command  : default size 1 byte  => is a request or a response
  Info     : request or response parameters
  Padding  : default size until 20 bytes
  -------------------------------------------------------------

Checksum is a standard computed checksum over the Command and  Info  parameters.
Command indicates if the cookie contains a request or  a  response.  Padding  is
something optional which allow to change the cookie size.

Let's look at what kind of cookie we may have with a basic client  command  that
will tell to the server : I am up, here is my local IP address, my starting time
and my contact delay :

  ------------------------------------------------------------------------------
    01 : I am up (4+4+2 bytes) : IP address, start time , contact delay

    \x7E\x58         : Checksum
    \x01             : Command 01
    \x01\x02\x03\x04 : IP : 1.2.3.4
    \x07\x5B\xCD\x15 : start time : 123456789
    \x00\x0A         : contact period : 10 seconds
    \x42[*7]         : 7 bytes of padding
  ------------------------------------------------------------------------------

  will give a cookie: '7e580101020304075bcd15000a42424242424242'

  [Refer to Annex 1]

Now that we have a cookie, it would be a good idea not to send it in  cleartext.
If we can have enough 'random' bytes we can use to xor the cookie,  we  may  get
something a little bit less suspicious. So let's suppose we  have  a  static key
and x 'random' bytes known by client and  server,  we  then  can  use  a  digest
function to get enough "pseudo-random" bytes to xor our cookie before sending it
to the server.

  ------------------------------------------------------------------------------
  TOXOR=prepare_xor(len(CHECKSUM+CMDINFO+PADDING))
  XOR=strxor(CHECKSUM+CMDINFO+PADDING,TOXOR)
  COOKED=binascii.b2a_hex(XOR)
  ------------------------------------------------------------------------------

  will give a cookie: '582c76b3d761f5741774f9786603e2438853b8b0'

  [Refer to Annex 2]

We now may use cookies to send and receive data and we have a way to alter  them
so that they look "obscure" and "random". Let's focus  on some command types  it
would be interesting to implement :

  Client commands :
  ------------------------------------------------------------------------------
    01 : I am up (4+4+2 bytes) : IP address, start time , contact delay
  ------------------------------------------------------------------------------

  Server commands :
  ------------------------------------------------------------------------------
  Requests :
    01 : Change contact period (2 bytes) : set a new 'contact period'
    02 : New rbytes (Max is Size-3 bytes) : add new 'len' + 'random bytes'
    03 : Change cookie size and padding activation (3 bytes) : 'size' 'enable'
         (we can change the cookie size and enable or disable the padding)
  ------------------------------------------------------------------------------

With these commands, we basically can manage our control  communication  channel
so that it stays online as long as we need but we may face another problem:  How
do we know if a client or a server got the command we sent ? Let's use a command
/acknowledge mechanism such as the one described thereafter :

  Client commands :
  ------------------------------------------------------------------------------
    01 : I am up (4+4+2 bytes) : IP address, start time , contact delay

    FE : Same but next contact is changed to match the server 01 command.
    FD : Same.
    FC : Same but the new cookie size is used along with the padding activation
  ------------------------------------------------------------------------------

  Server commands :
  ------------------------------------------------------------------------------
    01 : Change contact period (2 bytes) : set a new 'contact period'
    02 : New rbytes (Max is Size-3 bytes) : add new 'len' + 'random bytes'
    03 : Change cookie size and padding activation (3 bytes) : 'size' 'enable'
         (we can change the cookie size and enable or disable the padding)

    FE : not used, no acknowledgement for a UP client message
  ------------------------------------------------------------------------------

Main advantage not using an acknowledgement for the client UP  message  is  that
the client will be able to send and resend the same cookie  without  1.  loosing
random bytes and 2. as any standard web client is doing.

1.4 Telling about the recette to friends
----------------------------------------

We arbitrary chose to "hex-ify" our cookie but you may choose another  algorithm
to encode your cookie. Let's start our favorite MS13 browser and watch about our
cookies :
  o Name is usually '-_1-9a-zA-Z' and 1 < x < 24 bytes long.
  o Domain is 50% fqdn and 50% .fqdn
  o Path is 90% '/' (is it ?)
  o Expiration is usually between today's year+1 and 2016 or 2038 (?)
  o Content looks like 

  ------------------------------------------------------------------------------
  Our cookie is :
  582c76b3d761f5741774f9786603e2438853b8b0
  and without padding :
  582c76b3d761f5741774f97866

  Other are (one per line) :
  a%3A0%3A%6A%7E
  RD4hwMCoACkAAHlIYdM
  B=cgqeo1l23r2a8&b=3&s=qi
  67.161.52.178.1150515143441505
  RMID=3ea03bc3443e21f0; RMFL=022FTyfuU1026D
  s_vi=[CS]v1|443E1E3D00002C59-A290C75000006B0[CE]
  210647688.476418719.1144933410.1144933410.1144933410.1
  id=ip.ip.ip.ip-1734349632.2977633:lv=116733416527:ss=114213316627
  ID=ad309d77f7453199:TM=1140474596:LM=1141314596:S=OcpTXoHx5MTCUQFl
  37692917347247624 bb=41K"KAKt_4KKQtotrKKA1|K"KAKt_4UURtotrKKA1| adv=
  MC1=V=3&GUID=2b5039af05c385919ecb1181f92bcaa; s_cc=true; s_sq=%5B%5BB%5D%5D; \
      MUID=A259C327D12B8C528ADD1787F3ED94&TUID=1
  pdomid=11; TestIfCookieP=ok; TestIfCookie=ok; ASPSESSIONIDSCQSQDTB=KMHHNNICFL\
      FPELFKJFMQPMPB; sasarea=91; vs=252=1225845; pbw=%24b%3D11%3B%24c%1242%3B%\
      14o%1D3; pid=8867356354182511254
  MUID=0F1BAEAF00C2765C9052128A0702B37A; MC1=V=3&GUID=2b5039af03dce61903b181f92\
      beaaa; FlightId=; FlightEligible=False{ expires=Mon, 25-Jan-2010 05:jxYf0\
      GMT; FlightGroupId=213; FlightStatus=
  ------------------------------------------------------------------------------
  Cookies are a little bit altered but who knows, you may recognize something :)

  [Refer to Annex 3]

Now, our next step is to study what's our friends  behavior  when  they  face  a
cookie so that we know when and how we can send and receive data. Hereunder  are
described sessions to famous "masked" websites.

  Standard session 1 :
  ------------------------------------------------------------------------------
  HTTP GET on A.XXX
  => Reply with a document location to www.A.XXX with :
     Set-Cookie: PREF=ID=af4xxab993229877f:TM=1134401:LM=1122401:S=7Ib_Bgu9cf5L;
     expires=Sun, 23-Jan-2038 19:14:07 GMT; path=/; domain=.A.YYY

  HTTP GET on www.A.XXX
  => Reply with :
     Set-Cookie: PREF=ID=ef6ed1bdb2a7b217:TM=11821401:LM=1221401:S=-MwFEtY3L1_Xe 

  Some HTTP GET on www.A.XXX having :
         Cookie: PREF=ID=ef6ed1bdb2a7b217:TM=11821401:LM=1221401:S=-MwFEtY3L1_Xe 

  Now we close the browser, wait a few seconds and do it again.

  HTTP GET on A.XXX having :
         Cookie: PREF=ID=ef6ed1bdb2a7b217:TM=11821401:LM=1221401:S=-MwFEtY3L1_Xe 
  => Reply with a document location to www.A.XXX without Set-Cookie

  HTTP GET on www.A.XXX having :
         Cookie: PREF=ID=ef6ed1bdb2a7b217:TM=11821401:LM=1221401:S=-MwFEtY3L1_Xe 

  etc...  
  ------------------------------------------------------------------------------

We conclude that our cooked client can send cookies to the server  even  if  the
server didn't send a Set-Cookie (until 2038 ?) because the server may have  send
this cookie 32 years ago ?

  Standard session 2 :
  ------------------------------------------------------------------------------
  HTTP GET on B.XXX
  => Reply with a document location to www.B.XXX
     Set-Cookie: ASPSESSIONIDATRSCS=HAEBGHTVCSXZFJLLLDIAJJMN; path=/

  HTTP GET on www.B.XXX without cookie
  ------------------------------------------------------------------------------

We conclude that we have few (only :)) practical (not only theoretically written
in the rfc) solutions for the server to send a cookie so that the client doesn't
have to reply with that cookie :
  o we Set-Cookie with a domain different from the one in HTTP URI (session 1)
  o we Set-Cookie without giving the domain (session 2)

It seems that our beta recette looks quiet interesting, let's start cooking.

================================================================================

====================
2. FAST FOOD COOKING
====================

Now that we know approximately what we'll cook, we need to choose what  kind  of
Bryan (who always is in the kitchen as we all know) will help us  to  cook  some
fast food for our (probable) future new friends.

We chose to use a [PYTHON] Bryan so that you and your "friends" can  taste  that
meal no matter if you have a Win32 or a *Nix kitchen. However, if you read  this
recette, you probably want to taste another meal  that  would  be  cooked  in  a
[Win32 C/C++] kitchen and that no one has  heard  before  because   it's  always
better not to tell anyone when you prepare a surprise ;)

So, our meal is built upon 2 ingredients : the client part which is a standalone
python application and the server part which is a CGI script you have to  upload
on a webserver.

2.1 The client
--------------

The client connects to the web server and sends  a  GET  request  along  with  a
cookie embedding the 'I am up' command. If the server response includes a cookie
the client decodes the cookie and sends back the related acknowledgement. If the
server doesn't reply to a client cookie, the client sleeps for x seconds.

As the server may answer with multiple cookies in a single response, the  client
parses all the cookies commands before sending the related  acknowledgement  (so
that server and client keep synchronization for random bytes).

The client sends its HTTP request with a MS13 or Firefox behavior: both browsers
act the same way at the TCP level for our CGI (TCP HandShake,  HTTP  GET,   HTTP
REPLY, TCP FIN HandShake) but do not  send  the  same  HTTP  headers  when  they
request the remote HTTP server.

  ------------------------------------------------------------------------------
  $ ./cook_cl.py -h
  cook_cl.py - v0.1
  -----------------
  
  Usage:
    ./cook_cl.py [-h|-V]
    ./cook_cl.py [-d server] [-p port] [-u url] [-s sec]
                 [-a proxy_ip:proxy_port:user:pass] [-m mimic] [-v]
  
  Arguments:
    -h    help
    -V    version
    -v    verbose mode
  
    -d    remote server ip or fqdn (default '127.0.0.1')
    -p    remote server HTTP port  (default '80')
    -u    remote server HTTP url   (default '/cgi-bin/cook_cgi')
    -s    sending delay (seconds)  (default '10')
    -a    HTTP proxy configuration (ip:port:user:pass)
    -m    Mimic browser ('msie' or 'firefox') (default: 'msie')
  ------------------------------------------------------------------------------

2.2 The server
--------------

The CGI server provides two services:
  o It manages client requests :  cookie  decoding,  keeping  information  about
    clients and admin commands to send, ...
  o It implements a basic web interface allowing the admin  to  display  clients
    information and issue commands.

When a client sends a GET request, the CGI checks the cookie and tries to decode
it, it updates the client information (stores them in a file) and finally  sends
the response to the client along with the commands the administrator prepared.

When an administrator  accesses  the  web  interface,  he  may  display  clients
information and prepare commands that will be sent to the client during the next
contact period.

if the administrator stocks more than 1 command to  send  to  the  client,  each
command will become a cookie and all cookies will  be  sent  in  a  single  HTTP
response to the client.

2.3 How does it looks like ?
----------------------------

We access the admin interface http://ip:port/cgi-bin/cook_cgi?pass=grayworld
  ------------------------------------------------------------------------------
  | $ ./cook_cgi
  | How to cook a covert channel - cook_cgi.py - v0.1
  | 
  | Bryan says : No clients
  | 
  | $ _
  ------------------------------------------------------------------------------
  fig. 1

We run a client on 10.1.1.7 and stop it:
  ------------------------------------------------------------------------------
  | ./cook_cl.py -d ip -v -s 60
  | 19:50:17 - Sending cookie to ip:80/cgi-bin/cook_cgi (2/16):\
  |   2d6852e6aeeb52b56c8fe9b01b16bb5095b27c9a28586498
  | ^C
  ------------------------------------------------------------------------------
  fig. 2

We run a second client on 10.1.1.8 and let it running:
  ------------------------------------------------------------------------------
  | ./cook_cl.py -d ip -v -s 180
  | 19:51:27 - Sending cookie to ip:80/cgi-bin/cook_cgi (2/16):\
  |   db0452e6aeeb5db56c8e2fb09316bb5095b27c9a28586498
  ------------------------------------------------------------------------------
  fig. 3

Let's look at our admin interface and stock 2 commands for the 10.1.1.8  client.
We'll stock a New contact period to 5 seconds (line 2) and disable  the  padding
(lines 1 and 3) :
  ------------------------------------------------------------------------------
     | $ ./cook_cgi
     | How to cook a covert channel - cook_cgi.py - v0.1
     | 
  1  | Bryan says : Stocked size update to 24 with padding to 0 for client 2.
     | 
     | Bryan says : Welcome in the kitchen, we have 2 client(s) (Wed Apr [...]
     |   o Remove clients quiet for more than 3600 seconds.
     |   o Don't double stock idem command : 1
     |   o Fake cookie for standard clients : None
     |   o Burn the kitchen
     | 
     | Clients list :
     | 
     |   #2 - Public IP 10.1.1.8 (last conn. time: Wed Apr 26 19:51:27 2006)
     |        => Local IP 10.1.1.8 (started [...] 19:51:27 2006 / contact:\
     |                                                                 180 secs)
     |        => RBYTES_POS: 2 (123:2460/125:2500 bytes:rbytes available) /\
     |                                                           RBYTES_POSI: 16
     |        => RBYTES: 'Soon her eye fel [...]  small c...ookie'
     |        => Cookie size is 24 bytes and padding activation is set to 1
     |        => Last cookie: \
     |    'PREF=db0452e6aeeb5db56c8e2fb09316bb5095b27c9a28586498' / Lost sync: 0
     | 
     |     What you have ? 
     |       New contact period , New rbytes , Change cookie size
     |       Disable / Enable padding, Remove commands
     | 
     |     Stocked commands:
     | 
   2 |       o '47aa01000542424242424242424242424242424242424242'
     | 
   3 |       o 'e8ab03001800424242424242424242424242424242424242'
     | 
     |   #1 - Public IP 10.1.1.7 (last conn. time: Wed Apr 26 19:50:17 2006)
     |        => Local IP 10.1.1.7 (started [...] 19:50:17 2006 / contact:\
     |                                                                  60 secs)
     |        => RBYTES_POS: 2 (123:2460/125:2500 bytes:rbytes available) /\
     |                                                           RBYTES_POSI: 16
     |        => RBYTES: 'Soon her eye fel [...]  small c...ookie'
     |        => Cookie size is 24 bytes and padding activation is set to 1
     |        => Last cookie: \
     |    'PREF=2d6852e6aeeb52b56c8fe9b01b16bb5095b27c9a28586498' / Lost sync: 0
     | 
     |     What you have ? 
     |       New contact period , New rbytes , Change cookie size
     |       Disable / Enable padding, Remove commands
     | 
     |     Stocked commands:
     | 
     | $ _
  ------------------------------------------------------------------------------
  fig. 4 (updated to fit on 80 cols)

Our client is connecting back 180 seconds later (line  1)  and  sends  the  same
cookie as previously. The CGI sends the 2 stocked commands (lines 2 -> 7) :  the
client updates its contact period to 5 seconds and then  disables  the  padding.
Then it sends back to the server the two acknowledgement  with  two  connections
(lines 8 and 9). It sleeps for 5 seconds and then contacts the server with a new
'I am up' message (line 10). Then it sleeps again and repeats the 'I am up' each
5 seconds (line 11):
  ------------------------------------------------------------------------------
   1 | 19:54:27 - Sending cookie to ip:80/cgi-bin/cook_cgi (2/16):\
     |   db0452e6aeeb5db56c8e2fb09316bb5095b27c9a28586498
   2 | 19:54:27 - Got 24 bytes cookie (4/16):\
     |   'G\xaa\x01\x00\x05BBBBBBBBBBBBBBBBBBB'
   3 | 19:54:27 - Command Update contact time
   4 | 19:54:27 - Updating contact period to 5 secs
   5 | 19:54:27 - Got 24 bytes cookie (6/16):\
     |   '\xe8\xab\x03\x00\x18\x00BBBBBBBBBBBBBBBBBB'
   6 | 19:54:27 - Command Update Size
   7 | 19:54:27 - Updating cookie size to 24 (padding activation: 0)
   8 | 19:54:27 - Sending cookie to ip:80/cgi-bin/cook_cgi (7/7):\
     |   22984fcc75fc01b0af217350eb
   9 | 19:54:27 - Sending cookie to ip:80/cgi-bin/cook_cgi (8/7):\
     |   14a4087e1e5cf3d5724b522fe6
  10 | 19:54:32 - Sending cookie to ip:80/cgi-bin/cook_cgi (9/7):\
     |   943d58cb1fd5864a98a1a47067
  11 | 19:54:38 - Sending cookie to ip:80/cgi-bin/cook_cgi (9/7):\
     |   943d58cb1fd5864a98a1a47067
  ------------------------------------------------------------------------------
  fig. 5

When we check back the admin interface we notice that  the  client  10.1.1.8  is
updated and that stocked commands are not registered anymore :
  ------------------------------------------------------------------------------
     | $ ./cook_cgi
     | How to cook a covert channel - cook_cgi.py - v0.1
     | 
     | Bryan says : Welcome in the kitchen, we have 2 client(s) (Wed Apr [...]
     |   o Remove clients quiet for more than 3600 seconds.
     |   o Don't double stock idem command : 1
     |   o Fake cookie for standard clients : None
     |   o Burn the kitchen
     | 
     | Clients list :
     | 
     |   #2 - Public IP 10.1.1.8 (last conn. time: Wed Apr 26 19:54:43 2006)
     |        => Local IP 10.1.1.8 (started [...] 19:51:27 2006 / contact:\
     |                                                                   5 secs)
     |        => RBYTES_POS: 9 (116:2320/125:2500 bytes:rbytes available) /\
     |                                                            RBYTES_POSI: 7
     |        => RBYTES: 'Soon her eye fel [...]  small c...ookie'
     |        => Cookie size is 24 bytes and padding activation is set to 0
     |        => Last cookie: 'PREF=943d58cb1fd5864a98a1a47067' / Lost sync: 0
     | 
     |     What you have ? 
     |       New contact period , New rbytes , Change cookie size
     |       Disable / Enable padding, Remove commands
     | 
     |     Stocked commands:
     | 
     |   #1 - Public IP 10.1.1.7 (last conn. time: Wed Apr 26 19:50:17 2006)
     |        => Local IP 10.1.1.7 (started [...] 19:50:17 2006 / contact:\
     |                                                                  60 secs)
     |        => RBYTES_POS: 2 (123:2460/125:2500 bytes:rbytes available) /\
     |                                                           RBYTES_POSI: 16
     |        => RBYTES: 'Soon her eye fel [...]  small c...ookie'
     |        => Cookie size is 24 bytes and padding activation is set to 1
     |        => Last cookie:\
     |    'PREF=2d6852e6aeeb52b56c8fe9b01b16bb5095b27c9a28586498' / Lost sync: 0
     | 
     |     What you have ? 
     |       New contact period , New rbytes , Change cookie size
     |       Disable / Enable padding, Remove commands
     | 
     |     Stocked commands:
     | 
     | $ _
  ------------------------------------------------------------------------------
  fig. 6

2.4 hazard game
---------------

Each client connecting for the first time to the server  uses  the  same  random
bytes (line  1, fig. 7 and fig. 8). However, each time you send new random bytes
to  a  client (line  2, fig. 7  and  fig. 8  and  lines 1/2  fig. 9),  they  are
dedicated to this client only.

  ------------------------------------------------------------------------------
     | # ./cook_cl.py -d ip -s 10 -v
     | 20:02:59 - Sending cookie to ip:80/cgi-bin/cook_cgi (2/16):\
   1 |    96a152e6aeeb52b5726263b02d16bb5095b27c9a28586498
     | 20:03:09 - Sending cookie to ip:80/cgi-bin/cook_cgi (2/16):\
     |    96a152e6aeeb52b5726263b02d16bb5095b27c9a28586498
     | 20:03:09 - Got 24 bytes (4/16): '5\x80\x02\x00\x10priatnovoapetitaBBB'
     | 20:03:09 - Command Update Rbytes
   2 | 20:03:09 - Updating rbytes with 'priatnovoapetita'
     | 20:03:09 - Sending cookie to ip:80/cgi-bin/cook_cgi (6/16):\
     |    4df16f06ec172a8b8a1bbca7ed2d154944584b2a5b2a31f0
     | 20:03:19 - Sending cookie to ip:80/cgi-bin/cook_cgi (8/16):\
     |    7f8db0cc75fc0eb0b1cd3f50e4e3fce0ec63cbaaf742b636
  ------------------------------------------------------------------------------
  fig. 7

  ------------------------------------------------------------------------------
     | # ./cook_cl.py -d ip -s 10 -v
     | 20:07:33 - Sending cookie to ip:80/cgi-bin/cook_cgi (2/16):\
   1 |   d55d52e6aeeb5db5726577b02d16bb5095b27c9a28586498
     | 20:07:43 - Sending cookie to ip:80/cgi-bin/cook_cgi (2/16):\
     |   d55d52e6aeeb5db5726577b02d16bb5095b27c9a28586498
     | 20:07:43 - Got 24 bytes (4/16): 'Q\x08\x02\x00\ndozvidaniaBBBBBBBBB'
     | 20:07:43 - Command Update Rbytes
   2 | 20:07:43 - Updating rbytes with 'dozvidania'
     | 20:07:43 - Sending cookie to ip:80/cgi-bin/cook_cgi (6/16):\
     |   0e0d6f06ec17258b8a1ca8a7ed2d154944584b2a5b2a31f0
     | 20:07:53 - Sending cookie to ip:80/cgi-bin/cook_cgi (8/16):\
     |   3c71b0cc75fc01b0b1ca2b50e4e3fce0ec63cbaaf742b636
  ------------------------------------------------------------------------------
  fig. 8

  ------------------------------------------------------------------------------
     | $ ./cook_cgi
     | How to cook a covert channel - cook_cgi.py - v0.1
     | 
     | [...]
     | 
     | Clients list :
     | 
     |   #2 - Public IP 10.1.1.8 (last conn. time: Thu Apr 27 20:07:53 2006)
     |        => Local IP 10.1.1.8 (started [...] 20:07:03 2006 / contact:\
     |                                                                  10 secs)
     |        => RBYTES_POS: 8 (127:2540/135:2700 bytes:rbytes available) /\
     |                                                           RBYTES_POSI: 16
   1 |        => RBYTES: 'Soon her eye fel [...] .ookiedozvidania'
     | 
     | [...]
     | 
     |   #1 - Public IP 10.1.1.7 (last conn. time: Thu Apr 27 20:03:19 2006)
     |        => Local IP 10.1.1.7 (started [...] 20:02:59 2006 / contact:\
     |                                                                  10 secs)
     |        => RBYTES_POS: 8 (133:2660/141:2820 bytes:rbytes available) /\
     |                                                           RBYTES_POSI: 16
   2 |        => RBYTES: 'Soon her eye fel [...] priatnovoapetita'
     | 
     | [...]
  ------------------------------------------------------------------------------
  fig. 9

As you may notice on fig. 7 and fig. 8, when client use the  same  random  bytes
with padding enabled, the padding part of the cookie is exactly the  same.  That
part will of course be different as soon as the  client  will  be  updated  with
new rbytes, but this behavior may be suspicious. For  this  reason,  padding  is
disabled by default. To use padding option, the best process would be :
  o disable padding
  o set few initialization random bytes for each client
  o once a client connects for the first time, stock the following  commands  or
    send them one after another (multiple HTTP requests/responses) :
    $ update contact period to short delay
    $ update cookie size to 'high' value
    $ add 'high' new random bytes
    $ update cookie size to standard size and enable padding
    $ update contact period to standard waiting time

You'll thus have client with  dedicated  random  bytes  and  the  initialization
cookies will be different as long as two clients don't start with the same local
ip address at the same time.

================================================================================

====================
3. PRIATNOVO APETITA
====================

For sure, having fast food for lunch isn't so good for health isn't it? Our meal
presents various problems : for example, its design implies  that  every  client
has to start with the same random bytes (and thus that you "cannot" use  padding
during initialization). It also means that if one client is 'compromised',  then
the whole communication for this client will be cleartext. A solution  would  be
to secure delete RBYTES from time to time for every client.

Another problem lays on the synchronization. If it is lost for any  reason, then
the client is lost. A solution would be, for example, to use another cookie  (or
any HTTP request field) to re-synchronize client : the server  sends  RBYTES_POS
+ x to the client and the client has to use it for its next 'I am  up'  message.
If the y next messages are wrong, then it means the client is 'compromised'  and
soon will the server be investigated too :)

Again, another problem lays on the scheme we use to  register  the  clients.  As
they're registered with the Public IP address,  one  single  client  per  public
IP address is possible. Few solutions to this problem may  be  implemented,  you
just have to find them :)

And what about the server ? Suppose your server is down, wouldn't it be fun that
the client automatically registers on a second one ? The  client  may  thus  use
RBYTES_POS[x] for x servers. Of course, we also could implement  a  new  command
which would be used to ask the client to switch to another server. If you  don't
want every server to be 'compromised' when a client  is,  just  store  4  xor-ed
bytes on the client side and send the 'key' when you want to switch.

Another funny idea is that once you've checked that the client  can  communicate
with the outside world you're done isn't it ? ;) so  another  command  would  be
'please my dear client, wipe yourself but <ironic>take care of your  environment
</ironic>'.

Thus, the 'suggestion du chef' for tomorrow would be to implement a safer RBYTES
behavior and to implement some online  behavior alteration (so that  our  client
becomes more and more useful once we know it is online). Of course,  the  'chef'
would like to suggest you to cook with unusual spices so that we  get  something
hot to taste : browser process injection because people often don't like  eating
python and because piggybacking  over  legitimate  HTTP  transactions  would  be
funky - at least if you want strangers to taste your recette :)

Priatnovo apetita : http://gray-world.net/projects/cooking_channels/

3.1 Location of cookies
-----------------------

We chose to embeed our Set-Cookie directive  in  the  HTTP  header  reply.  Note
that we also may use a META directive such as :

  ------------------------------------------------------------------------------
  <meta http-equiv="Set-Cookie" Content="PREF=42;path=/;domain=.gray-world.net">
  ------------------------------------------------------------------------------

This doesn't mean a lot for the current project, but you'll understand the trick
in the 3.2 part of the paper :)

3.2 Second level caching
------------------------

As described in '1.1 The cookie theory', it is possible to use caching  services
as an intermediate level to store and forward data to multiple clients and  then
stop using remote server.

The easiest way to implement this theory (even if more complicated schemes exist
- follow the white rabbit ;)) lays on :

  ----------------------------------------------------------------
  1. Client C1 requests an URI from server S through proxy P
  2. Server S  replies and response is cached in P
  3. Client C2 requests the same URI from server S through proxy P
  4. Proxy  P  replies with the 2. response
  ----------------------------------------------------------------

Basically, it means that clients C1 and C2 can  communicate  without  having  to 
reach the remote server for each message. It does mean something  in  the  mouse
and cat game we may play versus the detection team : it means that the detection
engine has to catch traffic between the clients and their first hop-to-target if
it is a caching service.

So, is it possible to implement that point with our cookies ? Let's look on  the
Squid FAQ. The FAQ (http://www.squid-cache.org/Doc/FAQ/) states : "Thus, Squid-2
does cache replies with Set-Cookie headers, but it filters  out  the  Set-Cookie
header itself for cache hits". Ok. It means that if we decide to use  Set-Cookie
header directives, we won't be able to cache our cookies. But does Squid filters
out the Meta equivalent (refer to '3.1 Location of cookies') ? Check yourself ;)

As discussed in '3. PRIATNOVO APETITA', that behavior may be interesting if  you
decide to ask the client to switch to another server. You only have to send  the
command once for the first client and then every client going through  the  same
cache service will be answered to switch to the second server.

================================================================================

======
THANKS
======

  TGW

================================================================================

==========
WEBOGRAPHY
==========

[RFC_2109] : RFC 2109 - HTTP State Management Mechanism - February 1997
http://gray-world.net/rfc/rfc2109.txt

[CC] : Covert channels through the looking glass v1.0 - October 2005
http://gray-world.net/projects/papers/cc.txt

[SCAPY] : Scapy - Interactive packet manipulation tool - v1.0.4.3
http://www.secdev.org/projects/scapy/files/scapy.py

[PYTHON] : Python
http://www.python.org/

================================================================================

=====
ANNEX
=====

Annex 1 : I am up python example
--------------------------------

### Common between server and client
import struct,time,binascii
from socket import inet_aton,inet_ntoa

def checksum(str):
  return(binascii.crc32(CMDINFO) & 0xffff)

SIZE=20
PADBYTE='\x42'
cmd=1
ip='1.2.3.4'
starttime=123456789
ntime=10

### Building the cookie
CMDINFO=struct.pack("!b4sIH",cmd,inet_aton(ip),int(starttime),ntime)
PADDING=PADBYTE*((SIZE-2)-(len(CMDINFO))) # SIZE-2 because of checksum
CHECKSUM=struct.pack("!H",checksum(CMDINFO))
COOKED=binascii.b2a_hex(CHECKSUM+CMDINFO+PADDING)
  
### Retrieving the cookie
# ... First check the size to unxor and get something we wantU
UNCOOKED=binascii.a2b_hex(COOKED)
# ... Get checksum and command
(CHECKSUM2,CMD2) = struct.unpack(">"+"2s1s",UNCOOKED[:3])
# ... Check the command, check the parameters size and get them
(IP2,TIME2,NTIME2) = struct.unpack(">"+"4s4s2s",UNCOOKED[3:13])
INFO2=IP2+TIME2+NTIME2
PADSIZE=(SIZE-2)-(len(CMD2)+len(INFO2))
PADDING2=struct.unpack(">"+str(PADSIZE)+"s",UNCOOKED[13:SIZE])[0]
# Verify checksum
VERIFY=struct.pack("!H",checksum(CMD2+INFO2))

Annex 2 : Xoring cookie python example
---------------------------------------------------

KEY="123456789"
RBYTES="ABCDEF"
RBYTES_POS=0
RBYTES_POSI=0
shasize=20

def strxor(x,y):
    return "".join(map(lambda x,y:chr(ord(x)^ord(y)),x,y))

def prepare_xor(lg) :
  global RBYTES_POS,RBYTES_POSI
  if RBYTES_POSI > 0 :
    toxor=sha(KEY+RBYTES[:RBYTES_POS]).digest()[20-RBYTES_POSI:] # 20 is sha size
    RBYTES_POSI=0
  RBYTES_POS += 1
  if RBYTES_POS > len(RBYTES)-1 : return ""
  toxor=sha(KEY+RBYTES[:RBYTES_POS]).digest()
  while lg > len(toxor) :
    RBYTES_POS += 1
    if RBYTES_POS > len(RBYTES)-1 : return ""
    toxor = toxor + sha(KEY+RBYTES[:RBYTES_POS]).digest()
  RBYTES_POSI = len(toxor)-lg
  return toxor[:len(toxor)-RBYTES_POSI]

# Call this function with the number of bytes you want  and  check  the return
# value. If it is null, then there is no more bytes we can use
TOXOR = prepare_xor(20)

# Call this function to (un)xor your cookie (after)before hexifying it
XOR=strxor(COOKIE,TOXOR)

Annex 3 : Hungry ? ([SCAPY] required) 
-------------------------------------

#!/usr/bin/env python
#
# How to cook a covert channel - v1.0 - http://gray-world.net
# Copyright (c) 2006, Gray World Team <team [at] gray-world.net>
#
# ./get_cook.py cookie1.cap > cookie1.txt
#

import sys, time, struct, binascii
from scapy import *

def get_cookie(p):
  set="Set-Cookie: "
  cook="Cookie: "
  res=None
  ok=0
  if TCP in p and p[TCP].haslayer(Raw):
    http=p[TCP].load
    for line in http.splitlines():
      if line.find(set) == 0 or line.find(cook) == 0: ok=1
      if line.find(set) == 0 or line.find(cook) == 0\
        or line.find("Host: ") == 0 or line.find("Location: ") == 0\
        or line.find("GET ") == 0 or line.find("POST ") == 0\
        or line.find("HTTP/1") == 0:
        if res == None:
          res = line
        else:
          res = res+"\n  "+line
  if not res == None:
    res = p.sprintf("%IP.src%:%TCP.sport% -> %IP.dst%:%TCP.dport%")+"\n  "+res
  if ok == 0: return None
  return res

def has_been_cooked():
  sys.stderr.write("Looking for cookies in "+FILE+" :\n")
  c=i=0
  try:
    list=PcapReader(FILE)
  except:
    print "Cannot PcapRead "+FILE
    sys.exit(0)
  
  p=list.read_packet()
  while p != None:
    p=list.read_packet()
    if p != None:
      cook=get_cookie(p)
      if cook:
        print cook
        i=i+1
    c=c+1
    sys.stderr.write("\r"+str(c)+" packets : Found "+str(i)+" cookies... ")
  sys.stderr.write(" Done\n")

def is_beeing_cooked():
  sys.stderr.write("Smelling cookies...\n")
  sniff(filter="tcp and ( port 80 or port 8080 )",prn=lambda x: get_cookie(x))

### Main

if len(sys.argv) == 1:
  print sys.argv[0]+" [capture.cap || sniff]"
  sys.exit(0)
else:
  if sys.argv[1] == "sniff":
    is_beeing_cooked()
  else:
    FILE=sys.argv[1]
    has_been_cooked()
