IO操作Dex文件加密,APK加固项目实战_小肩膀 04.加固开发(替换mclassloader)-程序员宅基地

技术标签: java  Android  

APK加固原理分析

1.1 APK文件结构

首先让我们先了解一下一个完整的Android应用程序都由哪些文件组成。解压一个apk包,我们可以看到一下的这些文件及文件夹:

image

每个文件及文件夹的作用如下表所示。

这里说明下META-INF文件夹下3个文件的关系:

(1) 首先对apk包中每个文件做一次算法(数据摘要+Base64编码),然后保存到MANIFEST.MF文件中

(2) 然后对MANIFEST.MF整个文件同样做一次算法(数据摘要+Base64编码),存放到CERT.SF文件的头属性中,再对MANIFEST.MF文件中各个属性块做一次算法(数据摘要+Base64编码),存放到CERT.SF文件中

(3) 最后对CERT.SF文件做签名,内容保存到CERT.RSA中

TODO:META-INF目录下三个文件中SHA-1值的计算

1.2 APK打包流程

我们看一下APK打包的完整流程:

image

上图中涉及到的工具及其作用如下:

在这里插入图片描述
TODO:单独学习APK打包流程

1.3 Dex文件结构

Dex文件整体结构如下:

image

dex文件结构的详细分析可以回看第四章-Dex文件结构分析。这里我们在dex整体加密,暂时只用到Dex文件头,所以我们主要介绍下Dex文件头的结构。

image

这里面,有3个成员我们需要特别关注,这在后面加固里会用到,它们分别是checksum、signature和fileSize。

0x01 checksum字段

checksum是校验码字段,占4bytes,主要用来检查从该字段(不包含checksum字段,也就是从12bytes开始算起)开始到文件末尾,这段数据是否完整,也就是完整性校验。它使用alder32算法校验。

0x02 signature字段

signature是SHA-1签名字段,占20bytes,作用跟checksum一样,也是做完整性校验。之所以有两个完整性校验字段,是由于先使用checksum字段校验可以先快速检查出错的dex文件,然后才使用第二个计算量更大的校验码进行计算检查。

0x03 fileSize字段

占4bytes,保存classes.dex文件总长度。

这3个字段当我们修改dex文件的时候,这3个字段的值是需要更新的,否则在加载到Dalvik虚拟机的时候会报错。

1.4 Dex整体加固原理

image

替换classloader时机
Application:attachBaseContext()
ContentProvider:onCreate()
APplication:onCreate()

加载原始application
1.获取原始application的class name
2.加载原始application并生成对象
3.替换API层所有Application引用
4.设置baseContext并调用原始application的onCreate()

Dex 文件整体加固原理如下:

image

在该过程中涉及到三个对象,分别如下:

l 源程序

源程序也就是我们的要加固的对象,这里面主要修改的是原apk文件中的classes.dex文件和AndroidManifest.xml文件。

l 壳程序

壳程序主要用于解密经过加密了的dex文件,并加载解密后的原dex文件,并正常启动原程序。

l 加密程序

加密程序主要是对原dex文件进行加密,加密算法可以是简单的异或操作、反转、rc4、des、rsa等加密算法。

该加固过程可以分为如下4个阶段:

(1) 加密阶段

(2)合成新的dex文件

(3)修改原apk文件并重打包签名

(4)运行壳程序加载原dex文件

0x01 加密阶段

加密阶段主要是讲把原apk文件中提取出来的classes.dex文件通过加密程序进行加密。加密的时候如果使用des对称加密算法,则需要注意处理好密钥的问题。同样的,如果采用非对称加密,也同样存在公钥保存的问题。

image

0x02 合成新的dex文件

这一阶段主要是讲上一步生成的加密的dex文件和我们的壳dex文件合并,将加密的dex文件追加在壳dex文件后面,并在文件末尾追加加密dex文件的大小数值。这样我们就合成了新的classes.dex文件。由于我们修改了dex文件,这时候

当然,还可以这样处理。将加密的dex文件保存到其他目录下,如assets资源目录。那么这时候我们新的classes.dex文件就直接是壳程序,不需要合成。两种方案都可以。

前面讲了讲加密dex文件保存到文件末尾,以及保存到资源目录下2中方法。其实还有一种更复杂的方案,就是讲加密dex文件保存到dex header中。我们回顾10.1.3节 Dex文件结构。Dex Header中有一个字段:header_size,这个字段是记录dex文件头的长度,一般情况下dex头文件大小为112bytes。我们可以将header_size修改为header_size和加密dex文件长度两者的总长度,并将加密dex文件嵌入到dex header末尾来达到隐藏原dex文件的目的。但是这里有个问题,就是原来dex文件头部以下的一些类、方法、字段等位移可能发生改变需要修改,这个修复工程很复杂,或者我们可以尝试使用修改dx工具的源码来实现。

