DroidPlugin之Hook機(jī)制

在DroidPlugin源碼中,作者提供了一份關(guān)于其實(shí)現(xiàn)原理的PPT講義,這里我把其中介紹基本實(shí)現(xiàn)原理的章節(jié)貼出來(lái)。

以下用DP代指DroidPlugin

DP基本原理.png

本文主要介紹Hook機(jī)制,看下作者是如何做到“偷天換日”。

Hook機(jī)制

我認(rèn)為,DP所指的“Hook”和傳統(tǒng)意義上的Hook是有點(diǎn)區(qū)別的,例如在API Hook中提到這樣一句話“通過(guò)hook‘接觸’到需要修改的api函數(shù)入口點(diǎn),改變它的地址指向新的自定義的函數(shù)”。Window上的API Hook可能會(huì)導(dǎo)致被hook的函數(shù)不被執(zhí)行(不知道我這樣理解對(duì)不對(duì))。而DP的Hook機(jī)制,被hook的函數(shù)是會(huì)被執(zhí)行的。閱讀DP源碼過(guò)程中我就有這樣的疑問(wèn),那DP的Hook到底做了啥!

Java動(dòng)態(tài)代理

再回答上面的疑問(wèn)之前,我們需要了解“Java動(dòng)態(tài)代理”這個(gè)玩意。關(guān)于動(dòng)態(tài)代理的資料有很多,這里我就簡(jiǎn)單介紹下。
Java動(dòng)態(tài)代理是Java反射提供的一個(gè)強(qiáng)大功能,使用動(dòng)態(tài)代理,我們可以在被hook方法執(zhí)行前后,做些額外操作。也可以修改被hook方法的入?yún)?,修改方法返回值。下面舉個(gè)最簡(jiǎn)單的代理例子,來(lái)說(shuō)明代理能做什么。
首先定義一個(gè)接口IShopping,聲明一個(gè)購(gòu)物的方法shopping。

package com.kisson.proxy;

public interface IShopping {
    void shopping(String money);
}

接著定義一個(gè)類(lèi)GoShopping,實(shí)現(xiàn)IShopping的shopping方法。

package com.kisson.proxy;

public class GoShopping implements IShopping{

    @Override
    public void shopping() {
        // TODO Auto-generated method stub
        System.out.println("我去逛街咯!"+" 我有"+money+"元");
    }
}

接著我們需要利用動(dòng)態(tài)代理去hook接口IShopping的shopping方法。然后定義一個(gè)IShoppingHook類(lèi)并實(shí)現(xiàn)InvocationHandler接口

package com.kisson.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class IShoppingHook implements InvocationHandler {

    private Object mHookedObject;

    public IShoppingHook(Object hookedObject) {
        this.mHookedObject = hookedObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // 方法執(zhí)行前,做預(yù)處理
        beforeHook();
        //錢(qián)越多越好?。。?        args[0] = "2000000";
       //在這里就是執(zhí)行shopping方法的過(guò)程,通過(guò)反射來(lái)完成
        Object result = method.invoke(mHookedObject, args);
        // 方法執(zhí)行后,做收尾處理
        afterHook();
        return result;
    }

    public void beforeHook() {
        // todo:something
        System.out.println("工作都已經(jīng)做完了,可以安心購(gòu)物咯!");
    }

    public void afterHook() {
        // todo:something
        System.out.println("買(mǎi)的真開(kāi)心!");
    }
}

上面的準(zhǔn)備工作就緒,接著看如何實(shí)現(xiàn)。

package com.kisson.proxy;

import java.lang.reflect.Proxy;

public class ProxyTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ProxyTest test = new ProxyTest();
        IShopping shopping = new GoShopping();
        test.newProxyInstance(shopping).shopping("100");;
        
    }

    private IShopping newProxyInstance(IShopping shopping) {
        return (IShopping) Proxy.newProxyInstance(shopping.getClass()
                .getClassLoader(), shopping.getClass().getInterfaces(),
                new IShoppingHook(shopping));
    }

}

