固定参数-以京东sign逆向为例
前言
在逆向过程中,需要结合frida或unidbg,对整个算法进行一步步的分析,有时候我们分析完某一部分,想要继续往下分析时,需要重新发起请求,这时候的参数往往都已经改变了,这样会打断我们的节奏,影响效率。此外,有些算法除了我们外部传进去的参数外,还有一些其他的参数参与了加密,比如时间戳,随机数,一旦这些参与了算法,那么即使每次的传入参数不变,加密的结果还是会变。
外部输入参数的固定
frida
在京东app的hook中,我们选择编写一个函数,能够固定的调用Java层的native函数。
var bptr = Module.findBaseAddress('libjdbitmapkit.so')
console.log(bptr);function hook_12ECC() {Interceptor.attach(bptr.add(0x12ECC+1), {onEnter: function(args) {this.arg0 = args[0];this.arg3 = args[3];console.log(args[0], args[1], args[2], args[3], args[4]);dump("arg0-ptr0", args[0].readPointer());dump("arg0-ptr1", args[0]);dump("arg0-ptr1", args[0].add(4).readPointer(), 64);console.log("arg1", args[1].readCString());// dump("arg2", args[2]);console.log("arg3", args[3].readCString(parseInt(args[4])));},onLeave: function(){console.log("arg0-ret", this.arg0);dump("ret-arg0-ptr1", this.arg0);// dump("ret-arg0-ptr2", this.arg0.add(8).readPointer());dump("12ecc-ret-arg2", this.arg3)}})
}function callBitmapkitUtils() {var BitmapkitUtils = Java.use('com.jingdong.common.utils.BitmapkitUtils');var currentApplication = Java.use("android.app.ActivityThread").currentApplication();var context = currentApplication.getApplicationContext();var b = 'clientImage';var c = '{"moduleParams":{"18":"1565611060638","19":"1565229712150","25":"1567478504636","27":"1602488415048","28":"1631069159956","30":"1567404005627","32":"1567997588476","34":"1593508185597","35":"1568708316462","37":"1630293538664","42":"1623741761542","44":"1569247647090","46":"1588839806224","47":"1571295610042","61":"1582091758495","70":"1585279774645","74":"1586781606615"}}';var d = 'd5a585639f505b18';var e = 'android';var f = '10.2.0';var res = BitmapkitUtils.getSignFromJni(context, b, c, d, e, f);console.log('res: ', res);
}Java.perform(function() {hook_12ECC();
})
然后启动frida之后,可以在shell中输入callBitmapkitUtils()来调用函数。
这样一来不像在手机上滑动页面、点击页面那样,有时会有多个请求发出,会多次调用加密的方法。这样的好处是,再也不用在手机上操作了,而且请求的内容和个数是可控的。不过我们注意到京东的参数里有st和sv这两个参数,这可不是由我们传入的,属于内部输入参数,接下来我们要固定它们。
unidbg
对于unidbg而言,在我们编写代码的时候,一般都是固定输入的
内部输入参数的固定
frida
对于内部输入参数而言,可能有时间戳,随机数,常量(数字或字符),其中前2个是会改变的,这会影响逆向分析,所以需要固定这两个参数。时间戳一般是调用libc.so的gettimeofday函数,随机数则是调用libc.so的lrand48或srand48
function hook_libc(){var ptr_t = Module.findExportByName("libc.so", "gettimeofday");Interceptor.attach(ptr_t, {onEnter: function(args){this.arg0 = args[0];},onLeave: function() {this.arg0.writeU32(1639393559);this.arg0.add(4).writeU32(0);}});Interceptor.attach(Module.findExportByName("libc.so" , "lrand48"), {onEnter: function(args) {},onLeave:function(retval){retval.replace(1);}});
}
Java.perform(function() {hook_libc();hook_12ECC();
})
这样一来,即使多次运行,st和sv也不会改变,有利于我们的分析。
不过,固定时间戳有个问题,其他函数在获取时间戳的时候发现不对,可能会导致frida环境崩溃,所以我们希望只在调用的时候固定时间戳。所以我们更新如下代码
var logvar = 0;function callBitmapkitUtils() {var BitmapkitUtils = Java.use('com.jingdong.common.utils.BitmapkitUtils');var currentApplication = Java.use("android.app.ActivityThread").currentApplication();var context = currentApplication.getApplicationContext();var b = 'clientImage';var c = '{"moduleParams":{"18":"1565611060638","19":"1565229712150","25":"1567478504636","27":"1602488415048","28":"1631069159956","30":"1567404005627","32":"1567997588476","34":"1593508185597","35":"1568708316462","37":"1630293538664","42":"1623741761542","44":"1569247647090","46":"1588839806224","47":"1571295610042","61":"1582091758495","70":"1585279774645","74":"1586781606615"}}';var d = 'd5a585639f505b18';var e = 'android';var f = '10.2.0';logvar = 1;var res = BitmapkitUtils.getSignFromJni(context, b, c, d, e, f);logvar = 0;console.log('res: ', res);
}function hook_libc(){var ptr_t = Module.findExportByName("libc.so", "gettimeofday");Interceptor.attach(ptr_t, {onEnter: function(args){this.arg0 = args[0];},onLeave: function() {if (logvar) {this.arg0.writeU32(1639393559);this.arg0.add(4).writeU32(0);}}});Interceptor.attach(Module.findExportByName("libc.so" , "lrand48"), {onEnter: function(args) {},onLeave:function(retval){if (logvar){retval.replace(1);}}});
}
这样一来,只有在logvar值为1时才会固定参数。而logvar的默认值为0,只有在调用callBitmapkitUtils方法的时候才会改为1,调用完成后又会改为0。
unidbg
对于unidbg,我们运行多次后会发现,时间戳st会变,sv一直是111,好像和frida上的表现不一样,难道出了什么问题?
其实如果研究了京东libjdbitmapkit.so就会发现sv的后两位都是随机数余3。
而在unidbg对libc.so的随机数生成的实现中,种子是固定的(我猜的,没深究源码),导致生成的随机数的顺序是固定的,继而导致余数是固定的。
public void hook_libc() {IHookZz hookZz = HookZz.getInstance(emulator);hookZz.wrap(module.findSymbolByName("lrand48"), new WrapCallback<HookZzArm32RegisterContext>() {@Overridepublic void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {}@Overridepublic void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {int old = ctx.getIntArg(0);System.out.println("Origin rand:" + old);ctx.setR0(1);}});hookZz.wrap(module.findSymbolByName("gettimeofday"), new WrapCallback<HookZzArm32RegisterContext>() {@Overridepublic void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {Pointer pointer = ctx.getR0Pointer();ctx.push(pointer);}@Overridepublic void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {Pointer pointer = ctx.pop();pointer.setLong(0, 1639388888);pointer.setLong(4, 0);}});
}
public static void main(String[] args) {JingDong test = new JingDong();test.hook_libc();test.callSign();
}
可以看到,时间戳已经被固定下来了,而打印出来的两个随机数,他们的除以3的余数都为1,这也说明了为什么固定参数之前sv的值一直是111。
unidbg完整代码
package com.jingdong;import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.hook.hookzz.*;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.linux.android.dvm.wrapper.DvmInteger;
import com.github.unidbg.memory.Memory;
import com.sun.jna.Pointer;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.ParsingException;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.security.cert.X509Certificate;public class JingDong extends AbstractJni {private final AndroidEmulator emulator;private final VM vm;private final Module module;public static String pkgName = "com.jingdong.app.mall";public static String apkPath = "unidbg-android/src/test/java/com/jingdong/jingdong9.2.2.apk";public static String soPath = "unidbg-android/src/test/java/com/jingdong/libjdbitmapkit.so";private static final String APK_PATH = "/data/app/com.jingdong.app.mall.apk";JingDong() {emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(pkgName).build();final Memory memory = emulator.getMemory();memory.setLibraryResolver(new AndroidResolver(23));vm = emulator.createDalvikVM(new File(apkPath));DalvikModule dm = vm.loadLibrary(new File(soPath), false);vm.setJni(this);vm.setVerbose(true);dm.callJNI_OnLoad(emulator);module = dm.getModule();}@Overridepublic DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {switch (signature) {case "com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;": {return vm.resolveClass("android/app/Activity", vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(null);}}return super.getStaticObjectField(vm, dvmClass, signature);}@Overridepublic DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {switch (signature) {case "android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;": {return new StringObject(vm, APK_PATH);}}return super.getObjectField(vm, dvmObject, signature);}@Overridepublic DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {switch (signature) {case "com/jingdong/common/utils/BitmapkitZip->unZip(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[B": {StringObject apkPath = varArg.getObjectArg(0);StringObject directory = varArg.getObjectArg(1);StringObject filename = varArg.getObjectArg(2);if (APK_PATH.equals(apkPath.getValue()) &&"META-INF/".equals(directory.getValue()) &&".RSA".equals(filename.getValue())) {byte[] data = vm.unzip("META-INF/JINGDONG.RSA");return new ByteArray(vm, data);}}case "com/jingdong/common/utils/BitmapkitZip->objectToBytes(Ljava/lang/Object;)[B": {DvmObject<?> obj = varArg.getObjectArg(0);byte[] bytes = objectToBytes(obj.getValue());return new ByteArray(vm, bytes);}}return super.callStaticObjectMethod(vm ,dvmClass, signature, varArg);}@Overridepublic DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {switch (signature) {case "sun/security/pkcs/PKCS7-><init>([B)V": {ByteArray array = varArg.getObjectArg(0);try {return vm.resolveClass("sun/security/pkcs/PKCS7").newObject(new PKCS7(array.getValue()));} catch (ParsingException e) {throw new IllegalStateException(e);}}}return super.newObject(vm, dvmClass, signature, varArg);}@Overridepublic DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {switch (signature) {case "sun/security/pkcs/PKCS7->getCertificates()[Ljava/security/cert/X509Certificate;": {PKCS7 pkcs7 = (PKCS7) dvmObject.getValue();X509Certificate[] certificates = pkcs7.getCertificates();return ProxyDvmObject.createObject(vm, certificates);}}return super.callObjectMethod(vm, dvmObject, signature, varArg);}@Overridepublic DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {switch (signature) {case "java/lang/StringBuffer-><init>()V": {return vm.resolveClass("java/lang/StringBuffer").newObject(new StringBuffer());}case "java/lang/Integer-><init>(I)V": {return DvmInteger.valueOf(vm, vaList.getIntArg(0));}}return super.newObjectV(vm, dvmClass, signature, vaList);}@Overridepublic DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {switch (signature) {case "java/lang/StringBuffer->append(Ljava/lang/String;)Ljava/lang/StringBuffer;": {StringBuffer buffer = (StringBuffer) dvmObject.getValue();StringObject str = vaList.getObjectArg(0);buffer.append(str.getValue());return dvmObject;}case "java/lang/Integer->toString()Ljava/lang/String;": {return new StringObject(vm, ((Integer)dvmObject.getValue()).toString());}case "java/lang/StringBuffer->toString()Ljava/lang/String;": {return new StringObject(vm, ((StringBuffer)dvmObject.getValue()).toString());}}return super.callObjectMethodV(vm, dvmObject, signature, vaList);}private static byte[] objectToBytes(Object obj) {try {ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(obj);oos.flush();byte[] array = baos.toByteArray();oos.close();baos.close();return array;} catch (IOException e) {throw new IllegalStateException(e);}}public void hook_libc() {IHookZz hookZz = HookZz.getInstance(emulator);hookZz.wrap(module.findSymbolByName("lrand48"), new WrapCallback<HookZzArm32RegisterContext>() {@Overridepublic void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {}@Overridepublic void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {int old = ctx.getIntArg(0);System.out.println("Origin rand:" + old);ctx.setR0(1);}});hookZz.wrap(module.findSymbolByName("gettimeofday"), new WrapCallback<HookZzArm32RegisterContext>() {@Overridepublic void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {Pointer pointer = ctx.getR0Pointer();ctx.push(pointer);}@Overridepublic void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {Pointer pointer = ctx.pop();pointer.setLong(0, 1639388888);pointer.setLong(4, 0);}});}public void callSign() {DvmClass cBitmapkitUtils = vm.resolveClass("com/jingdong/common/utils/BitmapkitUtils");StringObject ret = cBitmapkitUtils.callStaticJniMethodObject(emulator, "getSignFromJni()(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",vm.resolveClass("android/content/Context").newObject(null),"clientImage","{\"moduleParams\":{\"18\":\"1565611060638\",\"19\":\"1565229712150\",\"25\":\"1567478504636\",\"27\":\"1602488415048\",\"28\":\"1631069159956\",\"30\":\"1567404005627\",\"32\":\"1567997588476\",\"34\":\"1593508185597\",\"35\":\"1568708316462\",\"37\":\"1630293538664\",\"42\":\"1623741761542\",\"44\":\"1569247647090\",\"46\":\"1588839806224\",\"47\":\"1571295610042\",\"61\":\"1582091758495\",\"70\":\"1585279774645\",\"74\":\"1586781606615\"}}","d5a585639f505b18","android","10.2.0");System.out.println(ret.getValue());}public static void main(String[] args) {JingDong test = new JingDong();test.hook_libc();test.callSign();}
}
关于京东加密
其实京东app有3套加密方案,会根据随机数不同来选择不同的方案,而unidbg生成的随机数和京东cv的生成方案导致sv一直是固定的,从而一直调用其中一套方案。我们在逆向的时候,其实只需要逆向出其中一套方案即可,那个简单选哪个,如果刚好是调用的那套方案的话那还好。如果不是,一时之间又不知道怎么处理的话就有点倒霉了。
我将这个解密方法封装成api了,直接调用api可以解密,很方便了
技术交流QQ 408737515
相关文章:
固定参数-以京东sign逆向为例
前言 在逆向过程中,需要结合frida或unidbg,对整个算法进行一步步的分析,有时候我们分析完某一部分,想要继续往下分析时,需要重新发起请求,这时候的参数往往都已经改变了,这样会打断我们的节奏&a…...
在macOS 上执行sed命令报错问题
错误描述 在macOS 上执行sed命令,报错 sed -i s/book/books/g demo.txt sed: 1: extra characters at the end of d command解决方法 原因是mac的和linux写法不一样 linux sed -i s/book/books/g demo.txtmac sed -i s/book/books/g demo.txt...
ARP欺骗
ARP协议: 地址解析协议,将IP地址转换为对应的mac地址,属链路层协议 ip地址: ip地址本义是为互联网上的每一个网络和每一台主机配置一个唯一的逻辑地址,它的格式表示为:(A.B.C.D)。其…...
pip切换下载源(多种国内源)
pip切换下载源 一、pip二、使用步骤1.查看源2.切换源 一、pip pip 是一个现代的,通用的 Python 包管理工具 二、使用步骤 1.查看源 使用以下命令查看当前pip的下载源 pip config list2.切换源 在国内使用官方下载依赖往往速度慢,易出错,…...
ARM Cortex-M 的 SP
文章目录 1、栈2、栈操作3、Cortex-M中的栈4、MDK中的SP操作流程5、Micro-Lib的SP差别1. 使用 Micro-Lib2. 未使用 Micro-Lib 在嵌入式开发中,堆栈是一个很基础,同时也是非常重要的名词,堆栈可分为堆 (Heap) 和栈 (Stack) 。 栈(Stack): 一种…...
【原创】鲲鹏ARM构架openEuler操作系统安装Oracle 19c
作者:einyboy 【原创】鲲鹏ARM构架openEuler操作系统安装Oracle 19c | 云非云计算机科学、自然科学技术科谱http://www.nclound.com/index.php/2023/09/03/%E3%80%90%E5%8E%9F%E5%88%9B%E3%80%91%E9%B2%B2%E9%B9%8Farm%E6%9E%84%E6%9E%B6openeuler%E6%93%8D%E4%BD%9C%E7%B3%BB%…...
k8s之存储篇---数据卷-挂载
挂载是指将定义在 Pod 中的数据卷关联到容器,同一个 Pod 中的同一个数据卷可以被挂载到该 Pod 中的多个容器上。 数据卷内子路径 有时候我们需要在同一个 Pod 的不同容器间共享数据卷。使用 volumeMounts.subPath 属性,可以使容器在挂载数据卷时指向数…...
无涯教程-JavaScript - TDIST函数
The TDIST function replaces the T.DIST.2T & T.DIST.RT functions in Excel 2010. 描述 该函数返回学生t分布的百分点(概率),其中数值(x)是t的计算值,将为其计算百分点。 t分布用于小样本数据集的假设检验。使用此函数代替t分布的临界值表。 语法 TDIST(x,deg_fr…...
IP子网的划分
文章目录 一、子网掩码1. 产生背景2. 定义3. 分类 二、VLSM算法1. 得出下列参数2. 计算划分结果3. 举例子计算 三、常见子网划分对应关系四、练习IP编址题目需求解题1. 192.168.1.100/282. 172.16.0.58/263. 25.83.149.222/254. 100.100.243.18/205. 10.100.100.100/10 首先可以…...
弹性盒子的使用
一、定义 弹性盒子是一种用于按照布局元素的一维布局方法,它可以简便、完整、响应式地实现各种页面布局。 容器中存在两条轴,主轴和交叉轴(相当于我们坐标轴的x轴和y轴)。我们可以通过flex-direction来决定主轴的方向。 主轴(main axis&am…...
软件测试/测试开发丨Selenium 网页frame与多窗口处理
点此获取更多相关资料 本文为霍格沃兹测试开发学社学员学习笔记分享 原文链接:https://ceshiren.com/t/topic/27048 一、多窗口处理. 1.1、多窗口简介 点击某些链接,会重新打开⼀个窗⼜,对于这种情况,想在新页⾯上操作࿰…...
MySQL高阶语句(三)
一、NULL值 在 SQL 语句使用过程中,经常会碰到 NULL 这几个字符。通常使用 NULL 来表示缺失 的值,也就是在表中该字段是没有值的。如果在创建表时,限制某些字段不为空,则可以使用 NOT NULL 关键字,不使用则默认可以为空…...
链表OJ练习(2)
一、分割链表 题目介绍: 思路:创建两个链表,ghead尾插大于x的节点,lhead尾插小于x的节点。先遍历链表。最后将ghead尾插到lhead后面,将大小链表链接。 我们需要在创建两个链表指针,指向两个链表的头节点&…...
ssh常用操作
ssh常用操作 SSH是一种安全协议,ssh是该协议的客户端程序,openssh-server则是该协议的服务端程序 常用系统都自带了ssh客户端程序,服务端程序则可能要安装 密码远程登陆 前提:服务器安装了openssh-server,未安装时…...
从AD迁移至AAD,看体外诊断领军企业如何用网络准入方案提升内网安全基线
摘要: 某医用电子跨国集团中国分支机构在由AD向AzureAD Global迁移时,创新使用宁盾网络准入,串联起上海、北京、无锡等国内多个职场与海外总部,实现平滑、稳定、全程无感知的无密码认证入网体验,并通过合规基线检查,确…...
Flutter系列文章-Flutter在实际业务中的应用
不同场景下的解决方案 1. 跨平台开发: 在移动应用开发中,面对不同的平台(iOS和Android),我们通常需要编写两套不同的代码。而Flutter通过一套代码可以构建适用于多个平台的应用,大大提高了开发效率&#x…...
FPGA | Verilog仿真VHDL文件
当VHDL模块中有Generic块时,应该怎么例化? VHDL模块代码 entity GenericExample isgeneric (DATA_WIDTH : positive : 8; -- 泛型参数:数据宽度ENABLE_FEATURE : boolean : true -- 泛型参数:是否启用特定功能);Port ( clk : …...
微服务--Gatway:网关
routes: - id:order_route(路由唯一 标识,路由到order) uri:http://localhost:8020 #需要转发的地址 #断言规则(用于路由规则的匹配) predicates: -path/order-serv/** -pathlb://order-service # lb: 使用nacos中的本地…...
Django传递dataframe对象到前端网页
在django前端页面上展示的数据,还是使用django模板自带的语法 方式1 不推荐使用 直接使用 【df.to_html(indexFalse)】 使用to_html他会生成一个最基本的表格没有任何的样式,一点都不好看,如果有需要的话可以自行修改表格的样式,…...
iOS swift5 弹出提示文字(停留1~2s)XHToastSwift
CoderZhuXH/XHToastSwift - github // // XHToast.swift // XHToastSwiftExample // // Created by xiaohui on 16/8/12. // Copyright © 2016年 CoderZhuXH. All rights reserved. // 代码地址:https://github.com/CoderZhuXH/XHToastSwiftimport UIKit/*** Toast…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...
数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)
名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 原创笔记:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 上一篇:《数据结构第4章 数组和广义表》…...
