Pinenut666 发表于 2021-4-8 15:45

恶灵进化[Evolve Legacy] 游戏局域网化脱离Steam的思路记录以及一些问题

提前说明:
这个做法我个人认为不算私服,它更倾向于是绕过验证,因为游戏联机本身是不靠和服务器的通信的(心跳检测不算)
新人第一次发帖,难免错漏,如有错误,麻烦管理了,这里先立好准备挨打!
Evolve(国内叫进化,不是那个方舟生存进化),是之前一个4V1不对称对抗的游戏,相信不少人看到过游戏风云的视频,或者看到过B站里的介绍。
由于2K运营不善,在几次折腾之后,游戏关服,Steam下架。我从朋友口中听说了这个游戏,顺着痕迹搜了一下,搜到了一篇文章(来源3DM):


于是再度研究,发现这个游戏“不使用第三方服务器”(这里埋个伏笔,2K太损了),而是调用Steam的P2P功能来联机,这一下就激发了我的兴趣:

取到一份游戏本体,然后使用Goldberg,Smartsteamemu等软件进行模拟,不就可以进行局域网联机了吗?

说干就干,在哥们(真哥们啊,国外discord的朋友)的帮助下我成功拿到一个账号得到了本体,之后开始了旷日持久的“破解战”!

阶段一:游戏和Steam的欺骗战
首先查看steam_api.dll的版本号:
版本较老!我使用了Smartsteamemu进行游戏模拟,游戏成功打开,单机游戏正常,但是当点选“MultiPlayer"时,游戏弹出提示:不能链接到服务器,请检查网络连接。
由于服务器已经关闭,我首先想到的是模拟有缺陷,故换上了Goldberg补丁(替换关键文件steam_api64.dll)结果,Goldberg双击游戏,直接链接到了Steam!
这使我心生警惕:游戏似乎并未使用steam_api64.dll进行游戏和Steam之间的通信?
在打好Goldberg补丁的前提下,我登陆哥们的账号,果然游戏顺利进入菜单,信息全部来源于正版。(后来测试发现,游戏会校验这个文件的正确与否,否则游戏无法打开,这个我们回头再说)
于是我改用Goldberg的steamclient版本,这个版本是虚拟自己为steam(个人理解),然后让游戏去链接它,于是避开了steam_api64.dll的验证,游戏成功进入 ->结果如旧,仍然是提示:不能链接到服务器,但是,Goldberg补丁使用,会无限卡住教程,看来单纯Steam部分已经被模拟的差不多了,一定有什么地方有了问题。

阶段二:网络验证的欺骗战
到这里我其实已经黔驴技穷,于是我首先考虑使用SmartSteamEmu。接下来考虑是不是真的有个服务器:使用mitmproxy,安装根证书后进行抓包,抓到了几个Post的包,返回的都是类似下面的json:

我点击多人游戏,等待到游戏结束,也没有什么类似验证的包。
于是我换上正版游戏监视,发现了一个包访问了dsx_1_b.4v1game.net/auth.php,它的请求是一个json文件,返回也是一个json文件:
(下图是返回的,由于游戏在打包,所以不好意思没办法上图,只能多口述,以后再补)

这个json文件显而易见是加密的,我多次测试发现它还会变化,所以我暂时放到了一边。
经过多次正版测试后我发现,该游戏会访问”两个“(为什么加引号呢?我们后文再说)网站,一个是上面说的dsx_1_b.4v1game.net,另一个是89oyxxxxx(具体没记),第二个多数时间是使用Post方法,收到的请求也都一致,有的时候会请求一个MicroPatch.pak,其他的请求大部分都是失败的,显然服务器关闭了他们也消失了。从这点判断,不难推出,第一个网站是验证游戏是否可以联机的,第二个网站应该之前用于传输更新文件。
但是为什么使用SmartSteamEmu的时候没有这个请求呢?
我翻看了auth.php的访问,发现它的请求部分有一串乱码(抱歉没法截图,以后有空补上),这串乱码经过比对(因为我监视了Steam和游戏,所以从Steam里发现了),是一个叫做SteamAuthTicket的东东。
这使我有所想法:
由于使用Goldberg的时候会无限卡住教程,但是相对来说,Goldberg补丁模拟较为完美,可能这个Ticket SmartSteamEmu没有模拟起来,但是Goldberg可以模拟?
但是Goldberg补丁使用,会无限卡住教程(游戏进入就会显示教程) 于是我想到一个办法:首先先用正版的云存档,然后将Goldberg的SteamID(唯一识别码)设置为正版的,之后将存档替换进去,于是这样”跳过“了教程,成功进入游戏。
我尝试点击多人游戏,等待一会儿后,游戏果然发送了请求,而且果不其然服务器返回了Error,403 Forbidden(大概就是识别不通过什么的)。

