home *** CD-ROM | disk | FTP | other *** search
/ DEFCON 12 / DEFCON_12_CD-ROM_2004.iso / Holz-Dornseif-Klein / NoSEBrEaKer.py < prev   
Text File  |  2004-07-07  |  11KB  |  319 lines

  1. #!/usr/bin/python
  2.  
  3. """NoSEBrEaKer.py - Python 2.3 demostration of Sebek detection and
  4. extraction of Sebeks internal data. See
  5. http://md.hudora.de/publications/index.html#NoSEBrEaK for further
  6. information."""
  7.  
  8. __rcsid__ = "$Id$"
  9.  
  10. # NoSEBrEaKer.py by Maximillian Dornseif, Thorsten Holz & Christian Klein spring 2004
  11. # proof of concept code - do not use.
  12. # kudos to madsys for module_hunter.c
  13.  
  14. import struct, socket
  15.  
  16.  
  17. debug = False
  18. symbolTable = {}
  19.  
  20. def readSymbolTable():
  21.     """Read the kernel symbol table and keep it in a global variable named symbolTable."""
  22.     global symbolTable 
  23.     
  24.     fd = open("/proc/ksyms", "r")
  25.     for x in fd:
  26.         x = x.split()
  27.         addr = long(x[0], 16)
  28.         symbol = x[1]
  29.         symbolTable[symbol] = addr
  30.     fd.close()
  31.  
  32.  
  33. def getSymbol(symbol):
  34.     """Get a symbol from the symbol table."""
  35.     return symbolTable[symbol]
  36.  
  37.  
  38. def readKmem(addr, length):
  39.     """Read 'length' bytes from kernel memory at 'addr'."""
  40.     fd = open("/dev/kmem", "r")
  41.     fd.seek(addr)
  42.     return fd.read(length)
  43.  
  44.  
  45. def readKmemString(addr):
  46.     """Read from kmem at 'addr' until \0 is found."""
  47.     
  48.     fd = open("/dev/kmem", "r")
  49.     fd.seek(addr)
  50.     out = []
  51.     while 1:
  52.         out.append(fd.read(1))
  53.         if not out[-1] or out[-1] == '\0':
  54.             out = out[:-1]
  55.             break
  56.         if len(out) > 512:
  57.             out.append(" ...")
  58.             break
  59.     return "".join(out)
  60.  
  61.  
  62. def getSymbolUint(symbol):
  63.     addr = getSymbol(symbol)
  64.     data = readKmem(addr, 4)
  65.     return struct.unpack("I", data)[0]
  66.  
  67.  
  68. def validKernelAddr(addr):
  69.     """Checks if a number represents a valid address in the kernel"""
  70.     if not addr:
  71.         return None
  72.  
  73.     if addr < VMALLOC_START:
  74.         return None
  75.  
  76.     return True
  77.  
  78.     # possible more elaborate code from module_hunter.c:
  79.     """
  80.     page = ((unsigned long *)0xc0101000)[address >> 22];        
  81.     
  82.     if (page & 1)
  83.     {
  84.         page &= PAGE_MASK;
  85.         address &= 0x003ff000;
  86.         page = ((unsigned long *) __va(page))[address >> PAGE_SHIFT];  //pte
  87.         if (page)
  88.     return 1;
  89.     }                                                                                            
  90.     return None
  91.     """
  92.  
  93. def parseModule(data, addr = 0):
  94.     """Try to parse a chunk of memory as an module"""
  95.  
  96.     """struct module {
  97.     unsigned long size_of_struct;   /* == sizeof(module) == 96 on my machine */
  98.     struct module *next;
  99.     const char *name;
  100.     unsigned long size;
  101.     union {
  102.     atomic_t usecount;
  103.     long pad;
  104.     } uc;                           /* Needs to keep its size - so says rth */
  105.     unsigned long flags;            /* AUTOCLEAN et al */
  106.     unsigned nsyms;
  107.     unsigned ndeps;
  108.     struct module_symbol *syms;
  109.     struct module_ref *deps;
  110.     struct module_ref *refs;
  111.     int (*init)(void);
  112.     void (*cleanup)(void);
  113.     """
  114.  
  115.     # unpack possible module header
  116.     size_of_struct, nextm, nameaddr, size, usecount, flags, \
  117.                     nsymsm, ndeps, syms, deps, refs, init, cleanup \
  118.                     = struct.unpack("LIILlLIIIIIII", data[:52])
  119.  
  120.     if size == 0:
  121.         if debug:
  122.             print '"size == 0"'
  123.         return None
  124.     # check if data is valid
  125.     if size_of_struct > 256 or size_of_struct < 52:
  126.         # on my system size_of_struct == 96
  127.         if debug:
  128.             print '"size_of_struct > 256 or size_of_struct < 52"'
  129.         return None
  130.     #if size_of_struct != 96:
  131.     #    return None
  132.     if size > 1024*1024:
  133.         if debug:
  134.             print '"size > 1024*1024"'
  135.         # there should be no kernel modules bigger than 1M
  136.         return None
  137.     if (size_of_struct & nextm & nameaddr & size) == 0xffffffffL:
  138.         if debug:
  139.             print '"(size_of_struct & nextm & nameaddr & size) == 0xffffffffL"'
  140.         return None
  141.     if not (validKernelAddr(nextm) or (nextm == 0)):
  142.         if debug:
  143.             print '"not (validKernelAddr(nextm) or (nextm == 0))"'
  144.         return None
  145.     if not (validKernelAddr(nameaddr)):
  146.         if debug:
  147.             print '"not (validKernelAddr(nameaddr))"'
  148.         return None
  149.  
  150.     name = readKmemString(nameaddr)
  151.  
  152.     # check for sebek
  153.     # we also could look for the string which is in sebek " face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed\n"
  154.  
  155.     # usualy sebec has a numeric only name. we might want to check for that
  156.     #if not name.isdigit():
  157.     #    if debug:
  158.     #        print 'module name is not strictly numeric'
  159.     #    return
  160.  
  161.     if usecount == 0 and nsymsm == 0 and syms == 0 and refs == 0:
  162.         if debug:
  163.             print
  164.         print "*** suspected sebek module at %x named %r, size %d bytes" % (addr, name, size)
  165.  
  166.         # Guess Sebek Variables.
  167.         
  168.         # The Sebeck 2.1.7 data block can be up to 256 ints (1024 bytes) big.
  169.         # We assume it starts minimum 320 bytes after the module name and
  170.         # maximum 400 bytes. Add more fuzz to this values for other versions.
  171.         minimumblockoffset = 320
  172.         intstoread = 256 + 20
  173.         block = struct.unpack("I" * intstoread, readKmem(nameaddr+320, intstoread*4))
  174.         ports = []
  175.         macs = []
  176.         ips = []
  177.         magics = []
  178.         for x in block:
  179.             if x != 0:
  180.                 if x <= 0xff:
  181.                     macs.append(int(x))
  182.                 if x <= 0xffff and x > 0xff:
  183.                     ports.append(int(x))
  184.                 if x > 0xffff:
  185.                     ips.append(x)
  186.                     magics.append(x)
  187.         ports.sort()
  188.         macs.sort()
  189.         ips.sort()
  190.         magics.sort()
  191.         ips = [socket.inet_ntoa(struct.pack("I", x)) for x in ips]
  192.         ips.sort()
  193.  
  194.         print "* possible mac fragments: %s" % ", ".join([hex(x).strip("L")[2:] for x in macs])
  195.         print "* possible ports: %s" % ", ".join([str(x) for x in ports])
  196.         print "              or: %s" % ", ".join([str(x) for x in macs])
  197.         print "* possibles ips:",
  198.         for ip in  ips:
  199.             if not ip.startswith("0.") and not ip.endswith(".0") \
  200.                    and int(ip.split('.')[0]) < 224 :
  201.                 print "%s," % ip,
  202.         print "\n*           or:",
  203.         for ip in ips:
  204.             if not ip.startswith("0.") and ip.endswith(".0") \
  205.                    and int(ip.split('.')[0]) < 224 :
  206.                 print "%s," % ip,
  207.         print
  208.         print "* possible magic values: %s" % ", ".join([hex(x).strip("L")[2:] for x in magics])
  209.         print "*                    or: %s" % ", ".join([str(x) for x in ports])
  210.         print "*                    or: %s" % ", ".join([str(x) for x in macs]) 
  211.         print "* init() = %x ; cleanup() = %x" % (init, cleanup)
  212.         #print size_of_struct, hex(nextm), hex(nameaddr), size, usecount, flags, \
  213.         #    nsymsm, ndeps, hex(syms), hex(deps), refs, hex(init), hex(cleanup)
  214.         if sys_write > addr and sys_write < addr + size: 
  215.             print "* sys_write is hijacked into a function in this module!"
  216.         return True
  217.  
  218. def getSyscalltable():
  219.     """Find the syscall table."""
  220.  
  221.     # The Linux folks have decided in their wisdom to remove the
  222.     # kernel symbol pointing to the syscall table in newer linux
  223.     # versions. So be have to reconstruct the adresst by searching
  224.     # memory between two known symbols for something which looks like
  225.     # the syscall_table
  226.     
  227.     global sys_read, sys_write, sys_open
  228.  
  229.     # this symbols are reconstructed above
  230.     distance = boot_cpu_data_addr - loops_per_jiffy_addr
  231.     print "loops_per_jiffy = %x, boot_cpu_data = %x, distance = %x" % (loops_per_jiffy_addr, \
  232.                                                                        boot_cpu_data_addr, distance)
  233.     # this kernel might actually have a symmbol for the syscall_table
  234.     try:
  235.         sys_call_table_addr = getSymbol("sys_call_table")
  236.         print "sys_call_table claimed to be at %x, verifying ..." % sys_call_table_addr
  237.     except:
  238.         pass
  239.  
  240.     # loop over the memory between loops_per_jiffy_addr and
  241.     # boot_cpu_data_addr and check if something looks like the syscall
  242.     # table.
  243.     data = readKmem(loops_per_jiffy_addr, distance)
  244.     p = 0
  245.     while 1:
  246.         p += 2
  247.         if p+4 > distance:
  248.             raise RuntimeError, "syscall table not found"
  249.         # if this looks like there is a valid pointer to sys_close in
  250.         # the assumed syscall_table we go for it and assume that this
  251.         # is really the syscall table.
  252.         a = struct.unpack("I", data[p + (NR_close * 4):p + (NR_close * 4) + 4])[0]
  253.         if a == sys_close_addr:
  254.             print "sys_call_table found at %x" % (loops_per_jiffy_addr + p)
  255.             break  
  256.  
  257.     # now read the whole table and set certain variables to the corrospondending syscalls
  258.     sys_call_table = readKmem(getSymbol("sys_call_table"), 256*4)
  259.     sys_read = struct.unpack("I", sys_call_table[((NR_read) * 4):((NR_read) * 4) + 4])[0]
  260.     sys_write = struct.unpack("I", sys_call_table[((NR_write) * 4):((NR_write) * 4) + 4])[0]    
  261.     sys_open = struct.unpack("I", sys_call_table[((NR_open) * 4):((NR_open) * 4) + 4])[0]
  262.     print "sys_read = %x sys_write = %x sys_open = %x" % (sys_read, sys_write, sys_open)
  263.     if abs(sys_read - sys_write) > 4096:
  264.         print "syscalls are far apart ... probably sys_write is hijacked"
  265.  
  266.  
  267. # indices in the syscall table
  268. NR_read = 3
  269. NR_write = 4
  270. NR_open = 5
  271. NR_close = 6
  272.  
  273.  
  274. def main():
  275.     global high_memory, loops_per_jiffy_addr, boot_cpu_data_addr, sys_close_addr
  276.     global VMALLOC_OFFSET, VMALLOC_START, VMALLOC_RESERVE
  277.  
  278.     print "reading kernel symmbol table ...",
  279.     readSymbolTable()
  280.     print "done"
  281.  
  282.     print "resolving variables:",
  283.     high_memory = getSymbolUint("high_memory")
  284.     print "high_memory",
  285.     
  286.     loops_per_jiffy_addr = getSymbol("loops_per_jiffy")
  287.     print "loops_per_jiffy_addr",
  288.     
  289.     boot_cpu_data_addr = getSymbol("boot_cpu_data")
  290.     print "boot_cpu_data",
  291.     
  292.     sys_close_addr = getSymbol("sys_close")
  293.     print "sys_close"
  294.     
  295.     VMALLOC_OFFSET  = 8 * 1024 * 1024
  296.     VMALLOC_START   = (high_memory + (2 * VMALLOC_OFFSET) - 1) & (~(VMALLOC_OFFSET - 1))
  297.     VMALLOC_RESERVE = 128 << 20
  298.     PAGE_SHIFT      = 12L
  299.     PAGE_SIZE       = 1L << PAGE_SHIFT
  300.  
  301.     getSyscalltable()
  302.  
  303.     # Check all possiblelocations where vmalloc could have reserved
  304.     # space for the Sebek module. The assumptions this is based on
  305.     # might be wrong for non-Intel architectures or Linux with BIGMEM.
  306.     
  307.     p = VMALLOC_START
  308.     while(p <= VMALLOC_START + VMALLOC_RESERVE - PAGE_SIZE):
  309.         p = p + PAGE_SIZE
  310.         data = readKmem(p, 4096)
  311.         if data:
  312.             # try to pase the data as an module header.
  313.             if debug:
  314.                 print 'checking vmalloc %x' % p,
  315.             if parseModule(data, p):
  316.                 break
  317.  
  318. main()
  319.