shithub: libdvdcss

Download patch

ref: 9d7698f671f51ec94efbce21040c37d7eef88e61
parent: ce5ffc6fe913e0720a7c8fed716990a5af4ee4c3
author: Håkan Hjort <d95hjort@dtek.chalmers.se>
date: Sat Mar 9 12:57:53 EST 2002

Split the code into more functions. The disc and title decryption /
cracking code is now each in it's own function.  Also moved the code that
looks for a weak block in the VOB to crack the title key from out from
the loop that reads them.  Added another crack method, it's disabled for
now though.  Lowered some limits on the current 'weak' test.  Added more
comments.


--- a/src/css.c
+++ b/src/css.c
@@ -2,7 +2,7 @@
  * css.c: Functions for DVD authentification and unscrambling
  *****************************************************************************
  * Copyright (C) 1999-2001 VideoLAN
- * $Id: css.c,v 1.3 2002/02/25 18:21:57 hjort Exp $
+ * $Id: css.c,v 1.4 2002/03/09 17:57:53 hjort Exp $
  *
  * Author: St�phane Borel <stef@via.ecp.fr>
  *         H�kan Hjort <d95hjort@dtek.chalmers.se>
@@ -47,22 +47,26 @@
 #include "csstables.h"
 #include "ioctl.h"
 
-#ifdef HAVE_CSSKEYS
-#   include "csskeys.h"
-#endif
-
 /*****************************************************************************
  * Local prototypes
  *****************************************************************************/
-static int  CSSAuth      ( dvdcss_handle dvdcss );
+static int  CSSGetBusKey ( dvdcss_handle dvdcss );
 static int  CSSGetASF    ( dvdcss_handle dvdcss );
 static void CSSCryptKey  ( int i_key_type, int i_varient,
                            u8 const *p_challenge, u8 *p_key );
-static void CSSDecryptKey( u8 *p_crypted, u8 const *p_key, u8 );
+static void CSSDecryptKey( u8 invert, u8 const *p_key, u8 const *p_crypted, 
+                           u8 *p_result );
+static int  CSSDecryptDiscKey ( u8 const *p_struct_disckey,
+                                dvd_key_t p_disc_key );
+static void CSSDecryptTitleKey( dvd_key_t p_disc_key, dvd_key_t p_titlekey );
 static int  CSSDiscCrack ( dvdcss_handle dvdcss, u8 *p_disc_key );
-static int  CSSTitleCrack( int i_start, unsigned char *p_crypted,
-                           unsigned char *p_decrypted,
-                           u8 *p_sector_key, u8 *p_key );
+static int  CSSRecoverKey( int i_start, 
+                           u8 const *p_crypted, u8 const *p_decrypted, 
+                           u8 const *p_sector_seed, u8 *p_key );
+static int  CSSTitleCrack( dvdcss_handle dvdcss, int i_pos, int i_len, 
+                           dvd_key_t p_titlekey );
+static int  CSSAttackPattern( u8 const p_sec[0x800], int i_pos, u8 *p_key );
+static int  CSSAttackPadding( u8 const p_sec[0x800], int i_pos, u8 *p_key );
 
 /*****************************************************************************
  * CSSTest : check if the disc is encrypted or not
@@ -87,7 +91,7 @@
 }
 
 /*****************************************************************************
- * CSSAuth : DVD CSS authentication.
+ * CSSGetBusKey : Go through the CSS Authentication process
  *****************************************************************************
  * It simulates the mutual authentication between logical unit and host,
  * and stops when a session key (called bus key) has been established.
@@ -96,7 +100,7 @@
  * that ASF=1 from the start and then later fail with a 'read of scrambled 
  * block without authentication' error.
  *****************************************************************************/
