/*## (c) SECURITY EXPLORATIONS    2018 poland                                #*/
/*##     http://www.security-explorations.com                                #*/

/* THIS SOFTWARE IS PROTECTED BY DOMESTIC AND INTERNATIONAL COPYRIGHT LAWS    */
/* UNAUTHORISED COPYING OF THIS SOFTWARE IN EITHER SOURCE OR BINARY FORM IS   */
/* EXPRESSLY FORBIDDEN. ANY USE, INCLUDING THE REPRODUCTION, MODIFICATION,    */
/* DISTRIBUTION, TRANSMISSION, RE-PUBLICATION, STORAGE OR DISPLAY OF ANY      */
/* PART OF THE SOFTWARE, FOR COMMERCIAL OR ANY OTHER PURPOSES REQUIRES A      */
/* VALID LICENSE FROM THE COPYRIGHT HOLDER.                                   */

/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS    */
/* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,*/
/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    */
/* SECURITY EXPLORATIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
/* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF  */
/* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE     */
/* SOFTWARE.                                                                  */

import java.lang.*;
import java.io.*;
import java.util.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.tv.service.*;

public class CW {
 public static class Chunk {
  int flags;
  int stamp;   //from CWFaker/ECMInjector
  int cw_idx;  //from CWFaker/ECMInjector
  boolean cur; //from CWFaker/ECMInjector
  int key_num; //from GsecHal/Tkd

  public Chunk(int stamp,int cw_idx,boolean current,int key_num) {
   this.flags=0;
   stamp(stamp);
   cw_idx(cw_idx);
   current(current);
   key_num(key_num);
  }

  public void flags(int flags) {
   this.flags|=flags;
  }

  public boolean flag(int flag) {
   return (flags&flag)!=0;
  }

  public boolean current() {
   return cur;
  }

  public int key_num() {
   return key_num;
  }

  public boolean complete() {
   return ((flags&COMPLETE_INFO)==COMPLETE_INFO);   
  }

  public void stamp(int stamp) {
   this.stamp=stamp;
  }

  public void cw_idx(int cw_idx) {
   this.cw_idx=cw_idx;
  }

  public void current(boolean current) {
   this.cur=current;
  }

  public void key_num(int key_num) {
   this.key_num=key_num;
  }
 }

 public static final int HERMES_MAGIC_IDX          = 0x32;

 public static final int CARBO_LAST_IDX            = 0x31;
 public static final int CARBO_CW_SET_EXCEPT_RANGE = 0x02;

 public static final int CW_INIT_SIZE              = 0x20;

 public static final int CW_CHUNK_SIZE             = 0x10;

 public static final int SCARD_INFO                = 0x01;
 public static final int CHIP_INFO                 = 0x02;

 public static final int COMPLETE_INFO = (SCARD_INFO|CHIP_INFO);

 private static int cw_idx;
 private static int cur_key_num;
 private static int nxt_key_num;

 private static long time;

 private static Chunk chunk_data[];

 private static int cw_initialized[];

 private static final int hermes_key_indices[]={
  0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
 };

 static {
  cw_idx=1;
  cur_key_num=-1;
  nxt_key_num=-1;
  time=0;

  cw_initialized=new int[CW_INIT_SIZE];
  chunk_data=new Chunk[CW_CHUNK_SIZE];
 }

 public static Chunk chunk_by_cwidx(int cw_idx) {
  Chunk res=null;

  Chunk chunk=chunk_data[cw_idx%CW_CHUNK_SIZE];

  if (chunk!=null) {
   if (chunk.cw_idx==cw_idx) {
    res=chunk;
   }
  }

  return res;
 }

 public static Chunk chunk_by_stamp(int stamp,int base) {
  Chunk res=null;

  for(int i=base;i<CW_CHUNK_SIZE;i++) {
   Chunk chunk=chunk_data[i%CW_CHUNK_SIZE];

   if (chunk!=null) {
    if (chunk.stamp==stamp) {
     res=chunk;
     break;
    }
   }
  }

  return res;
 }

