2

I need to locate the address of a function in a DLL at runtime which isn't exported. The DLL in question is vbe7.dll related to Visual Basic (and macros). The function is ___vbaInStr, can be found in IDA if symbols are downloaded from Microsoft.

Locating it in IDA to get an offset is no biggie but I want to use it in Python and be able to locate it even if it's running on other machines. The reason being that I'm using winappdbg and need to place a breakpoint on the particular function to access the arguments pushed to the stack.

My theories so far have been:

  1. Find exported functions used close to a call to the targeted function and calculate the offset. Haven't tried it though.
  2. Find a instruction pattern around the call to the targeted function. This failed depending on the DLL version and compiler used.
  3. Look at the targeted function to locate the starting address, this failed for the same reason as above.

So getting the address of an exported function is easy but getting the address of a non-exported function? Target is .text:102242E3.

.text:10224212 ; int __stdcall sub_10224212(int, LCID Locale, VARIANTARG *, VARIANTARG *pvarSrc, int)
.text:10224212 sub_10224212    proc near               ; CODE XREF: sub_1000B164+859p
.text:10224212                                         ; rtcInStrChar+52p
.text:10224212
.text:10224212 arg_0           = dword ptr  8
.text:10224212 Locale          = dword ptr  0Ch
.text:10224212 arg_8           = dword ptr  10h
.text:10224212 pvarSrc         = dword ptr  14h
.text:10224212 arg_10          = dword ptr  18h
.text:10224212
.text:10224212                 mov     edi, edi
.text:10224214                 push    ebp
.text:10224215                 mov     ebp, esp
.text:10224217                 push    ebx
.text:10224218                 push    edi
.text:10224219                 mov     edi, [ebp+pvarSrc]
.text:1022421C                 push    edi
.text:1022421D                 call    rtcIsNull
.text:10224222                 test    ax, ax
.text:10224225                 jnz     loc_10224330
.text:1022422B                 mov     ebx, [ebp+arg_8]
.text:1022422E                 push    ebx
.text:1022422F                 call    rtcIsNull
.text:10224234                 test    ax, ax
.text:10224237                 jnz     loc_10224330
.text:1022423D                 push    esi
.text:1022423E                 push    8
.text:10224240                 pop     esi
.text:10224241                 cmp     [edi], si
.text:10224244                 jnz     short loc_1022424B
.text:10224246                 mov     edi, [edi+8]
.text:10224249                 jmp     short loc_10224261
.text:1022424B ; ---------------------------------------------------------------------------
.text:1022424B
.text:1022424B loc_1022424B:                           ; CODE XREF: sub_10224212+32j
.text:1022424B                 push    edi             ; pvarSrc
.text:1022424C                 call    sub_1006D9C8
.text:10224251                 mov     edi, eax
.text:10224253                 mov     eax, esi
.text:10224255                 mov     word ptr pvarg.anonymous_0, ax ; jumptable 1000BE78 cases 56231,63056
.text:1022425B                 mov     dword ptr pvarg.anonymous_0+8, edi ; jumptable 1000BE78 cases 56231,63056
.text:10224261
.text:10224261 loc_10224261:                           ; CODE XREF: sub_10224212+37j
.text:10224261                 movzx   eax, word ptr [ebx]
.text:10224264                 cmp     ax, si
.text:10224267                 jnz     short loc_1022426E
.text:10224269                 mov     eax, [ebx+8]
.text:1022426C                 jmp     short loc_102242DB
.text:1022426E ; ---------------------------------------------------------------------------
.text:1022426E
.text:1022426E loc_1022426E:                           ; CODE XREF: sub_10224212+55j
.text:1022426E                 cmp     word ptr pvarg.anonymous_0, si ; jumptable 1000BE78 cases 56231,63056
.text:10224275                 jnz     short loc_102242C7
.text:10224277                 cmp     ax, 9
.text:1022427B                 jnz     short loc_102242C7
.text:1022427D                 xor     eax, eax
.text:1022427F                 mov     esi, offset pvarSrc
.text:10224284                 push    esi
.text:10224285                 mov     word ptr pvarg.anonymous_0, ax ; jumptable 1000BE78 cases 56231,63056
.text:1022428B                 push    dword ptr [ebx+8]
.text:1022428E                 call    sub_1022103A
.text:10224293                 mov     ebx, eax
.text:10224295                 test    ebx, ebx
.text:10224297                 jz      short loc_102242A6
.text:10224299                 push    edi             ; bstrString
.text:1022429A                 call    ds:__imp_SysFreeString ; jumptable 1000BE78 cases 171,16878,16935,30141,31177,32098,43703,61383,65216
.text:102242A0                 push    ebx
.text:102242A1                 call    sub_1005DCF1
.text:102242A6
.text:102242A6 loc_102242A6:                           ; CODE XREF: sub_10224212+85j
.text:102242A6                 push    8
.text:102242A8                 pop     eax
.text:102242A9                 push    eax             ; vt
.text:102242AA                 push    esi             ; pvarSrc
.text:102242AB                 push    esi             ; pvargDest
.text:102242AC                 mov     word ptr pvarg.anonymous_0, ax ; jumptable 1000BE78 cases 56231,63056
.text:102242B2                 mov     dword ptr pvarg.anonymous_0+8, edi ; jumptable 1000BE78 cases 56231,63056
.text:102242B8                 call    sub_10082A4C
.text:102242BD                 mov     eax, dword ptr pvarSrc.anonymous_0+8
.text:102242C2                 push    8
.text:102242C4                 pop     esi
.text:102242C5                 jmp     short loc_102242DB
.text:102242C7 ; ---------------------------------------------------------------------------
.text:102242C7
.text:102242C7 loc_102242C7:                           ; CODE XREF: sub_10224212+63j
.text:102242C7                                         ; sub_10224212+69j
.text:102242C7                 push    ebx             ; pvarSrc
.text:102242C8                 call    sub_1006D9C8
.text:102242CD                 mov     ecx, esi
.text:102242CF                 mov     word ptr pvarSrc.anonymous_0, cx
.text:102242D6                 mov     dword ptr pvarSrc.anonymous_0+8, eax
.text:102242DB
.text:102242DB loc_102242DB:                           ; CODE XREF: sub_10224212+5Aj
.text:102242DB                                         ; sub_10224212+B3j
.text:102242DB                 push    [ebp+arg_10]    ; int
.text:102242DE                 push    edi             ; LPCWSTR
.text:102242DF                 push    eax             ; lpSrcStr
.text:102242E0                 push    [ebp+Locale]    ; Locale
.text:102242E3                 call    sub_10083C60 
.text:102242E8                 mov     edi, eax
.text:102242EA                 cmp     word ptr pvarg.anonymous_0, si ; jumptable 1000BE78 cases 56231,63056
.text:102242F1                 jnz     short loc_10224307
.text:102242F3                 push    dword ptr pvarg.anonymous_0+8 ; bstrString
1

