Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hooked function is called only once #36

Open
lazybind opened this issue Dec 11, 2016 · 8 comments
Open

Hooked function is called only once #36

lazybind opened this issue Dec 11, 2016 · 8 comments

Comments

@lazybind
Copy link

First of all I want to say thank you for this library! It's really interesting thing.

Now, about a bug. Sorry in advance, if I use wrong terminology.

Short description

I've seen situation when hook function was called only once. 'Some magic' restores the original address of function.

Explanation is simple: 'fishkook' is patching a lazy binded symbol at the moment when original symbols was not yet resolved.
My hook calls original function and this restores original function. This call looks like this:

void* hook_bindBlobStr(void *StorageMapThis, int index, void* WTFStrText)
{
    printf("-----------------------hook_bindBlobStr\n");
    
    return orig_SQLiteStatement_bindBlobStr(StorageMapThis, index, WTFStrText);
}

Assembler code of original call (from debugger) is here:

0x1000097a6 <+70>:  movq   0xb4eb(%rip), %rdx        ; orig_SQLiteStatement_bindBlobStr
  0x1000097ad <+77>:  movq   -0x8(%rbp), %rdi
  0x1000097b1 <+81>:  movl   -0xc(%rbp), %esi
  0x1000097b4 <+84>:  movq   -0x18(%rbp), %rcx
  0x1000097b8 <+88>:  movq   %rdx, -0x30(%rbp)
  0x1000097bc <+92>:  movq   %rcx, %rdx
  0x1000097bf <+95>:  movq   -0x30(%rbp), %rcx
  0x1000097c3 <+99>:  movl   %eax, -0x34(%rbp)
->  0x1000097c6 <+102>: callq  *%rcx

As you can see, code calls original function from $rcx register. Lets take a look what is this:

(lldb) image lookup -a $rcx
      Address: WebKitLegacy[0x00000000000a00be] (WebKitLegacy.__TEXT.__stub_helper + 5906)
      Summary: 
(lldb)
(lldb) disa -s $rcx
    0x1060f50be: pushq  $0x7424                   ; imm = 0x7424 
    0x1060f50c3: jmp    0x1060f39ac
(lldb) 

This is stub for unresolved symbols. So, we will call stub_helper wich will resovle (actually restore) original address of function.

(lldb) image lookup -a 0x1060f39ac
      Address: WebKitLegacy[0x000000000009e9ac] (WebKitLegacy.__TEXT.__stub_helper + 0)
(lldb) 
(lldb) disa -s 0x1060f39ac
->  0x1060f39ac: leaq   0x4d655(%rip), %r11       ; (void *)0x000000010005b108: initialPoolContent + 49368
    0x1060f39b3: pushq  %r11
    0x1060f39b5: jmpq   *0x4d645(%rip)            ; (void *)0x0000000103da03fc: dyld_stub_binder
(lldb) 

Conclusion

When my hook function calls original function then this leads to restore (unhook) original addrress of symbol, and my hook will be never called again.

Environment

Simulator iPhone 6s Plus 10.0
Platform: x64
Hooked function: WebCore::SQLiteStatement::bindBlob() == _ZN7WebCore15SQLiteStatement8bindBlobEiRKN3WTF6StringE
Project works with UIWebView.

Question

Is this the bug or expected behavior?

@grp
Copy link
Contributor

grp commented Dec 11, 2016

Did you make your GitHub account just to report this bug? Either way, great username.

I can see why this would happen with lazy binding. Also not sure if it's a bug, but it's definitely confusing. Here's two potential workarounds:

  1. Load the library with dlopen(..., RTLD_NOW) before using fishhook for force immediate symbol binding.
  2. Call the function yourself to force it to be bound first.

Do either of those help for you?

@lazybind
Copy link
Author

Did you make your GitHub account just to report this bug? Either way, great username.

ok Thanks, will know.

Do either of those help for you?

Doesn't work.

Option A Simple call dlopen/dlsym/original function

  1. Force loading framework
    dlopen("/Applications/Xcode8.0.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk//System/Library/PrivateFrameworks/WebCore.framework/WebCore", RTLD_NOW);
  2. Obtain pointer to function (dlsym)
  3. Force call problematic function.
    Doesn't work, because dlsym provide real address of function, not a bind stub.

Code is below:

        void *handle = dlopen("/Applications/Xcode8.0.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk//System/Library/PrivateFrameworks/WebCore.framework/WebCore", RTLD_NOW);
        
        typedef void * (*SQLiteStatement_bindBlob_func)(void *StorageMapThis, int index, void* WTFStrText);
        SQLiteStatement_bindBlob_func func= (SQLiteStatement_bindBlob_func)dlsym(handle, "_ZN7WebCore15SQLiteStatement8bindBlobEiRKN3WTF6StringE");
        char *stubData[100] = {0};
        func(stubData, 0, stubData);

Call of func looks like this:

    0x10000987a <+170>: movq   -0x3b0(%rbp), %rax
    0x100009881 <+177>: movq   -0x3d8(%rbp), %rdi
    0x100009888 <+184>: movl   -0x3cc(%rbp), %esi
    0x10000988e <+190>: movq   -0x3d8(%rbp), %rdx
->  0x100009895 <+197>: callq  *%rax

And $rax is original address of function:

(lldb) image lookup -a $rax
      Address: WebCore[0x0000000000e12e60] (WebCore.__TEXT.__text + 14749488)
      Summary: WebCore`WebCore::SQLiteStatement::bindBlob(int, WTF::String const&)
(lldb) 

Code from framework still contains stubs for lazy binding, those operations don't trigger the binding.

Option B More complex way: dlopen/dlsym but call upper function from framework which will call required function. Those must trigger binding mechanism. Unfortunately upper function is not exported, so can't force trigger binding mechanism for required function.

@grp
Copy link
Contributor

grp commented Dec 11, 2016

Sorry, I meant dlopen(..., RTLD_NOW) on WebCoreLegacy, which is the one that has the lazy binding.

@lazybind
Copy link
Author

Do not really understand.
Lazy binding function will be 'resolved' only after first real call of it. Loading framework which contains it does not resolve issue, because it needs to call original function at least once. Only after that hook can be installed and call of original function from hook will be safety (this will not lead to restoring original function).

In other words, if I postpone installing hook for 10 seconds, than all work as expected. Unfortunately, this is bad case for me, because I lose some important information in that 10 seconds.

I guess, you mean that I should load framework, and this would lead to call original function... but loaded framework doesn't call this function. This functionality linked with browsing webpage inside UIWebview component, and requires interaction from user side. Only in that way is posible to trigger this function.

@grp
Copy link
Contributor

grp commented Dec 11, 2016

My understanding is that RLTD_NOW immediately binds the lazy symbols, without needing them to be called. From the manpage:

RTLD_NOW    All external function references are bound immediately during the call to dlopen().

You can then hook the functions after dlopen() returns since the symbols should all be bound.

@lazybind
Copy link
Author

Usual case of using hook functionality

We have target library which functions have to be hooked.
Also, we have some other code (that could be our App or 3-rd party library) which uses target library functions. And we use fishhook for hooking target library functions.
So, when our App or 3-rd party library is calling target function then our hook is executed. Our hook is calling original function, and at that moment system binds original symbol.
Here is very tricky moment: system code patches/binds lazy symbol inside target library.

#0	0x00000001085ecb8d in ImageLoaderMachO::bindLocation(ImageLoader::LinkContext const&, unsigned long, unsigned long, unsigned char, char const*, long, char const*, char const*, char const*) ()
#1	0x00000001085f197b in ImageLoaderMachOCompressed::doBindFastLazySymbol(unsigned int, ImageLoader::LinkContext const&, void (*)(), void (*)()) ()
#2	0x00000001085dfbb9 in dyld::fastBindLazySymbol(ImageLoader**, unsigned long) ()
#3	0x000000010c358516 in dyld_stub_binder ()

This is fine for us, because lazy symbols inside our App (or 3-rd party library) still contain correct link on our hooked function.

Not obvious case

Now, another case, and it's more interesting (not obvious).
If we hook exported (external) function inside our module. When we are calling from hook function an original function then system does the same work - binds lazy symbol. But this also means that system re-writes our hook pointer. As the result, our hook called only once.
Of course this case looks very strange. Why do we need to hook own function in own module? But bellow is next case which explains my situation.

Now imagine that one function could be present in many frameworks. How it's posible? Well, some frameworks could be based on the same component, as in my case. For example WebCore component (with some functions set) is built with WebKit and also with WebKitLegacy frameworks.
So, in two frameworks is present the same function. When my hook is calling from WebKit and calls original function from WebKitLegacy then this works fine. Becuase system will bind original function inside WebKitLegacy. So, symbol inside WebKit will still has our hook.
But if we are calling hook from WebKitLegacy then our hook also calls original function from this framework, and system rewrites our hook.
In simple words: if we 'install' hook inside module/framework which calls own external function then we will see this bug - our hook will be called only once.

My case

Now imagine that one function could be present in many frameworks. How it's posible? Well, some frameworks could be based on the same component, as in my case. For example WebCore component (with some functions set) is built with WebKit and also with WebKitLegacy frameworks.
So, in two frameworks is present the same function. When my hook is calling from WebKit and calls original function from WebKitLegacy then this works fine. Because system will bind original function inside WebKitLegacy (rewrite our pointer on hook). So, symbol inside WebKit will still have our hook.
But if we are calling hook from WebKitLegacy then our hook also calls original function from this framework, and system rewrites our hook.
In simple words: if we 'install' hook inside module / framework which calls own external function then we will see this bug - our hook will be called only once.

@grp
I do not know why dlopen (RTLD_NOW) does not resolve lazy binding symbols.

Summary

I know the root cause of bug, but still don't know how to fix it in appropriate way. I can again install hook after first call of original function but this requires additional logic in code. Looks like the best way it's reading Apple documentation to understand how to force bind lazy symbols and only after that install hook.

@lazybind
Copy link
Author

I've found solution. I take original pointer from dlsym instead of result from rebind_symbols, and that solves my issue.

    void *handle = dlopen("..framework...", RTLD_NOW);
    original_SomeFunction = dlsym(handle, "SomeFunction");

    if ((rebind_symbols((struct rebinding[1]){{(char *)"SomeFunction", (void *)replaced_SomeFunction}}, 1) < 0))
    {
        Log(@"Hooking failed.");
    }

Call of dlsym provides direct address of function, and that's all.

Conclusion
I based my code on the sample, but looks like it's wrong for some cases. Call of original function could rewrite hook. To avoid that, the simpler solution would be obtained pointer to original function from dlsym, instead of using it from rebind_symbols.

coolstar added a commit to coolstar/electra that referenced this issue Jan 22, 2018
@jobsyu jobsyu mentioned this issue Jun 18, 2020
@saagarjha
Copy link

I wonder if fishhook could improve the situation here checking the GOT entry it's replacing to see if it's dyld_stub_binder, and if so, replacing it with a thunk that "does the right thing" when called.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants