也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大

少走了弯路,也就错过了风景,无论如何,感谢经历


ATTACK付费专栏长期更新,本篇最新内容请前往:

  • [车联网安全自学篇] ATTACK安全之检测车机中ADB远程调试控制Android系统攻击

0x01 前言

我们常说的adb,也称为Android 调试桥 (adb) 是一种功能多样的命令行工具,可让您与设备进行通信。adb 命令可用于执行各种设备操作(例如安装和调试应用),并提供对 Unix shell(可用来在设备上运行各种命令)的访问权限。它是一种客户端-服务器程序,包括以下三个组件:

  • 客户端(adb):用于发送命令。客户端在开发机器上运行,您可以通过发出 adb 命令从命令行终端调用客户端
  • 守护程序 (adbd):用于在设备上运行命令,守护程序在每个设备上作为后台进程运行
  • 服务器(adb server):用于管理客户端与守护程序之间的通信,服务器在开发机器上作为后台进程运行

:Android adb源码路径为:system⁩/⁨core⁩/⁨adb,根据adb运行的地方不同,可以分为PC端和Android 手机设备端两部分

  • PC端: adb clientadb server 是运行在PC端,adb就是控制台命令adb,adb server是由adb fork出的一个常驻后台的子进程,作为adb的server进程,adb kill-server就是kill掉这个进程。adb与adb server通过local socket进行通信
  • Android 手机设备端:adbd运行在Android 手机设备端,是在内核初始化完毕之后,由init进程启动

adb 包含在 Android SDK 平台工具软件包中。您可以使用 SDK 管理器下载此软件包,该管理器会将其安装在 android_sdk/platform-tools/ 下。也可以单独下载独立的 Android SDK 平台工具软件包

1)adb工作原理

当用户启动某个 adb 客户端时,客户端会先检查是否有 adb 服务器进程正在运行。如果没有,它将启动服务器进程。服务器在启动后会与本地 TCP 端口 5037 绑定,并监听 adb 客户端发出的命令,所有 adb 客户端均通过端口 5037 与 adb 服务器通信,adb原理图如下:

然后,服务器会与所有正在运行的设备建立连接。它通过扫描 5555 到 5585 之间(该范围供前 16 个模拟器使用)的奇数号端口查找模拟器。服务器一旦发现 adb 守护进程 (adbd),便会与相应的端口建立连接。注意,每个模拟器都使用一对按顺序排列的端口 - 用于控制台连接的偶数号端口和用于 adb 连接的奇数号端口。例如,如下:

  • 模拟器 A,控制台:5554

    • 模拟器 A,adb:5555
  • 模拟器 B,控制台:5556
    • 模拟器 B,adb:5557
  • :以此类推,如上所示,在端口 5555 处与 adb 连接的模拟器与控制台监听端口为 5554 的模拟器是同一个

服务器与所有设备均建立连接后,用户就可以使用 adb 命令访问这些设备。由于服务器管理与设备的连接,并处理来自多个 adb 客户端的命令,因此您可以从任意客户端(或从某个脚本)控制任意设备

2)Android APK 的root 权限和USB ADB 权限的区别

USB adb 权限是指,当adb 连接手机时,手机中的守护进程adbd 的权限为root 权限,从而它的子进程也具有root 权限,通常如果adb shell 看到是:

  • Android 10版本 (以树莓派为例):
D:\>adb shell
rpi4:/ #

即表明adb 的连接是root 权限的,相反如果看到是$ 即表明是shell 权限。 Android 的APK 本身都是不具备root 权限的,如果想启用root 权限,那么就必须借助具有root 权限的进程或者具有s bit 的文件,目前比较通用的手法是,手机root 后,内置了su到system/bin, 然后普通APP 即可借助su 命令来达到root 权限切换。

网络上已经有同仁修改su 命令,并通过一个APK 来控制su 命令的权限控制。 如常见的Superuser 即可人为的控制root 权限的使用(Superuser 因很久都没有更新了,只能用于ICS 以及以前的版本,不推荐使用), SuperSU (也老了,不推荐使用),推荐使用Magisk (更新速度快,推荐使用)

1.1 Android 设备端开启USB调试

  • 进程列表

无需特殊权限,部分高版本设备无法获取进程列表

  • 检测init.svc.adbd属性取值是否running

1.2 adbd启动流程简介

