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

public class SCInterface extends ApiHandler {
 public static final String HANDLER_CLASS      = "SCInterface";

 public static final String MONITOR_READ_NAME  = "SC read handler";
 public static final String MONITOR_WRITE_NAME = "SC write handler";

 public static final int IST_UNKNOWN           =-1;
 public static final int IST_READY             = 0;

 public static final int IST_WRITE_REQ_HDR     = 0;
 public static final int IST_READ_REQ_CMD      = 1;
 public static final int IST_WRITE_REQ_BODY    = 2;
 public static final int IST_READ_RESP_CODE1   = 3;
 public static final int IST_READ_RESP_CODE2   = 4;
 public static final int IST_WRITE_RESP_REQ    = 5;
 public static final int IST_READ_RESP_REQ_CMD = 6;
 public static final int IST_READ_RESP_BODY    = 7;

 public static final int IST_PRECALL           = 0;
 public static final int IST_POSTCALL          = 1;

 public static final int IST_REQUEST           = 0;
 public static final int IST_RESPONSE          = 1;

 public static final int MAX_FILTER            = 8;
 public static final int MAX_WATCH             = 2;

 public static final int IDX_DATA              = 0;
 public static final int IDX_SIZE              = 1;

 private static Vector filters[];
 private static Vector watches[];

 private static Mutex sc_access;
 private static Mutex flt_access;
 private static Mutex wtch_access;

 private static boolean ready;

 private static int scfd;

 private static int read_mid;
 private static int write_mid;

 private static int org_read;
 private static int org_write;

 private static boolean hermes;
 private static boolean carbo;

 private static int istate;
 private static int nextstate;

 private static boolean invoke_original;

 private static int cur_cmd;
 private static int cur_P0;
 private static int cur_P1;
 private static int cur_RET0;
 private static int cur_RET1;

 private static int cur_req;
 private static int cur_req_size;
 private static int cur_resp;
 private static int cur_resp_size;

 private static int req_resp;

 private static NativeCode nc;
 private static int tmp;

 static {
  if (Config.isHERMES()) {
   hermes=true;
  } else

  if (Config.isCARBO()) {
   carbo=true;
  }

  scfd=-1;
  istate=IST_UNKNOWN;

  nc=NativeCode.getInstance();

  cur_req=nc.malloc(Conax.MAX_PDU_SIZE);
  cur_resp=nc.malloc(Conax.MAX_PDU_SIZE);

  filters=new Vector[MAX_FILTER];

  for(int i=0;i<MAX_FILTER;i++) {
   filters[i]=new Vector();
  }

  watches=new Vector[MAX_WATCH];

  for(int i=0;i<MAX_WATCH;i++) {
   watches[i]=new Vector();
  }

  sc_access=new Mutex();
  flt_access=new Mutex();
  wtch_access=new Mutex();

  tmp=nc.malloc(0x08);

  req_resp=nc.malloc(0x05);
  Memory.write_byte(req_resp+0x00,Conax.CLASS);
  Memory.write_byte(req_resp+0x01,Conax.CMD_READ_RESP);
  Memory.write_byte(req_resp+0x02,0x00);
  Memory.write_byte(req_resp+0x03,0x00);
  Memory.write_byte(req_resp+0x04,0x00);

  ready=false;
  invoke_original=true;

  start();
 }

 public static boolean BIT_SET(int mask,int bit) {
  if ((mask&bit)!=0) return true;
   else return false;
 }

 public static void access_lock() {
  sc_access.lock();
 }

 public static void access_unlock() {
  sc_access.unlock();
 }

 public static void filter_lock() {
  flt_access.lock();
 }

 public static void filter_unlock() {
  flt_access.unlock();
 }

 public static void watch_lock() {
  wtch_access.lock();
 }

 public static void watch_unlock() {
  wtch_access.unlock();
 }

 public static boolean has_filter(String name) {
  for(int i=0;i<filters.length;i++) {
   Vector flt_vector=filters[i];

   for(int j=0;j<flt_vector.size();j++) {
    SCFilter flt=(SCFilter)flt_vector.elementAt(j);
    if (flt.name().equals(name)) return true;
   }
  }

  return false;
 }

 public static boolean has_watch(String name) {
  for(int i=0;i<watches.length;i++) {
   Vector wtch_vector=watches[i];

   for(int j=0;j<wtch_vector.size();j++) {
    SCWatch wtch=(SCWatch)wtch_vector.elementAt(j);
    if (wtch.name().equals(name)) return true;
   }
  }

  return false;
 }

