Hook入门与抓包

Hook入门与抓包

0x01 Frida基础介绍

Frida介绍

Frida 的 介 绍 是 “Frida 是 平 台 原 生 App 的“Greasemonkey”,专业一点来说就是一种动态插桩工具,可以插入一些代码到原生App的内存空间去动态地监视和修改其行为,这些原生平台可以是Windows、Mac、Linux、Android或者iOS,同时Frida还是开源的。

Frida通过将JavaScript脚本插入到App的内存中来对App的逻辑进行跟踪和监控,甚至重新修改程序的逻辑,实现逆向开发和分析人员想要实现的功能,这样的方式也可以称为Hook(钩住,即通过钩子机制与钩子函数建立关联)。

Frida安装

客户端(电脑主机)安装直接使用命令

1

2

pip3 install frida==12.8.0

pip3 install frida-tools==5.3.0

服务端(手机端)安装

在GitHub上找到与客户端版本相同且与手机硬件架构相同的服务端进行下载,下载后上传到手机/data/local/tmp​ 目录下,添加可执行权限,进行运行即可

1

2

3

4

5

6

adb push frida

adb shell

su

cd /data/local/tmp

chmod 777 frida

./frida

Frida操作模式
  • CLI(命令行)模式

CLI模式直接通过命令行将JS脚本注入到进程中,对APP进程进行操作。

  • RPC远程调用模式

RPC模式又称为远程过程调用模式,这种模式利用Python对Hook的Js脚本进行包装,但实际上对进程进行操作的还是Js脚本。

Frida注入注入APP进程的方式(attach/spawn)
  • attch(附加)模式

这种模式是建立在目标App已经启动的情况下,frida直接利用ptrace原理将Hook的Js脚本注入程序而完成Hook操作。

1

frida -U APP进程 -l HookJs脚本.js

  • Spawn(调用)模式

这种模式将启动App的权力交由Frida来控制,当使用此模式时,即使App已经启动还是会将App重新启动并注入Hook的JS脚本。

1

frida -U -f App进程 -l HookJs脚本.js --no-pause

Frida Java层Hook 基础API
  • setTimeout(指定注入的函数,延迟时间毫秒)

setTimeout函数本身是JS提供的一个函数,通过这个函数可以将FridaHook脚本的函数延迟注入到目标进程,在App有加固情况下比较好用比如: Root检测、Wifi代理检测

  • setImmediate(指定注入的函数)

这个函数和setTimeout()函数类似,都是用于指定要执行的函数,不同的是setTimeout可以用于指定Frida注入App多长时间后执行函数,往往用于延时注入。

  • Java.perform(function)

Java.perform() 函数表示将参数中的函数注入到Java运行时,如果没有该函数的包裹注入目标App进程会提示如下图的报错信息:

  • Java.use(类完整路径)

从内存中获取指定类(包名.类名)的handle句柄,相当于Java中通过反射机制获取到的Class对象。

Frida Hook Java层demo

这段代码表示每秒调用一次show()方法,其中一个方法没有返回值和参数,另一个方法有返回值和参数

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

package com.example.demo02;

import android.os.Bundle;

import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;

public class example extends AppCompatActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_choose);

        while (true){

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e){

                e.printStackTrace();

            }

            show();

            show("---parameter---");

        }

    }

    public static void show(){

        Log.d("---no parameter---", "show方法日志打印");

    }

    public static boolean show(String tag){

        Log.d(tag, "show方法日志打印");

        return true;

    }

}

Hook 该程序show方法的Frida脚本

function main(){
    // Java.perform() 函数表示将参数中的函数注入到Java运行时
    Java.perform(function(){
        // Java.use() 函数表示从内存中获取指定类(包名.类名)的handle句柄
        var MainActivity = Java.use('com.example.demo02.example')
        // hook show()重载方法
        MainActivity.show.overload().implementation = function(){
            // 在控制台打印"Hook show()" 
            console.log("Hook show()")
            this.show()
        }
      
        // hook show(tag) 重载方法
        MainActivity.show.overload("java.lang.String").implementation = function(tag){
            // 修改APP内存中的值
            var result = this.show("Hook")
            // // 获取 Hook 到方法的返回值并在控制台打印
            var AppTag = this.show(tag)
            console.log("show(tag) retval => ", AppTag)
            // 返回修改后的值(Hook)
            return result
        }
    }
    )
}


// 指定要注入到APP进程的函数
setImmediate(main)

执行如下命令:

1

frida -U com.example.demo02 -l 6_example.js

0x02 基于Frida开发的动态分析工具Objection

Objection集成的功能主要支持Android和iOS两大移动平台。在对Android的支持中,Objection可以快速完成诸如内存搜索、类和模块搜索、方法Hook以及打印参数、返回值、调用栈等常用功能,是一个非常方便的逆向必备工具和内存漫游神器。

使用命令安装:

1

pip3 install -U objection

Objection默认通过USB连接设备,不必和Frida的命令行一样通过-U参数指定USB模式连接,主要通过-g参数指定注入的进程并通过explore命令进入REPL模式。在进入REPL模式后便可以使用Objection进行Hook的常用命令

1

objection -g com.roysue.httpurlconnectiondemo(包名) explore

常用基础命令:

1

2

3

4

5

6

7

8

9

10

11

12

jobs命令:作业系统很好用,用于查看和管理当前所执行Hook的任务

      查看当前hook任务:  jobs list

      终止hook任务:  jobs kill id

内存漫游相关命令:

      列出当前内存中所有类:android hooking list classes

      搜索内存中包含关键字的类:  android hooking search class 关键字

      搜索内存中包含关键字的方法:  android hooking search methods 关键字

      列出指定类中的所有方法:  android hooking list class_methods 类完整路径

      列出当前APP四大组件: android hooking list activities|services|receivers|providers

Hook相关命令:

      Hook指定的方法:  android hooking watch class_method 方法名称 --dump-args --dump-backtrace --dump-return

      Hook某个类的所有方法:   android hooking watch class 类名

0x03 root检测绕过

方法一

使用jadx反编译工具反编译apk文件,搜索root检测相关代码

 

找到包名为目标APP包名的那个方法,跟踪代码 

然后根据root检测逻辑编写绕过检测的js代码,具体代码略

方法二

上面的方法有个缺点就是只能针对特定的APP绕过,下面介绍通用的绕过方式

直接Hook java.io.FIle类的exists方法,这个方式的好处是:
1.其他APP不敢对这个类进行混淆
2.这个类一定存在于内存中无需延迟注入

仔细观察方法一种root检测部分截图的代码,发现使用的是Java基础类File的exists()方法进行检测

 

因此我们可以直接对File进行Hook,代码如下

function main(){
    Java.perform(function(){
        var File = Java.use("java.io.File")  // 获取java.io.File
        File.exists.implementation = function(){  // Hook exists方法并获取path值
            var path = this.path.value;  // 获取path值
            var result = this.exists()  // 获取exists()原始值
            // console.log(path)
            if(path == "/system/bin/su" || path == "/system/xbin/su" || path == "/sbin/su" || path == "/vendor/bin/su"){
                result = false  // 将返回值设为false代表未获取root
            }
            return result
        }
    })
}

setImmediate(main)

0x04 Hook HTTP/S网络请求框架

安卓开发中常用的HTTP/S网络请求框架
  • HttpURLConnection

HttpURLConnection属于原生的网络通信库,从Android 5(2014年)开始,Android官方不再推荐使用HttpClient。Android 6.0的SDK中去掉了HttpClient的支持。 在Android 9之后,Android更是彻底取消了对HTTPClient的支持,因此可以说原生的网络通信库就只剩下了HttpURLConnection。

  • OKHTTP

因为网络通信的操作涉及异步、多线程和效率等问题,在HttpURLConnection中并未对这些操作进行完整的封装,而是交给了开发者去完成,因此就出现了第二类网络通信框架——第三方HTTP(S)网络请求框架。okhttp 是大名鼎鼎的Square公司的开源网络请求框架,有2、3、4几个大版 本,目前主流使用的是okhttp3。相比HttpUrlConnection,okhttp3更加优雅和高效,大部分Android第三方网络框架(比如Retrofit2网络通信框架)也都是基于okhttp3的再封装。

另外,还有Volley网络通信框架,它是在2013年 的Google I/O大会上被推出的基于HttpUrlConnection的一款异步 网络请求框架和图片加载框架,Volley特别适合数据量小、通信频繁的网络操作。