if you have pdb then this code can fetch you either Symbol or Type from it

#include <windows.h>
#include <stdio.h>
#include <dbghelp.h>
#pragma comment(lib , "dbghelp.lib")

HANDLE hProcess;

BOOL CALLBACK EnumSymProc(PSYMBOL_INFO pSymInfo,ULONG ,PVOID UserContext){
  size_t maxcmplen = strlen((PCHAR)UserContext);
  if( maxcmplen == pSymInfo->NameLen ) {
    if((strncmp(pSymInfo->Name,(PCHAR)UserContext,pSymInfo->NameLen)) == 0) {
      TI_FINDCHILDREN_PARAMS childs = {0};
      SymGetTypeInfo(hProcess,pSymInfo->ModBase,pSymInfo->TypeIndex,
      TI_GET_CHILDRENCOUNT,&childs.Count);
      printf("%8s%10s%10s%16s %s" , "Size" ,"TypeIndex","Childs","Address","Name\n");
      printf ("%8x %8x %8x %16I64x %10s\n",pSymInfo->Size,pSymInfo->TypeIndex,
      childs.Count,pSymInfo->Address , pSymInfo->Name );
    }
  }
  return TRUE;
}
void main(int argc , char *argv[]) {
  hProcess = GetCurrentProcess();
  SymInitialize(hProcess, NULL, FALSE);  
  DWORD64 BaseOfDll=SymLoadModuleEx(hProcess,NULL,argv[1],NULL,
  0x400000,0x20000,NULL,0);
  SymEnumSymbols(hProcess , BaseOfDll , "*!*" , EnumSymProc , argv[2]);
  SymEnumTypes(hProcess, BaseOfDll, EnumSymProc, argv[2]);  
  SymCleanup(hProcess);
}