執(zhí)行結(jié)果如下:

工作都已經(jīng)做完了,可以安心購(gòu)物咯!
我去逛街咯! 我有2000000元
買(mǎi)的真開(kāi)心!

以上動(dòng)態(tài)代理的過(guò)程我們做了兩件事:
1.在shopping方法執(zhí)行前后,分別執(zhí)行了beforeHook和afterHook方法。
2.改變了shopping方法的入?yún)?,本?lái)只有100元,最終結(jié)果卻得到了2000000元!
DP使用Hook機(jī)制完成了三個(gè)工作:

  1. 動(dòng)態(tài)代理實(shí)現(xiàn)函數(shù)hook。
  2. Binder代理繞過(guò)部分系統(tǒng)服務(wù)限制。
  3. IO重定向

以后針對(duì)這三個(gè)點(diǎn)分別介紹下。

Binder代理繞過(guò)部分系統(tǒng)服務(wù)限制

原理

        LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);

上述代碼,是獲取系統(tǒng)位置服務(wù)。如果我們需要獲取類(lèi)似的系統(tǒng)服務(wù),都是通過(guò)Context的getSystemService方法來(lái)獲取。

getSystemService類(lèi)圖

getSystemService方法的是在ContextImpl類(lèi)中實(shí)現(xiàn)的。很久沒(méi)看源碼,看了下源碼發(fā)現(xiàn)該方法的實(shí)現(xiàn)被修改了。

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

這里出現(xiàn)了一個(gè)SystemServiceRegistry類(lèi),其作用是用來(lái)管理所有通過(guò)getSystemService返回的系統(tǒng)服務(wù)。
在類(lèi)中SystemServiceRegistry類(lèi)中有很大一坨靜態(tài)代碼塊,用于注冊(cè)各種系統(tǒng)服務(wù)。

//位置服務(wù)的注冊(cè)
registerService(Context.LOCATION_SERVICE, LocationManager.class,
                new CachedServiceFetcher<LocationManager>() {
            @Override
            public LocationManager createService(ContextImpl ctx) {
                IBinder b = ServiceManager.getService(Context.LOCATION_SERVICE);
                return new LocationManager(ctx, ILocationManager.Stub.asInterface(b));
            }});

在這里我們可以看到LocationManager的創(chuàng)建過(guò)程了,通過(guò)調(diào)用ServiceManager的getService方法來(lái)創(chuàng)建IBinder對(duì)象,作為L(zhǎng)ocationManager構(gòu)造方法的參數(shù)。在ContexImpl類(lèi)中有個(gè)mServiceCache對(duì)象,用于保存創(chuàng)建的各種XXManager對(duì)象,類(lèi)似單例模式吧。

public final class ServiceManager {
    private static final String TAG = "ServiceManager";

    private static IServiceManager sServiceManager;
    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }

    /**
     * Returns a reference to a service with the given name.
     * 
     * @param name the name of the service to get
     * @return a reference to the service, or <code>null</code> if the service doesn't exist
     */
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }
//....省略部分代碼
}

通過(guò)ServiceManager源碼,可以知道:

  1. 使用sCache對(duì)象來(lái)保存系統(tǒng)中已經(jīng)創(chuàng)建的IBinder。
  2. getService方法中,首先會(huì)在sCache中去查找是否有緩存,如果沒(méi)有,則調(diào)用IServiceManager的getService方法創(chuàng)建IBinder。

DP hook系統(tǒng)服務(wù)的原理:

  1. 對(duì)ServiceManager進(jìn)行操作,反射得到sCache對(duì)象,將系統(tǒng)已經(jīng)原有的系統(tǒng)移除掉(Remove方法),接著再通過(guò)反射調(diào)用ServiceManager的getService方法獲取新的服務(wù)(IBinder),并在此加入sCache。
  2. 將系統(tǒng)服務(wù)中(IBinder)某些有執(zhí)行限制的方法,進(jìn)行動(dòng)態(tài)代理處理,在這些方法執(zhí)行的前后進(jìn)行“欺騙”操作。