 public static boolean add_filter(SCFilter flt) {
  boolean res=false;

  filter_lock();

  if (!has_filter(flt.name())) {
   int mask=flt.actionmask();

   if (mask!=0) res=true;

   if (BIT_SET(mask,SCFilter.ST_WRITE_REQ_HDR)) {
    filters[IST_WRITE_REQ_HDR].addElement(flt);
   }

   if (BIT_SET(mask,SCFilter.ST_READ_REQ_CMD)) {
    filters[IST_READ_REQ_CMD].addElement(flt);
   }

   if (BIT_SET(mask,SCFilter.ST_WRITE_REQ_BODY)) {
    filters[IST_WRITE_REQ_BODY].addElement(flt);
   }

   if (BIT_SET(mask,SCFilter.ST_READ_RESP_CODE1)) {
    filters[IST_READ_RESP_CODE1].addElement(flt);
   }

   if (BIT_SET(mask,SCFilter.ST_READ_RESP_CODE2)) {
    filters[IST_READ_RESP_CODE2].addElement(flt);
   }

   if (BIT_SET(mask,SCFilter.ST_WRITE_RESP_REQ)) {
    filters[IST_WRITE_RESP_REQ].addElement(flt);
   }

   if (BIT_SET(mask,SCFilter.ST_READ_RESP_REQ_CMD)) {
    filters[IST_READ_RESP_REQ_CMD].addElement(flt);
   }

   if (BIT_SET(mask,SCFilter.ST_READ_RESP_BODY)) {
    filters[IST_READ_RESP_BODY].addElement(flt);
   }

  }

  filter_unlock();

  return res;
 }

 public static boolean add_watch(SCWatch watch) {
  boolean res=false;

  watch_lock();

  if (!has_watch(watch.name())) {
   int mask=watch.actionmask();

   if (mask!=0) res=true;

   if (BIT_SET(mask,SCWatch.ST_REQUEST)) {
    watches[IST_REQUEST].addElement(watch);
   }

   if (BIT_SET(mask,SCWatch.ST_RESPONSE)) {
    watches[IST_RESPONSE].addElement(watch);
   }

  }

  watch_unlock();

  return res;
 }

 public SCInterface(String name,String function,int mid,int addr) {
  super(name,function,mid,addr);

  if (hermes) {
   if (function.equals("read")) {
    read_mid=mid;
   } else

   if (function.equals("write")) {
    write_mid=mid;
   }
  }
 }

 public void process_filters(int prefix,int state,int ptr,int buf,int size,int res) {
  if ((!ready)||(state==IST_UNKNOWN)) return;

  Vector flt_vector=filters[state];

  if (flt_vector.size()==0) return;

  filter_lock();

  for(int j=0;j<flt_vector.size();j++) {
   SCFilter flt=(SCFilter)flt_vector.elementAt(j);
   if (!flt.enabled()) continue;

   if (flt.match(prefix,state,cur_cmd,ptr,buf,size)) {
    boolean actres=flt.action(prefix,state,cur_cmd,ptr,buf,size);

    if (prefix==IST_PRECALL) invoke_original=invoke_original&actres;
   }
  }

  filter_unlock();
 }

 public void process_watches(int state,int buf,int size) {
  if (!ready) return;

  Vector wtch_vector=watches[state];

  if (wtch_vector.size()==0) return;

  watch_lock();

  for(int j=0;j<wtch_vector.size();j++) {
   SCWatch watch=(SCWatch)wtch_vector.elementAt(j);
   if (!watch.enabled()) continue;

   if (watch.match(state,cur_cmd,buf,size)) {
    watch.action(state,cur_cmd,buf,size);
   }
  }

  watch_unlock();
 }

 public void pre_read(int ptr,int fd,int buf,int size) {
  process_filters(IST_PRECALL,istate,ptr,buf,size,0);
 }