此时的我的考虑:它的发送一定会保存到内存里,所以我用CE(该游戏反调试,CE不知道为啥能用)修改那块内存改成正版的,然后返回一个正版用的回复Token,这样的话就能骗过去……
游戏一定有什么加盐啊,时间计算Token啊,多重加密啊……

事实上,我多想了{:1_924:}
在一次误操作的时候,我将Error的请求改成了正版的请求,结果:游戏就进!到!了!联!机!界!面!
(当时我人都傻了)
我当时还考虑是不是Token没过期,于是我日期往后调了一堆,结果照旧,说明这货估计的验证就是把那个返回的解密之后,判断了个Success啥的……(这段我很侥幸,如果有大佬还请不吝指教)
于是我使用mitmproxy的脚本功能,写下了如下代码:
(文件是直接用mitmproxy下载下来的)
multiheader = {"Content-Type": "application/json,charset=utf-8",
               "Cache-Control": "no-cache, no-store, must-revalidate",
               "Connection": "keep-alive",
               "Date": str(time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(time.time()))),
               "Expires": "0",
               "Server": "Pinenut-Crack/1.1",
               "Pragma": "no-cache",
               "X-Powered-By": "PHP/5.3.29"
               }
def evolvecrack(jsonfile, logstr, optheader, flow):
    print("[松子日志]:" + logstr)
    flow.response = http.HTTPResponse.make(
      200,# (optional) status code
      jsonfile,# (optional) content
      optheader)# (optional)


def readjson(jsonname):
    with open(os.path.abspath('.') + "\\EvolveCrack\\" + jsonname, encoding="utf-8") as f:
      return f.read()
    def request(self, flow) -> None:
      elif "auth.php" in flow.request.pretty_url:
            # This is for dsx-1-b.4v1game.net.
            authmulti = str(readjson("SteamLogin.json"))
            evolvecrack(authmulti, "Game are Trying to Go MultiPlayer.", multiheader, flow)
      elif "requestserver.php" in flow.request.pretty_url:
            # This is for dsx-1-b.4v1game.net.
            requestlb = str(readjson("requestserver.json"))
            evolvecrack(requestlb, "Game are Trying to Go MultiPlayer.", multiheader, flow)
之后我使用透明模式全局代{过}{滤}理,果然成功进入了游戏的联机界面。
之后我找了一个朋友测试,然后……我俩就无限卡加载了……
阶段三:本地验证的Trust战
确认不是网络问题后,我直接想到了”教程打不开“这件事。自己开了一句单人,果然,也是无限加载。
于是怀疑还有特殊的验证。(这里其实是请了国外的Nemirtingas大佬研究,他是Goldberg Emulator的参与开发者之一,非常感谢他,这边为了叙述方便,我将以我的视角来说明)使用OpenArk查看Evolve.exe仔细瞧瞧……
嗯?这是?:

网上搜索了一下这个,找到了API文档和CSDN论坛的:

”如何用WinVerifyTrust验证文件的数字签名?-CSDN论坛“
于是豁然开朗。看来是它会验证什么东西的数字签名。于是我下载了Nemirtingas Steam Emu(这里提这个是因为后面会讲到一个坑,这个是Nemirtingas大佬自己做的Steam Emulator,功能基本上就是Goldberg翻版)
确认了有数字签名……进入游戏……还是不行……
找大佬要了签名的根证书……安装信任……还是不行……
奇哉怪哉,明明有证书为什么不行?莫非有其他验证?
于是我细细比对了一下两个的证书:(这里为了复现当时的场景)

发现Nemirtingas给的注册的是sha256证书。会不会是因为这个呢?
我下载了一个Vsign签名工具,它自带一个测试的代码签名,一路安装,进行双签(SHA1 + SHA256,就是上图左边不打码那样) + 安装证书到信任, 一番操作之后,果然成功,游戏可以正常联机了!
但是这样的话每个人都得安装一个根证书,这对萌新很不友好,那么应该怎么办呢?当然是:干它!
Goldberg有一个功能是禁止程序联网,于是我下载了它的代码,根据它的Hook程序联网的代码,照着抄了一个自己的,然后开始准备编译:


一番操作之后代码和上面差不多,之后根据微软原文:

ZeroThe trust provider can use the interactive desktop to display its user interface.

让他返回0:

//Add Wintrust support:
static decltype(WinVerifyTrust)* pfnWinVerifyTrust;

LONG Mine_WinVerifyTrust(
    HWND   hwnd,
    GUID* pgActionID,
    LPVOID pWVTData
) {
    PRINT_DEBUG("Mine_WinVerifyTrust Hook Successfully.\n");
    return (LONG)0;
};

