首页
论坛
课程
招聘
[原创]从正向对某网盘进行逆向分析
2020-9-14 11:51 2195

[原创]从正向对某网盘进行逆向分析

2020-9-14 11:51
2195

本文首发于我的个人博客


前言

谈到逆向,大家第一反应就是OD, IDA, x64dbg这类的工具,由于我不是专业做逆向安全的,对于这些逆向工具的使用仅仅属于会用的程度,所以,本文,我将从一个软件开发者的正向角度在Windows平台下对某网盘的通信协议进行逆向分析。

工具准备

协议分析

准备工作

既然我们要分析协议,首先就要抓包,主要有两类工具,其实现原理也不太一样。

Wireshark+Chrome

 

首先添加SSLKEYLOGFILE环境变量,然后用Chrome把需要抓包的域名都访问一遍,然后Wireshark配置Secretlog项即可解密对应的https流量,具体实施方式不写了,有兴趣的同学以Wireshark Chrome SSLKEYLOGFILE这三个关键词进行搜索,教程实在是太多。

 

这种方式的优点是不会对系统或者通信本身产生任何伤害,因为它没有做任何劫持,而是实打实的解密了,解密的原理是利用了NSS Key Log,对于启用了SSL-Pinning的客户端效果更好。缺点是无法指定进程抓包,而且操作起来有一定的不便行,在分析客户端的过程中,一旦有一个新的域名出现,就需要先用Chrome访问一遍才能解密。

 

本文将采用第二种方法来进行抓包。

Proxifier+Charles

 

Charles使用https中间人劫持技术,所以它同样可以解密https流量,但是它只对使用了wininet系列api的流量有效,而我们的网盘客户端使用的是libcurl,所以需要借助Proxifier来将客户端的流量重定向到Charles,从而实现https解密。

  • 添加Proxifier的Proxy Server

    Proxy Servers

  • 添加Proxifier的Proxification Rules

    Proxification Rules

  • Charles启用SSL代理

    SSL Proxying Settings

到这里,我们的环境准备工作就做完了,只要启动客户端就能看到成功解密的https流量。不过由于客户端默认以管理员权限运行,为了避免不必要的麻烦,还是使用Resource Hacker修改下manifest文件,以普通用户运行。

 

Resource Hacker

 

修改高亮部分由requireAdministrator修改为asInvoker,然后点击工具栏的编译按钮,最后保存即可。

 

到了激动人心的时刻,启动客户端!

 

启动客户端

登录

输入账号密码,点击登录按钮,我们看到了这样的数据

 

登录请求头

 

登录请求数据

 

app_id看起来应该是标识不同渠道的,很可能是固定值;password是32位16进制字符串,猜测是密码的md5值,我找了个工具试了下,结果还真是-v-;timestamp是毫秒单位的时间戳,这个很明显,没什么好说的;platfrom看起来是系统信息拼接的字符串,虽然信息比较多,但是这里也能看出个大概,[app_id]+[exe version]+[mac]+pc+[computer name]+[system version],当然尚不能完全确定,暂时先放一边;signpassword类似,看起来也是md5值,但具体是谁的md5值就不得而知了,尝试了把所有参数拼接为字符串计算md5结果也不对。

 

到这里,从抓包的角度来说,我们已经走不下去了,因为platformsign参数搞不定的话流程就断了。

签名算法分析

是时候了,来吧!

定位函数位置

为了方便分析,首先将客户端修改为静态基址。

 

静态基址

 

这样,我们就可以将OD的基址和IDA的基址设置为一致,即使重启进程也不用重新设置了。

 

Rebase Section

 

通过前面的分析,我们知道登录地址为/loginServer/login,在IDA中所搜字符串,只有一处即aHttpsPanApiBit,按x查找引用,仍然只有一处(由于我已经分析过,所以改了函数名)。

 

aHttpsPanApiBit