adbd守护进程:只要是操作系统,就肯定会运行着一些守护进程(daemon)来完成重复或繁琐的工作。通过Android系统中的init.rc文件可以发现,其中每个service中就包含着系统后台服务进程。而这些服务被分为:

  • core类服务(adbd/servicemanager/healthd/lmkd/logd/vold)和main类服务

    • main类服务又分为:

      • 网络类服务(netd/mdnsd/mtpd/rild)、图形及媒体类服务(surfaceflinger/bootanimation/mediaserver/dnnserver)、其他类服务(installd/keystore/debuggerd/sdcard/Zygote

  • init.rc:Android在启动过程中读取的启动脚本文件,主要完成一些初级的初始化,在/system/core/init/init.c中解析。rc 经常被用作程序之启动脚本的文件名。它是“run commands”(运行命令)的缩写
  • init.xx.rc:与具体CPU相关的启动脚本,比如对于MTK的CPU,名字为init.usb.rc,init.trace.rc。在init.rc之后得到解析

Android adbd:Android 系统特有的ADB功能中运行在Android设备端的守护进程,其在根目录下的/init.rc/init.usb.rc中的启动配置如下

下面是该文件的一些注释信息:

# Copyright (C) 2012 The Android Open Source Project
#
# IMPORTANT: Do not create world writable files or directories.
# This is a common source of Android security bugs.
#"【import <filename>一个init配置文件,扩展当前配置。】"
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.${ro.zygote}.rc
import /init.trace.rc"【触发条件early-init,在early-init阶段调用以下行】"
on early-init# Set init and its forked children's oom_adj.write /proc/1/oom_score_adj -1000"【打开路径为<path>的一个文件,并写入一个或多个字符串】"# Apply strict SELinux checking of PROT_EXEC on mmap/mprotect calls.write /sys/fs/selinux/checkreqprot 0# Set the security context for the init process.# This should occur before anything else (e.g. ueventd) is started."【这段脚本的意思是init进程启动之后就马上调用函数setcon将自己的安全上下文设置为“u:r:init:s0”,即将init进程的domain指定为init。】"setcon u:r:init:s0# Set the security context of /adb_keys if present."【恢复指定文件到file_contexts配置中指定的安全上线文环境】"restorecon /adb_keys"【执行start ueventd的命令。ueventd是一个service后面有定义】 "start ueventd"【mkdir <path> [mode] [owner] [group]   //创建一个目录<path>,可以选择性地指定mode、owner以及group。如果没有指定,默认的权限为755,并属于root用户和root组。】"# create mountpointsmkdir /mnt 0775 root systemon init"【设置系统时钟的基准,比如0代表GMT,即以格林尼治时间为准】"sysclktz 0"【设置kernel日志等级】"
loglevel 6 ####write /proc/bootprof "INIT: on init start" ####"【symlink <target> <path>    //创建一个指向<path>的软连接<target>。】"# Backward compatibilitysymlink /system/etc /etcsymlink /sys/kernel/debug /d# Right now vendor lives on the same filesystem as system,# but someday that may change.symlink /system/vendor /vendor"【创建一个目录<path>,可以选择性地指定mode、owner以及group。】"# Create cgroup mount point for cpu accountingmkdir /acctmount cgroup none /acct cpuacctmkdir /acct/uid"【mount <type> <device> <dir> [ <mountoption> ]   //在目录<dir>挂载指定的设备。<device> 可以是以 mtd@name 的形式指定一个mtd块设备。<mountoption>包括 ro、rw、remount、noatime、 ...】"# Create cgroup mount point for memorymount tmpfs none /sys/fs/cgroup mode=0750,uid=0,gid=1000mkdir /sys/fs/cgroup/memory 0750 root systemmount cgroup none /sys/fs/cgroup/memory memorywrite /sys/fs/cgroup/memory/memory.move_charge_at_immigrate 1"【chown <owner> <group> <path>   //改变文件的所有者和组。】""【后面的一些行因为类似,就省略了】".....# Healthd can trigger a full boot from charger mode by signaling this
# property when the power button is held.
on property:sys.boot_from_charger_mode=1"【停止指定类别服务类下的所有已运行的服务】"class_stop charger"【触发一个事件,将该action排在某个action之后(用于Action排队)】"trigger late-init# Load properties from /system/ + /factory after fs mount.
on load_all_props_action"【从/system,/vendor加载属性。默认包含在init.rc】"load_all_props# Indicate to fw loaders that the relevant mounts are up.
on firmware_mounts_complete"【删除指定路径下的文件】"rm /dev/.booting# Mount filesystems and start core system services.
on late-init"【触发一个事件。用于将一个action与另一个 action排列。】"trigger early-fstrigger fstrigger post-fstrigger post-fs-data# Load properties from /system/ + /factory after fs mount. Place# this in another action so that the load will be scheduled after the prior# issued fs triggers have completed.trigger load_all_props_action# Remove a file to wake up anything waiting for firmware.trigger firmware_mounts_completetrigger early-boottrigger booton post-fs..."【一些创造目录,建立链接,更改权限的操作,这里省略】"on post-fs-data..."【一些创造目录,建立链接,更改权限的操作,这里省略】""【恢复指定文件到file_contexts配置中指定的安全上线文环境】"restorecon /data/mediaserver"【将系统属性<name>的值设置为<value>,即以键值对的方式设置系统属性】"# Reload policy from /data/security if present.setprop selinux.reload_policy 1"【以递归的方式恢复指定目录到file_contexts配置中指定的安全上下文中】"# Set SELinux security contexts on upgrade or policy update.restorecon_recursive /data# If there is no fs-post-data action in the init.<device>.rc file, you# must uncomment this line, otherwise encrypted filesystems# won't work.# Set indication (checked by vold) that we have finished this action#setprop vold.post_fs_data_done 1on boot"【初始化网络】"# basic network initifup lo"【设置主机名为localhost】"hostname localhost"【设置域名localdomain】"domainname localdomain"【设置资源限制】"# set RLIMIT_NICE to allow priorities from 19 to -20setrlimit 13 40 40"【这里省略了一些chmod,chown,等操作,不多解释】"...# Define default initial receive window size in segments.setprop net.tcp.default_init_rwnd 60"【重启core服务】"class_start coreon nonencryptedclass_start mainclass_start late_starton property:vold.decrypt=trigger_default_encryptionstart defaultcryptoon property:vold.decrypt=trigger_encryptionstart surfaceflingerstart encrypton property:sys.init_log_level=*loglevel ${sys.init_log_level}on chargerclass_start chargeron property:vold.decrypt=trigger_reset_mainclass_reset mainon property:vold.decrypt=trigger_load_persist_propsload_persist_propson property:vold.decrypt=trigger_post_fs_datatrigger post-fs-dataon property:vold.decrypt=trigger_restart_min_frameworkclass_start mainon property:vold.decrypt=trigger_restart_frameworkclass_start mainclass_start late_starton property:vold.decrypt=trigger_shutdown_frameworkclass_reset late_startclass_reset mainon property:sys.powerctl=*powerctl ${sys.powerctl}# system server cannot write to /proc/sys files,
# and chown/chmod does not work for /proc/sys/ entries.
# So proxy writes through init.
on property:sys.sysctl.extra_free_kbytes=*write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}# "tcp_default_init_rwnd" Is too long!
on property:sys.sysctl.tcp_def_init_rwnd=*write /proc/sys/net/ipv4/tcp_default_init_rwnd ${sys.sysctl.tcp_def_init_rwnd}"【守护进程】"
## Daemon processes to be run by init.
##
service ueventd /sbin/ueventdclass corecriticalseclabel u:r:ueventd:s0"【日志服务进程】"
service logd /system/bin/logdclass coresocket logd stream 0666 logd logdsocket logdr seqpacket 0666 logd logdsocket logdw dgram 0222 logd logdseclabel u:r:logd:s0"【Healthd是android4.4之后提出来的一种中介模型,该模型向下监听来自底层的电池事件,向上传递电池数据信息给Framework层的BatteryService用以计算电池电量相关状态信息】"
service healthd /sbin/healthdclass corecriticalseclabel u:r:healthd:s0"【控制台进程】"
service console /system/bin/sh"【为当前service设定一个类别.相同类别的服务将会同时启动或者停止,默认类名是default】"class core"【服务需要一个控制台】"console"【服务不会自动启动,必须通过服务名显式启动】"disabled"【在执行此服务之前切换用户名,当前默认的是root.自Android M开始,即使它要求linux capabilities,也应该使用该选项.很明显,为了获得该功能,进程需要以root用户运行】"user shellseclabel u:r:shell:s0on property:ro.debuggable=1start console# adbd is controlled via property triggers in init.<platform>.usb.rc
service adbd /sbin/adbd --root_seclabel=u:r:su:s0class core"【创建一个unix域下的socket,其被命名/dev/socket/<name>. 并将其文件描述符fd返回给服务进程.其中,type必须为dgram,stream或者seqpacke,user和group默认是0.seclabel是该socket的SELLinux的安全上下文环境,默认是当前service的上下文环境,通过seclabel指定】"socket adbd stream 660 system systemdisabledseclabel u:r:adbd:s0# adbd on at boot in emulator
on property:ro.kernel.qemu=1start adbd"【内存管理服务,内存不够释放内存】"
service lmkd /system/bin/lmkdclass corecriticalsocket lmkd seqpacket 0660 system system"【ServiceManager是一个守护进程,它维护着系统服务和客户端的binder通信。
在Android系统中用到最多的通信机制就是Binder,Binder主要由Client、Server、ServiceManager和Binder驱动程序组成。其中Client、Service和ServiceManager运行在用户空间,而Binder驱动程序运行在内核空间。核心组件就是Binder驱动程序了,而ServiceManager提供辅助管理的功能,无论是Client还是Service进行通信前首先要和ServiceManager取得联系。而ServiceManager是一个守护进程,负责管理Server并向Client提供查询Server的功能。】"
service servicemanager /system/bin/servicemanagerclass coreuser systemgroup systemcriticalonrestart restart healthd"【servicemanager 服务启动时会重启zygote服务】"onrestart restart zygoteonrestart restart mediaonrestart restart surfaceflingeronrestart restart drm"【Vold是Volume Daemon的缩写,它是Android平台中外部存储系统的管控中心,是管理和控制Android平台外部存储设备的后台进程】"
service vold /system/bin/voldclass coresocket vold stream 0660 root mountioprio be 2"【Netd是Android系统中专门负责网络管理和控制的后台daemon程序】"
service netd /system/bin/netdclass mainsocket netd stream 0660 root systemsocket dnsproxyd stream 0660 root inetsocket mdns stream 0660 root systemsocket fwmarkd stream 0660 root inet"【debuggerd是一个daemon进程,在系统启动时随着init进程启动。主要负责将进程运行时的信息dump到文件或者控制台中】"
service debuggerd /system/bin/debuggerdclass mainservice debuggerd64 /system/bin/debuggerd64class main"【Android RIL (Radio Interface Layer)提供了Telephony服务和Radio硬件之间的抽象层】"
# for using TK init.modem.rc rild-daemon setting
#service ril-daemon /system/bin/rild
#    class main
#    socket rild stream 660 root radio
#    socket rild-debug stream 660 radio system
#    user root
#    group radio cache inet misc audio log"【提供系统 范围内的surface composer功能,它能够将各种应用 程序的2D、3D surface进行组合。】"
service surfaceflinger /system/bin/surfaceflingerclass coreuser systemgroup graphics drmrpconrestart restart zygote"【DRM可以直接访问DRM clients的硬件。DRM驱动用来处理DMA,内存管理,资源锁以及安全硬件访问。为了同时支持多个3D应用,3D图形卡硬件必须作为一个共享资源,因此需要锁来提供互斥访问。DMA传输和AGP接口用来发送图形操作的buffers到显卡硬件,因此要防止客户端越权访问显卡硬件。】"
#make sure drm server has rights to read and write sdcard ####
service drm /system/bin/drmserverclass mainuser drm# group drm system inet drmrpc ####group drm system inet drmrpc sdcard_r ####"【媒体服务,无需多说】"
service media /system/bin/mediaserverclass mainuser root ####
#   google default ####
#   user media    ####group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc mediadrm media sdcard_r system net_bt_stack ####
#   google default ####
#   group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc mediadrm ####ioprio rt 4"【设备加密相关服务】"
# One shot invocation to deal with encrypted volume.
service defaultcrypto /system/bin/vdc --wait cryptfs mountdefaultencrypteddisabled"【当服务退出时,不重启该服务】"oneshot# vold will set vold.decrypt to trigger_restart_framework (default# encryption) or trigger_restart_min_framework (other encryption)# One shot invocation to encrypt unencrypted volumes
service encrypt /system/bin/vdc --wait cryptfs enablecrypto inplace defaultdisabledoneshot# vold will set vold.decrypt to trigger_restart_framework (default# encryption)"【开机动画服务】"
service bootanim /system/bin/bootanimationclass coreuser graphics
#    group graphics audio ####group graphics media audio ####disabledoneshot"【在Android系统中,PackageManagerService用于管理系统中的所有安装包信息及应用程序的安装卸载,但是应用程序的安装与卸载并非PackageManagerService来完成,而是通过PackageManagerService来访问installd服务来执行程序包的安装与卸载的。】"
service installd /system/bin/installdclass mainsocket installd stream 600 system systemservice flash_recovery /system/bin/install-recovery.shclass mainseclabel u:r:install_recovery:s0oneshot"【vpn相关的服务】"
service racoon /system/bin/racoonclass mainsocket racoon stream 600 system system# IKE uses UDP port 500. Racoon will setuid to vpn after binding the port.group vpn net_admin inetdisabledoneshot"【android中有mtpd命令可以连接vpn】"
service mtpd /system/bin/mtpdclass mainsocket mtpd stream 600 system systemuser vpngroup vpn net_admin inet net_rawdisabledoneshotservice keystore /system/bin/keystore /data/misc/keystoreclass mainuser keystoregroup keystore drmrpc"【可以用dumpstate 获取设备的各种信息】"
service dumpstate /system/bin/dumpstate -sclass mainsocket dumpstate stream 0660 shell logdisabledoneshot"【mdnsd 是多播 DNS 和 DNS 服务发现的守护程序。】"
service mdnsd /system/bin/mdnsdclass mainuser mdnsrgroup inet net_rawsocket mdnsd stream 0660 mdnsr inetdisabledoneshot"【触发关机流程继续往下走】"
service pre-recovery /system/bin/uncryptclass maindisabled"【当服务退出时,不重启该服务】"oneshot

  • 在默认情况下,adbd是以uid root的权限启动的。不过它还会通过函数drop_privileges()主动把自己降到uid shell: shell和几个GID权限,源码地址

  • user-debug版本的系统中(系统属性为ro.debuggable=0),或通过adb root命令让adbd恢复root权限(通过修改系统属性service.adb.root=1

手机端的adbd是在init解析rc文件的时候启动的,具体可以查看init.rc中的配置信息。根据注释,可以了解到adbd的启动是根据一个属性触发器来控制的,具体的触发器在init.<platform>.usb.rc文件中,如下:

# adbd is controlled via property triggers in init.<platform>.usb.rc
service adbd /sbin/adbd --root_seclabel=u:r:su:s0class core"【创建一个unix域下的socket,其被命名/dev/socket/<name>. 并将其文件描述符fd返回给服务进程.其中,type必须为dgram,stream或者seqpacke,user和group默认是0.seclabel是该socket的SELLinux的安全上下文环境,默认是当前service的上下文环境,通过seclabel指定】"socket adbd stream 660 system systemdisabledseclabel u:r:adbd:s0

接着,跟进去根目录/init.usb.rc看看,看看在init.<platform>.usb.rc文件中如何定义触发器。触发器根据sys.usb.config的值来决定是否启动adbd,其实就是仅当sys.usb.config的值中包含adb选项时才会启动adbd

# adb only USB configuration
# This is the fallback configuration if the
# USB manager fails to set a standard configuration
on property:sys.usb.config=adb && property:sys.usb.configfs=0write /sys/class/android_usb/android0/enable 0write /sys/class/android_usb/android0/idVendor 18d1write /sys/class/android_usb/android0/idProduct 4EE7write /sys/class/android_usb/android0/functions ${sys.usb.config}write /sys/class/android_usb/android0/enable 1start adbdsetprop sys.usb.state ${sys.usb.config}

:Android O新增的关于usb的三个属性【sys.usb.configfs、sys.usb.ffs.ready、sys.usb.ffs.mtp.ready】:

  • sys.usb.configfs:该属性默认为0,通过判断kernel是否支持configfs,即判断/config/usb_gadget存在设置为1,代码在init.qcom.usb.sh中(高通平台下)
  • sys.usb.ffs.ready:当sys.usb.config设置为none时,设置sys.usb.ffs.ready为0。每次设置的属性有adb时,会先start adbd。如sys.usb.config=mtp,adb时,在init.usb.configfs.rc中有(如下代码),然后在system/core/adb/daemon/main.cpp中,会调用usb_init,进而调用system/core/adb/daemon/usb.cpp的usb_ffs_open_thread()函数中,然后调用init_functionfs()初始化该属性为1。只有sys.usb.ffs.ready=1时,才能开启adb端口:
sys.usb.config=mtp,adb && sys.usb.configfs=1
start adbd
  • sys.usb.ffs.mtp.ready:当设置为none时,设置sys.usb.ffs.mtp.ready为0。android O新增,当功能为mtp或者ptp时,在 trySetEnableFuntction()函数中会先发送一次广播给mtpReceiver

    • 给数据库添加mtp_connected
    • 调用mtpServer.configure(),最后调用mtpServer.cpp设置sys.usb.ffs.mtp.ready属性为1,才能响应on property:sys.usb.config=mtp(ptp也是),然后收到kernel发送的uevent后,再发一次广播

Android 手机设备端USB模式:

  • PTP:“图像传输协议【Picture Transfer Protocol】”(PTP),为文件传输模式(PTP)

    • 在接入Windows系统之后可以更好地被系统和应用程序所共享,尤其在网络传输方面,系统可以直接访问这些设备用于建立网络相册时图片的上传、网上聊天时图片的传送等
    • PTP适合多播消息的分布式网络通信系统,同时提供单播消息的支持
  • MTP:“媒体传输协议【Media Transfer Protocol】”,是基于PTP(Picture Transfer Protocol)协议的扩展,主要用于传输媒体文件。如果选择MTP模式连接的话,电脑上和手机上都可以读取内置和外置SD卡
    • 其应用分两种角色,一个是作为Initiator,另一个作为Responder。Responder都是被动的回复Initiator的命令,不会主动发命令。主要的用途是传输媒体文件,并从设备关联元数据,对设备的远程控制有可选的额外支持,读取和设置设备参数,如特别的DRM相关的受限内容设备参数
    • MTP既可以实现在USB协议上,也可以实现在TCP/IP协议上,它属于上层的应用协议,而不关心底层传输协议

adbd的启动代码在system/core/adb/daemon/main.cpp中,其中main函数在获取了selinux标签、banner名称、版本信息参数以及设置一些调试信息后,调用了adbd_main完成启动过程。adbd是一个linux程序,通过tcp或者usb与PC端的adb server通信,调用logcat shell等等程序实现各种调试功能,源码地址如下

int adbd_main(int server_port) {umask(0);signal(SIGPIPE, SIG_IGN);// 负责完成adbd接受PC端adb server连接功能的初始化工作init_transport_registration();// We need to call this even if auth isn't enabled because the file// descriptor will always be open.adbd_cloexec_auth_socket();if (ALLOW_ADBD_NO_AUTH && property_get_bool("ro.adb.secure", 0) == 0) {auth_required = false;}// 负责完成对连入的PC端身份验证功能的初始化工作adbd_auth_init();// Our external storage path may be different than apps, since// we aren't able to bind mount after dropping root.const char* adb_external_storage = getenv("ADB_EXTERNAL_STORAGE");if (adb_external_storage != nullptr) {setenv("EXTERNAL_STORAGE", adb_external_storage, 1);} else {D("Warning: ADB_EXTERNAL_STORAGE is not set.  Leaving EXTERNAL_STORAGE"" unchanged.\n");}// 负责是否要去掉adbd的root权限,降级为shell权限drop_privileges(server_port);bool is_usb = false;if (access(USB_ADB_PATH, F_OK) == 0 || access(USB_FFS_ADB_EP0, F_OK) == 0) {  // 如果可以usb调试,初始化usb,等待连接// Listen on USB.usb_init();is_usb = true;}// If one of these properties is set, also listen on that port.// If one of the properties isn't set and we couldn't listen on usb, listen// on the default port.char prop_port[PROPERTY_VALUE_MAX];property_get("service.adb.tcp.port", prop_port, "");if (prop_port[0] == '\0') {property_get("persist.adb.tcp.port", prop_port, "");}int port;if (sscanf(prop_port, "%d", &port) == 1 && port > 0) {D("using port=%d", port);// Listen on TCP port specified by service.adb.tcp.port property.// 如果开启了网络调试,初始化端口,等待连接local_init(port);} else if (!is_usb) {// Listen on default port.local_init(DEFAULT_ADB_LOCAL_TRANSPORT_PORT);}D("adbd_main(): pre init_jdwp()");// 初始化 java 调试框架init_jdwp();D("adbd_main(): post init_jdwp()");// 对监听的fd进行消息处理(死循环)D("Event loop starting");fdevent_loop();return 0;
}
int main(int argc, char** argv) {while (true) {static struct option opts[] = {{"root_seclabel", required_argument, nullptr, 's'},{"device_banner", required_argument, nullptr, 'b'},{"version", no_argument, nullptr, 'v'},};int option_index = 0;int c = getopt_long(argc, argv, "", opts, &option_index);if (c == -1) {break;}switch (c) {case 's':root_seclabel = optarg;break;case 'b':adb_device_banner = optarg;break;case 'v':printf("Android Debug Bridge Daemon version %d.%d.%d %s\n",ADB_VERSION_MAJOR, ADB_VERSION_MINOR, ADB_SERVER_VERSION,ADB_REVISION);return 0;default:// getopt already prints "adbd: invalid option -- %c" for us.return 1;}}close_stdin();debuggerd_init(nullptr);adb_trace_init(argv);D("Handling main()");return adbd_main(DEFAULT_ADB_PORT);
}

1.3 adbd权限

adbd启动过程中的权限处理,在adbd_main函数中调用了drop_privileges进行降权处理。因为由init启动的进程启动,所以同样拥有和init一样的root权限。root权限过高,如果不是非必要,就要进行降权处理。如果当ro_debuggable && adb_root为true时,可以不用降权,维持root权限。简而言之就是当ro.debuggable为1且ro.secure为0的时候不用降权

static void drop_privileges(int server_port) {ScopedMinijail jail(minijail_new());// Add extra groups:// AID_ADB to access the USB driver// AID_LOG to read system logs (adb logcat)// AID_INPUT to diagnose input issues (getevent)// AID_INET to diagnose network issues (ping)// AID_NET_BT and AID_NET_BT_ADMIN to diagnose bluetooth (hcidump)// AID_SDCARD_R to allow reading from the SD card// AID_SDCARD_RW to allow writing to the SD card// AID_NET_BW_STATS to read out qtaguid statistics// AID_READPROC for reading /proc entries across UID boundariesgid_t groups[] = {AID_ADB,      AID_LOG,       AID_INPUT,AID_INET,     AID_NET_BT,    AID_NET_BT_ADMIN,AID_SDCARD_R, AID_SDCARD_RW, AID_NET_BW_STATS,AID_READPROC};minijail_set_supplementary_gids(jail.get(), arraysize(groups), groups);// Don't listen on a port (default 5037) if running in secure mode.// Don't run as root if running in secure mode.if (should_drop_privileges()) {drop_capabilities_bounding_set_if_needed(jail.get());minijail_change_gid(jail.get(), AID_SHELL);minijail_change_uid(jail.get(), AID_SHELL);// minijail_enter() will abort if any priv-dropping step fails.minijail_enter(jail.get());D("Local port disabled");} else {// minijail_enter() will abort if any priv-dropping step fails.minijail_enter(jail.get());if (root_seclabel != nullptr) {if (selinux_android_setcon(root_seclabel) < 0) {LOG(FATAL) << "Could not set SELinux context";}}std::string error;std::string local_name =android::base::StringPrintf("tcp:%d", server_port);if (install_listener(local_name, "*smartsocket*", nullptr, 0, nullptr, &error)) {LOG(FATAL) << "Could not install *smartsocket* listener: " << error;}}
}static bool should_drop_privileges() {#if defined(ALLOW_ADBD_ROOT)// The properties that affect `adb root` and `adb unroot` are ro.secure and// ro.debuggable. In this context the names don't make the expected behavior// particularly obvious.//// ro.debuggable://   Allowed to become root, but not necessarily the default. Set to 1 on//   eng and userdebug builds.//// ro.secure://   Drop privileges by default. Set to 1 on userdebug and user builds.bool ro_secure = android::base::GetBoolProperty("ro.secure", true);bool ro_debuggable = __android_log_is_debuggable();// Drop privileges if ro.secure is set...bool drop = ro_secure;// ... except "adb root" lets you keep privileges in a debuggable build.std::string prop = android::base::GetProperty("service.adb.root", "");bool adb_root = (prop == "1");bool adb_unroot = (prop == "0");if (ro_debuggable && adb_root) {drop = false;}// ... and "adb unroot" lets you explicitly drop privileges.if (adb_unroot) {drop = true;}return drop;
#elsereturn true; // "adb root" not allowed, always drop privileges.
#endif // ALLOW_ADBD_ROOT
}

1.4 user版本默认开启adb以及root权限

1.4.1 修改ro.adb.secure和ro.secure属性

修改build/core/main.mk,修改ro.secure和ro.adb.secure位0,注释掉enable_target_debugging或者将其设置为true

--- a/core/main.mk
+++ b/core/main.mk
@@ -239,11 +239,11 @@ enable_target_debugging := true
tags_to_install :=
ifneq (,$(user_variant))
# Target is secure in user builds.
-  ADDITIONAL_DEFAULT_PROPERTIES += ro.secure=1
+  ADDITIONAL_DEFAULT_PROPERTIES += ro.secure=0
ADDITIONAL_DEFAULT_PROPERTIES += security.perf_harden=1ifeq ($(user_variant),user)
-    ADDITIONAL_DEFAULT_PROPERTIES += ro.adb.secure=1
+    ADDITIONAL_DEFAULT_PROPERTIES += ro.adb.secure=0
endififeq ($(user_variant),userdebug)
@@ -251,7 +251,7 @@ ifneq (,$(user_variant))
tags_to_install += debug
else
# Disable debugging in plain user builds.
-    enable_target_debugging :=
+   # enable_target_debugging :=
endif# Disallow mock locations by default for user builds

1.4.2 修改selinux

修改system/core/init/init.cpp,将selinux_is_enforcing的返回值调整为false。这里还有另外一种改法,就是在Boardconfig.mk里面对BOARD_KERNEL_CMDLINE进行编辑,添加androidboot.selinux=permissive

static bool selinux_is_enforcing(void)
{return false;
}

1.4.3 修改adb模块的android.mk文件

修改system/core/adb/Android.mk中的编译选项,允许adb root,如下:

--- a/adb/Android.mk
+++ b/adb/Android.mk
@@ -350,9 +350,9 @@ LOCAL_CFLAGS := \
-D_GNU_SOURCE \
-Wno-deprecated-declarations \-LOCAL_CFLAGS += -DALLOW_ADBD_NO_AUTH=$(if $(filter userdebug eng,$(TARGET_BUILD_VARIANT)),1,0)
+LOCAL_CFLAGS += -DALLOW_ADBD_NO_AUTH=$(if $(filter user userdebug eng,$(TARGET_BUILD_VARIANT)),1,0-ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
+ifneq (,$(filter user userdebug eng,$(TARGET_BUILD_VARIANT)))
LOCAL_CFLAGS += -DALLOW_ADBD_DISABLE_VERITY=1
LOCAL_CFLAGS += -DALLOW_ADBD_ROOT=1
endif

1.4.4 设置默认打开adb端口

配置device.mk或者devicecommon.mk,设置USB配置选项,添加adb功能

--- a/common/DeviceCommon.mk
+++ b/common/DeviceCommon.mk
@@ -153,7 +153,7 @@ PRODUCT_DEFAULT_PROPERTY_OVERRIDES += \# Set default USB interface
PRODUCT_DEFAULT_PROPERTY_OVERRIDES += \
-    persist.sys.usb.config=ptp
+    persist.sys.usb.config=ptp,adbPRODUCT_PROPERTY_OVERRIDES += \
persist.sys.modem.diag=,gser \

1.5 adb安全认证

大家在通过adb连接设备的时候,是否会有提示让确认是否允某台设备要进行USB调试的确认框(如下):

ADB具有功能非常强大的调试和跟踪功能,尽管它是以shell这个UID的身份运行,但由于shell这个UID是好几个组(log、graphic等)的成员,这也使得它具有很强的能力。使用ADB可以访问到用户的个人数据,也能把任意APP、Native层可执行文件上传到设备中。因此,在Android 4.3 以后的Android系统中引入了公钥认证机制(前提条件ro.adb.secure=1),让ADB的安全性进一步提高

AUTH消息将作为对OPEN消息的响应,发送到电脑端,要求在其执行任何命令之前,先完成认证。AUTH消息的参数总是一个TOKEN,它是由移动设备中的随机数发生器(/dev/urandom)生成的一个大小为20个字节的随机数数组。移动设备端将等待电脑端用自己的私钥(该私钥应该己经生成,并存放在$HOME/.android/adbkey这个文件中)对这个TOKEN 进行签名,然后回复一个AUTH SIGNATURE消息,把用私钥签名过的随机数数组放在这个消息里返回给移动设备端。如果移动设备知道相应的公钥,那么验证就能继续,而且如过验证通过了,该会话就会切换到online状态上去

因为,所有这些操作都依赖于公钥,比如怎么让Android 手机设备端提前就知道这个公钥,并将其用于验证呢?

答:允许电脑端响应一个AUTHRSAPUBLICKEY消息。因为此时这个公钥是不可信的,ADB将把这个公钥通过/dev/socket/adbd这个UDS传递给system_server(特别是由com.android.server.usb.UsbDeviceManager启动的UsbDebuggingManager),然后,system_server将会弹出一个对话框(com.android.server.usb.UsbDebuggingActivity),要求用户确认该公钥的指纹(MD5 Hash)。如果用户选择信任该公钥,这个公钥就会被添加到adb的key store(位于/data/misc/adb/adb_key)中去。作为厂商的话可以重新编译adbd,删掉这一功能(相关代码位于adb_auth_client.c中的adb_auth_confirm_key()函数中),源码地址

输入dumpsys usb命令,就能查看当前USB调试状态以及当前使用的adb_keys,如下:

0x02 Android 开启USB 调试模式检测方法

USB调试者,可直接在PC上使用adb命令对设备进行调试,也可通过DDMS命令间接调试。在调试之前,要在系统开发者模式中打开“USB调试”的开关,其在底层(不太理解的话,可以看看0x01中的内容)其实是通过修改系统属性sys.usb.config的值为adb时打开,大家看到这里可能想问通过sys.usb.config里是否存在adb调试来判断是否开启,那么怎么判断Android 设备端是否有adb连入呢?

先用usb.config来搜索看看,有几个能匹配:

getprop |  grep usb.config

匹配到如下几个,具体啥意思,就是字面意思:

  • [persist.sys.usb.config]: [mtp,adb]:设置当前ADB开启状态的值
  • [sys.usb.config]: [mtp,adb]:设置当前USB功能的值
  • [sys.usb.configfs]: [1]
    • 如果sys.usb.configfs其值为1,则会使用init.usb.configfs.rc文件中相关条目,否则将使用init.usb.rc文件中的相关条目

对于Android系统,和应用,要改变usb gadget通过配置如下两个prop属性: persist.sys.usb.config【设置当前ADB开启状态的值】和sys.usb.configfs【设置当前USB功能的值】,persist.sys.usb.config【设置当前ADB开启状态的值】和sys.usb.configfs【设置当前USB功能的值】该属性的值包含adb这个字段则会触发打开adb功能,即可实现关闭或打开USB调试和文件传输功能,其中mtp表示文件传输,adb表示adb调试,none表示都不启用

0x03 检测Android adb是否成功连入Android 设备端

3.1 adbd 进程检测方法

进程检测分为两种方式:

  • 【不推荐】进程对应的端口号,比如adb无线调试默认端口5555【此方法比较适合Wi-Fi adb无线调试,加上判断service.adb.tcp.port+是否默认端口来检测,跟我们后面要讲的通过网络流量形式检测adbd是否接入的规则类似】

    • :此端口是可更改的,单独以端口来判断,会超多误报,因为不能保障5555端口会不会被其它进程占用
  • 【推荐】进程名,比如监视进程里面匹配上的adbd名字的进程

1)进程对应的端口号

先在PC端用tasklist命令查询adb相关的进程:

  • 此处第一个标红的地方有一个3172号进程,再通过netstat命令查询3172号进程对应的端口,发现3172号进程占用的端口是5037。根据adb原理图1,得知此adb是服务端

    • taskliste | findstr adb
    • netstat -ano | findstr 3172
  • 此处第二个标红的地方有一个22344号进程,再通过netstat命令查询22344号进程对应的端口,发现22344号进程占用的端口是22630。根据adb原理图2,得知此adb是客户端
    • taskliste | findstr adb
    • netstat -ano | findstr 22344

:由上可知,adb服务端和客户端都是sdk下的adb.exe

接下来,我们进入adb shell后使用netstat命令,发现有一个2014号进程(adbd)占用5555端口,此进程即为守护进程

netstat -anp | grep 5555

通过上面的了解,可以发现可通过netstat -anp | grep adb来定位,可以看到会有一个adbd socket连接的进程,这里就是后面要讲的域套接字形式的检测。在介绍下面一种之前,同学们肯定有一个疑问,咋是5555端口?其实这里是Wi-Fi 连接adb默认配置的端口,你配置什么端口就是什么端口,所以,就衍生到第二种通过adbd子进程来检测

2)进程名

聊聊通过进程名来判断adbd来确定是否有adb接入Android 设备,执行如下命令:

netstat -anp | grep adbd

:上面这里大家可能看到有个socket链接的进程,就是下面要讲的通过域套接字形式来检测adb是否连入Android 设备

3.2 域套接字形式检测

  • 域套接字的概念:进程与进程之间通讯的一种方式,客户端与服务端建立连接,需要有相同的套接字,和相应的服务端端口号,套接字处于监控状态,监听客户端发送的请求

这里没什么好讲的,直接晒出结果吧,如下

  • 通过检测adbd是否有子进程来确定是否有客户端连入

为了有明显区别对比,此处我们再次新开一个cmd窗口,然后执行如下命令:

ps  | grep adb
ps  | grep 7873

再次查看adb的子进程,其中已经少了一个adbd的子进程,如下:

ps  |grep adb
ps  |grep 7873

  • 最后,我们检测/proc/net/unix 中是否有dev/socket/adbd



3.3 网络流量形式检测

3.3.1 Wi-Fi 调试(Android 11 及更高版本,无需借助 USB)

从 Android 11 开始支持 ADB 以无线方式连接手机调试,可以彻底摆脱 USB 线:

  • 1)手机和电脑需连接在同一 Wi-Fi 下
  • 2)保证 SDK 为最新版本(adb --version ≥ 30.0.0
  • 3)手机启用开发者选项和无线调试模式(会提示确认)
  • 4)允许无线调试后,选择使用配对码配对。记下显示的配对码、IP 地址和端口号
  • 5)运行adb pair ip:port,使用第 4 步中的 IP 地址和端口号
  • 6)根据提示,输入第 3 步中的配对码,系统会显示一条消息,表明您的设备已成功配对
  • 7)(仅适用于 Linux 或 Windows)运行 adb connect ip:port

