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类名称字符串

  • 相关阅读:
    CodeForces Gym 100500A A. Poetry Challenge DFS
    CDOJ 486 Good Morning 傻逼题
    CDOJ 483 Data Structure Problem DFS
    CDOJ 482 Charitable Exchange bfs
    CDOJ 481 Apparent Magnitude 水题
    Codeforces Gym 100637G G. #TheDress 暴力
    Gym 100637F F. The Pool for Lucky Ones 暴力
    Codeforces Gym 100637B B. Lunch 找规律
    Codeforces Gym 100637A A. Nano alarm-clocks 前缀和
    TC SRM 663 div2 B AABB 逆推
  • 原文地址:https://www.cnblogs.com/csnd/p/11800588.html
Copyright © 2011-2022 走看看