// 发送登录请求
int __stdcall login_request_412A20(char *passport, int a2)
{
  LPSTR v2; // eax
  __time64_t v3; // ST2C_8
  int v4; // eax
  int v5; // eax
  int v6; // eax
  int v7; // eax
  int v8; // eax
  int v9; // eax
  int v10; // eax
  int v11; // eax
  int v12; // eax
  int v13; // eax
  LPSTR v14; // eax
  int *v15; // edi
  int *v16; // esi
  int *v17; // ebx
  int *v18; // eax
  __time64_t v19; // ST28_8
  int *v20; // ST20_4
  int *v21; // ST08_4
  int v22; // eax
  int v23; // eax
  int v24; // eax
  int v25; // eax
  int v26; // eax
  int v27; // eax
  int v28; // eax
  int v29; // eax
  int v30; // eax
  int v31; // eax
  int v32; // eax
  _DWORD *v33; // eax
  signed int v34; // edi
  int v35; // eax
  _DWORD *v36; // eax
  int v37; // ebx
  int v38; // eax
  _DWORD *v39; // eax
  int v40; // eax
  _DWORD *v41; // eax
  int v43; // [esp+0h] [ebp-38Ch]
  char v44; // [esp+10h] [ebp-37Ch]
  char v45; // [esp+20h] [ebp-36Ch]
  char v46; // [esp+B8h] [ebp-2D4h]
  char v47; // [esp+C8h] [ebp-2C4h]
  int *v48; // [esp+160h] [ebp-22Ch]
  int v49; // [esp+164h] [ebp-228h]
  __time64_t v50; // [esp+168h] [ebp-224h]
  char v51; // [esp+170h] [ebp-21Ch]
  char v52; // [esp+178h] [ebp-214h]
  int v53; // [esp+180h] [ebp-20Ch]
  char v54; // [esp+184h] [ebp-208h]
  char v55; // [esp+204h] [ebp-188h]
  int v56; // [esp+220h] [ebp-16Ch]
  char v57; // [esp+23Ch] [ebp-150h]
  char v58; // [esp+258h] [ebp-134h]
  CHAR v59; // [esp+274h] [ebp-118h]
  char v60; // [esp+290h] [ebp-FCh]
  char v61; // [esp+2ACh] [ebp-E0h]
  int v62[7]; // [esp+2ECh] [ebp-A0h]
  int MultiByteStr[7]; // [esp+308h] [ebp-84h]
  char v64; // [esp+324h] [ebp-68h]
  char v65; // [esp+340h] [ebp-4Ch]
  int v66; // [esp+350h] [ebp-3Ch]
  int v67; // [esp+354h] [ebp-38h]
  char v68; // [esp+35Ch] [ebp-30h]
  int v69; // [esp+36Ch] [ebp-20h]
  int v70; // [esp+370h] [ebp-1Ch]
  int *v71; // [esp+37Ch] [ebp-10h]
  int v72; // [esp+388h] [ebp-4h]

  v71 = &v43;
  v49 = a2;
  v53 = 1;
  v72 = 0;
  sub_4096D0(&v44);
  LOBYTE(v72) = 1;
  sub_4096D0(&v46);
  v70 = 15;
  v69 = 0;
  v68 = 0;
  LOBYTE(v72) = 3;
  v50 = 1000 * _time64(0);
  sub_51BA80(v62);                              // 获取各种信息
  LOBYTE(v72) = 4;
  v2 = W2A_51CF00((LPSTR)MultiByteStr);
  LOBYTE(v72) = 5;
  sub_40A7F0(v2, (int)&v68);
  LOBYTE(v72) = 4;
  sub_407400((int)MultiByteStr);
  LOBYTE(v72) = 3;
  sub_438100((int)v62);
  v3 = v50;
  v4 = sub_410AD0((int)&v45, "app_id=");
  v5 = sub_410AD0(v4, "d0beea3fe1a1423392690dfd4d3713fd");
  v6 = sub_410AD0(v5, "&passport=");
  v7 = sub_4351B0(v6, passport);
  v8 = sub_410AD0(v7, "&password=");
  v9 = sub_4351B0(v8, (_DWORD *)passport + 7);
  v10 = sub_410AD0(v9, "&platform=");
  v11 = sub_4351B0(v10, &v68);
  v12 = sub_410AD0(v11, "&timestamp=");
  v13 = sub_434CA0(v12, v3, SHIDWORD(v3));
  sub_410AD0(v13, "ZWtVdHB0WjBocFIxdmNzZlg3cUQ5SFhEYU5pWGsv");
  sub_4073A0(MultiByteStr, "d0beea3fe1a1423392690dfd4d3713fd");
  LOBYTE(v72) = 6;
  sub_409840(&v44, (int)&v64);                  // 获取拼接完成待签名的字符串 [edx]
  LOBYTE(v72) = 7;
  sub_516850((int)&v56);                        // 数据签名
  LOBYTE(v72) = 8;
  v14 = W2A_51CF00(&v59);
  LOBYTE(v72) = 9;
  v15 = sub_51D080((int *)&v55, v14);
  LOBYTE(v72) = 10;
  v48 = sub_51D080((int *)&v58, &v68);
  LOBYTE(v72) = 11;
  v16 = sub_51D080((int *)&v57, (_DWORD *)passport + 7);
  LOBYTE(v72) = 12;
  v17 = sub_51D080((int *)&v60, passport);
  LOBYTE(v72) = 13;
  v18 = sub_51D080(v62, MultiByteStr);
  LOBYTE(v72) = 14;
  v19 = v50;
  v20 = v48;
  v21 = v18;
  v22 = sub_410AD0((int)&v47, "app_id=");
  v23 = sub_4351B0(v22, v21);
  v24 = sub_410AD0(v23, "&passport=");
  v25 = sub_4351B0(v24, v17);
  v26 = sub_410AD0(v25, "&password=");
  v27 = sub_4351B0(v26, v16);
  v28 = sub_410AD0(v27, "&platform=");
  v29 = sub_4351B0(v28, v20);
  v30 = sub_410AD0(v29, "&timestamp=");
  v31 = sub_434CA0(v30, v19, SHIDWORD(v19));
  v32 = sub_410AD0(v31, "&sign=");
  sub_4351B0(v32, v15);
  LOBYTE(v72) = 13;
  sub_407400((int)v62);
  LOBYTE(v72) = 12;
  sub_407400((int)&v60);
  LOBYTE(v72) = 11;
  sub_407400((int)&v57);
  LOBYTE(v72) = 10;
  sub_407400((int)&v58);
  LOBYTE(v72) = 9;
  sub_407400((int)&v55);
  LOBYTE(v72) = 8;
  sub_407400((int)&v59);
  LOBYTE(v72) = 7;
  sub_438100((int)&v56);
  LOBYTE(v72) = 6;
  sub_407400((int)&v64);
  LOBYTE(v72) = 3;
  sub_407400((int)MultiByteStr);
  sub_4AE570(&v61);
  v67 = 15;
  v66 = 0;
  v65 = 0;
  LOBYTE(v72) = 16;
  sub_4073A0(MultiByteStr, "https://pan-api.bitqiu.com/loginServer/login");
  LOBYTE(v72) = 17;
  v33 = (_DWORD *)sub_409840(&v46, (int)&v64);
  LOBYTE(v72) = 18;
  v34 = sub_4AEB40((int)&v61, MultiByteStr, v33, (unsigned int)&v65);
  LOBYTE(v72) = 17;
  sub_407400((int)&v64);
  LOBYTE(v72) = 16;
  sub_407400((int)MultiByteStr);
  if ( !v34 )
  {
    sub_521840(0);
    LOBYTE(v72) = 19;
    sub_524230(&v54);
    LOBYTE(v72) = 20;
    if ( !(unsigned __int8)sub_524310((int)&v65, &v54, (int)&v51, 1) )
    {
      v53 = 0;
LABEL_19:
      LOBYTE(v72) = 19;
      sub_402AC0(&v54);
      goto LABEL_20;
    }
    if ( (unsigned __int8)sub_522200(&v51) )
    {
      v53 = 0;
      LOBYTE(v72) = 19;
      sub_402AC0(&v54);
    }
    else
    {
      if ( v52 == 7 )
      {
        if ( (unsigned __int8)sub_522A70("message") && *(_BYTE *)(sub_522700(&v51, "message") + 8) == 4 )
        {
          v35 = sub_522700(&v51, "message");
          v36 = (_DWORD *)sub_521E20(v35);
          LOBYTE(v72) = 21;
          v37 = v49;
          sub_40A7F0(v36, v49 + 28);
          LOBYTE(v72) = 20;
          sub_407400((int)&v64);
        }
        else
        {
          v37 = v49;
        }
        if ( (unsigned __int8)sub_522A70("code") && *(_BYTE *)(sub_522700(&v51, "code") + 8) == 4 )
        {
          v38 = sub_522700(&v51, "code");
          v39 = (_DWORD *)sub_521E20(v38);
          LOBYTE(v72) = 22;
          sub_40A7F0(v39, v37);
          LOBYTE(v72) = 20;
          sub_407400((int)&v64);
        }
        if ( (unsigned __int8)sub_522A70("data") && *(_BYTE *)(sub_522700(&v51, "data") + 8) == 7 )
        {
          v40 = sub_522700(&v51, "data");
          v41 = (_DWORD *)sub_522D00(v40);
          LOBYTE(v72) = 23;
          sub_40A7F0(v41, v37 + 56);
          LOBYTE(v72) = 20;
          sub_407400((int)&v64);
        }
        goto LABEL_19;
      }
      v53 = 0;
      LOBYTE(v72) = 19;
      sub_402AC0(&v54);
    }
LABEL_20:
    LOBYTE(v72) = 16;
    sub_521BF0(&v51);
    goto LABEL_21;
  }
  v53 = 256;
LABEL_21:
  LOBYTE(v72) = 15;
  sub_407400((int)&v65);
  LOBYTE(v72) = 3;
  sub_4AE810(&v61);
  LOBYTE(v72) = 2;
  sub_407400((int)&v68);
  LOBYTE(v72) = 1;
  sub_403F30(&v46);
  LOBYTE(v72) = 0;
  sub_403F30(&v44);
  return v53;
}

