a1046830 发表于 2021-3-31 16:56

某cocos2dx MOBA游戏分析

1、小地图的战争迷雾一般对于MOBA游戏透视来说,用GG修改器就足以很简单的实现了:开局敌人全部在小地图上看不到,那直接搜0就行了,然后对着对面一个特定的英雄,简称H。当H小头像出现在小地图上的时候搜1,没有的时候搜0。这样很快就能找到H在内存中的数据,将那个值改成1冻结掉就可以实现H的小地图透视了。如果说想实现对面全部英雄透视的话。那就在H的那个“1”值的内存中往上找特征码,然后搜索特征码+偏移,找到的一起改成1就行了。这样的结果一般是对面塔、小兵、水晶、英雄一起暴露在小地图的视野里面。
实现的GG修改器脚本可以参考以下代码
-----------------------------------------------------------------------------------------

function split(szFullString, szSeparator)
local nFindStartIndex = 1
local nSplitIndex = 1
local nSplitArray = {}
while true do
local nFindLastIndex = string.find
(szFullString, szSeparator, nFindStartIndex)
if not nFindLastIndex then
nSplitArray =
string.sub(szFullString, nFindStartIndex, string.len
(szFullString))
break end
nSplitArray = string.sub
(szFullString, nFindStartIndex, nFindLastIndex - 1)
nFindStartIndex = nFindLastIndex + string.len
(szSeparator)
nSplitIndex = nSplitIndex + 1 end return
nSplitArray end
function xgxc(szpy, qmxg)
for x = 1, #(qmxg) do
xgpy = szpy + qmxg["offset"]
xglx = qmxg["type"]
xgsz = qmxg["value"]
xgdj = qmxg["freeze"]
if xgdj == nil or xgdj == "" then
gg.setValues({
= {address = xgpy, flags = xglx, value = xgsz}})
else
gg.addListItems({
= {address = xgpy, flags = xglx,
freeze = xgdj, value = xgsz}}) end
xgsl = xgsl + 1 xgjg = true end end
function xqmnb(qmnb)
gg.clearResults()
gg.setRanges(qmnb["memory"])
gg.searchNumber(qmnb["value"], qmnb["type"])
if gg.getResultCount() == 0 then
gg.toast(qmnb["name"] .. "开启失败")
else
gg.refineNumber(qmnb["value"], qmnb["type"])
gg.refineNumber(qmnb["value"], qmnb["type"])
gg.refineNumber(qmnb["value"], qmnb["type"])
if gg.getResultCount() == 0 then
gg.toast(qmnb["name"] .. "开启失败")
else
      sl = gg.getResults(999999)
sz = gg.getResultCount()
      xgsl = 0 if sz > 999999 then
sz = 999999 end for i = 1, sz do
pdsz = true for v = 4, #(qmnb) do if
pdsz == true then
pysz = {} pysz
= {} pysz.address
= sl.address + qmnb["offset"] pysz.flags
= qmnb["type"]
szpy = gg.getValues(pysz)
pdpd = qmnb["lv"] .. ";" .. szpy.value szpd
= split(pdpd, ";") tzszpd
= szpd pyszpd = szpd
if tzszpd == pyszpd then
pdjg = true pdsz = true else
pdjg = false pdsz = false end end end if
pdjg == true then szpy
= sl.address xgxc(szpy, qmxg) end end
if xgjg == true then
gg.toast(qmnb["name"] .. "开启成功,共修改" .. xgsl .. "条数据")
else
gg.toast(qmnb["name"] .. "开启失败")
end
end
end
end
-------配置↑↑↑勿修改-------
-------支持冻结------
-----------------------------------------------------------------------------------------
function Main()
SN = gg.choice({
"扩大视野开启",
"扩大视野关闭",
"小视野开启",
"小视野关闭",   
   "锁头像透视开启",
"锁头像透视关闭",
"高视角(小型)",
"高视角(小型关闭)",
"高视角(中型)",
"高视角(中型关闭)",
"高视角(大型)",
"高视角(大型关闭)",
   "高视角(巨型)",
"高视角(巨型关闭)",
"退出脚本"
}, nil, "")
if SN == 1 then a() end
if SN == 2 then b() end
if SN == 3 then c() end
if SN == 4 then d() end
if SN == 5 then e() end
if SN == 6 then f() end

