Розыгрыш Premium и Уникальной юзергруппы на форуме! Перейти

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

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

Сразу скажу, ту же сигнатуру 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 · Просмотры: 0
  • 1767303095284.png
    1767303095284.png
    8.6 KB · Просмотры: 0
Исключительно для специалистов аналитиков угроз и разработчиков средств защиты. Все примеры - для изучения методов атак с целью разработки контрмер.
Статья писалась исключительно для югейма, ну, потому что скучно)

Сразу скажу, ту же сигнатуру 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ну да, эвристическим движкам так-то всё пофиг, как ты к одному и тому же месту обращаешься. хоть по ординалу, хоть по названию.

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