前言:
作为一款高难度即时战略游戏,确实对笔者游戏水平造成了严峻的挑战。
无奈笔者游戏水平低下,75%难度过不去咋办呢。
这时笔者想起了一句老话:水平不够科技来凑
但是工具也不是那么好写,They Are Billion的更新还是挺多的,如果以内存补丁的形式修改游戏数据显然每一个版本你都要写一个工具,不能通用。
后来发现游戏是基于.net平台写的,Net程序反编译没有难点。那么反编译修改游戏代码可不可行呢。当看到几千错误需要手工修复时果断放弃了这个想法。
更换他的dll组件,这个可能造成游戏更新后无法运行之类的bug
那么,只有注入这么一条道路。这篇讲的就是.Net程序的注入
原料:
《They Are Billion》
下载地址:去Steam下载
《dnspy》歪果仁写的反编译工具
下载地址:https://www.softpedia.com/get/Programming/Debuggers-Decompilers-Dissasemblers/dnSpy.shtml
《FastWin32》类库 国内牛人作品
平台:Winform
首先让我们打开cmd
建立一个TABHelper文件夹 然后将FastWin32工程clone到本地。
稍等一小会就下载完毕了
打开工程我们可以看到2个例子和一个类库,先不管这些例子里写的是什么我们先做一些有用的工作
我们新建一个winform项目就行了,在上面拖几个控件。然后设置一些资源。
好的,没有2分钟界面就拖好了。
接下来我们要考虑一些其他功能比如热键,以HotKey为关键字上百度搜果然一下就有结果了,接下来我们做一些比较常见的事情。Ctrl+C Ctrl+V
然后我们注册一下全局热键
接下来是功能部分,.Net程序的注入和PE可执行程序的注入略有不同,需要通过mscoree.dll加载CLR环境。这里就用到国人的一个作品FastWin32,封装了.Net程序注入所需要用到的所有东西。
注入时需要注意的东西:
1、需要判断注入程序与目标程序运行环境是否相同 x86 ? x64?
2、CLR版本是否相同 2.0? 4.0?
接下来让我们用一段简短的代码来实现功能
首先添加一个窗体用于等待注入
写下一段代码
[C#] 纯文本查看 复制代码 try
{
while (!this.IsDisposed)
{
var traget = Process.GetProcesses().FirstOrDefault(x => x.ProcessName == "TheyAreBillions");
if (traget != null && ExLink.Is64bit(traget, out bool isX64))
{
if (isX64 == SelfIsX64)
{
string TypeName = "TABHelper.Program";
string Method = "SneakOn";
string args = null;
Injector.InjectManaged((uint)traget.Id, Application.ExecutablePath, TypeName, Method, args);
break;
}
else
{
this.Invoke(new EventHandler(delegate { try { this.label1.Text = SelfIsX64 ? "请运行x86版本" : "请运行x64版本"; } catch { } }));
break;
}
}
Thread.Sleep(1000);
}
this.Invoke(new EventHandler(delegate { try { this.Close(); } catch { } }));
}
catch { }
在入口处写下对应方法以区别注入和正常启动
[C#] 纯文本查看 复制代码 static class Program
{
public static bool IsSneak { get; set; } = false;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
static int SneakOn(string args)
{
IsSneak = true;
Main();
return 0;
}
}
好的 接下来我们先测试一下能不能成功注入
先启动软件
再启动游戏
等待一会就会发现已经成功注入了
在任务管理器中可以看到They Are Billion进程拥有了两个窗体
TabHelper就是我们注入的窗体
既然已经注入了目标进程,那么其中的变量就可以通过反射或dynamic的方式获取到,这里我选择用反射方法,毕竟性能并不是小工具应该关心的事情
我们打开dnSpy 找到游戏目录的主程序 拖入
可以正常看到一些信息 类名 方法等等,部分资源被混淆加密了,不过显然不是我们要关心的部分。
搜索Wood 木头 很容易就找到了 ZXLevelState 这个类
分析一下这个类发现被实例化在GameState这个类中作为属性公开
在GameState中有一个静态方法返回了这个类,那么我们只要反射调用这个静态方法就行了
名字长得奇怪?没关系,我们通过遍历GameState中的所有方法根据返回类型和参数个数来定位它
[AppleScript] 纯文本查看 复制代码 public object LevelState => GameState == null ? null : GameState.GetProperty("LevelState");
public object GameState => GetGameState == null ? null : GetGameState.Invoke(null, null);
MethodInfo getGameState = null;
MethodInfo GetGameState => getGameState != null ? getGameState :
(getGameState = GetMethod("ZXGameState", null, "ZXGameState"));
public MethodInfo GetMethod(string ClassName, string MethodName, string ReturnType,int args = -1)
{
foreach (var x in AppDomain.CurrentDomain.GetAssemblies())
foreach (var a in x.GetTypes())
if (a.Name == ClassName)
foreach (var b in a.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
if ((MethodName == null || b.Name == MethodName) && (ReturnType == null | b.ReturnType.Name == ReturnType) && (args == -1 || b.GetParameters().Length == args))
return b;
return null;
}
运行一下,已经成功访问到了游戏运行数据
最后效果图:
案例源码(解压密码:bbs.cskin.net):
|