if SN == 7 then g() end
if SN == 8 then h() end
    if SN == 9 then i() end
if SN == 10 then j() end
if SN == 11 then k() end
if SN == 12 then l() end
if SN == 13 then m() end
if SN == 14 then n() end


if SN == 15 then os.exit() end XGCK = -1 end
   

function a()
qmnb = {
{["memory"] = gg.REGION_C_ALLOC},
{["name"] ="视野扩大开启"},
{["value"] =, ["type"] = 4},
{["lv"] = , ["offset"] = -4, ["type"] = 4},
{["lv"] = , ["offset"] = 4, ["type"] = 4},
{["lv"] = , ["offset"] = -8, ["type"] = 4},
}
qmxg = {
{["value"] =1, ["offset"] = 20, ["type"] = 4},
}
xqmnb(qmnb)
end


function b()
qmnb = {
{["memory"] = gg.REGION_C_ALLOC},
{["name"] ="视野扩大关闭"},
{["value"] =, ["type"] = 4},
{["lv"] = , ["offset"] = -4, ["type"] = 4},
{["lv"] = , ["offset"] = 4, ["type"] = 4},
{["lv"] = , ["offset"] = -8, ["type"] = 4},
}
qmxg = {
{["value"] =, ["offset"] = 20, ["type"] = 4},
}
xqmnb(qmnb)
end

function c()
qmnb = {
{["memory"] = gg.REGION_C_ALLOC},
{["name"] ="小视野开启"},
{["value"] =, ["type"] = 4},
{["lv"] = , ["offset"] = -4, ["type"] = 4},
{["lv"] = , ["offset"] = -12, ["type"] = 4},
{["lv"] = , ["offset"] = -24, ["type"] = 4},
}
qmxg = {
{["value"] =1, ["offset"] = 12, ["type"] = 4},
}
xqmnb(qmnb)
end

function d()
qmnb = {
{["memory"] = gg.REGION_C_ALLOC},
{["name"] ="小视野关闭"},
{["value"] =, ["type"] = 4},
{["lv"] = , ["offset"] = -4, ["type"] = 4},
{["lv"] = , ["offset"] = -12, ["type"] = 4},
{["lv"] = , ["offset"] = -24, ["type"] = 4},
}
qmxg = {
{["value"] =0, ["offset"] = 12, ["type"] = 4},
}
xqmnb(qmnb)
end


function e()
qmnb = {
{["memory"] = gg.REGION_C_ALLOC},
{["name"] ="锁头像透视开启"},
{["value"] =, ["type"] = 4},
{["lv"] = , ["offset"] = -12, ["type"] = 4},
{["lv"] = , ["offset"] = -24, ["type"] = 4},
{["lv"] = , ["offset"] = -32, ["type"] = 4},
}
qmxg = {
{["value"] =1, ["offset"] = -64, ["type"] = 4,["freeze"] = true},
}
xqmnb(qmnb)
end

function f()
qmnb = {
{["memory"] = gg.REGION_C_ALLOC},
{["name"] ="锁头像透视关闭"},
{["value"] =, ["type"] = 4},
{["lv"] = , ["offset"] = -12, ["type"] = 4},
{["lv"] = , ["offset"] = -24, ["type"] = 4},
{["lv"] = , ["offset"] = -32, ["type"] = 4},
}
qmxg = {
{["value"] =1, ["offset"] = -64, ["type"] = 4,["freeze"] = false},
}
xqmnb(qmnb)
end



function g()
       gg.clearResults()
       gg.setRanges(37)
       gg.searchNumber("", gg.TYPE_FLOAT, false, gg.SIGN_EQUAL, 0, -1)
       gg.getResults(100)
       gg.editAll("", gg.TYPE_FLOAT)
       gg.toast("小型高视角开启成功")
       gg.clearResults()
end

function h()
gg.clearResults()
       gg.setRanges(37)
       gg.searchNumber("", gg.TYPE_FLOAT, false, gg.SIGN_EQUAL, 0, -1)
       gg.getResults(100)
       gg.editAll("", gg.TYPE_FLOAT)
       gg.toast("小型高视角关闭成功")
       gg.clearResults()
end

function i()
gg.clearResults()
       gg.setRanges(37)
       gg.searchNumber("", gg.TYPE_FLOAT, false, gg.SIGN_EQUAL, 0, -1)
       gg.getResults(100)
       gg.editAll("", gg.TYPE_FLOAT)
       gg.toast("中型高视角开启成功")
       gg.clearResults()
end

function j()
gg.clearResults()
       gg.setRanges(37)
       gg.searchNumber("", gg.TYPE_FLOAT, false, gg.SIGN_EQUAL, 0, -1)
       gg.getResults(100)
       gg.editAll("", gg.TYPE_FLOAT)
       gg.toast("中型高视角关闭成功")
       gg.clearResults()
end


function k()
gg.clearResults()
       gg.setRanges(37)
       gg.searchNumber("", gg.TYPE_FLOAT, false, gg.SIGN_EQUAL, 0, -1)
       gg.getResults(100)
       gg.editAll("", gg.TYPE_FLOAT)
       gg.toast("大型高视角开启成功")
       gg.clearResults()
end


function l()
gg.clearResults()
       gg.setRanges(37)
       gg.searchNumber("", gg.TYPE_FLOAT, false, gg.SIGN_EQUAL, 0, -1)
       gg.getResults(100)
       gg.editAll("", gg.TYPE_FLOAT)
       gg.toast("大型高视角关闭成功")
       gg.clearResults()
end


function m()
gg.clearResults()
       gg.setRanges(37)
       gg.searchNumber("", gg.TYPE_FLOAT, false, gg.SIGN_EQUAL, 0, -1)
       gg.getResults(100)
       gg.editAll("", gg.TYPE_FLOAT)
       gg.toast("巨型高视角开启成功")
       gg.clearResults()
end


function n()
gg.clearResults()
       gg.setRanges(37)
       gg.searchNumber("", gg.TYPE_FLOAT, false, gg.SIGN_EQUAL, 0, -1)
       gg.getResults(100)
       gg.editAll("", gg.TYPE_FLOAT)
       gg.toast("巨型高视角关闭成功")
       gg.clearResults()
end







while true do

if gg.isVisible(true) then
    XGCK = 1
    gg.setVisible(false)
end
gg.clearResults()
if XGCK == 1 then
    Main()
end
end


如果说不喜欢用GG修改器,而且喜欢frida的话。可以参考以下:
cocos2dx中,MOBA的地图、战争迷雾、英雄的头像在小地图上不在一个layer里面,关于迷雾的实现,有一大段乱七八糟的东西,具体实现关心的话可以搜索cocos2dx 战争迷雾。这里我们不关心战争迷雾的实现方式。
在我分析的那个游戏里面,关于英雄在小地图上实现头像显示的流程如下:首先它会将一个房间里面的英雄分成两个阵营:0和1;然后在0和1阵营里面分别给五个英雄做标号,通俗来说就是2个数组。在游戏加载时,系统会做一些初始化的东西,关于战争迷雾的R半径初始化和是否隔墙显示战争魔物这些,最重要的是它会让1阵营的英雄的小头像不在0阵营英雄的小地图上。

然后关于在游戏中小头像在地图上的显示(也就是战争迷雾的显示与遮盖)实际上是在一个帧同步的进程里面。而且它的计算是放在本地进行的。为什么它会放到本地主要是因为手机网速不稳定,性能差异大,而且moba游戏对延时要求高。
而且一般cocos2dx游戏的主要游戏逻辑是放在这个游戏安装包里最大的那个so里面进行的,为什么是最大的?
主要是一般cocos2dx游戏都内嵌了lua引擎,所以最大的so最有可能含有lua引擎。
如何查找战争迷雾的显示与遮盖:
对于一般的cocos2dx游戏来说,你需要查找什么函数你就在cocos2dx官方文档上找这种函数的实现名字,找到它的名字直接在so里面搜就行了。
比如说战争迷雾,先搜索process,找找游戏逻辑主进程。

