prev Translate Page next

The Catcher in the INT 80h

by Dan Kogai (小飼弾)

This document is also available as:

http://www.dan.co.jp/~dankogai/
shibuya-pm-11/lleval.html

The Catcher in the Rye

asin:0140237496

"sole guardian of numerous children running and playing in a huge rye field on the edge of a cliff"

ライ麦畑でつかまえて

asin:4560070512

「自分は、広いライ麦畑で遊んでいる子どもたちが、気付かずに崖っぷちから落ちそうになったときに、捕まえてあげるような、そんな人間になりたい」

The Catcher in the INT 80h

"sole guardian of numerous processes running and playing in a huge binary field on the edge of a syscalls"

バイナリ麦畑でつかまえて

「自分は、広いバイナリ畑で遊んでいるプログラムたちが、気付かずにシステムコールから落ちそうになったときに、捕まえてあげるような、そんなサービスを作りたい」

lleval is born

http://colabv6.dan.co.jp/lleval.html

Inside lleval

is a simple CGI that runs a given program in a chrooted field

Directory Hierarchy

% find . -xdev | sort
./0123456789abcdef
./0123456789abcdef/eval.pl
./bin
./dev
./etc
./etc/localtime
./etc/protocols
./etc/services
./lib
./libexec
./libexec/ld-elf.so.1
./libexec/ld-elf.so.1.old
./tmp
./usr
./usr/bin
./usr/bin/bwbasic
...
./usr/bin/perl
...
./usr/bin/tclsh
./usr/lib
./usr/local
./usr/local/bin
./usr/local/lib
./usr/local/libexec
./usr/local/perl6
./usr/local/share
./var
./var/run
./var/run/ld-elf.so.hints
./var/run/ld.so.hints

Directory Hierarchy

libraries are mounted read-only via nullfs

/lib	/home/chroot/lib	nullfs	ro,noatime,nosuid  0 0
/usr/lib           /home/chroot/usr/lib	      nullfs ro,noatime,nosuid  0 0
/usr/local/lib     /home/chroot/usr/local/lib     nullfs ro,noatime,nosuid  0 0
/usr/local/libexec /home/chroot/usr/local/libexec nullfs ro,noatime,nosuid  0 0
/usr/local/share   /home/chroot/usr/local/share   nullfs ro,noatime,nosuid  0 0
/usr/local/perl6   /home/chroot/usr/local/perl6   nullfs ro,noatime,nosuid  0 0

But where is the catcher?

FreeBSD::i386::Ptrace

Ports also available: p5-FreeBSD-i386-Ptrace

http://api.dan.co.jp/lleval.cgi
Web API
$DOCUMENT_ROOT/lleval.cgi
a wrapper binary just to chroot and exec eval.pl
$CHROOT/0123456789abcdef/eval.pl
the catcher in the int 80h
Usage:
 # simple strace in perl
  use strict;
  use warnings;
  use FreeBSD::i386::Ptrace;
  use FreeBSD::i386::Ptrace::Syscall;
  die "$0 prog args ..." unless @ARGV;
  my $pid = fork();
  die "fork failed:$!" if !defined($pid);
Usage:
  if ( $pid == 0 ) {    # son
    pt_trace_me;
    exec @ARGV;
  }
  else {                # mom
    wait;               # for exec;
    my $count = 0;
    while ( pt_to_sce($pid) == 0 ) {
        last if wait == -1;
        my $call = pt_getcall($pid);
        pt_to_scx($pid);
        wait;
        my $retval = pt_getcall($pid);
        my $name = $SYS{$call} || 'unknown';
        print "$name() = $retval\n";
        $count++;
    }
    warn "$count system calls issued\n";
  }
"So you can find the kids on the edge of the cliff. Can you catch them?"

http://labs.cybozu.co.jp/blog/kazuho/
archives/2009/03/freebsd_ptrace.php

Oku-san is half-right

pt_to_sce() happens BEFORE the system call invocation.

Oku-san is half-right

BUT YOU CAN'T STOP IT! -- at least by simply sending pt_kill().

Workaround #0:

You can't stop the invocation but you can still poke the argument on the stack.

sub check_open {
    my $pid  = shift;
    my $regs = pt_getregs($pid);
    my $st1  = pt_read( $pid, $regs->esp + 8 );
    if ( $st1 & ( O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC ) ) {
        if ( my $st0 = pt_read( $pid, $regs->esp + 4 ) ) {
            if ( my $path = pt_peekstr( $pid, $st0 ) ) {
                pt_write( $pid, $st0, 0 );
                ptrace( PT_CONTINUE, $pid, 0, 9 );
                die qq{XXX:open("$path",$st1)\n};
            }
        }
    }
}

Workaround #1:

You can't prevent [rv]?fork but you still send them to the oblivion

pt_continue($pid, 0, 9);

both son & mom die w/ SEGV

Signal handler

But you can still run a program on SEGV via $SIG{SEGV}

$SIG{SEGV} = sub{
    while(1){
        warn "$$: don't kill me, please!";
        sleep(1);
    }
};

You can catch that, too.

sub check_sigaction {
    my ($pid)  = @_;
    my $regs = pt_getregs($pid);
    my $st0 = pt_read( $pid, $regs->esp + 4 );
    my $st1 = pt_read( $pid, $regs->esp + 8 );
    my $st2 = pt_read( $pid, $regs->esp + 12 );
    if ($st0 == 11 && $st1 != 0 && $sigactions++ >= $sigaction_ok){
        pt_write( $pid, $st0, 0 );
        pt_write( $pid, $st1, 0 );
        pt_write( $pid, $st2, 0 );
        ptrace( PT_CONTINUE, $pid, 0, 9 );
        die qq{XXX:sigaction($st0, $st1, $st2)\n};
    }
}

Address 0x00000000 might not be the oblivion.

syscall(&SYS_mmap,
        0,4096,
        PROT_READ|PROT_WRITE|PROT_EXEC,
        MAP_FIXED|MAP_ANON,
        -1,0,0);

You can catch that, too.

sub check_mmap {
    my ($pid)  = @_;
    my $regs = pt_getregs($pid);
    my $st0 = pt_read( $pid, $regs->esp + 4 );  # addr
    my $st1 = pt_read( $pid, $regs->esp + 8 );  # len
    my $st2 = pt_read( $pid, $regs->esp + 12 ); # prot
    my $st3 = pt_read( $pid, $regs->esp + 16 ); # flags
    my $st4 = pt_read( $pid, $regs->esp + 20 ); # fd
    my $st5 = pt_read( $pid, $regs->esp + 24 ); # offset
    if ($st2 & 0x04 && $st3 & 0x10){
        pt_write( $pid, $st0, 0 );
        pt_write( $pid, $st1, 0 );
        pt_write( $pid, $st2, 0 );
        pt_write( $pid, $st3, 0 );
        pt_write( $pid, $st4, 0 );
        pt_write( $pid, $st5, 0 );
        ptrace( PT_CONTINUE, $pid, 0, 9 );
        die qq{XXX:mmap($st0, $st1, $st2, $st3, $st4, $st5)\n};
    }
}

Conclusion

Let children play free -- Just CATCH before they fall off from the cliff!

Thank you!
while(my $q = Questions->new){
  $q->answer;
}