/*## (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.text.*;

public class EMMSniffer extends SCWatch {
 private static EMMSniffer emmsniffer;

 private static int emmreq;
 private static int emmreq_size;

 private static Entitlement[] emm;
 private static int[] rawemm;

 private static boolean check_entitlements;

 private static int ecm_idx;

 static {
  NativeCode nc=NativeCode.getInstance();

  emmreq=nc.malloc(Conax.MAX_PDU_SIZE);
  emmreq_size=0;

  ecm_idx=0;

  EMMStore.start_lazy_store();
 }

 public EMMSniffer() {
  super("EMM Sniffer",SCWatch.ST_REQUEST|SCWatch.ST_RESPONSE);
 }

 public boolean same_emm(int emm1[],int emm2[]) {
  boolean res=true;

  int buf1=emm1[SCInterface.IDX_DATA];
  int size1=emm1[SCInterface.IDX_SIZE];

  int buf2=emm2[SCInterface.IDX_DATA];
  int size2=emm2[SCInterface.IDX_SIZE];

  if (size1==size2) {
   for(int i=0;i<size1;i++) {
    int v1=Memory.read_byte(buf1+i);
    int v2=Memory.read_byte(buf2+i);

    if (v1!=v2) return false;
   }
  } else return false;

  return res;  
 }

 private String get_name(Entitlement[] diff) {
  String name="unknown_ecm_"+Utils.hex_value(ecm_idx,2);

  try {
   Date date=Calendar.getInstance().getTime();
   SimpleDateFormat df=new SimpleDateFormat("hh.mm_dd.MM.yyyy");
   String sdate=df.format(date);

   if (diff.length==1) {
    Entitlement e=diff[0];

    Vector subs=e.subscriptions();

    Subscription sub=(Subscription)subs.elementAt(0);

    int bitmask=sub.bitmask();
    String op=null;

    if ((bitmask&Entitlement.ADD)!=0) {
     bitmask&=~Entitlement.ADD;
     op="add";
    } else {
     op="del";
    }

    name=e.provider()+"_"+sdate+"_0x"+Utils.hex_value(bitmask,8)+"_"+op+".emm";

    name=name.replace(' ', '_');
   } else {
    name="multi"+"_"+sdate+".emm";
   }
  } catch(Throwable t) {
   ApiMonitor.log_stacktrace(t);
  }

  return name;
 }

 public boolean process_emm(int[] newrawemm) {
  ApiMonitor.log("process_emm begin");

  ApiMonitor.log("emmreq data:");
  byte tab1[]=Utils.buf2barray(emmreq,emmreq_size);
  ApiMonitor.log_buf(tab1);

  boolean res=false;

  int buf=newrawemm[SCInterface.IDX_DATA];
  int size=newrawemm[SCInterface.IDX_SIZE];

  Entitlement[] newemm=Conax.buf2ent(buf,size);

  ApiMonitor.log("Entitlement.diff");
  Entitlement[] diff=Entitlement.diff(emm,newemm);   

  ApiMonitor.log("- emm:");
  Entitlement.log(emm);

  ApiMonitor.log("- newemm:");
  Entitlement.log(newemm);

  ApiMonitor.log("- diff:");
  Entitlement.log(diff);

  if (diff.length>0) {
   SCInterface.free_response(rawemm);

   rawemm=newrawemm;
   emm=newemm;

   String filename=get_name(diff);

   EMMStore.store(filename,emmreq,emmreq_size);

   res=true;
  }

  return res;
 }

 public void check_emm() {
  try {
   int resp[]=Conax.cmd_get_entitlements();

   boolean newemm=false;

   if (resp[SCInterface.IDX_SIZE]>2) {
    if (!same_emm(rawemm,resp)) {
     newemm=process_emm(resp);
    }
   }

   if (!newemm) {
    SCInterface.free_response(resp);
   }

  } catch(Throwable t) {
   ApiMonitor.log_stacktrace(t);
  }

 }

 public boolean match(int state,int cmd,int buf,int size) {
  boolean res=false;

  switch(state) {
   case SCInterface.IST_REQUEST:
    if (cmd==Conax.CMD_EMM) {
     check_entitlements=true;
     res=true;
    }
    break;
   case SCInterface.IST_RESPONSE:
    res=check_entitlements;
    break;
  }

  return res;
 }

 public void action(int state,int cmd,int buf,int size) {
  try {
   switch(state) {
    case SCInterface.IST_REQUEST:
     Utils.memcpy(buf,emmreq,size);
     emmreq_size=size;
     break;
    case SCInterface.IST_RESPONSE:
     check_emm();
     check_entitlements=false;
     break;
   }
  } catch(Throwable t) {
   ApiMonitor.log_stacktrace(t);
  }
 }

 public static void start() {
  if (emmsniffer==null) {
   emmsniffer=new EMMSniffer();
   SCInterface.add_watch(emmsniffer);
  }

  emm=Conax.get_entitlements();
  rawemm=Conax.cmd_get_entitlements();

  check_entitlements=false;

  emmsniffer.enable();
 }

 public static void stop() {
  if (emmsniffer!=null) {
   emmsniffer.disable();
  }
 }

 public static boolean status() {
  if (emmsniffer!=null) {
   return emmsniffer.enabled();
  } else return false;
 }

}
