Month: September 2010

Brown Noise in Written Language

Today’s email brought a loaf of spam with this in it:

[Name withheld] is a world-class developer and provider of leading-edge solutions that help customers optimize the physical infrastructure through simplification, agility, and operational efficiency.

This passage is the informational equivalent of this audio file. If you can read it without feeling sad, sarcastic, vaguely scummy, or bitter about humanity’s perverse unwillingness to combine thought and language in a useful way, then I beg you to read Revising Prose and Rework.

Please.

That is all.

Hijacking HTTP traffic on your home subnet using ARP and iptables

Let’s talk about how to hijack HTTP traffic on your home subnet using ARP and iptables. It’s an easy and fun way to harass your friends, family, or flatmates while exploring the networking protocols.

Please don’t experiment with this outside of a subnet under your control — it’s against the law and it might be hard to get things back to their normal state.

The setup

Significant other comes home from work. SO pulls out laptop and tries to catch up on social media like every night. SO instead sees awesome personalized web page proposing marriage:

will you marry me, with unicorns

How do we accomplish this?

The key player is ARP, the “Address Resolution Protocol” responsible for associating Internet Layer addresses with Link Layer addresses. This usually means determining the MAC address corresponding to a given IP address.

ARP comes into play when you, for example, head over to a friend’s house, pull out your laptop, and try to use the wireless to surf the web. One of the first things that probably needs to happen is determining the MAC address of the gateway (probably your friend’s router), so that the Ethernet packets containing all those IP[TCP[HTTP]] requests you want to send out to the Internet know how to get to their first hop, the gateway.

Your laptop finds out the MAC address of the gateway by asking. It broadcasts an ARP request for “Who has IP address 192.168.1.1“, and the gateway broadcasts an ARP response saying “I have 192.168.1.1, and my MAC address is xx:xx:xx:xx:xx:xx“. Your laptop, armed with the MAC address of the gateway, can then craft Ethernet packets that will go to the gateway and get routed out to the Internet.

But the gateway didn’t really have to prove who it was. It just asserted who it was, and everyone listened. Anyone else can send an ARP response claiming to have IP address 192.168.1.1. And that’s the ticket: if you can pretend to be the gateway, you can control all the packets that get routed through the gateway and the content returned to clients.

Step 1: The layout

I did this at home. The three machines involved were:

  • real gateway router: IP address 192.168.1.1, MAC address 68:7f:74:9a:f4:ca
  • fake gateway: a desktop called kid-charlemagne, IP address 192.168.1.200, MAC address 00:30:1b:47:f2:74
  • test machine getting duped: a laptop on wireless called pixeleen, IP address 192.168.1.111, MAC address 00:23:6c:8f:3f:95

The gateway router, like most modern routers, is bridging between the wireless and wired domains, so ARP packets get broadcast to both domains.

Step 2: Enable IPv4 forwarding

kid-charlemagne wants to be receiving packets that aren’t destined for it (eg the web traffic). Unless IP forwarding is enabled, the networking subsystem is going to ignore packets that aren’t destined for us. So step 1 is to enable IP forwarding. All that takes is a non-zero value in /proc/sys/net/ipv4/ip_forward:

root@kid-charlemagne:~# echo 1 > /proc/sys/net/ipv4/ip_forward

Step 3: Set routing rules so packets going through the gateway get routed to you

kid-charlemagne is going to act like a little NAT. For HTTP packets heading out to the Internet, kid-charlemagne is going to rewrite the destination address in the IP packet headers to be its own IP address, so it becomes final destination for the web traffic:

PREROUTING rule to rewrite the source IP address

For HTTP packets heading back from kid-charlemagne to the client, it’ll rewrite the source address to be that of the original destination out on the Internet.

We can set up this routing rule with the following iptables command:

jesstess@kid-charlemagne:~$ sudo iptables -t nat -A PREROUTING \
> -p tcp --dport 80 -j NETMAP --to 192.168.1.200

The iptables command has 3 components:

  • When to apply a rule (-A PREROUTING)
  • What packets get that rule (-p tcp --dport 80)
  • The actual rule (-t nat-j NETMAP --to 192.168.1.200)

When

-t says we’re specifying a table. The nat table is where a lookup happens on packets that create new connections. The nat table comes with 3 built-in chains: PREROUTING, OUTPUT, and POSTROUTING. We want to add a rule in the PREROUTING chain, which will alter packets right as they come in, before routing rules have been applied.

What packets

