Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Гайд Ординалы в малвари: как малвари снижают детекты с 35% до 15%

Начинающий
Начинающий
Статус
Оффлайн
Регистрация
21 Июн 2025
Сообщения
128
Реакции
7
Исключительно для специалистов аналитиков угроз и разработчиков средств защиты. Все примеры - для изучения методов атак с целью разработки контрмер.
Статья писалась исключительно для югейма, ну, потому что скучно)

Сразу скажу, ту же сигнатуру UPX это точно сломает. Я видел, как школьники(малварь-девы) тупо вешали на наличие в импорте только трёх импортов - VirtualProtect и ещё каких-то двух функций.
Меняем VirtualProtect на ординал, и нет UPX.

Мини-статья, погнали.
Возможно кому-то будет полезна


1) Что такое ординал и как его найти
В первую очередь нужно задуматься о том, как вообще нам найти сам ординал, но перед этим стоит рассказать про ординал.
Что такое ординал функции? - Это просто индексы в массиве экспортируемых функций
Я попробую привести аналогию: Как мы знаем, в ntdll каждая функция ядра имеет сискол-таблы, то есть, у каждого сисемного вызова есть сискол-номер.
Тоже самое и с ординалом функции, каждая функция имеет некий идентификатор(ординал) пример: MessageBoxA -> 2151


Ну, что, что такое ординал мы узнали, теперь можно переходить к вопросу: Как найти ординал функции, все просто - через dumpbin
1) Запускаем x64 Native Tools Command Prompt For VS 2022 или обычную cmd.
2) После чего вводим dumpbin, экспортирование и указанием полного пути к библиотеке, после чего через findstr непосредственно находим ординал нужной нам функции:
dumpbin:
Expand Collapse Copy
dumpbin /exports C:\Windows\System32\kernel32.dll | findstr VirtualAlloc
После чего мы видим:


1767301775674.png
По левому ряду расположены непосредственно сами ординалы, теперь вопрос, как малварь-девы используют подобную технику для скрытия детектов? Ну, или сокращения их количества, об этом ниже:

2) Практика
Как инструмент мы будем использовать компилятор и ассемблер FASM, он простой и понятен.
Немного стоит поговорить про кодовую базу и про сам FASM.
Чаще всего, малварь разработчики не используют никакие упаковщики, потому что понимают, что кодовая база должна быть чистой и уникальной. В последнее время часто пишут малвари на GO, поскольку их кодовая база достаточно чиста и пока не разложилась на вредоносные гены, как на Си и С++.
А касаемо фасма, он дает полностью сырой и девственный бинарь. Для антивирусов - для них это аномалия.(он тоже не чист и имеет вредоносные гены)

Ну, что, переходим к практике.
Я реализовал немного подозрительный код для антивирусов и сканеров.
Наш код будет выделять 4096 байт в памяти процесса, после чего моментально их освободит.
Регистр r9 имеет значение 40h(PAGE_EXECUTE_READWRITE)


Это максимальное противоественно для нормальных программ, что вызывает недоверие со стороны антивируса.
C:
Expand Collapse Copy
// чистая программа
.text -> PAGE_EXECUTE_READ    (0x20) // исполнить код
.data -> PAGE_READWRITE       (0x04) // читать или писать данные

// малварь объеденяют все сразу:
.shellcode → PAGE_EXECUTE_READWRITE (0x40)

1767302701241.png


Весь наш исполняемый код:​
C:
Expand Collapse Copy
format PE64 GUI
entry start

include 'C:\FLAT\INCLUDE\win64a.inc'

section '.text' code readable executable

start:
    sub rsp, 28h
 
    mov rcx, 0    
    mov rdx, 4096 // тут выделяемые байты
    mov r8, 3000h  
    mov r9, 40h      // тот самый PAGE
    call [VirtualAlloc]
 
    test rax, rax
    jz error_alloc
 
    mov [alloc_ptr], rax // указатель который хранит информацию касаемо ошибки в случае безуспешности
 
    mov rcx, [alloc_ptr]
    xor edx, edx
    mov r8, 8000h
    xor r9, r9
    call [VirtualFree]
 
    test rax, rax
    jz error_free
 
 
    xor rcx, rcx
    lea rdx, [msg]
    lea r8, [title]
    xor r9, r9
    call [MessageBoxA]
    jmp exit
 
error_alloc:
    lea rdx, [msg_alloc_fail]
    jmp show_error
 
error_free:
      lea rdx, [msg_free_fail]
 
show_error:
    xor rcx, rcx
    lea r8, [title]
    mov r9, 10h
    call [MessageBoxA]
 