總結(jié)起來(lái)就是偷天換日、欺上瞞下!

源碼分析之偷天換日

首先來(lái)看看“偷天換日”的類(lèi)圖。


“偷天換日”類(lèi)圖

結(jié)合類(lèi)圖,我們來(lái)說(shuō)明下各個(gè)類(lèi)的作用。

1.Hook類(lèi)

import android.content.Context;

/**
 * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/2.
 * ---------------------------------
 * Hook類(lèi)是所有需要進(jìn)行Hook操作類(lèi)的基類(lèi),直接子類(lèi)有BinderHook,
 * ServiceManagerCacheBinderHook,ProxyHook,SQLiteDatabaseHook等
 */
public abstract class Hook {
    //mEnable用于標(biāo)識(shí)是否開(kāi)啟Hook操作,如果不開(kāi)啟,則走系統(tǒng)自由流程
    private boolean mEnable = false;
    //宿主的上下文環(huán)境
    protected Context mHostContext;

    protected BaseHookHandle mHookHandles;

    public void setEnable(boolean enable, boolean reInstallHook) {
        this.mEnable = enable;
    }

    public final void setEnable(boolean enable) {
        setEnable(enable, false);
    }

    public boolean isEnable() {
        return mEnable;
    }


    protected Hook(Context hostContext) {
        mHostContext = hostContext;
        mHookHandles = createHookHandle();
    }

    //創(chuàng)建BaseHookHandle對(duì)象
    protected abstract BaseHookHandle createHookHandle();

    /**
     * 子類(lèi)實(shí)現(xiàn)onInstall方法,主要用于替換ServiceManager中sCache已經(jīng)存在的系統(tǒng)服務(wù)。
     * 首先移除已經(jīng)存在的系統(tǒng)服務(wù)IBinder對(duì)象,然后自己生成這些系統(tǒng)服務(wù)的IBinder對(duì)象,并放入sCache對(duì)象中。
     * onInstall方法可以理解為“安裝Fake服務(wù)的過(guò)程”
     */
    protected abstract void onInstall(ClassLoader classLoader) throws Throwable;

    protected void onUninstall(ClassLoader classLoader) throws Throwable {

    }
}

2.BaseHookHandle

import android.content.Context;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/28.
 * 所有IXXXBinderHook的基類(lèi),該類(lèi)的作用主要是負(fù)責(zé)HookedMethodHandler的“載入”、“讀取”
 */
public abstract class BaseHookHandle {
    //宿主的上下文
    protected Context mHostContext;
    //HookedMethodHandler的對(duì)象緩存集合
    protected Map<String, HookedMethodHandler> sHookedMethodHandlers = new HashMap<String, HookedMethodHandler>(5);

    public BaseHookHandle(Context hostContext) {
        mHostContext = hostContext;
        init();
    }
    //將所有HookedMethodHandler對(duì)象存入到sHookedMethodHandlers中
    protected abstract void init();

    //獲取sHookedMethodHandlers中所有key值
    public Set<String> getHookedMethodNames(){
        return sHookedMethodHandlers.keySet();
    }

    //根據(jù)sHookedMethodHandlers的key值獲取對(duì)應(yīng)的HookedMethodHandler對(duì)象
    public HookedMethodHandler getHookedMethodHandler(Method method) {
        if (method != null) {
            return sHookedMethodHandlers.get(method.getName());
        } else {
            return null;
        }
    }
}

3.HookedMethodHandler

import android.content.Context;
import com.morgoo.helper.Log;
import java.lang.reflect.Method;

