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

public class HermesUpgrade {
 public static class Info {
  String desc;
  String date;
  int sector_cnt;
  int key[];

  public Info(String desc,String date,int sector_cnt,int key[]) {
   this.desc=desc;
   this.date=date;
   this.sector_cnt=sector_cnt;
   this.key=key;
  }
 }

 public static final int MAX_SECTION_COUNT     = 256;
 public static final int MAX_BLOCK_COUNT       = 256;

 public static final int DEFAULT_SECTION_COUNT = 0xb0;
 public static final int DEFAULT_BLOCK_COUNT   = 0x52;

 public static final int DEFAULT_KEY_SIZE      = 0x20;

 public static final int PAD = 20;

 public static Info extract_info(byte data[]) {
  Info info=null;
 
  try {
   int pos=10;

   /* check for "WLDO" magic string */
   if ((data[pos]==0x57)&&(data[pos+1]==0x4c)&&(data[pos+2]==0x44)&&(data[pos+3]==0x4f)) {
    pos+=9;

    /* read description             */
    int size=((int)data[pos])&0xff;
    pos++;

    byte tab[]=new byte[size];
    for(int i=0;i<size;i++) {
     tab[i]=data[pos+i];
    }

    String desc=new String(tab);
    pos+=size;

    int sector_cnt=((((int)data[pos])&0xff)<<8)|(((int)data[pos+1])&0xff);

    pos+=6;

    /* read date and time           */
    size=((int)data[pos])&0xff;
    pos++;

    tab=new byte[size];
    for(int i=0;i<size;i++) {
     tab[i]=data[pos+i];
    }

    String date=new String(tab);
    pos+=size;

    /* skip desc                    */
    size=((int)data[pos])&0xff;
    pos++;

    pos+=size;

    /* read key                     */    
    pos+=2+1;

    int key[]=new int[DEFAULT_KEY_SIZE>>2];

    for(int i=0;i<key.length;i++) {
     int b1=(((int)data[pos+4*i])&0xff);
     int b2=(((int)data[pos+4*i+1])&0xff)<<8;
     int b3=(((int)data[pos+4*i+2])&0xff)<<16;
     int b4=(((int)data[pos+4*i+3])&0xff)<<24;

     int val=b1|b2|b3|b4;
     key[i]=val;
    }

    info=new Info(desc,date,sector_cnt,key);
   }
  } catch(Throwable t) {}

  return info;    
 }

 private static int hermes_rewrite_image(byte image[],int block_total[],int block_size[],int sec_max) {
  int total_size=0;

  int dstpos=0;

  for(int sec_id=1;sec_id<sec_max;sec_id++) {
   int block_cnt=block_total[sec_id];

   if (block_cnt<=2) continue;

   for(int i=1;i<block_cnt;i++) {
    int off=sec_id*HermesUpgrade.DEFAULT_BLOCK_COUNT*MPEGDefs.MAX_SECTION_SIZE+i*MPEGDefs.MAX_SECTION_SIZE;

    int idx=(sec_id<<8)+i;
    int size=block_size[idx];

    if (i==(block_cnt-1)) {
      size-=0x40;     
    }

    System.arraycopy(image,off,image,dstpos,size);
    dstpos+=size;
    total_size+=size;
   }
  }

  return total_size;
 }


 public static void hermes_decrypt_image(byte image[],int size,int key[]) {
  byte xor_tab[]=new byte[0x40];
  byte tmp_tab[]=new byte[0x40];

  for(int i=0;i<size;i+=0x40) {

   if ((i%0x20000)==0) {
    for(int j=0;j<0x40;j++) {
     xor_tab[j]=(byte)(0xff-j);
    }
   }
	 
   for(int j=0;j<0x40;j++) {
    tmp_tab[j]=image[i+j];
   }
  
   Twofish.decrypt_ecb(image,i,0x40,key,key.length);

   for(int j=0;j<0x40;j++) {
    image[i+j]^=xor_tab[j%0x40];
   }

   for(int j=0;j<0x40;j++) {
    xor_tab[j]=tmp_tab[j];
   }
  }
 }

 public static void hermes_write_image(String name,byte image[],int size) {
  try {
   FileOutputStream fos=new FileOutputStream("upgrade.dat",false);
   fos.write(image,0,size);
   fos.close();
  } catch(Throwable t) {}
 }

 public static void process_data(byte image[],int block_total[],int block_size[],int sec_max,int key[]) {
  try {
   System.out.println("- processing image");

   int size=hermes_rewrite_image(image,block_total,block_size,sec_max);

   String s=ConsoleUtils.padded_string("  total size:",PAD)+"0x"+ConsoleUtils.hex_value(size,8);
   System.out.println(s);

   System.out.println("- decrypting image");

   s=ConsoleUtils.padded_string("  algorithm:",PAD)+"tweaked Twofish ECB "+key.length*32+"bit";
   System.out.println(s);

   s=ConsoleUtils.padded_string("  key:",PAD);
   System.out.println(s);

   byte kdata[]=new byte[key.length<<2];

   for(int i=0;i<key.length;i++) {
    kdata[4*i]=(byte)(key[i]&0xff);
    kdata[4*i+1]=(byte)((key[i]>>8)&0xff);
    kdata[4*i+2]=(byte)((key[i]>>16)&0xff);
    kdata[4*i+3]=(byte)((key[i]>>24)&0xff);
   }

   ConsoleIf.print_buf(kdata);

   hermes_decrypt_image(image,size,key);

   String filename="upgrade.dat";

   System.out.println("- saving image");

   hermes_write_image("upgrade.dat",image,size);

   s=ConsoleUtils.padded_string("  output:",PAD)+filename;
   System.out.println(s);
  } catch(Throwable t) {}
 }

}