image

在壳程序里面,有个重要的类:ProxyApplication类,该类继承Application类,也是应用程序最先运行的类。所以,我们就是在这个类里面,在原程序运行之前,进行一些解密dex文件和加载原dex文件的操作。

0x03 修改原apk文件并重打包签名

在这一阶段,我们首先将apk解压,会看到如下图的6个文件和目录。其中,我们需要修改的只有2个文件,分别是classes.dex和AndroidManifest.xml文件,其他文件和文件加都不需要改动。

首先,我们把解压后apk目录下原来的classes.dex文件替换成我们在0x02上一步合成的新的classes.dex文件。然后,由于我们程序运行的时候,首先加载的其实是壳程序里的ProxyApplication类。所以,我们需要修改AndroidManifest.xml文件,指定application为ProxyApplication,这样才能正常找到识别ProxyApplication类并运行壳程序。这里,需要注意的是。

完成上述两个文件的替换后,我们就重新打包apk并签名。到此,我们就完成了dex文件的整体加固。

image

0x04 运行壳程序加载原dex文件

通过上面3个步骤,我们了解了dex文件整体加固的流程。那么,当这个加固后的dex文件加载到Dalvik虚拟机中,它又是如何工作的呢?下面我们就来讲解下壳程序在这里面发挥的重要作用。

首先,Dalvik虚拟机会加载我们经过修改的新的classes.dex文件,并最先运行ProxyApplication类。在这个类里面,有2个关键的方法:attachBaseContext和onCreate方法。ProxyApplication显示运行attachBaseContext再运行onCreate方法。

在attachBaseContext方法里,主要做两个工作:

  1. 读取classes.dex文件末尾记录加密dex文件大小的数值,则加密dex文件在新classes.dex文件中的位置为:len(新classes.dex文件) – len(加密dex文件大小)。然后将加密的dex文件读取出来,加密并保存到资源目录下

  2. 然后使用自定义的DexClassLoader加载解密后的原dex文件

在onCreate方法中,主要做两个工作:

  1. 通过反射修改ActivityThread类,并将Application指向原dex文件中的Application

  2. 创建原Application对象,并调用原Application的onCreate方法启动原程序

image

具体的app启动过程可以回看第2章的内容。

2 APK加固实现

2.1 加固Demo源程序

源程序我们使用NDK开发一个简单的demo,并在assets目录存放一个txt文件读取。这样,我们就可以得到跟10.1.1一样的文件结构,也可以检测下加固后的文件是否影响lib文件的和资源目录下文件的操作。

工程结构如下:

image

MyApplication.java关键代码:

1.  public class MyApplication extends Application{

4.  @Override

6.  public void onCreate()  {

8.  // TODO Auto-generated method stub

10.  super.onCreate();

12.  }

16.  @Override

18.  protected void attachBaseContext(Context base)  {

20.  // TODO Auto-generated method stub

22.  super.attachBaseContext(base);

24.  }

26.  }

MainActivity.java关键代码:

1.  public class MainActivity extends Activity

3.  {

5.  /** Called when the activity is first created. */

9.  public native void test();

13.  @Override

15.  public void onCreate(Bundle savedInstanceState)

17.  {

19.  super.onCreate(savedInstanceState);

21.  setContentView(R.layout.main);

25.  System.loadLibrary("demo1");

27.  test();

31.  Log.v("demo", getFromAssets("test.txt"));

35.  }

39.  public String getFromAssets(String fileName){

41.  String Result="";

43.  try {

45.  InputStreamReader inputReader = new InputStreamReader(  getResources().getAssets().open(fileName)  );

47.  BufferedReader bufReader = new BufferedReader(inputReader);

49.  String line="";

53.  while((line = bufReader.readLine()) != null)

55.  Result += line;

59.  }  catch  (Exception e)  {

61.  e.printStackTrace();

63.  }

65.  return Result;

67.  }

69.  }

demo1.cpp关键代码:

1.  #include "com_demo_MainActivity.h"

3.  #include <android log.h="">

5.  #include <stdio.h>

9.  #define LOG_TAG "AndroidNDK"

11.  #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

15.  JNIEXPORT void JNICALL Java_com_demo_MainActivity_test  (JNIEnv * env, jobject obj)

17.  {

19.  LOGI("Hello World...\n");

21.  }
22.  </stdio.h></android>

2.2 加密程序

加密程序是个java工程。

Main.java源码:

1.  public static void main(String[] args) throws Exception {

3.  if(args.length != 2){
4.  System.out.println("Error! Please input 2 parameters!");
5.  return ;
6.  }

8.  //参数1:脱壳classes.dex
9.  //参数2:TargetApk.zip
10.  File tuokeDexFile = new File(args[0]);
11.  File targetFile = new File(args[1]);

13.  byte[] tuokeDexFileArray = readFileBytes(tuokeDexFile);
14.  byte[] targetFileArray = encrypt(readFileBytes(targetFile));
15.  int tuokeDexFileLen = tuokeDexFileArray.length;
16.  int targetFileLen = targetFileArray.length;
17.  int totalLen = targetFileLen + tuokeDexFileLen + 4;
18.  byte[] newdex = new byte[totalLen];

20.  //添加脱壳classes.dex文件
21.  System.arraycopy(tuokeDexFileArray, 0, newdex, 0, tuokeDexFileLen);

23.  //添加目标TargetApk.zip文件
24.  System.arraycopy(targetFileArray, 0, newdex, tuokeDexFileLen, targetFileLen);

26.  //添加目标zip文件大小
27.  System.arraycopy(intToByte(targetFileLen), 0, newdex, tuokeDexFileLen + targetFileLen, 4);

29.  //修改Dex file size文件头
30.  fixFileSizeHeader(newdex);

32.  //修改Dex SHA1文件头
33.  fixSHA1Header(newdex);

35.  //修改Dex CheckSum文件头
36.  fixCheckSumHeader(newdex);

38.  File file = new File("classes.dex");
39.  if(!file.exists()){
40.  file.createNewFile();
41.  }

43.  FileOutputStream out = new FileOutputStream(file);
44.  out.write(newdex);
45.  out.flush();
46.  out.close();

48.  System.out.println("Enforce apk successfully!");

50.  }

修改dex头的fileSize字段

1.  private static void fixSHA1Header(byte[] dexBytes)

3.  throws NoSuchAlgorithmException {

5.  MessageDigest md = MessageDigest.getInstance("SHA-1");

7.  md.update(dexBytes, 32, dexBytes.length - 32);//从32为到结束计算sha--1

9.  byte[] newdt = md.digest();

11.  System.arraycopy(newdt, 0, dexBytes, 12, 20);//修改sha-1值(12-31)

13.  //输出sha-1值,可有可无

15.  String hexstr = "";

17.  for  (int i = 0; i < newdt.length; i++)  {

19.  hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16).substring(1);

21.  }

23.  }

修改dex头的checkSum字段

修改dex头的signature字段

1.  private static void fixCheckSumHeader(byte[] dexBytes)  {

3.  Adler32 adler = new Adler32();
4.  long value = adler.getValue();
5.  int va = (int) value;
6.  byte[] newcs = intToByte(va);

8.  //高位在前,低位在前掉个个
9.  byte[] recs = new byte[4];

11.  for  (int i = 0; i < 4; i++)  {
12.  recs[i] = newcs[newcs.length - 1 - i];
13.  }

15.  System.arraycopy(recs, 0, dexBytes, 8, 4);//效验码赋值(8-11)

17.  }

加密:

1.  private static byte[]  encrypt(byte[] srcdata){

3.  for(int i = 0;i < srcdata.length;i++){

5.  srcdata[i] = (byte)(0xFF ^ srcdata[i]);

7.  }

9.  return srcdata;

11.  }

读取二进制文件内容

1.  private static byte[]  readFileBytes(File file) throws IOException {

3.  byte[] arrayOfByte = new byte[1024];

5.  ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();

7.  FileInputStream fis = new FileInputStream(file);

9.  while  (true)  {

11.  int i = fis.read(arrayOfByte);

13.  if  (i != -1)  {

15.  localByteArrayOutputStream.write(arrayOfByte, 0, i);

17.  } else {

19.  return localByteArrayOutputStream.toByteArray();

21.  }

23.  }

25.  }

int转byte数组

1.  public static byte[]  intToByte(int number)  {

3.  byte[] b = new byte[4];

5.  for  (int i = 3; i >= 0; i--)  {

7.  b[i] = (byte)  (number % 256);

9.  number >>= 8;

11.  }

13.  return b;

15.  }

2.3 壳程序

壳程序我们主要分析ProxyApplication类:

0x01 attachBaseContext方法

首先分析attachBaseContext方法:

1.  protected void attachBaseContext(Context base)  {

3.  super.attachBaseContext(base);
4.  Log.v("demo", "[JiaguApk]=>attachBaseContext() start...");

6.  try {

8.  File odex = this.getDir("assets", MODE_PRIVATE);
9.  String odexPath = odex.getAbsolutePath();
10.  String targetFilename = odexPath + "/TargetApk.zip";
11.  File targetApkZipFile = new File(targetFilename);

13.  if(!targetApkZipFile.exists()){
14.  //从classes.dex中提取TargetApk.zip
15.  targetApkZipFile.createNewFile();
16.  byte[] classesDexData = readClassesDexFromApk();

18.  extractTargetZipFileFromDex(classesDexData, targetFilename);
19.  }

21.  Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread", new Class[]  {}, new Object[]  {});
22.  String packageName = getPackageName();
23.  Map mPackages = (Map) RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mPackages");
24.  WeakReference wr = (WeakReference) mPackages.get(packageName);

26.  DexClassLoader dLoader = new DexClassLoader(odexPath + "/TargetApk.zip", odexPath, "/data/data/" + packageName + "/lib", base.getClassLoader().getParent());

28.  //替換成TargetApk.dex的ClassLoader
29.  RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", wr.get(), dLoader);

31.  }  catch  (Exception e)  {
32.  Log.v("demo", "[JiaguApk]=>attachBaseContext() " + Log.getStackTraceString(e));

34.  }

36.  Log.v("demo", "[JiaguApk]=>attachBaseContext() end...");

38.  }

在该方法中,我们首先获取加密dex文件,并保存到assets资源目录下,然后进行解密。

由于运行加固后的apk文件时,应用程序使用的是加固后dex文件的类加载器,而不是原dex文件的类加载器。所以,我们需要事先将类加载器替换成员dex文件的类加载器。

这里,我们通过反射获取ActivityThread,通过其mPackages成员获取LoadedApk对象。通过第二章的app启动过程分析,我们知道类加载器的创建是在LoadedApk中完成,这也是为什么我们需要获取LoadedApk对象。通过该对象的mClassLoader成员,我们可以修改该成员指向我们自定义的DexClassLoader,这个类加载器就是原dex中的类加载器。这样我们就可以在后面的步骤中用该类加载器加载原dex文件。

0x02 onCreate方法

1.  public void onCreate()  {

3.  try {

5.  // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
6.  String appClassName = "com.demo.MyApplication";

8.  /**
9.  * 调用静态方法android.app.ActivityThread.currentActivityThread
10.  * 获取当前activity所在的线程对象
11.  */

13.  Object currentActivityThread = RefInvoke.invokeStaticMethod(
14.  "android.app.ActivityThread", "currentActivityThread",
15.  new Class[]  {}, new Object[]  {});
16.  /**
17.  * 获取currentActivityThread中的mBoundApplication属性对象,该对象是一个
18.  * AppBindData类对象,该类是ActivityThread的一个内部类
19.  */
20.  Object mBoundApplication = RefInvoke.getFieldOjbect(
21.  "android.app.ActivityThread", currentActivityThread,
22.  "mBoundApplication");
23.  /**
24.  * 获取mBoundApplication中的info属性,info 是 LoadedApk类对象
25.  */

27.  Object loadedApkInfo = RefInvoke.getFieldOjbect(
28.  "android.app.ActivityThread$AppBindData",
29.  mBoundApplication, "info");

31.  if(null == loadedApkInfo){
32.  Log.v("demo", "[JiaguApk]=>onCreate()=>loadedApkInfo is null!!!");
33.  }else{
34.  Log.v("demo", "[JiaguApk]=>onCreate()=>loadedApkInfo:" + loadedApkInfo);
35.  }

37.  /**
38.  * loadedApkInfo对象的mApplication属性置为null
39.  */
40.  RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",
41.  loadedApkInfo, null);

45.  /**
46.  * 获取currentActivityThread对象中的mInitialApplication属性
47.  * 这货是个正牌的 Application
48.  */

50.  Object oldApplication = RefInvoke.getFieldOjbect(
51.  "android.app.ActivityThread", currentActivityThread,
52.  "mInitialApplication");

55.  /**
56.  * 获取currentActivityThread对象中的mAllApplications属性
57.  * 这货是 装Application的列表
58.  */

60.  ArrayList<application> mAllApplications = (ArrayList<application>) RefInvoke
61.  .getFieldOjbect("android.app.ActivityThread",
62.  currentActivityThread, "mAllApplications");

65.  //列表对象终于可以直接调用了 remove调了之前获取的application 抹去记录的样子
66.  mAllApplications.remove(oldApplication);

68.  /**
69.  * 获取前面得到LoadedApk对象中的mApplicationInfo属性,是个ApplicationInfo对象
70.  */
71.  ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
72.  .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,
73.  "mApplicationInfo");