exit:
    xor rcx, rcx
    call [ExitProcess]

section '.data' data readable writeable

    msg db 'mem test - yes not error',0
    titles db 'memory test',0
    alloc_ptr     dq 0
    msg_success   db 'VirtualAlloc + VirtualFree - sucessfyly!',0
    msg_alloc_fail db 'virtualloc failed!',0
    msg_free_fail  db 'virtualfree failed!',0
    title         db 'mem test',0
 
 

section '.idata' import data readable writeable
    library user32, 'USER32.DLL', \
            kernel32, 'KERNEL32.DLL'
 
    import user32, \
           MessageBoxA, 'MessageBoxA'
 
    import kernel32, \
           ExitProcess, 'ExitProcess',\
           VirtualAlloc, 'VirtualAlloc',\
           VirtualFree, 'VirtualFree'


Прошу заметить, что пока-что мы не используем ординалы функций, а просто вызываем ее напрямую, антивирус это видит судя по IAT:
1767302833219.png

Проверяем работоспособность нашей программы:

1767302880485.png

Все прекрасно работает, в процессе выделяется память, наши байты, теперь преобразуем нашу программу в EXE файл и посмотрим что выведет VirusTotal:
1767303013192.png

Получилось 7 детектов, что, кстати не столь много, но для малварь-дрочеров это считается большим, поэтому они решают прибегнуть к использованию ординаров, давайте же сделаем ту же самую программу, только уже вызывая функцию через ее идентификатор, а не напрямую:
Заходим в CMD находим ординар нужным нам функций:

1767303113924.png

Теперь в таблице импорта используем наши ординары вместо прямого вызова:
1767303173807.png

Код:​
C:
Expand Collapse Copy
format PE64 GUI
entry start

include 'C:\FLAT\INCLUDE\win64a.inc'

section '.text' code readable executable

start:
    sub rsp, 28h
    
    mov rcx, 0         
    mov rdx, 4096     
    mov r8, 3000h       
    mov r9, 40h           
    call [VirtualAlloc]
    
    test rax, rax
    jz error_alloc
    
    mov [alloc_ptr], rax
    
    mov rcx, [alloc_ptr]
    xor edx, edx
    mov r8, 8000h
    xor r9, r9
    call [VirtualFree]
    
    test rax, rax
    jz error_free
    
    
    xor rcx, rcx
    lea rdx, [msg]
    lea r8, [title]
    xor r9, r9
    call [MessageBoxA] 
    jmp exit
    
error_alloc:
    lea rdx, [msg_alloc_fail]
    jmp show_error
    
error_free:
      lea rdx, [msg_free_fail]
      
show_error:
    xor rcx, rcx
    lea r8, [title]
    mov r9, 10h
    call [MessageBoxA]
    
exit:
    xor rcx, rcx
    call [ExitProcess]

section '.data' data readable writeable

    msg db 'mem test - yes not error',0
    titles db 'memory test',0
    alloc_ptr     dq 0
    msg_success   db 'VirtualAlloc + VirtualFree - sucessfyly!',0
    msg_alloc_fail db 'virtualloc failed!',0
    msg_free_fail  db 'virtualfree failed!',0
    title         db 'mem test',0
    
    

section '.idata' import data readable writeable
    library user32, 'USER32.DLL', \
            kernel32, 'KERNEL32.DLL'
    
    import user32, \
           MessageBoxA, 'MessageBoxA'
    
    import kernel32, \
           ExitProcess, 'ExitProcess',\
           VirtualAlloc, 1536,\ // ОРДИНАР
           VirtualFree, 1539 // ОРДИНАР
Теперь в таблице импорта красуются наши ординары функций, теперь, давайте скомпилируем нашу программу и посмотрим результат на VirusTotal:
1767303293381.png

Вауля! Получается уже 5 детектов.



Заключение.
Вообще, малварь-разработчики любят использовать code-save инъекцию в процесс шеллкодом, чтобы снизить процесс детектов.
Во вредоносной сфере используется так называемый "крипт". И тут проходит главная цепочка - пишем на ассемблере (широкие возможности в реализации, девственный бинарь, малый размер), а внедряем его в доверенные легитимные высокоуровневые программы. "Волк в овечьей шкуре".

Если делать наоборот - писать на высокоуровнем языке вредонос, то:
1. Внедрить не получится - вес большой
2. Внедрить получится = из-за большого веса антивирус сразу увидит жирное пятно на бинарнике - артефакт
3. Заморочишься делать адекватную инъекцию. Если на ассемблере я могу из бинарника изъять RVA функции из импорт-таблицы и сделать call [hMod+RVA] и полностью слиться с программой, быть с ней одним целым, то на С так не сделать - нужно танцевать с бубном как дурак.
4. Без инъекции адекватный "крипт" не сделать - будешь пытаться что-то своё мусолить, что-то генерировать, но нейросеть на антивирусе скажет, что это херня и аномалия.


