某 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

原文地址:https://www.cnblogs.com/algonote/p/15350928.html