usage

symenumtype.exe .\vbe7.pdb __vbaInStr
    Size TypeIndex    Childs         Address Name
       0        0        0           4c3e4f __vbaInStr
0

If you target known versions you can hardcode the addresses as found it Ida. If you don't know for different versions than best approach is fetch pdb via api and find address that way.

This might help you tonget started: https://stackoverflow.com/questions/3092609/how-to-get-field-names-and-offsets-of-a-struct-using-dbghlp-and-pdb

  • As there is too many unknown versions, hardcoded offsets wouldn't be a good solution, it was although my initial approach. I've noticed that IDA fails to get the symbols from Microsoft (E_PDB_NOT_FOUND) for many of the versions. – user17903 Oct 28 '16 at 8:34
  • In which case I think the only remaining option is to find out if your function is always following a known exported one. Then disassemble yourself eg with help.madshi.net/madDisAsm.htm to find the end of function then next address will be yours... – Remko Oct 28 '16 at 9:49
  • or use pattern search... – Remko Oct 28 '16 at 9:49
0

The best option here is probably your first suggestion - finding an exported function that calls your function, and then disassembling it in run time to figure out where the call points to.

This is pretty easily done by using IDA's cross-reference for example (ctrl+x when your cursor is on the function's name in IDA). Keep in mind that you'd want to avoid/reduce the amount of certain parameters used to find the function you're looking for. For example, you should be looking for the path with least dereferences.

Instead of a function you can look for a construct that points to the function you're looking for (if that function is used in a function pointer).

0

I mangaged to solve it using pefile and capstone. Finding a exported function that leads to my target (as suggested above) and then using capstone to disassemble the function and find my way to the target.

In a nutshell what I did (can of course be beautified etc.):

import sys
import pefile
from capstone import Cs, CS_ARCH_X86, CS_MODE_32

dll = pefile.PE(sys.argv[1])
#dll = pefile.PE('C:\Program Files\Common Files\microsoft shared\VBA\VBA7\VBE7.DLL')


for export in dll.DIRECTORY_ENTRY_EXPORT.symbols:
    if export.name == 'rtcInStrChar':
        exp_addr = export.address
        break

for imp in dll.DIRECTORY_ENTRY_IMPORT:
    for entry in imp.imports:
        if entry.name == 'SysFreeString':
            imp_addr = entry.address
            break
            #print('0x%x - %s' % (entry.address, entry.name))

memory = dll.get_memory_mapped_image()

dsm = Cs(CS_ARCH_X86, CS_MODE_32)

for op in dsm.disasm(memory[exp_addr:exp_addr + 0xA0], (exp_addr + dll.OPTIONAL_HEADER.ImageBase)):
    if op.mnemonic == 'call':
        last_call = op.op_str[2:]
    if op.mnemonic == 'ret':
        break
    #print("0x%x:\t%s\t%s" %(op.address, op.mnemonic, op.op_str))

next_func = int(last_call, 16) - dll.OPTIONAL_HEADER.ImageBase

calls = 0
call_free = 0
for op in dsm.disasm(memory[next_func:next_func + 0x100], (next_func + dll.OPTIONAL_HEADER.ImageBase)):
    print("0x%x:\t%s\t%s" %(op.address, op.mnemonic, op.op_str))
    if op.mnemonic == 'call' and '0x%x' % imp_addr in op.op_str:
        call_free += 1
    if call_free == 2:
        print('GOT IT: 0x%x' % last_call)
        break
    if op.mnemonic == 'call':
        last_call = op.address

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.