x-zse-96,android端,伪dex加固,so加固,白盒AES,字符串加密

x-zse-96,android端,伪dex加固,so加固,白盒AES,字符串加密

上一篇某招聘软件的sig及sp参数被和谐掉了,所以懂得都懂啊!
在这里插入图片描述
因为web的api没有那么全,所以来看了下app的,ios的防护几乎没有,纸糊的一样,android端的有点复杂了,到最后我也没能完整的实现整个加密过程,我也只复现到DFA还原出了秘钥,iv也找到了,就是结果不对,也许是魔改AES的程度比较高,后续搞出来了的话再发下文吧.
网上找了下,都是分析web的,几乎没有分析app的,所以这篇应该算是首篇比较详细的文章了.

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

流程

抓包

抓包的话可以发现头部有个x-zse-96参数,和web是一样的名字.web的之前我也搞过,没什么难度说实话.
在这里插入图片描述

关键代码定位

正常来说是先查壳看看有没有加固,加固的脱壳,没加固的直接拖到jadx里反编译,因为我觉得这应该算是个大厂吧,凭着经验大厂很少加壳,所以我也没有查壳,直接扔到jadx里反编译了.
尝试搜索一下字符串x-zse-96
在这里插入图片描述
什么也没有找到,其实是字符串被加密了
这个时候就有很多方法可以选择了,比如hook java层的系统hashmap,x-zse-96像是base64过的,也可以hook base64,又像是1.0_拼接后面一串得来的,也可以hook StringBuilder的tostring方法,甚至如果你觉得它是so层的加密也可以直接hook NewStringUTF函数.
这里我根据习惯先hook了hashmap

Java.perform(function (){var hashMap = Java.use("java.util.LinkedHashMap");  //LinkedHashMap HashMap
hashMap.put.implementation = function (a, b) {if(a!=null && a.equals("X-Zse-96")){ //X-Zse-96 x-zse-96console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()))console.log("hashMap.put: ", a, b);}return this.put(a, b);
}}

什么也没有hook到,看来不是通过hashmap来添加的
接着我hook了StringBuilder的tostring方法,因为做过web的就知道,这个参数就是前面的1.0_拼接后面的得到的.事实上hook base64也是可以hook到的

var sb = Java.use("java.lang.StringBuilder");
sb.toString.implementation = function () {
var retval = this.toString();
if (retval.indexOf("1.0_") != -1) {console.log("StringBuilder.toString: ", retval);console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()))
}
return retval;
}

在这里插入图片描述
从堆栈来看这个参数的生成是最后通过拦截器添加上去的,tostring的下面一行点进去看看
在这里插入图片描述
按堆栈的意思就是说这个sb.tostring就是结果了,hook了下b函数看看传进去的是不是x-zse-96,结果并不是,是一个32位的md5的结果,堆栈后面有一句native method,这里我也是比较困惑为什么按照堆栈里找的和反编译出来的结果不一样.
这里我以为是反编译出了什么问题,扔到pkid里查个壳看看
在这里插入图片描述
好家伙,不按常理出牌,正常来说大厂都不加壳的,看了一眼so的名字,dexhelper确实是梆梆企业版的
在这里插入图片描述
这个时候我拿出了我的px4定制的fart脱壳机,结果直接运行不起来,由于fart脱壳机太热门了,很多厂商对这个都有检测,我这个还是去过特征版的!
但是我记得这个梆梆加固主要是指令抽取,正常来说反编译的代码都是空的函数,为什么和jadx中的不一样,并且里面的dex都是有数据的.
在这里插入图片描述
用mt管理器看一下
在这里插入图片描述
发现是个伪加固,app虚晃一枪,要是正常人估计还在想办法怎么脱壳,这样的话也不用脱壳了,正好挺省事的.
在这里插入图片描述

接着上面的逻辑,b2不为空,所以走了下面的逻辑,并且有一个addHeader的方法,这个像是往请求头里添加键值对.并且可以看到是添加了两组,hook一下

