tcpsnitch
是一个跟踪工具,旨在调查应用程序和 TCP/IP 堆栈之间的交互。 tcpsnitch
运行指定的命令,直到退出并拦截 Internet 套接字上的所有 libc 函数调用。
为了温和地启动,可以运行以下命令来跟踪curl
程序:
$ tcpsnitch curl google.com
对于每个打开的互联网套接字, tcpsnitch
都会构建一个函数调用的有序列表(在本文档的其余部分中,函数调用称为事件)。对于每个事件, tcpsnitch
都会记录参数、返回值和各种信息,例如当前时间戳或线程 ID。具体来说, connect()
事件在套接字跟踪中可能如下所示:
{
"type" : " connect " ,
"timestamp_usec" : 1491043720731853 ,
"return_value" : 0 ,
"success" : true ,
"thread_id" : 17313 ,
"details" : {
"addr" : {
"sa_family" : " AF_INET " ,
"ip" : " 127.0.1.1 " ,
"port" : " 53 "
}
}
}
套接字跟踪写入文本文件,其中每一行都是表示单个事件的 JSON 对象。这样的跟踪的头部可能是这样的:
{ "type" : " socket " , "timestamp_usec" : 1491043720731840 , "return_value" : 6 , "success" : true , "thread_id" : 17313 , "details" : { "sock_info" : { "domain" : " AF_INET " , "type" : " SOCK_DGRAM " , "protocol" : 0 , "SOCK_CLOEXEC" : false , "SOCK_NONBLOCK" : true }}}
{ "type" : " ioctl " , "timestamp_usec" : 1491043720765019 , "return_value" : 0 , "success" : true , "thread_id" : 17313 , "details" : { "request" : " FIONREAD " }}
{ "type" : " recvfrom " , "timestamp_usec" : 1491043720765027 , "return_value" : 44 , "success" : true , "thread_id" : 17313 , "details" : { "bytes" : 2048 , "flags" : { "MSG_CMSG_CLOEXEC" : false , "MSG_DONTWAIT" : false , "MSG_ERRQUEUE" : false , "MSG_OOB" : false , "MSG_PEEK" : false , "MSG_TRUNC" : false , "MSG_WAITALL" : false }, "addr" : { "sa_family" : " AF_INET " , "ip" : " 127.0.1.1 " , "port" : " 53 " }}}
{ "type" : " ioctl " , "timestamp_usec" : 1491043720770075 , "return_value" : 0 , "success" : true , "thread_id" : 17313 , "details" : { "request" : " FIONREAD " }}
{ "type" : " recvfrom " , "timestamp_usec" : 1491043720770094 , "return_value" : 56 , "success" : true , "thread_id" : 17313 , "details" : { "bytes" : 65536 , "flags" : { "MSG_CMSG_CLOEXEC" : false , "MSG_DONTWAIT" : false , "MSG_ERRQUEUE" : false , "MSG_OOB" : false , "MSG_PEEK" : false , "MSG_TRUNC" : false , "MSG_WAITALL" : false }, "addr" : { "sa_family" : " AF_INET " , "ip" : " 127.0.1.1 " , "port" : " 53 " }}}
由于单个命令可能派生多个进程( tcpsnitch
跟随派生),因此属于给定进程的所有套接字跟踪都放在一个目录中,并以跟踪的进程命名。在这样的目录中,套接字跟踪是根据进程打开它们的顺序来命名的。
默认情况下,跟踪记录保存在/tmp
下的随机目录中,并自动上传到 www.tcpsnitch.org,这是一个旨在集中、可视化和分析跟踪记录的平台。请注意,所有上传的跟踪都是公开的,任何人都可以查阅和下载。
正如下一个代码片段所示, tcpsnitch
为您提供了可用跟踪的 URL。
$ tcpsnitch curl google.com
Trace saved in /tmp/tmp.4ERKizKyU3.
Uploading trace....
Trace successfully uploaded at https://tcpsnitch.org/app_traces/20.
Trace archive will be imported shortly. Refresh this page in a few minutes...
请注意,导入跟踪需要几分钟(即提取跟踪存档并将所有事件插入数据库)。导入后,可能还需要几分钟来计算迹线的定量分析。
最后, tcpsnitch
还允许按用户定义的时间间隔提取TCP_INFO
套接字选项,并记录每个单独套接字的.pcap
跟踪。请参阅使用部分以获取更多信息。
tcpsnitch
允许跟踪以下应用程序:
由于tcpsnitch
通过使用LD_PRELOAD
环境变量拦截对 libc 函数的调用来工作,因此无法对与 libc 静态链接的应用程序执行跟踪。
注意:在 Linux 上,Chrome(以及任何基于 Chromium 的应用程序,如 Electron、Opera 等)已知不兼容。
对于想要跟踪 Android 应用程序的用户,请向下滚动到“Android 编译”部分。
在 Ubuntu 16 和 14、Debian 8、Elementary 0.4、Mint 18 上测试
sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install make gcc gcc-multilib libc6-dev libc6-dev:i386 libjansson-dev libjansson-dev:i386 libpcap0.8 libpcap0.8:i386 libpcap0.8-dev
在 Fedora 25 和 CentOS 7 上测试
sudo yum install make gcc glibc-devel glibc-devel.i686 libgcc libgcc.i686 libpcap-devel.x86_64 libpcap-devel.i686 jansson jansson.i686 && curl -O http://www.digip.org/jansson/releases/jansson-2.10.tar.bz2 && bunzip2 -c jansson-2.10.tar.bz2 | tar xf - && rm -f jansson-2.10.tar.bz2 && cd jansson-2.10 && ./configure && make && sudo make install && cd .. && rm -rf jansson-2.10
构建和安装:
./configure
make
sudo make install
用法: tcpsnitch [<options>] <cmd> [<cmd_args>]
其中:
<options>
是tcpsnitch
选项<cmd>
是要跟踪的命令(强制)<cmd_args>
是<cmd>
的参数。这是一个使用curl
和所有默认选项的简单示例:
tcpsnitch curl google.com
您可以发出tcpsnitch -h
来获取有关受支持选项的更多信息。最重要的是以下几点:
-b
和-u
用于按用户定义的时间间隔提取TCP_INFO
。有关详细信息,请参阅“提取TCP_INFO
”部分。-c
用于捕获套接字的pcap
痕迹。有关详细信息,请参阅“数据包捕获”部分。-a
和-k
用于跟踪 Android 应用程序。有关详细信息,请参阅“Android 使用”部分。-n
停用跟踪的自动上传。-d
设置将写入跟踪的目录(而不是/tmp
中的随机目录)。-f
设置保存到文件的日志的详细级别。默认情况下,仅将 WARN 和 ERROR 消息写入日志。这主要用于报告错误和调试。-l
与-f
类似,但设置 STDOUT 上的日志详细程度,默认情况下仅显示错误消息。这用于调试目的。-t
控制将事件转储到文件的频率。默认情况下,事件每 1000 毫秒写入文件一次。-v
目前没什么用,但它应该以strace
的风格将tcpsnitch
置于详细模式。仍有待实施(目前仅显示事件名称)。TCP_INFO
-b <bytes>
和-u <usec>
允许按用户定义的时间间隔提取每个套接字的TCP_INFO
套接字选项的值。请注意, TCP_INFO
值与 socekt 的 JSON 跟踪中的任何其他事件一样显示。
-b <bytes>
,在套接字上发送和接收的每个<bytes>
都会记录TCP_INFO
。-u <usec>
,每<usec>
微秒记录一次TCP_INFO
。TCP_INFO
。默认情况下此选项处于关闭状态。另请注意, tcpsnitch
仅在调用重写函数时检查这些条件。
-c
选项激活对每个套接字的.pcap
跟踪的捕获。请注意,您需要具有适当的权限才能捕获接口上的流量(有关此类权限的更多信息,请参阅man pcap
)。
此功能目前不适用于 Android。
Android 上的使用过程分为两步,与 Linux 上的使用非常相似。首先, tcpsnitch
设置并启动要使用适当选项进行跟踪的应用程序,然后从设备中提取跟踪并将其复制到主机。
Android 支持所有选项,但用于捕获.pcap
跟踪的-c
选项除外。
必须在设备上完成一些初步设置步骤:
adb devices
并确保您的手机可见(您应该在第二列中看到device
)。当通过adb
访问设备时,用法与 Linux 上几乎相同:
-a
常规tcpsnitch
命令以指示您要跟踪所连接的 Android 设备上的应用程序。请注意, <cmd>
参数必须与通过简单grep
安装在设备上的包的名称匹配。例如,要跟踪包名为org.firefox.com
的 Firefox 应用程序,可以发出tcpsnitch -a firefox
。 tcpsnitch
将通知您找到匹配的软件包并立即启动应用程序。tcpsnitch -k <package>
来终止应用程序并终止跟踪进程。跟踪将从设备中提取并保存在磁盘上的/tmp
中,然后上传到 www.tcpsnitch.org。重要提示:您必须重新启动 Android 设备才能完全停用跟踪。由于tcpsnitch
使用 Android 属性来设置LD_PRELOAD
库,并且这些属性无法取消设置,因此必须重新启动设备才能删除这些属性(也许有人知道更好的解决方案?)。
以下是跟踪 Firefox 的完整示例:
$ tcpsnitch -a firefox
Found Android package: ' org.mozilla.firefox ' .
Uploading tcpsnitch library to /data/libtcpsnitch.so.0.1-arm.
Start package ' org.mozilla.firefox ' .
Execute ' ./tcpsnitch -k firefox ' to terminate the capture.
# INTERACTING WITH APPLICATION
$ tcpsnitch -k firefox
Found Android package: ' org.mozilla.firefox ' .
Pulling trace from Android device....
Trace saved in /tmp/tmp.MidCH9rm3x.
Uploading trace....
Trace successfully uploaded at https://tcpsnitch.org/app_traces/21.
Trace archive will be imported shortly. Refresh this page in a few minutes...
请注意,如果一个包有多个匹配项,则将使用第一个匹配的包。因此,您可能需要更加具体以避免冲突。您可以执行adb shell pm list packages
来获取设备上安装的所有软件包的名称。
另请注意,单个设备必须对adb
可见。
为了跟踪 Android 应用程序, tcpsnitch
必须使用 Android 本机开发工具包 (NDK) 进行编译。编译过程比较复杂,并且安装需要有 root 权限的 Android 设备。
基本上,它涉及以下步骤:
libjansson
和libpcap
,并使编译后的库和头文件可用于独立工具链。tcpsnitch
并准备 Android 设备。以下部分提供了一个复杂的示例,将引导您完成所有步骤。
一些假设:
<NDK_PATH>
。<TCPSNITCH_PATH>
。首先,我们定义几个变量:
export TCPSNITCH=<TCPSNITCH_PATH>
export NDK=<NDK_PATH>
# Where the standalone toolchain WILL be created
export TOOLCHAIN=<TOOLCHAIN_PATH>
现在,我们首先为运行 Android API 23(版本 6.0,Marshmallow)的 ARM 设备生成独立工具链。 Android 版本和 API 级别之间的对应关系如下页所示。
$NDK/build/tools/make_standalone_toolchain.py --arch arm --api 23 --install-dir $TOOLCHAIN
我们现在必须使用 NDK 编译libjansson
和libpcap
。完成后,我们必须在独立工具链的“sysroot”中安装它们的头文件和编译后的库。
让我们从libjansson
开始:
git clone https://github.com/akheron/jansson && cd jansson
# Configuration file which we don't use, we may leave it empty
touch src/jansson_private_config.h
sed -i 's/BUILD_SHARED_LIBRARY/BUILD_STATIC_LIBRARY/g' Android.mk
$NDK/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk
cp obj/local/armeabi/libjansson.a $TOOLCHAIN/sysroot/usr/lib/
cp src/jansson.h android/jansson_config.h $TOOLCHAIN/sysroot/usr/include/
cd .. && rm -rf jansson
现在,让我们来解决libpcap
:
git clone https://github.com/the-tcpdump-group/libpcap && cd libpcap
export CC=$TOOLCHAIN/bin/arm-linux-androideabi-gcc
./configure --host=arm-linux --with-pcap=linux --prefix=/usr
# You will need to install some missing dependencies (e.g. `flex` & `bison`)
sudo apt-get install flex bison
# Reissue ./configure untill all dependencies are met
./configure --host=arm-linux --with-pcap=linux --prefix=/usr
# Compile && install in toolchain
make && sudo make install DESTDIR=$TOOLCHAIN/sysroot
cd .. && rm -rf libpcap
我们现在准备编译tcpsnitch
:
# First, let's fix the buggy `tcp.h` header from the NDK
sed -i 's/include <linux/tcp.h>/include <sys/cdefs.h>n#include <linux/tcp.h>/g' $TOOLCHAIN/sysroot/usr/include/netinet/tcp.h
# Configure the compiler
export CC_ANDROID=$TOOLCHAIN/bin/arm-linux-androideabi-gcc
# Build & install tcpsnitch
make android && sudo make install
你准备好了!有关如何启动跟踪应用程序的信息,请参阅 Android 使用部分。
Linux 动态链接器 ( ld.so
) 的一个有趣功能是能够在程序依赖项列表中指定的库之前链接用户指定的共享库。此功能可以通过LD_PRELOAD
环境变量进行控制,该变量包含附加用户指定库的列表(可能为空)。特别是,此LD_PRELOAD
变量可能会强制动态链接器在libc
库之前链接用户指定的共享库。因此,在此用户指定的库中定义的任何函数都优先于libc
中定义的具有相同签名的函数。
这里的含义是它允许拦截对系统调用包装函数的调用。我们只需添加一个自定义共享库,将这些系统调用包装函数重新定义为LD_PRELOAD
。这样的 shim 库会透明地拦截libc
函数调用,并在调用原始libc
包装函数之前执行一些处理。
wrong ELF class
错误是什么?没什么不好的,这些可以忽略。 tcpsnitch
共享库针对 32 位和 64 位体系结构进行编译。跟踪命令时,两个库都会加载到LD_PRELOAD
环境变量中,因为没有简单的方法可以了解命令二进制文件的体系结构(通常是执行另一个二进制文件的 shell 脚本)。然后,动态链接器负责加载兼容库并忽略第二个库(但仍然抛出错误)。
欢迎来 https://gitter.im/Tcpsnitch 讨论tcpsnitch
。
作者的电子邮件是 gregory.vanderschueren[at]gmail.com