Of all the gin joints in all the towns in all the world, Feiyi Wang had to walk into mine and say: > Hi, there > > Can anyone give a detailed explaination about why land attack works on > some TCP/IP stack (say BSD-derived)? Which loop is trapped in by this > "self-connect" request? What's the state transition internally? I can't > figure it out. > > A related question is I can't use tcpdump get any output from the victim > machine, once it is received the "self-connect" request, it freeze, not > even a ACK packet. (I am trying it on FreeBSD 2.2.5) > > Any information is appreciated. > > /Feiyi With BSD-derived TCP implementations, the ultimate effect of the 'loopback SYN' attack is that the TCP layer in the kernel gets into an ACK war with itself. That is, things ultimately reach the point where the kernel is stuck transmitting ACK segments to itself over an over again. The reason you don't see the packets on the network with tcpdump is that they're actually being exchanged internally via the loopback interface. Generally there's no point in a host putting traffic on the wire just to talk to itself. If you have a BSD-derived system and can enable TCP debugging (using options TCPDEBUG in the kernel config file), then you can see the state transitions. I did this with FreeBSD 2.2.5 and 2.1.0 (which is vulnerable to this problem) using a sample program I wrote which creates a TCP socket and listens for connection on port 8000. The progam also enables the SO_DEBUG flag on the socket using setsockopt(2). Lastly, in order to get the kernel to generate printf()s that show the state transitions, you need to set tcpconsdebug to 1 (it defaults to 0). You can do this either by editing /sys/netinet/tcp_debug.c and recompiling the kernel or flipping it on in a running system with the debugger. On FreeBSD, you can use gdb to do this: # gdb -q --wcore -k /kernel /dev/mem (no debugging symbols found)... IdlePTD 23e000 current pcb at 37d2000 #0 0xf0118557 in mi_switch () (kgdb) set tcpconsdebug = 1 (kgdb) quit Also, since the problem essentially is that the kernel gets stuck in an infinite loop, you need to use the kernel debugger to set a breakpoint at tcp_output() so you can see each step in the cycle without your screen being flooded with printf()s. You need 'options DDB' in your kernel config file for this, then you can use the console BREAK sequence to drop into the debugger: # Debugger("manual escape to debugger") Stopped at _Debugger+0x35: movb $0,_in_Debugger.122 db> break tcp_output db> continue Here's a sample session showing what happens on a FreeBSD 2.2.5 machine (please excuse the wrapped lines): - First, I run a small program that creates an TCP socket, bind()s it to port 8000, turns on the SO_DEBUG flag, and does a listen. # ./a.out & [1] 157 # 0xf06cda00 CLOSED:user BIND -> CLOSED rcv_(nxt,wnd,up) (0,0,0) snd_(una,nxt,max) (0,0,0) snd_(wl1,wl2,wnd) (0,0,0) 0xf06cda00 CLOSED:user LISTEN -> LISTEN rcv_(nxt,wnd,up) (0,0,0) snd_(una,nxt,max) (0,0,0) snd_(wl1,wl2,wnd) (0,0,0) - Now I transmit a 'loopback SYN' to the listening program. Note that my program transmits the SYN with a TCP sequence number of 0xdead. # ./lose2 victimhost 8000 0xf0725900 CLOSED:user ATTACH -> CLOSED rcv_(nxt,wnd,up) (0,0,0) snd_(una,nxt,max) (0,0,0) snd_(wl1,wl2,wnd) (0,0,0) - The first step is that the SYN is received and the connection goes into the SYN_RECEIVED state. Since we start in the LISTEN state, tcp_input() allocates a fresh initial sequence number for the receive side of the connection. 0xf0725900 LISTEN:input dead@0, urp=0<SYN> -> SYN_RCVD rcv_(nxt,wnd,up) (deae,4000,deae) snd_(una,nxt,max) (e6ab6b,e6ab6b,e6ab6b) snd_(wl1,wl2,wnd) (dead,0,0) Breakpoint at _tcp_output: pushl %ebp - Next, tcp_input() initiates the second part of the 3-way handshake by sending a SYN,ACK to what it thinks is a remote host. db> continue 0xf0725900 SYN_RCVD:output [e6ab6b..e6ab6f)@deae, urp=0<SYN,ACK> -> SYN_RCVD rcv_(nxt,wnd,up) (deae,4000,deae) snd_(una,nxt,max) (e6ab6b,e6ab6c,e6ab6c) snd_(wl1,wl2,wnd) (dead,0,0) - Because the source and destination addresses and ports in the forged SYN packet were the same, the SYN,ACK is reflected back to tcp_input(). The TCP sequence numbers in the packet are the same as those that were sent out before, which is where the problem starts: tcp_input() expects that the sequence numbers will relate to the ACK segment that it wants as part of the three way handshake. Instead, it gets a segment with the same sequence numbers as before, which places the segment outside the window. The fact that the ACK bit is set is probably what keeps tcp_input() from throwing the segment away right at the start. This is part of the bug: in the SYN_RECEIVED state, you're only supposed to get an ACK, not a SYN,ACK. Eventually we arrive at the code detailed on page 958 of _TCP/IP Illustrated Vol. 2_. This is near line 1030 in our version of tcp_input.c. According to the commentary on page 959, "The entire segment lies outside the window and is not a window probe, so the segment is discarded and an ACK is sent. This ACK will contain the expected sequence number." This code does a 'goto dropafterack' which causes an ACK to be sent. In effect, tcp_input() is saying: "No, that's not the segment I want; try again." The connection meanwhile stays in the SYN_RECEIVED state. 0xf0725900 SYN_RCVD:drop e6ab6b@deae, urp=0<SYN,ACK> -> SYN_RCVD rcv_(nxt,wnd,up) (deae,4470,deae) snd_(una,nxt,max) (e6ab6b,e6ab6c,e6ab6c) snd_(wl1,wl2,wnd) (dead,0,0) Breakpoint at _tcp_output: pushl %ebp db> continue 0xf0725900 SYN_RCVD:output e6ab6c@deae, urp=0<ACK> -> SYN_RCVD rcv_(nxt,wnd,up) (deae,4470,deae) snd_(una,nxt,max) (e6ab6b,e6ab6c,e6ab6c) snd_(wl1,wl2,wnd) (dead,0,0) - Now the ACK segment is again reflected back to tcp_input(), again with exactly the same sequence numbers, which are _not_ the sequence numbers tcp_input wants. So again, tcp_input() acknowledges the segment but says: "No stupid, that's still not the segment I wanted; try again" 0xf0725900 SYN_RCVD:drop e6ab6c@deae, urp=0<ACK> -> SYN_RCVD rcv_(nxt,wnd,up) (deae,4470,deae) snd_(una,nxt,max) (e6ab6b,e6ab6c,e6ab6c) snd_(wl1,wl2,wnd) (dead,0,0) Breakpoint at _tcp_output: pushl %ebp db> continue 0xf0725900 SYN_RCVD:output e6ab6c@deae, urp=0<ACK> -> SYN_RCVD rcv_(nxt,wnd,up) (deae,4470,deae) snd_(una,nxt,max) (e6ab6b,e6ab6c,e6ab6c) snd_(wl1,wl2,wnd) (dead,0,0) The kernel is now trapped in a loop: tcp_input() will again send an ACK with the same sequence numbers, which will be reflected right back to tcp_input(), which will cause tcp_input() to send another ACK with the same sequence numbers, which will be reflected right back to tcp_input(), which will cause tcp_input() to send another ACK with the same sequence numbers, which will be reflected right back to tcp_input(), which will cause tcp_input() to send another ACK with the same sequence numbers, which will be reflected right back to tcp_input(), which will cause tcp_input() to send another ACK with the same sequence numbers, and so on, and so on, and so on, etc... All this takes place with some interrupts masked, which means the OS is spinning out of control with no way to stop it short of a shutgun blast to the head. With BSD you can still manually break into the kernel debugger and regain partial control of the system and at the very least force a panic rather than pushing the big red button. A very quick (but not entirely correct) way to short-circuit the loop is to test tp->t_state and tiflags before unconditionally branching to dropafterack; if we are in the TCPS_SYN_RECEIVED and tiflags has something other than the TH_ACK bit set, then we should not jump directly to dropafterack. This traps the case where the SYN,ACK is received while in the SYN_RECEIVED case (which is bogus) and stops the loop condition. This is not the completely right thing to do though because there's another case where it won't help. Say for a moment that an attacker has a way to predict the initial sequence number chosen by the 'victim machine' and uses that in his forged 'loopback SYN' segment. If this happens, the connection will get all the way to the ESTABLISHED state, and the process listen()ing on the socket will end up talking to itself. I tested this on my home machine by hacking tcp_input() to choose a predictable sequence number and them modifying the 'loopback SYN' program to use the same ISS that the kernel would. This time, the reflected SYN,ACK that comes back to tcp_input() appears to be inside the expected window, so it avoids the branch to dropafterack. Instead, the connection becomes ESTABLISHED and the server processes becomes schitzophrenic and talks to itself. Whether or not this problem affects a given system depends (obviously) on the TCP input processing. Some systems that are BSD-derived had made changes that affect SYN processing (like deflecting SYN floods for instance) which also happen to provide protection from this attack. Non-BSD systems like Solaris 2.x may perform more stringent checks, or do the state processing and window checking in a different order which allows them to spot the bogus segment before it does any damage. -Bill -- ============================================================================= -Bill Paul (212) 854-6020 | System Manager, Master of Unix-Fu Work: wpaulat_private | Center for Telecommunications Research Home: wpaulat_private | Columbia University, New York City ============================================================================= "Now, that's "Open" as used in the sentence "Open your wallet", right?" =============================================================================
This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 13:34:09 PDT