3.3.2 WLAN 调试(Android 10 及更低版本,需要借助 USB)

做过Android 开发的基本都知道,平常一般都是直接通过 USB 线或者 Type C线的方式连接,来完成日常的开发和调试,这种开发模式存在几个问题点,是一个极简主义者所不能忍受的

  • 电脑的 USB 口比较少,特别是 MAC 电脑,新版的就更是少得可怜
  • 有时候有些功能模块比较耗电的时候,手机耗电的速度会比电脑充电的速度慢,比如开发直播间模块,要长时间开摄像头的情况下

Android 10以及更低的版本,必须通过 USB 连接后,才可实现同一 WLAN 下无线调试:

  • 手机和电脑需连接在同一 WiFi 下;
  • 手机开启开发者选项和 USB 调试模式,并通过 USB 连接电脑(即adb devices -l,可以查看到手机)
  • 设置手机的监听adb tcpip 5555
  • 拔掉 USB 线,找到手机的 IP 地址
  • 通过 IP 连接到手机adb connect ip(端口默认:5555)
  • adb devices -l命令查看

:Android 8 以上默认是开启Wi-Fi调试模式的,Android 4~Android 8默认是不开启Wi-Fi调试模式的

1)手机和电脑通过USB方式连接

  • 输入adb tcpip PORT,PORT表示要开启WI-FI调试的端口,建议使用默认端口5555