That PREROUTING rule is going to apply to TCP packets destined for port 80 (-p tcp --dport 80), aka HTTP traffic. For packets that match this filter, jump (-j) to the following action:

The rule

If we receive a packet heading for some destination, rewrite the destination in the IP header to be 192.168.1.200 (NETMAP --to 192.168.1.200). Have the nat table keep a mapping between the original destination and rewritten destination. When a packet is returning through us to its source, rewrite the source in the IP header to be the original destination.

In summary: “If you’re a TCP packet destined for port 80 (HTTP traffic), actually make my address, 192.168.1.200, the destination, NATting both ways so this is transparent to the source.”

One last thing:

The networking subsystem will not allow you to ARP for a random IP address on an interface — it has to be an IP address actually assigned to that interface, or you’ll get a bind error along the lines of "Cannot assign requested address". We can handle this by adding an ip entry on the interface that is going to send packets to pixeleen, the test client. kid-charlemagne is wired, so it’ll be eth0.

jesstess@kid-charlemagne:~$ sudo ip addr add 192.168.1.1/24 dev eth0

We can check our work by listing all our interfaces’ addresses and noting that we now have two IP addresses for eth0, the original IP address 192.168.1.200, and the gateway address 192.168.1.1.

jesstess@kid-charlemagne:~$ ip addr
...
3: eth0:  mtu 1500 qdisc noqueue state UNKNOWN
    link/ether 00:30:1b:47:f2:74 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.200/24 brd 192.168.1.255 scope global eth0
    inet 192.168.1.1/24 scope global secondary eth0
    inet6 fe80::230:1bff:fe47:f274/64 scope link
       valid_lft forever preferred_lft forever
...
,multicast,up,lower_up>

Step 4: Set yourself up to respond to HTTP requests

kid-charlemagne happens to have Apache set up. You could run any minimalist web server that would, given a request for an arbitrary resource, do something interesting.

Step 5: Test pretending to be the gateway

At this point, kid-charlemagne is ready to pretend to be the gateway. The trouble is convincing pixeleen that the MAC address for the gateway has changed, to that of kid-charlemagne. We can do this by sending a Gratuitous ARP, which is basically a packet that says “I know nobody asked, but I have the MAC address for 192.168.1.1”. Machines that hear that Gratuitous ARP will replace an existing mapping from 192.168.1.1 to a MAC address in their ARP caches with the mapping advertised in that Gratuitous ARP.

We can look at the ARP cache on pixeleen before and after sending the Gratuitous ARP to verify that the Gratuitious ARP is working.

pixeleen’s ARP cache before the Gratuitous ARP:

jesstess@pixleen$ arp -a
? (192.168.1.1) at 68:7f:74:9a:f4:ca on en1 ifscope [ethernet]
? (192.168.1.200) at 0:30:1b:47:f2:74 on en1 ifscope [ethernet]

68:7f:74:9a:f4:ca is the MAC address of the real gateway router.

There are lots of command line utilities and bindings in various programming language that make it easy to issue ARP packets. I used the arping tool:

jesstess@kid-charlemagne:~$ sudo arping -c 3 -A -I eth0 192.168.1.1

We’ll send a Gratuitous ARP reply (-A), three times (-c -3), on the eth0 interface (-l eth0) for IP address 192.168.1.1.

As soon as we generate the Gratuitous ARPs, if we check pixeleen’s ARP cache:

jesstess@pixeleen$ arp -a
? (192.168.1.1) at 0:30:1b:47:f2:74 on en1 ifscope [ethernet]
? (192.168.1.200) at 0:30:1b:47:f2:74 on en1 ifscope [ethernet]

Bam. pixeleen now thinks the MAC address for IP address 192.169.1.1 is 0:30:1b:47:f2:74, which is kid-charlemagne’s address.

If I try to browse the web on pixeleen, I am served the resource matching the rules in kid-charlemagne’s web server.

We can watch this whole exchange in Wireshark:

First, the Gratuitous ARPs generated by kid-charlemagne:

Gratuitous ARPs generated by kid-c

The only traffic getting its headers rewritten so that kid-charlemagne is the destination is HTTP traffic: TCP traffic on port 80. That means all of the non-HTTP traffic associated with viewing a web page still happens as normal. In particular, when kid-charlemagne gets the DNS resolution requests for lycos.com, the test site I visited, it will follow its routing rules and forward them to the real router, which will send them out to the Internet:

DNS response to pixeleen for lycos.com

The HTTP traffic gets served by kid-charlemagne:

HTTP traffic viewed in Wireshark

