Android Hook 机制之简单实战

发表于 2年以前  | 总阅读数:2006 次

简介

什么是 Hook

Hook 又叫“钩子”,它可以在事件传送的过程中截获并监控事件的传输,将自身的代码与系统方法进行融入。这样当这些方法被调用时,也就可以执行我们自己的代码,这也是面向切面编程的思想(AOP)。

Hook 分类

1.根据Android开发模式,Native模式(C/C++)和Java模式(Java)区分,在Android平台上

  • Java层级的Hook;
  • Native层级的Hook;

2.根 Hook 对象与 Hook 后处理事件方式不同,Hook还分为:

  • 消息Hook;
  • API Hook;

3.针对Hook的不同进程上来说,还可以分为:

  • 全局Hook;
  • 单个进程Hook;

常见 Hook 框架

在Android开发中,有以下常见的一些Hook框架:

1 . Xposed

通过替换 /system/bin/app_process 程序控制 Zygote 进程,使得 app_process 在启动过程中会加载 XposedBridge.jar 这个 Jar 包,从而完成对 Zygote 进程及其创建的 Dalvik 虚拟机的劫持。 Xposed 在开机的时候完成对所有的 Hook Function 的劫持,在原 Function 执行的前后加上自定义代码。

2 . Cydia Substrate

Cydia Substrate 框架为苹果用户提供了越狱相关的服务框架,当然也推出了 Android 版 。Cydia Substrate 是一个代码修改平台,它可以修改任何进程的代码。不管是用 Java 还是 C/C++(native代码)编写的,而 Xposed 只支持 Hook app_process 中的 Java 函数。

3 . Legend

Legend 是 Android 免 Root 环境下的一个 Apk Hook 框架,该框架代码设计简洁,通用性高,适合逆向工程时一些 Hook 场景。大部分的功能都放到了 Java 层,这样的兼容性就非常好。 原理是这样的,直接构造出新旧方法对应的虚拟机数据结构,然后替换信息写到内存中即可。

Hook 必须掌握的知识

  • 反射

如果你对反射还不是很熟悉的话,建议你先复习一下 java 反射的相关知识。有兴趣的,可以看一下我的这一篇博客 Java 反射机制详解

  • java 的动态代理

动态代理是指在运行时动态生成代理类,不需要我们像静态代理那个去手动写一个个的代理类。在 java 中,我们可以使用 InvocationHandler 实现动态代理,有兴趣的,可以查看我的这一篇博客 java 代理模式详解

本文的主要内容是讲解单个进程的 Hook,以及怎样 Hook。


Hook 使用实例

Hook 选择的关键点

  • Hook 的选择点:尽量静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。

  • Hook 过程:

  • 寻找 Hook 点,原则是尽量静态变量或者单例对象,尽量 Hook public 的对象和方法。

  • 选择合适的代理方式,如果是接口可以用动态代理。

  • 偷梁换柱——用代理对象替换原始对象。

  • Android 的 API 版本比较多,方法和类可能不一样,所以要做好 API 的兼容工作。

简单案例一: 使用 Hook 修改 View.OnClickListener 事件

首先,我们先分析 View.setOnClickListener 源码,找出合适的 Hook 点。可以看到 OnClickListener 对象被保存在了一个叫做 ListenerInfo 的内部类里,其中 mListenerInfo 是 View 的成员变量。ListeneInfo 里面保存了 View 的各种监听事件。因此,我们可以想办法 hook ListenerInfo 的 mOnClickListener 。

 1public void setOnClickListener(@Nullable OnClickListener l) {
 2    if (!isClickable()) {
 3        setClickable(true);
 4    }
 5    getListenerInfo().mOnClickListener = l;
 6}
 7
 8static class ListenerInfo {
 9
10     ---
11
12    ListenerInfo getListenerInfo() {
13        if (mListenerInfo != null) {
14            return mListenerInfo;
15        }
16        mListenerInfo = new ListenerInfo();
17        return mListenerInfo;
18    }
19
20    ---
21}

接下来,让我们一起来看一下怎样 Hook View.OnClickListener 事件?

大概分为三步:

  • 第一步:获取 ListenerInfo 对象

从 View 的源代码,我们可以知道我们可以通过 getListenerInfo 方法获取,于是,我们利用反射得到 ListenerInfo 对象

  • 第二步:获取原始的 OnClickListener事件方法

从上面的分析,我们知道 OnClickListener 事件被保存在 ListenerInfo 里面,同理我们利用反射获取

  • 第三步:偷梁换柱,用 Hook代理类 替换原始的 OnClickListener