在充满各种v开头的符号中间,字符串总是显得如此亲切。都是&k=v的形式,很明显这里是拼接http请求参数的地方,大多数参数我们已经知道了,我们只重点看platformsign参数即可。跟到这里,就没有捷径可走了,只能耐下性子一点点分析。

platform参数解析

// 获取系统信息并拼接为字符串
// [app_id];[exe_version];[mac];pc;[computer_name];[sys_version]
// 
_DWORD *__thiscall sub_51BA80(_DWORD *this)
{
  _DWORD *v1; // edi
  int v2; // ecx
  _DWORD *v3; // eax
  int v4; // ecx
  int v5; // ecx
  _DWORD *v6; // eax
  int v7; // ecx
  int v8; // ecx
  int v9; // ecx
  int v10; // ecx
  _DWORD *v11; // eax
  int v12; // ecx
  int v13; // ecx
  _DWORD *v14; // eax
  int v15; // ecx
  int v17; // [esp+0h] [ebp-48h]
  int v18; // [esp+10h] [ebp-38h]
  _DWORD *v19; // [esp+14h] [ebp-34h]
  char v20; // [esp+18h] [ebp-30h]
  int *v21; // [esp+38h] [ebp-10h]
  int v22; // [esp+44h] [ebp-4h]

  v21 = &v17;
  v1 = this;
  v19 = this;
  this[5] = 7;
  this[4] = 0;
  *(_WORD *)this = 0;
  v18 = 1;
  v22 = 1;
  sub_408120(L"d0beea3fe1a1423392690dfd4d3713fd", (int)this);
  sub_411600(v2, L";", (int)v1);
  v3 = get_exe_version_51B7B0(&v20);
  LOBYTE(v22) = 2;
  if ( v3[5] >= 8u )
    v3 = (_DWORD *)*v3;
  sub_411600(v4, v3, (int)v1);
  LOBYTE(v22) = 1;
  sub_438100((int)&v20);                        // 重置缓冲区
  sub_411600(v5, L";", (int)v1);
  v6 = (_DWORD *)get_mac_51B1F0((int)&v20);
  LOBYTE(v22) = 3;
  if ( v6[5] >= 8u )
    v6 = (_DWORD *)*v6;
  sub_411600(v7, v6, (int)v1);
  LOBYTE(v22) = 1;
  sub_438100((int)&v20);
  sub_411600(v8, L";", (int)v1);
  sub_411600(v9, L"pc", (int)v1);
  sub_411600(v10, L";", (int)v1);
  v11 = (_DWORD *)get_computer_name_51B390(&v20);
  LOBYTE(v22) = 4;
  if ( v11[5] >= 8u )
    v11 = (_DWORD *)*v11;
  sub_411600(v12, v11, (int)v1);                // 增加计算机名
  LOBYTE(v22) = 1;
  sub_438100((int)&v20);
  sub_411600(v13, L";", (int)v1);
  v14 = (_DWORD *)get_sysver_string_51B460(&v20);
  LOBYTE(v22) = 5;
  if ( v14[5] >= 8u )
    v14 = (_DWORD *)*v14;
  sub_411600(v15, v14, (int)v1);
  LOBYTE(v22) = 1;
  sub_438100((int)&v20);
  return v1;
}