于是果然不再需要检查证书。大获全胜……了吗?
阶段四:2K的DLC验证战
游戏很成功的可以使用局域网联机了。但是我们都发现一个问题:新版本的游戏,它……不能解锁DLC。
一般游戏的DLC都是靠Steam的接口请求解锁的,但是Goldberg配置解锁失败。
此时我突然想起来之前看到的一些东西:
在全局的mitmproxy里,有几个提示:
xxxxx.2k.com 可能不信任你的证书(注:这里偷懒了,我没放原文,意思就是这个意思)
所以根据它的尿性,我大胆猜测:这货使用了证书固定技术,防了一手中间人攻击?!
……然后好吧,确实,因为人家把证书都放外面了……

里面全是吊销的证书。
网上搜了一下名字,感觉和这个有关:


/etc/pki/tls/certs/ca-bundle.crt 文件存储了各大证书颁发证的根证书交叉文件。curl 访问https网站时,会比对这个文件里的根证书。如果这个文件过老,那就是有新的根证书未加入到这个文件里。导致curl无法正常访问https网站“
也就是说里面放了一堆证书都是验证用的。这怎么办呢?
我想到了mitmproxy下载的时候有个”其他平台”可以下载一个证书,要不,换了试试?说干就干,顺手换掉,再折腾一下(挂梯子什么的),果然抓到了信息。
在一堆信息里筛选之后发现了这个东东:

(这里是改了的,之前都是一堆false)
我将返回值改成True,然后将ownersteamid改成破解的ID,果然解锁了全DLC!
(后来测试,这货好像有个steamid就能解锁……是多不走心……)
于是局域网化联机,胜利结束……了吗?
阶段五:易于使用的优化战
现在要想联机,我首先得管理员权限打开mitmproxy的透明模式,这样很不方便,有些软件是会不工作的。
所以考虑了一下,决定:搞成一键使用的!
在Goldberg里找到如下代码(橙色的框是我加的):

这样加了一段之后,就可以把请求重定向到任何我想要的IP和端口了!
但是它还是https呀,那怎么办呢?好说,摘掉它的HTTPS头:

之后跑了一下,发现:在这个模式下2K 服务器一点消息都没了……按理说你就算是不奏效,也不能一点消息都没吧?
查看了一下发现了这些代码:

