开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 99|回复: 0
收起左侧

[图文资料] AI逆向修改lua单机同人游戏《阿拉德英雄传》

[复制链接]
结帖率:69% (9/13)
发表于 昨天 22:37 | 显示全部楼层 |阅读模式   内蒙古自治区通辽市
前言: 如果有大佬有空,研究一下这个游戏修改更好的方法,或者说揭秘一下原理,也算是满足童年愿望了。
链接: https://pan.baidu.com/s/1b8BdvN-XfKWL4nm46GVZdw?pwd=cc7h 提取码: cc7h //游戏下载地址 新系统需要用win7或者XP兼容运行,需要安装DX库

这款游戏最初版本快有10年了吧,当年还是小登的我,和小伙伴们发现了这个游戏,当时只会用CE改改地址用ODjmp一下,nop一下的程度。

然后接触这款游戏,最初版1.4 1.5是没有检测的, CE可以改一些东西,但是没有基址,原理就是lua虚拟机那一套。比如力量,速度啥的还不能改。改了就会修正。
最吸引人的就是这个视频工具测试视频,但是因为技术不行,就一直放到现在。 但是时代不一样了,有了AI趁着有空就去尝试了一下,居然搞定了一部分


正文:
1.首先这个游戏是EM游戏脚本制作大师做的,脚本都是lua支持中文代码。但是作者魔改了lua库 比如常用的  luaL_loadbuffer   lua_dostring  魔改了,函数1下断点,堆栈信息都不对。函数2压根就没有。 最后通过 luaL_loadstring  - lua_pcall这两个函数注入了我们的代码。我没接触过lua,通过AI解释,就是前者加载字符串代码,后者执行。


函数原型 int luaL_loadstring (lua_State *L, const char *s);   
功能
Lua 代码字符串编译为一个函数,并把这个函数压入栈顶。
等价于:luaL_loadbuffer(L, s, strlen(s), s)。


参数
  • lua_State *L:Lua 虚拟机状态(必须)
  • const char *s:要加载的 Lua 代码字符串(如 "print(1+2)")

int lua_pcall (    lua_State *L,    int nargs,    int nresults,    int errfunc);


功能
保护模式调用栈上的函数(安全调用,出错不会崩溃)。

参数
  • lua_State *L:Lua 状态机
  • int nargs:函数的参数个数(已压入栈)
  • int nresults:期望的返回值个数(LUA_MULTRET 表示全部返回)
  • int errfunc:错误处理函数在栈中的索引
    • 0 表示不使用自定义错误处理(最常用)



所以我们,需要找到这两个函数位置,还有 lua_State

函数位置我们在CE枚举DLL就可以定位到 1.png 一步寻找L。也非常简单在函数返回处下断点。 2.png


进入游戏切换一下场景,就会断下,此时观察堆栈 3.png 这个结构确实符合,参数2我们查看文本直接可以看到部分代码。
参数1就是L ,单步执行,走出这个call,就会看到L的基地址。 4.png



lua_pcall重复上面的操作看堆栈, 5.png 结构也符合。直接照抄就行了。L和上面获取到的是一样的。

然后就会得到一个CE注入代码脚本:


[ENABLE]
alloc(code, 2048)
code:
pushad
push 0x00980000 //此处为申请的字符串地址,类型是代码页。为什么不直接写db写入因为试过了不支持中文代码。后面需要注入的代码都写在这里
push [Arad.exe+A051A]
call LScript.luaL_loadstring
push 0
push 1
push 0
push [Arad.exe+A051A]
call LScript.lua_pcall
popad
ret
createthread(code)
ret
[DISABLE]


之后就是注入lua代码,因为不会lua,后面全程DeepSeek发力。  不知道从哪里入手,DS告诉我可以遍历他的全局变量表。代码如下


local f = io.open("C:\\global_all.txt", "w")
if not f then
    f = io.open("D:\\global_all.txt", "w")
end
if not f then return end

for k, v in pairs(_G) do
    if type(v) == "table" or type(v) == "userdata" then
        f:write("--- " .. k .. " ---\n")
        local ok, t = pcall(function() return pairs(v) end)
        if ok then
            for kk, vv in pairs(v) do
                local ok2, val = pcall(function() return tostring(vv) end)
                if ok2 then
                    f:write(string.format("  %s = %s\n", tostring(kk), tostring(vv)))
                end
            end
        end
    end
