zoukankan      html  css  js  c++  java
  • 开启Android Apk调试与备份选项的Xposed模块的编写

    本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80963610

    在进行Android应用程序逆向分析的时候,经常需要进行Android应用程序的动态调试,一般情况下基于Android应用程序的安全性考虑,发布版的Android应用程序都会关闭它的调试选项,因此只有开启该Android应用程序的调试选项,才能在Android应用程序的Activity界面显示之前(即Application类的attach函数和onCreate函数执行之前)使Android应用程序处于暂停等待调试的状态,并且没有开启调试选项的Android应用程序通过DDMS工具是看不到进程的。

    Android应用程序支持dex代码调试的条件


    上面提到的两种开启Android应用程序调试选项的方法都不是最佳的,作者泉哥编写了一个基于Xposed框架实现的开启APK调试与备份选项的Xposed模块,只要在Android系统上成功安装了Xposed框架,再安装和激活泉哥编写的 BDOpener插件 重启Android系统就可以开启Android应用的dex代码调试模式。之前我曾尝试过修改Android系统源码的方法开启Android应用的dex代码调试模式,但是效果不好,影响到Android系统app的启动,需要过滤Android系统应用。

    BDOpener插件的下载地址:https://security.tencent.com/index.php/opensource/down/17

    BDOpener插件的Xposed Hook代码编写原理如下:


    java Hook处理类"com.android.server.pm.PackageManagerService"中所有名称为"getPackageInfo"的类方法,修改该类方法的函数返回值PackageInfo,为返回值PackageInfo对象中的实例成员变量applicationInfo的flags增加调试模式选项和备份模式选项,这用每次调用类PackageManagerService的类方法"getPackageInfo"获取到的Android应用程序的包信息中都带有调试模式选项和备份模式选项的信息。

    源码路径:/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java

    源码路径:/frameworks/base/core/java/android/content/pm/ApplicationInfo.java

    Android调试模式选项的flags定义:


    Android备份模式选项的flags定义:

    源码路径:/frameworks/base/core/java/android/app/ActivityThread.java

    Android应用的调试模式运行判断是在类ActivityThread的函数handleBindApplication中进行的。


    Android应用的调试模式运行判断是在Android应用的dex文件加载完成以后,类Application的attach函数和onCreate函数执行之前,因此在进行Android应用程序的dex代码调试时,Apk程序会暂停等待调试于类Application的代码执行之前,此时Android应用程序的Activity界面还没有开始显示。

        // 在apk程序的Activity显示之前执行的代码
        private void handleBindApplication(AppBindData data) {
        	
            mBoundApplication = data;
            mConfiguration = new Configuration(data.config);
            mCompatConfiguration = new Configuration(data.config);
    
            mProfiler = new Profiler();
            mProfiler.profileFile = data.initProfileFile;
            mProfiler.profileFd = data.initProfileFd;
            mProfiler.autoStopProfiler = data.initAutoStopProfiler;
    
            // send up app name; do this *before* waiting for debugger
            Process.setArgV0(data.processName);
            android.ddm.DdmHandleAppName.setAppName(data.processName,
                                                    UserHandle.myUserId());
    
            if (data.persistent) {
                // Persistent processes on low-memory devices do not get to
                // use hardware accelerated drawing, since this can add too much
                // overhead to the process.
                if (!ActivityManager.isHighEndGfx()) {
                    HardwareRenderer.disable(false);
                }
            }
    
            if (mProfiler.profileFd != null) {
                mProfiler.startProfiling();
            }
    
            // If the app is Honeycomb MR1 or earlier, switch its AsyncTask
            // implementation to use the pool executor.  Normally, we use the
            // serialized executor as the default. This has to happen in the
            // main thread so the main looper is set right.
            if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
                AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
            }
    
            /*
             * Before spawning a new process, reset the time zone to be the system time zone.
             * This needs to be done because the system time zone could have changed after the
             * the spawning of this process. Without doing this this process would have the incorrect
             * system time zone.
             */
            TimeZone.setDefault(null);
    
            /*
             * Initialize the default locale in this process for the reasons we set the time zone.
             */
            Locale.setDefault(data.config.locale);
    
            /*
             * Update the system configuration since its preloaded and might not
             * reflect configuration changes. The configuration object passed
             * in AppBindData can be safely assumed to be up to date
             */
            mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
            mCurDefaultDisplayDpi = data.config.densityDpi;
            applyCompatConfiguration(mCurDefaultDisplayDpi);
    
            ///////////////////////////////////////////////////////////////////
            // LoadedApk info;	
            // 对Android应用的dex文件进行加载返回LoadedApk实例对象
            data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
            ///////////////////////////////////////////////////////////////////
    
            /**
             * Switch this process to density compatibility mode if needed.
             */
            if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
                    == 0) {
                mDensityCompatMode = true;
                Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
            }
            updateDefaultDensity();
    
            // 创建并初始化Android应用程序的Context
            final ContextImpl appContext = new ContextImpl();
            appContext.init(data.info, null, this);
            if (!Process.isIsolated()) {
                final File cacheDir = appContext.getCacheDir();
    
                if (cacheDir != null) {
                    // Provide a usable directory for temporary files
                    System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
    
                    setupGraphicsSupport(data.info, cacheDir);
                } else {
                    Log.e(TAG, "Unable to setupGraphicsSupport due to missing cache directory");
                }
            }
            /**
             * For system applications on userdebug/eng builds, log stack
             * traces of disk and network access to dropbox for analysis.
             */
            if ((data.appInfo.flags &
                 (ApplicationInfo.FLAG_SYSTEM |
                  ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) {
                StrictMode.conditionallyEnableDebugLogging();
            }
    
            /**
             * For apps targetting SDK Honeycomb or later, we don't allow
             * network usage on the main event loop / UI thread.
             *
             * Note to those grepping:  this is what ultimately throws
             * NetworkOnMainThreadException ...
             */
            if (data.appInfo.targetSdkVersion > 9) {
                StrictMode.enableDeathOnNetwork();
            }
    
            // 判断Android应用是否打开调试模式
            if (data.debugMode != IApplicationThread.DEBUG_OFF) {
                // XXX should have option to change the port.
                Debug.changeDebugPort(8100);
                if (data.debugMode == IApplicationThread.DEBUG_WAIT) {
                    Slog.w(TAG, "Application " + data.info.getPackageName()
                          + " is waiting for the debugger on port 8100...");
    
                    IActivityManager mgr = ActivityManagerNative.getDefault();
                    try {
                        mgr.showWaitingForDebugger(mAppThread, true);
                    } catch (RemoteException ex) {
                    }
                    // 调试模式运行app并调试等待
                    Debug.waitForDebugger();
    
                    try {
                    	// Android程序调试模式启动的提示
                        mgr.showWaitingForDebugger(mAppThread, false);
                    } catch (RemoteException ex) {
                    }
    
                } else {
                    Slog.w(TAG, "Application " + data.info.getPackageName()
                          + " can be debugged on port 8100...");
                }
            }
    
            // Enable OpenGL tracing if required
            if (data.enableOpenGlTrace) {
                GLUtils.setTracingLevel(1);
            }
    
            // Allow application-generated systrace messages if we're debuggable.
            boolean appTracingAllowed = (data.appInfo.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0;
            Trace.setAppTracingAllowed(appTracingAllowed);
    
            /**
             ** Initialize the default http proxy in this process for the reasons we set the time zone.
             **/
            IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
            if (b != null) {
                // In pre-boot mode (doing initial launch to collect password), not
                // all system is up.  This includes the connectivity service, so don't
                // crash if we can't get it.
                IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
                try {
                    ProxyProperties proxyProperties = service.getProxy();
                    Proxy.setHttpProxySystemProperty(proxyProperties);
                } catch (RemoteException e) {}
            }
    
            if (data.instrumentationName != null) {
                InstrumentationInfo ii = null;
                try {
                    ii = appContext.getPackageManager().
                        getInstrumentationInfo(data.instrumentationName, 0);
                } catch (PackageManager.NameNotFoundException e) {
                }
                if (ii == null) {
                    throw new RuntimeException(
                        "Unable to find instrumentation info for: "
                        + data.instrumentationName);
                }
    
                mInstrumentationAppDir = ii.sourceDir;
                mInstrumentationAppLibraryDir = ii.nativeLibraryDir;
                mInstrumentationAppPackage = ii.packageName;
                mInstrumentedAppDir = data.info.getAppDir();
                mInstrumentedAppLibraryDir = data.info.getLibDir();
    
                ApplicationInfo instrApp = new ApplicationInfo();
                instrApp.packageName = ii.packageName;
                instrApp.sourceDir = ii.sourceDir;
                instrApp.publicSourceDir = ii.publicSourceDir;
                instrApp.dataDir = ii.dataDir;
                instrApp.nativeLibraryDir = ii.nativeLibraryDir;
                
                // 得到Android应用dex文件加载后的LoadedApk实例
                LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
                        appContext.getClassLoader(), false, true);
                
                ContextImpl instrContext = new ContextImpl();
                instrContext.init(pi, null, this);
    
                try {
                    java.lang.ClassLoader cl = instrContext.getClassLoader();
                    mInstrumentation = (Instrumentation)
                        cl.loadClass(data.instrumentationName.getClassName()).newInstance();
                } catch (Exception e) {
                    throw new RuntimeException(
                        "Unable to instantiate instrumentation "
                        + data.instrumentationName + ": " + e.toString(), e);
                }
    
                mInstrumentation.init(this, instrContext, appContext,
                       new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
                       data.instrumentationUiAutomationConnection);
    
                if (mProfiler.profileFile != null && !ii.handleProfiling
                        && mProfiler.profileFd == null) {
                    mProfiler.handlingProfiling = true;
                    File file = new File(mProfiler.profileFile);
                    file.getParentFile().mkdirs();
                    Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
                }
    
            } else {
            	// 构建Instrumentation对象实例
                mInstrumentation = new Instrumentation();
            }
    
            if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
                dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
            }
    
            // Allow disk access during application and provider setup. This could
            // block processing ordered broadcasts, but later processing would
            // probably end up doing the same disk access.
            final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
            try {
                // If the app is being launched for full backup or restore, bring it up in
                // a restricted environment with the base application class.
            	
            	////////////////////////////////////////////////////////////////
                // 创建Android应用的Application类对象的实例并调用其attach方法
                // 间接通过调用attach方法调用Android应用的attachBaseContext方法
                Application app = data.info.makeApplication(data.restrictedBackupMode, null);
                // 在类ActivityThread的成员变量mInitialApplication中保存创建的Application类对象实例(3)
                // 将第1个Application视为进程的初始化Application
                mInitialApplication = app;
                ////////////////////////////////////////////////////////////////
    
                // don't bring up providers in restricted mode; they may depend on the
                // app's custom Application class
                if (!data.restrictedBackupMode) {
                	// 获取当前Android应用的ContentProvider
                    List<ProviderInfo> providers = data.providers;
                    if (providers != null) {
                        // 安装该Android应用程序的ContentProvider
                        installContentProviders(app, providers);
                        // For process that contains content providers, we want to
                        // ensure that the JIT is enabled "at some point".
                        mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
                    }
                }
    
                // Do this after providers, since instrumentation tests generally start their
                // test thread at this point, and we don't want that racing.
                try {
                	// 调用Instrumentationde的OnCreate方法
                    mInstrumentation.onCreate(data.instrumentationArgs);
                } catch (Exception e) {
                    throw new RuntimeException(
                        "Exception thrown in onCreate() of "
                        + data.instrumentationName + ": " + e.toString(), e);
                }
    
                try {
                	// 调用Application的OnCreate方法
                    mInstrumentation.callApplicationOnCreate(app);
                } catch (Exception e) {
                    if (!mInstrumentation.onException(app, e)) {
                        throw new RuntimeException(
                            "Unable to create application " + app.getClass().getName()
                            + ": " + e.toString(), e);
                    }
                }
            } finally {
                StrictMode.setThreadPolicy(savedPolicy);
            }
        }

    整理一下BDOpener插件的逆向代码,Xposed Hook的插件代码编写如下所示:

    import android.content.pm.ApplicationInfo;
    import android.content.pm.PackageInfo;
    import android.util.Log;
    
    ......
    
    	@Override
    	public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
    		
    		// 打开Android应用的调试模式和备份选项
    		openDB(lpparam);
    		
    ......
    
    
    	boolean isDebugable(ApplicationInfo applicationInfo) {
    		
            if((applicationInfo.flags & 2) != 0) {
                
            	Log.i("BDOpener", "Open Debugable");
            	return true;
            }
            
            Log.i("BDOpener", "Close Debugable");
            return false;
        }
    	
        public boolean isBackup(ApplicationInfo applicationInfo) {
        	
            if((applicationInfo.flags & 32768) != 0) {
            	
                Log.i("BDOpener", "Open Backup");
                return true;
            }
    
            Log.i("BDOpener", "Close Backup");
            return false;
        }
    	
    	// 开启Android应用的调试和备份选项
    	void openDB(LoadPackageParam lpparam) {
    		
    	    /**
    	     * Value for {@link #flags}: set to <code>false</code> if the application does not wish
    	     * to permit any OS-driven backups of its data; <code>true</code> otherwise.
    	     * 
    	     * <p>Comes from the
    	     * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup}
    	     * attribute of the <application> tag.
    	     */
    	    final int FLAG_ALLOW_BACKUP = 1<<15;
    	    
    	    /**
    	     * Value for {@link #flags}: set to true if this application would like to
    	     * allow debugging of its
    	     * code, even when installed on a non-development system.  Comes
    	     * from {@link android.R.styleable#AndroidManifestApplication_debuggable
    	     * android:debuggable} of the <application> tag.
    	     */
    	    final int FLAG_DEBUGGABLE = 1<<1;
    		
    		Class<?> packageManagerService = 
    		    XposedHelpers.findClass("com.android.server.pm.PackageManagerService", lpparam.classLoader);
    		XC_MethodHook callback = new XC_MethodHook() {
    
    			@Override
    			protected void beforeHookedMethod(MethodHookParam param)
    					throws Throwable {
    			}
    
    			@Override
    			protected void afterHookedMethod(MethodHookParam param)
    					throws Throwable {
    				
    				PackageInfo packageInfo = (PackageInfo) param.getResult();
    				if (packageInfo != null) {
    					
    					ApplicationInfo applicationInfo = packageInfo.applicationInfo;
    					int nFlags = applicationInfo.flags;
    					
                                            Log.i("BDOpener", "Load App : " + applicationInfo.packageName);
                                            Log.i("BDOpener", "==== After Hook ====");
    					
        					// 判断当前Android应用是否开启调试模式选项
    					if ((nFlags & FLAG_DEBUGGABLE) == 0) {
    						
    						nFlags |= FLAG_DEBUGGABLE;
    					}
    					// 判断当前Android应用是否开启备份选项
    					if ((nFlags & FLAG_ALLOW_BACKUP) == 0) {
    						
    						nFlags |= FLAG_ALLOW_BACKUP;
    					}
    					
    					applicationInfo.flags = nFlags;
    					// 修改函数的返回值
    					param.setResult(packageInfo);
    					
    					Log.i("BDOpener", "flags = " + nFlags);
                                            isDebugable(applicationInfo);
                                            isBackup(applicationInfo);
    				}
    			}
    			
    		};
    		// 执行java Hook操作
    		XposedBridge.hookAllMethods(packageManagerService, "getPackageInfo", callback);
    	}

    效果截图:

    开启了Android应用的调试模式选项以后,通过DDMS能够看到很多Android应用的进程名称和列表,如下图所示:

    开启了调试模式选项的Android应用,可以使用下面的命令进行调试模式的启动,让Android应用程序等待调试:

    adb shell am start -W -D -n 包名/主Activity类名称字符串

  • 相关阅读:
    SystemVerilog搭建测试平台---第一章:验证导论
    二线制I2C CMOS串行EEPROM续
    二线制I2C CMOS串行EEPROM
    Codeforces 777E:Hanoi Factory(贪心)
    2019HPU-ICPC-Training-1
    Codeforces 777B:Game of Credit Cards(贪心)
    Codeforces 777D:Cloud of Hashtags(暴力,水题)
    Codeforces 777C:Alyona and Spreadsheet(预处理)
    Codeforces 888D: Almost Identity Permutations(错排公式,组合数)
    Codeforces 888E:Maximum Subsequence(枚举,二分)
  • 原文地址:https://www.cnblogs.com/csnd/p/11800588.html
Copyright © 2011-2022 走看看