Java.perform(function(){
let Builder = Java.use("okhttp3.Request$Builder");
Builder["addHeader"].implementation = function (str, str2) {let result = this["addHeader"](str, str2);if(str=='X-Zse-96' || str=='X-Zse-93'){console.log(`Builder.addHeader is called: str=${str}, str2=${str2}`);console.log(`Builder.addHeader result=${result}`);}return result;
};
})

有结果,并且入参是x-zse-96的值
在这里插入图片描述
这里的H.d(“G51CEEF09BA7DF27F”)其实就是字符串加密了,执行后结果就是x-zse-96,点进去是一个native方法.在so层加密了,现在很多app都弄成这样了,通过搜索几乎定位不到关键代码.
接着看
H.d(“G38CD8525”)就是1.0_了,hook H.d方法同样可以得到结果.真正的加密结果来自new String(this.c.a(a2)),a2是查询参数md5后的结果转为了字节数组.
接着一路跟下来到了native方法
在这里插入图片描述
看名字应该是一个aes.中间的str是一个很长的字符串,这个字符串每次都是一样的,但加密结果不一样,所以不用管这个,主要是1和3参数,分别是两个字节数组,1是params md5后的字节数组,第3个数组初步可以当成是aes的秘钥,如果是ecb模式的话(初步认为,其实是iv,一般人都会认为是秘钥的,因为标题说了白盒AES,秘钥内嵌在程序里)
在这里插入图片描述

so模块没有出现在代码里,前面我hook了jni方法NewStringUTF发现是libbangcle_crypto_tool.so,还可以hook libart.so来找动态注册以及libdl.so找静态注册.
接下来看libbangcle_crypto_tool.so,拖到ida里反汇编
在这里插入图片描述
可以看到都是seg段,没有text段
在这里插入图片描述
并且导出函数中也是有java中的静态注册函数
在这里插入图片描述
点过去也是这样,这里你应该感觉到不对劲了,似乎ida无法识别这个so,其实是so被加固了.

dump so

so加固的碰到的比较少,这里我能想到的办法就是dump内存中的so,再修复一下.

function dump_so(so_name) {Java.perform(function () {var currentApplication = Java.use("android.app.ActivityThread").currentApplication();var dir = currentApplication.getApplicationContext().getFilesDir().getPath();var libso = Process.getModuleByName(so_name);console.log("[name]:", libso.name);console.log("[base]:", libso.base);console.log("[size]:", ptr(libso.size));console.log("[path]:", libso.path);var file_path = dir + "/" + libso.name + "_" + libso.base + "_" + ptr(libso.size) + ".so";var file_handle = new File(file_path, "wb");if (file_handle && file_handle != null) {Memory.protect(ptr(libso.base), libso.size, 'rwx');var libso_buffer = ptr(libso.base).readByteArray(libso.size);file_handle.write(libso_buffer);file_handle.flush();file_handle.close();console.log("[dump]:", file_path);}});
}
dump_so("libbangcle_crypto_tool.so")

dump下来后拖到电脑上修复一下就好了,工具https://github.com/F8LEFT/SoFixer
不过这样dump下来的效果不是很好,unidbg压根跑不起来这个so,里面还是有些数据无法访问,偏偏这些就是白盒aes中的关键数据s盒.折腾了大半天,一路打patch,最后还是放弃了unidbg这条路.
还是老老实实看so里的内容把,修复后的so至少可以看到代码段了.
在这里插入图片描述
进来后先转jnienv对象,这样代码会好分析些.
这个函数稍微有点控制流混淆以及虚假指令
在这里插入图片描述
不过从后往前推就可以看出来走的那个函数了,v12是返回值,v12来自v10创建的字节数组,v10来着上面的8E74函数,这个函数就是核心的加密流程了,点进去瞧瞧
v9是a1赋值过来的,所以也是env对象在这里插入图片描述
进来后看左下角的流程图,用了好几个switch case来判断走哪个流程,还好我们知道我们的函数是laes,并且从提示来看知道是cbc模式,也就是说需要一个初始化iv,这个laes的意思应该是local aes,字面意思就是本地的aes.从这个函数进去看看.进去后再进入一个laes就到了下面这个图
在这里插入图片描述
前面那些内容函数最好hook看一下入参,其实我都hook过了,只是没写,写了篇幅太长了,后面还有白盒AES,这里有个a3>15,a3是明文的长度,aes分组长度都是16个字节,所以这里应该是判断明文的长度来决定加密几轮.这个a6是个函数
在这里插入图片描述
直接点进去是这样,不知道是不是加固的原因,这里只好看汇编了,鼠标放到a6那,按tab转汇编视图
在这里插入图片描述