这是ws2_32.dll的函数。我跟着日志搜了一下,发现2K的请求混在里面被拦截了(因为不是局域网IP会被拦截)
真是奇怪,它居然是靠ws2_32.dll来进行HTTPS工作的?(有没有大佬科普一下?我本人技术不高,这是啥操作?)
因为不懂这个,我摘不掉它的HTTPS头。但是我可以修改LAN_IP的判断,让他也重定向到我想要的IP和端口嘛!
一番操作之后……(各路大佬轻喷,我写的烂我知道,上面那段是Goldberg原文,下面有中文注释的那一行之后就是我干的了)
// I use this check to redirect the 2K check.
static bool is_lan_ip(const sockaddr *addr, int namelen)
{
    if (!namelen) return false;
      if (addr->sa_family == AF_INET) {
      struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
      unsigned char ip;
      memcpy(ip, &addr_in->sin_addr, sizeof(ip));
      PRINT_DEBUG("CHECK LAN IP %hhu.%hhu.%hhu.%hhu:%u\n", ip, ip, ip, ip, ntohs(addr_in->sin_port));
      if (is_adapter_ip(ip)) return true;
      if (ip == 127) return true;
      if (ip == 10) return true;
      if (ip == 192 && ip == 168) return true;
      if (ip == 169 && ip == 254 && ip != 0) return true;
      if (ip == 172 && ip >= 16 && ip <= 31) return true;
      if ((ip == 100) && ((ip & 0xC0) == 0x40)) return true;
      if (ip == 239) return true; //multicast
      if (ip == 0) return true; //Current network
      if (ip == 192 && (ip == 18 || ip == 19)) return true; //Used for benchmark testing of inter-network communications between two separate subnets.
      if (ip >= 224) return true; //ip multicast (224 - 239) future use (240.0.0.0–255.255.255.254) broadcast (255.255.255.255)
    } else if (addr->sa_family == AF_INET6) {
      struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;
      unsigned char ip;
      unsigned char zeroes = {};
      memcpy(ip, &addr_in6->sin6_addr, sizeof(ip));
      PRINT_DEBUG("CHECK LAN IP6 %hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu...%hhu\n", ip, ip, ip, ip, ip, ip, ip, ip, ip);
      if (((ip == 0xFF) && (ip < 3) && (ip == 1)) ||
      ((ip == 0xFE) && ((ip & 0xC0) == 0x80))) return true;
      if (memcmp(zeroes, ip, sizeof(ip)) == 0) return true;
      if (memcmp(zeroes, ip, sizeof(ip) - 1) == 0 && ip == 1) return true;
      if (ip == 0xff) return true; //multicast
      if (ip == 0xfc) return true; //unique local
      if (ip == 0xfd) return true; //unique local
      //TODO: ipv4 mapped?
    }
      //If not LAN IP, there will redirect.
    //不是LAN IP的,在这里会被拦截到
    if (addr->sa_family == AF_INET) {
      struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
      unsigned char ip;
      memcpy(ip, &addr_in->sin_addr, sizeof(ip));
      PRINT_DEBUG("CHECK NON LAN IP %hhu.%hhu.%hhu.%hhu:%u\n", ip, ip, ip, ip, ntohs(addr_in->sin_port));
                //check if there have serverip.txt/serverport.txt, if have,redirect to it; Or ignore it.
      //修改这一部分,使它强行定位到某个IP和某个Port,否则正常访问。
      if (file_exists(get_full_program_path() + "serverip.txt"))
       {
            std::ifstream readFile(get_full_program_path() + "serverip.txt");
            //Read and put it in.
            std::string temp;
            readFile >> temp;
            const std::string& tempip = temp;
                        //Put it in.
            //这里得到了String的临时IP,接下来将这个IP想办法放进去
            addr_in->sin_addr.s_addr = inet_addr(temp.c_str());
            PRINT_DEBUG("Crack 2K DLC Check - IP\n");
      }
      
      if (file_exists(get_full_program_path() + "serverport.txt"))
      {
            std::ifstream readFile(get_full_program_path() + "serverport.txt");
            std::string temp;
            readFile >> temp;
            int port = atoi(temp.c_str());
            addr_in->sin_port = htons(port);
            PRINT_DEBUG("Crack 2K DLC Check - PORT\n");
      }
                //Send it back
      //修改后,将修改的转回sockaddr,通过重新赋值给该指针来修改指针指向的值。
      addr = (struct sockaddr*)addr_in;
                //display the new IP for debug.
      //记得重新读取IP的值……
      memcpy(ip, &addr_in->sin_addr, sizeof(ip));
      PRINT_DEBUG("NOW LAN IP %hhu.%hhu.%hhu.%hhu:%u\n", ip, ip, ip, ip, ntohs(addr_in->sin_port));
      return true;
    }

    PRINT_DEBUG("NOT LAN IP\n");
    return false;
}
可能有人问了:你重定向到哪儿去呢?别急,我们知道mitmproxy有个功能叫“反向代{过}{滤}理”,设置一个反向代{过}{滤}理就好啦!
最后在整合了网上知乎陈泽安大佬的代码之后(来源:https://www.zhihu.com/question/422464846/answer/1619189974)
最后成品大概就是这样的:(各位大佬正好求教,这么一堆if elif有没有办法优化呀,好长好麻烦啊)
# Mitmproxy读取部分:
# 作者:陈泽安
# 链接:https://www.zhihu.com/question/422464846/answer/1521657468
# 来源:知乎
# 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

from mitmproxy.options import Options
from mitmproxy.proxy.config import ProxyConfig
from mitmproxy.proxy.server import ProxyServer
from mitmproxy.tools.dump import DumpMaster
from mitmproxy import http

import threading
import asyncio
import json
import time
# 判断文件是否存在
import os
# 彩蛋
import datetime

# Sorry I don't use english to make this file.




# 搬运他们到这里……
"""
HTTPS HEADER.I DON'T KNOW WHETHER IS THIS FOR USE.
"""
twokheader = {"Server": "mitmcrack",
            "Date": str(time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(time.time()))),
            "Content-Type": "application/json",
            "Connection": "close",
            "P3P": "CP=\"See http://www.take2games.com/privacy\""
            }

multiheader = {"Content-Type": "application/json,charset=utf-8",
               "Cache-Control": "no-cache, no-store, must-revalidate",
               "Connection": "keep-alive",
               "Date": str(time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(time.time()))),
               "Expires": "0",
               "Server": "Pinenut-Crack/1.1",
               "Pragma": "no-cache",
               "X-Powered-By": "PHP/5.3.29"
               }


# For Update Server, there aren't the same, so I write it below, not define here.


def evolvecrack(jsonfile, logstr, optheader, flow):
    print("[松子日志]:" + logstr)
    flow.response = http.HTTPResponse.make(
      200,# (optional) status code
      jsonfile,# (optional) content
      optheader)# (optional)


def readjson(jsonname):
    with open(os.path.abspath('.') + "\\EvolveCrack\\" + jsonname, encoding="utf-8") as f:
      return f.read()


