boss app sig及sp参数,魔改base64

news2024/11/24 7:43:30

前言

大家好呀,欢迎来到我的博客.2023年12月4日,boss web上线了最新的zp_token,环境检测点又增加了,与此同时app端的关键加密so从32位换成了64位,两者ida反编译so的时候都有反调试,无法直接f5,需要手动调整让ida重新识别.google了一下几乎找不到任何有关boss app的文章,所以这篇文章讲解app端的加密.篇幅较长,坐稳发车啦!

设备 pixel 4xl android10

版本: 11.240

下载地址: aHR0cHM6Ly93d3cud2FuZG91amlhLmNvbS9hcHBzLzYyMDIyMjIvaGlzdG9yeV92MTEyNDAxMA==

工具: charles(抓包) socksdroid(流量转发) jadx(反编译dex) ida(反编译so) frida(注入) frida-trace(还原算法)

目录

前言

声明

抓包

sig分析

sp分析

总结


声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除!

抓包

反复抓包后确定了是这个包,只不过响应是加密的,params里有sp和sig参数,sp参数是一个长串,sig由V3.0拼接一个32位字符串,猜测是md5

sig分析

可以尝试搜索字符串sig或者hook hashmap等等方法定位,我这里hook hashmap

Java.perform(function (){
    var hashMap = Java.use("java.util.HashMap");
    hashMap.put.implementation = function (a, b) {
        console.log("hashMap.put: ", a, b);
    return this.put(a, b);
}
})

 

 搜索net.bosszhipin.base.m 这个类可以看到sp和sig,八成就是这里了,这里先hook sig

从h方法点进去看看

右键复制frida 片段

抓个包后确认就是这里了

接着从signature方法点进去

接下来方便还原算法我们需要固定好入参,写java层的主动调用,然后配合着ida静态分析来还原算法

function call(){
    Java.perform(function (){
let YZWG = Java.use("com.twl.signer.YZWG");
var str = '/api/batch/batchRunV2batch_method_feed=%5B%22method%3DzpCommon.adActivity.getV2%26dataType%3D0%26expectId%3D802924422%26dataSwitch%3D1%22%2C+%22method%3Dzpgeek.app.f1.newgeek.jobcard%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%26expectId%3D802924422%22%2C+%22method%3Dzpgeek.app.geek.trait.tip%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%26expectId%3D802924422%22%2C+%22method%3Dzpgeek.cvapp.applystatus.change.tip%22%2C+%22method%3Dzpinterview.geek.interview.f1.complainTip%22%2C+%22method%3Dzpgeek.cvapp.geek.remind.warnexp%26entrance%3D1%26itemType%3D1%22%2C+%22method%3Dzpgeek.app.f1.banner.query%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%26expectId%3D802924422%26filterParams%3D%257B%2522cityCode%2522%253A%2522101010100%2522%252C%2522switchCity%2522%253A%25220%2522%257D%26gpsCityCode%3D0%26jobType%3D0%26mixExpectType%3D0%26sortType%3D1%22%2C+%22method%3Dzpinterview.geek.interview.f1%22%2C+%22method%3Dzpgeek.app.f1.recommend.filter%26commute%3D%26distance%3D0%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%26expectPosition%3D%26filterFlag%3D0%26filterParams%3D%257B%2522cityCode%2522%253A%2522101010100%2522%252C%2522switchCity%2522%253A%25220%2522%257D%26filterValue%3D%26jobType%3D0%26mixExpectType%3D0%26partTimeDirection%3D%26positionCode%3D%26sortType%3D1%22%2C+%22method%3Dzpgeek.app.bluecollar.topic.banner%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%22%2C+%22method%3Dzpgeek.cvapp.geek.homeexpectaddress.query%26cityCode%3D101010100%22%2C+%22method%3Dzpgeek.app.f1.interview.recjob.tip%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%26expectId%3D802924422%22%2C+%22method%3Dzpgeek.app.geek.recommend.joblist%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%26sortType%3D1%26expectPosition%3D100514%26pageSize%3D15%26expectId%3D802924422%26page%3D1%26filterParams%3D%257B%2522cityCode%2522%253A%2522101010100%2522%252C%2522switchCity%2522%253A%25220%2522%257D%22%2C+%22method%3Dzpgeek.app.studyabroad.article.headlines%22%2C+%22method%3Dzpgeek.cvapp.geek.resume.queryquality%22%5D&client_info=%7B%22version%22%3A%2210%22%2C%22os%22%3A%22Android%22%2C%22start_time%22%3A%221703159294618%22%2C%22resume_time%22%3A%221703159294618%22%2C%22channel%22%3A%2227%22%2C%22model%22%3A%22google%7C%7CPixel+4+XL%22%2C%22dzt%22%3A0%2C%22loc_per%22%3A0%2C%22uniqid%22%3A%227fe541f3-a666-4845-9186-cff9d5429f77%22%2C%22oaid%22%3A%22NA%22%2C%22did%22%3A%22DUzpQpzBYtoakGWwhYSfr2VDxKhBVPnGWdbfRFV6cFFwekJZdG9ha0dXd2hZU2ZyMlZEeEtoQlZQbkdXZGJmc2h1%22%2C%22is_bg_req%22%3A0%2C%22network%22%3A%22wifi%22%2C%22operator%22%3A%22UNKNOWN%22%2C%22abi%22%3A0%7D&curidentity=0&req_time=1703162554818&uniqid=7fe541f3-a666-4845-9186-cff9d5429f77&v=11.200'
       var str2 = null
var res = YZWG["signature"](str,str2)
        console.log(res)
    })
}