 public void post_read(int ptr,int fd,int buf,int size,int res) {
  switch(istate) {
   case IST_UNKNOWN:
    break;

   case IST_WRITE_REQ_HDR:
    break;

   case IST_READ_REQ_CMD:
    if (size==1) {
     int cmd=Memory.read_byte(buf);
     if (cmd==cur_cmd) {
      process_filters(IST_POSTCALL,IST_READ_REQ_CMD,ptr,buf,size,res);
      istate=IST_WRITE_REQ_BODY;
     }
    }
    break;
   case IST_WRITE_REQ_BODY:
    break;

   case IST_READ_RESP_CODE1:
    process_filters(IST_POSTCALL,IST_READ_RESP_CODE1,ptr,buf,size,res);
    cur_RET0=Memory.read_byte(buf);
    istate=IST_READ_RESP_CODE2;
    break;

   case IST_READ_RESP_CODE2:
    process_filters(IST_POSTCALL,IST_READ_RESP_CODE2,ptr,buf,size,res);
    cur_RET1=Memory.read_byte(buf);

    if (cur_RET0==Conax.CMD_RET_OK1) {
     Memory.write_byte(cur_resp,cur_RET0);
     Memory.write_byte(cur_resp+1,cur_RET1);
     cur_resp_size=2;

     ready=true;
     istate=IST_READY;

     access_unlock();

     process_watches(IST_RESPONSE,cur_resp,cur_resp_size);
    } else

    if  (cur_RET0==Conax.CMD_RET_RESP) {
     istate=IST_WRITE_RESP_REQ;
    } else {
     // error responses
     Memory.write_byte(cur_resp,cur_RET0);
     Memory.write_byte(cur_resp+1,cur_RET1);
     cur_resp_size=2;

     ready=true;
     istate=IST_READY;

     access_unlock();

     process_watches(IST_RESPONSE,cur_resp,cur_resp_size);
    }
    break;

   case IST_WRITE_RESP_REQ:
    break;

   case IST_READ_RESP_REQ_CMD:
    if (size==1) {
     process_filters(IST_POSTCALL,IST_READ_RESP_REQ_CMD,ptr,buf,size,res);
     int cmd=Memory.read_byte(buf);

     if (cmd==Conax.CMD_READ_RESP) {
      istate=IST_READ_RESP_BODY;
     }
    }
    break;

   case IST_READ_RESP_BODY:
    process_filters(IST_POSTCALL,IST_READ_RESP_BODY,ptr,buf,size,res);

    Utils.memcpy(buf,cur_resp,size);
    cur_resp_size=size;

    ready=true;
    istate=IST_READY;

    access_unlock();

    process_watches(IST_RESPONSE,cur_resp,cur_resp_size);
    break;
  }
 }

 public void pre_write(int ptr,int fd,int buf,int size) {
  nextstate=istate;

  switch(istate) {
   case IST_UNKNOWN:
   case IST_WRITE_REQ_HDR:
    if (size==Conax.CMD_REQ_HDR_SIZE) {
     int cmd=Memory.read_byte(buf+1);
     if (cmd!=Conax.CMD_READ_RESP) {
      access_lock();
      cur_cmd=cmd;
      cur_P0=Memory.read_byte(buf+2);
      cur_P1=Memory.read_byte(buf+3);
      cur_req_size=Conax.CMD_REQ_HDR_SIZE;

      Utils.memcpy(buf,cur_req,Conax.CMD_REQ_HDR_SIZE);

      process_filters(IST_PRECALL,IST_WRITE_REQ_HDR,ptr,buf,size,0);
      nextstate=IST_READ_REQ_CMD;
     }
    }
    break;
   case IST_READ_REQ_CMD:
    break;
   case IST_WRITE_REQ_BODY:
    Utils.memcpy(buf,cur_req+cur_req_size,size);
    cur_req_size+=size;

    process_filters(IST_PRECALL,IST_WRITE_REQ_BODY,ptr,buf,size,0);
    process_watches(IST_REQUEST,cur_req,cur_req_size);

    nextstate=IST_READ_RESP_CODE1;
    break;
   case IST_READ_RESP_CODE1:
    break;
   case IST_READ_RESP_CODE2:
    break;
   case IST_WRITE_RESP_REQ:
    if (size==Conax.CMD_REQ_HDR_SIZE) {
     int cmd=Memory.read_byte(buf+1);
     if (cmd==Conax.CMD_READ_RESP) {
      process_filters(IST_PRECALL,IST_WRITE_RESP_REQ,ptr,buf,size,0);
      nextstate=IST_READ_RESP_REQ_CMD;
     }
    }
    break;
   case IST_READ_RESP_REQ_CMD:
    break;
   case IST_READ_RESP_BODY:
    break;
  }
 }

 public void post_write(int ptr,int fd,int buf,int size,int res) {
  process_filters(IST_POSTCALL,istate,ptr,buf,size,res);
  istate=nextstate;
 }

 public int pre_call(int ptr,int[] args) {     
  invoke_original=true;

  if (hermes) {
   if (scfd>0) {
    if (args[0]==scfd) {
     if (mid()==read_mid) pre_read(ptr,args[0],args[1],args[2]);
      else pre_write(ptr,args[0],args[1],args[2]);
    }
   } else {
    if (mid()==write_mid) {
     if (args[2]==Conax.CMD_REQ_HDR_SIZE) {
      int cmd=Memory.read_byte(args[1]);
      if (cmd==Conax.CLASS) {
       scfd=args[0];
      }      
     }
    }
   }
  } else

  if (carbo) {
   if (scfd>0) {
    if (args[0]==scfd) {
     if (mid()==read_mid) pre_read(ptr,args[0],args[1],args[2]);
      else pre_write(ptr,args[0],args[1],args[2]);
    }
   } else {
    scfd=args[0];
   }
  }

  if (invoke_original) return ApiHandler.INVOKE_ORIGINAL;
   else return ApiHandler.SKIP_ORIGINAL;
 }