HttpURLConnection基础开发流程

1.通过传入目标网络地址来新建一个URL对象,然后通过 openConnection()函数获取一个HttpURLConnection实例

2.获取到HttpURLConnection实例后,按照HTTP建立连接的流程设置HTTP请求头和参数信息例如:
setRequestMethod()函数设置HTTP请求方法
setRequestProperty() 设置请求参数
setConnectionTimeout() 函数设置连接超时时间
setReadTimeout()函数设置接收超时时间

3.调用HttpURLConnection实例的getInputStream()方法与服务器连接并获取到服务器返回的输入流,对输入流完成读取,最终调用disconnection()方法将HTTP连接关闭掉。

实例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

package com.roysue.httpurlconnectiondemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import android.util.Log;

import java.io.IOException;

import java.io.InputStream;

import java.net.HttpURLConnection;

import java.net.URL;

import java.nio.charset.StandardCharsets;

public class MainActivity extends AppCompatActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        new Thread(new Runnable() {

            @Override

            public void run() {

                while (true){

                    try {

                        // 通过url字符串创建一个URL对象

                        URL url = new URL("https://www.baidu.com");

                        // URL对象的openConnection()方法获取HttpURLConnection对象的实例

                        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

                        // 设置网络请求头部参数

                        connection.setRequestMethod("GET");

                        connection.setRequestProperty("token","r0ysue666");

                        connection.setConnectTimeout(8000);

                        connection.setReadTimeout(8000);

                        connection.connect(); // 开始连接

                        // 通过HttpURLConnection对象实例的getInputStream()方法获取网络请求响应的数据流

                        InputStream in = connection.getInputStream();

                        //if(in.available() > 0){

                        // 每次写入1024字节

                        int bufferSize = 1024;

                        byte[] buffer = new byte[bufferSize];

                        StringBuffer sb = new StringBuffer();

                        while ((in.read(buffer)) != -1) {

                            sb.append(new String(buffer));

                        }

                        Log.d("r0ysue666", sb.toString());

                        // 关闭HTTP网络请求

                        connection.disconnect();

                       // }

                    } catch (IOException e) {

                        e.printStackTrace();

                    }

                    try {

                        Thread.sleep(10*1000);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                }

            }

        }).start();

    }

}

HttpURLConnection“自吐”脚本开发

从HttpURLConnection基础开发流程我们发现几个关键的收发包函数

1.URL类的构造函数,其包含了目标网址的字符串。

2.setRequestMethod()和setRequestProperty()方法设置请求头和请求参数等信息。

3.getInputStream()方法获取response服务器响应数据。

利用Objection动态分析HttpURLConnection
  1. URL类的构造函数,其包含了目标网址的字符串。

Objection hook java.net.HttpURL类构造函数的方法

 

  1. setRequestMethod()和setRequestProperty()方法设置请求头和请求参数等信息。

Hook setRequestProperty()方法,发现无日志信息

 

 Hook java.net.HttpURLConnection​整个类的方法调用,观察调用了哪些方法

 

当hook HttpURLConnection类的方法时,没有打印出预想的setRequestProperty()、setRequestMethod()等方法的调用,这是因为HttpURLConnection是一个抽象类,无法创建对象实例所以内存中没有对象实例,需要Hook它的子类实例。

HttpURLConnection类定义

通过断点调试查看实现HttpURLConnection的子类实例

在获取HttpURLConnection对象后断点

 HttpURLConnectoin具体实现类com.android.okhttp.internal.huc.HttpURLConnectionImpl

hook com.android.okhttp.internal.huc.HttpURLConnectionImpl类获取请求头部和参数信息

1

android hooking watch class com.android.okhttp.internal.huc.HttpURLConnectionImpl

 