可以发现return 的值是来着nativeSignature方法,看名字就知道他应该是一个native方法         

点进去后发现确实是native方法,并且上面还有许多native方法,包含sp的加密方法nativeEncodeRequest(sp的寻找方式后续就不介绍了,和sig差不多),而且从字面上看,里面有解密数据的方法,正好对应响应数据的解密

 

往上找可以发现加载自yzwg这个so文件  

解包后可以看到只有64的so 11.230版本以前都是32的so,找到libyzwg.so并拖到ida64里反编译

 在导出表里搜索jni发现是动态注册,这里可以直接上脚本找出这个so注册的函数

// 获取 RegisterNatives 函数的内存地址,并赋值给addrRegisterNatives。
var addrRegisterNatives = null;
// 列举 libart.so 中的所有导出函数(成员列表)
var symbols = Module.enumerateSymbolsSync("libart.so");
for (var i = 0; i < symbols.length; i++) {
    var symbol = symbols[i];
    // console.log(symbol.name)
    //_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
    if (symbol.name.indexOf("art") >= 0 &&
        symbol.name.indexOf("JNI") >= 0 &&
        symbol.name.indexOf("RegisterNatives") >= 0 &&
        symbol.name.indexOf("CheckJNI") < 0) {

        addrRegisterNatives = symbol.address;
        console.log("RegisterNatives is at ", symbol.address, symbol.name);
        break
    }
}
if (addrRegisterNatives) {
    // RegisterNatives(env, 类型, Java和C的对应关系,个数)
    Interceptor.attach(addrRegisterNatives, {
        onEnter: function (args) {
            var env = args[0];        // jni对象
            var java_class = args[1]; // 类
            var class_name = Java.vm.tryGetEnv().getClassName(java_class);
            var taget_class = "com.twl.signer.YZWG";   //111 某个类中动态注册的so
            if (class_name === taget_class) {
                //只找我们自己想要类中的动态注册关系
                console.log("\n[RegisterNatives] method_count:", args[3]);
                var methods_ptr = ptr(args[2]);
                var method_count = parseInt(args[3]);
                for (var i = 0; i < method_count; i++) {
                    // Java中函数名字的
                    var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
                    // 参数和返回值类型
                    var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
                    // C中的函数内存地址
                    var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
                    var name = Memory.readCString(name_ptr);
                    var sig = Memory.readCString(sig_ptr);
                    var find_module = Process.findModuleByAddress(fnPtr_ptr);
                    // 地址、偏移量、基地址
                    var offset = ptr(fnPtr_ptr).sub(find_module.base);
                    console.log("name:", name, "sig:", sig,'module_name:',find_module.name ,"offset:", offset);

                }

            }
        }
    });
}
命令 frida -U -f com.hpbr.bosszhipin -l 文件名.js

