Skip to content

Conversation

@physics-sec
Copy link
Contributor

@physics-sec physics-sec commented Jul 24, 2021

Hello again 😄

With this PR, we find the export address of all DLL functions ourselves, so we no longer rely on GetProcAddress and LdrGetProcedureAddress. The idea is to stay away from Kernel32 and NTDLL as much as possible. Resolving several functions might lead to detection

I have tried to resolve all exports of Kernel32, NTDLL, User32. vcruntime40 and stdlib and it worked very well, so I am confident the implementation is solid.

One interesting thing I found while developing this:
For some weird reason, Kernel32 (and a few others) exports some functions that aren't really defined in Kernel32, but in other libraries (maybe everybody knew this but me?)
When you try to resolve those functions, you get a pointer to a string that describes where that function is truly defined.
For example, AcquireSRWLockExclusive is a function exported by Kernel32, that resolves to an address that has this string: NTDLL.RtlAcquireSRWLockExclusive, meaning the function is defined in ntdll as RtlAcquireSRWLockExclusive.
So that is why the function is recursive, it needs to handle those cases. (luckily your test DLL loads one of these "fake" exports, so I was able to find this behavior)

Anyway, hope you find it useful

Edit:
I have also found that in some weird cases, libraries such as Kernel32 does this thing where a function "points" to another library and function name, but the library is like deprecated and the function is in truth implemented in Kernel32.
That is why if the resolve fails, it tries with Kernel32 and KernelBase. You can try to resolve all exports of the DLL you like and compare the result with what LdrGetProcedureAddress says it is and confirm that it works for all cases.
Let me know if this is unclear and you want to clarify something.

@physics-sec
Copy link
Contributor Author

physics-sec commented Aug 1, 2021

Edit:
Added a few more changes:

  • Now DarkLoadLibrary returns a pointer to the structure (it is better to store that in the heap rather than in the stack, because when the function returns the stack will most likely get overwritten)
  • Removed most of the warnings
  • Now we find the address of LoadLibraryA and RtlRbInsertNodeEx dynamically, no more static import (the idea is to make the binary less interesting to static analysis, we might add more functions in the future)
  • The function LocalLdrGetProcedureAddress now has a fallback, if the address of the function is not found, now we use LdrGetProcedureAddress (I checked and this is never called for TestDLL, but is better to be safe than sorry)
  • If the DLL we are loading doesn't export an entry point (some DLLs don't) we no longer crash
  • Now we actually check that DllMain returns TRUE (that function might fail)
  • We now overwrite the "desired base" with the actual base of the DLL once loaded (I found that LoadLibrary does this for some reason, so we should also)

Hope you find this useful.

@bats3c
Copy link
Owner

bats3c commented Aug 2, 2021

Some really nice work here, sorry for taking a while to review it... but it looks good, thanks.

@bats3c bats3c merged commit 22459ca into bats3c:master Aug 2, 2021
@physics-sec
Copy link
Contributor Author

Awesome! 😃

@physics-sec physics-sec deleted the find-dll-exports branch August 2, 2021 12:46
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

Successfully merging this pull request may close these issues.

2 participants