class Addon(object):
    def __init__(self):
      print("EvolveEmulator插件成功挂载!\nEvolveEmulator addon is successfully running!")

    def response(self):
      print("\n")

    def request(self, flow) -> None:
      #print("我觉得这里怎么不得执行一下啊嗯?")
      if "doorman/1" in flow.request.pretty_url:
            evolvecrack(readjson("doorman.json"), "Game are Trying to request doorman in 2K", twokheader, flow)
      elif "telemetry/1" in flow.request.pretty_url:
            if "configs.generate" in flow.request.text:
                telemetryconfiggenerate = str(readjson("telemetryconfiggenerate.json"))
                # Set Time to Local Time.
                teleconfig = telemetryconfiggenerate.replace("\"serverTime\": 0",
                                                             "\"serverTime\": " + str(int(time.time())))
                evolvecrack(teleconfig, "Game are Trying to request teleconfig in 2K", twokheader, flow)
      elif "sessions/1" in flow.request.pretty_url:
            session = str(readjson("sessions.json"))
            # Here,it changed not only serverTime but also appContext.
            sessionto = json.loads(str(flow.request.get_text()))
            appConText = sessionto["header"]["appContext"]
            sessionre = session.replace("\"serverTime\": 0", "\"serverTime\": " + str(int(time.time())))
            sessionre = sessionre.replace("\"appContext\": 0", "\"appContext\": " + str(appConText))
            evolvecrack(sessionre, "Game are Trying to request sessions-heartbeat in 2K", twokheader, flow)
      elif "news/1" in flow.request.pretty_url:
            news = str(readjson("news.json"))
            evolvecrack(news, "Game are Trying to request news in 2K", twokheader, flow)
      elif "storage/1" in flow.request.pretty_url:
            storage = str(readjson("iteminsert.json"))
            evolvecrack(storage, "Game are Trying to request storages - iteminsert in 2K", twokheader, flow)
      elif "sso/1" in flow.request.pretty_url:
            # It change the appContext too,shit!
            ssoto = json.loads(str(flow.request.get_text()))
            sso = str(readjson("applogon.json"))
            appConText = ssoto["header"]["appContext"]
            sso = sso.replace("\"appContext\": 0", "\"appContext\": " + str(appConText))
            evolvecrack(sso, "Game are Trying to request sso-authlogon in 2K", twokheader, flow)
      elif "stats/1" in flow.request.pretty_url:
            # It change the appContext too.
            statsto = json.loads(str(flow.request.get_text()))
            stats = str(readjson("stats.json"))
            appConText = statsto["header"]["appContext"]
            stats = stats.replace("\"appContext\": 0", "\"appContext\": " + str(appConText))
            evolvecrack(stats, "Game are Trying to request stats in 2K", twokheader, flow)
      elif "entitlements/1" in flow.request.pretty_url:
            if "steamApi.user.checkAppOwnership" in flow.request.text:
                # Set All DLC Unlock(No Steam ID will lock all DLC):
                checkapp = str(readjson("checkAppOwnership.json"))
                checkappto = json.loads(str(flow.request.get_text()))
                appConText = checkappto["header"]["appContext"]
                checkapp1 = checkapp.replace("\"appContext\": 0", "\"appContext\": " + str(appConText))
                checkapps = checkapp1.replace("\"ownersteamid\": \"0\"", "\"ownersteamid\": " + "\"76561198587806892\"")
                evolvecrack(checkapps, "Game are Trying to request checkAppOwnership in 2K", twokheader, flow)
            elif "entitlementDefs.getFirstPartyMapping" in flow.request.text:
                # Unknown request.Just send back as its format.
                mapto = json.loads(str(flow.request.get_text()))
                map = str(readjson("getFirstPartyMapping.json"))
                appConText = mapto["header"]["appContext"]
                map = map.replace("\"appContext\": 0", "\"appContext\": " + str(appConText))
                evolvecrack(map, "Game are Trying to request getFirstPartyMapping in 2K", twokheader, flow)
            elif "grants.find" in flow.request.text:
                # Unknown request 2.Just send back as its format.Need 2 replace.
                grants = str(readjson("grantsfind.json"))
                grantsto = json.loads(str(flow.request.get_text()))
                appConText = grantsto["header"]["appContext"]
                grants1 = grants.replace("\"appContext\": 0", "\"appContext\": " + str(appConText))
                grantss = grants1.replace("\"createdOn\": 0", "\"createdOn\": " + str(int(time.time())))
                evolvecrack(grantss, "Game are Trying to request grantsfind in 2K", twokheader, flow)
      elif "content/1" in flow.request.pretty_url:
            if "strings.get" in flow.request.text:
                strsto = json.loads(str(flow.request.get_text()))
                strs = str(readjson("stringsget.json"))
                appConText = strsto["header"]["appContext"]
                strs = strs.replace("\"appContext\": 0", "\"appContext\": " + str(appConText))
                evolvecrack(strs, "Game are Trying to request stringsget in 2K", twokheader, flow)
      elif "auth.php" in flow.request.pretty_url:
            # This is for dsx-1-b.4v1game.net.
            authmulti = str(readjson("SteamLogin.json"))
            evolvecrack(authmulti, "Game are Trying to Go MultiPlayer.", multiheader, flow)
      elif "requestserver.php" in flow.request.pretty_url:
            # This is for dsx-1-b.4v1game.net.
            requestlb = str(readjson("requestserver.json"))
            evolvecrack(requestlb, "Game are Trying to Go MultiPlayer.", multiheader, flow)
      elif "Production/252019.252019.0/build_config_signed.json" in flow.request.pretty_url:
            # mb_exclusions KrakenElder, Don't know what it is.
            # Although it is transparent with "octet-stream", I think it will works using "readjson".
            # There are two build_config_signed.json here.
            mb_exclusions = readjson("build_config_signed0.json")
            mb_header = {
                "Content-Type": "application/octet-stream",
                "Connection": "keep-alive",
                "Last-Modified": "Tue, 19 Jan 2016 17:29:48 GMT",# I am not sure whether it will check for that.
                "Accept-Ranges": "bytes",
                "Server": "BmazonS3",# It's Okay I think,lol.
                "Date": str(time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(time.time()))),# now time.
                "ETag": "\"302fd31b05d6893d009f81f5babe6de2\"",# Seems game doesn't use this.
                "X-Cache": "Never from cloudfront",
                "Via": "1.1 646b6f21a2659c68f7a3822d035b97d3.cloudfront.net (CloudFront)",# Well, no speed up I think.
                "X-Amz-Cf-Pop": "NRT57-C2",
                "X-Amz-Cf-Id": "Bg5xfYkwkZXBisBgsbBLjoR_u3V_zJfPK9W7T1Jy57bmsktcUqTE5A=="
            }
            evolvecrack(mb_exclusions, "Game are Trying to request mbclusions from updateserver.", mb_header, flow)
      elif "Production/252019.252019/micro_patch_version_signed.json" in flow.request.pretty_url:
            # Micro patch json. If game are not have it in GameDLC08, it will send request to server to get.
            microjson = readjson("micro_patch_version_signed.json")
            micro_header = {"Content-Type": "application/octet-stream",
                            "Connection": "keep-alive",
                            "Date": str(time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(time.time()))),
                            # now time.
                            "Last-Modified": "Thu, 28 Apr 2016 19:21:44 GMT",
                            "ETag": "\"0b8122e356ca818bf97c80e71379bef4\"",
                            "Cache-Control": "max-age=259200",
                            "Accept-Ranges": "bytes",
                            "Server": "BmazonS3",
                            "X-Cache": "Never from cloudfront",
                            "Via": "1.1 646b6f21a2659c68f7a3822d035b97d3.cloudfront.net (CloudFront)",
                            "X-Amz-Cf-Pop": "NRT57-C2",
                            "X-Amz-Cf-Id": "pKEfUlyGx4Sn1J-uWqjQ6U3OskCD-9iXUYffyW-U99MhYGQOdJIE2Q==",
                            "Age": "172185"
                            }
            evolvecrack(microjson, "Game are Trying to request micro version from updateserver.", micro_header, flow)
      elif "Production/252019.252019/micro_patch_252019.252019_31.pak" in flow.request.pretty_url:
            # This will redirect it to Gitee, which are the same like github, Or you can return the file on your server.
            # flow.request.url = "https://gitee.com/firehomework/evolve-legacy-files" \
            #                   "/raw/master/micro_patch_252019.252019_31.pak"
            print("Here")
            mb_header = {
                "Content-Type": "text/plain",
                "Connection": "keep-alive",
                "Transfer - Encoding": " chunked",
                "Last-Modified": "Tue, 19 Jan 2016 17:29:48 GMT",# I am not sure whether it will check for that.
                "Accept-Ranges": "bytes",
                "Server": "BmazonS3",# It's Okay I think,lol.
                "Date": str(time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(time.time()))),# now time.
                "ETag": "\"302fd31b05d6893d009f81f5babe6de2\"",# Seems game doesn't use this.
                "X-Cache": "Never from cloudfront",
                "Via": "1.1 646b6f21a2659c68f7a3822d035b97d3.cloudfront.net (CloudFront)",# Well, no speed up I think.
                "X-Amz-Cf-Pop": "NRT57-C2",
                "X-Amz-Cf-Id": "Bg5xfYkwkZXBisBgsbBLjoR_u3V_zJfPK9W7T1Jy57bmsktcUqTE5A=="
            }
            with open(os.path.abspath('.') + "\\EvolveCrack\\" + "micro_patch_252019.252019_31.pak", "rb") as f:
                evolvecrack(f.read(), "Game are Trying to request micro pak from updateserver.", mb_header, flow)
            # Actually I don't surely know whether it will work.
            # And the game won't request it when using fake server. Why?!
      elif "Production/252019.252019.31/build_config_signed.json" in flow.request.pretty_url:
            # This is another build_config_signed.json.
            anocof = readjson("build_config_signed.json")
            mb_header = {
                "Content-Type": "application/octet-stream",
                "Connection": "keep-alive",
                "Last-Modified": "Tue, 19 Jan 2016 17:29:48 GMT",# I am not sure whether it will check for that.
                "Accept-Ranges": "bytes",
                "Server": "BmazonS3",# It's Okay I think,lol.
                "Date": str(time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(time.time()))),# now time.
                "ETag": "\"302fd31b05d6893d009f81f5babe6de2\"",# Seems game doesn't use this.
                "X-Cache": "Never from cloudfront",
                "Via": "1.1 646b6f21a2659c68f7a3822d035b97d3.cloudfront.net (CloudFront)",# Well, no speed up I think.
                "X-Amz-Cf-Pop": "NRT57-C2",
                "X-Amz-Cf-Id": "Bg5xfYkwkZXBisBgsbBLjoR_u3V_zJfPK9W7T1Jy57bmsktcUqTE5A=="
            }
            evolvecrack(anocof, "Game are Trying to request another build_config from updateserver.", mb_header, flow)
      else:
            print("[松子日志]:" + "Maybe there are something I forgot to emulate.")
            print("[松子日志]:It's okay as seems there are lots of them just need a response...")
            print(flow.request.pretty_url)
            requestlb = str(readjson("requestserver.json"))
            evolvecrack(requestlb, "Something I forgot to emulate but seems okay", multiheader, flow)