adb tcpip 5555

  • 使用adb shell在手机端执行setprop service.adb.tcp.port PORT命令,port表示要开启WI-FI调试的端口,建议使用默认端口5555
  • 使用adb shell在手机端执行stop adbd
  • 使用adb shell在手机端执行start adbd。执行adb shell会默认启动adbd,所以adbd start不是必须执行的
adb shell
# ">"为在手机端shell模式中
> setprop service.adb.tcp.port 5555
> stop adbd # 执行完会断开"adb shell"
adb shell adbd start

2)不需要adb的方式

  • root的手机不需要用usb链接电脑使用adb的方式来开启WI-FI调试模式。不过需要安装一个可以执行命令的App

  • 手机需要Root,且安装了可以执行命令的App。下载安装Android-Terminal-Emulator,然后打开“模拟终端”,然后执行以下命令

    • Android-Terminal-Emulator下载地址:https://jackpal.github.io/Android-Terminal-Emulator/

TCP/IP方式:

# 执行如下命令来,使用WI-FI调试模式
su
setprop service.adb.tcp.port 5555
stop adbd
start adbd

su && setprop service.adb.tcp.port 5555 && stop adbd && start adbd

USB方式:

su
setprop service.adb.tcp.port -1 # 表示打开adb的usb调试功能
stop adbd
start adbd
  • 保证手机和电脑在同一个局域网内,查看手机Wi-Fi的IP地址,并且手机开启了Wi-Fi调试功能
  • 使用adb connect HOST[:PORT]与手机进行连接,port默认5555
# 以下两个命令都可以,不输入端口号则默认使用":5555"
adb connect 192.168.9.5
adb connect 192.168.9.5:5555

断开WI-FI调试连接的命令

# 以下两个命令都可以,不输入端口号则默认使用":5555"
adb disconnect 192.168.9.5
adb disconnect 192.168.9.5:5555

3.3.3 网络流量形式检测是否攻击者adb接入Android 设备方法

网络形式的ADB会监听一个端口,那有一个非常简单的方法就是判断这个端口是不是有TCP连接,只要有连接,我们就认为被ADB控制了(很简单的判断,没去管有无数据传输,就算没得数据传输,但是连接了,说明这个设备其实也被ADB控制过)

service.adb.tcp.port表示网络无线ADB要配置的默认端口

回到检测规则上来,如果没设置service.adb.tcp.port的话,得到的应该是空值或啥也没有回显

  • 先用getprop命令去得到adb监听的端口,如果为空的话就是5555,然后就可以看adb监听的端口是否有连接,下面是统计5555端口连接次数,来判断当前有几个掉毛接入进来
setprop service.adb.tcp.port 5555
getprop | grep adb.tcp.port
netstat -an | grep 5555

  • 统计adb已连接的次数,状态为"established"
netstat -nat | grep -i "5555" | grep ESTABLISHED | wc -l

