走读OpenSSL代码从一张奇怪的证书说起(六)

为了解OpenSSL完整的证书验证流程,从本节起我们不再进行逆向调试,而是顺着代码执行逻辑进行真正意义上的走读,并在过程中进行查看和问题定位。

在这之前,我们先来解决前面章节中提到的问题:利用脚本(半自动)生成一个OpenSSL的VC工程。

实现思路如下:
1、想得到VC的工程文件.vcproj(*.sln文件中无关键内容)
2、观察到.vcproj是XML(文本)格式
3、观察到.vcproj中编译相关的部分<Files>...</Files>为目录结构
4、联想到Perl强大的文本处理功能,考虑根据文件的编译参数(来自正常编译输出结果)构造出此目录结构

下面以 Visual Studio 2008 & OpenSSL 0.9.8e 为例进行说明

1、打开 Visual Studio 2008 命令提示符(D0S)窗口,进入源代码主目录(假设是 d:\openssl-0.9.8e),依次输入如下命令
perl Configure VC-WIN32
perl util\mkfiles.pl >MINFO # 下面几行其实是 ms\do_ms.bat 脚本内容(部分修改:增加调试选项)
perl util\mk1mf.pl debug dll no-asm VC-WIN32 >ms\ntdll.mak
perl util\mkdef.pl 32 libeay > ms\libeay32.def
perl util\mkdef.pl 32 ssleay > ms\ssleay32.def
nmake -f ms\ntdll.mak > make_nt_dll_output.txt 2>&1 # 重定向编译输出结果

2、用 VC2008 创建新工程(菜单【文件】-->【新建】-->【项目】), 在弹出对话框中输入如下
项目类型:选择 Win32
名称: openssl-0.9.8e
位置: d:\openssl-0.9.8e
创建解决方案的目录:不勾选
模板:选择 "Win32 控制台应用程序"
点击"确定",点击"下一步",在附加选项中选择"空项目",单击"完成"
VC2008 将生成解决方案,并创建项目目录 d:\openssl-0.9.8e\openssl-0.9.8e
关闭刚刚创建的解决方案,退出 VC2008

3、DOS 下运行
copy d:\openssl-0.9.8e\openssl-0.9.8e\openssl-0.9.8e.sln    d:\openssl-0.9.8e
copy d:\openssl-0.9.8e\openssl-0.9.8e\openssl-0.9.8e.vcproj d:\openssl-0.9.8e
perl makefile2vcproject.pl d:\openssl-0.9.8e\openssl-0.9.8e.vcproj d:\openssl-0.9.8e\make_nt_dll_output.txt
VC2008 双击 d:\openssl-0.9.8e\openssl-0.9.8e.sln,最终出现我们想要的窗口

4、脚本 makefile2vcproject.pl 内容如下

