zoukankan      html  css  js  c++  java
  • 走读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、如果有时间, 可以进一步完善, 最终全部由脚本自动执行

  • 相关阅读:
    性能百万/s:腾讯轻量级全局流控方案详解
    Swagger2
    shiro 入门
    01、单例模式
    02、工厂方法
    04、模板模式
    13、Adapter 适配器
    14、迭代器
    Java 面向切面 AOP
    spring boot 中使用 Redis 与 Log
  • 原文地址:https://www.cnblogs.com/efzju/p/2637289.html
Copyright © 2011-2022 走看看