补充:还有一个方法,是获取TCP流量data数据的二进制流,来检测adbd接收到的TCP数据包中是否包含”host:version”,且相应回去的TCP数据包中包含有:"OKAY",就告警。但许多时候Android 终端因为隐私问题,数据采集受限,感兴趣的可以自行尝试一下,博主同学这里没有没有去尝试

参考链接

https://www.jianshu.com/p/790b29b80117

https://cloud.tencent.com/developer/article/1809910

https://blog.csdn.net/Xiaoma_Pedro/article/details/103919142

https://blog.csdn.net/u014135607/article/details/80011192

https://www.jianshu.com/p/a47e1c90b9bf

https://zhuanlan.zhihu.com/p/472198430

https://cczheng.blog.csdn.net/article/details/103434756

http://www.shibaking.com/blog/2020-04-08-Android%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91user%E7%89%88%E6%9C%AC%E9%BB%98%E8%AE%A4%E5%BC%80%E5%90%AFadb%E8%B0%83%E8%AF%95.html


你以为你有很多路可以选择,其实你只有一条路可以走


[免费专栏] ATTACK安全之检测车机中ADB远程调试控制Android系统攻击相关推荐

  1. intellij远程调试_IntelliJ中的远程调试Wildfly应用程序

    intellij远程调试 远程调试Java应用程序意味着使用本地开发环境连接到远程运行的应用程序. Java开箱即-agentlib:jdwp[=options]支持远程调试:目标应用程序必须使用-a ...

  2. IntelliJ中的远程调试Wildfly应用程序

    远程调试Java应用程序意味着使用本地开发环境连接到远程运行的应用程序. Java开箱即-agentlib:jdwp[=options]支持远程调试:目标应用程序必须使用-agentlib:jdwp[ ...

  3. [免费专栏] ATTACK安全之Android车机证书攻击场景检测「检测系统代理」

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 ATTACK付费专栏长期更新,本篇最新内容请前往: [车联网 ...

  4. 微信小程序showToast在真机中显示时间不可控制,显示时间短

    现象: 使用uniapp开发微信小程序,使用showToast,设置duration来控制提示展现时长,发现在微信开发者工具正常,在真机中显示时间比较短,并且设置duration不生效. 原因: 排查 ...

  5. VS.net中的远程调试

    简介: 我们开发完一个软件后,提交给客户或者放到服务器上执行,如果有问题,我们可以使用远程调试进行发现bug. 这篇文字简单介绍一下远程调试. vs.net远程调试服务 在vs.net的安装目录下,有 ...

  6. 开发机多用户 xdebug 远程调试 PhpStorm

    在公司都用的远程开发机开发,每次有错误调试就得dd(xxx)然后保存真是,让我在本地开发用惯xdebug的情何以堪,所以有了下文. 1.安装配置xdebug 直接使用pecl安装即可 # pecl i ...

  7. Android开发中adb命令的常用方法

    Android的SDK中提供了很多有用的工具,在开发过程中如果能熟练使用这些工具,会让我们的开发事半功倍.adb是SDK提供的一个常用的命令行工具,全称为Android Debug Bridge,用于 ...

  8. IDEA中JAVA代码调试技巧

    提示:以下debug调试方式仅适用于在IDEA中使用 文章目录 前言 一.条件断点调试 二.回到"上一步"调试 三.多线程调试 四.远程调试 (1)项目启动时,先允许远程调试: ( ...

  9. [免费专栏] Android安全之Android APP应用程序的汉化功能 (修改so中的字符串内容)

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

最新文章

  1. 【Quartz】实现接口封装化(二)
  2. R语言eval函数评估表达式或者字符串实战
  3. Spring-Boot:写出来的网站访问不到静态资源?怎样通过url访问SpringBoot项目中的静态资源?localhost:8989/favicon.ico访问不了工程中的图标资源?
  4. matplotlib嵌入到pyqt中
  5. tee 和 ree分别是什么意思?
  6. 树莓派4视频输出接口_树莓派第四代来啦!4G内存、支持双屏4K输出和H265硬解
  7. 二十、 二叉树的同构
  8. 微软正式发布 gRPC-Web for .NET
  9. 使用PowerMock测试对象的内部状态
  10. visio2013复制到word有多余白边_学习工坊(一)|实用技巧之Word篇
  11. 深入理解iPhone数据持久化(手把手教你iphone开发 - 基础篇)
  12. 一、 zedboardubuntu 14.04 的前期准备(定期更新)
  13. PAIP.FLEX与JAVA数据对应关系.txt
  14. 关于php中gettext的用法?
  15. Java项目 yaml明文密码加密
  16. 关于html的实训日志,满足你的甜蜜幻想, 《我与她的实习日志》登陆NS
  17. 谈一下MVVM 框架
  18. 英语——长难句分析及技巧
  19. (5)CC3200学习之串口
  20. 简谈高通Trustzone的实现【转】

热门文章

  1. 惊世奇闻,原来曹操 周瑜 诸葛亮 是一家人?
  2. 敲7 输出7和7的倍数,还有包含7的数字例如(17,27,37...70,71,72,73...,首先输入一个整数t,表示有t组数据。 然后有t行,每行一个整数N。(N不大于30000)
  3. JOSN数据格式—JOSN是什么
  4. VS2008C#Sqlserver2008数据库的连接以及增删改查
  5. 全国计算机等级考试网怎么注册
  6. excel做地图热力图_汇报更出彩使用Excel制作热力图
  7. TCP 三次握爪 四次挥手
  8. 详解GIoU、DIoU、CIoU Loss
  9. python 小说 云_用python实现自己的小说阅读器
  10. 【学习】02 今日头条爬虫-采集和下载关键词“新垣结衣”的图集图片