# see source mitmproxy/master.py for details
def loop_in_thread(loop, m):
    asyncio.set_event_loop(loop)# This is the key.
    m.run_loop(loop.run_forever)


# 用于文件读取的函数
def read_file(filepath):
    with open(filepath) as fp:
      content = fp.read()
    return content


if __name__ == "__main__":
    print("Welcome to use EvolveReborn Software By Pinenut. 严禁倒卖!")
    print("Lots of thanks:Nemirtingas,schmogmog,nemerod,kiagam")
    # This is an egg,and it doesn't have any useful function, Like a joke, so just ignore it,please?
    d1 = datetime.datetime.today()
    d2 = datetime.datetime(2019, 10, 16)
    if(d1 - d2).days % 365 == 0:
      print("小(夹)彩(私)蛋(活)!今天是对于作者来讲很重要的周年纪念日哦!")
      print("Hey!Today is a very important day for author! Anniversary!")
    else:
      print("A number:" + str((d1 - d2).days % 365))
      print("今天是:" + str(d1))
      print("Today is " + str(d1))
    # This is an egg,and it doesn't have any useful function, Like a joke, so just ignore it,please?
    serverport = 2000
    print("================================================================")
    # 添加读取文件部分
    if not os.path.exists("EvolveCrack"):
      print("严重错误……您的EvolveCrack文件夹呢……考虑重新拷贝一下文件吧……")
      print("Fatal Error...Cannot found EvolveCrack Folder...Copy the crack and try again...")
      os.system("pause")
      exit(1)
    if os.path.exists("motd.txt"):
      print("下面的是motd的公告/包含的信息不由我做主。")
      print("The thing below will read from motd.txt, I don't know what in it.")
      print("===========================MOTD START===================================")
      with open("motd.txt", "r",encoding="utf-8") as fr:
            for line in fr:
                print(line)
      print("===========================MOTD OVER===================================")
    if os.path.exists("serverport.txt"):
      with open("serverport.txt", "r") as fr:
            # 以r形式打开文件
            for line in fr:# 一行一行读取
                # 读取到了端口号信息
                print("读取到修改后的端口信息,信息为\n" + str(line))
                print("Read successfully!The port now is :\n" + str(line))
                print("正在修改端口号。\nChanging the server port...")
                serverport = int(line)
    # 添加昵称修改部分
    if os.path.exists("local_save.txt"):
      localsave = read_file("local_save.txt")
      if os.path.exists(localsave + "\\" + "settings" + "\\" + "account_name.txt"):
            account_name = read_file(localsave + "\\" + "settings" + "\\" + "account_name.txt")
            if account_name == "Goldberg":
                print("您要考虑修改一个名字吗?这个名字是默认名字,辨识度很差!\n"
                      "Do you wanna to change the name? This name is the default name, "
                      "there will lots of people named that.\n")
                choose = input("输入Y来修改名字,输入其他不修改名字并启动游戏\nInput Y then press Enter to change the name, Others to run "
                               "the game without changing the name\n")
                if choose == "Y" or choose == "y":
                  newname = input("请输入新的名字!(不支持中文!)\nPlease Input your new name!")
                  with open(localsave + "\\" + "settings" + "\\" + "account_name.txt", "w+", encoding="utf-8") as f:
                        f.write(newname)
                  print("修改新名字成功,新名字为:" + newname + "\n Set new name successfully!New name is" + newname)
      else:
            print("看起来这是您第一次启动游戏。请按照向导设置您的游戏属性!\nSeems this is the first time you run the game,please follow the "
                  "guide to set your game!")
            newname = input("请输入您游戏内的名字!(不支持中文!)\nPlease Input your new name!")
            #先得创建文件夹
            os.mkdir(localsave + "\\" + "settings")
            with open(localsave + "\\" + "settings" + "\\" + "account_name.txt", "w+", encoding="utf-8") as f:
                f.write(newname)
            print("设置名字成功,您游戏内的名字为:" + newname + "\n Set the name successfully, your new name is" + newname)

    options = Options(listen_host='0.0.0.0', listen_port=serverport, http2=True, confdir="./certs",
                      mode="reverse:http://www.baidu.com")
    m = DumpMaster(options, with_termlog=False, with_dumper=False)
    config = ProxyConfig(options)
    m.server = ProxyServer(config)
    m.addons.add(Addon())
    print("服务器已经运行!请配置您的serverip为127.0.0.1(默认即配置)")
    print("Server is running, Port = " + str(serverport) + ", Please configure your serverip to 127.0.0.1(A default "
                                                         "config)")
    print("================================================================")
    print("正在启动游戏……如果游戏没有成功启动,请手动启动RebornEvolve.exe!\nRunning game...If the game failed to run,please run "
          "RebornEvolve.exe by yourself")
    if(os.path.exists("RebornEvolve.exe")):
      os.startfile("RebornEvolve.exe")
    else:
      print("启动失败,可能这个文件被误杀了。")
      print("Failed to run the game, maybe the RebornEvolve.exe was deleted by anti-virus software.")
    # run mitmproxy in background, especially integrated with other server
    loop = asyncio.get_event_loop()
    t = threading.Thread(target=loop_in_thread, args=(loop, m))
    t.start()

    # Other servers might be started too.
    # time.sleep(20)
    # print('going to shutdown mitmproxy')
    # m.shutdown()