都是体力活,没啥好说的,一步步跟就好了,只要找准了缓冲区地址,就很开心了,使用内存窗口能看到字符串逐渐累加,感觉自己离胜利越来越近。

 

系统信息拼接

 

这里有两点要说明的是,首先,eax里是二级指针,所以需要[[eax]]才能定位到字符串位置,另外一点就是每次会申请新的内存空间来存放数据,所以字符串地址会变化,不过只要认准了eax就问题不大。

 

最终拼接完成的格式为:[app_id];[exe_version];[mac];pc;[computer_name];[sys_version]

sign参数分析

函数sub_410AD0是对字符串进行拼接,最终拼接完成的格式为:app_id=d0beea3fe1a1423392690dfd4d3713fd&passport=lniwn&password=1a46be489100d6a089358eff29b98f7a&platform=[platform]&timestamp=1599636155000ZWtVdHB0WjBocFIxdmNzZlg3cUQ5SFhEYU5pWGsv,拿到数据之后,由函数sub_516850进行签名,该函数内部调用了sub_516730对数据进行加密,下面截取核心代码

 if ( md5_516730(v1, v2, &v12) )             // 数据加密 v1:原始串 v2:原始串长度 v12:接收缓冲区,16字节长
    {
      Dst[0] = 0;
      for ( i = 0; ; ++i )
      {
        v6 = i;
        if ( i >= 0x10 )
          break;
        *(_DWORD *)Dst = 0;
        v19 = 0;
        swprintf_s(Dst, 4u, L"%02x", *(&v12 + i));
        sub_411600((int)&v9, Dst, (int)&v9);
      }
    }

