#!/usr/bin/ruby -w # # Copyright (C) 1999-2014 Rockstar Games. All Rights Reserved. # require 'getoptlong' require 'Logger' require 'singleton' require 'fileutils' require 'csv' require 'pp' require 'zlib' require 'sys/proctable' include Sys def message_box(message, title, type) # Create a Windows MessageBox. # 0 = 'OK' Button # 1 = 'OK' 'Cancel' Buttons # 2 = 'Abort' 'Retry' 'Ignore' Buttons # 3 = 'Yes' 'No' 'Cancel' Buttons # 4 = 'Yes' 'No' Buttons # 5 = 'Retry' 'Cancel' Buttons # 6 = 'Cancel' 'Try Again' 'Continue' ####################### # 16 = 'OK' Button with 'Error' Symbol # ... SEE ABOVE EXAMPLES # 22 = 'Cancel' 'Try Again' 'Continue' Buttons with 'Error' ####################################### # 32 = 'OK' Button with 'Question' Symbol # ... SEE ABOVE EXAMPLES # 38 = 'Cancel' 'Try Again' 'Continue' Buttons with 'Question' ######################################### # 48 = 'OK' Button with 'Warning' Symbol # ... SEE ABOVE EXAMPLES # 54 = 'Cancel' 'Try Again' 'Continue' Buttons with 'Warning' ######################################## # 64 = 'OK' Button with 'Info' Symbol # ... SEE ABOVE EXAMPLES # 70 = 'Cancel' 'Try Again' 'Continue' Buttons with 'Info' ###################################### mb = Win32API.new("user32", "MessageBox", ['i','p','p','i'], 'i') mb.call(0, message, title, type) end # Setting up RAGE_DIR = ENV['RAGE_DIR'] RS_CODEBRANCH = ENV['RS_CODEBRANCH'] RS_BUILDBRANCH = ENV['RS_BUILDBRANCH'] RS_TOOLSROOT = ENV['RS_TOOLSROOT'] USER_PROFILE = ENV['USERPROFILE'] RAGE_3RDPARTY = ENV['RAGE_3RDPARTY'].gsub("\\", "/") USER_DESKTOP = USER_PROFILE + "/Desktop" GAME = "GTAV" X86DIR = ENV['ProgramFiles(x86)'] RS_TITLE = ENV['RS_TITLE'] || GAME PROTECTION_PATH = RS_TOOLSROOT + "/script/coding/protection" GENERATED_PATH = PROTECTION_PATH + "/generated" GUARDSCRIPT_PATH = PROTECTION_PATH + "/gtav_pc.gsml" ARXAN_VERSION = "11.0.0" GUARDIT_VERSION_DIR = "/" + ARXAN_VERSION GUARDIT_ROOT = RAGE_3RDPARTY + "/bin/Arxan/GuardIT" + GUARDIT_VERSION_DIR GUARDIT_PATH = GUARDIT_ROOT + "/plugins/com.arxan.guardit_" + ARXAN_VERSION COMMON_RUBY_PATH = RS_TOOLSROOT + "/script/coding/protection/common/ruby" DUMPBIN_PATH = "dumpbin.exe" $LOAD_PATH << COMMON_RUBY_PATH ENV['PATH'] = ENV['PATH'] + ";" + ENV['ProgramFiles(x86)'] + "/Microsoft Visual Studio 11.0/VC/bin;" + ENV['ProgramFiles(x86)'] + "/Microsoft Visual Studio 11.0/Common7/IDE" require 'RsgLog' # Defines LOG_NAME = "" DEFAULT_GUARD_PATTERN_DEBUGGING = "" DEFAULT_GUARD_DEBUGGING = false DEFAULT_OBFUSCATION_LEVEL = "2" DEFAULT_VERSION = 0 DEFAULT_VERBOSE = false DEFAULT_DEBUG = false DEFAULT_RELEASE = "" DEFAULT_OVERRIDE = "" DEFAULT_ARCH = "x64" DEFAULT_CLOBBER = false DEFAULT_PORTCULLIS = false DEFAULT_FASTPROTECT = false DEFAULT_NETWORK_TAMPERFY = false CURR_TIME = Time.now.strftime("%Y%m%d.%H.%M.%S") DEFAULT_TAMPERFY = false DEFAULT_GUARD_DISABLE = false RANDOM_NUMBER = rand(2147483647).to_s GUARD_OBFUSCATION_CONFIG_FILE= PROTECTION_PATH + "/guardobfuscations.txt" # # # Make sure that our generated directory is created # First our protection path begin Dir.mkdir(PROTECTION_PATH) rescue Exception =>e puts e.message end # Then our generated path begin Dir.mkdir(GENERATED_PATH) rescue Exception =>e puts e.message end class MultiDelegator def initialize(*targets) @targets = targets end def self.delegate(*methods) methods.each do |m| define_method(m) do |*args| @targets.map { |t| t.send(m, *args) } end end self end class < # LOG_SEVERITY_LEVEL = Logger::INFO log = RsgLog::RsgLog.instance() log.SetParameters(GENERATED_PATH,CURR_TIME, LOG_SEVERITY_LEVEL) log.info(" _ _ _ _ _ _ _ _ _ _ _ _ _ "); log.info(" / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ "); log.info(" ( R | o | c | k | s | t | a | r ) ( G | a | m | e | s )"); log.info(" \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ "); log.info(" Protection Script"); log.info(""); # # class Parameters attr_reader :verbose attr_reader :verbose attr_reader :debug attr_reader :release attr_reader :guardscript attr_reader :guarditroot attr_reader :architecture attr_reader :clobber attr_reader :portcullis attr_reader :tamperfy attr_reader :inputOverride attr_reader :fastProtect attr_reader :guardDisable attr_reader :networkTamperfy attr_reader :guardDebugging attr_reader :guardPatternDebugging attr_reader :version def initialize @verbose = DEFAULT_VERBOSE #false @debug = DEFAULT_DEBUG #false @release = DEFAULT_RELEASE #nil @guardscript = GUARDSCRIPT_PATH @guarditroot = GUARDIT_PATH @architecture = DEFAULT_ARCH @clobber = DEFAULT_CLOBBER @portcullis = DEFAULT_PORTCULLIS @tamperfy = DEFAULT_TAMPERFY @inputOverride = DEFAULT_OVERRIDE @fastProtect = DEFAULT_FASTPROTECT @guardDisable = DEFAULT_GUARD_DISABLE @guardPatternDebugging = DEFAULT_GUARD_PATTERN_DEBUGGING @networkTamperfy = DEFAULT_NETWORK_TAMPERFY @version = DEFAULT_VERSION @opts = GetoptLong.new( [ '--arch', '-a', GetoptLong::REQUIRED_ARGUMENT ], [ '--clobber', '-c', GetoptLong::NO_ARGUMENT], [ '--debug', '-d', GetoptLong::NO_ARGUMENT], [ '--version', '-e', GetoptLong::REQUIRED_ARGUMENT], [ '--fastProtect', '-f', GetoptLong::NO_ARGUMENT], [ '--guardscript', '-g', GetoptLong::REQUIRED_ARGUMENT ], [ '--help', '-h', GetoptLong::NO_ARGUMENT], [ '--inputOverride', '-i', GetoptLong::REQUIRED_ARGUMENT], [ '--portcullis', '-l', GetoptLong::NO_ARGUMENT], [ '--networkTamperfy', '-n', GetoptLong::NO_ARGUMENT], [ '--guarditroot', '-p', GetoptLong::REQUIRED_ARGUMENT ], [ '--release', '-r', GetoptLong::REQUIRED_ARGUMENT ], [ '--tamperfy', '-t', GetoptLong::NO_ARGUMENT], [ '--verbose', '-v', GetoptLong::NO_ARGUMENT], [ '--guardDisable', '-x', GetoptLong::NO_ARGUMENT], [ '--guardDebugging', '-y', GetoptLong::NO_ARGUMENT], [ '--guardPatternDebugging', '-z', GetoptLong::REQUIRED_ARGUMENT] ) end def PrintHelp() RsgLog.instance().info("Usage: ruby protectit.rb -r [options] "); RsgLog.instance().info(" "); RsgLog.instance().info("-h --help Display this "); RsgLog.instance().info("-v --verbose Displays the output from GuardIT to stdout "); RsgLog.instance().info("-d --debug Displays how the command line arguments are parsed, and"); RsgLog.instance().info(" skips running GuardIT "); RsgLog.instance().info("-r --release Required. Supported releases are: "); RsgLog.instance().info(" bankrelease beta debug final release master "); RsgLog.instance().info(" steambeta steamfinal diskrelease diskfinal "); RsgLog.instance().info(" noupdate "); RsgLog.instance().info(" preloaderrelease preloaderfinal preloaderdebug "); RsgLog.instance().info(" Default is bankrelease "); RsgLog.instance().info("-g -guardscript Override the guardscript to specify your own. "); RsgLog.instance().info(" Default is generated from "); RsgLog.instance().info(" $(RS_BUILDBRANCH)/protection/gtav.gsml "); RsgLog.instance().info("-p -guarditroot Specifies the root path of GuardIT, in case somebody "); RsgLog.instance().info(" wants to be rebellious and install it somewhere else "); RsgLog.instance().info("-a -arch Specifies the architecture of GuardIT used. "); RsgLog.instance().info(" Supported architectures are: x64 (default) x86 "); RsgLog.instance().info(" Default is x64 "); RsgLog.instance().info("-c --clobber Takes the generated protected executable and overwrites"); RsgLog.instance().info(" the copy in $(RS_BUILDBRANCH). NOT SUPPORTED YET "); RsgLog.instance().info("-l --portcullis Enables the Portcullis Protections "); RsgLog.instance().info("-t --tamperfy Swaps out notify user tamper actions with fo-real ones "); RsgLog.instance().info("-x --guardDisable Swaps out notify user tamper actions with empty "); RsgLog.instance().info(" function calls. Cannot be used with -t "); RsgLog.instance().info("-n --networkTamperfy Swaps out notify user tamper actions with telemetry "); RsgLog.instance().info(" actions "); end def ParseArguments() @opts.each do |opt, arg| case opt when '--help' PrintHelp() exit when '--verbose' @verbose = true when '--debug' @debug = true when '--release' case arg when /release/i,/bankrelease/i,/debug/i,/final/i,/beta/i, /master/i, /steamfinal/i, /steammaster/i, /steambeta/i, /diskrelease/i, /diskfinal/i, /preloaderfinal/i, /preloadrrelease/i, /noupdate/i @release = arg else RsgLog.instance().error("Invalid release [#{arg}] specified") exit end when '--guardscript' @guardscript = arg when '--guarditroot' @guarditroot = arg when '--tamperfy' @tamperfy = true when '--networkTamperfy' @networkTamperfy = true when '--inputOverride' @inputOverride = arg when '--guardDebugging' @guardDebugging = true when '--guardPatternDebugging' @guardPatternDebugging = arg when '--guardDisable' @guardDisable = true when '--version' @version = arg when '--arch' case arg when /x64/i,/x86/i @architecture = arg else RsgLog.instance().error("Invalid architecture [#{arg}] specified") exit end when '--clobber' @clobber = true when '--fastProtect' @fastProtect = true when '--portcullis' @portcullis = true end end end end class GuardScriptFiller @@knownFunctionsThatCrashSteam = ["DirectInput8Create", "CreateProcessW", "CreateProcessA", "LoadLibraryExW","LoadLibraryExA", "LoadLibraryW", "LoadLibraryA", "GetStringTypeW", "ShellExecuteExA", "DispatchMessageA", "PeekMessageA", "SetCapture", "ReleaseCapture","ClipCursor","GetKeyState","ShowCursor","SetCursorPos", "GetCursorPos", "GetRawInputData", "RegisterRawInputDevices", "FreeLibrary","CoCreateInstance", "PeekMessageW", "DispatchMessageW","Direct3DCreate9", "GetMessageW", "SetCursor","GetAsyncKeyState", "GetKeyboardState"] class DllItem attr_reader :dllName attr_reader :functions def initialize @dllName = "" @functions = Array.new end def setName(name) @dllName = name end def addFunction(name) @functions.push(name) end end def self.GetGuardObfuscationCommands(fileContents) enabledGuards = Array.new foundMatchingGuard = false guardName = "" fileContents.each_line do |line| if foundMatchingGuard == true if line =~ // if line =~ /true/ # If it's disabled, just move on else # Otherwise lets add it to our queue # RsgLog::RsgLog.instance().debug("Pushing #{guardName}") if guardName =~ /OBF/ RsgLog::RsgLog.instance().warn("Not adding obfuscation guard #{guardName} to the mix") else enabledGuards.push(guardName) end end # Indicate that we've found it, so we don't # Add it later foundMatchingGuard = false end elsif line =~ /guard_cmd name/ if foundMatchingGuard == true # This means that the previously found guard # didn't have an associated disable command # which means we should add it to our queue #enabledGuards.push(guardName) if guardName =~ /OBF/ RsgLog::RsgLog.instance().warn("Not adding obfuscation guard #{guardName} to the mix") else enabledGuards.push(guardName) end end foundMatchingGuard = true guardCmdRegex = line.scan(/.*guard_cmd name=\"(.*)\".*/) # Now lets fetch the guard name guardCmdRegex.each { |match| guardName = match[0] } end end returnString = "" # Read JSON from a file, iterate over objects imageCommandRegex = fileContents.scan(/.*image_cmd name=\"(.*)\".*/) imageCommandName = imageCommandRegex[0] obfConfigFile = open(GUARD_OBFUSCATION_CONFIG_FILE) obfConfigContents = obfConfigFile.read obfConfigContents.each_line { |line| RsgLog::RsgLog.instance().debug("Reading #{line.strip}") lineParts = line.split(",") obfLevel = lineParts[0] currGuardArray = Array.new lineParts.drop(1).each do |guard| guard = guard.strip # do the same general thing to all elements except the first # RsgLog::RsgLog.instance().debug("Dropping #{guard} from the main list for a custom obfuscation level") if enabledGuards.delete(guard) == nil # Indicate that we couldn't find it RsgLog::RsgLog.instance().warn("Couldn't find #{guard} in the list of enabled guards") else currGuardArray.push(guard) end end returnString = returnString + CreateObfuscationCommand(obfLevel,currGuardArray,imageCommandName) } pp enabledGuards returnString = returnString + CreateObfuscationCommand(2, enabledGuards,imageCommandName) return returnString end def self.CreateObfuscationCommand(level, guards, imageCommandName) if guards.length == 0 return "" end returnString = "" nowTime = rand(2147483647).to_s returnString = returnString + returnString = returnString + "\n" returnString = returnString + "\n " returnString = returnString + "\n " returnString = returnString + "\n " # Now lets get programmatic with it, adding all of our queued guards guards.each { |guard| if guard =~ /OBF/ RsgLog::RsgLog.instance().warn("Not adding obfuscation guard #{guard} to the mix") else returnString = returnString + "\n " returnString = returnString + "\n #{imageCommandName}" returnString = returnString + "\n #{guard}" returnString = returnString + "\n " end } # Close out our included regions returnString = returnString + "\n " returnString = returnString + "\n " returnString = returnString + "\n #{level}" returnString = returnString + "\n false" returnString = returnString + "\n false" returnString = returnString + "\n " returnString = returnString + "\n" end def self.GetRevolvingProbability(fileContents) guardCmdRegex = fileContents.scan(/.*guard_cmd name=\"(.*REVOLVING.*)\".*/) RsgLog::RsgLog.instance().info("Found #{guardCmdRegex.length} number of revolving guards") return (1.5 / guardCmdRegex.length).to_s[0..5] end def self.GetRevolvingLocationIndex(fileContents) end def self.GetRevolvingGuards(fileContents) guardCmdRegex = fileContents.scan(/.*guard_cmd name=\"(.*REVOLVING.*)\".*/) returnString = "" guardCmdRegex.each { |match| returnString = returnString + "\n " returnString = returnString + "\n gtav_pc" returnString = returnString + "\n #{match}" returnString = returnString + "\n " } return returnString end def self.GetQAPrintCommands(fileContents) # guardCmdRegex = fileContents.scan(/.*guard_cmd name=\"(.*(CHK|ADB).*)\".*/) returnString = "" guardCmdRegex.each { |match| if match[0] =~ /PRTCL/ next end returnString = returnString + "\n" returnString = returnString + "\n " returnString = returnString + "\n gtav_pc" returnString = returnString + "\n #{match[0]}" returnString = returnString + "\n has_run_expected" returnString = returnString + "\n " returnString = returnString + "\n false" returnString = returnString + "\n" if match[1] == "CHK" returnString = returnString + "\n" returnString = returnString + "\n " returnString = returnString + "\n gtav_pc" returnString = returnString + "\n #{match[0]}" returnString = returnString + "\n key" returnString = returnString + "\n " returnString = returnString + "\n false" returnString = returnString + "\n" end } return returnString end def self.GetHookDebug(fileContents, parameters) inputFileRegex = Regexp.new('(.*)<\/input_file>') matchData = inputFileRegex.match(fileContents) filePath = matchData[1] cmd = "#{DUMPBIN_PATH} /imports #{filePath}" output = `#{cmd}` dllItems = Array.new currDLLItem = DllItem.new() output.each_line do |line| if line =~ /Import Address Table/i next elsif line =~ /Import Name Table/i next elsif line =~ /time date stamp/i next elsif line =~ /Index of first forwarder reference/i next elsif line =~ /Ordinal/i next elsif line =~ /Dump of file/i next elsif line.strip.empty? next elsif line =~ /\.dll/i if currDLLItem.dllName != "" dllItems.push(currDLLItem) end currDLLItem = DllItem.new() currDLLItem.setName(line.strip) elsif line.strip == "Summary" break elsif currDLLItem.dllName.empty? == false functionLine = line.strip functionNameRegex = Regexp.new('[0-9a-fA-F]* (.*)') matchData = functionNameRegex.match(functionLine) if matchData == nil # puts "No bueno RegExp for #{functionLine}" else functionName = matchData[1] currDLLItem.addFunction(functionName) end else next end end trueOutput = "" potentialOutput = "" guardIteration = 0 socialClubInstallationLocations = ["MAIN_INITGAMEHEAP_ADDALLOCATOR"] gameInstallationLocations = ["CSYSTEM_INIT_INIT_MEMORY_BUCKETS"] launcherInstallationLocations = ["CFSM_CONSTRUCTOR"] dllItems.each { |x| if x.functions.size > 0 x.functions.each { |y| if filePath =~ /socialclub.dll/i || parameters.release =~ /steam/i || parameters.guardscript =~ /launcher/i # Handle things that make Steam sad. if @@knownFunctionsThatCrashSteam.include?(y) == false if filePath =~ /socialclub.dll/i trueOutput = trueOutput + "\n " elsif parameters.guardscript =~ /launcher/i trueOutput = trueOutput + "\n " else trueOutput = trueOutput + "\n " end trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n " if filePath =~ /socialclub.dll/i trueOutput = trueOutput + "\n SCDLL_#{guardIteration}_HKD_A " elsif parameters.guardscript =~ /launcher/i trueOutput = trueOutput + "\n LNCHR_#{guardIteration}_HKD_A " else trueOutput = trueOutput + "\n GTAVB_#{guardIteration}_HKD_A " end trueOutput = trueOutput + "\n #{guardIteration} " trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n " if filePath =~ /socialclub.dll/i socialClubInstallationLocations.each { |installationLoc| trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n rgsc " trueOutput = trueOutput + "\n #{installationLoc} " trueOutput = trueOutput + "\n " } elsif parameters.guardscript =~ /launcher/i launcherInstallationLocations.each { |installationLoc| trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n launcher " trueOutput = trueOutput + "\n #{installationLoc} " trueOutput = trueOutput + "\n " } else gameInstallationLocations.each { |installationLoc| trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n gtav_pc " trueOutput = trueOutput + "\n #{installationLoc} " trueOutput = trueOutput + "\n " } end trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n #{x.dllName}" trueOutput = trueOutput + "\n #{y}" trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n default " trueOutput = trueOutput + "\n 0 " trueOutput = trueOutput + "\n false " trueOutput = trueOutput + "\n false " trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n " guardIteration +=1 end end } end } trueOutput = trueOutput + "\n" return trueOutput end def self.GetHookTargets(fileContents, parameters) inputFileRegex = Regexp.new('(.*)<\/input_file>') matchData = inputFileRegex.match(fileContents) if parameters.inputOverride == "" filePath = matchData[1] else filePath = parameters.inputOverride end cmd = "#{DUMPBIN_PATH} /imports \"#{filePath}\"" output = `#{cmd}` dllItems = Array.new currDLLItem = DllItem.new() output.each_line do |line| if line =~ /Import Address Table/i next elsif line =~ /Import Name Table/i next elsif line =~ /time date stamp/i next elsif line =~ /Index of first forwarder reference/i next elsif line =~ /Ordinal/i next elsif line =~ /Dump of file/i next elsif line.strip.empty? next elsif line =~ /\.dll/i if currDLLItem.dllName != "" dllItems.push(currDLLItem) end currDLLItem = DllItem.new() currDLLItem.setName(line.strip) elsif line.strip == "Summary" break elsif currDLLItem.dllName.empty? == false functionLine = line.strip functionNameRegex = Regexp.new('[0-9a-fA-F]* (.*)') matchData = functionNameRegex.match(functionLine) if matchData == nil # puts "No bueno RegExp for #{functionLine}" else functionName = matchData[1] currDLLItem.addFunction(functionName) end else next end end trueOutput = "" potentialOutput = "" dllItems.each { |x| if x.functions.size > 0 x.functions.each { |y| skip = false if filePath =~ /socialclub.dll/i || parameters.release =~ /steam/i # Handle things that make Steam sad. if @@knownFunctionsThatCrashSteam.include?(y) skip = true end end if !skip trueOutput = trueOutput + "\n " trueOutput = trueOutput + "\n #{x.dllName}" trueOutput = trueOutput + "\n #{y}" trueOutput = trueOutput + "\n " end } end } trueOutput = trueOutput + "\n" return trueOutput end def self.Populate(fileContents, parameters) newContents = fileContents newContents = newContents.gsub("$OBFUSCATION_LEVEL", DEFAULT_OBFUSCATION_LEVEL ) newContents = newContents.gsub("$RS_CODEBRANCH", RS_CODEBRANCH ) if parameters.release =~ /release/i || parameters.release =~ /noupdate/i newContents = newContents.gsub("$TINYXML_RELEASE", "release" ) elsif parameters.release =~ /debug/i newContents = newContents.gsub("$TINYXML_RELEASE", "debug" ) elsif parameters.release =~ /final/i newContents = newContents.gsub("$TINYXML_RELEASE", "final" ) end #if parameters.guardscript =~ /prelauncher/i || parameters.guardscript =~ /bootstrap/i # # The space afterwarsd is intentional. This prevents it from # # picking up "prerelease" and setting it as release. # if parameters.release =~ /release/i # newContents = newContents.gsub("$RELEASE", "release" ) # elsif parameters.release =~ /debug/i # newContents = newContents.gsub("$RELEASE", "debug" ) # elsif parameters.release =~ /final/i # newContents = newContents.gsub("$RELEASE", "final" ) # end #else£ #if parameters.release =~ /^master/i # newContents = newContents.gsub("$FRAMEWORKRELEASE", "master" ) # newContents = newContents.gsub("$RELEASE", "mastereuropean" ) # newContents = newContents.gsub("$FILENAME", "final_eu" ) #else #if parameters.release =~ /mastereuropean/i # newContents = newContents.gsub("$FRAMEWORKRELEASE", "master" ) #else # newContents = newContents.gsub("$FRAMEWORKRELEASE", parameters.release ) #end newContents = newContents.gsub("$RELEASE", parameters.release ) newContents = newContents.gsub("$FILENAME", parameters.release ) #end #end if parameters.inputOverride != "" newContents = newContents.gsub(/.*<\/input_file>/ , "#{parameters.inputOverride}") end newContents = newContents.gsub("$RS_BUILDBRANCH", RS_BUILDBRANCH ) newContents = newContents.gsub("$RAGE_DIR", RAGE_DIR ) newContents = newContents.gsub("$RAGE_3RDPARTY", RAGE_3RDPARTY ) newContents = newContents.gsub("$GUARDIT_PATH", GUARDIT_PATH ) newContents = newContents.gsub("$PROTECTION_PATH", PROTECTION_PATH ) newContents = newContents.gsub("$CURR_TIME", CURR_TIME ) newContents = newContents.gsub("$RS_TITLE", RS_TITLE ) newContents = newContents.gsub("$PORTCULLIS_ENABLE", parameters.portcullis ? "false" : "true" ) newContents = newContents.gsub("$ARXAN_VERSION", ARXAN_VERSION ) newContents = newContents.gsub("\\", "/" ) newContents = newContents.gsub("$BACKSLASH", "\\" ) newContents = newContents.gsub("$SEED_VALUE", RANDOM_NUMBER ) newContents = newContents.gsub("$FAST_PROTECT", parameters.fastProtect ? "true" : "false" ) newContents = newContents.gsub("$HOOK_TARGETS", GetHookTargets(newContents, parameters)) newContents = newContents.gsub("$HOOK_GUARD_DEBUG", GetHookDebug(newContents, parameters)) newContents = newContents.gsub("$REVOLVING_GUARDS", GetRevolvingGuards(newContents)) newContents = newContents.gsub("$QA_PRINT_COMMANDS", GetQAPrintCommands(newContents)) newContents = newContents.gsub("$GUARD_OBFUSCATION", GetGuardObfuscationCommands(newContents)) locationNumbers = (1..99).to_a.shuffle revolvingIdx = 0 while(newContents.include?("$REVOLVING_LOCATION_GEN") == true) formattedNum = sprintf("%03d", locationNumbers[revolvingIdx]) revolvingIdx = (revolvingIdx % 99) + 1 newContents = newContents.sub("$REVOLVING_LOCATION_GEN", "REVOLVING_LOCATION_#{formattedNum}") end # Now we need to replace the DWORDS to something reliable. # This is where having a version number is important # in addition to being able to parse out the token randomDwordsArray = fileContents.scan(/.*\$RANDOM_DWORD_(.*)<\/action_param>/) randomDwordsArray += fileContents.scan(/.*\$RANDOM_DWORD_(.*)<\/exit_code>/) versionCrc = Zlib.crc32(parameters.version.to_s) RsgLog::RsgLog.instance().info("Version XOR Value: #{versionCrc.to_i.to_s(16)}"); randomDwordsArray.each { |randomDwordIdentifier| identifierCrc = Zlib.crc32(randomDwordIdentifier[0]) output = "#{randomDwordIdentifier},0x#{identifierCrc.to_s(16).rjust(8,"0")}" # puts output thisCrc = identifierCrc.to_i ^ versionCrc.to_i #puts "[A,#{randomDwordIdentifier}]\t\t[I,#{identifierCrc.to_i.to_s(16)}]\t\t[V,#{versionCrc.to_i.to_s(16)}]\t\t[T,#{thisCrc.to_i.to_s(16)}]" RsgLog::RsgLog.instance().info("#{randomDwordIdentifier},#{identifierCrc.to_i.to_s(16)},#{thisCrc.to_i.to_s(16)}") #puts "Replacing $RANDOM_DWORD_#{randomDwordIdentifier} with 0x#{thisCrc.to_i.to_s(16)}" # This is a total hack. It wsn't matching only the whole string, so I added the < qualifier to be sure # that strings only replaced themselves newContents = newContents.gsub("$RANDOM_DWORD_#{randomDwordIdentifier}<","0x#{thisCrc.to_i.to_s(16)}<") } #doneSubbing = false #while doneSubbing == false do # randStr = rand(2147483647).to_s(16) # test = newContents.sub!("$RANDOM_DWORD", "0x#{randStr}" ) # if test == nil # doneSubbing = true # else # newContents = test # end #end netContentsPatternDebugging = "" if parameters.guardDebugging == true newContents = newContents.gsub("false", "true") elsif parameters.guardPatternDebugging != "" foundDebug = false foundMatchingGuard = false patternSplit = parameters.guardPatternDebugging.split(";") newContents.each_line do |line| if foundMatchingGuard == true if line =~ // netContentsPatternDebugging+=line.gsub("false", "true") foundMatchingGuard = false else netContentsPatternDebugging+=line end elsif line =~ /guard_cmd name/ patternSplit.each { |p| if line =~ /#{p}/ foundMatchingGuard = true end } netContentsPatternDebugging+=line else netContentsPatternDebugging+=line end end newContents = netContentsPatternDebugging end newContentsDeux = "" if parameters.tamperfy == true # Lets open our CSV file #@tamperHash = {} #CSV.foreach("ProtectIT.TamperActions.csv") do |row| # guardName, guardAction = row # @tamperHash[guardName] = guardAction #end foundNotify = false oldMessage = "" oldTabs = "" # What are the steps here? # Iterate line by line, looking for things we care about newContents.each_line do |line| if foundNotify == true if line =~ /(.*)<\/message>/ oldMessage = $1 elsif line =~ /<\/notify_user>/ # This is where we do the work of indexing into our hash to see what tamper action we should take # In the event to which one doesn't exist, it probably seems safe to default to fail # First lets set this to false so we still get the rest of the file foundNotify = false # Good, now lets see if the thing exists in our hash newContentsDeux +="#{oldTabs}\n" end elsif line =~ /(.*)/ oldTabs = $1 foundNotify = true else newContentsDeux +=line end end return newContentsDeux elsif (parameters.guardDisable == true || parameters.networkTamperfy == true) foundNotify = false oldMessage = "" oldTabs = "" maintainOriginal = true bufferOld = "" # What are the steps here? # Iterate line by line, looking for things we care about newContents.each_line do |line| if foundNotify == true bufferOld+=line if line =~ /(.*)<\/message>/ oldMessage = $1 elsif line =~ /<\/notify_user>/ # First lets set this to false so we still get the rest of the file foundNotify = false # This is where we do the work of indexing into our hash to see what tamper action we should take # In the event to which one doesn't exist, it probably seems safe to default to fail if oldMessage =~ /enc/i newContentsDeux+=bufferOld else randomNumber = rand(2147483647).to_s(16).upcase # Good, now lets see if the thing exists in our hash newContentsDeux +="#{oldTabs}\n" if parameters.networkTamperfy == true newContentsDeux +="#{oldTabs}\tTamperAction_SendTelemetry\n" else newContentsDeux +="#{oldTabs}\tTamperAction_None\n" end newContentsDeux +="#{oldTabs}\t0x#{randomNumber}\n" newContentsDeux +="#{oldTabs}\n" end end elsif line =~ /(.*)/ bufferOld = line oldTabs = $1 foundNotify = true else newContentsDeux +=line end end return newContentsDeux else return newContents end end end # log.info("Parsing arguments") parameters = Parameters.new parameters.ParseArguments() if parameters.release == "" parameters.PrintHelp() log.error("Invalid release [#{parameters.release}] specified") exit end if parameters.debug == true log.SetLevel(Logger::DEBUG) end if parameters.tamperfy && parameters.guardDisable && parameters.networkTamperfy parameters.PrintHelp() log.error("Cannot use both the tamperfy (-t) or networktamperfy (-n) and the guardDisable (-x), as you're trying to use two different flags to affect the tamper actions. You'll have to chose one. Exiting."); exit elsif parameters.tamperfy && parameters.networkTamperfy parameters.PrintHelp() log.error("Cannot use both the tamperfy (-t) and the networktamperfy (-n) , as you're trying to use two different flags to affect the tamper actions. You'll have to chose one. Exiting."); exit end # (.*)\<\/input_file/.match(populatedGsContents) outputFile = /output_file\>(.*)\<\/output_file/.match(populatedGsContents) # Ensure that our output file isn't currently running (saves me some time) ProcTable.ps{ |proc_struct| if outputFile == proc_struct.comm log.error("The application you're trying to protect is currently running. #{proc_struct.comm}") end } # Fixing a bug with GuardIT breaking successfulProtection = false log.info("Command to issue to console:\n#{guardItCommand}") if parameters.debug == false IO.popen guardItCommand do |fd| until fd.eof? myline = fd.readline if parameters.verbose == true log.info(myline, false) else log.debug(myline, false) end if myline =~ /Protection completed successfully/ successfulProtection = true end end end # # The below commented code is used to test the scenario for error codes so that I can check when 0 is returned and when it's not # The guardscript that's at that location is local to Amir Soofi, and should return 0 on success # #else # guardItCommand = guardItCommand + "/guardit.exe /pm " + "\"C:/Users/asoofi/Documents/Visual Studio 2012/Projects/guardItTesting/guardItTesting.gsml\"" # #guardItCommand = guardItCommand + "/guardit.exe /pm " + "\"C:/asoofi/Documents/Visual Studio 2012/Projects/guardItTesting/guardItTesting.gsml\"" # # # if parameters.verbose == false # guardItOutput = `#{guardItCommand}` # else # IO.popen guardItCommand do |fd| # until fd.eof? # log.info(fd.readline, false) # end # end # end end log.info("Generated Guardscript: " + GENERATED_PATH + "/" + generatedGsFileName) log.info("GuardIT Finished!") if ENV['USERNAME'] == "asoofi" #message_box("Protection Finished!", "Danger Will Robinson!",48) end # Handle the case where I'm protecting a DLL binaryType = ".exe" if inputFile[1].include? ".dll" binaryType = ".dll" end errorCode = $?.to_i if errorCode !=0 && successfulProtection == true log.warn("No bueno: error code came back non-zero (#{errorCode}); forcing it") errorCode = 0 end if errorCode == 0 || successfulProtection == true # Copy over artifacts, because it's just easier to do here. inputFileBaseName = File.basename(inputFile[1], binaryType) inputDir = File.dirname(inputFile[1]) artifactsICareAbout = [".map", ".pdb"] artifactsICareAbout.push(binaryType) # Build the files I care about # Assume that it houses the contents of the files we care about artifactsICareAbout.each {|artifact| localPath = inputDir + "/" + inputFileBaseName + artifact begin destinationPath = GENERATED_PATH + "/" + CURR_TIME + "." + inputFileBaseName + ".unprotected" + artifact FileUtils.cp(localPath, destinationPath) rescue Exception =>e log.warn(e.message) end } if parameters.clobber == true if binaryType == ".exe" log.info("Overwriting original input file") log.debug("Clobbering input ") log.debug("\t" + inputFile[1]) log.debug("with") log.debug("\t" + outputFile[1]) if parameters.debug == false FileUtils.cp(inputFile[1], inputFile[1] + ".unprotected" + binaryType) FileUtils.cp(outputFile[1],inputFile[1]) end elsif binaryType == ".dll" # Here we're handling Social Club DLL # Bit of a dirty hack, but I need it for the distribution tonight dllOutputDirectory = USER_DESKTOP + "/Rockstar Games" if parameters.architecture == "x64" if parameters.release.downcase == "debug" dllOutputDirectory = dllOutputDirectory + "/x64/Social Club Debug" elsif parameters.release.downcase == "release" dllOutputDirectory = dllOutputDirectory + "/x64/Social Club" end elsif parameters.architecture == "x86" if parameters.release.downcase == "debug" dllOutputDirectory = dllOutputDirectory + "/x86/Social Club Debug" elsif parameters.release.downcase == "release" dllOutputDirectory = dllOutputDirectory + "/x86/Social Club" end end if File.directory?(dllOutputDirectory) == false begin Dir.mkdir(dllOutputDirectory) rescue Exception =>e puts e.message end end dllOutputFile = dllOutputDirectory + "/socialclub.dll" log.info("Writing file to " + dllOutputFile) if parameters.debug == false FileUtils.cp(outputFile[1],dllOutputFile) end end else if parameters.debug == false FileUtils.cp(outputFile[1],inputFile[1].gsub(binaryType, ".protected" + binaryType)) end end log.info("Protection Complete & Successful!") else begin log.info("Protection Failed!") FileUtils.cp(inputFile[1], outputFile[1].gsub(binaryType, ".failed" + binaryType)) rescue Exception => e log.info("Unable to recover artifacts: " + e.message) end end log.info("Return code: " + errorCode.to_s) # Play a sound print "\a" exit errorCode