Files
2025-09-29 00:52:08 +02:00

1178 lines
55 KiB
Ruby
Executable File

#!/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"
# </globals>
# <loggingSetup>
# 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 <<self
alias to new
end
end
# </loggingSetup>
# <banner>
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("");
# </banner>
# <commandLineArguments>
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 <release> [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 =~ /<disable>/
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<guard_cmd name=\"GUARD_OBFUSCATIONS_LEVEL_#{level}_#{nowTime}\">"
returnString = returnString + "\n <obfuscation>"
returnString = returnString + "\n <protected_range>"
returnString = returnString + "\n <include>"
# 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 <range>"
returnString = returnString + "\n <image_name>#{imageCommandName}</image_name>"
returnString = returnString + "\n <guard_name>#{guard}</guard_name>"
returnString = returnString + "\n </range>"
end
}
# Close out our included regions
returnString = returnString + "\n </include>"
returnString = returnString + "\n </protected_range>"
returnString = returnString + "\n <level>#{level}</level>"
returnString = returnString + "\n <debug>false</debug>"
returnString = returnString + "\n <disable>false</disable>"
returnString = returnString + "\n </obfuscation>"
returnString = returnString + "\n</guard_cmd>"
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 <range>"
returnString = returnString + "\n <image_name>gtav_pc</image_name>"
returnString = returnString + "\n <guard_name>#{match}</guard_name>"
returnString = returnString + "\n </range>"
}
return returnString
end
def self.GetQAPrintCommands(fileContents)
#<guard_cmd name="CHLRP_0013_CHK_AF">
guardCmdRegex = fileContents.scan(/.*guard_cmd name=\"(.*(CHK|ADB).*)\".*/)
returnString = ""
guardCmdRegex.each { |match|
if match[0] =~ /PRTCL/
next
end
returnString = returnString + "\n<print_cmd name=\"print_#{match[0]}_has_run\">"
returnString = returnString + "\n <range>"
returnString = returnString + "\n <image_name>gtav_pc</image_name>"
returnString = returnString + "\n <guard_name>#{match[0]}</guard_name>"
returnString = returnString + "\n <guard_symbol_name>has_run_expected</guard_symbol_name>"
returnString = returnString + "\n </range>"
returnString = returnString + "\n <disable>false</disable>"
returnString = returnString + "\n</print_cmd>"
if match[1] == "CHK"
returnString = returnString + "\n<print_cmd name=\"print_#{match[0]}_key\">"
returnString = returnString + "\n <range>"
returnString = returnString + "\n <image_name>gtav_pc</image_name>"
returnString = returnString + "\n <guard_name>#{match[0]}</guard_name>"
returnString = returnString + "\n <guard_symbol_name>key</guard_symbol_name>"
returnString = returnString + "\n </range>"
returnString = returnString + "\n <disable>false</disable>"
returnString = returnString + "\n</print_cmd>"
end
}
return returnString
end
def self.GetHookDebug(fileContents, parameters)
inputFileRegex = Regexp.new('<input_file>(.*)<\/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 <guard_cmd name=\"SCDLL_#{guardIteration}_HKD_A\"> "
elsif parameters.guardscript =~ /launcher/i
trueOutput = trueOutput + "\n <guard_cmd name=\"LNCHR_#{guardIteration}_HKD_A\"> "
else
trueOutput = trueOutput + "\n <guard_cmd name=\"GTAVB_#{guardIteration}_HKD_A\"> "
end
trueOutput = trueOutput + "\n <hook_detection> "
trueOutput = trueOutput + "\n <action> "
trueOutput = trueOutput + "\n <notify_user> "
if filePath =~ /socialclub.dll/i
trueOutput = trueOutput + "\n <message>SCDLL_#{guardIteration}_HKD_A</message> "
elsif parameters.guardscript =~ /launcher/i
trueOutput = trueOutput + "\n <message>LNCHR_#{guardIteration}_HKD_A</message> "
else
trueOutput = trueOutput + "\n <message>GTAVB_#{guardIteration}_HKD_A</message> "
end
trueOutput = trueOutput + "\n <exit_code>#{guardIteration}</exit_code> "
trueOutput = trueOutput + "\n </notify_user> "
trueOutput = trueOutput + "\n </action> "
trueOutput = trueOutput + "\n <invocation> "
trueOutput = trueOutput + "\n <locationSet> "
trueOutput = trueOutput + "\n <include> "
if filePath =~ /socialclub.dll/i
socialClubInstallationLocations.each { |installationLoc|
trueOutput = trueOutput + "\n <location> "
trueOutput = trueOutput + "\n <image_name>rgsc</image_name> "
trueOutput = trueOutput + "\n <location_name>#{installationLoc}</location_name> "
trueOutput = trueOutput + "\n </location> "
}
elsif parameters.guardscript =~ /launcher/i
launcherInstallationLocations.each { |installationLoc|
trueOutput = trueOutput + "\n <location> "
trueOutput = trueOutput + "\n <image_name>launcher</image_name> "
trueOutput = trueOutput + "\n <location_name>#{installationLoc}</location_name> "
trueOutput = trueOutput + "\n </location> "
}
else
gameInstallationLocations.each { |installationLoc|
trueOutput = trueOutput + "\n <location> "
trueOutput = trueOutput + "\n <image_name>gtav_pc</image_name> "
trueOutput = trueOutput + "\n <location_name>#{installationLoc}</location_name> "
trueOutput = trueOutput + "\n </location> "
}
end
trueOutput = trueOutput + "\n </include> "
trueOutput = trueOutput + "\n </locationSet> "
trueOutput = trueOutput + "\n </invocation> "
trueOutput = trueOutput + "\n <hook_targets> "
trueOutput = trueOutput + "\n <target>"
trueOutput = trueOutput + "\n <module_name>#{x.dllName}</module_name>"
trueOutput = trueOutput + "\n <function_name>#{y}</function_name>"
trueOutput = trueOutput + "\n </target>"
trueOutput = trueOutput + "\n </hook_targets> "
trueOutput = trueOutput + "\n <mode>default</mode> "
trueOutput = trueOutput + "\n <instances>0</instances> "
trueOutput = trueOutput + "\n <debug>false</debug> "
trueOutput = trueOutput + "\n <disable>false</disable> "
trueOutput = trueOutput + "\n </hook_detection> "
trueOutput = trueOutput + "\n </guard_cmd> "
guardIteration +=1
end
end
}
end
}
trueOutput = trueOutput + "\n"
return trueOutput
end
def self.GetHookTargets(fileContents, parameters)
inputFileRegex = Regexp.new('<input_file>(.*)<\/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 <target>"
trueOutput = trueOutput + "\n <module_name>#{x.dllName}</module_name>"
trueOutput = trueOutput + "\n <function_name>#{y}</function_name>"
trueOutput = trueOutput + "\n </target>"
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>.*<\/input_file>/ , "<input_file>#{parameters.inputOverride}</input_file>")
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("<debug>false</debug>", "<debug>true</debug>")
elsif parameters.guardPatternDebugging != ""
foundDebug = false
foundMatchingGuard = false
patternSplit = parameters.guardPatternDebugging.split(";")
newContents.each_line do |line|
if foundMatchingGuard == true
if line =~ /<debug>/
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>(.*)<\/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}<fail/>\n"
end
elsif line =~ /(.*)<notify_user>/
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>(.*)<\/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}<call>\n"
if parameters.networkTamperfy == true
newContentsDeux +="#{oldTabs}\t<action_proc>TamperAction_SendTelemetry</action_proc>\n"
else
newContentsDeux +="#{oldTabs}\t<action_proc>TamperAction_None</action_proc>\n"
end
newContentsDeux +="#{oldTabs}\t<action_param>0x#{randomNumber}</action_param>\n"
newContentsDeux +="#{oldTabs}</call>\n"
end
end
elsif line =~ /(.*)<notify_user>/
bufferOld = line
oldTabs = $1
foundNotify = true
else
newContentsDeux +=line
end
end
return newContentsDeux
else
return newContents
end
end
end
# </commandLineArguments>
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
# <print command line arguments for good measure"
log.debug("Verbose \t#{parameters.verbose}");
log.debug("Debug \t#{parameters.debug}");
log.debug("Guardscript \t#{parameters.guardscript}");
log.debug("GuardIT Root \t#{parameters.guarditroot}");
log.debug("Release \t#{parameters.release}");
log.debug("Architecture \t#{parameters.architecture}");
log.debug("Clobber \t#{parameters.clobber}");
log.debug("Portcullis \t#{parameters.portcullis}");
log.debug("Tamperfy \t#{parameters.tamperfy}");
log.debug("Network Tamp \t#{parameters.networkTamperfy}");
log.debug("Guard Disable\t#{parameters.guardDisable}");
# Now we actually have to do the work of building the guardscript together
# Open up the file
log.info("Opening template guardscript")
templateGs = File.new(parameters.guardscript, "r")
if(templateGs)
else
log.error("Unable to open path to guardscript '#{parameters.guardscript}'")
end
templateGsContents = templateGs.read
# Now lets fill it in with our tokens
log.info("Generating guardscript")
populatedGsContents = GuardScriptFiller.Populate(templateGsContents,parameters)
# Now that we've got the generated, lets write it out to disk.
# Create a file name so that we can debug it accordingly
generatedGsFileName = CURR_TIME + ".gsml"
log.info("Writing out generated guardscript to disk")
File.open(GENERATED_PATH + "/" + generatedGsFileName,'w') do |outputFile|
outputFile.puts populatedGsContents
end
log.info("Writing out have list to disk")
# First lets create the command
if parameters.debug == false
generatedHaveFileName = CURR_TIME + ".haveList"
haveCommand = "p4 have #{RS_CODEBRANCH}/..."
haveOutput = `#{haveCommand}`
File.open(GENERATED_PATH + "/" + generatedHaveFileName,'w') do |outputFile|
outputFile.puts haveOutput
end
end
log.info("Running GuardIT")
guardItCommand = GUARDIT_PATH
#if parameters.architecture == "x64"
guardItCommand = guardItCommand + "/bin64"
#else
# guardItCommand = guardItCommand + "/bin"
#end
extraParameters = ""
# HACKHACKHACKALERT
# We only want to add the disable section check for the game, and not the launcher.
#if parameters.release =~ /steam/i && parameters.guardscript =~ /gtav/i
# extraParameters = extraParameters + "/disablesectioncheck "
#end
# HACKHACKHACKALERT
# Supporting /arxmap for final builds
extraParameters = extraParameters + "/arxmap /arxinstmap /winfixedcalls "
if parameters.fastProtect == false
extraParameters = extraParameters + "/i6 "
end
guardItCommand = guardItCommand + "/guardit.exe /pm #{extraParameters}" + GENERATED_PATH + "/" + generatedGsFileName
# Get our input and output file names
inputFile = /input_file\>(.*)\<\/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