为了解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 内容如下
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、如果有时间, 可以进一步完善, 最终全部由脚本自动执行