android脱壳 细节源码解析

news/2024/11/16 18:04:31/文章来源:https://www.cnblogs.com/ovo-fisherman/p/18549617

加固和脱壳

加固:在 Android 中,应用的代码是通过 DEX 文件存储的,DEX(Dalvik Executable)文件包含了用于在 Dalvik 或 ART 虚拟机中执行的字节码。这些 DEX 文件通常是应用的核心代码,包含了类、方法、字段等信息。

  • DEX 文件通常被压缩或加密,以防止直接提取和反编译。
  • 加壳通常是通过对 DEX 文件进行加密或混淆,或者将其存储在一个非标准的格式(比如自定义的容器)中来实现。

分类为:dex加固,整体加固(将整个dex进行加固,反编译得到是壳的代码)、抽取加固(将细节函数进行抽取,比如抽空或者是smail代码为nop)、VMP、dex2c等;so加固,对so结构进行处理、对so数据进行加密、自定义linker一般处理方式就是so dump,然后so修复

脱壳:将加固app运行过程中,解密后加载的dex文件保存下来 与加密算法差不多,一个加密的是字符/字节数据,一个加密的是文件(也是字节数据)逆向加密算法主要要的是过程,做算法还原,而脱壳要的是解密后的dex文件。

整体加固: 可分为落地加载、内存加载

本质上都是将app自身的dex整体加密,app运行过程中解密后加载
有些壳还会抹掉dex文件头、dex的文件大小filesize等
一般会有字符串加密、资源加密、反调试、签名验证

正常一个Dex文件的运行过程:

  1. 获取加密或混淆的 DEX 文件

如果应用的 DEX 文件经过加密或混淆,它们可能被存储在 APK 文件的某个特定位置,并且会在运行时被动态解密或解压缩。例如,开发者可以在加载 DEX 文件之前,通过一些方法(如使用 DexClassLoader 或自定义的解密机制)来解密它们。

  1. 使用 DexFile 加载 DEX 文件 (不一定是使用DexFile来加载,但是一定会加载DEX文件)

    在 ART 环境中,DexFile 是用来表示和加载 DEX 文件的类。它负责加载 DEX 文件并解析出其中的字节码。在没有加壳或没有加密的情况下,应用会直接从磁盘上加载并执行 DEX 文件。但在加壳情况下,必须首先解密或恢复 DEX 文件才能被 ART 或 Dalvik 解释执行。

    • DexFile 类通过 DexFileLoader 等类加载 DEX 文件。
    • 加载 DEX 文件时,通常会涉及到校验和验证步骤。
  2. 破解 DEX 加密或脱壳过程(对于加密或者加壳的程序总会到真正程序执行的位置)

    1. 动态加载: 在应用运行时,解密 DEX 文件的代码通常是通过动态加载机制(如 DexClassLoader)来执行的。这意味着在加载过程中,DEX 文件的内容可以被解密、反调试或修改。
    2. 绕过加壳: 如果加壳机制采用了简单的加密(例如 AES 加密),攻击者可以通过静态分析(例如通过调试或反编译应用)查找加密密钥或解密过程。绕过加壳过程的方式通常包括:
      • 破解加密密钥:如果密钥在代码中明文存储,通过反编译找到密钥。
      • 调试:通过调试工具捕获解密过程,或手动修改程序流来直接获取解密后的 DEX 文件。
      • Hook 技术:通过在运行时修改应用的行为,绕过加密或解密过程,直接提取 DEX 文件。

所以我们需要就是在完成加密之后把dex文件dump下来。通过 ART 提供的 DexFile 或其他加载机制,可以获取到解密后的 DEX 文件,最后我们需要做的就是通过 DexFile 对象获取 DEX 文件中的字节码,反编译 DEX 文件,得到原始的 Java 类、方法、字段等代码,修改字节码后,可以重新打包并绕过原先的加壳机制。从而完成脱壳

dex相关类InMemoryDexClassLoader源码分析:

这个类是最初处理dex文件数据的位置,其自身是去继承了父类的类对象BaseDexClassLoader

    public InMemoryDexClassLoader(@NonNull ByteBuffer @NonNull [] dexBuffers,@Nullable String librarySearchPath, @Nullable ClassLoader parent) {super(dexBuffers, librarySearchPath, parent);}

在BaseDexClassLoader中去实现了parent属性的赋值,以及initByteBufferDexPath初始化

    public BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent) {super(parent);this.sharedLibraryLoaders = null;this.sharedLibraryLoadersAfter = null;this.pathList = new DexPathList(this, librarySearchPath);this.pathList.initByteBufferDexPath(dexFiles);// Run background verification after having set 'pathList'.this.pathList.maybeRunBackgroundVerification(this);}

在这里开始去处理dex文件了——>new DexFile(dexFiles, definingContext, null_elements)

 void initByteBufferDexPath(ByteBuffer[] dexFiles) {final List<IOException> suppressedExceptions = new ArrayList<IOException>();try {Element[] null_elements = null;DexFile dex = new DexFile(dexFiles, definingContext, null_elements);dexElements = new Element[] { new Element(dex) };} catch (IOException suppressed) {System.logE("Unable to load dex files", suppressed);suppressedExceptions.add(suppressed);dexElements = new Element[0];}if (suppressedExceptions.size() > 0) {dexElementsSuppressedExceptions = suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);}}

mCookie

这里出现了我们的mCookie了,脱壳工具blackdex通过mCookie来脱壳 ,也就是说这里的mCookie可以被利用于脱壳的

    DexFile(ByteBuffer[] bufs, ClassLoader loader, DexPathList.Element[] elements)throws IOException {mCookie = openInMemoryDexFiles(bufs, loader, elements);mInternalCookie = mCookie;mFileName = null;}

继续往里走

