64位汇编语言之HTTPS(64位汇编语言之HTTPS)
64位汇编语言之HTTPS(64位汇编语言之HTTPS)5、invoke AcquireCredentialsHandleA 0 addr sspi SECPKG_CRED_OUTBOUND 0 \ addr @SCHANNEL_CRED_buffer 0 0 _pCredHandle _ptimstamp请求证书处理句柄:3、invoke htons4434、invoke connect @hSocket addr @stSin sizeof @stSin以上为正常的套接口初始化
越来越多的网站开始使用HTTPS协议,虽然这样会增加服务器的负担,但是从保护用户信息的安全层面来讲是很有必要的。安全套接口是基于SHA 256 、RSA算法为基础的双向认证的证书保护接口。下面我简单谈论下以windows原生API为基础的 基于tcp套接口的HTTPS通信。微软在应用层提供了安全支持提供程序(Security Support Providers)共有11种之多其中的Microsoft Unified Security Protocol Provider 就是我们要着重介绍的https通道协议 (用 Default TLS SSP也可以)
windbg
1、invoke WSAStartup 0202h addr @stWsa
2、invoke socket AF_INET SOCK_STREAM IPPROTO_TCP
3、invoke htons443
4、invoke connect @hSocket addr @stSin sizeof @stSin
以上为正常的套接口初始化
请求证书处理句柄:
5、invoke AcquireCredentialsHandleA 0 addr sspi SECPKG_CRED_OUTBOUND 0 \ addr @SCHANNEL_CRED_buffer 0 0 _pCredHandle _ptimstamp
初始化安全上下文:
6、InitializeSecurityContextA _pCredHandle 0 _pTargetName flages \
0 0 0 0 _pCtxtHandle addr @output_SecBufferDesc_buffer addr @pfContextAttr 0
此函数最低需要调用两次,两次调用的参数有很大出入 ,请读者朋友留意源代码的区 别。
7、EncryptMessage ,用EncryptMessage 加密数据后就可以用send发送了。
8、recv 用recv 接收数据后用DecryptMessage 解密出原始数据。
https://www.baidu.com
option casemap:none
OPTION DOTNAME
include https.inc
.code
ASCII2HEX proc uses rbx rsi rdi pret_buffer:qword size_ret_buffer:qword pascii_buffer:qword size_ascii_buffer:qword
mov rcx size_ascii_buffer
mov rsi pascii_buffer
mov rdx size_ret_buffer
mov rdi pret_buffer
.while sqword ptr rcx } 0 && sqword ptr rdx } 0
and rax 0
and rbx 0
mov al byte ptr [rsi]
.if al }= "0" && al {= "9"
sub al "0"
.elseif al }= "A" && al {= "F"
sub al "0"
sub al 7
.elseif al }= "a" && al {= "f"
sub al "0"
sub al 27h
.elseif al == 0
; invoke MessageBox 0 addr compcheck1 0 MB_OK
; mov rax -1
; ret
.else
mov rax -1
ret
.endif
dec rcx
inc rsi
mov bl byte ptr [rsi]
.if bl }= "0" && bl {= "9"
sub bl "0"
.elseif bl }= "A" && bl {= "F"
sub bl "0"
sub bl 7
.elseif bl }= "a" && bl {= "f"
sub bl "0"
sub bl 27h
.elseif bl == 0
.else
mov rax -1
ret
.endif
shl al 4
or al bl
cld
stosb
dec rcx
dec rdx
inc rsi
.endw
ret
ASCII2HEX endp
HostnameToIP proc _lpszHostName:qword
local @szBuffer[256]:byte
local @dwIP:qword
invoke inet_addr _lpszHostName
.if eax {} INADDR_NONE;nozero?
;********************************************************************
; 输入的是IP地址
;********************************************************************
mov @dwIP rax
.else
;********************************************************************
; 输入的是主机名称
;********************************************************************
invoke gethostbyname _lpszHostName
.if sqword ptr rax } 0 ;greater?
mov rax [rax hostent.h_list]
.while sqword ptr [rax]{}0;nozero?
mov rbx [rax]
mov ecx dword ptr [rbx]
mov dword ptr @dwIP ecx
add rax 8
.break
.endw
.else
xor eax eax
ret
.endif
.endif
mov rax @dwIP
ret
HostnameToIP endp
_recv proc _hSocket:qword _ipbuffer:qword _size:qword _flags:qword
LOCAL @sizecount:qword
LOCAL @ipbuffer:qword
mov rax _ipbuffer
mov @ipbuffer rax
mov rax _size
mov @sizecount rax
zzzz:
@@:
.while sqword ptr @sizecount } 0
invoke recv _hSocket @ipbuffer @sizecount _flags; 包含flags标志位的结构数据发送服务器 若无错误发生,recv()返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
; 在阻塞模式下recv,recvfrom操作将会阻塞到缓冲区里有至少一个字节(TCP)或者一个完整的UDP数据报才返回。
;在没有数据到来时,对它们的调用都将处于睡眠状态,不会返回。
.if eax ==SOCKET_ERROR;>> zero?
mov eax 0
ret
.elseif eax == 0 ;如果连接已中止,返回0
ret
.else ;若无错误发生,recv()返回读入的字节数
add @ipbuffer rax
sub @sizecount rax
.endif
.endw
mov eax 1
add rsp 40h
ret
_recv endp
_send proc _hSocket:qword _ipbuffer:qword _size:qword _flags:qword
LOCAL @sizecount:qword
LOCAL @ipbuffer:qword
LOCAL @timecount:qword
mov @timecount 0
mov rax _ipbuffer
mov @ipbuffer rax
mov rax _size
mov @sizecount rax
@@:
.while sqword ptr @sizecount } 0
invoke send _hSocket @ipbuffer @sizecount _flags; 包含flags标志位的结构数据发送服务器 若无错误发生,send()返回所发送数据的总数
.if eax ==SOCKET_ERROR;>> zero?
invoke WSAGetLastError
.if eax == WSAEWOULDBLOCK;>> zero?
invoke Sleep 64h ;延时重发
.if @timecount } 30; greater?
mov eax 0
ret
.else
inc @timecount
.endif
.continue
.else
mov eax 0
ret
.endif
.elseif eax == 0; zero?
ret
.else
add @ipbuffer rax
sub @sizecount rax
mov @timecount 0
.endif
.endw
mov eax 1
add rsp 40h
ret
_send endp
wait_and_recv_data_with_https proc uses rbx rsi rdi _hSocket:qword _pCtxtHandle:qword _precv_buffer:qword _precv_buffer_length:qword _return_lehgth:qword ;_;https解密后的缓冲区,原则上讲包含压缩数据
;_precv_buffer:body 数据 (如果压缩的话,那么是解压缩数据)_pdecrypt_buffer:原始html数据
LOCAL @readcount:qword ;每次接收到的数据
LOCAL @retry_count:qword ;接收等待次数
LOCAL @hSocket:qword
LOCAL @ptemp_ip_buffer:qword
LOCAL @return_count:qword
LOCAL @temp_buffer_for_list [5]:_SecBuffer
LOCAL @EncryptMessage_SecBufferDesc_buffer :_SecBufferDesc
LOCAL @heap_mid_buffer:qword
LOCAL @flags:qword
LOCAL @deccount:qword
mov rcx _hSocket
mov @hSocket rcx
mov r8 _precv_buffer
mov @ptemp_ip_buffer r8
invoke GetProcessHeap
invoke HeapAlloc rax HEAP_ZERO_MEMORY heap_recv__size
.if rax == 0; zero?
mov rax 0
ret
.endif
mov @heap_mid_buffer rax
and @deccount 0
mov rdi _precv_buffer
mov @ptemp_ip_buffer rdi
and @readcount 0
and @retry_count 0
and @flags 0
@again_:
.while TRUE;第一次接收,给服务端3秒的等待时间
and @readcount 0
invoke ioctlsocket @hSocket FIONREAD addr @readcount ;阻塞模式 在一次recv()中所接收的所有数据量。
; 这通常与套接口中排队的数据总量相同。
; 如果S是SOCK_DGRAM 型,则FIONREAD
; 返回套接口上排队的第一个数据报大小。
;正常返回0
.if rax ==0
.if @readcount ==0 ;客户端connect 之后 如果没有发送数据 就会 @readcount== 0
invoke Sleep 100
inc @retry_count
; int 3
.if sqword ptr @retry_count { 10;给服务端3秒的等待时间;如果三次都读不到数据,说明数据已经读完了,或者服务端已经关闭了
.continue
.else
mov rax 0
jmp @exit
.endif
.else
; int 3
mov rcx @ptemp_ip_buffer
;lea rdi EncryptMessage_recv_buffer
mov rdi _precv_buffer
sub rcx rdi ;缓冲区已经占用的空间
.if sqword ptr rcx }= _precv_buffer_length;>> noless?
mov rcx 0
mov @ptemp_ip_buffer rdi
.endif
mov rax _precv_buffer_length
sub rax rcx;剩余可用的空间
.if sqword ptr @readcount } rax ;单次接收的数据不能超过缓冲区剩余空间容量
mov @readcount rax
.endif
mov rdi @ptemp_ip_buffer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
invoke _recv @hSocket rdi @readcount 0 ;
.if rax ==0; zero?
mov rax 0
jmp @exit
.endif
mov rcx @readcount
add @ptemp_ip_buffer rcx
and @readcount 0
and @retry_count 0
.endif
.else
mov rax 0
jmp @exit
.endif
mov @EncryptMessage_SecBufferDesc_buffer.ulVersion SECBUFFER_VERSION;== 0
mov @EncryptMessage_SecBufferDesc_buffer.cBuffers 4
lea rax @temp_buffer_for_list
mov @EncryptMessage_SecBufferDesc_buffer.pBuffers rax
mov rcx @ptemp_ip_buffer
;lea rdi EncryptMessage_recv_buffer
mov rdi _precv_buffer
sub rcx rdi
mov [rax _SecBuffer.cbBuffer] ecx
mov [rax _SecBuffer.BufferType] SECBUFFER_DATA ;==1
;lea rsi EncryptMessage_recv_buffer
mov rsi _precv_buffer
mov [rax _SecBuffer.pvBuffer] rsi
add rsi rcx
add rax sizeof _SecBuffer
mov [rax _SecBuffer.cbBuffer] 0
mov [rax _SecBuffer.BufferType] 0
mov [rax _SecBuffer.pvBuffer] 0
add rsi rcx
add rax sizeof _SecBuffer
mov [rax _SecBuffer.cbBuffer] 0
mov [rax _SecBuffer.BufferType] 0
mov [rax _SecBuffer.pvBuffer] 0
add rax sizeof _SecBuffer
mov [rax _SecBuffer.cbBuffer] 0
mov [rax _SecBuffer.BufferType] 0
mov [rax _SecBuffer.pvBuffer] 0
invoke DecryptMessage _pCtxtHandle addr @EncryptMessage_SecBufferDesc_buffer 0 0
.if eax == SEC_I_CONTEXT_EXPIRED;>> zero? ;发送方已经关闭了,可能已经发送完成了The message sender has finished using the connection and has initiated a shutdown.
lea rax @temp_buffer_for_list
.break
.elseif eax == SEC_E_OUT_OF_SEQUENCE;>> zero? ;该消息没有按正确的顺序接收。
lea rax @temp_buffer_for_list
nop
.break
.elseif eax == SEC_E_INVALID_HANDLE;>> zero? ;在phContext参数中指定了一个无效的上下文句柄
.break
.elseif eax == SEC_E_INVALID_TOKEN;>> zero? ;缓冲区类型错误或没有找到类型为SECBUFFER_DATA的缓冲区
.break
.elseif eax == SEC_E_MESSAGE_ALTERED;>> zero? ;消息已被更改
.break
.elseif eax == SEC_E_OK;>> zero? ;正常完成没有关闭
lea rax @temp_buffer_for_list
mov rdi @heap_mid_buffer
mov r8 4
add rdi @deccount
.while sqword ptr r8 } 0 ;解密的数据保存到发送缓冲区
.if [rax _SecBuffer.BufferType] == SECBUFFER_DATA;>> zero?
mov ecx [rax _SecBuffer.cbBuffer]
mov rsi [rax _SecBuffer.pvBuffer]
add @deccount rcx
rep movsb
.endif
add rax sizeof _SecBuffer
dec r8
.endw
lea rax @temp_buffer_for_list
mov r8 4
;lea rdi EncryptMessage_recv_buffer
mov rdi _precv_buffer
.while sqword ptr r8 } 0; GREATER? ;未解密的数据下次一并解密,需要调整接收指针 也可以再解密一次
.if [rax _SecBuffer.BufferType] == SECBUFFER_EXTRA;>> zero? ;The security package uses this value to indicate the number of extra or unprocessed bytes in a message.
mov ecx [rax _SecBuffer.cbBuffer]
mov rsi [rax _SecBuffer.pvBuffer]
rep movsb
mov @EncryptMessage_SecBufferDesc_buffer.ulVersion SECBUFFER_VERSION;== 0
mov @EncryptMessage_SecBufferDesc_buffer.cBuffers 4
lea rax @temp_buffer_for_list
mov @EncryptMessage_SecBufferDesc_buffer.pBuffers rax
mov @ptemp_ip_buffer rdi
.endif
add rax sizeof _SecBuffer
dec r8
.endw
.elseif eax == SEC_E_INCOMPLETE_MESSAGE;>> zero? ;equ 80090318hhe data in the input buffer is incomplete. The application needs to read more data
;from the server and call DecryptMessage (Schannel) again.
;个人理解::仅仅为了避免多次调用这个函数
;这里有个问题:如果服务端一直返回0数据,那么就会死循环
.if sqword ptr @retry_count }10
;我们的处理就是忽略这个请求!也就是说已经连续10次没有读到数据了,那就别指望服务端会提供数据了
; invoke logout addr atext (_error_server "服务端一直返回0数据 虽然连接没有断开,我们主动断开吧!") sizeof _error_server
jmp @exit
.endif
; lea rax @temp_buffer_for_list
; mov ecx [rax _SecBuffer.cbBuffer] ;这里应该是总共需要这么多的数据才能完成本次请求。
; lea rdi EncryptMessage_recv_buffer
; mov rdi _precv_buffer
; add rdi rcx
; mov @ptemp_ip_buffer rdi
.elseif eax == SEC_I_RENEGOTIATE;需要重新初始化
.break
; InitializeSecurityContext
.else
nop
nop
nop
nop
nop
.break
.endif
.endw
@exit:
mov rsi @heap_mid_buffer
mov rcx @deccount
mov _return_lehgth rcx
mov rdi _precv_buffer
cld
rep movsb
invoke GetProcessHeap
invoke HeapFree rax 0 @heap_mid_buffer
mov rax @flags
ret
wait_and_recv_data_with_https endp
connect_SecurityContext proc uses rbx rsi rdi _hSocket:qword _pTargetName:qword _pCredHandle:qword _pCtxtHandle:qword _ptimstamp:qword
LOCAL @hSocket:qword
LOCAL @readcount:qword
LOCAL @retry_count:qword
LOCAL @ptemp_ip_buffer:qword ;缓冲区指针
LOCAL @ppChainContext :qword
LOCAL @pCertContext:qword
LOCAL @pbcomputedhsah [20h]:byte
LOCAL @pcbcomputedhash:qword
LOCAL @psz [100h]:byte
LOCAL @count_input :qword
LOCAL @temp_ip_buffer_input:qword
LOCAL @SecPkgContext_StreamSizes_buffer :SecPkgContext_StreamSizes
LOCAL @SCHANNEL_CRED_buffer :_SCHANNEL_CRED
LOCAL @flags:qword
LOCAL @pfContextAttr :qword;指向变量的指针,用于接收一组指示已建立上下文属性的位标志: flages 设置情况的反馈
LOCAL @output_SecBufferDesc_buffer :_SecBufferDesc
LOCAL @output_SecBuffer_buffer :_SecBuffer
LOCAL @input_SecBufferDesc_buffer :_SecBufferDesc
LOCAL @input_SecBuffer_buffer :_SecBuffer
LOCAL @temp_buffer_for_list [5]:_SecBuffer; 5 dup (<?>
LOCAL @temp_buffer_for_list_input [5]:_SecBuffer;_SecBuffer 5 dup (<?>)
sub rsp 100h
and @flags 0
mov @hSocket rcx
invoke RtlZeroMemory addr @SCHANNEL_CRED_buffer sizeof @SCHANNEL_CRED_buffer
mov @SCHANNEL_CRED_buffer.dwVersion SCHANNEL_CRED_VERSION
SP_PROT_SSL3_CLIENT equ 20h
SP_PROT_SSL2_CLIENT equ 8
mov @SCHANNEL_CRED_buffer.grbitEnabledProtocols SP_PROT_TLS1_CLIENT SP_PROT_TLS1_1_CLIENT SP_PROT_TLS1_2_CLIENT
;mov @SCHANNEL_CRED_buffer.dwFlags 40h;SCH_CRED_MANUAL_CRED_VALIDATION SCH_CRED_NO_DEFAULT_CREDS;SCH_CRED_USE_DEFAULT_CREDS == 40
invoke AcquireCredentialsHandleA 0 addr sspi SECPKG_CRED_OUTBOUND 0 addr @SCHANNEL_CRED_buffer 0 0 _pCredHandle _ptimstamp
.if rax {} SEC_E_OK;>> nozero?
jmp exit
.endif
mov @output_SecBufferDesc_buffer.ulVersion SECBUFFER_VERSION;== 0
mov @output_SecBufferDesc_buffer.cBuffers 1 ;缓冲区个数
lea rax @output_SecBuffer_buffer
mov @output_SecBufferDesc_buffer.pBuffers rax
mov @output_SecBuffer_buffer.cbBuffer 0;sizeof output_pOutput_buffer ;函数返回后,这里会有接收的数据大小
mov @output_SecBuffer_buffer.BufferType SECBUFFER_EMPTY;SECBUFFER_TOKEN;== 2
;lea rax @output_pOutput_buffer
mov @output_SecBuffer_buffer.pvBuffer 0;rax
flages equ ISC_REQ_SEQUENCE_DETECT ISC_REQ_REPLAY_DETECT ISC_REQ_CONFIDENTIALITY ISC_REQ_STREAM ISC_REQ_ALLOCATE_MEMORY ISC_REQ_USE_SUPPLIED_CREDS
;flages equ ISC_REQ_STREAM ISC_REQ_CONFIDENTIALITY ISC_REQ_REPLAY_DETECT ISC_REQ_SEQUENCE_DETECT ISC_REQ_INTEGRITY ISC_REQ_MUTUAL_AUTH;ISC_REQ_ALLOCATE_MEMORY
;ISC_REQ_USE_SUPPLIED_CREDS:Schannel不得尝试自动为客户端提供凭据 ;ISC_REQ_CONFIDENTIALITY 使用EncryptMessage函数加密消息。ISC_REQ_REPLAY_DETECT:检测使用EncryptMessage或MakeSignature函数编码的重播消息。ISC_REQ_SEQUENCE_DETECT:检测不按顺序接收的消息。
;invoke InitializeSecurityContextA addr CredHandle addr CtxtHandle 0 ISC_REQ_STREAM ISC_REQ_USE_SUPPLIED_CREDS ISC_REQ_CONFIDENTIALITY ISC_REQ_REPLAY_DETECT ISC_REQ_SEQUENCE_DETECT \
invoke InitializeSecurityContextA _pCredHandle 0 _pTargetName flages \
0 0 0 0 _pCtxtHandle addr @output_SecBufferDesc_buffer addr @pfContextAttr 0
; int 3
.if rax {} SEC_I_CONTINUE_NEEDED;第一次调用应该是必需返回SEC_I_CONTINUE_NEEDED
;/* send initial handshake data which is now stored in output buffer */
;The client must send the output token to the server and wait for a return token. The returned token is then passed in another call to InitializeSecurityContext (Schannel). The output token can be empty.
jmp exit
.endif
;this function only if the InitializeSecurityContext (Digest) call returned SEC_I_COMPLETE_NEEDED or SEC_I_COMPLETE_AND_CONTINUE.
;This function is supported only by the Digest security support provider (SSP).
lea rbx @output_SecBuffer_buffer
mov r8d @output_SecBuffer_buffer.cbBuffer
mov rax @output_SecBuffer_buffer.pvBuffer
nop
invoke _send @hSocket @output_SecBuffer_buffer.pvBuffer r8 0
.if rax == 0; zero?
.if @output_SecBuffer_buffer.pvBuffer {} 0;>> nozero?
invoke FreeContextBuffer @output_SecBuffer_buffer.pvBuffer
.endif
jmp exit
.endif
invoke FreeContextBuffer @output_SecBuffer_buffer.pvBuffer
and @readcount 0
and @retry_count 0
lea rdi recv_token_buffer;这个缓冲接收服务器返回的令牌
mov @ptemp_ip_buffer rdi
.while TRUE
again_:
invoke ioctlsocket @hSocket FIONREAD addr @readcount ;阻塞模式 在一次recv()中所接收的所有数据量。
; 这通常与套接口中排队的数据总量相同。
; 如果S是SOCK_DGRAM 型,则FIONREAD
; 返回套接口上排队的第一个数据报大小。
;正常返回0
.if eax == 0;>> zero?
.if @readcount == 0;>> zero? ;客户端connect 之后 如果没有发送数据 就会 @readcount== 0
invoke Sleep 100
inc @retry_count
.if sqword ptr @retry_count {=30;>> less?||equal? ;
.continue
;jmp again_
.endif
.else
mov rdi @ptemp_ip_buffer
mov rax @readcount
invoke _recv @hSocket rdi @readcount 0
.if rax == 0; zero?
jmp exit
.endif
mov rdi @ptemp_ip_buffer
add rdi @readcount
mov @ptemp_ip_buffer rdi
and @retry_count 0
and @readcount 0
.endif
.else
mov rax 0
jmp exit
.endif
mov @input_SecBufferDesc_buffer.ulVersion SECBUFFER_VERSION;== 0
mov @input_SecBufferDesc_buffer.cBuffers 2 ;缓冲区个数
lea rax @temp_buffer_for_list
mov @input_SecBufferDesc_buffer.pBuffers rax
lea rdi recv_token_buffer
mov rcx @ptemp_ip_buffer
sub rcx rdi
mov [rax _SecBuffer.cbBuffer] ecx;recv接收的数据
mov [rax _SecBuffer.BufferType] SECBUFFER_TOKEN;== 2 ;On calls to this function after the initial call there must be two buffers. The first has type SECBUFFER_TOKEN
; and contains the token received from the server. The second buffer has type SECBUFFER_EMPTY; set both the pvBuffer and cbBuffer members to zero.
lea rsi recv_token_buffer
mov [rax _SecBuffer.pvBuffer] rsi
add rax sizeof _SecBuffer
mov [rax _SecBuffer.cbBuffer] 0;SECBUFFER_EMPTY 0
mov [rax _SecBuffer.BufferType] 0
mov [rax _SecBuffer.pvBuffer] 0
mov @output_SecBufferDesc_buffer.ulVersion SECBUFFER_VERSION;== 0
mov @output_SecBufferDesc_buffer.cBuffers 3 ;缓冲区个数
lea rax @temp_buffer_for_list_input
mov @output_SecBufferDesc_buffer.pBuffers rax
mov [rax _SecBuffer.cbBuffer] 0;SECBUFFER_EMPTY 0
mov [rax _SecBuffer.BufferType] 0
mov [rax _SecBuffer.pvBuffer] 0
add rax sizeof _SecBuffer
mov [rax _SecBuffer.cbBuffer] 0;SECBUFFER_EMPTY 0
mov [rax _SecBuffer.BufferType] 0
mov [rax _SecBuffer.pvBuffer] 0
add rax sizeof _SecBuffer
mov [rax _SecBuffer.cbBuffer] 0;SECBUFFER_EMPTY 0
mov [rax _SecBuffer.BufferType] 0
mov [rax _SecBuffer.pvBuffer] 0
add rax sizeof _SecBuffer
mov [rax _SecBuffer.cbBuffer] 0;SECBUFFER_EMPTY 0
mov [rax _SecBuffer.BufferType] 0
mov [rax _SecBuffer.pvBuffer] 0
add rax sizeof _SecBuffer
mov [rax _SecBuffer.cbBuffer] 0;SECBUFFER_EMPTY 0
mov [rax _SecBuffer.BufferType] 0
mov [rax _SecBuffer.pvBuffer] 0
invoke InitializeSecurityContextA _pCredHandle _pCtxtHandle 0 flages \
0 0 addr @input_SecBufferDesc_buffer 0 \
0 addr @output_SecBufferDesc_buffer addr @pfContextAttr 0
.if eax == SEC_E_OK;>> zero? ;成功了
;The security context was successfully initialized. There is no need for another InitializeSecurityContext (Schannel) call.
; If the function returns an output token that is if the SECBUFFER_TOKEN in pOutput is of nonzero length that token must be sent to the server.
;https://docs.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel
lea rax @pfContextAttr
mov rax _pCtxtHandle
lea rsi @temp_buffer_for_list_input
nop
mov @count_input 3
.while sqword ptr @count_input } 0;>> greater?
.if [rsi _SecBuffer.BufferType] == SECBUFFER_TOKEN;>> zero?
.if sqword ptr [rsi _SecBuffer.cbBuffer] } 0;>> greater?
.if sqword ptr [rsi _SecBuffer.pvBuffer] } 0;>> greater?
mov r8d [rsi _SecBuffer.cbBuffer]
mov rdx [rsi _SecBuffer.pvBuffer]
nop
invoke _send @hSocket rdx r8 0
.if rax == 0;zero?
.break
.endif
; mov rax @temp_ip_buffer_input
; mov rcx [rax _SecBuffer.pvBuffer]
; invoke FreeContextBuffer rcx
.endif
.endif
.endif
add rsi sizeof _SecBuffer
dec @count_input
.endw
mov @flags 1
.break
.elseif eax == SEC_E_INCOMPLETE_MESSAGE;>> zero? ;Data for the whole message was not read from the wire.
;When this value is returned the pInput buffer contains a SecBuffer structure
;with a BufferType member of SECBUFFER_MISSING. The cbBuffer member of SecBuffer
;contains a value that indicates the number of additional bytes that the function
;must read from the client before this function succeeds. While this number is not always accurate
;using it can help improve performance by avoiding multiple calls to this function.
;"schannel: received incomplete message need more data\n"));
;未从连线读取整条消息的数据。返回此值时,pInput缓冲区包含一个SecBuffer结构,其中缺少SecBuffer_的BufferType成员。SecBuffer的cbBuffer成员包含一个值,
;该值指示在该函数成功之前该函数必须从客户端读取的附加字节数。虽然这个数字并不总是准确的,但是使用它可以避免多次调用这个函数,从而帮助提高性能。
;个人理解::仅仅为了避免多次调用这个函数
.if sqword ptr @retry_count } 10;>> greater?
;我们的处理就是忽略这个请求!也就是说已经连续10次没有读到数据了,那就别指望服务端会提供数据了
; invoke logout addr atext (_error_connect_SecurityContext "证书生成过程中服务端一直返回0数据 虽然连接没有断开,我们主动断开吧!") sizeof _error_connect_SecurityContext
.break
.endif
; lea rax @temp_buffer_for_list_input
; mov ecx [rax _SecBuffer.cbBuffer] ;这里应该是总共需要这么多的数据才能完成本次请求。
;.elseif <<cmp eax SEC_I_INCOMPLETE_CREDENTIALS>> zero?
; nop
.elseif eax == SEC_I_COMPLETE_NEEDED;>> zero?
nop ; InitializeSecurityContext (Digest) 时有这种情况
.elseif eax == SEC_I_CONTINUE_NEEDED;>> zero? ;这种情况是我们的input_SecBufferDesc_buffer 指定的缓冲区里的数据没有处理完,需要继续处理
;send handshake token to server */
lea rsi @temp_buffer_for_list_input
mov @count_input 3
.while sqword ptr @count_input } 0;greater?
.if [rsi _SecBuffer.BufferType] == SECBUFFER_TOKEN;>> zero?
.if sqword ptr [rsi _SecBuffer.cbBuffer] } 0;>> greater?
.if [rsi _SecBuffer.pvBuffer] } 0;>> greater?
mov r8d [rsi _SecBuffer.cbBuffer]
mov rdx [rsi _SecBuffer.pvBuffer]
nop
invoke _send @hSocket rdx r8 0
.if rax == 0 ;>> zero?
.break
.endif
.endif
.endif
.endif
add rsi sizeof _SecBuffer
dec @count_input
.endw
lea rax @temp_buffer_for_list_input
add rax sizeof _SecBuffer
mov ecx [rax _SecBuffer.BufferType]
.if ecx == SECBUFFER_EXTRA;>> zero?;The security package uses this value to
; indicate the number of extra or
; unprocessed bytes in a message.
mov ecx [rax _SecBuffer.cbBuffer];未处理的数据大小
mov rsi [rax _SecBuffer.pvBuffer]
; int 3
add rsi rcx
lea rdi recv_token_buffer
rep movsb
and @readcount 0
and @retry_count 0
mov @ptemp_ip_buffer rdi
.else
and @readcount 0
and @retry_count 0
lea rdi recv_token_buffer
mov @ptemp_ip_buffer rdi
.endif
.else
.break
.endif
mov @count_input 3
lea rax @temp_buffer_for_list_input
mov @temp_ip_buffer_input rax
.while sqword ptr @count_input } 0;greater?
.if sqword ptr [rax _SecBuffer.cbBuffer] } 0;>> greater?
.if sqword ptr [rax _SecBuffer.pvBuffer] } 0;>> greater?
mov rax @temp_ip_buffer_input
mov rcx [rax _SecBuffer.pvBuffer]
invoke FreeContextBuffer rcx
.endif
.endif
add @temp_ip_buffer_input sizeof _SecBuffer
mov rax @temp_ip_buffer_input
dec @count_input
.endw
.endw
mov @count_input 3
lea rax @temp_buffer_for_list_input
mov @temp_ip_buffer_input rax
.while sqword ptr @count_input } 0; greater?
.if sqword ptr [rax _SecBuffer.cbBuffer] } 0;>> greater?
.if sqword ptr [rax _SecBuffer.pvBuffer] } 0;>> greater?
mov rax @temp_ip_buffer_input
mov rcx [rax _SecBuffer.pvBuffer]
invoke FreeContextBuffer rcx
.endif
.endif
add @temp_ip_buffer_input sizeof _SecBuffer
mov rax @temp_ip_buffer_input
dec @count_input
.endw
jmp exit
mov rax 1
exit:
mov rax @flags
add rsp 100h
ret
connect_SecurityContext endp
disconnect_server_for_https proc _hSocket:qword _pCredHandle:qword _pCtxtHandle:qword
LOCAL @hheap:qword
invoke FreeCredentialsHandle _pCredHandle
invoke DeleteSecurityContext _pCtxtHandle
invoke shutdown _hSocket 2;0 不能再读,1不能再写,2 读写都不能。
invoke closesocket _hSocket
ret
disconnect_server_for_https endp
connect_server_for_https proc uses rbx rsi rdi _ipaddress:PVOID _TargetName:PVOID _pCredHandle:PVOID _pCtxtHandle:PVOID _ptimstamp:PVOID
LOCAL @stWsa:WSADATA
LOCAL @stSin:sockaddr_in
LOCAL @hSocket:qword
LOCAL @hheap:qword
LOCAL @SecPkgContext_StreamSizes_buffer :SecPkgContext_StreamSizes
invoke WSAStartup 0202h addr @stWsa
invoke socket AF_INET SOCK_STREAM IPPROTO_TCP
.if rax ==INVALID_SOCKET
; invoke logout addr atext (https_WSAStartup_error "htttps_WSAStartup_error") sizeof https_WSAStartup_error
; invoke MessageBoxA hWnd addr errorsocket addr errorsocket MB_OK
mov rax 0
jmp exit
.endif
mov @hSocket rax
invoke HostnameToIP _ipaddress
.if eax == 0
invoke MessageBox hWnd addr error_ip addr error_ip MB_OK
ret
.endif
mov @stSin.sin_addr eax
mov @stSin.sin_family AF_INET
mov eax 443
invoke htons rax ;htonl()表示将32位的主机字节顺序转化为32位的网络字节顺序 htons()表示将16位的主机字节顺序转化为16位的网络字节顺序(ip地址是32位的端口号是16位的 )
mov @stSin.sin_port ax
@@:
invoke connect @hSocket addr @stSin sizeof @stSin
.if eax == SOCKET_ERROR
invoke WSAGetLastError
.if eax {} WSAEWOULDBLOCK;nozero?
invoke Sleep 1000
; invoke logout addr atext (_https_again_connect "https_again_connect?") sizeof _https_again_connect
; invoke MessageBoxA hWnd addr again_connect addr again_connect MB_OKCANCEL;;"确定== 1" "取消== 2"
mov rax 0
jmp exit
.endif
.endif
invoke connect_SecurityContext @hSocket _TargetName _pCredHandle _pCtxtHandle _ptimstamp
.if rax == 0;zero?
; invoke logout addr atext(error_SecurityContext "connect_SecurityContext调用失败") sizeof error_SecurityContext
jmp exit
.endif
invoke QueryContextAttributesA _pCtxtHandle SECPKG_ATTR_STREAM_SIZES addr @SecPkgContext_StreamSizes_buffer
.if rax {} 0;nozero?
; invoke logout addr atext(_QueryContextAttributes "QueryContextAttributes调用失败") sizeof _QueryContextAttributes
mov rax 0
jmp exit
.endif
mov eax @SecPkgContext_StreamSizes_buffer.cbMaximumMessage
.if eax == 0;zero?
; invoke logout addr atext(_cbMaximumMessage "cbMaximumMessage为空值") sizeof _cbMaximumMessage
jmp exit
.endif
mov eax @SecPkgContext_StreamSizes_buffer.cBuffers
.if eax { 4;>> above?
; invoke logout addr atext(_StreamSizes_buffer_cBuffers "没有加解密缓冲区") sizeof _StreamSizes_buffer_cBuffers
mov rax 0
jmp exit
.endif
mov rax @hSocket
ret
exit:
mov rax 0
ret
connect_server_for_https endp
WinMain proc hInst:qword hPrevInst:qword CmdLine:qword CmdShow:qword
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL @hInst:qword
LOCAL @hPrevInst:qword
LOCAL @CmdLine:qword
LOCAL @CmdShow:qword
LOCAL icex:INITCOMMONCONTROLSEX
invoke GetModuleHandle 0
mov hInstance rax
invoke GetCommandLine
mov CommandLine rax
invoke RtlZeroMemory addr wc sizeof wc
invoke InitCommonControls
mov icex.dwSize sizeof INITCOMMONCONTROLSEX
mov icex.dwICC ICC_TAB_CLASSES
invoke InitCommonControlsEx addr icex
mov wc.cbSize sizeof WNDCLASSEX
mov wc.style CS_HREDRAW or CS_VREDRAW
lea rax WndProc
mov wc.lpfnWndProc rax;offset WndProc
mov wc.cbClsExtra NULL
mov wc.cbWndExtra DLGWINDOWEXTRA
push hInstance
pop wc.hInstance
mov wc.hbrBackground COLOR_BTNFACE 1
mov wc.lpszMenuName IDM_MENU;0
lea rax ClassName
mov wc.lpszClassName rax;offset ClassName
invoke LoadIcon NULL IDI_APPLICATION
mov wc.hIcon rax
mov wc.hIconSm rax
invoke LoadCursor 0 IDC_ARROW
mov wc.hCursor rax
invoke RegisterClassEx addr wc
invoke CreateDialogParam hInstance IDD_DIALOG NULL addr WndProc NULL
invoke ShowWindow hWnd SW_SHOWNORMAL
invoke UpdateWindow hWnd
.while TRUE
invoke GetMessage addr msg 0 0 0
.if eax == 0
.break
.endif
invoke TranslateMessage addr msg
invoke DispatchMessage addr msg
.endw
invoke ExitProcess 0
mov rax msg.wParam
ret
WinMain endp
EncryptMessage_and_send proc uses rbx rsi rdi _hSocket:qword _pCtxtHandle:qword _pget_or_post_buffer:qword _pget_or_post_buffer_length:qword _psend_buffer:qword _send_buffer_length:qword
LOCAL @hSocket:qword
LOCAL @buffer_for_https_count:qword
LOCAL @pget_or_post_buffer:qword
LOCAL @_pget_or_post_buffer_length:qword
LOCAL @thistimelength:qword;本次参与运算的长度
LOCAL @hheap:qword
LOCAL @SecPkgContext_StreamSizes_buffer :SecPkgContext_StreamSizes
LOCAL @temp_buffer_for_list [5]:_SecBuffer; 5 dup (<?>
LOCAL @EncryptMessage_SecBufferDesc_buffer :_SecBufferDesc
mov @hSocket rcx
mov @pget_or_post_buffer r8
mov @_pget_or_post_buffer_length r9
mov @buffer_for_https_count r9
.while sqword ptr @buffer_for_https_count }0;>> greater?
invoke RtlZeroMemory _psend_buffer _send_buffer_length
invoke QueryContextAttributesA _pCtxtHandle SECPKG_ATTR_STREAM_SIZES addr @SecPkgContext_StreamSizes_buffer
.if rax {} 0;>> nozero?
jmp exit
.endif
mov rcx @buffer_for_https_count
mov ebx @SecPkgContext_StreamSizes_buffer.cbMaximumMessage ;最大单次可以处理的内存空间(数据大小)
.if sqword ptr rcx } rbx;>> GREATER? ;谁的空间小就用谁
mov @thistimelength rbx
sub @buffer_for_https_count rbx
.else
mov @thistimelength rcx
mov @buffer_for_https_count 0
.endif
mov @EncryptMessage_SecBufferDesc_buffer.ulVersion SECBUFFER_VERSION;== 0
mov @EncryptMessage_SecBufferDesc_buffer.cBuffers 4
lea rax @temp_buffer_for_list
mov @EncryptMessage_SecBufferDesc_buffer.pBuffers rax
mov ecx @SecPkgContext_StreamSizes_buffer.cbHeader
mov [rax _SecBuffer.cbBuffer] ecx
mov [rax _SecBuffer.BufferType] SECBUFFER_STREAM_HEADER ;==7
mov rsi _psend_buffer
mov [rax _SecBuffer.pvBuffer] rsi
add rsi rcx
add rax sizeof _SecBuffer
mov rcx @thistimelength
mov [rax _SecBuffer.cbBuffer] ecx
mov [rax _SecBuffer.BufferType] SECBUFFER_DATA ;==1
mov [rax _SecBuffer.pvBuffer] rsi
mov rdi rsi
mov rsi _pget_or_post_buffer
mov rcx @thistimelength
shr rcx 3 ;/8
cld
rep movsq
mov rcx @thistimelength
and rcx 111b
rep movsb
add rax sizeof _SecBuffer
mov ecx @SecPkgContext_StreamSizes_buffer.cbTrailer
mov [rax _SecBuffer.cbBuffer] ecx
mov [rax _SecBuffer.BufferType] SECBUFFER_STREAM_TRAILER ;==6
mov [rax _SecBuffer.pvBuffer] rdi
add rax sizeof _SecBuffer
mov [rax _SecBuffer.cbBuffer] 0
mov [rax _SecBuffer.BufferType] 0
mov [rax _SecBuffer.pvBuffer] 0
;These buffers must be supplied in the order shown.
; Buffer type Description
; SECBUFFER_STREAM_HEADER Used internally. No initialization required.
; SECBUFFER_DATA Contains the plaintext message to be encrypted.
; SECBUFFER_STREAM_TRAILER Used internally. No initialization required.
; SECBUFFER_EMPTY Used internally. No initialization required. Size can be zero.
;https://docs.microsoft.com/en-us/windows/win32/secauthn/encryptmessage--schannel
mov ecx @SecPkgContext_StreamSizes_buffer.cbHeader
mov @EncryptMessage_SecBufferDesc_buffer.ulVersion SECBUFFER_VERSION;== 0
mov @EncryptMessage_SecBufferDesc_buffer.cBuffers 4
lea rax @temp_buffer_for_list
mov @EncryptMessage_SecBufferDesc_buffer.pBuffers rax
invoke EncryptMessage _pCtxtHandle 0 addr @EncryptMessage_SecBufferDesc_buffer 0
.if rax{} SEC_E_OK; nozero?
jmp exit
.endif
lea rax @temp_buffer_for_list
mov ecx @SecPkgContext_StreamSizes_buffer.cbHeader
mov edx [rax _SecBuffer.cbBuffer]
.if ecx {} edx;>> nozero?
;也就是说加密的数据大小发生了变化,需要调整一下每个类型的位置
; int 3
nop
mov rdi [rax _SecBuffer.pvBuffer]
add rdi rdx
add rax sizeof _SecBuffer;第一个尾部
mov rsi [rax _SecBuffer.pvBuffer];第二个
mov ecx [rax _SecBuffer.cbBuffer]
cld
rep movsb
add rax sizeof _SecBuffer
mov rsi [rax _SecBuffer.pvBuffer];第三个
mov ecx [rax _SecBuffer.cbBuffer]
cld
rep movsb
.endif
lea rax @temp_buffer_for_list
mov ecx [rax _SecBuffer.cbBuffer]
add rax sizeof _SecBuffer
add ecx [rax _SecBuffer.cbBuffer]
add rax sizeof _SecBuffer
add ecx [rax _SecBuffer.cbBuffer]
mov rax rcx
invoke _send @hSocket _psend_buffer rax 0
.if eax == 0; zero?
jmp exit
.endif
mov rcx @thistimelength
add @pget_or_post_buffer rcx
.endw
exit:
ret ;返回0 是发送错误,1是正确,其余值是EncryptMessage 错误
EncryptMessage_and_send endp
WndProc proc uses rbx rsi rdi hWin:HWND uMsg:UINT64 wParam:WPARAM lParam:LPARAM
LOCAL @PpcPackages:qword
LOCAL @ppPackageInfo:qword
LOCAL @hSocket:qword
LOCAL @pheap_send_buffer:qword
LOCAL @heap_send_buffer_size:qword
LOCAL @pheap_recv_buffer:qword
LOCAL @heap_recv_buffer_size:qword
LOCAL @return_count:qword
mov rax uMsg
.if eax==WM_INITDIALOG
push hWin
pop hWnd
.elseif eax==WM_COMMAND
mov rax wParam
and rax 0FFFFh
.if rax==IDM_FILE_EXIT
invoke SendMessage hWin WM_CLOSE 0 0
.elseif rax==IDM_HELP_ABOUT
invoke ShellAbout hWin addr AppName addr AboutMsg NULL
.elseif rax == 1003
invoke GetProcessHeap
mov rbx rax
invoke HeapAlloc rbx HEAP_ZERO_MEMORY heap_send__size
.if rax {} 0
mov @pheap_send_buffer rax
.else
ret
.endif
mov @heap_send_buffer_size heap_send__size
invoke HeapAlloc rbx HEAP_ZERO_MEMORY heap_recv__size
.if rax {} 0
mov @pheap_recv_buffer rax
.else
ret
.endif
mov @heap_recv_buffer_size heap_recv__size
invoke GetDlgItemText hWin 1002 addr net_buffer sizeof net_buffer
; int 3
invoke connect_server_for_https addr net_buffer addr TargetName addr CredHandle addr CtxtHandle addr timstamp
;
.if rax == 0
ret
.endif
mov @hSocket rax
mov r9d format_https_for_toutiao_size
invoke EncryptMessage_and_send @hSocket addr CtxtHandle addr format_https_for_toutiao r9 @pheap_send_buffer @heap_send_buffer_size
invoke wait_and_recv_data_with_https @hSocket addr CtxtHandle @pheap_recv_buffer @heap_recv_buffer_size addr @return_count
mov rsi @pheap_recv_buffer
; int 3
invoke SetDlgItemText hWin 1001 rsi
invoke HeapFree rbx 0 @pheap_send_buffer
invoke HeapFree rbx 0 @pheap_recv_buffer
invoke disconnect_server_for_https @hSocket addr CredHandle addr CtxtHandle
.endif
; int 3
; invoke EnumerateSecurityPackagesA addr @PpcPackages addr @ppPackageInfo
; .elseif eax==WM_SIZE
.elseif eax==WM_CLOSE
invoke DestroyWindow hWin
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage NULL
.else
invoke DefWindowProc hWin uMsg wParam lParam
ret
.endif
xor rax rax
ret
WndProc endp
end
include win64.inc
include ksamd64.inc
include Macros\x64macros.inc
include Macros\x64calling.inc
include Macros\vasily.inc
include user32.inc
include kernel32.inc
include shell32.inc
include comctl32.inc
include comdlg32.inc
include secur32.inc
include zlibstat.inc
include ws2_32.inc
includelib user32.lib
includelib kernel32.lib
includelib shell32.lib
includelib comctl32.lib
includelib comdlg32.lib
includelib Secur32.Lib
includelib ws2_32.lib
includelib zlibstat.lib
WinMain PROTO :QWORD :QWORD :QWORD :QWORD
WndProc PROTO :QWORD :QWORD :QWORD :QWORD
IDD_DIALOG equ 1000
IDM_MENU equ 10000
IDM_FILE_EXIT equ 10001
IDM_HELP_ABOUT equ 10101
atext macro _name:REQ _text:REQ other:VARARG
.data
align 8
_name db _text
for _ehter:REQ <other>
db _ehter ;0d0ah
endm
db 0
.code
exitm <_name>
endm
ifndef _SecHandle
_SecHandle struct
dwLower ULONG_PTR ?
dwUpper ULONG_PTR ?
SecHandle ends
endif
heap_send__size equ 1024*1024*8
heap_recv__size equ 1024*1024*8
.const
ClassName db 'DLGCLASS' 0
AppName db 'Dialog as main' 0
AboutMsg db 'MASM64 RadASM Dialog as main' 13 10 'Copyright ? masm64 2001' 0
;sspi db "Microsoft Unified Security Protocol Provider" 0 ;Schannel Security Package 微软提供了10几种安全包类型,这只是其中之一
sspi db "Default TLS SSP" 0
error_ip db "IP地址错误" 0
zlib_ver db "1.2.11" 0
TargetName db "baidu.com" 0
.data?
hInstance dq ?
CommandLine dq ?
hWnd dq ?
net_buffer dq 100h dup (?)
recv_token_buffer dd 4000h dup (?);这个缓冲接收服务器返回的令牌
z_stream_buffer z_stream <?>
CERT_CHAIN_ENGINE_CONFIG_buffer CERT_CHAIN_ENGINE_CONFIG <?>
CERT_CHAIN_PARA_buffer CERT_CHAIN_PARA <?>
CERT_CHAIN_POLICY_PARA_buffer CERT_CHAIN_POLICY_PARA <?>
HTTPSPolicyCallbackData_buffer HTTPSPolicyCallbackData <>
CERT_CHAIN_POLICY_STATUS_buffer CERT_CHAIN_POLICY_STATUS <?>
CredHandle _SecHandle <>
CtxtHandle _SecHandle <>
timstamp FILETIME <>
.data
format_https_for_toutiao db 'GET / HTTP/1.1'
dw 0a0dh
db 'Accept: text/html application/xhtml xml */*';告诉WEB服务器自己接受什么介质类型,/ 表示任何类型
dw 0a0dh
db 'Accept-Encoding: gzip deflate br';表示客户端支持gzip
dw 0a0dh
db 'Accept-Language: zh-CN zh;q=0.9'
dw 0a0dh
db 'Connection: keep-alive'
dw 0a0dh
; db 'DNT: 1'
; dw 0a0dh
db 'Host: www.baidu.com'
dw 0a0dh
; db 'Sec-Fetch-Dest: document'
; dw 0a0dh
; db 'Sec-Fetch-Mode: navigate'
; dw 0a0dh
; db 'Sec-Fetch-Site: none'
; dw 0a0dh
; db 'Sec-Fetch-User: ?1'
; dw 0a0dh
; db 'Upgrade-Insecure-Requests: 1'
; dw 0a0dh
; db 'Cache-Control: no-cache'
db 'Cache-Control: no-store'
dw 0a0dh
db 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/102.0.0.0 Safari/537.36';AppleWebKit/537.36 (KHTML like Gecko) Chrome/70.0.3538.110 Safari/537.36';该头域的内容包含发出请求的用户信息。
dw 0a0dh
;db 'Accept: text/html application/xhtml xml application/xml;q=0.9 image/webp image/apng */*';告诉WEB服务器自己接受什么介质类型,/ 表示任何类型
; dw 0a0dh
; db 'sec-ch-ua: " Not A;Brand";v="99" "Chromium";v="102" "Google Chrome";v="102"'
; dw 0a0dh
; db 'sec-ch-ua-mobile: ?0'
; dw 0a0dh
; db 'sec-ch-ua-platform: "Windows"'
dw 0a0dh
dw 0a0dh
dq 0
format_https_for_toutiao_size equ $-format_https_for_toutiao