1

So currently i have the following code which is just a simple trampoline type hook for ws2_32 to hook connect in order to eventually add a proxy to an application. The problematic area is inside int __stdcall connect_FUNC(SOCKET s, const struct sockaddr* name, int namelen). Whilst i don't need to understand or call anything after 'connect_ORI(s, name, namelen)` when proxying (all the work is done before the original function is called); it'd be nice to understand the following incorrect/weird behaviour:

Calls to functions like printf, MessageBox, even std::cout before connect_ORI(s, name, namelen) work fine and the connect is successful, However, moving the same call (printf etc) after connect_ORI(s, name, namelen) results in the detour'd function returning a different (incorrect) result (-1). It might be important to also add that the injected process does not crash in both test cases.

I know that it can't be the Patch/RemovePatch functions, I tried MinHook and it had the same weird behaviour. Also, patching other functions, inside user32.dll worked fine. Is there some special case or optimization in ws2_32.dll that i have to look out for? I'm thinking there's some sort of stack or frame pointer / register corruption but unsure if that is the case and unsure on how to fix it. I included all of the code for completeness. Could anyone explain this behaviour specifically using code?

I am using MinGW-w64 x86_64-720-posix-sjlj-rt_v5-rev1. 32Bit DLL injecting into a 32bit process. Using -Os optimization.

Thanks in advance.

typedef int (__stdcall *connect_DEF)(SOCKET, const struct sockaddr*, int);
connect_DEF connect_ORI = NULL;
void* jmpOffset = NULL;

std::uint8_t* Patch(std::uint8_t* OrigFunc, std::uint8_t* HookFunc)
{
    DWORD dwProtect = 0;
    const static std::uint8_t jmp[] = {0xB8, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0};
    const static std::int8_t jmp_size = sizeof(jmp) / sizeof(std::uint8_t);
    static std::uint8_t HookJump[jmp_size + 1] = {jmp_size};
    VirtualProtect(OrigFunc, jmp_size, PAGE_EXECUTE_READWRITE, &dwProtect);
    memcpy(&HookJump[1], OrigFunc, jmp_size);
    memcpy(OrigFunc, jmp, jmp_size);
    memcpy(OrigFunc + 1, &HookFunc, sizeof(void*));
    VirtualProtect(OrigFunc, jmp_size, dwProtect, &dwProtect);
    return HookJump;
}

void RemovePatch(std::uint8_t* OrigFunc, std::uint8_t* HookJump)
{
    DWORD dwProtect = 0;
    VirtualProtect(OrigFunc, HookJump[0], PAGE_EXECUTE_READWRITE, &dwProtect);
    memcpy(OrigFunc, &HookJump[1], HookJump[0]);
    VirtualProtect(OrigFunc, HookJump[0], dwProtect, &dwProtect);
}

int __stdcall connect_FUNC(SOCKET s, const struct sockaddr* name, int namelen)
{
    int Result = -1;

    printf("HERE\n"); // printf works here, connect func works with printf here.
    if (jmpOffset)
    {
        RemovePatch((uint8_t*)connect_ORI, (uint8_t*)jmpOffset);
        jmpOffset = NULL;
        Result = connect_ORI(s, name, namelen);
        jmpOffset = (void*)Patch((uint8_t *)connect_ORI, (uint8_t*)connect_FUNC);
    }

    //printf("HERE\n"); // stack gets corrupted or something happens when printf is here. connect returns -1.

    return Result;
}

BOOL __stdcall DllMain(HMODULE DLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            {
                HMODULE WS2_32DLL = GetModuleHandle("ws2_32.dll");
                if (WS2_32DLL)
                {
                    connect_ORI = (connect_DEF)GetProcAddress(WS2_32DLL, "connect");
                    if (connect_ORI)
                        jmpOffset = (void*)Patch((uint8_t*)connect_ORI, (uint8_t*)connect_FUNC);
                }
            }
            break;
        case DLL_PROCESS_DETACH:
            {
                if (jmpOffset)
                {
                    RemovePatch((uint8_t*)connect_ORI, (uint8_t*)jmpOffset);
                }
            }
            break;
        case DLL_THREAD_ATTACH:
            break;
        case DLL_THREAD_DETACH:
            break;
    }
    return TRUE;
}

asm of differences in both testcases (connect_FUNC)

printf before calling original function (works correctly):

LC0:
    .ascii "HERE\12\0"
    .text
    .globl  __Z12connect_FUNCjPK8sockaddri@12
    .def    __Z12connect_FUNCjPK8sockaddri@12;  .scl    2;  .type   32; .endef
__Z12connect_FUNCjPK8sockaddri@12:
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ebx
    orl $-1, %ebx
    subl    $20, %esp
    movl    $LC0, (%esp)
    call    __Z6printfPKcz
    movl    _jmpOffset, %eax
    testl   %eax, %eax
    je  L7
    movl    %eax, 4(%esp)
    movl    _connect_ORI, %eax
    movl    %eax, (%esp)
    call    __Z11RemovePatchPhS_
    movl    16(%ebp), %eax
    movl    $0, _jmpOffset
    movl    %eax, 8(%esp)
    movl    12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    *_connect_ORI
    movl    %eax, %ebx
    movl    _connect_ORI, %eax
    subl    $12, %esp
    movl    $__Z12connect_FUNCjPK8sockaddri@12, 4(%esp)
    movl    %eax, (%esp)
    call    __Z5PatchPhS_
    movl    %eax, _jmpOffset
L7:
    movl    %ebx, %eax
    movl    -4(%ebp), %ebx
    leave
    ret $12
    .section .rdata,"dr"

printf after calling original function (doesn't work correctly):

LC0:
    .ascii "HERE\12\0"
    .text
    .globl  __Z12connect_FUNCjPK8sockaddri@12
    .def    __Z12connect_FUNCjPK8sockaddri@12;  .scl    2;  .type   32; .endef
__Z12connect_FUNCjPK8sockaddri@12:
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ebx
    orl $-1, %ebx
    subl    $20, %esp
    movl    _jmpOffset, %eax
    testl   %eax, %eax
    je  L8
    movl    %eax, 4(%esp)
    movl    _connect_ORI, %eax
    movl    %eax, (%esp)
    call    __Z11RemovePatchPhS_
    movl    16(%ebp), %eax
    movl    $0, _jmpOffset
    movl    %eax, 8(%esp)
    movl    12(%ebp), %eax
    movl    %eax, 4(%esp)
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    *_connect_ORI
    movl    %eax, %ebx
    movl    _connect_ORI, %eax
    subl    $12, %esp
    movl    $__Z12connect_FUNCjPK8sockaddri@12, 4(%esp)
    movl    %eax, (%esp)
    call    __Z5PatchPhS_
    movl    %eax, _jmpOffset
L8:
    movl    $LC0, (%esp)
    call    __Z6printfPKcz
    movl    %ebx, %eax
    movl    -4(%ebp), %ebx
    leave
    ret $12
    .section .rdata,"dr"
Kasi
  • 11
  • 2
  • Have you done `#include `? What happens with `puts` instead of `printf`? How does the disassembly of `connect_FUNC` look in the wrong case? – Jester Jan 29 '18 at 19:13
  • What OS version? Because on any supported windows version this will fail because those are "Well Known" DLLs – Mgetz Jan 29 '18 at 19:42
  • @Jester `#include ` has been included, i accidentally cut the includes off, my bad. `puts` has the exact same behaviour. I edited the question and added the asm. @Mgetz On Windows 10. Could you elaborate on "Well Known"? the hook successfully works when the printf is before the call to the original connect func. – Kasi Jan 29 '18 at 20:19
  • Well the result is stored in `ebx` so that's probably getting thrashed by `printf` ... that of course shouldn't happen. – Jester Jan 29 '18 at 20:22
  • @Kasi so "Well Known" DLLs are protected by the kernel, check your return values on `VirtualProtect` they should be failing. The OS marks those DLLs code pages as non-writable permanently because they are shared between all processes. – Mgetz Jan 29 '18 at 20:24
  • @Mgetz in that case he shouldn't even get into `connect_FUNC`, but he does. – Jester Jan 29 '18 at 20:26
  • @Jester it might be giving him a local location used for delay load in their own process but those pages are very much locked to read only for security reasons. Otherwise any process on the system could mess with them. I would check to see what address they are getting back. More than likely it's not where `WS2_32.dll` is mapped. – Mgetz Jan 29 '18 at 20:35
  • @Mgetz so how do you explain him having it **working** with the first version? It's only the result that is broken in the second version. – Jester Jan 29 '18 at 20:40
  • @Mgetz It's DLL injection inside a single process during runtime if i didn't make it clear in my question. It's not system-wide/global hook/static hook. Modifying in the process' address space i'm injecting would have no effect on what is happening in other processes.`VirtualProtect` also doesn't fail. @Jester im not at all experienced in asm. I'm still real confused on why it works on other api functions like `MessageBox` in `user32` but not in `connect`. Would there be any way to prevent the value in the ebx value from getting destroyed/overwritten? Some sort of inline asm i'm assuming? – Kasi Jan 29 '18 at 20:51
  • code is monstrous and incorrect for hook at all. every time set and remove hook is nightmare. and hook `connect` not hook all. need hook WSP functions. anyway if `connect` return error, first what you need try - call `WSAGetLastError` or `GetLastError` (this is the same) – RbMm Jan 29 '18 at 20:57
  • Ahh, i found: https://stackoverflow.com/questions/36876026/why-does-printf-overwrite-the-ecx-register So essentially i have to preserve the value if i call any function which uses the `__cdecl` calling convention? @Jester – Kasi Jan 29 '18 at 20:58
  • @Kasi - how __cdecl calling convention here related at all ? you not need any preserve – RbMm Jan 29 '18 at 20:59
  • @RbMm connect doesn't fail. if you read Jester's replies and read https://stackoverflow.com/questions/36876026/why-does-printf-overwrite-the-ecx-register then you'll see how they are related. – Kasi Jan 29 '18 at 21:10
  • @Kasi - *connect doesn't fail* - may be this nonblocking socket for example ? your link - this is about ? how it related to your problem ? 1.) your code is monstrous and incorrect - you need full rewrite it. 2) for understand error in current version - call `GetLastError()` after -1 for begin. 3) hook `connect` at all bad idea - not all connections call connect. – RbMm Jan 29 '18 at 21:14
  • @RbMm Clearly you hate reading. Most of what you're asking has already been explained. 1. The issue has **nothing** to do with hooking code being incorrect. As i **clearly** specified in my question, i used MinHook and had the same incorrect behaviour. 2. `connect` doesn't fail. `GetLastError` returns no error. The process i'm injecting **DOES** use connect. I'm also not planning on using this for more than the process i'm injecting. Whilst i appreciate the time you took to read or didn't read this question, i feel as though your answers aren't really productive or constructive. – Kasi Jan 29 '18 at 21:26
  • so you try say that: 1. `connect_ORI` return `0` (in your code it saved in *ebx*, `Result`). and 2. `printf` change *ebx* (`Result`) to `SOCKET_ERROR` (-1). not believe. – RbMm Jan 29 '18 at 21:33
  • in any case - simply set breakpoint in you hook and debug disassembler code - all will be clear. *`GetLastError` returns no error* which is not visible in your code, that you call it at all and look for result – RbMm Jan 29 '18 at 21:36
  • Okay thanks for your time! – Kasi Jan 29 '18 at 21:52

0 Answers0