1public static void hookOnClickListener(View view) throws Exception {
 2    // 第一步:反射得到 ListenerInfo 对象
 3    Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");
 4    getListenerInfo.setAccessible(true);
 5    Object listenerInfo = getListenerInfo.invoke(view);
 6    // 第二步:得到原始的 OnClickListener事件方法
 7    Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
 8    Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
 9    mOnClickListener.setAccessible(true);
10    View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);
11    // 第三步:用 Hook代理类 替换原始的 OnClickListener
12    View.OnClickListener hookedOnClickListener = new HookedClickListenerProxy(originOnClickListener);
13    mOnClickListener.set(listenerInfo, hookedOnClickListener);
14}
1public class HookedClickListenerProxy implements View.OnClickListener {
 2
 3    private View.OnClickListener origin;
 4
 5    public HookedClickListenerProxy(View.OnClickListener origin) {
 6        this.origin = origin;
 7    }
 8
 9    @Override
10    public void onClick(View v) {
11        Toast.makeText(v.getContext(), "Hook Click Listener", Toast.LENGTH_SHORT).show();
12        if (origin != null) {
13            origin.onClick(v);
14        }
15    }
16
17}

执行以下代码,将会看到当我们点击该按钮的时候,会弹出 toast “Hook Click Listener”

1mBtn1 = (Button) findViewById(R.id.btn_1);
2mBtn1.setOnClickListener(this);
3try {
4    HookHelper.hookOnClickListener(mBtn1);
5} catch (Exception e) {
6    e.printStackTrace();
7}

简单案例二:HooK Notification

发送消息到通知栏的核心代码如下:

1NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
2notificationManager.notify(id, builder.build());

跟踪 notify 方法发现最终会调用到 notifyAsUser 方法

1public void notify(String tag, int id, Notification notification)
2{
3    notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
4}

而在 notifyAsUser 方法中,我们惊喜地发现 service 是一个单例,因此,我们可以想方法 hook 住这个 service,而 notifyAsUser 最终会调用到 service 的 enqueueNotificationWithTag 方法。因此 hook 住 service 的 enqueueNotificationWithTag 方法即可

1public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
 2{
 3    // 
 4    INotificationManager service = getService();
 5    String pkg = mContext.getPackageName();
 6    // Fix the notification as best we can.
 7    Notification.addFieldsFromContext(mContext, notification);
 8    if (notification.sound != null) {
 9        notification.sound = notification.sound.getCanonicalUri();
10        if (StrictMode.vmFileUriExposureEnabled()) {
11            notification.sound.checkFileUriExposed("Notification.sound");
12        }
13    }
14    fixLegacySmallIcon(notification, pkg);
15    if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
16        if (notification.getSmallIcon() == null) {
17            throw new IllegalArgumentException("Invalid notification (no valid small icon): "
18                    + notification);
19        }
20    }
21    if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
22    final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
23    try {
24        service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
25                copy, user.getIdentifier());
26    } catch (RemoteException e) {
27        throw e.rethrowFromSystemServer();
28    }
29}
30
31private static INotificationManager sService;
32
33static public INotificationManager getService()
34{
35    if (sService != null) {
36        return sService;
37    }
38    IBinder b = ServiceManager.getService("notification");
39    sService = INotificationManager.Stub.asInterface(b);
40    return sService;
41}

综上,要 Hook Notification,大概需要三步:

  • 第一步:得到 NotificationManager 的 sService
  • 第二步:因为 sService 是接口,所以我们可以使用动态代理,获取动态代理对象
  • 第三步:偷梁换柱,使用动态代理对象 proxyNotiMng 替换系统的 sService

于是,我们可以写出如下的代码

1public static void hookNotificationManager(final Context context) throws Exception {
 2    NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
 3
 4    Method getService = NotificationManager.class.getDeclaredMethod("getService");
 5    getService.setAccessible(true);
 6    // 第一步:得到系统的 sService
 7    final Object sOriginService = getService.invoke(notificationManager);
 8
 9    Class iNotiMngClz = Class.forName("android.app.INotificationManager");
10    // 第二步:得到我们的动态代理对象
11    Object proxyNotiMng = Proxy.newProxyInstance(context.getClass().getClassLoader(), new
12            Class[]{iNotiMngClz}, new InvocationHandler() {
13
14        @Override
15        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
16            Log.d(TAG, "invoke(). method:" + method);
17            String name = method.getName();
18            Log.d(TAG, "invoke: name=" + name);
19            if (args != null && args.length > 0) {
20                for (Object arg : args) {
21                    Log.d(TAG, "invoke: arg=" + arg);
22                }
23            }
24            Toast.makeText(context.getApplicationContext(), "检测到有人发通知了", Toast.LENGTH_SHORT).show();
25            // 操作交由 sOriginService 处理,不拦截通知
26            return method.invoke(sOriginService, args);
27            // 拦截通知,什么也不做
28            //                    return null;
29            // 或者是根据通知的 Tag 和 ID 进行筛选
30        }
31    });
32    // 第三步:偷梁换柱,使用 proxyNotiMng 替换系统的 sService
33    Field sServiceField = NotificationManager.class.getDeclaredField("sService");
34    sServiceField.setAccessible(true);
35    sServiceField.set(notificationManager, proxyNotiMng);
36
37}