sub_516730对数据处理完成后,通过v12返回,然后以十六进制字符串形式进行存储到v9中,我们看到v12的长度i为0x10即16,以%02x打印的话,v9最终就是长度为32的字符串了,下面看下加密逻辑sub_516730

char __cdecl md5_516730(BYTE *pbData, DWORD dwDataLen, BYTE *a3)
{
  int v4; // [esp+0h] [ebp-30h]
  DWORD pdwDataLen; // [esp+10h] [ebp-20h]
  HCRYPTPROV phProv; // [esp+14h] [ebp-1Ch]
  HCRYPTHASH phHash; // [esp+18h] [ebp-18h]
  char v8; // [esp+1Fh] [ebp-11h]
  int *v9; // [esp+20h] [ebp-10h]
  int v10; // [esp+2Ch] [ebp-4h]

  v9 = &v4;
  pdwDataLen = 16;
  v8 = 0;
  phProv = 0;
  phHash = 0;
  v10 = 0;
  if ( CryptAcquireContextW(&phProv, 0, 0, 1u, 0xF0000000)
    && CryptCreateHash(phProv, 0x8003u, 0, 0, &phHash)
    && CryptHashData(phHash, pbData, dwDataLen, 0)
    && CryptGetHashParam(phHash, 2u, a3, &pdwDataLen, 0) )
  {
    v8 = 1;
  }
  if ( phHash )
  {
    CryptDestroyHash(phHash);
    phHash = 0;
  }
  if ( phProv )
  {
    CryptReleaseContext(phProv, 0);
    phProv = 0;
  }
  return v8;
}

CryptCreateHash用来初始化hash表,0x8003表示CALG_MD5,所以这个函数就是对数据进行md5编码。

代码实现

目前,我们已经分析完客户端登录需要的所有参数,可以进行代码实现了

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import asyncio
import hashlib
import logging
import time
import aiohttp
from urllib.parse import quote

log = logging.getLogger()
app_id = 'd0beea3fe1a1423392690dfd4d3713fd'
magic_code = 'ZWtVdHB0WjBocFIxdmNzZlg3cUQ5SFhEYU5pWGsv'


def md5_string(src) -> str:
    if isinstance(src, str):
        return hashlib.md5(src.encode('utf-8')).hexdigest()
    elif isinstance(src, bytes):
        return hashlib.md5(src).hexdigest()
    else:
        raise TypeError('unsupported type {}'.format(type(src)))


def gen_platform() -> str:
    exe_version = '3.0.1.1'  # Matrix_Cloud.exe version
    mac = 'FF:FF:FF:FF:FF:FF'  # your mac address
    pc_name = 'LNIWN-PC'  # your pc name
    sys_version = 'Windows 10'
    return ';'.join((app_id, exe_version, mac, 'pc', pc_name, sys_version))