这里其实不用太仔细看,因为我们需要的是mCookie,也就是这个函数的返回值的位置,所以可以进入openInMemoryDexFilesNative(bufs, arrays, starts, ends, loader, elements)可以看出来这是Native化的

    private static Object openInMemoryDexFiles(ByteBuffer[] bufs, ClassLoader loader,DexPathList.Element[] elements) throws IOException {// Preprocess the ByteBuffers for openInMemoryDexFilesNative. We extract// the backing array (non-direct buffers only) and start/end positions// so that the native method does not have to call Java methods anymore.byte[][] arrays = new byte[bufs.length][];int[] starts = new int[bufs.length];int[] ends = new int[bufs.length];for (int i = 0; i < bufs.length; ++i) {arrays[i] = bufs[i].isDirect() ? null : bufs[i].array();//这里实现了 arrays之后要被转为jbyteArray  也就是说是另外一种形式的bufsstarts[i] = bufs[i].position();ends[i] = bufs[i].limit();}return openInMemoryDexFilesNative(bufs, arrays, starts, ends, loader, elements);}

Native化的 DexFile_openInMemoryDexFilesNative

static jobject DexFile_openInMemoryDexFilesNative(JNIEnv* env,jclass,jobjectArray buffers,jobjectArray arrays,jintArray jstarts,jintArray jends,jobject class_loader,jobjectArray dex_elements) {jsize buffers_length = env->GetArrayLength(buffers);CHECK_EQ(buffers_length, env->GetArrayLength(arrays));CHECK_EQ(buffers_length, env->GetArrayLength(jstarts));CHECK_EQ(buffers_length, env->GetArrayLength(jends));ScopedIntArrayAccessor starts(env, jstarts);ScopedIntArrayAccessor ends(env, jends);// Allocate memory for dex files and copy data from ByteBuffers.std::vector<MemMap> dex_mem_maps;dex_mem_maps.reserve(buffers_length);for (jsize i = 0; i < buffers_length; ++i) {jobject buffer = env->GetObjectArrayElement(buffers, i); //这是buffers转jobject的buffer了jbyteArray array = reinterpret_cast<jbyteArray>(env->GetObjectArrayElement(arrays, i));jint start = starts.Get(i);jint end = ends.Get(i);MemMap dex_data = AllocateDexMemoryMap(env, start, end);//这里申请的空间就是最后dex存储的位置if (!dex_data.IsValid()) {DCHECK(Thread::Current()->IsExceptionPending());return nullptr;}if (array == nullptr) {// Direct ByteBufferuint8_t* base_address = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));if (base_address == nullptr) {ScopedObjectAccess soa(env);ThrowWrappedIOException("dexFileBuffer not direct");return nullptr;}size_t length = static_cast<size_t>(end - start);memcpy(dex_data.Begin(), base_address + start, length);}         //之前说了array是bufs的jobject的形式,上面是按照buffer来实现获取各个dex文件的内容else {// ByteBuffer backed by a byte arrayjbyte* destination = reinterpret_cast<jbyte*>(dex_data.Begin());env->GetByteArrayRegion(array, start, end - start, destination);}//这里是按照array来实现获取各个dex文件的内容
--------------------------------------------------------------------------------------------------//最后push进dex_mem_maps对象dex_mem_maps.push_back(std::move(dex_data));}std::vector<std::string> error_msgs;const OatFile* oat_file = nullptr;std::vector<std::unique_ptr<const DexFile>> dex_files =Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(std::move(dex_mem_maps),class_loader,dex_elements,/*out*/ &oat_file,/*out*/ &error_msgs);return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);

到这里其实也只是把每一个的dex文件的起始地址转为了jbyte*或者是memcpy(dex_data.Begin(), base_address + start, length);到了申请了对应的空间的起始地址,最后又到了下一个函数了

CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);

static jobject CreateCookieFromOatFileManagerResult(JNIEnv* env,std::vector<std::unique_ptr<const DexFile>>& dex_files,const OatFile* oat_file,const std::vector<std::string>& error_msgs) {ClassLinker* linker = Runtime::Current()->GetClassLinker();if (dex_files.empty()) {ScopedObjectAccess soa(env);CHECK(!error_msgs.empty());// The most important message is at the end. So set up nesting by going forward, which will// wrap the existing exception as a cause for the following one.auto it = error_msgs.begin();auto itEnd = error_msgs.end();for ( ; it != itEnd; ++it) {ThrowWrappedIOException("%s", it->c_str());}return nullptr;}jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);if (array == nullptr) {ScopedObjectAccess soa(env);for (auto& dex_file : dex_files) {if (linker->IsDexFileRegistered(soa.Self(), *dex_file)) {dex_file.release();  // NOLINT}}}return array;
}

跟踪dex_files往下走到最后一个函数了ConvertDexFilesToJavaArray(env, oat_file, dex_files);顾名思义,将dexfile文件转化为javaarray去了,这里是最后的函数了,因为这里的返回值是array了,也就是ConvertDexFilesToJavaArray的返回值了

static jlongArray ConvertDexFilesToJavaArray(JNIEnv* env,const OatFile* oat_file,std::vector<std::unique_ptr<const DexFile>>& vec) {// Add one for the oat file.jlongArray long_array = env->NewLongArray(static_cast<jsize>(kDexFileIndexStart + vec.size()));if (env->ExceptionCheck() == JNI_TRUE) {return nullptr;}jboolean is_long_data_copied;jlong* long_data = env->GetLongArrayElements(long_array, &is_long_data_copied);if (env->ExceptionCheck() == JNI_TRUE) {return nullptr;}long_data[kOatFileIndex] = reinterpret_cast64<jlong>(oat_file);for (size_t i = 0; i < vec.size(); ++i) {long_data[kDexFileIndexStart + i] = reinterpret_cast64<jlong>(vec[i].get());}env->ReleaseLongArrayElements(long_array, long_data, 0);if (env->ExceptionCheck() == JNI_TRUE) {return nullptr;}// Now release all the unique_ptrs.for (auto& dex_file : vec) {dex_file.release();  // NOLINT}return long_array;
}

首先不看函数,为什么是返回的long_array 这样的长整型数组??我们为什么会利用mCookie来实现dex脱壳的突破点??想想就可以知道了,长整型用于存储地址。

  long_data[kOatFileIndex] = reinterpret_cast64<jlong>(oat_file);for (size_t i = 0; i < vec.size(); ++i) {long_data[kDexFileIndexStart + i] = reinterpret_cast64<jlong>(vec[i].get());}

我们之前获取的std::vector<std::unique_ptr>& vec 容器vector里面存储的是每个dex文件的起始地址,所以我们返回的长整型的数组其实是每一个dex文件的起始地址,同样还包含了size,这就是为什么mCookie为什么可以作为dex脱壳的脱壳点的原因了

openCommen和DexFile

[DexFile_openInMemoryDexFilesNative]的函数中,略过了

  std::vector<std::unique_ptr<const DexFile>> dex_files =Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(std::move(dex_mem_maps),class_loader,dex_elements,/*out*/ &oat_file,/*out*/ &error_msgs);

这个函数的分析,可以看出来这里是将DexFile文件转为Oat的处理,从这里可以找到另外的脱壳点openCommen和DexFile

跟踪OpenDexFilesFromOat——>看到到dex_mem_maps作为参数进入了OpenDexFilesFromOat_Impl,跟进

std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(std::vector<MemMap>&& dex_mem_maps,jobject class_loader,jobjectArray dex_elements,const OatFile** out_oat_file,std::vector<std::string>* error_msgs) {std::vector<std::unique_ptr<const DexFile>> dex_files = OpenDexFilesFromOat_Impl(std::move(dex_mem_maps),class_loader,dex_elements,out_oat_file,error_msgs);

OpenDexFilesFromOat_Impl:这里删除了各种检测校验的代码

std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat_Impl(std::vector<MemMap>&& dex_mem_maps,jobject class_loader,jobjectArray dex_elements,const OatFile** out_oat_file,std::vector<std::string>* error_msgs) {ScopedTrace trace(__FUNCTION__);std::string error_msg;DCHECK(error_msgs != nullptr);// Extract dex file headers from `dex_mem_maps`.const std::vector<const DexFile::Header*> dex_headers = GetDexFileHeaders(dex_mem_maps);// Determine dex/vdex locations and the combined location checksum.std::string dex_location;std::string vdex_path;// Load dex files. Skip structural dex file verification if vdex was found// and dex checksums matched.std::vector<std::unique_ptr<const DexFile>> dex_files;for (size_t i = 0; i < dex_mem_maps.size(); ++i) {static constexpr bool kVerifyChecksum = true;ArtDexFileLoader dex_file_loader(std::move(dex_mem_maps[i]),DexFileLoader::GetMultiDexLocation(i, dex_location.c_str()));std::unique_ptr<const DexFile> dex_file(dex_file_loader.Open(dex_headers[i]->checksum_,/* verify= */ (vdex_file == nullptr) && Runtime::Current()->IsVerificationEnabled(),kVerifyChecksum,&error_msg));if (dex_file != nullptr) {dex::tracking::RegisterDexFile(dex_file.get());  // Register for tracking.dex_files.push_back(std::move(dex_file));} else {error_msgs->push_back("Failed to open dex files from memory: " + error_msg);}}

可以看到最重要的代码位置了

    ArtDexFileLoader dex_file_loader(std::move(dex_mem_maps[i]),DexFileLoader::GetMultiDexLocation(i, dex_location.c_str()));std::unique_ptr<const DexFile> dex_file(dex_file_loader.Open(dex_headers[i]->checksum_,/* verify= */ (vdex_file == nullptr) && Runtime::Current()->IsVerificationEnabled(),kVerifyChecksum,&error_msg));

这里加载了dex( Load dex files.)我们往dex_file_loader.Open的位置走(这里传入的是四个参数,但是对象类里面没有,是因为继承了父类DexFileLoader的Open())

  std::unique_ptr<const DexFile> Open(uint32_t location_checksum,const OatDexFile* oat_dex_file,bool verify,bool verify_checksum,std::string* error_msg) {std::unique_ptr<const DexFile> dex_file = OpenOne(/*header_offset=*/0, location_checksum, oat_dex_file, verify, verify_checksum, error_msg);// This API returns only singe DEX file, so check there is just single dex in the container.CHECK(dex_file == nullptr || dex_file->IsDexContainerLastEntry()) << location_;return dex_file;}std::unique_ptr<const DexFile> Open(uint32_t location_checksum,bool verify,bool verify_checksum,std::string* error_msg) {return Open(location_checksum,/*oat_dex_file=*/nullptr,verify,verify_checksum,error_msg);}

这里的四个参数的open返回了五个参数的open,到了上面那个open的函数体从而跟进OpenOne()

从这里出现了OpenCommon的一个脱壳点了

std::unique_ptr<const DexFile> DexFileLoader::OpenOne(size_t header_offset,uint32_t location_checksum,const OatDexFile* oat_dex_file,bool verify,bool verify_checksum,std::string* error_msg) {DEXFILE_SCOPED_TRACE(std::string("Open dex file ") + location_);uint32_t magic;if (!InitAndReadMagic(header_offset, &magic, error_msg) || !MapRootContainer(error_msg)) {DCHECK(!error_msg->empty());return {};}DCHECK(root_container_ != nullptr);DCHECK_LE(header_offset, root_container_->Size());std::unique_ptr<const DexFile> dex_file = OpenCommon(root_container_,root_container_->Begin() + header_offset,root_container_->Size() - header_offset,location_,location_checksum,oat_dex_file,verify,verify_checksum,error_msg,nullptr);return dex_file;
}

OpenCommon:

std::unique_ptr<DexFile> DexFileLoader::OpenCommon(std::shared_ptr<DexFileContainer> container,const uint8_t* base,size_t app_compat_size,const std::string& location,std::optional<uint32_t> location_checksum,const OatDexFile* oat_dex_file,bool verify,bool verify_checksum,std::string* error_msg,DexFileLoaderErrorCode* error_code) {if (container == nullptr) {// We should never pass null here, but use reasonable default for app compat anyway.container = std::make_shared<MemoryDexFileContainer>(base, app_compat_size);}CHECK_GE(base, container->Begin());CHECK_LE(base, container->End());const size_t size = container->End() - base;if (error_code != nullptr) {*error_code = DexFileLoaderErrorCode::kDexFileError;}std::unique_ptr<DexFile> dex_file;auto header = reinterpret_cast<const DexFile::Header*>(base);if (size >= sizeof(StandardDexFile::Header) && StandardDexFile::IsMagicValid(base)) {uint32_t checksum = location_checksum.value_or(header->checksum_);dex_file.reset(new StandardDexFile(base, location, checksum, oat_dex_file, container));} else if (size >= sizeof(CompactDexFile::Header) && CompactDexFile::IsMagicValid(base)) {uint32_t checksum = location_checksum.value_or(header->checksum_);dex_file.reset(new CompactDexFile(base, location, checksum, oat_dex_file, container));

这里设置了dex_file(DexFile类)的reset方法,new了两个类

new StandardDexFile(base, location, checksum, oat_dex_file, container)
new CompactDexFile(base, location, checksum, oat_dex_file, container)

这个两个类都去实现了DexFile类对象的初始化

  StandardDexFile(const uint8_t* base,const std::string& location,uint32_t location_checksum,const OatDexFile* oat_dex_file,// Shared since several dex files may be stored in the same logical container.std::shared_ptr<DexFileContainer> container): DexFile(base,location,location_checksum,oat_dex_file,std::move(container),/*is_compact_dex*/ false) {}------------------------------------------------------------------------------------CompactDexFile::CompactDexFile(const uint8_t* base,const std::string& location,uint32_t location_checksum,const OatDexFile* oat_dex_file,std::shared_ptr<DexFileContainer> container): DexFile(base,location,location_checksum,oat_dex_file,std::move(container),/*is_compact_dex=*/true),debug_info_offsets_(DataBegin() + GetHeader().debug_info_offsets_pos_,GetHeader().debug_info_base_,GetHeader().debug_info_offsets_table_offset_) {}

这里就是DexFile的脱壳点了

DexFile::DexFile(const uint8_t* base,const std::string& location,uint32_t location_checksum,const OatDexFile* oat_dex_file,std::shared_ptr<DexFileContainer> container,bool is_compact_dex): begin_(base),data_(GetDataRange(base, container.get())),location_(location),location_checksum_(location_checksum),header_(reinterpret_cast<const Header*>(base)),string_ids_(GetSection<StringId>(&header_->string_ids_off_, container.get())),type_ids_(GetSection<TypeId>(&header_->type_ids_off_, container.get())),field_ids_(GetSection<FieldId>(&header_->field_ids_off_, container.get())),method_ids_(GetSection<MethodId>(&header_->method_ids_off_, container.get())),proto_ids_(GetSection<ProtoId>(&header_->proto_ids_off_, container.get())),class_defs_(GetSection<ClassDef>(&header_->class_defs_off_, container.get())),method_handles_(nullptr),num_method_handles_(0),call_site_ids_(nullptr),num_call_site_ids_(0),hiddenapi_class_data_(nullptr),oat_dex_file_(oat_dex_file),container_(std::move(container)),is_compact_dex_(is_compact_dex),hiddenapi_domain_(hiddenapi::Domain::kApplication) 

这里包含了dex文件的很多信息,全在这个DexFile文件中,这也就是为什么可以作为脱壳点的原因。

youpk的脱壳原理(借用DexCacheData)

youpk:通过ClassLinker的DexCacheData进一步得到DexFile

 ClassLinker* linker = Runtime::Current()->GetClassLinker();

linker在代码中一般由此获取得到

youpk脱壳源码中:

std::list<const DexFile*> Unpacker::getDexFiles() {std::list<const DexFile*> dex_files;Thread* const self = Thread::Current();ClassLinker* class_linker = Runtime::Current()->GetClassLinker();ReaderMutexLock mu(self, *class_linker->DexLock());const std::list<ClassLinker::DexCacheData>& dex_caches = class_linker->GetDexCachesData();for (auto it = dex_caches.begin(); it != dex_caches.end(); ++it) {ClassLinker::DexCacheData data = *it;const DexFile* dex_file = data.dex_file;const std::string& dex_location = dex_file->GetLocation();if (dex_location.rfind("/system/", 0) == 0) {continue;}dex_files.push_back(dex_file);}return dex_files;
}

实现了对于ClassLinker的获取,并且通过ClassLinker的GetDexCachesData()来获取 CachesData结构体

通过获取CachesData结构体的data.dex_file属性来直接获取DexFile类对象,进而实现脱壳

FART 8.0 脱壳原理——>整体加固脱壳(借用artmethod):

FART8.0的脱壳点存在于

static inline JValue Execute   //存在于  runtime下的interpreter.cc的中

这里存在了内联函数的问题:inline 这里细致的说一下

内联函数:内联函数是一种优化手段,旨在通过将函数调用替换为函数体来减少调用开销,提高程序运行速度。当函数被标记为 inline 时,编译器会尝试在调用该函数的位置插入函数体的代码,而不是进行通常的函数调用(跳转到函数地址执行)

具体来说就是希望系统用函数体来直接实现代码,而不是通过函数调用的形式。

之前遇到过一次讨论栈帧的时候,在函数调用过程中并没有去实现栈帧的开辟,而是直接利用了函数体来实现的,当时不知道这是内联

inline int add(int a, int b) {return a + b;
}int main() {int x = 5, y = 10;int result = add(x, y); // 调用 add 函数return 0;
}

内联优化: ——————>

int main() {int x = 5, y = 10;int result = x + y; // 函数体直接替换到调用位置return 0;
}

具体操作 : 脱壳的实现就是获取dexfile ——> begin size 这里通过ShadowFrame——>artMethod——>dexfile

为什么会选取在Execute的位置作为脱壳点?

Method对象有invoke()方法是用于进行函数调用的
这里去了解一下函数调用invoke()方法的源代码,invoke()直接是native函数,按照类名和函数的结构搜索到了Method_invoke函数
——————————————————————————————————————————————————————————————————————
Method_invoke——>InvokeMethod(soa, javaMethod, javaReceiver, javaArgs);
——————————————————————————————————————————————————————————————————————
而在这里实现了java的Method转为ArtMethod方法
ObjPtr<mirror::Executable> executable = soa.Decode<mirror::Executable>(javaMethod);
600  const bool accessible = executable->IsAccessible();
601  ArtMethod* m = executable->GetArtMethod();
——————————————————————————————————————————————————————————————————————
InvokeMethod——> InvokeWithArgArray(soa, m, &arg_array, &result, shorty);
——————————————————————————————————————————————————————————————————————
这里的函数很简单主要还是去调用了ArtMethod 对象的Invoke方法
method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, shorty);
——————————————————————————————————————————————————————————————————————
InvokeWithArgArray————> method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, shorty);
——————————————————————————————————————————————————————————————————————
这里到达了Invoke方法if (UNLIKELY(!runtime->IsStarted() || Dbg::IsForcedInterpreterNeededForCalling(self, this))) {if (IsStatic()) {art::interpreter::EnterInterpreterFromInvoke(self, this, nullptr, args, result, /*stay_in_interpreter*/ true);} 
这里去判断对应的程序是否是进入解释器模式,而在抽取加固的过程中是处于解释器模式的
——————————————————————————————————————————————————————————————————————
method->Invoke————> art::interpreter::EnterInterpreterFromInvoke(self, this, nullptr, args, result, /*stay_in_interpreter*/ true)
——————————————————————————————————————————————————————————————————————
而在这里调用了Execute方法
EnterInterpreterFromInvoke————>Execute(self, accessor, *shadow_frame, JValue(), stay_in_interpreter);
static inline JValue Execute(Thread* self,const DexFile::CodeItem* code_item,ShadowFrame& shadow_frame,JValue result_register,bool stay_in_interpreter = false) REQUIRES_SHARED(Locks::mutator_lock_) {//shadow_frame.GetMethod() return ——> artMethodif(strstr(shadow_frame.GetMethod()->PrettyMethod().c_str(),"<clinit>")){dumpdexfilebyExecute(shadow_frame.GetMethod());}

这里的shadow_frame.GetMethod()得到的是artMethod对象,可以直接去获取dexfile

dumpdexfilebyExecute:

extern "C" void dumpdexfilebyExecute(ArtMethod* artmethod)  REQUIRES_SHARED(Locks::mutator_lock_) {char *dexfilepath=(char*)malloc(sizeof(char)*1000);	if(dexfilepath==nullptr){LOG(ERROR)<< "ArtMethod::dumpdexfilebyArtMethod,methodname:"<<artmethod->PrettyMethod().c_str()<<"malloc 1000 byte failed";return;}int result=0;int fcmdline =-1;char szCmdline[64]= {0};char szProcName[256] = {0};int procid = getpid();//pidsprintf(szCmdline,"/proc/%d/cmdline", procid);///proc/7480/cmdline    cmdline ——>com.chen_chen_chen.myapplicationfcmdline = open(szCmdline, O_RDONLY,0644);if(fcmdline >0){result=read(fcmdline, szProcName,256);  //szProcName = packageNameif(result<0) {LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,open cmdline file error";}close(fcmdline);}if(szProcName[0]){const DexFile* dex_file = artmethod->GetDexFile();const uint8_t* begin_=dex_file->Begin();  // Start of data.size_t size_=dex_file->Size();  // Length of data.memset(dexfilepath,0,1000);int size_int_=(int)size_;memset(dexfilepath,0,1000);sprintf(dexfilepath,"%s","/sdcard/fart");mkdir(dexfilepath,0777);memset(dexfilepath,0,1000);///sdcard/fartsprintf(dexfilepath,"/sdcard/fart/%s",szProcName);//    /sdcard/fart/com.chen_chen_chen.myapplicationmkdir(dexfilepath,0777);memset(dexfilepath,0,1000);sprintf(dexfilepath,"/sdcard/fart/%s/%d_dexfile_execute.dex",szProcName,size_int_);//    /sdcard/fart/com.chen_chen_chen.myapplication/(dex size)_dexfile_execute.dexint dexfilefp=open(dexfilepath,O_RDONLY,0666);if(dexfilefp>0){close(dexfilefp);dexfilefp=0;}else{int fp=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);if(fp>0){result=write(fp,(void*)begin_,size_);if(result<0){LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,open dexfilepath error";}fsync(fp); close(fp);  memset(dexfilepath,0,1000);sprintf(dexfilepath,"/sdcard/fart/%s/%d_classlist_execute.txt",szProcName,size_int_);//     /sdcard/fart/com.chen_chen_chen.myapplication/(dex size)_classlist_execute.txtint classlistfile=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);if(classlistfile>0){for (size_t ii= 0; ii< dex_file->NumClassDefs(); ++ii) {const DexFile::ClassDef& class_def = dex_file->GetClassDef(ii);const char* descriptor = dex_file->GetClassDescriptor(class_def);result=write(classlistfile,(void*)descriptor,strlen(descriptor));if(result<0){LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error";								}const char* temp="\n";result=write(classlistfile,(void*)temp,1);if(result<0){LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error";}}fsync(classlistfile); close(classlistfile); 	}}}}if(dexfilepath!=nullptr){free(dexfilepath);dexfilepath=nullptr;}		
}

FART 8.0 脱壳原理——>抽取加固脱壳:

抽取加固:
1. 运行方法后回填,运行完后不再抽取(这样的结果就是运行完成之后就不会被抽取了,我们直接把运行之后的程序dump下来就可以实现抽取加固的脱壳)————> 延时dump       
2. 运行方法后回填,运行完后又抽取(这样函数被调用之后,回填以后执行完成之后又被抽取加固,这样的结果dump就没有用了,只能去实现主动调用对应抽取的函数,在执行过程中去保存加固的代码)—————> FART、youpk主动调用
3. 抽取加固中函数的加密过程问题(通过在加固的函数中添加解密函数,实现对于原函数内存的加密,实现解密)————>
解决思路:被抽取函数肯定要解密运行,抓住这个时机,dump被抽取的数据

我们这里主要针对的是抽取加固过程中的主动调用的方法

首先是,我们要实现对于这些抽取加固的函数的主动调用需要的是得到这个函数地址

函数地址——>类——>dex文件——>ClassLoader

在函数的执行过程中,利用反射以及NDK开发中,我们可以知道函数的执行过程都是通过classload来加载dex,加载类,最后来加载method,来实现函数的执行的。

所以首先我们要得到classload来能得到method

ClassLoader

BootClassLoader:单例模式,用来加载系统类
(如,String,read,write.....系统函数都是通过BootClassLoader来实现加载的)BaseDexClassLoader:是PathClassLoader、DexClassLoader、InMemoryDexClassLoader的父类,类加载的主要逻辑都是在BaseDexClassLoader完成的PathClassLoader: 是默认使用的类加载器,用于加载app自身的dex(如加载一个自身的app,com.chen_chen_chen.appliciton也是通过pathClassLoader来实现的)DexClassLoader: 用于实现插件化、热修复、dex加固等(这个主要用于开发者就是使用,可以实现parent的设置)本地加载dexInMemoryDexClassLoader: 安卓8.0以后引入,用于内存加载dex

在未加固一个app的执行过程中和抽取加固过程中ClassLoader的关系

这里首先是mClassLoader的常识认知:PathClassLoader加载dex以后,会记录在LoadedApk的mClassLoader属性中,默认使用这个ClassLoader去寻找类。也就是说mClassLoader是指向的PathClassLoader(来加载app的dex的类加载器)

未加固的app:
BootClassLoader  加载系统类
pathClassLoader 加载app的dex    <——————  mClassLoader    未加固的app  PathClassLoader直接加载dex抽取加固的app:BootClassLoader  加载系统类
pathClassLoader  壳的dex(壳dex执行完成之后利用自定义的classload加载) <——————  mClassLoader 加固的app PathClassLoader直接加载壳的dex
DexClassLoader  app的dex  <————壳的dex执行完成之后会索引到这里来

这里插入双亲委派机制的内容:

双亲委派机制的工作原理
1.1 如果一个类加载器收到了类加载请求,会先把这个请求委托给父类的加载器去执行
1.2 如果父类加载器还存在其父类加载器,则进一步向上委托,依次类推,最终到达顶层的启动类加载器
1.3 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载

大概的意思是,类加载器加载过程中会先让最顶层的父类去加载,加载不了才往子类去加载。

从上面在未加固一个app的执行过程中和抽取加固过程中ClassLoader的关系中我们就可以发现问题

加固的修正

BootClassLoader  加载系统类
pathClassLoader  壳的dex(壳dex执行完成之后利用自定义的classload加载) <——————  mClassLoader 加固的app PathClassLoader直接加载壳的dex
DexClassLoader  app的dex  <————壳的dex执行完成之后会索引到这里来

通过加固的app,是不能通过mClassLoader来实现找到app自身dex的,最多只能找到Boot和Path的ClassLoader

所以会进行app的修正,通过替换或者是插入的形式进行修正

插入ClassLoaderBootClassLoaderDexClassLoaderPathClassLoader  mClassLoader
替换ClassLoaderBootClassLoaderPathClassLoaderDexClassLoader mClassLoader

FART 8.0 实现ClassLoader的获取

我们需要做的事情就是找到每一个ClassLoader,获取每一个ClassLoader加载的类,函数。

通过双亲委派机制可以知道,我们只需要知道一个ClassLoader就可以知道所有的ClassLoader

Fart函数代码:

    public static void fart() {ClassLoader appClassloader = getClassloader();//获取一个classloaderClassLoader parentClassloader=appClassloader.getParent();//获取parentif(appClassloader.toString().indexOf("java.lang.BootClassLoader")==-1){fartwithClassloader(appClassloader);//只要不是BootClassLoader就去主要调用然后dump}while(parentClassloader!=null){if(parentClassloader.toString().indexOf("java.lang.BootClassLoader")==-1){fartwithClassloader(parentClassloader);}parentClassloader=parentClassloader.getParent();}}

获取一个classloader

    public static ClassLoader getClassloader() {ClassLoader resultClassloader = null;Object currentActivityThread = invokeStaticMethod("android.app.ActivityThread", "currentActivityThread",new Class[]{}, new Object[]{});//获取currentActivityThread类对象Object mBoundApplication = getFieldOjbect("android.app.ActivityThread", currentActivityThread,"mBoundApplication");//获取mBoundApplication类对象Object loadedApkInfo = getFieldOjbect("android.app.ActivityThread$AppBindData",mBoundApplication, "info");//通过mBoundApplication——>获取字段infoApplication mApplication = (Application) getFieldOjbect("android.app.LoadedApk", loadedApkInfo, "mApplication");//获取mApplication类对象resultClassloader = mApplication.getClassLoader();return resultClassloader;}//最终获取一个classloader

fartwithClassloader函数实现 加载classloader

    public static void fartwithClassloader(ClassLoader appClassloader) {List<Object> dexFilesArray = new ArrayList<Object>();Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");//通过当前的classloader,去获取父类BaseDexClassLoader的pathList Object[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");//通过获取pathList类来得到的Elements[]  这里面每一个元素都是dexfileField dexFile_fileField = null;try {dexFile_fileField = (Field) getClassField(appClassloader, "dalvik.system.DexPathList$Element", "dexFile");} catch (Exception e) {e.printStackTrace();} catch (Error e) {e.printStackTrace();}Class DexFileClazz = null;try {DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");//加载Dexfile类} catch (Exception e) {e.printStackTrace();} catch (Error e) {e.printStackTrace();}Method getClassNameList_method = null;Method defineClass_method = null;Method dumpDexFile_method = null;Method dumpMethodCode_method = null;
//获取每一个dexfile中对应的属性、函数的字段位置,for (Method field : DexFileClazz.getDeclaredMethods()) {if (field.getName().equals("getClassNameList")) {getClassNameList_method = field;getClassNameList_method.setAccessible(true);}if (field.getName().equals("defineClassNative")) {defineClass_method = field;defineClass_method.setAccessible(true);}if (field.getName().equals("dumpDexFile")) {dumpDexFile_method = field;dumpDexFile_method.setAccessible(true);}if (field.getName().equals("dumpMethodCode")) {dumpMethodCode_method = field;dumpMethodCode_method.setAccessible(true);}}Field mCookiefield = getClassField(appClassloader, "dalvik.system.DexFile", "mCookie");-----------------------------实现对应的dexfile的获取--------------------------------------------------------Log.v("ActivityThread->methods", "dalvik.system.DexPathList.ElementsArray.length:" + ElementsArray.length);for (int j = 0; j < ElementsArray.length; j++) {Object element = ElementsArray[j];Object dexfile = null;try {dexfile = (Object) dexFile_fileField.get(element);//通过dexFile_fileField字段来获取element中的dexFile_fileField的值,其实也就是dexfile的值} catch (Exception e) {e.printStackTrace();} catch (Error e) {e.printStackTrace();}if (dexfile == null) {Log.e("ActivityThread", "dexfile is null");continue;}if (dexfile != null) {dexFilesArray.add(dexfile);Object mcookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mCookie");if (mcookie == null) {Object mInternalCookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mInternalCookie");if(mInternalCookie!=null){mcookie=mInternalCookie;}else{Log.v("ActivityThread->err", "get mInternalCookie is null");continue;}}String[] classnames = null;try {classnames = (String[]) getClassNameList_method.invoke(dexfile, mcookie);} catch (Exception e) {e.printStackTrace();continue;} catch (Error e) {e.printStackTrace();continue;}if (classnames != null) {for (String eachclassname : classnames) {loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);//这里实现加载class和调用了}}}}return;}

floadClassAndInvoke函数实现 真正的classload加载类和调用函数

    public static void loadClassAndInvoke(ClassLoader appClassloader, String eachclassname, Method dumpMethodCode_method) {Class resultclass = null;Log.i("ActivityThread", "go into loadClassAndInvoke->" + "classname:" + eachclassname);try {resultclass = appClassloader.loadClass(eachclassname);//加载 class.loadClass(classname)加载类} catch (Exception e) {e.printStackTrace();return;} catch (Error e) {e.printStackTrace();return;}if (resultclass != null) {try {Constructor<?> cons[] = resultclass.getDeclaredConstructors();//得到类中的构造函数for (Constructor<?> constructor : cons) {if (dumpMethodCode_method != null) {try {dumpMethodCode_method.invoke(null, constructor);//这里去实现构造器调用} catch (Exception e) {e.printStackTrace();continue;} catch (Error e) {e.printStackTrace();continue;}} else {Log.e("ActivityThread", "dumpMethodCode_method is null ");}}} catch (Exception e) {e.printStackTrace();} catch (Error e) {e.printStackTrace();}try {Method[] methods = resultclass.getDeclaredMethods();//所有函数的调用if (methods != null) {for (Method m : methods) {if (dumpMethodCode_method != null) {try {dumpMethodCode_method.invoke(null, m);//实现加载} catch (Exception e) {e.printStackTrace();continue;} catch (Error e) {e.printStackTrace();continue;}} else {Log.e("ActivityThread", "dumpMethodCode_method is null ");}}}} catch (Exception e) {e.printStackTrace();} catch (Error e) {e.printStackTrace();}}}

而dumpMethodCode是定义在了DexFile.java的位置的native函数

    private static native void dumpMethodCode(Object m);

dumpMethodCode在dalvik_system_DexFile.cc中的DexFile_dumpMethodCode()函数

static void DexFile_dumpMethodCode(JNIEnv* env, jclass,jobject method) {if(method!=nullptr){ArtMethod* proxy_method = jobject2ArtMethod(env, method);myfartInvoke(proxy_method);}	 return;
}//这里把java method 转为了ArtMethod 然后进入了myfartInvoke

在art_method.cc中实现了myfartInvoke()

extern "C" void myfartInvoke(ArtMethod* artmethod)  REQUIRES_SHARED(Locks::mutator_lock_) {JValue *result=nullptr;Thread *self=nullptr;uint32_t temp=6;uint32_t* args=&temp;uint32_t args_size=6;artmethod->Invoke(self, args, args_size, result, "fart");
}//这里实现了调用Invoke()方法的参数初始化,不过肯定不会是正确的参数
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,const char* shorty) {if (self== nullptr) {dumpArtMethod(this);return;}.........................//这里是去修改了一下ArtMethod::Invoke的源码的,加入了这一句代码,也就是当Thread传入null的时候才会去走到dumpArtMethod函数去,实现我们对应函数的主动调用

dumpArtMethod:代码实现

extern "C" void dumpArtMethod(ArtMethod* artmethod)  REQUIRES_SHARED(Locks::mutator_lock_) {char *dexfilepath=(char*)malloc(sizeof(char)*1000);	if(dexfilepath==nullptr){LOG(ERROR) << "ArtMethod::dumpArtMethodinvoked,methodname:"<<artmethod->PrettyMethod().c_str()<<"malloc 1000 byte failed";return;}int result=0;int fcmdline =-1;char szCmdline[64]= {0};char szProcName[256] = {0};int procid = getpid();sprintf(szCmdline,"/proc/%d/cmdline", procid);//读取对应app包名 ————>com.chen_chen_chen.applictionfcmdline = open(szCmdline, O_RDONLY,0644);//写入if(fcmdline >0){result=read(fcmdline, szProcName,256);if(result<0){LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,open cmdline file file error";									}close(fcmdline);}if(szProcName[0]){const DexFile* dex_file = artmethod->GetDexFile();const uint8_t* begin_=dex_file->Begin();  // Start of data.size_t size_=dex_file->Size();  // Length of data.memset(dexfilepath,0,1000);int size_int_=(int)size_;memset(dexfilepath,0,1000);sprintf(dexfilepath,"%s","/sdcard/fart");mkdir(dexfilepath,0777);memset(dexfilepath,0,1000);sprintf(dexfilepath,"/sdcard/fart/%s",szProcName);//     /sdcard/fart/com.chen_chen_chen.applictionmkdir(dexfilepath,0777);memset(dexfilepath,0,1000);sprintf(dexfilepath,"/sdcard/fart/%s/%d_dexfile.dex",szProcName,size_int_);//    /sdcard/fart/com.chen_chen_chen.appliction/(dex_size)_dexfile.dexint dexfilefp=open(dexfilepath,O_RDONLY,0666);if(dexfilefp>0){close(dexfilefp);dexfilefp=0;}else{int fp=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);if(fp>0){result=write(fp,(void*)begin_,size_);//写入dex   begin——>begin+size_if(result<0){LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,open dexfilepath file error";}fsync(fp); close(fp);  memset(dexfilepath,0,1000);sprintf(dexfilepath,"/sdcard/fart/%s/%d_classlist.txt",szProcName,size_int_);int classlistfile=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);if(classlistfile>0){for (size_t ii= 0; ii< dex_file->NumClassDefs(); ++ii) {const DexFile::ClassDef& class_def = dex_file->GetClassDef(ii);const char* descriptor = dex_file->GetClassDescriptor(class_def);result=write(classlistfile,(void*)descriptor,strlen(descriptor));//这里写入对了类的函数if(result<0){LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error";}const char* temp="\n";result=write(classlistfile,(void*)temp,1);if(result<0){LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error";}}fsync(classlistfile); close(classlistfile); }}}//--------------------------------------------------------------------------这里以上是整体加固的脱壳
//---------------------------------------------------------------------------以下是抽取加固的主动调用的脱壳                const DexFile::CodeItem* code_item = artmethod->GetCodeItem();if (LIKELY(code_item != nullptr)) {int code_item_len = 0;uint8_t *item=(uint8_t *) code_item;if (code_item->tries_size_>0) {const uint8_t *handler_data = (const uint8_t *)(DexFile::GetTryItems(*code_item, code_item->tries_size_));uint8_t * tail = codeitem_end(&handler_data);code_item_len = (int)(tail - item);}else{code_item_len = 16+code_item->insns_size_in_code_units_*2;}  //获取CodeItem的字节大小memset(dexfilepath,0,1000);int size_int=(int)dex_file->Size();  uint32_t method_idx=artmethod->GetDexMethodIndexUnchecked();sprintf(dexfilepath,"/sdcard/fart/%s/%d_ins_%d.bin",szProcName,size_int,(int)gettidv1());//生成对应的文件int fp2=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);if(fp2>0){lseek(fp2,0,SEEK_END);memset(dexfilepath,0,1000);int offset=(int)(item - begin_);sprintf(dexfilepath,"{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:",artmethod->PrettyMethod().c_str(),method_idx,offset,code_item_len);//写入对应的函数CodeItem的信息int contentlength=0;while(dexfilepath[contentlength]!=0) contentlength++;result=write(fp2,(void*)dexfilepath,contentlength);if(result<0){LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write ins file error";}long outlen=0;char* base64result=base64_encode((char*)item,(long)code_item_len,&outlen);result=write(fp2,base64result,outlen); //base64编码写入 CodeItem的内容if(result<0){LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write ins file error";}result=write(fp2,"};",2);if(result<0){LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write ins file error";}fsync(fp2); close(fp2);if(base64result!=nullptr){free(base64result);base64result=nullptr;}}}}if(dexfilepath!=nullptr){free(dexfilepath);dexfilepath=nullptr;}}

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

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

相关文章

Meissonic 文生图模型:小参数,超轻量,本地部署推理教程

阿里巴巴等联合推出的 Meissonic 文生图模型,仅 1B 参数,能在普通电脑及未来无线端运行推理。本文将详细展示其在笔记本上的本地部署教程,带你领略 Meissonic 的独特魅力与强大功能……最近,阿里巴巴集团、Skywork AI 携手香港科技大学及其广州校区、浙江大学、加州大学伯克…

大模型应用开发基础 : 语言模型的重要里程碑

本文快速复习了基于统计的语言模型的基本玩法,然后介绍了语言模型发展过程中的三个重要里程碑:神经概率语言模型、词向量模型 和 预训练模型。基于这几个里程碑的发展,开启了NLP处理的新纪元,我们可以基于经过预训练的大模型进行微调,进而处理我们自己业务领域的实际问题。…

第七章 事务

事务是应用程序将多个读写操作组合成一个逻辑单元的一种方式 从概念上讲,事务中的所有读写操作被视作单个操作来执行:整个事务要么成功(提交(commit))要么失败(中止(abort),回滚(rollback))。如果失败,应用程序可以安全地重试。 并不是所有的应用都需要事务,有时…

AI摧毁很多岗位,也会诞生很多新的岗位,如小乔陪玩

未来5年随着AI革命带来的全球生产效率的提升,将会继续摧毁大量的就业岗位,那么新的产业形态必须来临,那就是情绪价值电商带来的就业,一种新型服务业,国家也会全面支持 情绪价值的提供,线下玩伴,是满足人类基因上的陪伴基因应运而生的项目.真的是一个不错的赛道,对于像我们…

甘特图

甘特图(Gantt chart)又称为横道图、条状图(Bar chart),通过条状图来显示项目、进度和其他时间相关的系统进展的内在关系随着时间进展的情况。

江苏科技大学大二《数据结构》课内实验报告模板答案

江苏科技大学 《数据结构》实验报告 (2024/2025学年第1学期) 学生姓名: 学生学号: 院 系: 计算机学院 专 业: 考核得分: 2024 年 12 月 实验一 线性表的操作 一、实验目的 掌握线性表的基本操作在存储结构上的实现,其中以单链表的操作作为重点。 …

20222315 2024-2025-1 《网络与系统攻防技术》实验六实验报告

1、实验内容 本实践目标是掌握metasploit的用法。 指导书参考Rapid7官网的指导教程。 https://docs.rapid7.com/metasploit/metasploitable-2-exploitability-guide/ 下载官方靶机Metasploitable2,完成下面实验内容。 (1)前期渗透 ①主机发现(可用Aux中的arp_sweep,search…

20222315 2024-2025-1 《网络与系统攻防实验六实验》实验六实验报告

1、实验内容 本实践目标是掌握metasploit的用法。 指导书参考Rapid7官网的指导教程。 https://docs.rapid7.com/metasploit/metasploitable-2-exploitability-guide/ 下载官方靶机Metasploitable2,完成下面实验内容。 (1)前期渗透 ①主机发现(可用Aux中的arp_sweep,search…

《Django 5 By Example》阅读笔记:p165-p210

《Django 5 By Example》学习第6天,p165-p210总结,总计46页。 一、技术总结 1.bookmarks项目 (1)登录认证 作者这里使用的是Django自带的auth。 (2)上传头像 图片处理,使用Pillow。 (3)扩展user 扩展user模型与自带的user使用外键进行关联,命名为profile。 二、英语总结(生…

记录---nextTick用过吗?讲一讲实现思路吧

🧑‍💻 写在开头 点赞 + 收藏 === 学会🤣🤣🤣源码实现思路(面试高分回答) 📖 面试官问我 Vue 的 nextTick 原理是怎么实现的,我这样回答: 在调用 this.$nextTick(cb) 之前:存在一个 callbacks 数组,用于存放所有的 cb 回调函数。 存在一个 flushCallbacks 函…

AI|经常崩溃的问题解决

Adobe Illustrator Crashes 网络上大部分说法都是重装AI,兼容性问题,管理员权限或者是版本不对,经测试均无效,甚至出现重装系统这种离谱的办法,正确的解决办法是把首选项的性能里的GPU取消勾选,或者再把电脑的虚拟内存扩大即可。Step1:打开首选项 Step 2:取消勾选GPU性…