ios 部分string颜色_iOS-代码混淆加固方案

对于iOS来说,由于系统是封闭的,APP上架需要通过App Store,安全性来说相当高。但是对于大厂和知名APP而言,别人给的安全保障永远没有自己做的来得踏实。所以对于大厂、少部分企业级和金融支付类应用来说加固是相当重要的。
下面是目前几个专业加固大厂提供的加固策略

  • 网易
9012b786b9867699105ca029b8aac090.png
  • 网易安全三板斧:
  1. 第一板斧是防静态分析,这里包括字符串加密、符号混淆、代码逻辑混淆和游戏存档加密;

2.第二板斧是防动态调试、反调试和通信安全(数据加密);

  1. 第三板斧是外挂检测、加速挂、内存修改挂和自动任务挂等
  • 爱加密
0c7a107c7f1b4bea312a453c5eda9e84.png
0c7a107c7f1b4bea312a453c5eda9e84.png
  • safengine
4e8248130cd93b42a5837ac2feaa95df.png
  • 几维安全
1d82980011088bfe1c892017fc06fe99.png
  • 梆梆安全
81a96ef879683b125d1ae75b846286ce.png

本文将针对以上几点进行实现,对于一些不太容易实现的将会做方向性讨论

  • 字符串加密
  • 代码混淆(方法名,类名,变量名,符号表)
  • 代码逻辑混淆
  • 反调试

字符串加密

对字符串加密的方式目前我所了解到掌握到的最可靠方式就是用脚本将代码中的所有标记需要加密的字符串进行异或转换,这样代码中就不存在明文字符串了。当然第三方的字符串加密不可能这么简单,具体怎么做的我也不太清楚。不过为了增加字符串加密的难度复杂性,我们可以先将字符串用加密工具转换(例如AES、base64等)后的把加字符串放在工程中,并且把解密的钥匙放在工程中,用异或转换,把解密钥匙和加密后的字符串转换,这样就有2层保障,增加了复杂度。

  • 首先 我们创建任意一个工程,在工程中写入下面的代码,并在每句打上断点,再选择Xcode工具栏的Debug --> Debug Workflow --> Always Show Disassembly。这样你就可以在断点处进入汇编模式界面,最后运行程序
/* 加密NSString字符串 */    NSString *str = @"Hello World";    NSLog(@"%@",str);    /* 加密char*字符串 */    char* cStr = "Super Man";    NSLog(@"%s",cStr);
db73cf37c6986d3731fa7e10ff34b3ca.png

你会发现,你的字符串内容暴露在了汇编模式中,这会导致别人在逆向分析你的工程时能看见你的字符串内容,我们一般接口、域名、加解密钥匙串、AppKey、AppId等比较重要的东西会放在客户端用作字符串,这就很容易暴露出来。

  • 步骤1 首先需要在工程代码中进行修改,把下面的宏和decryptConfusionCS,decryptConstString函数放入代码中,用宏包含每个需要转换的字符串。
/* 字符串混淆解密函数,将char[] 形式字符数组和 aa异或运算揭秘 */extern char* decryptConfusionCS(char* string){    char* origin_string = string;    while(*string) {        *string ^= 0xAA;        string++;    }    return origin_string;}/* 解密函数,返回的是NSString类型的 */extern NSString* decryptConstString(char* string){    /* 先执行decryptConfusionString函数解密字符串 */    char* str = decryptConfusionCS(string);    /* 获取字符串的长度 */    unsigned long len = strlen(str);    NSUInteger length = [[NSString stringWithFormat:@"%lu",len] integerValue];     NSString *resultString = [[NSString alloc]initWithBytes:str length:length encoding:NSUTF8StringEncoding];    return resultString;}/* * 使用heyujia_confusion宏控制加密解密 * 当heyujia_confusion宏被定义的时候,执行加密脚本,对字符串进行加密 * 当heyujia_confusion宏被删除或为定义时,执行解密脚本,对字符串解密 */#define heyujia_confusion#ifdef heyujia_confusion/* heyujia_confusion 宏被定义,那么就进行执行解密脚本 *//* confusion_NSSTRING宏的返回结果是NSString 类型的 */#define confusion_NSSTRING(string) decryptConstString(string)/* confusion_CSTRING宏的返回结果是char* 类型的 */#define confusion_CSTRING(string) decryptConfusionCS(string)#else/* heyujia_confusion 宏没有被定义,那么就执行加密脚本 *//* 加密NSString类型的 */#define confusion_NSSTRING(string) @string/* 加密char *类型的 */#define confusion_CSTRING(string) string#endif@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    /* 使用confusion_NSSTRING宏包含需要加密的NSString字符串 */    NSString *str = confusion_NSSTRING("Hello World");    NSLog(@"%@",str);    /* 使用confusion_NSSTRING宏包含需要加密的char*字符串 */    char* cStr = confusion_CSTRING("Super Man");    NSLog(@"%s",cStr);    }
  • 步骤2 使用终端cd 到需要加密的工程目录下 执行 touch confusion.py 和 touch decrypt.py 命令,生产加密和解密脚本文件
  • 步骤3 把下面代码加入解密脚本confusion.py中
#!/usr/bin/env python# encoding=utf8# -*- coding: utf-8 -*-# author by heyujia# 脚本将会用于对指定目录下的.h .m源码中的字符串进行转换# 替换所有字符串常量为加密的char数组,形式((char[]){1, 2, 3, 0})import importlibimport osimport reimport sys# replace替换字符串为((char[]){1, 2, 3, 0})的形式,同时让每个字节与0xAA异或进行加密# 当然可以不使用0xAA 使用其他的十六进制也行 例如0XBB、0X22、0X11def replace(match):    string = match.group(2) + 'x00'    replaced_string = '((char []) {' + ', '.join(["%i" % ((ord(c) ^ 0xAA) if c != '0' else 0) for c in list(string)]) + '})'    return match.group(1) + replaced_string + match.group(3)# obfuscate方法是修改传入文件源代码中用confusion_NSSTRING标记的所有字符串# 使用replace函数对字符串进行异或转换def obfuscate(file):    with open(file, 'r') as f:        code = f.read()        f.close()        code = re.sub(r'(confusion_NSSTRING(|confusion_CSTRING()"(.*?)"())', replace, code)        code = re.sub(r'//#define ggh_confusion', '#define ggh_confusion', code)        with open(file, 'w') as f:            f.write(code)            f.close()# openSrcFile方法是读取源码路径下的所有.h和.m 文件# 对每个文件执行obfuscate函数def openSrcFile(path):    print("混淆的路径为 "+ path)    # this folder is custom    for parent,dirnames,filenames in os.walk(path):        #case 1:        #        for dirname in dirnames:        #            print((" parent folder is:" + parent).encode('utf-8'))        #            print((" dirname is:" + dirname).encode('utf-8'))        #case 2        for filename in filenames:            extendedName = os.path.splitext(os.path.join(parent,filename))            if (extendedName[1] == '.h' or extendedName[1] == '.m'):                print("处理源代码文件: "+ os.path.join(parent,filename))                obfuscate(os.path.join(parent,filename))#这里需要修改源码的路径为自己工程的文件夹名称srcPath = '../daimahunxiao'if __name__ == '__main__':    print("本脚本用于对源代码中被标记的字符串进行加密")        if len(srcPath) > 0:        openSrcFile(srcPath)    else:        print("请输入正确的源代码路径")        sys.exit()
  • 步骤4 把下面的解密代码放入decrypt.py解密脚本中
#!/usr/bin/env python# encoding=utf8# -*- coding: utf-8 -*-# author by heyujia# 解密脚本# 替换所有标记过的加密的char数组为字符串常量,""import importlibimport osimport reimport sys# 替换((char[]){1, 2, 3, 0})的形式为字符串,同时让每个数组值与0xAA异或进行解密def replace(match):    string = match.group(2)    decodeConfusion_string = ""    for numberStr in list(string.split(',')):        if int(numberStr) != 0:            decodeConfusion_string = decodeConfusion_string + "%c" % (int(numberStr) ^ 0xAA)    replaced_string = '"' + decodeConfusion_string + '"'    print("replaced_string = " + replaced_string)        return match.group(1) + replaced_string + match.group(3)# 修改源代码,加入字符串加密的函数def obfuscate(file):    with open(file, 'r') as f:        code = f.read()        f.close()        code = re.sub(r'(confusion_NSSTRING(|confusion_CSTRING()((char []) {(.*?)})())', replace, code)        code = re.sub(r'[/]*#define ggh_confusion', '//#define ggh_confusion', code)        with open(file, 'w') as f:            f.write(code)            f.close()#读取源码路径下的所有.h和.m 文件def openSrcFile(path):    print("解密路径: "+ path)    # this folder is custom    for parent,dirnames,filenames in os.walk(path):        #case 1:        #        for dirname in dirnames:        #            print((" parent folder is:" + parent).encode('utf-8'))        #            print((" dirname is:" + dirname).encode('utf-8'))        #case 2        for filename in filenames:            extendedName = os.path.splitext(os.path.join(parent,filename))            #读取所有.h和.m 的源文件            if (extendedName[1] == '.h' or extendedName[1] == '.m'):                print("已解密文件:"+ os.path.join(parent,filename))                obfuscate(os.path.join(parent,filename))#源码路径srcPath = '../daimahunxiao'if __name__ == '__main__':    print("字符串解混淆脚本,将被标记过的char数组转为字符串,并和0xAA异或。还原代码")    if len(srcPath) > 0:        openSrcFile(srcPath)    else:        print("请输入正确的源代码路径!")        sys.exit()
  • 步骤5 根据自己的需求修改下脚本里面的代码 和 文件路径。
  • 步骤6 把步骤1中的宏heyujia_confusion注释了,然后执行加密脚本,在终端中输入 python confusion.py ,
    (1.如果报错,请查看下自己Mac电脑中的python版本,如果是python3就输入 python3 confusion.py .
    (2.如果报 Non-ASCII character 'xe8' in file confusion.py on line 2 相关的错,请确定脚本的前面3行是
#!/usr/bin/env python# encoding=utf8# -*- coding: utf-8 -*-

执行完步骤6后的结果

2ada7587c1af5d28d7c3152104e66ecb.png

此时字符串已被加密,运行程序会发现一切正常

c8e8838778cf1206257b990cb35aaaec.png
8ad8fb674b09d2a3d987a3e18f2dfdfe.png

加密后汇编界面看不见我们的字符串内容了,但是我们用来解密的方法还是暴露在了汇编界面,所以我们后期还需要对方法名,变量名,类命等做混淆。

  • 步骤7 把步骤1中的宏heyujia_confusion取消注释,然后执行解密脚本,在终端中输入 python decrypt.py
aa79809d538920f06450766267bd45d4.png
  • 解密后文本又变回了原样。

这里只是基本的异或转换加密,让代码中的字符串变成看不懂的char [],实际操作中远远不止这么简单 例如:

  • 首先:我们先用加密工具例如:AES.Base64等把需要转换的字符串先加密变成加密字符串
  • 然后:在用异或转换加密的脚本把加密字符串进行转换(包括解密用的钥匙串)
  • 在使用的时候:先异或解密字符串,然后根据解密钥匙串把字符串在转为可用的字符串

ps.还有一种保护字符串的方法,就是使用NSLocalizedString字符串本地化。

虽然跟着我的步骤你确实加密成功了,但是你却无法实际验证。所以要验证最终的混淆结果是否达到效果,你还需要学习如何破壳解密IPA如何动态静态逆向编程分析工程源码,大家可以先看看我 这篇文章 。先掌握逆向分析后再来做代码混淆,就能验证混淆结果是否有效

变量、方法名,类名混淆

对于混淆这一块,网上真的是千篇一律,基本都是copy的念大婶的内容,没有一点自己的创新和思考。网上的方法我也用过,但是有缺陷,只能混淆方法名或者说自己固定的内容去替换。第一不自动,对于大项目而言每个方法名自己添加,太麻烦。第二变量混淆有问题,因为只是单纯的字符串替换,用宏代替。当遇到使用_ 下划线访问变量时,就会出现错误。

对于变量、方法名,类名的混淆,其实跟字符串混淆差不多,都是加密混淆,然后解密混淆。不同的是,变量、方法名,类名的混淆目的是为了让别人反编译的时候不知道你的变量、方法,类是具体用来干什么的,不会像明文那样一目了然。增加逆向难度。混淆的内容不需要像字符串一样,最后程序运行时还要转成中文正常使用。由于本人对shell脚本语言也不是非常熟悉,想要按照自己的思路写一套完整的混淆脚本还不行。所以这部分也是在网上找的,算是目前最实用最完善的混淆

  • 首先 打开终端cd到需要混淆的工程目录下,输入
    touch obConfusion.sh (加密混淆脚本文件)
    touch obDecrypt.sh (解密混淆脚本文件)
    生成2个脚本文件
  • 然后在工程目录以外创建一个文件夹,用于保存加密时生成的加密文本内容,该内容会在解密时用到
  • 最后是在 obConfusion.sh 和 obDecrypt.sh 文件中加入脚本内容

下面是加密混淆脚本内容

#!/bin/sh###################################  (该脚本是在https://github.com/heqingliang/CodeObfus 上找到的)#  代码混淆脚本  heyujia 2018.03.15####################################识别含有多字节编码字符时遇到的解析冲突问题export LC_CTYPE=Cexport LANG=C#配置项:#项目路径,会混淆该路径下的文件ProjectPath="/Users/xieyujia/Desktop/ios/学习项目/daimahunxiao"#这个路径是混淆成功后,原文本和替换文本解密对应的文件存放路径(该路径不能在项目目录或其子目录),混淆成功后会在该路径下生成一个解密时需要的文件,根据该文件的文本内容把混淆后的内容更换为原文本内容,该文件名的组成由$(date +%Y%m%d)"_"$(date +%H%M)及日期_小时组成,每分钟会不一样。所以解密的时候需要每次更换文件路径SecretFile="/Users/xieyujia/Desktop/ios/学习项目/tihuan"$(date +%Y%m%d)"_"$(date +%H%M)#第一个参数为项目路径if [[ $1 ]]thenif [[ $1 != "_" ]]; thenProjectPath=$1fifi#第二个参数指定密钥文件路径及文件名if [[ $2 ]]thenif [[ $2 != "_" ]]; thenSecretFile=$2fifi###############################################################################查找文本中所有要求混淆的属性方法类,只会替换文本中ob_开头和_fus结尾的字符串(区分大小写,例如oB_就不会做混淆),如果注释内容有该类型的字符串,也会进行替换。对于使用 _下划线访问的变量属性,不会有影响,一样会替换成对应_的混淆内容。resultfiles=`grep 'ob_[A-Za-z0-9_]*_fus' -rl $ProjectPath`#查找结果为空则退出if [[ -z $resultfiles ]]thenecho "项目没有需要混淆的代码"exitelseecho "开始混淆代码..."echo  > $SecretFilefix=$(awk  'BEGIN{srand();k=0;}#随机数生成函数function random_int(min, max) {return int( rand()*(max-min+1) ) + min;}#随机字符串生成函数function random_string(len) {result="UCS"k;alpbetnum=split("a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z", alpbet, ",");for (i=0; i 0) {tempstr=substr(x, RSTART, RLENGTH);#判断是否有之前已经找过的重复字符串for ( i = 0; i < k; i++ ){if (strarr[i] == tempstr){break;}}if(i $SecretFilerecordnum=1while [[ 1 == 1 ]]; dorecord=`echo $x|cut -d "|" -f$recordnum`if [[ -z $record ]]thenbreakfirecord1=`echo $record|cut -d ":" -f1`echo "原项:"$record1record2=`echo $record|cut -d ":" -f2`echo "加密项:"$record2#替换文件夹中所有文件的内容(支持正则)#单引号不能扩展sed -i '' "s/${record1}/${record2}/g" `grep $record1 -rl $ProjectPath`echo "第"$recordnum"项混淆代码处理完毕"let "recordnum = $recordnum + 1"done#查找需要混淆的文件名并替换filerecordnum=1while [[ 1 == 1 ]]; dofilerecord=`echo $x|cut -d "|" -f$filerecordnum`if [[ -z $filerecord ]]thenbreakfifilerecord1=`echo $filerecord|cut -d ":" -f1`#echo "原项:"$filerecord1filerecord2=`echo $filerecord|cut -d ":" -f2`#echo "加密项:"$filerecord2#改文件名find $ProjectPath -name $filerecord1"*"| awk 'BEGIN{frecord1="'"$filerecord1"'";frecord2="'"$filerecord2"'";finish=1}{filestr=$0;gsub(frecord1,frecord2,filestr);print "mv " $0 " " filestr";echo 第"finish"个混淆文件处理完毕";finish++;}'|bashlet "filerecordnum = $filerecordnum + 1"done

下面是解密混淆脚本的内容

#!/bin/sh########################################  代码还原脚本  RyoHo 2018.03.15########################################识别含有多字节编码字符时遇到的解析冲突问题export LC_CTYPE=Cexport LANG=C#配置项:#已经混淆的项目路径ProjectPath="/Users/xieyujia/Desktop/ios/学习项目/daimahunxiao"#这个是文件路径而不是目录,是混淆的时候生成的文本文件路径,每次不一样。所以每次加密后,解密时需要更换路径SecretFile="/Users/xieyujia/Desktop/ios/学习项目/tihuan20180315_1456"#第一个参数为项目路径if [[ $1 ]]thenif [[ $1 != "_" ]]; thenProjectPath=$1fifi#第二个参数指定密钥文件路径及文件名if [[ $2 ]]thenif [[ $2 != "_" ]]; thenSecretFile=$2fifi###############################################################################内容还原x=`cat $SecretFile`recordnum=1while [[ 1 == 1 ]]; dorecord=`echo $x|cut -d "|" -f$recordnum`if [[ -z $record ]]thenbreakfirecord1=`echo $record|cut -d ":" -f1`echo "原项:"$record1record2=`echo $record|cut -d ":" -f2`echo "加密项:"$record2#若项目中加密项与密钥文件的加密项不符合则退出程序searchresult=`grep $record2 -rl $ProjectPath`if [[ -z $searchresult ]]; thenecho "指定的密钥文件不能还原"exitfi#替换文件夹中所有文件的内容(支持正则)#单引号不能扩展sed -i '' "s/${record2}/${record1}/g" $searchresultecho "第"$recordnum"项混淆代码还原完毕"let "recordnum = $recordnum + 1"done#文件还原filerecordnum=1while [[ 1 == 1 ]]; dofilerecord=`echo $x|cut -d "|" -f$filerecordnum`if [[ -z $filerecord ]]thenbreakfifilerecord1=`echo $filerecord|cut -d ":" -f1`#echo "原项:"$filerecord1filerecord2=`echo $filerecord|cut -d ":" -f2`#echo "加密项:"$filerecord2#改文件名find $ProjectPath -name $filerecord2"*"| awk 'BEGIN{frecord1="'"$filerecord1"'";frecord2="'"$filerecord2"'";finish=1;}{filestr=$0;gsub(frecord2,frecord1,filestr);print "mv " $0 " "filestr ";echo 第"finish"个混淆文件还原完毕"finish++;}'|bashlet "filerecordnum = $filerecordnum + 1"done

应大家需要把脚本源码:https://github.com/xkftkffz/DMHXDemo 地址 放出来

建议大家看看脚本内容,有利于学习理解。该脚本是有针对性的混淆内容,可以自己修改脚本中的正则表达式来确定混淆的内容。脚本中只会替换文本中ob_开头和_fus结尾的字符串(区分大小写,例如oB_就不会做混淆),如果注释内容有该类型的字符串,也会进行替换。对于使用 _下划线访问的变量属性,不会有影响,一样会替换成对应_的混淆内容。

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

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

相关文章

Python入门基础之迭代和列表生成式

什么是迭代 在Python中&#xff0c;如果给定一个list或tuple&#xff0c;我们可以通过for循环来遍历这个list或tuple&#xff0c;这种遍历我们成为迭代&#xff08;Iteration&#xff09;。 在Python中&#xff0c;迭代是通过 for ... in 来完成的&#xff0c;而很多语言比如C或…

安川最小巧机器人_2020工博会,安川展品前瞻(机器人篇)

&#xff5e;基于YASKAWA(安川)核心产品和i-Mechatronics(i立方-机电一体化)概念&#xff0c;实现客户的生产改革&#xff5e;这次的中国国际工业博览会安川将展出至今为止最多的演示机数量。•提供现在重点关注的智能制造、半导体、汽车制造个性化解决方案•提供适用于所有生产…

如何调度spark程序_如何定时,周期性的运行程序?Python APScheduler实现任务灵活调度...

在我们的开发工作中&#xff0c;时常会有这样的开发需求&#xff0c;如需要定时或者周期性的运行某些程序&#xff0c;因此经常用到一些定时服务&#xff0c;如在 Linux系统中使用 Crond 服务实现程序的定时运行。在 Python中也有这样的一个模块&#xff0c;那就是 APScheduler…

redis实现轮询算法_【07期】Redis中是如何实现分布式锁的?

点击上方“Java面试题精选”&#xff0c;关注公众号面试刷图&#xff0c;查缺补漏分布式锁常见的三种实现方式&#xff1a;数据库乐观锁&#xff1b;基于Redis的分布式锁&#xff1b;基于ZooKeeper的分布式锁。本地面试考点是&#xff0c;你对Redis使用熟悉吗&#xff1f;Redis…

前端radio单选框默认选中_开发记录篇前端内容1

有段时间没有更新文章了&#xff0c;因为是用的公司电脑&#xff0c;没有虚拟机&#xff0c;所以就没法演示hadoop相关的东西了&#xff0c;而且大数据篇的东西需要花费一些时间和精力去收集整理内容&#xff0c;那大数据篇就先暂停一下。最近这段时间的话我可能会更新一些开发…

专属海报小程序_剑3泡泡 | 小程序给你一份专属的账号海报!

01按照惯例&#xff0c;这里是简介paopaods.com本期推送的是&#xff1a;如何正确的使用小程序每个账号均可小程序【剑3泡泡】搜到&#xff0c;生成专属账号海报&#xff01;点击底部【点我卖号】即可拥有&#xff01;02教程开始之前&#xff0c;安利paopaods.com泡泡家定金调整…

td不允许自己扩展_V神原文详解:通过及时性检测器(TD)解决区块链的51%攻击问题...

注&#xff1a;原文作者是以太坊联合创始人Vitalik Buterin&#xff0c;在这篇文章中&#xff0c;他提出了一种称为及时性检测器(TD)的构造&#xff0c;以试图解决区块链51%攻击的问题。(图&#xff1a;Vitalik Buterin)以下为译文&#xff1a;摘要我提出了一种基于Lamport 99%…

Hadoop安装之JDK在Centos虚拟机中安装

安装jdk.bin和jdk.tar.gz打的办法 安装jdk.bin 安装好的VM Centos7的虚拟机&#xff0c; 1、查看是否是64位操作系统&#xff1a; cat /proc/cpuinfo | grep flags | grep lm | wc -l 如果结果>0 则是64位操作系统 2、JDK 中 jdk-6u41-linux-x64.bin 和 jdk-6u41-linux-x64…

Exp3 免杀原理与实践

---恢复内容开始--- 一&#xff0c;实验内容 利用多种工具实现实现恶意代码免杀在另一台电脑上&#xff0c;杀软开启的情况下&#xff0c;实现运行后门程序并回连成功二&#xff0c;实验步骤 &#xff08;1&#xff09;使用msf编码器生成的后门程序 这里可以直接用上次实验生成…

如何进入指定文件目录_Python如何遍历操作指定文件目录下的全部Excel文件?

Python Tablib是麻省理工学院授权的与格式无关的表格数据集库。支持导入、导出和操作表格数据集&#xff0c;轻松的将数据导出为各种不同的格式&#xff0c;包括excel&#xff0c;json&#xff0c;html&#xff0c;yaml&#xff0c;csv&#xff0c;tsv等格式。接下来&#xff0…

《雪吁》

凛冽隆冬风骨寒&#xff0c;层云避日雾无边&#xff1b; 渐絮残花萧萧夜&#xff0c;窗前瘦花犹遮帘。 转载于:https://www.cnblogs.com/morron/p/8749430.html

条形图坐标轴_解密咨询报告中常见的双层条形图的制作方法

为了增加PPT的设计灵感&#xff0c;我除了经常逛一些设计社区之外&#xff0c;也会收集的各个公司咨询报告来学习。昨天我看了4份数据报告&#xff0c;发现这4份数据报告中都出现了一个共同的图表类型。第1个图表自于IXDC发布的《2018年中国用户体验行业调查报告》&#xff0c;…

赋值给集合_ArrayList集合源码

ArrayList简介ArrayList 是 Java 集合框架中比较常用的数据结构了。ArrayList是可以动态增长和缩减的索引序列&#xff0c;内部封装了一个动态再分配的Object[]数组这里我们可以看到ArrayList继承抽象类AbstractList&#xff0c;实现了 List 接口&#xff0c;同时还实现了 Rand…

剪切文件_lammps模拟带缺陷镍板剪切变形(in文件及注释)

本期给大家带来lammps模拟带缺陷镍板剪切变形的in文件及其详细注释。初始模型如图一所示&#xff1a;图1 生成的初始模型 in文件及注释如下&#xff1a;#利用eam势函数模拟带缺陷镍板的剪切#模型构成——上下镍板夹可动镍块&#xff0c;镍块中有圆柱形缺陷&#xff0c;移动上镍…

为什么将表格的method改为post后就无法工作_用Python将Keras深度学习模型部署为Web应用程序...

构建一个很棒的机器学习项目是一回事&#xff0c;但归根结底&#xff0c;你希望其他人能够看到你的辛勤工作。当然&#xff0c;你可以将整个项目放在GitHub上&#xff0c;但是怎么让你的祖父母也看到呢&#xff1f;我们想要的是将深度学习模型部署为世界上任何人都可以访问的We…

centos 源码安装mysql5.6_CentOS 7下源码安装MySQL 5.6

目录准备工作运行环境确认你的安装版本下载MySQL安装MySQL准备安装环境编译和安装配置MySQL单实例配置单实例配置方法添加防火墙启动MySQL重启MySQL多实例配置什么是多实例多实例配置方法创建启动文件初始化数据库配置防火墙启动MySQL登陆MySQL重启MySQL准备工作运行环境本文的…

跳一跳

转载于:https://www.cnblogs.com/shanhua-fu/p/8807348.html

树莓派 无法安装mysql_树莓派安装mysql

前置&#xff0c;更新系统sudo apt-get updatesudo apt-get upgrade安装与配置MySQL直接安装mysql的话&#xff0c;默认下载的是MariaDB&#xff0c;两者差别不大&#xff0c;用法一样。如果真想下载mysql&#xff0c;需要换源&#xff0c;新版的Linux系统自带的是MariaDB&…

ABP框架使用 Swagger

在最近的一个项目中用到了 ABP框架 http://aspnetboilerplate.com/ ,第一次接触到 Swagger https://swagger.io/ 以及前后端的完全分离 在ABP官网下载下来的ABP框架结构【基于ASP.NET MVC5.x的】如图&#xff1a; ABP的EntityFramework 是Code First Mode的&#xff0c;所以在配…

JVM第五部分 高效并发

java 内存模型与线程 硬件内存模型 java内存模型 主内存vs工作内存 所有变量都在主内存&#xff08;虚拟机内存的一部分&#xff09;&#xff0c;每条线程都有自己的工作内存&#xff0c;线程所有用到的变量都必须从主内存拷贝出来&#xff08;不能直接读写主内存变量&#xff…