最笨的办法就是全部hook,看那个函数会不停地被调用。定位到bloodMoveString2::process(ulong long)这个函数
查找这个函数出现warfog的名字,然后跳过去看


这个很像,isWarfogVisble。F5反编译

bool函数,而且看代码也很像。应该是了。然后frida hook一下,成功。
function hook_WarforgOpen(){// var warfogoepn=Module.findExportByName("libcos.so","_ZN4CMap14isWarfogVisbleEff");
//console.log("the addr of this func is :",warfogoepn);
Interceptor.attach(flagaddr,{
onEnter:function(args){
//console.log("enter this merhod successful",args);
},onLeave:function(retval){
//console.log(retval);
retval.replace(1);
// console.log("the retval changged is :",retval);
// retval.replace(201); //修改flag 同样可以生效
}
});
}

2、lua脚本dump
cocos2dx游戏lua脚本dump时机点就在loadbuffer这个函数里面。一般找到这个函数就是了luaex_loadbuffer(lua_State *,char const*,uint,char const*) 或者相似的。
找到直接frida就能dump下来。我分析的这个游戏没有加密所有dump下来的全是明文,如果加密的话,可以顺着这个函数往上找,应该可以找到解密函数。
function hook_LuaBufferload(){
    var loadbuffer=Module.findExportByName("libcos.so","_ZN2tq16luaex_loadbufferEP9lua_StatePKcjS3_");
    console.log("the addr of this func is :",loadbuffer);
    Interceptor.attach(loadbuffer,{
      onEnter:function(args){
               console.log(ptr(args).readCString());
                //参数3是lua放置的位置和名字
                var tmp =ptr(args).readCString();
                var tmp1=tmp.split("/");
                var siz=tmp1.length;
                //console.log(tmp1);
                //console.log("大小是:",args.toInt32());
                write_lua(tmp1,ptr(args).readCString());
      },onLeave:function(retval){
      }
    });


}

function write_lua(name , content){
      //frida 的api来写文件
      var file = new File("/sdcard/lua/"+name, "w");
      file.write(content);
      file.flush();
      file.close();
}


dump下来的lua脚本存放在sdcard/lua文件夹下面。直接adb pull拉出来就好了

一路无忧 发表于 2021-3-31 21:19

厉害了 值得学习

卖血上网 发表于 2021-3-31 22:38

大佬有空闲研究一下 全球行动这个游戏的迷雾么,手机版红警 毛子游戏去年tx代{过}{滤}理

oostudy 发表于 2021-4-1 09:51

这个厉害,牛逼

阳光好青年 发表于 2021-4-1 15:47

厉害!想知道 找0 找1 的思想是什么?是框架有相关参数设定吗?

a1046830 发表于 2021-4-1 16:03

阳光好青年 发表于 2021-4-1 15:47
厉害!想知道 找0 找1 的思想是什么?是框架有相关参数设定吗?

0说明敌方小头像在地图上不显示,1就是相反

a1046830 发表于 2021-4-1 16:59

卖血上网 发表于 2021-3-31 22:38
大佬有空闲研究一下 全球行动这个游戏的迷雾么,手机版红警 毛子游戏去年tx代{过}{滤}理

战争迷雾原理都差不多哈,或许你可以在它ill2cpp.so里面找到类似的函数。{:301_998:}

卖血上网 发表于 2021-4-1 20:32

a1046830 发表于 2021-4-1 16:59
战争迷雾原理都差不多哈,或许你可以在它ill2cpp.so里面找到类似的函数。

菜鸡刚尝试用gg把迷雾去了,但好像是伪迷雾,看着像是地图全亮,但建筑物小兵还是没法显示出来{:301_980:}
页: [1]
查看完整版本: 某cocos2dx MOBA游戏分析