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

public class CWFaker extends SCFilter {
 public static final int M_FAKE_INVALID_ECM = 0x01;
 public static final int M_FAKE_VALID_ECM   = 0x02;

 public static final int M_FAKE_ALL_ECM     = (M_FAKE_INVALID_ECM|M_FAKE_VALID_ECM);

 public static final int ECM_SIZE           = 0x22;

 public static final int STAMP_OFFSET       = ECMServer.STAMP_OFFSET+3;

 public static final int CW_FAKE_VALUE      = 0x11111111;

 private static CWFaker cwfaker;

 private static boolean disable_req   = false;
 private static boolean faking_ecm    = false;

 private int res1;
 private int res2;

 private int org_res2;

 private int mode;

 private int cur_key   = 0;
 private int ecm_stamp = 0;

 public static int calc_stamp(int ecm) {
  int b1=Memory.read_byte(ecm+STAMP_OFFSET);
  int b2=Memory.read_byte(ecm+STAMP_OFFSET+1);
  int b3=Memory.read_byte(ecm+STAMP_OFFSET+2);
  int b4=Memory.read_byte(ecm+STAMP_OFFSET+3);

  int stamp=((b1&0xff)<<24)|((b2&0xff)<<16)|((b3&0xff)<<8)|((b4&0xff));

  return stamp;
 }

 public CWFaker(int mode) {
  super("CW Faker",ST_ALL);
  this.mode=mode;
 }

 private boolean faking_ecm() {
  return faking_ecm;
 }

 public boolean match(int prefix,int state,int cmd,int ptr,int buf,int size) {
  if (cmd==Conax.CMD_ECM) {
   if (prefix==SCInterface.IST_PRECALL) {
    switch(state) {
     case SCInterface.IST_WRITE_REQ_HDR:
     case SCInterface.IST_WRITE_REQ_BODY:
     case SCInterface.IST_WRITE_RESP_REQ:
     case SCInterface.IST_READ_RESP_REQ_CMD:
     case SCInterface.IST_READ_RESP_BODY:
      return true;
     default:
      return false;
    }
   } else {
    switch(state) {
     case SCInterface.IST_READ_RESP_CODE1:
     case SCInterface.IST_READ_RESP_CODE2:
     case SCInterface.IST_WRITE_RESP_REQ:
     case SCInterface.IST_READ_RESP_REQ_CMD:
     case SCInterface.IST_READ_RESP_BODY:
      return true;
     default:
      return false;
    }
   }

  } else return false;
 }

 private static void put_cw_word(int ptr,int cw) {
  int b1=(cw>>24)&0xff;
  int b2=(cw>>16)&0xff;;
  int b3=(cw>>8)&0xff;;
  int b4=(cw)&0xff;;

  Memory.write_byte(ptr+0x00,b4);
  Memory.write_byte(ptr+0x01,b3);
  Memory.write_byte(ptr+0x02,b2);
  Memory.write_byte(ptr+0x03,b1);
 }

 public static void put_cw(int ptr,int cw1,int cw2,int idx) {
  Memory.write_byte(ptr+0x00,0x25);
  Memory.write_byte(ptr+0x01,0x0d);
  Memory.write_byte(ptr+0x02,0x60);
  Memory.write_byte(ptr+0x03,0xf0);
  Memory.write_byte(ptr+0x04,idx);
  Memory.write_byte(ptr+0x05,0x00);
  Memory.write_byte(ptr+0x06,0x00);

  put_cw_word(ptr+0x07,cw1);
  put_cw_word(ptr+0x0b,cw2);
 }

 public boolean action(int prefix,int state,int cmd,int ptr,int buf,int size) {
  boolean res=true;

  switch(prefix) {
   case SCInterface.IST_PRECALL:
    if (state==SCInterface.IST_WRITE_REQ_HDR) {
     faking_ecm=false;

     if (disable_req) {
      cwfaker.disable();
      disable_req=false;
     }
    } else

    if (state==SCInterface.IST_WRITE_REQ_BODY) {
     ecm_stamp=calc_stamp(buf);

     if (ecm_stamp==ECMServer.ecm_stamp()) {
      faking_ecm=true;

      cur_key=Memory.read_byte(buf+0x03)&0x01;

      byte tab[]=Utils.buf2barray(buf,size);
      ApiMonitor.log("SCARD MATCHING ECM stamp = "+Utils.hex_value(ecm_stamp,8));
     }
    } else

    if (state==SCInterface.IST_WRITE_RESP_REQ) {
     if (faking_ecm()) {
      Memory.write_byte(buf+4,org_res2);
     }
    } else

    if (state==SCInterface.IST_READ_RESP_REQ_CMD) {
     if (faking_ecm()) {
     }
    } else

    if (state==SCInterface.IST_READ_RESP_BODY) {
     if (faking_ecm()) {
      if (res2!=org_res2) {
       ApiHandler.change_arg(ptr,2,org_res2+2);
      }
     }
    }
    break;

   case SCInterface.IST_POSTCALL:
    if (state==SCInterface.IST_READ_RESP_CODE1) {
     if (faking_ecm()) {
      Memory.write_byte(buf,Conax.CMD_RET_RESP);
     }

     res1=Memory.read_byte(buf);
    } else

    if (state==SCInterface.IST_READ_RESP_CODE2) {
     org_res2=res2=Memory.read_byte(buf);

     if (faking_ecm()) {
      Memory.write_byte(buf,ECM_SIZE);
      res2=Memory.read_byte(buf);
     }
    } else

    if (state==SCInterface.IST_WRITE_RESP_REQ) {
     if (faking_ecm()) {
      Memory.write_byte(buf+4,res2);
      change_res(ptr,Conax.CMD_REQ_HDR_SIZE);
     }
    } else

    if (state==SCInterface.IST_READ_RESP_REQ_CMD) {
     if (faking_ecm()) {
      Memory.write_byte(buf,Conax.CMD_READ_RESP);
      change_res(ptr,1);
     }
    } else

    if (state==SCInterface.IST_READ_RESP_BODY) {
     if (faking_ecm()) {
      CW.inc_word();

      int cur_cw=CW.cur_word();
      int nxt_cw=CW.nxt_word();

      if (cur_key==0) {
       put_cw(buf+0x00,CW_FAKE_VALUE,nxt_cw,1);
       put_cw(buf+0x0f,CW_FAKE_VALUE,cur_cw,0);
      } else {
       put_cw(buf+0x00,CW_FAKE_VALUE,cur_cw,1);
       put_cw(buf+0x0f,CW_FAKE_VALUE,nxt_cw,0);
      }

      Memory.write_byte(buf+0x1e,0x31);
      Memory.write_byte(buf+0x1f,0x02);
      Memory.write_byte(buf+0x20,0x40);
      Memory.write_byte(buf+0x21,0x00);
      Memory.write_byte(buf+0x22,0x90);
      Memory.write_byte(buf+0x23,0x00);

      change_res(ptr,ECM_SIZE+2);   

      CW.set_scard_info(ecm_stamp,cur_cw,true);
      CW.set_scard_info(ecm_stamp,nxt_cw,false);

      faking_ecm=false;
     }
    }
    break;
  }

  return res;
 }

 public static void start(int mode) {
  if (cwfaker==null) {
   cwfaker=new CWFaker(mode);
   SCInterface.add_filter(cwfaker);
  }

  cwfaker.enable();
 }

 public static void stop() {
  if (cwfaker!=null) {
   disable_req=true;
  }
 }

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

}