结果 nativeSignature也就是sig的加密,偏移0x21864 nativeEncodeRequest也就是sp的加密,偏移0x209a4,后续ida中按g就可以跳到制定函数处

 可以看到text段是金色的,也就是说ida错误的把原本是代码的地方识别成了数据,这个时候需要手动帮ida一把,让他重新把数据识别成代码

选中金色段按c(code) 转为代码

转化后出现红色段就按p(create function)

重复这个过程直到把关键函数转为代码后就可以f5了

跳到0x21864位置转为伪c代码

点进去300多行代码,不太好分析,可以借助frida trace来跟踪native函数执行的时候调用了哪些函数

使用方法https://github.com/Pr0214/trace_natives

然后主动调用上面的java方法

把执行的函数复制到notepad里分析一下,调用了挺多函数,这里就没什么特殊的技巧了,只能凭借着经验猜测哪个是关键函数

在hook 1c714函数的时候我发现了结果,hook代码

var soAddr = Module.findBaseAddress("libyzwg.so");
var funcAddr = soAddr.add(0x1c714)  //32位+1

Interceptor.attach(funcAddr,{
            onEnter: function(args){
                console.log('onEnter arg[0]: ',hexdump(args[0],{length:args[1].toInt32()}))
                console.log('onEnter arg[1]: ',args[1])

                this.arg0 = args[0]
            },
            onLeave: function(retval){
                // console.log('onLeave arg[0]: ',hexdump(this.arg0.readPointer()))
                console.log('onLeave result: ',hexdump(retval))

            }
        });

arg0是java传进来的明文,onleave的时候结果出来了,并且明文传进去的时候还加了一个salt

复制到CyberChef里加密一下

是标准的md5,笔者在分析这个sig的时候当时并没有直接尝试加密,而是hook了他下面的函数2a5b8

hook 2a5b8代码

// 2A5B8
var soAddr = Module.findBaseAddress("libyzwg.so");
var funcAddr = soAddr.add(0x2A5B8)  //32位+1

var num = 0
Interceptor.attach(funcAddr,{
            onEnter: function(args){
                num+=1
                console.log(`onEnter arg[0]  ${num}次:  ${args[2]} `,hexdump(args[0]))
                console.log('onEnter arg[1]: ',hexdump(args[1],{length:512}))
                // console.log('onEnter arg[2]: ',args[2])

                this.arg0 = args[0]
            },
            onLeave: function(retval){
                console.log('onLeave arg[0]: ',hexdump(this.arg0))

            }
        });

可以看到在执行第8次2a5b8函数后结果也是出现了  

 再来看第一次调用,可以看到arg0像是md5的4个初始化魔数,只不过内存中是小端字节续,由于md5的分组处理长度是512bit,所以需要多次压入数据,正好对应调用多次2a5b8函数,这个函数类似c md5中的updata和final过程

这里可以修改c++中md5的最后填充数据和附加消息长度来验证是否是标准md5

可以发现是标准的md5,到此sig参数就分析完毕了.我为什么要提这个2a5b8函数的执行过程,有人会说,我直接把明文拼接salt后md5发现是结果了不就行了吗,是的,但是如果有一天你把明文和salt拼接后md5发现不是你要的结果你该如何处理?你不懂算法细节如何在不准确的伪c代码中分析还原算法?并且还有可能遇到魔改算法你又该如何处理?