Всем поки.
 

Вложения

  • 1767302669481.png
    1767302669481.png
    9.4 KB · Просмотры: 19
  • 1767303095284.png
    1767303095284.png
    8.6 KB · Просмотры: 15
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
Исключительно для специалистов аналитиков угроз и разработчиков средств защиты. Все примеры - для изучения методов атак с целью разработки контрмер.
Статья писалась исключительно для югейма, ну, потому что скучно)

Сразу скажу, ту же сигнатуру UPX это точно сломает. Я видел, как школьники(малварь-девы) тупо вешали на наличие в импорте только трёх импортов - VirtualProtect и ещё каких-то двух функций.
Меняем VirtualProtect на ординал, и нет UPX.

Мини-статья, погнали.
Возможно кому-то будет полезна


1) Что такое ординал и как его найти
В первую очередь нужно задуматься о том, как вообще нам найти сам ординал, но перед этим стоит рассказать про ординал.
Что такое ординал функции? - Это просто индексы в массиве экспортируемых функций
Я попробую привести аналогию: Как мы знаем, в ntdll каждая функция ядра имеет сискол-таблы, то есть, у каждого сисемного вызова есть сискол-номер.
Тоже самое и с ординалом функции, каждая функция имеет некий идентификатор(ординал) пример: MessageBoxA -> 2151


Ну, что, что такое ординал мы узнали, теперь можно переходить к вопросу: Как найти ординал функции, все просто - через dumpbin
1) Запускаем x64 Native Tools Command Prompt For VS 2022 или обычную cmd.
2) После чего вводим dumpbin, экспортирование и указанием полного пути к библиотеке, после чего через findstr непосредственно находим ординал нужной нам функции:
dumpbin:
Expand Collapse Copy
dumpbin /exports C:\Windows\System32\kernel32.dll | findstr VirtualAlloc
После чего мы видим:


По левому ряду расположены непосредственно сами ординалы, теперь вопрос, как малварь-девы используют подобную технику для скрытия детектов? Ну, или сокращения их количества, об этом ниже:

2) Практика
Как инструмент мы будем использовать компилятор и ассемблер FASM, он простой и понятен.
Немного стоит поговорить про кодовую базу и про сам FASM.
Чаще всего, малварь разработчики не используют никакие упаковщики, потому что понимают, что кодовая база должна быть чистой и уникальной. В последнее время часто пишут малвари на GO, поскольку их кодовая база достаточно чиста и пока не разложилась на вредоносные гены, как на Си и С++.
А касаемо фасма, он дает полностью сырой и девственный бинарь. Для антивирусов - для них это аномалия.(он тоже не чист и имеет вредоносные гены)

Ну, что, переходим к практике.
Я реализовал немного подозрительный код для антивирусов и сканеров.
Наш код будет выделять 4096 байт в памяти процесса, после чего моментально их освободит.
Регистр r9 имеет значение 40h(PAGE_EXECUTE_READWRITE)


Это максимальное противоественно для нормальных программ, что вызывает недоверие со стороны антивируса.
C:
Expand Collapse Copy
// чистая программа
.text -> PAGE_EXECUTE_READ    (0x20) // исполнить код
.data -> PAGE_READWRITE       (0x04) // читать или писать данные

// малварь объеденяют все сразу:
.shellcode → PAGE_EXECUTE_READWRITE (0x40)

Посмотреть вложение 323543

Весь наш исполняемый код:​
C:
Expand Collapse Copy
format PE64 GUI
entry start

include 'C:\FLAT\INCLUDE\win64a.inc'

section '.text' code readable executable

start:
    sub rsp, 28h
 
    mov rcx, 0    
    mov rdx, 4096
    mov r8, 3000h  
    mov r9, 40h      
    call [VirtualAlloc]
 
    test rax, rax
    jz error_alloc
 
    mov [alloc_ptr], rax
 
    mov rcx, [alloc_ptr]
    xor edx, edx
    mov r8, 8000h
    xor r9, r9
    call [VirtualFree]
 
    test rax, rax
    jz error_free
 
 
    xor rcx, rcx
    lea rdx, [msg]
    lea r8, [title]
    xor r9, r9
    call [MessageBoxA]
    jmp exit
 
error_alloc:
    lea rdx, [msg_alloc_fail]
    jmp show_error
 
error_free:
      lea rdx, [msg_free_fail]
 
