内容简介:前面的几篇文章都是静态分析osquery的源代码,网上鲜有网站讲如何调试osquery的源代码。为了更加方便地理解osquery的整个运行机制,本篇文章对osquery的动态调试的环境搭建以及osquery的基本架构进行一个简要的说明。本文是利用
前面的几篇文章都是静态分析osquery的源代码,网上鲜有网站讲如何调试osquery的源代码。为了更加方便地理解osquery的整个运行机制,本篇文章对osquery的动态调试的环境搭建以及osquery的基本架构进行一个简要的说明。
调试环境搭建
本文是利用 ubuntu18
配合 clion
进行调试的。除此之外,还需要安装 git
、 cmake
、 ruby
, python2
以及 pip(latest)
下载源代码
git clone https://github.com/facebook/osquery.git
,注意此时的分支是 experimental
分支(此分支不适用于 cmake
的方式构建和调试),我们首先要切换至 master
分支。 git checkout master
安装依赖
make sysprep
运行这个将会从aws上面下载编译/运行所需要的所有的依赖。由于依赖较多,所以这将是一个漫长的过程。 make sysprep
将会做
- 更新系统
- 安装需要的依赖
- 初始化CMake项目引用的第三方git子模块
- 安装osquery的toolchain
- 使用
Linuxbrew
安装需要在/usr/local/osquery
中需要存在的lib
设置编译选项
首先需要在 CMakeLists.txt
的最前面加上 set(CMAKE_BUILD_TYPE DEBUG)
,表示开启调试。
然后由于我们使用 clion
打开osquery的源代码。由于 osquery
采用的是 clang
调试。所以在 File/Settings/Build,Execution,Deployment/CMake
中的 Environment
中进行如下的设置:
-
LDFLAGS
,-L/usr/local/osquery/legacy/lib -L/usr/local/osquery/lib -B/usr/local/osquery/legacy/lib -rtlib=compiler-rt -fuse-ld=lld
-
CXX
,/usr/local/osquery/bin/clang++
-
CC
,/usr/local/osquery/bin/clang
-
PATH
,/usr/local/osquery/bin:<YOUR_ORIGINAL_PATH>
分析 CMakeLists.txt
中的250行左右的设置,我们发现
# make debug (environment variable from Makefile) if(DEFINED ENV{DEBUG}) set(CMAKE_BUILD_TYPE "DEBUG") WARNING_LOG("Setting DEBUG build") ....
其中 DEFINED ENV{DEBUG}
表示需要通过环境变量设置 DEBUG
才能够使之生效,所以我们还要在 Environment
中加上 DEBUG:whatever
。最终就变为了:
创建文件夹
由于 cmake
在编译时,需要向 cmake-build-debug/generated
中写入内容。但是 cmake
并不会自动地创建 generated
这个文件夹,所以我们需要在 cmake-build-debug
文件夹中创建 generated
文件夹。
设置调试模式
之前的文章中也说到,osquery存在 osqueryi
和 osqueryd
两种模式。为了方便分析调试osquery的功能,我们采用 osqueryi
的模式进行调试。我们编辑 Run/Debug Configuration
,选择 daemon
,在 Program arguments
中添加 -S
的参数。如下所示:
运行测试
Debug 'daemon'
,如果在 Messages
中出现
[100%] Building CXX object osquery/CMakeFiles/libosquery.dir/numeric_monitoring/numeric_monitoring.cpp.o [100%] Building CXX object osquery/CMakeFiles/libosquery.dir/numeric_monitoring/plugin_interface.cpp.o [100%] Building CXX object osquery/CMakeFiles/libosquery.dir/numeric_monitoring/pre_aggregation_cache.cpp.o [100%] Building CXX object osquery/CMakeFiles/libosquery.dir/profiler/posix/profiler.cpp.o [100%] Linking CXX static library libosquery.a [100%] Built target libosquery [100%] Built target libosquery_additional [100%] Building CXX object osquery/CMakeFiles/daemon.dir/devtools/shell.cpp.o [100%] Building CXX object osquery/CMakeFiles/daemon.dir/main/main.cpp.o [100%] Building CXX object osquery/CMakeFiles/daemon.dir/main/posix/main.cpp.o [100%] Linking CXX executable osqueryd -- Building osqueryd: /home/develope/osquery/cmake-build-debug/osquery/osqueryd [100%] Built target daemon Build finished
在 Debug
中出现
/path/to/cmake-build-debug/osquery/osqueryd -S Using a virtual database. Need help, type '.help' osquery>
至此,环境就搭建成功了。
启动分析
首先程序会进入 osquery/main/posix/main.cpp
,
int main(int argc, char* argv[]) { // On POSIX systems we can jump immediately into startOsquery. // A short abstraction exists to allow execute-as-service checks in Windows. return osquery::startOsquery(argc, argv); }
跟踪 startOsquery
,进入到 osquery/main/main.cpp
:
int startOsquery(int argc, char* argv[], std::function<void()> shutdown) { // Parse/apply flags, start registry, load logger/config plugins. osquery::Initializer runner(argc, argv, osquery::ToolType::SHELL_DAEMON); ...... runner.installShutdown(shutdown); runner.initDaemon(); // When a watchdog is used, the current daemon will fork/exec into a worker. // In either case the watcher may start optionally loaded extensions. runner.initWorkerWatcher(kWatcherWorkerName); if (runner.isDaemon()) { return startDaemon(runner); } return startShell(runner, argc, argv); }
在 startOsquery()
中,发现是根据 runner.isDaemon()
的结果来判断是运行 osqueryd
还是 osqueryi
。发现是利用 osquery::Initializer runner(argc, argv, osquery::ToolType::SHELL_DAEMON);
初始化了 runner
。
跟踪 runner()
,进入到 osquery/core/init.cpp
:
发现会根据 osquery
的启动路径判断采用什么模式。如果文件路径中存在 osqueryd
,则采用 osqueryd
模式,设置 kToolType = ToolType::DAEMON;binary_ = "osqueryd";
否则设置 kToolType = ToolType::SHELL;binary_ = "osqueryi";
。
在后面还存在对参数的检测,如下:
其中 *argv_
保存的就是参数值。通过 auto help = std::string((*argv_)[i]);
得到参数类型。如果发现是 -S
,设置 kToolType = ToolType::SHELL;binary_ = "osqueryi"
否则设置 kToolType = ToolType::DAEMON;binary_ = "osqueryd"
。
回到 osquery/main/main.cpp
中:
static bool isDaemon() { return kToolType == ToolType::DAEMON; } /// Check the program is the osquery shell. static bool isShell() { return kToolType == ToolType::SHELL; } if (runner.isDaemon()) { return startDaemon(runner); } return startShell(runner, argc, argv);
这也就是为什么我们在前面 设置调试模式
中需要通过 -S
来开启 osqueryi
的调试方法的原因了。
最后就会在 Debug
中出现如下的界面:
/path/to/cmake-build-debug/osquery/osqueryd -S Thrift: Thu Jan 3 23:33:51 2019 TSocket::open() connect() <Host: Port: 0>Connection refused I0104 13:07:53.836697 111926 database.cpp:563] Checking database version for migration I0104 13:07:53.885915 111926 database.cpp:587] Performing migration: 0 -> 1 I0104 13:07:53.925979 111926 database.cpp:619] Migration 0 -> 1 successfully completed! I0104 13:07:53.926038 111926 database.cpp:587] Performing migration: 1 -> 2 I0104 13:07:53.926084 111926 database.cpp:619] Migration 1 -> 2 successfully completed! I0104 13:07:53.999048 111926 dispatcher.cpp:81] Adding new service: ExtensionWatcher (0x3288a18) to thread: 140001576392448 (0x3270540) in process 111926 I0104 13:07:54.022429 111926 dispatcher.cpp:81] Adding new service: ExtensionRunnerCore (0x3241f38) to thread: 140001567999744 (0x32711a0) in process 111926 Using a virtual database. Need help, type '.help' osquery>
动态调试
以一个简单的 SQL 语句 select * from hosts;
来说明如何调试。
当我们在 osquery>
中输入 select * from hosts;
按下回车之后,上面的启动流程又会重新走一遍。整个调用执行过程如下:
当执行到 osquery/devtools/shell.cpp
中的 process_input(struct callback_data* p, FILE* in)
函数中的 rc = shell_exec(zSql, shell_callback, p, &zErrMsg);
其中 zSql
就是我们在 shell
中输入的SQL语句。如下
之后会进入到 shell.cpp::shell_exec()
中的 rc = sqlite3_step(pStmt);
来调用。之后就会进入到 sqlite3.c
中使用 sqlite
来执行所输入的查询语句。
但是在前面中已经说到了,osquery的 shell 查询语法采用的是sqlite,但是底层的数据存储采用的是 rocksdb
。所以这个中间就会存在一个转换的过程。所以当 third-party/sqlite3/sqlite3.c
在中执行 rc = pModule->xFilter(pVCur, iQuery, pOp->p4.z, nArg, apArg);
,就会调用 osquery/sql/virtual_table.cpp::xFilter(...)
函数。
在 xFilter(..)
函数中的关键代码是 pCur->data = table->generate(context);
通过 context
去寻找到对应的表,同时生成对应的数据。
通过上图可以发现,在 context
中保存了我们查询所有的信息,包括表名、列名等信息(具体信息,需要进一步地深入研究,但是本人的水平较菜,信息较多,不知如何查看)。
之后程序根据 context
的信息会进入到 cmake-build-debug/generated/additional_amalgamation.cpp
。其中存在 etcHostsTablePlugin
类,调用其中的 generate(QueryContext& context)
方法。如下:
最终程序会进入到 osquery/tables/networking/etc_hosts.cpp
。在此文件中存在两个函数 genEtcHosts()
和 parseEtcHostsContent()
。其中 genEtcHosts()
是入口函数,而 parseEtcHostsContent()
则是具体的功能实现函数,用于读取不同平台下的存储 Linux 下的文件。例如在Linux下读取的是 /etc/hosts
文件。在 windows
平台下是读取 system32\\drivers\\etc\\hosts
和 system32\\drivers\\etc\\hosts.ics
文件。
函数实现比较简单,就不作说明了。最终将查询的数据层层向上返回,这样 sqlite 就查询到了数据,输出结果:
osquery> select * from hosts; select * from hosts;select * from hosts; +-----------+----------------------------+ | address | hostnames | +-----------+----------------------------+ | 127.0.0.1 | localhost | | 127.0.1.1 | ubuntu | | ::1 | ip6-localhost ip6-loopback | | fe00::0 | ip6-localnet | | ff00::0 | ip6-mcastprefix | | ff02::1 | ip6-allnodes | | ff02::2 | ip6-allrouters | +-----------+----------------------------+
一个简单的动态分析过程就结束了。整个过程中,比较复杂的地方是在于 sqlite
与 VirtualTable
的交互。这一点由于个人能力有限,也无法抽丝剥茧,分析得很透彻。
打包
之前的文章都是分析源代码,我们可以修改源代码,然后配合本篇的文章重新编译运行得到一个新的可运行的osquery进程。但是很多时候我们会有这样的需求,我们通过修改osquery完成一些我们定制化的需求,然后安装在其他的主机上面。此时我们就需要将我们修改之后的osquery打包成为一个rpm/deb/tar.gz的包以供其他的主机安装。
package
osquery中的文档说明直接使用 make package
即可进行打包。但是可能会出现以下的问题:
[develope@localhost osquery]$ make package which: no ctags in (/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/develope/.local/bin:/home/develope/bin) which: no cscope in (/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/develope/.local/bin:/home/develope/bin) # Alias for packages (do not use CPack) -- Welcome to osquery's build -- thank you for your patience! :) -- For a brief tutorial see: http://osquery.readthedocs.io/en/stable/development/building/ -- If at first you dont succeed, perhaps: make distclean; make depsclean -- Building for platform CentOS (centos, centos7) -- Building osquery version 3.3.1-1-g5188ce5 sdk 3.3.1 -- Cannot find fpm executable in path -- Configuring done -- Generating done -- Build files have been written to: /home/develope/osquery/build/centos7 Building linux packages (no custom config)
最为关键的错误信息是: Cannot find fpm executable in path
表示缺少fpm包;根据 https://www.digitalocean.com/community/tutorials/how-to-use-fpm-to-easily-create-packages-in-multiple-formats这篇文章的介绍,fpm是一个打包工具,可以将代码打包成为多个平台的安装包;
fpm
根据文档 installing-things-fpm-needs 进行安装
sudo yum install ruby-devel gcc make rpm-build rubygems gem install --no-ri --no-rdoc fpm
安装成功之后,使用fpm –version查看版本;
package
再次使用 make package
打包。打包成功之后就会在 home/develope/osquery/build/centos7
生成以下文件
-rw-rw-r--. 1 develope develope 7450048 Nov 20 07:28 osquery-3.3.1-1-g5188ce5-1.arch-x86_64.pkg.tar.xz -rw-rw-r--. 1 develope develope 10359040 Nov 20 07:27 osquery_3.3.1-1-g5188ce5_1.linux.amd64.deb -rw-rw-r--. 1 develope develope 10296480 Nov 20 07:28 osquery-3.3.1_1_g5188ce5-1.linux.x86_64.rpm -rw-rw-r--. 1 develope develope 10350920 Nov 20 07:28 osquery-3.3.1-1-g5188ce5_1.linux_x86_64.tar.gz -rw-rw-r--. 1 develope develope 41435800 Nov 20 07:27 osquery-dbg_3.3.1-1-g5188ce5_1.linux.amd64.deb -rw-rw-r--. 1 develope develope 43372360 Nov 20 07:28 osquery-debuginfo-3.3.1_1_g5188ce5-1.linux.x86_64.rpm
rpm包和deb包均有两个版本(debug版本和正式版本)
总结
前面的文章都是静态分析,本篇文章是从动态分析的角度来看osquery的运行过程,从而能够对osquery的整个运行机制,整个执行流程能够有更加清晰的认识。最后还要感觉组内大佬们无私的帮助以及alessandrogar的耐心解答。
拥有快速学习能⼒的⽩帽子,是不能有短板的。有的只是⼤量的标准板和⼏块长板。
以上
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 解决游戏开发人员“累死累活改界面,调试打包总通宵”的难题
- iOS常用调试方法:断点调试
- 断点调试和日志调试之间的平衡点:函数计算调试之 Python 篇
- .NET高级调试系列-Windbg调试入门篇
- VisualStudio 通过外部调试方法快速调试库代码
- 【前端打包部署】谈一谈我在SPA项目打包=>部署的处理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。