据Hook HttpURLConnection的结果编写“自吐”脚本
function main(){
    Java.perform(function(){
        var URL = Java.use("java.net.URL")
        var HttpURLConnection = Java.use("com.android.okhttp.internal.huc.HttpURLConnectionImpl")
        var BufferedReader = Java.use("java.io.BufferedReader")
        var StringBuilder = Java.use("java.lang.StringBuilder")
        var InputStreamReader = Java.use("java.io.InputStreamReader")

        // Hook URL构造方法获取请求url
        URL.$init.overload("java.lang.String").implementation = function(url){
            console.log("--NetWork Start--")
            console.log("--url--", url)
            this.$init(url)
        }

        // Hook setRequestMethod方法获取请求方法
        HttpURLConnection.setRequestMethod.implementation = function(method){
            console.log(method)
            this.setRequestMethod(method)
        }

        // Hook setRequestProperty方法获取请求参数
        HttpURLConnection.setRequestProperty.implementation = function(key, value){
            console.log("--param-- : key=>", key, "value=>",value)
            this.setRequestProperty(key, value)
        }
      
        // Hook getInputStream方法获取响应报文
        HttpURLConnection.getInputStream.implementation = function(){
            var inputStream = this.getInputStream()
            var buffer = BufferedReader.$new(InputStreamReader.$new(inputStream));
            var sb = StringBuilder.$new()
            var line = null;
            while((line = buffer.readLine()) != null){
                sb.append(line);
            }
            var data = sb.toString()
            console.log("--response--: " +data)
            console.log("--Network Stop--")
            return inputStream
        }
    }
    )
}

setImmediate(main)

执行结果如下:

OkHttp基础开发流程

1.创建一个OkhttpClient客户端对象

2.创建Request对象设置header、body、url等参数

3.使用OkhttpClient客户端将Request请求封装成Call对象后,调用enqueue()方法产生一次真实的网络请求,onResponse()等回调方法处理网络请求结果(网络请求可分为同步和异步两种,在Android中主要使用异步)

 

MainActivity代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

package com.r0ysue.okhttp3demo;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    private static String TAG = "r0ysue666";

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        // 定位发送请求按钮

        Button btn = findViewById(R.id.mybtn);

        btn.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                // 访问百度首页

//                String requestUrl = "https://www.baidu.com/";

                String requestUrl = "https://sapi.k780,com/";

                example myexample = new example();

                try {

                    myexample.run(requestUrl);

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }

        });

    }

}

example代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

package com.r0ysue.okhttp3demo;

import android.util.Log;

import java.io.IOException;

import java.net.InetSocketAddress;

import java.net.Proxy;

import java.util.HashMap;

import okhttp3.Call;

import okhttp3.Callback;

import okhttp3.FormBody;

import okhttp3.OkHttpClient;

import okhttp3.Request;

import okhttp3.RequestBody;

import okhttp3.Response;

public class example {

    // TAG即为日志打印时的标签

    private static final String TAG = "---okhttp3---";

    // 创建OkHttpClient客户端

    OkHttpClient client = new OkHttpClient

            .Builder()

            .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("192.168.110.125", 8080)))

            .addNetworkInterceptor(new LoggingInterceptor())

            .build();

    void run(String url) throws IOException {

        // 创建RequestBody对象(请求体)

        HashMap<String, String> paramMap = new HashMap<>();

        paramMap.put("app", "weather.today");

        paramMap.put("appkey","10003");

        paramMap.put("sign", "b59bc3ef6191eb9f747dd4e83c99f2a4");

        paramMap.put("format", "json");

        paramMap.put("cityNm","重庆");

        FormBody.Builder builder = new FormBody.Builder();

        for (String key:paramMap.keySet()){

            builder.add(key, paramMap.get(key));

        }

        RequestBody body = builder.build();

        // 创建Request对象设置请求头和请求体

        Request request = new Request.

                Builder()

                .url(url)

                .header("city","shanghai")

                .post(body)

                .build();

        // 发起异步请求

        // 通过OkHttpClient对象的newCall方法设置Request,调用enqueue方式完成一次网络请求

        client.newCall(request).enqueue(

                new Callback() {

                    @Override

                    public void onFailure(Call call, IOException e) {

                        call.cancel();

                    }

                    // 网络请求完毕后响应的回调函数

                    @Override

                    public void onResponse(Call call, Response response) throws IOException {

                        //打印输出

                        Log.d(TAG, response.body().string());

                    }

                }

        );

    }

}

Okhttp3“自吐”脚本开发

从Okhttp3基础开发流程我们发现几个关键的点

1.请求的服务器URL。
2.使用的协议版本,比如HTTP/1.0或者HTTP/1.1等。
3.请求头Header以及请求的Body数据。
4.请求的返回数据。

如果通过Hook的方式实现另类的“抓包”,那么我们的需求是保留URL、请求Body和headers以及请求的返回数据。可以在okhttpClient.newCall(request)函数中找到我们关注的请求Request对象,而这个对象中会包含我们关注的1、2、3项。但是由于Okhttp3设计的原因,如果按照HttpURLConnection自吐脚本输出Request和Response的难度比较大。

 

Okhttp3 Hook客户端强行设置代理

通过查看OkHttp3官方API发现可以在构造OkhttpClient对象时设置代理,之后的请求就会发送到代理服务器。

 

一般来说开发者使用OkHttpClient创建客户端对象并不会使用proxy方法设置代理,但是我们可以通过Frida Hook的方式在调用build()方法时强行设置代理,这样就能在不设置WIFI代理或VPN代理的方式抓取到请求包。Frida代码如下:

function main(){
    Java.perform(function(){
        // 在内存中获取okhttp3.OkHttpClient$Builder内部句柄
        var Builder = Java.use("okhttp3.OkHttpClient$Builder")
        // 为创建代理对象做准备
        var Proxy = Java.use("java.net.Proxy")
        var TYPE = Java.use("java.net.Proxy$Type")
        var InetSocketAddress = Java.use("java.net.InetSocketAddress")
        var String = Java.use("java.lang.String")
        console.log(Builder)
        // hook okhttp3.OkHttpClient$Builder内部类的build方法代表最终构造OkHttpClient对象
        Builder.build.implementation = function(){
            // 设置代理服务器IP地址
            var ip_str = String.$new("192.168.110.125")
            // 创建代理服务器对象
            var Proxy_IP_PORT = InetSocketAddress.$new(ip_str, 8080)
            // 调用当前对象的proxy强行设置代理
            this.proxy(Proxy.$new(TYPE.HTTP.value, Proxy_IP_PORT))
            console.log("设置代理成功:协议:", TYPE.HTTP.value)
            return this.build()
        }
    })
}

setImmediate(main)

运行效果

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue基于Spring Boot的中医在线学习课程购买服务管理系统

SpinrgBoot的主要优点有&#xff1a; 1、为所有spring开发提供了一个更快、更广泛的入门体验&#xff1b; 2、零配置&#xff1b; 3、集成了大量常用的第三方库的配置&#xff1b; 4、提供准备好的特性。当今&#xff0c;nodejs领域的开发者机会都在使用SpinrgBoot,在开发领域逐…

ONLYOFFICE服务器无法连接,请联系管理员问题解决

1、现象 部署好了nextcloud和onlyoffice后&#xff0c;新建文本文档报错ONLYOFFICE服务器无法连接&#xff0c;请联系管理员。 用快捷键“F12”进入控制台&#xff0c;点开错误提示栏&#xff0c;找到有“api.js“文件&#xff0c;“https://ONLYOFFICED的地址/web-apps/apps/…

冻结LM微调Prompt

这一章我们介绍在下游任务微调中固定LM参数&#xff0c;只微调Prompt的相关模型。这类模型的优势很直观就是微调的参数量小&#xff0c;能大幅降低LLM的微调参数量&#xff0c;是轻量级的微调替代品。和前两章微调LM和全部冻结的prompt模板相比&#xff0c;微调Prompt范式最大的…

基于android的违章处理APP 前后端服务 -毕业设计

基于android的违章处理APP 该项目是基于android版本的违章处理APP&#xff0c;系统包含前端android服务和后端web服务&#xff0c;内容和技术都是目前比较流行的架构。 技术介绍 前端android端&#xff1a; jdk17 gradle8.0 android studio 采用2023版本 后端web端&#xff…

PyTorch 中的距离函数深度解析:掌握向量间的距离和相似度计算

目录 Pytorch中Distance functions详解 pairwise_distance 用途 用法 参数 数学理论公式 示例代码 cosine_similarity 用途 用法 参数 数学理论 示例代码 输出结果 pdist 用途 用法 参数 数学理论 示例代码 总结 Pytorch中Distance functions详解 pair…

到店商详架构变迁