 public static void new_chunk(Chunk chunk) {
  chunk_data[chunk.cw_idx%CW_CHUNK_SIZE]=chunk;
 }

 public static void del_chunk(Chunk chunk) {
  synchronized(chunk_data) {
   chunk_data[chunk.cw_idx%CW_CHUNK_SIZE]=null;
  }
 }

 public static void set_scard_info(int stamp,int cw_idx,boolean current) {
  synchronized(chunk_data) {
   Chunk chunk=chunk_by_cwidx(cw_idx);

   if (chunk==null) {
    chunk=new Chunk(stamp,cw_idx,current,-1);
    new_chunk(chunk);
   } else {
    chunk.stamp(stamp);
    chunk.cw_idx(cw_idx);
    chunk.current(current);
   }

   chunk.flags(SCARD_INFO);
  }
 }

 public static void set_chip_info(int cw_idx,int key_num) {
  synchronized(chunk_data) {
   Chunk chunk=chunk_by_cwidx(cw_idx);

   if (chunk==null) {
    chunk=new Chunk(-1,cw_idx,false,key_num);
    new_chunk(chunk);
   } else {
    chunk.cw_idx(cw_idx);
    chunk.key_num(key_num);
   }

   chunk.flags(CHIP_INFO);
  }
 }

 public static Chunk get_cw_data(int stamp) {
  Chunk chunk=null;

  synchronized(chunk_data) {
   chunk=chunk_by_stamp(stamp,0);
  }

  return chunk;
 }

 public static Chunk get_next_cw_data(Chunk bchunk) {
  Chunk chunk=null;
 
  if (bchunk==null) return null;

  int base=(bchunk.cw_idx+1)%CW_CHUNK_SIZE;

  synchronized(chunk_data) {
   chunk=chunk_by_stamp(bchunk.stamp,base);
  }

  return chunk;
 }

 public static void wait_for_chunk(int stamp,int maxtime) {
  int time=0;

  while(time<maxtime) {
   Chunk chunk=null;

   synchronized(chunk_data) {
    chunk=chunk_by_stamp(stamp,0);
   }

   if (chunk!=null) {
    if ((chunk.stamp!=-1)&&(chunk.key_num!=-1)) break;
   }

   Utils.sleep(100);
   time+=100;
  }
 }

 public static void set_initialized(int cw) {
  cw_initialized[cw%CW_INIT_SIZE]=cw;
 }

 public static void clear_initialized() {
  for(int i=0;i<CW_INIT_SIZE;i++) {
   cw_initialized[i]=-1;
  }

  cw_idx+=2;
 }

 public static void wait_for_cw(int cw) {
  while(cw_initialized[cw%CW_INIT_SIZE]!=cw) {
   Utils.sleep(50);
  }
 }

 public static void inc_word() {
  cw_idx++;
 }

 public static int cur_word() {
  return cw_idx;
 }

 public static int nxt_word() {
  return cw_idx+1;
 }

 public static int cur_key_num() {
  return cur_key_num;
 }

 public static int nxt_key_num() {
  return nxt_key_num;
 }

 public static void cur_key_num(int num) {
  cur_key_num=num;
 }

 public static void nxt_key_num(int num) {
  nxt_key_num=num;
 }

 public static int map_key_idx_for_hermes(int idx) {
  int res=HERMES_MAGIC_IDX+1;

  switch(idx) {
   case 0x0a:
    res+=0;
    break;

   case 0x0b:
    res+=1;
    break;

   case 0x0c:
    res+=2;
    break;

   case 0x0d:
    res+=3;
    break;

   case 0x0e:
    res+=4;
    break;

   case 0x0f:
    res+=5;
    break;
  }

  ApiMonitor.log("map key: "+Utils.hex_value(idx,2)+" -> "+Utils.hex_value(res,2));

  return res;
 }

 public static int map_key_idx_for_carbo(int idx) {
  int res=idx;

  ApiMonitor.log("map key: "+Utils.hex_value(idx,2)+" -> "+Utils.hex_value(res,2));

  return res;
 }

