Jun_22.md

2018-06-22  本文已影响0人  深蓝Yearth

0x00 如何从零开始分析一个 app

在接触到这个任务之前,我从没分析过 app,写过任何一个 Android 应用程序,也没接触过 java,甚至连面向对象编程这个概念都不熟悉。

那么在这样的状况下,要如何开始分析一个 app 呢?

搞清楚需求很重要。

那么任务是什么呢?

梳理三星音乐内的不同模块的源码,找到支撑 APK 功能的主要代码模块,例如下载,播放,搜索,http 链接,文件打开等等,梳理函数之间的调用关系,形成较为完整的模块逻辑,例如播放中有哪几个功能函数,调用逻辑关系,最终以文档形式反馈。

总结一下,我需要完成的任务有:

既然这个任务拆分成了相对比较明确的问题,就可以着手开始尝试解决这些相对明确的问题了。

0x01 反汇编

首先我要能够阅读这款 app 中的代码,然而这是一个打包好的 app,并没有源码,应该怎么办?

答案显而易见,使用反汇编工具对 app 进行反汇编,然后阅读反汇编之后的代码。

一开始我使用的是 APK改之理,这个工具能够对 app 进行反汇编,修改,重新打包等等,功能非常详尽,不过它反汇编出来的是 smail 汇编代码,对于这么大一款 app,如果强行分析汇编代码一定会十分耗时,并且不怎么见得到收益,所以扁哥给我推荐了一款反汇编工具:jadx

目前看来,jadx 完全满足我当前的需求,由于目标 app 并没有加壳和混淆,反汇编出来的代码看起来甚至和源码差不多,安装上有一些小坑,不过问题不大,谷歌一下就能解决。

0x02 定位入口

现在的情况相当于是把一款 app 的源码给我,让我对其分析,然而刚开始的时候,我甚至连语法都搞不懂,直接开始盲目的分析显然也不太现实,所以在豪哥的帮助下开始尝试定位一个入口,从这个入口开始分析。

抓包定位

一开始的时候想的是首先分析下载这个功能。那么如何定位到下载呢?尝试全局搜索 Downloader 并没有什么比较理想的结果。

于是豪哥提出了一个思路:(虽然后面证明这个思路没什么卵用,但确实给了无从下手的我一个比较明确的方法)

通过抓包的手段,抓到这款 app 在下载文件的时候的链接,找到其中特征的部分,在代码中全局搜索,尝试定位到 Downloader 模块。

那么问题就再次细化,当前关心的问题就是:

Fiddler

结合前辈们的经验来看,对手机 app 进行抓包,Fiddler 是个不错的选择。

安装上 Fiddler 没有什么坑,选择好安装路径即可。

在抓包的时候记得先验证手机和运行 Fiddler 的机器在同一网段,能够互相 ping 通即可,最后记得关闭 Win10 的防火墙。

一番折腾之后,成功抓到了 Samsung Music 下载音乐时候的包:

Request Headers
GET /content/01/100/1100050-MP3-128K-FTD.mp3?sign=CQqSIbb5AME7PJqxVitA+/Gax0hhPTEwMDM5ODY3Jms9QUtJRE1Fdm53SXdwNFlqUlU1NHhxd3VLQlRYMExOOWdJVFNRJmU9MTUyODk4Mzg2NiZ0PTE1Mjg5NjIyNjYmcj0xOTM3Nzg3NDQ2JmY9L2NvbnRlbnQvMDEvMTAwLzExMDAwNTAtTVAzLTEyOEstRlRELm1wMyZiPXVsdGltYXRl&transDeliveryCode=SS@4@1528962266@D@20BFE04507592793 HTTP/1.1

然而对这串数据不管怎么删减,都没法搜索到相关的代码。

然后,虽然不太重要,不过验证可知:/content/01/100/1100050-MP3-128K-FTD.mp3 对于同一目标音乐是不变的,并不是随机生成的。