一、项目背景 到店商详是平台为京东到店业务提供的专属商详页面&#xff0c;将传统电商购物路径打造成以LBS门店属性的本地生活服务交易链路。 二、架构变迁 1、 主站商详扩展点 **优点&#xff1a;**到店侧仅关注业务&#xff0c;无需过度关注服务部署、性能优化等。 **缺…

【EI会议征稿通知】2024年通信技术与软件工程国际学术会议 (CTSE 2024)

2024年通信技术与软件工程国际学术会议 (CTSE 2024) 2024 International Conference on Communication Technology and Software Engineering (CTSE 2024) 2024年通信技术与软件工程国际学术会议 (CTSE 2024)将于2024年03月15-17日在中国长沙举行。会议专注于通信技术与软件工…

【C++初阶】第二站:类与对象(上) -- 上部分

前言: C学习的第二站&#xff1a;类和对象(上)文章的上半部分,知识点:面向过程和面向对象初步认识、类的引入、类的定义、类的访问限定符及封装、类的作用域、类的实例化. 目录 面向过程和面向对象初步认识 类的引入 类的定义 类的访问限定符及封装 访问限定符 封装 类的…

Git提交 ssh: connect to host github.com port 22: Connection timed out解决方案

你们好&#xff0c;我是金金金。 场景 之前都是好好的&#xff0c;不知道今天为什么提交代码就这样了 排查 根据英文可以看出&#xff0c;ssh端口号被拒绝了&#xff0c;22号端口不行&#xff0c;那就换一个端口 造成error的原因 ssh端口被拒绝 解决 找到.ssh文件&#xff…

AI新势力|将创业当作修行的BookGPT

近期&#xff0c;科技慢半拍联合AIGC开放社区采访了AI创业产品BootGPT的创始人陆再谋。陆总分享了他的创业之旅&#xff0c;从贵州到北京&#xff0c;再回到贵州的整段创业经历&#xff0c;从最初的困难到逐渐取得的成果&#xff0c;打造出了BookGPT这款创业产品。 在本次访谈中…

RT-Thread Studio学习(十五)PWM测量

RT-Thread Studio学习&#xff08;十五&#xff09;PWM测量 一、简介二、新建RT-Thread项目并使用外部时钟三、启用PWM输入捕获功能四、测试 一、简介 本文将基于STM32F407VET芯片介绍如何在RT-Thread Studio开发环境下使用定时器的PWM输入模式进行脉宽和周期测量。硬件及开发…

轻量化/高效扩散模型文献综述

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

算法刷题——拿出最少数目的魔法豆(力扣)

文章目录 题目描述我的解法思路结果分析 官方题解分析 查漏补缺更新日期参考来源 题目描述 传送门 拿出最少数目的魔法豆&#xff1a;给定一个正整数 数组beans &#xff0c;其中每个整数表示一个袋子里装的魔法豆的数目。请你从每个袋子中拿出 一些豆子&#xff08;也可以 拿…

TypeScript实现一个贪吃蛇小游戏

游戏效果 文件目录 准备1&#xff1a;新建index.html&#xff0c;编写游戏静态页面 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-…

基于Java图书商城系统设计与实现(源码+部署文档)

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

Windows连接Ubuntu桌面

平时Windows连接Ubuntu服务器都是使用Xshell、FinalShell等工具&#xff0c;但这些连接之后只能通过终端进行操作&#xff0c;无法用桌面方式与服务器交互。 本文介绍如何通过工具&#xff0c;实现Window连接远程Ubuntu服务器&#xff0c;并使用桌面方式交互。 系统版本&#x…

【leetcode】消失的数字

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家刷题&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1.暴力求解法2.采用异或的方法&#xff08;同单身狗问题&#xff09;3.先求和再减去数组元素 点击查看…

基于ssm+vue的宠物医院系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目背景…

后面的输入框与前面的联动,输入框只能输入正数

概要 提示&#xff1a;这里可以描述概要 前面的输入框是发票金额&#xff0c;后面的输入框是累计发票金额&#xff08;含本次&#xff09;--含本次就代表后倾请求的接口的数据&#xff08;不是保存后返显的-因为保存后返显的是含本次&#xff09;是不含本次的所以在输入发票金…