View Code
  1 # 名称: makefile2vcproject.pl
  2 # 功能: 将 OpenSSL 的 (VC编译器识别的)makefile 转换为 VC 工程文件(.vcproj), 并生成可执行文件
  3 # 使用方法: perl makefile2vcproject.pl VC生成的空项目文件[全路径] OpenSSL正常编译的重定向输出文件[全路径]
  4 # 举    例: perl makefile2vcproject.pl d:\openssl-0.9.8e\openssl-0.9.8e.vcproj d:\openssl-0.9.8e\make_nt_dll_output.txt
  5 # 实现思路: 读取以 cl 开头编译命令,提取源文件名和编译参数,插入到 .vcproj 中的适当地方(.vcproj 是 XML 文件)
  6 # 说明: 读者需要基本掌握 perl, 对 VC 的 XML 工程文件结构略懂
  7 #       脚本以 quick and dirty 的方式完成
  8 #       可以修改成针对其他软件包的 makefile/VC 工程文件 转换工具
  9 #       修改需要考虑的地方: 源文件和编译/链接参数的位置可能不同
 10 #
 11 # 联系: chen_yan_hua@163.com
 12 
 13 use v5.10; # 启用 say 函数
 14 
 15 if ( $#ARGV != 1)
 16 {
 17   say "Usage: perl makefile2vcproject.pl openssl_vcproject_file[generated_by_vc] openssl_makefile_compile_output_file";
 18   exit 0;
 19 }
 20 
 21 my $openssl_root_dir;
 22 
 23 $openssl_root_dir=$1 if ($ARGV[0] =~ /(^.*)(\\[^\\]+)/); # 确保 .vcproj 位于 OpenSSL 主目录
 24 chdir $1 or die "unable to change to $1 : $!\n" if ($openssl_root_dir =~ /^(.:)\\/); # 切换盘符
 25 chdir $openssl_root_dir or die "unable to change to $openssl_root_dir : $!\n";
 26 
 27 my %filelist; # (目录, 文件&编译选项)Hash 对: key--当前目录 value--当前目录下的文件名和编译选项, 文件&编译选项 之间用回车符隔开
 28 open(IN,"<$ARGV[1]") or die "unable to open $ARGV[1] : $!\n";
 29 while (<IN>)
 30 {
 31   if (/^\s+cl\s+(.*)\s+-c\s+(.*)$/) # 提取源文件名 和 编译参数
 32   {                                 # 下面以 cl {编译参数} -c .\crypto\hmac\hmac.c 为例
 33     $cflag = $1; # 编译参数
 34     $file = $2;  # .\crypto\hmac\hmac.c
 35 
 36     if($file =~ /(^.*\\)([^\\]+)/) # 拆分为 .\crypto\hmac\ 和 hmac.c 两部分
 37     {
 38       $curdir = $1;            # .\crypto\hmac\
 39       $curdir =~ s/(^\.\\)?//; #   crypto\hmac\
 40       $curdir =~ s/\\$//;      #   crypto\hmac
 41     }                          # 源文件的相对路径作为 VC 工程文件中的 Filter 名称
 42 
 43     $file =~ s/^\.\\//;
 44 
 45     $filelist{$curdir} .= $file . "\t" .$cflag. "\n";
 46   }
 47 }
 48 close(IN);
 49 
 50 # 处理 filelist 的 key/value 对
 51 # crypto\asn1 =>
 52 # crypto\asn1\a_object.c TAB键 编译选项
 53 # crypto\asn1\a_bitstr.c TAB键 编译选项
 54 # crypto\asn1\a_utctm.c  TAB键 编译选项
 55 # crypto\asn1\a_gentm.c  TAB键 编译选项
 56 foreach (sort (keys %filelist))
 57 {
 58   $filter = $_;
 59   @files = split /\n/, $filelist{$_};
 60 
 61   # value 中的每一行转换为如下适合 VC 工程文件的格式, key(crypto\asn1) 则组成解决方案资源管理器中的树形目录(Filter)
 62   #     <File
 63   #       RelativePath="crypto\asn1\a_object.c"
 64   #       >
 65   #       <FileConfiguration
 66   #         Name="Debug|Win32"
 67   #         >
 68   #         <Tool
 69   #           Name=...
 70   #           AdditionalOptions=...
 71   #           ObjectFile=...
 72   #         />
 73   #       </FileConfiguration>
 74   #     </File>
 75   foreach $file_cflag_pair(@files)
 76   {
 77     ($file,$cflag) = split /\t/, $file_cflag_pair; # 分离文件名和编译选项
 78 
 79     # 跳过文件 -- 否则 error LNK2005: _main 已经在 openssl.obj 中定义
 80     # 被跳过的文件在 OpenSSL 不同版本中可能有所区别, 实际请查看正常编译输出结果, 如下
 81     # link /nologo /subsystem:console /opt:ref ... md2test.exe ...
 82     next if ($file =~ /(test\.c|sha512t.c|sha256t.c)$/);
 83 
 84     # 源文件编译选项, 从 makefile 中提取, 可以根据需要修改
 85     $cflag =~ s/\/Fo[^\s]+//; # 去掉 /Fo /Fd 选项
 86     $cflag =~ s/\/Fd[^\s]+//;
 87     $cflag =~ s/\/MDd//; # 将 MDd 改为 MTd
 88     $cflag .= " -D_CRT_NON_CONFORMING_SWPRINTFS /Zi";
 89     $cflag =~ s/\/WX//; # 去除 "将警告视为错误"
 90     $filter_path{$filter} .= <<"CompileOptionPerFile";
 91       <File
 92         RelativePath="$file"
 93         >
 94         <FileConfiguration
 95           Name="Debug|Win32"
 96           >
 97           <Tool
 98             Name="VCCLCompilerTool"
 99             AdditionalOptions="$cflag /MTd"
100             ObjectFile="\$(IntDir)\\\$(InputName).obj"
101           />
102         </FileConfiguration>
103       </File>
104 CompileOptionPerFile
105   }
106 }
107 # 最终 %filter_path 的内容示例如下
108 # crypto\asn1 =>
109 #       <File RelativePath="crypto\asn1\a_object.c">
110 #         <FileConfiguration>...</FileConfiguration>
111 #       </File>
112 #       <File RelativePath="crypto\asn1\a_bitstr.c">
113 #         <FileConfiguration>...</FileConfiguration>
114 #       </File>
115 #       ......
116 #
117 # crypto\x509 =>
118 #       <File RelativePath="crypto\x509\x509_def.c">
119 #         <FileConfiguration>...</FileConfiguration>
120 #       </File>
121 #       <File RelativePath="crypto\x509\x509_d2.c">
122 #         <FileConfiguration>...</FileConfiguration>
123 #       </File>
124 #       ......
125 
126 my $vc_project_embed_text; # 保存嵌入到 VC 工程中的编译选项
127 
128 my @openssl_subdir=<*>; # 递归调用直接子目录, 生成 XML 的 <Files> 部分
129 foreach (@openssl_subdir){
130     if (-d){
131       generate_files_compile_option($openssl_root_dir . "\\" . $_);
132   }
133 }
134 
135 # 遍历生成目录树中文件的编译选项
136 sub generate_files_compile_option
137 {
138   my $full_path = shift @_;
139   $relative_path = $full_path;                 #        d:\openssl-0.9.8e\apps\demoCA
140   $relative_path =~ s#\Q$openssl_root_dir\E##; # 去掉 OpenSSL 主目录变为 \apps\demoCA
141   $relative_path =~ s#^\\##;                   # 去掉行首 \         变为  apps\demoCA
142   return if ( "" eq $relative_path );          # 当前路径为 OpenSSL 主目录, 退出
143   $dir_name = $1 if ($relative_path =~ /([^\\]+)$/);
144   $vc_project_embed_text .= <<"Enter_Filter";
145       <Filter
146         Name="$dir_name"
147         >
148 Enter_Filter
149 
150   # 当前目录 匹配 编译文件所在目录
151   foreach $source_file_path (keys %filter_path)
152   {
153     if( $source_file_path eq $relative_path)
154     {
155       $vc_project_embed_text .= $filter_path{$source_file_path}; # 匹配, 合并编译选项
156       last;
157     }
158   }
159 
160   chdir $full_path; # 进入当前目录
161   my @sub_dir=<*>;
162   foreach (@sub_dir){
163     if (-d){
164       &generate_files_compile_option($full_path . "\\" . $_); # 递归调用下一层子目录
165     }
166   }
167   chdir "..";
168 
169   $vc_project_embed_text .= <<"Leave_Filter";
170       </Filter>
171 Leave_Filter
172 
173 }
174 
175 # 复制 VC 工程文件中的配置[XML 文本]到临时工程文件
176 $openssl_vcproj_file = $ARGV[0];
177 $openssl_vcproj_bak_file = $openssl_vcproj_file . ".bak";
178 $openssl_vcproj_temp_file = $openssl_vcproj_file . ".temp";
179 
180 rename $openssl_vcproj_bak_file, $openssl_vcproj_file if -e $openssl_vcproj_bak_file; # 如果存在备份,先恢复
181 
182 open(OPENSSL_VCPROJ,"<$openssl_vcproj_file") or die "unable to open $openssl_vcproj_file : $!\n";
183 open(OPENSSL_VCPROJ_TEMP,">$openssl_vcproj_temp_file") or die "unable to open $openssl_vcproj_temp_file : $!\n";
184 
185 # 复制链接选项之前的部分
186 while (<OPENSSL_VCPROJ>)
187 {
188   print OPENSSL_VCPROJ_TEMP;
189   last if (/Name=\"Debug|Win32\"/);
190 }
191 while (<OPENSSL_VCPROJ>)
192 {
193   print OPENSSL_VCPROJ_TEMP;
194   last if (/Name=\"VCLinkerTool\"/);
195 }
196 
197 # 增加链接附加选项 -- 编译成 可执行文件 -- 这部分选项
198 print OPENSSL_VCPROJ_TEMP <<"Link_Option";
199         AdditionalOptions="/out:debug/openssl-0.9.8e.exe /debug /nologo /subsystem:console /opt:ref wsock32.lib gdi32.lib advapi32.lib user32.lib"
200 Link_Option
201 
202 # 复制<Files>之前的部分
203 while (<OPENSSL_VCPROJ>)
204 {
205   print OPENSSL_VCPROJ_TEMP;
206   last if (/<Files>/);
207 }
208 
209 # 增加编译选项
210 print OPENSSL_VCPROJ_TEMP $vc_project_embed_text;
211 
212 # 补充剩余部分
213 print OPENSSL_VCPROJ_TEMP "\t</Files>\n";
214 print OPENSSL_VCPROJ_TEMP "\t<Globals>\n";
215 print OPENSSL_VCPROJ_TEMP "\t</Globals>\n";
216 print OPENSSL_VCPROJ_TEMP "</VisualStudioProject>\n";
217 
218 close(OPENSSL_VCPROJ);
219 close(OPENSSL_VCPROJ_TEMP);
220 
221 # 备份原工程文件, 再用临时工程文件 覆盖 之
222 rename $openssl_vcproj_file, $openssl_vcproj_file . ".bak";
223 rename $openssl_vcproj_temp_file, $openssl_vcproj_file;

5、如果有时间, 可以进一步完善, 最终全部由脚本自动执行

原文地址:https://www.cnblogs.com/efzju/p/2637289.html