blx R12,意思是跳到R12寄存器中的地址执行,这里直接看看不出跳到哪,可以用frida 的inlinehook看看

function inlineHook() {var nativePointer = Module.findBaseAddress("libbangcle_crypto_tool.so");var hookAddr = nativePointer.add(0x6140); // 6140	Interceptor.attach(hookAddr, {onEnter: function (args) {console.log(nativePointer)console.log("onEnter: ", this.context.r12);}, onLeave: function (retval) {}});
}
inlineHook()

这个so是32位的,正常情况下地址是要加1的,但是因为这个so是从内存中dump下来的,所以不需要加1了,这个需要注意下.结果是0x6420.这个app的好几年都是用的这个so,一点都没变,偏移也没变,属于是so加固了以为高枕无忧了.
在这里插入图片描述
进来后看到一个名字WB white box,白盒的意思,现在的安卓逆向java层的加密已经几乎没有了,so的逆向上来就是各种白盒,各种魔改算法以及自写算法,不如直接去搞ios逆向,现在的ios逆向很多都是调用的系统函数,可以直接hook整个系统函数,类似安卓的那个算法助手,而且ios逆向的人少,对抗少,系统也闭源,风控也比安卓小得多,现在从0开始搞安卓逆向难度很大,就算你有天赋至少也需要半年的时间(ios的可以看看沐阳老哥的课程,当然只是题外话,后面也会出几篇文章从0开始搞ios逆向.)
之前我发过一篇白盒aes的文章,很多js逆向的反馈压根没听过这个词,感觉很高级的样子,正常,js逆向你可以直接扣js啊,ida中的这个伪c代码扣下来很难跑通,对c的要求有点高.我是扣了一下,跑不起来,放弃了.
看下面的内容前你最好了解下什么是白盒AES,这是我从网上找的一篇文章https://blog.csdn.net/qq_37638441/article/details/128968233
接着正题,前面java层传入了一个参数3,16个字节,如果是白盒aes的话,那这个大概率就是iv了.

function callAES(){var base_addr = Module.findBaseAddress("libbangcle_crypto_tool.so");var real_addr = base_addr.add(0x6420);var wbaes_encrypt_ecb_func = new NativeFunction(real_addr, "void", ["pointer", "pointer", "pointer", "pointer","pointer"]);inputPtr = Memory.alloc(0x10);var inputArray = hexToBytes("b11812121a8886852e2e868786252e2e");Memory.writeByteArray(inputPtr, inputArray)var inputPtr3 = Memory.alloc(0x10);// var inputArray = hexToBytes("803d17b5b00000000400000080000000");var inputArray = hexToBytes("401948d4b00000000400000080000000");Memory.writeByteArray(inputPtr3, inputArray)var inputPtr4 = Memory.alloc(0x10);var inputArray = hexToBytes("00000000000000000000000000000000");Memory.writeByteArray(inputPtr4, inputArray)var inputPtr5 = Memory.alloc(0x10);var inputArray = hexToBytes("00000000000000000000000000000000");Memory.writeByteArray(inputPtr5, inputArray)wbaes_encrypt_ecb_func(inputPtr, inputPtr, inputPtr3, inputPtr4,inputPtr5);console.log(hexdump(inputPtr,{length: 0x10}));
}

so主动调用这个函数,其实它有5个参数,ida识别成了3个.前两个是同一个参数,iv和明文异或的结果,cbc模式下比ecb模式多了个iv,后两个参数作用不大,不用管,关键是第3个参数,有点奇怪,现在我也没搞清楚它的作用是什么,后面再说吧.

DFA攻击