/**
 * 該類(lèi)可以說(shuō)整個(gè)Hook機(jī)制的核心,前面在介紹動(dòng)態(tài)代理的時(shí)候,我們聲明了IShoppingHook類(lèi)并實(shí)現(xiàn)InvocationHandler接口,
 * 在其invoke方法中,通過(guò)發(fā)射機(jī)制調(diào)用被Hook的方法并在其調(diào)用前后分別調(diào)用了beforeHook和afterHook方法。
 * 那么本類(lèi)的作用也是如此:在InvocationHandler的invoke方法需要調(diào)用的被Hook方法的過(guò)程,挪到doHookInner中實(shí)現(xiàn)。
 * 在真正調(diào)用被Hook的方法前后,做一些處理,以繞過(guò)系統(tǒng)限制!
 * **/
public class HookedMethodHandler {

    private static final String TAG = HookedMethodHandler.class.getSimpleName();

    protected final Context mHostContext;
    //Hook后的fake服務(wù)(IBinder對(duì)象)
    private Object mFakedResult = null;
    //是否使用fake服務(wù)
    private boolean mUseFakedResult = false;

    public HookedMethodHandler(Context hostContext) {
        this.mHostContext = hostContext;
    }


    public synchronized Object doHookInner(Object receiver, Method method, Object[] args) throws Throwable {
        long b = System.currentTimeMillis();
        try {
            mUseFakedResult = false;
            mFakedResult = null;
            boolean suc = beforeInvoke(receiver, method, args);
            Object invokeResult = null;
            if (!suc) {
                invokeResult = method.invoke(receiver, args);
            }
            afterInvoke(receiver, method, args, invokeResult);
            if (mUseFakedResult) {
                return mFakedResult;
            } else {
                return invokeResult;
            }
        } finally {
            long time = System.currentTimeMillis() - b;
            if (time > 5) {
                Log.i(TAG, "doHookInner method(%s.%s) cost %s ms", method.getDeclaringClass().getName(), method.getName(), time);
            }
        }
    }

    public void setFakedResult(Object fakedResult) {
        this.mFakedResult = fakedResult;
        mUseFakedResult = true;
    }

    /**
     * 在某個(gè)方法被調(diào)用之前執(zhí)行,如果返回true,則不執(zhí)行原始的方法,否則執(zhí)行原始方法
     */
    protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
        return false;
    }

    protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable {
    }

    public boolean isFakedResult() {
        return mUseFakedResult;
    }

    public Object getFakedResult() {
        return mFakedResult;
    }
}

4.ServiceManagerCacheBinderHook

**
 * Created by Andy Zhang(zhangyong232@gmail.com) on 2015/3/2.
 * 本類(lèi)主要完成對(duì)系統(tǒng)ServiceManager類(lèi)中的對(duì)象和方法進(jìn)行hook操作。
 * 包括:1.從sCache對(duì)象中remove掉已經(jīng)存在的系統(tǒng)服務(wù)Binder對(duì)象
 * 2.生成fake系統(tǒng)服務(wù)Binder對(duì)象,并加入到sCache對(duì)象中
 */
public class ServiceManagerCacheBinderHook extends Hook implements InvocationHandler {

    //系統(tǒng)服務(wù)名字,如ActivityManager的“activity”
    private String mServiceName;

    public ServiceManagerCacheBinderHook(Context hostContext, String servicename) {
        super(hostContext);
        mServiceName = servicename;
        setEnable(true);
    }