sp分析

md5算法是hash算法,不可逆,作用是用来验签的,防止数据包被篡改,那就肯定有一个传递加密前明文的参数,从上面的抓包来看只有可能是sp参数,这就说的通了,明文加密成sp,并且和明文的MD5一起传给后台,后台接受数据包后解密sp得到明文,并再次加密明文和传来的sig对比以防止数据包被篡改

分析sig的时候已经提了sp的分析,和sig差不多,同理可以主动调用

function call(){
    Java.perform(function (){
        let a = Java.use("com.twl.signer.a");
        var str = 'batch_method_feed=%5B%22method%3DzpCommon.adActivity.getV2%26dataType%3D0%26expectId%3D802924422%26dataSwitch%3D1%22%2C+%22method%3Dzpgeek.app.f1.newgeek.jobcard%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%26expectId%3D802924422%22%2C+%22method%3Dzpgeek.app.geek.trait.tip%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%26expectId%3D802924422%22%2C+%22method%3Dzpgeek.cvapp.applystatus.change.tip%22%2C+%22method%3Dzpinterview.geek.interview.f1.complainTip%22%2C+%22method%3Dzpgeek.cvapp.geek.remind.warnexp%26entrance%3D1%26itemType%3D1%22%2C+%22method%3Dzpgeek.app.f1.banner.query%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%26expectId%3D802924422%26filterParams%3D%257B%2522cityCode%2522%253A%2522101010100%2522%252C%2522switchCity%2522%253A%25220%2522%257D%26gpsCityCode%3D0%26jobType%3D0%26mixExpectType%3D0%26sortType%3D1%22%2C+%22method%3Dzpinterview.geek.interview.f1%22%2C+%22method%3Dzpgeek.app.f1.recommend.filter%26commute%3D%26distance%3D0%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%26expectPosition%3D%26filterFlag%3D0%26filterParams%3D%257B%2522cityCode%2522%253A%2522101010100%2522%252C%2522switchCity%2522%253A%25220%2522%257D%26filterValue%3D%26jobType%3D0%26mixExpectType%3D0%26partTimeDirection%3D%26positionCode%3D%26sortType%3D1%22%2C+%22method%3Dzpgeek.app.bluecollar.topic.banner%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%22%2C+%22method%3Dzpgeek.cvapp.geek.homeexpectaddress.query%26cityCode%3D101010100%22%2C+%22method%3Dzpgeek.app.f1.interview.recjob.tip%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%26expectId%3D802924422%22%2C+%22method%3Dzpgeek.app.geek.recommend.joblist%26encryptExpectId%3Def7d7c83e4017a4233R40t-5FFBS%26sortType%3D1%26expectPosition%3D100514%26pageSize%3D15%26expectId%3D802924422%26page%3D1%26filterParams%3D%257B%2522cityCode%2522%253A%2522101010100%2522%252C%2522switchCity%2522%253A%25220%2522%257D%22%2C+%22method%3Dzpgeek.app.studyabroad.article.headlines%22%2C+%22method%3Dzpgeek.cvapp.geek.resume.queryquality%22%5D&client_info=%7B%22version%22%3A%2210%22%2C%22os%22%3A%22Android%22%2C%22start_time%22%3A%221703222473770%22%2C%22resume_time%22%3A%221703222473770%22%2C%22channel%22%3A%2228%22%2C%22model%22%3A%22google%7C%7CPixel+4+XL%22%2C%22dzt%22%3A0%2C%22loc_per%22%3A0%2C%22uniqid%22%3A%22b99e5c38-858b-4097-8b4d-084a6e75ec62%22%2C%22oaid%22%3A%22NA%22%2C%22did%22%3A%22DUzpQpzBYtoakGWwhYSfr2VDxKhBVPnGWdbfRFV6cFFwekJZdG9ha0dXd2hZU2ZyMlZEeEtoQlZQbkdXZGJmc2h1%22%2C%22is_bg_req%22%3A0%2C%22network%22%3A%22wifi%22%2C%22operator%22%3A%22UNKNOWN%22%2C%22abi%22%3A1%7D&curidentity=0&req_time=1703222656138&uniqid=b99e5c38-858b-4097-8b4d-084a6e75ec62&v=11.240'
        var str2 = null
        var res = a["d"](str, str2)
        console.log(res)
    })
}