Note that the HTTP request has a source IP of 192.168.1.111, pixeleen, and a destination IP of 209.202.254.14, which dig -x 209.202.254.14 +short tells us is search-core1.bo3.lycos.com. The HTTP response has a source IP of 209.202.254.14 and a destination IP of 192.168.1.111. The fact that kid-charlemagne has rerouted and served the request is totally transparent to the client at the IP layer.

Step 6: Deploy against friends and family

I trust you to get creative with this.

Step 7: Reset everything to the normal state

To get the normal gateway back in control, delete the IP address from the interface on kid-charlemagne and delete the iptables routing rule:

jesstess@kid-charlemagne:~$ sudo ip addr delete 192.168.1.1/24 dev eth0
jesstess@kid-charlemagne:~$ sudo iptables -t nat -D PREROUTING -p tcp --dport 80 -j NETMAP --to 192.168.1.200

To get the client machines to believe the router is the real gateway, you might have to clear the gateway entry from the ARP cache with arp -d 192.168.1.1, or bring your interfaces down and back up. I can verify that my TiVo corrected itself quickly without any intervention, but I won’t make any promises about your networked devices.

In summary

That was a lot of explanatory text, but the steps required to hijack the HTTP traffic on your home subnet can be boiled down to:

  1. enabled IP forwarding: echo 1 > /proc/sys/net/ipv4/ip_forward
  2. set your routing rule: iptables -t nat -A PREROUTING -p tcp --dport 80 -j NETMAP --to 192.168.1.200
  3. add the gateway IP address to the appropriate interface: ip addr add 192.168.1.1/24 dev eth0
  4. ARP for the gateway MAC address: arping -c 3 -A -I eth0 192.168.1.1

substituting the appropriate IP address and interface information and tearing down when you’re done.

And that’s all there is to it!

This has been tested as working in a few environments, but it might not work in yours. I’d love to hear the details on if this works, works with modifications, or doesn’t work (because the devices are being too clever about Gratuitous ARPs, or otherwise) in the comments.

–> Huge thank-you to fellow experimenter adamf. <—

~jesstess

OOW10

Yesterday I came back from my third Oracle OpenWorld. This year I was not on a bloggers pass, so I feel less obliged to cover the event. But I know there are people out there that like to read about my experiences and I still like it very much at San Francisco, so here is a small write-up about my experiences at Oracle OpenWorld 2010.The event started at Sunday, but later than usual: 12:30 PM I

OOW10

Yesterday I came back from my third Oracle OpenWorld. This year I was not on a bloggers pass, so I feel less obliged to cover the event. But I know there are people out there that like to read about my experiences and I still like it very much at San Francisco, so here is a small write-up about my experiences at Oracle OpenWorld 2010.The event started at Sunday, but later than usual: 12:30 PM I

Working Around an Internal Error Based on Source Mnemonics