 public void post_call(int ptr,int[] args,int[] res) {
  if (hermes) {
   if (scfd>0) {
    if (args[0]==scfd) {
     if (mid()==read_mid) post_read(ptr,args[0],args[1],args[2],res[0]);
      else post_write(ptr,args[0],args[1],args[2],res[0]);
    }
   }
  } else

  if (carbo) {
   if (scfd>0) {
    if (args[0]==scfd) {
     if (mid()==read_mid) post_read(ptr,args[0],args[1],args[2],res[0]);
      else post_write(ptr,args[0],args[1],args[2],res[0]);  
    }
   }
  }
 }

 public static void start() {
  if (hermes) {
   ApiMonitor.add(MONITOR_READ_NAME,"read",HANDLER_CLASS);
   ApiMonitor.add(MONITOR_WRITE_NAME,"write",HANDLER_CLASS);
  } else

  if (carbo) {
   int handle=Dl.open_flags("libstd_drv_smartcard.so",Dl.RTLD_NOW|Dl.RTLD_NODELETE);
   int sym=Dl.getsym(handle,"SC_Read");

   int scm_base=ELFUtils.elf_base(sym);  

   ELFUtils eu=new ELFUtils(scm_base);
   int pread=eu.lookup_got_entry("read");
   int pwrite=eu.lookup_got_entry("write");

   read_mid=ApiMonitor.add(MONITOR_READ_NAME,pread,HANDLER_CLASS);
   write_mid=ApiMonitor.add(MONITOR_WRITE_NAME,pwrite,HANDLER_CLASS);
  }

  ApiHandler rhandler=ApiMonitor.lookup_handler(read_mid);

  if (rhandler!=null) {
   org_read=rhandler.orghandler();
  }  

  ApiHandler whandler=ApiMonitor.lookup_handler(write_mid);

  if (whandler!=null) {
   org_write=rhandler.orghandler();
  }  
 }

 public static void free_response(int resp[]) {
  if (resp[IDX_DATA]!=0) {
   nc.free(resp[IDX_DATA]);
  }
 }

 public static void read_fully(int fd,int buf,int cnt) {
  int pos=0;

  while(cnt>0) {
   int read=Syscall.read(fd,buf+pos,cnt);

   if (read>0) {
    cnt-=read;
    pos+=read;
   }      
  }

  Utils.sleep(10);
 }

 public static void write_fully(int fd,int buf,int cnt) {
  int pos=0;

  while(cnt>0) {
   int written=Syscall.write(fd,buf+pos,cnt);

   if (written>0) {
    cnt-=written;
    pos+=written;
   }      
  }

  Utils.sleep(10);
 }


 public static int sc_write(int buf,int size) {
  int res=-1;
  int fd=-1;

  if (hermes) {
   fd=scfd;
  } else

  if (carbo) {
   fd=scfd;
  }

  if (fd>0) {
   write_fully(fd,buf,size);
  }

  return size;
 }

 public static int sc_read(int buf,int size) {
  int res=-1;
  int fd=-1;

  if (hermes) {
   fd=scfd;
  } else

  if (carbo) {
   fd=scfd;
  }

  if (fd>0) {
   read_fully(fd,buf,size);
  }

  return size;
 }

 public static int[] send_request(int req,int size) {
  int resp[]=new int[2];
  int rsize=0;
  int len=0;
  int buf=0;

  while(!ready) Utils.sleep(10);

  if (size<5) return null;

  access_lock();

  sc_write(req,5);
  sc_read(tmp,1);
  sc_write(req+5,size-5);
  sc_read(tmp,1);
  sc_read(tmp+1,1);

  if (Memory.read_byte(tmp)==Conax.CMD_RET_RESP) {
   len=Memory.read_byte(tmp+1);

   Memory.write_byte(req_resp+0x04,len);

   sc_write(req_resp,5);

   sc_read(tmp,1);

   buf=nc.malloc(len);

   sc_read(buf,len);
  } else {
   len=2;   

   buf=nc.malloc(len);
   Utils.memcpy(tmp,buf,2);
  }

  access_unlock();

  resp[IDX_DATA]=buf;
  resp[IDX_SIZE]=len;

  return resp;
 }
}