定位 Activity

抓包定位 Downloader 模块的思路失败了,于是豪哥又提出了一个新的思路:

定位目标 app 的某一个存在 Download 按钮的 activity,分析这个 activity,尝试找到下载模块。

有现成的 adb 命令能够实现这个思路:

adb shell dumpsys activity | grep "mFocusedActivity"

然而测试发现,这个命令并不能在目标手机 Samsung S8 中生效,导出 dumpsys activity 分析发现并没有 mFocusedActivity 关键字,于是想了个笨办法:

对比上面的命令能够生效的 dumpsys activity 和 Samsung S8 的 dumpsys activity,找到对于顶层 activity 描述的关键词。

方法虽然笨,效果还不错,最终发现 Samsung S8 的关键字是 mResumedActivity,这里仅记录一下测试过的手机能使用的命令:

adb shell dumpsys activity | grep "mResumedActivity" # 三星 S8
adb shell dumpsys activity | grep "mFocusedActivity" # 坚果 Pro2

分析历程(持续更新ing...)

通过上面的方法,定位到了第一个开始分析的 activity:com.samsung.android.app.music.common.player.PlayerController

首先来看看主类的声明 :(当然一开始我并不知道什么是主类)

public class PlayController implements OnMediaChangeObserver, com.samsung.android.app.music.core.service.mediacenter.OnMediaChangeObserver

可以看出它继承了两个接口:

public interface OnMediaChangeObserver {
    void onExtraChanged(String str, Bundle bundle);

    void onMetaChanged(Meta meta, PlayState playState);

    void onPlayStateChanged(PlayState playState);
}

public interface OnMediaChangeObserver {
    void onExtrasChanged(String str, Bundle bundle);

    void onMetadataChanged(MusicMetadata musicMetadata);

    void onPlaybackStateChanged(MusicPlaybackState musicPlaybackState);

    void onQueueChanged(List<QueueItem> list, Bundle bundle);
}

总共实现了四个方法:

onExtraChanged

public void onExtraChanged(String action, Bundle data) {
        if ("com.samsung.musicplus.action.DRM_REQUEST".equals(action)) {
            updatePlayState(this.mPlayerController.isPlaying());
        }
    }

private void updatePlayState(boolean isPlaying) {
        TalkBackUtils.setContentDescriptionAll(this.mContext, this.mPlay, isPlaying ? R.string.tts_pause : R.string.tts_play);
        if (this.mPlay.isActivated() != isPlaying) {
            this.mPlay.setActivated(isPlaying);
            if (this.mPlayToPauseAnimationResId != -1 && this.mPauseToPlayAnimationResId != -1) {
                ImageView iv = (ImageView) this.mPlay.findViewById(R.id.play_pause_icon);
                if (isPlaying) {
                    iv.setImageResource(this.mPlayToPauseAnimationResId);
                } else {
                    iv.setImageResource(this.mPauseToPlayAnimationResId);
                }
                AnimationDrawable d = (AnimationDrawable) iv.getDrawable();
                if (this.mPlay.isLaidOut()) {
                    d.start();
                } else {
                    iv.setImageDrawable(d.getFrame(d.getNumberOfFrames() - 1));
                }
            }
        }
    }

public static void setContentDescriptionAll(Context context, View v, int stringResId) {
        v.setContentDescription(getButtonDescription(context, stringResId));
        if (DefaultUiUtils.isHoverUiEnabled(context)) {
            HoverPopupWindowCompat.setContent(v, context.getString(stringResId));
        }
    }

onMetaChanged

public void onMetaChanged(Meta m, PlayState s) {
        updatePlayState(s.isPlaying);
        setPrevNextEnabled(m.listCount != 0);
    }

private void setPrevNextEnabled(boolean enabled) {
        float prevNextAlpha = enabled ? 1.0f : 0.37f;
        if (this.mPrev != null) {
            this.mPrev.setEnabled(enabled);
            this.mPrev.setAlpha(prevNextAlpha);
        }
        if (this.mNext != null) {
            this.mNext.setEnabled(enabled);
            this.mNext.setAlpha(prevNextAlpha);
        }
    }