 public static int map_key_idx(int idx) {
  int res=-1;

  if (Config.isHERMES()) {
   res=map_key_idx_for_hermes(idx);
  } else

  if (Config.isCARBO()) {
   res=map_key_idx_for_carbo(idx);
  }

  return res;  
 }

 private static int[] cw_decrypt_hermes(int[] cw) {
  int[] plain_cw=new int[2];

  GsecHal.DecryptSCK_0(HERMES_MAGIC_IDX,cw[0],cw[1]);

  cw=GsecHal.GetCW(HERMES_MAGIC_IDX);

  plain_cw[0]=cw[0];
  plain_cw[1]=cw[1];

  return plain_cw;  
 }

 public static byte[] tdes_key(byte data[]) {
  byte key[]=new byte[24];
  System.arraycopy(data,0,key,0,16);
  System.arraycopy(data,0,key,16,8);

  return key;
 }

 public static byte[] tdes_decrypt(byte key[],byte data[]) throws Throwable {
  Cipher c=Cipher.getInstance("DESede/ECB/NoPadding");
  SecretKeySpec k=new SecretKeySpec(key,"DESede");
  c.init(Cipher.DECRYPT_MODE,k);

  data=c.doFinal(data);

  return data;
 }

 public static byte[] tdes_encrypt(byte key[],byte data[]) throws Throwable {
  Cipher c=Cipher.getInstance("DESede/ECB/NoPadding");
  SecretKeySpec k=new SecretKeySpec(key,"DESede");
  c.init(Cipher.ENCRYPT_MODE,k);

  data=c.doFinal(data);

  return data;
 }

 public static int[] cw_manual_decrypt(int[] cw) {
  int plain_cw[]=new int[2];
 
  try {
   int cwpk[]=STTKDMA.get_cwpk();

   byte byte_cwpk[]=new byte[0x10];

   for(int i=0;i<4;i++) {
    int val=cwpk[i];

    int b4=(val>>24)&0xff;
    int b3=(val>>16)&0xff;
    int b2=(val>>8)&0xff;
    int b1=(val)&0xff;

    byte_cwpk[i*4+0]=(byte)b4;
    byte_cwpk[i*4+1]=(byte)b3;
    byte_cwpk[i*4+2]=(byte)b2;
    byte_cwpk[i*4+3]=(byte)b1;
   }

   byte key[]=tdes_key(byte_cwpk);

   byte cw_data[]=new byte[0x08];

   for(int i=0;i<2;i++) {
    int val=cw[i];

    int b4=(val>>24)&0xff;
    int b3=(val>>16)&0xff;
    int b2=(val>>8)&0xff;
    int b1=(val)&0xff;

    cw_data[i*4+0]=(byte)b4;
    cw_data[i*4+1]=(byte)b3;
    cw_data[i*4+2]=(byte)b2;
    cw_data[i*4+3]=(byte)b1;
   }

   byte res[]=tdes_decrypt(key,cw_data);

   for(int i=0;i<2;i++) {
    int b4=((int)(res[4*i+0]))&0xff;
    int b3=((int)(res[4*i+1]))&0xff;
    int b2=((int)(res[4*i+2]))&0xff;
    int b1=((int)(res[4*i+3]))&0xff;

    int val=(b4<<24)|(b3<<16)|(b2<<8)|(b1);

    plain_cw[i]=val;
   }

  } catch(Throwable t) {}

  return plain_cw;
 }

