首页
社区
课程
招聘
[原创] 安卓协议逆向 cxdx 分析与实现
发表于: 2023-1-11 11:33 16539

[原创] 安卓协议逆向 cxdx 分析与实现

2023-1-11 11:33
16539

app 版本:5.0.0
设备:K40 刷 piexl 11 rom
抓包工具:Charles
反汇编工具:JEB、JADX、IDA
inject:frida

POST /v1/api/app/login/doLogin HTTP/1.1
X-OsVersion: 30
User-Agent: Mo
zilla/5.0 (Linux; Android 11; M2012K11AC Build/RQ3A.211001.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/108.0.5359.128 Mobile Safari/537.36 CSDNApp/5.0.0(Android)wToken/0.0.1
X-RandomNum: 54736
X-Access-Token: 00871d5df0d4f51efb5883b3b2fd2359
platform: android
X-Ca-Signature-Headers: X-Ca-Timestamp,X-Ca-Key,X-Ca-Nonce
Authorization:
X-OS: Android
c_appVersion: 5.0.0
X-App-ID: CSDN-APP
X-App-Theme: day
content-type: application/json; charset=UTF-8
X-Ca-Signature: BqhPpXbobBOndykiyCtOVK06GHLkfLbs1y4B3Ek0gnY=
X-ConnectionType: WIFI
UserToken:
X-TimeStamp: 1671939318488
Cookie: UserName=;UserToken=
X-Ca-Key: 203789067
Accept: application/json
X-Device-ID: aid0f0fef992b53479187546b3c621157f0
wToken: e447_5rU5WWYQegPo5EMOZSnKzF4YPtJSetwo+lGrEUrtZaKEe73GkiTOoE83PLp0yivsi4pZV7HySc+lbsebppMqHlXmQwVLx0vrlQpYC0b99vOYBRZWnVbeMhLZ4WUuAAKH/V07WkNSNXORUsgHumLj1BZZ7x1riK8beahdp9ctmSwP3AdA/sZA4OkzEVF4rJ+G6nwyyGcI/JLoRH0/1hPUT91sdBKKA64yj1QKRAZuJjsX9WRcqo9xYgfcJDnpqVnhObGQD96CfSok8z8d9otv+Fl6ULZrddvcnvzs6cJhjuW3ryBn151Xat/2CU/9EXUEKG3e0g4/K9rEaDRb2JhDGEDwIj+Qd5RU1uaBKxS/7jlSVq8wQ7x3qVVN1tHqS4AXhVow6eMABT6PArcEfkFm42bwXOFWsAUd5C7uGvHGIlTGytU6Vx/CJPwPvCuSffef5mQL7daszEzN+zQJ9VjxgOrjKXkDVkt6O6UpyA+1u3lowOqUaSPK6u2vND/Xqus7&ff4b_85475962D8E15A4E7AE60ED42FF3568E8EDB86EE620E495591
X-DeviceModel: Redmi M2012K11AC
version: 5.0.0
X-Ca-Nonce: be0eca5c-e959-4b0f-b4e7-22e00118157e
X-Ca-Timestamp: 1671939318489
X-Sign: 70B21B02FD0EFD2353F0D7F4F2E7CDB6FC1C3C42
Host: passport.csdn.net
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 95
{"pwdOrVerifyCode":"123456","loginType":"1","userIdentification":"17750659921","checkAli":true}

意料之中一大堆参数,反复几次总结需分析的参数应该为以下几个:

X-Sign、wToken、X-Ca-Signature、X-Access-Token、X-Ca-Timestamp

先搜索 X-Sign,就一处:
跟进得:

pretty nice,很多参数都在这里,那就从上往下分析:

hashMap.put("platform", "android"); // 固定值
hashMap.put("version", xn3.u()); // 固定值
hashMap.put("c_appVersion", xn3.u()); // 固定值
hashMap.put("Authorization", StringUtils.isEmpty(it3.g()) ? "" : "Bearer " + it3.g()); // 无用,请求头中为空
hashMap.put("X-Device-ID", a2); // a2 在 String a2 = wo3.a(CSDNApp.csdnApp);

hook wo3.a看看

结果:

a is called, context: net.csdn.csdnplus.CSDNApp@dc96acb
a ret value is aid0f0fef992b53479187546b3c621157f0

多次 hook 该值并没有改变,查看不同数据包的内容也是一样的,但在 Java 层仅分析到 aid 复制点,后面数据同一设备都是一样的,怀疑是消息散列值,有可能是 DeviceID 或者 UUID,有在 Java 层看到,但 hook 不到,往下分析:

hashMap.put("X-OS", "Android"); // 固定值
hashMap.put("X-App-ID", "CSDN-APP"); // 固定值
hashMap.put("X-Access-Token", MD5.md5(a2 + "AndroidCSDN-APPb85fF96d-7Aa4-4Ec1-bf1D-2133c1A45656")); // a2 就是上面的 X-Device-ID 再进行加盐处理后进行 MD5 加密
复现一下:

结果 00871d5df0d4f51efb5883b3b2fd2359,校验无误,继续往下:

hashMap.put("X-OsVersion", Build.VERSION.SDK_INT + ""); // sdk 版本吧,可随机的样子
hashMap.put("X-DeviceModel", str2); // 通过上面获取来的,分析一下就是手机 + 手机名称
hashMap.put("X-ConnectionType", yp3.b(CSDNApp.csdnApp)); // 网络连接类型
hashMap.put("UserToken", StringUtils.isEmpty(it3.q()) ? "" : it3.q()); // 抓包为空值,放弃
hashMap.put("X-App-Theme", CSDNApp.isDayMode ? "day" : "night"); // 主题模式
hashMap.put("X-Sign", str);

str 是上面计算来的,拿来分析下

c2: 10000 - 99999 之间的随机值;
str3:时间戳转字符串;
a2:上面分析过为 X-Device-ID 值;
zf1.o:跟进查看为定值:public static final String o = "F403F982CA92F73AC142D50FFA69853D";

参数搞定,看 mq3.a 方法:

SHA1算法,先 hook 再还原:

结果:

a is called, decrypt:
aid0f0fef992b53479187546b3c621157f0709751671957807054F403F982CA92F73AC142D50FFA69853D
a ret value is E11539C9183644EEB69C7FEBAC1D58A2D874895C

还原:

结果:E11539C9183644EEB69C7FEBAC1D58A2D874895C 校验无误,继续往下分析:

hashMap.put("X-RandomNum", c2 + ""); // 10000 - 99999 之间的随机值;
hashMap.put("X-TimeStamp", str3); // str3:时间戳转字符串;
hashMap.put(IWebview.COOKIE, sb.toString()); // 看抓包结果应该是 Cookie 值,对照结果 UserName=;UserToken= 啥操作没做,应该是要登录后才有值也是固定值
hashMap.put("User-Agent", CSDNApp.csdnApp.userAgent + " CSDNApp/" + xn3.u() + "(Android)wToken/0.0.1");

对照其抓包内容:CSDNApp.csdnApp.userAgent 获取设备 header,xn3.u() 获取 app 版本号

看到 wToken 就有点不好的预感,应该是阿里安全的,前段时间很火的某box就是用的这个,跟进看看,最终定位到:

so 层了,放着先看看别的参数吧,这块代码,能解决的参数都解决了,但 X-Ca-Signature 没在这出现,jadx 再搜跟到以下代码:

不难看出该值是通过 HmacSHA256 加密后再进行 Base64 编码后得到的结果,其 key 值为:

结果:

strBuilder:
POST
application/json
application/json; charset=UTF-8
X-Ca-Key:203789067
X-Ca-Nonce:3339aae3-e295-410c-8345-52c9ebc56b5a
X-Ca-Timestamp:1671963564343
/v1/api/app/login/doLogin

其中 X-Ca-Key 是个定值:

headerParams.put("X-Ca-Key", y12.b);
public static final String b = "203789067";
X-Ca-Nonce 为 UUID 值:
headerParams.put("X-Ca-Nonce", UUID.randomUUID().toString());
X-Ca-Timestamp 为时间戳。

python还原算法,与抓包结果一直:

抓包结果:QJYeguZxkE+ZojwTP0rIJ+IzjaSHI82uR2y0xOIG35U=

到这几乎参数都解决了,剩个 so 层的 wtoken,在 Java 层能够定位到:com/aliyun/TigerTally/TigerTallyAPI,其中,要找的就是 libtiger_tally.so 了:

阿里安全。。。。,换思路做吧,不想折腾,想了想,我直接 rpc 调用不就好了。

frida rpc 脚本:

补环境:

远程调用:

结果,校验一致:

{"message":"用户名或密码错误","status":false,"code":"1039"}

 
public static Map<String, String> z(String url, Map<String, String> requestMap) {
    String str;
    String a2 = wo3.a(CSDNApp.csdnApp);
    HashMap hashMap = new HashMap();
    hashMap.put("platform", "android");
    hashMap.put("version", xn3.u());
    hashMap.put("c_appVersion", xn3.u());
    if (!TextUtils.isEmpty(it3.g())) {
        hashMap.put("JWT-TOKEN", it3.g());
        no3.a("==JWT-TOKEN==", it3.g());
    }
    hashMap.put("Authorization", StringUtils.isEmpty(it3.g()) ? "" : "Bearer " + it3.g());
    hashMap.put("X-Device-ID", a2);
    hashMap.put("X-OS", "Android");
    hashMap.put("X-App-ID", "CSDN-APP");
    hashMap.put("X-Access-Token", MD5.md5(a2 + "AndroidCSDN-APPb85fF96d-7Aa4-4Ec1-bf1D-2133c1A45656"));
    hashMap.put("X-OsVersion", Build.VERSION.SDK_INT + "");
    String str2 = Build.BRAND + Operators.SPACE_STR + Build.MODEL;
    int length = str2.length();
    for (int i = 0; i < length; i++) {
        char charAt = str2.charAt(i);
        if ((charAt <= 31 && charAt != '\t') || charAt >= 127) {
            str2.replace(charAt, ' ');
        }
    }
    hashMap.put("X-DeviceModel", str2);
    hashMap.put("X-ConnectionType", yp3.b(CSDNApp.csdnApp));
    hashMap.put("UserToken", StringUtils.isEmpty(it3.q()) ? "" : it3.q());
    hashMap.put("X-App-Theme", CSDNApp.isDayMode ? "day" : "night");
    int c2 = xn3.c(10000, 99999);
    String str3 = new Date().getTime() + "";
    try {
        str = mq3.a(a2 + c2 + str3 + zf1.o);
    } catch (DigestException e2) {
        e2.printStackTrace();
        str = "";
    }
    hashMap.put("X-Sign", str);
    hashMap.put("X-RandomNum", c2 + "");
    hashMap.put("X-TimeStamp", str3);
    StringBuilder sb = new StringBuilder();
    sb.append("UserName=");   
    sb.append(it3.p());
    sb.append(";UserToken=");
    sb.append(StringUtils.isEmpty(it3.q()) ? "" : it3.q());
    hashMap.put(IWebview.COOKIE, sb.toString());
    if (!StringUtils.isEmpty(url) && requestMap != null && requestMap.containsKey("category")) {
        hashMap.put("X-PageKey", "blog." + requestMap.get("category"));
        hashMap.put("X-Path", "app.csdn.net/blog/" + requestMap.get("category"));
    }
    if (!StringUtils.isEmpty(url) && url.equals(s22.G0)) {
        hashMap.put("X-PageKey", vr3.Q6);
        hashMap.put("X-Path", "app.csdn.net/blog/detail");
        if (requestMap != null && requestMap.containsKey("from")) {
            hashMap.put("X-Referer", "blog." + requestMap.get("from"));
        }
    }
    hashMap.put("User-Agent", CSDNApp.csdnApp.userAgent + " CSDNApp/" + xn3.u() + "(Android)wToken/0.0.1");
    try {
        hashMap.put("wToken", TigerTallyAPI.vmpSign(1, str3.getBytes("UTF-8")));
    } catch (UnsupportedEncodingException e3) {
        e3.printStackTrace();
    }
    return hashMap;
}
public static Map<String, String> z(String url, Map<String, String> requestMap) {
    String str;
    String a2 = wo3.a(CSDNApp.csdnApp);
    HashMap hashMap = new HashMap();
    hashMap.put("platform", "android");
    hashMap.put("version", xn3.u());
    hashMap.put("c_appVersion", xn3.u());
    if (!TextUtils.isEmpty(it3.g())) {
        hashMap.put("JWT-TOKEN", it3.g());
        no3.a("==JWT-TOKEN==", it3.g());
    }
    hashMap.put("Authorization", StringUtils.isEmpty(it3.g()) ? "" : "Bearer " + it3.g());
    hashMap.put("X-Device-ID", a2);
    hashMap.put("X-OS", "Android");
    hashMap.put("X-App-ID", "CSDN-APP");
    hashMap.put("X-Access-Token", MD5.md5(a2 + "AndroidCSDN-APPb85fF96d-7Aa4-4Ec1-bf1D-2133c1A45656"));
    hashMap.put("X-OsVersion", Build.VERSION.SDK_INT + "");
    String str2 = Build.BRAND + Operators.SPACE_STR + Build.MODEL;
    int length = str2.length();
    for (int i = 0; i < length; i++) {
        char charAt = str2.charAt(i);
        if ((charAt <= 31 && charAt != '\t') || charAt >= 127) {
            str2.replace(charAt, ' ');
        }
    }
    hashMap.put("X-DeviceModel", str2);
    hashMap.put("X-ConnectionType", yp3.b(CSDNApp.csdnApp));
    hashMap.put("UserToken", StringUtils.isEmpty(it3.q()) ? "" : it3.q());
    hashMap.put("X-App-Theme", CSDNApp.isDayMode ? "day" : "night");
    int c2 = xn3.c(10000, 99999);
    String str3 = new Date().getTime() + "";
    try {
        str = mq3.a(a2 + c2 + str3 + zf1.o);
    } catch (DigestException e2) {
        e2.printStackTrace();
        str = "";
    }
    hashMap.put("X-Sign", str);
    hashMap.put("X-RandomNum", c2 + "");
    hashMap.put("X-TimeStamp", str3);
    StringBuilder sb = new StringBuilder();
    sb.append("UserName=");   
    sb.append(it3.p());
    sb.append(";UserToken=");
    sb.append(StringUtils.isEmpty(it3.q()) ? "" : it3.q());
    hashMap.put(IWebview.COOKIE, sb.toString());
    if (!StringUtils.isEmpty(url) && requestMap != null && requestMap.containsKey("category")) {
        hashMap.put("X-PageKey", "blog." + requestMap.get("category"));
        hashMap.put("X-Path", "app.csdn.net/blog/" + requestMap.get("category"));
    }
    if (!StringUtils.isEmpty(url) && url.equals(s22.G0)) {
        hashMap.put("X-PageKey", vr3.Q6);
        hashMap.put("X-Path", "app.csdn.net/blog/detail");
        if (requestMap != null && requestMap.containsKey("from")) {
            hashMap.put("X-Referer", "blog." + requestMap.get("from"));
        }
    }
    hashMap.put("User-Agent", CSDNApp.csdnApp.userAgent + " CSDNApp/" + xn3.u() + "(Android)wToken/0.0.1");
    try {
        hashMap.put("wToken", TigerTallyAPI.vmpSign(1, str3.getBytes("UTF-8")));
    } catch (UnsupportedEncodingException e3) {
        e3.printStackTrace();
    }
    return hashMap;
}
 
function main() {
    Java.perform(function () {
        var wo3 = Java.use("wo3");
        wo3["a"].implementation = function (context) {
            console.log('a is called' + ', ' + 'context: ' + context);
            var ret = this.a(context);
            console.log('a ret value is ' + ret);
            return ret;
        };
    });
}
setImmediate(main)
function main() {
    Java.perform(function () {
        var wo3 = Java.use("wo3");
        wo3["a"].implementation = function (context) {
            console.log('a is called' + ', ' + 'context: ' + context);
            var ret = this.a(context);
            console.log('a ret value is ' + ret);
            return ret;
        };
    });
}
setImmediate(main)
 
from hashlib import md5
 
def encrypt_md5(mes):
    new_md5 = md5()
    # 这里必须用encode()函数对字符串进行编码,不然会报 TypeError: Unicode-objects must be encoded before hashing
    new_md5.update(mes.encode(encoding='utf-8'))
    # 加密
    return new_md5.hexdigest()
 
if __name__ == '__main__':
    print(encrypt_md5('aid0f0fef992b53479187546b3c621157f0AndroidCSDN-APPb85fF96d-7Aa4-4Ec1-bf1D-2133c1A45656'))
from hashlib import md5
 
def encrypt_md5(mes):
    new_md5 = md5()
    # 这里必须用encode()函数对字符串进行编码,不然会报 TypeError: Unicode-objects must be encoded before hashing
    new_md5.update(mes.encode(encoding='utf-8'))
    # 加密
    return new_md5.hexdigest()
 
if __name__ == '__main__':
    print(encrypt_md5('aid0f0fef992b53479187546b3c621157f0AndroidCSDN-APPb85fF96d-7Aa4-4Ec1-bf1D-2133c1A45656'))
 
int c2 = xn3.c(10000, 99999);
String str3 = new Date().getTime() + "";
try {
    str = mq3.a(a2 + c2 + str3 + zf1.o);
} catch (DigestException e2) {
    e2.printStackTrace();
    str = "";
}
hashMap.put("X-Sign", str);
int c2 = xn3.c(10000, 99999);
String str3 = new Date().getTime() + "";
try {
    str = mq3.a(a2 + c2 + str3 + zf1.o);
} catch (DigestException e2) {
    e2.printStackTrace();
    str = "";
}
hashMap.put("X-Sign", str);
 
public static String a(String decrypt) throws DigestException {
    try {
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
        messageDigest.update(decrypt.getBytes());
        byte[] digest = messageDigest.digest();
        StringBuffer stringBuffer = new StringBuffer();
        for (byte b : digest) {
            String hexString = Integer.toHexString(b & 255);
            if (hexString.length() < 2) {
                stringBuffer.append(0);
            }
            stringBuffer.append(hexString);
        }
        return stringBuffer.toString().toUpperCase();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        throw new DigestException("签名错误!");
    }
}
public static String a(String decrypt) throws DigestException {
    try {
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
        messageDigest.update(decrypt.getBytes());
        byte[] digest = messageDigest.digest();
        StringBuffer stringBuffer = new StringBuffer();
        for (byte b : digest) {
            String hexString = Integer.toHexString(b & 255);
            if (hexString.length() < 2) {
                stringBuffer.append(0);
            }
            stringBuffer.append(hexString);
        }
        return stringBuffer.toString().toUpperCase();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
        throw new DigestException("签名错误!");
    }
}
function main() {
    Java.perform(function () {
        var mq3 = Java.use("mq3");
        mq3["a"].implementation = function (decrypt) {
            console.log('a is called' + ', ' + 'decrypt: \n' + decrypt);
            var ret = this.a(decrypt);
            console.log('a ret value is ' + ret);
            return ret;
        };
    });
}
setImmediate(main)
// frida -FU -l CSDN/csdn.js
function main() {
    Java.perform(function () {
        var mq3 = Java.use("mq3");
        mq3["a"].implementation = function (decrypt) {
            console.log('a is called' + ', ' + 'decrypt: \n' + decrypt);
            var ret = this.a(decrypt);
            console.log('a ret value is ' + ret);
            return ret;
        };
    });
}
setImmediate(main)
// frida -FU -l CSDN/csdn.js
 
import hashlib
 
# 使用sha1加密算法,返回str加密后的字符串
def sha1_secret_str(s: str):
    sha = hashlib.sha1(s.encode('utf-8'))
    encrypts = sha.hexdigest()
    return encrypts.upper()
 
if __name__ == '__main__':
    # 待加密的字符串
    s = 'aid0f0fef992b53479187546b3c621157f0709751671957807054F403F982CA92F73AC142D50FFA69853D'
    res = sha1_secret_str(s)
    print(res)
import hashlib
 
# 使用sha1加密算法,返回str加密后的字符串
def sha1_secret_str(s: str):
    sha = hashlib.sha1(s.encode('utf-8'))
    encrypts = sha.hexdigest()
    return encrypts.upper()
 
if __name__ == '__main__':
    # 待加密的字符串
    s = 'aid0f0fef992b53479187546b3c621157f0709751671957807054F403F982CA92F73AC142D50FFA69853D'
    res = sha1_secret_str(s)
    print(res)
 
// Mozilla/5.0 (Linux; Android 11; M2012K11AC Build/RQ3A.211001.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/108.0.5359.128 Mobile Safari/537.36 CSDNApp/5.0.0(Android)wToken/0.0.1
hashMap.put("wToken", TigerTallyAPI.vmpSign(1, str3.getBytes("UTF-8")));
// Mozilla/5.0 (Linux; Android 11; M2012K11AC Build/RQ3A.211001.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/108.0.5359.128 Mobile Safari/537.36 CSDNApp/5.0.0(Android)wToken/0.0.1
hashMap.put("wToken", TigerTallyAPI.vmpSign(1, str3.getBytes("UTF-8")));
private static native String _genericNt3(int i, byte[] bArr);
private static native String _genericNt3(int i, byte[] bArr);
private static Map<String, String> c(StringBuilder strBuilder, Map<String, String> headerParams) {
    if (ft3.b() != 1) {
        headerParams.put("X-Ca-Stage", ft3.b() == 2 ? "TEST" : "PRE");
    }
    try {
        Mac mac = Mac.getInstance("HmacSHA256");
        byte[] bytes = y12.c.getBytes("UTF-8");
        mac.init(new SecretKeySpec(bytes, 0, bytes.length, "HmacSHA256"));
        String str = new String(Base64.encodeBase64(mac.doFinal(strBuilder.toString().getBytes("UTF-8"))));
        headerParams.put("X-Ca-Signature", str);
        no3.a("==HmacSHA256==", str);
    } catch (UnsupportedEncodingException e2) {
        e2.printStackTrace();
    } catch (InvalidKeyException e3) {
        e3.printStackTrace();
    } catch (NoSuchAlgorithmException e4) {
        e4.printStackTrace();
    }
    return headerParams;
}
private static Map<String, String> c(StringBuilder strBuilder, Map<String, String> headerParams) {
    if (ft3.b() != 1) {
        headerParams.put("X-Ca-Stage", ft3.b() == 2 ? "TEST" : "PRE");
    }
    try {
        Mac mac = Mac.getInstance("HmacSHA256");
        byte[] bytes = y12.c.getBytes("UTF-8");
        mac.init(new SecretKeySpec(bytes, 0, bytes.length, "HmacSHA256"));
        String str = new String(Base64.encodeBase64(mac.doFinal(strBuilder.toString().getBytes("UTF-8"))));
        headerParams.put("X-Ca-Signature", str);
        no3.a("==HmacSHA256==", str);
    } catch (UnsupportedEncodingException e2) {
        e2.printStackTrace();
    } catch (InvalidKeyException e3) {
        e3.printStackTrace();
    } catch (NoSuchAlgorithmException e4) {
        e4.printStackTrace();
    }
    return headerParams;
}
public static final String c = "0u94vkvsewic9kkgsp1r3nuq3ir0lv3n";
hook 看看要加密 strBuilder 有啥:
function main() {
    Java.perform(function () {
        var r12 = Java.use("r12");
        r12["c"].implementation = function (strBuilder, headerParams) {
            console.log('strBuilder: \n' + strBuilder);
            var ret = this.c(strBuilder, headerParams);
            var keyset = ret.keySet();
            var result = "";
            var it = keyset.iterator();
            while (it.hasNext()) {
                var keystr = it.next().toString();
                var valuestr = ret.get(keystr).toString();
                console.log(keystr)
                console.log(valuestr)
                result += valuestr;
            }
            return ret;
        };
    });
}
setImmediate(main)
// frida -FU -l CSDN/csdn.js
public static final String c = "0u94vkvsewic9kkgsp1r3nuq3ir0lv3n";
hook 看看要加密 strBuilder 有啥:
function main() {
    Java.perform(function () {
        var r12 = Java.use("r12");
        r12["c"].implementation = function (strBuilder, headerParams) {
            console.log('strBuilder: \n' + strBuilder);
            var ret = this.c(strBuilder, headerParams);
            var keyset = ret.keySet();
            var result = "";
            var it = keyset.iterator();
            while (it.hasNext()) {
                var keystr = it.next().toString();
                var valuestr = ret.get(keystr).toString();
                console.log(keystr)
                console.log(valuestr)

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2023-1-11 11:40 被行简编辑 ,原因:
收藏
免费 11
支持
分享
最新回复 (4)
雪    币: 222
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
太强了
2023-7-19 11:36
0
雪    币: 2
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
tql
2023-11-2 20:49
0
雪    币: 3004
活跃值: (30866)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2023-11-6 09:36
1
雪    币: 3
活跃值: (316)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
厉害,感谢分享。
2023-11-6 09:45
0
游客
登录 | 注册 方可回帖
返回
//