-static int CSSAuth( dvdcss_handle dvdcss )
+static int CSSGetBusKey( dvdcss_handle dvdcss )
 {
     u8        p_buffer[10];
     u8        p_challenge[2*KEY_SIZE];
@@ -238,7 +242,8 @@
 
 /*****************************************************************************
  * CSSPrintKey : debug function that dumps a key value 
- *****************************************************************************/static void CSSPrintKey( dvdcss_handle dvdcss, u8 *data )
+ *****************************************************************************/
+static void CSSPrintKey( dvdcss_handle dvdcss, u8 const *data )
 {
     char psz_output[80];
 
@@ -248,23 +253,22 @@
 }
 
 /*****************************************************************************
- * CSSGetDiscKey : get disc key and optionnaly decrypts it.
+ * CSSGetDiscKey : get disc key.
  *****************************************************************************
  * This function should only be called if DVD ioctls are present.
- * Two decryption methods are then offered:
+ * It will set dvdcss->i_method = DVDCSS_METHOD_TITLE if it fails to find 
+ * a valid disc key.
+ * Two decryption methods are offered:
  *  -disc key hash crack,
  *  -decryption with player keys if they are available.
  *****************************************************************************/
 int CSSGetDiscKey( dvdcss_handle dvdcss )
 {
-    unsigned char   p_buffer[2048];
-#ifdef HAVE_CSSKEYS
-    dvd_key_t       disc_key;
-    dvd_key_t       test_key;
-#endif
+    unsigned char p_buffer[2048];
+    dvd_key_t p_disc_key;
     int i;
 
-    if( CSSAuth( dvdcss ) )
+    if( CSSGetBusKey( dvdcss ) < 0)
     {
         return -1;
     }
@@ -276,6 +280,15 @@
         return -1;
     }
 
+    /* This should have invaidated the AGID and got us ASF=1. */
+    if( CSSGetASF( dvdcss ) != 1 )
+    {
+        /* Region mismatch (or region not set) is the most likely source. */  
+        _dvdcss_error( dvdcss, "ASF not 1 after reading disc key" );
+        ioctl_InvalidateAgid( dvdcss->i_fd, &dvdcss->css.i_agid );
+        return -1;
+    }
+
     /* Unencrypt disc key using bus key */
     for( i = 0 ; i < 2048 ; i++ )
     {
@@ -285,46 +298,37 @@
     switch( dvdcss->i_method )
     {
         case DVDCSS_METHOD_KEY:
-#ifdef HAVE_CSSKEYS
-            /* Decrypt disc key with player keys from csskeys.h */
+            /* Decrypt disc key with player key. */
             _dvdcss_debug( dvdcss, "decrypting disc key with player keys" );
-            i = 0;
-            do
+            if( ! CSSDecryptDiscKey( p_buffer, p_disc_key ) )
             {
-                /* Take encrypted disc key and decrypt it */
-                memcpy( disc_key,
-                        p_buffer + playerkeys[i].i_offset,
-                        KEY_SIZE );
-                CSSDecryptKey( disc_key, playerkeys[i].p_key, 0 );
-
-                /* Encrypt disc key hash with disc key to
-                 * check we have disc key */
-                memcpy( test_key, p_buffer, KEY_SIZE );
-                CSSDecryptKey( test_key, disc_key, 0);
-
-                i++;
-
-            } while( ( playerkeys[i].i_offset != -1 ) &&
-                     ( memcmp( test_key, disc_key, KEY_SIZE ) ) );
-
-            /* Store decypted disk for use when decrypting title keys */
-            memcpy( dvdcss->css.p_disc_key, disc_key, KEY_SIZE );
-            break;
+                break;
+            }
+#ifdef WITHOUT_CSSKEYS
+            _dvdcss_debug( dvdcss, "player keys not compiled in" );
+            dvdcss->i_method = DVDCSS_METHOD_DISC;
 #else
-            dvdcss->i_method = DVDCSS_METHOD_DISC;            
+            _dvdcss_debug( dvdcss, "no valid player key" );
+            /* Fall through */
 #endif
         case DVDCSS_METHOD_DISC:
             /* Crack Disc key to be able to use it */
-            _dvdcss_debug( dvdcss, "cracking disc key with key hash" );
+            _dvdcss_debug( dvdcss, "cracking disc key from key hash" );
             _dvdcss_debug( dvdcss, "building 64MB table ... this will take some time" );
-            CSSDiscCrack( dvdcss, p_buffer );
-            memcpy( dvdcss->css.p_disc_key, p_buffer, KEY_SIZE );
+            memcpy( p_disc_key, p_buffer, KEY_SIZE );
+            if( CSSDiscCrack( dvdcss, p_disc_key ) )
+            {
+                _dvdcss_debug( dvdcss, "failed cracking disc key" );
+                dvdcss->i_method = DVDCSS_METHOD_TITLE;
+            }
             break;
 
         default:
             _dvdcss_debug( dvdcss, "disc key won't be decrypted" );
+	    memset( p_disc_key, 0, KEY_SIZE );
     }
 
+    memcpy( dvdcss->css.p_disc_key, p_disc_key, KEY_SIZE );
     CSSPrintKey( dvdcss, dvdcss->css.p_disc_key );
     return 0;
 }
@@ -333,113 +337,17 @@
 /*****************************************************************************
  * CSSGetTitleKey : get title key.
  *****************************************************************************/