onPlaybackStateChanged

public void onPlaybackStateChanged(MusicPlaybackState s) {
        updatePlayState(s.isSupposedToPlaying());
    }

onQueueChanged

public void onQueueChanged(List<QueueItem> list, Bundle extras) {
    }

经过上面的分析,大致了解一点点关于 Android 编程的内容,不再像一开始那样连一两行代码看起来都头疼,紧接着又分析了一些简单的 activity:

OnAirViewPopupListener

public interface OnAirViewPopupListener {
        View getAirView(View view);
    }

public View getAirView(View v) {
            Context context = this.mActivity.getApplicationContext();
            switch (v.getId()) {
                case R.id.next_btn:
                    String nextTitle = UiUtils.getTitle(context, this.mPlayerController.getNextUri());
                    if (nextTitle == null) {
                        nextTitle = TalkBackUtils.getButtonDescription(context, (int) R.string.tts_next);
                    }
                    return UiUtils.getAirTextView(this.mActivity, nextTitle);
                case R.id.prev_btn:
                    String prevTitle = UiUtils.getTitle(context, this.mPlayerController.getPrevUri());
                    if (prevTitle == null) {
                        prevTitle = TalkBackUtils.getButtonDescription(context, (int) R.string.tts_previous);
                    }
                    return UiUtils.getAirTextView(this.mActivity, prevTitle);
                default:
                    return null;
            }
        }

PlayController

public PlayController(Activity activity, View view, IPlayerController playerController, ForwardRewindInputListener forwardRewindInputListener, MediaChangeObservable mediaChangeObservable, com.samsung.android.app.music.core.service.mediacenter.MediaChangeObservable coreMediaChangeObservable) {
        this.mContext = activity.getApplicationContext();
        this.mPlayerController = playerController;
        this.mPrev = view.findViewById(R.id.prev_btn);
        this.mNext = view.findViewById(R.id.next_btn);
        this.mPlay = view.findViewById(R.id.play_pause_btn);
        ConvertTouchEventListener convertTouchEventListener = new ConvertTouchEventListener();
        if (this.mPrev != null) {
            this.mPrev.setOnKeyListener(convertTouchEventListener);
            this.mPrev.setOnTouchListener(forwardRewindInputListener);
            TalkBackUtils.setContentDescriptionAll(this.mContext, this.mPrev, R.string.tts_previous);
        }
        if (this.mNext != null) {
            this.mNext.setOnKeyListener(convertTouchEventListener);
            this.mNext.setOnTouchListener(forwardRewindInputListener);
            TalkBackUtils.setContentDescriptionAll(this.mContext, this.mNext, R.string.tts_next);
        }
        this.mPlay.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                PlayController.this.mPlayerController.togglePlay();
                if (PlayController.this.mOnPlayClickListener != null) {
                    PlayController.this.mOnPlayClickListener.onClick(v);
                }
            }
        });
        setAirView(activity);
        mediaChangeObservable.registerMediaChangeObserver(this);
    }

随后接触到了 Binder 通信机制,这里问了东哥很多问题,对 Binder 通信机制大致有了一个认识:

getBuffering

public int getBuffering() {
        return ServiceUtils.getBuffering();
    }


public static int getBuffering() {
        try {
            if (sService != null) {
                return sService.buffering();
            }
            return -1;
        } catch (RemoteException e) {
            e.printStackTrace();
            return -1;
        }
    }