end

f:close()


之后得到了一个全局变量表,选一个主要的内容 6.png ,一个叫“Q_主角”的全局变量还有一些他的属性方法。

于是乎,我们注入一段代码, Q_主角.攻速=-9  默认攻速 7.png 注入后 8.png 血量经验等级蓝量都可以这么改。

新问题:
这么修改移动速度和力量等数值的时候,都会被修正回去。
DeepSeek最终的解决方案,遍历完整和主角相关的 函数,属性方法。


local obj = Q_主角
local f = io.open("C:\\Q_dump.txt", "w")
if not f then return end

local function writeLine(...)
    f:write(string.format(...) .. "\n")
end

writeLine("========== Q_主角 完整属性遍历 ==========")
writeLine("对象类型: %s", type(obj))

-- 1. 如果是 table,直接 pairs
if type(obj) == "table" then
    writeLine("\n[直接 table 遍历]")
    for k, v in pairs(obj) do
        writeLine("%s = %s (type: %s)", tostring(k), tostring(v), type(v))
    end
end

-- 2. 检查元表
local mt = getmetatable(obj)
if mt then
    writeLine("\n[元表存在]")
    writeLine("__index 类型: %s", type(mt.__index))

    if type(mt.__index) == "table" then
        writeLine("\n[元表 __index 表遍历]")
        for k, v in pairs(mt.__index) do
            writeLine("%s = %s (type: %s)", tostring(k), tostring(v), type(v))
        end
    elseif type(mt.__index) == "function" then
        writeLine("\n[__index 是函数,尝试提取上值]")
        local i = 1
        while true do
            local name, val = debug.getupvalue(mt.__index, i)
            if not name then break end
            writeLine("upvalue %d: %s = %s (type: %s)", i, name, tostring(val), type(val))
            -- 如果上值是 table,也直接展开
            if type(val) == "table" then
                writeLine("  --> 展开该上值表:")
                for kk, vv in pairs(val) do
                    writeLine("    %s = %s (type: %s)", tostring(kk), tostring(vv), type(vv))
                end
            end
            i = i + 1
        end
    end

    -- 3. 其他元方法也可能存有信息(如 __pairs 可能重定义遍历行为)
    if mt.__pairs then
        writeLine("\n[元方法 __pairs 存在,尝试调用]")
        local ok, iter, state, start = pcall(mt.__pairs, obj)
        if ok then
            for k, v in iter, state, start do
                writeLine("%s = %s (type: %s)", tostring(k), tostring(v), type(v))
            end
        end
    end
end

-- 4. 如果是 userdata,尝试 uservalue (Lua 5.2+)
if type(obj) == "userdata" then
    local ok, uv = pcall(debug.getuservalue, obj)
    if ok and type(uv) == "table" then
        writeLine("\n[uservalue 表遍历]")
        for k, v in pairs(uv) do
            writeLine("%s = %s (type: %s)", tostring(k), tostring(v), type(v))
        end
    end
end


f:close()


最终得到了只关于主角的一些属性和函数,主要看函数 9.png DeepSeek说,可以寻找关于移动相关的函数,然后在函数赋值完移动速度以后我们在hook 赋值一遍。

10.png 默认移动速度,修改后 11.png ,不能修改的问题解决。

后续扩展:
遍历关于怪物的一些数据比如修改血量,等等。还有调用死亡处理函数(这里没太弄明白)效果不太好。


总结一下,这次的修改,了解了一种没接触过的修改游戏方法。在AI写代码的加持下一路走的很顺。虽然还有好多问题没有解决但是也挺满足了。


最后我还试了一下dump了一下lua文件。 dump出来了,但是lua字节码,但是不是标准的lua库还没办法还原就有点难受...
































您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报QQ: 793400750,邮箱:wp@125.la
网站简介:精易论坛成立于2009年,是一个程序设计学习交流技术论坛,隶属于揭阳市揭东区精易科技有限公司所有。
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备2025452707号) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表