-int CSSGetTitleKey( dvdcss_handle dvdcss, int i_pos )
+int CSSGetTitleKey( dvdcss_handle dvdcss, int i_pos, dvd_key_t p_title_key )
 {
-    dvd_key_t   p_key;
-    int         i,j;
+    u8  p_key[KEY_SIZE];
+    int i, i_ret = 0;
 
     if( ( dvdcss->i_method == DVDCSS_METHOD_TITLE )
         || ( dvdcss->b_ioctls == 0 ) )
     {
-        /*
-         * Title key cracking method from Ethan Hawke,
-         * with Frank A. Stevenson algorithm.
-         * Does not use any player key table and ioctls.
-         */
-        u8          p_buf[0x800];
-        u8          p_packstart[4] = { 0x00, 0x00, 0x01, 0xba };
-        boolean_t   b_encrypted;
-        boolean_t   b_stop_scanning;
-        int         i_blocks_read;
-        int         i_best_plen;
-        int         i_best_p;
-
-        _dvdcss_debug( dvdcss, "cracking title key ... this may take some time" );
-
-        for( i = 0 ; i < KEY_SIZE ; i++ )
-        {
-            p_key[i] = 0;
-        }
-
-        b_encrypted = 0;
-        b_stop_scanning = 0;
-        i_blocks_read = 0;
-
-        do
-        {
-            i_pos = _dvdcss_seek( dvdcss, i_pos );
-            if( _dvdcss_read( dvdcss, p_buf, 1 ) != 1 ) break;
-
-            /* Stop when we find a non MPEG stream block */
-            if( memcmp( p_buf, p_packstart, 4 ) )
-            {
-                /* The title is unencrypted */
-                if( !b_encrypted )
-                    break;
-                /* dvdcss some times fail to find/crack the key, 
-                   hope that it's the same as the one in the next title
-                   _dvdcss_debug( dvdcss, "no key found at end of title" );
-                */
-            }
-
-            /* PES_scrambling_control on and make sure that the packet type 
-               is one that can be scrambled */
-            if( p_buf[0x14] & 0x30  && ! ( p_buf[0x11] == 0xbb 
-                                           || p_buf[0x11] == 0xbe  
-                                           || p_buf[0x11] == 0xbf ) )
-            {
-                b_encrypted = 1;
-                i_best_plen = 0;
-                i_best_p = 0;
-
-                for( i = 2 ; i < 0x30 ; i++ )
-                {
-                    for( j = i+1 ;
-                         j < 0x80 && ( p_buf[0x7F - (j%i)] == p_buf[0x7F-j] );
-                         j++ );
-                    {
-                        if( j > i_best_plen )
-                        {
-                            i_best_plen = j;
-                            i_best_p = i;
-                        }
-                    }
-                }
-
-                if( ( i_best_plen > 20 ) && ( i_best_plen / i_best_p >= 2) )
-                {
-                    i = CSSTitleCrack( 0,  &p_buf[0x80],
-                            &p_buf[0x80 - ( i_best_plen / i_best_p) *i_best_p],
-                            &p_buf[0x54],
-                            p_key );
-                    b_stop_scanning = ( i >= 0 );
-                }
-            }
-
-            i_pos += 1;
-            i_blocks_read += 1;
-
-            /* If we haven't seen any encrypted ones after 3000 blocks stop */
-            if( !b_encrypted && i_blocks_read >= 1000 ) break;
-
-        } while( !b_stop_scanning );
-
-        if( b_stop_scanning )
-        {
-            memcpy( dvdcss->css.p_title_key, &p_key, sizeof(dvd_key_t) );
-            CSSPrintKey( dvdcss, dvdcss->css.p_title_key );
-            _dvdcss_debug( dvdcss, "vts key initialized" );
-            return 0;
-        }
-
-        if( !b_encrypted )
-        {
-            _dvdcss_debug( dvdcss, "file was unscrambled" );
-            return 0;
-        }
-
-        return -1;
-    }
+        /* For now, the limit is 9Gb / 2048 =  4718592 sectors. */
+        i_ret = CSSTitleCrack( dvdcss, i_pos, 4718592, p_key);
+    }
     else
     {
         /* 
@@ -449,9 +357,11 @@
 
         _dvdcss_debug( dvdcss, "decrypting title key with disc key" );
 
-        /* We need to authenticate again for every title key, 
-	 * a new bus key is used each time. */
-        CSSAuth( dvdcss );
+        /* We need to authenticate again every time to get a new session key */
+        if( CSSGetBusKey( dvdcss ) < 0 )
+        {
+            return -1;
+        }
 
         /* Get encrypted title key */
         if( ioctl_ReadTitleKey( dvdcss->i_fd, &dvdcss->css.i_agid,
@@ -458,9 +368,35 @@
                                 i_pos, p_key ) < 0 )
         {
             _dvdcss_error( dvdcss, "ioctl_ReadTitleKey failed" );
-            return -1;
+            i_ret = -1;
         }
-        /* Unencrypt title key using bus key */
+
+        /* Test ASF, it will be reset to 0 if we got a Region error */
+        switch( CSSGetASF( dvdcss ) )
+        {
+            case -1:
+                /* An error getting the ASF status, something must be wrong. */
+                // ioctl_InvalidateAgid( dvdcss->i_fd, &dvdcss->css.i_agid );
+                return -1;
+
+            case 0:
+                /* This might either be an title that has no key, 
+                 * or we encountered a region error. */
+                _dvdcss_debug( dvdcss, "Lost ASF reqesting Title key" );
+                return i_ret;
+
+            case 1:
+                /* Drive status is ok, check if we got the title key. */
+                if( i_ret )
+                {
+                    /* The request failed but we didn't lose ASF. */
+                  //ioctl_InvalidateAgid( dvdcss->i_fd, &dvdcss->css.i_agid );
+                    return i_ret;
+                }
+                break;
+        }
+
+        /* Unencrypt title key using the bus key */
         for( i = 0 ; i < KEY_SIZE ; i++ )
         {
             p_key[ i ] ^= dvdcss->css.p_bus_key[ 4 - (i % KEY_SIZE) ];
@@ -467,22 +403,22 @@
         }
 
         /* If p_key is all zero then there realy wasn't any key pressent
-	 * even though we got to read it without an error. */
+         * even though we got to read it without an error. */
         if( !( p_key[0] | p_key[1] | p_key[2] | p_key[3] | p_key[4] ) )
         {
-            memset( dvdcss->css.p_title_key, 0, sizeof(dvd_key_t) );
-            CSSPrintKey( dvdcss, dvdcss->css.p_title_key );
-            return 0;
+            i_ret = 0;
         }
+        else
+        {
+            CSSDecryptTitleKey( dvdcss->css.p_disc_key, p_key );
+            i_ret = 1;
+        }
+    }
 
-        /* Title key decryption needs one inversion 0xff */
-        CSSDecryptKey( p_key, dvdcss->css.p_disc_key, 0xff );
+    memcpy( p_title_key, p_key, KEY_SIZE );
+    CSSPrintKey( dvdcss, p_title_key );
 
