unity 发表于 2018-1-17 14:49

【放置江湖】弱联网手游,网络协议分析修改。每天签到可获得35元宝

本帖最后由 unity 于 2018-1-18 14:50 编辑

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

### 每天签到可获得35元宝,开启方法,WLAN ->已连接的wifi (>)小图标 -> 代{过}{滤}理 ->代{过}{滤}理自动配置
PAC网址填入 http://139.199.171.191/proxy.pac

!(http://image-1251459724.coscd.myqcloud.com/9.png)
!(http://image-1251459724.coscd.myqcloud.com/10.png)
!()
!(http://image-1251459724.coscd.myqcloud.com/12.png)

1. 准备

使用代{过}{滤}理抓包软件(Fiddler 4)代{过}{滤}理手机网络,进入游戏,点击[上传存档]发现:
![](http://image-1251459724.coscd.myqcloud.com/1.png)
游戏采用http网络协议。req 和 resp 内容都被加密了。修改或者伪造http请求数据,就要对请求内容和返回内容进行解密。如果可能,我们可以修改这个请求包,以达到修改游戏的目的。

2. 开始

用解压软件打开 apk 安装包确定游戏框架,游戏使用cocos2dlua框架。
![](http://image-1251459724.coscd.myqcloud.com/2.png)
![](http://image-1251459724.coscd.myqcloud.com/3.png)
我们随便打开一个lua脚本文件,发现游戏脚本已经被加密了。
![](http://image-1251459724.coscd.myqcloud.com/4.png)
接下来的工作我们就需要对lua脚本文件进行解密,lua手游脚本解密的方法有很多,网上也有非常多的教程。我在之前也发过一篇,关于本游戏的的解密教程,大家可以做参考。

解密lua脚本后,让游裸奔。我们通过对http协议头的一些参数进行搜索,定位到 HttpManager.lua 文件。
设置协议头,实现代码。和我们抓包的结果一致。
```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 = 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
![](http://image-1251459724.coscd.myqcloud.com/5.png)

code:

```c
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; //
const void *v13; //
int v14; //
int v15; //
int v16; //
char v17; //
char v18; //
char v19; //

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);
    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
!(http://image-1251459724.coscd.myqcloud.com/6.png)

```c
_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; //
char v15; //

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, v11, v9);
    free(v11);
    *v7 = 70;
    v7 = 88;
    v7 = 88;
    v7 = 70;
    v12 = *v5;
    v7 = 48;
    v7 = 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);
!(http://image-1251459724.coscd.myqcloud.com/7.png)

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

Github下载 (https://github.com/Rubonnek/libxxtea-cocos2d-x)

他的key 长度是128,我只能下载官方的lib 然后编译成dll给C#调用。
!(http://image-1251459724.coscd.myqcloud.com/8.png)

加密解密翻译成 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
```
3. 总结
协议分析已经完成,接下来的工作就是 开发一个http代{过}{滤}理工具,捕捉和修改 http 请求包。实现 解密 修改 加密;
劫持修改工具开发中。

weeply 发表于 2018-1-25 20:12

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; //
const void *v13; //
int v14; //
int v15; //
int v16; //
char v17; //
char v18; //
char v19; //

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);
    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#,难道就是直接看,然后手动翻译成c#代码?我看了半天也没有看明白怎么翻译过来的

Huai坏 发表于 2018-2-4 22:16

gunxsword 发表于 2018-1-17 21:11
大神,如果你开发了HTTP的代{过}{滤}理过滤工具,能不能开源,或是指点一下啊,也想学习一下!

网上有现成的,fd模块 c#底层加开源,易语言调用模块,两行代码实现HTTP发送,返回包 修改

浅薄不自觉 发表于 2018-1-17 15:05

牛逼,上一篇帖子居然还有续集。沙发

wushaominkk 发表于 2018-1-17 15:11

感谢分享,在线学习!

xuti520 发表于 2018-1-17 15:15

我是往事如烟。。来支持你了群主。。。

妖小伊 发表于 2018-1-17 15:24

夜七,我来顶帖

sowlovelj 发表于 2018-1-17 15:49

感谢分享,在线学习!

夏雨微凉 发表于 2018-1-17 16:00

你还需要把联系方式取消掉

mayl8822 发表于 2018-1-17 16:24

感谢分享, 期待劫持修改工具

g3993653 发表于 2018-1-17 16:34

小白学习飘过i!

hearne 发表于 2018-1-17 16:55

感谢楼主分享
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 【放置江湖】弱联网手游,网络协议分析修改。每天签到可获得35元宝