------ Tor(洋葱路由器)匿名网络源码分析——主程序入口点(一)------

 

————————————————————————————————————————————————————————
《概览》shell

tor 的源码包能够从官网下载,可能须要预先利用其它=*翻^=*墙*软件才能访问该站点。分析 tor 源码有助于咱们理解当代最强大之一的编程

互联网匿名、隐身、审查规避软件的运做原理。windows

为了从总体上把握住程序的逻辑与功能,本系列会将源码重要部分经过函数调用流程图总结,以便站在设计思想的高度来考察 tor。数组

 

《约定》安全

当引用函数/结构体/宏/定义/声明时,我会在圆括号内给出它所在源文件的完整路径,必要时还会给出代码行号,例如:网络

tor_main()(\tor-0.3.1.8\src\or\main.c)——3682编程语言

中间的路径省略掉解压至磁盘上的驱动器号,假定存放于根目录下 
函数

源码的版本为 0.3.1.8(引用代码片断的截图中也会在右下角给出完整路径,以及代码行号)源码分析

 

《要求》单元测试

tor 以 C 编程语言开发,故须要各位具有关于 C 的基本知识和开发经验,才能有较佳的源码分析体验。

此外,tor 为了实现跨 OS 平台兼容性,源码中常常出现 OS 相关的“条件编译”代码块,所以也要求各位对主流操做系统的用户态编程接口有必定程度的了

解。

 

《反馈》

因为本人知识水平有限,加上工做的关系,本系列内容可能存在谬误之处,且会不按期更新。

欢迎反馈任何勘误,或者加入行列提升分析进展,可在评论处提交本身感兴趣分析的模块和本身的分析博文 URL

————————————————————————————————————————————————————————

 

tor 主程序的入口点从 tor_main()(\tor-0.3.1.8\src\or\main.c)开始,实际上它是被 main()(\tor-0.3.1.8\src\or\tor_main.c)所调用的。

 

把 main() 从 main.c 中分离的缘由在于——其它实现单元测试的源文件(test_*.c)中的 main() 函数,就能够连接 main.c ,

由于后者中不存在同名的 main() 函数,不会产生名称冲突。三者的关系请参考下图:

首先来看 main(),它把调用 tor_main() 返回的整型值保存到局部变量 r 中,而后根据 r 的值来做出相应处理:

若是 0 <= r <= 255 ,则返回 r 的具体值到 main() 的调用者(一般会是负责初始化 Tor 进程运行环境的 CRT 启动例程),不然返回 1。

 

进一步而言,tor_main() 的开头部分就定义了一个整型变量 result,并初始化为 0, tor_main() 的内部逻辑会根据不一样场景把

result 设定为相应的值而后返回给 main()。相关的代码片断以下:

 

 

 

从上两张图可知,main() 把本身收到的两个参数:argc(执行 tor 命令时的参数数量)和 argv(包含具体参数的列表/数组)原封不动的传递给

tor_main(),后者会在特定状况下用到这两个参数,好比调用 tor_init() 时传递过去,而 tor_init() 的主要任务之一,就是经过解析 argv 中携带的命令行

参数信息,来按照用户的意图初始化 tor 系统。

 

关于 Tor 命令行参数字串与个数的传递部分流程,请参考下图:

 

tor_main() 初始化自身的返回值后,咱们遇到了第一个代码条件编译块,它是与 Windows 平台相关的。块中的内容仅在知足

特定条件时才被构建为可执行代码。相关的代码片断以下:

 

对于 32 位 windows 平台,且没有定义“当堆数据损坏时,终止进程的功能(HeapEnableTerminationOnCorruption)”,则按照

MSDN 文档的描述,将其枚举常量的值定义为 1

(HeapEnableTerminationOnCorruption 的做用是,假设 OS 的堆管理器检测到由进程使用的任意堆中有错误,

堆管理器会调用 WER 服务[Windows 错误报告],而后终止进程,该功能打开后,没法由进程关闭)


若已定义该功能,则调用 HeapSetInformation() ,为它的第二个参数传入 HeapEnableTerminationOnCorruption,来打开该功能;

HeapSetInformation() 的第一个参数是到要设置的堆句柄,一般由 HeapCreate() 或 GetProcessHeap() 返回;

因为在此以前,tor_main() 并无建立堆,因此堆句柄为 NULL;从 Windows Vista 开始,默认就启用了“低碎片堆”

(low-fragmenation heap,LFH),因此应用程序会使用或建立 LFH;关于 LFH,请参考:

https://msdn.microsoft.com/en-us/library/windows/desktop/aa366750(v=vs.85).aspx

注意,即使 HeapSetInformation() 调用失败, OS 也会让应用程序继续运行,因此应该对 HeapSetInformation() 的返回值进行

检测:若是没法打开该功能,则 Tor 进程应该返回失败并退出——这正是源码中所遗漏的逻辑。