-        memcpy( dvdcss->css.p_title_key, p_key, sizeof(dvd_key_t) );
-        CSSPrintKey( dvdcss, dvdcss->css.p_title_key );
-
-        return 0;
-    } // (dvdcss->i_method == DVDCSS_METHOD_TITLE) || (dvdcss->b_ioctls == 0)
+    return i_ret;
 }
 
 /*****************************************************************************
@@ -494,7 +430,7 @@
 int CSSDescrambleSector( dvd_key_t p_key, u8 *p_sec )
 {
     unsigned int    i_t1, i_t2, i_t3, i_t4, i_t5, i_t6;
-    u8 *            p_end = p_sec + 0x800;
+    u8             *p_end = p_sec + 0x800;
 
     /* PES_scrambling_control */
     if( p_sec[0x14] & 0x30)
@@ -565,7 +501,7 @@
 /*****************************************************************************
  * CSSCryptKey : shuffles bits and unencrypt keys.
  *****************************************************************************
- * Used during authentication and disc key negociation in CSSAuth.
+ * Used during authentication and disc key negociation in CSSGetBusKey.
  * i_key_type : 0->key1, 1->key2, 2->buskey.
  * i_varient : between 0 and 31.
  *****************************************************************************/
@@ -772,6 +708,9 @@
 /*****************************************************************************
  * CSSDecryptKey: decrypt p_crypted with p_key.
  *****************************************************************************
+ * Used to decrypt the disc key, with a player key, after requesting it 
+ * in CSSGetDiscKey and to decrypt title keys, with a disc key, requested 
+ * in CSSGetTitleKey.
  * The player keys and the resulting disc key are only used as KEKs 
  * (key encryption keys).
  * Decryption is slightly dependant on the type of key:
@@ -778,15 +717,16 @@
  *  -for disc key, invert is 0x00,
  *  -for title key, invert if 0xff. 
  *****************************************************************************/
-static void CSSDecryptKey( u8 *p_crypted, u8 const *p_key, u8 invert )
+static void CSSDecryptKey( u8 invert, u8 const *p_key, 
+                           u8 const *p_crypted, u8 *p_result )
 {
     unsigned int    i_lfsr1_lo;
     unsigned int    i_lfsr1_hi;
     unsigned int    i_lfsr0;
     unsigned int    i_combined;
-    byte_t          o_lfsr0;
-    byte_t          o_lfsr1;
-    byte_t          k[5];
+    u8              o_lfsr0;
+    u8              o_lfsr1;
+    u8              k[5];
     int             i;
 
     i_lfsr1_lo = p_key[0] | 0x100;
@@ -818,22 +758,115 @@
         i_combined >>= 8;
     }
 
-    p_crypted[4] = k[4] ^ p_css_tab1[p_crypted[4]] ^ p_crypted[3];
-    p_crypted[3] = k[3] ^ p_css_tab1[p_crypted[3]] ^ p_crypted[2];
-    p_crypted[2] = k[2] ^ p_css_tab1[p_crypted[2]] ^ p_crypted[1];
-    p_crypted[1] = k[1] ^ p_css_tab1[p_crypted[1]] ^ p_crypted[0];
-    p_crypted[0] = k[0] ^ p_css_tab1[p_crypted[0]] ^ p_crypted[4];
+    p_result[4] = k[4] ^ p_css_tab1[p_crypted[4]] ^ p_crypted[3];
+    p_result[3] = k[3] ^ p_css_tab1[p_crypted[3]] ^ p_crypted[2];
+    p_result[2] = k[2] ^ p_css_tab1[p_crypted[2]] ^ p_crypted[1];
+    p_result[1] = k[1] ^ p_css_tab1[p_crypted[1]] ^ p_crypted[0];
+    p_result[0] = k[0] ^ p_css_tab1[p_crypted[0]] ^ p_result[4];
 
-    p_crypted[4] = k[4] ^ p_css_tab1[p_crypted[4]] ^ p_crypted[3];
-    p_crypted[3] = k[3] ^ p_css_tab1[p_crypted[3]] ^ p_crypted[2];
-    p_crypted[2] = k[2] ^ p_css_tab1[p_crypted[2]] ^ p_crypted[1];
-    p_crypted[1] = k[1] ^ p_css_tab1[p_crypted[1]] ^ p_crypted[0];
-    p_crypted[0] = k[0] ^ p_css_tab1[p_crypted[0]];
+    p_result[4] = k[4] ^ p_css_tab1[p_result[4]] ^ p_result[3];
+    p_result[3] = k[3] ^ p_css_tab1[p_result[3]] ^ p_result[2];
+    p_result[2] = k[2] ^ p_css_tab1[p_result[2]] ^ p_result[1];
+    p_result[1] = k[1] ^ p_css_tab1[p_result[1]] ^ p_result[0];
+    p_result[0] = k[0] ^ p_css_tab1[p_result[0]];
 
     return;
 }
 