同sig的frida-trace方法一样,trace后有几千行,这时需要分析哪些函数是关键函数并hook

最终的sp结果是魔改的base64,码表从A-Za-z0-9+/=替换成了A-Za-z0-9-_~ 并且这个结果是可以被des解密的,并且解密出来的raw也是不可见的,只能转为hex看看,这里埋个坑,尚不清楚传进去的明文和des解密后的密文有什么联系,先写到这里,后续再看看

总结

1出于安全考虑,本章未提供完整流程,调试环节省略较多,只提供大致思路,具体细节要你自己还原,相信你也能调试出来.

2本人写作水平有限,如有讲解不到位或者讲解错误的地方,还请各位大佬在评论区多多指教,共同进步,也可加本人微信lyaoyao__i(两个_)

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

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

相关文章

玩转 Scrapy 框架 (一):Scrapy 框架介绍及使用入门

目录 一、Scrapy 框架介绍二、Scrapy 入门 一、Scrapy 框架介绍 简介&#xff1a; Scrapy 是一个基于 Python 开发的爬虫框架&#xff0c;可以说它是当前 Python 爬虫生态中最流行的爬虫框架&#xff0c;该框架提供了非常多爬虫的相关组件&#xff0c;架构清晰&#xff0c;可扩…

SpringSecurity深度解析与实践(3)

这里写自定义目录标题 引言SpringSecurity之授权授权介绍java权限集成 登录失败三次用户上锁 引言 SpringSecurity深度解析与实践&#xff08;2&#xff09;的网址 SpringSecurity之授权 授权介绍 Spring Security 中的授权分为两种类型&#xff1a; 基于角色的授权&#…