My frenzied hacking through the unexplored territory of Expression Filters was derailed (to mix my metaphors) by an internal error when using the Evaluate() function in a rather complex query that uses an inline view, analytic functions etc.. The following was extracted from the alert log: ORA-07445: exception encountered: core dump [kkqsCkLegalEqvExpCB()+199] [SIGSEGV] [Address not […]

Anatomy of an exploit: CVE-2010-3081

It has been an exciting week for most people running 64-bit Linux systems. Shortly after “Ac1dB1tch3z” released his or her exploit of the vulnerability known as CVE-2010-3081, we saw this exploit aggressively compromising machines, with reports of compromises all over the hosting industry and many machines using our diagnostic tool and testing positive for the backdoors left by the exploit.

The talk around the exploit has mostly been panic and mitigation, though, so now that people have had time to patch their machines and triage their compromised systems, what I’d like to do for you today is talk about how this bug worked, how the exploit worked, and what we can learn about Linux security.

The Ingredients of an Exploit

There are three basic ingredients that typically go into a kernel exploit: the bug, the target, and the payload. The exploit triggers the bug — a flaw in the kernel — to write evil data corrupting the target, which is some kernel data structure. Then it prods the kernel to look at that evil data and follow it to run the payload, a snippet of code that gives the exploit the run of the system.

The bug is the one ingredient that is unique to a particular vulnerability. The target and the payload may be reused by an attacker in exploits for other vulnerabilities — if ‘Ac1dB1tch3z’ didn’t copy them already from an earlier exploit, by himself or by someone else, he or she will probably reuse them in future exploits.

Let’s look at each of these in more detail.

The Bug: CVE-2010-3081

An exploit starts with a bug, or vulnerability, some kernel flaw that allows a malicious user to make a mess — to write onto its target in the kernel. This bug is called CVE-2010-3081, and it allows a user to write a handful of words into memory almost anywhere in the kernel.

The bug was present in Linux’s ‘compat’ subsystem, which is used on 64-bit systems to maintain compatibility with 32-bit binaries by providing all the system calls in 32-bit form. Now Linux has over 300 different system calls, so this was a big job. The Linux developers made certain choices in order to keep the task manageable:

  • We don’t want to rewrite the code that actually does the work of each system call, so instead we have a little wrapper function for compat mode.
  • The wrapper function needs to take arguments from userspace in 32-bit form, then put them in 64-bit form to pass to the code that does the system call’s work. Often some arguments are structs which are laid out differently in the 32-bit and 64-bit worlds, so we have to make a new 64-bit struct based on the user’s 32-bit struct.
  • The code that does the work expects to find the struct in the user’s address space, so we have to put ours there. Where in userspace can we find space without stepping on toes? The compat subsystem provides a function to find it on the user’s stack.

Now, here’s the core problem. That allocation routine went like this:

  static inline void __user *compat_alloc_user_space(long len)
  {
          struct pt_regs *regs = task_pt_regs(current);
          return (void __user *)regs->sp - len;
  }

The way you use it looks a lot like the old familiar malloc(), or the kernel’s kmalloc(), or any number of other memory-allocation routines: you pass in the number of bytes you need, and it returns a pointer where you are supposed to read and write that many bytes to your heart’s content. But it comes — came — with a special catch, and it’s a big one: before you used that memory, you had to check that it was actually OK for the user to use that memory, with the kernel’s access_ok() function. If you’ve ever helped maintain a large piece of software, you know it’s inevitable that someone will eventually be fooled by the analogy, miss the incongruence, and forget that check.

Fortunately the kernel developers are smart and careful people, and they defied that inevitability almost everywhere. Unfortunately, they missed it in at least two places. One of those is this bug. If we call getsockopt() in 32-bit fashion on the socket that represents a network connection over IP, and pass an optname of MCAST_MSFILTER, then in a 64-bit kernel we end up in compat_mc_getsockopt():

  int compat_mc_getsockopt(struct sock *sock, int level, int optname,
          char __user *optval, int __user *optlen,
          int (*getsockopt)(struct sock *,int,int,char __user *,int __user *))
  {

This function calls compat_alloc_user_space(), and it fails to check the result is OK for the user to access — and by happenstance the struct it’s making room for has a variable length, supplied by the user.

So the attacker’s strategy goes like so:

  • Make an IP socket in a 32-bit process, and call getsockopt() on it with optname MCAST_MSFILTER. Pass in a giant length value, almost the full possible 2GB. Because compat_alloc_user_space() finds space by just subtracting the length from the user’s stack pointer, with a giant length the address wraps around, down past zero, to where the kernel lives at the top of the address space.
  • When the bug fires, the kernel will copy the original struct, which the attacker provides, into the space it has just ‘allocated’, starting at that address up in kernel-land. So fill that struct with, say, an address for evil code.
  • Tune the length value so that the address where the ‘new struct’ lives is a particularly interesting object in the kernel, a target.

The fix for CVE-2010-3081 was to make compat_alloc_user_space() call access_ok() to check for itself.

More technical details are ably explained in the original report by security researcher Ben Hawkes, who brought the vulnerability to light.

The Target: Function Pointers Everywhere

The target is some place in the kernel where if we make the right mess, we can leverage that into the kernel running the attacker’s code, the payload. Now the kernel is full of function pointers, because secretly it’s object oriented. So for example the attacker may poke some userspace object like a special file to cause the kernel to invoke a certain method on it — and before doing so will target that method’s function pointer in the object’s virtual method table (called an “ops struct” in kernel lingo) which says where to find all the methods, scribbling over it with the address of the payload.

A key constraint for the attacker is to pick something that will never be used in normal operation, so that nothing goes awry to catch the user’s attention. This exploit uses one of three targets: the interrupt descriptor table, timer_list_fops, and the LSM subsystem.

  • The interrupt descriptor table (IDT) is morally a big table of function pointers. When an interrupt happens, the hardware looks it up in the IDT, which the kernel has set up in advance, and calls the handler function it finds there. It’s more complicated than that because each entry in the table also needs some metadata to say who’s allowed to invoke the interrupt, whether the handler should be called with user or kernel privileges, etc. This exploit picks interrupt number 221, higher than anybody normally uses, and carefully sets up that entry in the IDT so that its own evil code is the handler and runs in kernel mode. Then with the single instruction int $221, it makes that interrupt happen.
  • timer_list_fops is the “ops struct” or virtual method table for a special file called /proc/timer_list. Like many other special files that make up the proc filesystem, /proc/timer_list exists to provide kernel information to userspace. This exploit scribbles on the pointer for the poll method, which is normally not even provided for this file (so it inherits a generic behavior), and which nobody ever uses. Then it just opens that file and calls poll(). I believe this could just as well have been almost any file in /proc/.
  • The LSM approach attacks several different ops structs of type security_operations, the tables of methods for different ‘Linux security modules’. These are gigantic structs with hundreds of function pointers; the one the exploit targets in each struct is msg_queue_msgctl, the 100th one. Then it issues a msgctl system call, which causes the kernel to check whether it’s authorized by calling the msg_queue_msgctl method… which is now the exploit’s code.

Why three different targets? One is enough, right? The answer is flexibility. Some kernels don’t have timer_list_fops. Some kernels have it, but don’t make available a symbol to find its address, and the address will vary from kernel to kernel, so it’s tricky to find. Other kernels pose the same obstacle with the security_operations structs, or use a different security_operations than the ones the exploit corrupts. Different kernels offer different targets, so a widely applicable exploit has to have several targets in its repertoire. This one picks and chooses which one to use depending on what it can find.

The Payload: Steal Privileges

Finally, once the bug is used to corrupt the target and the target is triggered, the kernel runs the attacker’s payload, or shellcode. A simple exploit will run the bare minimum of code inside the kernel, because it’s much easier to write code that can run in userspace than in kernelspace — so it just sets the process up to have the run of the system, and then returns.

This means setting the process’s user ID to 0, root, so that everything else it does is with root privileges. A process’s user ID is stored in different places in different kernel versions — the system became more complicated in 2.6.29, and again in 2.6.30 — so the exploit needs to have flexibility again. This one checks the version with uname and assembles the payload accordingly.

This exploit can also clear a couple of flags to turn off SELinux, with code it optionally includes in the payload — more flexibility. Then it lets the kernel return to userspace, and starts a root shell.

In a real attack, that root shell might be used to replace key system binaries, steal data, start a botnet daemon, or install backdoors on disk to cement the attacker’s control and hide their presence.

Flexibility, or, You Can’t Trust a Failing Exploit

All the points of flexibility in this exploit illustrate a key lesson: you can’t determine you’re vulnerable just because an exploit fails. For example, on a Fedora 13 system, this exploit errors out with a message like this:

  $ ./ABftw
  Ac1dB1tCh3z VS Linux kernel 2.6 kernel 0d4y
  $$$ Kallsyms +r
  $$$ K3rn3l r3l3as3: 2.6.34.6-54.fc13.i686
  [...]
  !!! Err0r 1n s3tt1ng cr3d sh3llc0d3z 

Sometimes a system administrator sees an exploit fail like that and concludes they’re safe. “Oh, Red Hat / Debian / my vendor says I’m vulnerable”, they may say. “But the exploit doesn’t work, so they’re just making stuff up, right?”

Unfortunately, this can be a fatal mistake. In fact, the machine above is vulnerable. The error message only comes about because the exploit can’t find the symbol per_cpu__current_task, whose value it needs in the payload; it’s the address at which to find the kernel’s main per-process data structure, the task_struct. But a skilled attacker can find the task_struct without that symbol, by following pointers from other known data structures in the kernel.

In general, there is almost infinitely much work an exploit writer could put in to make the exploit function on more and more kernels. Use a wider repertoire of targets; find missing symbols by following pointers or by pattern-matching in the kernel; find missing symbols by brute force, with a table prepared in advance; disable SELinux, as this exploit does, or grsecurity; or add special code to navigate the data structures of unusual kernels like OpenVZ. If the bug is there in a kernel but the exploit breaks, it’s only a matter of work or more work to extend the exploit to function there too.

That’s why the only way to know that a given kernel is not affected by a vulnerability is a careful examination of the bug against the kernel’s source code and configuration, and never to rely on a failing exploit — and even that examination can sometimes be mistakenly optimistic. In practice, for a busy system administrator this means that when the vendor recommends you update, the only safe choice is to update.

~price

TEL/電話+86 13764045638
Email service@parnassusdata.com
QQ 47079569