+static const dvd_key_t player_keys[] = 
+{
+#ifdef WITHOUT_CSSKEYS
+    { 0x00, 0x00, 0x00, 0x00, 0x00 }
+#else
+    { 0x01, 0xaf, 0xe3, 0x12, 0x80 },
+    { 0x12, 0x11, 0xca, 0x04, 0x3b },
+    { 0x14, 0x0c, 0x9e, 0xd0, 0x09 },
+    { 0x14, 0x71, 0x35, 0xba, 0xe2 },
+    { 0x1a, 0xa4, 0x33, 0x21, 0xa6 },
+    { 0x26, 0xec, 0xc4, 0xa7, 0x4e },
+    { 0x2c, 0xb2, 0xc1, 0x09, 0xee },
+    { 0x2f, 0x25, 0x9e, 0x96, 0xdd },
+    { 0x33, 0x2f, 0x49, 0x6c, 0xe0 },
+    { 0x35, 0x5b, 0xc1, 0x31, 0x0f },
+    { 0x36, 0x67, 0xb2, 0xe3, 0x85 },
+    { 0x39, 0x3d, 0xf1, 0xf1, 0xbd },
+    { 0x3b, 0x31, 0x34, 0x0d, 0x91 },
+    { 0x45, 0xed, 0x28, 0xeb, 0xd3 },
+    { 0x48, 0xb7, 0x6c, 0xce, 0x69 },
+    { 0x4b, 0x65, 0x0d, 0xc1, 0xee },
+    { 0x4c, 0xbb, 0xf5, 0x5b, 0x23 },
+    { 0x51, 0x67, 0x67, 0xc5, 0xe0 },
+    { 0x53, 0x94, 0xe1, 0x75, 0xbf },
+    { 0x57, 0x2c, 0x8b, 0x31, 0xae },
+    { 0x63, 0xdb, 0x4c, 0x5b, 0x4a },
+    { 0x7b, 0x1e, 0x5e, 0x2b, 0x57 },
+    { 0x85, 0xf3, 0x85, 0xa0, 0xe0 },
+    { 0xab, 0x1e, 0xe7, 0x7b, 0x72 },
+    { 0xab, 0x36, 0xe3, 0xeb, 0x76 },
+    { 0xb1, 0xb8, 0xf9, 0x38, 0x03 },
+    { 0xb8, 0x5d, 0xd8, 0x53, 0xbd },
+    { 0xbf, 0x92, 0xc3, 0xb0, 0xe2 },
+    { 0xcf, 0x1a, 0xb2, 0xf8, 0x0a },
+    { 0xec, 0xa0, 0xcf, 0xb3, 0xff },
+    { 0xfc, 0x95, 0xa9, 0x87, 0x35 }
+#endif
+};
+
 /*****************************************************************************
+ * CSSDecryptDiscKey
+ *****************************************************************************
+ * Decryption of the disc key with player keys if they are available.
+ * Try to decrypt the disc key from every position with every player key.
+ * p_struct_disckey: the 2048 byte DVD_STRUCT_DISCKEY data
+ * p_disc_key: result, the 5 byte disc key
+ *****************************************************************************/
+static int CSSDecryptDiscKey( u8 const *p_struct_disckey, 
+                              dvd_key_t p_disc_key )
+{
+    u8 p_verify[KEY_SIZE];
+    int i, n = 0;
+
+    /* Decrypt disc key with player keys from csskeys.h */
+    while( n < sizeof(player_keys) / sizeof(dvd_key_t) )
+    {
+        for( i = 1; i < 409; i++ )
+        {
+            /* Check if player key n is the right key for position i. */
+            CSSDecryptKey( 0, player_keys[n], p_struct_disckey + 5 * i,
+                           p_disc_key );
+
+            /* The first part in the struct_disckey block is the 
+             * 'disc key' encrypted with it self.  Using this we 
+             * can check if we decrypted the correct key. */
+            CSSDecryptKey( 0, p_disc_key, p_struct_disckey, p_verify );
+
+            /* If the position / player key pair worked then return. */
+            if( memcmp( p_disc_key, p_verify, 5 ) == 0 )
+            {
+                return 0;
+            }
+        }
+        n++;
+    }
+    
+    /* Have tried all combinations of positions and keys, 
+     * and we still didn't succeed. */
+    return -1;
+}
+
+/*****************************************************************************
+ * CSSDecryptTitleKey
+ *****************************************************************************
+ * Decrypt the title key using the disc key.
+ * p_disc_key: result, the 5 byte disc key
+ * p_titlekey: the encrypted title key, gets overwritten by the decrypted key
+ *****************************************************************************/
+static void CSSDecryptTitleKey( dvd_key_t p_disc_key, dvd_key_t p_titlekey )
+{
+    CSSDecryptKey( 0xff, p_disc_key, p_titlekey, p_titlekey );
+}
+
+/*****************************************************************************
  * CSSDiscCrack: brute force disc key
  * CSS hash reversal function designed by Frank Stevenson
  *****************************************************************************
@@ -847,15 +880,11 @@
  */
 static int investigate( unsigned char *hash, unsigned char *ckey )
 {
-    unsigned char key[5];
-    unsigned char pkey[5];
+    unsigned char key[KEY_SIZE];
 
-    memcpy( key, hash, 5 );
-    memcpy( pkey, ckey, 5 );
+    CSSDecryptKey( 0, ckey, hash, key);
 
-    CSSDecryptKey( key, pkey, 0 );
-
-    return memcmp( key, pkey, 5 );
+    return memcmp( key, ckey, KEY_SIZE );
 }
 
 static int CSSDiscCrack( dvdcss_handle dvdcss, u8 *p_disc_key )