public int buffering() throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                try {
                    _data.writeInterfaceToken(Stub.DESCRIPTOR);
                    this.mRemote.transact(23, _data, _reply, 0);
                    _reply.readException();
                    int _result = _reply.readInt();
                    return _result;
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

getNextUri

public String getNextUri() {
        return ServiceUtils.getNextUri();
    }

public static String getNextUri() {
        if (sService == null) {
            return null;
        }
        String uri = null;
        try {
            return sService.getNextUri();
        } catch (RemoteException e) {
            e.printStackTrace();
            return uri;
        }
    }

public String getNextUri() throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                try {
                    _data.writeInterfaceToken(Stub.DESCRIPTOR);
                    this.mRemote.transact(51, _data, _reply, 0);
                    _reply.readException();
                    String _result = _reply.readString();
                    return _result;
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

getPosition

public long getPosition() {
        return ServiceUtils.getPosition();
    }

public static long getPosition() {
        try {
            if (sService != null) {
                return sService.position();
            }
            return -1;
        } catch (RemoteException e) {
            e.printStackTrace();
            return -1;
        }
    }

public long position() throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                try {
                    _data.writeInterfaceToken(Stub.DESCRIPTOR);
                    this.mRemote.transact(22, _data, _reply, 0);
                    _reply.readException();
                    long _result = _reply.readLong();
                    return _result;
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

getPrevUri

public String getPrevUri() {
        return ServiceUtils.getPrevUri();
    }

public static String getPrevUri() {
        if (sService == null) {
            return null;
        }
        String uri = null;
        try {
            return sService.getPrevUri();
        } catch (RemoteException e) {
            e.printStackTrace();
            return uri;
        }
    }

public String getPrevUri() throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                try {
                    _data.writeInterfaceToken(Stub.DESCRIPTOR);
                    this.mRemote.transact(52, _data, _reply, 0);
                    _reply.readException();
                    String _result = _reply.readString();
                    return _result;
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

在分析了这样的方法之后,通过搜索关键字的手段找到了本地实现播放的一些方法:

play

private void play() {
        play(false);
    }


private void play(boolean applyFadeUp) {
        iLog.d(TAG, "play() - mPlayerState : " + this.mPlayerState + ", applyFadeUp : " + applyFadeUp);
        if (!CscFeatures.SUPPORT_MUSIC_PLAYBACK_DURING_CALL && !CallStateChecker.isCallIdle(this.mContext)) {
            iLog.d(TAG, "play() - Can't play during call.");
            this.mOnSoundPlayerChangedListener.onError(1);
        } else if (this.mAudioManager.requestAudioFocus(this.mAudioFocusChangeListener, 3, 1) == 0) {
            iLog.d(TAG, "play() - Can't play because audio focus request is failed.");
        } else if (!isPlaying()) {
            if (canPlayState()) {
                if (applyFadeUp) {
                    this.mPlayerHandler.sendEmptyMessageDelayed(0, 20);
                } else {
                    this.mPlayer.setVolume(this.mMaxVolume, this.mMaxVolume);
                }
                this.mPlayer.start();
                this.mPlayerState = 4;
                this.mOnSoundPlayerStateListener.onPlayStateChanged(this);
                setPlaybackState(getPosition());
                setBatteryTemperatureCheck(true);
                if (USAFeatures.REGIONAL_USA_GATE_ENABLED) {
                    GateMessageUtils.printMessage("AUDI_PLAYING", this.mUri.getPath());
                    return;
                }
                return;
            }
            setDataSource(this.mUri, true);
            this.mOnSoundPlayerStateListener.onMetaChanged(this);
        }
    }

pause

public void pause() {
        iLog.d(TAG, "pause() - mPlayerState : " + this.mPlayerState);
        if (this.mPlayerState == 4) {
            this.mPlayerHandler.removeMessages(0);
            this.mPlayer.pause();
            this.mPlayerState = 5;
            this.mOnSoundPlayerStateListener.onPlayStateChanged(this);
            setPlaybackState(getPosition());
            setBatteryTemperatureCheck(false);
        }
    }

stop

public void stop() {
        iLog.d(TAG, "stop() - mPlayerState : " + this.mPlayerState);
        this.mPlayerHandler.removeMessages(0);
        if (isPlaying()) {
            this.mPlayer.pause();
            this.mPlayerState = 5;
        }
        seek(0);
        this.mPlayer.stop();
        this.mPlayerState = 6;
        this.mOnSoundPlayerStateListener.onPlayStateChanged(this);
        this.mOnSoundPlayerStateListener.onSeekComplete(this);
        reset();
    }

reset

private void reset() {
        iLog.d(TAG, "reset()");
        this.mPlayer.reset();
        this.mPlayerState = 0;
}

seek

public void seek(long position) {
        if (this.mPlayerState > 2) {
            setPlaybackState(position);
            this.mPlayer.seekTo((int) position);
        }
    }

前面的分析虽然找到了很多方法的实现,不过实际上有些杂乱无章,不过这帮助我逐渐找到了分析的节奏和方法,后面逐渐发现,以类为目标来分析是比较清晰的,这里找到了比较关键的一个类:PlayerListManager,顾名思义,播放列表管理器

PlayerListManager

这是一个纯自定义的类,没有继承其他任何类或者接口。

OnListChangeListener

interface OnListChangeListener {
        void onListChanged(boolean z, int i);
    }

getNowPlayingListPosition

public int getNowPlayingListPosition() {
        if (this.mShuffleMode == 1) {
            synchronized (this.mShuffleList) {
                if (!this.mShuffleList.isEmpty() && this.mShufflePlayPos > -1) {
                    this.mPlayPos = ((Integer) this.mShuffleList.get(this.mShufflePlayPos)).intValue();
                }
            }
        }
        Log.d("SMUSIC-SV-List", "getCurrentListPosition : " + this.mPlayPos);
        return this.mPlayPos;
    }

getNextPosition

private int[] getNextPosition(int position, int shufflePosition) {
        if (this.mShuffleMode == 1) {
            if (shufflePosition < this.mShuffleList.size() - 1) {
                shufflePosition++;
            } else {
                shufflePosition = 0;
            }
            if (this.mShuffleList.isEmpty()) {
                position = 0;
            } else {
                position = ((Integer) this.mShuffleList.get(shufflePosition)).intValue();
            }
        } else if (position < this.mPlayListLength - 1) {
            position++;
        } else {
            position = 0;
        }
        return new int[]{position, shufflePosition};
    }

getNextPositionMediaUri

public Uri getNextMediaUri() {
        int position = getNextPosition(this.mPlayPos, this.mShufflePlayPos, false);
        if (this.mPlayList == null || this.mPlayListLength == 0) {
            return null;
        }
        Uri uri = appendWithBaseUri(this.mPlayList[position]);
        iLog.d("SV-List", "getNextMediaUri() Uri : " + uri);
        return uri;
    }

private Uri appendWithBaseUri(long audioId) {
        if (audioId > -1) {
            return ContentUris.withAppendedId(getCurrentBaseUri(), audioId);
        }
        return null;
    }

private Uri getCurrentBaseUri() {
        if (this.mBaseUri == null) {
            changeBaseUri(Tracks.CONTENT_URI);
        }
        return this.mBaseUri;
    }

private void changeBaseUri(Uri uri) {
        registerContentObserver(uri);
        this.mBaseUri = uri;
    }

private void registerContentObserver(Uri uri) {
        if (this.mObserver != null) {
            unregisterContentObserver();
            this.mContentResoler.registerContentObserver(uri, false, this.mObserver);
            this.mIsRegistered = true;
        }
    }

当前分析到这里.......持续分析 ing,之后的分析计划以类为对象进行分析,搞清楚每个类的功能,具体怎么实现的,然后找到其调用关系,应该就能大致理解这个 app 的实现了。

另外通过这两天无数次的使用搜索引擎搜索相关功能发现,如果有过 Music App 的开发经验,在分析相关 app 的时候肯定会有相当深刻的理解。

上一篇 下一篇

猜你喜欢

热点阅读