------ Tor(洋葱路由器)匿名网络源码分析——主程序入口点(一)------
————————————————————————————————————————————————————————
《概览》
tor 的源码包可以从官网下载,可能需要预先利用其它=*翻^=*墙*软件才能访问该站点。分析 tor 源码有助于我们理解当代最强大之一的
互联网匿名、隐身、审查规避软件的运作原理。
为了从整体上把握住程序的逻辑与功能,本系列会将源码重要部分通过函数调用流程图总结,以便站在设计思想的高度来考察 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 平台,更健壮的代码如下:
BOOL bResult;
bResult = HeapSetInformation(NULL,
HeapEnableTerminationOnCorruption,
NULL,
); if (bResult != FALSE) {
_tprintf(TEXT("Heap terminate-on-corruption has been enabled.\n"));
}
else {
_tprintf(TEXT("Failed to enable heap terminate-on-corruption with LastError %d.\n"),
GetLastError());
return ;
}
上图关键之处在于调用 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 业务逻辑相关的代码,这才是“干货”所在!
(未完待续。。。。。。)
------ Tor(洋葱路由器)匿名网络源码分析——主程序入口点(一)------的更多相关文章
- ------ 开源软件 Tor(洋葱路由器,构建匿名网络的方案之一)源码分析——主程序入口点(二)------
		
---------------------------------------------------------- 第二部分仅考察下图所示的代码片段--configure_backtrace_han ...
 - [阿里DIN] 深度兴趣网络源码分析 之 如何建模用户序列
		
[阿里DIN] 深度兴趣网络源码分析 之 如何建模用户序列 目录 [阿里DIN] 深度兴趣网络源码分析 之 如何建模用户序列 0x00 摘要 0x01 DIN 需要什么数据 0x02 如何产生数据 2 ...
 - [阿里DIN] 深度兴趣网络源码分析 之 整体代码结构
		
[阿里DIN] 深度兴趣网络源码分析 之 整体代码结构 目录 [阿里DIN] 深度兴趣网络源码分析 之 整体代码结构 0x00 摘要 0x01 文件简介 0x02 总体架构 0x03 总体代码 0x0 ...
 - [阿里DIEN] 深度兴趣进化网络源码分析 之 Keras版本
		
[阿里DIEN] 深度兴趣进化网络源码分析 之 Keras版本 目录 [阿里DIEN] 深度兴趣进化网络源码分析 之 Keras版本 0x00 摘要 0x01 背景 1.1 代码进化 1.2 Deep ...
 - 从SpringBoot源码分析 主程序配置类加载过程
		
1.@Import(AutoConfigurationPackages.Registrar.class) 初始SpringBoot 我们知道在SpringBoot 启动类上有一个@SpringBoot ...
 - # Volley源码解析(二) 没有缓存的情况下直接走网络请求源码分析#
		
Volley源码解析(二) 没有缓存的情况下直接走网络请求源码分析 Volley源码一共40多个类和接口.除去一些工具类的实现,核心代码只有20多个类.所以相对来说分析起来没有那么吃力.但是要想分析透 ...
 - nginx源码分析之网络初始化
		
nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...
 - Android网络框架源码分析一---Volley
		
转载自 http://www.jianshu.com/p/9e17727f31a1?utm_campaign=maleskine&utm_content=note&utm_medium ...
 - Docker源码分析(八):Docker Container网络(下)
		
1.Docker Client配置容器网络模式 Docker目前支持4种网络模式,分别是bridge.host.container.none,Docker开发者可以根据自己的需求来确定最适合自己应用场 ...
 
随机推荐
- JavaScript使用点滴
			
JavaScript使用点滴 一.字符串替换的小插曲 遇到一个小插曲,想要把后台返回的字符串输出给前端视图,字符串中包含\n换行,需要使用javascript对其进行替换成<br />. ...
 - Vijos 1404 遭遇战
			
Vijos 1404 遭遇战 背景 你知道吗,SQ Class的人都很喜欢打CS.(不知道CS是什么的人不用参加这次比赛). 描述 今天,他们在打一张叫DUSTII的地图,万恶的恐怖分子要炸掉藏在A区 ...
 - 卷积神经网络(CNN)在句子建模上的应用
			
之前的博文已经介绍了CNN的基本原理,本文将大概总结一下最近CNN在NLP中的句子建模(或者句子表示)方面的应用情况,主要阅读了以下的文献: Kim Y. Convolutional neural n ...
 - [翻译]【目录】编写高性能 .NET 代码
			
本篇是 Writing High-Performance .NET Code 的目录索引,翻译内容不定时更新,目录也会同步修改. 性能测量及工具 选择什么来衡量 平均数vs百分比 工具介绍 Visua ...
 - iOS 添加WKWebView导致控制器无法释放的问题
			
在WkWebView与JavaScript交互中,经常会在原生中注入MessageHandler,app中注入MessageHandler的方法 WKWebViewConfiguration *con ...
 - 使用nio对磁盘下的文件进行过滤
			
上篇博文讲到为了解决tomcat日志自动清理的问题,翻看了tomcat-juli这个jar包.在FileHandler类下有一个利用nio完成对磁盘下过期文件进行过滤的功能实现,正好这段时间正在学习n ...
 - LNMP搭建环境遇到的N多坑
			
最近配置开发用的lnmp环境,环境配置完成后,爆500错误,查看nginx错误日志 open_basedir 将 PHP 所能打开的文件限制在指定的目录树,包括文件本身 错误日志显示,访问脚本不在 o ...
 - 《android开发进阶从小工到专家》读书笔记--HTTP网络请求
			
No1: 客户端与服务器的交互流程: 1)客户端执行网络请求,从URL中解析出服务器的主机名 2)将服务器的主机名转换成服务器的IP地址 3)将端口号从URL中解析出来 4)建立一条从客户端与Web服 ...
 - 修长城   (区间DP)
			
Time Limit: 1000 ms Memory Limit: 256 MB Description 大家都知道,长城在自然条件下会被侵蚀,因此,我们需要修复.现在是21世纪,修复长城的事情当 ...
 - linux 搭建CA服务器 http+ssl  mail+ssl 扫描与抓包
			
搭建CA服务器 CA服务是给服务器发放数字证书,被通信双方信任,独立的第三方机构 国内常见的CA机构 中国金融认证中心(CFCA) 中国电信安全认证中心(CTCA) 北京数字证书认证中心(BJCA) ...