其他
拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点
现有脱壳方法原理分析
284 * Parse an optimized or unoptimized .dex file sitting in memory. This is
285 * called after the byte-ordering and structure alignment has been fixed up.
286 *
287 * On success, return a newly-allocated DexFile.
288 */
289DexFile* dexFileParse(const u1* data, size_t length, int flags)
290{
291 DexFile* pDexFile = NULL;
292 const DexHeader* pHeader;
293 const u1* magic;
294 int result = -1;
295
296 if (length < sizeof(DexHeader)) {
297 ALOGE("too short to be a valid .dex");
298 goto bail; /* bad file format */
299 }
300
301 pDexFile = (DexFile*) malloc(sizeof(DexFile));
302 if (pDexFile == NULL)
303 goto bail; /* alloc failure */
304 memset(pDexFile, 0, sizeof(DexFile));
305
306 /*
307 * Peel off the optimized header.
308 */
309 if (memcmp(data, DEX_OPT_MAGIC, 4) == 0) {
310 magic = data;
311 if (memcmp(magic+4, DEX_OPT_MAGIC_VERS, 4) != 0) {
312 ALOGE("bad opt version (0x%02x %02x %02x %02x)",
313 magic[4], magic[5], magic[6], magic[7]);
314 goto bail;
315 }
316
317 pDexFile->pOptHeader = (const DexOptHeader*) data;
318 ALOGV("Good opt header, DEX offset is %d, flags=0x%02x",
319 pDexFile->pOptHeader->dexOffset, pDexFile->pOptHeader->flags);
320
321 /* parse the optimized dex file tables */
322 if (!dexParseOptData(data, length, pDexFile))
323 goto bail;
324
325 /* ignore the opt header and appended data from here on out */
326 data += pDexFile->pOptHeader->dexOffset;
327 length -= pDexFile->pOptHeader->dexOffset;
328 if (pDexFile->pOptHeader->dexLength > length) {
329 ALOGE("File truncated? stored len=%d, rem len=%d",
330 pDexFile->pOptHeader->dexLength, (int) length);
331 goto bail;
332 }
333 length = pDexFile->pOptHeader->dexLength;
334 }
335
336 dexFileSetupBasicPointers(pDexFile, data);
337 pHeader = pDexFile->pHeader;
338
339 if (!dexHasValidMagic(pHeader)) {
340 goto bail;
341 }
342
343 /*
344 * Verify the checksum(s). This is reasonably quick, but does require
345 * touching every byte in the DEX file. The base checksum changes after
346 * byte-swapping and DEX optimization.
347 */
348 if (flags & kDexParseVerifyChecksum) {
349 u4 adler = dexComputeChecksum(pHeader);
350 if (adler != pHeader->checksum) {
351 ALOGE("ERROR: bad checksum (%08x vs %08x)",
352 adler, pHeader->checksum);
353 if (!(flags & kDexParseContinueOnError))
354 goto bail;
355 } else {
356 ALOGV("+++ adler32 checksum (%08x) verified", adler);
357 }
358
359 const DexOptHeader* pOptHeader = pDexFile->pOptHeader;
360 if (pOptHeader != NULL) {
361 adler = dexComputeOptChecksum(pOptHeader);
362 if (adler != pOptHeader->checksum) {
363 ALOGE("ERROR: bad opt checksum (%08x vs %08x)",
364 adler, pOptHeader->checksum);
365 if (!(flags & kDexParseContinueOnError))
366 goto bail;
367 } else {
368 ALOGV("+++ adler32 opt checksum (%08x) verified", adler);
369 }
370 }
371 }
372
373 /*
374 * Verify the SHA-1 digest. (Normally we don't want to do this --
375 * the digest is used to uniquely identify the original DEX file, and
376 * can't be computed for verification after the DEX is byte-swapped
377 * and optimized.)
378 */
379 if (kVerifySignature) {
380 unsigned char sha1Digest[kSHA1DigestLen];
381 const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum) +
382 kSHA1DigestLen;
383
384 dexComputeSHA1Digest(data + nonSum, length - nonSum, sha1Digest);
385 if (memcmp(sha1Digest, pHeader->signature, kSHA1DigestLen) != 0) {
386 char tmpBuf1[kSHA1DigestOutputLen];
387 char tmpBuf2[kSHA1DigestOutputLen];
388 ALOGE("ERROR: bad SHA1 digest (%s vs %s)",
389 dexSHA1DigestToStr(sha1Digest, tmpBuf1),
390 dexSHA1DigestToStr(pHeader->signature, tmpBuf2));
391 if (!(flags & kDexParseContinueOnError))
392 goto bail;
393 } else {
394 ALOGV("+++ sha1 digest verified");
395 }
396 }
397
398 if (pHeader->fileSize != length) {
399 ALOGE("ERROR: stored file size (%d) != expected (%d)",
400 (int) pHeader->fileSize, (int) length);
401 if (!(flags & kDexParseContinueOnError))
402 goto bail;
403 }
404
405 if (pHeader->classDefsSize == 0) {
406 ALOGE("ERROR: DEX file has no classes in it, failing");
407 goto bail;
408 }
409
410 /*
411 * Success!
412 */
413 result = 0;
414
415bail:
416 if (result != 0 && pDexFile != NULL) {
417 dexFileFree(pDexFile);
418 pDexFile = NULL;
419 }
420 return pDexFile;
421}
147{
148 DvmDex* pDvmDex;
149 DexFile* pDexFile;
150 int parseFlags = kDexParseDefault;
151 int result = -1;
152
153 /* -- file is incomplete, new checksum has not yet been calculated
154 if (gDvm.verifyDexChecksum)
155 parseFlags |= kDexParseVerifyChecksum;
156 */
157
158 pDexFile = dexFileParse((u1*)addr, len, parseFlags);
159 if (pDexFile == NULL) {
160 ALOGE("DEX parse failed");
161 goto bail;
162 }
163 pDvmDex = allocateAuxStructures(pDexFile);
164 if (pDvmDex == NULL) {
165 dexFileFree(pDexFile);
166 goto bail;
167 }
168
169 pDvmDex->isMappedReadOnly = false;
170 *ppDvmDex = pDvmDex;
171 result = 0;
172
173bail:
174 return result;
175}
273 uint32_t location_checksum,
274 MemMap* mem_map,
275 std::string* error_msg) {
276 return OpenMemory(mem_map->Begin(),
277 mem_map->Size(),
278 location,
279 location_checksum,
280 mem_map,
281 nullptr,
282 error_msg);
283}
396 const std::string& location,
397 uint32_t location_checksum,
398 MemMap* mem_map,
399 const OatDexFile* oat_dex_file)
400 : begin_(base),
401 size_(size),
402 location_(location),
403 location_checksum_(location_checksum),
404 mem_map_(mem_map),
405 header_(reinterpret_cast<const Header*>(base)),
406 string_ids_(reinterpret_cast<const StringId*>(base + header_->string_ids_off_)),
407 type_ids_(reinterpret_cast<const TypeId*>(base + header_->type_ids_off_)),
408 field_ids_(reinterpret_cast<const FieldId*>(base + header_->field_ids_off_)),
409 method_ids_(reinterpret_cast<const MethodId*>(base + header_->method_ids_off_)),
410 proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)),
411 class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)),
412 find_class_def_misses_(0),
413 class_def_index_(nullptr),
414 oat_dex_file_(oat_dex_file) {
415 CHECK(begin_ != nullptr) << GetLocation();
416 CHECK_GT(size_, 0U) << GetLocation();
417}
+ if (location.find("/data/data/") != std::string::npos) {
+ LOG(WARNING) << "Dex File: OAT file unpacking launched";
+ std::ofstream dst(location + "__unpacked_oat", std::ios::binary);
+ dst.write(reinterpret_cast<const char*>(base), size);
+ dst.close();
+ } else {
+ LOG(WARNING) << "Dex File: OAT file unpacking not launched";
+ }
60 CHECK(magic != nullptr);
61 ScopedFd fd(open(filename, O_RDONLY, 0));
62 if (fd.get() == -1) {
63 *error_msg = StringPrintf("Unable to open '%s' : %s", filename, strerror(errno));
64 return -1;
65 }
66 int n = TEMP_FAILURE_RETRY(read(fd.get(), magic, sizeof(*magic)));
67 if (n != sizeof(*magic)) {
68 *error_msg = StringPrintf("Failed to find magic in '%s'", filename);
69 return -1;
70 }
71 if (lseek(fd.get(), 0, SEEK_SET) != 0) {
72 *error_msg = StringPrintf("Failed to seek to beginning of file '%s' : %s", filename,
73 strerror(errno));
74 return -1;
75 }
76 return fd.release();
77}
+ // let's limit processing file list
+
+ LOG(WARNING) << "File_magic: Filename: "<<filename;
+ if (strstr(filename, "/data/data") != NULL) {
+ LOG(WARNING) << "File_magic: DEX file unpacking launched";
+ char* fn_out = new char[PATH_MAX];
+ strcpy(fn_out, filename);
+ strcat(fn_out, "__unpacked_dex");
+
+ int fd_out = open(fn_out, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ if (!fstat(fd.get(), &st)) {
+ char* addr = (char*)mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd.get(), 0);
+ int ret=write(fd_out, addr, st.st_size);
+ ret+=1;
+ munmap(addr, st.st_size);
+ }
+
+ close(fd_out);
+ delete[] fn_out;
+ } else {
+ LOG(WARNING) << "File_magic: DEX file unpacking not launched";
+ }
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Class cls = (Class) param.getResult();
if (cls == null) {
//XposedBridge.log("cls == null");
return;
}
String name = cls.getName();
XposedBridge.log("当前类名:" + name);
byte[] bArr = (byte[]) Dex_getBytes.invoke(getDex.invoke(cls, new Object[0]), new Object[0]);
if (bArr == null) {
XposedBridge.log("数据为空:返回");
return;
}
XposedBridge.log("开始写数据");
String dex_path = "/data/data/" + packagename + "/" + packagename + "_" + bArr.length + ".dex";
XposedBridge.log(dex_path);
File file = new File(dex_path);
if (file.exists()) return;
writeByte(bArr, file.getAbsolutePath());
}
} );
}
public void initRefect() {
try {
Dex = Class.forName("com.android.dex.Dex");
Dex_getBytes = Dex.getDeclaredMethod("getBytes", new Class[0]);
getDex = Class.forName("java.lang.Class").getDeclaredMethod("getDex", new Class[0]);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public void writeByte(byte[] bArr, String str) {
try {
OutputStream outputStream = new FileOutputStream(str);
outputStream.write(bArr);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
总结Android脱壳的本质
501 /* directly-mapped "opt" header */
502 const DexOptHeader* pOptHeader;
503
504 /* pointers to directly-mapped structs and arrays in base DEX */
505 const DexHeader* pHeader;
506 const DexStringId* pStringIds;
507 const DexTypeId* pTypeIds;
508 const DexFieldId* pFieldIds;
509 const DexMethodId* pMethodIds;
510 const DexProtoId* pProtoIds;
511 const DexClassDef* pClassDefs;
512 const DexLink* pLinkData;
513
514 /*
515 * These are mapped out of the "auxillary" section, and may not be
516 * included in the file.
517 */
518 const DexClassLookup* pClassLookup;
519 const void* pRegisterMapPool; // RegisterMapClassPool
520
521 /* points to start of DEX file data */
522 const u1* baseAddr;
523
524 /* track memory overhead for auxillary structures */
525 int overhead;
526
527 /* additional app-specific data structures associated with the DEX */
528 //void* auxData;
529};
530
55 public:
56 static const uint8_t kDexMagic[];
57 static const uint8_t kDexMagicVersion[];
58 static constexpr size_t kSha1DigestSize = 20;
59 static constexpr uint32_t kDexEndianConstant = 0x12345678;
61 // name of the DexFile entry within a zip archive
62 static const char* kClassesDex;
64 // The value of an invalid index.
65 static const uint32_t kDexNoIndex = 0xFFFFFFFF;
67 // The value of an invalid index.
68 static const uint16_t kDexNoIndex16 = 0xFFFF;
70 // The separator charactor in MultiDex locations.
71 static constexpr char kMultiDexSeparator = ':';
73 // A string version of the previous. This is a define so that we can merge string literals in the
74 // preprocessor.
75
78 struct Header {
79 uint8_t magic_[8];
80 uint32_t checksum_; // See also location_checksum_
81 uint8_t signature_[kSha1DigestSize];
82 uint32_t file_size_; // size of entire file
83 uint32_t header_size_; // offset to start of next section
84 uint32_t endian_tag_;
85 uint32_t link_size_; // unused
86 uint32_t link_off_; // unused
87 uint32_t map_off_; // unused
88 uint32_t string_ids_size_; // number of StringIds
89 uint32_t string_ids_off_; // file offset of StringIds array
90 uint32_t type_ids_size_; // number of TypeIds, we don't support more than 65535
91 uint32_t type_ids_off_; // file offset of TypeIds array
92 uint32_t proto_ids_size_; // number of ProtoIds, we don't support more than 65535
93 uint32_t proto_ids_off_; // file offset of ProtoIds array
94 uint32_t field_ids_size_; // number of FieldIds
95 uint32_t field_ids_off_; // file offset of FieldIds array
96 uint32_t method_ids_size_; // number of MethodIds
97 uint32_t method_ids_off_; // file offset of MethodIds array
98 uint32_t class_defs_size_; // number of ClassDefs
99 uint32_t class_defs_off_; // file offset of ClassDef array
100 uint32_t data_size_; // unused
101 uint32_t data_off_; // unused
102
103 private:
104 DISALLOW_COPY_AND_ASSIGN(Header);
105 };
106
107 /
........
直接查找法
>>>> 间接查找法
间接查找法
原理分析
2256 uint32_t access_flags, InvokeType invoke_type,
2257 uint16_t class_def_idx, uint32_t method_idx,
2258 jobject class_loader, const DexFile& dex_file,
2259 DexToDexCompilationLevel dex_to_dex_compilation_level,
2260 bool compilation_enabled) {
2261 CompiledMethod* compiled_method = nullptr;
2262 uint64_t start_ns = kTimeCompileMethod ? NanoTime() : 0;
2263 MethodReference method_ref(&dex_file, method_idx);
2264
2265 if ((access_flags & kAccNative) != 0) {
2266 // Are we interpreting only and have support for generic JNI down calls?
2267 if (!compiler_options_->IsCompilationEnabled() &&
2268 InstructionSetHasGenericJniStub(instruction_set_)) {
2269 // Leaving this empty will trigger the generic JNI version
2270 } else {
2271 compiled_method = compiler_->JniCompile(access_flags, method_idx, dex_file);
2272 CHECK(compiled_method != nullptr);
2273 }
2274 } else if ((access_flags & kAccAbstract) != 0) {
2275 // Abstract methods don't have code.
2276 } else {
2277 bool has_verified_method = verification_results_->GetVerifiedMethod(method_ref) != nullptr;
2278 bool compile = compilation_enabled &&
2279 // Basic checks, e.g., not <clinit>.
2280 verification_results_->IsCandidateForCompilation(method_ref, access_flags) &&
2281 // Did not fail to create VerifiedMethod metadata.
2282 has_verified_method &&
2283 // Is eligable for compilation by methods-to-compile filter.
2284 IsMethodToCompile(method_ref);
2285 if (compile) {
2286 // NOTE: if compiler declines to compile this method, it will return null.
2287 compiled_method = compiler_->Compile(code_item, access_flags, invoke_type, class_def_idx,
2288 method_idx, class_loader, dex_file);
2289 }
2290 if (compiled_method == nullptr && dex_to_dex_compilation_level != kDontDexToDexCompile) {
2291 // TODO: add a command-line option to disable DEX-to-DEX compilation ?
2292 // Do not optimize if a VerifiedMethod is missing. SafeCast elision, for example, relies on
2293 // it.
2294 (*dex_to_dex_compiler_)(*this, code_item, access_flags,
2295 invoke_type, class_def_idx,
2296 method_idx, class_loader, dex_file,
2297 has_verified_method ? dex_to_dex_compilation_level : kRequired);
2298 }
2299 }
2300 if (kTimeCompileMethod) {
2301 uint64_t duration_ns = NanoTime() - start_ns;
2302 if (duration_ns > MsToNs(compiler_->GetMaximumCompilationTimeBeforeWarning())) {
2303 LOG(WARNING) << "Compilation of " << PrettyMethod(method_idx, dex_file)
2304 << " took " << PrettyDuration(duration_ns);
2305 }
2306 }
2307
2308 if (compiled_method != nullptr) {
2309 // Count non-relative linker patches.
2310 size_t non_relative_linker_patch_count = 0u;
2311 for (const LinkerPatch& patch : compiled_method->GetPatches()) {
2312 if (!patch.IsPcRelative()) {
2313 ++non_relative_linker_patch_count;
2314 }
2315 }
2316 bool compile_pic = GetCompilerOptions().GetCompilePic(); // Off by default
2317 // When compiling with PIC, there should be zero non-relative linker patches
2318 CHECK(!compile_pic || non_relative_linker_patch_count == 0u);
2319
2320 DCHECK(GetCompiledMethod(method_ref) == nullptr) << PrettyMethod(method_idx, dex_file);
2321 {
2322 MutexLock mu(self, compiled_methods_lock_);
2323 compiled_methods_.Put(method_ref, compiled_method);
2324 non_relative_linker_patch_count_ += non_relative_linker_patch_count;
2325 }
2326 DCHECK(GetCompiledMethod(method_ref) != nullptr) << PrettyMethod(method_idx, dex_file);
2327 }
2328
2329 // Done compiling, delete the verified method to reduce native memory usage. Do not delete in
2330 // optimizing compiler, which may need the verified method again for inlining.
2331 if (compiler_kind_ != Compiler::kOptimizing) {
2332 verification_results_->RemoveVerifiedMethod(method_ref);
2333 }
2334
2335 if (self->IsExceptionPending()) {
2336 ScopedObjectAccess soa(self);
2337 LOG(FATAL) << "Unexpected exception compiling: " << PrettyMethod(method_idx, dex_file) << "\n"
2338 << self->GetException()->Dump();
2339 }
2340}
实现代码
JValue result_register)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item,
ShadowFrame& shadow_frame, JValue result_register) {
//addcodestart
char *dexfilepath=(char*)malloc(sizeof(char)*1000);
if(dexfilepath!=nullptr)
{
ArtMethod* artmethod=shadow_frame.GetMethod();
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.
int size_int_=(int)size_;
int fcmdline =-1;
char szCmdline[64]= {0};
char szProcName[256] = {0};
int procid = getpid();
sprintf(szCmdline,"/proc/%d/cmdline", procid);
fcmdline = open(szCmdline, O_RDONLY,0644);
if(fcmdline >0)
{
read(fcmdline, szProcName,256);
close(fcmdline);
}
if(szProcName[0])
{
memset(dexfilepath,0,1000);
sprintf(dexfilepath,"/sdcard/%s_%d_dexfile.dex",szProcName,size_int_);
int 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)
{
write(fp,(void*)begin_,size_);
fsync(fp);
close(fp);
}
}
}
if(dexfilepath!=nullptr)
{
free(dexfilepath);
dexfilepath=nullptr;
}
}
//addcodeend
DCHECK(!shadow_frame.GetMethod()->IsAbstract());
DCHECK(!shadow_frame.GetMethod()->IsNative());
shadow_frame.GetMethod()->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);
bool transaction_active = Runtime::Current()->IsActiveTransaction();
if (LIKELY(shadow_frame.GetMethod()->IsPreverified())) {
// Enter the "without access check" interpreter.
if (kInterpreterImplKind == kSwitchImpl) {
if (transaction_active) {
return ExecuteSwitchImpl<false, true>(self, code_item, shadow_frame, result_register);
} else {
return ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame, result_register);
}
} else {
DCHECK_EQ(kInterpreterImplKind, kComputedGotoImplKind);
if (transaction_active) {
return ExecuteGotoImpl<false, true>(self, code_item, shadow_frame, result_register);
} else {
return ExecuteGotoImpl<false, false>(self, code_item, shadow_frame, result_register);
}
}
} else {
// Enter the "with access check" interpreter.
if (kInterpreterImplKind == kSwitchImpl) {
if (transaction_active) {
return ExecuteSwitchImpl<true, true>(self, code_item, shadow_frame, result_register);
} else {
return ExecuteSwitchImpl<true, false>(self, code_item, shadow_frame, result_register);
}
} else {
DCHECK_EQ(kInterpreterImplKind, kComputedGotoImplKind);
if (transaction_active) {
return ExecuteGotoImpl<true, true>(self, code_item, shadow_frame, result_register);
} else {
return ExecuteGotoImpl<true, false>(self, code_item, shadow_frame, result_register);
}
}
}
}
>>>> 测试效果
测试效果
01
02
开班时间:
9:00~19:00 ,合计四天;
两次开班中间会间隔两三周;
制定符合业务实际情况、帮助学员切实提高业务技能的教学计划;
所有核心思路和案例现场手把手教会,并总结方法论教会;
为保证服务质量,每次开班名额上限十人;
开班地点:
优先同城满十人即开班
异地愿意加入开班亦可(酌情承担路费和住宿)
03
工具追新
教材更新
答疑解惑
新的案例
坚持服务第一的理念
每次培训都附带午餐、晚宴
营造良好氛围
学员技术形象PR包装指导
学员文章、优先看雪推送
学员议题优先推送各大安全会议
04
培训价格:
5999 元/人
赠送《安卓高级研修班》结业考试一次,限培训结束后三个月内
长按/扫描上方二维码,立即报名!
联系我们:
看雪ID:hanbingle
https://bbs.pediy.com/user-632473.htm
推荐文章++++
* Metasploit BlueKeep漏洞利用模块简要分析