/*## (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.util.*;
import java.io.*;

public class Conax {
 public static final int CA_ID = 0x0b01;

 public static final int CLASS                = 0xdd;

 public static final int CMD_EMM_INIT         = 0x82;
 public static final int CMD_EMM              = 0x84;
 public static final int CMD_ECM              = 0xa2;
 public static final int CMD_READ_RESP        = 0xca;

 public static final int CMD_GET_CARDINFO     = 0x26;
 public static final int CMD_GET_CARDADDR     = 0xc2;
 public static final int CMD_GET_ENTITLEMENTS = 0xc6;

 public static final int CMD_RET_OK1          = 0x90;
 public static final int CMD_RET_OK2          = 0x00;
 public static final int CMD_RET_RESP         = 0x98;

 public static final int CMD_REQ_HDR_SIZE     = 0x05;
 public static final int MAX_PDU_SIZE         = 0x100;

 public static final int TAG_ENTITLEMENT      = 0x32;

 public static final int ENT_PROVNAME         = 0x01;
 public static final int ENT_DATE             = 0x30;
 public static final int ENT_BITMASK          = 0x20;

 public static final int TAG_UNIQCARDADDR     = 0x74;

 public static final int TAG_CARDVER          = 0x20;
 public static final int TAG_SYSID            = 0x28;

 public static final int DEFAULT_EMM_PID      = 0xc0;

 public static final int TAG_CARDADDR         = 0x23;

 public static final int PAD = 25;

 private static SCInfo cachedinfo;

 private static int msg;
 private static Hashtable id_lookup;
 private static int id_unknown;
 
 static {
  msg=NativeCode.getInstance().malloc(Conax.MAX_PDU_SIZE); 

  Memory.write_byte(msg+0x00,Conax.CLASS);
  Memory.write_byte(msg+0x01,Conax.CMD_ECM);
  Memory.write_byte(msg+0x02,0x00);
  Memory.write_byte(msg+0x03,0x00);
  Memory.write_byte(msg+0x04,0x00);
  Memory.write_byte(msg+0x05,0x14);
  Memory.write_byte(msg+0x06,0x00);
  Memory.write_byte(msg+0x07,0x00);

  id_lookup=new Hashtable();

  id_lookup.put("ITI Neovision  ",new Integer(0x1010));
  id_lookup.put("ITI VOD 1      ",new Integer(0x1020));
  id_lookup.put("ITI VOD 2      ",new Integer(0x1030));
  id_lookup.put("ITI VOD 3      ",new Integer(0x1040));
  id_lookup.put("ITI 2 Neovision",new Integer(0x1050));

  id_unknown=0x1070;
 }

 private static int lookup_id(String provider) {
  Integer ival=(Integer)id_lookup.get(provider);

  int res=-1;

  if (ival!=null) {
   res=ival.intValue();
  } else {
   res=id_unknown++;
  }

  return res;
 }

 public static int[] cmd_get_entitlements() {
  NativeCode nc=NativeCode.getInstance();

  int buf=nc.malloc(0x08);

  Memory.write_byte(buf+0x00,CLASS);
  Memory.write_byte(buf+0x01,CMD_GET_ENTITLEMENTS);
  Memory.write_byte(buf+0x02,0x00);
  Memory.write_byte(buf+0x03,0x00);
  Memory.write_byte(buf+0x04,0x03);
  Memory.write_byte(buf+0x05,0x1c);
  Memory.write_byte(buf+0x06,0x01);
  Memory.write_byte(buf+0x07,0x00);

  return SCInterface.send_request(buf,0x08); 
 }

 private static int count_entitlements(int buf,int size) {
  int cnt=0;
  int pos=0;

  while(pos<size) {
   int tag=Memory.read_byte(buf+pos);
   int len=Memory.read_byte(buf+pos+1);

   if (tag==TAG_ENTITLEMENT) {
    cnt++;
   }

   pos+=len+2;
  }

  return cnt;
 }

 private static Entitlement extract_entitlement(int buf,int size) {
  Entitlement ent=null;
  String provname=null;
  int period[][]=null;
  int mask=0;

  int pos=0;
  int dateidx=Subscription.FROM;

  Vector subscriptions=new Vector();

  while(pos<size) {
   int type=Memory.read_byte(buf+pos);
   int len=Memory.read_byte(buf+pos+1);

   switch(type) {
    case ENT_PROVNAME:
     provname=Utils.buf2javastring(buf+pos+2,len);
     ent=new Entitlement(provname,subscriptions);
     break;
    case ENT_DATE:
     if (dateidx==Subscription.FROM) {
      period=new int[2][];
      period[Subscription.FROM]=new int[3];      
      period[Subscription.TO]=new int[3];      
     }

     period[dateidx][Subscription.DAY]=Memory.read_byte(buf+pos+2)&0x1f;   
     period[dateidx][Subscription.MONTH]=Memory.read_byte(buf+pos+3)&0x0f;      
     period[dateidx][Subscription.YEAR]=1990+((Memory.read_byte(buf+pos+2)>>5)&0x07)*10+((Memory.read_byte(buf+pos+3)>>4)&0x0f);

     if (dateidx==Subscription.FROM) dateidx++;
      else dateidx=Subscription.FROM;
     break;
    case ENT_BITMASK:
     mask=(Memory.read_byte(buf+pos+2)<<24)|(Memory.read_byte(buf+pos+3)<<16)|(Memory.read_byte(buf+pos+4)<<8)|Memory.read_byte(buf+pos+5);
     Subscription s=new Subscription(period,mask);
     subscriptions.addElement(s);
     break;
   }

   pos+=len+2;
  }

  return ent;
 }

 public static Entitlement[] buf2ent(int buf,int size) {
  size-=2;

  int cnt=count_entitlements(buf,size);

  if (cnt==0) return null;

  Entitlement[] enttab=new Entitlement[cnt];

  int pos=0;
  int idx=0;

  while(pos<size) {
   int tag=Memory.read_byte(buf+pos);
   int len=Memory.read_byte(buf+pos+1);

   if (tag==TAG_ENTITLEMENT) {
    enttab[idx++]=extract_entitlement(buf+pos+4,len-2); 
   }

   pos+=len+2;
  }
  
  return enttab;
 }

 private static void put_date(ByteArrayOutputStream baos,int date[]) {
  baos.write(ENT_DATE);
  baos.write(0x02);

  /* xxxd dddd yyyy mmmm | x*10+y = year-1990 */

  int day=date[Subscription.DAY];
  int month=date[Subscription.MONTH];
  int year=date[Subscription.YEAR]-1990;

  int x=year/10;
  int d=day&0x1f;
  int m=month&0x0f;
  int y=(year-x*10)&0x0f;

  int b1=(x<<5)|d;
  int b2=(y<<4)|m;

  baos.write(b1);
  baos.write(b2);
 }

 private static void put_subscription(ByteArrayOutputStream baos,Subscription sub) {
  int period[][]=sub.subsperiod();
  int mask=sub.bitmask();

  put_date(baos,period[Subscription.FROM]);
  put_date(baos,period[Subscription.TO]);

  baos.write(ENT_BITMASK);
  baos.write(0x04);

  baos.write((mask>>24)&0xff);
  baos.write((mask>>16)&0xff);
  baos.write((mask>>8)&0xff);
  baos.write((mask)&0xff);
 }

 private static void put_entitlement(ByteArrayOutputStream baos,Entitlement ent,int id) {
  String provider=ent.provider();
  Vector subs=ent.subscriptions();

  baos.write(TAG_ENTITLEMENT);

  int size=4+provider.length()+subs.size()*14;

  baos.write(size);

  baos.write((id>>8)&0xff);
  baos.write((id)&0xff);

  baos.write(ENT_PROVNAME);
  baos.write(provider.length());

  for(int i=0;i<provider.length();i++) {
   baos.write(provider.charAt(i));
  }

  for(int i=0;i<subs.size();i++) {
   put_subscription(baos,(Subscription)subs.elementAt(i));
  }
 }

 public static byte[] ent2buf(Entitlement enttab[]) {
  ByteArrayOutputStream baos=new ByteArrayOutputStream();

  for(int i=0;i<enttab.length;i++) {
   Entitlement ent=enttab[i];

   int id=lookup_id(ent.provider());

   put_entitlement(baos,ent,id);
  }

  return baos.toByteArray();
 }

 public static Entitlement[] get_entitlements() {
  int resp[]=cmd_get_entitlements();
  Entitlement[] ent=null;

  if (resp[SCInterface.IDX_SIZE]>2) {
   ent=buf2ent(resp[SCInterface.IDX_DATA],resp[SCInterface.IDX_SIZE]);
  }

  SCInterface.free_response(resp);

  return ent;
 }

 public static int[] cmd_get_cardaddr() {
  NativeCode nc=NativeCode.getInstance();

  int buf=nc.malloc(0x07);

  Memory.write_byte(buf+0x00,CLASS);
  Memory.write_byte(buf+0x01,CMD_GET_CARDADDR);
  Memory.write_byte(buf+0x02,0x00);
  Memory.write_byte(buf+0x03,0x00);
  Memory.write_byte(buf+0x04,0x02);
  Memory.write_byte(buf+0x05,0x66);
  Memory.write_byte(buf+0x06,0x00);

  return SCInterface.send_request(buf,0x07); 
 }

 public static byte[] get_cardaddr() {
  int resp[]=cmd_get_cardaddr();
  byte[] addr=null;

  if (resp[SCInterface.IDX_SIZE]>2) {
   int buf=resp[SCInterface.IDX_DATA];

   int tag=Memory.read_byte(buf);
   int len=Memory.read_byte(buf+1);

   if (tag==TAG_UNIQCARDADDR) {
    addr=new byte[len];
    for(int i=0;i<len;i++) {
     addr[i]=(byte)Memory.read_byte(buf+2+i);
    }
   }
  }

  SCInterface.free_response(resp);

  return addr;
 }

 public static int[] cmd_get_cardinfo() {
  NativeCode nc=NativeCode.getInstance();

  int buf=nc.malloc(0x08);

  Memory.write_byte(buf+0x00,CLASS);
  Memory.write_byte(buf+0x01,CMD_GET_CARDINFO);
  Memory.write_byte(buf+0x02,0x00);
  Memory.write_byte(buf+0x03,0x00);
  Memory.write_byte(buf+0x04,0x03);
  Memory.write_byte(buf+0x05,0x10);
  Memory.write_byte(buf+0x06,0x01);
  Memory.write_byte(buf+0x07,0x40);

  return SCInterface.send_request(buf,0x08); 
 }

 public static int[] cmd_emminit(int sys_id,int emm_pid) {
  NativeCode nc=NativeCode.getInstance();

  int buf=nc.malloc(0x15);

  emm_pid&=0x1fff;

  Memory.write_byte(buf+0x00,CLASS);
  Memory.write_byte(buf+0x01,CMD_EMM_INIT);
  Memory.write_byte(buf+0x02,0x00);
  Memory.write_byte(buf+0x03,0x00);
  Memory.write_byte(buf+0x04,0x10);
  Memory.write_byte(buf+0x05,0x11);
  Memory.write_byte(buf+0x06,0x0e);
  Memory.write_byte(buf+0x07,0x01);
  Memory.write_byte(buf+0x08,0xd0);
  Memory.write_byte(buf+0x09,0x0f);
  Memory.write_byte(buf+0x0a,0xff);
  Memory.write_byte(buf+0x0b,0xff);
  Memory.write_byte(buf+0x0c,0xc9);
  Memory.write_byte(buf+0x0d,0x00);
  Memory.write_byte(buf+0x0e,0x00);
  Memory.write_byte(buf+0x0f,0x09);
  Memory.write_byte(buf+0x10,0x04);
  Memory.write_byte(buf+0x11,(sys_id>>8));
  Memory.write_byte(buf+0x12,sys_id&0xff);
  Memory.write_byte(buf+0x13,0xe0|(emm_pid>>8));
  Memory.write_byte(buf+0x14,emm_pid&0xff);

  return SCInterface.send_request(buf,0x15); 
 }

 public static SCInfo get_cardinfo() {
  if (cachedinfo!=null) return cachedinfo;

  int resp[]=cmd_get_cardinfo();
  SCInfo info=null;

  if (resp[SCInterface.IDX_SIZE]>2) {
   int buf=resp[SCInterface.IDX_DATA];
   int size=resp[SCInterface.IDX_SIZE];

   size-=2;

   int ver=-1;
   int sysid=-1;

   int pos=0;

   while(pos<size) {
    int tag=Memory.read_byte(buf+pos);
    int len=Memory.read_byte(buf+pos+1);

    switch(tag) {
     case TAG_CARDVER:
      ver=Memory.read_byte(buf+pos+2);
      break;
     case TAG_SYSID:
      sysid=(Memory.read_byte(buf+pos+2)<<8)|Memory.read_byte(buf+pos+3);
      break;
    }

    pos+=len+2;
   }

   SCInterface.free_response(resp);

   int emm_pid=MPEG.get_emm_pid(CA_ID);

   if (emm_pid==0) emm_pid=DEFAULT_EMM_PID;

   resp=cmd_emminit(sysid,emm_pid);

   if (resp[SCInterface.IDX_SIZE]>2) {
    buf=resp[SCInterface.IDX_DATA];
    size=resp[SCInterface.IDX_SIZE];

    size-=2;

    byte addrtab[][]=new byte[2][];

    int idx=0;
    pos=2;

    while(pos<size) {
     int tag=Memory.read_byte(buf+pos);
     int len=Memory.read_byte(buf+pos+1);

     switch(tag) {
      case TAG_CARDADDR:
       if (idx<2) {
        addrtab[idx]=new byte[len];

        for(int i=0;i<len;i++) {
         addrtab[idx][i]=(byte)Memory.read_byte(buf+pos+2+i);
        }
        idx++;
       }
       break;
     }

     pos+=len+2;
    }

    SCInterface.free_response(resp);

    info=new SCInfo(ver,sysid,emm_pid,addrtab[0],addrtab[1]);
    cachedinfo=info;
   }

  } else SCInterface.free_response(resp);

  return info;
 }

 public static byte[] decrypt_ecm(byte[] buf) {
  int msgsize=((int)buf[0x02])&0xff;

  Memory.write_byte(msg+0x04,msgsize+6);
  Memory.write_byte(msg+0x06,msgsize+4);
   
  msgsize+=8+3;

  for(int i=0;i<buf.length;i++) {
   int val=((int)buf[i])&0xff;
   Memory.write_byte(msg+8+i,val);
  }

  int resp[]=SCInterface.send_request(msg,msgsize);

  int rdata=resp[SCInterface.IDX_DATA];
  int rsize=resp[SCInterface.IDX_SIZE];

  byte data[]=Utils.buf2barray(rdata,rsize);

  SCInterface.free_response(resp);  

  return data;
 }

 public static int[] decrypt_cw(byte[] data) {
  byte enccw[]=Conax.decrypt_ecm(data);

  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;
  }

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

  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;
  }

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

  int resp_cw[]=new int[4];

  resp_cw[0]=cur_cw[0];
  resp_cw[1]=cur_cw[1];
  resp_cw[2]=next_cw[0];
  resp_cw[3]=next_cw[1];

  return resp_cw;
 }

 private static String cwpk_str(int cwpk[]) {
  String res="";

  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;

   res+=Utils.hex_value(b1,2)+" "+Utils.hex_value(b2,2)+" "+Utils.hex_value(b3,2)+" "+Utils.hex_value(b4,2)+" "; 
  }
  
  return res;
 }

 public static void print_info() {
  try {
   Output.text("[Conax info]");

   int chipid=-1;

   int enc_cwpk[]=null;
   int cwpk[]=null;
   String s=null;
   String type="";

   if (Config.isHERMES()) {
    type="GSECHAL";
    chipid=GsecHal.get_chip_id();
   } else

   if (Config.isCARBO()) {
    type="STTKDMA";

    if (STTKDMA.fixed_version()) {
     type+=" (with a fix/mitigation)";
    }

    chipid=STTKDMA.get_chip_id();
    enc_cwpk=STTKDMA.get_enc_cwpk();
    cwpk=STTKDMA.get_cwpk();
   }

   s="- "+Utils.padded_string("type",PAD)+type;
   Output.text(s);

   if (chipid!=-1) {
    s="- "+Utils.padded_string("chip id",PAD)+Utils.hex_value(chipid,8);
    Output.text(s);
   }

   if (enc_cwpk!=null) {
    s="- "+Utils.padded_string("encrypted CWPK",PAD)+cwpk_str(enc_cwpk);
    Output.text(s);
   }

   if (cwpk!=null) {
    s="- "+Utils.padded_string("plaintext CWPK",PAD)+cwpk_str(cwpk);
    Output.text(s);
   }
  } catch(Throwable t) {}
 }
}
