JH Pseudo Full Stack Engineer

逆向wininet.dll的过程

2017-04-29
John He

wininet.dll是Windows提供的通过HTTP, FTP等应用层协议访问网络资源的一个库。

提供的API例如InternetOpen, InternetConnect, HttpOpenRequest, HttpSendRequest等。

HINTERNET作为wininet提供给用户应用的句柄, HttpOpenRequest, HttpSendRequest发起请求均是通过HINTERNET句柄来完成.

这个HINTERNET并不是raw socket handler,而是wininet封装raw socket handler提供给用户应用使用的一个数据结构。

这个HINTERNET带来了很多不便:

  • HINTERNET并没有暴露足够多的接口去获取所有的TCP连接信息;
  • 即便HINTERNET提供了足够的接口, 但是这些接口并不是socket标准接口, 获取不到raw socket handler, 很多标准socket方法无法使用。

于是便萌生了想法, 逆向一下wininet.dll看有没有办法挖出HINTERNET和raw socket handler之间的关系, 从而获取到raw socket handler.

用WinDbg抓下wininet.dll的symbol, 用Visual Studio反汇编, 单步跟进HttpSendRequest方法.

发现HttpSendRequest调用了一个_mapHandleToAddress的内部方法.

从名字上看,这个内部方法很像是我要找的方法。

继续用Visual Studio的Assembly模式,跟进_mapHandleToAddress内部, 从汇编代码逻辑可以看到, 该方法接受HINTERNET为参数, 在一个全局数据结构中以HINTERNET作为key查找, 辗转调用其他几个内部方法获取了一个value.

这个value就是我要找的raw socket handler.

大体逆向出来的流程和细节如下:

  • 其中_mapHandleToAddress用ecx, esi接收HINTERNET参数, 返回值放在edx指向的内存单元(这个不同于常规函数, 并未使用eax存返回值);

  • 用_mapHandleToAddress的返回值, 调用另外几个内部方法GetSourcePort, GetDestPort, GetSocket等, 其中GetSocket返回的便是我要找的raw socket handler

那用GetSocket的返回值能否调用标准的socket函数呢?

用如下代码验证一下:

HANDLE hProcess = GetCurrentProcess();
DWORD cbNeeded;
HMODULE hMods[1024];
unsigned int i = 0;
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
    for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
        TCHAR szModName[MAX_PATH];
        MODULEINFO modinfo = { 0 };
 
        // Get module’s name.
 
        if (GetModuleBaseName(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(TCHAR)))
        {
            char pModName[sizeof(szModName) / sizeof(TCHAR)];
            int iLength = WideCharToMultiByte(CP_ACP, 0, szModName, -1, NULL, 0, NULL, NULL);
            WideCharToMultiByte(CP_ACP, 0, szModName, -1, pModName, iLength, NULL, NULL);
 
            if (strcmp(pModName, "WININET.dll") == 0) {
                if (GetModuleInformation(hProcess, hMods[i], &modinfo, sizeof(modinfo)) != 0) {
                    LPVOID baseOfDll = modinfo.lpBaseOfDll;
                    
                    // TODO: get entry point of MapHandleToAddress dynamically rather than hardcoding offset.
                    ULONG64 entryOfMapHandleToAddress = (ULONG64)hMods[i] + 0xB08F0;
                    PDWORD pdwRet = new DWORD[1];
 
                    // TODO: get entry point of GetSourcePort dynamically rather than hardcoding offset.
                    ULONG64 entryOfGetSourcePort = (ULONG64)hMods[i] + 0x16EA39;
                    PDWORD pdwSourcePort = new DWORD[1];
 
                    // TODO: get entry point of GetDestPort dynamically rather than hardcoding offset.
                    ULONG64 entryOfGetDestPort = (ULONG64)hMods[i] + 0x16E965;
                    PDWORD pdwDestPort = new DWORD[1];
 
                    // TODO: get entry point of GetSocket dynamically rather than hardcoding offset.
                    ULONG64 entryOfGetSocket = (ULONG64)hMods[i] + 0x16EA03;
                    PDWORD pdwSocket = new DWORD[1];
 
                    __asm {
                        mov ecx, hFile
                        mov esi, hFile
                        mov edx, pdwRet
                        call entryOfMapHandleToAddress
                        sub esp, 0x04 /* Not sure why MapHandleToAddress doesn't restore esp correctly, manually restore it */
 
                        mov ecx, pdwRet
                        mov ecx, [ecx]
                        call entryOfGetDestPort
                        mov ebx, pdwDestPort
                        mov dword ptr[ebx], eax
 
                        mov ecx, pdwRet
                        mov ecx, [ecx]
                        call entryOfGetSourcePort
                        mov ebx, pdwSourcePort
                        mov dword ptr[ebx], eax
 
                        mov ecx, pdwRet
                        mov ecx, [ecx]
                        call entryOfGetSocket
                        mov ebx, pdwSocket
                        mov dword ptr[ebx], eax
                    }
 
                    printf("source port: %d, destination port: %d\n", *pdwSourcePort, *pdwDestPort);
 
                    struct sockaddr_in serv, guest;
                    PWSTR serv_ip = new wchar_t[128];
                    PWSTR guest_ip = new wchar_t[128];
                    int serv_len = sizeof(serv);
                    int guest_len = sizeof(guest);
                    memset(&serv, 0, serv_len);
                    memset(&guest, 0, guest_len);
                    int ret1 = getsockname(*pdwSocket, (struct sockaddr *)&serv, &serv_len);
                    int ret2 = getpeername(*pdwSocket, (struct sockaddr *)&guest, &guest_len);
                    if (ret1 == 0 && ret2 == 0)
                    {
                        char ServAddrName[NI_MAXHOST];
                        if (getnameinfo((LPSOCKADDR)&guest, guest_len, ServAddrName, sizeof(ServAddrName), NULL, 0, NI_NUMERICHOST) != 0)
                        {
                            strcpy(ServAddrName, "<unknown>");
                        }
 
                        char ClientAddrName[NI_MAXHOST];
                        if (getnameinfo((LPSOCKADDR)&serv, serv_len, ClientAddrName, sizeof(ClientAddrName), NULL, 0, NI_NUMERICHOST) != 0)
                        {
                            strcpy(ServAddrName, "<unknown>");
                        }
 
                        printf("server ip: %s, client ip: %s\n", ServAddrName, ClientAddrName);
                    }
                }
            }
        }
    }
}

可见, 通过调用GetSocket内部方法返回的pdwSocket, 用来调用getpeername, getsockname这样的标准socket函数, 能得到正确的结果.

调试汇编的过程有一些trick, 都在代码的注释中体现了.


Similar Posts

Comments