76.  /**
77.  * 获取前面得到AppBindData对象中的appInfo属性,也是个ApplicationInfo对象
78.  */

80.  ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
81.  .getFieldOjbect("android.app.ActivityThread$AppBindData",
82.  mBoundApplication, "appInfo");

86.  //把这两个对象的className属性设置为从meta-data中获取的被加密apk的application路径
87.  appinfo_In_LoadedApk.className = appClassName;

89.  /**
90.  * 调用LoadedApk中的makeApplication 方法 造一个application
91.  * 前面改过路径了
92.  */

94.  Application app = (Application) RefInvoke.invokeMethod(
95.  "android.app.LoadedApk", "makeApplication", loadedApkInfo,
96.  new Class[]  { boolean.class, Instrumentation.class },
97.  new Object[]  { false, null });

99.  RefInvoke.setFieldOjbect("android.app.ActivityThread",
100.  "mInitialApplication", currentActivityThread, app);

102.  if(null == app){
103.  Log.v("demo", "[JiaguApk]=>onCreate()=>app is null!!!");
104.  }else{
105.  app.onCreate();
106.  Log.v("demo", "[JiaguApk]=>onCreate() success!");
107.  }

109.  }  catch  (Exception e)  {

111.  Log.v("demo", "[JiaguApk]=>onCreate() " + Log.getStackTraceString(e));

113.  }

115.  Toast.makeText(this, "Enforced by 01hackcode", Toast.LENGTH_LONG).show();

117.  }
118.  </application></application>

在onCreate方法中,我们主要做的是还原Application对象。由于在加固dex文件的时候,我们讲原来的Application替换成了我们定义的ProxyApplication对象。所以,在这一步里,我们需要还原它。至于怎么还原呢?同样参考第二章的app启动流程,我们知道,Application对象的创建发生在ActivityThread类的handleBindApplication方法中,该方法其实是调用LoadedApk类的makeApplication方法进行创建,而在该方法中,最终是调用Instrumentation类的newApplication方法完成Application对象的创建。最后调用原Application对象的onCreate方法完成启动过程。

在这个过程中,我们要将所有引用到Application的地方都进行修改,这也是为什么上面那么多种反射修改操作。通过第二章的分析,我们发现引用了Application的类有3个地方。

(1) ActivityThread类的mInitialApplication成员,它是Application类,我们需要将替换成原dex文件的Application对象

(2) ActivityThread类的mAllApplication成员,每次创建完Application对象后,就会将该对象添加到mAllApplication列表中,所以我们要讲该列表中的ProxyApplication对象移除。在后面我们通过反射调用makeApplication方法的时候会将创建的原dex文件的Application添加到该列表中

(3) LoadedApk类中的mApplicationInfo对象的className成员记录了ProxyApplication的类名,这里我们也需要修改,替换成原dex文件的Application类名

(4) 最后是ActivityThread的内部类AppBindData类对象mBoundApplication成员的appInfo对象的className成员也记录了ProxyApplication类名,我们也将它修改成员dex原dex文件的Application类名。

至此,我们就差不多修改完了所有引用ProxyApplication对象的地方。最后还差一步,我们需要反射获取ActivityThread类的makeApplication方法完成原Dex文件中Application的创建工作,并调用该Application对象的onCreate方法。到这里,我们才可以正常运行原程序。

ActivityThread.java文件:

1.  public final class ActivityThread{

3.  …

5.  Application mInitialApplication;

7.  final ArrayList<application> mAllApplications = new ArrayList<application>();

9.  final ArrayMap<string, weakreference<loadedapk="">> mPackages

11.  = new ArrayMap<string, weakreference<loadedapk="">>();

13.  …

15.  static final class AppBindData {

17.  …

19.  ApplicationInfo appInfo;

21.  …

23.  }

25.  }
26.  </string,></string,></application></application>

LoadedApk.java文件:

1.  public final class LoadedApk {

3.  …

5.  private final ApplicationInfo mApplicationInfo;

7.  …

9.  }

ApplicationInfo.java文件:

1.  public class ApplicationInfo extends PackageItemInfo implements Parcelable {

3.  …

5.  /**

7.  * Class implementing the Application object. From the "class"
8.  * attribute.
9.  */

11.  public String className;

13.  …

15.  }
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_47933729/article/details/111223808

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法