调用后有结果,结果也是正确的.接下来寻找dfa攻击点,第8次列混淆到第9次列混淆之间,并且需要寻找到state块
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
按aes的算法应该是这样,最后面的第十轮少了列混淆,所以不在这个循环里面.
加下来寻找哪个是state块,初看选了result,每次核心运算都有他,一hook发现经过9轮他的值都不变,直到最后赋值结果的时候才变了.
后来仔细看了下v20也有可能,但是这个v20不太好hook他的变化,因为这里没有什么函数调用,所有没有入参什么的,要hook到这个v20的地址有些困难
在这里插入图片描述

这里我寻找九轮循环最开始的地方hook上,这个赋值写的也有点复杂.鼠标放到v20的地方,转汇编更方便看.
在这里插入图片描述
在这里插入图片描述
这里鼠标放到var_34按h可以转成立即数.STRB R3, [R11,#-52]
这条指令的意思就是将寄存器 R3 中的值存储到距离 R11 寄存器所指向的内存位置偏移为 -52 的地方。所以v20的地址就是R11的地址减去52的偏移的位置处.

function hookwb(){var count = 0;var base_addr = Module.findBaseAddress("libbangcle_crypto_tool.so");var real_addr = base_addr.add(0x6580)Interceptor.attach(real_addr, {onEnter: function (args) {count += 1;console.log("onEnter: ",count, hexdump(this.context.r11.sub(0x24),{length:0x10}));console.log("start:"+count);}});
}

结果也确实是9轮,接下来进行dfa攻击.

function hexToBytes(hex) {for (var bytes = [], c = 0; c < hex.length; c += 2)bytes.push(parseInt(hex.substr(c, 2), 16));return bytes;
}
var inputPtr;
function callAES(){var base_addr = Module.findBaseAddress("libbangcle_crypto_tool.so");var real_addr = base_addr.add(0x6420);var wbaes_encrypt_ecb_func = new NativeFunction(real_addr, "void", ["pointer", "pointer", "pointer", "pointer","pointer"]);inputPtr = Memory.alloc(0x10);var inputArray = hexToBytes("b11812121a8886852e2e868786252e2e");Memory.writeByteArray(inputPtr, inputArray)var inputPtr3 = Memory.alloc(0x10);// var inputArray = hexToBytes("803d17b5b00000000400000080000000");var inputArray = hexToBytes("401948d4b00000000400000080000000");Memory.writeByteArray(inputPtr3, inputArray)var inputPtr4 = Memory.alloc(0x10);var inputArray = hexToBytes("00000000000000000000000000000000");Memory.writeByteArray(inputPtr4, inputArray)var inputPtr5 = Memory.alloc(0x10);var inputArray = hexToBytes("00000000000000000000000000000000");Memory.writeByteArray(inputPtr5, inputArray)wbaes_encrypt_ecb_func(inputPtr, inputPtr, inputPtr3, inputPtr4,inputPtr5);// var output = Memory.readByteArray(inputPtr, 0x10);// console.log(bufferToHex(output))console.log(hexdump(inputPtr,{length: 0x10}));
}
function bufferToHex (buffer) {return [...new Uint8Array (buffer)].map (b => b.toString (16).padStart (2, "0")).join ("");
}
function hookwb(){var count = 0;var base_addr = Module.findBaseAddress("libbangcle_crypto_tool.so");var real_addr = base_addr.add(0x6580)Interceptor.attach(real_addr, {onEnter: function (args) {count += 1;console.log("onEnter: ",count, hexdump(this.context.r11.sub(0x24),{length:0x10}));if(count===9){this.context.r11.sub(0x24).add(randomNum(0,15)).writeS8(randomNum(0, 0xff));console.log("onEnter: ", hexdump(this.context.r11.sub(0x24),{length:0x10}));}console.log("start:"+count);}});
}
function randomNum(minNum,maxNum){if (arguments.length === 1) {return parseInt(Math.random() * minNum + 1, 10);} else if (arguments.length === 2) {return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);} else {return 0;}
}
function dfa(){for(var i=0;i<300;i++){hookwb()callAES()}
}

