Android Vulnerabilities Revealed-Survey on Android Screencasting scrcpy Supporting Sound Parts-Maximum Framework Screencasting Development

hi, fans and friends!

I just used the new version of scrcpy today, and found that the new version of scrcpy supports audio. I have to say that this makes me very interested. Here I just give the relevant research results.
More framework dry goods knowledge hands-on teaching

Log.i("qq群",“422901085);

1. How to get audio by scrcpy

Everyone knows that to get the system sound, you generally need to get the system sound through the following interface. The class is what we call internal recording:
builder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX);
but this REMOTE_SUBMIX requires permission, because it belongs to A high-risk behavior, you want any third-party app to record your mobile phone system sound at will,
so scrcpy can get the sound, in fact, it is related to the Android system permission vulnerability (maybe it is not a loophole, it is just being used by scrcpy I just found out that this can be done)
2. Analysis of scrcpy audio acquisition principle
After scrcpy code analysis, it is found that the actual implementation principle is this kind of
builder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX); recording method:

 private static AudioRecord createAudioRecord() {
    
    
        AudioRecord.Builder builder = new AudioRecord.Builder();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    
    
            // On older APIs, Workarounds.fillAppInfo() must be called beforehand
            builder.setContext(FakeContext.get());
        }
        builder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX);
        builder.setAudioFormat(createAudioFormat());
        int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, FORMAT);
        // This buffer size does not impact latency
        builder.setBufferSizeInBytes(8 * minBufferSize);
        return builder.build();
    }

But this call is actually very simple, the key is the permission, why this scrcpy can get this REMOTE_SUBMIX

    <!-- Allows an application to capture audio output.
         Use the {@code CAPTURE_MEDIA_OUTPUT} permission if only the {@code USAGE_UNKNOWN}),
         {@code USAGE_MEDIA}) or {@code USAGE_GAME}) usages are intended to be captured.
         <p>Not for use by third-party applications.</p> -->
    <permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"
        android:protectionLevel="signature|privileged|role" />

It is obvious that platform signature and privileged are needed here,
but scrcpy does not have these
, so how can it be called normally?
Here I go through the relevant permissions

 @Override
        @PermissionCheckerManager.PermissionResult
        public int checkPermission(@NonNull String permission,
                @NonNull AttributionSourceState attributionSourceState, @Nullable String message,
                boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource,
                int attributedOp) {
    
    
            Objects.requireNonNull(permission);
            Objects.requireNonNull(attributionSourceState);
            final AttributionSource attributionSource = new AttributionSource(
                    attributionSourceState);
            android.util.Log.i("lsm","checkPermission permission " + permission + " attributionSource = " + attributionSource);
            final int result = checkPermission(mContext, mPermissionManagerServiceInternal,
                    permission, attributionSource, message, forDataDelivery, startDataDelivery,
                    fromDatasource, attributedOp);
            // Finish any started op if some step in the attribution chain failed.
            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED
                    && result != PermissionChecker.PERMISSION_SOFT_DENIED) {
    
    
                if (attributedOp == AppOpsManager.OP_NONE) {
    
    
                    finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
                            attributionSource.asState(), fromDatasource);
                } else {
    
    
                    finishDataDelivery(attributedOp, attributionSource.asState(), fromDatasource);
                }
            }
            return result;
        }

After adding the print is as follows:

03-07 18:09:07.367  2240  2597 I lsm     : checkPermission permission android.permission.CAPTURE_AUDIO_OUTPUT attributionSource = AttributionSource {
    
     uid = 2000, packageName = com.android.shell, attributionTag = null, token = android.os.BinderProxy@584a956, next = null }

It is found that the uid 2000 detected here can be understood by everyone, because the user of scrcpy itself is the shell, but where does the packageName = com.android.shell come from?
Here we need to see, even if it is this package name, how can we get CAPTURE_AUDIO_OUTPUT?

1. Here first solve the first why packageName = com.android.shell

On the s, it is actually relying on this

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    
    
            // On older APIs, Workarounds.fillAppInfo() must be called beforehand
            builder.setContext(FakeContext.get());
        }

The FakeContext code is as follows:

public final class FakeContext extends ContextWrapper {
    
    

    public static final String PACKAGE_NAME = "com.android.shell";
    public static final int ROOT_UID = 0; // Like android.os.Process.ROOT_UID, but before API 29

    private static final FakeContext INSTANCE = new FakeContext();

    public static FakeContext get() {
    
    
        return INSTANCE;
    }

    private FakeContext() {
    
    
        super(null);
    }

    @Override
    public String getPackageName() {
    
    
        return PACKAGE_NAME;
    }

    @Override
    public String getOpPackageName() {
    
    
        return PACKAGE_NAME;
    }

    @TargetApi(Build.VERSION_CODES.S)
    @Override
    public AttributionSource getAttributionSource() {
    
    
        AttributionSource.Builder builder = new AttributionSource.Builder(Process.SHELL_UID);
        builder.setPackageName(PACKAGE_NAME);
        return builder.build();
    }
}

Therefore, it is generally known that a Context was passed in when the call was made.

Of course, there will be doubts at the same time, so if it is not passed, is it okay? Android 12 is not passed, why is it possible, in fact, I have verified here that even if
builder.setContext(FakeContext.get()) is blocked; in fact, it is normal to have a packageName = com.android.shell,
so it seems that this Context setting is just Not the most critical, there must be other places to set it up. After searching, I found that it is also set here. If the Context is null:

private AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
            int sessionId, @Nullable Context context,
            int maxSharedAudioHistoryMs) throws IllegalArgumentException {
    
    
        //省略

        AttributionSource attributionSource = (context != null)
                ? context.getAttributionSource() : AttributionSource.myAttributionSource();
        //省略
    }

  public static @NonNull AttributionSource myAttributionSource() {
    
    
//省略
        try {
    
    
            if (uid == Process.SHELL_UID) {
    
    
                android.util.Log.i("lsm","uid Process.SHELL_UID packageName = "+ AppGlobals.getPackageManager().getPackagesForUid(uid)[0]);
            }
            //这里最为关键,或根据uid从AppGlobals.getPackageManager().getPackagesForUid查询的第一个作为packageName
            return new AttributionSource.Builder(uid)
                .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
                .build();
        } catch (Exception ignored) {
    
    
        }

        throw new IllegalStateException("Failed to resolve AttributionSource");
    }

Join the corresponding print as follows:

03-07 18:09:07.356  4700  4730 I lsm     : uid Process.SHELL_UID packageName = com.android.shell

2. Why com.android.shell can get permission?

com.android.shell is actually Shell.apk. Of course, Shell.apk belongs to the platform signature, so it can be said that it can be authorized by the system. Of course, there must be a corresponding permission statement in the manifest. Haha, in fact, it is like this The truth is as follows:

insert image description here

Then scrcpy can support audio as a whole and decrypt it
insert image description here

Guess you like

Origin blog.csdn.net/learnframework/article/details/130797656