def gen_login_payload(passport, password, platform) -> str:
    password = md5_string(password)
    timestamp = str(int(time.time()) * 1000)
    raw_data = '&'.join((
        'app_id=' + app_id,
        'passport=' + passport,
        'password=' + password,
        'platform=' + platform,
        'timestamp=' + timestamp,
    ))
    raw_data += magic_code
    sign = md5_string(raw_data)
    return '&'.join((
        'app_id=' + app_id,
        'passport=' + quote(passport),
        'password=' + password,
        'platform=' + quote(platform),
        'timestamp=' + timestamp,
        'sign=' + sign,
    ))


async def login(client: aiohttp.ClientSession, passport, password):
    url = "https://pan-api.bitqiu.com/loginServer/login"
    async with client.post(url, data=gen_login_payload(passport, password, gen_platform()),
                           headers={
                               'Content-Type': 'application/x-www-form-urlencoded'
                           }) as resp:
        data = await resp.json(encoding='utf-8')
        log.debug(data)
        if data["code"] == "10200":
            return data["data"]
        else:
            log.error(data["message"])
            return


async def user_get(client: aiohttp.ClientSession, user: dict):
    url = "https://pan-api.bitqiu.com/user/get"
    async with client.post(url, data=aiohttp.FormData({
        'app_id': app_id,
        'platform': gen_platform(),
        'access_token': user['token'],
        'open_id': user['open_id'],
        'user_id': user['user_id'],
    }), headers={
        'Content-Type': 'application/x-www-form-urlencoded'
    }) as resp:
        data = await resp.json(encoding='utf-8')
        log.debug(data)
        if data["code"] == "10200":
            return data["data"]
        else:
            log.error(data["message"])
            return


async def run(loop):
    async with aiohttp.ClientSession(loop=loop, headers={
        'User-Agent': 'curl/3C48359D-C0B2-4C09-A7A7-608910AA4D62',
        'Accept': 'application/json,text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Encoding': 'gzip, deflate',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        'Connection': 'keep-alive',
        'Pragma': 'no-cache'
    }) as client:
        user = await login(client, "your passport", "your password")
        await user_get(client, user)


def main():
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s - %(name)-12s - %(levelname)-5s - %(message)s',
        datefmt='%y-%m-%d %H:%M:%S'
    )

    loop = asyncio.new_event_loop()
    try:
        loop.run_until_complete(run(loop))
    except (asyncio.TimeoutError, asyncio.CancelledError, aiohttp.ClientError) as e:
        log.exception(e)
    finally:
        loop.run_until_complete(loop.shutdown_asyncgens())
        loop.close()


if __name__ == '__main__':
    main()

以上只是正常登录流程,对于初次登录的用户,或者mac地址变化的用户,会进入

{'code': '10807', 'message': '更换设备登录验证'}

这个流程顺着上面的思路分析即可,这里就不再继续写了。

后记

对于安卓端,抓包看了下,参数结构与windows端是一致的,只是sign参数不一致,所以需要解包拿到magic_code,由于该网盘客户端使用了360加固,且在模拟器无法运行,而我手头又没有真机环境,就暂且放下了,后面有机会再写安卓端的吧。


看雪论坛2020激励机制:能力值、活跃值和雪币体系!会员积分、权限和会员发帖、回帖活跃程度关联!

最后于 2020-9-14 15:39 被kanxue编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (7)
雪    币: 164
活跃值: 活跃值 (167)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
fengyunabc 活跃值 1 2020-9-14 14:00
2
0
感谢分享!
雪    币: 3393
活跃值: 活跃值 (109)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wanttobeno 活跃值 2020-9-14 15:05
3
0
感谢分享!
雪    币: 29
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
居客 活跃值 2020-9-15 17:10
4
0
感谢分享
雪    币: 153
活跃值: 活跃值 (76)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
人在塔在 活跃值 2020-9-16 09:11
5
0
好文,支持一个
雪    币: 712
活跃值: 活跃值 (128)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
吻你裙 活跃值 2020-9-18 20:32
6
0
感谢分享! !
雪    币: 193
活跃值: 活跃值 (54)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cd37ycs 活跃值 2020-9-18 21:03
7
0
好文,支持一个
雪    币: 3304
活跃值: 活跃值 (104)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Crakme 活跃值 4天前
8
0
楼主有心了,poc都用上协程了。
游客
登录 | 注册 方可回帖
返回