show_error:
    xor rcx, rcx
    lea r8, [title]
    mov r9, 10h
    call [MessageBoxA]
 
exit:
    xor rcx, rcx
    call [ExitProcess]

section '.data' data readable writeable

    msg db 'mem test - yes not error',0
    titles db 'memory test',0
    alloc_ptr     dq 0
    msg_success   db 'VirtualAlloc + VirtualFree - sucessfyly!',0
    msg_alloc_fail db 'virtualloc failed!',0
    msg_free_fail  db 'virtualfree failed!',0
    title         db 'mem test',0
 
 

section '.idata' import data readable writeable
    library user32, 'USER32.DLL', \
            kernel32, 'KERNEL32.DLL'
 
    import user32, \
           MessageBoxA, 'MessageBoxA'
 
    import kernel32, \
           ExitProcess, 'ExitProcess',\
           VirtualAlloc, 'VirtualAlloc',\
           VirtualFree, 'VirtualFree'


Прошу заметить, что пока-что мы не используем ординалы функций, а просто вызываем ее напрямую, антивирус это видит судя по IAT:
Посмотреть вложение 323545

Проверяем работоспособность нашей программы:

Посмотреть вложение 323546
Все прекрасно работает, в процессе выделяется память, наши байты, теперь преобразуем нашу программу в EXE файл и посмотрим что выведет VirusTotal:
Посмотреть вложение 323547
Получилось 7 детектов, что, кстати не столь много, но для малварь-дрочеров это считается большим, поэтому они решают прибегнуть к использованию ординаров, давайте же сделаем ту же самую программу, только уже вызывая функцию через ее идентификатор, а не напрямую:
Заходим в CMD находим ординар нужным нам функций:

Посмотреть вложение 323549
Теперь в таблице импорта используем наши ординары вместо прямого вызова:
Посмотреть вложение 323550
Теперь в таблице импорта красуются наши ординары функций, теперь, давайте скомпилируем нашу программу и посмотрим результат на VirusTotal:

Посмотреть вложение 323553
Вауля! Получается уже 5 детектов.



Заключение.
Вообще, малварь-разработчики любят использовать code-save инъекцию в процесс шеллкодом, чтобы снизить процесс детектов.
Во вредоносной сфере используется так называемый "крипт". И тут проходит главная цепочка - пишем на ассемблере (широкие возможности в реализации, девственный бинарь, малый размер), а внедряем его в доверенные легитимные высокоуровневые программы. "Волк в овечьей шкуре".

Если делать наоборот - писать на высокоуровнем языке вредонос, то:
1. Внедрить не получится - вес большой
2. Внедрить получится = из-за большого веса антивирус сразу увидит жирное пятно на бинарнике - артефакт
3. Заморочишься делать адекватную инъекцию. Если на ассемблере я могу из бинарника изъять RVA функции из импорт-таблицы и сделать call [hMod+RVA] и полностью слиться с программой, быть с ней одним целым, то на С так не сделать - нужно танцевать с бубном как дурак.
4. Без инъекции адекватный "крипт" не сделать - будешь пытаться что-то своё мусолить, что-то генерировать, но нейросеть на антивирусе скажет, что это херня и аномалия.


Всем поки.
Инфа +- бесполезная, ибо ей уже несколько лет. Видел подобный трюк в малваре около 3 лет назад.
 
Инфа +- бесполезная, ибо ей уже несколько лет. Видел подобный трюк в малваре около 3 лет назад.
для тебя мб да, но есть люди которые про это не знают)
у малварь-дрочеров это малопопулярно, они чаще просто динамически загружают нужные им функции предварительно загрузив кернелку через peb
 
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
для тебя мб да, но есть люди которые про это не знают)
у малварь-дрочеров это малопопулярно, они чаще просто динамически загружают нужные им функции предварительно загрузив кернелку через peb
С точки зрения использования я думаю популярные ав детектят вызов через порядковый номер. Если при первой скане популярный ав не нашёл в exe паттерны малвары значит он просто не реагирует на боксы. Попробуй подергать что-то что делает зачастую малвары.
 
С точки зрения использования я думаю популярные ав детектят вызов через порядковый номер. Если при первой скане популярный ав не нашёл в exe паттерны малвары значит он просто не реагирует на боксы. Попробуй подергать что-то что делает зачастую малвары.

tну да, эвристическим движкам так-то всё пофиг, как ты к одному и тому же месту обращаешься. хоть по ординалу, хоть по названию.

выше речь шла только про статику - именно импорт-таблицу
часто там строки названий функций светятся, вот их можно на ординалы заменить.
 
Назад
Сверху Снизу