@@ -930,12 +959,6 @@
 
     for( i = 0 ; i < 16777216 ; i++ )
     {
-/*
-        if( ( i & 0x07ffff ) == 0 )
-        {
-            fprintf( stderr, "#" );
-        }
-*/
         tmp = (( i + i ) & 0x1fffff0 ) | 0x8 | ( i & 0x7 );
 
         for( j = 0 ; j < 5 ; j++ )
@@ -950,8 +973,6 @@
         BigTable[j] = i;
     }
 
-/*    fprintf( stderr, "\n" ); */
-
     /*
      * We are done initing, now reverse hash
      */
@@ -1051,23 +1072,21 @@
     free( K1table );
     free( BigTable );
 
-    return( 0 );
+    return 0;
 }
 
 /*****************************************************************************
- * CSSTitleCrack : title key decryption by cracking
+ * CSSRecoverKey : (title) key recovery from chiper and plain text
  * Function designed by Frank Stevenson
  *****************************************************************************
- * This function is called by CSSGetTitleKey to find a title key, if we've
- * chosen to crack title key instead of decrypting it with the disc key.
+ * Called from CSSAttack* which are inturn called by CSSTitleCrack.  Given
+ * a guessed(?) plain text and the chiper text.  Returns -1 on failure.
  *****************************************************************************/
