• XSS.stack #1 – первый литературный журнал от юзеров форума

RtlCaptureStackBackTrace() не правильно трейсит стек

Roller

RAID-массив
Пользователь
Регистрация
26.05.2020
Сообщения
71
Решения
1
Реакции
54
Всем привет!
Столкнулся с проблемой, что RtlCaptureStackBackTrace() из Kernel32.dll всегда возвращает одинаковое значение - это так задумано, или есть нюансы? На х64 всегда получаю всего 1 фрейм с текущим адресом-возврата, а на х32 через WOW уже 3 фрейма. При этом такое впечатление, что значения где-то кэшируются, т.к. если вызываю одну и ту же процедуру из разных мест своего-же кода, значение фремов всегда одинаковые. Система 64-бит Win7, пишу на ассемблере FASM, вот исходник (в скрепке ехе для тестов). Может кто сталкивался с подобным?

C-подобный:
format   pe console
include 'win32ax.inc'
entry    start
;//-----------
section '.data' data readable writeable

struct IMAGEHLP_SYMBOL
  SizeOfStruct  dd  sizeof.IMAGEHLP_SYMBOL
  Address       dd  0
  Size          dd  0
  Flags         dd  0
  MaxNameLen    dd  128
  Name          rb  128
ends

pSymInfo        IMAGEHLP_SYMBOL
pHandle         dd  0
count           dd  0
stacks          dd  32 dup(0)

;//-----------
section '.text' code readable executable
start:   invoke  GetCurrentProcessId
         invoke  OpenProcess,PROCESS_ALL_ACCESS,0,eax
         mov     [pHandle],eax

         invoke  SymInitialize,eax,0,1
         invoke  RtlCaptureStackBackTrace,0,32,stacks,0  ;//<----- Запрашиваю 32 кадра от текущего(0),
         mov     [count],eax           ;//<----------------------- а возвращается макс 3
         call    PrintStackTrace

         invoke  GetTickCount
         invoke  RtlCaptureStackBackTrace,0,32,stacks,0
         mov     [count],eax
         call    PrintStackTrace

         invoke  GetModuleHandle,<'user32.dll',0>
         invoke  RtlCaptureStackBackTrace,0,32,stacks,0
         mov     [count],eax
         call    PrintStackTrace

@exit:  cinvoke  _getch
        cinvoke  exit,0

;//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
align 8
proc  PrintStackTrace
         mov     eax,[count]
         cmp     eax,32
         ja      @fuck
         push    eax
        cinvoke  printf,<10,10,' Found stack frames: %d',0>,eax

         pop     ecx
         mov     esi,stacks
@@:      push    ecx esi esi
         invoke  SymGetSymFromAddr,[pHandle],dword[esi],0,pSymInfo
         lea     ebx,[pSymInfo.Name]
         pop     esi
        cinvoke  printf,<10,'   Frame[%d]:  0x%08x  %s',0>,[count],dword[esi],ebx
         pop     esi ecx
         add     esi,4
         dec     [count]
         loop    @b

;//----- Зачистить структуру и буфер для сл.вызова -------

         mov     [count],0
         mov     ecx,sizeof.IMAGEHLP_SYMBOL -4
         mov     edi,pSymInfo
         add     edi,4
         xor     al,al
         rep     stosb
         mov     dword[pSymInfo.MaxNameLen],128

         mov     edi,stacks
         mov     ecx,32/4
         rep     stosd
@fuck:   ret
endp
;//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

section '.idata' import data readable writeable
library  msvcrt,'msvcrt.dll',kernel32,'kernel32.dll',\
         dbghelp,'dbghelp.dll',user32,'user32.dll'
import   dbghelp,SymInitialize,'SymInitialize',SymGetSymFromAddr,'SymGetSymFromAddr'
include 'api\msvcrt.inc'
include 'api\kernel32.inc'
include 'api\user32.inc'

stackTrace.png


Здесь значения в поле "Frame[3]" это адрес-возврата после вызова RtlCaptureStackBackTrace(), что совпадает с адресом инструкций mov [count],eax в коде. Как видно, хоть RetAddr и разный, остальные 2 указателя во-всех вызовах совпадают, при этом всегда макс(3) фрейма, хотя если запросить вместо 32 всего два фрейма, то возвращаются 2. Почему так, не могу сообразить. Кстати из Kernel32.dll функция форвардится в Ntdll, и я без результатно пробовал звать её напрямую.
 

Вложения

  • StackTrace32.zip
    991 байт · Просмотры: 22
