某 IM 软件登录代理异常分析
日志分析
通过 EXCEPTION_ACCESS_VIOLATION
可以判断异常类型是非法内存访问。
触发异常的指令地址位于 EIP=A95A3399
,指向非法内存区域。
栈帧底部地址位于 EBP=1127F7C4
,可以帮助恢复函数调用栈的结构。
Type: EXCEPTION_ACCESS_VIOLATION
Error: Execute address 0xA95A3399
Address: A95A3399
CallStack:
0xA95A3399<unknown module>
msf + D602
xpng_dll + 3A3B2
xpng_dll + 3A514
xpng_dll + 39989
xpng_dll + 3B161
xpng_dll + 31374
...
Regs:
EAX=10D6DE00, EBX=10D490E0, ECX=10D521E0, EDX=00000000
ESI=10D52250, EDI=10A4AA20, EBP=1127F7C4, ESP=1127F760, EIP=A95A3399
堆栈分析
通过日志记录的调用栈可以恢复函数调用链:
xpng::PacketStreamSocket::NotifyOnClose()
msf::TCPChannelConnector::OnChannelClose()
msf::TCPChannelConnector::HandleConnectFailed()
delete msf::MSFChannelTCP
msf::MSFChannelTCP::Close()
流量分析
使用通过 HTTP
代理的 Happy Eyeballs
连接算法。
IPv4
连接:
CONNECT 180.109.156.44:443 HTTP/1.1
Host: 180.109.156.44:443
Accept: */*
Content-Type: text/html
Proxy-Connection: Keep-Alive
Content-length: 0
HTTP/1.1 200 Connection established
Connection: close
IPv6
连接:
CONNECT msfwifiv6.3g.qq.com:14000 HTTP/1.1
Host: msfwifiv6.3g.qq.com:14000
Accept: */*
Content-Type: text/html
Proxy-Connection: Keep-Alive
Content-length: 0
HTTP/1.1 200 Connection established
Connection: close
代码分析
当网络连接因为异常原因而中止时,OnChannelClose
函数首先会调用 HandleConnectFailed
函数,其中 HandleConnectFailed
函数会把 MSFChannelTCP
类给释放掉,然后再调用 MSFChannelTCP
类中的 Close
虚函数。
由于此时 MSFChannelTCP
类已经被释放掉,结构体中第一项的虚表指针被 msvcrt(ucrt)
堆管理机制重新指向了之前被释放的一个 Chunk
,所以 Chunk
中的第三项会被当作 Close
虚函数的地址进行调用,导致程序崩溃并抛出 EXCEPTION_ACCESS_VIOLATION
异常。
上面那个虚表指针指向的 Chunk
实际是由相邻的两个小 Chunk
释放之后被 RtlpHpVsChunkCoalesce
函数合并得到的,其中第三项正好是高地址的小 chunk
中被内核 RtlpHpHeapGlobals
异或保护的 Header
,所以只要不重启系统每一次取出的虚函数指针都是相同的,即触发非法执行的 EIP
保持不变。
调试脚本如下,可以在断点处自动打印调用栈和对象地址:
from __future__ import print_function
import ida_dbg
import ida_ida
import ida_lines
from idc import *
class MyDbgHook(ida_dbg.DBG_Hooks):
""" Own debug hook class that implementd the callback functions """
def __init__(self):
ida_dbg.DBG_Hooks.__init__(self) # important
self.steps = 0
def dbg_stack_trace(self):
tmp_ea = get_reg_value('eip')
print('#### 0x%X %s' % (tmp_ea, get_func_off_str(tmp_ea)))
ebp = get_reg_value('ebp')
dep = 1
while 1:
tmp_ea = read_dbg_dword(ebp+4)
if tmp_ea == 0:
break
print('#' * dep + '#### 0x%X %s' % (tmp_ea, get_func_off_str(tmp_ea)))
ebp = read_dbg_dword(ebp)
dep += 1
def dbg_bpt(self, tid, ea):
# Object trace
print('### 0x%X %s 0x%X' % (ea, get_func_off_str(ea), get_reg_value('ecx')))
# Stack trace
self.dbg_stack_trace()
# ida_dbg.continue_process()
return 0
# Remove an existing debug hook
try:
if debughook:
print("Removing previous hook ...")
debughook.unhook()
except:
pass
# Install the debug hook
debughook = MyDbgHook()
debughook.hook()
参考文章
https://www.blackhat.com/docs/us-16/materials/us-16-Yason-Windows-10-Segment-Heap-Internals-wp.pdf