在这样配合之下,终于!完成了整个游戏的局域网化!实现了脱离Steam!
小插曲:歪门邪道解决游戏的输入法崩溃
游戏当出现中文输入法的时候会导致游戏崩溃,一按就崩溃。我不知道原因,也不知道咋解决,但是我想到一个好办法(大嘘)
我使用AheadLib做了一个imm32.dll的中间dll,之后用UltraEdit修改了Evolve.exe,将imm32.dll改成cmm32.dll。
之后在中间dll内写了这么个代码:

哎,我不知道你为啥崩溃,但是我让你的输入法不能用了不就完了.jpg
(如果有大佬愿意指点为什么崩溃,感激不尽哈哈)
阶段??:一些解决不了的问题
1. 游侠Hook问题。
我刚开始是打算给游侠用的,但是游侠的补丁是会Hook ws2_32.dll的,这样就会导致2K验证失效,请问有无好办法规避?
2.2K验证的https问题。
我网上搜索几乎没有看到相关资料(ws2_32和https),大概是我搜索的方向不对?而且很神奇的不在于此,改了端口后抓包明显是http(显示都改成了HTTP,没假),但是仍然需要证书验证,个人猜测是不是这部分在游戏的exe代码里?该软件反调试,我不擅长于此,所以我的重心都是围绕在“骗过游戏“进行的,对此并不了解。