    @Override
    protected void onInstall(ClassLoader classLoader) throws Throwable {
        //通過(guò)反射獲取sCache對(duì)象
        Object sCacheObj = FieldUtils.readStaticField(ServiceManagerCompat.Class(), "sCache");
        //判斷sCacheObj類(lèi)型是否是Map
        if (sCacheObj instanceof Map) {
            Map sCache = (Map) sCacheObj;
            //通過(guò)Service Name獲取對(duì)應(yīng)系統(tǒng)服務(wù)的IBinder對(duì)象
            Object Obj = sCache.get(mServiceName);
            if (Obj != null && false) {
                //FIXME 已經(jīng)有了怎么處理?這里我們只是把原來(lái)的給remove掉,再添加自己的。程序下次取用的時(shí)候就變成我們hook過(guò)的了。
                //但是這樣有缺陷。
                //add by me
                // 作者這個(gè)疑問(wèn)是需要解決。因?yàn)橄到y(tǒng)原有的已經(jīng)被替換了,等于后續(xù)其他app使用系統(tǒng)服務(wù)的時(shí)候都是被替換的IBinder對(duì)象
                //雖然說(shuō)沒(méi)有啥大影響,但是還是要做到善始善終。how to do?
                throw new RuntimeException("Can not install binder hook for " + mServiceName);
            } else {
                //移除掉mServiceName對(duì)應(yīng)的系統(tǒng)服務(wù)IBinder對(duì)象
                sCache.remove(mServiceName);
                //這里又重新創(chuàng)建了對(duì)應(yīng)的系統(tǒng)服務(wù),注意這里不是被Hook過(guò)的,是通過(guò)反射調(diào)用ServiceManager的getService方法獲取
                //到的。這里多這一步的目的,是獲取由系統(tǒng)創(chuàng)建的服務(wù),因?yàn)樯弦徊奖晃覀價(jià)emove調(diào)用的系統(tǒng)服務(wù)也有可能是被Hook過(guò)的。
                IBinder mServiceIBinder = ServiceManagerCompat.getService(mServiceName);
                if (mServiceIBinder != null) {
                    /**
                     *這里出現(xiàn)一個(gè)MyServiceManager類(lèi),該類(lèi)可以理解為類(lèi)似ServiceManger的存在。
                     * 將原始的mServiceIBinder對(duì)象通過(guò)addOriginService加入到緩存中
                     * */
                    MyServiceManager.addOriginService(mServiceName, mServiceIBinder);
                    //以下使用Java動(dòng)態(tài)代理來(lái)完成生成fake IBinder的過(guò)程
                    Class clazz = mServiceIBinder.getClass();
                    List<Class<?>> interfaces = Utils.getAllInterfaces(clazz);
                    Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
                    IBinder mProxyServiceIBinder = (IBinder) MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, this);
                    sCache.put(mServiceName, mProxyServiceIBinder);
                    MyServiceManager.addProxiedServiceCache(mServiceName, mProxyServiceIBinder);
                }
            }
        }
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //這里比較簡(jiǎn)單,就不說(shuō)明了!
            IBinder originService = MyServiceManager.getOriginService(mServiceName);
            if (!isEnable()) {
                return method.invoke(originService, args);
            }
            HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method);
            if (hookedMethodHandler != null) {
                return hookedMethodHandler.doHookInner(originService, method, args);
            } else {
                return method.invoke(originService, args);
            }
        }
        //打印各種異常!
        catch (InvocationTargetException e) {
            Throwable cause = e.getTargetException();
            if (cause != null && MyProxy.isMethodDeclaredThrowable(method, cause)) {
                throw cause;
            } else if (cause != null) {
                RuntimeException runtimeException = !TextUtils.isEmpty(cause.getMessage()) ? new RuntimeException(cause.getMessage()) : new RuntimeException();
                runtimeException.initCause(cause);
                throw runtimeException;
            } else {
                RuntimeException runtimeException = !TextUtils.isEmpty(e.getMessage()) ? new RuntimeException(e.getMessage()) : new RuntimeException();
                runtimeException.initCause(e);
                throw runtimeException;
            }
        } catch (IllegalArgumentException e) {
            try {
                StringBuilder sb = new StringBuilder();
                sb.append(" DROIDPLUGIN{");
                if (method != null) {
                    sb.append("method[").append(method.toString()).append("]");
                } else {
                    sb.append("method[").append("NULL").append("]");
                }
                if (args != null) {
                    sb.append("args[").append(Arrays.toString(args)).append("]");
                } else {
                    sb.append("args[").append("NULL").append("]");
                }
                sb.append("}");

                String message = e.getMessage() + sb.toString();
                throw new IllegalArgumentException(message, e);
            } catch (Throwable e1) {
                throw e;
            }
        } catch (Throwable e) {
            if (MyProxy.isMethodDeclaredThrowable(method, e)) {
                throw e;
            } else {
                RuntimeException runtimeException = !TextUtils.isEmpty(e.getMessage()) ? new RuntimeException(e.getMessage()) : new RuntimeException();
                runtimeException.initCause(e);
                throw runtimeException;
            }
        }
    }

    /**
     * 前面有提到過(guò)BaseHookHandle主要是用來(lái)管理HookedMethodHandler對(duì)象的。
     * 那么這里的ServiceManagerHookHandle類(lèi)是handle IBinder中“queryLocalInterface”方法的hook
     * 該類(lèi)中有一個(gè)queryLocalInterface內(nèi)部類(lèi),繼承自HookedMethodHandler,前面有提到過(guò),HookedMethodHandler類(lèi)作用
     * 主要是在調(diào)用被hook方法前后進(jìn)行某些處理。例如這里就是在IBinder調(diào)用queryLocalInterface方法后進(jìn)行處理:
     * 設(shè)置被hook后的系統(tǒng)服務(wù)。
     * queryLocalInterface返回的是IInterface對(duì)象(一定要注意IInterface和IBinder的區(qū)別,
     * 在MyServiceManager中針對(duì)這兩個(gè)對(duì)象進(jìn)行了不同緩存),那么在這里就是IServiceManager對(duì)象(IServiceManager繼承自IInterface);
     * 花絮:DP中,作者寫(xiě)了一個(gè)ServiceManagerBinderHook類(lèi),其實(shí)這個(gè)類(lèi)原本是用來(lái)Hook這個(gè)IServiceManager對(duì)象然后fake。
     * 估計(jì)后來(lái)發(fā)現(xiàn)通過(guò)IBinder的queryLocalInterface方法可以生成IServiceManager,就棄用了ServiceManagerBinderHook類(lèi),所以在DP
     * 中,這個(gè)類(lèi)沒(méi)有被使用過(guò)!?。?     */
    private class ServiceManagerHookHandle extends BaseHookHandle {

        private ServiceManagerHookHandle(Context context) {
            super(context);
        }

        @Override
        protected void init() {
            //創(chuàng)建IBinder的方法
            sHookedMethodHandlers.put("queryLocalInterface", new queryLocalInterface(mHostContext));
        }


        class queryLocalInterface extends HookedMethodHandler {
            public queryLocalInterface(Context context) {
                super(context);
            }

            //在queryLocalInterface方法調(diào)用后,調(diào)用setFakedResult方法,設(shè)置fake過(guò)的系統(tǒng)服務(wù)
            @Override
            protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable {
                Object localInterface = invokeResult;
                Object proxiedObj = MyServiceManager.getProxiedObj(mServiceName);
                if (localInterface == null && proxiedObj != null) {
                    setFakedResult(proxiedObj);
                }
            }
        }
    }

    @Override
    protected BaseHookHandle createHookHandle() {
        return new ServiceManagerHookHandle(mHostContext);
    }
}

Hook、HookedMethodHandler、BinderHook這個(gè)三基類(lèi)共同協(xié)作完成了偷天換日的操作。其實(shí)只要理解這個(gè)三個(gè)類(lèi)的功能是什么,就很好理解DP中Hook的精髓!

小結(jié)

越深入閱讀DP源碼,就越佩服作者思路。雖然原理看起來(lái)很簡(jiǎn)單,但是其中所付出時(shí)間可見(jiàn)一斑。
這節(jié)筆記就暫時(shí)做到這里,因?yàn)榘婷嬉呀?jīng)很長(zhǎng)了。希望各位不吝嗇你的喜歡!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容