SFC
SFC (Stack Frame Chain) - цепочка стековых фреймов. Обычно процедуры используют адресацию аргументов и локальных переменных посредством регистра Ebp. Это следствие stdcall-конвенции вызова. При этом каждая процедура владеет своим пространством стека. Эта область где находятся аргументы функции, локальные переменные и данные сохраненные в текущий момент называется стековым фреймом. В начале работы функции сохраняется в стеке текущее значение регистра Ebp и в него загружается ссылка на дно стека(текущее значение регистра Esp). Таким образом в любой момент времени регистр Ebp адресует аргументы функции и локальные переменные расположенные ниже(стек растёт вниз). Изза сохранённого значения регистра Ebp при входе в функцию формируется цепочка, фреймы таким образом связываются:
- Дно стека - Esp: [...] [Локальные переменные] Ebp: [Предыдущее значение регистра Ebp, тоесть ссылка на следующий фрейм] [Адрес возврата из функции, который сформирован процедурным ветвлением] [Аргументы функции] [...]
Для формирования фрейма и его удаления существуют инструкции Enter и Leave. Обычно вместо инструкции Enter используется пара инструкций push ebp и mov ebp,esp. Часть стека из двух слов, которую адресует регистр Ebp всегда может быть описана структурой:
STACK_FRAME struct Frame PVOID ? ; Ссылка на следующий фрейм. Ip PVOID ? ; Адрес возврата из процедуры. STACK_FRAME ends
Часто фрейм содержит защитную информацию, это например куки(програмный DEP) и SEH-фрейм:
LdrLoadDll: push 26C ; Размер стека выделяемого под локальные переменные. push ntdll.7C9164F8 ; Информация для SEH. call ntdll.__SEH_prolog mov eax,dword ptr ds:[___security_cookie] ; Куки для защиты стека от переполнений. mov dword ptr ss:[ebp - 0x1C],eax
Системная функция _SEH_prolog формирует стековый фрейм и устанавливает SEH. Фрейм будет выглядеть следующим образом:
Esp: $ [Локальные переменные] $+26C [Куки, рандомное значение] $+270 [XXXX] $+274 [XXXX] $+278 [Ссылка на следующий SEH-фрейм] $+27C [Ссылка на SEH] $+280 [Информация для SEH] $+284 [-//-] Ebp: $+288 [Предыдущее значение Ebp] $+28C [Адрес возврата] $+290 [Аргументы]
Так как стековые фреймы связаны, то возможно их перечисление, тоесть проход по цепочке. Эта манипуляция называется бактрейсом. Получив значение регистра Ebp из контекста потока которое постоянно в пределах текущей процедуры, можно определить аргументы функции и её локальные переменные, также и следующей процедуры получив ссылку на её фрейм из текущего.
NL(Nesting Level) - это уровень вложенности процедур, тоесть число процедурных ветвлений между вызывающей и вызываемой процедурой.
SFN(Stack Frame Number) - это номер стекового фрейма, равный NL относительно вызывающей процедуры.
Так как NL является константой, то зная его и значение регистра Ebp вызываемой процедуры можно определить фрейм вызывающей процедуры, выполнив бактрейс из NL итераций. Так например получив управление гдето внутри целевой функции можно выполнить бактрейс, найти необходимый фрейм вызывающей процедуры и заменить в нём адрес возврата. Тогда при возврате из вызывающей процедуры будет выполнен переход на новую процедуру. Этот механизм лежит в основе альтернативных способов перехвата кода(таких как IDP), применяемых в руткитах(тут можно увидеть семпл для CreateProcess()). В SFC последний фрейм не содержит ссылку, она заменена маркером конца цепи(EXCEPTION_CHAIN_END = -1), либо ноль. Это позволяет прекратить бактрейс и избежать обращения за пределы стека. В ядре модель SFC немного иная - происходит изоляция трап-фремов. Пример бактрейса в ядре для поиска трап-фрейма:
STACK_FRAME struct Frame PVOID ? Ip PVOID ? STACK_FRAME ends TsDbgArgMark equ 00008H ; KTRAP_FRAME.DbgArgMark PcStackBase equ 4 PcStackLimit equ 8 TsEip equ 00068H TsSegCs equ 0006CH TsEflags equ 00070H MODE_MASK equ 00001H mov eax,ebp assume eax:PTR STACK_FRAME Scan: cmp eax,-1 je ChainEnd test eax,eax jz ChainEnd cmp fs:[PcStackBase],eax jna Error cmp fs:[PcStackLimit],eax ja Error cmp dword ptr [eax + TsDbgArgMark],0BADB0D00H je TrapFrame mov eax,[eax].Next jmp Scan TrapFrame: assume eax:PKTRAP_FRAME test dword ptr [eax + TsSegCs],MODE_MASK jz KMode test dword ptr [eax + TsEflags],EFLAGS_IF jz Error ...