1
第一代壳Dex整体加固
第一代壳主要是对dex/apk文件整体加密,然后自定义类加载器动态加载dex/apk文件并执行。在动态加载dex/apk文件的时候有落地加载和不落地加载,落地加载就是通过DexClassLoader从磁盘加载dex/apk文件,不落地加载就是通过InMemoryDexClassLoader从内存中加载dex/apk文件。
Dex文件结构
一代壳实现原理
app的启动流程
通过将原apk进行加密后保存在加壳apk的dex文件尾部,在加壳apk运行时将dex文件尾部加密的原apk文件解密后进行动态加载。
壳代码需要最早获取到加壳apk的执行时机,所以加壳apk的Application实现了attachContextApplication函数,此函数在handleBindApplication函数中通过调用makeApplicaton进行调用,是一个APP进程最早的执行入口。加壳apk需要进行如下操作
◆attachContextApplication解密原apk保存到磁盘文件中(不落地加载可以不保存磁盘文件),动态加载解密后的apk并替换掉mClassLoader
◆Application::onCreate重定位Application,调用原apk的Application的onCreate函数
一代壳实践
步骤:
一个源程序,即apk。
加壳工具。
脱壳程序,释放源程序并加载。
源程序
package com.example.pack_test_one;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import com.example.pack_test_one.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
// Used to load the 'pack_test_one' library on application startup.
static {
System.loadLibrary("pack_test_one");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
Log.i("test", "rea111111111:"+getApplicationContext());
// Example of a call to a native method
TextView tv = binding.sampleText;
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'pack_test_one' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
加壳工具
import hashlib
import os
import shutil
import sys
import zlib
import zipfile
import subprocess
def process_apk_data(apk_data: bytes):
"""
用于处理apk数据,比如加密,压缩等,都可以放在这里。
:param apk_data:
:return:
"""
return apk_data
# 使用前需要修改的部分
keystore_path = 'D:/Android/Key.jks'
keystore_password = '######'
key0_password="########"
src_apk_file_path = 'D:/Android/Projection/pack_test_one/app/debug/app-debug.apk'
shell_apk_file_path = 'D:/Android/Projection/unshell/app/debug/app-debug.apk'
buildtools_path = 'D:/Android/SDK/build-tools/34.0.0'
# 承载apk的文件名
carrier_file_name= 'classes.dex'
# 中间文件夹
intermediate_dir= 'intermediates'
intermediate_apk_name='app-debug.apk'
intermediate_aligned_apk_name='app-debug-aligned.apk'
intermediate_apk_path="intermediates/app-debug.apk"
intermediate_carrier_path=os.path.join(intermediate_dir, carrier_file_name)
intermediate_aligned_apk_path=os.path.join(intermediate_dir,intermediate_aligned_apk_name)
if os.path.exists(intermediate_dir):
shutil.rmtree(intermediate_dir)
os.mkdir(intermediate_dir)
# 解压apk
shell_apk_file=zipfile.ZipFile(shell_apk_file_path)
shell_apk_file.extract(carrier_file_name,intermediate_dir)
# 查找dex
if not os.path.exists(os.path.join(intermediate_dir, carrier_file_name)):
raise FileNotFoundError(f'{carrier_file_name} not found')
src_dex_file_path= os.path.join(intermediate_dir, carrier_file_name)
#读取
src_apk_file=open(src_apk_file_path, 'rb')
src_dex_file=open(src_dex_file_path, 'rb')
src_apk_data=src_apk_file.read()
src_dex_data=src_dex_file.read()
# 处理apk数据
processed_apk_data=process_apk_data(src_apk_data)
processed_apk_size=len(processed_apk_data)
# 构建新dex数据
new_dex_data=src_dex_data+processed_apk_data+int.to_bytes(processed_apk_size,8,'little')
# 更新文件大小
file_size=len(processed_apk_data)+len(src_dex_data)+8
new_dex_data=new_dex_data[:32]+int.to_bytes(file_size,4,'little')+new_dex_data[36:]
# 更新sha1摘要
signature=hashlib.sha1().digest()
new_dex_data=new_dex_data[:12]+signature+new_dex_data[32:]
# 更新checksum
checksum=zlib.adler32(new_dex_data[12:])
new_dex_data=new_dex_data[:8]+int.to_bytes(checksum,4,'little')+new_dex_data[12:]
# 写入新dex
intermediate_carrier_file= open(intermediate_carrier_path, 'wb')
intermediate_carrier_file.write(new_dex_data)
intermediate_carrier_file.close()
src_apk_file.close()
src_dex_file.close()
os.environ.update({'PATH': os.environ.get('PATH') + f';{buildtools_path}'})
# 复制文件,Windows 下使用 copy 命令
shell_apk_file_path = r"D:/Android/Projection/unshell/app/debug/app-debug.apk"
intermediate_apk_path = r"intermediates/app-debug.apk"
# 检查源文件是否存在
if os.path.exists(shell_apk_file_path):
try:
# 使用 shutil.copy 进行文件复制
shutil.copy(shell_apk_file_path, intermediate_apk_path)
print(f"文件已成功复制到 {intermediate_apk_path}")
except Exception as e:
print(f"复制文件时出错: {e}")
else:
print(f"源文件不存在: {shell_apk_file_path}")
# 切换到 intermediate_dir 目录
os.chdir(intermediate_dir)
# 使用 7z 压缩文件,替代 zip 命令
zip_command = f'"D://tool//7-Zip//7z.exe" a -tzip "{intermediate_apk_name}" "{carrier_file_name}"'
print( f'"D://tool//7-Zip//7z.exe" a -tzip "{intermediate_apk_name}" "{carrier_file_name}"')
r = subprocess.getoutput(zip_command)
print(r)
# 切换回上一级目录
os.chdir('../')
# 对齐 APK,使用 Android SDK 的 zipalign 工具
zipalign_command = f'zipalign 4 "{intermediate_apk_path}" "{intermediate_aligned_apk_path}"'
r = subprocess.getoutput(zipalign_command)
print(r)
# 使用 apksigner 工具进行签名
apksigner_command = f'apksigner sign -ks "{keystore_path}" --ks-pass pass:{keystore_password} "{intermediate_aligned_apk_path}"'
# 使用 subprocess.run 直接执行命令
result = subprocess.run(apksigner_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,input=f"{key0_password}n")
print("STDOUT:", result.stdout)
print("STDERR:", result.stderr)
# 复制对齐并签名后的 APK 到输出目录
copy_final_command = f'copy "{intermediate_aligned_apk_path}" "./app-out.apk"'
r = subprocess.getoutput(copy_final_command)
print(r)
print('Success')
看到打包这块想起自己之前每次都懒得写脚本,每次都自己手动重打包。
这个脚本的话,是linux下的,我改写一下命令后,os.popen无法很完美的实现copy等一些命令,copy也很奇怪,推测是无法对和自身所在盘符不同的文件操作,很难受。将文件都复制到了脚本这里,改用了subprocess,签名的时候需要二次输入密码交互所以用run,其他getoutput即可。
具体的作用就是将源程序apk和脱壳程序构建成了下面这个:
脱壳程序
脱壳程序要做的就是在启动流程调用Application的attachBaseContext时点释放我们的源程序并将其替换为我们的application实例。
代理application
添加android:name=".ProxyApplication"还有替换活动为我们脱壳后实际会运行的活动。
提取apk文件
先将apk文件的dex提出,然后根据之前加壳时在文件中留下的大小信息,将源apk提取出来。
然后将apk存储起来。
dexclassloader加载apk
so需要手动提取一下到目标路径,之后需要通过反射替换一下classloader。这里替换classloader的原因需要了解一下双亲委派模式。
这样子在之后加载我们的源程序的活动时,可以正常找到类。
主要代码
package com.example.unshell;
import android.app.*; // 导入 android.app 包中的所有类
import android.content.*; // 导入 android.content 包中的所有类
import android.os.Build;
import android.util.*; // 导入 android.util 包中的所有类
import java.io.*; // 导入 java.io 包中的所有类
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.*; // 导入 java.nio 包中的所有类
import java.util.*;
import java.util.zip.*; // 导入 java.util.zip 包中的所有类
import dalvik.system.DexClassLoader;
public class ProxyApplication extends Application {
private String srcApkPath;
private String optDirPath;
private String libDirPath;
private ZipFile getApkZip() throws IOException {
Log.i("demo", this.getApplicationInfo().sourceDir);
ZipFile apkZipFile = new ZipFile(this.getApplicationInfo().sourceDir);
return apkZipFile;
}
private byte[] readDexFileFromApk() throws IOException {
/* 从本体apk中获取dex文件 */
ZipFile apkZip = this.getApkZip();
ZipEntry zipEntry = apkZip.getEntry("classes.dex");
InputStream inputStream = apkZip.getInputStream(zipEntry);
byte[] buffer = new byte[1024];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int length;
while ((length = inputStream.read(buffer)) > 0) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
}
private byte[] reverseProcessApkData(byte[] data) { //解密函数
for (int i = 0; i < data.length; i++) {
data[i] = data[i];
}
return data;
}
private byte[] splitSrcApkFromDex(byte[] dexFileData) {
/* 从dex文件中分离源apk文件 */
int length = dexFileData.length;
ByteBuffer bb = ByteBuffer.wrap(Arrays.copyOfRange(dexFileData, length - 8, length));
bb.order(java.nio.ByteOrder.LITTLE_ENDIAN); // 设置为小端模式
long processedSrcApkDataSize = bb.getLong(); // 读取这8个字节作为long类型的值
byte[] processedSrcApkData = Arrays.copyOfRange(dexFileData, (int) (length - 8 - processedSrcApkDataSize), length - 8);
byte[] srcApkData = reverseProcessApkData(processedSrcApkData);
return srcApkData;
}
public static void replaceClassLoader1(Context context, DexClassLoader dexClassLoader) {
ClassLoader pathClassLoader = ProxyApplication.class.getClassLoader();
try {
// 1.通过currentActivityThread方法获取ActivityThread实例
Class ActivityThread = pathClassLoader.loadClass("android.app.ActivityThread");
Method currentActivityThread = ActivityThread.getDeclaredMethod("currentActivityThread");
Object activityThreadObj = currentActivityThread.invoke(null);
// 2.拿到mPackagesObj
Field mPackagesField = ActivityThread.getDeclaredField("mPackages");
mPackagesField.setAccessible(true);
ArrayMap mPackagesObj = (ArrayMap) mPackagesField.get(activityThreadObj);
// 3.拿到LoadedApk
String packageName = context.getPackageName();
WeakReference wr = (WeakReference) mPackagesObj.get(packageName);
Object LoadApkObj = wr.get();
// 4.拿到mClassLoader
Class LoadedApkClass = pathClassLoader.loadClass("android.app.LoadedApk");
Field mClassLoaderField = LoadedApkClass.getDeclaredField("mClassLoader");
mClassLoaderField.setAccessible(true);
Object mClassLoader = mClassLoaderField.get(LoadApkObj);
Log.i("mClassLoader", mClassLoader.toString());
// 5.将系统组件ClassLoader给替换
mClassLoaderField.set(LoadApkObj, dexClassLoader);
} catch (Exception e) {
Log.i("demo", "error:" + Log.getStackTraceString(e));
e.printStackTrace();
}
}
private void extractSoFiles(File srcApkFile, File libDir) throws IOException {
// 获取当前设备的架构
String abi = Build.CPU_ABI; // 或者使用 Build.SUPPORTED_ABIS 来获取更全面的信息
// 提取 APK 中的 lib 文件夹并复制到 libDir 目录中
ZipFile apkZip = new ZipFile(srcApkFile);
ZipEntry libDirEntry = null;
Enumeration<? extends ZipEntry> entries = apkZip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
// 找到 lib 文件夹并提取里面的 .so 文件
if (entry.getName().startsWith("lib/") && entry.getName().endsWith(".so")) {
// 检查 .so 文件是否属于当前设备架构
if (entry.getName().contains("lib/" + abi + "/")) {
File destSoFile = new File(libDir, entry.getName().substring(entry.getName().lastIndexOf("/") + 1));
// 创建目标文件夹
if (!destSoFile.getParentFile().exists()) {
destSoFile.getParentFile().mkdirs();
}
// 复制 .so 文件
try (InputStream inputStream = apkZip.getInputStream(entry);
FileOutputStream fos = new FileOutputStream(destSoFile)) {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
fos.write(buffer, 0, length);
}
}
}
}
}
}
@Override
protected void attachBaseContext(Context base) {
Log.i("demo", "attachBaseContext");
super.attachBaseContext(base);
try {
byte[] dexFileData = this.readDexFileFromApk();
byte[] srcApkData = this.splitSrcApkFromDex(dexFileData);
// 创建储存apk的文件夹,写入src.apk
File apkDir = base.getDir("apk_out", MODE_PRIVATE);
srcApkPath = apkDir.getAbsolutePath() + "/src.apk";
File srcApkFile = new File(srcApkPath);
srcApkFile.setWritable(true);
try (FileOutputStream fos = new FileOutputStream(srcApkFile)) {
Log.i("demo", String.format("%d", srcApkData.length));
fos.write(srcApkData);
}
srcApkFile.setReadOnly(); // 受安卓安全策略影响,dex必须为只读
Log.i("demo", "Write src.apk into " + srcApkPath);
// 新建加载器
File optDir = base.getDir("opt_dex", MODE_PRIVATE);
File libDir = base.getDir("lib_dex", MODE_PRIVATE);
optDirPath = optDir.getAbsolutePath();
libDirPath = libDir.getAbsolutePath();
// 提取 .so 文件到 lib_dex 目录
extractSoFiles(srcApkFile, libDir);
ClassLoader pathClassLoader = ProxyApplication.class.getClassLoader();
DexClassLoader dexClassLoader = new DexClassLoader(srcApkPath, optDirPath, libDirPath, pathClassLoader);
Log.i("demo", "Successfully initiate DexClassLoader.");
// 修正加载器
replaceClassLoader1(base, dexClassLoader);
Log.i("demo", "ClassLoader replaced.");
} catch (Exception e) {
Log.i("demo", "error:" + Log.getStackTraceString(e));
e.printStackTrace();
}
}
}
安装时如果报这个错的话
是因为zipaligned工具自身的问题,这里需要添加在application下添加**android:extractNativeLibs="true"**安装时才不会报错。
启动的是我们的源程序,jadx里显示的是我们的壳。
一代壳脱壳
一代壳的防护性并不高,frida-dump应该几乎是秒杀一代壳,不多赘述。
2
二代壳—函数抽取壳
二代壳完全复现的话感觉时间会花费较多,所以我会以这个项目的代码作为学习。
https://www.cnblogs.com/luoyesiqiu/p/dpt.html
该项目的代码有两个部分,proccessor和shell
proccessor是对apk进行字节提取然后重新打包处理成加壳apk的模块,shell模块最终生成的dex和so集成到加壳的apk中。
不过在开始前对dex的文件结构有些了解比较好,因为函数抽取,抽取的就是dex文件中的codeitem,具体是codeItem的insns,它保存了函数的实际字节码。
processor的工作流程:
shell模块工作流程:
处理Androidmanifest.xml
Androidmainfest.xml在编译为apk时,会以axml格式存放,不是普通可读的形式,该项目采取的ManifestEditor进行解析。
对于Androidmanifest.xml,我们主要的操作是,备份原Application的类名和写入壳的代理application类名。也就是在代理Application中实现一些加载dex或者HOOK等操作。
提取xml的代码段:
以及还有相应的写入Application类的操作。
public static void writeApplicationName(String inManifestFile, String outManifestFile, String newApplicationName){
ModificationProperty property = new ModificationProperty();
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME,newApplicationName));
FileProcesser.processManifestFile(inManifestFile, outManifestFile, property);
}
提取CodeItem
我们可以随便找一个dex放010里看一下。
然后我们具体提取的是CodeItem的insns,insns是函数实现的具体字节码。
提取所用工具为安卓提供的dx工具,同样存放在lib目录下。
该项目实现的函数是extractAllMethods,具体的实现先不谈了,结果就是读取所有的classdef(一个类的定义)的所有函数,并是否进行函数抽取。提取的数据怎么存储比较好呢,之前遇到的一道题是直接提取出来作为静态值最后填回去了。该项目的话是存储到了instruction结构体里,再看下这个结构体。
public class Instruction {
public int getOffsetOfDex() {
return offsetOfDex;
}
public void setOffsetOfDex(int offsetOfDex) {
this.offsetOfDex = offsetOfDex;
}
public int getMethodIndex() {
return methodIndex;
}
public void setMethodIndex(int methodIndex) {
this.methodIndex = methodIndex;
}
public int getInstructionDataSize() {
return instructionDataSize;
}
public void setInstructionDataSize(int instructionDataSize) {
this.instructionDataSize = instructionDataSize;
}
public byte[] getInstructionsData() {
return instructionsData;
}
public void setInstructionsData(byte[] instructionsData) {
this.instructionsData = instructionsData;
}
@Override
public String toString() {
return "Instruction{" +
"offsetOfDex=" + offsetOfDex +
", methodIndex=" + methodIndex +
", instructionDataSize=" + instructionDataSize +
", instructionsData=" + Arrays.toString(instructionsData) +
'}';
}
//Offset in dex
private int offsetOfDex;
//Corresponding method_idx in dex
private int methodIndex;
//instructionsData size
private int instructionDataSize;
//insns data
private byte[] instructionsData;
}
这个结构体是专门用来存储方法相关的东西的,如偏移,字节码。
Shell模块
这个部分是壳的主要逻辑。
Hook函数
mmap
Hook这个函数是为了使得我们加载dex时修改dex的属性,使其可写,才能将字节码回填。https://bbs.kanxue.com/thread-266527.htm。
具体是<font style="color:rgb(89, 97, 114);">给__prot参数追加PROT_WRITE属性</font>
void* fake_mmap(void* __addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset){
BYTEHOOK_STACK_SCOPE();
int hasRead = (__prot & PROT_READ) == PROT_READ;
int hasWrite = (__prot & PROT_WRITE) == PROT_WRITE;
int prot = __prot;
if(hasRead && !hasWrite) {
prot = prot | PROT_WRITE;
DLOGD("fake_mmap call fd = %p,size = %d, prot = %d,flag = %d",__fd,__size, prot,__flags);
}
void *addr = BYTEHOOK_CALL_PREV(fake_mmap,__addr, __size, prot, __flags, __fd, __offset);
return addr;
}
LoadMethod
类加载的调用链
ClassLoader.java::loadClass -> DexPathList.java::findClass -> DexFile.java::defineClass -> class_linker.cc::LoadClass -> class_linker.cc::LoadClassMembers -> class_linker.cc::LoadMethod
◆**ClassLoader.java::loadClass**
— 被调用时,检查类是否已加载。如果未加载,调用findClass
来继续加载。
◆**DexPathList.java::findClass**
— 查找指定的类,如果找不到,返回ClassNotFoundException
。
◆**DexFile.java::defineClass**
— 通过DexFile
查找类的字节码,并在内存中创建一个Class
对象。
◆**class_linker.cc::LoadClass**
— 在 native 层解析类字节码,进行验证,并在内存中创建类的表示。
◆**class_linker.cc::LoadClassMembers**
— 加载类的所有成员(字段、方法等)。
◆**class_linker.cc::LoadMethod**
— 加载类的方法,处理方法的符号信息。
void ClassLinker::LoadMethod(const DexFile& dex_file,
const ClassDataItemIterator& it,
Handle<mirror::Class> klass,
ArtMethod* dst);
LoadMethod中有两个关键参数DexFile和ClassDataItemIterator
ClassDataItemIterator结构体的**code_off **是加载的函数的CodeItem相对于DexFile的偏移。所以LoadMethod是一个绝佳的HOOK点。
还有就是LoadMethod不同的安卓版本函数声明可能不同,所以需要做一个版本的不同处理,这个也确实在二代壳的ctf题中有看到。
填充insns
void LoadMethod(void *thiz, void *self, const void *dex_file, const void *it, const void *method,
void *klass, void *dst) {
if (g_originLoadMethod25 != nullptr
|| g_originLoadMethod28 != nullptr
|| g_originLoadMethod29 != nullptr) {
uint32_t location_offset = getDexFileLocationOffset();
uint32_t begin_offset = getDataItemCodeItemOffset();
callOriginLoadMethod(thiz, self, dex_file, it, method, klass, dst);
ClassDataItemReader *classDataItemReader = getClassDataItemReader(it,method);
uint8_t **begin_ptr = (uint8_t **) ((uint8_t *) dex_file + begin_offset);
uint8_t *begin = *begin_ptr;
// vtable(4|8) + prev_fields_size
std::string *location = (reinterpret_cast<std::string *>((uint8_t *) dex_file +
location_offset));
if (location->find("base.apk") != std::string::npos) {
//code_item_offset == 0说明是native方法或者没有代码
if (classDataItemReader->GetMethodCodeItemOffset() == 0) {
DLOGW("native method? = %s code_item_offset = 0x%x",
classDataItemReader->MemberIsNative() ? "true" : "false",
classDataItemReader->GetMethodCodeItemOffset());
return;
}
uint16_t firstDvmCode = *((uint16_t*)(begin + classDataItemReader->GetMethodCodeItemOffset() + 16));
if(firstDvmCode != 0x0012 && firstDvmCode != 0x0016 && firstDvmCode != 0x000e){
NLOG("this method has code no need to patch");
return;
}
uint32_t dexSize = *((uint32_t*)(begin + 0x20));
int dexIndex = dexNumber(location);
auto dexIt = dexMap.find(dexIndex - 1);
if (dexIt != dexMap.end()) {
auto dexMemIt = dexMemMap.find(dexIndex);
if(dexMemIt == dexMemMap.end()){
changeDexProtect(begin,location->c_str(),dexSize,dexIndex);
}
auto codeItemMap = dexIt->second;
int methodIdx = classDataItemReader->GetMemberIndex();
auto codeItemIt = codeItemMap->find(methodIdx);
if (codeItemIt != codeItemMap->end()) {
CodeItem* codeItem = codeItemIt->second;
uint8_t *realCodeItemPtr = (uint8_t*)(begin +
classDataItemReader->GetMethodCodeItemOffset() +
16);
memcpy(realCodeItemPtr,codeItem->getInsns(),codeItem->getInsnsSize());
}
}
}
}
}
codeitem的传递是assets/OoooooOooo。
加载dex
重新加载dex并替换dexElements,使ClassLoader从我们新加载的dex文件中加载类。这里还是之前那个双亲委派机制的原因。
这就是二代壳的大致过程,但是详细的具体实现没有过多去分析,后续有需求实现的话会再补充一下,这里有一篇dpt-shell分析文章,比我分析的要详细很多https://tttang.com/archive/1728/
3
二代壳脱壳
最老实的方法就是找到字节码自己填充回去再分析,这是我之前ctf一道题这样干的。
大体思路是找到函数填充回去的时机,如HOOK loadmethod来dump出已经填充回去的dex。
function Hook_Invoke() {
var InvokeFunc = Module.findExportByName("libart.so", "_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc")
Interceptor.attach(InvokeFunc, {
onEnter: function (args) {
console.log(args[1])
// args[0].add(8).readInt().toString(16) == 0
console.log("Method Idx -> " + args[0].add(8).readInt().toString(16) + " " + args[0].add(12).readInt().toString(16))
dump_memory(dex_begin, dex_size)
//dump_memory(dex_begin, dex_size)
},
onLeave: function (retval) {}
})
}
var dex_begin = 0
var dex_size = 0
function dump_memory(base, size) {
Java.perform(function () {
var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
var dir = "/sdcard/Download/";
var file_path = dir + "/mydumpp";
var file_handle = new File(file_path, "w+");
if (file_handle && file_handle != null) {
Memory.protect(ptr(base), size, 'rwx');
var libso_buffer = ptr(base).readByteArray(size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log("[dump]:", file_path);
}
});
}
function getDexFile() {
Hook_Invoke()
Java.perform(function () {
var ActivityThread = Java.use("android.app.ActivityThread")
ActivityThread["performLaunchActivity"].implementation = function (args) {
var env = Java.vm.getEnv()
var classloader = this.mInitialApplication.value.getClassLoader()
var BaseDexClassLoader = Java.use("dalvik.system.BaseDexClassLoader")
var elementsClass = Java.use("dalvik.system.DexPathList$Element")
classloader = Java.cast(classloader, BaseDexClassLoader)
var pathList = classloader.pathList.value
var elements = pathList.dexElements.value
console.log(elements)
//console.log(elements.value)
for (var i in elements) {
var element;
try {
element = Java.cast(elements[i], elementsClass);
} catch (e) {
console.log(e)
}
//之后需要获取DexFile
var dexFile = element.dexFile.value
//getMethod(dexFile, classloader)
var mCookie = dexFile.mCookie.value
//$h获取句柄
var length = env.getArrayLength(mCookie.$h, 0)
//console.log(length)
var Array = env.getLongArrayElements(mCookie.$h, 0)
//第一个不是DexFile
for (var i = 1; i < length; ++i) {
var DexFilePtr = Memory.readPointer(ptr(Array).add(i * 8))
var DexFileBegin = ptr(DexFilePtr).add(Process.pointerSize).readPointer()
var DexFileSize = ptr(DexFilePtr).add(Process.pointerSize * 2).readU32()
console.log(hexdump(DexFileBegin))
console.log("Size => " + DexFileSize.toString(16))
dex_begin = DexFileBegin
dex_size = DexFileSize
}
}
return this.performLaunchActivity(arguments[0], arguments[1])
}
})
}
function main() {
getDexFile()
}
setImmediate(main)
看雪ID:螺丝兔
https://bbs.kanxue.com/user-home-992277.htm
#
原文始发于微信公众号(看雪学苑):安卓壳学习记录(上)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论