例如,在 Windows 平台,更健壮的代码以下:

 

 1 BOOL bResult;  2  bResult = HeapSetInformation(NULL,  3  HeapEnableTerminationOnCorruption,  4  NULL,  5                                  0);  6 
 7     if (bResult != FALSE) {  8         _tprintf(TEXT("Heap terminate-on-corruption has been enabled.\n"));  9  } 10     else { 11         _tprintf(TEXT("Failed to enable heap terminate-on-corruption with LastError %d.\n"), 12  GetLastError()); 13         return 1; 14     }

 

 

上图关键之处在于调用 SetProcessDEPPolicy(),永久打开 Tor 主进程的数据执行保护(DEP)功能,

由于 Tor 是一个网络应用程序,须要经过网络频繁收发数据,程序中的任何编码缺陷,均可能致使远程代码执行,因此

添加此一调用能够缓解缓冲区溢出/堆栈溢出攻击形成的危害。

SetProcessDEPPolicy() 是位于 Kernel32.dll 中的 API 函数(参见相关 MSDN 文档中的“系统要求”),

而 Kernel32.dll 采用“加载时动态链接”到 Tor 进程的内存映射中,因此首先要以 GetModuleHandleA() 取得该模块的句柄,

(采用“运行时动态连接”的应用程序会调用 LoadLibrary/Ex())

在成功获取的前提下,以 GetProcAddress() 取得 SetProcessDEPPolicy() 函数的地址,将其赋给以“typedef”类型定义和

声明的函数指针“setdeppolicy”,如能解析到该函数的地址,就能够直接经过调用 setdeppolicy 来“尝试”打开 DEP。

为啥我要强调“尝试”呢?

在较早版本的 Windows 中,经过 GetProcAddress() 来获取 SetProcessDEPPolicy() 的地址会解析失败,因此须要对返回的函数

指针进行检查,若是为空,就不应,也没法对 Tor 主进程启动数据执行保护(DEP);另外,假使系统可以解析到

SetProcessDEPPolicy() 的地址,而对它的调用失败却不会致使危险,因此无需错误处理,只管调用便可——就算没法

开启 DEP,Tor 也要继续运行下去,这就彻底依赖于安全的编码意识了。。。。。

 

而根据 MSDN 文档的描述,为 SetProcessDEPPolicy() 的 DWORD 型参数 dwFlags 传入 0x3 

代表 PROCESS_DEP_ENABLE(0x00000001)PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION(0x00000002)特性被同时打开,这与

源码中的注解相符。

最后,PROCESS_DEP_ENABLE(0x00000001)标志的设定,意味着在整个 Tor 进程的生命周期内,都没法关闭 DEP(若是成功启用)

第一个条件编译块到此结束,其充分利用了 Windows 平台为应用程序提供的额外安全机制。

 

通过平台特定的代码块后,为了让 Tor 进程在崩溃时转储栈信息,以便后续的调试分析?调用了 configure_backtrace_handler()

函数(\tor-0.3.1.8\src\common\backtrace.c),来配置回溯处理程序。相关的代码片断以下:

 

 

configure_backtrace_handler() 接受一个指向字符常量的指针,这能够经过 get_version() 辅助例程获取到 Tor 应用程序的版本信息。

configure_backtrace_handler() 首先调用宏 tor_free()(\tor-0.3.1.8\src\common\util.h——83),来释放由 backtrace.c 静态

分配的全局变量 bt_version,它是一个 NULL 指针,而 tor_free() 能够安全地释放 NULL 指针,并且把它引用的内存位置也设为

NULL。

根据是否获取到版本信息,它调用 tor_asprintf() 向控制台/shell 输出相应的程序启动信息,而后调用

install_bt_handler() (在同一份源文件内),并将其返回值传递给 tor_main()。

若是注册崩溃回调函数失败,install_bt_handler() 返回 -1;不然返回 0,最终通知 tor_main() 成功“挂钩”!

 

阅读 backtrace.c 咱们能够了解到:若是没有在编译时指定 USE_BACKTRACE 选项,则 install_bt_handler() 仅仅只是返回 0 到

调用者——不会实际注册回溯处理程序。不然,install_bt_handler() 将针对包含崩溃在内的一些信号

(SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGSYS, SIGIO),安装一致的回溯处理程序 crash_handler(),

后者调用 backtrace() ,最终成生栈回溯信息。

这里咱们要明确一点,当程序启动并正常运行时,只会调用 configure_backtrace_handler() 与 install_bt_handler();当程序

崩溃或收到上述六种信号之一,crash_handler() 与 backtrace() 就会被调用(所以后二者才是 CallBack)。

咱们能够从 crash_handler() 内部最后的 abort() 调用逻辑得证——只有在崩溃时才须要停止继续运行。

相关的代码片断以下:

 

 

 

上面的长篇大论用下面一张图清晰地总结:

 

至此,咱们解剖了 tor 程序入口点 tor_main() 中,到 configure_backtrace_handler() 为止的代码意图,但这仅仅只是开场白

而已,在后面的博文我将继续分析与 tor 业务逻辑相关的代码,这才是“干货”所在!

(未完待续。。。。。。)

相关文章
相关标签/搜索