连续调用300次后取得故障密文再用phoenixAES拿到第10轮秘钥E48E8AA0E4449B580CF7ECC13CC17CD3,接着用aes_keyschedule还原出主密钥6BA6737912D31F3A1B53066645FABEA3
在这里插入图片描述
秘钥6BA6737912D31F3A1B53066645FABEA3,iv99303a3a32343a3992923a3b3a999292(java层的)都有了.事实上只有1轮的话,也可以直接用ecb模式,因为入参1就是明文和iv异或的结果,拿去加密一下发现结果不对…
前面我就说了这个参数3不太清楚是什么,因为这个入参这个时刻下主动调用是这个结果,重开app主动调用逻辑不变结果取变了.这里有个很大的问号?
这里先分析到这里吧,先埋个坑,后面有空再看看吧.

最后

微信公众号
在这里插入图片描述
知识星球
在这里插入图片描述
如果你觉得这篇文章对你有帮助,不妨请作者喝杯咖啡吧!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/760094.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hadoop安装部署-单机版

Apache Hadoop是一个使用HDFS&#xff08;Hadoop Distributed File System&#xff09;分布式文件系统执行可靠的、规模化的分布式计算的开源项目&#xff0c;Hadoop是使用Java语言开发&#xff0c;其运行在Linux操作系统上集群规模最大支持几千个分布式节点&#xff0c;本文主…

【嵌入式——QT】QThread创建多线程

【嵌入式——QT】QThread创建多线程 概述主要函数图示代码示例 概述 QThread类提供不依赖于平台的管理线程的方法&#xff0c;一个QThread类的对象管理一个线程&#xff0c;一般从QThread继承一个自定义类&#xff0c;并重定义虚函数run()&#xff0c;在run()函数里实现线程需…

基于java的健身房管理系统的设计与实现论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本健身房管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&…

【MySQL】数据库的基础概念

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习计网、mysql和算法 ✈️专栏&#xff1a;MySQL学习 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac…

Redis 更新开源许可证 - 不再支持云供应商提供商业化的 Redis

原文&#xff1a;Rowan Trollope - 2024.03.20 未来的 Redis 版本将继续在 RSALv2 和 SSPLv1 双许可证下提供源代码的免费和宽松使用&#xff1b;这些版本将整合先前仅在 Redis Stack 中可用的高级数据类型和处理引擎。 从今天开始&#xff0c;所有未来的 Redis 版本都将以开…

Vue.js前端开发零基础教学(二)

目录 前言 2.1 单文件组件 2.2 数据绑定 2.2.2 响应式数据绑定 2.3 指令 2.3.1 内容渲染指令 2.3.2 属性绑定指令 ​编辑 2.3.3 事件绑定指令 2.3.4 双向数据绑定指令 2.3.5 条件渲染指令 2.3.6 列表渲染指令 2.4 事件对象 2.5 事件修饰符 学习目标&am…

sentinel使用控制台实现

1、添加依赖 <!--整合控制台--><dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>1.8.0</version></dependency> 此项方法&#xff0…

HarmonyOS入门学习

HarmonyOS入门学习 前言快速入门ArkTS组件基础组件Image组件Text组件TextInput 文本输入框Buttonslider 滑动组件 页面布局循环控制ForEach循环创建组件 List自定义组件创建自定义组件Builder 自定义函数 状态管理Prop和LinkProvide和ConsumeObjectLink和Observed ArkUI页面路由…

JVM第八讲:GC - Java 垃圾回收基础知识

GC - Java 垃圾回收基础知识 本文是JVM第八讲&#xff0c; Java 垃圾回收基础知识。垃圾收集主要是针对堆和方法区进行&#xff1b;程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的&#xff0c;只存在于线程的生命周期内&#xff0c;线程结束之后也会消失&#xff0…

单片机第四季-第二课:uCos2源码-BSP

1&#xff0c;初始uCos2 文件中uC开头的为uCos相关的。 2&#xff0c;uCos2源码工程建立 建立Source Insight工程 寻找main函数 (1)RTOS其实就是一个大的裸机程序&#xff0c;也是从main开始运行的 (2)main之前也是有一个汇编的启动文件的 (3)main中调用了很多初始化函数 bsp部…

Linux信号补充——信号发送和保存

三、信号的发送与保存 3.1信号的发送 ​ 必须有操作系统来保存信号&#xff0c;因为他是管理者&#xff1b; ​ 信号给进程的task_struct发送信号&#xff0c;在task_struct中维护了一个整数signal有0-31位&#xff0c;共32个bit位&#xff1b;对于信号的管理使用的是位图结…

Java常见的垃圾回收器GC

本节讲解一下常见的垃圾回收器。需要特别注意的是&#xff0c;每一种垃圾回收器都会存在用户线程&#xff08;即用户程序&#xff09;暂停的问题&#xff0c;只不过每种回收器用户线程暂停的时长优化程度不一样。在启动JVM时&#xff0c;可以通过“指定参数-xx:垃圾回收器名称”…

贵价茶叶产区成谜,竹叶青茶是“行业黑马”还是“韭菜镰刀”?

撰稿|行星 来源|贝多财经 近日&#xff0c;央视“315”晚会曝光了自诩“高端商务白酒”的听花酒。这款白酒号称拥有提升免疫力、改善睡眠、保障男性功能等多种保健功能&#xff0c;标准装售价5860元&#xff0c;精品装售价更是达到5.86万元&#xff0c;昂贵程度令人瞠目结舌。…

jmeter的函数助手使用方法

如某个上传文件接口&#xff0c;一个文件只能同时被一个接口调用&#xff0c;如果被并发同时调用就会报错 创建多个测试文件 比如50并发&#xff0c;创建更多的文件防止并发多时随机数生成重复 生成随机数函数 工具–函数助手-选择random-输入范围&#xff08;1-696&#…

网络学习:IPV6基础配置

目录 一、配置接口的全球单播地址 二、配置接口本地链路地址 三、配置接口任播地址 四、配置接口PMTU 配置静态PMTU&#xff1a; 配置动态PMTU&#xff1a; 五、接口配置IPV6地址示例&#xff1a; 一、配置接口的全球单播地址 全球单播地址类似于IPv4公网地址&#xff0…

Redis技术学习|实战项目记录|短信登录(重点:拦截器)+ Redis代替session存储用户登录信息

学习资料声明 黑马程序员的Redis学习视频&#xff1a;黑马程序员Redis入门到实战教程 需要用到的知识&#xff1a;linux&#xff08;推荐韩顺平老师的教程&#xff0c;学到p30&#xff0c;创建好虚拟机和简单的几个命令就好。&#xff09;SSM。SpringBoot。 还用到了MybatisPl…

5G里面NR,gNB,en-gNB,ng-eNB是什么意思

不得不提一个国际组织&#xff0c;叫国际电信联盟(ITU, International Telecommunication Union)&#xff0c;简称国际电联。我们先看看国际电联的自我介绍&#xff1a; 国际电信联盟 『国际电联 (国际电信联盟) 是主管信息通信技术事务&#xff08;ICT&#xff09;的联合国机…

微信打卡小程序怎么做_用户的每日习惯培养神器

微信打卡小程序&#xff1a;你的每日习惯培养神器 在这个快节奏的现代社会&#xff0c;我们每天都在忙碌中度过&#xff0c;有时候甚至会忘记自己曾经立下的那些小目标、小习惯。然而&#xff0c;随着科技的不断发展&#xff0c;微信打卡小程序的出现&#xff0c;为我们的生活…

早期零撸项目,预注册即将截止, AI身份项目GenomeFi明牌空投教程

简介&#xff1a;GenomeFi基金会旨在用个人基因信息推动生物产业发展&#xff0c;他们通过AI大数据平台和区块链技术验证身份、保护个人信息、提升收入。GenomeFi和CLINOMICS&#xff08;一家基于基因组学疾病早期诊断的领先公司&#xff09;合作提供基因诊断、早期疾病检测、个…

朋友圈之于私域的意义

朋友圈作为社交媒体的一种&#xff0c;是品牌主进行私域流量营销的主要载体&#xff0c;具有推动产品销售、传播信息等多种作用。具体来说&#xff0c;朋友圈在私域中的意义可以体现在以下几个方面&#xff1a; 1. 传播信息&#xff1a;朋友圈可以成为品牌主向其潜在客户传递信…