-static int CSSTitleCrack( int i_start,
-                          unsigned char *p_crypted,
-                          unsigned char *p_decrypted,
-                          u8 *p_sector_seed,
-                          u8 *p_key )
+static int CSSRecoverKey( int i_start,
+                          u8 const *p_crypted, u8 const *p_decrypted,
+                          u8 const *p_sector_seed, u8 *p_key )
 {
-    unsigned char p_buffer[10];
+    u8 p_buffer[10];
     unsigned int i_t1, i_t2, i_t3, i_t4, i_t5, i_t6;
     unsigned int i_try;
     unsigned int i_candidate;
@@ -1074,7 +1093,6 @@
     unsigned int i, j;
     int i_exit = -1;
 
-
     for( i = 0 ; i < 10 ; i++ )
     {
         p_buffer[i] = p_css_tab1[p_crypted[i]] ^ p_decrypted[i];
@@ -1185,3 +1203,265 @@
 
     return i_exit;
 }
+
+
+/******************************************************************************
+ * Various pices for the title crack engine.
+ ******************************************************************************
+ * The length of the PES packet is located at 0x12-0x13.
+ * The the copyrigth protection bits are located at 0x14 (bits 0x20 and 0x10).
+ * The data of the PES packet begins at 0x15 (if there isn't any PTS/DTS)
+ * or at 0x?? if there are both PTS and DTS's.
+ * The seed value used with the descrambler key is the 5 bytes at 0x54-0x58.
+ * The scrabled part of a sector begins at 0x80. 
+ *****************************************************************************/
+
+/* Statistics */
+static int i_tries = 0, i_success = 0;
+
+/*****************************************************************************
+ * CSSTitleCrack : try to crack title key from the contents of a VOB.
+ *****************************************************************************
+ * This function is called by CSSGetTitleKey to find a title key, if we've
+ * chosen to crack title key instead of decrypting it with the disc key.
+ * The DVD should have been opened and be in an authenticated state.
+ * i_pos is the starting sector, i_len is the maximum number of sectors to read
+ *****************************************************************************/
+static int CSSTitleCrack( dvdcss_handle dvdcss, int i_pos, int i_len, 
+                          dvd_key_t p_titlekey )
+{
+    u8       p_buf[0x800];
+    const u8 p_packstart[4] = { 0x00, 0x00, 0x01, 0xba };
+    int      i_reads = 0;
+    int      i_encrypted = 0;
+    int      b_stop_scanning = 0;
+    int      i_blocks_read;
+
+    i_tries = 0;
+    i_success = 0;
+
+    do
+    {
+        i_pos = _dvdcss_seek( dvdcss, i_pos );
+        i_blocks_read = dvdcss_read( dvdcss, p_buf, 1, DVDCSS_NOFLAGS );
+        
+        /* Either we are at the end of the physical device or the auth
+         * have faild / where not done and we got a read error. */
+        if( !i_blocks_read )
+        {
+            _dvdcss_debug( dvdcss, "read returned 0 (end of device?)" );
+            break;
+        }
+
+        /* Stop when we find a non MPEG stream block. 
+         * (We must have reached the end of the stream).
+         * For now, allow all blocks that begin with a start code. */
+        if( memcmp( p_buf, p_packstart, 3 ) )
+        {
+            _dvdcss_debug( dvdcss, "non MPEG block found (end of title)" );
+            break;
+        }
+
+        if( p_buf[0x0d] & 0x07 )
+            _dvdcss_debug( dvdcss, "stuffing in pack header" ); 
+
+        /* PES_scrambling_control does not exist in a system_header,
+         * a padding_stream or a private_stream2 (and others?). */
+        if( p_buf[0x14] & 0x30  && ! ( p_buf[0x11] == 0xbb 
+                                       || p_buf[0x11] == 0xbe  
+                                       || p_buf[0x11] == 0xbf ) )
+        {
+            i_encrypted++;
+
+            if( CSSAttackPattern(p_buf, i_reads, p_titlekey) > 0 )
+            {
+                b_stop_scanning = 1;
+            }
+#if 0
+            if( CSSAttackPadding(p_buf, i_reads, p_titlekey) > 0 )
+            {
+                b_stop_scanning = 1;
+            }
+#endif
+        }
+
+        i_pos += i_blocks_read;
+        i_len -= i_blocks_read;
+        i_reads += i_blocks_read;
+
+        /* Emit a progress indication now and then. */
+        if( !( i_reads & 0xfff ) ) 
+          _dvdcss_debug( dvdcss, "still working..." );
+
+        /* Stop after 2000 blocks if we haven't seen any encrypted blocks. */
+        if( i_reads >= 2000 && i_encrypted == 0 ) break;
+
+    } while( !b_stop_scanning && i_len > 0);
+
+    if( i_len <= 0 )
+        _dvdcss_debug( dvdcss, "end of title reached" );
+
+    { /* Print some statistics. */
+        char psz_info[128];
+        snprintf( psz_info, sizeof(psz_info), 
+                  "%d of %d attempts successful, %d of %d blocks scrambled", 
+                  i_success, i_tries, i_encrypted, i_reads );
+        _dvdcss_debug( dvdcss, psz_info );
+    }
+
+    if( i_success > 0 /* b_stop_scanning */ )
+    {
+        _dvdcss_debug( dvdcss, "vts key initialized" );
+        return 1;
+    }
+
+    if( i_encrypted == 0 )
+    {
+        memset( p_titlekey, 0, KEY_SIZE );
+        _dvdcss_debug( dvdcss, "file was unscrambled" );
+        return 0;
+    }
+
+    memset( p_titlekey, 0, KEY_SIZE );
+    return -1;
+}
+
+
+/******************************************************************************
+ * The original Ethan Hawke (DeCSSPlus) attack (modified). 
+ ******************************************************************************
+ * Tries to find a repeating pattern just before the encrypted part starts.
+ * Then it guesses that the plain text for first encrypted bytes are
+ * a contiuation of that pattern.
+ *****************************************************************************/
+static int CSSAttackPattern( u8 const p_sec[0x800], int i_pos, u8 *p_key )
+{
+    unsigned int i_best_plen = 0;
+    unsigned int i_best_p = 0;
+    unsigned int i, j;
+    
+    /* For all cycle length from 2 to 48 */
+    for( i = 2 ; i < 0x30 ; i++ )
+    {
+        /* Find the number of bytes that repeats in cycles. */
+        for( j = i + 1;
+             j < 0x80 && ( p_sec[0x7F - (j%i)] == p_sec[0x7F - j] );
+             j++ )
+        {
+            /* We have found j repeating bytes with a cycle length i. */
+            if( j > i_best_plen )
+            {
+                i_best_plen = j;
+                i_best_p = i;
+            }
+        }
+    }
+    
+    /* We need at most 10 plain text bytes?, so a make sure that we
+     * have at least 20 repeated bytes and that they have cycled at
+     * least one time.  */
+    if( ( i_best_plen > 3 ) && ( i_best_plen / i_best_p >= 2) )
+    {
+        int res;
+        
+        i_tries++;
+        memset( p_key, 0, KEY_SIZE );
+        res = CSSRecoverKey( 0,  &p_sec[0x80],
+                             &p_sec[ 0x80 - 
+                                     ( i_best_plen / i_best_p) * i_best_p ],
+                             &p_sec[0x54] /* key_seed */, p_key );
+        i_success += ( res >= 0 );
+#if 0        
+        if( res >= 0 )
+        {
+            fprintf( stderr, "key is %02x %02x %02x %02x %02x ",
+                     p_key[0], p_key[1], p_key[2], p_key[3], p_key[4] );
+            fprintf( stderr, "at block %5d pattern len %3d period %3d %s\n", 
+                     i_pos, i_best_plen, i_best_p, (res>=0?"y":"n") );
+        }
+#endif
+        return ( res >= 0 );
+    }
+    
+    return 0;
+}
+
+
+#if 0
+/******************************************************************************
+ * Encrypted Padding_stream attack.
+ ******************************************************************************
+ * DVD specifies that there must only be one type of data in every sector.
+ * Every sector is one pack and so must obviously be 2048 bytes long.
+ * For the last pice of video data before a VOBU boundary there might not
+ * be exactly the right amount of data to fill a sector. They one has to 
+ * pad the pack to 2048 bytes. For just a few bytes this is doen in the
+ * header but for any large amount you insert a PES packet from the 
+ * Padding stream. This looks like 0x00 00 01 be xx xx ff ff ...
+ * where xx xx is the length of the padding stream.
+ *****************************************************************************/
+static int CSSAttackPadding( u8 const p_sec[0x800], int i_pos, u8 *p_key )
+{
+    unsigned int i_pes_length;
+    //static int i_tries = 0, i_success = 0;
+
+    i_pes_length = (p_sec[0x12]<<8) | p_sec[0x13];
+    
+    /* Coverd by the test below but usfull for debuging. */
+    if( i_pes_length == 0x800 - 0x14 ) return 0;
+    
+    /* There must be room for at least 4? bytes of padding stream, 
+     * and it must be encrypted.
+     * sector size - pack/pes header - padding startcode - padding length */
+    if( ( 0x800 - 0x14 - 4 - 2 - i_pes_length < 4 ) ||
+        ( p_sec[0x14 + i_pes_length + 0] == 0x00 &&
+          p_sec[0x14 + i_pes_length + 1] == 0x00 &&
+          p_sec[0x14 + i_pes_length + 2] == 0x01 ) )
+    { 
+      fprintf( stderr, "plain %d %02x %02x %02x %02x (type %02x sub %02x)\n", 
+               0x800 - 0x14 - 4 - 2 - i_pes_length,
+               p_sec[0x14 + i_pes_length + 0],
+               p_sec[0x14 + i_pes_length + 1],
+               p_sec[0x14 + i_pes_length + 2],
+               p_sec[0x14 + i_pes_length + 3],
+               p_sec[0x11], p_sec[0x17 + p_sec[0x16]]);
+      return 0;
+    }
+    
+    /* If we are here we know that there is a where in the pack a
+       encrypted PES header is (startcode + lenght). It's never more 
+       than  two packets in the pack, so we 'know' the length. The 
+       plaintext at offset (0x14 + i_pes_length) will then be 
+       00 00 01 e0/bd/be xx xx, in the case of be the following bytes 
+       are also known. */
+    
+    /* An encrypted SPU PES packet with another encrypted PES packet following.
+       Normaly if the following was a padding stream that would be in plain
+       text. So it will be another SPU PES packet. */
+    if( p_sec[0x11] == 0xbd && 
+        p_sec[0x17 + p_sec[0x16]] >= 0x20 &&
+        p_sec[0x17 + p_sec[0x16]] <= 0x3f )
+    {
+        i_tries++;
+    }
+    
+    /* A Video PES packet with another encrypted PES packet following.
+     * No reason execpt for time stamps to break the data into two packets.
+     * So it's likely that the following PES packet is a padding stream. */
+    if( p_sec[0x11] == 0xe0 )
+    { 
+        i_tries++;
+    }
+   
+    if( 1 )
+    {
+        //fprintf( stderr, "key is %02x %02x %02x %02x %02x ",
+        //           p_key[0], p_key[1], p_key[2], p_key[3], p_key[4] );
+        fprintf( stderr, "at block %5d padding len %4d "
+                 "type %02x sub %02x\n",  i_pos, i_pes_length, 
+                 p_sec[0x11], p_sec[0x17 + p_sec[0x16]]);
+    }
+    
+    return 0;
+}
+#endif
--- a/src/css.h
+++ b/src/css.h
@@ -2,7 +2,7 @@
  * css.h: Structures for DVD authentification and unscrambling
  *****************************************************************************
  * Copyright (C) 1999-2001 VideoLAN
- * $Id: css.h,v 1.3 2002/02/25 18:21:57 hjort Exp $
+ * $Id: css.h,v 1.4 2002/03/09 17:57:53 hjort Exp $
  *
  * Author: St�phane Borel <stef@via.ecp.fr>
  *
@@ -50,6 +50,6 @@
  *****************************************************************************/
 int   CSSTest             ( dvdcss_handle );
 int   CSSGetDiscKey       ( dvdcss_handle );
-int   CSSGetTitleKey      ( dvdcss_handle, int );
+int   CSSGetTitleKey      ( dvdcss_handle, int , dvd_key_t );
 int   CSSDescrambleSector ( u8 *, u8 * );
 
--- a/src/libdvdcss.c
+++ b/src/libdvdcss.c
@@ -2,7 +2,7 @@
  * libdvdcss.c: DVD reading library.
  *****************************************************************************
  * Copyright (C) 1998-2001 VideoLAN
- * $Id: libdvdcss.c,v 1.2 2001/12/22 00:52:46 sam Exp $
+ * $Id: libdvdcss.c,v 1.3 2002/03/09 17:57:53 hjort Exp $
  *
  * Authors: St�phane Borel <stef@via.ecp.fr>
  *          Samuel Hocevar <sam@zoy.org>
@@ -50,7 +50,6 @@
 #include "common.h"
 #include "css.h"
 #include "libdvdcss.h"
-#include "csstables.h"
 #include "ioctl.h"
 
 /*****************************************************************************
@@ -291,6 +290,7 @@
 {
     dvd_title_t *p_title;
     dvd_title_t *p_newtitle;
+    dvd_key_t    p_title_key;
     int          i_ret;
 
     if( ! dvdcss->b_encrypted )
@@ -316,7 +316,7 @@
     }
 
     /* Crack or decrypt CSS title key for current VTS */
-    i_ret = CSSGetTitleKey( dvdcss, i_block );
+    i_ret = CSSGetTitleKey( dvdcss, i_block, p_title_key );
 
     if( i_ret < 0 )
     {
@@ -323,10 +323,10 @@
         _dvdcss_error( dvdcss, "fatal error in vts css key" );
         return i_ret;
     }
-    else if( i_ret > 0 )
+    else if( i_ret == 0 )
     {
-        _dvdcss_error( dvdcss, "decryption unavailable" );
-        return -1;
+        _dvdcss_debug( dvdcss, "unecrypted title" );
+        /* Still store this in the cache, so we don't need to check again. */
     }
 
     /* Find our spot in the list */
@@ -344,7 +344,7 @@
     /* Write in the new title and its key */
     p_newtitle = malloc( sizeof( dvd_title_t ) );
     p_newtitle->i_startlb = i_block;
-    memcpy( p_newtitle->p_key, dvdcss->css.p_title_key, KEY_SIZE );
+    memcpy( p_newtitle->p_key, p_title_key, KEY_SIZE );
 
     /* Link the new title, either at the beginning or inside the list */
     if( p_title == NULL )
@@ -358,6 +358,7 @@
         p_title->p_next = p_newtitle;
     }
 
+    memcpy( dvdcss->css.p_title_key, p_title_key, KEY_SIZE );
     return 0;
 }