 public static int[] cw_manual_encrypt(int[] cw) {
  int crypted_cw[]=new int[2];
 
  try {
   int cwpk[]=STTKDMA.get_cwpk();

   byte byte_cwpk[]=new byte[0x10];

   for(int i=0;i<4;i++) {
    int val=cwpk[i];

    int b4=(val>>24)&0xff;
    int b3=(val>>16)&0xff;
    int b2=(val>>8)&0xff;
    int b1=(val)&0xff;

    byte_cwpk[i*4+0]=(byte)b4;
    byte_cwpk[i*4+1]=(byte)b3;
    byte_cwpk[i*4+2]=(byte)b2;
    byte_cwpk[i*4+3]=(byte)b1;
   }

   byte key[]=tdes_key(byte_cwpk);

   byte cw_data[]=new byte[0x08];

   for(int i=0;i<2;i++) {
    int val=cw[i];

    int b4=(val>>24)&0xff;
    int b3=(val>>16)&0xff;
    int b2=(val>>8)&0xff;
    int b1=(val)&0xff;

    cw_data[i*4+0]=(byte)b4;
    cw_data[i*4+1]=(byte)b3;
    cw_data[i*4+2]=(byte)b2;
    cw_data[i*4+3]=(byte)b1;
   }

   byte res[]=tdes_encrypt(key,cw_data);

   for(int i=0;i<2;i++) {
    int b4=((int)(res[4*i+0]))&0xff;
    int b3=((int)(res[4*i+1]))&0xff;
    int b2=((int)(res[4*i+2]))&0xff;
    int b1=((int)(res[4*i+3]))&0xff;

    int val=(b4<<24)|(b3<<16)|(b2<<8)|(b1);

    crypted_cw[i]=val;
   }

  } catch(Throwable t) {}

  return crypted_cw;
 }

 private static int[] cw_decrypt_carbo(int[] cw) {
  int[] plain_cw=new int[2];

  if (STTKDMA.hascwpk()) {
   if (!STTKDMA.fixed_version()) {
    STTKDMA.set_input(cw);
    STTKDMA.tkd_cmd(STTKDMA.MAGIC_KEY,STTKDMA.REGS,STTKDMA.CWPK1_KEY,STTKDMA.DECRYPT);

    cw=STTKDMA.get_key(STTKDMA.MAGIC_IDX);

    plain_cw[0]=cw[0];
    plain_cw[1]=cw[1];
   } else {
    plain_cw=cw_manual_decrypt(cw);
   }
  }

  return plain_cw;
 }

 public static int[] cw_encrypt_carbo(int[] cw) {
  int[] crypted_cw=new int[2];

  if (STTKDMA.hascwpk()) {
   if (!STTKDMA.fixed_version()) {
    STTKDMA.set_input(cw);

    STTKDMA.tkd_cmd(STTKDMA.REGS,STTKDMA.REGS,STTKDMA.MAGIC_KEY+1,STTKDMA.ENCRYPT);

    cw=STTKDMA.get_output();

    crypted_cw[0]=cw[0];
    crypted_cw[1]=cw[1];
   } else {
    crypted_cw=cw_manual_encrypt(cw);
   } 
  }

  return crypted_cw;
 }

 public static int[] decrypt(int[] cw) {
  int plain_cw[]=null;

  if (Config.isHERMES()) {
   plain_cw=cw_decrypt_hermes(cw);
  } else

  if (Config.isCARBO()) {
   plain_cw=cw_decrypt_carbo(cw);
  }

  return plain_cw;
 }

 private static void cw_set_hermes(int keyidx,int[] cw) {
  ApiMonitor.log("cw_set_hermes idx: "+Utils.hex_value(keyidx,8));

  GsecHal.SetCW(keyidx,cw);
 }

 private static void cw_set_carbo(int keyidx,int[] cw) {
  ApiMonitor.log("cw_set_carbo idx: "+Utils.hex_value(keyidx,8));

  int crypted_cw[]=cw_encrypt_carbo(cw);

  STTKDMA.set_input(crypted_cw);
  STTKDMA.tkd_cmd(STTKDMA.CW_BASE_KEY_IDX+keyidx,STTKDMA.REGS,STTKDMA.CWPK_KEY,STTKDMA.DECRYPT);
 }

 public static void set(int keyidx,int[] cw) {
  if (keyidx==-1) return;

  if (Config.isHERMES()) {
   cw_set_hermes(keyidx,cw);
  } else

  if (Config.isCARBO()) {
   cw_set_carbo(keyidx,cw);
  }
 }

 public static long decrypt_time() {
  return time;
 }

 public static void decrypt_time(long val) {
  time=val;
 }