Hook 使用进阶

Hook ClipboardManager

第一种方法

从上面的 hook NotificationManager 例子中,我们可以得知 NotificationManager 中有一个静态变量 sService,这个变量是远端的 service。因此,我们尝试查找 ClipboardManager 中是不是也存在相同的类似静态变量。

查看它的源码发现它存在 mService 变量,该变量是在 ClipboardManager 构造函数中初始化的,而 ClipboardManager 的构造方法用 @hide 标记,表明该方法对调用者不可见。

而我们知道 ClipboardManager,NotificationManager 其实这些都是单例的,即系统只会创建一次。因此我们也可以认为 ClipboardManager 的 mService 是单例的。因此 mService 应该是可以考虑 hook 的一个点。

 1public class ClipboardManager extends android.text.ClipboardManager {
 2    private final Context mContext;
 3    private final IClipboard mService;
 4
 5    /** {@hide} */
 6    public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException {
 7        mContext = context;
 8        mService = IClipboard.Stub.asInterface(
 9                ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));
10    }
11}

接下来,我们再来一个看一下 ClipboardManager 的相关方法 setPrimaryClip , getPrimaryClip

 1public void setPrimaryClip(ClipData clip) {
 2    try {
 3        if (clip != null) {
 4            clip.prepareToLeaveProcess(true);
 5        }
 6        mService.setPrimaryClip(clip, mContext.getOpPackageName());
 7    } catch (RemoteException e) {
 8        throw e.rethrowFromSystemServer();
 9    }
10}
11
12/**
13 * Returns the current primary clip on the clipboard.
14 */
15public ClipData getPrimaryClip() {
16    try {
17        return mService.getPrimaryClip(mContext.getOpPackageName());
18    } catch (RemoteException e) {
19        throw e.rethrowFromSystemServer();
20    }
21}

可以发现这些方法最终都会调用到 mService 的相关方法。因此,ClipboardManager 的 mService 确实是一个可以 hook 的一个点。

hook ClipboardManager.mService 的实现

大概需要三个步骤

  • 第一步:得到 ClipboardManager 的 mService
  • 第二步:初始化动态代理对象
  • 第三步:偷梁换柱,使用 proxyNotiMng 替换系统的 mService
 1public static void hookClipboardService(final Context context) throws Exception {
 2    ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
 3    Field mServiceFiled = ClipboardManager.class.getDeclaredField("mService");
 4    mServiceFiled.setAccessible(true);
 5    // 第一步:得到系统的 mService
 6    final Object mService = mServiceFiled.get(clipboardManager);
 7
 8    // 第二步:初始化动态代理对象
 9    Class aClass = Class.forName("android.content.IClipboard");
10    Object proxyInstance = Proxy.newProxyInstance(context.getClass().getClassLoader(), new
11            Class[]{aClass}, new InvocationHandler() {
12
13        @Override
14        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
15            Log.d(TAG, "invoke(). method:" + method);
16            String name = method.getName();
17            if (args != null && args.length > 0) {
18                for (Object arg : args) {
19                    Log.d(TAG, "invoke: arg=" + arg);
20                }
21            }
22            if ("setPrimaryClip".equals(name)) {
23                Object arg = args[0];
24                if (arg instanceof ClipData) {
25                    ClipData clipData = (ClipData) arg;
26                    int itemCount = clipData.getItemCount();
27                    for (int i = 0; i < itemCount; i++) {
28                        ClipData.Item item = clipData.getItemAt(i);
29                        Log.i(TAG, "invoke: item=" + item);
30                    }
31                }
32                Toast.makeText(context, "检测到有人设置粘贴板内容", Toast.LENGTH_SHORT).show();
33            } else if ("getPrimaryClip".equals(name)) {
34                Toast.makeText(context, "检测到有人要获取粘贴板的内容", Toast.LENGTH_SHORT).show();
35            }
36            // 操作交由 sOriginService 处理,不拦截通知
37            return method.invoke(mService, args);
38
39        }
40    });
41
42    // 第三步:偷梁换柱,使用 proxyNotiMng 替换系统的 mService
43    Field sServiceField = ClipboardManager.class.getDeclaredField("mService");
44    sServiceField.setAccessible(true);
45    sServiceField.set(clipboardManager, proxyInstance);
46
47}