基于Springboot的留守儿童爱心网站(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的留守儿童爱心网站(有报告)。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring…

怎么做好数字化工厂的建设?

怎样建设好的数字化工厂&#xff0c;不但须要有充足的费用预算&#xff0c;更加需要科学研究的计划和设计方案&#xff0c;一般做好智能化基本建设&#xff0c;务必要根据下列流程&#xff1a; 一.信息管理系统的计划和设计方案   许多的工厂会购买许多的单独的信息管理系统&…

基于SpringBoot+Vue的办公OA系统

开发环境 IDEA JDK1.8 MySQL8.0Node14.17.0 系统简介 本系统为前后端分离项目&#xff0c;主要拥有两个身份登录系统&#xff0c;管理员可以发布公告等信息&#xff0c;员工登录可以申请请假等信息&#xff0c;系统难度适中&#xff0c;适合学习研究使用&#xff0c;具体请…

深度学习中用来训练的train.py 探究学习2.0( 数据预处理)

数据预处理 下列代码为train.py中常见的一些数据处理方法 train_transform transforms.Compose([transforms.Resize((224, 224)),transforms.RandomVerticalFlip(),# 随机旋转&#xff0c;-45度到45度之间随机选transforms.RandomRotation(45),# 从中心开始裁剪transforms.C…

DaVinci各版本安装指南

链接: https://pan.baidu.com/s/1g1kaXZxcw-etsJENiW2IUQ?pwd0531 ​ #2024版 1.鼠标右击【DaVinci_Resolve_Studio_18.5(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到 DaVinci_Resolve_Studio_18.5(64bit)】。 2.打开解压后的文…

rk3588 之启动

目录 uboot版本配置修改编译 linux版本配置修改编译 启动sd卡启动制作spi 烧录 参考 uboot 版本 v2024.01-rc2 https://github.com/u-boot/u-boot https://github.com/rockchip-linux/rkbin 配置修改 使用这两个配置即可&#xff1a; orangepi-5-plus-rk3588_defconfig r…

Flink 客户端操作命令及可视化工具

Flink提供了丰富的客户端操作来提交任务和与任务进行交互。下面主要从Flink命令行、Scala Shell、SQL Client、Restful API和 Web五个方面进行整理。 在Flink安装目录的bin目录下可以看到flink&#xff0c;start-scala-shell.sh和sql-client.sh等文件&#xff0c;这些都是客户…

【C->Cpp】深度解析#由C迈向Cpp(2)

目录 &#xff08;一&#xff09;缺省参数 全缺省参数 半缺省参数 缺省参数只能在函数的声明中出现&#xff1a; 小结&#xff1a; &#xff08;二&#xff09;函数重载 函数重载的定义 三种重载 在上一篇中&#xff0c;我们从第一个Cpp程序为切入&#xff0c;讲解了Cpp的…

纯HTML代码实现给图片增加水印并下载保存到本地

<!DOCTYPE html> <html> <head><meta charset"utf-8"><meta name"viewport" content"widthdevice-width, initial-scale1, maximum-scale1, user-scalableno"/><title>图片水印打码工具-宋佳乐博客</tit…

智能优化算法应用:基于白鲸算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于白鲸算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于白鲸算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.白鲸算法4.实验参数设定5.算法结果6.参考文献7.MA…

vue3 新项目 - 搭建路由router

创建router/index 文件 main.ts 安装 router 然后 在 app下面 去 设置 路由出口

P6 音频格式—— AAC

目录 前言 01 AAC是什么&#xff1f; 02 为什么需要进行AAC进行音频压缩处理&#xff1f; 03 AAC的特点以及优势 04 AAC格式详解&#xff1a; 4.1. ADIF的数据结构&#xff1a; 4.1.1 ADIF Header具体的表格: 4.2. ADTS的结构&#xff08;重点&#xff09;&#xff1a; …

项目管理4321方法论

文章目录 一、项目立项准备&#xff08;4步&#xff09;case1、识别价值---解决背后痛点的才是价值&#xff0c;价值是做任何事情的出发点case2、明确目标---支撑价值实现的&#xff0c;目标是 具体/可衡量/可达到/相关性/有时限的case3、识别干系人---找对人才能做对事&#x…

MYSQL函数\约束\多表查询\事务

函数 字符串函数 数值函数 mod就是取余 日期函数 流程函数 约束 外键约束 删除更新\外键 多表查询 多表关系 一对多 多对多 一对一 多表查询 内连接 select e.name d.name from emp e join dept d on e.id d.id; 外连接 select emp.*, d.name from emp left join tm…

堆与二叉树(下)

接着上次的&#xff0c;这里主要介绍的是堆排序&#xff0c;二叉树的遍历&#xff0c;以及之前讲题时答应过的简单二叉树问题求解 堆排序 给一组数据&#xff0c;升序&#xff08;降序&#xff09;排列 思路 思考&#xff1a;如果排列升序&#xff0c;我们应该建什么堆&#x…

【贪心】买卖股票的最佳时机含手续费

/** 贪心&#xff1a;每次选取更低的价格买入&#xff0c;遇到高于买入的价格就出售(此时不一定是最大收益)。* 使用buy表示买入股票的价格和手续费的和。遍历数组&#xff0c;如果后面的股票价格加上手续费* 小于buy&#xff0c;说明有更低的买入价格更新buy。如…

面向船舶结构健康监测的数据采集与处理系统(一)系统架构

世界贸易快速发展起始于航海时代&#xff0c;而船舶作为重要的水上交通工具&#xff0c;有 其装载量大&#xff0c;运费低廉等优势。但船舶在运营过程中出现的某些结构处应力值 过大问题往往会给运营部门造成重大的损失&#xff0c;甚至造成大量的人员伤亡和严重 的环境污染…