Похоже что RtlCaptureStackBackTrace() не захватыет стекфреймы из библиотечных функций, с локальными всё ок:
C:
USHORT get_frames_count() {
    LPVOID frames[32] = { 0 };
    USHORT captured = RtlCaptureStackBackTrace(0, 32, &frames, NULL);
    return captured;
}

void local_func() {
    assert(get_frames_count() == 7);
}

int main() {
    assert(get_frames_count() == 6);
    assert(get_frames_count() == 6);
    local_func();
    assert(get_frames_count() == 6);
    return 0;
}
Есть более универсальный метод: StackWalkEx/StackWalk
 
с локальными всё ок
странно.. у меня и с локальными так-же.
наверное всё-таки StackWalk() нужно пробовать, хотя она и громоздкая
 
наверное всё-таки StackWalk() нужно пробовать, хотя она и громоздкая
Большинство ЯП для бэктрейса юзают это апи, т.к оно наиболее универсальное, да и сами Майки рекомендуют.
 
Похоже что RtlCaptureStackBackTrace() не захватыет стекфреймы из библиотечных функций, с локальными всё ок:
C:
USHORT get_frames_count() {
    LPVOID frames[32] = { 0 };
    USHORT captured = RtlCaptureStackBackTrace(0, 32, &frames, NULL);
    return captured;
}

void local_func() {
    assert(get_frames_count() == 7);
}

int main() {
    assert(get_frames_count() == 6);
    assert(get_frames_count() == 6);
    local_func();
    assert(get_frames_count() == 6);
    return 0;
}
Есть более универсальный метод: StackWalkEx/StackWalk

It isn't really about ignoring lib frames, it's that there is no valid 64bit unwind info for certain code, so the OS can’t keep walking. In a typical MSVC compiled bin with normal prolog/epilog, you’ll see libcalls in the call stack just fine.
 
Есть более универсальный метод: StackWalkEx/StackWalk
Подскажите, а как её использовать?
Как я понял нужно в цикле её вызывать, пока не вернёт ошибку False что-ли?
Просто у меня сейчас первый вызов StackWalk() возвращает ОК (свой адрес-возврата), а при втором пусто, хотя в отладчике х64dbg вижу 3 фрейма. Переписал на обе разрядности х32/64, пробовал вызовы лок.процедур - везде такая ситуация.
 
Подскажите, а как её использовать?
Как я понял нужно в цикле её вызывать, пока не вернёт ошибку False что-ли?
Просто у меня сейчас первый вызов StackWalk() возвращает ОК (свой адрес-возврата), а при втором пусто, хотя в отладчике х64dbg вижу 3 фрейма. Переписал на обе разрядности х32/64, пробовал вызовы лок.процедур - везде такая ситуация.
SymInitialize, capture CONTEXT, fill out STACKFRAME64, then loop (until FALSE).
x64: if a func (especially raw asm) has no registered unwind metadata, StackWalk64 cannot unwind from it. It rets only the caller’s IP, then fails.
If everything's clear to this point + nothing worked, consider the lame answer: compiler settings.

PS. Honorable mention: machine type param
 
then loop (until FALSE).
значит нужно крутить цикл до False..
а в остальном я всё сделал правильно, хотя при тестах под WOW64 выяснилось (отладчиком), что почему-то RtlCaptureContext() не правильно заполняет поле EIP в структуре CONTEXT - тогда я использовал совет от сюда, и теперь получаю только один свой фрейм.
 
значит нужно крутить цикл до False..
а в остальном я всё сделал правильно, хотя при тестах под WOW64 выяснилось (отладчиком), что почему-то RtlCaptureContext() не правильно заполняет поле EIP в структуре CONTEXT - тогда я использовал совет от сюда, и теперь получаю только один свой фрейм.
Regarding Wow64, sometimes RtlCaptureContext / GetThreadContext doesn’t fill EIP/ESP/EBP the way you expect. The workaround from that blog (manually populating the CONTEXT in inline asm) is pretty normal for correct initial reg vals.

Like I said with the missing unwind info earlier, StackWalk64 needs valid unwind metadata. The OS can’t keep walking past your 1st func. If the func doesn’t have the typical prolog/epilog or has opts/inlining, the debugger can’t climb further up the stack.

The overall approach stays the same: SymInitialize() -> capture your CONTEXT (with the inline asm trick in 32bit if needed / RtlCaptureContext in x64) -> init a STACKFRAME64 with AddrPC, AddrStack, AddrFrame from that context -> loop until false.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх