После этих проверок происходит вызов Ke386SetLdtProcess->Ki386LoadTargetLdtr->KiLoadLdtr-> asm lldt.
Антиэмуляция
Основана на том, что автоматические системы не учитывают, что при формировании линейного адреса к смещению прибавляется база дескриптора, номер которого содержится в одном из сегментных регистров. Рассмотрим небольшой пример. Допустим, есть такой код:
Большинство считает, что, будучи выполненным в OS Windows, данный код сгенерирует исключение и произойдет вызов SEH. Но это так не во всех случаях. Ведь stosd эквивалентна паре инструкций:
В общем случае в пользовательской программе es=23h, указывает на 4ый дескриптор в LDT, который описывает сегмент с базой 0. Тогда обращение произойдет по нулевому линейному адресу и действительно возникнет исключение. Но если, например, добавить свой дескриптор с базой 401000h и в es поместить селектор, который содержит его номер, то в результате выполнения вышеприведенного кода произойдет обращение по адресу 401000h, где может находиться доступный для записи сегмент данных программы.
Приведу в качестве иллюстрации код, который вводит в заблуждение эмулятор NOD'a (на других АВ не проверял) и некоторых программистов.
Следует обратить внимание также на то, что перед вызовом АПИ следует обязательно восстановить значения сегментных регистров, так как обращение к какому-либо адресу приведет к тому, что на самом деле произойдет обращение по адресу бОльшему на базу, указанному в дескрипторе. Другими словами, если где-то в коде MessageBoxA встретится команда типа mov eax,es:[77d91234h], то на самом деле будет попытка записи в еах значения ячейки по адресу 78192234h, что, скорее всего, вызовет исключение.
Антиотладка
Основана на том же принципе, только с учетом того, что формируется дескриптор для сегмента кода. Всем известный отладчик OllyDbg при изменении значения cs путем выполнения дальнего вызова, либо перехода в сегмент с другой базой тихонько выпадает в осадок. Приведу код, иллюстрирующий данный подход.
Удачная комбинация
Также можно рассмотреть комбинацию двух данных подходов - добавить два дескриптора с одинаковой базой: один для кода, один для данных. После этого пройтись по всем модулям процесса, пофиксить релоки с учетом новой базы, да и сам PEB тоже, наткнутся на кучу "подводных камней", после чего спокойно, загрузив в сегментные регистры новые селекторы, вызывать АПИ, не восстанавливая оригинальные значения сегментных регистров.
На этом заканчиваю статью, wasm.ru forever
© FreeMan
xor edi,edi
stosd
mov dword[es:edi],eax
add(sub) edi,4
format PE GUI 4.0
entry start
include '%fasminc%\win32a.inc'
;
;Данная строка находится по адресу 401000h
;
mess db '1234AGE',0
;
;Дескриптор, который необходимо добавить в LDT
;
LDT_Entry:
;
;Младшие 16бит лимита
;
dw 0100h
;
;Младшие 24бита базы (401000h)
;
db 0,10h,40h
;
;Тип: 1010 - так как S=1, 0-сегмент данных 010-для чтения/записи
;S(тип дескриптора): 1 (сегмент данных или кода)
;DPL: 11 (ring3)
;P: 1 (сегмент присутствует)
;
db 11110010b
;
;16-19 биты лимита: 1111b
;AVL: 0 - чо угодно
;Reserved: 0 - надо чтоб был 0 иначе конец света
;G: 1 - лимит умножаем на 1000h
;
db 11000000b
;
;Cтарший байт базы
;
db 0
start:
;
;Добавим дескриптор в LDT
;
invoke NtSetLdtEntries,1111111b,dword[LDT_Entry],dword[LDT_Entry+4],0,0,0
;
;Селектор с номером данного дескриптора - в es
;
push es
push 1111111b
pop es
;!!!!!!
;Обращение произойдет по адресу 401000h, а не 0
;!!!!!!
mov eax,'MESS'
xor edi,edi
stosd
;
;Восстановим es
;
pop es
invoke MessageBox,0,mess,mess,MB_OK
invoke ExitProcess,0
data import
library kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL',\
comdlg32,'comdlg32.dll',\
ntdll,'ntdll.dll'
include '%fasminc%\apia\comdlg32.inc'
include '%fasminc%\apia\user32.inc'
include '%fasminc%\apia\kernel32.inc'
include '%fasminc%\ntdll.inc'; import ntdll, NtSetLdtEntries,'NtSetLdtEntries'
end data
format PE GUI 4.0
entry start
include '%fasminc%\win32a.inc'
;
;Опять же адрес данной строки 401000h
;
mess db 'MESSAGE',0
data import
library kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL',\
comdlg32,'comdlg32.dll',\
ntdll,'ntdll.dll'
include '%fasminc%\apia\comdlg32.inc'
include '%fasminc%\apia\user32.inc'
include '%fasminc%\apia\kernel32.inc'
include '%fasminc%\ntdll.inc'
end data
;
;Для удобства вызова функций, рассчитанных на
;работу в сегменте с base=0
;
macro invokes [arg]
{
common
if ~ arg eq
reverse
pushd arg
common
end if
call invoker
}
;
;На этот раз дескриптор кода
;
LDT_Entry:
;
;Младшие 16бит лимита
;
dw 0ffffh
;
;Младшие 24бита базы 401000h
;
db 0,10h,40h
;
;Тип: 1010 - так как S=1, 1-сегмент кода 010-для чтения/записи
;S(тип дескриптора): 1 (сегмент данных или кода)
;DPL: 11 (ring3)
;P: 1 (сегмент присутствует)
;
db 11111010b
;
;16-19 биты лимита: 0000
;AVL: 0 - чо угодно
;Reserved: 0 - надо чтоб был 0, иначе конец света
;G: 1 - лимит умножаем на 1000h
;
db 11000000b
;
;Старший байт базы
;
db 0
start:
;
;Добавляем дескриптор
;
invoke NtSetLdtEntries,1111111b,dword[LDT_Entry],dword[LDT_Entry+4],0,0,0
mov ax,cs
;
;Мега фокус-покус
;
jmp 1111111b:ёпт-401000h
ёпт:
;
;База кода теперь 401000, а не 0
;Сохраним старое значение cs для успешного вызова АПИ
;
mov [cseg],ax
;
;Вызов АПИ следует осуществлять через "переходник"
;
invokes MessageBox,0,mess,mess,MB_OK
invokes ExitProcess,0
;
;Переходник работает так:
; переход к базе кода 401000
; вызов АПИ
; переход к базе 0
;
invoker:
;
;дальний переход, чтоб загрузить оригинальный cs
;
db 0eah ;опкод jmp far
dd inv ;смещение
cseg dw ? ;сегмент
;
;Возврат из процедуры
;
rets:
jmp [reteng]
reteng dd ?
;
;Тут база 0
;
inv:
;
;Запомним адрес вызываемой процы(относительно 0)
;
mov eax,dword[esp+4]
;
;Запомним адрес возврата из процедуры(относительно 0)
;
mov edx,dword[esp]
mov [reteng],edx
;
;Вершина стека должна указывать на параметры, переданные процедуре
;
add esp,8
;
;Вызов процедуры
;
call dword[eax]
;
;Прыжок, дабы вернуться к базе 0
;
db 0eah
;
;Смещение относительно 401000h
;
dd rets-401000h
dw 1111111b