上次我们停在
inl = ASN1_item_i2d(asn, &buf_in, it);
调用处(注意是第2次中断),此时的调用栈为
> openssl.exe!ASN1_item_verify
openssl.exe!X509_verify
openssl.exe!internal_verify
openssl.exe!X509_verify_cert
openssl.exe!check
openssl.exe!verify_main
openssl.exe!do_cmd
openssl.exe!main
我们目标锁定在函数唯一的入参asn上,只有它才可能引起返回参数错误
查看上一层函数栈,代码上下文如下
1 int X509_verify(X509 *a, EVP_PKEY *r) 2 { 3 return(ASN1_item_verify(ASN1_ITEM_rptr(X509_CINF),a->sig_alg, 4 a->signature,a->cert_info,r)); 5 }
原来asn是参数a->cert_info,其类型为指向X509_CINF *的指针
在VC自动变量查看窗口,查看a->cert_info的当前值,如下
这里简单介绍下,cert_info 是指向结构X509_CINF(x509_cinf_st)的指针,而 X509_CINF 是 OpenSSL 内部表示证书信息的数据结构
上图中cert_info展开的成员表示证书的某个属性,比如subject表示当前证书持有者的身份信息,等等
再看serialNumber这个成员,它是asn1_string_st*类型,表示证书的序列号,见下面数据结构定义
typedef struct x509_cinf_st { ASN1_INTEGER *version; /* [ 0 ] default of v1 */ ASN1_INTEGER *serialNumber; X509_ALGOR *signature; X509_NAME *issuer; X509_VAL *validity; X509_NAME *subject; X509_PUBKEY *key; ASN1_BIT_STRING *issuerUID; /* [ 1 ] optional in v2 */ ASN1_BIT_STRING *subjectUID; /* [ 2 ] optional in v2 */ STACK_OF(X509_EXTENSION) *extensions; /* [ 3 ] optional in v3 */ } X509_CINF; typedef struct asn1_string_st ASN1_INTEGER; typedef struct asn1_string_st { int length; int type; unsigned char *data; /* The value of the following field depends on the type being * held. It is mostly being used for BIT_STRING so if the * input data has a non-zero 'unused bits' value, it will be * handled correctly */ long flags; } ASN1_STRING;
当前我们看到证书序列号长度为8(见length成员),内容(由data指出)如下
0x006053c0 00 a2 42 4a a2 6a 51 df [cd cd fd fd fd fd ab ab] -- []中的内容不是序列号部分
刚好比证书中的序列号 00 00 a2 42 4a a2 6a 51 df 少一个0x00字节
现在基本可以肯定,正是这里被截断了一个字节,造成后面的一系列错误并最终导致证书验证不过
我们可以做个实验,来验证 serialNumber 的 data 内容不对是造成后面错误的原因
当程序第2次中断在 inl = ASN1_item_i2d(asn, &buf_in, it); 调用处时,我们将内存中序列号的内容临时修改为正确的值
00 a2 42 4a a2 6a 51 df
改为
00 00 a2 42 4a a2 6a 51 df
F10继续执行,再在 int main(int Argc, char *Argv[]) 中的第8行(注意是显示行)设断点
1 /* ok, now check that there are not arguments, if there are, 2 * run with them, shifting the ssleay off the front */ 3 if (Argc != 1) 4 { 5 Argc--; 6 Argv++; 7 ret=do_cmd(prog,Argc,Argv); 8 if (ret < 0) ret=0; 9 goto end; 10 }
按F5全速前进,屏幕上终于打出久违的
openssl.cert.verify.error.pem: OK
这说明,到此为止我们的猜测都是正确
剩下的任务很简单,就是追踪为什么serialNumber记录的序列号出错
而正是从这里开始,我们将进入证书解析Asn1parse命令的Kernel部分