 private static void start_decrypt_handler_hermes() {
  GSecHandler.start();
 }

 private static void start_decrypt_handler_carbo() {
  TkdHandler.start();
 }

 public static void start_decrypt_handler() {
  if (Config.isHERMES()) {
   start_decrypt_handler_hermes();
  } else

  if (Config.isCARBO()) {
   start_decrypt_handler_carbo();
  }  
 }

 public static void print_cw_info() {
  try {
   Service srv=SrvUtils.current_service();
   Output.text("[CW info]");

   if (srv!=null) {
    if (SrvUtils.srv_scrambled(srv)) {
     int pid=SrvUtils.get_ca_pid(srv);

     if (pid!=0) {
      byte data[]=MPEGSniffer.get_section(null,pid);

      if (data!=null) {
       Output.text("- ECM PACKET");
       MPEG.print_buf(2,data);

       byte enccw[]=Conax.decrypt_ecm(data);

       Output.text("- CARD RESPONSE");
       MPEG.print_buf(2,enccw);

       int curkey=((int)data[0])&0x01;

       int idx=1-curkey;

       int b1=((int)enccw[0x07+0x0f*idx])&0xff;
       int b2=((int)enccw[0x08+0x0f*idx])&0xff;
       int b3=((int)enccw[0x09+0x0f*idx])&0xff;
       int b4=((int)enccw[0x0a+0x0f*idx])&0xff;
       int b5=((int)enccw[0x0b+0x0f*idx])&0xff;
       int b6=((int)enccw[0x0c+0x0f*idx])&0xff;
       int b7=((int)enccw[0x0d+0x0f*idx])&0xff;
       int b8=((int)enccw[0x0e+0x0f*idx])&0xff;

       int cw[]=new int[2];

       if (Config.isHERMES()) {
        cw[0]=(b5<<24)|(b6<<16)|(b7<<8)|b8;
        cw[1]=(b1<<24)|(b2<<16)|(b3<<8)|b4;
       } else

       if (Config.isCARBO()) {
        cw[0]=(b1<<24)|(b2<<16)|(b3<<8)|b4;
        cw[1]=(b5<<24)|(b6<<16)|(b7<<8)|b8;
       }

       Output.text("- CUR CW crypted:   "+Utils.hex_value(cw[0],8)+" "+Utils.hex_value(cw[1],8));

       int cur_cw[]=CW.decrypt(cw);

       Output.text("- CUR CW plaintext: "+Utils.hex_value(cur_cw[0],8)+" "+Utils.hex_value(cur_cw[1],8));

       idx=curkey;

       b1=((int)enccw[0x07+0x0f*idx])&0xff;
       b2=((int)enccw[0x08+0x0f*idx])&0xff;
       b3=((int)enccw[0x09+0x0f*idx])&0xff;
       b4=((int)enccw[0x0a+0x0f*idx])&0xff;
       b5=((int)enccw[0x0b+0x0f*idx])&0xff;
       b6=((int)enccw[0x0c+0x0f*idx])&0xff;
       b7=((int)enccw[0x0d+0x0f*idx])&0xff;
       b8=((int)enccw[0x0e+0x0f*idx])&0xff;

       if (Config.isHERMES()) {
        cw[0]=(b5<<24)|(b6<<16)|(b7<<8)|b8;
        cw[1]=(b1<<24)|(b2<<16)|(b3<<8)|b4;
       } else

       if (Config.isCARBO()) {
        cw[0]=(b1<<24)|(b2<<16)|(b3<<8)|b4;
        cw[1]=(b5<<24)|(b6<<16)|(b7<<8)|b8;
       }

       Output.text("- NXT CW crypted:   "+Utils.hex_value(cw[0],8)+" "+Utils.hex_value(cw[1],8));

       int next_cw[]=CW.decrypt(cw);

       Output.text("- NXT CW plaintext: "+Utils.hex_value(next_cw[0],8)+" "+Utils.hex_value(next_cw[1],8));
      }
     }
    }
   }
  } catch(Throwable t) {
   ApiMonitor.log("exception: "+t);
  }
 }
}