最后:
新人一枚,求大佬轻喷

maxism 发表于 2021-4-9 20:49

.2K验证的https问题。
我网上搜索几乎没有看到相关资料(ws2_32和https),大概是我搜索的方向不对?而且很神奇的不在于此,改了端口后抓包明显是http(显示都改成了HTTP,没假),但是仍然需要证书验证,个人猜测是不是这部分在游戏的exe代码里?该软件反调试,我不擅长于此,所以我的重心都是围绕在“骗过游戏“进行的,对此并不了解。

大佬 看看我的帖子 是是不是有点类似

Pinenut666 发表于 2021-4-10 09:09

maxism 发表于 2021-4-9 20:49
.2K验证的https问题。
我网上搜索几乎没有看到相关资料(ws2_32和https),大概是我搜索的方向不对?而且很 ...

感觉有相同点,我个人倾向于我这个是先验证证书,验证之后才通信。

First丶云心 发表于 2021-4-9 15:03

谢谢分享,学习学习!

17551812648 发表于 2021-4-9 15:13

谢谢分享,学习学习

395552895 发表于 2021-4-9 15:40

没看懂 还是支持一下吧

leon3539 发表于 2021-4-9 16:19

发现宝了!感谢楼主

sadtear 发表于 2021-4-9 16:37

没看懂...感觉厉害的一批呀= =佩服

bjxiaoyao 发表于 2021-4-9 18:37

内容精彩,感谢分享。

FREEunique 发表于 2021-4-9 23:22

我跪着看的,还是没看懂:'(weeqw

wobzhidao 发表于 2021-4-10 07:10

这个游戏好玩吗
页: [1] 2 3 4 5 6 7 8
查看完整版本: 恶灵进化[Evolve Legacy] 游戏局域网化脱离Steam的思路记录以及一些问题