第二种方法

对 Android 源码有基本了解的人都知道,Android 中的各种 Manager 都是通过 ServiceManager 获取的。因此,我们可以通过 ServiceManager hook 所有系统 Manager,ClipboardManager 当然也不例外。

 1public final class ServiceManager {
 2
 3
 4    /**
 5     * Returns a reference to a service with the given name.
 6     * 
 7     * @param name the name of the service to get
 8     * @return a reference to the service, or <code>null</code> if the service doesn't exist
 9     */
10    public static IBinder getService(String name) {
11        try {
12            IBinder service = sCache.get(name);
13            if (service != null) {
14                return service;
15            } else {
16                return getIServiceManager().getService(name);
17            }
18        } catch (RemoteException e) {
19            Log.e(TAG, "error in getService", e);
20        }
21        return null;
22    }
23}

老套路

  • 第一步:通过反射获取剪切板服务的远程Binder对象,这里我们可以通过 ServiceManager getService 方法获得
  • 第二步:创建我们的动态代理对象,动态代理原来的Binder对象
  • 第三步:偷梁换柱,把我们的动态代理对象设置进去
 1public static void hookClipboardService() throws Exception {
 2
 3    //通过反射获取剪切板服务的远程Binder对象
 4    Class serviceManager = Class.forName("android.os.ServiceManager");
 5    Method getServiceMethod = serviceManager.getMethod("getService", String.class);
 6    IBinder remoteBinder = (IBinder) getServiceMethod.invoke(null, Context.CLIPBOARD_SERVICE);
 7
 8    //新建一个我们需要的Binder,动态代理原来的Binder对象
 9    IBinder hookBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
10            new Class[]{IBinder.class}, new ClipboardHookRemoteBinderHandler(remoteBinder));
11
12    //通过反射获取ServiceManger存储Binder对象的缓存集合,把我们新建的代理Binder放进缓存
13    Field sCacheField = serviceManager.getDeclaredField("sCache");
14    sCacheField.setAccessible(true);
15    Map<String, IBinder> sCache = (Map<String, IBinder>) sCacheField.get(null);
16    sCache.put(Context.CLIPBOARD_SERVICE, hookBinder);
17
18}
 1public class ClipboardHookRemoteBinderHandler implements InvocationHandler {
 2
 3    private IBinder remoteBinder;
 4    private Class iInterface;
 5    private Class stubClass;
 6
 7    public ClipboardHookRemoteBinderHandler(IBinder remoteBinder) {
 8        this.remoteBinder = remoteBinder;
 9        try {
10            this.iInterface = Class.forName("android.content.IClipboard");
11            this.stubClass = Class.forName("android.content.IClipboard$Stub");
12        } catch (Exception e) {
13            e.printStackTrace();
14        }
15    }
16
17    @Override
18    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
19        Log.d("RemoteBinderHandler", method.getName() + "() is invoked");
20        if ("queryLocalInterface".equals(method.getName())) {
21            //这里不能拦截具体的服务的方法,因为这是一个远程的Binder,还没有转化为本地Binder对象
22            //所以先拦截我们所知的queryLocalInterface方法,返回一个本地Binder对象的代理
23            return Proxy.newProxyInstance(remoteBinder.getClass().getClassLoader(),
24                    new Class[]{this.iInterface},
25                    new ClipboardHookLocalBinderHandler(remoteBinder, stubClass));
26        }
27
28        return method.invoke(remoteBinder, args);
29    }
30}

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/oT1KkFky5urZWDSQHDn-_w

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:7月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:7月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:7月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:7月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:7月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:7月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:7月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:7月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:7月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:7月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:7月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:7月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:7月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:7月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:7月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:7月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:7月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:7月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:7月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:7月以前  |  398次阅读  |  详细内容 »
 相关文章
简化Android的UI开发 4年以前  |  520700次阅读
Android 深色模式适配原理分析 3年以前  |  28625次阅读
Android阴影实现的几种方案 1年以前  |  10784次阅读
Android 样式系统 | 主题背景覆盖 3年以前  |  9586次阅读
 目录