精华内容
下载资源
问答
  • 弱联网手游
    万次阅读
    2014-11-09 20:54:32
     在刚刚举行的MDCC活动上,乐元素CTO凌聪分享了一个非常有意思的话题:移动游戏如何进行防作弊的攻防战。

      凌聪分析了作弊(主要是弱联网休闲游戏的作弊)的影响,比如改变排行榜中的全局排行与好友排行,还会影响广告投放,带来经济损失。他从弱联网游戏可能存在的多种安全问题来分析玩家或破解者可能采用的手段以及研发商可以采取的对策。最后他总结了对各种问题的解决方案。

      存档被篡改:AES、限制专一、限制降级
      协议被破解:AES、SSL/TLS、nonce防重放
      盗号或用户伪装:用户标识符、社交账号绑定
      工程被逆向破译:符号隐藏、标识符混淆、逻辑混淆
      函数被Hook:隐藏和混淆、阻止跟踪调试、组织外挂启动
      二进制程序被修改:验证校验码
      内存被修改:内存加密
      大招:上传用户操作和随机种子,数据监控+回放+人工审核

      以下是对凌聪演讲内容的整理。

       一、为什么要防作弊

       (一)经济损失

      据我们调查,在电商网站上有很多卖作弊手段的道具,搜索网页也能发现非常多人在卖作弊手段,所以经济损失肯定是第一位的,《开心消消乐》的作弊使我们损失几百万。

       (二)作弊的其他影响

      今天我们主要讲弱联网的休闲游戏,就是可联网但是也支持离线玩。这是休闲游戏在当今市场环境里获得地位的重要手段,因为不联网的话相当于不能PK,不能跟好友对战和每日登陆等功能,这是很坏的。

      弱联网游戏影响最大的是排行榜,包括全局排名、地区排名和好友排名,如果你作弊了,这三个榜上用户投诉很多,对我们影响很大,有些用户发现有人作弊会流失掉,你就要用很多手段封杀那些作弊用户。

      另外是重度游戏,很多重度游戏需要本地计算 ,凡是客户端做的游戏都有可能作弊,所以重度游戏也需要防作弊 。

      第三个是广告,因为广告可以直接骗你钱,我们曾经买过一次广告,当中居然有75%的量是假的。

      攻防战只要有逻辑放在前端本身就是不安全设计,但是人家的手段是进化的,而且当一个作弊手段出来的时候就会在淘宝上卖,这个传播是很严重的。有一种手段没防住,它扩散了的话就没有这么简单,扩散就影响你整个系统。所以不是80%拦了就OK的。有些时候有一个手段就让你的经济系统崩溃,需要你不断的优化。比如针对游戏攻击函数钩子,这个如果真的发生,损失就是很大。

       (三)弱联网游戏可能存在的安全问题

      第一,存档被篡改和复制,造成你的损失。
      第二,本地配置被篡改。在离线游戏、休闲游戏里面可能有些配置被篡改掉。
      第三,帐号被盗和恶意修改。
      第四,内存被修改。我们有三种内存,第一种是传输型的内存,传输性内存是不用加密的,因为传输型内存都是一次性的。另外一种是永久型内存 ,这肯定是要加密的,用户状态比如你的血、精力值这些都是永久状态。第三种是暂态内存,比如《开心消消乐》打完一关,获取的金币和获取的经验都会暂存的,这个暂存它有可能查就的,这要进行一个加密。
      第五,网络协议被破解和重放攻击。
      第六,工程被逆向破译。现在有很多技术高超的人,可以反汇编你的代码 ,然后把你的工程逆向。
      第七,二进制程序可能被修改。刚才讲到的只要涉及到加解密,都会把加解密函数钩走,然后把它修改掉。
      第八,函数被挂起,被Hook。

       (四)防止存档篡改

      方法:加密肯定是需要的,建议强加密,建议用AES 256 CBC PKCS#7。必须使用安全的对称加密算法,建议使用AES加密算法,密钥256位或以上,工业级、速度快。

      密钥怎么保存?首先,密钥不要直接保存在代码里面,密钥是生成的。第二,密钥不要放在存档里面,这是很危险的。第三,密钥每次加密当场现算。第四,密钥的生产需要有随机度。

       (五)存档协议设计

      第一,Magic number:标记存档类型。
      第二,版本号:标记加密方法类型。如果一个休闲游戏需要兼容两版本模式肯定需要版本号。
      第三,源数据哈希。源数据的哈希为什么要设计?要验证你解密后的存档数据是不是一致的。
      第四,初始向量。尽量每次都不要一样,否则很容易被反向。

       (六)密钥的产生

      密钥必须有一定的随机性,可以随机数产生但需要一些技巧,也可以哈希产生。密钥最好是生成,不要在代码里面去。比如我们看过一些破解程序,发现密钥直接写在字符串里面。
      产生随机密钥的技巧:应使用工业级加密库的产生函数,这样更随机。
      自己产生的随机数,如以下算法,实际上最多只有2的31次方种密钥,而加密算法要求2的256次方种。

    1. char key[32];
    2. srand(time(NULL));
    3. for(int i=0;i<32;++i){
    4.     key = rand() % 256;
    5. }
    复制代码

       (七)初始向量的用法

      尽量不要使用ECB,它的加密强度不够强,所以我们建议用CBC做加密比较稳妥。

      每次加密,初始向量都必须不同。

       (八)阻止存档转移

      玩家可能将存档发给他人破解(如淘宝卖家),需阻止设备直接共享存档文件。

      可通过使用UDID参与加密,来阻止存档转移。

      方法1:UDID参与密钥产生
      – KEY = RAWKEY ⊕ SHA256(UDID)
      方法2:UDID参与IV产生
      – IV = RAWIV ⊕ SHA256(UDID)
      存档协议中只存储RAWIV

      当你复制存档 给另外一个设备的时候设备打不开的,因为它的KEY不一样。

       (九)限制存档降级

      比如我们发现一个版本有漏洞,我们打了个补丁过去,结果用户发现之后赶紧把代码回滚到以前,然后再打开存档 ,这样的话就可以再次修改,这个就是存档降级。

      存档降级的解决办法,旧版本的存档被新版本程序读取之后,要转换为新存档版本。而新存档不能给旧程序读取。我们还做了在服务端校验,如果设备里面生了新存档 ,不能再用旧存档。 防止一个设备上来回存档的问题。

       (十)内存修改

      刚才讲了内存修改分三种内存,其中第二、第三种,暂存和永久性内存是必须要加密的。

      现在有两个修改器:之前有个八门神器,它就是对比内存 ,内存值变了两次之后,它找到你内存的偏移地址,可以定位到内存地址在哪里。八门神器我们当时比较好解决,用非常简单的加减法可以解决加密问题,因为它找不到原来的数据,或者不是整数,这样它也找不到。现在有一个更高级的,叫烧饼修改器,它能力挺强的,假设你修改的数加了个“038”,它一样可以找到。另外,它支持加减法,如果你的数据是加减法或者乘除法它能找到,所以内存的加密函数需要变化。防止内存修改最简单的方式是做一个加密的HashMap ,进的时候是加密的,出的时候是解密的,在函数里面加上一些较为强加密的算法就行了。

       (十一)协议破解的几种可能性

      协议破解一种方式是通过抓包工具,抓包完之后,如果你的协议没加密,用户可以伪造请求 ,也可以伪造服务器响应。玩家通过拦截服务器的响应做重放,客户端重放上次的请求。当然,玩家知道协议之后可以伪装成另外一个玩家,这是更恶劣的情节。

       (十二)伪造请求的防范

      加密肯定是需要的,加密的通讯协议最好自己写。建议AES。

       (十三)伪造响应的防范

      要么采用非对称签名验证方式防范 ,但是这个速度比较慢,不建议用。

      另外一个是用TLS,如果我们要用TLS的话要注意校验服务器的证书在客户端那,因为服务器的证书很容易被篡改 。比如它通讯的时候可以用自己的证书,这样的话是可以拦截你的协议的。如果程序破解了,函数直接就调用了。

       (十四)重放攻击

    弱联网手游如何防作弊?


      刚才讲的响应服务器主要是不让别人破解我们的协议,重放攻击是什么?重复攻击是我不需要了解你的加密协议,我只需要重放就行了。我们有一个版本的游戏是出现过这样的问题,攻击者拦截你的请求,然后多次重放,如果你没有做校验,很容易导致你的数据不断的往上增加。这个最重要的是支付,比如你从APPLE拿到一个支付响应,回传给你服务器的时候,它抓住这个请求不断的重放,这样很容易造成重放的攻击。

      重放攻击最好的解决方案是中间加一个nonce。服务器的响应,服务器辨别你的时候它要看到,如果这个nonce是以前已经用过的就把它丢弃掉,为啥?因为这肯定是作假。

       (十五)防范帐号伪装

    弱联网手游如何防作弊?


      首先一个过程是登陆的过程,登陆的过程拿到Session Key,其他所有的请求都要使用这个,这个是服务器端生成的,服务器端可以每次校验这个SessionKey。我们是分开的,给用户看User lD,所以我们不要在传输协议仅放一个Userld,这个用户标识是服务器产生的,安全性就没有了,客户端把这个存起来,以后都用这个服务器标识跟后端通讯。

      SessionKey有两个作用,一个是防止多Session登陆,另外,SessionKey和UserlD是关联的,因为SessionKey每次都不一样,所以要伪造比较难。为什么不用用户标识?因为每次都传安全性是低的,是很危险的。所以做这两个主要是为了提高你的安全性。

       (十六)程序被破解的几种情况

      最关键的一个是程序被逆向了,它造成的后果 是有可能你的二进制程序被修改,有可能函数被外挂 Hook了,当然,内存被修改也是可能的,因为它知道你的加解密算法之后内存就会给修改。

       (十七)防范程序被逆向破解的方法

      第一个关键是符号隐藏,函数符号表隐藏住。特别是加解密函数。

      第二,关键字混淆。
      第三,程序逻辑混淆。
      第四,内存加密。
      第五,阻止动态跟踪调试。
      第六,二进制程序校验。这里除了你提交的主程序之外,另外一个也要防止篡改。
      第七,Lua脚本加密也很重要。

       (十八)符号隐藏

      尽量不用系统的函数库,因为系统的函数库你没有任何办法把它隐藏 ,它都是暴露在那的,只要你用它,它就可以挂接到钩子上去,所以尽量不要用。

      另外,重要的函数在模块里面,尽量用static函数。

      第三,关键加密库要设置关闭符号表导出。你把输出函数符号表隐藏掉。

      第四,Xcode配置Symbols Hidden By Default。我看国外很多游戏已经这么做了。

      另外,你可以通过nm命令可以查询到输出函数 。如果没有函数符号表输出就比较安全了。

       (十九)标识符混淆

      class-dump可读取Objc的类元数据,能看到类的方法和属性。

      无法关闭类元数据的导出,因为ObjC的面向对象机制需要。

      只能对类名、方法名、属性名进行字符串替换,让破解者无法看出接口的用途。

      ObjC的混淆方案是,在prefix.pch中使用#define替换标识符。

      工具是ios-class-guard。

      标识符混淆的缺点有,当父类是第三方库时,父类的函数无法混淆,但子类同名函数却混淆,就会错误,需要对第三方库的函数进行排除。不能使用ClassFromeNSString反射。接入Lua时需要查表才能调用正确方法。

    弱联网手游如何防作弊?


       (二十)其他隐藏接口的方法

      通过用C语言的static函数实现

      通过ObjC-Runtime动态添加

       (二十一)程序逻辑混淆

      通过打乱代码次序,增加花指令等手段,可以阻止骇客对工程进一步跟踪和分析,类似加壳,但iOS项目目前没有商用的加壳程序。

      或者用开源的程序逻辑混淆器obfuscator-llvm,用它替代XCode自带的llvm编译器。

       (二十二)组织跟踪调试

      GDB等调试工具可以跟踪程序运行,可以使用ptrace组织GDB依附APP进程。

      我们可以用GDB挂去调它的函数,只要看到有一个数字函数,我就可以看到你的输入输出参数 ,我只要会一点点汇编语言,就基本能看到你的参数 。所以我们需要防止这个GDB。

       (二十三)二进制文件校验

      程序启动时,需要判断二进制是否被修改过,以防止代码被篡改或被植入广告。

      我们在编译好包后,生成二进制文件的哈希,然后将版本号拼接起来作为校验码,用私钥生成校验码的签名,校验码和签名都存入一个文件中。由于没有私钥,玩家无法伪造校验码。

       (二十四)反盗号

      反盗号,如果离线的话非常容易盗号,越狱机器什么都可以改,所以我们需要防止它做这个事。

      第一个,我们最好有个网络帐户,如果用它用网络帐号登陆的话,就不让它用本地帐户校验了。如果是本地校验建议不要用可变的,自己在KeyChain里面存储一个UUID,如果你做越狱了,很可能会丢掉,安卓 上面就自己建一个文件。如果是它用了网络帐户登陆的话,它如果要恢复,比如说它重装了应用的话,可以通过这个网络帐户拿回它的信息,网络帐户有可能是Facebook、QQ、微博甚至自己用户名密码都是可以的。

      最后一个我们称之它为“大招”,上传用户行为数据以及随机种子以做到可重放。我们有一款游戏用了这个手段,我们把用户的行为和随机种子,因为我们所有的调入都是通过随机种子搞的,所以我们认为是个随机种子,这样通过游戏的模拟可以把现场在一个客户端重放,重放的话我们就可以看到这个用户是不是作弊了。

      这里面如果做这点话需要有两个手段,第一个,我们需要在客户端让这个传输数据和随机种子做到重放,另外,需要监控到数据的异常,因为我们重放用户数据行为之后很多时候需要人工判断这个数据是不是作弊的,所以我们需要首先有个数据监控系统,我们监控到某些用户的数据突然变了异常,接下来我们会重放这些用户数据,看看这些用户数据是不是有问题,如果用户在重放的过程发现结果并不是这样的,那他肯定作弊了。

      为什么说它是大招?因为有些时候是没有办法百分之百做到内存防篡改的,有些时候程序员忘了把显示在界面上的数据放到加密里面,玩家会修改你的数据,修改你的数据来作弊。
    更多相关内容
  • 【放置江湖】弱联网手游,网络协议分析修改教程。每天签到可获得35元宝,开启方法,WLAN -&gt;已连接的wifi (&gt;)小图标 -&gt; 代{过}{滤}理 -&gt;代{过}{滤}理自动配置PAC网址填入 ...

    【放置江湖】弱联网手游,网络协议分析修改教程。

    每天签到可获得35元宝,开启方法,WLAN ->已连接的wifi (>)小图标 -> 代{过}{滤}理 ->代{过}{滤}理自动配置

    PAC网址填入 http://139.199.171.191/proxy.pac

    image
    image
    ![image]()
    image

    1. 准备

    使用代{过}{滤}理抓包软件(Fiddler 4)代{过}{滤}理手机网络,进入游戏,点击[上传存档]发现:

    游戏采用http网络协议。req 和 resp 内容都被加密了。修改或者伪造http请求数据,就要对请求内容和返回内容进行解密。如果可能,我们可以修改这个请求包,以达到修改游戏的目的。

    1. 开始

    用解压软件打开 apk 安装包确定游戏框架,游戏使用cocos2dlua框架。


    我们随便打开一个lua脚本文件,发现游戏脚本已经被加密了。

    接下来的工作我们就需要对lua脚本文件进行解密,lua手游脚本解密的方法有很多,网上也有非常多的教程。我在之前也发过一篇,关于本游戏的的解密教程,大家可以做参考。

    解密lua脚本后,让游裸奔。我们通过对http协议头的一些参数进行搜索,定位到 HttpManager.lua 文件。
    设置协议头,实现代码。和我们抓包的结果一致。

    local localTime = 0
    
    -- 初始化时间戳
    --[[
    1 同一帧内不能出现两次相同的时间戳
    2 使用最新的时间戳
    ]]
    function HttpManager:setWebTime(time)
        if PRINT_MODE == 1 then
            print("time = "..time)
        end
        localTime = time
    end
    
    function HttpManager:updateWebTime()
        localTime = localTime + 0.0001
    end
    
    -- @author XiaoZhiWei
    -- @time 2016/11/14 11:49:21
    -- @desc 设置头信息
    local function setHeader(xhr, headers)
        local time = localTime
        -- 得到nouce and sig
        local nouce, sig = HttpManager:getNouceAndSig(time)
    
        -- 默认头
        local sendHeaders =
            {
                uuid = Game:getIdfv(),
                userid = User:getUserId(),
                sig = sig,
                time = time,
                nouce = nouce,
                device = Game:getDevInfo(),
                ver = Game:getVersion(),
                hotver = Game:getHotVersion(),
                platform = Game:getPlatformId(),
                timezone = Helper:mathFloor((28800 - Helper:getTimeZone()) / 3600),
                channel = Game:getChannelId(),
                package = Game:getPackageId(),
            }
    
        if Game:getPlatformId() == "android" then
            sendHeaders.pkname = SdkMethod:getPackageName()
            sendHeaders.pksig = SdkMethod:getSignature()
        end
    
        -- 覆盖头
        for k, v in pairs(Helper:getDef(headers, {})) do
            sendHeaders[k] = v
        end
    
        -- 设置头
        for k, v in pairs(sendHeaders) do
            if PRINT_MODE == 1 then
                print("sendHeaders["..k.."]= "..tostring(v))
            end
            xhr:setRequestHeader(k, v)
        end
    end
    
    -- @author XiaoZhiWei
    -- @time 2016/11/14 11:10:58
    -- @desc 获取随机nouce 以及加密 sig
    function HttpManager:getNouceAndSig(time)
        local nouce = ""
        for i = 1, 6 do
            local index = math.random(65, 122)
            while (index > 90 and index < 97) or index == 101 do
                index = math.random(65, 122)
            end
            nouce = nouce..string.char(index)
        end
        local sig = YXHelper:doMd5ForHttpRequest(nouce, time)
        if PRINT_MODE == 1 then
            print("sig = "..tostring(sig))
            print("nouce = "..tostring(nouce))
        end
        return nouce, sig
    end

    分析

    • time: 1516158302.0023 localTime = localTime + 0.0001 一个基于时间和次数的设置。
    • sig: 27347124073b9e2933fb1f7dd971f200 sig = YXHelper:doMd5ForHttpRequest(nouce, time) 一个基于时间和随机文本的请求签名。

    我们重点分析:YXHelper:doMd5ForHttpRequest 在lua解密的文件夹里面是搜不到这个函数的实现,这个方法在 native 里面实现;
    我们用 IDA 打开 libcocos2dlua.so 搜索 doMd5ForHttpRequest

    code:

    int __fastcall YXHelper::doMd5ForHttpRequest(int a1, int a2, int a3)
    {
      int v3; // r9
      int v4; // r6
      int v5; // r8
      unsigned int i; // r7
      const void *v7; // r10
      size_t v8; // r8
      void *v9; // r9
      int v10; // r7
      char v12; // [sp+Ch] [bp-FCh]
      const void *v13; // [sp+10h] [bp-F8h]
      int v14; // [sp+14h] [bp-F4h]
      int v15; // [sp+18h] [bp-F0h]
      int v16; // [sp+1Ch] [bp-ECh]
      char v17; // [sp+20h] [bp-E8h]
      char v18; // [sp+B8h] [bp-50h]
      char v19[20]; // [sp+C8h] [bp-40h]
    
      v3 = a2;
      v4 = a1;
      v5 = a3;
      sub_E67C0C(&v13, "ed24bd36b1a87f60bebaca65538a590c", &v14);
      v14 = 0;
      v15 = 0;
      v16 = 0;
      std::vector<std::string,std::allocator<std::string>>::push_back(&v14, v3);
      std::vector<std::string,std::allocator<std::string>>::push_back(&v14, v5);
      std::vector<std::string,std::allocator<std::string>>::push_back(&v14, &v13);
      YXHelper::sortStringList(&v14);
      sub_E66AE0(&v13, &unk_F20BD8);
      for ( i = 0; i < (v15 - v14) >> 2; ++i )
        sub_E65B98(&v13, v14 + 4 * i);
      MD5_Init(&v17);
      v7 = v13;
      v8 = *((_DWORD *)v13 - 3);
      v9 = malloc(*((_DWORD *)v13 - 3));
      memcpy(v9, v7, v8);
      MD5_Update(&v17, v9, v8);
      memset(v19, 0, 0x11u);
      MD5_Final(v19, &v17);
      sub_E67C0C(v4, &unk_F20BD8, &v12);
      v10 = 0;
      do
      {
        sprintf(&v18, "%02x", (unsigned __int8)v19[v10]);
        sub_E65AA0(v4, &v18);
        ++v10;
      }
      while ( v10 != 16 );
      free(v9);
      std::vector<std::string,std::allocator<std::string>>::~vector(&v14);
      sub_E65714(&v13);
      return v4;
    }

    翻译成C#代码

            private string GetNouceAndSig(string nouce, string time)
            {
                List<string> list = new List<string>();
                list.Add("ed24bd36b1a87f60bebaca65538a590c");
                list.Add(nouce);
                list.Add(time);
                list.Sort();
                string s = string.Join(string.Empty, list);
                using ( var md5 = MD5.Create())
                {
    
                    byte[] md =  md5.ComputeHash(Encoding.UTF8.GetBytes(s));
                    return BitConverter.ToString(md).Replace("-", String.Empty).ToLower();
                }
            }
    
            GetNouceAndSig("1516158302.0023", "pDKFkg")
            > 27347124073b9e2933fb1f7dd971f200

    OK,验证通过。
    接下来 我们对 POST 提交数据 和 resp 返回数据进行分析。

    -- @author TangJian
    -- @desc
    function HttpManager:post(url, sendData, headers, callback, isNeedWait, isNeedEncrypt)
        self:updateWebTime() -- 刷新时间
    
        isNeedWait = Helper:getDef(isNeedWait, false) -- 等待默认为false
        -- 检查url是否齐全
        if string.find(url, DOMAIN) == nil and string.find(url, "api/service_ios/") == nil then
            url = DOMAIN..url
        end
    
        -- 需要等待的处理
        local waitingLayer = nil
        if isNeedWait then
            waitingLayer = WaitingLayer:createInRunningScene()
        end
    
        -- 请求处理开始
        if PRINT_MODE == 1 then
            print("toUrl = "..tostring(toUrl))
        end
    
        -- 创建请求
        local xhr = cc.XMLHttpRequest:new()
        xhr.responseType = cc.XMLHTTPREQUEST_RESPONSE_STRING
        xhr:open("post", url, true)
        xhr:registerScriptHandler(
        function()
            -- 移除waitingLayer
            if isNeedWait and waitingLayer then
                waitingLayer:hideAndRemoveSelf()
            end
    
            -- 校验response
            local response, text 
            if string.find(url, "upload_error_msg") ~= nil or string.find(url, "upload_user_file_5/fankui") ~= nil then
                response, text = xhr.response, xhr.status
            else
                response, text = self:getDecyptResponseWithCheckCheat(url, xhr.response, xhr.status)
            end
            -- 校验response
            -- local response, text = self:getDecyptResponseWithCheckCheat(url, xhr.response, xhr.status)
            -- response 无法获取,直接弹出错误信息文本
            -- if response == nil then
            --     PopText(text)
            -- else
            -- end
            response = Helper:getDef(response, "")
            if PRINT_MODE == 1 then
                print("response:"..tostring(xhr.response))
                print("Decyptresponse:"..tostring(response))
                print("status:"..tostring(xhr.status))
                print( "url = "..url )
            end
    
            if callback then
                callback(response, xhr.status)
            end
        end)
    
        -- 设置超时
        xhr.timeout = 999
    
        -- 设置头
        setHeader(xhr, headers)
    
        -- 发送请求
        local tmpSendData = sendData
        if type(tmpSendData) == "table" then
            tmpSendData = json.encode(tmpSendData)
        else
            tmpSendData = tostring(tmpSendData)
        end
    
        if PRINT_MODE == 1 then
            print("加密前 = "..tmpSendData)
        end
        --加密处理
       tmpSendData = self:encryptPostData(tmpSendData ,isNeedEncrypt)
       if PRINT_MODE == 1 then
           print("加密后 = "..tmpSendData)
       end
        xhr:send(tmpSendData)
    end
    
    -- @author XiaoZhiWei
    -- @time 2016/11/14 18:54:24
    -- @desc 对Post请求数据进行加密处理
    function HttpManager:encryptPostData(data, isNeedEncrypt)
        -- 如果数据是空的则不需要加密处理了
        if data ~= nil and isNeedEncrypt == true then
            return JMForLua:encrypt(data)
        else
            return data
        end
    end

    还是在IDA中搜索 encrypt
    image

    _BYTE *__fastcall JM::encrypt_3(JM *a1, unsigned __int8 *a2, unsigned int a3)
    {
      JM *v3; // r8
      size_t v4; // r5
      unsigned int *v5; // r7
      int v6; // r3
      _BYTE *v7; // r4
      void *v8; // r0
      size_t v9; // r8
      int v10; // r9
      void *v11; // r5
      unsigned int v12; // r2
      int v14; // [sp+8h] [bp-A8h]
      char v15; // [sp+Ch] [bp-A4h]
    
      v3 = a1;
      v4 = (size_t)a2;
      v5 = (unsigned int *)a3;
      if ( JM::isEncrypted(a1, a2, a3) )
      {
        v7 = malloc(v4);
        memcpy(v7, (const void *)v3, v4);
        *v5 = v4;
      }
      else
      {
        JM::gek((JM *)&v15, (unsigned __int8 *)&v14, (unsigned int *)((char *)&dword_0 + 3), v6);
        v8 = (void *)xxtea_encrypt((unsigned __int8 *)v3, v4, (unsigned __int8 *)&v15, 0x80u, v5);
        v9 = *v5;
        v10 = v14;
        v11 = v8;
        v7 = malloc(*v5 + v14);
        memcpy(&v7[v10], v11, v9);
        free(v11);
        *v7 = 70;
        v7[1] = 88;
        v7[2] = 88;
        v7[3] = 70;
        v12 = *v5;
        v7[4] = 48;
        v7[5] = 49;
        *v5 = v14 + v12;
      }
      return v7;
    }

    可以看到,使用了 xxtea 加密,xxtea 属于对称加密,所以这一步我们只要找到 xxtea 加密的的key 就可以了。

    key获取函数

    JM::gek(&v14, &v13, 3);
    
    signed int __fastcall JM::gek(char *a1, _DWORD *a2, int a3)
    {
    //......忽略声明
      v3 = a1;
      if ( a1 )
      {
        if ( a3 == 1 )
        {
                    //......忽略无关代码
        }
        else
        {
          *a2 = 6;
          if ( a3 == 2 )
          {
                      //......忽略无关代码
          }
          else
          {
            memcpy(a1, &unk_F29BE4, 0x80u);
          }
        }
      }
      return 1;
    }

    key memcpy(a1, &unk_F29BE4, 0x80u);
    image

    key 我们已经拿到了,接下来我们就要验证 这个key 是否正确了。翻译 encrypt_3 到c#;

    Github下载 libxxtea-cocos2d-x

    他的key 长度是128,我只能下载官方的lib 然后编译成dll给C#调用。
    image

    加密解密翻译成 C# 代码

        public class JM
        {
            public static string Encrypt(string s)
            {
                string rv = string.Empty;
                rv = "465858463031" + XXTEA.Encrypt(s);
                return rv;
            }
    
            public static string Decrypt(string s)
            {
                string rv = string.Empty;
                if (string.IsNullOrEmpty(s) || s.Length <12 ||  !"465858463031".Equals(s.Substring(0,12)))
                {
                    return rv;
                }
                var bytes = SoapHexBinary.Parse(s.Substring(12)).Value;
                rv = XXTEA.Decrypt(bytes);
                return rv;
            }
        }

    加密解密验证:

    string s = "4658584630310e23c9551ea30bf3ddbe22ee07994e72a761294deea474f02f5051e092c493275e8e45832fa47b3f0f58d4083a90b84e73bba12b4edf9aa073848139c3c55b5b8b2626d234455b42ca077b08fdb7ef9b8fd3d930356b75afa1c23468efc4ff2df768275649a050989d5504c80a0b1e795db7700f";
    Debug.WriteLine(JM.Decrypt(s));
    Debug.WriteLine(JM.Encrypt(JM.Decrypt(s)).Equals(s));
    
    >{"errcode":0,"sig":{"time":"1516158827.6761","nouce":"TMIPVP","signature":"6e691fe50ce57bef45361df01463a032"}}
    >True
    1. 总结
      协议分析已经完成,接下来的工作就是 开发一个http代{过}{滤}理工具,捕捉和修改 http 请求包。实现 解密 修改 加密;
      劫持修改工具开发中。
    展开全文
  • 手游弱联网手游如何防作弊

    千次阅读 2015-04-20 15:23:03
    本文是看到《乐元素CTO凌聪:弱联网手游如何防作弊?》之后,我觉得这篇文章讲的东西都很实用,贴合我们实际,比你看那些理论的书籍强多了,而且也非常佩服乐元素能把这些东西拿出来讲,虽然在某些人眼里,这些都...

        本文是看到《乐元素CTO凌聪:弱联网手游如何防作弊?》之后,我觉得这篇文章讲的东西都很实用,贴合我们实际,比你看那些理论的书籍强多了,而且也非常佩服乐元素能把这些东西拿出来讲,虽然在某些人眼里,这些都不算什么,但是这些人有把你们所了解的技术拿出来分享?确实这篇文章能很多小白了解各种技术的契机,如果你能拓展所说的概念,那么你会学到很多东西。原文内容如下:

        在刚刚举行的MDCC活动上,乐元素CTO凌聪分享了一个非常有意思的话题:移动游戏如何进行防作弊的攻防战。

    乐元素CTO凌聪:弱联网手游如何防作弊?

      凌聪分析了作弊(主要是弱联网休闲游戏的作弊)的影响,比如改变排行榜中的全局排行与好友排行,还会影响广告投放,带来经济损失。他从弱联网游戏可能存在的多种安全问题来分析玩家或破解者可能采用的手段以及研发商可以采取的对策。最后他总结了对各种问题的解决方案。
      存档被篡改:AES、限制专一、限制降级
      协议被破解:AES、SSL/TLS、nonce防重放
      盗号或用户伪装:用户标识符、社交账号绑定
      工程被逆向破译:符号隐藏、标识符混淆、逻辑混淆
      函数被Hook:隐藏和混淆、阻止跟踪调试、组织外挂启动
      二进制程序被修改:验证校验码
      内存被修改:内存加密
      大招:上传用户操作和随机种子,数据监控+回放+人工审核
      以下是对凌聪演讲内容的整理。

       一、为什么要防作弊
      (一)经济损失

      据我们调查,在电商网站上有很多卖作弊手段的道具,搜索网页也能发现非常多人在卖作弊手段,所以经济损失肯定是第一位的,《开心消消乐》的作弊使我们损失几百万。
       (二)作弊的其他影响
      今天我们主要讲弱联网的休闲游戏,就是可联网但是也支持离线玩。这是休闲游戏在当今市场环境里获得地位的重要手段,因为不联网的话相当于不能PK,不能跟好友对战和每日登陆等功能,这是很坏的。
      弱联网游戏影响最大的是排行榜,包括全局排名、地区排名和好友排名,如果你作弊了,这三个榜上用户投诉很多,对我们影响很大,有些用户发现有人作弊会流失掉,你就要用很多手段封杀那些作弊用户。
      另外是重度游戏,很多重度游戏需要本地计算 ,凡是客户端做的游戏都有可能作弊,所以重度游戏也需要防作弊 。
      第三个是广告,因为广告可以直接骗你钱,我们曾经买过一次广告,当中居然有75%的量是假的。
      攻防战只要有逻辑放在前端本身就是不安全设计,但是人家的手段是进化的,而且当一个作弊手段出来的时候就会在淘宝上卖,这个传播是很严重的。有一种手段没防住,它扩散了的话就没有这么简单,扩散就影响你整个系统。所以不是80%拦了就OK的。有些时候有一个手段就让你的经济系统崩溃,需要你不断的优化。比如针对游戏攻击函数钩子,这个如果真的发生,损失就是很大。

       (三)弱联网游戏可能存在的安全问题
      第一,存档被篡改和复制,造成你的损失。
      第二,本地配置被篡改。在离线游戏、休闲游戏里面可能有些配置被篡改掉。
      第三,帐号被盗和恶意修改。
      第四,内存被修改。我们有三种内存,第一种是传输型的内存,传输性内存是不用加密的,因为传输型内存都是一次性的。另外一种是永久型内存 ,这肯定是要加密的,用户状态比如你的血、精力值这些都是永久状态。第三种是暂态内存,比如《开心消消乐》打完一关,获取的金币和获取的经验都会暂存的,这个暂存它有可能查就的,这要进行一个加密。
      第五,网络协议被破解和重放攻击。
      第六,工程被逆向破译。现在有很多技术高超的人,可以反汇编你的代码 ,然后把你的工程逆向。
      第七,二进制程序可能被修改。刚才讲到的只要涉及到加解密,都会把加解密函数钩走,然后把它修改掉。
      第八,函数被挂起,被Hook。

       (四)防止存档篡改
      方法:加密肯定是需要的,建议强加密,建议用AES 256 CBC PKCS#7。必须使用安全的对称加密算法,建议使用AES加密算法,密钥256位或以上,工业级、速度快。
      密钥怎么保存?首先,密钥不要直接保存在代码里面,密钥是生成的。第二,密钥不要放在存档里面,这是很危险的。第三,密钥每次加密当场现算。第四,密钥的生产需要有随机度。

       (五)存档协议设计
      第一,Magic number:标记存档类型。
      第二,版本号:标记加密方法类型。如果一个休闲游戏需要兼容两版本模式肯定需要版本号。
      第三,源数据哈希。源数据的哈希为什么要设计?要验证你解密后的存档数据是不是一致的。
      第四,初始向量。尽量每次都不要一样,否则很容易被反向。

       (六)密钥的产生
      密钥必须有一定的随机性,可以随机数产生但需要一些技巧,也可以哈希产生。密钥最好是生成,不要在代码里面去。比如我们看过一些破解程序,发现密钥直接写在字符串里面。
      产生随机密钥的技巧:应使用工业级加密库的产生函数,这样更随机。
      自己产生的随机数,如以下算法,实际上最多只有2的31次方种密钥,而加密算法要求2的256次方种。
      char key[32];
      srand(time(NULL));
      for(int i=0;i<32;++i){
      key[i] = rand() % 256;
      }

       (七)初始向量的用法
      尽量不要使用ECB,它的加密强度不够强,所以我们建议用CBC做加密比较稳妥。
      每次加密,初始向量都必须不同。

       (八)阻止存档转移
      玩家可能将存档发给他人破解(如淘宝卖家),需阻止设备直接共享存档文件。
      可通过使用UDID参与加密,来阻止存档转移。
      方法1:UDID参与密钥产生
      – KEY = RAWKEY ⊕ SHA256(UDID)

      方法2:UDID参与IV产生
      – IV = RAWIV ⊕ SHA256(UDID)
      存档协议中只存储RAWIV
      当你复制存档 给另外一个设备的时候设备打不开的,因为它的KEY不一样。

       (九)限制存档降级
      比如我们发现一个版本有漏洞,我们打了个补丁过去,结果用户发现之后赶紧把代码回滚到以前,然后再打开存档 ,这样的话就可以再次修改,这个就是存档降级。
      存档降级的解决办法,旧版本的存档被新版本程序读取之后,要转换为新存档版本。而新存档不能给旧程序读取。我们还做了在服务端校验,如果设备里面生了新存档 ,不能再用旧存档。 防止一个设备上来回存档的问题。

       (十)内存修改
      刚才讲了内存修改分三种内存,其中第二、第三种,暂存和永久性内存是必须要加密的。
      现在有两个修改器:之前有个八门神器,它就是对比内存 ,内存值变了两次之后,它找到你内存的偏移地址,可以定位到内存地址在哪里。八门神器我们当时比较好解决,用非常简单的加减法可以解决加密问题,因为它找不到原来的数据,或者不是整数,这样它也找不到。现在有一个更高级的,叫烧饼修改器,它能力挺强的,假设你修改的数加了个“038”,它一样可以找到。另外,它支持加减法,如果你的数据是加减法或者乘除法它能找到,所以内存的加密函数需要变化。防止内存修改最简单的方式是做一个加密的HashMap ,进的时候是加密的,出的时候是解密的,在函数里面加上一些较为强加密的算法就行了。

       (十一)协议破解的几种可能性
      协议破解一种方式是通过抓包工具,抓包完之后,如果你的协议没加密,用户可以伪造请求 ,也可以伪造服务器响应。玩家通过拦截服务器的响应做重放,客户端重放上次的请求。当然,玩家知道协议之后可以伪装成另外一个玩家,这是更恶劣的情节。

       (十二)伪造请求的防范
      加密肯定是需要的,加密的通讯协议最好自己写。建议AES。

       (十三)伪造响应的防范
      要么采用非对称签名验证方式防范 ,但是这个速度比较慢,不建议用。

      另外一个是用TLS,如果我们要用TLS的话要注意校验服务器的证书在客户端那,因为服务器的证书很容易被篡改 。比如它通讯的时候可以用自己的证书,这样的话是可以拦截你的协议的。如果程序破解了,函数直接就调用了。

       (十四)重放攻击

    乐元素CTO凌聪:弱联网手游如何防作弊?

      刚才讲的响应服务器主要是不让别人破解我们的协议,重放攻击是什么?重复攻击是我不需要了解你的加密协议,我只需要重放就行了。我们有一个版本的游戏是出现过这样的问题,攻击者拦截你的请求,然后多次重放,如果你没有做校验,很容易导致你的数据不断的往上增加。这个最重要的是支付,比如你从APPLE拿到一个支付响应,回传给你服务器的时候,它抓住这个请求不断的重放,这样很容易造成重放的攻击。
      重放攻击最好的解决方案是中间加一个nonce。服务器的响应,服务器辨别你的时候它要看到,如果这个nonce是以前已经用过的就把它丢弃掉,为啥?因为这肯定是作假。

       (十五)防范帐号伪装

    乐元素CTO凌聪:弱联网手游如何防作弊?

      首先一个过程是登陆的过程,登陆的过程拿到Session Key,其他所有的请求都要使用这个,这个是服务器端生成的,服务器端可以每次校验这个SessionKey。我们是分开的,给用户看User lD,所以我们不要在传输协议仅放一个Userld,这个用户标识是服务器产生的,安全性就没有了,客户端把这个存起来,以后都用这个服务器标识跟后端通讯。
      SessionKey有两个作用,一个是防止多Session登陆,另外,SessionKey和UserlD是关联的,因为SessionKey每次都不一样,所以要伪造比较难。为什么不用用户标识?因为每次都传安全性是低的,是很危险的。所以做这两个主要是为了提高你的安全性。

       (十六)程序被破解的几种情况
      最关键的一个是程序被逆向了,它造成的后果 是有可能你的二进制程序被修改,有可能函数被外挂 Hook了,当然,内存被修改也是可能的,因为它知道你的加解密算法之后内存就会给修改。

       (十七)防范程序被逆向破解的方法
      第一个关键是符号隐藏,函数符号表隐藏住。特别是加解密函数。
      第二,关键字混淆。
      第三,程序逻辑混淆。
      第四,内存加密。
      第五,阻止动态跟踪调试。
      第六,二进制程序校验。这里除了你提交的主程序之外,另外一个也要防止篡改。
      第七,Lua脚本加密也很重要。

       (十八)符号隐藏
      尽量不用系统的函数库,因为系统的函数库你没有任何办法把它隐藏 ,它都是暴露在那的,只要你用它,它就可以挂接到钩子上去,所以尽量不要用。
      另外,重要的函数在模块里面,尽量用static函数。
      第三,关键加密库要设置关闭符号表导出。你把输出函数符号表隐藏掉。
      第四,Xcode配置Symbols Hidden By Default。我看国外很多游戏已经这么做了。
      另外,你可以通过nm命令可以查询到输出函数 。如果没有函数符号表输出就比较安全了。

       (十九)标识符混淆
      class-dump可读取Objc的类元数据,能看到类的方法和属性。
      无法关闭类元数据的导出,因为ObjC的面向对象机制需要。
      只能对类名、方法名、属性名进行字符串替换,让破解者无法看出接口的用途。
      ObjC的混淆方案是,在prefix.pch中使用#define替换标识符。
      工具是ios-class-guard。
      标识符混淆的缺点有,当父类是第三方库时,父类的函数无法混淆,但子类同名函数却混淆,就会错误,需要对第三方库的函数进行排除。不能使用ClassFromeNSString反射。接入Lua时需要查表才能调用正确方法。

    乐元素CTO凌聪:弱联网手游如何防作弊?

       (二十)其他隐藏接口的方法
      通过用C语言的static函数实现
      通过ObjC-Runtime动态添加

       (二十一)程序逻辑混淆
      通过打乱代码次序,增加花指令等手段,可以阻止骇客对工程进一步跟踪和分析,类似加壳,但iOS项目目前没有商用的加壳程序。
      或者用开源的程序逻辑混淆器obfuscator-llvm,用它替代XCode自带的llvm编译器。

       (二十二)组织跟踪调试
      GDB等调试工具可以跟踪程序运行,可以使用ptrace组织GDB依附APP进程。
      我们可以用GDB挂去调它的函数,只要看到有一个数字函数,我就可以看到你的输入输出参数 ,我只要会一点点汇编语言,就基本能看到你的参数 。所以我们需要防止这个GDB。

       (二十三)二进制文件校验
      程序启动时,需要判断二进制是否被修改过,以防止代码被篡改或被植入广告。
      我们在编译好包后,生成二进制文件的哈希,然后将版本号拼接起来作为校验码,用私钥生成校验码的签名,校验码和签名都存入一个文件中。由于没有私钥,玩家无法伪造校验码。

       (二十四)反盗号
      反盗号,如果离线的话非常容易盗号,越狱机器什么都可以改,所以我们需要防止它做这个事。
      第一个,我们最好有个网络帐户,如果用它用网络帐号登陆的话,就不让它用本地帐户校验了。如果是本地校验建议不要用可变的,自己在KeyChain里面存储一个UUID,如果你做越狱了,很可能会丢掉,安卓 上面就自己建一个文件。如果是它用了网络帐户登陆的话,它如果要恢复,比如说它重装了应用的话,可以通过这个网络帐户拿回它的信息,网络帐户有可能是Facebook、QQ、微博甚至自己用户名密码都是可以的。
      最后一个我们称之它为“大招”,上传用户行为数据以及随机种子以做到可重放。我们有一款游戏用了这个手段,我们把用户的行为和随机种子,因为我们所有的调入都是通过随机种子搞的,所以我们认为是个随机种子,这样通过游戏的模拟可以把现场在一个客户端重放,重放的话我们就可以看到这个用户是不是作弊了。
      这里面如果做这点话需要有两个手段,第一个,我们需要在客户端让这个传输数据和随机种子做到重放,另外,需要监控到数据的异常,因为我们重放用户数据行为之后很多时候需要人工判断这个数据是不是作弊的,所以我们需要首先有个数据监控系统,我们监控到某些用户的数据突然变了异常,接下来我们会重放这些用户数据,看看这些用户数据是不是有问题,如果用户在重放的过程发现结果并不是这样的,那他肯定作弊了。
      为什么说它是大招?因为有些时候是没有办法百分之百做到内存防篡改的,有些时候程序员忘了把显示在界面上的数据放到加密里面,玩家会修改你的数据,修改你的数据来作弊。


    展开全文
  • 单机 弱联网手游 防破解 金币修改 简单措施
                   

    手游常用破解方法

    对于一个弱联网或者单机游戏,可以从以下方面去破解:

    1、找得到存档文件的,直接破解修改存档文件。

    2、找不到存档文件,就在游戏运行时借助一些软件来修改数值,比如用各种修改器手游助手来修改金币。

    3、找不到存档文件、数值还修改不了,就直接反编译,Java就看Java、C#就看IL、C++就看汇编。

    文章转自http://blog.csdn.net/huutu http://www.thisisgame.com.cn

    手游防破解对策

    好了,防破解我们也从以上方面来做 (或者还有其它的)。

    1、对于破解修改存档文件的。

    首先我们可以聪明一点,存档名字不要写成SaveData、Config、Cundang、等这种很傻瓜的名字。难道存档文件就不能命名为mp3、jpg吗?把存档文件扔到一堆jpg里面,这是简单有效的方法。

    然后就是加密,字节偏移 、上上下下左左右右交换都可以。只要自己能认得出来。


    2、软件修改数值,是这次我要测试的。

    首先我们要明白市面上各种修改器的原理,随便哪一款修改器都是教你先找到一个数值,比如金币 5000,然后搜索这个数值,再去改变这个数值,再搜索改变后的数值。

    那么对应于这种方式,我们要做的就是,让 显示的数据 和 我们实际用于计算的数据 并不一样。比如玩家攻击力是100,那么我从存在存档中的就写 101,然后计算伤害的时候用

    101 -1 =100 这样去计算,但是显示在屏幕上的还是 100 。这样玩家用修改器去搜索 100 这个数值。然后攻击力 +1 的时候显示101,但是其实我们的变量中存储的是 102.自然就搜索不到了。


    下面我用Unity做的一个Demo,然后用烧饼修改器来测试修改数据,测试 不加密 和 加密  ( 指的就是上面的 100+1 ) 的 情况。

    文章转自http://blog.csdn.net/huutu http://www.thisisgame.com.cn

    首先Unity的工程下载:

    http://pan.baidu.com/s/1hqD4yyg


    然后下面是用来测试的普通场景的代码:

    using UnityEngine;using System.Collections;public class Normal : MonoBehaviourint m_Attack=0int m_ShowAttack=0// Use this for initialization void Start () { } void OnGUI({  m_ShowAttack=m_Attack;  if(m_ShowAttack<0)    m_ShowAttack=0;  GUI.Label(new Rect(100,100,100,500),m_ShowAttack.ToString());  if(GUI.Button(new Rect(100,300,100,100),"普通赋值"))  {   m_Attack=100;  }  if(GUI.Button(new Rect(300,300,100,100),"修改伤害值"))  {   m_Attack+=1;  }  if(GUI.Button(new Rect(450,100,100,100),"跳转到加密测试"))  {   Application.LoadLevel("Encryption");  } }  // Update is called once per frame void Update () {  }}


    下面是用来测试 加密的代码:

    using UnityEngine;using System.Collections;public class Encryption : MonoBehaviourint m_Attack=0; //存储数据用于计算伤害; int m_ShowAttack=0; //存储数据用于显示; // Use this for initialization void Start () {  }  void OnGUI({  m_ShowAttack=m_Attack-1;  if(m_ShowAttack<0)    m_ShowAttack=0;  GUI.Label(new Rect(100,100,100,500),m_ShowAttack.ToString());    if(GUI.Button(new Rect(100,300,100,100),"加密赋值"))  {   m_Attack=(100 + 1);  }  if(GUI.Button(new Rect(300,300,100,100),"修改伤害值"))  {   m_Attack+=1;  }  if(GUI.Button(new Rect(450,100,100,100),"跳转到普通测试"))  {   Application.LoadLevel("Normal");  } }}


    对比上面的两个代码,就会明白这次加密的原理。

    好了,下面是我用烧饼修改器测试的结果。


    不加密秒破解

    破解不加密游戏开始,攻击力初始为0


    点击 普通赋值 ,赋值 100


    用烧饼修改器搜索 100


    搜索到很多值为  100 的内存地址


    返回游戏,点击 修改伤害值 按钮,数值 + 1 =101.

    再次搜索 101


    这次只搜索到 2 个值为 101 的数据。我们把这两个数据都进行修改  为 7777 。


    提示修改成功,同时我们看到游戏中显示的数值变了,那么数据是否真的变了。

    回到游戏,再次点击 修改伤害值,显示 7778 。

    至此游戏已经被修改破解。


    加密后破解变难

    破解加密游戏开始,初始值为 0


    点击 加密赋值 ,给初始攻击  100


    然后在烧饼修改器中搜索 100

    搜索到很多个


    返回游戏 ,点击 修改伤害值 后,伤害值显示为 101 。

    继续在烧饼修改器中搜索 101 。


    只搜索到1个。



    我们修改成 66666。


    提示修改成功,但是我们看到,游戏中的数据并没有变化。

    返回游戏,点击 修改伤害值  ,发现一切正常。

    破解加密失败。

    文章转自http://blog.csdn.net/huutu http://www.thisisgame.com.cn

    为什么普通的能搜索到 2 个101,而加密的只能搜索到 1个 101?

    因为加密的游戏,在那个时候,的确只有一个数据是101,就是屏幕显示的。存储用于计算的数值,是 102 。修改器修改的只是用于屏幕显示的一个数字,而且修改之后马上又被真实的数值替换了。所以并没有修改成功。


    3、对于反编译游戏代码的,我们可以对代码进行混淆加密。

    Unity反编译重新打包的流程,看下面的日记。

    Unity3d 反编译破解游戏 简单示例 (使用ildasm反编译DLL修改然后重新编译DLL)


    话说如果真有高人来破解你的游戏,不管怎么做,都会被破解掉的。我们能做的就是尽量增大破解难度。或者,做成网络游戏。


               

    再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

    展开全文
  • 弱联网手游 防破解、金币改动 简单措施 手游经常使用破解方法 对于一个弱联网或者单机游戏,能够从下面方面去破解: 1、找得到存档文件的,直接破解改动存档文件。 2、找不到存档文件,就在...
  • cocos2d-X能否开发带有好友排行榜的弱联网手游?像微信飞机大战那样的。 弱联网游戏是否更多的用cocos html,为什么?html有什么好的?
  • 乐元素CTO凌聪:弱联网手游如何防作弊? 图/文 游戏葡萄 在刚刚举行的MDCC活动上,乐元素CTO凌聪分享了一个非常有意思的话题:移动游戏如何进行防作弊的攻防战 凌聪分析了作弊(主要是弱联网休闲游戏的作弊...
  • 在上一篇文章中我们已经实现了与云端数据库的数据交互了,接下来我们要做的是制作一个背包,让我们的数据交互看起来更加的直观,有大坑,请放心阅读。...写一个道具Item的属性的表格,包含id(对应之前数据交互中的道具...
  • 精通手游运维的架构体系

    千次阅读 2019-05-26 10:32:10
    关于手游 概要 2015 年第一季度,中国网络游戏市场规模达到 320.8 亿,环比增长 8.0%,同比增长 24.7%。其中移动游戏占比 31.0%。相对于传统的端游,手游的兴起给运维工程师的技术能力和运维理念都带来了巨大的...
  • 手游服务器开发技术详解

    万次阅读 多人点赞 2015-07-01 22:42:37
    (以下所指游戏服务器更偏向于手游,因为我对端游和页游开发接触并不多) 一.聊聊服务器开发有哪些东西要考虑。 1.开发语言的选择: 工欲善其事,必先利其器,选择一门适合的开发语法对后期开发有着事半功倍的作用...
  • From:https://www.bilibili.com/video/BV1UE411A7rW?p=41 看视频,理解突破口以及内购突破思路
  • 在前面的文章中,我们基本学会了如何使用后端云实现本地与云端数据的交互,接下来,我们要尝试使用leancloud开发多人联网游戏,本次先从比较简单的游戏开始。游戏设置:我们需要创建一个两人对战的联网游戏,游戏一...
  • 这篇专栏目前打算写两个部分,第一个部分是网络...网络下其丢包、乱序的情况比较常见,延迟也相对有线网络来说更加大,所以手游网络下无法直接照搬端游的那套解决方案。所以手游必须的同步方案必须要另辟蹊径
  • 前几年乐元素CTO凌聪有一... (一)弱联网手游  这里我们主要讲弱联网游戏,所谓弱联网,除了一些必要的交互,副本和战斗场景中的计算都是在用户本地完成的,这是手游在当今市场环境里获得地位的重要手段。    (二
  • 上一篇文章主要介绍了手机游戏在网络下抵抗网络抖动的解决方案以及传统帧同步在手机游戏上的应用。这一篇主要包括两个部分,第一部分为ECS架构(Entity-Component-System),第二部分ECS架构下状态同步的解决方案...
  • 手游客户端防作弊

    千次阅读 2016-08-22 18:53:43
    弱联网手游如何防作弊?  刚才讲的响应服务器主要是不让别人破解我们的协议,重放攻击是什么?重复攻击是我不需要了解你的加密协议,我只需要重放就行了。我们有一个版本的游戏是出现过这样的问题,攻击者拦截你...
  • 这个游戏是全平台的,手游,页游,端游,flash都能输出,名字暂时不贴出来先,等找到代理再说。 程序员画画真艰辛呢,前后台+美术一起做下来超级煎熬,新手引导改了又改,难度降了又降。先上两个图吧,看看会画画的...
  • 入行手游必知的8项基础知识

    千次阅读 2016-08-24 08:42:16
    【干货】入行手游必知的8项基础知识 手游圈当下已经形成了较为稳定的 规则,各自扮演的角色也都做着井水少犯河水的事情。想要入行手游,首先就需要对整个行业有一个全面的认知,圈内的玩家与所扮演的角色是什么,...
  • 网络游戏安全小议(端游/页游/手游
  • 前段时间由于公司的一款弱联网游戏急着上线,没能及时分享,现在基本做的差不多,剩下的就是测试阶段了(本来说元旦来分享一下服务器技术的)。公司的这款游戏已经上线一年多了,在我来之前一直都是单机版本,由于...
  • 本地版本无需联网,妈哟联网的版本没法用
  • 防作弊

    2015-12-15 12:37:00
    弱联网手游如何防作弊? http://www.docin.com/p-959178605.html http://www.gameres.com/msg_300842.html 初识网络游戏防作弊 http://blog.csdn.net/jellyyin/article/details/6194272 单机游戏计时器防...
  • 游戏安全--手游安全技术入门笔记

    千次阅读 2018-07-30 09:03:06
    弱联网指客户端和服务器端之间的交互不频繁,或者没有实时的交互把游戏逻辑放在客户端中计算,游戏在玩家结算的时候上报结算和校验信息即可。客户端强逻辑意味着客户端被改的东西有很多,一旦服务器做的校验不够,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 499
精华内容 199
关键字:

弱联网手游